Entity.init improvements

- Move EntityConfig to Entity.Config
- Now able to specify .parent and .path
- Add "Entity_init_id_path" test
main
copygirl 1 year ago
parent 8e4a99bfe5
commit cf60a43e10
  1. 117
      src/entity.zig
  2. 2
      src/main.zig
  3. 3
      src/world.zig
  4. 18
      test/entity.zig

@ -23,12 +23,6 @@ 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,
@ -60,36 +54,117 @@ pub fn Entity(comptime ctx: anytype) type {
return .{ .world = world, .raw = raw };
}
/// Creates a new `Entity` in the specified world.
/// May return an existing entity if one with the given name exists.
/// May return an error if the entity creation failed.
pub const Config = struct {
/// Set to specify the entity's name.
name: ?[]const u8 = null,
/// Set to specify the entity's symbol.
symbol: ?[]const u8 = null,
/// Whether to use a small id when generating one.
/// These are typically reserved for components.
use_low_id: bool = false,
/// Set to specify the parent to create the entity under.
/// - Asserts when used together with an absolute `path`.
parent: ?Self = null,
/// Set to specify the `Path` under which to create the entity.
/// - Asserts when used together with a `name`.
/// - Asserts when the final part of the path is an id.
/// - Asserts when any parts refer to a non-existent id.
path: ?Path = null,
};
/// Creates a new `Entity` in the specified world. If `config` refers
/// to an existing entity, it is modified, then returned. Returns an
/// error if the entity creation failed.
///
/// 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.
/// Does not take ownership of any of the provided values.
/// `add` is a tuple that specifies `Id`s added to the entity.
/// For example: `.{ Position, Velocity, .{ ChildOf, parent } }`.
///
/// The `add` argument is a tuple that specifies the `Id`s the entity
/// is created with initially. For example:
/// `.{ Position, Velocity, .{ ChildOf, parent } }`
/// There are three ways to set a parent entity: Through `.parent`,
/// an absolute `.path` (resulting in no parent), or by passing a
/// `ChildOf` relationship in `add`. This function panics if these
/// methods are combined. If no parent is specified, it defaults to
/// the current world's scope.
///
/// 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 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;
pub fn new(world: *World(ctx), config: Config, add: anytype) !Self {
if (config.name != null and config.path != null)
std.debug.panic(".name and .path can't be used together", .{});
if (config.parent != null and config.path != null and config.path.?.absolute)
std.debug.panic(".parent and an absolute .path can't be used together", .{});
if (config.path) |path| if (path.parts[path.parts.len - 1] == .id)
std.debug.panic(".path must not have a final part that is an 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");
// Get name either from `.name` or last part of `.path`.
// Specifying a path with a single part is equivalent to using `.name`.
const name = if (config.path) |path|
path.parts[path.parts.len - 1].name
else if (config.name) |name|
name
else
null;
const absolute = if (config.path) |path| path.absolute else false;
var scope: ?c.ecs_entity_t = if (config.parent) |parent| parent.raw else if (absolute) 0 else null;
// Ensure the parent entities specified in `path` exist or create them.
if (config.path) |path| if (path.parts.len > 1)
for (path.parts[0..(path.parts.len - 1)]) |part| switch (part) {
.name => |n| {
// TODO: Use an allocator that's well-fitted for super short-lived allocations.
const nameZ = try world.allocator.dupeZ(u8, n);
defer world.allocator.free(nameZ);
const parent = scope orelse c.ecs_get_scope(world.raw);
const found = c.ecs_lookup_child(world.raw, parent, nameZ.ptr);
if (found == 0) {
var desc = std.mem.zeroInit(c.ecs_entity_desc_t, .{ .name = nameZ.ptr });
desc.add[0] = c.ecs_pair(c.EcsChildOf, parent);
scope = c.ecs_entity_init(world.raw, &desc);
if (scope == 0) return err.getLastErrorOrUnknown();
} else scope = found;
},
.id => |i| {
const found = c.ecs_get_alive(world.raw, i);
if (found == 0) std.debug.panic("No entity with id '{d}' found", .{i});
// TODO: Allow formatting entity, use it to improve this error message.
if (c.ecs_get_parent(world.raw, found) != scope.?) std.debug.panic("Entity '{d}' does not have expected parent", .{i});
scope = found;
},
};
// Set the scope to create the entity under (its parent).
// This avoids using up an id for a `ChildOf` relationship.
const previous = if (scope) |s| c.ecs_set_scope(world.raw, s) else null;
defer _ = if (previous) |s| c.ecs_set_scope(world.raw, s);
// TODO: Use an allocator that's well-fitted for super short-lived allocations.
const nameZ = if (name) |n| try world.allocator.dupeZ(u8, n) else null;
defer if (nameZ) |n| world.allocator.free(n);
const symbolZ = if (config.symbol) |s| try world.allocator.dupeZ(u8, s) else null;
defer if (symbolZ) |s| world.allocator.free(s);
var desc = std.mem.zeroInit(c.ecs_entity_desc_t, .{
.sep = "".ptr, // Disable tokenization.
.name = if (nameZ) |n| n.ptr else null,
.symbol = if (symbolZ) |s| s.ptr else null,
.use_low_id = config.use_low_id,
});
inline for (add, 0..) |a, i|
desc.add[i] = util.anyToId(ctx, a);
for (desc.add[0..add.len]) |id|
if (c.ecs_id_is_pair(id) and c.ECS_PAIR_FIRST(id) == c.EcsChildOf)
if (config.parent != null or config.path != null)
std.debug.panic("Found ChildOf relationship, but .parent or .path was specified", .{});
const result = c.ecs_entity_init(world.raw, &desc);
if (result == 0) return err.getLastErrorOrUnknown();
return Self.fromRaw(world, result);

@ -16,6 +16,8 @@ pub fn Context(comptime ctx: anytype) type {
pub const Iter = @import("./iter.zig").Iter(ctx);
pub const Pair = @import("./pair.zig").Pair(ctx);
pub const World = @import("./world.zig").World(ctx);
pub const Path = @import("./path.zig").Path;
};
}

