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| { .name => |n| {
const found = c.ecs_lookup_child(world.raw, parent_, n.ptr); const found = c.ecs_lookup_child(world.raw, parent_, n.ptr);
if (found == 0) { 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_); desc.add[0] = c.ecs_pair(c.EcsChildOf, parent_);
scope = c.ecs_entity_init(world.raw, &desc); scope = c.ecs_entity_init(world.raw, &desc);
if (scope == 0) return errors.getLastErrorOrUnknown(); 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; const previous = if (scope) |s| world.setScope(s) else null;
defer _ = if (scope != null) world.setScope(previous); 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. .sep = "".ptr, // Disable tokenization.
.id = if (id) |i| i else 0, .id = if (id) |i| i else 0,
.name = if (name_) |n| n.ptr else null, .name = if (name_) |n| n.ptr else null,
.symbol = if (config.symbol) |s| s.ptr else null, .symbol = if (config.symbol) |s| s.ptr else null,
.use_low_id = config.use_low_id, .use_low_id = config.use_low_id,
}); };
inline for (ids, 0..) |a, i| inline for (ids, 0..) |a, i|
desc.add[i] = Context.anyToId(a); 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. /// the entity is alive and its name doesn't change.
pub fn name(self: Self) ?[:0]const u8 { pub fn name(self: Self) ?[:0]const u8 {
const result = c.ecs_get_name(self.world.raw, self.raw); 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. /// 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. /// the entity is alive and its symbol doesn't change.
pub fn symbol(self: Self) ?[:0]const u8 { pub fn symbol(self: Self) ?[:0]const u8 {
const result = c.ecs_get_symbol(self.world.raw, self.raw); 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. /// 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); const id = Context.anyToId(T);
_ = c.ecs_set_id(self.world.raw, self.raw, id, @sizeOf(@TypeOf(value)), &value); _ = 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 expect = @import("./test/expect.zig");
const flecszigble = @import("./main.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" { test "Entity get and set" {
flecszigble.init(std.testing.allocator); flecszigble.init(std.testing.allocator);
var world = try flecszigble.World(void).initMinimal(); 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(.{ .value = 9001 }, entity.get(.{ Rank, Position }));
try expect.equal(.{ .x = 60, .y = 80 }, entity.get(.{ Copy, 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, writer: anytype,
) !void { ) !void {
_ = options; // TODO: Actually make use of this. _ = 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 FormatOptions.default
else if (@hasDecl(FormatOptions, fmt) and @TypeOf(@field(FormatOptions, fmt)) == FormatOptions) else if (@hasDecl(FormatOptions, fmt) and @TypeOf(@field(FormatOptions, fmt)) == FormatOptions)
@field(FormatOptions, fmt) @field(FormatOptions, fmt)
else else
std.fmt.invalidFmtError(fmt, Path); @compileError("invalid format string '" ++ fmt ++ "'");
try self.write(opt, writer);
} }
/// Returns whether the contents of the specified `Path`s are equivalent. /// 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 { fn write(self: Path, opt: FormatOptions, writer: anytype) !void {
// Write root separator (if applicable). // Write root separator (if applicable).
if (self.absolute) { if (self.absolute)
if (opt.root_sep) |p| if (opt.root_sep) |sep|
try writer.writeAll(p); try writer.writeAll(sep);
}
// Write the first part. // Write the first part.
switch (self.parts[0]) { switch (self.parts[0]) {
.id => |id| try writer.writeInt(u32, id, native_endian), .id => |id| try writer.writeInt(u32, id, native_endian),
.name => |name| try writer.writeAll(name), .name => |name| try writer.writeAll(name),
} }
// Write the remaining parts, each preceeded bu separator. // Write the remaining parts, each preceeded bu separator.
for (self.parts[1..]) |part| { for (self.parts[1..]) |part| {
try writer.writeAll(opt.sep); 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"); 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); c.ecs_set_ctx(world.raw, &ctx, null);
e1.set(Position, .{ .x = 0, .y = 0 }); 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"); 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); c.ecs_set_ctx(world.raw, &ctx, null);
e1.set(Position, .{ .x = 0, .y = 0 }); e1.set(Position, .{ .x = 0, .y = 0 });

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

@ -59,13 +59,32 @@ pub fn World(comptime ctx: anytype) type {
return result; 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 lookup functions should return errors or just optionals.
// TODO: Reconsider whether "Entity" should always be in backticks in our doc comments. // TODO: Reconsider whether "Entity" should always be in backticks in our doc comments.
// TODO: We really need tests for this function. // TODO: We really need tests for this function.
/// Returns the `Entity` at the specified path, or an error if the /// Returns the `Entity` at the specified path, or an error if the
/// entity does not exist. If the path is not absolute, the operation /// entity does not exist. If the path is not absolute, the operation
/// will use the current scope, or the world root. /// 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 parent = if (path.absolute) 0 else c.ecs_get_scope(self.raw);
var current: c.ecs_entity_t = undefined; var current: c.ecs_entity_t = undefined;
for (path.parts) |part| { for (path.parts) |part| {
@ -107,12 +126,10 @@ pub fn World(comptime ctx: anytype) type {
const name = meta.simpleTypeName(T); const name = meta.simpleTypeName(T);
const entity_ = try self.entity(.{ .name = name, .symbol = name, .use_low_id = true }, .{}); 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, .entity = entity_.raw,
.type = .{ .size = @sizeOf(T), .alignment = @alignOf(T) }, .type = .{ .size = @sizeOf(T), .alignment = @alignOf(T) },
}); });
const result = c.ecs_component_init(self.raw, &desc);
if (result == 0) return errors.getLastErrorOrUnknown(); if (result == 0) return errors.getLastErrorOrUnknown();
Context.lookupMut(T).* = result; Context.lookupMut(T).* = result;
return Entity.fromRaw(self, result); return Entity.fromRaw(self, result);
@ -167,15 +184,13 @@ pub fn World(comptime ctx: anytype) type {
self.entity(.{ .name = name }, .{}); self.entity(.{ .name = name }, .{});
const context = try SystemCallbackContext.init(self, callback); 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, .entity = entity_.raw,
.callback = &SystemCallbackContext.invoke, .callback = &SystemCallbackContext.invoke,
.binding_ctx = context, .binding_ctx = context,
.binding_ctx_free = &SystemCallbackContext.free, .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(); if (result == 0) return errors.getLastErrorOrUnknown();
return Entity.fromRaw(self, result); 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. /// that have a specific `Id`. This function supports wildcards.
pub fn term(self: *Self, id: anytype) TermIterator { pub fn term(self: *Self, id: anytype) TermIterator {
const id_ = Context.anyToId(id); const id_ = Context.anyToId(id);
var term_ = std.mem.zeroInit(c.ecs_term_t, .{ .id = id_ }); const iter = c.ecs_term_iter(self.raw, &.{ .id = id_ });
const iter = c.ecs_term_iter(self.raw, &term_);
return .{ .world = self, .iter = iter }; return .{ .world = self, .iter = iter };
} }

Loading…
Cancel
Save