From da7371404e36f104d059c2e66bf4fe7603867ea2 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 29 Sep 2026 06:54:42 -0700 Subject: [PATCH] Improve `bit_array` (#7665) Fix `index: uint` -> `index: int`, examples. Add `create_from_enum` helper. --- core/container/bit_array/bit_array.odin | 140 +++++++++++++++--------- core/container/bit_array/doc.odin | 35 +++--- 2 files changed, 105 insertions(+), 70 deletions(-) diff --git a/core/container/bit_array/bit_array.odin b/core/container/bit_array/bit_array.odin index 7d74d9ba7..8ff569319 100644 --- a/core/container/bit_array/bit_array.odin +++ b/core/container/bit_array/bit_array.odin @@ -3,9 +3,7 @@ package container_dynamic_bit_array import "base:builtin" import "base:intrinsics" -/* - Note that these constants are dependent on the backing being a u64. -*/ +// Note that these constants are dependent on the backing being a u64. @(private="file") INDEX_SHIFT :: 6 @@ -27,27 +25,29 @@ Bit_Array_Iterator :: struct { word_idx: int, bit_idx: uint, } + /* -Wraps a `Bit_Array` into an Iterator +Wraps a `Bit_Array` into an `Bit_Array_Iterator`. Inputs: -- ba: Pointer to the Bit_Array +- ba: Pointer to the `Bit_Array` Returns: -- it: Iterator struct +- it: `Bit_Array_Iterator` */ make_iterator :: proc (ba: ^Bit_Array) -> (it: Bit_Array_Iterator) { return Bit_Array_Iterator { array = ba } } + /* -Returns the next bit, including its set-state. ok=false once exhausted +Returns the next bit, including its set-state. ok=false once exhausted. Inputs: - it: The iterator that holds the state. Returns: - set: `true` if the bit at `index` is set. -- index: The next bit of the Bit_Array referenced by `it`. +- index: The next bit of the `Bit_Array` referenced by `it`. - ok: `true` if the iterator can continue, `false` if the iterator is done */ iterate_by_all :: proc (it: ^Bit_Array_Iterator) -> (set: bool, index: int, ok: bool) { @@ -65,34 +65,37 @@ iterate_by_all :: proc (it: ^Bit_Array_Iterator) -> (set: bool, index: int, ok: return set, index, true } + /* -Returns the next Set Bit, for example if `0b1010`, then the iterator will return index={1, 3} over two calls. +Returns the next *set* bit, for example if `0b1010`, then the iterator will return index={1, 3} over two calls. Inputs: - it: The iterator that holds the state. Returns: -- index: The next *set* bit of the Bit_Array referenced by `it`. +- index: The next *set* bit of the `Bit_Array` referenced by `it`. - ok: `true` if the iterator can continue, `false` if the iterator is done */ iterate_by_set :: proc (it: ^Bit_Array_Iterator) -> (index: int, ok: bool) { - return iterate_internal_(it, true) + return iterate_internal(it, true) } + /* -Returns the next Unset Bit, for example if `0b1010`, then the iterator will return index={0, 2} over two calls. +Returns the next *unset* bit, for example if `0b1010`, then the iterator will return index={0, 2} over two calls. Inputs: - it: The iterator that holds the state. Returns: -- index: The next *unset* bit of the Bit_Array referenced by `it`. +- index: The next *unset* bit of the `Bit_Array` referenced by `it`. - ok: `true` if the iterator can continue, `false` if the iterator is done */ iterate_by_unset:: proc (it: ^Bit_Array_Iterator) -> (index: int, ok: bool) { - return iterate_internal_(it, false) + return iterate_internal(it, false) } + /* -Iterates through set/unset bits +Iterates through set/unset bits. *Private* @@ -101,11 +104,11 @@ Inputs: - ITERATE_SET_BITS: `true` for returning only set bits, false for returning only unset bits Returns: -- index: The next *unset* bit of the Bit_Array referenced by `it`. +- index: The next *unset* bit of the `Bit_Array` referenced by `it`. - ok: `true` if the iterator can continue, `false` if the iterator is done */ @(private="file") -iterate_internal_ :: proc (it: ^Bit_Array_Iterator, $ITERATE_SET_BITS: bool) -> (index: int, ok: bool) { +iterate_internal :: proc (it: ^Bit_Array_Iterator, $ITERATE_SET_BITS: bool) -> (index: int, ok: bool) { word := it.array.bits[it.word_idx] if builtin.len(it.array.bits) > it.word_idx else 0 when ! ITERATE_SET_BITS { word = ~word } @@ -137,19 +140,20 @@ iterate_internal_ :: proc (it: ^Bit_Array_Iterator, $ITERATE_SET_BITS: bool) -> } return index, index < it.array.length + it.array.bias } + /* -Gets the state of a bit in the bit-array +Gets the state of a bit in the `Bit_Array`. Inputs: -- ba: Pointer to the Bit_Array +- ba: Pointer to the `Bit_Array` - index: Which bit in the array Returns: - res: `true` if the bit at `index` is set. - ok: Whether the index was valid. Returns `false` if the index is smaller than the bias. */ -get :: proc(ba: ^Bit_Array, #any_int index: uint) -> (res: bool, ok: bool) #optional_ok { - idx := int(index) - ba.bias +get :: proc(ba: ^Bit_Array, #any_int index: int) -> (res: bool, ok: bool) #optional_ok { + idx := index - ba.bias if ba == nil || int(index) < ba.bias { return false, false } @@ -167,28 +171,30 @@ get :: proc(ba: ^Bit_Array, #any_int index: uint) -> (res: bool, ok: bool) #opti return res, true } + /* -Gets the state of a bit in the bit-array +Gets the state of a bit in the `Bit_Array`. *Bypasses all Checks* Inputs: -- ba: Pointer to the Bit_Array +- ba: Pointer to the `Bit_Array` - index: Which bit in the array Returns: - `true` if bit is set */ -unsafe_get :: #force_inline proc(ba: ^Bit_Array, #any_int index: uint) -> bool #no_bounds_check { - return bool((ba.bits[index >> INDEX_SHIFT] >> uint(index & INDEX_MASK)) & 1) +unsafe_get :: #force_inline proc(ba: ^Bit_Array, #any_int index: int) -> bool #no_bounds_check { + return bool((ba.bits[index >> INDEX_SHIFT] >> (uint(index) & INDEX_MASK)) & 1) } + /* -Sets the state of a bit in the bit-array +Sets the state of a bit in the `Bit_Array`. *Conditionally Allocates (Resizes backing data when `index > len(ba.bits)`)* Inputs: -- ba: Pointer to the Bit_Array +- ba: Pointer to the `Bit_Array` - index: Which bit in the array - set_to: `true` sets the bit on, `false` to turn it off - allocator: (default is context.allocator) @@ -196,9 +202,9 @@ Inputs: Returns: - ok: Whether the set was successful, `false` on allocation failure or bad index */ -set :: proc(ba: ^Bit_Array, #any_int index: uint, set_to: bool = true, allocator := context.allocator) -> (ok: bool) { +set :: proc(ba: ^Bit_Array, #any_int index: int, set_to: bool = true, allocator := context.allocator) -> (ok: bool) { - idx := int(index) - ba.bias + idx := index - ba.bias if ba == nil || int(index) < ba.bias { return false } context.allocator = allocator @@ -218,41 +224,44 @@ set :: proc(ba: ^Bit_Array, #any_int index: uint, set_to: bool = true, allocator return true } + /* -Sets the state of a bit in the bit-array +Sets the state of a bit in the `Bit_Array`. *Bypasses all checks* Inputs: -- ba: Pointer to the Bit_Array +- ba: Pointer to the `Bit_Array` - index: Which bit in the array */ unsafe_set :: proc(ba: ^Bit_Array, bit: int) #no_bounds_check { ba.bits[bit >> INDEX_SHIFT] |= 1 << uint(bit & INDEX_MASK) } + /* -Unsets the state of a bit in the bit-array. (Convienence wrapper for `set`) +Unsets the state of a bit in the `Bit_Array`. (Convienence wrapper for `set`) *Conditionally Allocates (Resizes backing data when `index > len(ba.bits)`)* Inputs: -- ba: Pointer to the Bit_Array +- ba: Pointer to the `Bit_Array` - index: Which bit in the array - allocator: (default is context.allocator) Returns: - ok: Whether the unset was successful, `false` on allocation failure or bad index */ -unset :: #force_inline proc(ba: ^Bit_Array, #any_int index: uint, allocator := context.allocator) -> (ok: bool) { +unset :: #force_inline proc(ba: ^Bit_Array, #any_int index: int, allocator := context.allocator) -> (ok: bool) { return set(ba, index, false, allocator) } -/* -Unsets the state of a bit in the bit-array -*Bypasses all Checks* +/* +Unsets the state of a bit in the `Bit_Array`. + +*Bypasses all checks* Inputs: -- ba: Pointer to the Bit_Array +- ba: Pointer to the `Bit_Array` - index: Which bit in the array */ unsafe_unset :: proc(b: ^Bit_Array, bit: int) #no_bounds_check { @@ -260,7 +269,7 @@ unsafe_unset :: proc(b: ^Bit_Array, bit: int) #no_bounds_check { } /* -A helper function to create a Bit Array with optional bias, in case your smallest index is non-zero (including negative). +A helper function to create a `Bit_Array` with optional bias, in case your smallest index is non-zero (including negative). The range of bits created by this procedure is `min_index.. (res: ^Bit_Array, ok: bool) #optional_ok { +create :: proc(max_index: int, min_index: int = 0, allocator := context.allocator, loc := #caller_location) -> (res: ^Bit_Array, ok: bool) #optional_ok { size_in_bits := max_index - min_index + assert(max_index >= min_index, loc=loc) if size_in_bits < 0 { return {}, false } @@ -290,7 +300,27 @@ create :: proc(max_index: int, min_index: int = 0, allocator := context.allocato } /* -A helper function to initialize a Bit Array with optional bias, in case your smallest index is non-zero (including negative). +A helper function to create a `Bit_Array` from an enum `E`. + +The range of bits created by this procedure is `min(E).. (res: ^Bit_Array, ok: bool) where intrinsics.type_is_enum(T) #optional_ok { + return create(int(max(T)), int(min(T)), allocator) +} + +/* +A helper function to initialize a `Bit_Array` with optional bias, in case your smallest index is non-zero (including negative). The range of bits created by this procedure is `min_index.. (length: int) { if ba == nil { return } return ba.length } + /* -Shrinks the Bit_Array's backing storage to the smallest possible size. +Shrinks the `Bit_Array`'s backing storage to the smallest possible size. Inputs: -- ba: The target Bit_Array +- ba: The target `Bit_Array` */ shrink :: proc(ba: ^Bit_Array) #no_bounds_check { if ba == nil { return } @@ -372,22 +404,24 @@ shrink :: proc(ba: ^Bit_Array) #no_bounds_check { resize(&ba.bits, legs_needed) builtin.shrink(&ba.bits) } + /* -Deallocates the Bit_Array and its backing storage +Deallocates the `Bit_Array` and its backing storage Inputs: -- ba: The target Bit_Array +- ba: The target `Bit_Array` */ destroy :: proc(ba: ^Bit_Array) { if ba == nil { return } delete(ba.bits) - if ba.free_pointer { // Only free if this Bit_Array was created using `create`, not when on the stack. + if ba.free_pointer { // Only free if this `Bit_Array` was created using `create`, not when on the stack. free(ba) } } + /* - Resizes the Bit Array. For internal use. Provisions needed capacity+1 - If you want to reserve the memory for a given-sized Bit Array up front, you can use `create`. + Resizes the `Bit_Array`. For internal use. Provisions needed capacity+1 + If you want to reserve the memory for a given-sized `Bit_Array` up front, you can use `create`. */ @(private="file") resize_if_needed :: proc(ba: ^Bit_Array, legs: int, allocator := context.allocator) -> (ok: bool) { diff --git a/core/container/bit_array/doc.odin b/core/container/bit_array/doc.odin index e86059ecd..de7ba22a4 100644 --- a/core/container/bit_array/doc.odin +++ b/core/container/bit_array/doc.odin @@ -8,20 +8,18 @@ Example: package test import "core:fmt" - import "core:container/bit_array" + import ba "core:container/bit_array" main :: proc() { - using bit_array - - bits: Bit_Array + bits: ba.Bit_Array // returns `true` - fmt.println(set(&bits, 42)) + fmt.println(ba.set(&bits, 42)) // returns `false`, `false`, because this Bit Array wasn't created to allow negative indices. - was_set, was_retrieved := get(&bits, -1) + was_set, was_retrieved := ba.get(&bits, -1) fmt.println(was_set, was_retrieved) - destroy(&bits) + ba.destroy(&bits) } A `Bit_Array` can optionally allow for negative indices, if the minimum value was given during creation. @@ -29,7 +27,7 @@ Example: package test import "core:fmt" - import "core:container/bit_array" + import ba "core:container/bit_array" main :: proc() { Foo :: enum int { @@ -38,17 +36,20 @@ Example: Leaves = 69105, } - using bit_array + bits := ba.create_from_enum(Foo) + defer ba.destroy(bits) - bits := create(int(max(Foo)), int(min(Foo))) - defer destroy(bits) + assert(bits.bias == int(Foo.Negative_Test)) + assert(bits.length == abs(int(min(Foo))) + int(max(Foo))) - fmt.printf("Set(Bar): %v\n", set(bits, Foo.Bar)) - fmt.printf("Get(Bar): %v, %v\n", get(bits, Foo.Bar)) - fmt.printf("Set(Negative_Test): %v\n", set(bits, Foo.Negative_Test)) - fmt.printf("Get(Leaves): %v, %v\n", get(bits, Foo.Leaves)) - fmt.printf("Get(Negative_Test): %v, %v\n", get(bits, Foo.Negative_Test)) - fmt.printf("Freed.\n") + fmt.printfln("Set(Bar): %v", ba.set(bits, Foo.Bar)) + fmt.printfln("Get(Bar): %v", ba.get(bits, Foo.Bar)) + fmt.printfln("Set(Negative_Test): %v", ba.set(bits, Foo.Negative_Test)) + fmt.printfln("Get(Leaves): %v", ba.get(bits, Foo.Leaves)) + fmt.printfln("Get(Leaves): %v", ba.unsafe_get(bits, Foo.Leaves)) + fmt.printfln("Get(Negative_Test): %v", ba.get(bits, Foo.Negative_Test)) + fmt.printfln("Unset(Negative_Test): %v", ba.unset(bits, Foo.Negative_Test)) + assert(ba.get(bits, Foo.Negative_Test) == false) } */ package container_dynamic_bit_array \ No newline at end of file