From 6a9788406776387f9458b767de862d6b27a93dea Mon Sep 17 00:00:00 2001 From: Zachary Levy Date: Fri, 24 Apr 2026 13:20:50 -0700 Subject: [PATCH 1/4] init / create / destroy naming consistency --- many_bits/many_bits.odin | 62 +++++++++------- ring/ring.odin | 154 ++++++++++++++++++++++++--------------- 2 files changed, 131 insertions(+), 85 deletions(-) diff --git a/many_bits/many_bits.odin b/many_bits/many_bits.odin index 659cb03..802279a 100644 --- a/many_bits/many_bits.odin +++ b/many_bits/many_bits.odin @@ -2,6 +2,7 @@ package many_bits import "base:builtin" import "base:intrinsics" +import "base:runtime" import "core:fmt" import "core:slice" @@ -25,15 +26,20 @@ Bits :: struct { length: int, // Total number of bits being stored } -delete :: proc(bits: Bits, allocator := context.allocator) { - delete_slice(bits.int_array, allocator) +destroy :: proc(bits: Bits, allocator := context.allocator) -> runtime.Allocator_Error { + return delete_slice(bits.int_array, allocator) } -make :: proc(#any_int length: int, allocator := context.allocator) -> Bits { - return Bits { - int_array = make_slice([]Int_Bits, ((length - 1) >> INDEX_SHIFT) + 1, allocator), - length = length, - } +create :: proc( + #any_int length: int, + allocator := context.allocator, +) -> ( + bits: Bits, + err: runtime.Allocator_Error, +) #optional_allocator_error { + bits.int_array, err = make_slice([]Int_Bits, ((length - 1) >> INDEX_SHIFT) + 1, allocator) + bits.length = length + return bits, err } // Sets all bits to 0 (false) @@ -507,8 +513,8 @@ import "core:testing" @(test) test_set :: proc(t: ^testing.T) { - bits := make(128) - defer delete(bits) + bits := create(128) + defer destroy(bits) set(bits, 0, true) testing.expect_value(t, bits.int_array[0], Int_Bits{0}) @@ -524,8 +530,8 @@ test_set :: proc(t: ^testing.T) { @(test) test_get :: proc(t: ^testing.T) { - bits := make(128) - defer delete(bits) + bits := create(128) + defer destroy(bits) // Default is false testing.expect(t, !get(bits, 0)) @@ -560,8 +566,8 @@ test_get :: proc(t: ^testing.T) { @(test) test_set_true_set_false :: proc(t: ^testing.T) { - bits := make(128) - defer delete(bits) + bits := create(128) + defer destroy(bits) // set_true within first uint set_true(bits, 0) @@ -605,8 +611,8 @@ all_true_test :: proc(t: ^testing.T) { uint_max := UINT_MAX all_ones := transmute(Int_Bits)uint_max - bits := make(132) - defer delete(bits) + bits := create(132) + defer destroy(bits) bits.int_array[0] = all_ones bits.int_array[1] = all_ones @@ -616,8 +622,8 @@ all_true_test :: proc(t: ^testing.T) { bits.int_array[2] = {0, 1, 2} testing.expect(t, !all_true(bits)) - bits2 := make(1) - defer delete(bits2) + bits2 := create(1) + defer destroy(bits2) bits2.int_array[0] = {0} testing.expect(t, all_true(bits2)) @@ -628,8 +634,8 @@ test_range_true :: proc(t: ^testing.T) { uint_max := UINT_MAX all_ones := transmute(Int_Bits)uint_max - bits := make(192) - defer delete(bits) + bits := create(192) + defer destroy(bits) // Empty range is vacuously true testing.expect(t, range_true(bits, 0, 0)) @@ -676,7 +682,7 @@ test_range_true :: proc(t: ^testing.T) { @(test) nearest_true_handles_same_word_and_boundaries :: proc(t: ^testing.T) { - bits := make(128, context.temp_allocator) + bits := create(128, context.temp_allocator) set_true(bits, 0) set_true(bits, 10) @@ -710,7 +716,7 @@ nearest_true_handles_same_word_and_boundaries :: proc(t: ^testing.T) { @(test) nearest_false_handles_same_word_and_boundaries :: proc(t: ^testing.T) { - bits := make(128, context.temp_allocator) + bits := create(128, context.temp_allocator) // Start with all bits true, then clear a few to false. for i := 0; i < bits.length; i += 1 { @@ -749,7 +755,7 @@ nearest_false_handles_same_word_and_boundaries :: proc(t: ^testing.T) { @(test) nearest_false_scans_across_words_and_returns_false_when_all_true :: proc(t: ^testing.T) { - bits := make(192, context.temp_allocator) + bits := create(192, context.temp_allocator) // Start with all bits true, then clear a couple far apart. for i := 0; i < bits.length; i += 1 { @@ -773,7 +779,7 @@ nearest_false_scans_across_words_and_returns_false_when_all_true :: proc(t: ^tes @(test) nearest_true_scans_across_words_and_returns_false_when_empty :: proc(t: ^testing.T) { - bits := make(192, context.temp_allocator) + bits := create(192, context.temp_allocator) set_true(bits, 5) set_true(bits, 130) @@ -790,7 +796,7 @@ nearest_true_scans_across_words_and_returns_false_when_empty :: proc(t: ^testing @(test) nearest_false_handles_last_word_partial_length :: proc(t: ^testing.T) { - bits := make(130, context.temp_allocator) + bits := create(130, context.temp_allocator) // Start with all bits true, then clear the first and last valid bits. for i := 0; i < bits.length; i += 1 { @@ -811,7 +817,7 @@ nearest_false_handles_last_word_partial_length :: proc(t: ^testing.T) { @(test) nearest_true_handles_last_word_partial_length :: proc(t: ^testing.T) { - bits := make(130, context.temp_allocator) + bits := create(130, context.temp_allocator) set_true(bits, 0) set_true(bits, 129) @@ -828,7 +834,7 @@ nearest_true_handles_last_word_partial_length :: proc(t: ^testing.T) { @(test) iterator_basic_mixed_bits :: proc(t: ^testing.T) { // Use non-word-aligned length to test partial last word handling - bits := make(100, context.temp_allocator) + bits := create(100, context.temp_allocator) // Set specific bits: 0, 3, 64, 99 (last valid index) set_true(bits, 0) @@ -903,7 +909,7 @@ iterator_basic_mixed_bits :: proc(t: ^testing.T) { @(test) iterator_all_false_bits :: proc(t: ^testing.T) { // Use non-word-aligned length - bits := make(100, context.temp_allocator) + bits := create(100, context.temp_allocator) // All bits default to false, no need to set anything // Test iterate - should return all 100 bits as false @@ -944,7 +950,7 @@ iterator_all_false_bits :: proc(t: ^testing.T) { @(test) iterator_all_true_bits :: proc(t: ^testing.T) { // Use non-word-aligned length - bits := make(100, context.temp_allocator) + bits := create(100, context.temp_allocator) // Set all bits to true for i := 0; i < bits.length; i += 1 { set_true(bits, i) diff --git a/ring/ring.odin b/ring/ring.odin index ec6065f..4a3ad13 100644 --- a/ring/ring.odin +++ b/ring/ring.odin @@ -1,35 +1,81 @@ package ring +import "base:runtime" import "core:fmt" @(private) ODIN_BOUNDS_CHECK :: !ODIN_NO_BOUNDS_CHECK -Ring :: struct($T: typeid) { - data: []T, +Ring :: struct($E: typeid) { + data: []E, _end_index, len: int, } -Ring_Soa :: struct($T: typeid) { - data: #soa[]T, +Ring_Soa :: struct($E: typeid) { + data: #soa[]E, _end_index, len: int, } -from_slice_raos :: #force_inline proc(data: $T/[]$E) -> Ring(E) { - return {data = data, _end_index = -1} +destroy_aos :: #force_inline proc(ring: ^Ring($E)) -> runtime.Allocator_Error { + return delete(ring.data) } -from_slice_rsoa :: #force_inline proc(data: $T/#soa[]$E) -> Ring_Soa(E) { - return {data = data, _end_index = -1} +destroy_soa :: #force_inline proc(ring: ^Ring_Soa($E)) -> runtime.Allocator_Error { + return delete(ring.data) } -from_slice :: proc { - from_slice_raos, - from_slice_rsoa, +destroy :: proc { + destroy_aos, + destroy_soa, +} + +create_aos :: #force_inline proc( + $E: typeid, + capacity: int, + allocator := context.allocator, +) -> ( + ring: Ring(E), + err: runtime.Allocator_Error, +) #optional_allocator_error { + ring.data, err = make([]E, capacity, allocator) + ring._end_index = -1 + return ring, err +} + +create_soa :: #force_inline proc( + $E: typeid, + capacity: int, + allocator := context.allocator, +) -> ( + ring: Ring_Soa(E), + err: runtime.Allocator_Error, +) #optional_allocator_error { + ring.data, err = make(#soa[]E, capacity, allocator) + ring._end_index = -1 + return ring, err +} + +init_from_slice_aos :: #force_inline proc(ring: ^Ring($E), data: $T/[]E) { + ring.data = data + ring.len = 0 + ring._end_index = -1 + return +} + +init_from_slice_soa :: #force_inline proc(ring: ^Ring_Soa($E), data: $T/#soa[]E) { + ring.data = data + ring.len = 0 + ring._end_index = -1 + return +} + +init_from_slice :: proc { + init_from_slice_aos, + init_from_slice_soa, } // Index in the backing array where the ring starts -_start_index_raos :: proc(ring: Ring($T)) -> int { +_start_index_aos :: #force_inline proc(ring: Ring($E)) -> int { if ring.len < len(ring.data) { return 0 } else { @@ -39,7 +85,7 @@ _start_index_raos :: proc(ring: Ring($T)) -> int { } // Index in the backing array where the ring starts -_start_index_rsoa :: proc(ring: Ring_Soa($T)) -> int { +_start_index_soa :: #force_inline proc(ring: Ring_Soa($E)) -> int { if ring.len < len(ring.data) { return 0 } else { @@ -48,7 +94,7 @@ _start_index_rsoa :: proc(ring: Ring_Soa($T)) -> int { } } -advance_raos :: proc(ring: ^Ring($T)) { +advance_aos :: #force_inline proc(ring: ^Ring($E)) { // Length if ring.len != len(ring.data) do ring.len += 1 // End index @@ -59,7 +105,7 @@ advance_raos :: proc(ring: ^Ring($T)) { } } -advance_rsoa :: proc(ring: ^Ring_Soa($T)) { +advance_soa :: #force_inline proc(ring: ^Ring_Soa($E)) { // Length if ring.len != len(ring.data) do ring.len += 1 // End index @@ -71,33 +117,31 @@ advance_rsoa :: proc(ring: ^Ring_Soa($T)) { } advance :: proc { - advance_raos, - advance_rsoa, + advance_aos, + advance_soa, } -append_raos :: proc(ring: ^Ring($T), element: T) { +append_aos :: #force_inline proc(ring: ^Ring($E), element: E) { advance(ring) ring.data[ring._end_index] = element } -append_rsoa :: proc(ring: ^Ring_Soa($T), element: T) { +append_soa :: #force_inline proc(ring: ^Ring_Soa($E), element: E) { advance(ring) ring.data[ring._end_index] = element } append :: proc { - append_raos, - append_rsoa, + append_aos, + append_soa, } -get_raos :: proc(ring: Ring($T), index: int) -> ^T { +get_aos :: #force_inline proc(ring: Ring($E), index: int) -> ^E { when ODIN_BOUNDS_CHECK { - if index >= ring.len { - panic(fmt.tprintf("Ring index %i out of bounds for length %i", index, ring.len)) - } + fmt.assertf(index < ring.len, "Ring index %i out of bounds for length %i", index, ring.len) } - array_index := _start_index_raos(ring) + index + array_index := _start_index_aos(ring) + index if array_index < len(ring.data) { return &ring.data[array_index] } else { @@ -107,14 +151,12 @@ get_raos :: proc(ring: Ring($T), index: int) -> ^T { } // SOA can't return soa pointer to parapoly T. -get_rsoa :: proc(ring: Ring_Soa($T), index: int) -> T { +get_soa :: #force_inline proc(ring: Ring_Soa($E), index: int) -> E { when ODIN_BOUNDS_CHECK { - if index >= ring.len { - panic(fmt.tprintf("Ring index %i out of bounds for length %i", index, ring.len)) - } + fmt.assertf(index < ring.len, "Ring index %i out of bounds for length %i", index, ring.len) } - array_index := _start_index_rsoa(ring) + index + array_index := _start_index_soa(ring) + index if array_index < len(ring.data) { return ring.data[array_index] } else { @@ -124,36 +166,36 @@ get_rsoa :: proc(ring: Ring_Soa($T), index: int) -> T { } get :: proc { - get_raos, - get_rsoa, + get_aos, + get_soa, } -get_last_raos :: #force_inline proc(ring: Ring($T)) -> ^T { +get_last_aos :: #force_inline proc(ring: Ring($E)) -> ^E { return get(ring, ring.len - 1) } -get_last_rsoa :: #force_inline proc(ring: Ring_Soa($T)) -> T { +get_last_soa :: #force_inline proc(ring: Ring_Soa($E)) -> E { return get(ring, ring.len - 1) } get_last :: proc { - get_last_raos, - get_last_rsoa, + get_last_aos, + get_last_soa, } -clear_raos :: #force_inline proc "contextless" (ring: ^Ring($T)) { +clear_aos :: #force_inline proc "contextless" (ring: ^Ring($E)) { ring.len = 0 ring._end_index = -1 } -clear_rsoa :: #force_inline proc "contextless" (ring: ^Ring_Soa($T)) { +clear_soa :: #force_inline proc "contextless" (ring: ^Ring_Soa($E)) { ring.len = 0 ring._end_index = -1 } clear :: proc { - clear_raos, - clear_rsoa, + clear_aos, + clear_soa, } // --------------------------------------------------------------------------------------------------------------------- @@ -164,14 +206,13 @@ import "core:testing" @(test) test_ring_aos :: proc(t: ^testing.T) { - data := make_slice([]int, 10) - ring := from_slice(data) - defer delete(ring.data) + ring := create_aos(int, 10) + defer destroy(&ring) for i in 1 ..= 5 { append(&ring, i) log.debug("Length:", ring.len) - log.debug("Start index:", _start_index_raos(ring)) + log.debug("Start index:", _start_index_aos(ring)) log.debug("End index:", ring._end_index) log.debug(ring.data) } @@ -179,12 +220,12 @@ test_ring_aos :: proc(t: ^testing.T) { testing.expect_value(t, get(ring, 4)^, 5) testing.expect_value(t, ring.len, 5) testing.expect_value(t, ring._end_index, 4) - testing.expect_value(t, _start_index_raos(ring), 0) + testing.expect_value(t, _start_index_aos(ring), 0) for i in 6 ..= 15 { append(&ring, i) log.debug("Length:", ring.len) - log.debug("Start index:", _start_index_raos(ring)) + log.debug("Start index:", _start_index_aos(ring)) log.debug("End index:", ring._end_index) log.debug(ring.data) } @@ -194,12 +235,12 @@ test_ring_aos :: proc(t: ^testing.T) { testing.expect_value(t, get_last(ring)^, 15) testing.expect_value(t, ring.len, 10) testing.expect_value(t, ring._end_index, 4) - testing.expect_value(t, _start_index_raos(ring), 5) + testing.expect_value(t, _start_index_aos(ring), 5) for i in 15 ..= 25 { append(&ring, i) log.debug("Length:", ring.len) - log.debug("Start index:", _start_index_raos(ring)) + log.debug("Start index:", _start_index_aos(ring)) log.debug("End index:", ring._end_index) log.debug(ring.data) } @@ -219,14 +260,13 @@ test_ring_soa :: proc(t: ^testing.T) { x, y: int, } - data := make_soa_slice(#soa[]Ints, 10) - ring := from_slice(data) - defer delete(ring.data) + ring := create_soa(Ints, 10) + defer destroy(&ring) for i in 1 ..= 5 { append(&ring, Ints{i, i}) log.debug("Length:", ring.len) - log.debug("Start index:", _start_index_rsoa(ring)) + log.debug("Start index:", _start_index_soa(ring)) log.debug("End index:", ring._end_index) log.debug(ring.data) } @@ -234,12 +274,12 @@ test_ring_soa :: proc(t: ^testing.T) { testing.expect_value(t, get(ring, 4), Ints{5, 5}) testing.expect_value(t, ring.len, 5) testing.expect_value(t, ring._end_index, 4) - testing.expect_value(t, _start_index_rsoa(ring), 0) + testing.expect_value(t, _start_index_soa(ring), 0) for i in 6 ..= 15 { append(&ring, Ints{i, i}) log.debug("Length:", ring.len) - log.debug("Start index:", _start_index_rsoa(ring)) + log.debug("Start index:", _start_index_soa(ring)) log.debug("End index:", ring._end_index) log.debug(ring.data) } @@ -249,12 +289,12 @@ test_ring_soa :: proc(t: ^testing.T) { testing.expect_value(t, get_last(ring), Ints{15, 15}) testing.expect_value(t, ring.len, 10) testing.expect_value(t, ring._end_index, 4) - testing.expect_value(t, _start_index_rsoa(ring), 5) + testing.expect_value(t, _start_index_soa(ring), 5) for i in 15 ..= 25 { append(&ring, Ints{i, i}) log.debug("Length:", ring.len) - log.debug("Start index:", _start_index_rsoa(ring)) + log.debug("Start index:", _start_index_soa(ring)) log.debug("End index:", ring._end_index) log.debug(ring.data) } -- 2.43.0 From 6a82b4b7336338ed11fb62545b5c8935806974a1 Mon Sep 17 00:00:00 2001 From: Zachary Levy Date: Fri, 24 Apr 2026 14:02:28 -0700 Subject: [PATCH 2/4] Make ring suitable for 0 initialization --- ring/ring.odin | 108 +++++++++++++++++++++---------------------------- 1 file changed, 46 insertions(+), 62 deletions(-) diff --git a/ring/ring.odin b/ring/ring.odin index 4a3ad13..382e408 100644 --- a/ring/ring.odin +++ b/ring/ring.odin @@ -7,13 +7,13 @@ import "core:fmt" ODIN_BOUNDS_CHECK :: !ODIN_NO_BOUNDS_CHECK Ring :: struct($E: typeid) { - data: []E, - _end_index, len: int, + data: []E, + next_write_index, len: int, } Ring_Soa :: struct($E: typeid) { - data: #soa[]E, - _end_index, len: int, + data: #soa[]E, + next_write_index, len: int, } destroy_aos :: #force_inline proc(ring: ^Ring($E)) -> runtime.Allocator_Error { @@ -38,7 +38,6 @@ create_aos :: #force_inline proc( err: runtime.Allocator_Error, ) #optional_allocator_error { ring.data, err = make([]E, capacity, allocator) - ring._end_index = -1 return ring, err } @@ -51,21 +50,20 @@ create_soa :: #force_inline proc( err: runtime.Allocator_Error, ) #optional_allocator_error { ring.data, err = make(#soa[]E, capacity, allocator) - ring._end_index = -1 return ring, err } init_from_slice_aos :: #force_inline proc(ring: ^Ring($E), data: $T/[]E) { ring.data = data ring.len = 0 - ring._end_index = -1 + ring.next_write_index = 0 return } init_from_slice_soa :: #force_inline proc(ring: ^Ring_Soa($E), data: $T/#soa[]E) { ring.data = data ring.len = 0 - ring._end_index = -1 + ring.next_write_index = 0 return } @@ -74,46 +72,32 @@ init_from_slice :: proc { init_from_slice_soa, } +// Internal // Index in the backing array where the ring starts -_start_index_aos :: #force_inline proc(ring: Ring($E)) -> int { - if ring.len < len(ring.data) { - return 0 - } else { - start_index := ring._end_index + 1 - return 0 if start_index == len(ring.data) else start_index - } +start_index_aos :: #force_inline proc(ring: Ring($E)) -> int { + return ring.len < len(ring.data) ? 0 : ring.next_write_index } +// Internal // Index in the backing array where the ring starts -_start_index_soa :: #force_inline proc(ring: Ring_Soa($E)) -> int { - if ring.len < len(ring.data) { - return 0 - } else { - start_index := ring._end_index + 1 - return 0 if start_index == len(ring.data) else start_index - } +start_index_soa :: #force_inline proc(ring: Ring_Soa($E)) -> int { + return ring.len < len(ring.data) ? 0 : ring.next_write_index } advance_aos :: #force_inline proc(ring: ^Ring($E)) { // Length if ring.len != len(ring.data) do ring.len += 1 - // End index - if ring._end_index == len(ring.data) - 1 { // If we are at the end of the backing array - ring._end_index = 0 // Overflow end to 0 - } else { - ring._end_index += 1 - } + // Write index + ring.next_write_index += 1 + if ring.next_write_index == len(ring.data) do ring.next_write_index = 0 } advance_soa :: #force_inline proc(ring: ^Ring_Soa($E)) { // Length if ring.len != len(ring.data) do ring.len += 1 - // End index - if ring._end_index == len(ring.data) - 1 { // If we are at the end of the backing array - ring._end_index = 0 // Overflow end to 0 - } else { - ring._end_index += 1 - } + // Write index + ring.next_write_index += 1 + if ring.next_write_index == len(ring.data) do ring.next_write_index = 0 } advance :: proc { @@ -122,13 +106,13 @@ advance :: proc { } append_aos :: #force_inline proc(ring: ^Ring($E), element: E) { + ring.data[ring.next_write_index] = element advance(ring) - ring.data[ring._end_index] = element } append_soa :: #force_inline proc(ring: ^Ring_Soa($E), element: E) { + ring.data[ring.next_write_index] = element advance(ring) - ring.data[ring._end_index] = element } append :: proc { @@ -141,7 +125,7 @@ get_aos :: #force_inline proc(ring: Ring($E), index: int) -> ^E { fmt.assertf(index < ring.len, "Ring index %i out of bounds for length %i", index, ring.len) } - array_index := _start_index_aos(ring) + index + array_index := start_index_aos(ring) + index if array_index < len(ring.data) { return &ring.data[array_index] } else { @@ -156,7 +140,7 @@ get_soa :: #force_inline proc(ring: Ring_Soa($E), index: int) -> E { fmt.assertf(index < ring.len, "Ring index %i out of bounds for length %i", index, ring.len) } - array_index := _start_index_soa(ring) + index + array_index := start_index_soa(ring) + index if array_index < len(ring.data) { return ring.data[array_index] } else { @@ -185,12 +169,12 @@ get_last :: proc { clear_aos :: #force_inline proc "contextless" (ring: ^Ring($E)) { ring.len = 0 - ring._end_index = -1 + ring.next_write_index = 0 } clear_soa :: #force_inline proc "contextless" (ring: ^Ring_Soa($E)) { ring.len = 0 - ring._end_index = -1 + ring.next_write_index = 0 } clear :: proc { @@ -212,21 +196,21 @@ test_ring_aos :: proc(t: ^testing.T) { for i in 1 ..= 5 { append(&ring, i) log.debug("Length:", ring.len) - log.debug("Start index:", _start_index_aos(ring)) - log.debug("End index:", ring._end_index) + log.debug("Start index:", start_index_aos(ring)) + log.debug("Next write index:", ring.next_write_index) log.debug(ring.data) } testing.expect_value(t, get(ring, 0)^, 1) testing.expect_value(t, get(ring, 4)^, 5) testing.expect_value(t, ring.len, 5) - testing.expect_value(t, ring._end_index, 4) - testing.expect_value(t, _start_index_aos(ring), 0) + testing.expect_value(t, ring.next_write_index, 5) + testing.expect_value(t, start_index_aos(ring), 0) for i in 6 ..= 15 { append(&ring, i) log.debug("Length:", ring.len) - log.debug("Start index:", _start_index_aos(ring)) - log.debug("End index:", ring._end_index) + log.debug("Start index:", start_index_aos(ring)) + log.debug("Next write index:", ring.next_write_index) log.debug(ring.data) } testing.expect_value(t, get(ring, 0)^, 6) @@ -234,18 +218,18 @@ test_ring_aos :: proc(t: ^testing.T) { testing.expect_value(t, get(ring, 9)^, 15) testing.expect_value(t, get_last(ring)^, 15) testing.expect_value(t, ring.len, 10) - testing.expect_value(t, ring._end_index, 4) - testing.expect_value(t, _start_index_aos(ring), 5) + testing.expect_value(t, ring.next_write_index, 5) + testing.expect_value(t, start_index_aos(ring), 5) for i in 15 ..= 25 { append(&ring, i) log.debug("Length:", ring.len) - log.debug("Start index:", _start_index_aos(ring)) - log.debug("End index:", ring._end_index) + log.debug("Start index:", start_index_aos(ring)) + log.debug("Next write index:", ring.next_write_index) log.debug(ring.data) } testing.expect_value(t, get(ring, 0)^, 16) - testing.expect_value(t, ring._end_index, 5) + testing.expect_value(t, ring.next_write_index, 6) testing.expect_value(t, get_last(ring)^, 25) clear(&ring) @@ -266,21 +250,21 @@ test_ring_soa :: proc(t: ^testing.T) { for i in 1 ..= 5 { append(&ring, Ints{i, i}) log.debug("Length:", ring.len) - log.debug("Start index:", _start_index_soa(ring)) - log.debug("End index:", ring._end_index) + log.debug("Start index:", start_index_soa(ring)) + log.debug("Next write index:", ring.next_write_index) log.debug(ring.data) } testing.expect_value(t, get(ring, 0), Ints{1, 1}) testing.expect_value(t, get(ring, 4), Ints{5, 5}) testing.expect_value(t, ring.len, 5) - testing.expect_value(t, ring._end_index, 4) - testing.expect_value(t, _start_index_soa(ring), 0) + testing.expect_value(t, ring.next_write_index, 5) + testing.expect_value(t, start_index_soa(ring), 0) for i in 6 ..= 15 { append(&ring, Ints{i, i}) log.debug("Length:", ring.len) - log.debug("Start index:", _start_index_soa(ring)) - log.debug("End index:", ring._end_index) + log.debug("Start index:", start_index_soa(ring)) + log.debug("Next write index:", ring.next_write_index) log.debug(ring.data) } testing.expect_value(t, get(ring, 0), Ints{6, 6}) @@ -288,18 +272,18 @@ test_ring_soa :: proc(t: ^testing.T) { testing.expect_value(t, get(ring, 9), Ints{15, 15}) testing.expect_value(t, get_last(ring), Ints{15, 15}) testing.expect_value(t, ring.len, 10) - testing.expect_value(t, ring._end_index, 4) - testing.expect_value(t, _start_index_soa(ring), 5) + testing.expect_value(t, ring.next_write_index, 5) + testing.expect_value(t, start_index_soa(ring), 5) for i in 15 ..= 25 { append(&ring, Ints{i, i}) log.debug("Length:", ring.len) - log.debug("Start index:", _start_index_soa(ring)) - log.debug("End index:", ring._end_index) + log.debug("Start index:", start_index_soa(ring)) + log.debug("Next write index:", ring.next_write_index) log.debug(ring.data) } testing.expect_value(t, get(ring, 0), Ints{16, 16}) - testing.expect_value(t, ring._end_index, 5) + testing.expect_value(t, ring.next_write_index, 6) testing.expect_value(t, get_last(ring), Ints{25, 25}) clear(&ring) -- 2.43.0 From a0552febf66f0aba5fa388ab6cee03a45b06bdcc Mon Sep 17 00:00:00 2001 From: Zachary Levy Date: Fri, 24 Apr 2026 14:30:13 -0700 Subject: [PATCH 3/4] Phased executor naming consistency --- phased_executor/phased_executor.odin | 41 +++++++++++++++------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/phased_executor/phased_executor.odin b/phased_executor/phased_executor.odin index cfed63b..6061e70 100644 --- a/phased_executor/phased_executor.odin +++ b/phased_executor/phased_executor.odin @@ -4,7 +4,8 @@ package phased_executor import "base:intrinsics" -import q "core:container/queue" +import "base:runtime" +import que "core:container/queue" import "core:prof/spall" import "core:sync" import "core:thread" @@ -18,7 +19,7 @@ DEFT_SPIN_LIMIT :: 2_500_000 Harness :: struct($T: typeid) where intrinsics.type_has_nil(T) { mutex: sync.Mutex, condition: sync.Cond, - cmd_queue: q.Queue(T), + cmd_queue: que.Queue(T), spin: bool, lock: levsync.Spinlock, _pad: [64 - size_of(uint)]u8, // We want join_count to have its own cache line @@ -42,13 +43,13 @@ Executor :: struct($T: typeid) where intrinsics.type_has_nil(T) { } //TODO: Provide a way to set some aspects of context for the executor threads. Namely a logger. -init_executor :: proc( +init :: proc( executor: ^Executor($T), #any_int num_threads: int, $on_command_received: proc(command: T), #any_int spin_limit: uint = DEFT_SPIN_LIMIT, allocator := context.allocator, -) { +) -> runtime.Allocator_Error { was_initialized, _ := intrinsics.atomic_compare_exchange_strong_explicit( &executor.initialized, false, @@ -60,9 +61,9 @@ init_executor :: proc( slave_task := build_task(on_command_received) executor.spin_limit = spin_limit - executor.harnesses = make([]Harness(T), num_threads, allocator) + executor.harnesses = make([]Harness(T), num_threads, allocator) or_return for &harness in executor.harnesses { - q.init(&harness.cmd_queue, allocator = allocator) + que.init(&harness.cmd_queue, allocator = allocator) or_return harness.spin = true } @@ -72,11 +73,11 @@ init_executor :: proc( } thread.pool_start(&executor.thread_pool) - return + return nil } // Cleanly shuts down all executor tasks then destroys the executor -destroy_executor :: proc(executor: ^Executor($T), allocator := context.allocator) { +destroy :: proc(executor: ^Executor($T), allocator := context.allocator) -> runtime.Allocator_Error { was_initialized, _ := intrinsics.atomic_compare_exchange_strong_explicit( &executor.initialized, true, @@ -90,7 +91,7 @@ destroy_executor :: proc(executor: ^Executor($T), allocator := context.allocator for &harness in executor.harnesses { for { if levsync.try_lock(&harness.lock) { - q.push_back(&harness.cmd_queue, nil) + que.push_back(&harness.cmd_queue, nil) if !harness.spin { sync.mutex_lock(&harness.mutex) sync.cond_signal(&harness.condition) @@ -105,9 +106,11 @@ destroy_executor :: proc(executor: ^Executor($T), allocator := context.allocator thread.pool_join(&executor.thread_pool) thread.pool_destroy(&executor.thread_pool) for &harness in executor.harnesses { - q.destroy(&harness.cmd_queue) + que.destroy(&harness.cmd_queue) } - delete(executor.harnesses, allocator) + delete(executor.harnesses, allocator) or_return + + return nil } build_task :: proc( @@ -131,10 +134,10 @@ build_task :: proc( spin_count: uint = 0 spin_loop: for { if levsync.try_lock(&harness.lock) { - if q.len(harness.cmd_queue) > 0 { + if que.len(harness.cmd_queue) > 0 { // Execute command - command := q.pop_front(&harness.cmd_queue) + command := que.pop_front(&harness.cmd_queue) levsync.unlock(&harness.lock) if command == nil do return on_command_received(command) @@ -163,7 +166,7 @@ build_task :: proc( defer intrinsics.cpu_relax() if levsync.try_lock(&harness.lock) { defer levsync.unlock(&harness.lock) - if q.len(harness.cmd_queue) > 0 { + if que.len(harness.cmd_queue) > 0 { harness.spin = true break cond_loop } else { @@ -190,9 +193,9 @@ exec_command :: proc(executor: ^Executor($T), command: T) { } harness := &executor.harnesses[executor.harness_index] if levsync.try_lock(&harness.lock) { - if q.len(harness.cmd_queue) <= executor.cmd_queue_floor { - q.push_back(&harness.cmd_queue, command) - executor.cmd_queue_floor = q.len(harness.cmd_queue) + if que.len(harness.cmd_queue) <= executor.cmd_queue_floor { + que.push_back(&harness.cmd_queue, command) + executor.cmd_queue_floor = que.len(harness.cmd_queue) slave_sleeping := !harness.spin // Must release lock before signalling to avoid race from slave spurious wakeup levsync.unlock(&harness.lock) @@ -258,7 +261,7 @@ stress_test_executor :: proc(t: ^testing.T) { defer free(exec_counts) executor: Executor(Stress_Cmd) - init_executor(&executor, STRESS_NUM_THREADS, stress_handler, spin_limit = 500) + init(&executor, STRESS_NUM_THREADS, stress_handler, spin_limit = 500) for round in 0 ..< STRESS_NUM_ROUNDS { base := round * STRESS_CMDS_PER_ROUND @@ -281,6 +284,6 @@ stress_test_executor :: proc(t: ^testing.T) { // Explicitly destroy to verify clean shutdown. // If destroy_executor returns, all threads received the nil sentinel and exited, // and thread.pool_join completed without deadlock. - destroy_executor(&executor) + destroy(&executor) testing.expect(t, !executor.initialized, "Executor still marked initialized after destroy") } -- 2.43.0 From dd1329e2af83acef38e9976284f4cf1e263d97b4 Mon Sep 17 00:00:00 2001 From: Zachary Levy Date: Fri, 24 Apr 2026 14:40:20 -0700 Subject: [PATCH 4/4] Increased testing for ring --- ring/ring.odin | 150 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 148 insertions(+), 2 deletions(-) diff --git a/ring/ring.odin b/ring/ring.odin index 382e408..e897d96 100644 --- a/ring/ring.odin +++ b/ring/ring.odin @@ -16,11 +16,17 @@ Ring_Soa :: struct($E: typeid) { next_write_index, len: int, } -destroy_aos :: #force_inline proc(ring: ^Ring($E)) -> runtime.Allocator_Error { +destroy_aos :: #force_inline proc( + ring: ^Ring($E), + allocator := context.allocator, +) -> runtime.Allocator_Error { return delete(ring.data) } -destroy_soa :: #force_inline proc(ring: ^Ring_Soa($E)) -> runtime.Allocator_Error { +destroy_soa :: #force_inline proc( + ring: ^Ring_Soa($E), + allocator := context.allocator, +) -> runtime.Allocator_Error { return delete(ring.data) } @@ -53,6 +59,7 @@ create_soa :: #force_inline proc( return ring, err } +// All contents of `data` will be completely ignored, `data` is treated as an empty slice. init_from_slice_aos :: #force_inline proc(ring: ^Ring($E), data: $T/[]E) { ring.data = data ring.len = 0 @@ -60,6 +67,7 @@ init_from_slice_aos :: #force_inline proc(ring: ^Ring($E), data: $T/[]E) { return } +// All contents of `data` will be completely ignored, `data` is treated as an empty slice. init_from_slice_soa :: #force_inline proc(ring: ^Ring_Soa($E), data: $T/#soa[]E) { ring.data = data ring.len = 0 @@ -291,3 +299,141 @@ test_ring_soa :: proc(t: ^testing.T) { testing.expect_value(t, ring.len, 1) testing.expect_value(t, get(ring, 0), Ints{1, 1}) } + +@(test) +test_ring_aos_init_from_slice :: proc(t: ^testing.T) { + // Stack-allocated backing with pre-existing garbage and odd capacity. + backing: [7]int = {99, 99, 99, 99, 99, 99, 99} + + ring: Ring(int) + init_from_slice(&ring, backing[:]) + + // Empty ring invariants after init_from_slice. + testing.expect_value(t, ring.len, 0) + testing.expect_value(t, ring.next_write_index, 0) + testing.expect_value(t, start_index_aos(ring), 0) + + // Partial fill (3 / 7). + for i in 1 ..= 3 do append(&ring, i) + testing.expect_value(t, ring.len, 3) + testing.expect_value(t, ring.next_write_index, 3) + testing.expect_value(t, start_index_aos(ring), 0) + testing.expect_value(t, get(ring, 0)^, 1) + testing.expect_value(t, get(ring, 2)^, 3) + testing.expect_value(t, get_last(ring)^, 3) + + // Fill exactly to capacity. Pushing element 7 must make len == cap + // AND wrap next_write_index from 6 back to 0 in the same step. + for i in 4 ..= 7 do append(&ring, i) + testing.expect_value(t, ring.len, 7) + testing.expect_value(t, ring.next_write_index, 0) + testing.expect_value(t, start_index_aos(ring), 0) + testing.expect_value(t, get(ring, 0)^, 1) + testing.expect_value(t, get(ring, 6)^, 7) + testing.expect_value(t, get_last(ring)^, 7) + + // First overwrite — oldest element shifts by one. + append(&ring, 8) + testing.expect_value(t, ring.len, 7) + testing.expect_value(t, ring.next_write_index, 1) + testing.expect_value(t, start_index_aos(ring), 1) + testing.expect_value(t, get(ring, 0)^, 2) + testing.expect_value(t, get(ring, 6)^, 8) + testing.expect_value(t, get_last(ring)^, 8) + + // Stress: 3 more complete wrap cycles (21 more pushes). + // After 29 total pushes, ring contains the last 7 (23..=29), + // and next_write_index = 29 mod 7 = 1. + for i in 9 ..= 29 do append(&ring, i) + testing.expect_value(t, ring.len, 7) + testing.expect_value(t, ring.next_write_index, 1) + testing.expect_value(t, start_index_aos(ring), 1) + testing.expect_value(t, get(ring, 0)^, 23) + testing.expect_value(t, get(ring, 3)^, 26) + testing.expect_value(t, get(ring, 6)^, 29) + testing.expect_value(t, get_last(ring)^, 29) + + // Clear returns ring to empty-equivalent state. + clear(&ring) + testing.expect_value(t, ring.len, 0) + testing.expect_value(t, ring.next_write_index, 0) + testing.expect_value(t, start_index_aos(ring), 0) + + // Single-element edge case: get_last(len==1) routes through get(ring, 0). + append(&ring, 42) + testing.expect_value(t, ring.len, 1) + testing.expect_value(t, ring.next_write_index, 1) + testing.expect_value(t, get(ring, 0)^, 42) + testing.expect_value(t, get_last(ring)^, 42) +} + +@(test) +test_ring_soa_init_from_slice :: proc(t: ^testing.T) { + Ints :: struct { + x, y: int, + } + + // Stack-allocated backing with pre-existing garbage and odd capacity. + backing: #soa[7]Ints = {{99, 99}, {99, 99}, {99, 99}, {99, 99}, {99, 99}, {99, 99}, {99, 99}} + + ring: Ring_Soa(Ints) + init_from_slice(&ring, backing[:]) + + // Empty ring invariants after init_from_slice. + testing.expect_value(t, ring.len, 0) + testing.expect_value(t, ring.next_write_index, 0) + testing.expect_value(t, start_index_soa(ring), 0) + + // Partial fill (3 / 7). + for i in 1 ..= 3 do append(&ring, Ints{i, i}) + testing.expect_value(t, ring.len, 3) + testing.expect_value(t, ring.next_write_index, 3) + testing.expect_value(t, start_index_soa(ring), 0) + testing.expect_value(t, get(ring, 0), Ints{1, 1}) + testing.expect_value(t, get(ring, 2), Ints{3, 3}) + testing.expect_value(t, get_last(ring), Ints{3, 3}) + + // Fill exactly to capacity. Pushing element 7 must make len == cap + // AND wrap next_write_index from 6 back to 0 in the same step. + for i in 4 ..= 7 do append(&ring, Ints{i, i}) + testing.expect_value(t, ring.len, 7) + testing.expect_value(t, ring.next_write_index, 0) + testing.expect_value(t, start_index_soa(ring), 0) + testing.expect_value(t, get(ring, 0), Ints{1, 1}) + testing.expect_value(t, get(ring, 6), Ints{7, 7}) + testing.expect_value(t, get_last(ring), Ints{7, 7}) + + // First overwrite — oldest element shifts by one. + append(&ring, Ints{8, 8}) + testing.expect_value(t, ring.len, 7) + testing.expect_value(t, ring.next_write_index, 1) + testing.expect_value(t, start_index_soa(ring), 1) + testing.expect_value(t, get(ring, 0), Ints{2, 2}) + testing.expect_value(t, get(ring, 6), Ints{8, 8}) + testing.expect_value(t, get_last(ring), Ints{8, 8}) + + // Stress: 3 more complete wrap cycles (21 more pushes). + // After 29 total pushes, ring contains the last 7 (23..=29), + // and next_write_index = 29 mod 7 = 1. + for i in 9 ..= 29 do append(&ring, Ints{i, i}) + testing.expect_value(t, ring.len, 7) + testing.expect_value(t, ring.next_write_index, 1) + testing.expect_value(t, start_index_soa(ring), 1) + testing.expect_value(t, get(ring, 0), Ints{23, 23}) + testing.expect_value(t, get(ring, 3), Ints{26, 26}) + testing.expect_value(t, get(ring, 6), Ints{29, 29}) + testing.expect_value(t, get_last(ring), Ints{29, 29}) + + // Clear returns ring to empty-equivalent state. + clear(&ring) + testing.expect_value(t, ring.len, 0) + testing.expect_value(t, ring.next_write_index, 0) + testing.expect_value(t, start_index_soa(ring), 0) + + // Single-element edge case: get_last(len==1) routes through get(ring, 0). + append(&ring, Ints{42, 42}) + testing.expect_value(t, ring.len, 1) + testing.expect_value(t, ring.next_write_index, 1) + testing.expect_value(t, get(ring, 0), Ints{42, 42}) + testing.expect_value(t, get_last(ring), Ints{42, 42}) +} -- 2.43.0