@ -8,7 +8,6 @@ const util = @import("./util.zig");
const Lookup = @import("./main.zig").Lookup;
const Entity = @import("./entity.zig").Entity;
const EntityError = @import("./entity.zig").EntityError;
const EntityConfig = @import("./entity.zig").EntityConfig;
const Iter = @import("./iter.zig").Iter;
pub fn World(comptime ctx: anytype) type {
@ -64,7 +63,7 @@ pub fn World(comptime ctx: anytype) type {
/// Creates a new `Entity` in this `World`.
/// See `Entity.new(...)` for more information.
pub fn entity(self: *Self, config: EntityConfig, add: anytype) !Entity(ctx) {
pub fn entity(self: *Self, config: Entity(ctx).Config, add: anytype) !Entity(ctx) {
return Entity(ctx).new(self, config, add);
}

@ -14,8 +14,9 @@ const flecs = @import("../src/main.zig");
const c = flecs.c;
const context = flecs.Context(void);
const World = context.World;
const Entity = context.Entity;
const Path = context.Path;
const World = context.World;
test "Entity_init_id" {
var world = try World.initMinimal(alloc);
@ -38,3 +39,18 @@ test "Entity_init_id_name" {
defer path2.deinit();
try expectFmt("foo", "{}", .{path2});
}
test "Entity_init_id_path" {
var world = try World.initMinimal(alloc);
defer world.deinit();
const p = try Path.fromString("parent.child", null, alloc);
defer p.deinit();
const e = try world.entity(.{ .path = p }, .{});
// try expect(e.raw != 0); -- Not necessary, world.entity() returns error if result would be 0.
try expectEqualStrings("child", e.getName().?);
const path = try e.getPath(alloc);
defer path.deinit();
try expectFmt("parent.child", "{}", .{path});
}

Loading…
Cancel
Save