Compare commits

...

6 Commits

  1. 99
      src/entity.zig
  2. 19
      src/path.zig
  3. 4
      src/test/flecs/world.zig
  4. 28
      src/test/util.zig
  5. 34
      src/world.zig

@ -130,7 +130,7 @@ pub fn Entity(comptime ctx: anytype) type {
.name => |n| {
const found = c.ecs_lookup_child(world.raw, parent_, n.ptr);
if (found == 0) {
var desc = std.mem.zeroInit(c.ecs_entity_desc_t, .{ .sep = "".ptr, .name = n.ptr });
var desc = c.ecs_entity_desc_t{ .sep = "".ptr, .name = n.ptr };
desc.add[0] = c.ecs_pair(c.EcsChildOf, parent_);
scope = c.ecs_entity_init(world.raw, &desc);
if (scope == 0) return errors.getLastErrorOrUnknown();
@ -145,13 +145,13 @@ pub fn Entity(comptime ctx: anytype) type {
const previous = if (scope) |s| world.setScope(s) else null;
defer _ = if (scope != null) world.setScope(previous);
var desc = std.mem.zeroInit(c.ecs_entity_desc_t, .{
var desc = c.ecs_entity_desc_t{
.sep = "".ptr, // Disable tokenization.
.id = if (id) |i| i else 0,
.name = if (name_) |n| n.ptr else null,
.symbol = if (config.symbol) |s| s.ptr else null,
.use_low_id = config.use_low_id,
});
};
inline for (ids, 0..) |a, i|
desc.add[i] = Context.anyToId(a);
@ -227,7 +227,7 @@ pub fn Entity(comptime ctx: anytype) type {
/// the entity is alive and its name doesn't change.
pub fn name(self: Self) ?[:0]const u8 {
const result = c.ecs_get_name(self.world.raw, self.raw);
return std.mem.sliceTo(result, 0);
return if (result != 0) std.mem.sliceTo(result, 0) else null;
}
/// Sets the name of this `Entity` to the specified value, if any.
@ -243,7 +243,7 @@ pub fn Entity(comptime ctx: anytype) type {
/// the entity is alive and its symbol doesn't change.
pub fn symbol(self: Self) ?[:0]const u8 {
const result = c.ecs_get_symbol(self.world.raw, self.raw);
return std.mem.sliceTo(result, 0);
return if (result != 0) std.mem.sliceTo(result, 0) else null;
}
/// Sets the symbol of this `Entity` to the specified value, if any.
@ -340,12 +340,70 @@ pub fn Entity(comptime ctx: anytype) type {
const id = Context.anyToId(T);
_ = c.ecs_set_id(self.world.raw, self.raw, id, @sizeOf(@TypeOf(value)), &value);
}
/// The `fmt` parameter can be one of:
/// - `d`, `x` or `X`: Output entity ID as decimal or hexadecimal.
/// - `s`: Output entity name, if any, otherwise falls back to `d`.
/// - `any`: Output full entity path with `Path.FormatOptions.default`.
/// - Other `Path.FormatOptions` are also supported (such as `flecs_c`).
pub fn format(
self: Self,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
if (fmt.len == 1 and (fmt[0] == 'd' or fmt[0] == 'x' or fmt[0] == 'X')) {
try std.fmt.formatIntValue(self.entityId(), fmt, options, writer);
} else if (fmt.len == 1 and fmt[0] == 's') {
if (self.name()) |n|
try writer.writeAll(n)
else
try self.format("d", options, writer);
} else {
const opt = Path.fmtToFormatOptions(fmt);
if (self.parent()) |p| {
try p.format(fmt, .{}, writer);
try writer.writeAll(opt.sep);
} else if (opt.root_sep) |sep|
try writer.writeAll(sep);
try self.format("s", .{}, writer);
}
}
};
}
const expect = @import("./test/expect.zig");
const flecszigble = @import("./main.zig");
test "Entity with name and symbol" {
flecszigble.init(std.testing.allocator);
var world = try flecszigble.World(void).initMinimal();
defer world.deinit();
const entity = try world.entity(.{ .name = "name", .symbol = "symbol" }, .{});
try expect.equalStrings("name", entity.name());
try expect.equalStrings("symbol", entity.symbol());
try expect.equal(entity, world.lookupChild(null, "name"));
try expect.equal(entity, world.lookupSymbol("symbol"));
}
test "Entity get and set name" {
flecszigble.init(std.testing.allocator);
var world = try flecszigble.World(void).initMinimal();
defer world.deinit();
const entity = try world.entity(.{}, .{});
try expect.equal(null, entity.name());
entity.setName("hello");
try expect.equalStrings("hello", entity.name());
entity.setName(null);
try expect.equal(null, entity.name());
}
test "Entity get and set" {
flecszigble.init(std.testing.allocator);
var world = try flecszigble.World(void).initMinimal();
@ -418,3 +476,34 @@ test "Entity set and get with pair type" {
try expect.equal(.{ .value = 9001 }, entity.get(.{ Rank, Position }));
try expect.equal(.{ .x = 60, .y = 80 }, entity.get(.{ Copy, Position }));
}
test "Entity format" {
flecszigble.init(std.testing.allocator);
var world = try flecszigble.World(void).initMinimal();
defer world.deinit();
const grandparent = try world.entity(.{ .name = "grandparent" }, .{});
const parent = try world.entity(.{ .name = "parent", .parent = grandparent }, .{});
const child = try world.entity(.{ .name = "child", .parent = parent }, .{});
try expect.fmt("410", "{d}", .{grandparent});
try expect.fmt("411", "{d}", .{parent});
try expect.fmt("412", "{d}", .{child});
try expect.fmt("grandparent", "{s}", .{grandparent});
try expect.fmt("parent", "{s}", .{parent});
try expect.fmt("child", "{s}", .{child});
try expect.fmt("grandparent", "{}", .{grandparent});
try expect.fmt("grandparent.parent", "{}", .{parent});
try expect.fmt("grandparent.parent.child", "{}", .{child});
try expect.fmt("/grandparent", "{unix}", .{grandparent});
try expect.fmt("/grandparent/parent", "{unix}", .{parent});
try expect.fmt("/grandparent/parent/child", "{unix}", .{child});
parent.setName(null);
try expect.fmt("411", "{s}", .{parent});
try expect.fmt("/grandparent/411", "{unix}", .{parent});
try expect.fmt("/grandparent/411/child", "{unix}", .{child});
}

@ -250,15 +250,17 @@ pub fn format(
writer: anytype,
) !void {
_ = options; // TODO: Actually make use of this.
const opt = fmtToFormatOptions(fmt);
try self.write(opt, writer);
}
const opt = if (fmt.len == 0)
pub fn fmtToFormatOptions(comptime fmt: []const u8) FormatOptions {
return if (fmt.len == 0)
FormatOptions.default
else if (@hasDecl(FormatOptions, fmt) and @TypeOf(@field(FormatOptions, fmt)) == FormatOptions)
@field(FormatOptions, fmt)
else
std.fmt.invalidFmtError(fmt, Path);
try self.write(opt, writer);
@compileError("invalid format string '" ++ fmt ++ "'");
}
/// Returns whether the contents of the specified `Path`s are equivalent.
@ -295,15 +297,16 @@ fn calculateLength(self: Path, opt: FormatOptions) usize {
fn write(self: Path, opt: FormatOptions, writer: anytype) !void {
// Write root separator (if applicable).
if (self.absolute) {
if (opt.root_sep) |p|
try writer.writeAll(p);
}
if (self.absolute)
if (opt.root_sep) |sep|
try writer.writeAll(sep);
// Write the first part.
switch (self.parts[0]) {
.id => |id| try writer.writeInt(u32, id, native_endian),
.name => |name| try writer.writeAll(name),
}
// Write the remaining parts, each preceeded bu separator.
for (self.parts[1..]) |part| {
try writer.writeAll(opt.sep);

@ -41,7 +41,7 @@ test "World_progress_w_0" {
const move_system = try world.system("move", move, c.EcsOnUpdate, "Position, Velocity");
var ctx = util.Probe.init();
var ctx = util.Probe{};
c.ecs_set_ctx(world.raw, &ctx, null);
e1.set(Position, .{ .x = 0, .y = 0 });
@ -78,7 +78,7 @@ test "World_progress_w_t" {
const move_system = try world.system("move", move, c.EcsOnUpdate, "Position, Velocity");
var ctx = util.Probe.init();
var ctx = util.Probe{};
c.ecs_set_ctx(world.raw, &ctx, null);
e1.set(Position, .{ .x = 0, .y = 0 });

@ -14,22 +14,18 @@ pub const MAX_ENTITIES = 256;
pub const MAX_INVOCATIONS = 1024;
pub const Probe = struct {
system: c.ecs_entity_t,
event: c.ecs_entity_t,
eventId: c.ecs_id_t,
offset: usize,
count: usize,
invoked: usize,
termCount: usize,
termIndex: usize,
e: [MAX_ENTITIES]c.ecs_entity_t,
c: [MAX_SYS_COLUMNS][MAX_INVOCATIONS]c.ecs_entity_t,
s: [MAX_SYS_COLUMNS][MAX_INVOCATIONS]c.ecs_entity_t,
param: ?*anyopaque,
pub fn init() Probe {
return std.mem.zeroes(Probe);
}
system: c.ecs_entity_t = 0,
event: c.ecs_entity_t = 0,
eventId: c.ecs_id_t = 0,
offset: usize = 0,
count: usize = 0,
invoked: usize = 0,
termCount: usize = 0,
termIndex: usize = 0,
e: [MAX_ENTITIES]c.ecs_entity_t = .{0} ** MAX_ENTITIES,
c: [MAX_SYS_COLUMNS][MAX_INVOCATIONS]c.ecs_entity_t = .{.{0} ** MAX_INVOCATIONS} ** MAX_SYS_COLUMNS,
s: [MAX_SYS_COLUMNS][MAX_INVOCATIONS]c.ecs_entity_t = .{.{0} ** MAX_INVOCATIONS} ** MAX_SYS_COLUMNS,
param: ?*anyopaque = null,
pub fn probeSystemWithContext(ctx: *Probe, it: Iter) !void {
ctx.param = it.raw.param;

@ -59,13 +59,32 @@ pub fn World(comptime ctx: anytype) type {
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 lookupByPath(self: *Self, path: Path) !Entity {
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| {
@ -107,12 +126,10 @@ pub fn World(comptime ctx: anytype) type {
const name = meta.simpleTypeName(T);
const entity_ = try self.entity(.{ .name = name, .symbol = name, .use_low_id = true }, .{});
const desc = std.mem.zeroInit(c.ecs_component_desc_t, .{
const result = c.ecs_component_init(self.raw, &.{
.entity = entity_.raw,
.type = .{ .size = @sizeOf(T), .alignment = @alignOf(T) },
});
const result = c.ecs_component_init(self.raw, &desc);
if (result == 0) return errors.getLastErrorOrUnknown();
Context.lookupMut(T).* = result;
return Entity.fromRaw(self, result);
@ -167,15 +184,13 @@ pub fn World(comptime ctx: anytype) type {
self.entity(.{ .name = name }, .{});
const context = try SystemCallbackContext.init(self, callback);
var desc = std.mem.zeroInit(c.ecs_system_desc_t, .{
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 } },
});
desc.query.filter.expr = expr;
const result = c.ecs_system_init(self.raw, &desc);
if (result == 0) return errors.getLastErrorOrUnknown();
return Entity.fromRaw(self, result);
}
@ -212,8 +227,7 @@ pub fn World(comptime ctx: anytype) type {
/// that have a specific `Id`. This function supports wildcards.
pub fn term(self: *Self, id: anytype) TermIterator {
const id_ = Context.anyToId(id);
var term_ = std.mem.zeroInit(c.ecs_term_t, .{ .id = id_ });
const iter = c.ecs_term_iter(self.raw, &term_);
const iter = c.ecs_term_iter(self.raw, &.{ .id = id_ });
return .{ .world = self, .iter = iter };
}

Loading…
Cancel
Save