diff --git a/src/meta.zig b/src/meta.zig index fab8461..b041e9d 100644 --- a/src/meta.zig +++ b/src/meta.zig @@ -85,3 +85,82 @@ pub fn AnyToType(comptime expr: anytype) type { else => @compileError("Expression must be a type or a tuple of two types"), } } + +/// Gets a Flecs-compatible symbol name for the specified type. +/// Supports structs (except anonymous or generic), as well as +/// single-item pointers to supported structs. +/// +/// If the struct defines a public accessible `name`, it is used. +pub fn flecsTypeName(comptime T: type) [:0]const u8 { + const allowed_characters = + "abcdefghijklmnopqrstuvwxyz" ++ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" ++ + "0123456789_"; + switch (@typeInfo(T)) { + .Struct => { + comptime var name: [:0]const u8 = undefined; + comptime if (@hasDecl(T, "name")) { + name = @field(T, "name"); + } else { + name = @typeName(T); + if (std.mem.indexOfScalar(u8, name, '{') != null) + @compileError("Expected type with simple name, got '" ++ @typeName(T) ++ "' (anonymous type?)"); + + // If name includes fully qualified namespace, strip it. + if (std.mem.lastIndexOfScalar(u8, name, '.')) |i| + name = name[(i + 1)..]; + + if (std.mem.indexOfScalar(u8, name, '(') != null) + @compileError("Expected type with simple name, got '" ++ @typeName(T) ++ "' (generic type?)"); + if (std.mem.indexOf(u8, name, "__") != null) + @compileError("Expected type with simple name, got '" ++ @typeName(T) ++ "' (anonymous type?)"); + if (std.mem.indexOfNone(u8, name, allowed_characters) != null) + @compileError("Expected type with simple name, got '" ++ @typeName(T) ++ "' (invalid characters?)"); + }; + return name; + }, + .Pointer => |p| { + if (p.size != .One) + @compileError("Expected struct or single-item pointer, got '" ++ @typeName(T) ++ "'"); + return flecsTypeName(p.child); + }, + else => @compileError("Expected struct or single-item pointer, got '" ++ @typeName(T) ++ "'"), + } +} + +const expect = @import("./test/expect.zig"); + +test flecsTypeName { + const Foo = struct {}; + try expect.equal("Foo", flecsTypeName(Foo)); + try expect.equal("Foo", flecsTypeName(*Foo)); + + const Bar = struct { + const Baz = struct { + const Quux = struct {}; + }; + }; + try expect.equal("Quux", flecsTypeName(Bar.Baz.Quux)); + + try expect.equal("Anon", flecsTypeName((struct { + const Anon = struct {}; + }).Anon)); + + try expect.equal("Random", flecsTypeName(std.Random)); + + // error: Expected struct or single-item pointer, got 'i32' + // _ = flecsTypeName(i32); + + // error: Expected type with simple name, got 'array_list.ArrayListAligned(i32,null)' (generic type?) + // _ flecsTypeName(std.ArrayList(i32)); + + // error: Expected type with simple name, got 'meta.decltest.flecsTypeName__struct_...' (anonymous type?) + // _ = flecsTypeName(struct {}); + + // error: Expected type with simple name, got 'struct{comptime foo: comptime_int = 0}' (anonymous type?) + // _ = flecsTypeName(@TypeOf(.{ .foo = 0 })); + + // error: Expected type with simple name, got 'meta.decltest.flecsTypeName.%&!*' (invalid characters?) + // const @"%&!*" = struct {}; + // _ = flecsTypeName(@"%&!*"); +} diff --git a/src/world.zig b/src/world.zig index df8f5fd..31e56b2 100644 --- a/src/world.zig +++ b/src/world.zig @@ -137,14 +137,16 @@ pub fn World(comptime ctx: anytype) type { return Entity.init(self, config, add); } - pub fn tag(self: *Self, name: [:0]const u8, comptime T: type) !Entity { + pub fn tag(self: *Self, comptime T: type) !Entity { + const name = meta.flecsTypeName(T); if (@sizeOf(T) > 0) @compileError("'" ++ @typeName(T) ++ "' must be a zero-sized type"); const result = try self.entity(.{ .name = name, .symbol = name }, .{}); Context.lookupMut(T).* = result.raw; return result; } - pub fn component(self: *Self, name: [:0]const u8, comptime T: type) !Entity { + pub fn component(self: *Self, comptime T: type) !Entity { + const name = meta.flecsTypeName(T); if (@sizeOf(T) == 0) @compileError("'" ++ @typeName(T) ++ "' must not be a zero-sized type"); const entity_ = try self.entity(.{ .name = name, .symbol = name, .use_low_id = true }, .{}); @@ -167,8 +169,8 @@ pub fn World(comptime ctx: anytype) type { /// Use `get()` and `set()` to get and set the value of this singleton. /// /// Returns the created component entity. - pub fn singleton(self: *Self, name: [:0]const u8, comptime T: type, value: T) !Entity { - const single = try component(self, name, T); + pub fn singleton(self: *Self, comptime T: type, value: T) !Entity { + const single = try component(self, T); single.set(T, value); return single; }