diff --git a/builder/builder.odin b/builder/builder.odin index 4ab9e89..a7a1a18 100644 --- a/builder/builder.odin +++ b/builder/builder.odin @@ -296,6 +296,7 @@ main :: proc() { "odin", "build", "game", + "-use-separate-modules", "-define:NAME_STATIC_INIT=false", "-define:RAYLIB_SHARED=true", "-define:PHYSFS_SHARED=true", diff --git a/common/container/xarr/xarr.odin b/common/container/xarr/xarr.odin index 84e25ff..d379370 100644 --- a/common/container/xarr/xarr.odin +++ b/common/container/xarr/xarr.odin @@ -6,10 +6,11 @@ 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) { +Xarr :: struct($T: typeid, $SOA := false) { + chunks: ([NUM_CHUNKS]#soa[]T when SOA else [NUM_CHUNKS][^]T), len: int, - chunks: [30][^]T, allocated_chunks_mask: u32, } @@ -27,10 +28,25 @@ chunk_size :: #force_inline proc "contextless" (chunk_idx: i8) -> uint { return BASE_CHUNK_SIZE << u32(max(chunk_idx - 1, 0)) } -get_chunk_slice :: #force_inline proc "contextless" (a: $T/Xarr($E), chunk_idx: i8) -> []E { +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 + @@ -38,31 +54,36 @@ capacity_from_allocated_mask :: #force_inline proc(allocated_mask: uint) -> uint ) } -capacity :: #force_inline proc(a: $T/Xarr($E)) -> u32 { +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), cap: int, allocator := context.allocator) { +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 { - chunk_slice := make([]E, chunk_size(i), allocator) - a.chunks[i] = raw_data(chunk_slice) + 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), elems: ..E, allocator := context.allocator) { +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) + set_elems_assume_allocated(a, elems) a.len += len(elems) } @@ -81,7 +102,7 @@ translate_index :: #force_inline proc( } @(private = "file") -set_elems_assume_allocated :: proc(a: $T/Xarr($E), elems: []E) { +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) @@ -91,55 +112,55 @@ set_elems_assume_allocated :: proc(a: $T/Xarr($E), elems: []E) { } } -set :: proc(a: $T/Xarr($E), #any_int idx: int, val: 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), #any_int idx: int) -> E { +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), #any_int idx: int) -> ^E { +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), #any_int idx: int) { +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)) { +clear :: proc "contextless" (a: $T/^Xarr($E, $SOA)) { a.len = 0 } -delete :: proc(a: $T/^Xarr($E), allocator := context.allocator) { +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){} + a^ = Xarr(E, SOA){} } -Iterator :: struct($E: typeid) { - xarr: ^Xarr(E), +Iterator :: struct($E: typeid, $SOA: bool) { + xarr: ^Xarr(E, SOA), idx: int, } -iterator :: proc(a: $T/^Xarr($E), start_idx := 0) -> Iterator(E) { - return Iterator(E){xarr = a, idx = start_idx} +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)) -> (e: ^E, idx: int, ok: bool) { +iterator_next :: proc(it: ^Iterator($E, $SOA)) -> (e: ^E, idx: int, ok: bool) { if it.idx >= it.xarr.len { return nil, it.idx, false } diff --git a/common/container/xarr/xarr_test.odin b/common/container/xarr/xarr_test.odin index b3bbc74..af618c8 100644 --- a/common/container/xarr/xarr_test.odin +++ b/common/container/xarr/xarr_test.odin @@ -120,3 +120,18 @@ test_iterator :: proc(t: ^testing.T) { testing.expect_value(t, e^, i) } } + +@(test) +test_soa :: proc(t: ^testing.T) { + My_Struct :: struct { + x, y, z: f32, + } + a: Xarr(My_Struct, true) + defer delete(&a) + + append(&a, My_Struct{x = 1, y = 2, z = 3}) + + + testing.expect_value(t, get(a, 0), My_Struct{x = 1, y = 2, z = 3}) + testing.expect_value(t, size_of(Xarr(My_Struct, false)), 0) +}