const std = @import("std"); const c = @import("./c.zig"); const meta = @import("./meta.zig"); pub fn Context(comptime ctx: anytype) type { return struct { pub const Entity = @import("./entity.zig").Entity(ctx); pub const Id = @import("./id.zig").Id(ctx); pub const Iter = @import("./iter.zig").Iter(ctx); pub const Pair = @import("./pair.zig").Pair(ctx); pub const World = @import("./world.zig").World(ctx); /// Looks up an entity ID unique to this `Context` for the provided /// type that has been registered previously, typically done by /// calling functions such as `World.component(...)`. pub fn lookup(comptime T: type) c.ecs_entity_t { const result = lookupMut(T).*; if (result == 0) std.debug.panic("No lookup for {s}", .{@typeName(T)}); return result; } /// Returns a pointer unique to this `Context` and the provided type /// that holds an entity ID. Useful for associating types to entities. pub fn lookupMut(comptime T: type) *c.ecs_entity_t { const EntityHolder = struct { comptime { // Capture `ctx` and `T` to create a unique struct. _ = .{ ctx, T }; } pub var id: c.ecs_entity_t = 0; }; return &EntityHolder.id; } /// Converts the specified value to an `ecs_entity_t`. /// /// If `null` is passed to this function, either directly or through an /// optional type of one of the supported types, `0` is returned. It's up to /// the caller to do any necessary testing for `0` values, and whether the /// returned entity is valid or alive. /// /// The following can be converted: /// - `Entity` and `ecs_entity_t` - Self-explanatory. /// - `type` - Looks up the entity associated with that type. pub fn anyToEntity(value: anytype) c.ecs_entity_t { return switch (@TypeOf(value)) { @TypeOf(null) => 0, c.ecs_entity_t => value, Entity => value.raw, ?Entity => if (value) |v| v.raw else 0, type => lookup(value), else => @compileError("Value of type " ++ @typeName(@TypeOf(value)) ++ " can't be converted to Entity"), }; } /// Converts the specified value to an `ecs_id_t`. /// /// If `null` is passed to this function, either directly or through an /// optional type of one of the supported types, `0` is returned. It's up to /// the caller to do any necessary testing for `0` values, and whether the /// returned id is valid. /// /// The following can be converted: /// - `Id` and `ecs_id_it` - Self-explanatory. /// - `Entity` and `ecs_entity_t` - Self-explanatory. /// - `Pair` - Converts to the equivalent `Id`. /// - `.{ relation, target }` - A `Pair`, converted using `anyToEntity()`. /// - `type` - Looks up the entity associated with that type. pub fn anyToId(value: anytype) c.ecs_id_t { const T = @TypeOf(value); if (comptime meta.isTuple(T)) { if (@typeInfo(T).Struct.fields.len != 2) @compileError("Value of type " ++ @typeName(T) ++ " must be a tuple with 2 elements, to be a Pair"); const relation = anyToEntity(value[0]); const target = anyToEntity(value[1]); return c.ecs_make_pair(relation, target); } return switch (T) { @TypeOf(null) => 0, c.ecs_id_t => value, Id => value.raw, ?Id => if (value) |v| v.raw else 0, Pair => value.raw, ?Pair => if (value) |v| v.raw else 0, // Technically same type as `ecs_id_it`. // c.ecs_entity_t => value, Entity => value.raw, ?Entity => if (value) |v| v.raw else 0, type => lookup(value), else => @compileError("Value of type " ++ @typeName(T) ++ " can't be converted to Id"), }; } /// Registers type lookups for the builtin Flecs entities. /// These are defined in the `./src/builtin/` directory. pub fn registerFlecsLookups(world: *World) !void { const error_writer = std.io.getStdErr().writer(); const flecs = @import("./builtin/flecs.zig"); const root = try lookupAndRegister(world, null, "flecs", flecs, error_writer); try lookupAndRegisterDeclarations(world, root, flecs, error_writer); } /// Registers type lookups for the builtin Flecs entities, but limited /// to the `flecs.core` namespace. This is used by `World.initMinimal()`. pub fn registerFlecsCoreLookups(world: *World) !void { const error_writer = std.io.getStdErr().writer(); const flecs = @import("./builtin/flecs.zig"); const flecs_core = @import("./builtin/flecs.core.zig"); const root = try lookupAndRegister(world, null, "flecs", flecs, error_writer); const core = try lookupAndRegister(world, root, "core", flecs_core, error_writer); try lookupAndRegisterDeclarations(world, core, flecs_core, error_writer); } fn lookupAndRegister( world: *World, parent: ?Entity, comptime name: []const u8, comptime T: type, error_writer: anytype, ) !Entity { if (world.lookupChild(parent, name)) |entity| { const id_ptr = lookupMut(T); if (id_ptr.* != 0 and id_ptr.* != entity.raw) { if (!meta.isNull(error_writer)) try std.fmt.format(error_writer, "Trying to register lookup for entity '{}' as {d}, but it's already registered as {d}\n", .{ entity, entity.raw, id_ptr.* }); return error.LookupIdMismatch; } id_ptr.* = entity.raw; return entity; } else { if (!meta.isNull(error_writer)) { if (parent) |p| try std.fmt.format(error_writer, "Could not find child '{s}' of parent '{}'\n", .{ name, p }) else try std.fmt.format(error_writer, "Could not find root entity '{s}'\n", .{name}); } return error.EntityNotFound; } } fn lookupAndRegisterDeclarations( world: *World, parent: Entity, comptime T: type, error_writer: anytype, ) !void { inline for (@typeInfo(T).Struct.decls) |decl| { const ChildType = @field(T, decl.name); if (@TypeOf(ChildType) != type) continue; comptime var child_name = decl.name; if (@hasDecl(T, "_" ++ decl.name ++ "_Name")) child_name = @field(T, "_" ++ decl.name ++ "_Name"); const child = try lookupAndRegister(world, parent, child_name, ChildType, error_writer); try lookupAndRegisterDeclarations(world, child, ChildType, error_writer); } } }; } test "Context multiple contexts" { const flecszigble = @import("./main.zig"); const expect = @import("./test/expect.zig"); flecszigble.init(std.testing.allocator); var world1 = try Context(1).World.initMinimal(); var world2 = try Context(2).World.initMinimal(); defer world1.deinit(); defer world2.deinit(); const Foo = struct {}; const Bar = struct {}; const foo1 = try world1.tag(Foo); const bar1 = try world1.tag(Bar); // Register tags in opposite order in `world2`. const bar2 = try world2.tag(Bar); const foo2 = try world2.tag(Foo); try expect.equal(foo1, try world1.lookupType(Foo)); try expect.equal(bar1, try world1.lookupType(Bar)); try expect.equal(foo2, try world2.lookupType(Foo)); try expect.equal(bar2, try world2.lookupType(Bar)); // Due to registration order, and IDs being unique to each World, // `Foo` from `world1` should have the same ID as `Bar` from `world2`. try expect.equal(foo1.raw, bar2.raw); try expect.equal(bar1.raw, foo2.raw); }