Update Zig and Flecs, big refactor

main
copygirl 9 months ago
parent 10ce815c2a
commit 772908b3c1
  1. 42
      README.md
  2. 16
      build.zig
  3. 12
      build.zig.zon
  4. 2
      libs/flecs
  5. 52
      src/entity.zig
  6. 51
      src/id.zig
  7. 31
      src/iter.zig
  8. 110
      src/main.zig
  9. 57
      src/meta.zig
  10. 44
      src/pair.zig
  11. 29
      src/path.zig
  12. 3
      src/test/expect.zig
  13. 17
      src/test/flecs/entity.zig
  14. 35
      src/test/flecs/world.zig
  15. 4
      src/test/main.zig
  16. 4
      src/test/util.zig
  17. 75
      src/util.zig
  18. 97
      src/world.zig
  19. 5
      test/main.zig

@ -6,3 +6,45 @@ This library is still very much work-in-progress and not ready for use. It's ava
[Zig]: https://ziglang.org/ [Zig]: https://ziglang.org/
[Flecs]: https://github.com/SanderMertens/flecs [Flecs]: https://github.com/SanderMertens/flecs
## Example
```zig
const std = @import("std");
const flecs = @import("flecs-zig-ble");
const Context = flecs.Context(void);
const World = Context.World;
const Iter = Context.Iter;
const Position = struct { x: f32, y: f32 };
const Velocity = struct { x: f32, y: f32 };
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
flecs.init(gpa.allocator());
const world = try World.init();
defer world.deinit();
_ = try world.component(Position);
_ = try world.component(Velocity);
_ = try world.system("Move", move, flecs.c.EcsOnUpdate, "Position, Velocity");
const e = world.entity();
e.set(Position, .{ .x = 10, .y = 20 });
e.set(Velocity, .{ .x = 1, .y = 2 });
while (world.progress(0.0)) { }
}
fn move(it: Iter) void {
const pos_field = it.field(Position, 1);
const vel_field = it.field(Velocity, 2);
for (pos_field, vel_field) |*p, v| {
p.x += v.x;
p.y += v.y;
}
}
```

