aboutsummaryrefslogtreecommitdiff
path: root/core/container
diff options
context:
space:
mode:
authorFeoramund <161657516+Feoramund@users.noreply.github.com>2025-06-11 08:05:27 -0400
committerFeoramund <161657516+Feoramund@users.noreply.github.com>2025-06-11 11:55:30 -0400
commit6cb84e467bd9ea4b7ebf36640a192fe6e3e00fd8 (patch)
treea3c09ae4a2c58753b4aa861186c075b9d4d01ba4 /core/container
parent862442511a2684adecafb3688f2b7ad172a3a47d (diff)
container/queue: Document the package
Diffstat (limited to 'core/container')
-rw-r--r--core/container/queue/queue.odin215
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