|
|
@ -23,12 +23,6 @@ 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, |
|
|
@ -60,36 +54,117 @@ 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. |
|
|
|
pub const Config = struct { |
|
|
|
/// May return an existing entity if one with the given name exists. |
|
|
|
/// Set to specify the entity's name. |
|
|
|
/// May return an error if the entity creation failed. |
|
|
|
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 |
|
|
|
/// `add` is a tuple that specifies `Id`s added to the entity. |
|
|
|
/// `symbol` used to create the entity with. If an entity with the |
|
|
|
/// For example: `.{ Position, Velocity, .{ ChildOf, parent } }`. |
|
|
|
/// given name already exists, that one is modified instead. |
|
|
|
|
|
|
|
/// Does not take ownership of any of the provided values. |
|
|
|
|
|
|
|
/// |
|
|
|
/// |
|
|
|
/// The `add` argument is a tuple that specifies the `Id`s the entity |
|
|
|
/// There are three ways to set a parent entity: Through `.parent`, |
|
|
|
/// is created with initially. For example: |
|
|
|
/// an absolute `.path` (resulting in no parent), or by passing a |
|
|
|
/// `.{ Position, Velocity, .{ ChildOf, parent } }` |
|
|
|
/// `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 |
|
|
|
/// When you add components, they are default-initialized. To set |
|
|
|
/// their values you'll need to use one of the setter functions on the |
|
|
|
/// their values you'll need to use one of the setter functions on the |
|
|
|
/// `Entity` returned from this function. |
|
|
|
/// `Entity` returned from this function. |
|
|
|
pub fn new(world: *World(ctx), config: EntityConfig, add: anytype) !Self { |
|
|
|
pub fn new(world: *World(ctx), config: Config, add: anytype) !Self { |
|
|
|
var desc = std.mem.zeroes(c.ecs_entity_desc_t); |
|
|
|
if (config.name != null and config.path != null) |
|
|
|
desc.name = if (config.name) |n| n.ptr else null; |
|
|
|
std.debug.panic(".name and .path can't be used together", .{}); |
|
|
|
desc.symbol = if (config.symbol) |s| s.ptr else null; |
|
|
|
if (config.parent != null and config.path != null and config.path.?.absolute) |
|
|
|
desc.use_low_id = config.use_low_id; |
|
|
|
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)); |
|
|
|
const meta = @typeInfo(@TypeOf(add)); |
|
|
|
if (meta != .Struct or (meta.Struct.is_tuple == false and meta.Struct.fields.len > 0)) |
|
|
|
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)) ++ "'"); |
|
|
|
@compileError("Expected tuple or empty struct, got '" ++ @typeName(@TypeOf(add)) ++ "'"); |
|
|
|
if (meta.Struct.fields.len > c.FLECS_ID_DESC_MAX) |
|
|
|
if (meta.Struct.fields.len > c.FLECS_ID_DESC_MAX) |
|
|
|
@compileError("Adding more than FLECS_ID_DESC_MAX ids"); |
|
|
|
@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| |
|
|
|
inline for (add, 0..) |a, i| |
|
|
|
desc.add[i] = util.anyToId(ctx, a); |
|
|
|
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); |
|
|
|
const result = c.ecs_entity_init(world.raw, &desc); |
|
|
|
if (result == 0) return err.getLastErrorOrUnknown(); |
|
|
|
if (result == 0) return err.getLastErrorOrUnknown(); |
|
|
|
return Self.fromRaw(world, result); |
|
|
|
return Self.fromRaw(world, result); |
|
|
|