const std = @import("std"); const c = @import("./c.zig"); const Id = @import("./id.zig").Id; const Lookup = @import("./main.zig").Lookup; 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(); pub fn fromRaw(world: *World(ctx), raw: c.ecs_entity_t) Self { return .{ .world = world, .raw = raw }; } /// Ensures this `Entity` is valid, returning an error otherwise. /// Entities that are valid can be used with API functions. /// /// An `Entity` is valid if .. /// - .. it is not `0`. /// - .. it does not have invalid 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) !Self { // `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; return self; } /// Ensures this `Entity` is alive in this world, returning an error if not. pub fn ensureAlive(self: Self) !Self { return if ((try ensureValid(self)).isAlive()) self else EntityError.IsNotAlive; } pub fn isAlive(self: Self) bool { return c.ecs_is_alive(self.world.raw, self.raw); } 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); } 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) Self { const id = Lookup(ctx, T).id; _ = c.ecs_set_id(self.world.raw, self.raw, id, @sizeOf(T), &value); return self; } }; }