diff --git a/zig-ecs/src/ecs/utils.zig b/zig-ecs/src/ecs/utils.zig index f7d72e7..87cc054 100644 --- a/zig-ecs/src/ecs/utils.zig +++ b/zig-ecs/src/ecs/utils.zig @@ -49,8 +49,9 @@ pub fn typeId64(comptime T: type) u64 { return hashStringFnv(u64, @typeName(T)); } +/// u32 Fowler-Noll-Vo string hash pub fn hashString(comptime str: []const u8) u32 { - return @truncate(u32, std.hash.Wyhash.hash(0, str)); + return hashStringFnv(u32, str); } /// Fowler–Noll–Vo string hash. ReturnType should be u32/u64 diff --git a/zig-ecs/src/resources/assets.zig b/zig-ecs/src/resources/assets.zig index 34ad698..b231755 100644 --- a/zig-ecs/src/resources/assets.zig +++ b/zig-ecs/src/resources/assets.zig @@ -23,22 +23,26 @@ pub const Assets = struct { self.caches.deinit(); } - pub fn registerCache(self: *Assets, comptime AssetT: type, comptime LoaderT: type) *Cache(AssetT) { - var cache = Cache(AssetT).initPtr(self.allocator, LoaderT); + pub fn get(self: *Assets, comptime AssetT: type) *Cache(AssetT) { + if (self.caches.getValue(utils.typeId(AssetT))) |tid| { + return @intToPtr(*Cache(AssetT), tid); + } + + var cache = Cache(AssetT).initPtr(self.allocator); _ = self.caches.put(utils.typeId(AssetT), @ptrToInt(cache)) catch unreachable; return cache; } - pub fn get(self: Assets, comptime AssetT: type) *Cache(AssetT) { - if (self.caches.getValue(utils.typeId(AssetT))) |tid| { - return @intToPtr(*Cache(AssetT), tid); - } - unreachable; + pub fn load(self: *Assets, id: u16, comptime loader: var) ReturnType(loader, false) { + return self.get(ReturnType(loader, true)).load(id, loader); } - pub fn load(self: Assets, comptime AssetT: type, comptime LoaderT: type, args: LoaderT.LoadArgs) *AssetT { - var cache = self.get(AssetT); - return cache.load(666, LoaderT, args); + fn ReturnType(comptime loader: var, strip_ptr: bool) type { + var ret = @typeInfo(@TypeOf(@field(loader, "load"))).BoundFn.return_type.?; + if (strip_ptr) { + return ret.Child; + } + return ret; } }; @@ -50,13 +54,6 @@ test "assets" { } }; - const ThingLoader = struct { - pub const LoadArgs = struct {}; - pub fn load(self: @This(), args: var) *Thing { - return std.testing.allocator.create(Thing) catch unreachable; - } - }; - const OtherThing = struct { fart: i32, pub fn deinit(self: *@This()) void { @@ -64,20 +61,33 @@ test "assets" { } }; - const OtherThingLoader = struct { - pub const LoadArgs = struct {}; - pub fn load(self: @This(), args: var) *OtherThing { + const OtherThingLoadArgs = struct { + pub fn load(self: @This()) *OtherThing { return std.testing.allocator.create(OtherThing) catch unreachable; } }; + const ThingLoadArgs = struct { + pub fn load(self: @This()) *Thing { + return std.testing.allocator.create(Thing) catch unreachable; + } + }; + var assets = Assets.init(std.testing.allocator); defer assets.deinit(); - var cache = assets.registerCache(Thing, ThingLoader); - var thing = assets.get(Thing).load(6, ThingLoader, ThingLoader.LoadArgs{}); - std.testing.expectEqual(cache.size(), 1); + var thing = assets.get(Thing).load(6, ThingLoadArgs{}); + std.testing.expectEqual(assets.get(Thing).size(), 1); + + var thing2 = assets.load(4, ThingLoadArgs{}); + std.testing.expectEqual(assets.get(Thing).size(), 2); + + var other_thing = assets.get(OtherThing).load(6, OtherThingLoadArgs{}); + std.testing.expectEqual(assets.get(OtherThing).size(), 1); + + var other_thing2 = assets.load(8, OtherThingLoadArgs{}); + std.testing.expectEqual(assets.get(OtherThing).size(), 2); - var thing2 = assets.load(Thing, ThingLoader, ThingLoader.LoadArgs{}); - std.testing.expectEqual(cache.size(), 2); + assets.get(OtherThing).clear(); + std.testing.expectEqual(assets.get(OtherThing).size(), 0); } diff --git a/zig-ecs/src/resources/cache.zig b/zig-ecs/src/resources/cache.zig index b5be765..65563b5 100644 --- a/zig-ecs/src/resources/cache.zig +++ b/zig-ecs/src/resources/cache.zig @@ -1,24 +1,18 @@ const std = @import("std"); const ErasedPtr = @import("../ecs/utils.zig").ErasedPtr; -/// Simple cache for resources of a given type. TLoader should be a struct that implements a single -/// method: load(args: var) *T. If any resource has a deinit method it will be called when clear -/// or remove is called. +/// Simple cache for resources of a given type. If any resource has a deinit method it will be called when clear +/// or remove is called. Implementing a "loader" which is passed to "load" is a struct with one method: +/// - load(self: @This()) *T. pub fn Cache(comptime T: type) type { return struct { const Self = @This(); safe_deinit: fn (*@This()) void, - resources: std.AutoHashMap(u16, *T), - loader: ErasedPtr, + resources: std.AutoHashMap(u32, *T), allocator: ?*std.mem.Allocator = null, - pub fn initPtr(allocator: *std.mem.Allocator, comptime LoaderT: type) *@This() { - const ptr = if (@sizeOf(LoaderT) > 0) - ErasedPtr.init(allocator.create(LoaderT) catch unreachable) - else - ErasedPtr.init(LoaderT); - + pub fn initPtr(allocator: *std.mem.Allocator) *@This() { var cache = allocator.create(@This()) catch unreachable; cache.safe_deinit = struct { fn deinit(self: *Self) void { @@ -27,18 +21,12 @@ pub fn Cache(comptime T: type) type { self.allocator.?.destroy(self); } }.deinit; - cache.loader = ptr; - cache.resources = std.AutoHashMap(u16, *T).init(allocator); + cache.resources = std.AutoHashMap(u32, *T).init(allocator); cache.allocator = allocator; return cache; } - pub fn init(allocator: *std.mem.Allocator, comptime LoaderT: type) @This() { - const ptr = if (@sizeOf(LoaderT) > 0) - ErasedPtr.init(std.testing.allocator.create(LoaderT) catch unreachable) - else - ErasedPtr.init(LoaderT); - + pub fn init(allocator: *std.mem.Allocator) @This() { return .{ .safe_deinit = struct { fn deinit(self: *Self) void { @@ -46,8 +34,7 @@ pub fn Cache(comptime T: type) type { self.resources.deinit(); } }.deinit, - .loader = ptr, - .resources = std.AutoHashMap(u16, *T).init(allocator), + .resources = std.AutoHashMap(u32, *T).init(allocator), }; } @@ -55,21 +42,21 @@ pub fn Cache(comptime T: type) type { self.safe_deinit(self); } - fn load(self: *@This(), id: u16, comptime LoaderT: type, args: LoaderT.LoadArgs) *T { + pub fn load(self: *@This(), id: u32, comptime loader: var) @typeInfo(@TypeOf(@field(loader, "load"))).BoundFn.return_type.? { if (self.resources.getValue(id)) |resource| { return resource; } - var resource = self.loader.as(LoaderT).load(args); + var resource = loader.load(); _ = self.resources.put(id, resource) catch unreachable; return resource; } - pub fn contains(self: *@This(), id: u16) bool { + pub fn contains(self: *@This(), id: u32) bool { return self.resources.contains(id); } - pub fn remove(self: *@This(), id: u16) void { + pub fn remove(self: *@This(), id: u32) void { if (self.resources.remove(id)) |kv| { if (@hasDecl(T, "deinit")) { @call(.{ .modifier = .always_inline }, @field(kv.value, "deinit"), .{}); @@ -95,6 +82,8 @@ pub fn Cache(comptime T: type) type { } test "cache" { + const utils = @import("../ecs/utils.zig"); + const Thing = struct { fart: i32, pub fn deinit(self: *@This()) void { @@ -102,21 +91,20 @@ test "cache" { } }; - const ThingLoader = struct { - pub const LoadArgs = struct {}; - pub fn load(self: @This(), args: var) *Thing { + const ThingLoadArgs = struct { + pub fn load(self: @This()) *Thing { return std.testing.allocator.create(Thing) catch unreachable; } }; - var cache = Cache(Thing).init(std.testing.allocator, ThingLoader); + var cache = Cache(Thing).init(std.testing.allocator); defer cache.deinit(); - var thing = cache.load(6, ThingLoader, ThingLoader.LoadArgs{}); - var thing2 = cache.load(2, ThingLoader, ThingLoader.LoadArgs{}); + var thing = cache.load(utils.hashString("my/id"), ThingLoadArgs{}); + var thing2 = cache.load(utils.hashString("another/id"), ThingLoadArgs{}); std.testing.expectEqual(cache.size(), 2); - cache.remove(2); + cache.remove(utils.hashString("my/id")); std.testing.expectEqual(cache.size(), 1); cache.clear();