Backlog/lib/p2/src/structures/names.zig

244 lines
7.7 KiB
Zig

const std = @import("std");
const paged_vector = @import("paged-vector.zig");
const builtin = @import("builtin");
const PagedVectorAdvanced = paged_vector.PagedVectorAdvanced;
pub const NameRegistryStatus = enum {
Ok,
OutOfMemory,
};
pub const NameInvalid: Name = .{ .index = 0 };
const NameInvalidComptimeString = "Invalid";
// trying out a names scheme similar in vein to unreal engine's FNames
pub const NameRegistry = struct {
allocator: std.mem.Allocator,
map: std.StringHashMapUnmanaged(u32), // todo.. this can be replaced with a specialized hashmap
pagedVector: PagedVectorAdvanced([]const u8, 1024),
stringArena: std.heap.ArenaAllocator,
mutex: std.Thread.Mutex = .{},
// with this system errors should be nonexistent
// as a result I downgrade all errors into this error status field.
// I think the enhanced ergonomics of being able to use names ubiquitously is worth losing the
// immediate handling of the trace.
//
// usage of names should be null checked anyway.
errorStatus: NameRegistryStatus = .Ok,
pub fn init(allocator: std.mem.Allocator) !@This() {
var self = @This(){
.allocator = allocator,
.stringArena = std.heap.ArenaAllocator.init(allocator),
.map = .{},
.pagedVector = try PagedVectorAdvanced([]const u8, 1024).init(allocator),
};
_ = self.InstallNameInner(NameInvalidComptimeString, false);
return self;
}
pub fn count(self: @This()) u32 {
return @intCast(self.pagedVector.len());
}
pub fn name(self: *@This(), nameString: []const u8) Name {
const index = self.InstallNameInner(nameString, true);
return .{
.index = index,
.string = self.pagedVector.get(index).*,
};
}
fn InstallNameInner(self: *@This(), nameString: []const u8, shouldCopy: bool) u32 {
self.mutex.lock();
defer self.mutex.unlock();
var newString = nameString;
// check if the string exists if it does just return it as a name.
if (self.map.get(newString)) |index| {
return index;
}
if (shouldCopy) {
var allocator = self.stringArena.allocator();
newString = allocator.dupe(u8, nameString) catch {
self.errorStatus = .OutOfMemory;
@panic("Out of memory during name operation");
};
}
// add to registry
const newIndex = @as(u32, @intCast(self.pagedVector.len()));
self.pagedVector.append(self.allocator, newString) catch {
self.errorStatus = .OutOfMemory;
@panic("Out of memory during name operation");
};
self.map.put(self.allocator, newString, newIndex) catch {
self.errorStatus = .OutOfMemory;
@panic("Out of memory during name operation");
};
return newIndex;
}
pub fn deinit(self: *@This()) void {
self.mutex.lock();
defer self.mutex.unlock();
self.map.deinit(self.allocator);
self.pagedVector.deinit(self.allocator);
self.stringArena.deinit();
}
};
pub var gRegistry: *NameRegistry = undefined;
var registryCreated: bool = false;
pub fn createNameRegistry(allocator: std.mem.Allocator) !*NameRegistry {
gRegistry = try allocator.create(NameRegistry);
gRegistry.* = try NameRegistry.init(allocator);
registryCreated = true;
return gRegistry;
}
pub fn setNameRegistry(registry: *NameRegistry) void {
gRegistry = registry;
registryCreated = true;
}
pub fn getRegistry() *NameRegistry {
// specifically in the case of unittests we can silently just initialize a temporary registry
if (builtin.is_test) {
if (!registryCreated) {
// leaking here is fine? might piss off valgrind
_ = createNameRegistry(std.heap.c_allocator) catch unreachable;
}
}
return gRegistry;
}
pub fn destroyNameRegistry() void {
var allocator = gRegistry.allocator;
gRegistry.deinit();
allocator.destroy(gRegistry);
}
pub fn DefineName(comptime string: []const u8) Name {
return Name.MakeComptime(string);
}
pub fn MakeName(string: []const u8) Name {
return Name.Make(string);
}
pub const Name = struct {
index: ?u32 = 0, // do not directly access
string: []const u8 = "Invalid", // do not directly access
pub fn MakeComptime(string: []const u8) @This() {
return .{ .index = null, .string = string };
}
pub fn Make(string: []const u8) @This() {
return getRegistry().name(string);
}
pub fn fromUtf8(string: []const u8) @This() {
return getRegistry().name(string);
}
pub fn handle(self: *@This()) u32 {
if (self.index == null) {
const index = getRegistry().InstallNameInner(self.string, false);
self.index = index;
}
return self.index.?;
}
pub fn utf8(self: *@This()) []const u8 {
// so nasty... and const-violating. but the ergonomics is so good...
if (self.index == null) {
// screw it.. i'll pay the cost of duplicating even comptime strings
const index = getRegistry().InstallNameInner(self.string, true);
self.index = index;
}
return getRegistry().pagedVector.get(self.index.?).*;
}
pub fn eql(self: *const @This(), other: *const @This()) bool {
if (self.index == null) {
const index = getRegistry().InstallNameInner(self.string, false);
const mutableThis = @as(*@This(), @ptrCast(@constCast(self)));
mutableThis.*.index = index;
}
if (other.index == null) {
const index = getRegistry().InstallNameInner(self.string, false);
const mutableThis = @as(*@This(), @ptrCast(@constCast(self)));
mutableThis.*.index = index;
}
return self.index == other.index;
}
};
pub fn StaticName(comptime N: []const u8) type {
return struct {
pub const str = N;
pub var staticName: Name = .{ .string = str, .index = null };
pub fn ToName() Name {
.{ .string = N, .index = null };
}
pub fn get() Name {
return staticName;
}
pub fn handle() u32 {
return staticName.handle();
}
pub fn utf() []const u8 {
return staticName.utf8();
}
};
}
test "zname initialization and lookup" {
_ = try createNameRegistry(std.testing.allocator);
defer destroyNameRegistry();
const testComptimeName = "This is a name test";
const testComptimeName2 = "This is another name test";
var r = getRegistry();
var name = MakeName(testComptimeName);
var name2 = r.name(testComptimeName);
var name4 = r.name(testComptimeName2);
const fmtString = try std.fmt.allocPrint(std.testing.allocator, "morty I did horrible things to your {s}.", .{"locally elected representative"});
defer std.testing.allocator.free(fmtString);
var name3 = r.name(fmtString);
try std.testing.expectEqualSlices(u8, testComptimeName, name.utf8());
try std.testing.expectEqualSlices(u8, testComptimeName, name2.utf8());
try std.testing.expect(name2.eql(&name));
try std.testing.expect(!name3.eql(&name));
var uninitializedName: Name = .{};
try std.testing.expect(uninitializedName.eql(&r.name("Invalid")));
try std.testing.expect(!name4.eql(&name3));
try std.testing.expectEqualSlices(u8, name3.utf8(), fmtString);
}