You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
8.3 KiB
191 lines
8.3 KiB
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); |
|
}
|
|
|