New entity initialization code

- Add util.anyToId and .anyToEntity
  Used to convert any compatible value
  to an `ecs_id_t` / `ecs_entity_t`
- Add World.entity and Entity.new
  Allows adding any amount of ids to
  the newly created entity via a tuple.
main
copygirl 1 year ago
parent 1ee748cbbf
commit 846f6d0a78
  1. 40
      src/entity.zig
  2. 66
      src/util.zig
  3. 70
      src/world.zig
  4. 5
      test/world.zig

@ -1,6 +1,7 @@
const std = @import("std"); const std = @import("std");
const c = @import("./c.zig"); const c = @import("./c.zig");
const util = @import("./util.zig");
const Id = @import("./id.zig").Id; const Id = @import("./id.zig").Id;
const Lookup = @import("./main.zig").Lookup; const Lookup = @import("./main.zig").Lookup;
@ -19,6 +20,12 @@ pub const EntityError = error{
IsNotAlive, 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 /// 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 /// could be a traditional game object, but is also able to represent other
/// concepts such as component types, relationship types, systems, modules, /// 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 }; 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. /// Ensures this `Entity` is valid, returning an error otherwise.
/// Entities that are valid can be used with API functions. /// Entities that are valid can be used with API functions.
/// ///

@ -1,5 +1,71 @@
const std = @import("std"); 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. /// Gets the simplified type name of the specified type.
/// That is, without any namespace qualifiers. /// That is, without any namespace qualifiers.
pub fn simpleTypeName(comptime T: type) [:0]const u8 { pub fn simpleTypeName(comptime T: type) [:0]const u8 {

@ -6,6 +6,7 @@ const util = @import("./util.zig");
const Lookup = @import("./main.zig").Lookup; const Lookup = @import("./main.zig").Lookup;
const Entity = @import("./entity.zig").Entity; const Entity = @import("./entity.zig").Entity;
const EntityConfig = @import("./entity.zig").EntityConfig;
const Iter = @import("./iter.zig").Iter; const Iter = @import("./iter.zig").Iter;
pub fn World(comptime ctx: anytype) type { 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); return lookupAlive(self, Lookup(ctx, T).id);
} }
// TODO: Replace these functions with an entity builder interface. /// Creates a new `Entity` in this `World`.
///
pub fn entity( /// The `config` argument may be used to specify the `name` and
self: *Self, /// `symbol` used to create the entity with. If an entity with the
comptime add: []const type, /// given name already exists, that one is modified instead.
) Entity(ctx) { ///
var desc = std.mem.zeroes(c.ecs_entity_desc_t); /// The `add` argument is a tuple that specifies the `Id`s the entity
if (add.len > c.FLECS_ID_DESC_MAX) /// is created with initially. For example:
@compileLog("add.len > FLECS_ID_DESC_MAX"); /// `.{ Position, Velocity, .{ ChildOf, parent } }`
inline for (add, 0..) |T, i| ///
desc.add[i] = Lookup(ctx, T).id; /// When you add components, they are default-initialized. To set
const result = c.ecs_entity_init(self.*.raw, &desc); /// their values you'll need to use one of the setter functions on the
return Entity(ctx).fromRaw(self, result); /// `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( pub fn component(
@ -70,16 +73,11 @@ pub fn World(comptime ctx: anytype) type {
comptime T: type, comptime T: type,
) Entity(ctx) { ) Entity(ctx) {
const name = util.simpleTypeName(T); const name = util.simpleTypeName(T);
const entDesc = std.mem.zeroInit(c.ecs_entity_desc_t, .{ const desc = std.mem.zeroInit(c.ecs_component_desc_t, .{
.name = name, .entity = self.entity(.{ .name = name, .symbol = name, .use_low_id = true }, .{}).raw,
.symbol = name,
.use_low_id = true,
});
const compDesc = std.mem.zeroInit(c.ecs_component_desc_t, .{
.entity = c.ecs_entity_init(self.raw, &entDesc),
.type = .{ .size = @sizeOf(T), .alignment = @alignOf(T) }, .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); return Entity(ctx).fromRaw(self, Lookup(ctx, T).id);
} }
@ -87,23 +85,25 @@ pub fn World(comptime ctx: anytype) type {
self: *Self, self: *Self,
name: [:0]const u8, name: [:0]const u8,
callback: SystemCallback, callback: SystemCallback,
phase: ?Entity(ctx), phase: anytype,
expr: [:0]const u8, expr: [:0]const u8,
) Entity(ctx) { ) Entity(ctx) {
var context = SystemCallbackContext.init(self, callback); var context = SystemCallbackContext.init(self, callback);
var entDesc = std.mem.zeroes(c.ecs_entity_desc_t);
entDesc.name = name; const p = util.anyToEntity(ctx, phase);
if (phase) |p| { const e = if (p != 0)
entDesc.add[0] = c.ecs_pair(c.EcsDependsOn, p.raw); self.entity(.{ .name = name }, .{ .{ c.EcsDependsOn, p }, p })
entDesc.add[1] = p.raw; else
} self.entity(.{ .name = name }, .{});
var sysDesc = std.mem.zeroes(c.ecs_system_desc_t);
sysDesc.entity = c.ecs_entity_init(self.raw, &entDesc); var desc = std.mem.zeroes(c.ecs_system_desc_t);
sysDesc.query.filter.expr = expr; desc.entity = e.raw;
sysDesc.callback = &SystemCallbackContext.invoke; desc.query.filter.expr = expr;
sysDesc.binding_ctx = context; desc.callback = &SystemCallbackContext.invoke;
sysDesc.binding_ctx_free = &SystemCallbackContext.free; desc.binding_ctx = context;
const result = c.ecs_system_init(self.raw, &sysDesc); desc.binding_ctx_free = &SystemCallbackContext.free;
const result = c.ecs_system_init(self.raw, &desc);
return Entity(ctx).fromRaw(self, result); return Entity(ctx).fromRaw(self, result);
} }

@ -37,10 +37,9 @@ test "World_progress_w_0" {
_ = world.component(Position); _ = world.component(Position);
_ = world.component(Velocity); _ = 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, c.EcsOnUpdate, "Position, Velocity");
const move_system = world.system("move", move, phase, "Position, Velocity");
var ctx = util.Probe.init(); var ctx = util.Probe.init();
c.ecs_set_context(world.raw, &ctx); c.ecs_set_context(world.raw, &ctx);

Loading…
Cancel
Save