mirror of
https://github.com/odin-lang/Odin.git
synced 2026-02-12 06:18:39 +00:00
Add core:container/handle_map
This commit is contained in:
206
core/container/handle_map/handle_map.odin
Normal file
206
core/container/handle_map/handle_map.odin
Normal file
@@ -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..<it.m.used_len {
|
||||
e := &it.m.items[it.index]
|
||||
it.index += 1
|
||||
|
||||
if e.handle.idx != 0 {
|
||||
return e, e.handle, true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user