Implement some things, first test success?

main
copygirl 1 year ago
parent 0c6477c852
commit 41d5155d4d
  1. 6
      build.zig
  2. 5
      src/c.zig
  3. 19
      src/entity.zig
  4. 6
      src/id.zig
  5. 50
      src/iter.zig
  6. 8
      src/main.zig
  7. 64
      src/world.zig
  8. 0
      test/main.zig
  9. 54
      test/util.zig
  10. 68
      test/world.zig
  11. 71
      tests/world.zig

@ -19,7 +19,7 @@ pub fn build(b: *std.Build) !void {
b.installArtifact(lib);
const main_tests = b.addTest(.{
.root_source_file = .{ .path = "tests/main.zig" },
.root_source_file = .{ .path = "test/main.zig" },
// Has to be specified so tests can run autodoc tests from src/.
.main_pkg_path = .{ .path = "." },
.target = target,
@ -38,10 +38,6 @@ fn setupFlecs(step: *std.Build.CompileStep) void {
.file = .{ .path = "libs/flecs/flecs.c" },
.flags = &.{"-fno-sanitize=undefined"},
});
step.defineCMacro("FLECS_NO_CPP", null);
step.defineCMacro("FLECS_USE_OS_ALLOC", null);
if (@import("builtin").mode == .Debug)
step.defineCMacro("FLECS_SANITIZE", null);
if (step.target.isWindows())
step.linkSystemLibraryName("ws2_32");
}

@ -1,3 +1,8 @@
pub usingnamespace @cImport({
@cDefine("FLECS_NO_CPP", {});
@cDefine("FLECS_USE_OS_ALLOC", {});
if (@import("builtin").mode == .Debug)
@cDefine("FLECS_SANITIZE", {});
@cInclude("flecs.h");
});

@ -1,5 +1,6 @@
const c = @import("./c.zig");
const Lookup = @import("./main.zig").Lookup;
const World = @import("./world.zig").World;
pub fn Entity(comptime ctx: anytype) type {
@ -12,5 +13,23 @@ pub fn Entity(comptime ctx: anytype) type {
pub fn fromRaw(world: *World(ctx), raw: c.ecs_entity_t) Self {
return .{ .world = world, .raw = raw };
}
pub fn isValid(self: Self) bool {
return c.ecs_is_valid(self.world.raw, self.raw);
}
pub fn isAlive(self: Self) bool {
return c.ecs_is_alive(self.world.raw, self.raw);
}
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)));
}
pub fn set(self: Self, comptime T: type, value: T) void {
const id = Lookup(ctx, T).id;
_ = c.ecs_set_id(self.world.raw, self.raw, id, @sizeOf(T), &value);
}
};
}

@ -6,5 +6,11 @@ pub fn Id(comptime ctx: anytype) type {
return struct {
world: *World(ctx),
raw: c.ecs_id_t,
const Self = @This();
pub fn fromRaw(world: *World(ctx), raw: c.ecs_id_t) Self {
return .{ .world = world, .raw = raw };
}
};
}

@ -1,10 +1,58 @@
const c = @import("./c.zig");
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 {
return struct {
world: *World(ctx),
raw: c.ecs_iter_t,
raw: *c.ecs_iter_t,
owned: bool,
const Self = @This();
pub fn fromRawPtr(world: *World(ctx), ptr: *c.ecs_iter_t) Self {
return .{ .world = world, .raw = ptr, .owned = false };
}
pub fn fromRawValue(world: *World(ctx), value: c.ecs_iter_t) !Self {
var raw = try world.allocator.create(c.ecs_iter_t);
raw.* = value;
return .{ .world = world, .raw = raw, .owned = true };
}
pub fn deinit(self: Self) void {
if (self.isValid()) c.ecs_iter_fini(self.raw);
if (self.owned) self.world.allocator.destroy(self.raw);
}
pub fn isValid(self: Self) bool {
return (self.raw.flags & c.EcsIterIsValid) != 0;
}
pub fn getCount(self: Self) usize {
return @intCast(self.raw.count);
}
pub fn getDeltaTime(self: Self) f32 {
return self.raw.delta_time;
}
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));
var typed_ptr: [*]T = @alignCast(@ptrCast(raw_ptr));
return typed_ptr[0..self.getCount()];
}
pub fn fieldId(self: Self, index: usize) Id(ctx) {
const raw = c.ecs_field_id(self.raw, @intCast(index));
return Id(ctx).fromRaw(self.world, raw);
}
pub fn fieldSource(self: Self, index: usize) Entity(ctx) {
const raw = c.ecs_field_src(self.raw, @intCast(index));
return Entity(ctx).fromRaw(self.world, raw);
}
};
}

