core: add mach.Core module API

Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
Stephen Gutekanst 2024-04-08 22:54:13 -07:00 committed by Stephen Gutekanst
parent 69b749879d
commit 013546b189
8 changed files with 309 additions and 41 deletions

67
src/Core.zig Normal file
View file

@ -0,0 +1,67 @@
const std = @import("std");
const mach = @import("main.zig");
pub const name = .mach_core;
pub const Mod = mach.Mod(@This());
pub const global_events = .{
.init = .{ .handler = fn () void },
.deinit = .{ .handler = fn () void },
.tick = .{ .handler = fn () void },
};
pub const local_events = .{
.init = .{ .handler = init },
// TODO(important): need some way to tie event execution to a specific thread once we have a
// multithreaded dispatch implementation
.main_thread_tick = .{ .handler = mainThreadTick },
.main_thread_tick_done = .{ .handler = fn () void },
.deinit = .{ .handler = deinit },
.exit = .{ .handler = exit },
};
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
device: *mach.gpu.Device,
queue: *mach.gpu.Queue,
should_exit: bool = false,
fn init(core: *Mod) !void {
// Initialize GPU implementation
if (comptime mach.core.options.use_wgpu) try mach.core.wgpu.Impl.init(mach.core.allocator, .{});
if (comptime mach.core.options.use_sysgpu) try mach.core.sysgpu.Impl.init(mach.core.allocator, .{});
mach.core.allocator = gpa.allocator(); // TODO: banish this global allocator
try mach.core.init(.{});
core.init(.{
.device = mach.core.device,
.queue = mach.core.device.getQueue(),
});
core.sendGlobal(.init, .{});
}
fn deinit(core: *Mod) void {
core.state().queue.release();
// TODO: this triggers a device loss error, which we should handle correctly
// core.state().device.release();
mach.core.deinit();
_ = gpa.deinit();
}
fn mainThreadTick(core: *Mod) !void {
_ = try mach.core.update(null);
// Send .tick to anyone interested
core.sendGlobal(.tick, .{});
// Signal that mainThreadTick is done
core.send(.main_thread_tick_done, .{});
}
fn exit(core: *Mod) void {
core.state().should_exit = true;
}

View file

@ -7,6 +7,30 @@ pub const Timer = @import("Timer.zig");
const Frequency = @import("Frequency.zig");
const platform = @import("platform.zig");
const mach = @import("../main.zig");
pub var mods: mach.Modules = undefined;
pub fn initModule() !void {
// Initialize the global set of Mach modules used in the program.
try mods.init(std.heap.c_allocator);
mods.mod.mach_core.send(.init, .{});
}
/// Tick runs a single step of the main loop on the main OS thread.
///
/// Returns true if tick() should be called again, false if the application should exit.
pub fn tick() !bool {
mods.mod.mach_core.send(.main_thread_tick, .{});
// Dispatch events until this .mach_core.main_thread_tick_done is sent
try mods.dispatch(.{ .until = .{
.module_name = mods.moduleNameToID(.mach_core),
.local_event = mods.localEventToID(.mach_core, .main_thread_tick_done),
} });
return !mods.mod.mach_core.state().should_exit;
}
/// Returns the error set that the function F returns.
fn ErrorSet(comptime F: type) type {
return @typeInfo(@typeInfo(F).Fn.return_type.?).ErrorUnion.error_set;

View file

@ -581,13 +581,16 @@ pub fn appUpdateThreadTick(self: *Core, app: anytype) bool {
});
}
if (app.update() catch unreachable) {
self.done.set();
const use_app = @typeInfo(@TypeOf(app)) == .Pointer;
if (use_app) {
if (app.update() catch unreachable) {
self.done.set();
// Wake the main thread from any event handling, so there is not e.g. a one second delay
// in exiting the application.
glfw.postEmptyEvent();
return false;
// Wake the main thread from any event handling, so there is not e.g. a one second delay
// in exiting the application.
glfw.postEmptyEvent();
return false;
}
}
self.gpu_device.tick();
self.gpu_device.machWaitForCommandsToBeScheduled();
@ -605,10 +608,13 @@ pub fn appUpdateThread(self: *Core, app: anytype) void {
// Called on the main thread
pub fn update(self: *Core, app: anytype) !bool {
if (self.done.isSet()) return true;
if (!self.app_update_thread_started) {
self.app_update_thread_started = true;
const thread = try std.Thread.spawn(.{}, appUpdateThread, .{ self, app });
thread.detach();
const use_app = @typeInfo(@TypeOf(app)) == .Pointer;
if (use_app) {
if (!self.app_update_thread_started) {
self.app_update_thread_started = true;
const thread = try std.Thread.spawn(.{}, appUpdateThread, .{ self, app });
thread.detach();
}
}
if (self.state_update.isSet()) {
@ -748,14 +754,18 @@ pub fn update(self: *Core, app: anytype) !bool {
}
}
const frequency_delay = @as(f32, @floatFromInt(self.input.delay_ns)) / @as(f32, @floatFromInt(std.time.ns_per_s));
glfw.waitEventsTimeout(frequency_delay);
if (use_app) {
const frequency_delay = @as(f32, @floatFromInt(self.input.delay_ns)) / @as(f32, @floatFromInt(std.time.ns_per_s));
glfw.waitEventsTimeout(frequency_delay);
if (@hasDecl(std.meta.Child(@TypeOf(app)), "updateMainThread")) {
if (app.updateMainThread() catch unreachable) {
self.done.set();
return true;
if (@hasDecl(std.meta.Child(@TypeOf(app)), "updateMainThread")) {
if (app.updateMainThread() catch unreachable) {
self.done.set();
return true;
}
}
} else {
glfw.pollEvents();
}
glfw.getErrorCode() catch |err| switch (err) {
@ -764,6 +774,8 @@ pub fn update(self: *Core, app: anytype) !bool {
else => unreachable,
};
self.input.tick();
if (!use_app) return !self.appUpdateThreadTick(app);
return false;
}

View file

@ -6,6 +6,7 @@ pub const core = if (build_options.want_core) @import("core/main.zig") else stru
pub const Timer = if (build_options.want_core) core.Timer else struct {};
pub const gpu = if (build_options.want_core) core.gpu else struct {};
pub const sysjs = if (build_options.want_core) @import("mach-sysjs") else struct {};
pub const Core = if (build_options.want_core) @import("Core.zig") else struct {};
// Mach standard library
// gamemode requires libc on linux