diff --git a/src/entity.zig b/src/entity.zig index 575decb..65ce402 100644 --- a/src/entity.zig +++ b/src/entity.zig @@ -1,6 +1,7 @@ const std = @import("std"); const c = @import("./c.zig"); +const util = @import("./util.zig"); const Id = @import("./id.zig").Id; const Lookup = @import("./main.zig").Lookup; @@ -19,6 +20,12 @@ pub const EntityError = error{ IsNotAlive, }; +pub const EntityConfig = struct { + name: ?[:0]const u8 = null, + symbol: ?[:0]const u8 = null, + use_low_id: bool = false, +}; + /// An `Entity` is an `Id` which represents a "thing" within the world. This /// could be a traditional game object, but is also able to represent other /// concepts such as component types, relationship types, systems, modules, @@ -45,6 +52,39 @@ pub fn Entity(comptime ctx: anytype) type { return .{ .world = world, .raw = raw }; } + /// Creates a new `Entity` in the specified world. + /// See `World.entity(...)` for more information. + pub fn new(world: *World(ctx), config: EntityConfig, add: anytype) Self { + var desc = std.mem.zeroes(c.ecs_entity_desc_t); + desc.name = if (config.name) |n| n.ptr else null; + desc.symbol = if (config.symbol) |s| s.ptr else null; + desc.use_low_id = config.use_low_id; + + const meta = @typeInfo(@TypeOf(add)); + if (meta != .Struct or (meta.Struct.is_tuple == false and meta.Struct.fields.len > 0)) + @compileError("Expected tuple or empty struct, got '" ++ @typeName(@TypeOf(add)) ++ "'"); + if (meta.Struct.fields.len > c.FLECS_ID_DESC_MAX) + @compileError("Adding more than FLECS_ID_DESC_MAX ids"); + inline for (add, 0..) |a, i| + desc.add[i] = util.anyToId(ctx, a); + + switch (@typeInfo(@TypeOf(add))) { + .Struct => |s| { + if (s.is_tuple) { + if (s.fields.len > c.FLECS_ID_DESC_MAX) + @compileError("Adding more than FLECS_ID_DESC_MAX ids"); + inline for (add, 0..) |a, i| + desc.add[i] = util.anyToId(ctx, a); + } else if (s.fields.len > 0) + @compileError("Expected tuple or empty struct, got '" ++ @typeName(@TypeOf(add)) ++ "'"); + }, + else => @compileError("Expected tuple or empty struct, got '" ++ @typeName(@TypeOf(add)) ++ "'"), + } + + const result = c.ecs_entity_init(world.raw, &desc); + return Self.fromRaw(world, result); + } + /// Ensures this `Entity` is valid, returning an error otherwise. /// Entities that are valid can be used with API functions. /// diff --git a/src/util.zig b/src/util.zig index 0ca460b..71b1e2a 100644 --- a/src/util.zig +++ b/src/util.zig @@ -1,5 +1,71 @@ const std = @import("std"); +const c = @import("./c.zig"); + +const Lookup = @import("./main.zig").Lookup; +const Entity = @import("./entity.zig").Entity; +const Pair = @import("./pair.zig").Pair; +const Id = @import("./id.zig").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(comptime ctx: anytype, value: anytype) c.ecs_entity_t { + return switch (@TypeOf(value)) { + @TypeOf(null) => 0, + c.ecs_entity_t => value, + Entity(ctx) => value.raw, + ?Entity(ctx) => if (value) |v| v.raw else 0, + type => Lookup(ctx, value).id, + 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(comptime ctx: anytype, value: anytype) c.ecs_id_t { + const T = @TypeOf(value); + if (comptime std.meta.trait.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(ctx, value[0]); + const target = anyToEntity(ctx, value[1]); + return c.ecs_make_pair(relation, target); + } + return switch (T) { + @TypeOf(null) => 0, + c.ecs_id_t => value, + Id(ctx) => value.raw, + ?Id(ctx) => if (value) |v| v.raw else 0, + Pair(ctx) => value.raw, + ?Pair(ctx) => if (value) |v| v.raw else 0, + // Technically same type as `ecs_id_it`. + // c.ecs_entity_t => value, + Entity(ctx) => value.raw, + ?Entity(ctx) => if (value) |v| v.raw else 0, + type => Lookup(ctx, value).id, + else => @compileError("Value of type " ++ @typeName(T) ++ " can't be converted to Id"), + }; +} + /// Gets the simplified type name of the specified type. /// That is, without any namespace qualifiers. pub fn simpleTypeName(comptime T: type) [:0]const u8 { diff --git a/src/world.zig b/src/world.zig index 330e84b..333d1a1 100644 --- a/src/world.zig +++ b/src/world.zig @@ -6,6 +6,7 @@ const util = @import("./util.zig"); const Lookup = @import("./main.zig").Lookup; const Entity = @import("./entity.zig").Entity; +const EntityConfig = @import("./entity.zig").EntityConfig; const Iter = @import("./iter.zig").Iter; pub fn World(comptime ctx: anytype) type { @@ -50,19 +51,21 @@ pub fn World(comptime ctx: anytype) type { return lookupAlive(self, Lookup(ctx, T).id); } - // TODO: Replace these functions with an entity builder interface. - - pub fn entity( - self: *Self, - comptime add: []const type, - ) Entity(ctx) { - var desc = std.mem.zeroes(c.ecs_entity_desc_t); - if (add.len > c.FLECS_ID_DESC_MAX) - @compileLog("add.len > FLECS_ID_DESC_MAX"); - inline for (add, 0..) |T, i| - desc.add[i] = Lookup(ctx, T).id; - const result = c.ecs_entity_init(self.*.raw, &desc); - return Entity(ctx).fromRaw(self, result); + /// Creates a new `Entity` in this `World`. + /// + /// The `config` argument may be used to specify the `name` and + /// `symbol` used to create the entity with. If an entity with the + /// given name already exists, that one is modified instead. + /// + /// The `add` argument is a tuple that specifies the `Id`s the entity + /// is created with initially. For example: + /// `.{ Position, Velocity, .{ ChildOf, parent } }` + /// + /// When you add components, they are default-initialized. To set + /// their values you'll need to use one of the setter functions on the + /// `Entity` returned from this function. + pub fn entity(self: *Self, config: EntityConfig, add: anytype) Entity(ctx) { + return Entity(ctx).new(self, config, add); } pub fn component( @@ -70,16 +73,11 @@ pub fn World(comptime ctx: anytype) type { comptime T: type, ) Entity(ctx) { const name = util.simpleTypeName(T); - const entDesc = std.mem.zeroInit(c.ecs_entity_desc_t, .{ - .name = name, - .symbol = name, - .use_low_id = true, - }); - const compDesc = std.mem.zeroInit(c.ecs_component_desc_t, .{ - .entity = c.ecs_entity_init(self.raw, &entDesc), + const desc = std.mem.zeroInit(c.ecs_component_desc_t, .{ + .entity = self.entity(.{ .name = name, .symbol = name, .use_low_id = true }, .{}).raw, .type = .{ .size = @sizeOf(T), .alignment = @alignOf(T) }, }); - Lookup(ctx, T).id = c.ecs_component_init(self.raw, &compDesc); + Lookup(ctx, T).id = c.ecs_component_init(self.raw, &desc); return Entity(ctx).fromRaw(self, Lookup(ctx, T).id); } @@ -87,23 +85,25 @@ pub fn World(comptime ctx: anytype) type { self: *Self, name: [:0]const u8, callback: SystemCallback, - phase: ?Entity(ctx), + phase: anytype, expr: [:0]const u8, ) Entity(ctx) { var context = SystemCallbackContext.init(self, callback); - var entDesc = std.mem.zeroes(c.ecs_entity_desc_t); - entDesc.name = name; - if (phase) |p| { - entDesc.add[0] = c.ecs_pair(c.EcsDependsOn, p.raw); - entDesc.add[1] = p.raw; - } - var sysDesc = std.mem.zeroes(c.ecs_system_desc_t); - sysDesc.entity = c.ecs_entity_init(self.raw, &entDesc); - sysDesc.query.filter.expr = expr; - sysDesc.callback = &SystemCallbackContext.invoke; - sysDesc.binding_ctx = context; - sysDesc.binding_ctx_free = &SystemCallbackContext.free; - const result = c.ecs_system_init(self.raw, &sysDesc); + + const p = util.anyToEntity(ctx, phase); + const e = if (p != 0) + self.entity(.{ .name = name }, .{ .{ c.EcsDependsOn, p }, p }) + else + self.entity(.{ .name = name }, .{}); + + var desc = std.mem.zeroes(c.ecs_system_desc_t); + desc.entity = e.raw; + desc.query.filter.expr = expr; + desc.callback = &SystemCallbackContext.invoke; + desc.binding_ctx = context; + desc.binding_ctx_free = &SystemCallbackContext.free; + + const result = c.ecs_system_init(self.raw, &desc); return Entity(ctx).fromRaw(self, result); } diff --git a/test/world.zig b/test/world.zig index 03578f9..3045211 100644 --- a/test/world.zig +++ b/test/world.zig @@ -37,10 +37,9 @@ test "World_progress_w_0" { _ = world.component(Position); _ = world.component(Velocity); - const e1 = world.entity(&.{ Position, Velocity }); + const e1 = world.entity(.{}, .{ Position, Velocity }); - const phase = Entity.fromRaw(world, c.EcsOnUpdate); - const move_system = world.system("move", move, phase, "Position, Velocity"); + const move_system = world.system("move", move, c.EcsOnUpdate, "Position, Velocity"); var ctx = util.Probe.init(); c.ecs_set_context(world.raw, &ctx);