diff --git a/core/container/flexible_array/flexible_array.odin b/core/container/flexible_array/flexible_array.odin new file mode 100644 index 00000000000..a2a9613bb32 --- /dev/null +++ b/core/container/flexible_array/flexible_array.odin @@ -0,0 +1,196 @@ +/* +Flexible arrays use a fixed array as backing. But in contrast to fixed arrays +they keep track of how many elements that have been used. + +This means that they work a bit like an array with a dynamic size, without +requiring any dynamic memory allocations. + +The fixed array that is used as backing lives within the Flexible_Array struct, +this means that the if the fixed array is very large, then the Flexible_Array +struct becomes very large. Beware of this when you store a Flexible_Array on the +stack. + +The provided procedures are similar to those you'd use with dynamic arrays, with +the exception that you have to use the `get` proc instead of index operators. + +Example: + import fa "core:container/flexible_array" + + array: fa.Array(1024, int) + fa.push_back(&array, 5) + fa.push_back(&array, 2) + fa.push_back(&array, 7) + fa.unordered_remove(&array, 0) + element := fa.get(array, 1) + slc := fa.slice(&array) + + for e in slc { + fmt.println(e) + } +*/ +package container_flexible_array + +import "base:builtin" +import "base:runtime" +_ :: runtime + +Flexible_Array :: struct($N: int, $T: typeid) where N >= 0 { + data: [N]T, + len: int, +} + + +len :: proc "contextless" (a: $A/Flexible_Array) -> int { + return a.len +} + +cap :: proc "contextless" (a: $A/Flexible_Array) -> int { + return builtin.len(a.data) +} + +space :: proc "contextless" (a: $A/Flexible_Array) -> int { + return builtin.len(a.data) - a.len +} + +slice :: proc "contextless" (a: ^$A/Flexible_Array($N, $T)) -> []T { + return a.data[:a.len] +} + + +get :: proc "contextless" (a: $A/Flexible_Array($N, $T), index: int) -> T { + return a.data[index] +} +get_ptr :: proc "contextless" (a: ^$A/Flexible_Array($N, $T), index: int) -> ^T { + return &a.data[index] +} + +get_safe :: proc(a: $A/Flexible_Array($N, $T), index: int) -> (T, bool) #no_bounds_check { + if index < 0 || index >= a.len { + return {}, false + } + return a.data[index], true +} + +get_ptr_safe :: proc(a: ^$A/Flexible_Array($N, $T), index: int) -> (^T, bool) #no_bounds_check { + if index < 0 || index >= a.len { + return {}, false + } + return &a.data[index], true +} + +set :: proc "contextless" (a: ^$A/Flexible_Array($N, $T), index: int, item: T) { + a.data[index] = item +} + +resize :: proc "contextless" (a: ^$A/Flexible_Array, length: int) { + a.len = min(length, builtin.len(a.data)) +} + + +push_back :: proc "contextless" (a: ^$A/Flexible_Array($N, $T), item: T) -> bool { + if a.len < cap(a^) { + a.data[a.len] = item + a.len += 1 + return true + } + return false +} + +push_front :: proc "contextless" (a: ^$A/Flexible_Array($N, $T), item: T) -> bool { + if a.len < cap(a^) { + a.len += 1 + data := slice(a) + copy(data[1:], data[:]) + data[0] = item + return true + } + return false +} + +pop_back :: proc "odin" (a: ^$A/Flexible_Array($N, $T), loc := #caller_location) -> T { + assert(condition=(N > 0 && a.len > 0), loc=loc) + item := a.data[a.len-1] + a.len -= 1 + return item +} + +pop_front :: proc "odin" (a: ^$A/Flexible_Array($N, $T), loc := #caller_location) -> T { + assert(condition=(N > 0 && a.len > 0), loc=loc) + item := a.data[0] + s := slice(a) + copy(s[:], s[1:]) + a.len -= 1 + return item +} + +pop_back_safe :: proc "contextless" (a: ^$A/Flexible_Array($N, $T)) -> (item: T, ok: bool) { + if N > 0 && a.len > 0 { + item = a.data[a.len-1] + a.len -= 1 + ok = true + } + return +} + +pop_front_safe :: proc "contextless" (a: ^$A/Flexible_Array($N, $T)) -> (item: T, ok: bool) { + if N > 0 && a.len > 0 { + item = a.data[0] + s := slice(a) + copy(s[:], s[1:]) + a.len -= 1 + ok = true + } + return +} + +consume :: proc "odin" (a: ^$A/Flexible_Array($N, $T), count: int, loc := #caller_location) { + assert(condition=a.len >= count, loc=loc) + a.len -= count +} + +ordered_remove :: proc "contextless" (a: ^$A/Flexible_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check { + runtime.bounds_check_error_loc(loc, index, a.len) + if index+1 < a.len { + copy(a.data[index:], a.data[index+1:]) + } + a.len -= 1 +} + +unordered_remove :: proc "contextless" (a: ^$A/Flexible_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check { + runtime.bounds_check_error_loc(loc, index, a.len) + n := a.len-1 + if index != n { + a.data[index] = a.data[n] + } + a.len -= 1 +} + +clear :: proc "contextless" (a: ^$A/Flexible_Array($N, $T)) { + resize(a, 0) +} + +push_back_elems :: proc "contextless" (a: ^$A/Flexible_Array($N, $T), items: ..T) -> bool { + if a.len + builtin.len(items) <= cap(a^) { + n := copy(a.data[a.len:], items[:]) + a.len += n + return true + } + return false +} + +inject_at :: proc "contextless" (a: ^$A/Flexible_Array($N, $T), item: T, index: int) -> bool #no_bounds_check { + if a.len < cap(a^) && index >= 0 && index <= len(a^) { + a.len += 1 + for i := a.len - 1; i >= index + 1; i -= 1 { + a.data[i] = a.data[i - 1] + } + a.data[index] = item + return true + } + return false +} + +append_elem :: push_back +append_elems :: push_back_elems +push :: proc{push_back, push_back_elems} +append :: proc{push_back, push_back_elems} diff --git a/core/container/small_array/move_notice.odin b/core/container/small_array/move_notice.odin new file mode 100644 index 00000000000..70f19547a56 --- /dev/null +++ b/core/container/small_array/move_notice.odin @@ -0,0 +1,3 @@ +package container_small_array + +#panic(`"core:container/small_array" has moved to "core:container/flexible_array"`) \ No newline at end of file diff --git a/core/container/small_array/small_array.odin b/core/container/small_array/small_array.odin deleted file mode 100644 index 77bb21cbca2..00000000000 --- a/core/container/small_array/small_array.odin +++ /dev/null @@ -1,166 +0,0 @@ -package container_small_array - -import "base:builtin" -import "base:runtime" -_ :: runtime - -Small_Array :: struct($N: int, $T: typeid) where N >= 0 { - data: [N]T, - len: int, -} - - -len :: proc "contextless" (a: $A/Small_Array) -> int { - return a.len -} - -cap :: proc "contextless" (a: $A/Small_Array) -> int { - return builtin.len(a.data) -} - -space :: proc "contextless" (a: $A/Small_Array) -> int { - return builtin.len(a.data) - a.len -} - -slice :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> []T { - return a.data[:a.len] -} - - -get :: proc "contextless" (a: $A/Small_Array($N, $T), index: int) -> T { - return a.data[index] -} -get_ptr :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int) -> ^T { - return &a.data[index] -} - -get_safe :: proc(a: $A/Small_Array($N, $T), index: int) -> (T, bool) #no_bounds_check { - if index < 0 || index >= a.len { - return {}, false - } - return a.data[index], true -} - -get_ptr_safe :: proc(a: ^$A/Small_Array($N, $T), index: int) -> (^T, bool) #no_bounds_check { - if index < 0 || index >= a.len { - return {}, false - } - return &a.data[index], true -} - -set :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, item: T) { - a.data[index] = item -} - -resize :: proc "contextless" (a: ^$A/Small_Array, length: int) { - a.len = min(length, builtin.len(a.data)) -} - - -push_back :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool { - if a.len < cap(a^) { - a.data[a.len] = item - a.len += 1 - return true - } - return false -} - -push_front :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool { - if a.len < cap(a^) { - a.len += 1 - data := slice(a) - copy(data[1:], data[:]) - data[0] = item - return true - } - return false -} - -pop_back :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) -> T { - assert(condition=(N > 0 && a.len > 0), loc=loc) - item := a.data[a.len-1] - a.len -= 1 - return item -} - -pop_front :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) -> T { - assert(condition=(N > 0 && a.len > 0), loc=loc) - item := a.data[0] - s := slice(a) - copy(s[:], s[1:]) - a.len -= 1 - return item -} - -pop_back_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) { - if N > 0 && a.len > 0 { - item = a.data[a.len-1] - a.len -= 1 - ok = true - } - return -} - -pop_front_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) { - if N > 0 && a.len > 0 { - item = a.data[0] - s := slice(a) - copy(s[:], s[1:]) - a.len -= 1 - ok = true - } - return -} - -consume :: proc "odin" (a: ^$A/Small_Array($N, $T), count: int, loc := #caller_location) { - assert(condition=a.len >= count, loc=loc) - a.len -= count -} - -ordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check { - runtime.bounds_check_error_loc(loc, index, a.len) - if index+1 < a.len { - copy(a.data[index:], a.data[index+1:]) - } - a.len -= 1 -} - -unordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check { - runtime.bounds_check_error_loc(loc, index, a.len) - n := a.len-1 - if index != n { - a.data[index] = a.data[n] - } - a.len -= 1 -} - -clear :: proc "contextless" (a: ^$A/Small_Array($N, $T)) { - resize(a, 0) -} - -push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), items: ..T) -> bool { - if a.len + builtin.len(items) <= cap(a^) { - n := copy(a.data[a.len:], items[:]) - a.len += n - return true - } - return false -} - -inject_at :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T, index: int) -> bool #no_bounds_check { - if a.len < cap(a^) && index >= 0 && index <= len(a^) { - a.len += 1 - for i := a.len - 1; i >= index + 1; i -= 1 { - a.data[i] = a.data[i - 1] - } - a.data[index] = item - return true - } - return false -} - -append_elem :: push_back -append_elems :: push_back_elems -push :: proc{push_back, push_back_elems} -append :: proc{push_back, push_back_elems} diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 5bd45fda461..b9b003f2291 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -18,7 +18,7 @@ import avl "core:container/avl" import bit_array "core:container/bit_array" import priority_queue "core:container/priority_queue" import queue "core:container/queue" -import small_array "core:container/small_array" +import flexible_array "core:container/flexible_array" import lru "core:container/lru" import list "core:container/intrusive/list" import rbtree "core:container/rbtree" @@ -160,7 +160,7 @@ _ :: avl _ :: bit_array _ :: priority_queue _ :: queue -_ :: small_array +_ :: flexible_array _ :: lru _ :: list _ :: rbtree diff --git a/tests/core/container/test_core_flexible_array.odin b/tests/core/container/test_core_flexible_array.odin new file mode 100644 index 00000000000..36218c5be49 --- /dev/null +++ b/tests/core/container/test_core_flexible_array.odin @@ -0,0 +1,68 @@ +package test_core_container + +import "core:testing" +import fa "core:container/flexible_array" + +@(test) +test_small_array_removes :: proc(t: ^testing.T) { + array: fa.Flexible_Array(10, int) + fa.append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + + fa.ordered_remove(&array, 0) + testing.expect(t, slice_equal(fa.slice(&array), []int { 1, 2, 3, 4, 5, 6, 7, 8, 9 })) + fa.ordered_remove(&array, 5) + testing.expect(t, slice_equal(fa.slice(&array), []int { 1, 2, 3, 4, 5, 7, 8, 9 })) + fa.ordered_remove(&array, 6) + testing.expect(t, slice_equal(fa.slice(&array), []int { 1, 2, 3, 4, 5, 7, 9 })) + fa.unordered_remove(&array, 0) + testing.expect(t, slice_equal(fa.slice(&array), []int { 9, 2, 3, 4, 5, 7 })) + fa.unordered_remove(&array, 2) + testing.expect(t, slice_equal(fa.slice(&array), []int { 9, 2, 7, 4, 5 })) + fa.unordered_remove(&array, 4) + testing.expect(t, slice_equal(fa.slice(&array), []int { 9, 2, 7, 4 })) +} + +@(test) +test_small_array_inject_at :: proc(t: ^testing.T) { + array: fa.Flexible_Array(13, int) + fa.append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + + testing.expect(t, fa.inject_at(&array, 0, 0), "Expected to be able to inject into small array") + testing.expect(t, slice_equal(fa.slice(&array), []int { 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 })) + testing.expect(t, fa.inject_at(&array, 0, 5), "Expected to be able to inject into small array") + testing.expect(t, slice_equal(fa.slice(&array), []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9 })) + testing.expect(t, fa.inject_at(&array, 0, fa.len(array)), "Expected to be able to inject into small array") + testing.expect(t, slice_equal(fa.slice(&array), []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9, 0 })) +} + +@(test) +test_small_array_push_back_elems :: proc(t: ^testing.T) { + array: fa.Flexible_Array(2, int) + testing.expect(t, slice_equal(fa.slice(&array), []int { })) + testing.expect(t, fa.append(&array, 0), "Expected to be able to append to empty small array") + testing.expect(t, slice_equal(fa.slice(&array), []int { 0 })) + testing.expect(t, fa.append(&array, 1, 2) == false, "Expected to fail appending multiple elements beyond capacity of small array") + testing.expect(t, fa.append(&array, 1), "Expected to be able to append to small array") + testing.expect(t, slice_equal(fa.slice(&array), []int { 0, 1 })) + testing.expect(t, fa.append(&array, 1) == false, "Expected to fail appending to full small array") + testing.expect(t, fa.append(&array, 1, 2) == false, "Expected to fail appending multiple elements to full small array") + fa.clear(&array) + testing.expect(t, slice_equal(fa.slice(&array), []int { })) + testing.expect(t, fa.append(&array, 1, 2, 3) == false, "Expected to fail appending multiple elements to empty small array") + testing.expect(t, slice_equal(fa.slice(&array), []int { })) + testing.expect(t, fa.append(&array, 1, 2), "Expected to be able to append multiple elements to empty small array") + testing.expect(t, slice_equal(fa.slice(&array), []int { 1, 2 })) +} + +slice_equal :: proc(a, b: []int) -> bool { + if len(a) != len(b) { + return false + } + + for a, i in a { + if b[i] != a { + return false + } + } + return true +} diff --git a/tests/core/container/test_core_small_array.odin b/tests/core/container/test_core_small_array.odin deleted file mode 100644 index 21f35f112e1..00000000000 --- a/tests/core/container/test_core_small_array.odin +++ /dev/null @@ -1,68 +0,0 @@ -package test_core_container - -import "core:testing" -import "core:container/small_array" - -@(test) -test_small_array_removes :: proc(t: ^testing.T) { - array: small_array.Small_Array(10, int) - small_array.append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) - - small_array.ordered_remove(&array, 0) - testing.expect(t, slice_equal(small_array.slice(&array), []int { 1, 2, 3, 4, 5, 6, 7, 8, 9 })) - small_array.ordered_remove(&array, 5) - testing.expect(t, slice_equal(small_array.slice(&array), []int { 1, 2, 3, 4, 5, 7, 8, 9 })) - small_array.ordered_remove(&array, 6) - testing.expect(t, slice_equal(small_array.slice(&array), []int { 1, 2, 3, 4, 5, 7, 9 })) - small_array.unordered_remove(&array, 0) - testing.expect(t, slice_equal(small_array.slice(&array), []int { 9, 2, 3, 4, 5, 7 })) - small_array.unordered_remove(&array, 2) - testing.expect(t, slice_equal(small_array.slice(&array), []int { 9, 2, 7, 4, 5 })) - small_array.unordered_remove(&array, 4) - testing.expect(t, slice_equal(small_array.slice(&array), []int { 9, 2, 7, 4 })) -} - -@(test) -test_small_array_inject_at :: proc(t: ^testing.T) { - array: small_array.Small_Array(13, int) - small_array.append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) - - testing.expect(t, small_array.inject_at(&array, 0, 0), "Expected to be able to inject into small array") - testing.expect(t, slice_equal(small_array.slice(&array), []int { 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 })) - testing.expect(t, small_array.inject_at(&array, 0, 5), "Expected to be able to inject into small array") - testing.expect(t, slice_equal(small_array.slice(&array), []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9 })) - testing.expect(t, small_array.inject_at(&array, 0, small_array.len(array)), "Expected to be able to inject into small array") - testing.expect(t, slice_equal(small_array.slice(&array), []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9, 0 })) -} - -@(test) -test_small_array_push_back_elems :: proc(t: ^testing.T) { - array: small_array.Small_Array(2, int) - testing.expect(t, slice_equal(small_array.slice(&array), []int { })) - testing.expect(t, small_array.append(&array, 0), "Expected to be able to append to empty small array") - testing.expect(t, slice_equal(small_array.slice(&array), []int { 0 })) - testing.expect(t, small_array.append(&array, 1, 2) == false, "Expected to fail appending multiple elements beyond capacity of small array") - testing.expect(t, small_array.append(&array, 1), "Expected to be able to append to small array") - testing.expect(t, slice_equal(small_array.slice(&array), []int { 0, 1 })) - testing.expect(t, small_array.append(&array, 1) == false, "Expected to fail appending to full small array") - testing.expect(t, small_array.append(&array, 1, 2) == false, "Expected to fail appending multiple elements to full small array") - small_array.clear(&array) - testing.expect(t, slice_equal(small_array.slice(&array), []int { })) - testing.expect(t, small_array.append(&array, 1, 2, 3) == false, "Expected to fail appending multiple elements to empty small array") - testing.expect(t, slice_equal(small_array.slice(&array), []int { })) - testing.expect(t, small_array.append(&array, 1, 2), "Expected to be able to append multiple elements to empty small array") - testing.expect(t, slice_equal(small_array.slice(&array), []int { 1, 2 })) -} - -slice_equal :: proc(a, b: []int) -> bool { - if len(a) != len(b) { - return false - } - - for a, i in a { - if b[i] != a { - return false - } - } - return true -}