const std = @import("std"); const Allocator = std.mem.Allocator; const flecs = @import("./main.zig"); const c = flecs.c; 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.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. /// /// 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 modify an existing entity. id: ?Self = null, /// Set to specify the entity's name. name: ?[:0]const u8 = null, /// Set to specify the entity's symbol. symbol: ?[:0]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. parent: ?Self = null, /// Set to specify the `Path` under which to create the entity. path: ?Path = null, }; /// 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. /// /// `ids` is a tuple that specifies `Id`s added to the entity. /// For example: `.{ Position, Velocity, .{ ChildOf, parent } }`. /// /// 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 init(world: *World(ctx), config: Config, ids: anytype) !Self { const meta = @typeInfo(@TypeOf(ids)); if (meta != .Struct or (meta.Struct.is_tuple == false and meta.Struct.fields.len > 0)) @compileError("Expected tuple or empty struct, got '" ++ @typeName(@TypeOf(ids)) ++ "'"); if (meta.Struct.fields.len > c.FLECS_ID_DESC_MAX) @compileError("Adding more than FLECS_ID_DESC_MAX ids"); 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| { const found = c.ecs_lookup_child(world.raw, parent, n.ptr); if (found == 0) { var desc = std.mem.zeroInit(c.ecs_entity_desc_t, .{ .sep = "".ptr, .name = n.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 (scope != null) world.setScope(previous); var desc = std.mem.zeroInit(c.ecs_entity_desc_t, .{ .sep = "".ptr, // Disable tokenization. .id = if (id) |i| i else 0, .name = if (name) |n| n.ptr else null, .symbol = if (config.symbol) |s| s.ptr else null, .use_low_id = config.use_low_id, }); inline for (ids, 0..) |a, i| desc.add[i] = util.anyToId(ctx, a); for (desc.add[0..ids.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) return error.FoundChildOf; 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 .{ .world = self.world, .raw = self.raw }; } 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. /// /// The returned string is owned by Flecs and may only be valid while /// the entity is alive and its name doesn't change. 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. /// /// The returned string is owned by Flecs and may only be valid while /// the entity is alive and its symbol doesn't change. 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. /// /// Flecs does not take ownership of the provided value. 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. /// /// Flecs does not take ownership of the provided value. 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; } /// Returns whether this `Entity` has the specified value. /// /// `id` must be convertible to an id. See also: `util.anyToId(...)`. pub fn has(self: Self, id: anytype) bool { const ecs_id = util.anyToId(ctx, id); return c.ecs_has_id(self.world.raw, self.raw, ecs_id); } pub fn add(self: Self, id: anytype) void { const ecs_id = util.anyToId(ctx, id); c.ecs_add_id(self.world.raw, self.raw, ecs_id); } pub fn remove(self: Self, id: anytype) void { const ecs_id = util.anyToId(ctx, id); c.ecs_remove_id(self.world.raw, self.raw, ecs_id); } pub fn get(self: Self, comptime T: type) ?T { const id = Lookup(ctx, T).id; const ptr = c.ecs_get_id(self.world.raw, self.raw, id); const typed_ptr: ?*const T = @alignCast(@ptrCast(ptr)); return if (typed_ptr) |p| p.* else null; } pub fn get_mut(self: Self, comptime T: type) ?*T { const id = Lookup(ctx, T).id; const ptr = c.ecs_get_mut_id(self.world.raw, self.raw, id); return @alignCast(@ptrCast(ptr)); } 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); } }; }