const debug = @import("./debug.zig"); const io = @import("./io.zig"); const math = @import("./math.zig"); /// /// Retruns a dense mapping slots that may store `Element`s indexable by a [Slot], where `key` defines how many bits the /// [Slot] used is made from. /// pub fn Map(comptime key: Key, comptime Element: type) type { const KeySlot = Slot(key); const Index = math.Unsigned(key.index_bits); return struct { capacity: usize, values: []Element, slots: [*]KeySlot, erase: [*]Index, next_free: Index, 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 return an initialized slot map with an initial capacity of `initial_capacity` and `allocator` as /// the memory allocation strategy. /// /// Upon failure, a [io.AllocationError] is returned instead. /// pub fn init(allocator: io.Allocator, initial_capacity: usize) io.AllocationError!Self { const values = try io.allocate_many(Element, initial_capacity, allocator); errdefer io.deallocate(allocator, values); const slots = try io.allocate_many(KeySlot, initial_capacity, allocator); errdefer io.deallocate(allocator, slots); const erase = try io.allocate_many(Index, initial_capacity, allocator); errdefer io.deallocate(allocator, erase); return Self{ .capacity = initial_capacity, .values = values[0 .. 0], .slots = slots.ptr, .erase = erase.ptr, .next_free = 0, }; } /// /// 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), };