diff --git a/src/gtl.zig b/src/gtl.zig index 9ced6a7..e7659e4 100644 --- a/src/gtl.zig +++ b/src/gtl.zig @@ -32,6 +32,7 @@ pub const Handle = @import("handle.zig").Handle; pub const TypeID = @import("type_id.zig").of; // --- ARRAYS --- +pub const ResourceMap = @import("resource_map.zig").ResourceMap; pub const SlotMap = @import("slot_map.zig").SlotMap; pub const KeyedSlotMap = @import("keyed_slot_map.zig").KeyedSlotMap; pub const FixedArrayList = @import("fixed_array_list.zig").FixedArrayList; diff --git a/src/keyed_slot_map.zig b/src/keyed_slot_map.zig index a24d81c..70c125b 100644 --- a/src/keyed_slot_map.zig +++ b/src/keyed_slot_map.zig @@ -135,7 +135,7 @@ pub fn KeyedSlotMap( // // --- VALUES --- - slot_map: SlotMap(Entry), + map: SlotMap(Entry), lookup: std.AutoHashMap(K, Handle(V)), // --- MEMORY --- @@ -145,7 +145,7 @@ pub fn KeyedSlotMap( /// ---------------------------------------------------- pub fn init(alloc: std.mem.Allocator) Self { return .{ - .slot_map = .init(alloc), + .map = .init(alloc), .lookup = .init(alloc), .alloc = alloc, @@ -155,7 +155,7 @@ pub fn KeyedSlotMap( /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn deinit(self: *Self) void { - self.slot_map.deinit(); + self.map.deinit(); self.lookup.deinit(); } @@ -170,8 +170,8 @@ pub fn KeyedSlotMap( if (self.lookup.contains(key)) return error.Duplicate; // --- RESULT --- - const handle = try self.slot_map.put(.{ .value = value, .key = key }); - errdefer self.slot_map.remove(handle) catch {}; + const handle = try self.map.put(.{ .value = value, .key = key }); + errdefer self.map.remove(handle) catch {}; // --- NEW LOOKUP --- try self.lookup.put(key, handle); @@ -191,14 +191,14 @@ pub fn KeyedSlotMap( 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; + if (idx >= self.map.generations.items.len) return; + if (self.map.generations.items[idx] != handle.gen) return; // --- GRAB KEY BEFORE REMOVING --- - const key = self.slot_map.values.items[idx].key; + const key = self.map.values.items[idx].key; // --- REMOVE FROM SLOTMAP --- - try self.slot_map.remove(handle); + try self.map.remove(handle); // --- REMOVE KEY --- _ = self.lookup.remove(key); @@ -209,13 +209,13 @@ pub fn KeyedSlotMap( pub fn removeByKey(self: *Self, key: K) error{OutOfMemory}!void { const handle = self.lookup.get(key) orelse return; _ = self.lookup.remove(key); - try self.slot_map.remove(handle); + try self.map.remove(handle); } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn get(self: *Self, handle: Handle(V)) ?*V { - const entry = if (self.slot_map.get(handle)) |v| v else return null; + const entry = if (self.map.get(handle)) |v| v else return null; return &entry.value; } @@ -272,16 +272,16 @@ pub fn KeyedSlotMap( /// Removed (freed) slots are not counted /// ---------------------------------------------------- pub fn len(self: *const Self) usize { - return self.slot_map.values.items.len - self.slot_map.free_list.items.len; + return self.map.values.items.len - self.map.free_list.items.len; } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn iterator(self: *Self) Iterator { return .{ - .entries = self.slot_map.values.items, - .generations = self.slot_map.generations.items, - .free_list = self.slot_map.free_list.items, + .entries = self.map.values.items, + .generations = self.map.generations.items, + .free_list = self.map.free_list.items, }; } @@ -289,9 +289,9 @@ pub fn KeyedSlotMap( /// ---------------------------------------------------- pub fn keyIterator(self: *Self) KeyIterator { return .{ - .entries = self.slot_map.values.items, - .generations = self.slot_map.generations.items, - .free_list = self.slot_map.free_list.items, + .entries = self.map.values.items, + .generations = self.map.generations.items, + .free_list = self.map.free_list.items, }; } @@ -299,9 +299,9 @@ pub fn KeyedSlotMap( /// ---------------------------------------------------- pub fn valueIterator(self: *Self) ValueIterator { return .{ - .entries = self.slot_map.values.items, - .generations = self.slot_map.generations.items, - .free_list = self.slot_map.free_list.items, + .entries = self.map.values.items, + .generations = self.map.generations.items, + .free_list = self.map.free_list.items, }; } @@ -309,9 +309,9 @@ pub fn KeyedSlotMap( /// ---------------------------------------------------- pub fn handleIterator(self: *Self) HandleIterator { return .{ - .entries = self.slot_map.values.items, - .generations = self.slot_map.generations.items, - .free_list = self.slot_map.free_list.items, + .entries = self.map.values.items, + .generations = self.map.generations.items, + .free_list = self.map.free_list.items, }; } }; diff --git a/src/resource_map.zig b/src/resource_map.zig new file mode 100644 index 0000000..d4925ef --- /dev/null +++ b/src/resource_map.zig @@ -0,0 +1,279 @@ +//! ---------------------------------------------------- +//! ---------------------------------------------------- + +const std = @import("std"); +const Handle = @import("handle.zig").Handle; + +/// ---------------------------------------------------- +/// # Example +/// +/// ```zig +/// // --- MATERIALS --- +/// var materials: ResourceMap(Material, Material) = .init(alloc); +/// defer materials.deinit(); +/// +/// // --- ADD MATERIALS --- +/// _ = try materials.put(.{ .style = .{ .albedo = .{ 0.8, 0.2, 0.2, 1.0 } } }); +/// _ = try materials.put(.{ .unique = .{ .topology = .line_list } }); +/// ``` +/// ---------------------------------------------------- +pub fn ResourceMap( + comptime H: type, + comptime V: type, +) type { + return struct { + const Self = @This(); + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub const Entry = struct { + value: *V, + handle: Handle(H), + }; + + /// ---------------------------------------------------- + /// Cursor over live entries, yielding the value and its handle + /// Removed (freed) slots are skipped + /// ---------------------------------------------------- + pub const Iterator = struct { + values: []V, + 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: []V, + generations: []const u32, + free_list: []const u32, + index: usize = 0, + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub fn next(self: *HandleIterator) ?Handle(H) { + 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: []V, + generations: []const u32, + free_list: []const u32, + index: usize = 0, + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub fn next(self: *ValueIterator) ?*V { + 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: std.ArrayList(H), + + // --- LIFE TIMES --- + generations: std.ArrayList(u32), + free_list: std.ArrayList(u32), + + // --- GENERATION COUNTER --- + next_gen: u32 = 0, + + // --- MEMORY --- + alloc: std.mem.Allocator, + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub fn init(alloc: std.mem.Allocator) Self { + return .{ + .values = .empty, + .generations = .empty, + .free_list = .empty, + .alloc = alloc, + }; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub fn deinit(self: *Self) void { + self.values.deinit(self.alloc); + self.generations.deinit(self.alloc); + self.free_list.deinit(self.alloc); + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub fn put( + self: *Self, + value: V, + ) error{OutOfMemory}!Handle(H) { + // --- 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(self.alloc, value); + errdefer _ = self.values.pop(); + try self.generations.append(self.alloc, 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(H), + ) 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(self.alloc, idx); + self.generations.items[idx] = self.mint(); + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub fn get(self: *Self, handle: Handle(H)) ?*V { + if (handle.idx >= self.values.items.len) return null; + if (self.generations.items[handle.idx] != handle.gen) return null; + return &self.values.items[handle.idx]; + } + + /// ---------------------------------------------------- + /// ---------------------------------------------------- + pub fn contains(self: *Self, handle: Handle(H)) bool { + return self.get(handle) != null; + } + + /// ---------------------------------------------------- + /// Current valid handle for an index + /// Returns null if out of range or the slot is freed + /// ---------------------------------------------------- + pub fn getHandle(self: *const Self, idx: usize) ?Handle(H) { + if (idx >= self.values.items.len) return null; + if (std.mem.indexOfScalar(u32, self.free_list.items, @intCast(idx)) != null) return null; + return .{ .idx = @intCast(idx), .gen = self.generations.items[idx] }; + } + + /// ---------------------------------------------------- + /// Number of live entries + /// Removed (freed) slots are not counted + /// ---------------------------------------------------- + pub fn len(self: *const 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, + }; + } + + /// ---------------------------------------------------- + /// Reduce length to 0 + /// Invalidates all element pointers and handles + /// ---------------------------------------------------- + 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; + } + }; +}