const std = @import("std"); const App = @import("app"); const Handle = App.template.Handle; /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn Registry( comptime K: type, comptime V: type, ) type { return struct { const Self = @This(); /// ---------------------------------------------------- /// ---------------------------------------------------- pub const Metadata = struct { source: ?[]const u8 = null, reload: ?*const fn (*V, App.scheduler.Context) anyerror!void = null, }; /// ---------------------------------------------------- /// ---------------------------------------------------- pub const Entry = struct { key: K, value: V, handle: Handle(V), }; // // FIELDS // meta: std.ArrayList(Metadata), lookup: std.AutoHashMap(K, Handle(V)), entries: std.ArrayList(Entry), alloc: std.mem.Allocator, // // LIFECYCLE // /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn init(alloc: std.mem.Allocator) Self { return .{ .meta = .empty, .lookup = .init(alloc), .entries = .empty, .alloc = alloc, }; } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn deinit(self: *Self) void { self.meta.deinit(self.alloc); self.lookup.deinit(); self.entries.deinit(self.alloc); } // // INSERTION // /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn put( self: *Self, key: K, value: V, ) !void { if (self.lookup.contains(key)) return error.Duplicate; try self.putNoDup(key, value, .{}); } /// ---------------------------------------------------- /// Insert or replace /// ---------------------------------------------------- pub fn upsert( self: *Self, key: K, value: V, ) !?V { if (self.lookup.contains(key)) { const idx = self.lookup.get(key).?; const old = self.entries.items[idx].value; self.entries.items[idx].value = value; return old; } try self.putNoDup(key, value, .{}); return null; } /// ---------------------------------------------------- /// Insert with metadata /// ---------------------------------------------------- pub fn putWith( self: *Self, key: K, value: V, metadata: Metadata, ) !void { if (self.lookup.contains(key)) return error.Duplicate; try self.putNoDup(key, value, metadata); } /// ---------------------------------------------------- /// ---------------------------------------------------- fn putNoDup( self: *Self, key: K, value: V, metadata: Metadata, ) !void { const handle: Handle(V) = .{ .idx = @intCast(self.entries.items.len), }; try self.entries.append(self.alloc, .{ .key = key, .value = value, .handle = handle }); errdefer _ = self.entries.swapRemove(self.entries.items.len - 1); try self.meta.append(self.alloc, metadata); errdefer _ = self.meta.swapRemove(self.meta.items.len - 1); try self.lookup.put( key, handle, ); } // // REMOVAL // /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn remove(self: *Self, key: K) bool { const idx = self.lookup.get(key) orelse return false; self.removeIndex(idx); return true; } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn removeLast(self: *Self) bool { if (self.entries.items.len == 0) return false; const last = self.entries.items.len - 1; _ = self.lookup.remove(self.entries.items[last].key); self.entries.swapRemove(last); self.meta.swapRemove(last); return true; } /// ---------------------------------------------------- /// ---------------------------------------------------- fn removeIndex(self: *Self, idx: usize) void { const last = self.entries.items.len - 1; // --- UPDATE LOOKUP --- if (idx != last) { self.lookup.put(self.entries.items[last].key, idx) catch {}; } _ = self.meta.swapRemove(idx); _ = self.lookup.remove(self.entries.items[idx].key); _ = self.entries.swapRemove(idx); } // // LOOKUP // /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn contains(self: Self, key: K) bool { return self.lookup.contains(key); } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn get(self: Self, key: K) ?*const V { const idx = self.lookup.get(key) orelse return null; return &self.entries.items[idx].value; } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn getPtr(self: *Self, key: K) ?*V { const idx = self.lookup.get(key) orelse return null; return &self.entries.items[idx].value; } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn getEntry(self: Self, key: K) ?*const Entry { const handle = self.lookup.get(key) orelse return null; return &self.entries.items[@intCast(handle.idx)]; } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn getHandle(self: *Self, key: K) ?Handle(V) { return self.lookup.get(key); } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn getMetadata(self: Self, key: K) ?*const Metadata { const idx = self.lookup.get(key) orelse return null; return &self.meta.items[idx]; } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn getPtrMetadata(self: *Self, key: K) ?*Metadata { const idx = self.lookup.get(key) orelse return null; return &self.meta.items[idx]; } // // ITERATION // /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn count(self: Self) usize { return self.entries.items.len; } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn values(self: Self) []const Entry { return self.entries.items; } /// ---------------------------------------------------- /// Value, Metadata /// ---------------------------------------------------- pub fn iterator(self: *Self) Iterator { return .{ .registry = self, .index = 0 }; } /// ---------------------------------------------------- /// ---------------------------------------------------- pub const Iterator = struct { registry: *Self, index: usize, /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn next(it: *Iterator) ?struct { value: *V, meta: *Metadata } { if (it.index >= it.registry.entries.items.len) return null; defer it.index += 1; return .{ .value = &it.registry.entries.items[it.index].value, .meta = &it.registry.meta.items[it.index], }; } }; // // IDK // /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn reloadAll( self: *Self, ctx: App.scheduler.Context, ) anyerror!void { var it = self.iterator(); while (it.next()) |entry| { if (entry.meta.reload) |reload| try reload(entry.value, ctx); } } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn deinitReverse(self: *Self) void { while (self.entries.items.len > 0) { _ = self.removeLast(); } } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn clear(self: *Self) void { self.meta.clearRetainingCapacity(); self.lookup.clearRetainingCapacity(); self.entries.clearRetainingCapacity(); } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn clearAndFree(self: *Self) void { self.meta.clearAndFree(); self.lookup.clearAndFree(); self.entries.clearAndFree(); } }; }