Rename Entity.new to init, allow passing id

main
copygirl 1 year ago
parent 50c5a83c90
commit 8d0edc12f3
  1. 110
      src/entity.zig
  2. 6
      src/world.zig

@ -33,7 +33,7 @@ pub const EntityError = error{
/// components (such as `Position`), zero-size tags (such as `Disabled`) or
/// relationship `Pair`s (such as `.{ ChildOf, parent }`).
///
/// Entities can be created using `World.new()` and deleted with `delete()`.
/// Entities can be created using `World.entity()` and deleted with `delete()`.
/// When an entity is deleted it is no longer considered "alive". A world can
/// contain up to 4 billion alive entities.
///
@ -56,6 +56,8 @@ pub fn Entity(comptime ctx: anytype) type {
}
pub const Config = struct {
// Set to modify an existing entity.
id: ?Self = null,
/// Set to specify the entity's name.
name: ?[]const u8 = null,
/// Set to specify the entity's symbol.
@ -64,86 +66,87 @@ pub fn Entity(comptime ctx: anytype) type {
/// 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.
/// Creates or modifies an `Entity` in the specified `World`.
///
/// When the settings in `config` refer to an existing entity, it is
/// modified. When any of these settings conflict, such as when an
/// existing entity `.id` is specified but its `.name` doesn't match,
/// an error is returned.
///
/// `add` is a tuple that specifies `Id`s added to the entity.
/// 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
/// When adding components, they are just default-initialized.
/// To set their values you'll need to use `set(...)` on the
/// `Entity` returned from this function.
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", .{});
pub fn init(world: *World(ctx), config: Config, add: anytype) !Self {
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) {
var id = if (config.id) |i| i.raw else null;
var name = config.name;
var scope = if (config.parent) |p| p.raw else null;
if (config.path) |path| {
if (path.absolute) {
// Specifying parent with an absolute path is invalid.
if (config.parent != null) return error.ParentMismatch;
scope = 0;
}
// Ensure that if `id` or `name` is used alongside path,
// their values do actually match. Also sets these so they
// can be passed to the `ecs_entity_desc_t` struct.
switch (path.parts[path.parts.len - 1]) {
.id => |path_id| {
if (path_id == 0) return error.InvalidParameter;
if (id) |i| if (path_id != i) return error.IdMismatch;
id = path_id;
},
.name => |path_name| {
if (name) |n| if (!std.mem.eql(u8, path_name, n)) return error.NameMismatch;
name = path_name;
},
}
// Ensure the parent entities specified in `path` exist, or create them.
for (path.parts[0..(path.parts.len - 1)]) |part| {
const parent = scope orelse c.ecs_get_scope(world.raw);
switch (part) {
.id => |i| {
const found = c.ecs_get_alive(world.raw, i);
if (found == 0) return error.EntityNotFound;
if (parent != c.ecs_get_parent(world.raw, found)) return error.ParentMismatch;
scope = found;
},
.name => |n| {
// TODO: Use an allocator that's well-fitted for super short-lived allocations.
const nameZ = try flecs.allocator.dupeZ(u8, n);
defer flecs.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 });
var desc = std.mem.zeroInit(c.ecs_entity_desc_t, .{ .sep = "".ptr, .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| world.setScope(s) else null;
defer _ = if (previous) |s| world.setScope(s);
defer _ = if (scope != null) world.setScope(previous);
// TODO: Use an allocator that's well-fitted for super short-lived allocations.
const nameZ = if (name) |n| try flecs.allocator.dupeZ(u8, n) else null;
@ -153,6 +156,7 @@ pub fn Entity(comptime ctx: anytype) type {
var desc = std.mem.zeroInit(c.ecs_entity_desc_t, .{
.sep = "".ptr, // Disable tokenization.
.id = if (id) |i| i else 0,
.name = if (nameZ) |n| n.ptr else null,
.symbol = if (symbolZ) |s| s.ptr else null,
.use_low_id = config.use_low_id,
@ -161,10 +165,10 @@ pub fn Entity(comptime ctx: anytype) type {
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)
for (desc.add[0..add.len]) |i|
if (c.ecs_id_is_pair(i) and c.ECS_PAIR_FIRST(i) == c.EcsChildOf)
if (config.parent != null or config.path != null)
std.debug.panic("Found ChildOf relationship, but .parent or .path was specified", .{});
return error.FoundChildOf;
const result = c.ecs_entity_init(world.raw, &desc);
if (result == 0) return err.getLastErrorOrUnknown();

@ -61,10 +61,10 @@ pub fn World(comptime ctx: anytype) type {
return lookupAlive(self, Lookup(ctx, T).id);
}
/// Creates a new `Entity` in this `World`.
/// See `Entity.new(...)` for more information.
/// Creates or modifies an `Entity` in this `World`.
/// See `Entity.init(...)` for more information.
pub fn entity(self: *Self, config: Entity(ctx).Config, add: anytype) !Entity(ctx) {
return Entity(ctx).new(self, config, add);
return Entity(ctx).init(self, config, add);
}
pub fn tag(self: *Self, comptime T: type) !Entity(ctx) {

Loading…
Cancel
Save