Compare commits

..

2 commits

4 changed files with 314 additions and 24 deletions

View file

@ -32,6 +32,7 @@ pub const Handle = @import("handle.zig").Handle;
pub const TypeID = @import("type_id.zig").of; pub const TypeID = @import("type_id.zig").of;
// --- ARRAYS --- // --- ARRAYS ---
pub const ResourceMap = @import("resource_map.zig").ResourceMap;
pub const SlotMap = @import("slot_map.zig").SlotMap; pub const SlotMap = @import("slot_map.zig").SlotMap;
pub const KeyedSlotMap = @import("keyed_slot_map.zig").KeyedSlotMap; pub const KeyedSlotMap = @import("keyed_slot_map.zig").KeyedSlotMap;
pub const FixedArrayList = @import("fixed_array_list.zig").FixedArrayList; pub const FixedArrayList = @import("fixed_array_list.zig").FixedArrayList;

View file

@ -135,7 +135,7 @@ pub fn KeyedSlotMap(
// //
// --- VALUES --- // --- VALUES ---
slot_map: SlotMap(Entry), map: SlotMap(Entry),
lookup: std.AutoHashMap(K, Handle(V)), lookup: std.AutoHashMap(K, Handle(V)),
// --- MEMORY --- // --- MEMORY ---
@ -145,7 +145,7 @@ pub fn KeyedSlotMap(
/// ---------------------------------------------------- /// ----------------------------------------------------
pub fn init(alloc: std.mem.Allocator) Self { pub fn init(alloc: std.mem.Allocator) Self {
return .{ return .{
.slot_map = .init(alloc), .map = .init(alloc),
.lookup = .init(alloc), .lookup = .init(alloc),
.alloc = alloc, .alloc = alloc,
@ -155,7 +155,7 @@ pub fn KeyedSlotMap(
/// ---------------------------------------------------- /// ----------------------------------------------------
/// ---------------------------------------------------- /// ----------------------------------------------------
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
self.slot_map.deinit(); self.map.deinit();
self.lookup.deinit(); self.lookup.deinit();
} }
@ -170,8 +170,8 @@ pub fn KeyedSlotMap(
if (self.lookup.contains(key)) return error.Duplicate; if (self.lookup.contains(key)) return error.Duplicate;
// --- RESULT --- // --- RESULT ---
const handle = try self.slot_map.put(.{ .value = value, .key = key }); const handle = try self.map.put(.{ .value = value, .key = key });
errdefer self.slot_map.remove(handle) catch {}; errdefer self.map.remove(handle) catch {};
// --- NEW LOOKUP --- // --- NEW LOOKUP ---
try self.lookup.put(key, handle); try self.lookup.put(key, handle);
@ -191,14 +191,14 @@ pub fn KeyedSlotMap(
const idx: u32 = handle.idx; const idx: u32 = handle.idx;
// --- CHECK IF STALE --- // --- CHECK IF STALE ---
if (idx >= self.slot_map.generations.items.len) return; if (idx >= self.map.generations.items.len) return;
if (self.slot_map.generations.items[idx] != handle.gen) return; if (self.map.generations.items[idx] != handle.gen) return;
// --- GRAB KEY BEFORE REMOVING --- // --- GRAB KEY BEFORE REMOVING ---
const key = self.slot_map.values.items[idx].key; const key = self.map.values.items[idx].key;
// --- REMOVE FROM SLOTMAP --- // --- REMOVE FROM SLOTMAP ---
try self.slot_map.remove(handle); try self.map.remove(handle);
// --- REMOVE KEY --- // --- REMOVE KEY ---
_ = self.lookup.remove(key); _ = self.lookup.remove(key);
@ -209,13 +209,13 @@ pub fn KeyedSlotMap(
pub fn removeByKey(self: *Self, key: K) error{OutOfMemory}!void { pub fn removeByKey(self: *Self, key: K) error{OutOfMemory}!void {
const handle = self.lookup.get(key) orelse return; const handle = self.lookup.get(key) orelse return;
_ = self.lookup.remove(key); _ = self.lookup.remove(key);
try self.slot_map.remove(handle); try self.map.remove(handle);
} }
/// ---------------------------------------------------- /// ----------------------------------------------------
/// ---------------------------------------------------- /// ----------------------------------------------------
pub fn get(self: *Self, handle: Handle(V)) ?*V { 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; return &entry.value;
} }
@ -272,16 +272,16 @@ pub fn KeyedSlotMap(
/// Removed (freed) slots are not counted /// Removed (freed) slots are not counted
/// ---------------------------------------------------- /// ----------------------------------------------------
pub fn len(self: *const Self) usize { 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 { pub fn iterator(self: *Self) Iterator {
return .{ return .{
.entries = self.slot_map.values.items, .entries = self.map.values.items,
.generations = self.slot_map.generations.items, .generations = self.map.generations.items,
.free_list = self.slot_map.free_list.items, .free_list = self.map.free_list.items,
}; };
} }
@ -289,9 +289,9 @@ pub fn KeyedSlotMap(
/// ---------------------------------------------------- /// ----------------------------------------------------
pub fn keyIterator(self: *Self) KeyIterator { pub fn keyIterator(self: *Self) KeyIterator {
return .{ return .{
.entries = self.slot_map.values.items, .entries = self.map.values.items,
.generations = self.slot_map.generations.items, .generations = self.map.generations.items,
.free_list = self.slot_map.free_list.items, .free_list = self.map.free_list.items,
}; };
} }
@ -299,9 +299,9 @@ pub fn KeyedSlotMap(
/// ---------------------------------------------------- /// ----------------------------------------------------
pub fn valueIterator(self: *Self) ValueIterator { pub fn valueIterator(self: *Self) ValueIterator {
return .{ return .{
.entries = self.slot_map.values.items, .entries = self.map.values.items,
.generations = self.slot_map.generations.items, .generations = self.map.generations.items,
.free_list = self.slot_map.free_list.items, .free_list = self.map.free_list.items,
}; };
} }
@ -309,9 +309,9 @@ pub fn KeyedSlotMap(
/// ---------------------------------------------------- /// ----------------------------------------------------
pub fn handleIterator(self: *Self) HandleIterator { pub fn handleIterator(self: *Self) HandleIterator {
return .{ return .{
.entries = self.slot_map.values.items, .entries = self.map.values.items,
.generations = self.slot_map.generations.items, .generations = self.map.generations.items,
.free_list = self.slot_map.free_list.items, .free_list = self.map.free_list.items,
}; };
} }
}; };

279
src/resource_map.zig Normal file
View file

@ -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;
}
};
}

View file

@ -207,6 +207,16 @@ pub fn SlotMap(comptime T: type) type {
return self.get(handle) != null; 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(T) {
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 /// Number of live entries
/// Removed (freed) slots are not counted /// Removed (freed) slots are not counted