Files
Odin/core/container/pool/pool.odin
2026-01-14 20:02:08 +01:00

141 lines
3.5 KiB
Odin

package container_pool
import "base:intrinsics"
import "base:sanitizer"
import "core:mem"
import "core:sync"
_ :: sanitizer
DEFAULT_BLOCK_SIZE :: _DEFAULT_BLOCK_SIZE
Pool_Arena :: _Pool_Arena
/*
A thread-safe (between init and destroy) object pool backed by virtual growing arena returning stable pointers.
The element type requires an intrusive link node.
Example:
Elem :: struct {
link: ^Elem,
}
p: pool.Pool(Elem)
pool.init(&p, "link")
*/
Pool :: struct($T: typeid) {
arena: Pool_Arena,
num_outstanding: int,
num_ready: int,
link_off: uintptr,
free_list: ^T,
}
@(require_results)
init :: proc(p: ^Pool($T), $link_field: string, block_size: uint = DEFAULT_BLOCK_SIZE) -> (err: mem.Allocator_Error)
where intrinsics.type_has_field(T, link_field),
intrinsics.type_field_type(T, link_field) == ^T {
p.link_off = offset_of_by_string(T, link_field)
return _pool_arena_init(&p.arena, block_size)
}
destroy :: proc(p: ^Pool($T)) {
elem := sync.atomic_exchange_explicit(&p.free_list, nil, .Acquire)
sync.atomic_store_explicit(&p.num_ready, 0, .Relaxed)
when .Address in ODIN_SANITIZER_FLAGS {
for ; elem != nil; elem = _get_next(p, elem) {
_unpoison_elem(p, elem)
}
} else {
_ = elem
}
_pool_arena_destroy(&p.arena)
p.arena = {}
}
@(require_results)
get :: proc(p: ^Pool($T)) -> (elem: ^T, err: mem.Allocator_Error) #optional_allocator_error {
defer sync.atomic_add_explicit(&p.num_outstanding, 1, .Relaxed)
for {
elem = sync.atomic_load_explicit(&p.free_list, .Acquire)
if elem == nil {
// NOTE: pool arena has an internal lock.
return new(T, _pool_arena_allocator(&p.arena))
}
if _, ok := sync.atomic_compare_exchange_weak_explicit(&p.free_list, elem, _get_next(p, elem), .Acquire, .Relaxed); ok {
_set_next(p, elem, nil)
_unpoison_elem(p, elem)
sync.atomic_sub_explicit(&p.num_ready, 1, .Relaxed)
return
}
}
}
put :: proc(p: ^Pool($T), elem: ^T) {
mem.zero_item(elem)
_poison_elem(p, elem)
defer sync.atomic_sub_explicit(&p.num_outstanding, 1, .Relaxed)
defer sync.atomic_add_explicit(&p.num_ready, 1, .Relaxed)
for {
head := sync.atomic_load_explicit(&p.free_list, .Relaxed)
_set_next(p, elem, head)
if _, ok := sync.atomic_compare_exchange_weak_explicit(&p.free_list, head, elem, .Release, .Relaxed); ok {
return
}
}
}
num_outstanding :: proc(p: ^Pool($T)) -> int {
return sync.atomic_load(&p.num_outstanding)
}
num_ready :: proc(p: ^Pool($T)) -> int {
return sync.atomic_load(&p.num_ready)
}
cap :: proc(p: ^Pool($T)) -> int {
return sync.atomic_load(&p.num_ready) + sync.atomic_load(&p.num_outstanding)
}
_get_next :: proc(p: ^Pool($T), elem: ^T) -> ^T {
return (^^T)(uintptr(elem) + p.link_off)^
}
_set_next :: proc(p: ^Pool($T), elem: ^T, next: ^T) {
(^^T)(uintptr(elem) + p.link_off)^ = next
}
@(disabled=.Address not_in ODIN_SANITIZER_FLAGS)
_poison_elem :: proc(p: ^Pool($T), elem: ^T) {
if p.link_off > 0 {
sanitizer.address_poison_rawptr(elem, int(p.link_off))
}
len := size_of(T) - p.link_off - size_of(rawptr)
if len > 0 {
ptr := rawptr(uintptr(elem) + p.link_off + size_of(rawptr))
sanitizer.address_poison_rawptr(ptr, int(len))
}
}
@(disabled=.Address not_in ODIN_SANITIZER_FLAGS)
_unpoison_elem :: proc(p: ^Pool($T), elem: ^T) {
if p.link_off > 0 {
sanitizer.address_unpoison_rawptr(elem, int(p.link_off))
}
len := size_of(T) - p.link_off - size_of(rawptr)
if len > 0 {
ptr := rawptr(uintptr(elem) + p.link_off + size_of(rawptr))
sanitizer.address_unpoison_rawptr(ptr, int(len))
}
}