|
|
|
const std = @import("std");
|
|
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
|
|
|
|
const c = @import("./c.zig");
|
|
|
|
const err = @import("./error.zig");
|
|
|
|
const util = @import("./util.zig");
|
|
|
|
|
|
|
|
const Id = @import("./id.zig").Id;
|
|
|
|
const Lookup = @import("./main.zig").Lookup;
|
|
|
|
const Path = @import("./path.zig").Path;
|
|
|
|
const World = @import("./world.zig").World;
|
|
|
|
|
|
|
|
pub const EntityError = error{
|
|
|
|
/// Id is `0`.
|
|
|
|
IsNone,
|
|
|
|
/// Id is not valid.
|
|
|
|
IsInvalid,
|
|
|
|
/// Id is not an `Entity`.
|
|
|
|
IsNotEntity,
|
|
|
|
/// Generation doesn't match.
|
|
|
|
GenMismatch,
|
|
|
|
/// Entity is not alive.
|
|
|
|
IsNotAlive,
|
|
|
|
};
|
|
|
|
|
|
|
|
/// 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,
|
|
|
|
/// resources, and anything else you can make fit.
|
|
|
|
///
|
|
|
|
/// Each `Entity` can have a number of `Id`s added to it, which could be
|
|
|
|
/// 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()`.
|
|
|
|
/// When an entity is deleted it is no longer considered "alive". A world can
|
|
|
|
/// contain up to 4 billion alive entities.
|
|
|
|
///
|
|
|
|
/// An `Id` can be converted to an `Entity` using `Id.asEntity()` (fails if
|
|
|
|
/// the id isn't an entity), and back again with `asId()` (always succeeds).
|
|
|
|
pub fn Entity(comptime ctx: anytype) type {
|
|
|
|
return struct {
|
|
|
|
world: *World(ctx),
|
|
|
|
raw: c.ecs_entity_t,
|
|
|
|
|
|
|
|
const Self = @This();
|
|
|
|
|
|
|
|
/// Returns an `Entity` for the specified world and raw entity id.
|
|
|
|
///
|
|
|
|
/// No safety checks are done, so if you pass an invalid id, an
|
|
|
|
/// invalid `Entity` might be returned that could cause panics if
|
|
|
|
/// passed to other API functions without ensuring their validity.
|
|
|
|
pub fn fromRaw(world: *World(ctx), raw: c.ecs_entity_t) Self {
|
|
|
|
return .{ .world = world, .raw = raw };
|
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
|
|
|
///
|
|
|
|
/// `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
|
|
|
|
/// `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", .{});
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Ensures this `Entity` is valid, returning an error otherwise.
|
|
|
|
/// Invalid entities may cause panics when used with API functions.
|
|
|
|
///
|
|
|
|
/// An `Entity` is valid if ..
|
|
|
|
/// - .. it is not `0`.
|
|
|
|
/// - .. it does not have dead bits set.
|
|
|
|
/// - .. it does not have any `Id` flags set.
|
|
|
|
/// - .. no entity with this id is alive, but this entity's generation is `0`.
|
|
|
|
/// - .. an entity with this exact id and generation is alive in the world.
|
|
|
|
pub fn ensureValid(self: Self) !void {
|
|
|
|
// `0` is not a valid entity id.
|
|
|
|
if (self.raw == 0) return EntityError.IsNone;
|
|
|
|
// Entity ids should not contain data in dead zone bits.
|
|
|
|
const VALID_BITS_MASK: c.ecs_entity_t = 0xFF00FFFFFFFFFFFF;
|
|
|
|
if ((self.raw & ~VALID_BITS_MASK) != 0) return EntityError.IsInvalid;
|
|
|
|
// Entity ids should not contain `Id` flag bits.
|
|
|
|
if ((self.raw & c.ECS_ID_FLAGS_MASK) != 0) return EntityError.IsNotEntity;
|
|
|
|
|
|
|
|
// Get the currently alive entity entity with the same id.
|
|
|
|
// If entity is alive, the generation has to match.
|
|
|
|
// If entity is not alive, the generation has to be `0`.
|
|
|
|
const entity = c.ecs_get_alive(self.world.raw, self.raw);
|
|
|
|
if (c.ECS_GENERATION(entity) != c.ECS_GENERATION(self.raw))
|
|
|
|
return EntityError.GenMismatch;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Ensures this `Entity` is alive in this world, returning an error if not.
|
|
|
|
pub fn ensureAlive(self: Self) !void {
|
|
|
|
if (!c.ecs_is_alive(self.world.raw, self.raw)) return EntityError.IsNotAlive;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn asId(self: Self) Id(ctx) {
|
|
|
|
return @bitCast(self);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn getEntityId(self: Self) u32 {
|
|
|
|
return @intCast(self.raw & c.ECS_ENTITY_MASK);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn getGeneration(self: Self) u16 {
|
|
|
|
return @intCast(c.ECS_GENERATION(self.raw));
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Deletes this `Entity` and all of its components.
|
|
|
|
pub fn delete(self: Self) void {
|
|
|
|
c.ecs_delete(self.world.raw, self.raw);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the full, absolute `Path` of this `Entity`.
|
|
|
|
/// The entity is assumed to be alive.
|
|
|
|
/// See also: `Path.fromEntity(...)`.
|
|
|
|
pub fn getPath(self: Self, alloc: Allocator) !Path {
|
|
|
|
return Path.fromEntity(ctx, null, self, alloc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Gets the name of this `Entity`, or `null` if none.
|
|
|
|
pub fn getName(self: Self) ?[:0]const u8 {
|
|
|
|
const result = c.ecs_get_name(self.world.raw, self.raw);
|
|
|
|
return std.mem.sliceTo(result, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Gets the symbol of this `Entity`, or `null` if none.
|
|
|
|
pub fn getSymbol(self: Self) ?[:0]const u8 {
|
|
|
|
const result = c.ecs_get_symbol(self.world.raw, self.raw);
|
|
|
|
return std.mem.sliceTo(result, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the name of this `Entity` to the specified value, if any.
|
|
|
|
pub fn setName(self: Self, value: ?[*:0]const u8) void {
|
|
|
|
_ = c.ecs_set_name(self.world.raw, self.raw, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the symbol of this `Entity` to the specified value, if any.
|
|
|
|
pub fn setSymbol(self: Self, value: ?[*:0]const u8) void {
|
|
|
|
_ = c.ecs_set_symbol(self.world.raw, self.raw, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Gets the parent of this `Entity`, or `null` if it has none.
|
|
|
|
pub fn getParent(self: Self) ?Self {
|
|
|
|
const result = c.ecs_get_parent(self.world.raw, self.raw);
|
|
|
|
return if (result != 0) fromRaw(self.world, result) else null;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get(self: Self, comptime T: type) ?*const T {
|
|
|
|
const id = Lookup(ctx, T).id;
|
|
|
|
return @alignCast(@ptrCast(c.ecs_get_id(self.world.raw, self.raw, id)));
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set(self: Self, comptime T: type, value: T) void {
|
|
|
|
const id = Lookup(ctx, T).id;
|
|
|
|
_ = c.ecs_set_id(self.world.raw, self.raw, id, @sizeOf(T), &value);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|