{"id":21725660,"url":"https://github.com/jakubtomsu/sds","last_synced_at":"2026-01-04T14:11:08.915Z","repository":{"id":258689695,"uuid":"785093711","full_name":"jakubtomsu/sds","owner":"jakubtomsu","description":"A collection of static datastructures for Odin","archived":false,"fork":false,"pushed_at":"2024-11-19T16:35:13.000Z","size":114,"stargazers_count":56,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-25T19:29:11.614Z","etag":null,"topics":["datastructures","handles","odinlang","pool","spsc-queue"],"latest_commit_sha":null,"homepage":"","language":"Odin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jakubtomsu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-04-11T07:22:32.000Z","updated_at":"2025-01-20T13:32:44.000Z","dependencies_parsed_at":"2024-11-06T10:40:41.476Z","dependency_job_id":"a0275466-a361-4010-a9c0-617ad34b13d0","html_url":"https://github.com/jakubtomsu/sds","commit_stats":null,"previous_names":["jakubtomsu/sds"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakubtomsu%2Fsds","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakubtomsu%2Fsds/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakubtomsu%2Fsds/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakubtomsu%2Fsds/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jakubtomsu","download_url":"https://codeload.github.com/jakubtomsu/sds/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244706521,"owners_count":20496570,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["datastructures","handles","odinlang","pool","spsc-queue"],"created_at":"2024-11-26T03:19:52.717Z","updated_at":"2026-01-04T14:11:08.873Z","avatar_url":"https://github.com/jakubtomsu.png","language":"Odin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 💾 Static Data Sructures\nA small Odin library with useful fixed-size data structures. This means all of the data is internally stored just as static arrays (`[N]T`), no dynamic allocations whatsoever.\n\n[Why fixed size?](#why-fixed-size)\n\n## Datastructures\nName | Similar To | Info\n---- | ---------- | ----\nArray | `[dynamic]T` or `core:container/small_array` | Regular static array with dynamic number of elements (`[N]T` + `int` for length)\nSoa_Array | `#soa[dynamic]T` | Variant of `Array` with `#soa` backing buffer\nPool | none |  A sparse array, which uses [Handles](#handles) to refer to elements. Deleted elements are kept in a free list. All operations are O(1). Overhead is one index and one generation counter per item.\nQueue | core:container/queue | A simple ring buffer queue.\nBit_Array | bit_set for \u003e128 element support | Array of booleans stored as single bits. This can be useful in cases where `bit_set` is too small (\u003e128 elements).\nSPSC | Queue | Single-producer single-consumer lock-free ring buffer queue for multithreaded systems.\n\n\u003e Note: There used to be an Indirect_Array which remaps sparse handles to linear array using a pool. It was removed in commit [d381140](https://github.com/jakubtomsu/sds/commit/d3811401c59c02e3cf960c95229a85557e398276) because a pool pretty much covers all the use cases in practice.\n\nAll of the datastructures follow ZII - zero is initialization. So you don't need to ever call any `_init/_make` procs. There is also always a \"dummy\" invalid value which is returned in case `*_get_ptr` procs fail.\n\n\u003e [!NOTE]\n\u003e Some very basic procedures like `len`, `cap`, `resize` etc are intentionally missing for simplicity.\n\u003e Don't be afraid to just directly read the member values like `len` from the structs.\n\n### Pool Example\nThe Pool datastructure is probably the most useful to gamedevs, so here is a short example of practical usage:\n```odin\nimport \"sds\"\n\n// Distinct type for safety!\nEnemy_Handle :: distinct sds.Handle(u16, u16)\n\nEnemy :: struct {\n    pos:    [2]f32,\n    health: f32,\n}\n\nGame :: struct {\n    enemies: sds.Pool(1024, Enemy, Enemy_Handle),\n}\n\ngame_tick :: proc(game: ^Game, delta: f32) {\n    for i in 1..=game.enemies.max_index {\n        enemy, handle := sds.pool_index_get_ptr_safe(\u0026game.enemies, i) or_continue\n        // ...\n        if enemy.health \u003c 0 {\n            sds.remove(\u0026game.enemies, handle)\n        }\n    }\n}\n\ngame_draw :: proc(game: Game) {\n    for i in 1..=game.enemies.max_index {\n        enemy, handle := sds.pool_index_get_safe(game.enemies, i) or_continue\n        // ...\n    }\n}\n```\n\n## Handles\nPool uses Handles to address items. A handle is sort of like a unique ID, however it can optionally also have a \"generation index\". This is useful because IDs can be reused, but the generation index check makes sure you are accessing the item you _think_ you are. This prevents \"use-after-removed\" kinds of bugs.\n\nI recommend reading this blog post by Andre Weissflog to learn more about the benefits of Handles: [Handles are the better pointers](https://floooh.github.io/2018/06/17/handles-vs-pointers.html)\n\n\n## Why fixed size?\nYou might be thinking, why should I use fixed size datastructures, instead of letting them allocate memory dynamically? Odin has a great allocator system, but there are still reasons I find fixed-size nicer in 99% of cases.\n\n- it's good to be explicit about the limits because the code operating on the data has limits anyway, whether you acknowledge it or not.\n- prioritizes worst-case performance over the average case which is arguably much more important in general\n- it's very obvious when you're doing something that would use a LOT of memory, so you come up with a different way to manage the data. With dynamic memory it's much easier to use huge amounts of memory without realizing it.\n- no need to make and delete datastructures (if you wanted [dynamic] arrays with specific capacity for example)\n- it's trivial to do a \"deep copy\" of the entire datastructure. If you use only fixed-size datastructures and have a big `Global_Data` struct with all the program state, you can trivially serialize it, or pass it between modules when hotreloading.\n- pointers never get invalidated. That said, you still probably want to use indexes or handles\n- it's just a bit simpler than the alternatives\n\nThere are definitely cases when fixed-size is not a very good fit, but in software like games it works _really_ well in my experience.\n\nI also recommend reading the [TigerBeetle database coding style](https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/TIGER_STYLE.md), which also heavily relies on static memory allocation.\n\n## Contributing\nImprovements and bugfix PRs are welcome. If you want to add a new datastructure or a big feature like that I recommend opening an issue first.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjakubtomsu%2Fsds","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjakubtomsu%2Fsds","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjakubtomsu%2Fsds/lists"}