module: remove dispatchNoError, better loop implementation
Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
5737c62171
commit
af3c1e9155
6 changed files with 95 additions and 42 deletions
|
|
@ -45,13 +45,16 @@ pub const global_events = .{
|
||||||
.tick = .{ .handler = tick },
|
.tick = .{ .handler = tick },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const local_events = .{
|
||||||
|
.after_sprite_init = .{ .handler = afterSpriteInit },
|
||||||
|
};
|
||||||
|
|
||||||
pub const Pipeline = enum(u32) {
|
pub const Pipeline = enum(u32) {
|
||||||
default,
|
default,
|
||||||
text,
|
text,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn init(
|
fn init(
|
||||||
engine: *mach.Engine.Mod,
|
|
||||||
sprite_mod: *Sprite.Mod,
|
sprite_mod: *Sprite.Mod,
|
||||||
text_mod: *Text.Mod,
|
text_mod: *Text.Mod,
|
||||||
game: *Mod,
|
game: *Mod,
|
||||||
|
|
@ -66,11 +69,21 @@ fn init(
|
||||||
.texture = texture,
|
.texture = texture,
|
||||||
}});
|
}});
|
||||||
|
|
||||||
|
// Run the rest of our init code after sprite_mod's .init_pipeline
|
||||||
|
// TODO(important): relying on this event ordering is not good
|
||||||
|
game.send(.after_sprite_init, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn afterSpriteInit(
|
||||||
|
engine: *mach.Engine.Mod,
|
||||||
|
sprite_mod: *Sprite.Mod,
|
||||||
|
text_mod: *Text.Mod,
|
||||||
|
game: *Mod,
|
||||||
|
) !void {
|
||||||
// We can create entities, and set components on them. Note that components live in a module
|
// 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
|
// 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.
|
// 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 r = text_mod.state().regions.get('?').?;
|
||||||
const player = try engine.newEntity();
|
const player = try engine.newEntity();
|
||||||
try sprite_mod.set(player, .transform, Mat4x4.translate(vec3(-0.02, 0, 0)));
|
try sprite_mod.set(player, .transform, Mat4x4.translate(vec3(-0.02, 0, 0)));
|
||||||
|
|
@ -201,7 +214,7 @@ fn tick(
|
||||||
engine.send(.begin_pass, .{gpu.Color{ .r = 1.0, .g = 1.0, .b = 1.0, .a = 1.0 }});
|
engine.send(.begin_pass, .{gpu.Color{ .r = 1.0, .g = 1.0, .b = 1.0, .a = 1.0 }});
|
||||||
sprite_mod.send(.render, .{@intFromEnum(Pipeline.text)});
|
sprite_mod.send(.render, .{@intFromEnum(Pipeline.text)});
|
||||||
engine.send(.end_pass, .{});
|
engine.send(.end_pass, .{});
|
||||||
engine.send(.present, .{}); // Present the frame
|
engine.send(.frame_done, .{}); // Present the frame
|
||||||
|
|
||||||
// Every second, update the window title with the FPS
|
// Every second, update the window title with the FPS
|
||||||
if (game.state().fps_timer.read() >= 1.0) {
|
if (game.state().fps_timer.read() >= 1.0) {
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,7 @@ fn tick(
|
||||||
engine.send(.begin_pass, .{gpu.Color{ .r = 1.0, .g = 1.0, .b = 1.0, .a = 1.0 }});
|
engine.send(.begin_pass, .{gpu.Color{ .r = 1.0, .g = 1.0, .b = 1.0, .a = 1.0 }});
|
||||||
sprite_mod.send(.render, .{@intFromEnum(Pipeline.default)});
|
sprite_mod.send(.render, .{@intFromEnum(Pipeline.default)});
|
||||||
engine.send(.end_pass, .{});
|
engine.send(.end_pass, .{});
|
||||||
engine.send(.present, .{}); // Present the frame
|
engine.send(.frame_done, .{}); // Present the frame
|
||||||
|
|
||||||
// Every second, update the window title with the FPS
|
// Every second, update the window title with the FPS
|
||||||
if (game.state().fps_timer.read() >= 1.0) {
|
if (game.state().fps_timer.read() >= 1.0) {
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,6 @@ fn init(
|
||||||
text_mod.send(.init_pipeline, .{Text.PipelineOptions{
|
text_mod.send(.init_pipeline, .{Text.PipelineOptions{
|
||||||
.pipeline = @intFromEnum(Pipeline.default),
|
.pipeline = @intFromEnum(Pipeline.default),
|
||||||
}});
|
}});
|
||||||
engine.dispatchNoError(); // TODO: no dispatch in user code
|
|
||||||
|
|
||||||
game.init(.{
|
game.init(.{
|
||||||
.timer = try mach.Timer.start(),
|
.timer = try mach.Timer.start(),
|
||||||
|
|
@ -240,7 +239,7 @@ fn tick(
|
||||||
engine.send(.begin_pass, .{gpu.Color{ .r = 1.0, .g = 1.0, .b = 1.0, .a = 1.0 }});
|
engine.send(.begin_pass, .{gpu.Color{ .r = 1.0, .g = 1.0, .b = 1.0, .a = 1.0 }});
|
||||||
text_mod.send(.render, .{@intFromEnum(Pipeline.default)});
|
text_mod.send(.render, .{@intFromEnum(Pipeline.default)});
|
||||||
engine.send(.end_pass, .{});
|
engine.send(.end_pass, .{});
|
||||||
engine.send(.present, .{}); // Present the frame
|
engine.send(.frame_done, .{}); // Present the frame
|
||||||
|
|
||||||
// Every second, update the window title with the FPS
|
// Every second, update the window title with the FPS
|
||||||
if (game.state().fps_timer.read() >= 1.0) {
|
if (game.state().fps_timer.read() >= 1.0) {
|
||||||
|
|
|
||||||
|
|
@ -122,5 +122,5 @@ test "example" {
|
||||||
//-------------------------------------------------------------------------
|
//-------------------------------------------------------------------------
|
||||||
// Send events to modules
|
// Send events to modules
|
||||||
world.mod.renderer.sendGlobal(.tick, .{});
|
world.mod.renderer.sendGlobal(.tick, .{});
|
||||||
try world.dispatch();
|
try world.dispatch(.{});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,8 @@ pub const Engine = struct {
|
||||||
.exit = .{ .handler = exit },
|
.exit = .{ .handler = exit },
|
||||||
.begin_pass = .{ .handler = beginPass },
|
.begin_pass = .{ .handler = beginPass },
|
||||||
.end_pass = .{ .handler = endPass },
|
.end_pass = .{ .handler = endPass },
|
||||||
.present = .{ .handler = present },
|
.frame_done = .{ .handler = frameDone },
|
||||||
|
.tick_done = .{ .handler = fn () void },
|
||||||
};
|
};
|
||||||
|
|
||||||
fn init(engine: *Mod) !void {
|
fn init(engine: *Mod) !void {
|
||||||
|
|
@ -103,8 +104,9 @@ pub const Engine = struct {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn present() void {
|
fn frameDone(engine: *Mod) void {
|
||||||
core.swap_chain.present();
|
core.swap_chain.present();
|
||||||
|
engine.send(.tick_done, .{});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -115,22 +117,26 @@ pub const App = struct {
|
||||||
app.* = .{ .modules = undefined };
|
app.* = .{ .modules = undefined };
|
||||||
try app.modules.init(allocator);
|
try app.modules.init(allocator);
|
||||||
app.modules.mod.engine.send(.init, .{});
|
app.modules.mod.engine.send(.init, .{});
|
||||||
try app.modules.dispatch();
|
try app.modules.dispatch(.{});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(app: *@This()) !void {
|
pub fn deinit(app: *@This()) void {
|
||||||
app.modules.mod.engine.send(.deinit, .{});
|
app.modules.mod.engine.send(.deinit, .{});
|
||||||
// TODO: dispatch until no remaining events
|
// TODO: could it be worth enforcing that deinit dispatch cannot return errors at event handler level?
|
||||||
try app.modules.dispatch(); // dispatch .deinit
|
app.modules.dispatch(.{}) catch |err| std.debug.panic("mach: error during dispatching final .deinit event: {s}", .{@errorName(err)});
|
||||||
app.modules.deinit(gpa.allocator());
|
app.modules.deinit(gpa.allocator());
|
||||||
_ = gpa.deinit();
|
_ = gpa.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(app: *@This()) !bool {
|
pub fn update(app: *@This()) !bool {
|
||||||
// TODO: better dispatch implementation
|
// Send .tick to anyone interested
|
||||||
app.modules.mod.engine.sendGlobal(.tick, .{});
|
app.modules.mod.engine.sendGlobal(.tick, .{});
|
||||||
try app.modules.dispatch(); // dispatch .tick
|
|
||||||
try app.modules.dispatch(); // dispatch any events produced by .tick
|
// Wait until the .engine module sends a .tick_done event
|
||||||
|
try app.modules.dispatch(.{ .until = .{
|
||||||
|
.module_name = app.modules.moduleNameToID(.engine),
|
||||||
|
.local_event = app.modules.localEventToID(.engine, .tick_done),
|
||||||
|
} });
|
||||||
|
|
||||||
return app.modules.mod.engine.state().should_exit;
|
return app.modules.mod.engine.state().should_exit;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -255,8 +255,38 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: docs
|
||||||
|
pub fn moduleNameToID(m: *@This(), name: ModuleName(modules)) ModuleID {
|
||||||
|
_ = m;
|
||||||
|
return @intFromEnum(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: docs
|
||||||
|
pub fn localEventToID(
|
||||||
|
m: *@This(),
|
||||||
|
comptime module_name: ModuleName(modules),
|
||||||
|
// TODO(important): cleanup comptime
|
||||||
|
local_event: LocalEventEnumM(@TypeOf(@field(m.mod, @tagName(module_name)).__state)),
|
||||||
|
) EventID {
|
||||||
|
return @intFromEnum(local_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const DispatchOptions = struct {
|
||||||
|
/// If specified, instructs that dispatching should occur until the specified local
|
||||||
|
/// event has been dispatched.
|
||||||
|
///
|
||||||
|
/// If null, dispatching occurs until the event queue is completely empty.
|
||||||
|
until: ?struct {
|
||||||
|
module_name: ModuleID,
|
||||||
|
local_event: EventID,
|
||||||
|
} = null,
|
||||||
|
};
|
||||||
|
|
||||||
/// Dispatches pending events, invoking their event handlers.
|
/// Dispatches pending events, invoking their event handlers.
|
||||||
pub fn dispatch(m: *@This()) !void {
|
pub fn dispatch(
|
||||||
|
m: *@This(),
|
||||||
|
options: DispatchOptions,
|
||||||
|
) !void {
|
||||||
const Injectable = comptime blk: {
|
const Injectable = comptime blk: {
|
||||||
var types: []const type = &[0]type{};
|
var types: []const type = &[0]type{};
|
||||||
for (@typeInfo(ModsByName(modules)).Struct.fields) |field| {
|
for (@typeInfo(ModsByName(modules)).Struct.fields) |field| {
|
||||||
|
|
@ -275,32 +305,46 @@ pub fn Modules(comptime modules: anytype) type {
|
||||||
}
|
}
|
||||||
@compileError("failed to initialize Injectable field (this is a bug): " ++ field.name ++ " " ++ @typeName(field.type));
|
@compileError("failed to initialize Injectable field (this is a bug): " ++ field.name ++ " " ++ @typeName(field.type));
|
||||||
}
|
}
|
||||||
return m.dispatchInternal(injectable);
|
return m.dispatchInternal(options, injectable);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dispatchInternal(m: *@This(), injectable: anytype) !void {
|
pub fn dispatchInternal(
|
||||||
|
m: *@This(),
|
||||||
|
options: DispatchOptions,
|
||||||
|
injectable: anytype,
|
||||||
|
) !void {
|
||||||
// TODO: optimize to reduce send contention
|
// TODO: optimize to reduce send contention
|
||||||
// TODO: parallel / multi-threaded dispatch
|
// TODO: parallel / multi-threaded dispatch
|
||||||
// TODO: PGO
|
// TODO: PGO
|
||||||
|
|
||||||
// TODO(important): this is wrong
|
var buf: [8 * 1024 * 1024]u8 = undefined;
|
||||||
defer {
|
|
||||||
m.events_mu.lock();
|
|
||||||
m.args_queue.clearRetainingCapacity();
|
|
||||||
m.events_mu.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
// Dequeue the next event
|
||||||
m.events_mu.lock();
|
m.events_mu.lock();
|
||||||
const ev = m.events.readItem() orelse {
|
var ev = m.events.readItem() orelse {
|
||||||
m.events_mu.unlock();
|
m.events_mu.unlock();
|
||||||
break;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Pop the arguments off the stack, so we can release args_slice space.
|
||||||
|
// Otherwise when we release m.events_mu someone may add more events' arguments
|
||||||
|
// to the buffer which would make it tricky to find a good point-in-time to release
|
||||||
|
// argument buffer space.
|
||||||
|
@memcpy(buf[0..ev.args_slice.len], ev.args_slice);
|
||||||
|
ev.args_slice = buf[0..ev.args_slice.len];
|
||||||
|
m.args_queue.shrinkRetainingCapacity(m.args_queue.items.len - ev.args_slice.len);
|
||||||
m.events_mu.unlock();
|
m.events_mu.unlock();
|
||||||
|
|
||||||
if (ev.module_name) |module_name| {
|
if (ev.module_name) |module_name| {
|
||||||
|
// Dispatch the local event
|
||||||
try @This().callLocal(@enumFromInt(module_name), @enumFromInt(ev.event_name), ev.args_slice, injectable);
|
try @This().callLocal(@enumFromInt(module_name), @enumFromInt(ev.event_name), ev.args_slice, injectable);
|
||||||
|
|
||||||
|
// If we only wanted to dispatch until this event, then return.
|
||||||
|
if (options.until) |until| {
|
||||||
|
if (until.module_name == module_name and until.local_event == ev.event_name) return;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Dispatch the global event
|
||||||
try @This().callGlobal(@enumFromInt(ev.event_name), ev.args_slice, injectable);
|
try @This().callGlobal(@enumFromInt(ev.event_name), ev.args_slice, injectable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -509,15 +553,6 @@ pub fn ModSet(comptime modules: anytype) type {
|
||||||
const mods = @fieldParentPtr(ModulesT, "mod", mod_ptr);
|
const mods = @fieldParentPtr(ModulesT, "mod", mod_ptr);
|
||||||
mods.sendGlobal(module_tag, event_name, args);
|
mods.sendGlobal(module_tag, event_name, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: important! eliminate this
|
|
||||||
pub fn dispatchNoError(m: *@This()) void {
|
|
||||||
const ModulesT = Modules(modules);
|
|
||||||
const MByName = ModsByName(modules);
|
|
||||||
const mod_ptr: *MByName = @alignCast(@fieldParentPtr(MByName, @tagName(module_tag), m));
|
|
||||||
const mods = @fieldParentPtr(ModulesT, "mod", mod_ptr);
|
|
||||||
mods.dispatch() catch |err| @panic(@errorName(err));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -1425,11 +1460,11 @@ test "dispatch" {
|
||||||
// injected arguments.
|
// injected arguments.
|
||||||
modules.sendGlobal(.engine_renderer, .tick, .{});
|
modules.sendGlobal(.engine_renderer, .tick, .{});
|
||||||
try testing.expect(usize, 0).eql(global.ticks);
|
try testing.expect(usize, 0).eql(global.ticks);
|
||||||
try modules.dispatchInternal(.{&foo});
|
try modules.dispatchInternal(.{}, .{&foo});
|
||||||
try testing.expect(usize, 2).eql(global.ticks);
|
try testing.expect(usize, 2).eql(global.ticks);
|
||||||
// TODO: make sendDynamic take an args type to avoid footguns with comptime values, etc.
|
// TODO: make sendDynamic take an args type to avoid footguns with comptime values, etc.
|
||||||
modules.sendGlobalDynamic(@intFromEnum(@as(GE, .tick)), .{});
|
modules.sendGlobalDynamic(@intFromEnum(@as(GE, .tick)), .{});
|
||||||
try modules.dispatchInternal(.{&foo});
|
try modules.dispatchInternal(.{}, .{&foo});
|
||||||
try testing.expect(usize, 4).eql(global.ticks);
|
try testing.expect(usize, 4).eql(global.ticks);
|
||||||
|
|
||||||
// Global events which are not handled by anyone yet can be written as `pub const fooBar = fn() void;`
|
// Global events which are not handled by anyone yet can be written as `pub const fooBar = fn() void;`
|
||||||
|
|
@ -1439,7 +1474,7 @@ test "dispatch" {
|
||||||
|
|
||||||
// Local events
|
// Local events
|
||||||
modules.send(.engine_renderer, .update, .{});
|
modules.send(.engine_renderer, .update, .{});
|
||||||
try modules.dispatchInternal(.{&foo});
|
try modules.dispatchInternal(.{}, .{&foo});
|
||||||
try testing.expect(usize, 1).eql(global.renderer_updates);
|
try testing.expect(usize, 1).eql(global.renderer_updates);
|
||||||
modules.send(.engine_physics, .update, .{});
|
modules.send(.engine_physics, .update, .{});
|
||||||
modules.sendDynamic(
|
modules.sendDynamic(
|
||||||
|
|
@ -1447,14 +1482,14 @@ test "dispatch" {
|
||||||
@intFromEnum(@as(LE, .calc)),
|
@intFromEnum(@as(LE, .calc)),
|
||||||
.{},
|
.{},
|
||||||
);
|
);
|
||||||
try modules.dispatchInternal(.{&foo});
|
try modules.dispatchInternal(.{}, .{&foo});
|
||||||
try testing.expect(usize, 1).eql(global.physics_updates);
|
try testing.expect(usize, 1).eql(global.physics_updates);
|
||||||
try testing.expect(usize, 1).eql(global.physics_calc);
|
try testing.expect(usize, 1).eql(global.physics_calc);
|
||||||
|
|
||||||
// Local events
|
// Local events
|
||||||
modules.send(.engine_renderer, .basic_args, .{ @as(u32, 1), @as(u32, 2) }); // TODO: match arguments against fn ArgsTuple, for correctness and type inference
|
modules.send(.engine_renderer, .basic_args, .{ @as(u32, 1), @as(u32, 2) }); // TODO: match arguments against fn ArgsTuple, for correctness and type inference
|
||||||
modules.send(.engine_renderer, .injected_args, .{ @as(u32, 1), @as(u32, 2) });
|
modules.send(.engine_renderer, .injected_args, .{ @as(u32, 1), @as(u32, 2) });
|
||||||
try modules.dispatchInternal(.{&foo});
|
try modules.dispatchInternal(.{}, .{&foo});
|
||||||
try testing.expect(usize, 3).eql(global.basic_args_sum);
|
try testing.expect(usize, 3).eql(global.basic_args_sum);
|
||||||
try testing.expect(usize, 3).eql(foo.injected_args_sum);
|
try testing.expect(usize, 3).eql(foo.injected_args_sum);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue