256 lines
9 KiB
Zig
256 lines
9 KiB
Zig
//! ----------------------------------------------------
|
|
//! ----------------------------------------------------
|
|
|
|
const std = @import("std");
|
|
const FixedArrayList = @import("fixed_array_list.zig").FixedArrayList;
|
|
const Handle = @import("handle.zig").Handle;
|
|
|
|
/// ----------------------------------------------------
|
|
/// # Example
|
|
///
|
|
/// ```zig
|
|
/// var materials: FixedSlotMap(Material, 3) = .empty;
|
|
///
|
|
/// _ = try materials.put(.{});
|
|
/// _ = try materials.put(.{});
|
|
/// const handle = try materials.put(.{ .unique = .{ .topology = .line_list } });
|
|
///
|
|
/// if (materials.get(handle)) |v| {
|
|
/// std.debug.print("Material: {}\n", .{v.unique.topology});
|
|
/// }
|
|
/// ```
|
|
/// ----------------------------------------------------
|
|
pub fn FixedSlotMap(
|
|
comptime T: type,
|
|
comptime Capacity: usize,
|
|
) type {
|
|
return struct {
|
|
const Self = @This();
|
|
|
|
/// ----------------------------------------------------
|
|
/// ----------------------------------------------------
|
|
pub const Entry = struct {
|
|
value: *T,
|
|
handle: Handle(T),
|
|
};
|
|
|
|
/// ----------------------------------------------------
|
|
/// Cursor over live entries, yielding the value and its handle
|
|
/// Removed (freed) slots are skipped
|
|
/// ----------------------------------------------------
|
|
pub const Iterator = struct {
|
|
values: []T,
|
|
generations: []const u32,
|
|
free_list: []const u32,
|
|
index: usize = 0,
|
|
|
|
/// ----------------------------------------------------
|
|
/// ----------------------------------------------------
|
|
pub fn next(self: *Iterator) ?Entry {
|
|
const i = nextLive(self.values.len, self.free_list, &self.index) orelse return null;
|
|
return .{
|
|
.value = &self.values[i],
|
|
.handle = .{ .idx = @intCast(i), .gen = self.generations[i] },
|
|
};
|
|
}
|
|
};
|
|
|
|
/// ----------------------------------------------------
|
|
/// Cursor over live handles
|
|
/// Removed (freed) slots are skipped
|
|
/// ----------------------------------------------------
|
|
pub const HandleIterator = struct {
|
|
values: []T,
|
|
generations: []const u32,
|
|
free_list: []const u32,
|
|
index: usize = 0,
|
|
|
|
/// ----------------------------------------------------
|
|
/// ----------------------------------------------------
|
|
pub fn next(self: *HandleIterator) ?Handle(T) {
|
|
const i = nextLive(self.values.len, self.free_list, &self.index) orelse return null;
|
|
return .{ .idx = @intCast(i), .gen = self.generations[i] };
|
|
}
|
|
};
|
|
|
|
/// ----------------------------------------------------
|
|
/// Cursor over live values
|
|
/// Removed (freed) slots are skipped
|
|
/// ----------------------------------------------------
|
|
pub const ValueIterator = struct {
|
|
values: []T,
|
|
generations: []const u32,
|
|
free_list: []const u32,
|
|
index: usize = 0,
|
|
|
|
/// ----------------------------------------------------
|
|
/// ----------------------------------------------------
|
|
pub fn next(self: *ValueIterator) ?*T {
|
|
const i = nextLive(self.values.len, self.free_list, &self.index) orelse return null;
|
|
return &self.values[i];
|
|
}
|
|
};
|
|
|
|
/// ----------------------------------------------------
|
|
/// Next live index, or null
|
|
/// Skips slots that live on the free list ( `` tombstones `` )
|
|
/// ----------------------------------------------------
|
|
fn nextLive(
|
|
n: usize,
|
|
free_list: []const u32,
|
|
index: *usize,
|
|
) ?usize {
|
|
while (index.* < n) : (index.* += 1) {
|
|
if (std.mem.indexOfScalar(u32, free_list, @intCast(index.*)) == null) {
|
|
const i = index.*;
|
|
index.* += 1;
|
|
return i;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
//
|
|
// FIELDS
|
|
//
|
|
|
|
// --- VALUES ---
|
|
values: FixedArrayList(T, Capacity),
|
|
|
|
// --- LIFE TIMES ---
|
|
generations: FixedArrayList(u32, Capacity),
|
|
free_list: FixedArrayList(u32, Capacity),
|
|
|
|
// --- GENERATION COUNTER ---
|
|
next_gen: u32 = 0,
|
|
|
|
/// ----------------------------------------------------
|
|
/// ----------------------------------------------------
|
|
pub const empty = Self{
|
|
.values = .empty,
|
|
.generations = .empty,
|
|
.free_list = .empty,
|
|
};
|
|
|
|
/// ----------------------------------------------------
|
|
/// ----------------------------------------------------
|
|
pub fn put(
|
|
self: *Self,
|
|
value: T,
|
|
) error{Full}!Handle(T) {
|
|
// --- REUSE A FREED SLOT ---
|
|
if (self.free_list.pop()) |idx| {
|
|
const gen = self.mint();
|
|
self.values.items()[idx] = value;
|
|
self.generations.items()[idx] = gen;
|
|
|
|
return .{
|
|
.idx = idx,
|
|
.gen = gen,
|
|
};
|
|
}
|
|
|
|
// --- METADATA ---
|
|
const idx: u32 = @intCast(self.values.items().len);
|
|
const gen = self.mint();
|
|
|
|
// --- NEW VALUE ---
|
|
try self.values.append(value);
|
|
errdefer _ = self.values.pop();
|
|
try self.generations.append(gen);
|
|
|
|
// --- RESULT ---
|
|
return .{
|
|
.idx = idx,
|
|
.gen = gen,
|
|
};
|
|
}
|
|
|
|
/// ----------------------------------------------------
|
|
/// Returns an error if the free list could not grow
|
|
/// Stale handles are silently ignored
|
|
/// ----------------------------------------------------
|
|
pub fn remove(
|
|
self: *Self,
|
|
handle: Handle(T),
|
|
) error{OutOfMemory}!void {
|
|
const idx: u32 = handle.idx;
|
|
|
|
// --- CHECK IF STALE ---
|
|
if (idx >= self.generations.items().len) return;
|
|
if (self.generations.items()[idx] != handle.gen) return;
|
|
|
|
// --- FREE ---
|
|
try self.free_list.append(idx);
|
|
self.generations.items()[idx] = self.mint();
|
|
}
|
|
|
|
/// ----------------------------------------------------
|
|
/// ----------------------------------------------------
|
|
pub fn get(self: *Self, handle: Handle(T)) ?*T {
|
|
if (handle.idx >= self.values.len()) return null;
|
|
if (self.generations.items()[handle.idx] != handle.gen) return null;
|
|
return &self.values.items()[handle.idx];
|
|
}
|
|
|
|
/// ----------------------------------------------------
|
|
/// ----------------------------------------------------
|
|
pub fn clear(self: *Self) void {
|
|
self.values.clear();
|
|
self.generations.clear();
|
|
self.free_list.clear();
|
|
}
|
|
|
|
/// ----------------------------------------------------
|
|
/// ----------------------------------------------------
|
|
pub fn contains(self: *Self, handle: Handle(T)) bool {
|
|
return self.get(handle) != null;
|
|
}
|
|
|
|
/// ----------------------------------------------------
|
|
/// Number of live entries
|
|
/// Removed (freed) slots are not counted
|
|
/// ----------------------------------------------------
|
|
pub fn len(self: *Self) usize {
|
|
return self.values.items().len - self.free_list.items().len;
|
|
}
|
|
|
|
/// ----------------------------------------------------
|
|
/// ----------------------------------------------------
|
|
pub fn iterator(self: *Self) Iterator {
|
|
return .{
|
|
.values = self.values.items(),
|
|
.generations = self.generations.items(),
|
|
.free_list = self.free_list.items(),
|
|
};
|
|
}
|
|
|
|
/// ----------------------------------------------------
|
|
/// ----------------------------------------------------
|
|
pub fn handleIterator(self: *Self) HandleIterator {
|
|
return .{
|
|
.values = self.values.items(),
|
|
.generations = self.generations.items(),
|
|
.free_list = self.free_list.items(),
|
|
};
|
|
}
|
|
|
|
/// ----------------------------------------------------
|
|
/// ----------------------------------------------------
|
|
pub fn valueIterator(self: *Self) ValueIterator {
|
|
return .{
|
|
.values = self.values.items(),
|
|
.generations = self.generations.items(),
|
|
.free_list = self.free_list.items(),
|
|
};
|
|
}
|
|
|
|
/// ----------------------------------------------------
|
|
/// ----------------------------------------------------
|
|
fn mint(self: *Self) u32 {
|
|
const gen = self.next_gen +% 1;
|
|
self.next_gen = gen;
|
|
return gen;
|
|
}
|
|
};
|
|
}
|