adds the pop procedure family for soa arrays

This commit is contained in:
Mihail Moskov
2026-08-11 15:21:23 +03:00
parent 9932b4d2d4
commit 9e73c20197
3 changed files with 145 additions and 7 deletions

View File

@@ -215,6 +215,8 @@ remove_range_fixed_capacity_dynamic_array :: proc(array: ^$D/[dynamic; $N]$E, #a
unordered_remove :: proc{
unordered_remove_dynamic_array,
unordered_remove_fixed_capacity_dynamic_array,
unordered_remove_soa,
}
@@ -222,6 +224,8 @@ unordered_remove :: proc{
ordered_remove :: proc{
ordered_remove_dynamic_array,
ordered_remove_fixed_capacity_dynamic_array,
ordered_remove_soa,
}
@builtin
@@ -271,6 +275,8 @@ pop_fixed_capacity_dynamic_array :: proc(array: ^$T/[dynamic; $N]$E, loc := #cal
pop :: proc{
pop_dynamic_array,
pop_fixed_capacity_dynamic_array,
pop_soa,
}
// `pop_safe_dynamic_array` trys to remove and return the end value of dynamic array `array` and reduces the length of `array` by 1.
@@ -303,6 +309,8 @@ pop_safe_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[dynamic
pop_safe :: proc{
pop_safe_dynamic_array,
pop_safe_fixed_capacity_dynamic_array,
pop_safe_soa,
}
@@ -342,6 +350,8 @@ pop_front_fixed_capacity_dynamic_array :: proc(array: ^$T/[dynamic; $N]$E, loc :
pop_front :: proc{
pop_front_dynamic_array,
pop_front_fixed_capacity_dynamic_array,
pop_front_soa,
}
@@ -381,6 +391,8 @@ pop_front_safe_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[d
pop_front_safe :: proc {
pop_front_safe_dynamic_array,
pop_front_safe_fixed_capacity_dynamic_array,
pop_front_safe_soa,
}
@@ -1212,6 +1224,9 @@ inject_at :: proc{
inject_at_elem_fixed_capacity_dynamic_array,
inject_at_elems_fixed_capacity_dynamic_array,
inject_at_elem_string_fixed_capacity_dynamic_array,
inject_at_elem_soa,
inject_at_elems_soa,
}

View File

@@ -56,7 +56,7 @@ Raw_SOA_Footer_Dynamic_Array :: struct {
// Multipointer indexing lowers to GEP and doesn't capture, so prefer that throughout.
@(builtin, require_results)
raw_soa_footer_slice :: proc(array: ^$T/#soa[]$E) -> (footer: ^Raw_SOA_Footer_Slice) {
raw_soa_footer_slice :: proc "contextless" (array: ^$T/#soa[]$E) -> (footer: ^Raw_SOA_Footer_Slice) {
if array == nil {
return nil
}
@@ -65,7 +65,7 @@ raw_soa_footer_slice :: proc(array: ^$T/#soa[]$E) -> (footer: ^Raw_SOA_Footer_Sl
return
}
@(builtin, require_results)
raw_soa_footer_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) -> (footer: ^Raw_SOA_Footer_Dynamic_Array) {
raw_soa_footer_dynamic_array :: proc "contextless" (array: ^$T/#soa[dynamic]$E) -> (footer: ^Raw_SOA_Footer_Dynamic_Array) {
if array == nil {
return nil
}
@@ -704,6 +704,58 @@ into_dynamic_soa :: proc(array: $T/#soa[]$E) -> #soa[dynamic]E {
return d
}
// `pop_soa` will remove and return the end value of the #soa dynamic array `array` and reduces the length of `array` by 1.
//
// Note: If the #soa dynamic array has no elements (`len(array) == 0`), this procedure will panic.
@builtin
pop_soa :: proc(#no_alias array: ^$T/#soa[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check {
assert(len(array) > 0, loc=loc)
res = array[len(array)-1]
raw_soa_footer_dynamic_array(array).len -= 1
return
}
// `pop_safe_soa` trys to remove and return the end value of the #soa dynamic array `array` and reduces the length of `array` by 1.
// If the operation is not possible, it will return false.
@builtin
pop_safe_soa :: proc "contextless" (#no_alias array: ^$T/#soa[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check {
if len(array) == 0 {
return
}
res, ok = array[len(array)-1], true
raw_soa_footer_dynamic_array(array).len -= 1
return
}
// `pop_front_soa` will remove and return the first value of the #soa dynamic array `array` and reduces the length of `array` by 1,
// whilst keeping the order of the other elements.
//
// Note: This is an O(N) operation.
// Note: If the #soa dynamic array has no elements (`len(array) == 0`), this procedure will panic.
@builtin
pop_front_soa :: proc(#no_alias array: ^$T/#soa[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check {
assert(len(array) > 0, loc=loc)
res = array[0]
_ordered_remove_soa(array, 0)
return
}
// `pop_front_safe_soa` trys to remove and return the first value of the #soa dynamic array `array` and reduces the
// length of `array` by 1, whilst keeping the order of the other elements.
// If the operation is not possible, it will return false.
//
// Note: This is an O(N) operation.
@builtin
pop_front_safe_soa :: proc "contextless" (#no_alias array: ^$T/#soa[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check {
if len(array) == 0 {
return
}
res, ok = array[0], true
_ordered_remove_soa(array, 0)
return
}
// `unordered_remove_soa` removed the element at the specified `index`. It does so by replacing the current end value
// with the old value, and reducing the length of the dynamic array by 1.
//
@@ -714,7 +766,6 @@ into_dynamic_soa :: proc(array: $T/#soa[]$E) -> #soa[dynamic]E {
unordered_remove_soa :: proc(#no_alias array: ^$T/#soa[dynamic]$E, #any_int index: int, loc := #caller_location) #no_bounds_check {
bounds_check_error_loc(loc, index, len(array))
if index+1 < len(array) {
// Use the compiler's #soa element load and store lowering.
array[index] = array[len(array)-1]
}
raw_soa_footer_dynamic_array(array).len -= 1
@@ -728,6 +779,12 @@ unordered_remove_soa :: proc(#no_alias array: ^$T/#soa[dynamic]$E, #any_int inde
@builtin
ordered_remove_soa :: proc(#no_alias array: ^$T/#soa[dynamic]$E, #any_int index: int, loc := #caller_location) #no_bounds_check {
bounds_check_error_loc(loc, index, len(array))
_ordered_remove_soa(array, index)
}
// the unchecked body of ordered_remove_soa, shared with the front pops.
// index must already be known to be in bounds.
_ordered_remove_soa :: proc "contextless" (#no_alias array: ^$T/#soa[dynamic]$E, index: int) #no_bounds_check {
if index+1 < len(array) {
ti := type_info_of(typeid_of(T))
ti = type_info_base(ti)

View File

@@ -1081,11 +1081,9 @@ test_memory_compare_zero :: proc(t: ^testing.T) {
}
}
// Runs identical append/inject/remove sequences for a #soa[dynamic] array
// Runs identical append/inject/remove/pop sequences for a #soa[dynamic] array
// and an AoS [dynamic] reference, comparing all elements after each
// stage. Covers appends, injections (interior, at the end, and past the
// end where the gap must read as zero elements), unordered and ordered
// removes.
// stage. Covers appends, injections, pops, unordered and ordered removes.
@(test)
test_soa_array_append_inject_remove :: proc(t: ^testing.T) {
check :: proc(t: ^testing.T, $E: typeid, mk: proc(i: int) -> E) {
@@ -1242,6 +1240,74 @@ test_soa_array_append_inject_remove :: proc(t: ^testing.T) {
testing.expect_value(t, err, nil)
non_zero_append(&ref, mk(42))
expect_same(t, soa, ref)
// interleave pops from both ends
n, err = append(&soa, ..buf[:])
testing.expect_value(t, n, 32)
testing.expect_value(t, err, nil)
append(&ref, ..buf[:])
for _ in 0..<8 {
testing.expect_value(t, pop_soa(&soa), pop(&ref))
testing.expect_value(t, pop_front_soa(&soa), pop_front(&ref))
}
expect_same(t, soa, ref)
// safe pops from both ends, down to empty
popped: E
for len(ref) > 0 {
popped, ok = pop_safe_soa(&soa)
testing.expect(t, ok)
testing.expect_value(t, popped, pop(&ref))
if len(ref) == 0 {
break
}
popped, ok = pop_front_safe_soa(&soa)
testing.expect(t, ok)
testing.expect_value(t, popped, pop_front(&ref))
}
expect_same(t, soa, ref)
testing.expect_value(t, len(soa), 0)
// safe pops must report failure on an empty array
_, ok = pop_safe_soa(&soa)
testing.expect(t, !ok)
_, ok = pop_front_safe_soa(&soa)
testing.expect(t, !ok)
// test procedure groups resolution for the builtin names
for i in 0..<10 {
append(&soa, mk(700 + i))
append(&ref, mk(700 + i))
}
ok, err = inject_at(&soa, 3, mk(800))
testing.expect(t, ok)
testing.expect_value(t, err, nil)
inject_at(&ref, 3, mk(800))
expect_same(t, soa, ref)
ok, err = inject_at(&soa, 2, ..buf[:BATCH_LEN])
testing.expect(t, ok)
testing.expect_value(t, err, nil)
inject_at(&ref, 2, ..buf[:BATCH_LEN])
expect_same(t, soa, ref)
ordered_remove(&soa, 4)
ordered_remove(&ref, 4)
unordered_remove(&soa, 1)
unordered_remove(&ref, 1)
expect_same(t, soa, ref)
testing.expect_value(t, pop(&soa), pop(&ref))
testing.expect_value(t, pop_front(&soa), pop_front(&ref))
expect_same(t, soa, ref)
popped, ok = pop_safe(&soa)
testing.expect(t, ok)
testing.expect_value(t, popped, pop(&ref))
popped, ok = pop_front_safe(&soa)
testing.expect(t, ok)
testing.expect_value(t, popped, pop_front(&ref))
expect_same(t, soa, ref)
}
// mixed field widths + padding