ona/source/coral/slots.zig

211 lines
5.8 KiB
Zig
Executable File

const debug = @import("./debug.zig");
const io = @import("./io.zig");
const math = @import("./math.zig");
const stack = @import("./stack.zig");
///
/// Retruns a set of dense slots that may store `Element`s indexable by a [Slot], where `key` defines how many bits the
/// [Slot] used is made from.
///
pub fn Dense(comptime key: Key, comptime Element: type) type {
const KeySlot = Slot(key);
const Index = math.Unsigned(key.index_bits);
return struct {
capacity: usize = 0,
values: []Element = &.{},
slots: ?[*]KeySlot = null,
erase: ?[*]Index = null,
next_free: Index = 0,
const Self = @This();
///
/// Clears all elements from the slots in `self`.
///
/// *Note* that clearing the slots is not the same as deinitializing them, as it does not deallocate any memory
/// that has already been allocated to the slots structure.
///
pub fn clear(self: *Self) void {
self.next_free = 0;
self.values = self.values[0 .. 0];
{
var index = @as(usize, 0);
while (index < self.capacity) : (index += 1) {
const slot = &self.slots[index];
slot.salt = math.max(slot.salt +% 1, 1);
slot.index = index;
}
}
}
///
/// Frees all memory allocated by `allocator` to self.
///
/// *Note*: if `self` already contains allocated memory then `allocator` must reference the same [io.Allocator]
/// that was used to create the already-allocated memory.
///
pub fn deinit(self: *Self, allocator: io.Allocator) void {
io.deallocate(allocator, self.values.ptr);
io.deallocate(allocator, self.slots);
io.deallocate(allocator, self.erase);
self.values = &.{};
self.slots = null;
self.erase = null;
}
///
/// Attempts to fetch the element identified referenced by `slot` from `self`, returning it or `null` if `slot`
/// does not reference a valid element.
///
pub fn fetch(self: Self, slot: KeySlot) ?*Element {
if (slot.index >= self.values.len) {
return null;
}
const redirect = &self.slots[slot.index];
if (slot.salt != redirect.salt) {
return null;
}
return &self.values[redirect.index];
}
///
/// Attempts to transactionally grow `self` by `growth_amount` using `allocator`, returning a
/// [io.AllocationError] if it failed.
///
/// Should growing fail, `self` is left in an unmodified state.
///
/// *Note*: if `self` already contains allocated memory then `allocator` must reference the same [io.Allocator]
/// that was used to create the already-allocated memory.
///
pub fn grow(self: *Self, allocator: io.Allocator, growth_amount: usize) io.AllocationError!void {
const grown_capacity = self.capacity + growth_amount;
const values = try io.allocate_many(Element, grown_capacity, allocator);
errdefer io.deallocate(allocator, values);
const slots = try io.allocate_many(KeySlot, grown_capacity, allocator);
errdefer io.deallocate(allocator, slots);
const erase = try io.allocate_many(Index, grown_capacity, allocator);
errdefer io.deallocate(allocator, slots);
self.values = values;
self.slots = slots.ptr;
self.erase = erase.ptr;
self.capacity = grown_capacity;
// Add new values to the freelist
{
var index = @intCast(Index, self.values.len);
while (index < self.capacity) : (index += 1) {
const slot = &self.slots.?[index];
slot.salt = 1;
slot.index = index;
}
}
}
///
/// Attempts to insert `value` into `self`, growing the internal buffer with `allocator` if it is full and
/// returning a `Slot` of `key` referencing the inserted element or a [io.AllocationError] if it failed.
///
/// *Note*: if `self` already contains allocated memory then `allocator` must reference the same [io.Allocator]
/// that was used to create the already-allocated memory.
///
pub fn insert(self: *Self, allocator: io.Allocator, value: Element) io.AllocationError!KeySlot {
if (self.values.len == self.capacity) {
try self.grow(allocator, math.max(usize, 1, self.capacity));
}
const index_of_redirect = self.next_free;
const redirect = &self.slots.?[index_of_redirect];
// redirect.index points to the next free slot.
self.next_free = redirect.index;
redirect.index = @intCast(Index, self.values.len);
self.values = self.values.ptr[0 .. self.values.len + 1];
self.values[redirect.index] = value;
self.erase.?[redirect.index] = index_of_redirect;
return KeySlot{
.index = index_of_redirect,
.salt = redirect.salt,
};
}
///
/// Attempts to remove the element referenced by `slot` from `self`, returning `true` if it was successful or
/// `false` if `slot` does not reference a valid slot.
///
pub fn remove(self: *Self, slot: KeySlot) bool {
const redirect = &self.slots.?[slot.index];
if (slot.salt != redirect.salt) {
return false;
}
const free_index = redirect.index;
self.values = self.values[0 .. (self.values.len - 1)];
if (self.values.len > 0) {
const free_value = &self.values[free_index];
const free_erase = &self.erase.?[free_index];
const last_value = &self.values[self.values.len];
const last_erase = &self.erase.?[self.values.len];
free_value.* = last_value.*;
free_erase.* = last_erase.*;
self.slots.?[free_erase.*].index = free_index;
}
redirect.salt = math.max(Index, redirect.salt +% 1, 1);
redirect.index = self.next_free;
self.next_free = slot.index;
return true;
}
};
}
///
/// Describes the memory layout of an element-slot mapping.
///
pub const Key = struct {
index_bits: usize,
salt_bits: usize,
};
///
/// References a slot in a slot mapping.
///
pub fn Slot(comptime key: Key) type {
return extern struct {
index: math.Unsigned(key.index_bits),
salt: math.Unsigned(key.salt_bits),
};
}
///
/// [Key] that uses the same number of bits as a [usize].
///
pub const addressable_key = Key{
.index_bits = (@bitSizeOf(usize) / 2),
.salt_bits = (@bitSizeOf(usize) / 2),
};