diff --git a/src/entity.zig b/src/entity.zig index 4489fa7..ec2efe3 100644 --- a/src/entity.zig +++ b/src/entity.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Allocator = std.mem.Allocator; const c = @import("./c.zig"); const err = @import("./error.zig"); @@ -6,6 +7,7 @@ const util = @import("./util.zig"); const Id = @import("./id.zig").Id; const Lookup = @import("./main.zig").Lookup; +const Path = @import("./path.zig").Path; const World = @import("./world.zig").World; pub const EntityError = error{ @@ -141,6 +143,13 @@ pub fn Entity(comptime ctx: anytype) type { c.ecs_delete(self.world.raw, self.raw); } + /// Returns the full, absolute `Path` of this `Entity`. + /// The entity is assumed to be alive. + /// See also: `Path.fromEntity(...)`. + pub fn getPath(self: Self, alloc: Allocator) !Path { + return Path.fromEntity(ctx, null, self, alloc); + } + /// Gets the name of this `Entity`, or `null` if none. pub fn getName(self: Self) ?[:0]const u8 { const result = c.ecs_get_name(self.world.raw, self.raw); @@ -163,6 +172,12 @@ pub fn Entity(comptime ctx: anytype) type { _ = c.ecs_set_symbol(self.world.raw, self.raw, value); } + /// Gets the parent of this `Entity`, or `null` if it has none. + pub fn getParent(self: Self) ?Self { + const result = c.ecs_get_parent(self.world.raw, self.raw); + return if (result != 0) fromRaw(self.world, result) else null; + } + 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/path.zig b/src/path.zig index 0787efd..bde7d38 100644 --- a/src/path.zig +++ b/src/path.zig @@ -1,6 +1,8 @@ const std = @import("std"); const Allocator = std.mem.Allocator; +const Entity = @import("./entity.zig").Entity; + // TODO: Do something better than just `std.debug.assert`. // TODO: Offer a way to validate paths, like checking for empty parts. @@ -103,6 +105,39 @@ pub const Path = struct { }; } + pub fn fromEntity(comptime ctx: type, parent: ?Entity(ctx), child: Entity(ctx), alloc: Allocator) !Path { + if (parent) |p| std.debug.assert(p.raw != 0); + std.debug.assert(child.raw != 0); + + const starting_capacity: usize = 12; + var parts = try alloc.alloc(EntityPart, starting_capacity); + var num_parts: usize = 0; + + // Traverse up the entity hierarchy starting from the specified child + // entity up until either the specified parent or root of the hierarchy. + var current = child; + while (true) { + std.debug.assert(num_parts == 0 or current.raw != child.raw); // Cycle detected. + parts[num_parts] = if (current.getName()) |name| + .{ .name = name } + else + .{ .id = current.getEntityId() }; + num_parts += 1; + current = current.getParent() orelse break; + if (parent != null and current.raw == parent.?.raw) break; + } + + parts = try alloc.realloc(parts, num_parts); + std.mem.reverse(EntityPart, parts); + + return .{ + .absolute = parent != null, + .parts = parts, + .alloc = alloc, + .owns_array = true, + }; + } + /// Creates a deep clone of this `Path` using the specified `Allocator`. pub fn clone(orig: Path, alloc: Allocator) !Path { var parts = try alloc.dupe(EntityPart, orig.parts); diff --git a/test/entity.zig b/test/entity.zig index 5066600..ae9296c 100644 --- a/test/entity.zig +++ b/test/entity.zig @@ -2,9 +2,11 @@ // https://github.com/SanderMertens/flecs/blob/master/test/api/src/Entity.c const std = @import("std"); +const alloc = std.testing.allocator; const expect = std.testing.expect; -const expectEql = std.testing.expectEqual; -const expectStrEql = std.testing.expectEqualStrings; +const expectFmt = std.testing.expectFmt; +const expectEqual = std.testing.expectEqual; +const expectEqualStrings = std.testing.expectEqualStrings; const util = @import("./util.zig"); @@ -16,7 +18,7 @@ const World = context.World; const Entity = context.Entity; test "Entity_init_id" { - var world = try World.initMinimal(std.testing.allocator); + var world = try World.initMinimal(alloc); defer world.deinit(); const e = try world.entity(.{}, .{}); @@ -25,16 +27,14 @@ test "Entity_init_id" { } test "Entity_init_id_name" { - var world = try World.initMinimal(std.testing.allocator); + var world = try World.initMinimal(alloc); defer world.deinit(); const e = try world.entity(.{ .name = "foo" }, .{}); // try expect(e.raw != 0); -- Not necessary, world.entity() returns error if result would be 0. - try expectStrEql("foo", e.getName().?); + try expectEqualStrings("foo", e.getName().?); - // TODO: Implement EntityPath. - const path = c.ecs_get_fullpath(world.raw, e.raw); - defer c.ecs_os_api.free_.?(path); - try expect(path != null); - try expectStrEql("foo", std.mem.sliceTo(path.?, 0)); + const path2 = try e.getPath(alloc); + defer path2.deinit(); + try expectFmt("foo", "{}", .{path2}); }