Compare commits

...

6 Commits

Author SHA1 Message Date
copygirl 10ce815c2a Add Entity.getChildren 6 months ago
copygirl 1c9c65d8d2 Add world.term (temporary?) 6 months ago
copygirl 4e101a1643 Add Entity.getTargets 6 months ago
copygirl 2ff2579d87 Add World.lookupByPath 6 months ago
copygirl 2e36be7ad2 Add singleton API to World 6 months ago
copygirl 2a41366bd8 Have Entity.get return value instead of pointer 6 months ago
  1. 31
      src/entity.zig
  2. 94
      src/world.zig

@ -22,6 +22,7 @@ pub const EntityError = error{
GenMismatch,
/// Entity is not alive.
IsNotAlive,
// TODO: Rename this to `EntityMissing` and similar, use it elsewhere in the code.
};
/// An `Entity` is an `Id` which represents a "thing" within the world. This
@ -259,6 +260,31 @@ pub fn Entity(comptime ctx: anytype) type {
return if (result != 0) fromRaw(self.world, result) else null;
}
/// Returns an iterator that yields each of this `Entity`s children.
pub fn getChildren(self: Self) World(ctx).TermIterator {
return self.world.term(.{ c.EcsChildOf, self });
}
/// Returns an iterator that yields each of the targets of the
/// specified `relation` that this `Entity` has, if any.
pub fn getTargets(self: Self, relation: anytype) TargetIterator {
const rel = util.anyToEntity(ctx, relation);
return .{ .world = self.world, .entity = self.raw, .relation = rel.raw };
}
pub const TargetIterator = struct {
world: World(ctx),
entity: c.ecs_entity_t,
relation: c.ecs_entity_t,
index: c_int = 0,
pub fn next(self: *TargetIterator) ?Self {
var result = c.ecs_get_target(self.world.raw, self.entity, self.relation, self.index);
self.index += 1;
return if (result != 0) Self.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(...)`.
@ -277,10 +303,11 @@ pub fn Entity(comptime ctx: anytype) type {
c.ecs_remove_id(self.world.raw, self.raw, ecs_id);
}
pub fn get(self: Self, comptime T: type) ?*const T {
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);
return @alignCast(@ptrCast(ptr));
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 {

@ -10,6 +10,7 @@ const Lookup = @import("./main.zig").Lookup;
const Entity = @import("./entity.zig").Entity;
const EntityError = @import("./entity.zig").EntityError;
const Iter = @import("./iter.zig").Iter;
const Path = @import("./path.zig").Path;
pub fn World(comptime ctx: anytype) type {
return struct {
@ -59,6 +60,29 @@ pub fn World(comptime ctx: anytype) type {
return result;
}
// TODO: Reconsider whether lookup functions should return errors or just optionals.
// TODO: Reconsider whether "Entity" should always be in backticks in our doc comments.
// TODO: We really need tests for this function.
/// Returns the `Entity` at the specified path, or an error if the
/// entity does not exist. If the path is not absolute, the operation
/// will use the current scope, or the world root.
pub fn lookupByPath(self: *Self, path: Path) !Entity(ctx) {
var parent = if (path.absolute) 0 else c.ecs_get_scope(self.raw);
var current: c.ecs_entity_t = undefined;
for (path.parts) |part| {
current = switch (part) {
.id => |i| c.ecs_get_alive(self.raw, i),
.name => |n| c.ecs_lookup_child(self.raw, parent, n.ptr),
};
if (current == 0) return error.EntityNotFound;
// If a part is looked up by ID, parent must match.
// If a part is looked up by name, this check isn't necessary.
if (part == .id and parent != c.ecs_get_parent(self.raw, current)) return error.ParentMismatch;
parent = current;
}
return Entity(ctx).fromRaw(self, current);
}
/// Returns the component `Entity` registered for the specified
/// type `T`, or an error if an association has not been made.
pub fn lookupByType(self: *Self, comptime T: type) !Entity(ctx) {
@ -95,6 +119,41 @@ pub fn World(comptime ctx: anytype) type {
return Entity(ctx).fromRaw(self, result);
}
/// Registers a singleton component of type `T` with the specified value.
///
/// A singleton is a component which has itself added to its entity.
/// This allows looking up a value of `T` using its own type. Only one
/// singleton of each type can exist per world, but other entities may
/// still have this component added to them.
///
/// Use `get()` and `set()` to get and set the value of this singleton.
///
/// Returns the created component entity.
pub fn singleton(self: *Self, comptime T: type, value: T) !Entity(ctx) {
const single = try component(self, T);
single.set(T, value);
return single;
}
/// Gets the value of the singleton component with type `T`.
///
/// Returns `error.IsNotAlive` if the entity is missing.
/// Returns `error.ComponentMissing` if the component is missing.
/// Both of these may occur from not calling `singleton()` first.
pub fn get(self: *Self, comptime T: type) !T {
const e = try self.lookupByType(T);
return e.get(T) orelse error.ComponentMissing;
}
/// Sets the value of the singleton component with type `T`.
///
/// Returns `error.IsNotAlive` if the entity is missing.
/// This may occur from not calling `singleton()` first.
pub fn set(self: *Self, comptime T: type, value: T) !void {
const e = try self.lookupByType(T);
e.set(T, value);
}
pub fn system(
self: *Self,
name: [:0]const u8,
@ -150,6 +209,41 @@ pub fn World(comptime ctx: anytype) type {
}
};
/// Creates a term iterator which allows querying for all entities
/// that have a specific `Id`. This function supports wildcards.
pub fn term(self: *Self, id: anytype) TermIterator {
const id_ = util.anyToId(ctx, id);
var term_ = std.mem.zeroInit(c.ecs_term_t, .{ .id = id_ });
const iter = c.ecs_term_iter(self.raw, &term_);
return .{ .world = self, .iter = iter };
}
// TODO: Move this logic to `Iter`, add `TermIter` type?
// TODO: Rename `Iter` to `Iterator`?
pub const TermIterator = struct {
world: *World(ctx),
iter: c.ecs_iter_t,
index: i32 = 0,
pub fn next(self: *TermIterator) ?Entity(ctx) {
if (self.index >= self.iter.count)
if (!c.ecs_term_next(&self.iter))
return null;
const result = self.iter.entities[@intCast(self.index)];
self.index += 1;
return Entity(ctx).fromRaw(self.world, result);
}
// TODO: Check where else in the codebase it would make sense to have `deinit` take a pointer?
pub fn deinit(self: *TermIterator) void {
// Finalize the iterator if it hasn't run to completion.
if ((self.iter.flags & c.EcsIterIsValid) != 0)
c.ecs_iter_fini(&self.iter);
self.* = undefined;
}
};
/// Gets the current scope of this `World`. See also: `setScope()`.
pub fn getScope(self: *Self) ?Entity(ctx) {
const result = c.ecs_get_scope(self.raw);

Loading…
Cancel
Save