Core: use an explicit .start event sent by app to begin .tick events

Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
Stephen Gutekanst 2024-04-29 18:25:57 -07:00 committed by Stephen Gutekanst
parent 8089d3356e
commit 15fd2c3a64
8 changed files with 67 additions and 25 deletions

View file

@ -58,6 +58,8 @@ fn init(game: *Mod, core: *mach.Core.Mod) !void {
.pipeline = pipeline, .pipeline = pipeline,
}); });
try updateWindowTitle(core); try updateWindowTitle(core);
core.send(.start, .{});
} }
// TODO(important): remove need for returning an error here // TODO(important): remove need for returning an error here

View file

@ -74,6 +74,8 @@ fn init(
.spawn_timer = try mach.Timer.start(), .spawn_timer = try mach.Timer.start(),
.player = player, .player = player,
}); });
core.send(.start, .{});
} }
// TODO(important): remove need for returning an error here // TODO(important): remove need for returning an error here

View file

@ -46,7 +46,7 @@ fn deinit(core: *mach.Core.Mod, sprite_pipeline: *gfx.SpritePipeline.Mod, glyphs
core.send(.deinit, .{}); core.send(.deinit, .{});
} }
fn init(sprite_pipeline: *gfx.SpritePipeline.Mod, glyphs: *Glyphs.Mod, game: *Mod) !void { fn init(core: *mach.Core.Mod, sprite_pipeline: *gfx.SpritePipeline.Mod, glyphs: *Glyphs.Mod, game: *Mod) !void {
sprite_pipeline.send(.init, .{}); sprite_pipeline.send(.init, .{});
glyphs.send(.init, .{}); glyphs.send(.init, .{});
@ -55,6 +55,8 @@ fn init(sprite_pipeline: *gfx.SpritePipeline.Mod, glyphs: *Glyphs.Mod, game: *Mo
// Run our init code after glyphs module is initialized. // Run our init code after glyphs module is initialized.
game.send(.after_init, .{}); game.send(.after_init, .{});
core.send(.start, .{});
} }
fn afterInit( fn afterInit(

View file

@ -36,7 +36,7 @@ pub const components = .{
ghost_key_mode: bool = false, ghost_key_mode: bool = false,
fn init(audio: *mach.Audio.Mod, app: *Mod) void { fn init(core: *mach.Core.Mod, audio: *mach.Audio.Mod, app: *Mod) void {
// Initialize audio module, telling it to send our module's .audio_state_change event when an // Initialize audio module, telling it to send our module's .audio_state_change event when an
// entity's sound stops playing // entity's sound stops playing
audio.send(.init, .{app.event(.audio_state_change)}); audio.send(.init, .{app.event(.audio_state_change)});
@ -49,6 +49,8 @@ fn init(audio: *mach.Audio.Mod, app: *Mod) void {
std.debug.print("[spacebar] enable ghost-key mode (demonstrate seamless back-to-back sound playback)\n", .{}); std.debug.print("[spacebar] enable ghost-key mode (demonstrate seamless back-to-back sound playback)\n", .{});
std.debug.print("[arrow up] increase volume 10%\n", .{}); std.debug.print("[arrow up] increase volume 10%\n", .{});
std.debug.print("[arrow down] decrease volume 10%\n", .{}); std.debug.print("[arrow down] decrease volume 10%\n", .{});
core.send(.start, .{});
} }
fn deinit(core: *mach.Core.Mod, audio: *mach.Audio.Mod) void { fn deinit(core: *mach.Core.Mod, audio: *mach.Audio.Mod) void {

View file

@ -89,6 +89,8 @@ fn init(
.allocator = allocator, .allocator = allocator,
.pipeline = pipeline, .pipeline = pipeline,
}); });
core.send(.start, .{});
} }
fn tick( fn tick(

View file

@ -127,6 +127,8 @@ fn init(
.allocator = allocator, .allocator = allocator,
.pipeline = pipeline, .pipeline = pipeline,
}); });
core.send(.start, .{});
} }
fn tick( fn tick(

View file

@ -11,6 +11,10 @@ pub const name = .mach_core;
pub const Mod = mach.Mod(@This()); pub const Mod = mach.Mod(@This());
pub const events = .{ pub const events = .{
.start = .{ .handler = start, .description =
\\ Send this once your app is initialized and ready for .app.tick events.
},
.update = .{ .handler = update, .description = .update = .{ .handler = update, .description =
\\ Send this when window entities have been updated and you want the new values respected. \\ Send this when window entities have been updated and you want the new values respected.
}, },
@ -19,15 +23,24 @@ pub const events = .{
\\ Send this when rendering has finished and the swapchain should be presented. \\ Send this when rendering has finished and the swapchain should be presented.
}, },
.init = .{ .handler = init }, .exit = .{ .handler = exit, .description =
.init_done = .{ .handler = fn () void }, \\ Send this when you would like to exit the application.
\\
\\ When the next .present_frame occurs, then .app.deinit will be sent giving your app a chance
\\ to deinitialize itself and .app.tick will no longer be sent. Once your app is done with
\\ deinitialization, you should send the final .mach_core.deinit event which will cause the
\\ application to finish.
},
.deinit = .{ .handler = deinit, .description =
\\ Send this once your app is fully deinitialized and ready to exit for good.
},
// TODO(important): need some way to tie event execution to a specific thread once we have a // TODO(important): need some way to tie event execution to a specific thread once we have a
// multithreaded dispatch implementation // multithreaded dispatch implementation
.init = .{ .handler = init },
.main_thread_tick = .{ .handler = mainThreadTick }, .main_thread_tick = .{ .handler = mainThreadTick },
.main_thread_tick_done = .{ .handler = fn () void }, .main_thread_tick_done = .{ .handler = fn () void },
.deinit = .{ .handler = deinit },
.exit = .{ .handler = exit },
}; };
pub const components = .{ pub const components = .{
@ -81,14 +94,25 @@ allocator: std.mem.Allocator,
device: *mach.gpu.Device, device: *mach.gpu.Device,
queue: *mach.gpu.Queue, queue: *mach.gpu.Queue,
main_window: mach.EntityID, main_window: mach.EntityID,
should_exit: bool = false, run_state: enum {
initialized,
running,
exiting,
deinitializing,
exited,
} = .initialized,
fn start(core: *Mod) !void {
core.state().run_state = .running;
}
fn init(core: *Mod) !void { fn init(core: *Mod) !void {
mach.core.allocator = gpa.allocator(); // TODO: banish this global allocator
// Initialize GPU implementation // Initialize GPU implementation
if (comptime !mach.use_sysgpu) try mach.wgpu.Impl.init(mach.core.allocator, .{}); if (comptime !mach.use_sysgpu) try mach.wgpu.Impl.init(mach.core.allocator, .{});
if (comptime mach.use_sysgpu) try mach.sysgpu.Impl.init(mach.core.allocator, .{}); if (comptime mach.use_sysgpu) try mach.sysgpu.Impl.init(mach.core.allocator, .{});
mach.core.allocator = gpa.allocator(); // TODO: banish this global allocator
try mach.core.init(.{}); try mach.core.init(.{});
// TODO(important): update this information upon framebuffer resize events // TODO(important): update this information upon framebuffer resize events
@ -105,7 +129,6 @@ fn init(core: *Mod) !void {
}); });
mach.core.mods.send(.app, .init, .{}); mach.core.mods.send(.app, .init, .{});
core.send(.init_done, .{});
} }
fn update(core: *Mod) !void { fn update(core: *Mod) !void {
@ -130,16 +153,24 @@ fn update(core: *Mod) !void {
} }
fn presentFrame(core: *Mod) !void { fn presentFrame(core: *Mod) !void {
switch (core.state().run_state) {
.running => {
mach.core.swap_chain.present(); mach.core.swap_chain.present();
// Signal that mainThreadTick is done // Signal that mainThreadTick is done
core.send(.main_thread_tick_done, .{}); core.send(.main_thread_tick_done, .{});
},
.exiting => {
// Exit opportunity is here, deinitialize now
core.state().run_state = .deinitializing;
mach.core.mods.send(.app, .deinit, .{});
},
else => return,
}
} }
fn deinit(core: *Mod) void { fn deinit(core: *Mod) void {
core.state().queue.release(); core.state().queue.release();
// TODO: this triggers a device loss error, which we should handle correctly
// core.state().device.release();
mach.core.deinit(); mach.core.deinit();
var archetypes_iter = core.entities.query(.{ .all = &.{ var archetypes_iter = core.entities.query(.{ .all = &.{
@ -152,14 +183,18 @@ fn deinit(core: *Mod) void {
} }
_ = gpa.deinit(); _ = gpa.deinit();
core.state().should_exit = true; core.state().run_state = .exited;
// Signal that mainThreadTick is done
core.send(.main_thread_tick_done, .{});
} }
fn mainThreadTick() !void { fn mainThreadTick(core: *Mod) !void {
if (core.state().run_state != .running) return;
_ = try mach.core.update(null); _ = try mach.core.update(null);
mach.core.mods.send(.app, .tick, .{}); mach.core.mods.send(.app, .tick, .{});
} }
fn exit() void { fn exit(core: *Mod) void {
mach.core.mods.send(.app, .deinit, .{}); core.state().run_state = .exiting;
} }

View file

@ -15,13 +15,8 @@ var stack_space: [8 * 1024 * 1024]u8 = undefined;
pub fn initModule() !void { pub fn initModule() !void {
// Initialize the global set of Mach modules used in the program. // Initialize the global set of Mach modules used in the program.
try mods.init(std.heap.c_allocator); try mods.init(std.heap.c_allocator);
mods.send(.mach_core, .init, .{});
// Dispatch events until this .mach_core.init_done is sent mods.send(.mach_core, .init, .{});
try mods.dispatch(&stack_space, .{ .until = .{
.module_name = mods.moduleNameToID(.mach_core),
.local_event = mods.localEventToID(.mach_core, .init_done),
} });
} }
/// Tick runs a single step of the main loop on the main OS thread. /// Tick runs a single step of the main loop on the main OS thread.
@ -36,7 +31,7 @@ pub fn tick() !bool {
.local_event = mods.localEventToID(.mach_core, .main_thread_tick_done), .local_event = mods.localEventToID(.mach_core, .main_thread_tick_done),
} }); } });
return !mods.mod.mach_core.state().should_exit; return mods.mod.mach_core.state().run_state != .exited;
} }
/// Returns the error set that the function F returns. /// Returns the error set that the function F returns.