const debug = @import("./debug.zig"); const io = @import("./io.zig"); const math = @import("./math.zig"); const std = @import("std"); /// /// Addressable mapping of integers described by `index_int` to values of type `Value`. /// /// Slab maps are similar to slot maps in that they have O(1) insertion and removal, however, use a flat table layout /// instead of parallel arrays. This reduces memory usage in some cases and can be useful for data that does not need to /// be quickly iterated over, as values ordering is not guaranteed. /// /// *Note* `index_int` values may be as big or as small as desired per the use-case of the consumer, however, integers /// smaller than `usize` may result in the map reporting it is out of memory due to exhausting the addressable space /// provided by the integer. /// pub fn Map(comptime index_int: std.builtin.Type.Int, comptime Value: type) type { return struct { free_index: Index = 0, count: Index = 0, table: []Entry = &.{}, /// /// Table entry which may either store an inserted value or an index to the next free entry in the table. /// const Entry = union (enum) { free_index: Index, value: Value, }; /// /// Used for indexing into the slab map. /// const Index = math.Int(index_int); /// /// Slab map type. /// const Self = @This(); /// /// Overwrites the value referenced by `index` in `self`. /// pub fn assign(self: *Self, index: Index, value: Value) void { const entry = &self.table[index]; debug.assert(entry.* == .value); entry.value = value; } /// /// Fetches the value referenced by `index` in `self`, returning it. /// pub fn fetch(self: *Self, index: Index) Value { const entry = &self.table[index]; debug.assert(entry.* == .value); return entry.value; } /// /// Deinitializes `self` and sets it to an invalid state, freeing all memory allocated by `allocator`. /// /// *Note* if the `table` field of `self` is an allocated slice, `allocator` must reference the same allocation /// strategy as the one originally used to allocate the current table. /// pub fn deinit(self: *Self, allocator: io.Allocator) void { if (self.table.len == 0) { return; } io.deallocate(allocator, self.table); self.table = &.{}; self.count = 0; self.free_index = 0; } /// /// Attempts to grow the internal buffer of `self` by `growth_amount` using `allocator`. /// /// The function returns [io.AllocatorError] if `allocator` could not commit the memory required to grow the /// table by `growth_amount`, leaving `self` in the same state that it was in prior to starting the grow. /// /// Growing ahead of multiple insertion operations is useful when the upper bound of insertions is well- /// understood, as it can reduce the number of allocations required per insertion. /// /// *Note* if the `table` field of `self` is an allocated slice, `allocator` must reference the same allocation /// strategy as the one originally used to allocate the current table. /// pub fn grow(self: *Self, allocator: io.Allocator, growth_amount: usize) io.AllocationError!void { const grown_capacity = self.table.len + growth_amount; const entries = try io.allocate_many(allocator, grown_capacity, Entry); errdefer io.deallocate(allocator, entries); if (self.table.len != 0) { for (0 .. self.table.len) |index| { entries[index] = self.table[index]; } for (self.table.len .. entries.len) |index| { entries[index] = .{.free_index = 0}; } io.deallocate(allocator, self.table); } self.table = entries; } /// /// Attempts to insert `value` into `self` as a new entry using `allocator` as the allocation strategy, /// returning an index value representing a reference to the inserted value that may be queried through `self` /// after. /// /// The function returns [io.AllocationError] if `allocator` could not commit the memory required to grow the /// internal buffer of `self` when necessary. /// /// *Note* if the `table` field of `self` is an allocated slice, `allocator` must reference the same allocation /// strategy as the one originally used to allocate the current table. /// pub fn insert(self: *Self, allocator: io.Allocator, value: Value) io.AllocationError!Index { if (self.count == self.table.len) { try self.grow(allocator, math.max(1, self.count)); } if (self.free_index == self.count) { const entry_index = self.count; const entry = &self.table[entry_index]; entry.* = .{.value = value}; self.count += 1; self.free_index += 1; return entry_index; } const entry_index = self.free_index; const entry = &self.table[self.free_index]; debug.assert(entry.* == .free_index); self.free_index = entry.free_index; entry.* = .{.value = value}; return entry_index; } /// /// Removes the value referenced by `index` from `self`. /// pub fn remove(self: *Self, index: Index) void { const entry = &self.table[index]; debug.assert(entry.* == .value); entry.* = .{.free_index = self.free_index}; self.free_index = index; } }; }