@ -13,14 +13,14 @@ pub fn Context(comptime ctx: anytype) type {
pub const Id = @import("./id.zig").Id(ctx);
pub const Iter = @import("./iter.zig").Iter(ctx);
pub const World = @import("./world.zig").World(ctx);
};
}
pub fn Lookup(comptime T: type) type {
_ = T; // Only necessary to create a unique type.
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_id_t = 0;
};
}
};
}
test {

@ -4,10 +4,11 @@ const Allocator = std.mem.Allocator;
const c = @import("./c.zig");
const util = @import("./util.zig");
pub fn World(comptime ctx: anytype) type {
const Lookup = @import("./main.zig").Context(ctx).Lookup;
const Entity = @import("./entity.zig").Entity(ctx);
const Lookup = @import("./main.zig").Lookup;
const Entity = @import("./entity.zig").Entity;
const Iter = @import("./iter.zig").Iter;
pub fn World(comptime ctx: anytype) type {
return struct {
raw: *c.ecs_world_t,
allocator: Allocator,
@ -37,23 +38,29 @@ pub fn World(comptime ctx: anytype) type {
return c.ecs_progress(self.raw, delta_time);
}
pub fn lookup(self: *Self, comptime T: type) ?Entity(ctx) {
const id = Lookup(ctx, T).id;
const result = Entity(ctx).fromRaw(self, id);
return if (result.isAlive()) result else null;
}
pub fn entity(
self: *Self,
comptime add: []const type,
) Entity {
) Entity(ctx) {
var desc = std.mem.zeroes(c.ecs_entity_desc_t);
if (add.len > c.FLECS_ID_DESC_MAX)
@compileLog("add.len > FLECS_ID_DESC_MAX");
inline for (add, 0..) |T, i|
desc.add[i] = Lookup(T).id;
desc.add[i] = Lookup(ctx, T).id;
const result = c.ecs_entity_init(self.*.raw, &desc);
return Entity.fromRaw(self, result);
return Entity(ctx).fromRaw(self, result);
}
pub fn component(
self: *Self,
comptime T: type,
) Entity {
) Entity(ctx) {
const name = util.simpleTypeName(T);
const entDesc = std.mem.zeroInit(c.ecs_entity_desc_t, .{
.name = name,
@ -64,17 +71,18 @@ pub fn World(comptime ctx: anytype) type {
.entity = c.ecs_entity_init(self.raw, &entDesc),
.type = .{ .size = @sizeOf(T), .alignment = @alignOf(T) },
});
Lookup(T).id = c.ecs_component_init(self.raw, &compDesc);
return Entity.fromRaw(self, Lookup(T).id);
Lookup(ctx, T).id = c.ecs_component_init(self.raw, &compDesc);
return Entity(ctx).fromRaw(self, Lookup(ctx, T).id);
}
pub fn system(
self: *Self,
name: [:0]const u8,
callback: c.ecs_iter_action_t,
phase: ?Entity,
callback: SystemCallback,
phase: ?Entity(ctx),
expr: [:0]const u8,
) Entity {
) Entity(ctx) {
var context = SystemCallbackContext.init(self, callback);
var entDesc = std.mem.zeroes(c.ecs_entity_desc_t);
entDesc.name = name;
if (phase) |p| {
@ -84,9 +92,35 @@ pub fn World(comptime ctx: anytype) type {
var sysDesc = std.mem.zeroes(c.ecs_system_desc_t);
sysDesc.entity = c.ecs_entity_init(self.raw, &entDesc);
sysDesc.query.filter.expr = expr;
sysDesc.callback = callback;
const result = c.ecs_system_init(self.raw, *sysDesc);
return Entity.fromRaw(self, result);
sysDesc.callback = &SystemCallbackContext.invoke;
sysDesc.binding_ctx = context;
sysDesc.binding_ctx_free = &SystemCallbackContext.free;
const result = c.ecs_system_init(self.raw, &sysDesc);
return Entity(ctx).fromRaw(self, result);
}
const SystemCallback = *const fn (Iter(ctx)) void;
const SystemCallbackContext = struct {
world: *Self,
func: SystemCallback,
pub fn init(world: *Self, callback: SystemCallback) *SystemCallbackContext {
var result = world.allocator.create(SystemCallbackContext) catch @panic("OOM");
result.world = world;
result.func = callback;
return result;
}
fn free(context: ?*anyopaque) callconv(.C) void {
const self: *SystemCallbackContext = @alignCast(@ptrCast(context));
self.world.allocator.destroy(self);
}
fn invoke(it: ?*c.ecs_iter_t) callconv(.C) void {
const context: *SystemCallbackContext = @alignCast(@ptrCast(it.?.binding_ctx));
var iter = Iter(ctx).fromRawPtr(context.world, it.?);
context.func(iter);
}
};
};
}

@ -19,11 +19,11 @@ pub const Probe = struct {
system: c.ecs_entity_t,
event: c.ecs_entity_t,
eventId: c.ecs_id_t,
offset: i32,
count: i32,
invoked: i32,
termCount: i32,
termIndex: i32,
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,
@ -33,43 +33,43 @@ pub const Probe = struct {
return std.mem.zeroes(Probe);
}
pub fn probeSystemWithContext(ctx: *Probe, it: *Iter) !void {
ctx.*.param = it.*.param;
ctx.*.system = it.*.system;
ctx.*.event = it.*.event;
ctx.*.eventId = it.*.eventId;
ctx.*.offset = 0;
ctx.*.termCount = it.*.fieldCount;
ctx.*.termIndex = it.*.termIndex;
pub fn probeSystemWithContext(ctx: *Probe, it: Iter) !void {
ctx.param = it.raw.param;
ctx.system = it.raw.system;
ctx.event = it.raw.event;
ctx.eventId = it.raw.event_id;
ctx.offset = 0;
ctx.termCount = @intCast(it.raw.field_count);
ctx.termIndex = @intCast(it.raw.term_index);
for (0..ctx.*.term_count) |i| {
ctx.*.c[ctx.*.invoked][i] = it.*.ids[i];
ctx.*.s[ctx.*.invoked][i] = it.fieldSource(i + 1);
for (0..ctx.termCount) |i| {
ctx.c[ctx.invoked][i] = it.raw.ids[i];
ctx.s[ctx.invoked][i] = it.fieldSource(i + 1).raw;
const e = it.fieldId(i + 1);
const e = it.fieldId(i + 1).raw;
try expect(e != 0);
}
for (0..it.*.count) |i| {
if (i + ctx.*.count < 256) {
ctx.*.e[i + ctx.*.count] = it.*.entities[i];
for (0..it.getCount()) |i| {
if (i + ctx.count < 256) {
ctx.e[i + ctx.count] = it.raw.entities[i];
} else {
// Can't store more than that, tests shouldn't
// rely on getting back more than 256 results.
unreachable;
}
}
ctx.*.count += it.*.count;
ctx.count += it.getCount();
ctx.*.invoked += 1;
ctx.invoked += 1;
}
pub fn probeIter(it: *Iter) !void {
var ctx = c.ecs_get_context(it.*.world);
if (ctx == null) ctx = it.*.ctx;
pub fn probeIter(it: Iter) !void {
var ctx = c.ecs_get_context(it.world.raw);
if (ctx == null) ctx = it.raw.ctx;
if (ctx) |ct| {
var p: *Probe = @ptrCast(ct);
p.probeSystemWithContext(it);
var p: *Probe = @alignCast(@ptrCast(ct));
try p.probeSystemWithContext(it);
}
}
};

@ -0,0 +1,68 @@
// Reimplementations of the following tests from Flecs:
// https://github.com/SanderMertens/flecs/blob/master/test/api/src/World.c
const std = @import("std");
const expect = std.testing.expect;
const expectEql = std.testing.expectEqual;
const expectStrEql = std.testing.expectEqualStrings;
const util = @import("./util.zig");
const flecs = @import("../src/main.zig");
const c = flecs.c;
const context = flecs.Context(void);
const World = context.World;
const Iter = context.Iter;
const Entity = context.Entity;
const Position = struct { x: f32, y: f32 };
const Velocity = struct { x: f32, y: f32 };
fn move(it: Iter) void {
var pos = it.field(Position, 1);
var vel = it.field(Velocity, 2);
util.Probe.probeIter(it) catch unreachable;
for (pos, vel) |*p, *v| {
p.x += v.x * it.getDeltaTime();
p.y += v.y * it.getDeltaTime();
}
}
test "World_progress_w_0" {
var world = try World.init(std.testing.allocator);
defer world.deinit();
_ = world.component(Position);
_ = world.component(Velocity);
const e1 = world.entity(&.{ Position, Velocity });
const phase = Entity.fromRaw(world, c.EcsOnUpdate);
const move_system = world.system("move", move, phase, "Position, Velocity");
var ctx = util.Probe.init();
c.ecs_set_context(world.raw, &ctx);
e1.set(Position, .{ .x = 0, .y = 0 });
e1.set(Velocity, .{ .x = 1, .y = 2 });
_ = world.progress(0);
try expectEql(ctx.count, 1);
try expectEql(ctx.invoked, 1);
try expectEql(ctx.system, move_system.raw);
try expectEql(ctx.termCount, 2);
try expectEql(ctx.param, null);
try expectEql(ctx.e[0], e1.raw);
try expectEql(ctx.c[0][0], world.lookup(Position).?.raw);
try expectEql(ctx.c[0][1], world.lookup(Velocity).?.raw);
try expectEql(ctx.s[0][0], 0);
try expectEql(ctx.s[0][1], 0);
const p = e1.get(Position).?;
try expect(p.x != 0);
try expect(p.y != 0);
}

@ -1,71 +0,0 @@
// Reimplementations of the following tests from Flecs:
// https://github.com/SanderMertens/flecs/blob/master/test/api/src/World.c
const std = @import("std");
const expect = std.testing.expect;
const expectEql = std.testing.expectEqual;
const expectStrEql = std.testing.expectEqualStrings;
const util = @import("./util.zig");
const flecs = @import("../src/main.zig");
const c = flecs.c;
const context = flecs.Context(void);
const Lookup = context.Lookup;
const World = context.World;
const Iter = context.Iter;
const Entity = context.Entity;
const Position = struct { x: f32, y: f32 };
const Velocity = struct { x: f32, y: f32 };
fn move(it: *Iter) void {
var pos = it.field(Position, 1);
var vel = it.field(Velocity, 2);
util.Probe.probeIter(it);
for (0..it.count) |row| {
var p = &pos[row];
var v = &vel[row];
p.*.x += v.*.x * it.delta_time;
p.*.y += v.*.y * it.delta_time;
}
}
test "World_progress_w_0" {
var world = try World.init(std.testing.allocator);
defer world.deinit();
_ = world.component(Position);
_ = world.component(Velocity);
// const e1 = world.entity(&.{ Position, Velocity });
// // const phase = Entity.fromRaw(world, c.EcsOnUpdate);
// // const move_system = world.system("move", move, phase, "Position, Velocity");
// var ctx = util.Probe.init();
// c.ecs_set_context(world.raw, &ctx);
// // e1.set(Position, .{ 0, 0 });
// // e1.set(Velocity, .{ 1, 2 });
// _ = world.progress(0);
// try expectEql(ctx.count, 1);
// try expectEql(ctx.invoked, 1);
// // try expectEql(ctx.system, move_system);
// try expectEql(ctx.termCount, 2);
// try expectEql(ctx.param, null);
// try expectEql(ctx.e[0], e1.raw);
// try expectEql(ctx.c[0][0], Lookup(Position).id);
// try expectEql(ctx.c[0][1], Lookup(Velocity).id);
// try expectEql(ctx.s[0][0], 0);
// try expectEql(ctx.s[0][1], 0);
// // const p = try e1.get(Position);
// // try expect(p.x != 0);
// // try expect(p.y != 0);
}
Loading…
Cancel
Save