You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
7.9 KiB
191 lines
7.9 KiB
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, |
|
}; |
|
|
|
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 |
|
/// 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 }; |
|
} |
|
|
|
/// Creates a new `Entity` in the specified world. |
|
/// May return an existing entity if one with the given name exists. |
|
/// May return an error if the entity creation failed. |
|
/// |
|
/// The `config` argument may be used to specify the `name` and |
|
/// `symbol` used to create the entity with. If an entity with the |
|
/// 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 |
|
/// is created with initially. For example: |
|
/// `.{ Position, Velocity, .{ ChildOf, parent } }` |
|
/// |
|
/// 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: EntityConfig, add: anytype) !Self { |
|
var desc = std.mem.zeroes(c.ecs_entity_desc_t); |
|
desc.name = if (config.name) |n| n.ptr else null; |
|
desc.symbol = if (config.symbol) |s| s.ptr else null; |
|
desc.use_low_id = config.use_low_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"); |
|
inline for (add, 0..) |a, i| |
|
desc.add[i] = util.anyToId(ctx, a); |
|
|
|
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); |
|
} |
|
}; |
|
}
|
|
|