mach icon indicating copy to clipboard operation
mach copied to clipboard

core: consider removing `App` in favor of global vars

Open emidoots opened this issue 2 years ago • 6 comments

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;
}

emidoots avatar Sep 24 '23 04:09 emidoots

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 avatar Sep 24 '23 05:09 xdBronch

@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,
    };
}

emidoots avatar Sep 24 '23 15:09 emidoots

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 avatar Sep 24 '23 18:09 xdBronch

@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 };
}

emidoots avatar Sep 24 '23 22:09 emidoots

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

xdBronch avatar Sep 24 '23 23:09 xdBronch

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.

emidoots avatar Sep 25 '23 01:09 emidoots