From bb5f20e81021732ade85820a98cf69e7c1d667ad Mon Sep 17 00:00:00 2001 From: copygirl Date: Sat, 26 Aug 2023 10:54:48 +0200 Subject: [PATCH] Add Pair, functions and docs to Id and Entity --- src/entity.zig | 81 +++++++++++++++++++++++++++- src/id.zig | 141 +++++++++++++++++++++++++++++++++++++++++++++++++ src/pair.zig | 73 +++++++++++++++++++++++++ 3 files changed, 293 insertions(+), 2 deletions(-) create mode 100644 src/pair.zig diff --git a/src/entity.zig b/src/entity.zig index 9d1d70e..0ebfa95 100644 --- a/src/entity.zig +++ b/src/entity.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))); diff --git a/src/id.zig b/src/id.zig index 2b17d8f..4f07e7c 100644 --- a/src/id.zig +++ b/src/id.zig @@ -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); + } }; } diff --git a/src/pair.zig b/src/pair.zig new file mode 100644 index 0000000..3a6a0d0 --- /dev/null +++ b/src/pair.zig @@ -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? + }; +}