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 }