diff --git a/examples/core-custom-entrypoint/App.zig b/examples/core-custom-entrypoint/App.zig index 007f9695..2f8a27e9 100644 --- a/examples/core-custom-entrypoint/App.zig +++ b/examples/core-custom-entrypoint/App.zig @@ -23,11 +23,10 @@ pub fn deinit(app: *App) void { pub fn init( app: *App, core: *mach.Core, - app_tick: mach.Call(App, .tick), - app_deinit: mach.Call(App, .deinit), + app_mod: mach.Functions(App), ) !void { - core.on_tick = app_tick.id; - core.on_exit = app_deinit.id; + core.on_tick = app_mod.id.tick; + core.on_exit = app_mod.id.deinit; // Create our shader module const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl")); diff --git a/examples/core-triangle/App.zig b/examples/core-triangle/App.zig index fdd84f8d..44b97859 100644 --- a/examples/core-triangle/App.zig +++ b/examples/core-triangle/App.zig @@ -19,14 +19,10 @@ pipeline: *gpu.RenderPipeline, pub fn init( core: *mach.Core, app: *App, - app_tick: mach.Call(App, .tick), - app_deinit: mach.Call(App, .deinit), - // app_caller: mach.Caller(App), + app_mod: mach.Functions(App), ) !void { - core.on_tick = app_tick.id; - core.on_exit = app_deinit.id; - // core.on_tick = app_caller.tick; - // core.on_exit = app_caller.exit; + core.on_tick = app_mod.id.tick; + core.on_exit = app_mod.id.deinit; // Create our shader module const shader_module = core.device.createShaderModuleWGSL("shader.wgsl", @embedFile("shader.wgsl")); diff --git a/examples/custom-renderer/App.zig b/examples/custom-renderer/App.zig index bd2c7ec3..a7885607 100644 --- a/examples/custom-renderer/App.zig +++ b/examples/custom-renderer/App.zig @@ -20,7 +20,7 @@ direction: Vec2 = vec2(0, 0), spawning: bool = false, spawn_timer: mach.time.Timer, -// Components our game module defines. +// TODO(object) pub const components = .{ // Whether an entity is a "follower" of our player entity or not. The type is void because we // don't need any information, this is just a tag we assign to an entity with no data. @@ -49,11 +49,10 @@ fn init( core: *mach.Core, renderer: *Renderer, app: *App, - app_tick: mach.Call(App, .tick), - app_deinit: mach.Call(App, .deinit), + app_mod: mach.Functions(App), ) !void { - core.on_tick = app_tick.id; - core.on_exit = app_deinit.id; + core.on_tick = app_mod.id.tick; + core.on_exit = app_mod.id.deinit; // Create our player entity. const player = try entities.new(); diff --git a/examples/custom-renderer/Renderer.zig b/examples/custom-renderer/Renderer.zig index cb80fd2b..8dc5eae1 100644 --- a/examples/custom-renderer/Renderer.zig +++ b/examples/custom-renderer/Renderer.zig @@ -15,6 +15,7 @@ uniform_buffer: *gpu.Buffer, pub const mach_module = .renderer; +// TODO(object) pub const components = .{ .position = .{ .type = Vec3 }, .rotation = .{ .type = Vec3 }, diff --git a/examples/glyphs/App.zig b/examples/glyphs/App.zig index d2342fdf..c6282637 100644 --- a/examples/glyphs/App.zig +++ b/examples/glyphs/App.zig @@ -56,11 +56,10 @@ fn init( glyphs: *Glyphs.Mod, app: *App, core: *mach.Core, - app_tick: mach.Call(App, .tick), - app_deinit: mach.Call(App, .deinit), + app_mod: mach.Functions(App), ) !void { - core.on_tick = app_tick.id; - core.on_exit = app_deinit.id; + core.on_tick = app_mod.id.tick; + core.on_exit = app_mod.id.deinit; // Create a sprite rendering pipeline const texture = glyphs.state().texture; diff --git a/examples/hardware-check/App.zig b/examples/hardware-check/App.zig index 804107d5..3321f727 100644 --- a/examples/hardware-check/App.zig +++ b/examples/hardware-check/App.zig @@ -80,12 +80,10 @@ fn init( text: *gfx.Text.Mod, sprite_pipeline: *gfx.SpritePipeline.Mod, app: *App, - app_tick: mach.Call(App, .tick), - app_deinit: mach.Call(App, .deinit), - app_audio_state_change: mach.Call(App, .audio_state_change), + app_mod: mach.Functions(App), ) !void { - core.on_tick = app_tick.id; - core.on_exit = app_deinit.id; + core.on_tick = app_mod.id.tick; + core.on_exit = app_mod.id.deinit; // Configure the audio module to run our audio_state_change system when entities audio finishes // playing diff --git a/examples/piano/App.zig b/examples/piano/App.zig index b7f8c0bd..98b7c84b 100644 --- a/examples/piano/App.zig +++ b/examples/piano/App.zig @@ -25,6 +25,7 @@ pub const mach_systems = .{ .start, .init, .deinit, .tick, .audio_state_change } // TODO: banish global allocator var gpa = std.heap.GeneralPurposeAllocator(.{}){}; +// TODO(object) pub const components = .{ .play_after = .{ .type = f32 }, }; @@ -41,12 +42,10 @@ fn init( core: *mach.Core, audio: *mach.Audio, app: *App, - app_tick: mach.Call(App, .tick), - app_deinit: mach.Call(App, .deinit), - app_audio_state_change: mach.Call(App, .audio_state_change), + app_mod: mach.Functions(App), ) !void { - core.on_tick = app_tick.id; - core.on_exit = app_deinit.id; + core.on_tick = app_mod.id.tick; + core.on_exit = app_mod.id.deinit; // Configure the audio module to send our app's .audio_state_change event when an entity's sound // finishes playing. diff --git a/examples/play-opus/App.zig b/examples/play-opus/App.zig index db027dcf..c7b69abc 100644 --- a/examples/play-opus/App.zig +++ b/examples/play-opus/App.zig @@ -19,6 +19,7 @@ pub const mach_module = .app; pub const mach_systems = .{ .start, .init, .deinit, .tick, .audio_state_change }; +// TODO(object) pub const components = .{ .is_bgm = .{ .type = void }, }; @@ -40,12 +41,10 @@ fn init( core: *mach.Core, audio: *mach.Audio, app: *App, - app_tick: mach.Call(App, .tick), - app_deinit: mach.Call(App, .deinit), - app_audio_state_change: mach.Call(App, .audio_state_change), + app_mod: mach.Functions(App), ) !void { - core.on_tick = app_tick.id; - core.on_exit = app_deinit.id; + core.on_tick = app_mod.id.tick; + core.on_exit = app_mod.id.deinit; // Configure the audio module to send our app's .audio_state_change event when an entity's sound // finishes playing. diff --git a/examples/sprite/App.zig b/examples/sprite/App.zig index 00481d0e..e41386b7 100644 --- a/examples/sprite/App.zig +++ b/examples/sprite/App.zig @@ -58,11 +58,10 @@ fn init( sprite: *gfx.Sprite.Mod, sprite_pipeline: *gfx.SpritePipeline.Mod, app: *App, - app_tick: mach.Call(App, .tick), - app_deinit: mach.Call(App, .deinit), + app_mod: mach.Functions(App), ) !void { - core.on_tick = app_tick.id; - core.on_exit = app_deinit.id; + core.on_tick = app_mod.id.tick; + core.on_exit = app_mod.id.deinit; // We can create entities, and set components on them. Note that components live in a module // namespace, e.g. the `.mach_gfx_sprite` module could have a 3D `.location` component with a different diff --git a/examples/text/App.zig b/examples/text/App.zig index 5c00d135..ea674522 100644 --- a/examples/text/App.zig +++ b/examples/text/App.zig @@ -67,11 +67,10 @@ fn init( text_pipeline: *gfx.TextPipeline.Mod, text_style: *gfx.TextStyle.Mod, app: *App, - app_tick: mach.Call(App, .tick), - app_deinit: mach.Call(App, .deinit), + app_mod: mach.Functions(App), ) !void { - core.on_tick = app_tick.id; - core.on_exit = app_deinit.id; + core.on_tick = app_mod.id.tick; + core.on_exit = app_mod.id.deinit; // TODO: a better way to initialize entities with default values // TODO(text): ability to specify other style options (custom font name, font color, italic/bold, etc.) diff --git a/src/Audio.zig b/src/Audio.zig index 728fc4fd..b053f12a 100644 --- a/src/Audio.zig +++ b/src/Audio.zig @@ -5,9 +5,9 @@ const sysaudio = mach.sysaudio; pub const Opus = @import("mach-opus"); -pub const name = .mach_audio; -pub const Mod = mach.Mod(@This()); +pub const mach_module = .mach_audio; +// TODO(object) pub const components = .{ .samples = .{ .type = []const f32 }, .channels = .{ .type = u8 }, @@ -22,7 +22,7 @@ pub const systems = .{ .audio_tick = .{ .handler = audioTick }, }; -const log = std.log.scoped(name); +const log = std.log.scoped(mach_module); // The number of milliseconds worth of audio to render ahead of time. The lower this number is, the // less latency there is in playing new audio. The higher this number is, the less chance there is diff --git a/src/Core.zig b/src/Core.zig index 5de2d4e6..22bacd2e 100644 --- a/src/Core.zig +++ b/src/Core.zig @@ -233,17 +233,17 @@ pub fn init(core: *Core) !void { try core.input.start(); } -pub fn tick(core: *Core, present_frame: mach.Call(Core, .present_frame), runner: mach.Runner) void { - runner.run(core.on_tick.?); - runner.run(present_frame.id); +pub fn tick(core: *Core, core_mod: mach.Functions(Core)) void { + core_mod.run(core.on_tick.?); + core_mod.call(.presentFrame); } -pub fn main(core: *Core, present_frame: mach.Call(Core, .presentFrame), runner: mach.Runner) !void { +pub fn main(core: *Core, core_mod: mach.Functions(Core)) !void { if (core.on_tick == null) @panic("core.on_tick callback must be set"); if (core.on_exit == null) @panic("core.on_exit callback must be set"); - runner.run(core.on_tick.?); - runner.run(present_frame.id); + core_mod.run(core.on_tick.?); + core_mod.call(.presentFrame); // 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 @@ -259,8 +259,8 @@ pub fn main(core: *Core, present_frame: mach.Call(Core, .presentFrame), runner: // The user wants mach.Core to take control of the main loop. if (supports_non_blocking) { while (core.state().state != .exited) { - runner.run(core.on_tick.?); - runner.run(present_frame.id); + core_mod.run(core.on_tick.?); + core_mod.call(.presentFrame); } // Don't return, because Platform.run wouldn't either (marked noreturn due to underlying @@ -268,7 +268,7 @@ pub fn main(core: *Core, present_frame: mach.Call(Core, .presentFrame), runner: std.process.exit(0); } else { // Platform drives the main loop. - Platform.run(platform_update_callback, .{ core, present_frame.id, runner }); + Platform.run(platform_update_callback, .{ core, core_mod }); // 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 @@ -277,9 +277,9 @@ pub fn main(core: *Core, present_frame: mach.Call(Core, .presentFrame), runner: } } -fn platform_update_callback(core: *Core, present_frame: mach.FunctionID, runner: mach.Runner) !bool { - runner.run(core.on_tick.?); - runner.run(present_frame); +fn platform_update_callback(core: *Core, core_mod: mach.Functions(Core)) !bool { + core_mod.run(core.on_tick.?); + core_mod.call(.presentFrame); return core.state != .exited; } @@ -563,7 +563,7 @@ pub fn mousePosition(core: *@This()) Position { // return core.platform.nativeWindowWin32(); // } -pub fn presentFrame(core: *Core, core_deinit: mach.Call(Core, .deinit), runner: mach.Runner) !void { +pub fn presentFrame(core: *Core, core_mod: mach.Functions(Core)) !void { // TODO(object)(window-title) // // Update windows title // var num_windows: usize = 0; @@ -622,8 +622,8 @@ pub fn presentFrame(core: *Core, core_deinit: mach.Call(Core, .deinit), runner: .running => {}, .exiting => { core.state = .deinitializing; - runner.run(core.on_exit.?); - runner.run(core_deinit.id); + core_mod.run(core.on_exit.?); + core_mod.call(.deinit); }, .deinitializing => {}, .exited => @panic("application not running"), diff --git a/src/gfx/Sprite.zig b/src/gfx/Sprite.zig index f955a1c0..44ddd2ab 100644 --- a/src/gfx/Sprite.zig +++ b/src/gfx/Sprite.zig @@ -13,9 +13,9 @@ const Vec3 = math.Vec3; const Mat3x3 = math.Mat3x3; const Mat4x4 = math.Mat4x4; -pub const name = .mach_gfx_sprite; -pub const Mod = mach.Mod(@This()); +pub const mach_module = .mach_gfx_sprite; +// TODO(object) pub const components = .{ .transform = .{ .type = Mat4x4, .description = \\ The sprite model transformation matrix. A sprite is measured in pixel units, starting from diff --git a/src/gfx/SpritePipeline.zig b/src/gfx/SpritePipeline.zig index 3e4e4f2a..907f0ffe 100644 --- a/src/gfx/SpritePipeline.zig +++ b/src/gfx/SpritePipeline.zig @@ -4,9 +4,9 @@ const mach = @import("../main.zig"); const gpu = mach.gpu; const math = mach.math; -pub const name = .mach_gfx_sprite_pipeline; -pub const Mod = mach.Mod(@This()); +pub const mach_module = .mach_gfx_sprite_pipeline; +// TODO(object) pub const components = .{ .texture = .{ .type = *gpu.Texture, .description = \\ Texture to use when rendering. The default shader can handle only one texture input. diff --git a/src/gfx/Text.zig b/src/gfx/Text.zig index 5dfd10e0..c8d59d59 100644 --- a/src/gfx/Text.zig +++ b/src/gfx/Text.zig @@ -14,9 +14,9 @@ const Mat4x4 = math.Mat4x4; var gpa = std.heap.GeneralPurposeAllocator(.{}){}; -pub const name = .mach_gfx_text; -pub const Mod = mach.Mod(@This()); +pub const mach_module = .mach_gfx_text; +// TODO(object) pub const components = .{ .transform = .{ .type = Mat4x4, .description = \\ The text model transformation matrix. Text is measured in pixel units, starting from diff --git a/src/gfx/TextPipeline.zig b/src/gfx/TextPipeline.zig index bb60f431..e9f88381 100644 --- a/src/gfx/TextPipeline.zig +++ b/src/gfx/TextPipeline.zig @@ -5,9 +5,9 @@ const gfx = mach.gfx; const gpu = mach.gpu; const math = mach.math; -pub const name = .mach_gfx_text_pipeline; -pub const Mod = mach.Mod(@This()); +pub const mach_module = .mach_gfx_text_pipeline; +// TODO(object) pub const components = .{ .is_pipeline = .{ .type = void, .description = \\ Tag to indicate an entity represents a text pipeline. diff --git a/src/gfx/TextStyle.zig b/src/gfx/TextStyle.zig index 0252e804..2a1dafaa 100644 --- a/src/gfx/TextStyle.zig +++ b/src/gfx/TextStyle.zig @@ -1,9 +1,9 @@ const mach = @import("../main.zig"); const math = mach.math; -pub const name = .mach_gfx_text_style; -pub const Mod = mach.Mod(@This()); +pub const mach_module = .mach_gfx_text_style; +// TODO(object) pub const components = .{ // // TODO: ship a default font // .font_name = .{ .type = []const u8, .description = diff --git a/src/main.zig b/src/main.zig index 3684280e..3b927bf5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -26,8 +26,7 @@ pub const Modules = @import("module.zig").Modules; pub const ModuleID = @import("module.zig").ModuleID; pub const ModuleFunctionID = @import("module.zig").ModuleFunctionID; pub const FunctionID = @import("module.zig").FunctionID; -pub const Call = @import("module.zig").Call; -pub const Runner = @import("module.zig").Runner; +pub const Functions = @import("module.zig").Functions; pub const ObjectID = u32; diff --git a/src/module.zig b/src/module.zig index 4a87c46b..f90a1faf 100644 --- a/src/module.zig +++ b/src/module.zig @@ -9,27 +9,69 @@ pub const ModuleFunctionID = u16; /// Unique identifier for a function within a module, including those only known at runtime. pub const FunctionID = struct { module_id: ModuleID, fn_id: ModuleFunctionID }; -pub fn Call(module_tag_or_type: anytype, fn_name_tag: anytype) type { +pub fn Mod(comptime M: type) type { return struct { - pub const IsMachCall = void; + pub const IsMachMod = void; - pub const module_name = module_tag_or_type; - pub const fn_name = fn_name_tag; + pub const module_name = M.mach_module; + pub const Module = M; - id: FunctionID, + id: ModFunctionIDs(M), + _ctx: *anyopaque, + _run: *const fn (ctx: *anyopaque, fn_id: FunctionID) void, + + pub fn run(r: *const @This(), fn_id: FunctionID) void { + r._run(r._ctx, fn_id); + } + + pub fn call(r: *const @This(), comptime f: ModuleFunctionName2(M)) void { + const fn_id = @field(r.id, @tagName(f)); + r.run(fn_id); + } }; } -pub const Runner = struct { - pub const IsMachRunner = void; - - ctx: *anyopaque, - _run: *const fn (ctx: *anyopaque, fn_id: FunctionID) void, - - pub fn run(r: *const @This(), fn_id: FunctionID) void { - r._run(r.ctx, fn_id); +pub fn ModFunctionIDs(comptime Module: type) type { + var fields: []const std.builtin.Type.StructField = &[0]std.builtin.Type.StructField{}; + for (Module.mach_systems) |fn_name| { + fields = fields ++ [_]std.builtin.Type.StructField{.{ + .name = @tagName(fn_name), + .type = FunctionID, + .default_value = null, + .is_comptime = false, + .alignment = @alignOf(FunctionID), + }}; } -}; + return @Type(.{ + .Struct = .{ + .layout = .auto, + .is_tuple = false, + .fields = fields, + .decls = &[_]std.builtin.Type.Declaration{}, + }, + }); +} + +/// Enum describing all declarations for a given comptime-known module. +// TODO: unify with ModuleFunctionName +fn ModuleFunctionName2(comptime M: type) type { + validate(M); + var enum_fields: []const std.builtin.Type.EnumField = &[0]std.builtin.Type.EnumField{}; + var i: u32 = 0; + inline for (M.mach_systems) |fn_tag| { + // TODO: verify decls are Fn or mach.schedule() decl + enum_fields = enum_fields ++ [_]std.builtin.Type.EnumField{.{ .name = @tagName(fn_tag), .value = i }}; + i += 1; + } + return @Type(.{ + .Enum = .{ + .tag_type = if (enum_fields.len > 0) std.math.IntFittingRange(0, enum_fields.len - 1) else u0, + .fields = enum_fields, + .decls = &[_]std.builtin.Type.Declaration{}, + .is_exhaustive = true, + }, + }); +} pub fn Modules(module_lists: anytype) type { inline for (moduleTuple(module_lists)) |module| { @@ -147,16 +189,11 @@ pub fn Modules(module_lists: anytype) type { @field(args, arg.name) = &@field(m.mods, @tagName(std.meta.Child(arg.type).mach_module)); continue :outer; } - if (@typeInfo(arg.type) == .Struct and @hasDecl(arg.type, "IsMachCall")) { - const machCall = arg.type; - @field(args, arg.name) = .{ - .id = Module(@field(machCall, "module_name")).getFunction(@field(machCall, "fn_name")), - }; - continue :outer; - } - if (@typeInfo(arg.type) == .Struct and @hasDecl(arg.type, "IsMachRunner")) { - @field(args, arg.name) = Runner{ - .ctx = m.modules, + if (@typeInfo(arg.type) == .Struct and @hasDecl(arg.type, "IsMachMod")) { + const M = arg.type.Module; + var mv: Mod(M) = .{ + .id = undefined, + ._ctx = m.modules, ._run = (struct { pub fn run(ctx: *anyopaque, fn_id: FunctionID) void { const modules2: *Modules(module_lists) = @ptrCast(@alignCast(ctx)); @@ -164,6 +201,10 @@ pub fn Modules(module_lists: anytype) type { } }).run, }; + inline for (M.mach_systems) |m_fn_name| { + @field(mv.id, @tagName(m_fn_name)) = Module(M).getFunction(m_fn_name); + } + @field(args, arg.name) = mv; continue :outer; } @compileError("mach: function " ++ debug_name ++ " has an invalid argument(" ++ arg.name ++ ") type: " ++ @typeName(arg.type));