From 425e7ca780abef80bd646fce9a6541101bd5593d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 26 Jan 2026 11:46:59 +0000 Subject: Add `core:container/handle_map` --- core/container/handle_map/handle_map.odin | 206 ++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 core/container/handle_map/handle_map.odin diff --git a/core/container/handle_map/handle_map.odin b/core/container/handle_map/handle_map.odin new file mode 100644 index 000000000..da816d288 --- /dev/null +++ b/core/container/handle_map/handle_map.odin @@ -0,0 +1,206 @@ +/* +Handle-based map using fixed-length arrays. + +Example: + import hm "core:container/handle_map" + + Handle :: hm.Handle32 + + Entity :: struct { + handle: Handle, + pos: [2]f32, + } + + entities: hm.Handle_Map(1024, Entity, Handle) + + h1 := hm.add(&entities, Entity{pos = {1, 4}}) + h2 := hm.add(&entities, Entity{pos = {9, 16}}) + + if e, ok := hm.get(&entities, h2); ok { + e.pos.x += 32 + } + + hm.remove(&entities, h1) + + h3 := hm.add(&entities, Entity{pos = {6, 7}}) + + it := hm.iterator_make(&entities) + for e, h in hm.iterate(&it) { + e.pos += {1, 2} + } +*/ +package container_handle_map + +import "base:builtin" +import "base:intrinsics" + +// Default 16-bit Handle type which can be used for handle maps which only need a maximum of 254 (1<<8 - 2) items +Handle16 :: struct { + idx: u8, + gen: u8, +} + +// Default 32-bit Handle type which can be used for handle maps which only need a maximum of 65534 (1<<16 - 2) items +Handle32 :: struct { + idx: u16, + gen: u16, +} + +// Default 64-bit Handle type which can be used for handle maps which only need a maximum of 4294967294 (1<<32 - 2) items +Handle64 :: struct { + idx: u32, + gen: u32, +} + +Handle_Map :: struct($N: uint, $T: typeid, $Handle_Type: typeid) + where + 0 < N, N < uint(1<<31 - 1), + + intrinsics.type_has_field(Handle_Type, "idx"), + intrinsics.type_has_field(Handle_Type, "gen"), + intrinsics.type_is_unsigned(intrinsics.type_field_type(Handle_Type, "idx")), + intrinsics.type_is_unsigned(intrinsics.type_field_type(Handle_Type, "gen")), + intrinsics.type_field_type(Handle_Type, "idx") == intrinsics.type_field_type(Handle_Type, "gen"), + + N < uint(max(intrinsics.type_field_type(Handle_Type, "idx"))), + + intrinsics.type_has_field (T, "handle"), + intrinsics.type_field_type(T, "handle") == Handle_Type +{ + + // The zero element represent a zero-value sentinel (dummy value), allowing for `idx == 0` to mean a no-handle. + // This means the capacity is actually N-1 items. + items: [N]T, + + used_len: u32, // How many of the items are in use + unused_len: u32, // Use to calculate the number of valid items + unused_items: [N]u32, + next_unused: u32, +} + + +// `add` a value of type `T` to the handle map. This will return a pointer to the item and an optional boolean to check for validity. +@(require_results) +add :: proc "contextless" (m: ^$H/Handle_Map($N, $T, $Handle_Type), item: T) -> (handle: Handle_Type, ok: bool) #optional_ok { + if i := m.next_unused; i != 0 { + ptr := &m.items[i] + + m.next_unused = m.unused_items[i] + m.unused_items[i] = 0 + + prev_gen := ptr.handle.gen + ptr^ = item + + ptr.handle.idx = auto_cast i + ptr.handle.gen = auto_cast (prev_gen + 1) + m.unused_len -= 1 + return ptr.handle, true + } + + if m.used_len == 0 { + // initialize the zero-value sentinel + m.items[0] = {} + m.used_len += 1 + } + + if m.used_len == builtin.len(m.items) { + return {}, false + } + + ptr := &m.items[m.used_len] + ptr^ = item + + ptr.handle.idx = auto_cast m.used_len + ptr.handle.gen = 1 + m.used_len += 1 + return ptr.handle, true +} + +// `get` a stable pointer of type `^T` by resolving the handle `h`. If the handle is not valid, then `nil, false` is returned. +@(require_results) +get :: proc "contextless" (m: ^$H/Handle_Map($N, $T, $Handle_Type), h: Handle_Type) -> (^T, bool) #optional_ok { + if h.idx <= 0 || u32(h.idx) >= m.used_len { + return nil, false + } + if e := &m.items[h.idx]; e.handle == h { + return e, true + } + return nil, false +} + +// `remove` an item from the handle map from the handle `h`. +remove :: proc "contextless" (m: ^$H/Handle_Map($N, $T, $Handle_Type), h: Handle_Type) -> bool { + if h.idx <= 0 || u32(h.idx) >= m.used_len { + return false + } + + if item := &m.items[h.idx]; item.handle == h { + m.unused_items[h.idx] = m.next_unused + m.next_unused = u32(h.idx) + m.unused_len += 1 + item.handle.idx = 0 + return true + } + + return false +} + +// Returns true when the handle `h` is valid relating to the handle map. +@(require_results) +is_valid :: proc "contextless" (m: $H/Handle_Map($N, $T, $Handle_Type), h: Handle_Type) -> bool { + return h.idx > 0 && u32(h.idx) < m.used_len && m.items[h.idx].handle == h +} + +// Returns the number of possibly valid items in the handle map. +@(require_results) +len :: proc "contextless" (m: $H/Handle_Map($N, $T, $Handle_Type)) -> uint { + n := uint(m.used_len) - uint(m.unused_len) + return n-1 if n > 0 else 0 +} + +// Returns the capacity of the items in a handle map. +// This is equivalent to `N-1` as the zero value is reserved for the zero-value sentinel. +@(require_results) +cap :: proc "contextless" (m: $H/Handle_Map($N, $T, $Handle_Type)) -> uint { + // We could just return `N` but I am doing this for clarity + return builtin.len(m.items)-1 +} + +// `clear` the handle map by zeroing all of the memory. +// Internally this does not do `m^ = {}` but rather uses `intrinsics.mem_zero` explicitly improve performance. +clear :: proc "contextless" (m: ^$H/Handle_Map($N, $T, $Handle_Type)) { + intrinsics.mem_zero(m, size_of(m^)) +} + +// An iterator for a handle map. +Handle_Map_Iterator :: struct($H: typeid) { + m: ^H, + index: u32, +} + +// Makes an iterator from a handle map. +@(require_results) +iterator_make :: proc "contextless" (m: ^$H/Handle_Map($N, $T, $Handle_Type)) -> Handle_Map_Iterator(H) { + return {m, 1} +} + +/* + Iterate over a handle map. It will skip over unused item slots (e.g. handle.idx == 0). + Usage: + it := hm.iterator_make(&the_handle_map) + for item, handle in hm.iterate(&it) { + ... + } +*/ +@(require_results) +iterate :: proc "contextless" (it: ^$HI/Handle_Map_Iterator($H/Handle_Map($N, $T, $Handle_Type))) -> (val: ^T, h: Handle_Type, ok: bool) { + for _ in it.index.. Date: Mon, 26 Jan 2026 12:21:28 +0000 Subject: Add handle_map to examples/all --- examples/all/all_main.odin | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 9a7613ba5..69c9ad434 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -17,13 +17,14 @@ package all @(require) import "core:container/avl" @(require) import "core:container/bit_array" +@(require) import "core:container/handle_map" +@(require) import "core:container/intrusive/list" +@(require) import "core:container/lru" @(require) import "core:container/pool" @(require) import "core:container/priority_queue" @(require) import "core:container/queue" -@(require) import "core:container/small_array" -@(require) import "core:container/lru" -@(require) import "core:container/intrusive/list" @(require) import "core:container/rbtree" +@(require) import "core:container/small_array" @(require) import "core:container/topological_sort" @(require) import "core:container/xar" -- cgit v1.2.3 From 02e84f210843e4689090660131a3c6319ad06ee2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 26 Jan 2026 14:11:40 +0000 Subject: Keep `-strict-style` happy --- core/container/handle_map/handle_map.odin | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/container/handle_map/handle_map.odin b/core/container/handle_map/handle_map.odin index da816d288..37b8de9f6 100644 --- a/core/container/handle_map/handle_map.odin +++ b/core/container/handle_map/handle_map.odin @@ -65,8 +65,7 @@ Handle_Map :: struct($N: uint, $T: typeid, $Handle_Type: typeid) N < uint(max(intrinsics.type_field_type(Handle_Type, "idx"))), intrinsics.type_has_field (T, "handle"), - intrinsics.type_field_type(T, "handle") == Handle_Type -{ + intrinsics.type_field_type(T, "handle") == Handle_Type { // The zero element represent a zero-value sentinel (dummy value), allowing for `idx == 0` to mean a no-handle. // This means the capacity is actually N-1 items. -- cgit v1.2.3 From f2a8960ab0e94a2e94d6ddce1959c0373e251d4e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 26 Jan 2026 14:48:56 +0000 Subject: Add `Dynamic_Handle_Map` --- core/container/handle_map/doc.odin | 32 +++++ core/container/handle_map/dynamic_handle_map.odin | 142 ++++++++++++++++++++++ core/container/handle_map/handle_map.odin | 31 ----- 3 files changed, 174 insertions(+), 31 deletions(-) create mode 100644 core/container/handle_map/doc.odin create mode 100644 core/container/handle_map/dynamic_handle_map.odin diff --git a/core/container/handle_map/doc.odin b/core/container/handle_map/doc.odin new file mode 100644 index 000000000..e6ef0b344 --- /dev/null +++ b/core/container/handle_map/doc.odin @@ -0,0 +1,32 @@ +/* +Handle-based map using fixed-length arrays. + +Example: + import hm "core:container/handle_map" + + Handle :: hm.Handle32 + + Entity :: struct { + handle: Handle, + pos: [2]f32, + } + + entities: hm.Handle_Map(1024, Entity, Handle) + + h1 := hm.add(&entities, Entity{pos = {1, 4}}) + h2 := hm.add(&entities, Entity{pos = {9, 16}}) + + if e, ok := hm.get(&entities, h2); ok { + e.pos.x += 32 + } + + hm.remove(&entities, h1) + + h3 := hm.add(&entities, Entity{pos = {6, 7}}) + + it := hm.iterator_make(&entities) + for e, h in hm.iterate(&it) { + e.pos += {1, 2} + } +*/ +package container_handle_map \ No newline at end of file diff --git a/core/container/handle_map/dynamic_handle_map.odin b/core/container/handle_map/dynamic_handle_map.odin new file mode 100644 index 000000000..c754f2f25 --- /dev/null +++ b/core/container/handle_map/dynamic_handle_map.odin @@ -0,0 +1,142 @@ +package container_handle_map + +import "base:builtin" +import "base:runtime" +import "base:intrinsics" +import "core:container/xar" + + +Dynamic_Handle_Map :: struct($T: typeid, $Handle_Type: typeid) + where + intrinsics.type_has_field(Handle_Type, "idx"), + intrinsics.type_has_field(Handle_Type, "gen"), + intrinsics.type_is_unsigned(intrinsics.type_field_type(Handle_Type, "idx")), + intrinsics.type_is_unsigned(intrinsics.type_field_type(Handle_Type, "gen")), + intrinsics.type_field_type(Handle_Type, "idx") == intrinsics.type_field_type(Handle_Type, "gen"), + + intrinsics.type_has_field (T, "handle"), + intrinsics.type_field_type(T, "handle") == Handle_Type { + + items: xar.Array(T, 4), + unused_items: xar.Array(u32, 4), +} + +dynamic_init :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type), allocator: runtime.Allocator) { + xar.init(&m.items, allocator) + xar.init(&m.unused_items, allocator) +} + +dynamic_destroy :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type)) { + xar.destroy(&m.unused_items) + xar.destroy(&m.items) +} + +@(require_results) +dynamic_add :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type), item: T, loc := #caller_location) -> (handle: Handle_Type, err: runtime.Allocator_Error) { + if xar.len(m.unused_items) > 0 { + i := xar.pop(&m.unused_items) + ptr := xar.get_ptr(&m.items, i, loc) + prev_gen := ptr.handle.gen + ptr^ = item + + ptr.handle.idx = auto_cast i + ptr.handle.gen = auto_cast (prev_gen + 1) + return ptr.handle, nil + } + + if xar.len(m.items) == 0 { + // initialize the zero-value sentinel + xar.append(&m.items, T{}, loc) or_return + } + + i := xar.append(&m.items, item, loc) or_return + + ptr := xar.get_ptr(&m.items, i, loc) + ptr^ = item + + ptr.handle.idx = auto_cast i + ptr.handle.gen = 1 + return ptr.handle, nil +} + +@(require_results) +dynamic_get :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type), h: Handle_Type) -> (^T, bool) #optional_ok { + if h.idx <= 0 || int(u32(h.idx)) >= xar.len(m.items) { + return nil, false + } + if e := xar.get_ptr(&m.items, h.idx); e.handle == h { + return e, true + } + return nil, false +} + +dynamic_remove :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type), h: Handle_Type, loc := #caller_location) -> (found: bool, err: runtime.Allocator_Error) { + if h.idx <= 0 || int(u32(h.idx)) >= xar.len(m.items) { + return false, nil + } + + if item := xar.get_ptr(&m.items, h.idx); item.handle == h { + xar.append(&m.unused_items, u32(h.idx), loc) or_return + item.handle.idx = 0 + return true, nil + } + + return false, nil +} + +@(require_results) +dynamic_is_valid :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type), h: Handle_Type, loc := #caller_location) -> bool { + return h.idx > 0 && int(u32(h.idx)) < xar.len(m.items) && xar.get(&m.items, h.idx, loc).handle == h +} + +// Returns the number of possibly valid items in the handle map. +@(require_results) +dynamic_len :: proc(m: $D/Dynamic_Handle_Map($T, $Handle_Type)) -> uint { + n := xar.len(m.items) - xar.len(m.unused_items) + return uint(n-1 if n > 0 else 0) +} + +@(require_results) +dynamic_cap :: proc(m: $D/Dynamic_Handle_Map($T, $Handle_Type)) -> uint { + n := xar.cap(m.items) + return uint(n-1 if n > 0 else 0) +} + +dynamic_clear :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type)) { + xar.clear(&m.items) + xar.clear(&m.unused_items) +} + + +// An iterator for a handle map. +Dynamic_Handle_Map_Iterator :: struct($D: typeid) { + m: ^D, + index: int, +} + +// Makes an iterator from a handle map. +@(require_results) +dynamic_iterator_make :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type)) -> Dynamic_Handle_Map_Iterator(D) { + return {m, 1} +} + +/* + Iterate over a handle map. It will skip over unused item slots (e.g. handle.idx == 0). + Usage: + it := hm.dynamic_iterator_make(&the_dynamic_handle_map) + for item, handle in hm.iterate(&it) { + ... + } +*/ +@(require_results) +dynamic_iterate :: proc(it: ^$DHI/Dynamic_Handle_Map_Iterator($D/Dynamic_Handle_Map($T, $Handle_Type))) -> (val: ^T, h: Handle_Type, ok: bool) { + for _ in it.index.. Date: Mon, 26 Jan 2026 14:53:01 +0000 Subject: Make things contextless where possible --- core/container/handle_map/dynamic_handle_map.odin | 24 +++++++++++------------ core/container/xar/xar.odin | 15 ++++++++++---- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/core/container/handle_map/dynamic_handle_map.odin b/core/container/handle_map/dynamic_handle_map.odin index c754f2f25..600f49c28 100644 --- a/core/container/handle_map/dynamic_handle_map.odin +++ b/core/container/handle_map/dynamic_handle_map.odin @@ -35,7 +35,7 @@ dynamic_destroy :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type)) { dynamic_add :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type), item: T, loc := #caller_location) -> (handle: Handle_Type, err: runtime.Allocator_Error) { if xar.len(m.unused_items) > 0 { i := xar.pop(&m.unused_items) - ptr := xar.get_ptr(&m.items, i, loc) + ptr := xar.get_ptr_unsafe(&m.items, i) prev_gen := ptr.handle.gen ptr^ = item @@ -51,7 +51,7 @@ dynamic_add :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type), item: T, loc := i := xar.append(&m.items, item, loc) or_return - ptr := xar.get_ptr(&m.items, i, loc) + ptr := xar.get_ptr_unsafe(&m.items, i) ptr^ = item ptr.handle.idx = auto_cast i @@ -60,11 +60,11 @@ dynamic_add :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type), item: T, loc := } @(require_results) -dynamic_get :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type), h: Handle_Type) -> (^T, bool) #optional_ok { +dynamic_get :: proc "contextless" (m: ^$D/Dynamic_Handle_Map($T, $Handle_Type), h: Handle_Type) -> (^T, bool) #optional_ok { if h.idx <= 0 || int(u32(h.idx)) >= xar.len(m.items) { return nil, false } - if e := xar.get_ptr(&m.items, h.idx); e.handle == h { + if e := xar.get_ptr_unsafe(&m.items, h.idx); e.handle == h { return e, true } return nil, false @@ -85,24 +85,24 @@ dynamic_remove :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type), h: Handle_Ty } @(require_results) -dynamic_is_valid :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type), h: Handle_Type, loc := #caller_location) -> bool { - return h.idx > 0 && int(u32(h.idx)) < xar.len(m.items) && xar.get(&m.items, h.idx, loc).handle == h +dynamic_is_valid :: proc "contextless" (m: ^$D/Dynamic_Handle_Map($T, $Handle_Type), h: Handle_Type) -> bool { + return h.idx > 0 && int(u32(h.idx)) < xar.len(m.items) && xar.get_ptr_unsafe(&m.items, h.idx).handle == h } // Returns the number of possibly valid items in the handle map. @(require_results) -dynamic_len :: proc(m: $D/Dynamic_Handle_Map($T, $Handle_Type)) -> uint { +dynamic_len :: proc "contextless" (m: $D/Dynamic_Handle_Map($T, $Handle_Type)) -> uint { n := xar.len(m.items) - xar.len(m.unused_items) return uint(n-1 if n > 0 else 0) } @(require_results) -dynamic_cap :: proc(m: $D/Dynamic_Handle_Map($T, $Handle_Type)) -> uint { +dynamic_cap :: proc "contextless" (m: $D/Dynamic_Handle_Map($T, $Handle_Type)) -> uint { n := xar.cap(m.items) return uint(n-1 if n > 0 else 0) } -dynamic_clear :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type)) { +dynamic_clear :: proc "contextless" (m: ^$D/Dynamic_Handle_Map($T, $Handle_Type)) { xar.clear(&m.items) xar.clear(&m.unused_items) } @@ -116,7 +116,7 @@ Dynamic_Handle_Map_Iterator :: struct($D: typeid) { // Makes an iterator from a handle map. @(require_results) -dynamic_iterator_make :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type)) -> Dynamic_Handle_Map_Iterator(D) { +dynamic_iterator_make :: proc "contextless" (m: ^$D/Dynamic_Handle_Map($T, $Handle_Type)) -> Dynamic_Handle_Map_Iterator(D) { return {m, 1} } @@ -129,9 +129,9 @@ dynamic_iterator_make :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type)) -> Dy } */ @(require_results) -dynamic_iterate :: proc(it: ^$DHI/Dynamic_Handle_Map_Iterator($D/Dynamic_Handle_Map($T, $Handle_Type))) -> (val: ^T, h: Handle_Type, ok: bool) { +dynamic_iterate :: proc "contextless" (it: ^$DHI/Dynamic_Handle_Map_Iterator($D/Dynamic_Handle_Map($T, $Handle_Type))) -> (val: ^T, h: Handle_Type, ok: bool) { for _ in it.index.. int { +len :: proc "contextless" (x: $X/Array($T, $SHIFT)) -> int { return x.len } // Returns the number of allocated elements @(require_results) -cap :: proc(x: $X/Array($T, $SHIFT)) -> int { +cap :: proc "contextless" (x: $X/Array($T, $SHIFT)) -> int { #reverse for c, i in x.chunks { if c != nil { return 1 << (SHIFT + uint(i if i > 0 else 1)) @@ -132,7 +132,7 @@ cap :: proc(x: $X/Array($T, $SHIFT)) -> int { // Internal: computes chunk index, element index within chunk, and chunk capacity for a given index. @(require_results) -_meta_get :: #force_inline proc($SHIFT: uint, index: uint) -> (chunk_idx, elem_idx, chunk_cap: uint) { +_meta_get :: #force_inline proc "contextless" ($SHIFT: uint, index: uint) -> (chunk_idx, elem_idx, chunk_cap: uint) { elem_idx = index chunk_cap = uint(1) << SHIFT chunk_idx = 0 @@ -206,6 +206,13 @@ get_ptr :: proc(x: ^$X/Array($T, $SHIFT), #any_int index: int, loc := #caller_lo return &x.chunks[chunk_idx][elem_idx] } +// No bounds checking +@(require_results) +get_ptr_unsafe :: proc "contextless" (x: ^$X/Array($T, $SHIFT), #any_int index: int) -> (val: ^T) #no_bounds_check { + chunk_idx, elem_idx, _ := _meta_get(SHIFT, uint(index)) + return &x.chunks[chunk_idx][elem_idx] +} + /* Set the element at the specified index to the given value. -- cgit v1.2.3 From 25ecca7159c8cf1c63579ea796662357cd93d804 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 26 Jan 2026 15:00:00 +0000 Subject: Rename to `Static_Handle_Map` --- core/container/handle_map/handle_map.odin | 71 +++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 12 deletions(-) diff --git a/core/container/handle_map/handle_map.odin b/core/container/handle_map/handle_map.odin index 485371d42..f308c5e95 100644 --- a/core/container/handle_map/handle_map.odin +++ b/core/container/handle_map/handle_map.odin @@ -21,7 +21,7 @@ Handle64 :: struct { gen: u32, } -Handle_Map :: struct($N: uint, $T: typeid, $Handle_Type: typeid) +Static_Handle_Map :: struct($N: uint, $T: typeid, $Handle_Type: typeid) where 0 < N, N < uint(1<<31 - 1), @@ -49,7 +49,7 @@ Handle_Map :: struct($N: uint, $T: typeid, $Handle_Type: typeid) // `add` a value of type `T` to the handle map. This will return a pointer to the item and an optional boolean to check for validity. @(require_results) -add :: proc "contextless" (m: ^$H/Handle_Map($N, $T, $Handle_Type), item: T) -> (handle: Handle_Type, ok: bool) #optional_ok { +static_add :: proc "contextless" (m: ^$H/Static_Handle_Map($N, $T, $Handle_Type), item: T) -> (handle: Handle_Type, ok: bool) #optional_ok { if i := m.next_unused; i != 0 { ptr := &m.items[i] @@ -86,7 +86,7 @@ add :: proc "contextless" (m: ^$H/Handle_Map($N, $T, $Handle_Type), item: T) -> // `get` a stable pointer of type `^T` by resolving the handle `h`. If the handle is not valid, then `nil, false` is returned. @(require_results) -get :: proc "contextless" (m: ^$H/Handle_Map($N, $T, $Handle_Type), h: Handle_Type) -> (^T, bool) #optional_ok { +static_get :: proc "contextless" (m: ^$H/Static_Handle_Map($N, $T, $Handle_Type), h: Handle_Type) -> (^T, bool) #optional_ok { if h.idx <= 0 || u32(h.idx) >= m.used_len { return nil, false } @@ -97,7 +97,7 @@ get :: proc "contextless" (m: ^$H/Handle_Map($N, $T, $Handle_Type), h: Handle_Ty } // `remove` an item from the handle map from the handle `h`. -remove :: proc "contextless" (m: ^$H/Handle_Map($N, $T, $Handle_Type), h: Handle_Type) -> bool { +static_remove :: proc "contextless" (m: ^$H/Static_Handle_Map($N, $T, $Handle_Type), h: Handle_Type) -> bool { if h.idx <= 0 || u32(h.idx) >= m.used_len { return false } @@ -115,13 +115,13 @@ remove :: proc "contextless" (m: ^$H/Handle_Map($N, $T, $Handle_Type), h: Handle // Returns true when the handle `h` is valid relating to the handle map. @(require_results) -is_valid :: proc "contextless" (m: $H/Handle_Map($N, $T, $Handle_Type), h: Handle_Type) -> bool { +static_is_valid :: proc "contextless" (m: $H/Static_Handle_Map($N, $T, $Handle_Type), h: Handle_Type) -> bool { return h.idx > 0 && u32(h.idx) < m.used_len && m.items[h.idx].handle == h } // Returns the number of possibly valid items in the handle map. @(require_results) -len :: proc "contextless" (m: $H/Handle_Map($N, $T, $Handle_Type)) -> uint { +static_len :: proc "contextless" (m: $H/Static_Handle_Map($N, $T, $Handle_Type)) -> uint { n := uint(m.used_len) - uint(m.unused_len) return n-1 if n > 0 else 0 } @@ -129,26 +129,26 @@ len :: proc "contextless" (m: $H/Handle_Map($N, $T, $Handle_Type)) -> uint { // Returns the capacity of the items in a handle map. // This is equivalent to `N-1` as the zero value is reserved for the zero-value sentinel. @(require_results) -cap :: proc "contextless" (m: $H/Handle_Map($N, $T, $Handle_Type)) -> uint { +static_cap :: proc "contextless" (m: $H/Static_Handle_Map($N, $T, $Handle_Type)) -> uint { // We could just return `N` but I am doing this for clarity return builtin.len(m.items)-1 } // `clear` the handle map by zeroing all of the memory. // Internally this does not do `m^ = {}` but rather uses `intrinsics.mem_zero` explicitly improve performance. -clear :: proc "contextless" (m: ^$H/Handle_Map($N, $T, $Handle_Type)) { +static_clear :: proc "contextless" (m: ^$H/Static_Handle_Map($N, $T, $Handle_Type)) { intrinsics.mem_zero(m, size_of(m^)) } // An iterator for a handle map. -Handle_Map_Iterator :: struct($H: typeid) { +Static_Handle_Map_Iterator :: struct($H: typeid) { m: ^H, index: u32, } // Makes an iterator from a handle map. @(require_results) -iterator_make :: proc "contextless" (m: ^$H/Handle_Map($N, $T, $Handle_Type)) -> Handle_Map_Iterator(H) { +static_iterator_make :: proc "contextless" (m: ^$H/Static_Handle_Map($N, $T, $Handle_Type)) -> Static_Handle_Map_Iterator(H) { return {m, 1} } @@ -161,7 +161,7 @@ iterator_make :: proc "contextless" (m: ^$H/Handle_Map($N, $T, $Handle_Type)) -> } */ @(require_results) -iterate :: proc "contextless" (it: ^$HI/Handle_Map_Iterator($H/Handle_Map($N, $T, $Handle_Type))) -> (val: ^T, h: Handle_Type, ok: bool) { +static_iterate :: proc "contextless" (it: ^$HI/Static_Handle_Map_Iterator($H/Static_Handle_Map($N, $T, $Handle_Type))) -> (val: ^T, h: Handle_Type, ok: bool) { for _ in it.index.. Date: Mon, 26 Jan 2026 15:00:15 +0000 Subject: Rename to static_handle_map.odin --- core/container/handle_map/handle_map.odin | 221 ----------------------- core/container/handle_map/static_handle_map.odin | 221 +++++++++++++++++++++++ 2 files changed, 221 insertions(+), 221 deletions(-) delete mode 100644 core/container/handle_map/handle_map.odin create mode 100644 core/container/handle_map/static_handle_map.odin diff --git a/core/container/handle_map/handle_map.odin b/core/container/handle_map/handle_map.odin deleted file mode 100644 index f308c5e95..000000000 --- a/core/container/handle_map/handle_map.odin +++ /dev/null @@ -1,221 +0,0 @@ -package container_handle_map - -import "base:builtin" -import "base:intrinsics" - -// Default 16-bit Handle type which can be used for handle maps which only need a maximum of 254 (1<<8 - 2) items -Handle16 :: struct { - idx: u8, - gen: u8, -} - -// Default 32-bit Handle type which can be used for handle maps which only need a maximum of 65534 (1<<16 - 2) items -Handle32 :: struct { - idx: u16, - gen: u16, -} - -// Default 64-bit Handle type which can be used for handle maps which only need a maximum of 4294967294 (1<<32 - 2) items -Handle64 :: struct { - idx: u32, - gen: u32, -} - -Static_Handle_Map :: struct($N: uint, $T: typeid, $Handle_Type: typeid) - where - 0 < N, N < uint(1<<31 - 1), - - intrinsics.type_has_field(Handle_Type, "idx"), - intrinsics.type_has_field(Handle_Type, "gen"), - intrinsics.type_is_unsigned(intrinsics.type_field_type(Handle_Type, "idx")), - intrinsics.type_is_unsigned(intrinsics.type_field_type(Handle_Type, "gen")), - intrinsics.type_field_type(Handle_Type, "idx") == intrinsics.type_field_type(Handle_Type, "gen"), - - N < uint(max(intrinsics.type_field_type(Handle_Type, "idx"))), - - intrinsics.type_has_field (T, "handle"), - intrinsics.type_field_type(T, "handle") == Handle_Type { - - // The zero element represent a zero-value sentinel (dummy value), allowing for `idx == 0` to mean a no-handle. - // This means the capacity is actually N-1 items. - items: [N]T, - - used_len: u32, // How many of the items are in use - unused_len: u32, // Use to calculate the number of valid items - unused_items: [N]u32, - next_unused: u32, -} - - -// `add` a value of type `T` to the handle map. This will return a pointer to the item and an optional boolean to check for validity. -@(require_results) -static_add :: proc "contextless" (m: ^$H/Static_Handle_Map($N, $T, $Handle_Type), item: T) -> (handle: Handle_Type, ok: bool) #optional_ok { - if i := m.next_unused; i != 0 { - ptr := &m.items[i] - - m.next_unused = m.unused_items[i] - m.unused_items[i] = 0 - - prev_gen := ptr.handle.gen - ptr^ = item - - ptr.handle.idx = auto_cast i - ptr.handle.gen = auto_cast (prev_gen + 1) - m.unused_len -= 1 - return ptr.handle, true - } - - if m.used_len == 0 { - // initialize the zero-value sentinel - m.items[0] = {} - m.used_len += 1 - } - - if m.used_len == builtin.len(m.items) { - return {}, false - } - - ptr := &m.items[m.used_len] - ptr^ = item - - ptr.handle.idx = auto_cast m.used_len - ptr.handle.gen = 1 - m.used_len += 1 - return ptr.handle, true -} - -// `get` a stable pointer of type `^T` by resolving the handle `h`. If the handle is not valid, then `nil, false` is returned. -@(require_results) -static_get :: proc "contextless" (m: ^$H/Static_Handle_Map($N, $T, $Handle_Type), h: Handle_Type) -> (^T, bool) #optional_ok { - if h.idx <= 0 || u32(h.idx) >= m.used_len { - return nil, false - } - if e := &m.items[h.idx]; e.handle == h { - return e, true - } - return nil, false -} - -// `remove` an item from the handle map from the handle `h`. -static_remove :: proc "contextless" (m: ^$H/Static_Handle_Map($N, $T, $Handle_Type), h: Handle_Type) -> bool { - if h.idx <= 0 || u32(h.idx) >= m.used_len { - return false - } - - if item := &m.items[h.idx]; item.handle == h { - m.unused_items[h.idx] = m.next_unused - m.next_unused = u32(h.idx) - m.unused_len += 1 - item.handle.idx = 0 - return true - } - - return false -} - -// Returns true when the handle `h` is valid relating to the handle map. -@(require_results) -static_is_valid :: proc "contextless" (m: $H/Static_Handle_Map($N, $T, $Handle_Type), h: Handle_Type) -> bool { - return h.idx > 0 && u32(h.idx) < m.used_len && m.items[h.idx].handle == h -} - -// Returns the number of possibly valid items in the handle map. -@(require_results) -static_len :: proc "contextless" (m: $H/Static_Handle_Map($N, $T, $Handle_Type)) -> uint { - n := uint(m.used_len) - uint(m.unused_len) - return n-1 if n > 0 else 0 -} - -// Returns the capacity of the items in a handle map. -// This is equivalent to `N-1` as the zero value is reserved for the zero-value sentinel. -@(require_results) -static_cap :: proc "contextless" (m: $H/Static_Handle_Map($N, $T, $Handle_Type)) -> uint { - // We could just return `N` but I am doing this for clarity - return builtin.len(m.items)-1 -} - -// `clear` the handle map by zeroing all of the memory. -// Internally this does not do `m^ = {}` but rather uses `intrinsics.mem_zero` explicitly improve performance. -static_clear :: proc "contextless" (m: ^$H/Static_Handle_Map($N, $T, $Handle_Type)) { - intrinsics.mem_zero(m, size_of(m^)) -} - -// An iterator for a handle map. -Static_Handle_Map_Iterator :: struct($H: typeid) { - m: ^H, - index: u32, -} - -// Makes an iterator from a handle map. -@(require_results) -static_iterator_make :: proc "contextless" (m: ^$H/Static_Handle_Map($N, $T, $Handle_Type)) -> Static_Handle_Map_Iterator(H) { - return {m, 1} -} - -/* - Iterate over a handle map. It will skip over unused item slots (e.g. handle.idx == 0). - Usage: - it := hm.iterator_make(&the_handle_map) - for item, handle in hm.iterate(&it) { - ... - } -*/ -@(require_results) -static_iterate :: proc "contextless" (it: ^$HI/Static_Handle_Map_Iterator($H/Static_Handle_Map($N, $T, $Handle_Type))) -> (val: ^T, h: Handle_Type, ok: bool) { - for _ in it.index.. (handle: Handle_Type, ok: bool) #optional_ok { + if i := m.next_unused; i != 0 { + ptr := &m.items[i] + + m.next_unused = m.unused_items[i] + m.unused_items[i] = 0 + + prev_gen := ptr.handle.gen + ptr^ = item + + ptr.handle.idx = auto_cast i + ptr.handle.gen = auto_cast (prev_gen + 1) + m.unused_len -= 1 + return ptr.handle, true + } + + if m.used_len == 0 { + // initialize the zero-value sentinel + m.items[0] = {} + m.used_len += 1 + } + + if m.used_len == builtin.len(m.items) { + return {}, false + } + + ptr := &m.items[m.used_len] + ptr^ = item + + ptr.handle.idx = auto_cast m.used_len + ptr.handle.gen = 1 + m.used_len += 1 + return ptr.handle, true +} + +// `get` a stable pointer of type `^T` by resolving the handle `h`. If the handle is not valid, then `nil, false` is returned. +@(require_results) +static_get :: proc "contextless" (m: ^$H/Static_Handle_Map($N, $T, $Handle_Type), h: Handle_Type) -> (^T, bool) #optional_ok { + if h.idx <= 0 || u32(h.idx) >= m.used_len { + return nil, false + } + if e := &m.items[h.idx]; e.handle == h { + return e, true + } + return nil, false +} + +// `remove` an item from the handle map from the handle `h`. +static_remove :: proc "contextless" (m: ^$H/Static_Handle_Map($N, $T, $Handle_Type), h: Handle_Type) -> bool { + if h.idx <= 0 || u32(h.idx) >= m.used_len { + return false + } + + if item := &m.items[h.idx]; item.handle == h { + m.unused_items[h.idx] = m.next_unused + m.next_unused = u32(h.idx) + m.unused_len += 1 + item.handle.idx = 0 + return true + } + + return false +} + +// Returns true when the handle `h` is valid relating to the handle map. +@(require_results) +static_is_valid :: proc "contextless" (m: $H/Static_Handle_Map($N, $T, $Handle_Type), h: Handle_Type) -> bool { + return h.idx > 0 && u32(h.idx) < m.used_len && m.items[h.idx].handle == h +} + +// Returns the number of possibly valid items in the handle map. +@(require_results) +static_len :: proc "contextless" (m: $H/Static_Handle_Map($N, $T, $Handle_Type)) -> uint { + n := uint(m.used_len) - uint(m.unused_len) + return n-1 if n > 0 else 0 +} + +// Returns the capacity of the items in a handle map. +// This is equivalent to `N-1` as the zero value is reserved for the zero-value sentinel. +@(require_results) +static_cap :: proc "contextless" (m: $H/Static_Handle_Map($N, $T, $Handle_Type)) -> uint { + // We could just return `N` but I am doing this for clarity + return builtin.len(m.items)-1 +} + +// `clear` the handle map by zeroing all of the memory. +// Internally this does not do `m^ = {}` but rather uses `intrinsics.mem_zero` explicitly improve performance. +static_clear :: proc "contextless" (m: ^$H/Static_Handle_Map($N, $T, $Handle_Type)) { + intrinsics.mem_zero(m, size_of(m^)) +} + +// An iterator for a handle map. +Static_Handle_Map_Iterator :: struct($H: typeid) { + m: ^H, + index: u32, +} + +// Makes an iterator from a handle map. +@(require_results) +static_iterator_make :: proc "contextless" (m: ^$H/Static_Handle_Map($N, $T, $Handle_Type)) -> Static_Handle_Map_Iterator(H) { + return {m, 1} +} + +/* + Iterate over a handle map. It will skip over unused item slots (e.g. handle.idx == 0). + Usage: + it := hm.iterator_make(&the_handle_map) + for item, handle in hm.iterate(&it) { + ... + } +*/ +@(require_results) +static_iterate :: proc "contextless" (it: ^$HI/Static_Handle_Map_Iterator($H/Static_Handle_Map($N, $T, $Handle_Type))) -> (val: ^T, h: Handle_Type, ok: bool) { + for _ in it.index.. Date: Mon, 26 Jan 2026 15:06:15 +0000 Subject: Keep `-vet` happy --- core/container/handle_map/dynamic_handle_map.odin | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/container/handle_map/dynamic_handle_map.odin b/core/container/handle_map/dynamic_handle_map.odin index 600f49c28..067212a54 100644 --- a/core/container/handle_map/dynamic_handle_map.odin +++ b/core/container/handle_map/dynamic_handle_map.odin @@ -1,10 +1,9 @@ package container_handle_map -import "base:builtin" import "base:runtime" +import "base:builtin" import "base:intrinsics" -import "core:container/xar" - +@(require) import "core:container/xar" Dynamic_Handle_Map :: struct($T: typeid, $Handle_Type: typeid) where -- cgit v1.2.3 From 2859bc08534d00fe8b8b30b55a08a795221d1620 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 26 Jan 2026 15:11:07 +0000 Subject: Update doc.odin --- core/container/handle_map/doc.odin | 44 +++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/core/container/handle_map/doc.odin b/core/container/handle_map/doc.odin index e6ef0b344..c1949ffdd 100644 --- a/core/container/handle_map/doc.odin +++ b/core/container/handle_map/doc.odin @@ -11,22 +11,46 @@ Example: pos: [2]f32, } - entities: hm.Handle_Map(1024, Entity, Handle) + { // static map + entities: hm.Static_Handle_Map(1024, Entity, Handle) - h1 := hm.add(&entities, Entity{pos = {1, 4}}) - h2 := hm.add(&entities, Entity{pos = {9, 16}}) + h1 := hm.add(&entities, Entity{pos = {1, 4}}) + h2 := hm.add(&entities, Entity{pos = {9, 16}}) - if e, ok := hm.get(&entities, h2); ok { - e.pos.x += 32 + if e, ok := hm.get(&entities, h2); ok { + e.pos.x += 32 + } + + hm.remove(&entities, h1) + + h3 := hm.add(&entities, Entity{pos = {6, 7}}) + + it := hm.iterator_make(&entities) + for e, h in hm.iterate(&it) { + e.pos += {1, 2} + } } - hm.remove(&entities, h1) + { // dynamic map + entities: hm.Dynamic_Handle_Map(Entity, Handle) + hm.dynamic_init(&entities, context.allocator) + defer hm.dynamic_destroy(&entities) + + h1 := hm.add(&entities, Entity{pos = {1, 4}}) + h2 := hm.add(&entities, Entity{pos = {9, 16}}) + + if e, ok := hm.get(&entities, h2); ok { + e.pos.x += 32 + } + + hm.remove(&entities, h1) - h3 := hm.add(&entities, Entity{pos = {6, 7}}) + h3 := hm.add(&entities, Entity{pos = {6, 7}}) - it := hm.iterator_make(&entities) - for e, h in hm.iterate(&it) { - e.pos += {1, 2} + it := hm.iterator_make(&entities) + for e, h in hm.iterate(&it) { + e.pos += {1, 2} + } } */ package container_handle_map \ No newline at end of file -- cgit v1.2.3