@ -4,10 +4,9 @@ pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
const module = b.createModule(.{ _ = b.addModule("flecs-zig-ble", .{
.source_file = .{ .path = "src/main.zig" }, .root_source_file = .{ .path = "src/main.zig" },
}); });
try b.modules.put(b.dupe("flecs-zig-ble"), module);
const lib = b.addStaticLibrary(.{ const lib = b.addStaticLibrary(.{
.name = "flecs-zig-ble", .name = "flecs-zig-ble",
@ -20,9 +19,7 @@ pub fn build(b: *std.Build) !void {
b.installArtifact(lib); b.installArtifact(lib);
const main_tests = b.addTest(.{ const main_tests = b.addTest(.{
.root_source_file = .{ .path = "test/main.zig" }, .root_source_file = .{ .path = "src/main.zig" },
// Has to be specified so tests can run autodoc tests from src/.
.main_pkg_path = .{ .path = "." },
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
@ -32,13 +29,14 @@ pub fn build(b: *std.Build) !void {
test_step.dependOn(&run_main_tests.step); test_step.dependOn(&run_main_tests.step);
} }
fn setupFlecs(step: *std.Build.CompileStep) void { fn setupFlecs(step: *std.Build.Step.Compile) void {
step.linkLibC(); step.linkLibC();
step.addIncludePath(.{ .path = "libs/flecs" }); step.addIncludePath(.{ .path = "libs/flecs" });
step.addCSourceFile(.{ step.addCSourceFile(.{
.file = .{ .path = "libs/flecs/flecs.c" }, .file = .{ .path = "libs/flecs/flecs.c" },
.flags = &.{"-fno-sanitize=undefined"}, .flags = &.{"-fno-sanitize=undefined"},
}); });
if (step.target.isWindows()) if (step.rootModuleTarget().os.tag == .windows) {
step.linkSystemLibraryName("ws2_32"); step.linkSystemLibrary("ws2_32");
}
} }

@ -1,4 +1,16 @@
.{ .{
.name = "flecs-zig-ble", .name = "flecs-zig-ble",
.version = "0.1.0", .version = "0.1.0",
.paths = .{
"src",
"build.zig",
"build.zig.zon",
"README.md",
"UNLICENSE.txt",
// Flecs dependency.
"libs/flecs/flecs.c",
"libs/flecs/flecs.h",
"libs/flecs/LICENSE",
},
} }

@ -1 +1 @@
Subproject commit 74a7f74a2835d3946f05502ef44ba1d5d8b48eff Subproject commit 198607d10ab8f8fc44540043271d6e3be019250b

@ -2,14 +2,8 @@ const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const flecs = @import("./main.zig"); const flecs = @import("./main.zig");
const Path = flecs.Path;
const c = flecs.c; const c = flecs.c;
const err = @import("./error.zig");
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{ pub const EntityError = error{
/// Id is `0`. /// Id is `0`.
@ -42,17 +36,21 @@ pub const EntityError = error{
/// the id isn't an entity), and back again with `asId()` (always succeeds). /// the id isn't an entity), and back again with `asId()` (always succeeds).
pub fn Entity(comptime ctx: anytype) type { pub fn Entity(comptime ctx: anytype) type {
return struct { return struct {
world: *World(ctx),
raw: c.ecs_entity_t,
const Self = @This(); const Self = @This();
const Context = flecs.Context(ctx);
const World = Context.World;
const Id = Context.Id;
world: *World,
raw: c.ecs_entity_t,
/// Returns an `Entity` for the specified world and raw entity id. /// Returns an `Entity` for the specified world and raw entity id.
/// ///
/// No safety checks are done, so if you pass an invalid id, an /// No safety checks are done, so if you pass an invalid id, an
/// invalid `Entity` might be returned that could cause panics if /// invalid `Entity` might be returned that could cause panics if
/// passed to other API functions without ensuring their validity. /// passed to other API functions without ensuring their validity.
pub fn fromRaw(world: *World(ctx), raw: c.ecs_entity_t) Self { pub fn fromRaw(world: *World, raw: c.ecs_entity_t) Self {
return .{ .world = world, .raw = raw }; return .{ .world = world, .raw = raw };
} }
@ -85,7 +83,7 @@ pub fn Entity(comptime ctx: anytype) type {
/// When adding components, they are just default-initialized. /// When adding components, they are just default-initialized.
/// To set their values you'll need to use `set(...)` on the /// To set their values you'll need to use `set(...)` on the
/// `Entity` returned from this function. /// `Entity` returned from this function.
pub fn init(world: *World(ctx), config: Config, ids: anytype) !Self { pub fn init(world: *World, config: Config, ids: anytype) !Self {
const meta = @typeInfo(@TypeOf(ids)); const meta = @typeInfo(@TypeOf(ids));
if (meta != .Struct or (meta.Struct.is_tuple == false and meta.Struct.fields.len > 0)) if (meta != .Struct or (meta.Struct.is_tuple == false and meta.Struct.fields.len > 0))
@compileError("Expected tuple or empty struct, got '" ++ @typeName(@TypeOf(ids)) ++ "'"); @compileError("Expected tuple or empty struct, got '" ++ @typeName(@TypeOf(ids)) ++ "'");
@ -134,7 +132,7 @@ pub fn Entity(comptime ctx: anytype) type {
var desc = std.mem.zeroInit(c.ecs_entity_desc_t, .{ .sep = "".ptr, .name = n.ptr }); var desc = std.mem.zeroInit(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 err.getLastErrorOrUnknown(); if (scope == 0) return flecs.getLastErrorOrUnknown();
} else scope = found; } else scope = found;
}, },
} }
@ -155,7 +153,7 @@ pub fn Entity(comptime ctx: anytype) type {
}); });
inline for (ids, 0..) |a, i| inline for (ids, 0..) |a, i|
desc.add[i] = util.anyToId(ctx, a); desc.add[i] = Context.anyToId(a);
for (desc.add[0..ids.len]) |i| for (desc.add[0..ids.len]) |i|
if (c.ecs_id_is_pair(i) and c.ECS_PAIR_FIRST(i) == c.EcsChildOf) if (c.ecs_id_is_pair(i) and c.ECS_PAIR_FIRST(i) == c.EcsChildOf)
@ -163,7 +161,7 @@ pub fn Entity(comptime ctx: anytype) type {
return error.FoundChildOf; return error.FoundChildOf;
const result = c.ecs_entity_init(world.raw, &desc); const result = c.ecs_entity_init(world.raw, &desc);
if (result == 0) return err.getLastErrorOrUnknown(); if (result == 0) return flecs.getLastErrorOrUnknown();
return Self.fromRaw(world, result); return Self.fromRaw(world, result);
} }
@ -198,7 +196,7 @@ pub fn Entity(comptime ctx: anytype) type {
if (!c.ecs_is_alive(self.world.raw, self.raw)) return EntityError.IsNotAlive; if (!c.ecs_is_alive(self.world.raw, self.raw)) return EntityError.IsNotAlive;
} }
pub fn asId(self: Self) Id(ctx) { pub fn asId(self: Self) Id {
return .{ .world = self.world, .raw = self.raw }; return .{ .world = self.world, .raw = self.raw };
} }
@ -261,25 +259,25 @@ pub fn Entity(comptime ctx: anytype) type {
} }
/// Returns an iterator that yields each of this `Entity`s children. /// Returns an iterator that yields each of this `Entity`s children.
pub fn getChildren(self: Self) World(ctx).TermIterator { pub fn getChildren(self: Self) World.TermIterator {
return self.world.term(.{ c.EcsChildOf, self }); return self.world.term(.{ c.EcsChildOf, self });
} }
/// Returns an iterator that yields each of the targets of the /// Returns an iterator that yields each of the targets of the
/// specified `relation` that this `Entity` has, if any. /// specified `relation` that this `Entity` has, if any.
pub fn getTargets(self: Self, relation: anytype) TargetIterator { pub fn getTargets(self: Self, relation: anytype) TargetIterator {
const rel = util.anyToEntity(ctx, relation); const rel = Context.anyToEntity(relation);
return .{ .world = self.world, .entity = self.raw, .relation = rel.raw }; return .{ .world = self.world, .entity = self.raw, .relation = rel.raw };
} }
pub const TargetIterator = struct { pub const TargetIterator = struct {
world: World(ctx), world: World,
entity: c.ecs_entity_t, entity: c.ecs_entity_t,
relation: c.ecs_entity_t, relation: c.ecs_entity_t,
index: c_int = 0, index: c_int = 0,
pub fn next(self: *TargetIterator) ?Self { pub fn next(self: *TargetIterator) ?Self {
var result = c.ecs_get_target(self.world.raw, self.entity, self.relation, self.index); const result = c.ecs_get_target(self.world.raw, self.entity, self.relation, self.index);
self.index += 1; self.index += 1;
return if (result != 0) Self.fromRaw(self.world, result) else null; return if (result != 0) Self.fromRaw(self.world, result) else null;
} }
@ -287,37 +285,37 @@ pub fn Entity(comptime ctx: anytype) type {
/// Returns whether this `Entity` has the specified value. /// Returns whether this `Entity` has the specified value.
/// ///
/// `id` must be convertible to an id. See also: `util.anyToId(...)`. /// `id` must be convertible to an id. See also: `Context.anyToId(...)`.
pub fn has(self: Self, id: anytype) bool { pub fn has(self: Self, id: anytype) bool {
const ecs_id = util.anyToId(ctx, id); const ecs_id = Context.anyToId(id);
return c.ecs_has_id(self.world.raw, self.raw, ecs_id); return c.ecs_has_id(self.world.raw, self.raw, ecs_id);
} }
pub fn add(self: Self, id: anytype) void { pub fn add(self: Self, id: anytype) void {
const ecs_id = util.anyToId(ctx, id); const ecs_id = Context.anyToId(id);
c.ecs_add_id(self.world.raw, self.raw, ecs_id); c.ecs_add_id(self.world.raw, self.raw, ecs_id);
} }
pub fn remove(self: Self, id: anytype) void { pub fn remove(self: Self, id: anytype) void {
const ecs_id = util.anyToId(ctx, id); const ecs_id = Context.anyToId(id);
c.ecs_remove_id(self.world.raw, self.raw, ecs_id); c.ecs_remove_id(self.world.raw, self.raw, ecs_id);
} }
pub fn get(self: Self, comptime T: type) ?T { pub fn get(self: Self, comptime T: type) ?T {
const id = Lookup(ctx, T).id; const id = Context.lookup(T).*;
const ptr = c.ecs_get_id(self.world.raw, self.raw, id); const ptr = c.ecs_get_id(self.world.raw, self.raw, id);
const typed_ptr: ?*const T = @alignCast(@ptrCast(ptr)); const typed_ptr: ?*const T = @alignCast(@ptrCast(ptr));
return if (typed_ptr) |p| p.* else null; return if (typed_ptr) |p| p.* else null;
} }
pub fn get_mut(self: Self, comptime T: type) ?*T { pub fn get_mut(self: Self, comptime T: type) ?*T {
const id = Lookup(ctx, T).id; const id = Context.lookup(T).*;
const ptr = c.ecs_get_mut_id(self.world.raw, self.raw, id); const ptr = c.ecs_get_mut_id(self.world.raw, self.raw, id);
return @alignCast(@ptrCast(ptr)); return @alignCast(@ptrCast(ptr));
} }
pub fn set(self: Self, comptime T: type, value: T) void { pub fn set(self: Self, comptime T: type, value: T) void {
const id = Lookup(ctx, T).id; const id = Context.lookup(T).*;
_ = c.ecs_set_id(self.world.raw, self.raw, id, @sizeOf(T), &value); _ = c.ecs_set_id(self.world.raw, self.raw, id, @sizeOf(T), &value);
} }
}; };

@ -1,8 +1,5 @@
const c = @import("./c.zig"); const flecs = @import("./main.zig");
const c = flecs.c;
const Entity = @import("./entity.zig").Entity;
const Pair = @import("./pair.zig").Pair;
const World = @import("./world.zig").World;
pub const IdError = error{ pub const IdError = error{
/// Id is `0`. /// Id is `0`.
@ -17,12 +14,17 @@ pub const IdError = error{
/// It can be an `Entity` or `Pair`, and can have optional id flags. /// It can be an `Entity` or `Pair`, and can have optional id flags.
pub fn Id(comptime ctx: anytype) type { pub fn Id(comptime ctx: anytype) type {
return struct { return struct {
world: *World(ctx),
raw: c.ecs_id_t,
const Self = @This(); const Self = @This();
pub fn fromRaw(world: *World(ctx), raw: c.ecs_id_t) Self { const Context = flecs.Context(ctx);
const World = Context.World;
const Entity = Context.Entity;
const Pair = Context.Pair;
world: *World,
raw: c.ecs_id_t,
pub fn fromRaw(world: *World, raw: c.ecs_id_t) Self {
return .{ .world = world, .raw = raw }; return .{ .world = world, .raw = raw };
} }
@ -33,15 +35,14 @@ pub fn Id(comptime ctx: anytype) type {
/// - .. it is not `0`. /// - .. it is not `0`.
/// - .. it does not contain wildcards. /// - .. it does not contain wildcards.
/// - .. it does not contain invalid entities. /// - .. it does not contain invalid entities.
pub fn ensureValid(self: Self) !Self { pub fn ensureValid(self: Self) !void {
if (self.raw == 0) return IdError.IsNone; if (self.raw == 0) return IdError.IsNone;
if (c.ecs_id_is_wildcard(self.raw)) return IdError.IsWildcard; if (c.ecs_id_is_wildcard(self.raw)) return IdError.IsWildcard;
if (self.asPairUnsafe()) |p| if (self.asPairUnsafe()) |p|
_ = try p.ensureValid() try p.ensureValid()
else if (!self.isEntity()) else if (!self.isEntity())
if (!c.ecs_is_valid(self.raw & c.ECS_COMPONENT_MASK)) if (!c.ecs_is_valid(self.raw & c.ECS_COMPONENT_MASK))
return IdError.IsInvalid; return IdError.IsInvalid;
return self;
} }
pub fn isEntity(self: Self) bool { pub fn isEntity(self: Self) bool {
@ -50,13 +51,15 @@ pub fn Id(comptime ctx: anytype) type {
/// Attempts to return this `Id` as an `Entity`, or an error if it's /// Attempts to return this `Id` as an `Entity`, or an error if it's
/// not an entity, not a valid entity or not alive in the world. /// not an entity, not a valid entity or not alive in the world.
pub fn asEntityAlive(self: Self) !Entity(ctx) { pub fn asEntityAlive(self: Self) !Entity {
self.asEntityUnsafe().ensureAlive(); const result = self.asEntityUnsafe();
try result.ensureAlive();
return result;
} }
/// Returns this `Id` as an `Entity`, or `null` otherwise. /// Returns this `Id` as an `Entity`, or `null` otherwise.
/// This assumes the `Id` is valid, or an invalid result could be returned. /// This assumes the `Id` is valid, or an invalid result could be returned.
pub fn asEntityUnsafe(self: Self) ?Entity(ctx) { pub fn asEntityUnsafe(self: Self) ?Entity {
return if (isEntity(self)) .{ .world = self.world, .raw = self.raw } else null; return if (isEntity(self)) .{ .world = self.world, .raw = self.raw } else null;
} }
@ -67,19 +70,23 @@ pub fn Id(comptime ctx: anytype) type {
/// Attempts to return this `Id` as a `Pair`, or an error if it's not /// Attempts to return this `Id` as a `Pair`, or an error if it's not
/// a pair, or either of its elements are not valid or not alive in /// a pair, or either of its elements are not valid or not alive in
/// the world. /// the world.
pub fn asPairAlive(self: Self) !Pair(ctx) { pub fn asPairAlive(self: Self) !Pair {
return @as(Pair(ctx), .{ .world = self.world, .raw = self.raw }).ensureAlive(); const result = Pair{ .world = self.world, .raw = self.raw };
try result.ensureAlive();
return result;
} }
/// Attempts to return this `Id` as a `Pair`, or an error if it's not /// Attempts to return this `Id` as a `Pair`, or an error if it's not
/// a pair, or either of its elements are not valid. /// a pair, or either of its elements are not valid.
pub fn asPairValid(self: Self) !Pair(ctx) { pub fn asPairValid(self: Self) !Pair {
return @as(Pair(ctx), .{ .world = self.world, .raw = self.raw }).ensureValid(); const result = Pair{ .world = self.world, .raw = self.raw };
try result.ensureValid();
return result;
} }
/// Returns this `Id` as a `Pair`, or `null` otherwise. /// Returns this `Id` as a `Pair`, or `null` otherwise.
/// This assumes the `Id` is valid, or an invalid result could be returned. /// This assumes the `Id` is valid, or an invalid result could be returned.
pub fn asPairUnsafe(self: Self) ?Pair(ctx) { pub fn asPairUnsafe(self: Self) ?Pair {
return if (isPair(self)) .{ .world = self.world, .raw = self.raw } else null; return if (isPair(self)) .{ .world = self.world, .raw = self.raw } else null;
} }
@ -143,9 +150,9 @@ pub fn Id(comptime ctx: anytype) type {
/// - If `.relation` has the `Tag` property, `null` is returned. /// - If `.relation` has the `Tag` property, `null` is returned.
/// - If `.target` is a component, it is returned. /// - If `.target` is a component, it is returned.
/// - Otherwise, `null` is returned. /// - Otherwise, `null` is returned.
pub fn getTypeId(self: Self) ?Entity(ctx) { pub fn getTypeId(self: Self) ?Entity {
const raw = c.ecs_get_typeid(self.world.raw, self.raw); const raw = c.ecs_get_typeid(self.world.raw, self.raw);
return if (raw != 0) Entity(ctx).fromRaw(self.world, raw); return if (raw != 0) Entity.fromRaw(self.world, raw);
} }
/// Returns if the provided `pattern` matches this `Id`. /// Returns if the provided `pattern` matches this `Id`.

@ -1,24 +1,25 @@
const flecs = @import("./main.zig"); const flecs = @import("./main.zig");
const c = flecs.c; const c = flecs.c;
const Entity = @import("./entity.zig").Entity;
const Id = @import("./id.zig").Id;
const World = @import("./world.zig").World;
pub fn Iter(comptime ctx: anytype) type { pub fn Iter(comptime ctx: anytype) type {
return struct { return struct {
world: *World(ctx), const Self = @This();
const Context = flecs.Context(ctx);
const World = Context.World;
const Entity = Context.Entity;
const Id = Context.Id;
world: *World,
raw: *c.ecs_iter_t, raw: *c.ecs_iter_t,
owned: bool, owned: bool,
const Self = @This(); pub fn fromRawPtr(world: *World, ptr: *c.ecs_iter_t) Self {
pub fn fromRawPtr(world: *World(ctx), ptr: *c.ecs_iter_t) Self {
return .{ .world = world, .raw = ptr, .owned = false }; return .{ .world = world, .raw = ptr, .owned = false };
} }
pub fn fromRawValue(world: *World(ctx), value: c.ecs_iter_t) !Self { pub fn fromRawValue(world: *World, value: c.ecs_iter_t) !Self {
var raw = try flecs.allocator.create(c.ecs_iter_t); const raw = try flecs.allocator.create(c.ecs_iter_t);
raw.* = value; raw.* = value;
return .{ .world = world, .raw = raw, .owned = true }; return .{ .world = world, .raw = raw, .owned = true };
} }
@ -41,21 +42,21 @@ pub fn Iter(comptime ctx: anytype) type {
} }
pub fn field(self: Self, comptime T: type, index: usize) []T { pub fn field(self: Self, comptime T: type, index: usize) []T {
var raw_ptr = c.ecs_field_w_size(self.raw, @sizeOf(T), @intCast(index)); const raw_ptr = c.ecs_field_w_size(self.raw, @sizeOf(T), @intCast(index));
var typed_ptr: [*]T = @alignCast(@ptrCast(raw_ptr)); var typed_ptr: [*]T = @alignCast(@ptrCast(raw_ptr));
const is_self = c.ecs_field_is_self(self.raw, @intCast(index)); const is_self = c.ecs_field_is_self(self.raw, @intCast(index));
const count = if (is_self) self.getCount() else 1; const count = if (is_self) self.getCount() else 1;
return typed_ptr[0..count]; return typed_ptr[0..count];
} }
pub fn fieldId(self: Self, index: usize) Id(ctx) { pub fn fieldId(self: Self, index: usize) Id {
const raw = c.ecs_field_id(self.raw, @intCast(index)); const raw = c.ecs_field_id(self.raw, @intCast(index));
return Id(ctx).fromRaw(self.world, raw); return Id.fromRaw(self.world, raw);
} }
pub fn fieldSource(self: Self, index: usize) Entity(ctx) { pub fn fieldSource(self: Self, index: usize) Entity {
const raw = c.ecs_field_src(self.raw, @intCast(index)); const raw = c.ecs_field_src(self.raw, @intCast(index));
return Entity(ctx).fromRaw(self.world, raw); return Entity.fromRaw(self.world, raw);
} }
}; };
} }

@ -1,33 +1,18 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const meta = @import("./meta.zig");
pub const c = @import("./c.zig");
pub usingnamespace @import("./error.zig");
pub usingnamespace @import("./entity.zig"); pub usingnamespace @import("./entity.zig");
pub usingnamespace @import("./id.zig"); pub usingnamespace @import("./id.zig");
pub usingnamespace @import("./iter.zig"); pub usingnamespace @import("./iter.zig");
pub usingnamespace @import("./pair.zig"); pub usingnamespace @import("./pair.zig");
pub usingnamespace @import("./path.zig");
pub usingnamespace @import("./world.zig"); pub usingnamespace @import("./world.zig");
pub const c = @import("./c.zig"); pub const Path = @import("./path.zig");
pub fn Context(comptime ctx: anytype) type {
return struct {
pub const Entity = @import("./entity.zig").Entity(ctx);
pub const Id = @import("./id.zig").Id(ctx);
pub const Iter = @import("./iter.zig").Iter(ctx);
pub const Pair = @import("./pair.zig").Pair(ctx);
pub const World = @import("./world.zig").World(ctx);
pub const Path = @import("./path.zig").Path;
};
}
pub fn Lookup(comptime ctx: anytype, comptime T: type) type {
_ = .{ ctx, T }; // Only necessary to create a unique type.
return struct {
pub var id: c.ecs_entity_t = 0;
};
}
pub var is_initialized = false; pub var is_initialized = false;
pub var allocator: Allocator = undefined; pub var allocator: Allocator = undefined;
@ -49,6 +34,86 @@ pub fn init(alloc: Allocator) void {
c.ecs_os_api.free_ = flecsFree; c.ecs_os_api.free_ = flecsFree;
} }
pub fn Context(comptime ctx: anytype) type {
return struct {
pub const Entity = @import("./entity.zig").Entity(ctx);
pub const Id = @import("./id.zig").Id(ctx);
pub const Iter = @import("./iter.zig").Iter(ctx);
pub const Pair = @import("./pair.zig").Pair(ctx);
pub const World = @import("./world.zig").World(ctx);
pub const Path = @import("./path.zig");
/// Returns a pointer unique to this context and the provided type
/// that holds an entity ID. Useful for associating types to entities.
pub fn lookup(comptime T: type) *c.ecs_entity_t {
_ = T; // Only necessary to create a unique type.
return &(struct {
pub var id: c.ecs_entity_t = 0;
}).id;
}
/// Converts the specified value to an `ecs_entity_t`.
///
/// If `null` is passed to this function, either directly or through an
/// optional type of one of the supported types, `0` is returned. It's up to
/// the caller to do any necessary testing for `0` values, and whether the
/// returned entity is valid or alive.
///
/// The following can be converted:
/// - `Entity` and `ecs_entity_t` - Self-explanatory.
/// - `type` - Looks up the entity associated with that type.
pub fn anyToEntity(value: anytype) c.ecs_entity_t {
return switch (@TypeOf(value)) {
@TypeOf(null) => 0,
c.ecs_entity_t => value,
Entity => value.raw,
?Entity => if (value) |v| v.raw else 0,
type => lookup(value).*,
else => @compileError("Value of type " ++ @typeName(@TypeOf(value)) ++ " can't be converted to Entity"),
};
}
/// Converts the specified value to an `ecs_id_t`.
///
/// If `null` is passed to this function, either directly or through an
/// optional type of one of the supported types, `0` is returned. It's up to
/// the caller to do any necessary testing for `0` values, and whether the
/// returned id is valid.
///
/// The following can be converted:
/// - `Id` and `ecs_id_it` - Self-explanatory.
/// - `Entity` and `ecs_entity_t` - Self-explanatory.
/// - `Pair` - Converts to the equivalent `Id`.
/// - `.{ relation, target }` - A `Pair`, converted using `anyToEntity()`.
/// - `type` - Looks up the entity associated with that type.
pub fn anyToId(value: anytype) c.ecs_id_t {
const T = @TypeOf(value);
if (comptime meta.isTuple(T)) {
if (@typeInfo(T).Struct.fields.len != 2)
@compileError("Value of type " ++ @typeName(T) ++ " must be a tuple with 2 elements, to be a Pair");
const relation = anyToEntity(value[0]);
const target = anyToEntity(value[1]);
return c.ecs_make_pair(relation, target);
}
return switch (T) {
@TypeOf(null) => 0,
c.ecs_id_t => value,
Id => value.raw,
?Id => if (value) |v| v.raw else 0,
Pair => value.raw,
?Pair => if (value) |v| v.raw else 0,
// Technically same type as `ecs_id_it`.
// c.ecs_entity_t => value,
Entity => value.raw,
?Entity => if (value) |v| v.raw else 0,
type => lookup(value).*,
else => @compileError("Value of type " ++ @typeName(T) ++ " can't be converted to Id"),
};
}
};
}
fn flecsMalloc(size: i32) callconv(.C) ?*anyopaque { fn flecsMalloc(size: i32) callconv(.C) ?*anyopaque {
return allocLengthEncodedSlice(size, null).ptr; return allocLengthEncodedSlice(size, null).ptr;
} }
@ -58,7 +123,7 @@ fn flecsRealloc(ptr: ?*anyopaque, size: i32) callconv(.C) ?*anyopaque {
} }
fn flecsCalloc(size: i32) callconv(.C) ?*anyopaque { fn flecsCalloc(size: i32) callconv(.C) ?*anyopaque {
var slice = allocLengthEncodedSlice(size, null); const slice = allocLengthEncodedSlice(size, null);
@memset(slice, 0); @memset(slice, 0);
return slice.ptr; return slice.ptr;
} }
@ -97,4 +162,5 @@ fn sliceFromPtr(ptr: *anyopaque) []u8 {
test { test {
std.testing.refAllDecls(@This()); std.testing.refAllDecls(@This());
_ = @import("./test/main.zig");
} }

@ -0,0 +1,57 @@
//! Since the `std.meta.trait` module was removed from Zig's standard
//! library, we're re-introducing some of the functions we needed.
const std = @import("std");
/// Returns if the provided type is a tuple.
pub fn isTuple(comptime T: type) bool {
return @typeInfo(T) == .Struct and @typeInfo(T).Struct.is_tuple;
}
/// Returns true if the passed type will coerce to []const u8.
/// Any of the following are considered strings:
/// ```
/// []const u8, [:S]const u8, *const [N]u8, *const [N:S]u8,
/// []u8, [:S]u8, *[:S]u8, *[N:S]u8.
/// ```
/// These types are not considered strings:
/// ```
/// u8, [N]u8, [*]const u8, [*:0]const u8,
/// [*]const [N]u8, []const u16, []const i8,
/// *const u8, ?[]const u8, ?*const [N]u8.
/// ```
pub fn isZigString(comptime T: type) bool {
return comptime blk: {
// Only pointer types can be strings, no optionals
const info = @typeInfo(T);
if (info != .Pointer) break :blk false;
const ptr = &info.Pointer;
// Check for CV qualifiers that would prevent coerction to []const u8
if (ptr.is_volatile or ptr.is_allowzero) break :blk false;
// If it's already a slice, simple check.
if (ptr.size == .Slice) {
break :blk ptr.child == u8;
}
// Otherwise check if it's an array type that coerces to slice.
if (ptr.size == .One) {
const child = @typeInfo(ptr.child);
if (child == .Array) {
const arr = &child.Array;
break :blk arr.child == u8;
}
}
break :blk false;
};
}
/// Gets the simplified type name of the specified type.
/// That is, without any namespace qualifiers.
pub fn simpleTypeName(comptime T: type) [:0]const u8 {
const fullName = @typeName(T);
const index = std.mem.lastIndexOf(u8, fullName, ".");
return if (index) |i| fullName[(i + 1)..] else fullName;
}

@ -1,8 +1,5 @@
const c = @import("./c.zig"); const flecs = @import("./main.zig");
const c = flecs.c;
const Entity = @import("./entity.zig").Entity;
const Id = @import("./id.zig").Id;
const World = @import("./world.zig").World;
pub const PairError = error{ pub const PairError = error{
/// Id is `0`. /// Id is `0`.
@ -25,47 +22,50 @@ pub const PairError = error{
/// can be extracted by calling `getRelation()` and `getTarget()`. /// can be extracted by calling `getRelation()` and `getTarget()`.
pub fn Pair(comptime ctx: anytype) type { pub fn Pair(comptime ctx: anytype) type {
return struct { return struct {
world: *World(ctx),
raw: c.ecs_id_t,
const Self = @This(); const Self = @This();
const Context = flecs.Context(ctx);
const World = Context.World;
const Entity = Context.Entity;
const Id = Context.Id;
world: *World,
raw: c.ecs_id_t,
/// Build a pair from the specified `relation` and `target` entities. /// Build a pair from the specified `relation` and `target` entities.
/// The specified entities must be alive in the world. /// The specified entities must be alive in the world.
pub fn init(relation: Entity(ctx), target: Entity(ctx)) !Self { pub fn init(relation: Entity, target: Entity) !Self {
const raw = c.ecs_make_pair(relation.ensureAlive().raw, target.ensureAlive().raw); const raw = c.ecs_make_pair(relation.ensureAlive().raw, target.ensureAlive().raw);
return .{ .world = relation.world, .raw = raw }; return .{ .world = relation.world, .raw = raw };
} }
/// Ensures this `Pair` is valid and its `relation` and `target` /// Ensures this `Pair` is valid and its `relation` and `target`
/// entities are alive in the world, returning an error otherwise. /// entities are alive in the world, returning an error otherwise.
pub fn ensureAlive(self: Self) !Self { pub fn ensureAlive(self: Self) !void {
if (self.raw == 0) return error.IsNone; if (self.raw == 0) return error.IsNone;
if (!c.ecs_id_is_pair(self.raw)) return error.IsntPair; if (!c.ecs_id_is_pair(self.raw)) return error.IsntPair;
_ = try self.getRelation().ensureAlive(); try self.getRelation().ensureAlive();
_ = try self.getTarget().ensureAlive(); try self.getTarget().ensureAlive();
return self;
} }
/// Ensures this `Pair` and its elements are valid. /// Ensures this `Pair` and its elements are valid.
pub fn ensureValid(self: Self) !Self { pub fn ensureValid(self: Self) !void {
if (self.raw == 0) return error.IsNone; if (self.raw == 0) return error.IsNone;
if (!c.ecs_id_is_pair(self.raw)) return error.IsntPair; if (!c.ecs_id_is_pair(self.raw)) return error.IsntPair;
_ = try self.getRelation().ensureValid(); try self.getRelation().ensureValid();
_ = try self.getTarget().ensureValid(); try self.getTarget().ensureValid();
return self;
} }
pub fn asId(self: Self) Id(ctx) { pub fn asId(self: Self) Id {
return .{ .world = self.world, .raw = self.raw }; return .{ .world = self.world, .raw = self.raw };
} }
pub fn getRelation(self: Self) Entity(ctx) { pub fn getRelation(self: Self) Entity {
return Entity(ctx).fromRaw(self.world, c.ECS_PAIR_FIRST(self.raw)); return Entity.fromRaw(self.world, c.ECS_PAIR_FIRST(self.raw));
} }
pub fn getTarget(self: Self) Entity(ctx) { pub fn getTarget(self: Self) Entity {
return Entity(ctx).fromRaw(self.world, c.ECS_PAIR_SECOND(self.raw)); return Entity.fromRaw(self.world, c.ECS_PAIR_SECOND(self.raw));
} }
// TODO: Decide whether to copy `Id` functions over? // TODO: Decide whether to copy `Id` functions over?

@ -1,14 +1,20 @@
//! Represents the path of an `Entity`, describing its place in the world's
//! hierarchy which is constructed using `ChildOf` relationships.
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const meta = @import("./meta.zig");
const Entity = @import("./entity.zig").Entity; const Entity = @import("./entity.zig").Entity;
// TODO: See if we should even be using this?
const native_endian = @import("builtin").cpu.arch.endian();
// TODO: Do something better than just `std.debug.assert`. // TODO: Do something better than just `std.debug.assert`.
// TODO: Offer a way to validate paths, like checking for empty parts. // TODO: Offer a way to validate paths, like checking for empty parts.
/// Represents the path of an `Entity`, describing its place in the world's const Path = @This();
/// hierarchy which is constructed using `ChildOf` relationships.
pub const Path = struct {
/// Whether the path is specified to be absolute. /// Whether the path is specified to be absolute.
/// Note that a relative path can still be interpreted as absolute. /// Note that a relative path can still be interpreted as absolute.
absolute: bool, absolute: bool,
@ -63,11 +69,11 @@ pub const Path = struct {
/// In many cases, the lifetime of `Path`s is relatively short. When this /// In many cases, the lifetime of `Path`s is relatively short. When this
/// is not the case, it's recommended to `.clone()` the path after creation. /// is not the case, it's recommended to `.clone()` the path after creation.
pub fn buildParts(parts: anytype) [numElements(parts)]EntityPart { pub fn buildParts(parts: anytype) [numElements(parts)]EntityPart {
if (comptime !std.meta.trait.isTuple(@TypeOf(parts))) if (comptime !meta.isTuple(@TypeOf(parts)))
@compileError("Expected tuple, got '" ++ @typeName(@TypeOf(parts)) ++ "'"); @compileError("Expected tuple, got '" ++ @typeName(@TypeOf(parts)) ++ "'");
var result: [numElements(parts)]EntityPart = undefined; var result: [numElements(parts)]EntityPart = undefined;
inline for (&result, parts) |*res, part| { inline for (&result, parts) |*res, part| {
res.* = if (comptime std.meta.trait.isZigString(@TypeOf(part))) res.* = if (comptime meta.isZigString(@TypeOf(part)))
.{ .name = part } .{ .name = part }
else switch (@typeInfo(@TypeOf(part))) { else switch (@typeInfo(@TypeOf(part))) {
.Int, .ComptimeInt => .{ .id = part }, .Int, .ComptimeInt => .{ .id = part },
@ -79,7 +85,7 @@ pub const Path = struct {
/// Returns the number of elements of the specified tuple type. /// Returns the number of elements of the specified tuple type.
fn numElements(parts: anytype) usize { fn numElements(parts: anytype) usize {
return if (comptime std.meta.trait.isTuple(@TypeOf(parts))) return if (comptime meta.isTuple(@TypeOf(parts)))
@typeInfo(@TypeOf(parts)).Struct.fields.len @typeInfo(@TypeOf(parts)).Struct.fields.len
else else
0; 0;
@ -231,7 +237,7 @@ pub const Path = struct {
) ![:0]const u8 { ) ![:0]const u8 {
const opt = options orelse FormatOptions.default; const opt = options orelse FormatOptions.default;
const length = self.calculateLength(opt); const length = self.calculateLength(opt);
var result = try alloc.allocSentinel(u8, length, 0); const result = try alloc.allocSentinel(u8, length, 0);
var stream = std.io.fixedBufferStream(result); var stream = std.io.fixedBufferStream(result);
try write(self, opt, stream.writer()); try write(self, opt, stream.writer());
return result; return result;
@ -295,14 +301,14 @@ pub const Path = struct {
} }
// Write the first part. // Write the first part.
switch (self.parts[0]) { switch (self.parts[0]) {
.id => |id| try writer.writeIntNative(u32, id), .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);
switch (part) { switch (part) {
.id => |id| try writer.writeIntNative(u32, id), .id => |id| try writer.writeInt(u32, id, native_endian),
.name => |name| try writer.writeAll(name), .name => |name| try writer.writeAll(name),
} }
} }
@ -339,11 +345,10 @@ pub const Path = struct {
else 10; else 10;
// zig fmt: on // zig fmt: on
} }
};
test Path { test Path {
const alloc = std.testing.allocator; const alloc = std.testing.allocator;
const expect = @import("./expect.zig"); const expect = @import("./test/expect.zig");
// Paths may be constructed by parsing strings. // Paths may be constructed by parsing strings.
const relative = try Path.fromString("some.relative.path", null, alloc); const relative = try Path.fromString("some.relative.path", null, alloc);
@ -414,6 +419,8 @@ test Path {
// The default `FormatOptions` may be changed. // The default `FormatOptions` may be changed.
Path.FormatOptions.default = Path.FormatOptions.unix; Path.FormatOptions.default = Path.FormatOptions.unix;
// But be sure to unset them after this test so other tests can succeed.
defer Path.FormatOptions.default = Path.FormatOptions.flecs_c;
// This affects functions that use them to parse or format strings. // This affects functions that use them to parse or format strings.
try expect.fmt("some/relative/path", "{}", .{relative}); try expect.fmt("some/relative/path", "{}", .{relative});

@ -3,6 +3,7 @@
//! determines the type of `expected`, avoiding the use of `@as`. //! determines the type of `expected`, avoiding the use of `@as`.
const std = @import("std"); const std = @import("std");
const meta = @import("../meta.zig");
pub fn @"true"(value: anytype) !void { pub fn @"true"(value: anytype) !void {
return equal(true, value); return equal(true, value);
@ -21,7 +22,7 @@ pub fn err(expected: anyerror, actual: anytype) !void {
} }
pub fn equal(expected: anytype, actual: anytype) !void { pub fn equal(expected: anytype, actual: anytype) !void {
return if (comptime std.meta.trait.isZigString(@TypeOf(expected))) return if (comptime meta.isZigString(@TypeOf(expected)))
equalStrings(expected, actual) equalStrings(expected, actual)
else else
std.testing.expectEqual(@as(@TypeOf(actual), expected), actual); std.testing.expectEqual(@as(@TypeOf(actual), expected), actual);

@ -3,17 +3,18 @@
const std = @import("std"); const std = @import("std");
const alloc = std.testing.allocator; const alloc = std.testing.allocator;
const expect = @import("../src/expect.zig");
const util = @import("./util.zig"); const expect = @import("../expect.zig");
const FlecsError = @import("../src/error.zig").FlecsError; const util = @import("../util.zig");
const flecs = @import("../src/main.zig");
const flecs = @import("../../main.zig");
const FlecsError = flecs.FlecsError;
const c = flecs.c; const c = flecs.c;
const context = flecs.Context(void); const Context = flecs.Context(void);
const Entity = context.Entity; const Entity = Context.Entity;
const Path = context.Path; const Path = Context.Path;
const World = context.World; const World = Context.World;
test "Entity_init_id" { test "Entity_init_id" {
flecs.init(alloc); flecs.init(alloc);

@ -3,23 +3,24 @@
const std = @import("std"); const std = @import("std");
const alloc = std.testing.allocator; const alloc = std.testing.allocator;
const expect = @import("../src/expect.zig");
const util = @import("./util.zig"); const expect = @import("./../expect.zig");
const flecs = @import("../src/main.zig"); const util = @import("./../util.zig");
const flecs = @import("../../main.zig");
const c = flecs.c; const c = flecs.c;
const context = flecs.Context(void); const Context = flecs.Context(void);
const World = context.World; const World = Context.World;
const Iter = context.Iter; const Iter = Context.Iter;
const Entity = context.Entity; const Entity = Context.Entity;
const Position = struct { x: f32, y: f32 }; const Position = struct { x: f32, y: f32 };
const Velocity = struct { x: f32, y: f32 }; const Velocity = struct { x: f32, y: f32 };
fn move(it: Iter) void { fn move(it: Iter) void {
var pos = it.field(Position, 1); const pos = it.field(Position, 1);
var vel = it.field(Velocity, 2); const vel = it.field(Velocity, 2);
util.Probe.probeIter(it) catch unreachable; util.Probe.probeIter(it) catch unreachable;
for (pos, vel) |*p, *v| { for (pos, vel) |*p, *v| {
@ -28,10 +29,6 @@ fn move(it: Iter) void {
} }
} }
fn lookup(comptime T: type) c.ecs_entity_t {
return flecs.Lookup(void, T).id;
}
test "World_progress_w_0" { test "World_progress_w_0" {
flecs.init(std.testing.allocator); flecs.init(std.testing.allocator);
var world = try World.init(); var world = try World.init();
@ -45,7 +42,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.init();
c.ecs_set_context(world.raw, &ctx); c.ecs_set_ctx(world.raw, &ctx, null);
e1.set(Position, .{ .x = 0, .y = 0 }); e1.set(Position, .{ .x = 0, .y = 0 });
e1.set(Velocity, .{ .x = 1, .y = 2 }); e1.set(Velocity, .{ .x = 1, .y = 2 });
@ -59,8 +56,8 @@ test "World_progress_w_0" {
try expect.equal(null, ctx.param); try expect.equal(null, ctx.param);
try expect.equal(e1.raw, ctx.e[0]); try expect.equal(e1.raw, ctx.e[0]);
try expect.equal(lookup(Position), ctx.c[0][0]); try expect.equal(Context.lookup(Position).*, ctx.c[0][0]);
try expect.equal(lookup(Velocity), ctx.c[0][1]); try expect.equal(Context.lookup(Velocity).*, ctx.c[0][1]);
try expect.equal(0, ctx.s[0][0]); try expect.equal(0, ctx.s[0][0]);
try expect.equal(0, ctx.s[0][1]); try expect.equal(0, ctx.s[0][1]);
@ -82,7 +79,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.init();
c.ecs_set_context(world.raw, &ctx); c.ecs_set_ctx(world.raw, &ctx, null);
e1.set(Position, .{ .x = 0, .y = 0 }); e1.set(Position, .{ .x = 0, .y = 0 });
e1.set(Velocity, .{ .x = 1, .y = 2 }); e1.set(Velocity, .{ .x = 1, .y = 2 });
@ -96,8 +93,8 @@ test "World_progress_w_t" {
try expect.equal(null, ctx.param); try expect.equal(null, ctx.param);
try expect.equal(e1.raw, ctx.e[0]); try expect.equal(e1.raw, ctx.e[0]);
try expect.equal(lookup(Position), ctx.c[0][0]); try expect.equal(Context.lookup(Position).*, ctx.c[0][0]);
try expect.equal(lookup(Velocity), ctx.c[0][1]); try expect.equal(Context.lookup(Velocity).*, ctx.c[0][1]);
try expect.equal(0, ctx.s[0][0]); try expect.equal(0, ctx.s[0][0]);
try expect.equal(0, ctx.s[0][1]); try expect.equal(0, ctx.s[0][1]);

@ -0,0 +1,4 @@
test {
_ = @import("./flecs/entity.zig");
_ = @import("./flecs/world.zig");
}

@ -3,7 +3,7 @@ const expect = std.testing.expect;
const expectEql = std.testing.expectEqual; const expectEql = std.testing.expectEqual;
const expectStrEql = std.testing.expectEqualStrings; const expectStrEql = std.testing.expectEqualStrings;
const flecs = @import("../src/main.zig"); const flecs = @import("../main.zig");
const c = flecs.c; const c = flecs.c;
const context = flecs.Context(void); const context = flecs.Context(void);
@ -65,7 +65,7 @@ pub const Probe = struct {
} }
pub fn probeIter(it: Iter) !void { pub fn probeIter(it: Iter) !void {
var ctx = c.ecs_get_context(it.world.raw); var ctx = c.ecs_get_ctx(it.world.raw);
if (ctx == null) ctx = it.raw.ctx; if (ctx == null) ctx = it.raw.ctx;
if (ctx) |ct| { if (ctx) |ct| {
var p: *Probe = @alignCast(@ptrCast(ct)); var p: *Probe = @alignCast(@ptrCast(ct));

@ -1,75 +0,0 @@
const std = @import("std");
const c = @import("./c.zig");
const Lookup = @import("./main.zig").Lookup;
const Entity = @import("./entity.zig").Entity;
const Pair = @import("./pair.zig").Pair;
const Id = @import("./id.zig").Id;
/// Converts the specified value to an `ecs_entity_t`.
///
/// If `null` is passed to this function, either directly or through an
/// optional type of one of the supported types, `0` is returned. It's up to
/// the caller to do any necessary testing for `0` values, and whether the
/// returned entity is valid or alive.
///
/// The following can be converted:
/// - `Entity` and `ecs_entity_t` - Self-explanatory.
/// - `type` - Looks up the entity associated with that type.
pub fn anyToEntity(comptime ctx: anytype, value: anytype) c.ecs_entity_t {
return switch (@TypeOf(value)) {
@TypeOf(null) => 0,
c.ecs_entity_t => value,
Entity(ctx) => value.raw,
?Entity(ctx) => if (value) |v| v.raw else 0,
type => Lookup(ctx, value).id,
else => @compileError("Value of type " ++ @typeName(@TypeOf(value)) ++ " can't be converted to Entity"),
};
}
/// Converts the specified value to an `ecs_id_t`.
///
/// If `null` is passed to this function, either directly or through an
/// optional type of one of the supported types, `0` is returned. It's up to
/// the caller to do any necessary testing for `0` values, and whether the
/// returned id is valid.
///
/// The following can be converted:
/// - `Id` and `ecs_id_it` - Self-explanatory.
/// - `Entity` and `ecs_entity_t` - Self-explanatory.
/// - `Pair` - Converts to the equivalent `Id`.
/// - `.{ relation, target }` - A `Pair`, converted using `anyToEntity()`.
/// - `type` - Looks up the entity associated with that type.
pub fn anyToId(comptime ctx: anytype, value: anytype) c.ecs_id_t {
const T = @TypeOf(value);
if (comptime std.meta.trait.isTuple(T)) {
if (@typeInfo(T).Struct.fields.len != 2)
@compileError("Value of type " ++ @typeName(T) ++ " must be a tuple with 2 elements, to be a Pair");
const relation = anyToEntity(ctx, value[0]);
const target = anyToEntity(ctx, value[1]);
return c.ecs_make_pair(relation, target);
}
return switch (T) {
@TypeOf(null) => 0,
c.ecs_id_t => value,
Id(ctx) => value.raw,
?Id(ctx) => if (value) |v| v.raw else 0,
Pair(ctx) => value.raw,
?Pair(ctx) => if (value) |v| v.raw else 0,
// Technically same type as `ecs_id_it`.
// c.ecs_entity_t => value,
Entity(ctx) => value.raw,
?Entity(ctx) => if (value) |v| v.raw else 0,
type => Lookup(ctx, value).id,
else => @compileError("Value of type " ++ @typeName(T) ++ " can't be converted to Id"),
};
}
/// Gets the simplified type name of the specified type.
/// That is, without any namespace qualifiers.
pub fn simpleTypeName(comptime T: type) [:0]const u8 {
const fullName = @typeName(T);
const index = std.mem.lastIndexOf(u8, fullName, ".");
return if (index) |i| fullName[(i + 1)..] else fullName;
}

@ -1,23 +1,22 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const meta = @import("./meta.zig");
const flecs = @import("./main.zig"); const flecs = @import("./main.zig");
const EntityError = flecs.EntityError;
const Path = flecs.Path;
const c = flecs.c; const c = flecs.c;
const err = @import("./error.zig");
const util = @import("./util.zig");
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 { pub fn World(comptime ctx: anytype) type {
return struct { return struct {
raw: *c.ecs_world_t,
const Self = @This(); const Self = @This();
const Context = flecs.Context(ctx);
const Entity = Context.Entity;
const Iter = Context.Iter;
raw: *c.ecs_world_t,
pub fn init() !*Self { pub fn init() !*Self {
std.debug.assert(flecs.is_initialized); std.debug.assert(flecs.is_initialized);
var result = try flecs.allocator.create(Self); var result = try flecs.allocator.create(Self);
@ -54,8 +53,8 @@ pub fn World(comptime ctx: anytype) type {
/// Returns an `Entity` for the specified `ecs_entity_t` value, or an /// Returns an `Entity` for the specified `ecs_entity_t` value, or an
/// error if the entity is invalid or not alive in this `World`. /// error if the entity is invalid or not alive in this `World`.
pub fn lookupAlive(self: *Self, id: c.ecs_entity_t) !Entity(ctx) { pub fn lookupAlive(self: *Self, id: c.ecs_entity_t) !Entity {
const result = Entity(ctx).fromRaw(self, id); const result = Entity.fromRaw(self, id);
try result.ensureAlive(); try result.ensureAlive();
return result; return result;
} }
@ -66,7 +65,7 @@ pub fn World(comptime ctx: anytype) type {
/// 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(ctx) { pub fn lookupByPath(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| {
@ -80,43 +79,43 @@ pub fn World(comptime ctx: anytype) type {
if (part == .id and parent != c.ecs_get_parent(self.raw, current)) return error.ParentMismatch; if (part == .id and parent != c.ecs_get_parent(self.raw, current)) return error.ParentMismatch;
parent = current; parent = current;
} }
return Entity(ctx).fromRaw(self, current); return Entity.fromRaw(self, current);
} }
/// Returns the component `Entity` registered for the specified /// Returns the component `Entity` registered for the specified
/// type `T`, or an error if an association has not been made. /// type `T`, or an error if an association has not been made.
pub fn lookupByType(self: *Self, comptime T: type) !Entity(ctx) { pub fn lookupByType(self: *Self, comptime T: type) !Entity {
return lookupAlive(self, Lookup(ctx, T).id); return lookupAlive(self, Context.lookup(T).*);
} }
/// Creates or modifies an `Entity` in this `World`. /// Creates or modifies an `Entity` in this `World`.
/// See `Entity.init(...)` for more information. /// See `Entity.init(...)` for more information.
pub fn entity(self: *Self, config: Entity(ctx).Config, add: anytype) !Entity(ctx) { pub fn entity(self: *Self, config: Entity.Config, add: anytype) !Entity {
return Entity(ctx).init(self, config, add); return Entity.init(self, config, add);
} }
pub fn tag(self: *Self, comptime T: type) !Entity(ctx) { pub fn tag(self: *Self, comptime T: type) !Entity {
if (@sizeOf(T) > 0) @compileError("'" ++ @typeName(T) ++ "' must be a zero-sized type"); if (@sizeOf(T) > 0) @compileError("'" ++ @typeName(T) ++ "' must be a zero-sized type");
const name = util.simpleTypeName(T); const name = meta.simpleTypeName(T);
const result = try self.entity(.{ .name = name, .symbol = name }, .{}); const result = try self.entity(.{ .name = name, .symbol = name }, .{});
Lookup(ctx, T).id = result.raw; Context.lookup(T).* = result.raw;
return result; return result;
} }
pub fn component(self: *Self, comptime T: type) !Entity(ctx) { pub fn component(self: *Self, comptime T: type) !Entity {
if (@sizeOf(T) == 0) @compileError("'" ++ @typeName(T) ++ "' must not be a zero-sized type"); if (@sizeOf(T) == 0) @compileError("'" ++ @typeName(T) ++ "' must not be a zero-sized type");
const name = util.simpleTypeName(T); const name = meta.simpleTypeName(T);
const entity2 = 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 desc = std.mem.zeroInit(c.ecs_component_desc_t, .{
.entity = entity2.raw, .entity = entity_.raw,
.type = .{ .size = @sizeOf(T), .alignment = @alignOf(T) }, .type = .{ .size = @sizeOf(T), .alignment = @alignOf(T) },
}); });
var result = c.ecs_component_init(self.raw, &desc); const result = c.ecs_component_init(self.raw, &desc);
if (result == 0) return err.getLastErrorOrUnknown(); if (result == 0) return flecs.getLastErrorOrUnknown();
Lookup(ctx, T).id = result; Context.lookup(T).* = result;
return Entity(ctx).fromRaw(self, result); return Entity.fromRaw(self, result);
} }
/// Registers a singleton component of type `T` with the specified value. /// Registers a singleton component of type `T` with the specified value.
@ -129,7 +128,7 @@ pub fn World(comptime ctx: anytype) type {
/// Use `get()` and `set()` to get and set the value of this singleton. /// Use `get()` and `set()` to get and set the value of this singleton.
/// ///
/// Returns the created component entity. /// Returns the created component entity.
pub fn singleton(self: *Self, comptime T: type, value: T) !Entity(ctx) { pub fn singleton(self: *Self, comptime T: type, value: T) !Entity {
const single = try component(self, T); const single = try component(self, T);
single.set(T, value); single.set(T, value);
return single; return single;
@ -160,16 +159,16 @@ pub fn World(comptime ctx: anytype) type {
callback: SystemCallback, callback: SystemCallback,
phase: anytype, phase: anytype,
expr: [:0]const u8, expr: [:0]const u8,
) !Entity(ctx) { ) !Entity {
const phase2 = util.anyToEntity(ctx, phase); const phase_ = Context.anyToEntity(phase);
const entity2 = try if (phase2 != 0) const entity_ = try if (phase_ != 0)
self.entity(.{ .name = name }, .{ .{ c.EcsDependsOn, phase2 }, phase2 }) self.entity(.{ .name = name }, .{ .{ c.EcsDependsOn, phase_ }, phase_ })
else else
self.entity(.{ .name = name }, .{}); self.entity(.{ .name = name }, .{});
var context = try SystemCallbackContext.init(self, callback); const context = try SystemCallbackContext.init(self, callback);
var desc = std.mem.zeroInit(c.ecs_system_desc_t, .{ var desc = std.mem.zeroInit(c.ecs_system_desc_t, .{
.entity = entity2.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,
@ -177,11 +176,11 @@ pub fn World(comptime ctx: anytype) type {
desc.query.filter.expr = expr; desc.query.filter.expr = expr;
const result = c.ecs_system_init(self.raw, &desc); const result = c.ecs_system_init(self.raw, &desc);
if (result == 0) return err.getLastErrorOrUnknown(); if (result == 0) return flecs.getLastErrorOrUnknown();
return Entity(ctx).fromRaw(self, result); return Entity.fromRaw(self, result);
} }
const SystemCallback = *const fn (Iter(ctx)) void; const SystemCallback = *const fn (Iter) void;
const SystemCallbackContext = struct { const SystemCallbackContext = struct {
world: *Self, world: *Self,
func: SystemCallback, func: SystemCallback,
@ -204,7 +203,7 @@ pub fn World(comptime ctx: anytype) type {
fn invoke(it2: *anyopaque) callconv(.C) void { fn invoke(it2: *anyopaque) callconv(.C) void {
const it: ?*c.ecs_iter_t = @alignCast(@ptrCast(it2)); const it: ?*c.ecs_iter_t = @alignCast(@ptrCast(it2));
const context: *SystemCallbackContext = @alignCast(@ptrCast(it.?.binding_ctx)); const context: *SystemCallbackContext = @alignCast(@ptrCast(it.?.binding_ctx));
var iter = Iter(ctx).fromRawPtr(context.world, it.?); const iter = Iter.fromRawPtr(context.world, it.?);
context.func(iter); context.func(iter);
} }
}; };
@ -212,7 +211,7 @@ pub fn World(comptime ctx: anytype) type {
/// Creates a term iterator which allows querying for all entities /// Creates a term iterator which allows querying for all entities
/// 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_ = util.anyToId(ctx, id); const id_ = Context.anyToId(id);
var term_ = std.mem.zeroInit(c.ecs_term_t, .{ .id = 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, &term_);
return .{ .world = self, .iter = iter }; return .{ .world = self, .iter = iter };
@ -221,18 +220,18 @@ pub fn World(comptime ctx: anytype) type {
// TODO: Move this logic to `Iter`, add `TermIter` type? // TODO: Move this logic to `Iter`, add `TermIter` type?
// TODO: Rename `Iter` to `Iterator`? // TODO: Rename `Iter` to `Iterator`?
pub const TermIterator = struct { pub const TermIterator = struct {
world: *World(ctx), world: *World,
iter: c.ecs_iter_t, iter: c.ecs_iter_t,
index: i32 = 0, index: i32 = 0,
pub fn next(self: *TermIterator) ?Entity(ctx) { pub fn next(self: *TermIterator) ?Entity {
if (self.index >= self.iter.count) if (self.index >= self.iter.count)
if (!c.ecs_term_next(&self.iter)) if (!c.ecs_term_next(&self.iter))
return null; return null;
const result = self.iter.entities[@intCast(self.index)]; const result = self.iter.entities[@intCast(self.index)];
self.index += 1; self.index += 1;
return Entity(ctx).fromRaw(self.world, result); return Entity.fromRaw(self.world, result);
} }
// TODO: Check where else in the codebase it would make sense to have `deinit` take a pointer? // TODO: Check where else in the codebase it would make sense to have `deinit` take a pointer?
@ -245,9 +244,9 @@ pub fn World(comptime ctx: anytype) type {
}; };
/// Gets the current scope of this `World`. See also: `setScope()`. /// Gets the current scope of this `World`. See also: `setScope()`.
pub fn getScope(self: *Self) ?Entity(ctx) { pub fn getScope(self: *Self) ?Entity {
const result = c.ecs_get_scope(self.raw); const result = c.ecs_get_scope(self.raw);
return if (result != 0) Entity(ctx).fromRaw(self, result) else null; return if (result != 0) Entity.fromRaw(self, result) else null;
} }
/// Sets the current scope of this `World` to the specified entity. /// Sets the current scope of this `World` to the specified entity.
@ -259,9 +258,9 @@ pub fn World(comptime ctx: anytype) type {
/// Returns the previously set scope, if any. It's recommended to set /// Returns the previously set scope, if any. It's recommended to set
/// the scope back to the previous value after you're done operating /// the scope back to the previous value after you're done operating
/// in the desired scope. /// in the desired scope.
pub fn setScope(self: *Self, value: anytype) ?Entity(ctx) { pub fn setScope(self: *Self, value: anytype) ?Entity {
const result = c.ecs_set_scope(self.raw, util.anyToEntity(ctx, value)); const result = c.ecs_set_scope(self.raw, Context.anyToEntity(value));
return if (result != 0) Entity(ctx).fromRaw(self, result) else null; return if (result != 0) Entity.fromRaw(self, result) else null;
} }
}; };
} }

@ -1,5 +0,0 @@
test {
_ = @import("../src/main.zig");
_ = @import("./entity.zig");
_ = @import("./world.zig");
}
Loading…
Cancel
Save