Add Pair, functions and docs to Id and Entity

main
copygirl 1 year ago
parent 41d5155d4d
commit bb5f20e810
  1. 81
      src/entity.zig
  2. 141
      src/id.zig
  3. 73
      src/pair.zig

@ -1,8 +1,39 @@
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),
@ -14,14 +45,60 @@ pub fn Entity(comptime ctx: anytype) type {
return .{ .world = world, .raw = raw };
}
pub fn isValid(self: Self) bool {
return c.ecs_is_valid(self.world.raw, self.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)));

@ -1,7 +1,20 @@
const c = @import("./c.zig");
const Entity = @import("./entity.zig").Entity;
const Pair = @import("./pair.zig").Pair;
const World = @import("./world.zig").World;
pub const IdError = error{
/// Id is `0`.
IsNone,
/// Id is or contains wildcards.
IsWildcard,
/// Id is not valid.
IsInvalid,
};
/// `Id`s are the things that can be added to an `Entity`.
/// It can be an `Entity` or `Pair`, and can have optional id flags.
pub fn Id(comptime ctx: anytype) type {
return struct {
world: *World(ctx),
@ -12,5 +25,133 @@ pub fn Id(comptime ctx: anytype) type {
pub fn fromRaw(world: *World(ctx), raw: c.ecs_id_t) Self {
return .{ .world = world, .raw = raw };
}
/// Ensures this `Id` is valid.
/// That is, it can be added to an entity.
///
/// An `Id` is valid if ..
/// - .. it is not `0`.
/// - .. it does not contain wildcards.
/// - .. it does not contain invalid entities.
pub fn ensureValid(self: Self) !Self {
if (self.raw == 0) return IdError.IsNone;
if (c.ecs_id_is_wildcard(self.raw)) return IdError.IsWildcard;
if (self.asPairUnsafe()) |p|
_ = try p.ensureValid()
else if (!self.isEntity())
if (!c.ecs_is_valid(self.raw & c.ECS_COMPONENT_MASK))
return IdError.IsInvalid;
return self;
}
pub fn isEntity(self: Self) bool {
return (self.raw & c.ECS_ID_FLAGS_MASK) != 0;
}
/// Attempts to return this `Id` as an `Entity`, or an error if it's
/// not an entity, not a valid entity or not alive in the world.
pub fn asEntityAlive(self: Self) !Entity(ctx) {
self.asEntityUnsafe().ensureAlive();
}
/// Returns this `Id` as an `Entity`, or `null` otherwise.
/// This assumes the `Id` is valid, or an invalid result could be returned.
pub fn asEntityUnsafe(self: Self) ?Entity(ctx) {
return if (isEntity(self)) @bitCast(self) else null;
}
pub fn isPair(self: Self) bool {
return c.ecs_id_is_pair(self.raw);
}
/// Attempts to return this `Id` as a `Pair`, or an error if it's not
/// a pair, or either of its elements are not valid or not alive in
/// the world.
pub fn asPairAlive(self: Self) !Pair(ctx) {
return @as(Pair(ctx), @bitCast(self)).ensureAlive();
}
/// Attempts to return this `Id` as a `Pair`, or an error if it's not
/// a pair, or either of its elements are not valid.
pub fn asPairValid(self: Self) !Pair(ctx) {
return @as(Pair(ctx), @bitCast(self)).ensureValid();
}
/// Returns this `Id` as a `Pair`, or `null` otherwise.
/// This assumes the `Id` is valid, or an invalid result could be returned.
pub fn asPairUnsafe(self: Self) ?Pair(ctx) {
return if (isPair(self)) @bitCast(self) else null;
}
pub fn isWildcard(self: Self) bool {
return c.ecs_id_is_wildcard(self.raw);
}
/// Returns whether this `Id` a tag.
/// That is, a component without data / size.
///
/// An `Id` is a tag when it ..
/// - .. is an entity without the `EcsComponent` component.
/// - .. has an `EcsComponent` with size member set to 0.
/// - .. is a `Pair` where both elements are a tag.
/// - .. is a `Pair` where `.relation` has the `EcsTag` tag.
pub fn isTag(self: Self) bool {
return c.ecs_id_is_tag(self.world.raw, self.raw);
}
/// Returns whether this `Id` represents a union.
/// Only `Pair`s can be unions.
///
/// An `Id` represents a union when ..
/// - .. the `.relation` of the pair is `EcsUnion`.
/// - .. the `.relation` of the pair has `EcsUnion`.
pub fn isUnion(self: Self) bool {
return c.ecs_id_is_union(self.world.raw, self.raw);
}
/// Returns whether this `Id` is in use in the world.
/// That is, if it has been added to one or more tables.
pub fn isInUse(self: Self) bool {
return c.ecs_id_in_use(self.world.raw, self.raw);
}
/// Gets the number of entities in the world that have this `Id`.
pub fn count(self: Self) usize {
return @intCast(c.ecs_count_id(self.world.raw, self.raw));
}
// Get the type for an id.
// This function returnsthe type information for an id. The specified id can be
// any valid id. For the rules on how type information is determined based on
// id, see ecs_get_typeid.
// TODO: const ecs_type_info_t* ecs_get_type_info(const ecs_world_t *world, ecs_id_t id);
/// Attempts to get the component type for this `Id`.
///
/// This operation returns the entity representing the component type
/// for this `Id`, if it is associated with a type. For a regular
/// component with a non-zero size (an entity with the `EcsComponent`
/// component) the operation will return the `Entity` itself.
///
/// For an entity that does not have the `EcsComponent` component, or
/// with an `EcsComponent` value with size 0, the operation will
/// return `null`.
///
/// For a `Pair`, the operation will return the type associated with
/// the pair, by applying the following rules in order:
/// - If `.relation` is a component, it is returned.
/// - If `.relation` has the `Tag` property, `null` is returned.
/// - If `.target` is a component, it is returned.
/// - Otherwise, `null` is returned.
pub fn getTypeId(self: Self) ?Entity(ctx) {
const raw = c.ecs_get_typeid(self.world.raw, self.raw);
return if (raw != 0) Entity(ctx).fromRaw(self.world, raw);
}
/// Returns if the provided `pattern` matches this `Id`.
/// The pattern may contain a wildcard (or wildcards, if a `Pair`).
pub fn matches(self: Self, pattern: Self) bool {
return c.ecs_id_match(self.raw, pattern.raw);
}
};
}

@ -0,0 +1,73 @@
const c = @import("./c.zig");
const Entity = @import("./entity.zig").Entity;
const Id = @import("./id.zig").Id;
const World = @import("./world.zig").World;
pub const PairError = error{
/// Id is `0`.
IsNone,
/// Id is not a `Pair`.
IsNotPair,
};
/// A `Pair` is an `Id` which encodes a relationship between two entities.
/// It is made of two parts, `relation` and `target`, each represented as
/// entities themselves. `relation` is an entity describing the nature of the
/// relationship, and `target` is the target this relationship is about.
///
/// For example, if the entity `Alice` likes another entity `Bob`, you would
/// add the pair `(Likes, Bob)` to entity `Alice`.
///
/// `Pair`s are created using `init()`, or can be converted from `Id`s using
/// `Id.asPair()` (fails if the id isn't a pair). They can be turned back
/// to an `Id` with `asId()` (always succeeds). Elements of the relationship
/// can be extracted by calling `getRelation()` and `getTarget()`.
pub fn Pair(comptime ctx: anytype) type {
return struct {
world: *World(ctx),
raw: c.ecs_id_t,
const Self = @This();
/// Build a pair from the specified `relation` and `target` entities.
/// The specified entities must be alive in the world.
pub fn init(relation: Entity(ctx), target: Entity(ctx)) !Self {
const raw = c.ecs_make_pair(relation.ensureAlive().raw, target.ensureAlive().raw);
return .{ .world = relation.world, .raw = raw };
}
/// Ensures this `Pair` is valid and its `relation` and `target`
/// entities are alive in the world, returning an error otherwise.
pub fn ensureAlive(self: Self) !Self {
if (self.raw == 0) return error.IsNone;
if (!c.ecs_id_is_pair(self.raw)) return error.IsntPair;
_ = try self.getRelation().ensureAlive();
_ = try self.getTarget().ensureAlive();
return self;
}
/// Ensures this `Pair` and its elements are valid.
pub fn ensureValid(self: Self) !Self {
if (self.raw == 0) return error.IsNone;
if (!c.ecs_id_is_pair(self.raw)) return error.IsntPair;
_ = try self.getRelation().ensureValid();
_ = try self.getTarget().ensureValid();
return self;
}
pub fn asId(self: Self) Id(ctx) {
return @bitCast(self);
}
pub fn getRelation(self: Self) Entity(ctx) {
return Entity(ctx).fromRaw(self.world.raw, c.ECS_PAIR_FIRST(self.raw));
}
pub fn getTarget(self: Self) Entity(ctx) {
return Entity(ctx).fromRaw(self.world.raw, c.ECS_PAIR_SECOND(self.raw));
}
// TODO: Decide whether to copy `Id` functions over?
};
}
Loading…
Cancel
Save