162 lines
5.3 KiB
Zig
Raw Normal View History

2020-05-31 21:28:29 -07:00
const std = @import("std");
/// generates versioned "handles" (https://floooh.github.io/2018/06/17/handles-vs-pointers.html)
/// you choose the type of the handle (aka its size) and how much of that goes to the index and the version.
/// the bitsize of version + id must equal the handle size.
pub fn Handles(comptime HandleType: type, comptime IndexType: type, comptime VersionType: type) type {
std.debug.assert(@typeInfo(HandleType) == .Int and !HandleType.is_signed);
std.debug.assert(@typeInfo(IndexType) == .Int and !IndexType.is_signed);
std.debug.assert(@typeInfo(VersionType) == .Int and !VersionType.is_signed);
if (@bitSizeOf(IndexType) + @bitSizeOf(VersionType) != @bitSizeOf(HandleType))
@compileError("IndexType and VersionType must sum to HandleType's bit count");
return struct {
const Self = @This();
handles: []HandleType,
append_cursor: IndexType = 0,
last_destroyed: ?IndexType = null,
allocator: *std.mem.Allocator,
const invalid_id = std.math.maxInt(IndexType);
2020-06-13 14:29:13 -07:00
pub const Iterator = struct {
hm: Self,
index: usize = 0,
pub fn init(hm: Self) @This() {
return .{ .hm = hm };
}
pub fn next(self: *@This()) ?HandleType {
if (self.index == self.hm.append_cursor) return null;
for (self.hm.handles[self.index..self.hm.append_cursor]) |h| {
self.index += 1;
if (self.hm.alive(h)) {
return h;
}
}
return null;
}
};
2020-05-31 21:28:29 -07:00
pub fn init(allocator: *std.mem.Allocator) Self {
return initWithCapacity(allocator, 32);
}
pub fn initWithCapacity(allocator: *std.mem.Allocator, capacity: usize) Self {
return Self{
.handles = allocator.alloc(HandleType, capacity) catch unreachable,
.allocator = allocator,
};
}
pub fn deinit(self: Self) void {
self.allocator.free(self.handles);
}
pub fn extractId(self: Self, handle: HandleType) IndexType {
return @truncate(IndexType, handle);
}
pub fn extractVersion(self: Self, handle: HandleType) VersionType {
return @truncate(VersionType, handle >> @bitSizeOf(IndexType));
}
fn forge(id: IndexType, version: VersionType) HandleType {
return id | @as(HandleType, version) << @bitSizeOf(IndexType);
}
pub fn create(self: *Self) HandleType {
if (self.last_destroyed == null) {
// ensure capacity and grow if needed
if (self.handles.len - 1 == self.append_cursor) {
self.handles = self.allocator.realloc(self.handles, self.handles.len * 2) catch unreachable;
}
const id = self.append_cursor;
const handle = forge(self.append_cursor, 0);
self.handles[id] = handle;
self.append_cursor += 1;
return handle;
}
const version = self.extractVersion(self.handles[self.last_destroyed.?]);
const destroyed_id = self.extractId(self.handles[self.last_destroyed.?]);
const handle = forge(self.last_destroyed.?, version);
self.handles[self.last_destroyed.?] = handle;
self.last_destroyed = if (destroyed_id == invalid_id) null else destroyed_id;
return handle;
}
pub fn remove(self: *Self, handle: HandleType) !void {
const id = self.extractId(handle);
if (id > self.append_cursor or self.handles[id] != handle)
return error.RemovedInvalidHandle;
const next_id = self.last_destroyed orelse invalid_id;
if (next_id == id) return error.ExhaustedEntityRemoval;
const version = self.extractVersion(handle);
self.handles[id] = forge(next_id, version +% 1);
self.last_destroyed = id;
}
2020-06-13 14:29:13 -07:00
pub fn alive(self: Self, handle: HandleType) bool {
2020-05-31 21:28:29 -07:00
const id = self.extractId(handle);
return id < self.append_cursor and self.handles[id] == handle;
}
2020-06-13 14:29:13 -07:00
pub fn iterator(self: Self) Iterator {
return Iterator.init(self);
}
2020-05-31 21:28:29 -07:00
};
}
test "handles" {
var hm = Handles(u32, u20, u12).init(std.testing.allocator);
defer hm.deinit();
const e0 = hm.create();
const e1 = hm.create();
const e2 = hm.create();
2020-06-13 14:29:13 -07:00
std.debug.assert(hm.alive(e0));
std.debug.assert(hm.alive(e1));
std.debug.assert(hm.alive(e2));
2020-05-31 21:28:29 -07:00
hm.remove(e1) catch unreachable;
2020-06-13 14:29:13 -07:00
std.debug.assert(!hm.alive(e1));
2020-05-31 21:28:29 -07:00
std.testing.expectError(error.RemovedInvalidHandle, hm.remove(e1));
var e_tmp = hm.create();
2020-06-13 14:29:13 -07:00
std.debug.assert(hm.alive(e_tmp));
2020-05-31 21:28:29 -07:00
hm.remove(e_tmp) catch unreachable;
2020-06-13 14:29:13 -07:00
std.debug.assert(!hm.alive(e_tmp));
2020-05-31 21:28:29 -07:00
hm.remove(e0) catch unreachable;
2020-06-13 14:29:13 -07:00
std.debug.assert(!hm.alive(e0));
2020-05-31 21:28:29 -07:00
hm.remove(e2) catch unreachable;
2020-06-13 14:29:13 -07:00
std.debug.assert(!hm.alive(e2));
2020-05-31 21:28:29 -07:00
e_tmp = hm.create();
2020-06-13 14:29:13 -07:00
std.debug.assert(hm.alive(e_tmp));
2020-05-31 21:28:29 -07:00
e_tmp = hm.create();
2020-06-13 14:29:13 -07:00
std.debug.assert(hm.alive(e_tmp));
2020-05-31 21:28:29 -07:00
e_tmp = hm.create();
2020-06-13 14:29:13 -07:00
std.debug.assert(hm.alive(e_tmp));
2020-05-31 21:28:29 -07:00
}