make it clear how to use module system without mach.Core (remove mach.App)
Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
7ac5bef717
commit
642cc9b7f7
20 changed files with 567 additions and 334 deletions
190
src/Core.zig
190
src/Core.zig
|
|
@ -21,13 +21,40 @@ pub const Platform = switch (build_options.core_platform) {
|
|||
.null => @import("core/Null.zig"),
|
||||
};
|
||||
|
||||
// Whether or not you can drive the main loop in a non-blocking fashion, or if the underlying
|
||||
// platform must take control and drive the main loop itself.
|
||||
pub const supports_non_blocking = switch (build_options.core_platform) {
|
||||
.win32 => true,
|
||||
.x11 => true,
|
||||
.wayland => true,
|
||||
.web => false,
|
||||
.darwin => false,
|
||||
.null => false,
|
||||
};
|
||||
|
||||
/// Set this to true if you intend to drive the main loop yourself.
|
||||
///
|
||||
/// A panic will occur if `supports_non_blocking == false` for the platform.
|
||||
pub var non_blocking = false;
|
||||
|
||||
pub const name = .mach_core;
|
||||
|
||||
pub const Mod = mach.Mod(@This());
|
||||
|
||||
pub const systems = .{
|
||||
.init = .{ .handler = init, .description =
|
||||
\\ Send this once you've configured any options you want on e.g. the core.state().main_window
|
||||
\\ Initialize mach.Core
|
||||
},
|
||||
|
||||
.start = .{ .handler = start, .description =
|
||||
\\ Indicates mach.Core should start its loop and begin scheduling your .app.tick system to run.
|
||||
\\
|
||||
\\ You should register core.state().on_tick and core.state().on_exit callbacks before scheduling
|
||||
\\ this to run.
|
||||
},
|
||||
|
||||
.update = .{ .handler = update, .description =
|
||||
\\ TODO
|
||||
},
|
||||
|
||||
.present_frame = .{ .handler = presentFrame, .description =
|
||||
|
|
@ -37,14 +64,27 @@ pub const systems = .{
|
|||
.exit = .{ .handler = exit, .description =
|
||||
\\ 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.
|
||||
\\ When the next .present_frame runs, then core.state().on_exit will be scheduled to run giving
|
||||
\\ your app a chance to deinitialize itself after the last frame has been rendered, and
|
||||
\\ core.state().on_tick will no longer be sent.
|
||||
\\
|
||||
\\ When core.state().on_exit runs, it must schedule .mach_core.deinit to run which will cause
|
||||
\\ the app to finish.
|
||||
},
|
||||
|
||||
.deinit = .{ .handler = deinit, .description =
|
||||
\\ Send this once your app is fully deinitialized and ready to exit for good.
|
||||
\\ Send this once your app is fully deinitialized and you are ready for mach.Core to exit for
|
||||
\\ good.
|
||||
},
|
||||
|
||||
.started = .{ .handler = fn () void, .description =
|
||||
\\ An interrupt signal that mach.Core sends once it has started. This is an interrupt signal to
|
||||
\\ be used by the application entrypoint.
|
||||
},
|
||||
|
||||
.frame_finished = .{ .handler = fn () void, .description =
|
||||
\\ An interrupt signal that mach.Core sends once a frame has been finished. This is an interrupt
|
||||
\\ signal to be used by the application entrypoint.
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -85,11 +125,20 @@ pub const components = .{
|
|||
},
|
||||
};
|
||||
|
||||
// Callback systems
|
||||
on_tick: ?mach.AnySystem = null,
|
||||
on_exit: ?mach.AnySystem = null,
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
main_window: mach.EntityID,
|
||||
platform: Platform,
|
||||
title: [256:0]u8 = undefined,
|
||||
should_close: bool = false,
|
||||
state: enum {
|
||||
running,
|
||||
exiting,
|
||||
deinitializing,
|
||||
exited,
|
||||
} = .running,
|
||||
linux_gamemode: ?bool = null,
|
||||
frame: Frequency,
|
||||
|
||||
|
|
@ -114,6 +163,7 @@ pub const EventIterator = struct {
|
|||
}
|
||||
};
|
||||
|
||||
// TODO: this needs to be removed.
|
||||
pub const InitOptions = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
is_app: bool = false,
|
||||
|
|
@ -130,24 +180,53 @@ pub const InitOptions = struct {
|
|||
},
|
||||
};
|
||||
|
||||
fn init(core: *Mod, entities: *mach.Entities.Mod, options: InitOptions) !void {
|
||||
fn update(core: *Mod, entities: *mach.Entities.Mod) !void {
|
||||
_ = core; // autofix
|
||||
_ = entities; // autofix
|
||||
}
|
||||
|
||||
fn init(core: *Mod, entities: *mach.Entities.Mod) !void {
|
||||
// TODO: this needs to be removed.
|
||||
const options: InitOptions = .{
|
||||
.allocator = std.heap.c_allocator,
|
||||
};
|
||||
const allocator = options.allocator;
|
||||
|
||||
// TODO: fix all leaks and use options.allocator
|
||||
try mach.sysgpu.Impl.init(std.heap.c_allocator, .{});
|
||||
try mach.sysgpu.Impl.init(allocator, .{});
|
||||
|
||||
const state = core.state();
|
||||
|
||||
state.allocator = options.allocator;
|
||||
state.main_window = try entities.new();
|
||||
try core.set(state.main_window, .fullscreen, false);
|
||||
try core.set(state.main_window, .width, 1920 / 2);
|
||||
try core.set(state.main_window, .height, 1080 / 2);
|
||||
const main_window = try entities.new();
|
||||
try core.set(main_window, .fullscreen, false);
|
||||
try core.set(main_window, .width, 1920 / 2);
|
||||
try core.set(main_window, .height, 1080 / 2);
|
||||
|
||||
// Copy window title into owned buffer.
|
||||
if (options.title.len < state.title.len) {
|
||||
@memcpy(state.title[0..options.title.len], options.title);
|
||||
state.title[options.title.len] = 0;
|
||||
var title: [256:0]u8 = undefined;
|
||||
if (options.title.len < title.len) {
|
||||
@memcpy(title[0..options.title.len], options.title);
|
||||
title[options.title.len] = 0;
|
||||
}
|
||||
|
||||
core.init(.{
|
||||
.allocator = allocator,
|
||||
.main_window = main_window,
|
||||
|
||||
// TODO: remove undefined initialization (disgusting!)
|
||||
.platform = undefined,
|
||||
|
||||
// TODO: these should not be state, they should be components.
|
||||
.title = title,
|
||||
.frame = undefined,
|
||||
.input = undefined,
|
||||
.instance = undefined,
|
||||
.adapter = undefined,
|
||||
.device = undefined,
|
||||
.queue = undefined,
|
||||
.surface = undefined,
|
||||
.swap_chain = undefined,
|
||||
.descriptor = undefined,
|
||||
});
|
||||
const state = core.state();
|
||||
try Platform.init(&state.platform, options);
|
||||
|
||||
state.instance = gpu.createInstance(null) orelse {
|
||||
|
|
@ -227,8 +306,63 @@ fn init(core: *Mod, entities: *mach.Entities.Mod, options: InitOptions) !void {
|
|||
try state.input.start();
|
||||
}
|
||||
|
||||
pub inline fn deinit(entities: *mach.Entities.Mod, core: *Mod) !void {
|
||||
pub fn start(core: *Mod) !void {
|
||||
if (core.state().on_tick == null) @panic("core.state().on_tick callback system must be registered");
|
||||
if (core.state().on_exit == null) @panic("core.state().on_exit callback system must be registered");
|
||||
|
||||
// Signal that mach.Core has started.
|
||||
core.schedule(.started);
|
||||
|
||||
// Schedule the next app tick to run.
|
||||
core.scheduleAny(core.state().on_tick.?);
|
||||
|
||||
// If the user doesn't want mach.Core to take control of the main loop, we bail out - the next
|
||||
// app tick is already scheduled to run in the future and they'll .present_frame to return
|
||||
// control to us later.
|
||||
if (non_blocking) {
|
||||
if (!supports_non_blocking) std.debug.panic(
|
||||
"mach.Core: platform {s} does not support non_blocking=true mode.",
|
||||
.{@tagName(build_options.core_platform)},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// The user wants mach.Core to take control of the main loop.
|
||||
|
||||
// TODO: we already have stack space since we are an executing system, so in theory we could
|
||||
// deduplicate this allocation and just use 'our current stack space' - but accessing it from
|
||||
// the dispatcher is tricky.
|
||||
const stack_space = try core.state().allocator.alloc(u8, 8 * 1024 * 1024);
|
||||
|
||||
if (supports_non_blocking) {
|
||||
while (mach.mods.mod.mach_core.state != .exited) {
|
||||
try mach.mods.dispatchUntil(stack_space, .mach_core, .frame_finished);
|
||||
}
|
||||
// Don't return, because Platform.run wouldn't either (marked noreturn due to underlying
|
||||
// platform APIs never returning.)
|
||||
std.process.exit(1);
|
||||
} else {
|
||||
// Platform drives the main loop.
|
||||
Platform.run(platform_update_callback, .{ &mach.mods.mod.mach_core, stack_space });
|
||||
|
||||
// Platform.run should be marked noreturn, so this shouldn't ever run. But just in case we
|
||||
// accidentally introduce a different Platform.run in the future, we put an exit here for
|
||||
// good measure.
|
||||
std.process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn platform_update_callback(core: *Mod, stack_space: []u8) !bool {
|
||||
// Execute systems until .mach_core.frame_finished is dispatched, signalling a frame was
|
||||
// finished.
|
||||
try mach.mods.dispatchUntil(stack_space, .mach_core, .frame_finished);
|
||||
|
||||
return core.state().state != .exited;
|
||||
}
|
||||
|
||||
pub fn deinit(entities: *mach.Entities.Mod, core: *Mod) !void {
|
||||
const state = core.state();
|
||||
state.state = .exited;
|
||||
|
||||
var q = try entities.query(.{
|
||||
.titles = Mod.read(.title),
|
||||
|
|
@ -806,6 +940,20 @@ fn presentFrame(core: *Mod, entities: *mach.Entities.Mod) !void {
|
|||
try core.set(state.main_window, .width, state.platform.size.width);
|
||||
try core.set(state.main_window, .height, state.platform.size.height);
|
||||
|
||||
// Signal that the frame was finished.
|
||||
core.schedule(.frame_finished);
|
||||
|
||||
switch (core.state().state) {
|
||||
.running => core.scheduleAny(core.state().on_tick.?),
|
||||
.exiting => {
|
||||
core.scheduleAny(core.state().on_exit.?);
|
||||
core.state().state = .deinitializing;
|
||||
},
|
||||
.deinitializing => {},
|
||||
.exited => @panic("application not running"),
|
||||
}
|
||||
|
||||
// Record to frame rate frequency monitor that a frame was finished.
|
||||
state.frame.tick();
|
||||
}
|
||||
|
||||
|
|
@ -834,7 +982,7 @@ pub fn printTitle(
|
|||
}
|
||||
|
||||
fn exit(core: *Mod) void {
|
||||
core.state().should_close = true;
|
||||
core.state().state = .exiting;
|
||||
}
|
||||
|
||||
pub const RequestAdapterResponse = struct {
|
||||
|
|
|
|||
49
src/main.zig
49
src/main.zig
|
|
@ -45,53 +45,8 @@ pub const Entities = @import("module/main.zig").Entities;
|
|||
|
||||
pub const is_debug = builtin.mode == .Debug;
|
||||
|
||||
pub const core = struct {
|
||||
var mods: Modules = undefined;
|
||||
var stack_space: [8 * 1024 * 1024]u8 = undefined;
|
||||
|
||||
pub fn initModule() !void {
|
||||
try mods.init(std.heap.c_allocator); // TODO: allocator
|
||||
|
||||
// TODO: this is a hack
|
||||
mods.mod.mach_core.init(undefined);
|
||||
mods.scheduleWithArgs(.mach_core, .init, .{.{ .allocator = std.heap.c_allocator }});
|
||||
mods.schedule(.app, .init);
|
||||
}
|
||||
|
||||
pub fn tick() !bool {
|
||||
if (comptime builtin.target.isDarwin()) {
|
||||
// TODO: tick() should never block, but we should have a way to block for other platforms.
|
||||
Core.Platform.run(on_each_update, .{});
|
||||
} else {
|
||||
return try on_each_update();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: support deinitialization
|
||||
// pub fn deinit() void {
|
||||
// mods.deinit(std.heap.c_allocator); // TODO: allocator
|
||||
// }
|
||||
|
||||
fn on_each_update() !bool {
|
||||
// TODO: this should not exist here
|
||||
if (mods.mod.mach_core.state().should_close) {
|
||||
// Final Dispatch to deinitalize resources
|
||||
mods.schedule(.app, .deinit);
|
||||
try mods.dispatch(&stack_space, .{});
|
||||
mods.schedule(.mach_core, .deinit);
|
||||
try mods.dispatch(&stack_space, .{});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Dispatch events until queue is empty
|
||||
try mods.dispatch(&stack_space, .{});
|
||||
// Run `update` when `init` and all other systems are executed
|
||||
mods.schedule(.app, .update);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
// The global set of all Mach modules that may be used in the program.
|
||||
pub var mods: Modules = undefined;
|
||||
|
||||
test {
|
||||
// TODO: refactor code so we can use this here:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue