From 8d0edc12f34833d4bd38feac8fc4563e7ff4c1a2 Mon Sep 17 00:00:00 2001 From: copygirl Date: Fri, 8 Sep 2023 15:37:02 +0200 Subject: [PATCH] Rename Entity.new to init, allow passing id --- src/entity.zig | 128 +++++++++++++++++++++++++------------------------ src/world.zig | 6 +-- 2 files changed, 69 insertions(+), 65 deletions(-) diff --git a/src/entity.zig b/src/entity.zig index cb0730d..1655c6c 100644 --- a/src/entity.zig +++ b/src/entity.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) { - .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 }); - 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; + 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; }, - .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; + .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 found = c.ecs_lookup_child(world.raw, parent, nameZ.ptr); + if (found == 0) { + 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; + }, + } + } + } // 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(); diff --git a/src/world.zig b/src/world.zig index f5d53a2..7ae96e1 100644 --- a/src/world.zig +++ b/src/world.zig @@ -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) {