High-level wrapper around Flecs, a powerful ECS (Entity Component System) library, written in Zig language
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.

287 lines
12 KiB

const std = @import("std");
const Allocator = std.mem.Allocator;
const flecszigble = @import("./main.zig");
const c = @import("./c.zig");
const errors = @import("./errors.zig");
const meta = @import("./meta.zig");
const Path = @import("./path.zig");
const flecs = @import("./builtin/flecs.zig");
const DependsOn = flecs.core.DependsOn;
pub fn World(comptime ctx: anytype) type {
return struct {
const Self = @This();
const Context = @import("./context.zig").Context(ctx);
const Entity = Context.Entity;
const Iter = Context.Iter;
raw: *c.ecs_world_t,
pub fn init() !*Self {
std.debug.assert(flecszigble.is_initialized);
var result = try flecszigble.allocator.create(Self);
result.raw = c.ecs_init().?;
try Context.registerFlecsLookups(result);
return result;
}
pub fn initWithArgs(args: [][*:0]const u8) !*Self {
std.debug.assert(flecszigble.is_initialized);
var result = try flecszigble.allocator.create(Self);
result.raw = c.ecs_init_w_args(args.len, args.ptr).?;
try Context.registerFlecsLookups(result);
return result;
}
pub fn initMinimal() !*Self {
std.debug.assert(flecszigble.is_initialized);
var result = try flecszigble.allocator.create(Self);
result.raw = c.ecs_mini().?;
try Context.registerFlecsCoreLookups(result);
return result;
}
pub fn deinit(self: *Self) void {
_ = c.ecs_fini(self.raw);
flecszigble.allocator.destroy(self);
}
pub fn progress(self: *Self, delta_time: f32) bool {
return c.ecs_progress(self.raw, delta_time);
}
pub fn quit(self: *Self) void {
c.ecs_quit(self.raw);
}
/// Returns an `Entity` for the specified `ecs_entity_t` value, or an
/// error if the entity is invalid or not alive in this `World`.
pub fn lookupAlive(self: *Self, id: c.ecs_entity_t) !Entity {
const result = Entity.fromRaw(self, id);
try result.ensureAlive();
return result;
}
/// Returns the `Entity` with the specified unique symbol, or null if none.
pub fn lookupSymbol(self: *Self, symbol: []const u8) ?Entity {
const result = c.ecs_lookup_symbol(self.raw, symbol.ptr, false, false);
return if (result != 0) Entity.fromRaw(self, result) else null;
}
/// Returns the `Entity` with the specified parent and name, or null if none.
/// If no parent is specified, the world root is used instead.
pub fn lookupChild(self: *Self, parent: anytype, name: []const u8) ?Entity {
var name_buffer: [256]u8 = undefined;
if (name.len + 1 > name_buffer.len) @panic("Name too long");
@memcpy(name_buffer[0..name.len], name);
name_buffer[name.len] = 0;
const parent_ = Context.anyToEntity(parent);
const result = c.ecs_lookup_child(self.raw, parent_, &name_buffer);
return if (result != 0) Entity.fromRaw(self, result) else null;
}
// 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 lookupPath(self: *Self, path: Path) !Entity {
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.fromRaw(self, current);
}
/// Returns the component `Entity` registered for the specified type
/// `T`, or an error if an association has not been made, or the
/// entity is not alive.
pub fn lookupType(self: *Self, comptime T: type) !Entity {
return lookupAlive(self, Context.lookup(T));
}
/// Creates or modifies an `Entity` in this `World`.
/// See `Entity.init(...)` for more information.
pub fn entity(self: *Self, config: Entity.Config, add: anytype) !Entity {
return Entity.init(self, config, add);
}
pub fn tag(self: *Self, comptime T: type) !Entity {
if (@sizeOf(T) > 0) @compileError("'" ++ @typeName(T) ++ "' must be a zero-sized type");
const name = meta.simpleTypeName(T);
const result = try self.entity(.{ .name = name, .symbol = name }, .{});
Context.lookupMut(T).* = result.raw;
return result;
}
pub fn component(self: *Self, comptime T: type) !Entity {
if (@sizeOf(T) == 0) @compileError("'" ++ @typeName(T) ++ "' must not be a zero-sized type");
const name = meta.simpleTypeName(T);
const entity_ = try self.entity(.{ .name = name, .symbol = name, .use_low_id = true }, .{});
const result = c.ecs_component_init(self.raw, &.{
.entity = entity_.raw,
.type = .{ .size = @sizeOf(T), .alignment = @alignOf(T) },
});
if (result == 0) return errors.getLastErrorOrUnknown();
Context.lookupMut(T).* = result;
return Entity.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 {
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.lookupType(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.lookupType(T);
e.set(T, value);
}
pub fn system(
self: *Self,
name: [:0]const u8,
callback: SystemCallback,
phase: anytype,
expr: [:0]const u8,
) !Entity {
const phase_ = Context.anyToEntity(phase);
const entity_ = try if (phase_ != 0)
self.entity(.{ .name = name }, .{ .{ DependsOn, phase_ }, phase_ })
else
self.entity(.{ .name = name }, .{});
const context = try SystemCallbackContext.init(self, callback);
const result = c.ecs_system_init(self.raw, &.{
.entity = entity_.raw,
.callback = &SystemCallbackContext.invoke,
.binding_ctx = context,
.binding_ctx_free = &SystemCallbackContext.free,
.query = .{ .filter = .{ .expr = expr } },
});
if (result == 0) return errors.getLastErrorOrUnknown();
return Entity.fromRaw(self, result);
}
const SystemCallback = *const fn (Iter) void;
const SystemCallbackContext = struct {
world: *Self,
func: SystemCallback,
pub fn init(world: *Self, callback: SystemCallback) !*SystemCallbackContext {
var result = try flecszigble.allocator.create(SystemCallbackContext);
result.world = world;
result.func = callback;
return result;
}
fn free(context: ?*anyopaque) callconv(.C) void {
const self: *SystemCallbackContext = @alignCast(@ptrCast(context));
flecszigble.allocator.destroy(self);
}
// FIXME: Dependency loop.
// Currently needs manual changing of the generated C code.
// fn invoke(it: ?*c.ecs_iter_t) callconv(.C) void {
fn invoke(it2: *anyopaque) callconv(.C) void {
const it: ?*c.ecs_iter_t = @alignCast(@ptrCast(it2));
const context: *SystemCallbackContext = @alignCast(@ptrCast(it.?.binding_ctx));
const iter = Iter.fromRawPtr(context.world, it.?);
context.func(iter);
}
};
/// 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_ = Context.anyToId(id);
const iter = c.ecs_term_iter(self.raw, &.{ .id = id_ });
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,
iter: c.ecs_iter_t,
index: i32 = 0,
pub fn next(self: *TermIterator) ?Entity {
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.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 scope(self: *Self) ?Entity {
const result = c.ecs_get_scope(self.raw);
return if (result != 0) Entity.fromRaw(self, result) else null;
}
/// Sets the current scope of this `World` to the specified entity.
///
/// Setting a scope causes certain operations to be made in relation
/// to the scope entity, rather than the world root. Passing `null`
/// causes the scope to be set to the world root.
///
/// Returns the previously set scope, if any. It's recommended to set
/// the scope back to the previous value after you're done operating
/// in the desired scope.
pub fn setScope(self: *Self, value: anytype) ?Entity {
const result = c.ecs_set_scope(self.raw, Context.anyToEntity(value));
return if (result != 0) Entity.fromRaw(self, result) else null;
}
};
}