123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- package container_dynamic_bit_array
- import "base:intrinsics"
- import "core:mem"
- /*
- Note that these constants are dependent on the backing being a u64.
- */
- @(private="file")
- INDEX_SHIFT :: 6
- @(private="file")
- INDEX_MASK :: 63
- @(private="file")
- NUM_BITS :: 64
- Bit_Array :: struct {
- bits: [dynamic]u64,
- bias: int,
- max_index: int,
- free_pointer: bool,
- }
- Bit_Array_Iterator :: struct {
- array: ^Bit_Array,
- word_idx: int,
- bit_idx: uint,
- }
- /*
- Wraps a `Bit_Array` into an Iterator
- Inputs:
- - ba: Pointer to the Bit_Array
- Returns:
- - it: Iterator struct
- */
- 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
- 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`.
- - 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) {
- index = it.word_idx * NUM_BITS + int(it.bit_idx) + it.array.bias
- if index > it.array.max_index { return false, 0, false }
- word := it.array.bits[it.word_idx] if len(it.array.bits) > it.word_idx else 0
- set = (word >> it.bit_idx & 1) == 1
- it.bit_idx += 1
- if it.bit_idx >= NUM_BITS {
- it.bit_idx = 0
- it.word_idx += 1
- }
- return set, index, true
- }
- /*
- 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`.
- - 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)
- }
- /*
- 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`.
- - 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)
- }
- /*
- Iterates through set/unset bits
- *Private*
- Inputs:
- - it: The iterator that holds the state.
- - 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`.
- - 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) {
- word := it.array.bits[it.word_idx] if len(it.array.bits) > it.word_idx else 0
- when ! ITERATE_SET_BITS { word = ~word }
- // If the word is empty or we have already gone over all the bits in it,
- // b.bit_idx is greater than the index of any set bit in the word,
- // meaning that word >> b.bit_idx == 0.
- for it.word_idx < len(it.array.bits) && word >> it.bit_idx == 0 {
- it.word_idx += 1
- it.bit_idx = 0
- word = it.array.bits[it.word_idx] if len(it.array.bits) > it.word_idx else 0
- when ! ITERATE_SET_BITS { word = ~word }
- }
- // If we are iterating the set bits, reaching the end of the array means we have no more bits to check
- when ITERATE_SET_BITS {
- if it.word_idx >= len(it.array.bits) {
- return 0, false
- }
- }
- // Reaching here means that the word has some set bits
- it.bit_idx += uint(intrinsics.count_trailing_zeros(word >> it.bit_idx))
- index = it.word_idx * NUM_BITS + int(it.bit_idx) + it.array.bias
- it.bit_idx += 1
- if it.bit_idx >= NUM_BITS {
- it.bit_idx = 0
- it.word_idx += 1
- }
- return index, index <= it.array.max_index
- }
- /*
- Gets the state of a bit in the bit-array
- Inputs:
- - 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
- if ba == nil || int(index) < ba.bias { return false, false }
- leg_index := idx >> INDEX_SHIFT
- bit_index := idx & INDEX_MASK
- /*
- If we `get` a bit that doesn't fit in the Bit Array, it's naturally `false`.
- This early-out prevents unnecessary resizing.
- */
- if leg_index + 1 > len(ba.bits) { return false, true }
- val := u64(1 << uint(bit_index))
- res = ba.bits[leg_index] & val == val
- return res, true
- }
- /*
- Gets the state of a bit in the bit-array
- *Bypasses all Checks*
- Inputs:
- - 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)
- }
- /*
- 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
- - index: Which bit in the array
- - set_to: `true` sets the bit on, `false` to turn it off
- - allocator: (default is context.allocator)
- 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) {
- idx := int(index) - ba.bias
- if ba == nil || int(index) < ba.bias { return false }
- context.allocator = allocator
- leg_index := idx >> INDEX_SHIFT
- bit_index := idx & INDEX_MASK
- resize_if_needed(ba, leg_index) or_return
- ba.max_index = max(idx, ba.max_index)
- if set_to {
- ba.bits[leg_index] |= 1 << uint(bit_index)
- } else {
- ba.bits[leg_index] &~= 1 << uint(bit_index)
- }
- return true
- }
- /*
- Sets the state of a bit in the bit-array
- *Bypasses all checks*
- Inputs:
- - 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`)
- *Conditionally Allocates (Resizes backing data when `index > len(ba.bits)`)*
- Inputs:
- - 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) {
- return set(ba, index, false, allocator)
- }
- /*
- Unsets the state of a bit in the bit-array
- *Bypasses all Checks*
- Inputs:
- - ba: Pointer to the Bit_Array
- - index: Which bit in the array
- */
- unsafe_unset :: proc(b: ^Bit_Array, bit: int) #no_bounds_check {
- b.bits[bit >> INDEX_SHIFT] &~= 1 << uint(bit & INDEX_MASK)
- }
- /*
- A helper function to create a Bit Array with optional bias, in case your smallest index is non-zero (including negative).
- *Allocates (`new(Bit_Array) & make(ba.bits)`)*
- Inputs:
- - max_index: maximum starting index
- - min_index: minimum starting index (used as a bias)
- - allocator: (default is context.allocator)
- Returns:
- - ba: Allocates a bit_Array, backing data is set to `max-min / 64` indices, rounded up (eg 65 - 0 allocates for [2]u64).
- */
- create :: proc(max_index: int, min_index: int = 0, allocator := context.allocator) -> (res: ^Bit_Array, ok: bool) #optional_ok {
- context.allocator = allocator
- size_in_bits := max_index - min_index
- if size_in_bits < 1 { return {}, false }
- legs := size_in_bits >> INDEX_SHIFT
- if size_in_bits & INDEX_MASK > 0 {legs+=1}
- bits, err := make([dynamic]u64, legs)
- ok = err == mem.Allocator_Error.None
- res = new(Bit_Array)
- res.bits = bits
- res.bias = min_index
- res.max_index = max_index
- res.free_pointer = true
- return
- }
- /*
- Sets all values in the Bit_Array to zero.
- Inputs:
- - ba: The target Bit_Array
- */
- clear :: proc(ba: ^Bit_Array) {
- if ba == nil { return }
- mem.zero_slice(ba.bits[:])
- }
- /*
- Deallocates the Bit_Array and its backing storage
- Inputs:
- - 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.
- 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`.
- */
- @(private="file")
- resize_if_needed :: proc(ba: ^Bit_Array, legs: int, allocator := context.allocator) -> (ok: bool) {
- if ba == nil { return false }
- context.allocator = allocator
- if legs + 1 > len(ba.bits) {
- resize(&ba.bits, legs + 1)
- }
- return len(ba.bits) > legs
- }
|