From 15d9efcf263f8479fab010e5023fc4a4356f8576 Mon Sep 17 00:00:00 2001 From: Stephen Gutekanst Date: Sun, 24 Mar 2024 23:38:01 -0700 Subject: [PATCH] module: *World is no longer an injectable parameter Signed-off-by: Stephen Gutekanst --- examples/glyphs/Game.zig | 8 ++++---- examples/text/Game.zig | 3 +-- src/ecs/systems.zig | 38 +++++++++++++++++++------------------- src/engine.zig | 25 ++++++++++++++----------- src/module.zig | 8 ++++++-- 5 files changed, 44 insertions(+), 38 deletions(-) diff --git a/examples/glyphs/Game.zig b/examples/glyphs/Game.zig index 6a4937f4..c3656f48 100644 --- a/examples/glyphs/Game.zig +++ b/examples/glyphs/Game.zig @@ -55,7 +55,6 @@ fn init( sprite_mod: *Sprite.Mod, text_mod: *Text.Mod, game: *Mod, - world: *mach.World, ) !void { // The Mach .core is where we set window options, etc. core.setTitle("gfx.Sprite example"); @@ -65,17 +64,18 @@ fn init( // Tell sprite_mod to use the texture sprite_mod.send(.init, .{}); - world.dispatchNoError(); // TODO: no dispatch in user code + engine.dispatchNoError(); // TODO: no dispatch in user code + const texture = text_mod.state.texture; sprite_mod.send(.initPipeline, .{Sprite.PipelineOptions{ .pipeline = @intFromEnum(Pipeline.text), - .texture = text_mod.state.texture, + .texture = texture, }}); - world.dispatchNoError(); // TODO: no dispatch in user code // We can create entities, and set components on them. Note that components live in a module // namespace, e.g. the `Sprite` module could have a 3D `.location` component with a different // type than the `.physics2d` module's `.location` component if you desire. + engine.dispatchNoError(); // TODO: no dispatch in user code const r = text_mod.state.regions.get('?').?; const player = try engine.newEntity(); try sprite_mod.set(player, .transform, Mat4x4.translate(vec3(-0.02, 0, 0))); diff --git a/examples/text/Game.zig b/examples/text/Game.zig index f12b04d9..ff3717ab 100644 --- a/examples/text/Game.zig +++ b/examples/text/Game.zig @@ -68,7 +68,6 @@ fn init( engine: *mach.Engine.Mod, text_mod: *Text.Mod, game: *Mod, - world: *mach.World, ) !void { // The Mach .core is where we set window options, etc. core.setTitle("gfx.Text example"); @@ -113,7 +112,7 @@ fn init( text_mod.send(.initPipeline, .{Text.PipelineOptions{ .pipeline = @intFromEnum(Pipeline.default), }}); - world.dispatchNoError(); // TODO: no dispatch in user code + engine.dispatchNoError(); // TODO: no dispatch in user code game.state = .{ .timer = try mach.Timer.start(), diff --git a/src/ecs/systems.zig b/src/ecs/systems.zig index 73ae8f5f..90ebb879 100644 --- a/src/ecs/systems.zig +++ b/src/ecs/systems.zig @@ -19,8 +19,6 @@ pub fn World(comptime mods: anytype) type { const Modules = mach.Modules(mods); - pub const IsInjectedArgument = void; - const WorldT = @This(); pub fn Mod(comptime M: anytype) type { const module_tag = M.name; @@ -76,6 +74,18 @@ pub fn World(comptime mods: anytype) type { world.modules.sendToModule(module_tag, event_name, args); } + pub inline fn sendGlobal(m: *@This(), comptime event_name: anytype, args: anytype) void { + const mod_ptr: *Mods = @alignCast(@fieldParentPtr(Mods, @tagName(module_tag), m)); + const world = @fieldParentPtr(WorldT, "mod", mod_ptr); + world.modules.send(event_name, args); + } + + pub fn dispatchNoError(m: *@This()) void { + const mod_ptr: *Mods = @alignCast(@fieldParentPtr(Mods, @tagName(module_tag), m)); + const world = @fieldParentPtr(WorldT, "mod", mod_ptr); + world.modules.dispatch(world.injectable()) catch |err| @panic(@errorName(err)); + } + /// Returns a new entity. pub fn newEntity(m: *@This()) !EntityID { const mod_ptr: *Mods = @alignCast(@fieldParentPtr(Mods, @tagName(module_tag), m)); @@ -115,7 +125,6 @@ pub fn World(comptime mods: anytype) type { const Injectable = blk: { var types: []const type = &[0]type{}; - types = types ++ [_]type{*@This()}; for (@typeInfo(Mods).Struct.fields) |field| { const ModPtr = @TypeOf(@as(*field.type, undefined)); types = types ++ [_]type{ModPtr}; @@ -125,19 +134,14 @@ pub fn World(comptime mods: anytype) type { fn injectable(world: *@This()) Injectable { var v: Injectable = undefined; outer: inline for (@typeInfo(Injectable).Struct.fields) |field| { - if (field.type == *@This()) { - @field(v, field.name) = world; - continue :outer; - } else { - inline for (@typeInfo(Mods).Struct.fields) |injectable_field| { - if (*injectable_field.type == field.type) { - @field(v, field.name) = &@field(world.mod, injectable_field.name); + inline for (@typeInfo(Mods).Struct.fields) |injectable_field| { + if (*injectable_field.type == field.type) { + @field(v, field.name) = &@field(world.mod, injectable_field.name); - // TODO: better module initialization location - @field(v, field.name).entities = &world.entities; - @field(v, field.name).allocator = world.allocator; - continue :outer; - } + // TODO: better module initialization location + @field(v, field.name).entities = &world.entities; + @field(v, field.name).allocator = world.allocator; + continue :outer; } } @compileError("failed to initialize Injectable field (this is a bug): " ++ field.name ++ " " ++ @typeName(field.type)); @@ -149,10 +153,6 @@ pub fn World(comptime mods: anytype) type { try world.modules.dispatch(world.injectable()); } - pub fn dispatchNoError(world: *@This()) void { - world.modules.dispatch(world.injectable()) catch |err| @panic(@errorName(err)); - } - pub fn init(world: *@This(), allocator: mem.Allocator) !void { // TODO: switch Entities to stack allocation like Modules and World var entities = try Entities(ns_components).init(allocator); diff --git a/src/engine.zig b/src/engine.zig index 6cf4c14a..4bc7abb4 100644 --- a/src/engine.zig +++ b/src/engine.zig @@ -29,34 +29,31 @@ pub const Engine = struct { .{ .global = .exit, .handler = fn () void }, }; - fn init(world: *World) !void { + fn init(engine: *Mod) !void { core.allocator = allocator; try core.init(.{}); - const state = &world.mod.engine.state; + const state = &engine.state; state.device = core.device; state.queue = core.device.getQueue(); state.should_exit = false; state.encoder = state.device.createCommandEncoder(&gpu.CommandEncoder.Descriptor{ .label = "engine.state.encoder", }); - - world.modules.send(.init, .{}); + engine.sendGlobal(.init, .{}); } - fn deinit(world: *World, engine: *Mod) void { + fn deinit(engine: *Mod) void { // TODO: this triggers a device loss error, which we should handle correctly // engine.state.device.release(); engine.state.queue.release(); - world.modules.send(.deinit, .{}); + engine.sendGlobal(.deinit, .{}); core.deinit(); - world.deinit(); - _ = gpa.deinit(); } // Engine module's exit handler - fn exit(world: *World) void { - world.modules.send(.exit, .{}); - world.mod.engine.state.should_exit = true; + fn exit(engine: *Mod) void { + engine.sendGlobal(.exit, .{}); + engine.state.should_exit = true; } fn beginPass(engine: *Mod, clear_color: gpu.Color) void { @@ -110,6 +107,10 @@ pub const App = struct { pub fn deinit(app: *@This()) void { app.world.modules.sendToModule(.engine, .deinit, .{}); + // TODO: improve error handling + app.world.dispatch() catch |err| @panic(@errorName(err)); // dispatch .deinit + app.world.deinit(); + _ = gpa.deinit(); } pub fn update(app: *@This()) !bool { @@ -134,5 +135,7 @@ fn modules() Modules() { if (!@hasDecl(@import("root"), "modules")) { @compileError("expected `pub const modules = .{};` in root file"); } + // TODO: verify modules (causes loop currently) + // _ = @import("module.zig").Modules(@import("root").modules); return @import("root").modules; } diff --git a/src/module.zig b/src/module.zig index d649ae80..7ec5de9b 100644 --- a/src/module.zig +++ b/src/module.zig @@ -115,7 +115,11 @@ pub fn Modules(comptime mods: anytype) type { }, else => unreachable, }; - return UninjectedArgsTuple(std.meta.Tuple, Handler); + + // TODO: passing std.meta.Tuple here instead of TupleHACK results in a compiler + // segfault. The only difference is that TupleHACk does not produce a real tuple, + // `@Type(.{.Struct = .{ .is_tuple = false }})` instead of `.is_tuple = true`. + return UninjectedArgsTuple(TupleHACK, Handler); } } @compileError("No global event handler ." ++ @tagName(event_name) ++ " is defined in any module."); @@ -986,7 +990,7 @@ test "dispatch" { // Global events which are not handled by anyone yet can be written as `pub const fooBar = fn() void;` // within a module, which allows pre-declaring that `fooBar` is a valid global event, and enables // its arguments to be inferred still like this: - modules.send(.frame_done, .{1337}); + modules.send(.frame_done, .{ .@"0" = 1337 }); // Local events modules.sendToModule(.engine_renderer, .update, .{});