diff --git a/src/fixed_slot_map.zig b/src/fixed_slot_map.zig index 9b05d74..61d4950 100644 --- a/src/fixed_slot_map.zig +++ b/src/fixed_slot_map.zig @@ -38,6 +38,9 @@ pub fn FixedSlotMap( generations: FixedArrayList(u32, Capacity), free_list: FixedArrayList(u32, Capacity), + // --- GENERATION COUNTER --- + next_gen: u32 = 0, + /// ---------------------------------------------------- /// ---------------------------------------------------- pub const empty = Self{ @@ -52,66 +55,75 @@ pub fn FixedSlotMap( self: *Self, value: T, ) error{Full}!Handle(T) { - // --- NEW GENERATION --- + // --- REUSE A FREED SLOT --- if (self.free_list.pop()) |idx| { - self.values.data[@intCast(idx)] = value; - self.generations.data[@intCast(idx)] += 1; + const gen = self.mint(); + self.values.items()[idx] = value; + self.generations.items()[idx] = gen; return .{ .idx = idx, - .gen = self.generations.data[idx], + .gen = gen, }; } // --- METADATA --- - const idx = self.values.len(); - const handle: Handle(T) = .{ - .idx = @intCast(idx), - .gen = 1, - }; + const idx: u32 = @intCast(self.values.items().len); + const gen = self.mint(); // --- NEW VALUE --- try self.values.append(value); - errdefer _ = self.values.swapRemove(idx); - - // --- NEW GENERATION --- - try self.generations.append(1); + errdefer _ = self.values.pop(); + try self.generations.append(gen); // --- RESULT --- - return handle; + 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), - ) void { + ) error{OutOfMemory}!void { const idx: u32 = handle.idx; // --- CHECK IF STALE --- - if (idx >= self.generations.len()) return; - if (self.generations.data[idx] != handle.gen) return; + if (idx >= self.generations.items().len) return; + if (self.generations.items()[idx] != handle.gen) return; - // --- GENERATION --- - self.generations.data[idx] +|= 1; - self.free_list.append(idx) catch |err| { - std.debug.print("[SLOTMAP_ERROR] {s}\n", .{@errorName(err)}); - }; + // --- 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.data[handle.idx] != handle.gen) return null; - return &self.values.data[handle.idx]; + if (self.generations.items()[handle.idx] != handle.gen) return null; + return &self.values.items()[handle.idx]; } /// ---------------------------------------------------- /// ---------------------------------------------------- - pub fn items(self: *Self) []T { - return self.values.items(); + pub fn clear(self: *Self) void { + self.values.clear(); + self.generations.clear(); + self.free_list.clear(); + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + fn mint(self: *Self) u32 { + const gen = self.next_gen +% 1; + self.next_gen = gen; + return gen; } }; } diff --git a/src/gtl.zig b/src/gtl.zig index da8423f..245ed9e 100644 --- a/src/gtl.zig +++ b/src/gtl.zig @@ -92,13 +92,17 @@ test "SlotMap" { // --- ADD MATERIALS --- _ = try materials.put(.{ .style = .{ .albedo = .{ 0.8, 0.2, 0.2, 1.0 } } }); _ = try materials.put(.{ .unique = .{ .topology = .line_list } }); + const c = try materials.put(.{ .unique = .{ .topology = .line_list } }); + _ = try materials.put(.{ .unique = .{ .topology = .line_list } }); + + try materials.remove(c); // --- # --- if (log.print(.debug, "MATERIALS", ansi.blue)) |a| { defer a.end(); // --- # --- - for (materials.items()) |material| { + for (materials.values.items) |material| { if (a.section(.debug, "", ansi.cyan)) |b| { defer b.end(); @@ -124,67 +128,24 @@ test "SlotMap" { } } -test "SlotMap-DEP" { // TODO: Yes... - { - // --- LOG --- - log.warn("...", .{}, @src()); - log.err("...", .{}, @src()); - log.info("...", .{}, @src()); - log.debug("...", .{}, @src()); - log.trace("...", .{}, @src()); +test "KeyedSlotMap" { + std.debug.print("{s}━━━ KEYED SLOTMAP ━━━{s}\n", .{ ansi.blue, ansi.reset }); - // --- PRINT --- - if (log.print(.debug, "DEVICE INFO", ansi.bold ++ ansi.red)) |a| { - defer a.end(); + // --- # --- + var pipelines: KeyedSlotMap(Material.Unique, u32) = .init(std.testing.allocator); + defer pipelines.deinit(); - // --- VALUES --- - a.write("device -> {s}", .{"GTX"}); - a.write("vendor -> {s}", .{"Nvidia"}); + // --- ADD PIPELINES --- + _ = try pipelines.put(.{}, 0); + _ = try pipelines.put(.{ .topology = .line_list }, 0); - // --- SECTION --- - if (a.section(.err, "MEMORY", ansi.green)) |b| { - defer b.end(); - - b.write("VRAM -> {}GB", .{24}); - } - } - - log.set(.{ .style = .rounded }); - - // --- PRINT --- - if (log.print(.debug, "DEVICE INFO", ansi.bold ++ ansi.red)) |a| { - defer a.end(); - - // --- VALUES --- - a.write("device -> {s}", .{"GTX"}); - a.write("vendor -> {s}", .{"Nvidia"}); - - // --- SECTION --- - if (a.section(.err, "MEMORY", ansi.green)) |b| { - defer b.end(); - - b.write("VRAM -> {}GB", .{24}); - } - } - - log.set(.{ .style = .pipe }); - - // --- PRINT --- - if (log.print(.debug, "DEVICE INFO", ansi.bold ++ ansi.red)) |a| { - defer a.end(); - - // --- VALUES --- - a.write("device -> {s}", .{"GTX"}); - a.write("vendor -> {s}", .{"Nvidia"}); - - // --- SECTION --- - if (a.section(.err, "MEMORY", ansi.green)) |b| { - defer b.end(); - - // --- VALUES --- - b.write("VRAM -> {}GB", .{24}); - } - } + // --- ITERATOR --- + var it = pipelines.iterator(); + while (it.next()) |entry| { + std.debug.print("[{}] {}\n", .{ + entry.value, + entry.key.topology, + }); } } diff --git a/src/handle.zig b/src/handle.zig index 123f885..a264b29 100644 --- a/src/handle.zig +++ b/src/handle.zig @@ -1,3 +1,8 @@ +//! ---------------------------------------------------- +//! ---------------------------------------------------- + +const std = @import("std"); + /// ---------------------------------------------------- /// Generational Handle /// ---------------------------------------------------- @@ -6,9 +11,15 @@ pub fn Handle(comptime T: type) type { return struct { const Self = @This(); + // + // FIELDS + // + idx: u32 = 0, gen: u32 = 0, + /// ---------------------------------------------------- + /// ---------------------------------------------------- pub fn eql( self: Self, other: Self, @@ -16,5 +27,14 @@ pub fn Handle(comptime T: type) type { return self.idx == other.idx and self.gen == other.gen; } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub fn format( + self: @This(), + writer: *std.Io.Writer, + ) std.Io.Writer.Error!void { + try writer.print("{}:{}", .{ self.idx, self.gen }); + } }; } diff --git a/src/keyed_slot_map.zig b/src/keyed_slot_map.zig index 311b18d..d935fa1 100644 --- a/src/keyed_slot_map.zig +++ b/src/keyed_slot_map.zig @@ -9,19 +9,22 @@ const Handle = @import("handle.zig").Handle; /// # Example /// /// ```zig -/// // --- SHADER MAP --- -/// var shaders: KeyedSlotMap(ShaderID, GPUShader) = .init(alloc); -/// defer shaders.deinit(); -/// -/// // --- PIPELINE MAP --- -/// var pipelines: KeyedSlotMap(PipelineKey, GPUPipeline) = .init(alloc); +/// // --- # --- +/// var pipelines: KeyedSlotMap(Material.Unique, u32) = .init(std.testing.allocator); /// defer pipelines.deinit(); /// -/// // --- ADD MATERIALS --- -/// try materials.append(alloc, .{ .shader = .pbr }); // BLACK -/// try materials.append(alloc, .{ .shader = .pbr, .albedo = .{ 1, 0, 0, 1 } }); // RED -/// try materials.append(alloc, .{ .shader = .unlit, .albedo = .{ 0, 1, 0, 1 } }); // GREEN -/// try materials.append(alloc, .{ .shader = .unlit, .albedo = .{ 0, 0, 1, 1 } }); // GREEN +/// // --- ADD PIPELINES --- +/// _ = try pipelines.put(.{}, 0); +/// _ = try pipelines.put(.{ .topology = .line_list }, 0); +/// +/// // --- ITERATOR --- +/// var it = pipelines.iterator(); +/// while (it.next()) |entry| { +/// std.debug.print("[{}] {}\n", .{ +/// entry.value, +/// entry.key.topology, +/// }); +/// } /// ``` /// ---------------------------------------------------- pub fn KeyedSlotMap( @@ -38,6 +41,71 @@ pub fn KeyedSlotMap( value: V, }; + /// ---------------------------------------------------- + /// Cursor over live entries + /// Removed (freed) slots are skipped + /// ---------------------------------------------------- + pub const Iterator = struct { + entries: []Entry, + free_list: []const u32, + index: usize = 0, + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub fn next(self: *Iterator) ?*Entry { + return advance(self.entries, self.free_list, &self.index); + } + }; + + /// ---------------------------------------------------- + /// Cursor over live keys + /// ---------------------------------------------------- + pub const KeyIterator = struct { + entries: []Entry, + free_list: []const u32, + index: usize = 0, + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub fn next(self: *KeyIterator) ?*K { + const entry = advance(self.entries, self.free_list, &self.index) orelse return null; + return &entry.key; + } + }; + + /// ---------------------------------------------------- + /// Cursor over live values + /// ---------------------------------------------------- + pub const ValueIterator = struct { + entries: []Entry, + free_list: []const u32, + index: usize = 0, + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub fn next(self: *ValueIterator) ?*V { + const entry = advance(self.entries, self.free_list, &self.index) orelse return null; + return &entry.value; + } + }; + + /// ---------------------------------------------------- + /// Skips slots that live on the free list (tombstones) + /// ---------------------------------------------------- + fn advance( + entries: []Entry, + free_list: []const u32, + index: *usize, + ) ?*Entry { + while (index.* < entries.len) : (index.* += 1) { + if (std.mem.indexOfScalar(u32, free_list, @intCast(index.*)) != null) continue; + const entry = &entries[index.*]; + index.* += 1; + return entry; + } + return null; + } + // // FIELDS // @@ -79,7 +147,7 @@ pub fn KeyedSlotMap( // --- RESULT --- const handle = try self.slot_map.put(.{ .value = value, .key = key }); - errdefer self.slot_map.remove(handle); + errdefer self.slot_map.remove(handle) catch {}; // --- NEW LOOKUP --- try self.lookup.put(key, handle); @@ -89,28 +157,35 @@ pub fn KeyedSlotMap( } /// ---------------------------------------------------- + /// Returns an error if the free list could not grow + /// Stale handles are silently ignored /// ---------------------------------------------------- pub fn remove( self: *Self, handle: Handle(V), - ) void { - // --- GET IDX --- + ) error{OutOfMemory}!void { const idx: u32 = handle.idx; + // --- CHECK IF STALE --- + if (idx >= self.slot_map.generations.items.len) return; + if (self.slot_map.generations.items[idx] != handle.gen) return; + + // --- GRAB KEY BEFORE REMOVING --- + const key = self.slot_map.values.items[idx].key; + // --- REMOVE FROM SLOTMAP --- - self.slot_map.remove(handle); + try self.slot_map.remove(handle); // --- REMOVE KEY --- - const key = self.values.items[idx].key; _ = self.lookup.remove(key); } /// ---------------------------------------------------- /// ---------------------------------------------------- - pub fn removeByKey(self: *Self, key: K) void { + pub fn removeByKey(self: *Self, key: K) error{OutOfMemory}!void { const handle = self.lookup.get(key) orelse return; _ = self.lookup.remove(key); - self.slot_map.remove(handle); + try self.slot_map.remove(handle); } /// ---------------------------------------------------- @@ -165,7 +240,7 @@ pub fn KeyedSlotMap( /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn count(self: *Self) u32 { - return self.lookup.count(); + return @intCast(self.lookup.count()); } /// ---------------------------------------------------- @@ -173,5 +248,35 @@ pub fn KeyedSlotMap( pub fn items(self: *Self) []Entry { return self.slot_map.values.items; } + + /// ---------------------------------------------------- + /// Skips slots that live on the free list (tombstones) + /// ---------------------------------------------------- + pub fn iterator(self: *Self) Iterator { + return .{ + .entries = self.slot_map.values.items, + .free_list = self.slot_map.free_list.items, + }; + } + + /// ---------------------------------------------------- + /// Skips slots that live on the free list (tombstones) + /// ---------------------------------------------------- + pub fn keyIterator(self: *Self) KeyIterator { + return .{ + .entries = self.slot_map.values.items, + .free_list = self.slot_map.free_list.items, + }; + } + + /// ---------------------------------------------------- + /// Skips slots that live on the free list (tombstones) + /// ---------------------------------------------------- + pub fn valueIterator(self: *Self) ValueIterator { + return .{ + .entries = self.slot_map.values.items, + .free_list = self.slot_map.free_list.items, + }; + } }; } diff --git a/src/log.zig b/src/log.zig index 83e1f1d..ffe8fc4 100644 --- a/src/log.zig +++ b/src/log.zig @@ -255,7 +255,7 @@ pub fn print( // --- RESULT --- const box = boxChars(); - std.debug.print("{s}{s}{s}{s} {s}\n", .{ color, box.top_left, box.horizontal, ansi.reset, name }); + std.debug.print("{s}{s}{s}[{s}{s}{s}]{s}\n", .{ color, box.top_left, box.horizontal, ansi.reset, name, color, ansi.reset }); return .{ .color = color }; } diff --git a/src/slot_map.zig b/src/slot_map.zig index ca813d3..517de79 100644 --- a/src/slot_map.zig +++ b/src/slot_map.zig @@ -32,6 +32,9 @@ pub fn SlotMap(comptime T: type) type { generations: std.ArrayList(u32), free_list: std.ArrayList(u32), + // --- GENERATION COUNTER --- + next_gen: u32 = 0, + // --- MEMORY --- alloc: std.mem.Allocator, @@ -40,10 +43,8 @@ pub fn SlotMap(comptime T: type) type { pub fn init(alloc: std.mem.Allocator) Self { return .{ .values = .empty, - .generations = .empty, .free_list = .empty, - .alloc = alloc, }; } @@ -62,52 +63,51 @@ pub fn SlotMap(comptime T: type) type { self: *Self, value: T, ) error{OutOfMemory}!Handle(T) { - // --- NEW GENERATION --- + // --- REUSE A FREED SLOT --- if (self.free_list.pop()) |idx| { - self.values.items[@intCast(idx)] = value; - self.generations.items[@intCast(idx)] += 1; + const gen = self.mint(); + self.values.items[idx] = value; + self.generations.items[idx] = gen; return .{ .idx = idx, - .gen = self.generations.items[idx], + .gen = gen, }; } // --- METADATA --- - const idx = self.values.items.len; - const handle: Handle(T) = .{ - .idx = @intCast(idx), - .gen = 1, - }; + const idx: u32 = @intCast(self.values.items.len); + const gen = self.mint(); // --- NEW VALUE --- try self.values.append(self.alloc, value); - errdefer _ = self.values.swapRemove(idx); - - // --- NEW GENERATION --- - try self.generations.append(self.alloc, 1); + errdefer _ = self.values.pop(); + try self.generations.append(self.alloc, gen); // --- RESULT --- - return handle; + 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), - ) void { + ) 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; - // --- GENERATION --- - self.generations.items[idx] +|= 1; - self.free_list.append(self.alloc, idx) catch |err| { - std.debug.print("[SLOTMAP_ERROR] {s}\n", .{@errorName(err)}); - }; + // --- FREE --- + try self.free_list.append(self.alloc, idx); + self.generations.items[idx] = self.mint(); } /// ---------------------------------------------------- @@ -119,9 +119,21 @@ pub fn SlotMap(comptime T: type) type { } /// ---------------------------------------------------- + /// Reduce length to 0 + /// Invalidates all element pointers and handles /// ---------------------------------------------------- - pub fn items(self: *Self) []T { - return self.values.items; + pub fn clearRetainingCapacity(self: *Self) void { + self.values.clearRetainingCapacity(); + self.generations.clearRetainingCapacity(); + self.free_list.clearRetainingCapacity(); + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + fn mint(self: *Self) u32 { + const gen = self.next_gen +% 1; + self.next_gen = gen; + return gen; } }; }