diff options
| author | Feoramund <161657516+Feoramund@users.noreply.github.com> | 2025-06-11 08:05:27 -0400 |
|---|---|---|
| committer | Feoramund <161657516+Feoramund@users.noreply.github.com> | 2025-06-11 11:55:30 -0400 |
| commit | 6cb84e467bd9ea4b7ebf36640a192fe6e3e00fd8 (patch) | |
| tree | a3c09ae4a2c58753b4aa861186c075b9d4d01ba4 /core/container | |
| parent | 862442511a2684adecafb3688f2b7ad172a3a47d (diff) | |
container/queue: Document the package
Diffstat (limited to 'core/container')
| -rw-r--r-- | core/container/queue/queue.odin | 215 |
1 files changed, 193 insertions, 22 deletions
diff --git a/core/container/queue/queue.odin b/core/container/queue/queue.odin index c58da3e13..dd22a13d0 100644 --- a/core/container/queue/queue.odin +++ b/core/container/queue/queue.odin @@ -4,7 +4,13 @@ import "base:builtin" import "base:runtime" _ :: runtime -// Dynamically resizable double-ended queue/ring-buffer +/* +`Queue` is a dynamically resizable double-ended queue/ring-buffer. + +Being double-ended means that either end may be pushed onto or popped from +across the same block of memory, in any order, thus providing both stack and +queue-like behaviors in the same data structure. +*/ Queue :: struct($T: typeid) { data: [dynamic]T, len: uint, @@ -13,7 +19,9 @@ Queue :: struct($T: typeid) { DEFAULT_CAPACITY :: 16 -// Procedure to initialize a queue +/* +Initialize a `Queue` with a starting `capacity` and an `allocator`. +*/ init :: proc(q: ^$Q/Queue($T), capacity := DEFAULT_CAPACITY, allocator := context.allocator) -> runtime.Allocator_Error { if q.data.allocator.procedure == nil { q.data.allocator = allocator @@ -22,9 +30,17 @@ init :: proc(q: ^$Q/Queue($T), capacity := DEFAULT_CAPACITY, allocator := contex return reserve(q, capacity) } -// Procedure to initialize a queue from a fixed backing slice. -// The contents of the `backing` will be overwritten as items are pushed onto the `Queue`. -// Any previous contents are not available. +/* +Initialize a `Queue` from a fixed `backing` slice into which modifications are +made directly. + +The contents of the `backing` will be overwritten as items are pushed onto the +`Queue`. Any previous contents will not be available through the API but are +not explicitly zeroed either. + +Note that procedures which need space to work (`push_back`, ...) will fail if +the backing slice runs out of space. +*/ init_from_slice :: proc(q: ^$Q/Queue($T), backing: []T) -> bool { clear(q) q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{ @@ -36,8 +52,14 @@ init_from_slice :: proc(q: ^$Q/Queue($T), backing: []T) -> bool { return true } -// Procedure to initialize a queue from a fixed backing slice. -// Existing contents are preserved and available on the queue. +/* +Initialize a `Queue` from a fixed `backing` slice into which modifications are +made directly. + +The contents of the queue will start out with all of the elements in `backing`, +effectively creating a full queue from the slice. As such, no procedures will +be able to add more elements to the queue until some are taken off. +*/ init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool { clear(q) q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{ @@ -50,27 +72,45 @@ init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool { return true } -// Procedure to destroy a queue +/* +Delete memory that has been dynamically allocated from a `Queue` that was setup with `init`. + +Note that this procedure should not be used on queues setup with +`init_from_slice` or `init_with_contents`, as neither of those procedures keep +track of the allocator state of the underlying `backing` slice. +*/ destroy :: proc(q: ^$Q/Queue($T)) { delete(q.data) } -// The length of the queue +/* +Return the length of the queue. +*/ len :: proc(q: $Q/Queue($T)) -> int { return int(q.len) } -// The current capacity of the queue +/* +Return the capacity of the queue. +*/ cap :: proc(q: $Q/Queue($T)) -> int { return builtin.len(q.data) } -// Remaining space in the queue (cap-len) +/* +Return the remaining space in the queue. + +This will be `cap() - len()`. +*/ space :: proc(q: $Q/Queue($T)) -> int { return builtin.len(q.data) - int(q.len) } -// Reserve enough space for at least the specified capacity +/* +Reserve enough space in the queue for at least the specified capacity. + +This may return an error if allocation failed. +*/ reserve :: proc(q: ^$Q/Queue($T), capacity: int) -> runtime.Allocator_Error { if capacity > space(q^) { return _grow(q, uint(capacity)) @@ -78,7 +118,11 @@ reserve :: proc(q: ^$Q/Queue($T), capacity: int) -> runtime.Allocator_Error { return nil } +/* +Get the element at index `i`. +This will raise a bounds checking error if `i` is an invalid index. +*/ get :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> T { runtime.bounds_check_error_loc(loc, i, int(q.len)) @@ -86,6 +130,11 @@ get :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> T { return q.data[idx] } +/* +Get a pointer to the element at index `i`. + +This will raise a bounds checking error if `i` is an invalid index. +*/ get_ptr :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> ^T { runtime.bounds_check_error_loc(loc, i, int(q.len)) @@ -93,6 +142,11 @@ get_ptr :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> ^ return &q.data[idx] } +/* +Set the element at index `i` to `val`. + +This will raise a bounds checking error if `i` is an invalid index. +*/ set :: proc(q: ^$Q/Queue($T), #any_int i: int, val: T, loc := #caller_location) { runtime.bounds_check_error_loc(loc, i, int(q.len)) @@ -100,6 +154,11 @@ set :: proc(q: ^$Q/Queue($T), #any_int i: int, val: T, loc := #caller_location) q.data[idx] = val } +/* +Get the element at the front of the queue. + +This will raise a bounds checking error if the queue is empty. +*/ front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> T { when !ODIN_NO_BOUNDS_CHECK { ensure(q.len > 0, "Queue is empty.", loc) @@ -107,6 +166,11 @@ front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> T { return q.data[q.offset] } +/* +Get a pointer to the element at the front of the queue. + +This will raise a bounds checking error if the queue is empty. +*/ front_ptr :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T { when !ODIN_NO_BOUNDS_CHECK { ensure(q.len > 0, "Queue is empty.", loc) @@ -114,6 +178,11 @@ front_ptr :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T { return &q.data[q.offset] } +/* +Get the element at the back of the queue. + +This will raise a bounds checking error if the queue is empty. +*/ back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> T { when !ODIN_NO_BOUNDS_CHECK { ensure(q.len > 0, "Queue is empty.", loc) @@ -121,6 +190,12 @@ back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> T { idx := (q.offset+uint(q.len - 1))%builtin.len(q.data) return q.data[idx] } + +/* +Get a pointer to the element at the back of the queue. + +This will raise a bounds checking error if the queue is empty. +*/ back_ptr :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T { when !ODIN_NO_BOUNDS_CHECK { ensure(q.len > 0, "Queue is empty.", loc) @@ -140,7 +215,30 @@ peek_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T { return back_ptr(q, loc) } -// Push an element to the back of the queue +/* +Push an element to the back of the queue. + +If there is no more space left and allocation fails to get more, this will +return false with an `Allocator_Error`. + +Example: + + import "base:runtime" + import "core:container/queue" + + // This demonstrates typical queue behavior (First-In First-Out). + main :: proc() { + q: queue.Queue(int) + queue.init(&q) + queue.push_back(&q, 1) + queue.push_back(&q, 2) + queue.push_back(&q, 3) + // q.data is now [1, 2, 3, ...] + assert(queue.pop_front(&q) == 1) + assert(queue.pop_front(&q) == 2) + assert(queue.pop_front(&q) == 3) + } +*/ push_back :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) { if space(q^) == 0 { _grow(q) or_return @@ -151,7 +249,30 @@ push_back :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocato return true, nil } -// Push an element to the front of the queue +/* +Push an element to the front of the queue. + +If there is no more space left and allocation fails to get more, this will +return false with an `Allocator_Error`. + +Example: + + import "base:runtime" + import "core:container/queue" + + // This demonstrates stack behavior (First-In Last-Out). + main :: proc() { + q: queue.Queue(int) + queue.init(&q) + queue.push_back(&q, 1) + queue.push_back(&q, 2) + queue.push_back(&q, 3) + // q.data is now [1, 2, 3, ...] + assert(queue.pop_back(&q) == 3) + assert(queue.pop_back(&q) == 2) + assert(queue.pop_back(&q) == 1) + } +*/ push_front :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) { if space(q^) == 0 { _grow(q) or_return @@ -162,8 +283,30 @@ push_front :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocat return true, nil } +/* +Pop an element from the back of the queue. + +This will raise a bounds checking error if the queue is empty. + +Example: -// Pop an element from the back of the queue + import "base:runtime" + import "core:container/queue" + + // This demonstrates stack behavior (First-In Last-Out) at the far end of the data array. + main :: proc() { + q: queue.Queue(int) + queue.init(&q) + queue.push_front(&q, 1) + queue.push_front(&q, 2) + queue.push_front(&q, 3) + // q.data is now [..., 3, 2, 1] + log.infof("%#v", q) + assert(queue.pop_front(&q) == 3) + assert(queue.pop_front(&q) == 2) + assert(queue.pop_front(&q) == 1) + } +*/ pop_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) { when !ODIN_NO_BOUNDS_CHECK { ensure(q.len > 0, "Queue is empty.", loc) @@ -173,7 +316,11 @@ pop_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) { elem = q.data[idx] return } -// Safely pop an element from the back of the queue + +/* +Pop an element from the back of the queue if one exists and return true. +Otherwise, return a nil element and false. +*/ pop_back_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) { if q.len > 0 { q.len -= 1 @@ -184,7 +331,11 @@ pop_back_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) { return } -// Pop an element from the front of the queue +/* +Pop an element from the front of the queue + +This will raise a bounds checking error if the queue is empty. +*/ pop_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) { when !ODIN_NO_BOUNDS_CHECK { ensure(q.len > 0, "Queue is empty.", loc) @@ -194,7 +345,11 @@ pop_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) { q.len -= 1 return } -// Safely pop an element from the front of the queue + +/* +Pop an element from the front of the queue if one exists and return true. +Otherwise, return a nil element and false. +*/ pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) { if q.len > 0 { elem = q.data[q.offset] @@ -205,7 +360,12 @@ pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) { return } -// Push multiple elements to the back of the queue +/* +Push many elements at once to the back of the queue. + +If there is not enough space left and allocation fails to get more, this will +return false with an `Allocator_Error`. +*/ push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime.Allocator_Error) { n := uint(builtin.len(elems)) if space(q^) < int(n) { @@ -224,7 +384,11 @@ push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime return true, nil } -// Consume `n` elements from the front of the queue +/* +Consume `n` elements from the back of the queue. + +This will raise a bounds checking error if the queue does not have enough elements. +*/ consume_front :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) { when !ODIN_NO_BOUNDS_CHECK { ensure(q.len >= uint(n), "Queue does not have enough elements to consume.", loc) @@ -236,7 +400,11 @@ consume_front :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) { } } -// Consume `n` elements from the back of the queue +/* +Consume `n` elements from the back of the queue. + +This will raise a bounds checking error if the queue does not have enough elements. +*/ consume_back :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) { when !ODIN_NO_BOUNDS_CHECK { ensure(q.len >= uint(n), "Queue does not have enough elements to consume.", loc) @@ -254,7 +422,10 @@ push :: proc{push_back, push_back_elems} append :: proc{push_back, push_back_elems} -// Clear the contents of the queue +/* +Reset the queue's length and offset to zero, letting it write new elements over +old memory, in effect clearing the accessible contents. +*/ clear :: proc(q: ^$Q/Queue($T)) { q.len = 0 q.offset = 0 |