From 9bd2186c8a976bc11756630507c7498bcd743158 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 12 Dec 2025 00:35:40 +0000 Subject: [PATCH 1/6] `core:container/xar` --- core/container/xar/xar.odin | 128 ++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 core/container/xar/xar.odin diff --git a/core/container/xar/xar.odin b/core/container/xar/xar.odin new file mode 100644 index 000000000..48bc97edb --- /dev/null +++ b/core/container/xar/xar.odin @@ -0,0 +1,128 @@ +package container_xar + +@(require) import "core:mem" +@(require) import "base:intrinsics" +@(require) import "base:runtime" + +PLATFORM_BITS :: 8*size_of(uint) +_LOG2_PLATFORM_BITS :: intrinsics.constant_log2(PLATFORM_BITS) + +MAX_SHIFT :: PLATFORM_BITS>>1 + +Xar :: struct($T: typeid, $SHIFT: uint) where 0 < SHIFT, SHIFT <= MAX_SHIFT { + chunks: [(1 << (_LOG2_PLATFORM_BITS - intrinsics.constant_log2(SHIFT))) + 1][^]T, + len: int, + allocator: mem.Allocator, +} + +init :: proc(x: ^$X/Xar($T, $SHIFT), allocator := context.allocator) { + x^ = {allocator = allocator} +} + +destroy :: proc(x: ^$X/Xar($T, $SHIFT)) { + #reverse for c, i in x.chunks { + if c != nil { + n := 1 << (SHIFT + uint(i if i > 0 else 1) - 1) + size_in_bytes := n * size_of(T) + mem.free_with_size(c, size_in_bytes, x.allocator) + } + } + x^ = {} +} + +clear :: proc(x: $X/Xar($T, $SHIFT)) { + x.len = 0 +} + +@(require_results) +meta_get :: #force_inline proc($SHIFT: uint, index: uint) -> (chunk_idx, elem_idx, chunk_cap: uint) { + elem_idx = index + chunk_cap = uint(1) << SHIFT + chunk_idx = 0 + + index_shift := index >> SHIFT + if index_shift > 0 { + N :: 8*size_of(uint)-1 + CLZ :: intrinsics.count_leading_zeros + chunk_idx = N-CLZ(index_shift) // MSB(index_shift) + + chunk_cap = 1 << (chunk_idx + SHIFT) + elem_idx -= chunk_cap + chunk_idx += 1 + } + + return +} + +@(require_results) +get :: proc(x: ^$X/Xar($T, $SHIFT), #any_int index: int, loc := #caller_location) -> (val: T) #no_bounds_check { + runtime.bounds_check_error_loc(loc, index, x.len) + chunk_idx, elem_idx, _ := meta_get(SHIFT, uint(index)) + return x.chunks[chunk_idx][elem_idx] +} + +@(require_results) +get_ptr :: proc(x: ^$X/Xar($T, $SHIFT), #any_int index: int, loc := #caller_location) -> (val: ^T) #no_bounds_check { + runtime.bounds_check_error_loc(loc, index, x.len) + chunk_idx, elem_idx, _ := meta_get(SHIFT, uint(index)) + return &x.chunks[chunk_idx][elem_idx] +} + +set :: proc(x: ^$X/Xar($T, $SHIFT), #any_int index: int, value: T, loc := #caller_location) #no_bounds_check { + runtime.bounds_check_error_loc(loc, index, x.len) + chunk_idx, elem_idx, _ := meta_get(SHIFT, uint(index)) + x.chunks[chunk_idx][elem_idx] = value +} + +append :: proc{push_back_elem, push_back_elems} +push_back :: proc{push_back_elem, push_back_elems} + +push_back_elem :: proc(x: ^$X/Xar($T, $SHIFT), value: T, loc := #caller_location) -> (n: int, err: mem.Allocator_Error) { + chunk_idx, elem_idx, chunk_cap := meta_get(SHIFT, uint(x.len)) + if x.chunks[chunk_idx] == nil { + x.chunks[chunk_idx] = make([^]T, chunk_cap, x.allocator) or_return + } + x.chunks[chunk_idx][elem_idx] = value + x.len += 1 + n = 1 + return +} + +push_back_elems :: proc(x: ^$X/Xar($T, $SHIFT), values: ..T, loc := #caller_location) -> (n: int, err: mem.Allocator_Error) { + for value in values { + n += push_back_elem(x, value, loc) or_return + } + return +} + +pop :: proc(x: ^$X/Xar($T, $SHIFT), loc := #caller_location) -> (val: T) { + assert(x.len > 0, loc=loc) + index := uint(x.len-1) + chunk_idx, elem_idx, _ := meta_get(SHIFT, index) + x.len -= 1 + return x.chunks[chunk_idx][elem_idx] +} + +@(require_results) +pop_safe :: proc(x: ^$X/Xar($T, $SHIFT)) -> (val: T, ok: bool) { + if x.len == 0 { + return + } + index := uint(x.len-1) + chunk_idx, elem_idx, _ := meta_get(SHIFT, index) + x.len -= 1 + + val = x.chunks[chunk_idx][elem_idx] + ok = true + return +} + +unordered_remove :: proc(x: ^$X/Xar($T, $SHIFT), #any_int index: int, loc := #caller_location) { + runtime.bounds_check_error_loc(loc, index, x.len) + n := x.len-1 + if index != n { + end := get(x, n) + set(x, index, end) + } + x.len -= 1 +} \ No newline at end of file From 1e88cd6697b2a0db6e4039bfbaa36905a45f86f2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 12 Dec 2025 00:39:16 +0000 Subject: [PATCH 2/6] Add `container/xar` to `examples/all` --- examples/all/all_main.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index cda04278e..7895b4640 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -24,6 +24,7 @@ package all @(require) import "core:container/intrusive/list" @(require) import "core:container/rbtree" @(require) import "core:container/topological_sort" +@(require) import "core:container/xar" @(require) import "core:crypto" @(require) import "core:crypto/aead" From a2d361f17d75808d21bc2948ce2883dc243dc27d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 12 Dec 2025 00:40:03 +0000 Subject: [PATCH 3/6] Add basic docs --- core/container/xar/xar.odin | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/core/container/xar/xar.odin b/core/container/xar/xar.odin index 48bc97edb..746c77f77 100644 --- a/core/container/xar/xar.odin +++ b/core/container/xar/xar.odin @@ -1,3 +1,20 @@ +/* + Exponential Array (Xar). + + A fixed inline array of multi-pointers to exponentially growing chunks. + + Xar(T, SHIFT) + + + Size of chunks: + len(chunks[0]) == 1<<(SHIFT+0) + len(chunks[1]) == 1<<(SHIFT+0) + len(chunks[2]) == 1<<(SHIFT+1) + len(chunks[3]) == 1<<(SHIFT+2) + + + For more information: https://azmr.uk/dyn/#exponential-arrayxar +*/ package container_xar @(require) import "core:mem" From 23ddb8dd3f7fb1ed1cd6b1cd11aacdc9cdaccd7d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 12 Dec 2025 01:00:11 +0000 Subject: [PATCH 4/6] Add `push_back_elem_and_get_ptr` --- core/container/xar/xar.odin | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/core/container/xar/xar.odin b/core/container/xar/xar.odin index 746c77f77..29a8c3e00 100644 --- a/core/container/xar/xar.odin +++ b/core/container/xar/xar.odin @@ -112,6 +112,22 @@ push_back_elems :: proc(x: ^$X/Xar($T, $SHIFT), values: ..T, loc := #caller_loca return } +append_and_get_ptr :: push_back_elem_and_get_ptr + +@(require_results) +push_back_elem_and_get_ptr :: proc(x: ^$X/Xar($T, $SHIFT), value: T, loc := #caller_location) -> (ptr: ^T, err: mem.Allocator_Error) { + chunk_idx, elem_idx, chunk_cap := meta_get(SHIFT, uint(x.len)) + if x.chunks[chunk_idx] == nil { + x.chunks[chunk_idx] = make([^]T, chunk_cap, x.allocator) or_return + } + x.chunks[chunk_idx][elem_idx] = value + x.len += 1 + n = 1 + ptr = &x.chunks[chunk_idx][elem_idx] + return +} + + pop :: proc(x: ^$X/Xar($T, $SHIFT), loc := #caller_location) -> (val: T) { assert(x.len > 0, loc=loc) index := uint(x.len-1) From d43b00bb10f070a66fff6578cb6abd296e50acaa Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 12 Dec 2025 01:44:31 +0000 Subject: [PATCH 5/6] Add basic docs --- core/container/xar/xar.odin | 87 ++++++++++++++++++++++++++++--------- 1 file changed, 67 insertions(+), 20 deletions(-) diff --git a/core/container/xar/xar.odin b/core/container/xar/xar.odin index 29a8c3e00..e10033d3e 100644 --- a/core/container/xar/xar.odin +++ b/core/container/xar/xar.odin @@ -1,17 +1,8 @@ /* Exponential Array (Xar). - A fixed inline array of multi-pointers to exponentially growing chunks. - - Xar(T, SHIFT) - - - Size of chunks: - len(chunks[0]) == 1<<(SHIFT+0) - len(chunks[1]) == 1<<(SHIFT+0) - len(chunks[2]) == 1<<(SHIFT+1) - len(chunks[3]) == 1<<(SHIFT+2) - + A fixed inline array of multi-pointers to exponentially growing chunks, + allowing for stable memory addresses for elements. For more information: https://azmr.uk/dyn/#exponential-arrayxar */ @@ -26,6 +17,18 @@ _LOG2_PLATFORM_BITS :: intrinsics.constant_log2(PLATFORM_BITS) MAX_SHIFT :: PLATFORM_BITS>>1 +/* + An Exponential Array (Xar) is a fixed inline array of multi-pointers to exponentially growing chunks, + allowing for stable memory addresses for elements. + + The chunk length uses as many chunks as much as addressable memory the machine provides. + + Size of chunks: + len(chunks[0]) == 1<<(SHIFT+0) + len(chunks[1]) == 1<<(SHIFT+0) + len(chunks[2]) == 1<<(SHIFT+1) + len(chunks[3]) == 1<<(SHIFT+2) +*/ Xar :: struct($T: typeid, $SHIFT: uint) where 0 < SHIFT, SHIFT <= MAX_SHIFT { chunks: [(1 << (_LOG2_PLATFORM_BITS - intrinsics.constant_log2(SHIFT))) + 1][^]T, len: int, @@ -47,12 +50,30 @@ destroy :: proc(x: ^$X/Xar($T, $SHIFT)) { x^ = {} } +// Resets the array's length to zero. clear :: proc(x: $X/Xar($T, $SHIFT)) { x.len = 0 } +// Returns the length of the exponential-array @(require_results) -meta_get :: #force_inline proc($SHIFT: uint, index: uint) -> (chunk_idx, elem_idx, chunk_cap: uint) { +len :: proc(x: $X/Xar($T, $SHIFT)) -> int { + return x.len +} + +// Returns the number of allocated elements +@(require_results) +cap :: proc(x: $X/Xar($T, $SHIFT)) -> int { + #reverse for c, i in x.chunks { + if c != nil { + return 1 << (SHIFT + uint(i if i > 0 else 1)) + } + } + return 0 +} + +@(require_results) +_meta_get :: #force_inline proc($SHIFT: uint, index: uint) -> (chunk_idx, elem_idx, chunk_cap: uint) { elem_idx = index chunk_cap = uint(1) << SHIFT chunk_idx = 0 @@ -71,31 +92,40 @@ meta_get :: #force_inline proc($SHIFT: uint, index: uint) -> (chunk_idx, elem_id return } +// Gets the element at the index @(require_results) get :: proc(x: ^$X/Xar($T, $SHIFT), #any_int index: int, loc := #caller_location) -> (val: T) #no_bounds_check { runtime.bounds_check_error_loc(loc, index, x.len) - chunk_idx, elem_idx, _ := meta_get(SHIFT, uint(index)) + chunk_idx, elem_idx, _ := _meta_get(SHIFT, uint(index)) return x.chunks[chunk_idx][elem_idx] } +// Gets the pointer of the element at the index @(require_results) get_ptr :: proc(x: ^$X/Xar($T, $SHIFT), #any_int index: int, loc := #caller_location) -> (val: ^T) #no_bounds_check { runtime.bounds_check_error_loc(loc, index, x.len) - chunk_idx, elem_idx, _ := meta_get(SHIFT, uint(index)) + chunk_idx, elem_idx, _ := _meta_get(SHIFT, uint(index)) return &x.chunks[chunk_idx][elem_idx] } +// Sets the value at the index set :: proc(x: ^$X/Xar($T, $SHIFT), #any_int index: int, value: T, loc := #caller_location) #no_bounds_check { runtime.bounds_check_error_loc(loc, index, x.len) - chunk_idx, elem_idx, _ := meta_get(SHIFT, uint(index)) + chunk_idx, elem_idx, _ := _meta_get(SHIFT, uint(index)) x.chunks[chunk_idx][elem_idx] = value } append :: proc{push_back_elem, push_back_elems} push_back :: proc{push_back_elem, push_back_elems} +// `push_back_elem` pushes back (appends) an element to an exponential array push_back_elem :: proc(x: ^$X/Xar($T, $SHIFT), value: T, loc := #caller_location) -> (n: int, err: mem.Allocator_Error) { - chunk_idx, elem_idx, chunk_cap := meta_get(SHIFT, uint(x.len)) + if x.allocator.procedure == nil { + // to minic `[dynamic]T` behaviour + x.allocator = context.allocator + } + + chunk_idx, elem_idx, chunk_cap := _meta_get(SHIFT, uint(x.len)) if x.chunks[chunk_idx] == nil { x.chunks[chunk_idx] = make([^]T, chunk_cap, x.allocator) or_return } @@ -105,6 +135,7 @@ push_back_elem :: proc(x: ^$X/Xar($T, $SHIFT), value: T, loc := #caller_location return } +// `push_back_elems` pushes back (appends) multiple elements to an exponential array push_back_elems :: proc(x: ^$X/Xar($T, $SHIFT), values: ..T, loc := #caller_location) -> (n: int, err: mem.Allocator_Error) { for value in values { n += push_back_elem(x, value, loc) or_return @@ -114,9 +145,15 @@ push_back_elems :: proc(x: ^$X/Xar($T, $SHIFT), values: ..T, loc := #caller_loca append_and_get_ptr :: push_back_elem_and_get_ptr +// `push_back_elem` pushes back (appends) an element to an exponential array and returns its pointer @(require_results) push_back_elem_and_get_ptr :: proc(x: ^$X/Xar($T, $SHIFT), value: T, loc := #caller_location) -> (ptr: ^T, err: mem.Allocator_Error) { - chunk_idx, elem_idx, chunk_cap := meta_get(SHIFT, uint(x.len)) + if x.allocator.procedure == nil { + // to minic `[dynamic]T` behaviour + x.allocator = context.allocator + } + + chunk_idx, elem_idx, chunk_cap := _meta_get(SHIFT, uint(x.len)) if x.chunks[chunk_idx] == nil { x.chunks[chunk_idx] = make([^]T, chunk_cap, x.allocator) or_return } @@ -127,22 +164,26 @@ push_back_elem_and_get_ptr :: proc(x: ^$X/Xar($T, $SHIFT), value: T, loc := #cal return } - +// `pop` will remove and return the end value of an exponential array `x` and reduces the length of the array by 1. +// +// Note: If the exponential array has no elements (`xar.len(x) == 0`), this procedure will panic. pop :: proc(x: ^$X/Xar($T, $SHIFT), loc := #caller_location) -> (val: T) { assert(x.len > 0, loc=loc) index := uint(x.len-1) - chunk_idx, elem_idx, _ := meta_get(SHIFT, index) + chunk_idx, elem_idx, _ := _meta_get(SHIFT, index) x.len -= 1 return x.chunks[chunk_idx][elem_idx] } +// `pop_safe` trys to remove and return the end value of dynamic array `x` and reduces the length of the array by 1. +// If the operation is not possible, it will return false. @(require_results) pop_safe :: proc(x: ^$X/Xar($T, $SHIFT)) -> (val: T, ok: bool) { if x.len == 0 { return } index := uint(x.len-1) - chunk_idx, elem_idx, _ := meta_get(SHIFT, index) + chunk_idx, elem_idx, _ := _meta_get(SHIFT, index) x.len -= 1 val = x.chunks[chunk_idx][elem_idx] @@ -150,6 +191,12 @@ pop_safe :: proc(x: ^$X/Xar($T, $SHIFT)) -> (val: T, ok: bool) { return } +// `unordered_remove` 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 exponential array by 1. +// +// Note: This is an O(1) operation. +// Note: This is currently no procedure that is the equivalent of an "ordered_remove" +// Note: If the index is out of bounds, this procedure will panic. unordered_remove :: proc(x: ^$X/Xar($T, $SHIFT), #any_int index: int, loc := #caller_location) { runtime.bounds_check_error_loc(loc, index, x.len) n := x.len-1 From a66b84372abf588b7737fe721d7557df56fed17e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 12 Dec 2025 14:41:01 +0000 Subject: [PATCH 6/6] Improve docs and add `Iterator` --- core/container/xar/xar.odin | 326 +++++++++++++++++++++++++++++++++--- 1 file changed, 301 insertions(+), 25 deletions(-) diff --git a/core/container/xar/xar.odin b/core/container/xar/xar.odin index e10033d3e..616a05e06 100644 --- a/core/container/xar/xar.odin +++ b/core/container/xar/xar.odin @@ -1,10 +1,28 @@ /* Exponential Array (Xar). - A fixed inline array of multi-pointers to exponentially growing chunks, - allowing for stable memory addresses for elements. + A dynamically growing array using exponentially-sized chunks, providing stable + memory addresses for all elements. Unlike `[dynamic]T`, elements are never + moved once allocated, making it safe to hold pointers to elements. For more information: https://azmr.uk/dyn/#exponential-arrayxar + + Example: + + import "core:container/xar" + + example :: proc() { + x: xar.Xar(int, 4) + defer xar.destroy(&x) + + xar.push_back(&x, 10) + xar.push_back(&x, 20) + xar.push_back(&x, 30) + + ptr := xar.get_ptr(&x, 1) // ptr remains valid after more push_backs + xar.push_back(&x, 40) + fmt.println(ptr^) // prints 20 + } */ package container_xar @@ -18,16 +36,39 @@ _LOG2_PLATFORM_BITS :: intrinsics.constant_log2(PLATFORM_BITS) MAX_SHIFT :: PLATFORM_BITS>>1 /* - An Exponential Array (Xar) is a fixed inline array of multi-pointers to exponentially growing chunks, - allowing for stable memory addresses for elements. + An Exponential Array with stable element addresses. - The chunk length uses as many chunks as much as addressable memory the machine provides. + Unlike `[dynamic]T` which reallocates and moves elements when growing, `Xar` + allocates separate chunks of exponentially increasing size. This guarantees + that pointers to elements remain valid for the lifetime of the container. - Size of chunks: - len(chunks[0]) == 1<<(SHIFT+0) - len(chunks[1]) == 1<<(SHIFT+0) - len(chunks[2]) == 1<<(SHIFT+1) - len(chunks[3]) == 1<<(SHIFT+2) + Fields: + - `chunks`: Fixed array of multi-pointers to allocated chunks + - `len`: Number of elements currently stored + - `allocator`: Allocator used for chunk allocations + + Type Parameters: + - `T`: The element type + - `SHIFT`: Controls initial chunk size (1 << SHIFT). Must be in range (0, MAX_SHIFT]. + Larger values mean fewer, bigger chunks. Recommended: 4-8. + + Chunk sizes grow as: + - `chunks[0]`: 1 << SHIFT elements + - `chunks[1]`: 1 << SHIFT elements + - `chunks[2]`: 1 << (SHIFT + 1) elements + - `chunks[3]`: 1 << (SHIFT + 2) elements + - `chunks[4]`: 1 << (SHIFT + 3) elements + - ...and so on + + Example: + + import "core:container/xar" + + example :: proc() { + // Xar with initial chunk size of 16 (1 << 4) + x: xar.Xar(My_Struct, 4) + defer xar.destroy(&x) + } */ Xar :: struct($T: typeid, $SHIFT: uint) where 0 < SHIFT, SHIFT <= MAX_SHIFT { chunks: [(1 << (_LOG2_PLATFORM_BITS - intrinsics.constant_log2(SHIFT))) + 1][^]T, @@ -35,10 +76,24 @@ Xar :: struct($T: typeid, $SHIFT: uint) where 0 < SHIFT, SHIFT <= MAX_SHIFT { allocator: mem.Allocator, } + +/* +Initializes an exponential array with the given allocator. + +**Inputs** +- `x`: Pointer to the exponential array to initialize +- `allocator`: Allocator to use for chunk allocations (defaults to context.allocator) +*/ init :: proc(x: ^$X/Xar($T, $SHIFT), allocator := context.allocator) { x^ = {allocator = allocator} } +/* +Frees all allocated chunks and resets the exponential array. + +**Inputs** +- `x`: Pointer to the exponential array to destroy +*/ destroy :: proc(x: ^$X/Xar($T, $SHIFT)) { #reverse for c, i in x.chunks { if c != nil { @@ -50,8 +105,11 @@ destroy :: proc(x: ^$X/Xar($T, $SHIFT)) { x^ = {} } -// Resets the array's length to zero. -clear :: proc(x: $X/Xar($T, $SHIFT)) { +/* +Resets the array's length to zero without freeing memory. +Allocated chunks are retained for reuse. +*/ +clear :: proc(x: ^$X/Xar($T, $SHIFT)) { x.len = 0 } @@ -72,6 +130,7 @@ cap :: proc(x: $X/Xar($T, $SHIFT)) -> int { return 0 } +// Internal: computes chunk index, element index within chunk, and chunk capacity for a given index. @(require_results) _meta_get :: #force_inline proc($SHIFT: uint, index: uint) -> (chunk_idx, elem_idx, chunk_cap: uint) { elem_idx = index @@ -91,8 +150,16 @@ _meta_get :: #force_inline proc($SHIFT: uint, index: uint) -> (chunk_idx, elem_i return } +/* +Get a copy of the element at the specified index. -// Gets the element at the index +**Inputs** +- `x`: Pointer to the exponential array +- `index`: Position of the element (0-indexed) + +**Returns** +- a copy of the element +*/ @(require_results) get :: proc(x: ^$X/Xar($T, $SHIFT), #any_int index: int, loc := #caller_location) -> (val: T) #no_bounds_check { runtime.bounds_check_error_loc(loc, index, x.len) @@ -100,7 +167,38 @@ get :: proc(x: ^$X/Xar($T, $SHIFT), #any_int index: int, loc := #caller_location return x.chunks[chunk_idx][elem_idx] } -// Gets the pointer of the element at the index +/* +Get a pointer to the element at the specified index. + +The returned pointer remains valid even after additional elements are added, +as long as the element is not removed and the array is not destroyed. + +**Inputs** +- `x`: Pointer to the exponential array +- `index`: Position of the element (0-indexed) + +**Returns** +- a stable pointer to the element + +Example: + + import "core:container/xar" + + get_ptr_example :: proc() { + x: xar.Xar(int, 4) + defer xar.destroy(&x) + + xar.push_back(&x, 100) + ptr := xar.get_ptr(&x, 0) + + // Pointer remains valid after growing + for i in 0..<1000 { + xar.push_back(&x, i) + } + + fmt.println(ptr^) // Still prints 100 + } +*/ @(require_results) get_ptr :: proc(x: ^$X/Xar($T, $SHIFT), #any_int index: int, loc := #caller_location) -> (val: ^T) #no_bounds_check { runtime.bounds_check_error_loc(loc, index, x.len) @@ -108,7 +206,14 @@ get_ptr :: proc(x: ^$X/Xar($T, $SHIFT), #any_int index: int, loc := #caller_loca return &x.chunks[chunk_idx][elem_idx] } -// Sets the value at the index +/* +Set the element at the specified index to the given value. + +**Inputs** +- `x`: Pointer to the exponential array +- `index`: Position of the element (0-indexed) +- `value`: The value to set +*/ set :: proc(x: ^$X/Xar($T, $SHIFT), #any_int index: int, value: T, loc := #caller_location) #no_bounds_check { runtime.bounds_check_error_loc(loc, index, x.len) chunk_idx, elem_idx, _ := _meta_get(SHIFT, uint(index)) @@ -118,7 +223,33 @@ set :: proc(x: ^$X/Xar($T, $SHIFT), #any_int index: int, value: T, loc := #calle append :: proc{push_back_elem, push_back_elems} push_back :: proc{push_back_elem, push_back_elems} -// `push_back_elem` pushes back (appends) an element to an exponential array +/* +Append an element to the end of the exponential array. +Allocates a new chunk if necessary. Existing elements aren't moved, and their pointers remain stable. + +**Inputs** +- `x`: Pointer to the exponential array +- `value`: The element to append + +**Returns** +- number of elements added (always 1 on success) +- allocation error if chunk allocation failed + +Example: + + import "core:container/xar" + + push_back_example :: proc() { + x: xar.Xar(string, 4) + defer xar.destroy(&x) + + xar.push_back(&x, "hello") + xar.push_back(&x, "world") + + fmt.println(xar.get(&x, 0)) // hello + fmt.println(xar.get(&x, 1)) // world + } +*/ push_back_elem :: proc(x: ^$X/Xar($T, $SHIFT), value: T, loc := #caller_location) -> (n: int, err: mem.Allocator_Error) { if x.allocator.procedure == nil { // to minic `[dynamic]T` behaviour @@ -135,7 +266,17 @@ push_back_elem :: proc(x: ^$X/Xar($T, $SHIFT), value: T, loc := #caller_location return } -// `push_back_elems` pushes back (appends) multiple elements to an exponential array +/* +Append multiple elements to the end of the exponential array. + +**Inputs** +- `x`: Pointer to the exponential array +- `values`: The elements to append + +**Returns** +- number of elements successfully added +- allocation error if chunk allocation failed (partial append possible) +*/ push_back_elems :: proc(x: ^$X/Xar($T, $SHIFT), values: ..T, loc := #caller_location) -> (n: int, err: mem.Allocator_Error) { for value in values { n += push_back_elem(x, value, loc) or_return @@ -144,8 +285,31 @@ push_back_elems :: proc(x: ^$X/Xar($T, $SHIFT), values: ..T, loc := #caller_loca } append_and_get_ptr :: push_back_elem_and_get_ptr +/* +Append an element and return a stable pointer to it. +This is useful when you need to initialize a complex struct in-place or +retain a reference to the newly added element. -// `push_back_elem` pushes back (appends) an element to an exponential array and returns its pointer +**Inputs** +- `x`: Pointer to the exponential array +- `value`: The element to append + +**Returns** +- a stable pointer to the newly added element +- allocation error if chunk allocation failed + +Example: + + import "core:container/xar" + + push_back_and_get_ptr_example :: proc() { + x: xar.Xar(My_Struct, 4) + defer xar.destroy(&x) + + ptr := xar.push_back_elem_and_get_ptr(&x, My_Struct{}) or_else panic("alloc failed") + ptr.field = 42 // Initialize in-place + } +*/ @(require_results) push_back_elem_and_get_ptr :: proc(x: ^$X/Xar($T, $SHIFT), value: T, loc := #caller_location) -> (ptr: ^T, err: mem.Allocator_Error) { if x.allocator.procedure == nil { @@ -191,12 +355,35 @@ pop_safe :: proc(x: ^$X/Xar($T, $SHIFT)) -> (val: T, ok: bool) { return } -// `unordered_remove` 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 exponential array by 1. -// -// Note: This is an O(1) operation. -// Note: This is currently no procedure that is the equivalent of an "ordered_remove" -// Note: If the index is out of bounds, this procedure will panic. +/* + `unordered_remove` 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 exponential array by 1. + + Note: This is an O(1) operation. + Note: This is currently no procedure that is the equivalent of an "ordered_remove" + Note: If the index is out of bounds, this procedure will panic. + + Note: Pointers to the last element become invalid (it gets moved). Pointers to other elements remain valid. + + Example: + + import "core:encoding/xar" + + unordered_remove_example :: proc() { + x: xar.Xar(int, 4) + defer xar.destroy(&x) + + xar.push_back(&x, 10) + xar.push_back(&x, 20) + xar.push_back(&x, 30) + + xar.unordered_remove(&x, 0) // Removes 10, replaces with 30 + + // Array now contains [30, 20] + fmt.println(xar.get(&x, 0)) // 30 + fmt.println(xar.get(&x, 1)) // 20 + } +*/ unordered_remove :: proc(x: ^$X/Xar($T, $SHIFT), #any_int index: int, loc := #caller_location) { runtime.bounds_check_error_loc(loc, index, x.len) n := x.len-1 @@ -205,4 +392,93 @@ unordered_remove :: proc(x: ^$X/Xar($T, $SHIFT), #any_int index: int, loc := #ca set(x, index, end) } x.len -= 1 -} \ No newline at end of file +} + + +/* +Iterator state for traversing a `Xar`. + +Fields: +- `xar`: Pointer to the exponential array being iterated +- `idx`: Current iteration index +*/ +Iterator :: struct($T: typeid, $SHIFT: uint) { + xar: ^Xar(T, SHIFT), + idx: int, +} + +/* +Create an iterator for traversing the exponential array. + +**Inputs** +- `xar`: Pointer to the exponential array + +**Returns** +- an iterator positioned at the start + +Example: + + import "lib:xar" + + iteration_example :: proc() { + x: xar.Xar(int, 4) + defer xar.destroy(&x) + + xar.push_back(&x, 10) + xar.push_back(&x, 20) + xar.push_back(&x, 30) + + it := xar.iterator(&x) + for val in xar.iterate_by_ptr(&it) { + fmt.println(val^) + } + } + +Output: + + 10 + 20 + 30 +*/ +iterator :: proc(xar: ^$X/Xar($T, $SHIFT)) -> Iterator(T, SHIFT) { + return {xar = auto_cast xar, idx = 0} +} + +/* +Advance the iterator and returns the next element. + +**Inputs** +- `it`: Pointer to the iterator + +**Returns** +- current element +- `true` if an element was returned, `false` if iteration is complete +*/ +iterate_by_val :: proc(it: ^Iterator($T, $SHIFT)) -> (val: T, ok: bool) { + if it.idx >= it.xar.len { + return + } + val = get(it.xar, it.idx) + it.idx += 1 + return val, true +} + + +/* +Advance the iterator and returns a pointer to the next element. + +**Inputs** +- `it`: Pointer to the iterator + +**Returns** +- pointer to the current element +- `true` if an element was returned, `false` if iteration is complete +*/ +iterate_by_ptr :: proc(it: ^Iterator($T, $SHIFT)) -> (val: ^T, ok: bool) { + if it.idx >= it.xar.len { + return + } + val = get_ptr(it.xar, it.idx) + it.idx += 1 + return val, true +}