2025-08-10 03:15:22 +04:00

230 lines
5.3 KiB
Odin

package xarr
import "base:builtin"
import "base:intrinsics"
BASE_CHUNK_SIZE :: uint(64)
BASE_CHUNK_SIZE_LOG2 :: intrinsics.constant_log2(BASE_CHUNK_SIZE)
BASE_CHUNK_SHIFT :: BASE_CHUNK_SIZE_LOG2 - 1
NUM_CHUNKS :: 30
Xarr :: struct($T: typeid, $SOA := false) {
chunks: ([NUM_CHUNKS]#soa[]T when SOA else [NUM_CHUNKS][^]T),
len: int,
allocated_chunks_mask: u32,
}
UINT_BITS :: size_of(uint) * 8
msb :: #force_inline proc "contextless" (#any_int idx: uint) -> i8 {
return i8(UINT_BITS - intrinsics.count_leading_zeros(idx)) - 1
}
chunk_by_index :: #force_inline proc "contextless" (#any_int idx: uint) -> (chunk: i8) {
return max(msb(idx) - BASE_CHUNK_SHIFT, 0)
}
chunk_size :: #force_inline proc "contextless" (chunk_idx: i8) -> uint {
return BASE_CHUNK_SIZE << u32(max(chunk_idx - 1, 0))
}
get_chunk_slice_scalar :: #force_inline proc "contextless" (
a: $T/Xarr($E, false),
chunk_idx: i8,
) -> []E {
return a.chunks[chunk_idx][:chunk_size(chunk_idx)]
}
get_chunk_slice_soa :: #force_inline proc "contextless" (
a: $T/Xarr($E, true),
chunk_idx: i8,
) -> #soa[]E {
return a.chunks[chunk_idx]
}
get_chunk_slice :: proc {
get_chunk_slice_scalar,
get_chunk_slice_soa,
}
capacity_from_allocated_mask :: #force_inline proc(allocated_mask: uint) -> uint {
return(
(allocated_mask >> 1) << BASE_CHUNK_SIZE_LOG2 +
(allocated_mask & 1) << BASE_CHUNK_SIZE_LOG2 \
)
}
capacity :: #force_inline proc(a: $T/Xarr($E, $SOA)) -> u32 {
allocated_mask := a.allocated_chunks_mask
return capacity_from_allocated_mask(allocated_mask)
}
reserve :: proc(a: $T/^Xarr($E, $SOA), cap: int, allocator := context.allocator) {
allocated_mask := a.allocated_chunks_mask
current_chunk := msb(allocated_mask)
required_chunks := chunk_by_index(max(cap - 1, 0)) + 1
for i := current_chunk + 1; i < required_chunks; i += 1 {
when SOA {
chunk_slice := make_soa_slice(#soa[]E, chunk_size(i), allocator)
a.chunks[i] = chunk_slice
} else {
chunk_slice := make([]E, chunk_size(i), allocator)
a.chunks[i] = raw_data(chunk_slice)
}
a.allocated_chunks_mask |= u32(1) << u8(i)
}
}
append :: proc(a: $T/^Xarr($E, $SOA), elems: ..E, allocator := context.allocator) {
if len(elems) == 0 {
return
}
reserve(a, a.len + len(elems))
set_elems_assume_allocated(a, elems)
a.len += len(elems)
}
translate_index :: #force_inline proc(
#any_int idx: int,
) -> (
chunk_idx: i8,
idx_within_chunk: uint,
) {
assert(idx >= 0)
chunk_idx = chunk_by_index(idx)
idx_within_chunk = uint(idx) & (chunk_size(chunk_idx) - 1)
return
}
@(private = "file")
set_elems_assume_allocated :: proc(a: $T/^Xarr($E, $SOA), elems: []E) {
for &e, i in elems {
idx := a.len + i
chunk_idx, idx_within_chunk := translate_index(idx)
assert(a.chunks[chunk_idx] != nil)
a.chunks[chunk_idx][idx_within_chunk] = e
}
}
set :: proc(a: $T/Xarr($E, $SOA), #any_int idx: int, val: E) {
assert(idx >= 0 && idx < a.len)
chunk_idx, idx_within_chunk := translate_index(idx)
return get_chunk_slice(a, chunk_idx)[idx_within_chunk]
}
get :: proc(a: $T/Xarr($E, $SOA), #any_int idx: int) -> E {
assert(idx >= 0 && idx < a.len)
chunk_idx, idx_within_chunk := translate_index(idx)
return get_chunk_slice(a, chunk_idx)[idx_within_chunk]
}
get_ptr :: proc(a: $T/Xarr($E, $SOA), #any_int idx: int) -> ^E {
assert(idx >= 0 && idx < a.len)
chunk_idx, idx_within_chunk := translate_index(idx)
return &get_chunk_slice(a, chunk_idx)[idx_within_chunk]
}
unordered_remove :: proc(a: $T/^Xarr($E, $SOA), #any_int idx: int) {
assert(idx >= 0 && idx < a.len)
get_ptr(a^, idx)^ = get(a^, a.len - 1)
a.len -= 1
}
clear :: proc "contextless" (a: $T/^Xarr($E, $SOA)) {
a.len = 0
}
delete :: proc(a: $T/^Xarr($E, $SOA), allocator := context.allocator) {
for i in 0 ..< len(a.chunks) {
builtin.delete(get_chunk_slice(a^, i8(i)), allocator)
}
a^ = Xarr(E, SOA){}
}
Iterator :: struct($E: typeid, $SOA: bool) {
xarr: ^Xarr(E, SOA),
idx: int,
}
iterator :: proc(a: $T/^Xarr($E, $SOA), start_idx := 0) -> Iterator(E, SOA) {
return Iterator(E, SOA){xarr = a, idx = start_idx}
}
iterator_next :: proc(it: ^Iterator($E, $SOA)) -> (e: ^E, idx: int, ok: bool) {
if it.idx >= it.xarr.len {
return nil, it.idx, false
}
e = get_ptr(it.xarr^, it.idx)
idx = it.idx
ok = true
it.idx += 1
return
}
Chunk_Iterator :: struct($E: typeid, $SOA: bool) {
xarr: ^Xarr(E, SOA),
base_element_idx: int,
chunk_idx: i8,
}
chunk_iterator :: proc(a: $T/^Xarr($E, $SOA)) -> Chunk_Iterator(E, SOA) {
return Chunk_Iterator(E, SOA){xarr = a}
}
chunk_iterator_next_scalar :: proc(
it: ^Chunk_Iterator($E, false),
) -> (
chunk: []E,
base_element_idx: int,
ok: bool,
) {
if (it.xarr.allocated_chunks_mask & (u32(1) << it.idx)) == 0 {
return nil, 0, false
}
chunk = get_chunk_slice_scalar(it.xarr, it.idx)
base_element_idx = it.base_element_idx
ok = true
base_element_idx += chunk_size(it.chunk_idx)
it.chunk_idx += 1
return
}
chunk_iterator_next_soa :: proc(
it: ^Chunk_Iterator($E, true),
) -> (
chunk: #soa[]E,
base_element_idx: int,
ok: bool,
) {
if (it.xarr.allocated_chunks_mask & (u32(1) << it.idx)) == 0 {
return nil, 0, false
}
chunk = get_chunk_slice_soa(it.xarr, it.idx)
base_element_idx = it.base_element_idx
ok = true
base_element_idx += chunk_size(it.chunk_idx)
it.chunk_idx += 1
return
}
chunk_iterator_next :: proc {
chunk_iterator_next_scalar,
chunk_iterator_next_soa,
}