Added ResourceMap | Allows for different handle type, same as SlotMap
This commit is contained in:
parent
40b5ef05c9
commit
cf41b73241
3 changed files with 304 additions and 24 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
|||
279
src/resource_map.zig
Normal file
279
src/resource_map.zig
Normal 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue