Compare commits

..

1 Commits

Author SHA1 Message Date
copygirl 1dda16fa01 WIP new system initialization 7 months ago
  1. 1
      src/context.zig
  2. 3
      src/entity.zig
  3. 11
      src/iter.zig
  4. 1
      src/main.zig
  5. 324
      src/system.zig
  6. 14
      src/test/flecs/world.zig
  7. 129
      src/world.zig

@ -9,6 +9,7 @@ pub fn Context(comptime ctx: anytype) type {
pub const Id = @import("./id.zig").Id(ctx); pub const Id = @import("./id.zig").Id(ctx);
pub const Iter = @import("./iter.zig").Iter(ctx); pub const Iter = @import("./iter.zig").Iter(ctx);
pub const Pair = @import("./pair.zig").Pair(ctx); pub const Pair = @import("./pair.zig").Pair(ctx);
pub const System = @import("./system.zig").System(ctx);
pub const World = @import("./world.zig").World(ctx); pub const World = @import("./world.zig").World(ctx);
/// Looks up an entity ID unique to this `Context` for the provided /// Looks up an entity ID unique to this `Context` for the provided

@ -197,7 +197,8 @@ pub fn Entity(comptime ctx: anytype) type {
/// Ensures this `Entity` is alive in this world, returning an error if not. /// Ensures this `Entity` is alive in this world, returning an error if not.
pub fn ensureAlive(self: Self) !void { pub fn ensureAlive(self: Self) !void {
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 { pub fn asId(self: Self) Id {

@ -43,6 +43,17 @@ pub fn Iter(comptime ctx: anytype) type {
return self.raw.delta_time; return self.raw.delta_time;
} }
/// Returns the system `Entity` this `Iter` was created for, if any.
pub fn system(self: Self) ?Entity {
return self.world.lookupAlive(self.raw.system) catch null;
}
/// Returns the `Entity` being iterated over at offset `row`.
pub fn entity(self: Self, row: usize) Entity {
const entities = self.raw.entities[0..self.count()];
return Entity.fromRaw(self.world, entities[row]);
}
pub fn field(self: Self, comptime T: type, index: usize) []T { pub fn field(self: Self, comptime T: type, index: usize) []T {
const 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));

@ -17,6 +17,7 @@ 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("./system.zig");
pub usingnamespace @import("./world.zig"); pub usingnamespace @import("./world.zig");
/// Ensures that some global settings are set up to interface with Flecs. /// Ensures that some global settings are set up to interface with Flecs.

@ -0,0 +1,324 @@
const std = @import("std");
const os_api = @import("./os_api.zig");
const c = @import("./c.zig");
const errors = @import("./errors.zig");
const meta = @import("./meta.zig");
const EntityError = @import("./entity.zig").EntityError;
const flecszigble = @import("./main.zig");
const flecs = @import("./builtin/flecs.zig");
const DependsOn = flecs.core.DependsOn;
const OnUpdate = flecs.pipeline.OnUpdate;
pub fn System(comptime ctx: anytype) type {
return struct {
const Context = @import("./context.zig").Context(ctx);
const World = Context.World;
const Entity = Context.Entity;
const Iter = Context.Iter;
/// Registers a system with the `World`, that is associated with a
/// specific query matching certain entities, calling a function for
/// said matched entities.
///
/// Returns the `Entity` of the newly created system.
///
/// Example:
/// ```
/// const PositionUpdate = struct {
/// // The name of the system and resulting entity.
/// // (Optional for pre-defined, named structs.)
/// pub const name = "PositionUpdate";
/// // When to invoke the system. (Defaults to `OnUpdate`)
/// pub const phase = flecs.pipeline.OnUpdate;
/// // Query expression. (Required)
/// pub const expr = "[inout] Position, [in] Velocity";
///
/// // Callback invoked for every matched archtable. (Required)
/// pub fn callback(it: Iter) void {
/// const pos_col = it.field(Position, 1);
/// const vel_col = it.field(Velocity, 2);
///
/// // We know the columns to be the same
/// // size, we can iterate them together.
/// for (pos_col, vel_col) |*pos, vel| {
/// pos.x += vel.x;
/// pos.y += vel.y;
/// }
/// }
/// };
///
/// world.system(PositionUpdate)
/// // or
/// world.system(.{
/// .name = "PositionUpdate",
/// .phase = flecs.pipeline.OnUpdate,
/// .expr = "[inout] Position, [in] Velocity",
/// .callback = PositionUpdate.callback,
/// })
/// ```
pub fn init(world: *World, config: anytype) !Entity {
// TODO: const isComponent = @typeInfo(T).Struct.fields.len > 0;
const name: [:0]const u8 = if (@TypeOf(config) == type) meta.flecsTypeName(config) else @field(config, "name");
const expr: ?[:0]const u8 = fieldOr(config, "expr", null);
// If `phase` is not specified, it defaults to `OnUpdate`.
// If a literal `null` is specified, no phase is added to the system.
const phase = fieldOr(config, "phase", OnUpdate);
const entity = try world.entity(
.{ .name = name },
if (@TypeOf(phase) == @TypeOf(null)) .{} else blk: {
const phase_entity = Context.anyToEntity(phase);
if (phase_entity == 0) return EntityError.IsNotAlive;
break :blk .{ .{ DependsOn, phase_entity }, phase_entity };
},
);
const isEach = !has(config, "callback") and has(config, "each");
const callback = @field(config, if (isEach) "each" else "callback");
const return_type = @typeInfo(@typeInfo(@TypeOf(callback)).Fn.return_type.?);
const returns_error = switch (return_type) {
.Void => false,
.ErrorUnion => |e| if (e.payload == void) true else @compileError("Function return must be 'void' or '!void'"),
else => @compileError("Function return must be 'void' or '!void'"),
};
const SystemCallback = struct {
// FIXME: Dependency loop.
// Currently needs manual changing of the generated C code.
// fn invoke(it: ?*c.ecs_iter_t) callconv(.C) void {
fn invoke(any_it: *anyopaque) callconv(.C) void {
const ecs_iter: ?*c.ecs_iter_t = @alignCast(@ptrCast(any_it));
const world_: *World = @alignCast(@ptrCast(ecs_iter.?.binding_ctx));
const it = Iter.fromRawPtr(world_, ecs_iter.?);
const Args = std.meta.ArgsTuple(@TypeOf(callback));
var args: Args = undefined;
comptime var field_index = 0;
inline for (std.meta.fields(Args), 0..) |field, i|
switch (field.type) {
Iter => args[i] = it,
*World => args[i] = it.world,
f32 => args[i] = it.deltaTime(),
Entity => if (comptime !isEach)
@compileError("Entitiy variables only supported with 'each'"),
else => {
field_index += 1;
switch (@typeInfo(field.type)) {
.Struct => if (comptime !isEach) {
args[i] = it.field(field.type, field_index)[0];
},
.Pointer => |p| if (comptime !isEach) {
switch (comptime p.size) {
.One => args[i] = &it.field(p.child, field_index)[0],
.Slice => args[i] = it.field(p.child, field_index),
else => {},
}
} else if (comptime p.size == .One and p.is_const == true and @typeInfo(p.child) == .Pointer) {
const pInner = @typeInfo(p.child).Pointer;
if (pInner.size == .One)
args[i] = &it.field(pInner.child, field_index)[0]
else
@compileError("Unsupported type '" ++ @typeName(field.type) ++ "'");
} else @compileError("Unsupported type '" ++ @typeName(field.type) ++ "'"),
else => @compileError("Unsupported type '" ++ @typeName(field.type) ++ "'"),
}
},
};
// if (comptime std.mem.eql(u8, field.name, "this"))
// it.entity()
// else
// @compileError("Entity variables other than 'this' not supported yet"),
if (comptime isEach) {
for (0..count) |i| {
const err = @call(.auto, callback, args);
if (comptime returns_error) err catch |e| handleError(it, e);
}
} else {
const err = @call(.auto, callback, args);
if (comptime returns_error) err catch |e| handleError(it, e);
}
}
};
const result = c.ecs_system_init(world.raw, &.{
.entity = entity.raw,
.binding_ctx = world,
.callback = SystemCallback.invoke,
.query = .{ .filter = .{ .expr = if (expr) |e| e else null } },
});
if (result == 0) return errors.getLastErrorOrUnknown();
if (comptime has(config, "interval"))
_ = c.ecs_set_interval(world.raw, result, @field(config, "interval"));
return Entity.fromRaw(world, result);
}
inline fn handleFields(args: anytype, it: Iter, comptime isEach: bool) void {
comptime var field_index = 0;
inline for (std.meta.fields(@TypeOf(args)), 0..) |field, i|
switch (field.type) {
Iter => if (comptime !isEach) {
args[i] = it;
},
*World => args[i] = it.world,
f32 => args[i] = it.deltaTime(),
Entity => if (comptime !isEach)
@compileError("Entitiy variables only supported with 'each'"),
else => {
field_index += 1;
switch (@typeInfo(field.type)) {
.Struct => if (comptime !isEach) {
args[i] = it.field(field.type, field_index)[0];
},
.Pointer => |p| if (comptime !isEach) {
switch (comptime p.size) {
.One => args[i] = &it.field(p.child, field_index)[0],
.Slice => args[i] = it.field(p.child, field_index),
else => {},
}
} else if (comptime p.size == .One and p.is_const == true and @typeInfo(p.child) == .Pointer) {
const pInner = @typeInfo(p.child).Pointer;
if (pInner.size == .One)
args[i] = &it.field(pInner.child, field_index)[0]
else
@compileError("Unsupported type '" ++ @typeName(field.type) ++ "'");
} else @compileError("Unsupported type '" ++ @typeName(field.type) ++ "'"),
else => @compileError("Unsupported type '" ++ @typeName(field.type) ++ "'"),
}
},
};
}
/// Called when a Zig error is returned from a system's callback function.
fn handleError(it: Iter, err: anyerror) void {
const system_name = if (it.system()) |s| s.name() orelse "<unknown>" else "<unknown>";
std.debug.print("Error in system '{s}': {}\n", .{ system_name, err });
}
};
}
fn has(container: anytype, comptime name: []const u8) bool {
return comptime switch (@typeInfo(@TypeOf(container))) {
.Type => @hasDecl(container, name),
.Struct => @hasField(@TypeOf(container), name),
else => @compileError("Expected type or struct"),
};
}
fn fieldOr(
container: anytype,
comptime name: []const u8,
comptime default: anytype,
) @TypeOf(if (comptime has(container, name)) @field(container, name) else default) {
return if (comptime has(container, name)) @field(container, name) else default;
}
const expect = @import("./test/expect.zig");
test "System with struct type" {
flecszigble.init(std.testing.allocator);
var world = try flecszigble.World(void).init();
defer world.deinit();
const Position = struct { x: i32, y: i32 };
const Velocity = struct { x: i32, y: i32 };
_ = try world.component(Position);
_ = try world.component(Velocity);
const entity = try world.entity(.{}, .{ Position, Velocity });
entity.set(Position, .{ .x = 10, .y = 20 });
entity.set(Velocity, .{ .x = 1, .y = 2 });
const PositionUpdate = struct {
pub const expr = "[inout] Position, [in] Velocity";
pub fn callback(pos_col: []Position, vel_col: []Velocity) void {
for (pos_col, vel_col) |*pos, vel| {
pos.x += vel.x;
pos.y += vel.y;
}
}
};
const system = try world.system(PositionUpdate);
try expect.equal("PositionUpdate", system.name());
// TODO: Test more things.
try expect.equal(.{ .x = 10, .y = 20 }, entity.get(Position));
_ = world.progress(0.0);
try expect.equal(.{ .x = 11, .y = 22 }, entity.get(Position));
_ = world.progress(0.0);
try expect.equal(.{ .x = 12, .y = 24 }, entity.get(Position));
}
test "System with anonymous struct" {
flecszigble.init(std.testing.allocator);
var world = try flecszigble.World(void).init();
defer world.deinit();
const Position = struct { x: i32, y: i32 };
const Velocity = struct { x: i32, y: i32 };
_ = try world.component(Position);
_ = try world.component(Velocity);
const entity = try world.entity(.{}, .{ Position, Velocity });
entity.set(Position, .{ .x = 10, .y = 20 });
entity.set(Velocity, .{ .x = 1, .y = 2 });
const positionUpdateFn = struct {
fn callback(pos_col: []Position, vel_col: []Velocity) void {
for (pos_col, vel_col) |*pos, vel| {
pos.x += vel.x;
pos.y += vel.y;
}
}
}.callback;
const system = try world.system(.{
.name = "PositionUpdate",
.expr = "[inout] Position, [in] Velocity",
.callback = positionUpdateFn,
});
try expect.equal("PositionUpdate", system.name());
// TODO: Test more things.
try expect.equal(.{ .x = 10, .y = 20 }, entity.get(Position));
_ = world.progress(0.0);
try expect.equal(.{ .x = 11, .y = 22 }, entity.get(Position));
_ = world.progress(0.0);
try expect.equal(.{ .x = 12, .y = 24 }, entity.get(Position));
}
test "System built-in parameters" {
const World = flecszigble.World(void);
const Iter = flecszigble.Iter(void);
flecszigble.init(std.testing.allocator);
var world = try World.init();
defer world.deinit();
const Test = struct {
var times_called: usize = 0;
var captured_world: ?*World = null;
pub fn callback(it: Iter, world_: *World, delta: f32) !void {
times_called += 1;
captured_world = world_;
try expect.equal(12.34, delta);
try expect.equal(0, it.count());
}
};
_ = try world.system(Test);
_ = world.progress(12.34);
try expect.equal(1, Test.times_called);
try expect.equal(world, Test.captured_world);
}

@ -42,7 +42,12 @@ test "World_progress_w_0" {
const e1 = try world.entity(.{}, .{ Position, Velocity }); const e1 = try world.entity(.{}, .{ Position, Velocity });
const move_system = try world.system("move", move, OnUpdate, "Position, Velocity"); const move_system = try world.system(.{
.name = "move",
.phase = OnUpdate,
.expr = "Position, Velocity",
.callback = move,
});
var ctx = util.Probe{}; var ctx = util.Probe{};
c.ecs_set_ctx(world.raw, &ctx, null); c.ecs_set_ctx(world.raw, &ctx, null);
@ -79,7 +84,12 @@ test "World_progress_w_t" {
const e1 = try world.entity(.{}, .{ Position, Velocity }); const e1 = try world.entity(.{}, .{ Position, Velocity });
const move_system = try world.system("move", move, OnUpdate, "Position, Velocity"); const move_system = try world.system(.{
.name = "move",
.phase = OnUpdate,
.expr = "Position, Velocity",
.callback = move,
});
var ctx = util.Probe{}; var ctx = util.Probe{};
c.ecs_set_ctx(world.raw, &ctx, null); c.ecs_set_ctx(world.raw, &ctx, null);

@ -8,7 +8,6 @@ const meta = @import("./meta.zig");
const Path = @import("./path.zig"); const Path = @import("./path.zig");
const flecs = @import("./builtin/flecs.zig"); const flecs = @import("./builtin/flecs.zig");
const DependsOn = flecs.core.DependsOn;
pub fn World(comptime ctx: anytype) type { pub fn World(comptime ctx: anytype) type {
return struct { return struct {
@ -16,6 +15,7 @@ pub fn World(comptime ctx: anytype) type {
const Context = @import("./context.zig").Context(ctx); const Context = @import("./context.zig").Context(ctx);
const Entity = Context.Entity; const Entity = Context.Entity;
const System = Context.System;
const Iter = Context.Iter; const Iter = Context.Iter;
const Pair = Context.Pair; const Pair = Context.Pair;
@ -159,6 +159,12 @@ pub fn World(comptime ctx: anytype) type {
return Entity.fromRaw(self, result); return Entity.fromRaw(self, result);
} }
/// Creates a system to run in this `World`.
/// See `System.init(...)` for more information.
pub fn system(self: *Self, config: anytype) !Entity {
return System.init(self, config);
}
/// Registers a singleton component of type `T` with the specified value. /// Registers a singleton component of type `T` with the specified value.
/// ///
/// A singleton is a component which has itself added to its entity. /// A singleton is a component which has itself added to its entity.
@ -194,59 +200,6 @@ pub fn World(comptime ctx: anytype) type {
e.set(T, value); e.set(T, value);
} }
pub fn system(
self: *Self,
name: [:0]const u8,
callback: SystemCallback,
phase: anytype,
expr: [:0]const u8,
) !Entity {
const phase_ = Context.anyToEntity(phase);
const entity_ = try if (phase_ != 0)
self.entity(.{ .name = name }, .{ .{ DependsOn, phase_ }, phase_ })
else
self.entity(.{ .name = name }, .{});
const context = try SystemCallbackContext.init(self, callback);
const result = c.ecs_system_init(self.raw, &.{
.entity = entity_.raw,
.callback = &SystemCallbackContext.invoke,
.binding_ctx = context,
.binding_ctx_free = &SystemCallbackContext.free,
.query = .{ .filter = .{ .expr = expr } },
});
if (result == 0) return errors.getLastErrorOrUnknown();
return Entity.fromRaw(self, result);
}
const SystemCallback = *const fn (Iter) void;
const SystemCallbackContext = struct {
world: *Self,
func: SystemCallback,
pub fn init(world: *Self, callback: SystemCallback) !*SystemCallbackContext {
var result = try os_api.allocator.create(SystemCallbackContext);
result.world = world;
result.func = callback;
return result;
}
fn free(context: ?*anyopaque) callconv(.C) void {
const self: *SystemCallbackContext = @alignCast(@ptrCast(context));
os_api.allocator.destroy(self);
}
// FIXME: Dependency loop.
// Currently needs manual changing of the generated C code.
// fn invoke(it: ?*c.ecs_iter_t) callconv(.C) void {
fn invoke(it2: *anyopaque) callconv(.C) void {
const it: ?*c.ecs_iter_t = @alignCast(@ptrCast(it2));
const context: *SystemCallbackContext = @alignCast(@ptrCast(it.?.binding_ctx));
const iter = Iter.fromRawPtr(context.world, it.?);
context.func(iter);
}
};
/// 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 {
@ -305,40 +258,40 @@ pub fn World(comptime ctx: anytype) type {
const expect = @import("./test/expect.zig"); const expect = @import("./test/expect.zig");
test "World REST API" { // test "World REST API" {
flecszigble.init(std.testing.allocator); // flecszigble.init(std.testing.allocator);
var world = try World(void).init(); // var world = try World(void).init();
defer world.deinit(); // defer world.deinit();
try world.enableRest(42666); // try world.enableRest(42666);
const Runner = struct { // const Runner = struct {
pub fn run(w: *World(void)) void { // pub fn run(w: *World(void)) void {
while (w.progress(0.0)) {} // while (w.progress(0.0)) {}
} // }
}; // };
const alloc = std.testing.allocator; // const alloc = std.testing.allocator;
var thread = try std.Thread.spawn(.{ .allocator = alloc }, Runner.run, .{world}); // var thread = try std.Thread.spawn(.{ .allocator = alloc }, Runner.run, .{world});
defer thread.join(); // defer thread.join();
// Unsure if compiling or running this takes a really long time, but this // // Unsure if compiling or running this takes a really long time, but this
// test using `http.Client` adds about 10s to the test run time. Oh well. // // test using `http.Client` adds about 10s to the test run time. Oh well.
const url = "http://localhost:42666/entity/flecs/core/World"; // const url = "http://localhost:42666/entity/flecs/core/World";
var client = std.http.Client{ .allocator = alloc }; // var client = std.http.Client{ .allocator = alloc };
defer client.deinit(); // defer client.deinit();
var response = std.ArrayList(u8).init(alloc); // var response = std.ArrayList(u8).init(alloc);
defer response.deinit(); // defer response.deinit();
const result = try client.fetch(.{ // const result = try client.fetch(.{
.location = .{ .url = url }, // .location = .{ .url = url },
.response_storage = .{ .dynamic = &response }, // .response_storage = .{ .dynamic = &response },
}); // });
try expect.equal(std.http.Status.ok, result.status); // try expect.equal(std.http.Status.ok, result.status);
try expect.equalStrings("{\"path\":\"flecs.core.World\", \"ids\":[]}", response.items); // try expect.equalStrings("{\"path\":\"flecs.core.World\", \"ids\":[]}", response.items);
world.quit(); // world.quit();
} // }
test "World delete and reuse component" { test "World delete and reuse component" {
flecszigble.init(std.testing.allocator); flecszigble.init(std.testing.allocator);

Loading…
Cancel
Save