mirror of
https://github.com/Ed94/Odin.git
synced 2026-06-17 11:22:22 -07:00
679f9b4e41
Before this if you do `arr: [dynamic]int` and then append to arr, then it will have capacity 8. But if you did `arr := make([dynamic]int, context.temp_allocator)` then arr would have capacity 16. Now both `arr: [dynamic]int` and `arr := make([dynamic]int, context.temp_allocator)` will resut in arr having zero 0. The only reason to use `make` without an explicit len or cap now is because you want to set it up for a non-default allocator. After the first call to `append` it will now in both cases have capacity 8. I also updated the documentation on the strings builder, both to reflect this, and also to fix it incorrectly saying that len would be 'max(16,len)', which wasn't true even before these changes.
523 lines
13 KiB
Odin
523 lines
13 KiB
Odin
package runtime
|
|
|
|
import "base:intrinsics"
|
|
_ :: intrinsics
|
|
|
|
/*
|
|
|
|
SOA types are implemented with this sort of layout:
|
|
|
|
SOA Fixed Array
|
|
struct {
|
|
f0: [N]T0,
|
|
f1: [N]T1,
|
|
f2: [N]T2,
|
|
}
|
|
|
|
SOA Slice
|
|
struct {
|
|
f0: ^T0,
|
|
f1: ^T1,
|
|
f2: ^T2,
|
|
|
|
len: int,
|
|
}
|
|
|
|
SOA Dynamic Array
|
|
struct {
|
|
f0: ^T0,
|
|
f1: ^T1,
|
|
f2: ^T2,
|
|
|
|
len: int,
|
|
cap: int,
|
|
allocator: Allocator,
|
|
}
|
|
|
|
A footer is used rather than a header purely to simplify access to the fields internally
|
|
i.e. field index of the AOS == SOA
|
|
|
|
*/
|
|
|
|
|
|
Raw_SOA_Footer_Slice :: struct {
|
|
len: int,
|
|
}
|
|
|
|
Raw_SOA_Footer_Dynamic_Array :: struct {
|
|
len: int,
|
|
cap: int,
|
|
allocator: Allocator,
|
|
}
|
|
|
|
@(builtin, require_results)
|
|
raw_soa_footer_slice :: proc(array: ^$T/#soa[]$E) -> (footer: ^Raw_SOA_Footer_Slice) {
|
|
if array == nil {
|
|
return nil
|
|
}
|
|
field_count := uintptr(intrinsics.type_struct_field_count(E))
|
|
footer = (^Raw_SOA_Footer_Slice)(uintptr(array) + field_count*size_of(rawptr))
|
|
return
|
|
}
|
|
@(builtin, require_results)
|
|
raw_soa_footer_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) -> (footer: ^Raw_SOA_Footer_Dynamic_Array) {
|
|
if array == nil {
|
|
return nil
|
|
}
|
|
field_count: uintptr
|
|
when intrinsics.type_is_array(E) {
|
|
field_count = len(E)
|
|
} else {
|
|
field_count = uintptr(intrinsics.type_struct_field_count(E))
|
|
}
|
|
footer = (^Raw_SOA_Footer_Dynamic_Array)(uintptr(array) + field_count*size_of(rawptr))
|
|
return
|
|
}
|
|
raw_soa_footer :: proc{
|
|
raw_soa_footer_slice,
|
|
raw_soa_footer_dynamic_array,
|
|
}
|
|
|
|
|
|
|
|
@(builtin, require_results)
|
|
make_soa_aligned :: proc($T: typeid/#soa[]$E, length: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
|
|
if length <= 0 {
|
|
return
|
|
}
|
|
|
|
footer := raw_soa_footer(&array)
|
|
if size_of(E) == 0 {
|
|
footer.len = length
|
|
return
|
|
}
|
|
|
|
max_align := max(alignment, align_of(E))
|
|
|
|
ti := type_info_of(typeid_of(T))
|
|
ti = type_info_base(ti)
|
|
si := &ti.variant.(Type_Info_Struct)
|
|
|
|
field_count := uintptr(intrinsics.type_struct_field_count(E))
|
|
|
|
total_size := 0
|
|
for i in 0..<field_count {
|
|
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
|
|
total_size += type.size * length
|
|
total_size = align_forward_int(total_size, max_align)
|
|
}
|
|
|
|
allocator := allocator
|
|
if allocator.procedure == nil {
|
|
allocator = context.allocator
|
|
}
|
|
assert(allocator.procedure != nil)
|
|
|
|
new_bytes: []byte
|
|
new_bytes, err = allocator.procedure(
|
|
allocator.data, .Alloc, total_size, max_align,
|
|
nil, 0, loc,
|
|
)
|
|
if new_bytes == nil || err != nil {
|
|
return
|
|
}
|
|
new_data := raw_data(new_bytes)
|
|
|
|
data := uintptr(&array)
|
|
offset := 0
|
|
for i in 0..<field_count {
|
|
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
|
|
|
|
offset = align_forward_int(offset, max_align)
|
|
|
|
(^uintptr)(data)^ = uintptr(new_data) + uintptr(offset)
|
|
data += size_of(rawptr)
|
|
offset += type.size * length
|
|
}
|
|
footer.len = length
|
|
|
|
return
|
|
}
|
|
|
|
@(builtin, require_results)
|
|
make_soa_slice :: proc($T: typeid/#soa[]$E, length: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
|
|
return make_soa_aligned(T, length, align_of(E), allocator, loc)
|
|
}
|
|
|
|
@(builtin, require_results)
|
|
make_soa_dynamic_array :: proc($T: typeid/#soa[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
|
|
context.allocator = allocator
|
|
reserve_soa(&array, 0, loc) or_return
|
|
return array, nil
|
|
}
|
|
|
|
@(builtin, require_results)
|
|
make_soa_dynamic_array_len :: proc($T: typeid/#soa[dynamic]$E, #any_int length: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
|
|
context.allocator = allocator
|
|
resize_soa(&array, length, loc) or_return
|
|
return array, nil
|
|
}
|
|
|
|
@(builtin, require_results)
|
|
make_soa_dynamic_array_len_cap :: proc($T: typeid/#soa[dynamic]$E, #any_int length, capacity: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
|
|
context.allocator = allocator
|
|
reserve_soa(&array, capacity, loc) or_return
|
|
resize_soa(&array, length, loc) or_return
|
|
return array, nil
|
|
}
|
|
|
|
|
|
@builtin
|
|
make_soa :: proc{
|
|
make_soa_slice,
|
|
make_soa_dynamic_array,
|
|
make_soa_dynamic_array_len,
|
|
make_soa_dynamic_array_len_cap,
|
|
}
|
|
|
|
|
|
@builtin
|
|
resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error {
|
|
if array == nil {
|
|
return nil
|
|
}
|
|
reserve_soa(array, length, loc) or_return
|
|
footer := raw_soa_footer(array)
|
|
footer.len = length
|
|
return nil
|
|
}
|
|
|
|
@builtin
|
|
reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error {
|
|
if array == nil {
|
|
return nil
|
|
}
|
|
|
|
old_cap := cap(array)
|
|
if capacity <= old_cap {
|
|
return nil
|
|
}
|
|
|
|
if array.allocator.procedure == nil {
|
|
array.allocator = context.allocator
|
|
}
|
|
assert(array.allocator.procedure != nil)
|
|
|
|
footer := raw_soa_footer(array)
|
|
if size_of(E) == 0 {
|
|
footer.cap = capacity
|
|
return nil
|
|
}
|
|
|
|
ti := type_info_of(typeid_of(T))
|
|
ti = type_info_base(ti)
|
|
si := &ti.variant.(Type_Info_Struct)
|
|
|
|
field_count: uintptr
|
|
when intrinsics.type_is_array(E) {
|
|
field_count = len(E)
|
|
} else {
|
|
field_count = uintptr(intrinsics.type_struct_field_count(E))
|
|
}
|
|
assert(footer.cap == old_cap)
|
|
|
|
old_size := 0
|
|
new_size := 0
|
|
|
|
max_align :: align_of(E)
|
|
for i in 0..<field_count {
|
|
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
|
|
|
|
old_size += type.size * old_cap
|
|
new_size += type.size * capacity
|
|
|
|
old_size = align_forward_int(old_size, max_align)
|
|
new_size = align_forward_int(new_size, max_align)
|
|
}
|
|
|
|
old_data := (^rawptr)(array)^
|
|
|
|
new_bytes := array.allocator.procedure(
|
|
array.allocator.data, .Alloc, new_size, max_align,
|
|
nil, old_size, loc,
|
|
) or_return
|
|
new_data := raw_data(new_bytes)
|
|
|
|
|
|
footer.cap = capacity
|
|
|
|
old_offset := 0
|
|
new_offset := 0
|
|
for i in 0..<field_count {
|
|
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
|
|
|
|
old_offset = align_forward_int(old_offset, max_align)
|
|
new_offset = align_forward_int(new_offset, max_align)
|
|
|
|
new_data_elem := rawptr(uintptr(new_data) + uintptr(new_offset))
|
|
old_data_elem := rawptr(uintptr(old_data) + uintptr(old_offset))
|
|
|
|
mem_copy(new_data_elem, old_data_elem, type.size * old_cap)
|
|
|
|
(^rawptr)(uintptr(array) + i*size_of(rawptr))^ = new_data_elem
|
|
|
|
old_offset += type.size * old_cap
|
|
new_offset += type.size * capacity
|
|
}
|
|
|
|
array.allocator.procedure(
|
|
array.allocator.data, .Free, 0, max_align,
|
|
old_data, old_size, loc,
|
|
) or_return
|
|
|
|
return nil
|
|
}
|
|
|
|
@builtin
|
|
append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
|
if array == nil {
|
|
return 0, nil
|
|
}
|
|
|
|
if cap(array) <= len(array) + 1 {
|
|
// Same behavior as append_soa_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY.
|
|
cap := 2 * cap(array) + DEFAULT_DYNAMIC_ARRAY_CAPACITY
|
|
err = reserve_soa(array, cap, loc) // do not 'or_return' here as it could be a partial success
|
|
}
|
|
|
|
footer := raw_soa_footer(array)
|
|
|
|
if size_of(E) > 0 && cap(array)-len(array) > 0 {
|
|
ti := type_info_of(T)
|
|
ti = type_info_base(ti)
|
|
si := &ti.variant.(Type_Info_Struct)
|
|
field_count: uintptr
|
|
when intrinsics.type_is_array(E) {
|
|
field_count = len(E)
|
|
} else {
|
|
field_count = uintptr(intrinsics.type_struct_field_count(E))
|
|
}
|
|
|
|
data := (^rawptr)(array)^
|
|
|
|
soa_offset := 0
|
|
item_offset := 0
|
|
|
|
arg_copy := arg
|
|
arg_ptr := &arg_copy
|
|
|
|
max_align :: align_of(E)
|
|
for i in 0..<field_count {
|
|
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
|
|
|
|
soa_offset = align_forward_int(soa_offset, max_align)
|
|
item_offset = align_forward_int(item_offset, type.align)
|
|
|
|
dst := rawptr(uintptr(data) + uintptr(soa_offset) + uintptr(type.size * footer.len))
|
|
src := rawptr(uintptr(arg_ptr) + uintptr(item_offset))
|
|
mem_copy(dst, src, type.size)
|
|
|
|
soa_offset += type.size * cap(array)
|
|
item_offset += type.size
|
|
}
|
|
footer.len += 1
|
|
return 1, err
|
|
}
|
|
return 0, err
|
|
}
|
|
|
|
@builtin
|
|
append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
|
if array == nil {
|
|
return
|
|
}
|
|
|
|
arg_len := len(args)
|
|
if arg_len == 0 {
|
|
return
|
|
}
|
|
|
|
if cap(array) <= len(array)+arg_len {
|
|
cap := 2 * cap(array) + max(DEFAULT_DYNAMIC_ARRAY_CAPACITY, arg_len)
|
|
err = reserve_soa(array, cap, loc) // do not 'or_return' here as it could be a partial success
|
|
}
|
|
arg_len = min(cap(array)-len(array), arg_len)
|
|
|
|
footer := raw_soa_footer(array)
|
|
if size_of(E) > 0 && arg_len > 0 {
|
|
ti := type_info_of(typeid_of(T))
|
|
ti = type_info_base(ti)
|
|
si := &ti.variant.(Type_Info_Struct)
|
|
field_count := uintptr(intrinsics.type_struct_field_count(E))
|
|
|
|
data := (^rawptr)(array)^
|
|
|
|
soa_offset := 0
|
|
item_offset := 0
|
|
|
|
args_ptr := &args[0]
|
|
|
|
max_align :: align_of(E)
|
|
for i in 0..<field_count {
|
|
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
|
|
|
|
soa_offset = align_forward_int(soa_offset, max_align)
|
|
item_offset = align_forward_int(item_offset, type.align)
|
|
|
|
dst := uintptr(data) + uintptr(soa_offset) + uintptr(type.size * footer.len)
|
|
src := uintptr(args_ptr) + uintptr(item_offset)
|
|
for j in 0..<arg_len {
|
|
d := rawptr(dst + uintptr(j*type.size))
|
|
s := rawptr(src + uintptr(j*size_of(E)))
|
|
mem_copy(d, s, type.size)
|
|
}
|
|
|
|
soa_offset += type.size * cap(array)
|
|
item_offset += type.size
|
|
}
|
|
}
|
|
footer.len += arg_len
|
|
return arg_len, err
|
|
}
|
|
|
|
|
|
// The append_soa built-in procedure appends elements to the end of an #soa dynamic array
|
|
@builtin
|
|
append_soa :: proc{
|
|
append_soa_elem,
|
|
append_soa_elems,
|
|
}
|
|
|
|
|
|
delete_soa_slice :: proc(array: $T/#soa[]$E, allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
|
|
when intrinsics.type_struct_field_count(E) != 0 {
|
|
array := array
|
|
ptr := (^rawptr)(&array)^
|
|
free(ptr, allocator, loc) or_return
|
|
}
|
|
return nil
|
|
}
|
|
|
|
delete_soa_dynamic_array :: proc(array: $T/#soa[dynamic]$E, loc := #caller_location) -> Allocator_Error {
|
|
when intrinsics.type_struct_field_count(E) != 0 {
|
|
array := array
|
|
ptr := (^rawptr)(&array)^
|
|
footer := raw_soa_footer(&array)
|
|
free(ptr, footer.allocator, loc) or_return
|
|
}
|
|
return nil
|
|
}
|
|
|
|
|
|
@builtin
|
|
delete_soa :: proc{
|
|
delete_soa_slice,
|
|
delete_soa_dynamic_array,
|
|
}
|
|
|
|
|
|
clear_soa_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) {
|
|
when intrinsics.type_struct_field_count(E) != 0 {
|
|
footer := raw_soa_footer(array)
|
|
footer.len = 0
|
|
}
|
|
}
|
|
|
|
@builtin
|
|
clear_soa :: proc{
|
|
clear_soa_dynamic_array,
|
|
}
|
|
|
|
// Converts soa slice into a soa dynamic array without cloning or allocating memory
|
|
@(require_results)
|
|
into_dynamic_soa :: proc(array: $T/#soa[]$E) -> #soa[dynamic]E {
|
|
d: #soa[dynamic]E
|
|
footer := raw_soa_footer_dynamic_array(&d)
|
|
footer^ = {
|
|
cap = len(array),
|
|
len = 0,
|
|
allocator = nil_allocator(),
|
|
}
|
|
|
|
field_count: uintptr
|
|
when intrinsics.type_is_array(E) {
|
|
field_count = len(E)
|
|
} else {
|
|
field_count = uintptr(intrinsics.type_struct_field_count(E))
|
|
}
|
|
|
|
array := array
|
|
dynamic_data := ([^]rawptr)(&d)[:field_count]
|
|
slice_data := ([^]rawptr)(&array)[:field_count]
|
|
copy(dynamic_data, slice_data)
|
|
|
|
return d
|
|
}
|
|
|
|
// `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.
|
|
//
|
|
// Note: This is an O(1) operation.
|
|
// Note: If you the elements to remain in their order, use `ordered_remove_soa`.
|
|
// Note: If the index is out of bounds, this procedure will panic.
|
|
@builtin
|
|
unordered_remove_soa :: proc(array: ^$T/#soa[dynamic]$E, index: int, loc := #caller_location) #no_bounds_check {
|
|
bounds_check_error_loc(loc, index, len(array))
|
|
if index+1 < len(array) {
|
|
ti := type_info_of(typeid_of(T))
|
|
ti = type_info_base(ti)
|
|
si := &ti.variant.(Type_Info_Struct)
|
|
|
|
field_count: uintptr
|
|
when intrinsics.type_is_array(E) {
|
|
field_count = len(E)
|
|
} else {
|
|
field_count = uintptr(intrinsics.type_struct_field_count(E))
|
|
}
|
|
|
|
data := uintptr(array)
|
|
for i in 0..<field_count {
|
|
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
|
|
|
|
offset := rawptr((^uintptr)(data)^ + uintptr(index*type.size))
|
|
final := rawptr((^uintptr)(data)^ + uintptr((len(array)-1)*type.size))
|
|
mem_copy(offset, final, type.size)
|
|
data += size_of(rawptr)
|
|
}
|
|
}
|
|
raw_soa_footer_dynamic_array(array).len -= 1
|
|
}
|
|
|
|
// `ordered_remove_soa` removed the element at the specified `index` whilst keeping the order of the other elements.
|
|
//
|
|
// Note: This is an O(N) operation.
|
|
// Note: If you the elements do not have to remain in their order, prefer `unordered_remove_soa`.
|
|
// Note: If the index is out of bounds, this procedure will panic.
|
|
@builtin
|
|
ordered_remove_soa :: proc(array: ^$T/#soa[dynamic]$E, index: int, loc := #caller_location) #no_bounds_check {
|
|
bounds_check_error_loc(loc, index, len(array))
|
|
if index+1 < len(array) {
|
|
ti := type_info_of(typeid_of(T))
|
|
ti = type_info_base(ti)
|
|
si := &ti.variant.(Type_Info_Struct)
|
|
|
|
field_count: uintptr
|
|
when intrinsics.type_is_array(E) {
|
|
field_count = len(E)
|
|
} else {
|
|
field_count = uintptr(intrinsics.type_struct_field_count(E))
|
|
}
|
|
|
|
data := uintptr(array)
|
|
for i in 0..<field_count {
|
|
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
|
|
|
|
offset := (^uintptr)(data)^ + uintptr(index*type.size)
|
|
length := type.size*(len(array) - index - 1)
|
|
mem_copy(rawptr(offset), rawptr(offset + uintptr(type.size)), length)
|
|
data += size_of(rawptr)
|
|
}
|
|
}
|
|
raw_soa_footer_dynamic_array(array).len -= 1
|
|
}
|