handle_map.odin 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. /* Handle-based map using fixed arrays. By Karl Zylinski (karl@zylinski.se)
  2. The Handle_Map maps a handle to an item. A handle consists of an index and a
  3. generation. The item can be any type. Such a handle can be stored as a permanent
  4. reference, where you'd usually store a pointer. The benefit of handles is that
  5. you know if some other system has destroyed the object at that index, since the
  6. generation will then differ.
  7. This implementation uses fixed arrays and therefore
  8. involves no dynamic memory allocations.
  9. Example (assumes this package is imported under the alias `hm`):
  10. Entity_Handle :: hm.Handle
  11. Entity :: struct {
  12. // All items must contain a handle
  13. handle: Entity_Handle,
  14. pos: [2]f32,
  15. }
  16. // Note: We use `1024`, if you use a bigger number within a proc you may
  17. // blow the stack. In those cases: Store the array inside a global variable
  18. // or a dynamically allocated struct.
  19. entities: hm.Handle_Map(Entity, Entity_Handle, 1024)
  20. h1 := hm.add(&entities, Entity { pos = { 5, 7 } })
  21. h2 := hm.add(&entities, Entity { pos = { 10, 5 } })
  22. // Resolve handle -> pointer
  23. if h2e := hm.get(&entities, h2); h2e != nil {
  24. h2e.pos.y = 123
  25. }
  26. // Will remove this entity, leaving an unused slot
  27. hm.remove(&entities, h1)
  28. // Will reuse the slot h1 used
  29. h3 := hm.add(&entities, Entity { pos = { 1, 2 } })
  30. // Iterate. You can also use `for e in hm.items {}` and skip any item where
  31. // `e.handle.idx == 0`. The iterator does that automatically. There's also
  32. // `skip` procedure in this package that check `e.handle.idx == 0` for you.
  33. ent_iter := hm.make_iter(&entities)
  34. for e, h in hm.iter(&ent_iter) {
  35. e.pos += { 5, 1 }
  36. }
  37. */
  38. package handle_map_fixed
  39. import "base:intrinsics"
  40. // Returned from the `add` proc. Store these as permanent references to items in
  41. // the handle map. You can resolve the handle to a pointer using the `get` proc.
  42. Handle :: struct {
  43. // index into `items` array of the `Handle_Map` struct.
  44. idx: u32,
  45. // When using the `get` proc, this will be matched to the `gen` on the item
  46. // in the handle map. The handle is only valid if they match. If they don't
  47. // match, then it means that the slot in the handle map has been reused.
  48. gen: u32,
  49. }
  50. Handle_Map :: struct($T: typeid, $HT: typeid, $N: int) {
  51. // Each item must have a field `handle` of type `HT`.
  52. //
  53. // There's always a "dummy element" at index 0. This way, a Handle with
  54. // `idx == 0` means "no Handle". This means that you actually have `N - 1`
  55. // items available.
  56. items: [N]T,
  57. // How much of `items` that is in use.
  58. num_items: u32,
  59. // The index of the first unused element in `items`. At this index in
  60. // `unused_items` you'll find the next-next unused index. Used by `add` and
  61. // originally set by `remove`.
  62. next_unused: u32,
  63. // An element in this array that is non-zero means the thing at that index
  64. // in `items` is unused. The non-zero number is the index of the next unused
  65. // item. This forms a linked series of indices. The series starts with
  66. // `next_unused`.
  67. unused_items: [N]u32,
  68. // Only used for making it possible to quickly calculate the number of valid
  69. // elements.
  70. num_unused: u32,
  71. }
  72. // Clears the handle map using `mem_zero`. It doesn't do `m^ = {}` because that
  73. // may blow the stack for a handle map with very big `N`
  74. clear :: proc(m: ^Handle_Map($T, $HT, $N)) {
  75. intrinsics.mem_zero(m, size_of(m^))
  76. }
  77. // Add a value of type `T` to the handle map. Returns a handle you can use as a
  78. // permanent reference.
  79. //
  80. // Will reuse the item at `next_unused` if that value is non-zero.
  81. //
  82. // Second return value is `false` if the handle-based map is full.
  83. add :: proc(m: ^Handle_Map($T, $HT, $N), v: T) -> (HT, bool) #optional_ok {
  84. v := v
  85. if m.next_unused != 0 {
  86. idx := m.next_unused
  87. item := &m.items[idx]
  88. m.next_unused = m.unused_items[idx]
  89. m.unused_items[idx] = 0
  90. gen := item.handle.gen
  91. item^ = v
  92. item.handle.idx = u32(idx)
  93. item.handle.gen = gen + 1
  94. m.num_unused -= 1
  95. return item.handle, true
  96. }
  97. // We always have a "dummy item" at index zero. This is because handle.idx
  98. // being zero means "no item", so we can't use that slot for anything.
  99. if m.num_items == 0 {
  100. m.items[0] = {}
  101. m.num_items += 1
  102. }
  103. if m.num_items == len(m.items) {
  104. return {}, false
  105. }
  106. item := &m.items[m.num_items]
  107. item^ = v
  108. item.handle.idx = u32(m.num_items)
  109. item.handle.gen = 1
  110. m.num_items += 1
  111. return item.handle, true
  112. }
  113. // Resolve a handle to a pointer of type `^T`. The pointer is stable since the
  114. // handle map uses a fixed array. But you should _not_ store the pointer
  115. // permanently. The item may get reused if any part of your program destroys and
  116. // reuses that slot. Only store handles permanently and temporarily resolve them
  117. // into pointers as needed.
  118. get :: proc(m: ^Handle_Map($T, $HT, $N), h: HT) -> ^T {
  119. if h.idx <= 0 || h.idx >= m.num_items {
  120. return nil
  121. }
  122. if item := &m.items[h.idx]; item.handle == h {
  123. return item
  124. }
  125. return nil
  126. }
  127. // Remove an item from the handle map. You choose which item by passing a handle
  128. // to this proc. The item is not really destroyed, rather its index is just
  129. // set on `m.next_unused`. Also, the item's `handle.idx` is set to zero, this
  130. // is used by the `iter` proc in order to skip that item when iterating.
  131. remove :: proc(m: ^Handle_Map($T, $HT, $N), h: HT) {
  132. if h.idx <= 0 || h.idx >= m.num_items {
  133. return
  134. }
  135. if item := &m.items[h.idx]; item.handle == h {
  136. m.unused_items[h.idx] = m.next_unused
  137. m.next_unused = h.idx
  138. m.num_unused += 1
  139. item.handle.idx = 0
  140. }
  141. }
  142. // Tells you if a handle maps to a valid item. This is done by checking if the
  143. // handle on the item is the same as the passed handle.
  144. valid :: proc(m: Handle_Map($T, $HT, $N), h: HT) -> bool {
  145. return h.idx > 0 && h.idx < m.num_items && m.items[h.idx].handle == h
  146. }
  147. // Tells you how many valid items there are in the handle map.
  148. num_used :: proc(m: Handle_Map($T, $HT, $N)) -> int {
  149. return int(m.num_items - m.num_unused)
  150. }
  151. // The maximum number of items the handle map can contain.
  152. cap :: proc(m: Handle_Map($T, $HT, $N)) -> int {
  153. return N
  154. }
  155. // For iterating a handle map. Create using `make_iter`.
  156. Handle_Map_Iterator :: struct($T: typeid, $HT: typeid, $N: int) {
  157. m: ^Handle_Map(T, HT, N),
  158. index: u32,
  159. }
  160. // Create an iterator. Use with `iter` to do the actual iteration.
  161. make_iter :: proc(m: ^Handle_Map($T, $HT, $N)) -> Handle_Map_Iterator(T, HT, N) {
  162. return { m = m, index = 1 }
  163. }
  164. // Iterate over the handle map. Skips unused slots, meaning that it skips slots
  165. // with handle.idx == 0.
  166. //
  167. // Usage:
  168. // my_iter := hm.make_iter(&my_handle_map)
  169. // for e in hm.iter(&my_iter) {}
  170. //
  171. // Instead of using an iterator you can also loop over `items` and check if
  172. // `item.handle.idx == 0` and in that case skip that item.
  173. iter :: proc(it: ^Handle_Map_Iterator($T, $HT, $N)) -> (val: ^T, h: HT, cond: bool) {
  174. for _ in it.index..<it.m.num_items {
  175. item := &it.m.items[it.index]
  176. it.index += 1
  177. if item.handle.idx != 0 {
  178. return item, item.handle, true
  179. }
  180. }
  181. return nil, {}, false
  182. }
  183. // If you don't want to use iterator, you can instead do:
  184. // for &item in my_map.items {
  185. // if hm.skip(item) {
  186. // continue
  187. // }
  188. // // do stuff
  189. // }
  190. skip :: proc(e: $T) -> bool {
  191. return e.handle.idx == 0
  192. }