mirror of
https://github.com/odin-lang/Odin.git
synced 2025-12-29 17:34:34 +00:00
256 lines
6.7 KiB
Odin
256 lines
6.7 KiB
Odin
package container_queue
|
|
|
|
import "base:builtin"
|
|
import "base:runtime"
|
|
_ :: runtime
|
|
|
|
// Dynamically resizable double-ended queue/ring-buffer
|
|
Queue :: struct($T: typeid) {
|
|
data: [dynamic]T,
|
|
len: uint,
|
|
offset: uint,
|
|
}
|
|
|
|
DEFAULT_CAPACITY :: 16
|
|
|
|
// Procedure to initialize a queue
|
|
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
|
|
}
|
|
clear(q)
|
|
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.
|
|
init_from_slice :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
|
|
clear(q)
|
|
q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{
|
|
data = raw_data(backing),
|
|
len = builtin.len(backing),
|
|
cap = builtin.len(backing),
|
|
allocator = {procedure=runtime.nil_allocator_proc, data=nil},
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Procedure to initialize a queue from a fixed backing slice.
|
|
// Existing contents are preserved and available on the queue.
|
|
init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
|
|
clear(q)
|
|
q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{
|
|
data = raw_data(backing),
|
|
len = builtin.len(backing),
|
|
cap = builtin.len(backing),
|
|
allocator = {procedure=runtime.nil_allocator_proc, data=nil},
|
|
}
|
|
q.len = len(backing)
|
|
q.offset = len(backing)
|
|
return true
|
|
}
|
|
|
|
// Procedure to destroy a queue
|
|
destroy :: proc(q: ^$Q/Queue($T)) {
|
|
delete(q.data)
|
|
}
|
|
|
|
// The length of the queue
|
|
len :: proc(q: $Q/Queue($T)) -> int {
|
|
return int(q.len)
|
|
}
|
|
|
|
// The current capacity of the queue
|
|
cap :: proc(q: $Q/Queue($T)) -> int {
|
|
return builtin.len(q.data)
|
|
}
|
|
|
|
// Remaining space in the queue (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 :: proc(q: ^$Q/Queue($T), capacity: int) -> runtime.Allocator_Error {
|
|
if capacity > space(q^) {
|
|
return _grow(q, uint(capacity))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
|
|
get :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> T {
|
|
runtime.bounds_check_error_loc(loc, i, builtin.len(q.data))
|
|
|
|
idx := (uint(i)+q.offset)%builtin.len(q.data)
|
|
return q.data[idx]
|
|
}
|
|
|
|
front :: proc(q: ^$Q/Queue($T)) -> T {
|
|
return q.data[q.offset]
|
|
}
|
|
front_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
|
|
return &q.data[q.offset]
|
|
}
|
|
|
|
back :: proc(q: ^$Q/Queue($T)) -> T {
|
|
idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
|
|
return q.data[idx]
|
|
}
|
|
back_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
|
|
idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
|
|
return &q.data[idx]
|
|
}
|
|
|
|
set :: proc(q: ^$Q/Queue($T), #any_int i: int, val: T, loc := #caller_location) {
|
|
runtime.bounds_check_error_loc(loc, i, builtin.len(q.data))
|
|
|
|
idx := (uint(i)+q.offset)%builtin.len(q.data)
|
|
q.data[idx] = val
|
|
}
|
|
get_ptr :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> ^T {
|
|
runtime.bounds_check_error_loc(loc, i, builtin.len(q.data))
|
|
|
|
idx := (uint(i)+q.offset)%builtin.len(q.data)
|
|
return &q.data[idx]
|
|
}
|
|
|
|
peek_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
|
|
runtime.bounds_check_error_loc(loc, 0, builtin.len(q.data))
|
|
idx := q.offset%builtin.len(q.data)
|
|
return &q.data[idx]
|
|
}
|
|
|
|
peek_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
|
|
runtime.bounds_check_error_loc(loc, int(q.len - 1), builtin.len(q.data))
|
|
idx := (uint(q.len - 1)+q.offset)%builtin.len(q.data)
|
|
return &q.data[idx]
|
|
}
|
|
|
|
// Push an element to the back of the queue
|
|
push_back :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) {
|
|
if space(q^) == 0 {
|
|
_grow(q) or_return
|
|
}
|
|
idx := (q.offset+uint(q.len))%builtin.len(q.data)
|
|
q.data[idx] = elem
|
|
q.len += 1
|
|
return true, nil
|
|
}
|
|
|
|
// Push an element to the front of the queue
|
|
push_front :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) {
|
|
if space(q^) == 0 {
|
|
_grow(q) or_return
|
|
}
|
|
q.offset = uint(q.offset - 1 + builtin.len(q.data)) % builtin.len(q.data)
|
|
q.len += 1
|
|
q.data[q.offset] = elem
|
|
return true, nil
|
|
}
|
|
|
|
|
|
// Pop an element from the back of the queue
|
|
pop_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) {
|
|
assert(condition=q.len > 0, loc=loc)
|
|
q.len -= 1
|
|
idx := (q.offset+uint(q.len))%builtin.len(q.data)
|
|
elem = q.data[idx]
|
|
return
|
|
}
|
|
// Safely pop an element from the back of the queue
|
|
pop_back_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
|
|
if q.len > 0 {
|
|
q.len -= 1
|
|
idx := (q.offset+uint(q.len))%builtin.len(q.data)
|
|
elem = q.data[idx]
|
|
ok = true
|
|
}
|
|
return
|
|
}
|
|
|
|
// Pop an element from the front of the queue
|
|
pop_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) {
|
|
assert(condition=q.len > 0, loc=loc)
|
|
elem = q.data[q.offset]
|
|
q.offset = (q.offset+1)%builtin.len(q.data)
|
|
q.len -= 1
|
|
return
|
|
}
|
|
// Safely pop an element from the front of the queue
|
|
pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
|
|
if q.len > 0 {
|
|
elem = q.data[q.offset]
|
|
q.offset = (q.offset+1)%builtin.len(q.data)
|
|
q.len -= 1
|
|
ok = true
|
|
}
|
|
return
|
|
}
|
|
|
|
// Push multiple elements to the back of the queue
|
|
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) {
|
|
_grow(q, q.len + n) or_return
|
|
}
|
|
|
|
sz := uint(builtin.len(q.data))
|
|
insert_from := (q.offset + q.len) % sz
|
|
insert_to := n
|
|
if insert_from + insert_to > sz {
|
|
insert_to = sz - insert_from
|
|
}
|
|
copy(q.data[insert_from:], elems[:insert_to])
|
|
copy(q.data[:insert_from], elems[insert_to:])
|
|
q.len += n
|
|
return true, nil
|
|
}
|
|
|
|
// Consume `n` elements from the front of the queue
|
|
consume_front :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) {
|
|
assert(condition=int(q.len) >= n, loc=loc)
|
|
if n > 0 {
|
|
nu := uint(n)
|
|
q.offset = (q.offset + nu) % builtin.len(q.data)
|
|
q.len -= nu
|
|
}
|
|
}
|
|
|
|
// Consume `n` elements from the back of the queue
|
|
consume_back :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) {
|
|
assert(condition=int(q.len) >= n, loc=loc)
|
|
if n > 0 {
|
|
q.len -= uint(n)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
append_elem :: push_back
|
|
append_elems :: push_back_elems
|
|
push :: proc{push_back, push_back_elems}
|
|
append :: proc{push_back, push_back_elems}
|
|
|
|
|
|
// Clear the contents of the queue
|
|
clear :: proc(q: ^$Q/Queue($T)) {
|
|
q.len = 0
|
|
q.offset = 0
|
|
}
|
|
|
|
|
|
// Internal growing procedure
|
|
_grow :: proc(q: ^$Q/Queue($T), min_capacity: uint = 0) -> runtime.Allocator_Error {
|
|
new_capacity := max(min_capacity, uint(8), uint(builtin.len(q.data))*2)
|
|
n := uint(builtin.len(q.data))
|
|
builtin.resize(&q.data, int(new_capacity)) or_return
|
|
if q.offset + q.len > n {
|
|
diff := n - q.offset
|
|
copy(q.data[new_capacity-diff:], q.data[q.offset:][:diff])
|
|
q.offset += new_capacity - n
|
|
}
|
|
return nil
|
|
}
|