core: consider removing `App` in favor of global vars
Today mach-core requires you use an App type, which can e.g. have fields like title_timer and pipeline:
const std = @import("std");
const core = @import("core");
const gpu = core.gpu;
pub const App = @This();
title_timer: core.Timer,
pipeline: *gpu.RenderPipeline,
pub fn init(app: *App) !void {
try core.init(.{});
app.* = .{ .title_timer = try core.Timer.start(), .pipeline = pipeline };
}
pub fn deinit(app: *App) void {
defer core.deinit();
app.pipeline.release();
}
pub fn update(app: *App) !bool {
// app.title_timer
// app.pipeline
return false;
}
This used to be required because Core itself was stored in your App - but since we moved Core to a global approach instead.. that is no longer required.
This introduces the question: why have App at all? The above code could be rewritten using global variables instead, removing the need for an app parameter to everything:
const std = @import("std");
const core = @import("core");
const gpu = core.gpu;
var title_timer: core.Timer = undefined;
var pipeline: *gpu.RenderPipeline = undefined;
pub fn init() !void {
try core.init(.{});
title_timer = try core.Timer.start();
pipeline = pipeline;
}
pub fn deinit() void {
defer core.deinit();
pipeline.release();
}
pub fn update() !bool {
// title_timer
// pipeline
return false;
}
I think the App + fields approach might be better since when you're initializing it you have to explicitly define all the fields, you can't accidentally leave something as undefined unless you make that the default value (which you shouldn't)
@xdBronch that's fair and worth considering.. however one could do that on their own just the same? something like:
const MyApp = struct {
foo: bool,
bar: int,
};
var my_app: MyApp = undefined;
pub fn init() !void {
my_app.* = .{
.foo = true,
.bar = 0,
};
}
they could but i doubt that many people would, making users do something the safe and "correct" thing instead of the easy thing is kinda the zig way
@xdBronch that's a fair point which I think I agree with. Do you think we should enforce it even more than we do today, though? e.g. we could do this, which would be even safer - but it's uglier:
pub fn init(initApp: fn(app: App) *App) !void {
try core.init(.{});
// If this function is not called, then `init` crashes upon return
const app = initApp(.{
// parameters must be specified here
});
// with the above, you can't forget to write this:
//
// app.* = .{ .title_timer = try core.Timer.start(), .pipeline = pipeline };
}
that feels close to good. app.* = .{...}; is a bit clunky and those simple init tuple function parameters are generally nice to use but passing functions seems odd to me. it might be difficult to find a solution thats safer than status quo without creating an unfriendly api
Yeah, I agree. Hard to balance clean-looking API with safety here.
Let's ponder on it some more and come back to it later.