Skip to content

Instantly share code, notes, and snippets.

@karl-zylinski
Last active May 1, 2025 17:50

Revisions

  1. karl-zylinski revised this gist Dec 19, 2024. 1 changed file with 42 additions and 23 deletions.
    65 changes: 42 additions & 23 deletions handle_array.odin
    Original file line number Diff line number Diff line change
    @@ -1,12 +1,30 @@
    // Handle-based array. Used like this:
    //
    // EntityHandle :: distinct Handle
    // entities: HandleArray(Entity, EntityHandle)
    // Entity_Handle :: distinct Handle
    // entities: Handle_Array(Entity, Entity_Handle)
    //
    // It expects the type used (in this case Entity) to contains a field called handle
    // of type Handle
    // It expects the type used (in this case
    // Entity) to contains a field called
    // `handle`, of type `Entity_Handle`.
    //
    // You can then fetch entities using ha_get() and add handles using ha_add()
    // You can then fetch entities using
    // ha_get() and add handles using ha_add().
    //
    // This is just an example implementation.
    // There are many ways to implement this.
    //
    // Another example by Bill:
    // https://gist.github.com/gingerBill/7282ff54744838c52cc80c559f697051
    //
    // Bill's example is a bit more
    // complicated, but a bit more efficient.
    // It doesn't have to loop over all items
    // to "skip unoccupied holes".
    //
    // Jakub also has an implementation of
    // something similar. But his version
    // doesn't use dynamic memory:
    // https://github.com/jakubtomsu/sds/blob/main/pool.odin

    package game

    @@ -15,25 +33,25 @@ Handle :: struct {
    gen: u32,
    }

    HandleNone :: Handle {}
    HANDLE_NONE :: Handle {}

    HandleArray :: struct($T: typeid, $HT: typeid) {
    Handle_Array :: struct($T: typeid, $HT: typeid) {
    items: [dynamic]T,
    freelist: [dynamic]HT,
    num: int,
    }

    ha_clear :: proc(ha: ^HandleArray($T, $HT)) {
    ha_clear :: proc(ha: ^Handle_Array($T, $HT)) {
    clear(&ha.items)
    clear(&ha.freelist)
    }

    ha_delete :: proc(ha: HandleArray($T, $HT)) {
    ha_delete :: proc(ha: Handle_Array($T, $HT)) {
    delete(ha.items)
    delete(ha.freelist)
    }

    ha_add :: proc(ha: ^HandleArray($T, $HT), v: T) -> HT {
    ha_add :: proc(ha: ^Handle_Array($T, $HT), v: T) -> HT {
    v := v

    if len(ha.freelist) > 0 {
    @@ -57,51 +75,52 @@ ha_add :: proc(ha: ^HandleArray($T, $HT), v: T) -> HT {
    return v.handle
    }

    ha_get :: proc(ha: HandleArray($T, $HT), h: HT) -> (T, bool) {
    ha_get :: proc(ha: Handle_Array($T, $HT), h: HT) -> (T, bool) {
    if h.idx > 0 && int(h.idx) < len(ha.items) && ha.items[h.idx].handle == h {
    return ha.items[h.idx], true
    }

    return {}, false
    }

    ha_get_ptr :: proc(ha: HandleArray($T, $HT), h: HT) -> ^T {
    ha_get_ptr :: proc(ha: Handle_Array($T, $HT), h: HT) -> ^T {
    if h.idx > 0 && int(h.idx) < len(ha.items) && ha.items[h.idx].handle == h {
    return &ha.items[h.idx]
    }

    return nil
    }

    ha_remove :: proc(ha: ^HandleArray($T, $HT), h: HT) {
    ha_remove :: proc(ha: ^Handle_Array($T, $HT), h: HT) {
    if h.idx > 0 && int(h.idx) < len(ha.items) && ha.items[h.idx].handle == h {
    append(&ha.freelist, h)
    ha.items[h.idx] = {}
    ha.num -= 1
    }
    }

    ha_valid :: proc(ha: HandleArray($T, $HT), h: HT) -> bool {
    ha_valid :: proc(ha: Handle_Array($T, $HT), h: HT) -> bool {
    return ha_get(ha, h) != nil
    }

    // Iterators for iterating over all used slots in the array.
    // Used like this:
    // Iterators for iterating over all used
    // slots in the array. Used like this:
    //
    // ent_iter := ha_make_iter(your_handle_based_array)
    // for e in ha_iter_ptr(&ent_iter) {
    //
    // }

    HandleArrayIter :: struct($T: typeid, $HT: typeid) {
    ha: HandleArray(T, HT),
    Handle_Array_Iter :: struct($T: typeid, $HT: typeid) {
    ha: Handle_Array(T, HT),
    index: int,
    }

    ha_make_iter :: proc(ha: HandleArray($T, $HT)) -> HandleArrayIter(T, HT) {
    return HandleArrayIter(T, HT) { ha = ha }
    ha_make_iter :: proc(ha: Handle_Array($T, $HT)) -> Handle_Array_Iter(T, HT) {
    return Handle_Array_Iter(T, HT) { ha = ha }
    }

    ha_iter :: proc(it: ^HandleArrayIter($T, $HT)) -> (val: T, h: HT, cond: bool) {
    ha_iter :: proc(it: ^Handle_Array_Iter($T, $HT)) -> (val: T, h: HT, cond: bool) {
    in_range := it.index < len(it.ha.items)

    for in_range {
    @@ -121,7 +140,7 @@ ha_iter :: proc(it: ^HandleArrayIter($T, $HT)) -> (val: T, h: HT, cond: bool) {
    return
    }

    ha_iter_ptr :: proc(it: ^HandleArrayIter($T, $HT)) -> (val: ^T, h: HT, cond: bool) {
    ha_iter_ptr :: proc(it: ^Handle_Array_Iter($T, $HT)) -> (val: ^T, h: HT, cond: bool) {
    in_range := it.index < len(it.ha.items)

    for in_range {
    @@ -139,4 +158,4 @@ ha_iter_ptr :: proc(it: ^HandleArrayIter($T, $HT)) -> (val: ^T, h: HT, cond: boo
    }

    return
    }
    }
  2. karl-zylinski revised this gist Sep 8, 2023. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion handle_array.odin
    Original file line number Diff line number Diff line change
    @@ -85,7 +85,7 @@ ha_valid :: proc(ha: HandleArray($T, $HT), h: HT) -> bool {
    return ha_get(ha, h) != nil
    }

    // Iterators for going iterating over all used slots in the array.
    // Iterators for iterating over all used slots in the array.
    // Used like this:
    //
    // ent_iter := ha_make_iter(your_handle_based_array)
  3. karl-zylinski revised this gist Sep 8, 2023. 1 changed file with 7 additions and 0 deletions.
    7 changes: 7 additions & 0 deletions handle_array.odin
    Original file line number Diff line number Diff line change
    @@ -85,6 +85,13 @@ ha_valid :: proc(ha: HandleArray($T, $HT), h: HT) -> bool {
    return ha_get(ha, h) != nil
    }

    // Iterators for going iterating over all used slots in the array.
    // Used like this:
    //
    // ent_iter := ha_make_iter(your_handle_based_array)
    // for e in ha_iter_ptr(&ent_iter) {
    // }

    HandleArrayIter :: struct($T: typeid, $HT: typeid) {
    ha: HandleArray(T, HT),
    index: int,
  4. karl-zylinski created this gist Sep 8, 2023.
    135 changes: 135 additions & 0 deletions handle_array.odin
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,135 @@
    // Handle-based array. Used like this:
    //
    // EntityHandle :: distinct Handle
    // entities: HandleArray(Entity, EntityHandle)
    //
    // It expects the type used (in this case Entity) to contains a field called handle
    // of type Handle
    //
    // You can then fetch entities using ha_get() and add handles using ha_add()

    package game

    Handle :: struct {
    idx: u32,
    gen: u32,
    }

    HandleNone :: Handle {}

    HandleArray :: struct($T: typeid, $HT: typeid) {
    items: [dynamic]T,
    freelist: [dynamic]HT,
    num: int,
    }

    ha_clear :: proc(ha: ^HandleArray($T, $HT)) {
    clear(&ha.items)
    clear(&ha.freelist)
    }

    ha_delete :: proc(ha: HandleArray($T, $HT)) {
    delete(ha.items)
    delete(ha.freelist)
    }

    ha_add :: proc(ha: ^HandleArray($T, $HT), v: T) -> HT {
    v := v

    if len(ha.freelist) > 0 {
    h := pop(&ha.freelist)
    h.gen += 1
    v.handle = h
    ha.items[h.idx] = v
    ha.num += 1
    return h
    }

    if len(ha.items) == 0 {
    append_nothing(&ha.items) // Item at index zero is always "dummy" used for zero comparison
    }

    idx := u32(len(ha.items))
    v.handle.idx = idx
    v.handle.gen = 1
    append(&ha.items, v)
    ha.num += 1
    return v.handle
    }

    ha_get :: proc(ha: HandleArray($T, $HT), h: HT) -> (T, bool) {
    if h.idx > 0 && int(h.idx) < len(ha.items) && ha.items[h.idx].handle == h {
    return ha.items[h.idx], true
    }

    return {}, false
    }

    ha_get_ptr :: proc(ha: HandleArray($T, $HT), h: HT) -> ^T {
    if h.idx > 0 && int(h.idx) < len(ha.items) && ha.items[h.idx].handle == h {
    return &ha.items[h.idx]
    }

    return nil
    }

    ha_remove :: proc(ha: ^HandleArray($T, $HT), h: HT) {
    if h.idx > 0 && int(h.idx) < len(ha.items) && ha.items[h.idx].handle == h {
    append(&ha.freelist, h)
    ha.items[h.idx] = {}
    ha.num -= 1
    }
    }

    ha_valid :: proc(ha: HandleArray($T, $HT), h: HT) -> bool {
    return ha_get(ha, h) != nil
    }

    HandleArrayIter :: struct($T: typeid, $HT: typeid) {
    ha: HandleArray(T, HT),
    index: int,
    }

    ha_make_iter :: proc(ha: HandleArray($T, $HT)) -> HandleArrayIter(T, HT) {
    return HandleArrayIter(T, HT) { ha = ha }
    }

    ha_iter :: proc(it: ^HandleArrayIter($T, $HT)) -> (val: T, h: HT, cond: bool) {
    in_range := it.index < len(it.ha.items)

    for in_range {
    cond = it.index > 0 && in_range && it.ha.items[it.index].handle.idx > 0

    if cond {
    val = it.ha.items[it.index]
    h = it.ha.items[it.index].handle
    it.index += 1
    return
    }

    it.index += 1
    in_range = it.index < len(it.ha.items)
    }

    return
    }

    ha_iter_ptr :: proc(it: ^HandleArrayIter($T, $HT)) -> (val: ^T, h: HT, cond: bool) {
    in_range := it.index < len(it.ha.items)

    for in_range {
    cond = it.index > 0 && in_range && it.ha.items[it.index].handle.idx > 0

    if cond {
    val = &it.ha.items[it.index]
    h = it.ha.items[it.index].handle
    it.index += 1
    return
    }

    it.index += 1
    in_range = it.index < len(it.ha.items)
    }

    return
    }