Improved consistency with naming of init / create / destroy and when to propagate allocation errors and #18
+34
-28
@@ -2,6 +2,7 @@ package many_bits
|
|||||||
|
|
||||||
import "base:builtin"
|
import "base:builtin"
|
||||||
import "base:intrinsics"
|
import "base:intrinsics"
|
||||||
|
import "base:runtime"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:slice"
|
import "core:slice"
|
||||||
|
|
||||||
@@ -25,15 +26,20 @@ Bits :: struct {
|
|||||||
length: int, // Total number of bits being stored
|
length: int, // Total number of bits being stored
|
||||||
}
|
}
|
||||||
|
|
||||||
delete :: proc(bits: Bits, allocator := context.allocator) {
|
destroy :: proc(bits: Bits, allocator := context.allocator) -> runtime.Allocator_Error {
|
||||||
delete_slice(bits.int_array, allocator)
|
return delete_slice(bits.int_array, allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
make :: proc(#any_int length: int, allocator := context.allocator) -> Bits {
|
create :: proc(
|
||||||
return Bits {
|
#any_int length: int,
|
||||||
int_array = make_slice([]Int_Bits, ((length - 1) >> INDEX_SHIFT) + 1, allocator),
|
allocator := context.allocator,
|
||||||
length = length,
|
) -> (
|
||||||
}
|
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)
|
// Sets all bits to 0 (false)
|
||||||
@@ -507,8 +513,8 @@ import "core:testing"
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_set :: proc(t: ^testing.T) {
|
test_set :: proc(t: ^testing.T) {
|
||||||
bits := make(128)
|
bits := create(128)
|
||||||
defer delete(bits)
|
defer destroy(bits)
|
||||||
|
|
||||||
set(bits, 0, true)
|
set(bits, 0, true)
|
||||||
testing.expect_value(t, bits.int_array[0], Int_Bits{0})
|
testing.expect_value(t, bits.int_array[0], Int_Bits{0})
|
||||||
@@ -524,8 +530,8 @@ test_set :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_get :: proc(t: ^testing.T) {
|
test_get :: proc(t: ^testing.T) {
|
||||||
bits := make(128)
|
bits := create(128)
|
||||||
defer delete(bits)
|
defer destroy(bits)
|
||||||
|
|
||||||
// Default is false
|
// Default is false
|
||||||
testing.expect(t, !get(bits, 0))
|
testing.expect(t, !get(bits, 0))
|
||||||
@@ -560,8 +566,8 @@ test_get :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_set_true_set_false :: proc(t: ^testing.T) {
|
test_set_true_set_false :: proc(t: ^testing.T) {
|
||||||
bits := make(128)
|
bits := create(128)
|
||||||
defer delete(bits)
|
defer destroy(bits)
|
||||||
|
|
||||||
// set_true within first uint
|
// set_true within first uint
|
||||||
set_true(bits, 0)
|
set_true(bits, 0)
|
||||||
@@ -605,8 +611,8 @@ all_true_test :: proc(t: ^testing.T) {
|
|||||||
uint_max := UINT_MAX
|
uint_max := UINT_MAX
|
||||||
all_ones := transmute(Int_Bits)uint_max
|
all_ones := transmute(Int_Bits)uint_max
|
||||||
|
|
||||||
bits := make(132)
|
bits := create(132)
|
||||||
defer delete(bits)
|
defer destroy(bits)
|
||||||
|
|
||||||
bits.int_array[0] = all_ones
|
bits.int_array[0] = all_ones
|
||||||
bits.int_array[1] = 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}
|
bits.int_array[2] = {0, 1, 2}
|
||||||
testing.expect(t, !all_true(bits))
|
testing.expect(t, !all_true(bits))
|
||||||
|
|
||||||
bits2 := make(1)
|
bits2 := create(1)
|
||||||
defer delete(bits2)
|
defer destroy(bits2)
|
||||||
|
|
||||||
bits2.int_array[0] = {0}
|
bits2.int_array[0] = {0}
|
||||||
testing.expect(t, all_true(bits2))
|
testing.expect(t, all_true(bits2))
|
||||||
@@ -628,8 +634,8 @@ test_range_true :: proc(t: ^testing.T) {
|
|||||||
uint_max := UINT_MAX
|
uint_max := UINT_MAX
|
||||||
all_ones := transmute(Int_Bits)uint_max
|
all_ones := transmute(Int_Bits)uint_max
|
||||||
|
|
||||||
bits := make(192)
|
bits := create(192)
|
||||||
defer delete(bits)
|
defer destroy(bits)
|
||||||
|
|
||||||
// Empty range is vacuously true
|
// Empty range is vacuously true
|
||||||
testing.expect(t, range_true(bits, 0, 0))
|
testing.expect(t, range_true(bits, 0, 0))
|
||||||
@@ -676,7 +682,7 @@ test_range_true :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
nearest_true_handles_same_word_and_boundaries :: proc(t: ^testing.T) {
|
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, 0)
|
||||||
set_true(bits, 10)
|
set_true(bits, 10)
|
||||||
@@ -710,7 +716,7 @@ nearest_true_handles_same_word_and_boundaries :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
nearest_false_handles_same_word_and_boundaries :: proc(t: ^testing.T) {
|
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.
|
// Start with all bits true, then clear a few to false.
|
||||||
for i := 0; i < bits.length; i += 1 {
|
for i := 0; i < bits.length; i += 1 {
|
||||||
@@ -749,7 +755,7 @@ nearest_false_handles_same_word_and_boundaries :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
nearest_false_scans_across_words_and_returns_false_when_all_true :: proc(t: ^testing.T) {
|
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.
|
// Start with all bits true, then clear a couple far apart.
|
||||||
for i := 0; i < bits.length; i += 1 {
|
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)
|
@(test)
|
||||||
nearest_true_scans_across_words_and_returns_false_when_empty :: proc(t: ^testing.T) {
|
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, 5)
|
||||||
set_true(bits, 130)
|
set_true(bits, 130)
|
||||||
@@ -790,7 +796,7 @@ nearest_true_scans_across_words_and_returns_false_when_empty :: proc(t: ^testing
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
nearest_false_handles_last_word_partial_length :: proc(t: ^testing.T) {
|
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.
|
// Start with all bits true, then clear the first and last valid bits.
|
||||||
for i := 0; i < bits.length; i += 1 {
|
for i := 0; i < bits.length; i += 1 {
|
||||||
@@ -811,7 +817,7 @@ nearest_false_handles_last_word_partial_length :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
nearest_true_handles_last_word_partial_length :: proc(t: ^testing.T) {
|
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, 0)
|
||||||
set_true(bits, 129)
|
set_true(bits, 129)
|
||||||
@@ -828,7 +834,7 @@ nearest_true_handles_last_word_partial_length :: proc(t: ^testing.T) {
|
|||||||
@(test)
|
@(test)
|
||||||
iterator_basic_mixed_bits :: proc(t: ^testing.T) {
|
iterator_basic_mixed_bits :: proc(t: ^testing.T) {
|
||||||
// Use non-word-aligned length to test partial last word handling
|
// 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 specific bits: 0, 3, 64, 99 (last valid index)
|
||||||
set_true(bits, 0)
|
set_true(bits, 0)
|
||||||
@@ -903,7 +909,7 @@ iterator_basic_mixed_bits :: proc(t: ^testing.T) {
|
|||||||
@(test)
|
@(test)
|
||||||
iterator_all_false_bits :: proc(t: ^testing.T) {
|
iterator_all_false_bits :: proc(t: ^testing.T) {
|
||||||
// Use non-word-aligned length
|
// 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
|
// All bits default to false, no need to set anything
|
||||||
|
|
||||||
// Test iterate - should return all 100 bits as false
|
// Test iterate - should return all 100 bits as false
|
||||||
@@ -944,7 +950,7 @@ iterator_all_false_bits :: proc(t: ^testing.T) {
|
|||||||
@(test)
|
@(test)
|
||||||
iterator_all_true_bits :: proc(t: ^testing.T) {
|
iterator_all_true_bits :: proc(t: ^testing.T) {
|
||||||
// Use non-word-aligned length
|
// Use non-word-aligned length
|
||||||
bits := make(100, context.temp_allocator)
|
bits := create(100, context.temp_allocator)
|
||||||
// Set all bits to true
|
// Set all bits to true
|
||||||
for i := 0; i < bits.length; i += 1 {
|
for i := 0; i < bits.length; i += 1 {
|
||||||
set_true(bits, i)
|
set_true(bits, i)
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
package phased_executor
|
package phased_executor
|
||||||
|
|
||||||
import "base:intrinsics"
|
import "base:intrinsics"
|
||||||
import q "core:container/queue"
|
import "base:runtime"
|
||||||
|
import que "core:container/queue"
|
||||||
import "core:prof/spall"
|
import "core:prof/spall"
|
||||||
import "core:sync"
|
import "core:sync"
|
||||||
import "core:thread"
|
import "core:thread"
|
||||||
@@ -18,7 +19,7 @@ DEFT_SPIN_LIMIT :: 2_500_000
|
|||||||
Harness :: struct($T: typeid) where intrinsics.type_has_nil(T) {
|
Harness :: struct($T: typeid) where intrinsics.type_has_nil(T) {
|
||||||
mutex: sync.Mutex,
|
mutex: sync.Mutex,
|
||||||
condition: sync.Cond,
|
condition: sync.Cond,
|
||||||
cmd_queue: q.Queue(T),
|
cmd_queue: que.Queue(T),
|
||||||
spin: bool,
|
spin: bool,
|
||||||
lock: levsync.Spinlock,
|
lock: levsync.Spinlock,
|
||||||
_pad: [64 - size_of(uint)]u8, // We want join_count to have its own cache line
|
_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.
|
//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),
|
executor: ^Executor($T),
|
||||||
#any_int num_threads: int,
|
#any_int num_threads: int,
|
||||||
$on_command_received: proc(command: T),
|
$on_command_received: proc(command: T),
|
||||||
#any_int spin_limit: uint = DEFT_SPIN_LIMIT,
|
#any_int spin_limit: uint = DEFT_SPIN_LIMIT,
|
||||||
allocator := context.allocator,
|
allocator := context.allocator,
|
||||||
) {
|
) -> runtime.Allocator_Error {
|
||||||
was_initialized, _ := intrinsics.atomic_compare_exchange_strong_explicit(
|
was_initialized, _ := intrinsics.atomic_compare_exchange_strong_explicit(
|
||||||
&executor.initialized,
|
&executor.initialized,
|
||||||
false,
|
false,
|
||||||
@@ -60,9 +61,9 @@ init_executor :: proc(
|
|||||||
|
|
||||||
slave_task := build_task(on_command_received)
|
slave_task := build_task(on_command_received)
|
||||||
executor.spin_limit = spin_limit
|
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 {
|
for &harness in executor.harnesses {
|
||||||
q.init(&harness.cmd_queue, allocator = allocator)
|
que.init(&harness.cmd_queue, allocator = allocator) or_return
|
||||||
harness.spin = true
|
harness.spin = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,11 +73,11 @@ init_executor :: proc(
|
|||||||
}
|
}
|
||||||
thread.pool_start(&executor.thread_pool)
|
thread.pool_start(&executor.thread_pool)
|
||||||
|
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanly shuts down all executor tasks then destroys the executor
|
// 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(
|
was_initialized, _ := intrinsics.atomic_compare_exchange_strong_explicit(
|
||||||
&executor.initialized,
|
&executor.initialized,
|
||||||
true,
|
true,
|
||||||
@@ -90,7 +91,7 @@ destroy_executor :: proc(executor: ^Executor($T), allocator := context.allocator
|
|||||||
for &harness in executor.harnesses {
|
for &harness in executor.harnesses {
|
||||||
for {
|
for {
|
||||||
if levsync.try_lock(&harness.lock) {
|
if levsync.try_lock(&harness.lock) {
|
||||||
q.push_back(&harness.cmd_queue, nil)
|
que.push_back(&harness.cmd_queue, nil)
|
||||||
if !harness.spin {
|
if !harness.spin {
|
||||||
sync.mutex_lock(&harness.mutex)
|
sync.mutex_lock(&harness.mutex)
|
||||||
sync.cond_signal(&harness.condition)
|
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_join(&executor.thread_pool)
|
||||||
thread.pool_destroy(&executor.thread_pool)
|
thread.pool_destroy(&executor.thread_pool)
|
||||||
for &harness in executor.harnesses {
|
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(
|
build_task :: proc(
|
||||||
@@ -131,10 +134,10 @@ build_task :: proc(
|
|||||||
spin_count: uint = 0
|
spin_count: uint = 0
|
||||||
spin_loop: for {
|
spin_loop: for {
|
||||||
if levsync.try_lock(&harness.lock) {
|
if levsync.try_lock(&harness.lock) {
|
||||||
if q.len(harness.cmd_queue) > 0 {
|
if que.len(harness.cmd_queue) > 0 {
|
||||||
|
|
||||||
// Execute command
|
// Execute command
|
||||||
command := q.pop_front(&harness.cmd_queue)
|
command := que.pop_front(&harness.cmd_queue)
|
||||||
levsync.unlock(&harness.lock)
|
levsync.unlock(&harness.lock)
|
||||||
if command == nil do return
|
if command == nil do return
|
||||||
on_command_received(command)
|
on_command_received(command)
|
||||||
@@ -163,7 +166,7 @@ build_task :: proc(
|
|||||||
defer intrinsics.cpu_relax()
|
defer intrinsics.cpu_relax()
|
||||||
if levsync.try_lock(&harness.lock) {
|
if levsync.try_lock(&harness.lock) {
|
||||||
defer levsync.unlock(&harness.lock)
|
defer levsync.unlock(&harness.lock)
|
||||||
if q.len(harness.cmd_queue) > 0 {
|
if que.len(harness.cmd_queue) > 0 {
|
||||||
harness.spin = true
|
harness.spin = true
|
||||||
break cond_loop
|
break cond_loop
|
||||||
} else {
|
} else {
|
||||||
@@ -190,9 +193,9 @@ exec_command :: proc(executor: ^Executor($T), command: T) {
|
|||||||
}
|
}
|
||||||
harness := &executor.harnesses[executor.harness_index]
|
harness := &executor.harnesses[executor.harness_index]
|
||||||
if levsync.try_lock(&harness.lock) {
|
if levsync.try_lock(&harness.lock) {
|
||||||
if q.len(harness.cmd_queue) <= executor.cmd_queue_floor {
|
if que.len(harness.cmd_queue) <= executor.cmd_queue_floor {
|
||||||
q.push_back(&harness.cmd_queue, command)
|
que.push_back(&harness.cmd_queue, command)
|
||||||
executor.cmd_queue_floor = q.len(harness.cmd_queue)
|
executor.cmd_queue_floor = que.len(harness.cmd_queue)
|
||||||
slave_sleeping := !harness.spin
|
slave_sleeping := !harness.spin
|
||||||
// Must release lock before signalling to avoid race from slave spurious wakeup
|
// Must release lock before signalling to avoid race from slave spurious wakeup
|
||||||
levsync.unlock(&harness.lock)
|
levsync.unlock(&harness.lock)
|
||||||
@@ -258,7 +261,7 @@ stress_test_executor :: proc(t: ^testing.T) {
|
|||||||
defer free(exec_counts)
|
defer free(exec_counts)
|
||||||
|
|
||||||
executor: Executor(Stress_Cmd)
|
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 {
|
for round in 0 ..< STRESS_NUM_ROUNDS {
|
||||||
base := round * STRESS_CMDS_PER_ROUND
|
base := round * STRESS_CMDS_PER_ROUND
|
||||||
@@ -281,6 +284,6 @@ stress_test_executor :: proc(t: ^testing.T) {
|
|||||||
// Explicitly destroy to verify clean shutdown.
|
// Explicitly destroy to verify clean shutdown.
|
||||||
// If destroy_executor returns, all threads received the nil sentinel and exited,
|
// If destroy_executor returns, all threads received the nil sentinel and exited,
|
||||||
// and thread.pool_join completed without deadlock.
|
// and thread.pool_join completed without deadlock.
|
||||||
destroy_executor(&executor)
|
destroy(&executor)
|
||||||
testing.expect(t, !executor.initialized, "Executor still marked initialized after destroy")
|
testing.expect(t, !executor.initialized, "Executor still marked initialized after destroy")
|
||||||
}
|
}
|
||||||
|
|||||||
+269
-99
@@ -1,103 +1,139 @@
|
|||||||
package ring
|
package ring
|
||||||
|
|
||||||
|
import "base:runtime"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
|
|
||||||
@(private)
|
@(private)
|
||||||
ODIN_BOUNDS_CHECK :: !ODIN_NO_BOUNDS_CHECK
|
ODIN_BOUNDS_CHECK :: !ODIN_NO_BOUNDS_CHECK
|
||||||
|
|
||||||
Ring :: struct($T: typeid) {
|
Ring :: struct($E: typeid) {
|
||||||
data: []T,
|
data: []E,
|
||||||
_end_index, len: int,
|
next_write_index, len: int,
|
||||||
}
|
}
|
||||||
|
|
||||||
Ring_Soa :: struct($T: typeid) {
|
Ring_Soa :: struct($E: typeid) {
|
||||||
data: #soa[]T,
|
data: #soa[]E,
|
||||||
_end_index, len: int,
|
next_write_index, len: int,
|
||||||
}
|
}
|
||||||
|
|
||||||
from_slice_raos :: #force_inline proc(data: $T/[]$E) -> Ring(E) {
|
destroy_aos :: #force_inline proc(
|
||||||
return {data = data, _end_index = -1}
|
ring: ^Ring($E),
|
||||||
|
allocator := context.allocator,
|
||||||
|
) -> runtime.Allocator_Error {
|
||||||
|
return delete(ring.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
from_slice_rsoa :: #force_inline proc(data: $T/#soa[]$E) -> Ring_Soa(E) {
|
destroy_soa :: #force_inline proc(
|
||||||
return {data = data, _end_index = -1}
|
ring: ^Ring_Soa($E),
|
||||||
|
allocator := context.allocator,
|
||||||
|
) -> runtime.Allocator_Error {
|
||||||
|
return delete(ring.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
from_slice :: proc {
|
destroy :: proc {
|
||||||
from_slice_raos,
|
destroy_aos,
|
||||||
from_slice_rsoa,
|
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)
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
ring.next_write_index = 0
|
||||||
|
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
|
||||||
|
ring.next_write_index = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
init_from_slice :: proc {
|
||||||
|
init_from_slice_aos,
|
||||||
|
init_from_slice_soa,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal
|
||||||
// Index in the backing array where the ring starts
|
// 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 ring.len < len(ring.data) ? 0 : ring.next_write_index
|
||||||
return 0
|
|
||||||
} else {
|
|
||||||
start_index := ring._end_index + 1
|
|
||||||
return 0 if start_index == len(ring.data) else start_index
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Internal
|
||||||
// Index in the backing array where the ring starts
|
// 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 ring.len < len(ring.data) ? 0 : ring.next_write_index
|
||||||
return 0
|
|
||||||
} else {
|
|
||||||
start_index := ring._end_index + 1
|
|
||||||
return 0 if start_index == len(ring.data) else start_index
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
advance_raos :: proc(ring: ^Ring($T)) {
|
advance_aos :: #force_inline proc(ring: ^Ring($E)) {
|
||||||
// Length
|
// Length
|
||||||
if ring.len != len(ring.data) do ring.len += 1
|
if ring.len != len(ring.data) do ring.len += 1
|
||||||
// End index
|
// Write index
|
||||||
if ring._end_index == len(ring.data) - 1 { // If we are at the end of the backing array
|
ring.next_write_index += 1
|
||||||
ring._end_index = 0 // Overflow end to 0
|
if ring.next_write_index == len(ring.data) do ring.next_write_index = 0
|
||||||
} else {
|
|
||||||
ring._end_index += 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
advance_rsoa :: proc(ring: ^Ring_Soa($T)) {
|
advance_soa :: #force_inline proc(ring: ^Ring_Soa($E)) {
|
||||||
// Length
|
// Length
|
||||||
if ring.len != len(ring.data) do ring.len += 1
|
if ring.len != len(ring.data) do ring.len += 1
|
||||||
// End index
|
// Write index
|
||||||
if ring._end_index == len(ring.data) - 1 { // If we are at the end of the backing array
|
ring.next_write_index += 1
|
||||||
ring._end_index = 0 // Overflow end to 0
|
if ring.next_write_index == len(ring.data) do ring.next_write_index = 0
|
||||||
} else {
|
|
||||||
ring._end_index += 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
advance :: proc {
|
advance :: proc {
|
||||||
advance_raos,
|
advance_aos,
|
||||||
advance_rsoa,
|
advance_soa,
|
||||||
}
|
}
|
||||||
|
|
||||||
append_raos :: proc(ring: ^Ring($T), element: T) {
|
append_aos :: #force_inline proc(ring: ^Ring($E), element: E) {
|
||||||
|
ring.data[ring.next_write_index] = element
|
||||||
advance(ring)
|
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) {
|
||||||
|
ring.data[ring.next_write_index] = element
|
||||||
advance(ring)
|
advance(ring)
|
||||||
ring.data[ring._end_index] = element
|
|
||||||
}
|
}
|
||||||
|
|
||||||
append :: proc {
|
append :: proc {
|
||||||
append_raos,
|
append_aos,
|
||||||
append_rsoa,
|
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 {
|
when ODIN_BOUNDS_CHECK {
|
||||||
if index >= ring.len {
|
fmt.assertf(index < ring.len, "Ring index %i out of bounds for length %i", index, ring.len)
|
||||||
panic(fmt.tprintf("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) {
|
if array_index < len(ring.data) {
|
||||||
return &ring.data[array_index]
|
return &ring.data[array_index]
|
||||||
} else {
|
} else {
|
||||||
@@ -107,14 +143,12 @@ get_raos :: proc(ring: Ring($T), index: int) -> ^T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SOA can't return soa pointer to parapoly 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 {
|
when ODIN_BOUNDS_CHECK {
|
||||||
if index >= ring.len {
|
fmt.assertf(index < ring.len, "Ring index %i out of bounds for length %i", index, ring.len)
|
||||||
panic(fmt.tprintf("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) {
|
if array_index < len(ring.data) {
|
||||||
return ring.data[array_index]
|
return ring.data[array_index]
|
||||||
} else {
|
} else {
|
||||||
@@ -124,36 +158,36 @@ get_rsoa :: proc(ring: Ring_Soa($T), index: int) -> T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get :: proc {
|
get :: proc {
|
||||||
get_raos,
|
get_aos,
|
||||||
get_rsoa,
|
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)
|
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)
|
return get(ring, ring.len - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
get_last :: proc {
|
get_last :: proc {
|
||||||
get_last_raos,
|
get_last_aos,
|
||||||
get_last_rsoa,
|
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.len = 0
|
||||||
ring._end_index = -1
|
ring.next_write_index = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
clear_rsoa :: #force_inline proc "contextless" (ring: ^Ring_Soa($T)) {
|
clear_soa :: #force_inline proc "contextless" (ring: ^Ring_Soa($E)) {
|
||||||
ring.len = 0
|
ring.len = 0
|
||||||
ring._end_index = -1
|
ring.next_write_index = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
clear :: proc {
|
clear :: proc {
|
||||||
clear_raos,
|
clear_aos,
|
||||||
clear_rsoa,
|
clear_soa,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
@@ -164,28 +198,27 @@ import "core:testing"
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_ring_aos :: proc(t: ^testing.T) {
|
test_ring_aos :: proc(t: ^testing.T) {
|
||||||
data := make_slice([]int, 10)
|
ring := create_aos(int, 10)
|
||||||
ring := from_slice(data)
|
defer destroy(&ring)
|
||||||
defer delete(ring.data)
|
|
||||||
|
|
||||||
for i in 1 ..= 5 {
|
for i in 1 ..= 5 {
|
||||||
append(&ring, i)
|
append(&ring, i)
|
||||||
log.debug("Length:", ring.len)
|
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("Next write index:", ring.next_write_index)
|
||||||
log.debug(ring.data)
|
log.debug(ring.data)
|
||||||
}
|
}
|
||||||
testing.expect_value(t, get(ring, 0)^, 1)
|
testing.expect_value(t, get(ring, 0)^, 1)
|
||||||
testing.expect_value(t, get(ring, 4)^, 5)
|
testing.expect_value(t, get(ring, 4)^, 5)
|
||||||
testing.expect_value(t, ring.len, 5)
|
testing.expect_value(t, ring.len, 5)
|
||||||
testing.expect_value(t, ring._end_index, 4)
|
testing.expect_value(t, ring.next_write_index, 5)
|
||||||
testing.expect_value(t, _start_index_raos(ring), 0)
|
testing.expect_value(t, start_index_aos(ring), 0)
|
||||||
|
|
||||||
for i in 6 ..= 15 {
|
for i in 6 ..= 15 {
|
||||||
append(&ring, i)
|
append(&ring, i)
|
||||||
log.debug("Length:", ring.len)
|
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("Next write index:", ring.next_write_index)
|
||||||
log.debug(ring.data)
|
log.debug(ring.data)
|
||||||
}
|
}
|
||||||
testing.expect_value(t, get(ring, 0)^, 6)
|
testing.expect_value(t, get(ring, 0)^, 6)
|
||||||
@@ -193,18 +226,18 @@ test_ring_aos :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, get(ring, 9)^, 15)
|
testing.expect_value(t, get(ring, 9)^, 15)
|
||||||
testing.expect_value(t, get_last(ring)^, 15)
|
testing.expect_value(t, get_last(ring)^, 15)
|
||||||
testing.expect_value(t, ring.len, 10)
|
testing.expect_value(t, ring.len, 10)
|
||||||
testing.expect_value(t, ring._end_index, 4)
|
testing.expect_value(t, ring.next_write_index, 5)
|
||||||
testing.expect_value(t, _start_index_raos(ring), 5)
|
testing.expect_value(t, start_index_aos(ring), 5)
|
||||||
|
|
||||||
for i in 15 ..= 25 {
|
for i in 15 ..= 25 {
|
||||||
append(&ring, i)
|
append(&ring, i)
|
||||||
log.debug("Length:", ring.len)
|
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("Next write index:", ring.next_write_index)
|
||||||
log.debug(ring.data)
|
log.debug(ring.data)
|
||||||
}
|
}
|
||||||
testing.expect_value(t, get(ring, 0)^, 16)
|
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)
|
testing.expect_value(t, get_last(ring)^, 25)
|
||||||
|
|
||||||
clear(&ring)
|
clear(&ring)
|
||||||
@@ -219,28 +252,27 @@ test_ring_soa :: proc(t: ^testing.T) {
|
|||||||
x, y: int,
|
x, y: int,
|
||||||
}
|
}
|
||||||
|
|
||||||
data := make_soa_slice(#soa[]Ints, 10)
|
ring := create_soa(Ints, 10)
|
||||||
ring := from_slice(data)
|
defer destroy(&ring)
|
||||||
defer delete(ring.data)
|
|
||||||
|
|
||||||
for i in 1 ..= 5 {
|
for i in 1 ..= 5 {
|
||||||
append(&ring, Ints{i, i})
|
append(&ring, Ints{i, i})
|
||||||
log.debug("Length:", ring.len)
|
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("Next write index:", ring.next_write_index)
|
||||||
log.debug(ring.data)
|
log.debug(ring.data)
|
||||||
}
|
}
|
||||||
testing.expect_value(t, get(ring, 0), Ints{1, 1})
|
testing.expect_value(t, get(ring, 0), Ints{1, 1})
|
||||||
testing.expect_value(t, get(ring, 4), Ints{5, 5})
|
testing.expect_value(t, get(ring, 4), Ints{5, 5})
|
||||||
testing.expect_value(t, ring.len, 5)
|
testing.expect_value(t, ring.len, 5)
|
||||||
testing.expect_value(t, ring._end_index, 4)
|
testing.expect_value(t, ring.next_write_index, 5)
|
||||||
testing.expect_value(t, _start_index_rsoa(ring), 0)
|
testing.expect_value(t, start_index_soa(ring), 0)
|
||||||
|
|
||||||
for i in 6 ..= 15 {
|
for i in 6 ..= 15 {
|
||||||
append(&ring, Ints{i, i})
|
append(&ring, Ints{i, i})
|
||||||
log.debug("Length:", ring.len)
|
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("Next write index:", ring.next_write_index)
|
||||||
log.debug(ring.data)
|
log.debug(ring.data)
|
||||||
}
|
}
|
||||||
testing.expect_value(t, get(ring, 0), Ints{6, 6})
|
testing.expect_value(t, get(ring, 0), Ints{6, 6})
|
||||||
@@ -248,18 +280,18 @@ test_ring_soa :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, get(ring, 9), Ints{15, 15})
|
testing.expect_value(t, get(ring, 9), Ints{15, 15})
|
||||||
testing.expect_value(t, get_last(ring), 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.len, 10)
|
||||||
testing.expect_value(t, ring._end_index, 4)
|
testing.expect_value(t, ring.next_write_index, 5)
|
||||||
testing.expect_value(t, _start_index_rsoa(ring), 5)
|
testing.expect_value(t, start_index_soa(ring), 5)
|
||||||
|
|
||||||
for i in 15 ..= 25 {
|
for i in 15 ..= 25 {
|
||||||
append(&ring, Ints{i, i})
|
append(&ring, Ints{i, i})
|
||||||
log.debug("Length:", ring.len)
|
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("Next write index:", ring.next_write_index)
|
||||||
log.debug(ring.data)
|
log.debug(ring.data)
|
||||||
}
|
}
|
||||||
testing.expect_value(t, get(ring, 0), Ints{16, 16})
|
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})
|
testing.expect_value(t, get_last(ring), Ints{25, 25})
|
||||||
|
|
||||||
clear(&ring)
|
clear(&ring)
|
||||||
@@ -267,3 +299,141 @@ test_ring_soa :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, ring.len, 1)
|
testing.expect_value(t, ring.len, 1)
|
||||||
testing.expect_value(t, get(ring, 0), Ints{1, 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})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user