diff --git a/src/App.zig b/src/App.zig index 855902f..ae5db5b 100644 --- a/src/App.zig +++ b/src/App.zig @@ -5,24 +5,24 @@ const Bus = @import("nu/Bus.zig"); const Render = @import("nu/Render.zig"); const ImGui = @import("nu/ImGui.zig"); -pub fn init(alloc: std.mem.Allocator) !void { - _ = alloc; +pub fn module() nu.Module { + return nu.Module{ + .name = "App", + // .dependencies = &.{ Render.module(), ImGui.module() }, + .setup = setup, + .teardown = teardown, + .update = update, + }; } -pub fn connect() !void { - try ImGui.hooks.frame.register(nu_imgui_frame); +pub fn setup(_: std.mem.Allocator) !void { + try ImGui.gui.register(gui); } -pub fn deinit() void {} +pub fn teardown() void {} -// pub fn nu_frame() void {} +pub fn update() !void {} -// pub fn nu_events(events: []const Bus.Event) void { -// std.debug.print("{any}\n", .{events}); -// } - -// pub fn nu_render_present(_: Render.au.CommandBufferProxy) void {} - -pub fn nu_imgui_frame() void { +pub fn gui() void { ImGui.igShowMetricsWindow(null); } diff --git a/src/main.zig b/src/main.zig index 6f9602d..6c1751c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -23,30 +23,32 @@ pub const nu_options: nu.Options = .{ pub const nu_driver = nu.Window; pub const nu_modules = .{ - App, - nu.ImGui, nu.Render, + nu.ImGui, + App, }; -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - defer _ = gpa.detectLeaks(); - const alloc = gpa.allocator(); +pub const main = nu.main; - nu.init(alloc); - defer nu.deinit(); - - try nu.Window.init(alloc); - defer nu.Window.deinit(); - - try nu.Render.init(alloc); - defer nu.Render.deinit(); - - try nu.ImGui.init(alloc); - defer nu.ImGui.deinit(); - - try App.init(alloc); - defer App.deinit(); - - try nu.run(alloc); -} +// pub fn main() !void { +// var gpa = std.heap.GeneralPurposeAllocator(.{}){}; +// defer _ = gpa.detectLeaks(); +// const alloc = gpa.allocator(); +// +// nu.init(alloc); +// defer nu.deinit(); +// +// try nu.Window.init(alloc); +// defer nu.Window.deinit(); +// +// try nu.Render.init(alloc); +// defer nu.Render.deinit(); +// +// try nu.ImGui.init(alloc); +// defer nu.ImGui.deinit(); +// +// try App.init(alloc); +// defer App.deinit(); +// +// try nu.run(alloc); +// } diff --git a/src/nu.zig b/src/nu.zig index c1f15ee..09a2020 100644 --- a/src/nu.zig +++ b/src/nu.zig @@ -8,91 +8,80 @@ pub const ImGui = @import("nu/ImGui.zig"); pub const Bus = @import("nu/Bus.zig"); pub const Hook = @import("nu/hooks.zig").Hook; -pub const Options = struct { - window: Window.Options = .{}, - render: Render.Options = .{}, - imgui: ImGui.Options = .{}, +const Config = struct { + window: Window.Config = .{}, + render: Render.Config = .{}, }; -pub const options: Options = if (@hasDecl(root, "nu_options")) root.nu_options else .{}; -pub const modules = root.nu_modules; -pub const driver = root.nu_driver; +pub const config: Config = if (@hasDecl(root, "nu_config")) root.nu_config else .{}; -pub const Hooks = struct { - pub const Enter = Hook(fn () void); - pub const Events = Hook(fn ([]Bus.Event) void); - pub const Frame = Hook(fn () anyerror!void); - pub const Close = Hook(fn () void); - - enter: Enter, - events: Events, - frame: Frame, - close: Close, +pub const Module = struct { + // todo dependencies + name: []const u8, + setup: *const fn (alloc: std.mem.Allocator) anyerror!void, + teardown: *const fn () void, + update: ?*const fn () anyerror!void = null, + frame: ?*const fn () anyerror!void = null, + dependencies: []const Module = &.{}, }; -pub var hooks: Hooks = undefined; +pub const Driver = struct { + module: Module, + next: *const fn () bool, // events? callbacks? +}; -pub fn init(alloc: std.mem.Allocator) void { - hooks = .{ - .enter = Hooks.Enter.init(alloc), - .events = Hooks.Events.init(alloc), - .frame = Hooks.Frame.init(alloc), - .close = Hooks.Close.init(alloc), - }; +fn enable(modules: *std.StringArrayHashMap(Module), module: Module) void { + // this doesn't handle dependencies correctly. need to do real topological sort. + + if (modules.contains(module.name)) return; + for (module.dependencies) |dep| enable(modules, dep); + modules.putNoClobber(module.name, module) catch @panic("OOM"); } -pub fn deinit() void { - hooks.enter.deinit(); - hooks.events.deinit(); - hooks.frame.deinit(); - hooks.close.deinit(); -} +pub fn main() void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const alloc = gpa.allocator(); -pub fn run(alloc: std.mem.Allocator) !void { - // todo hooks should execute in priority order; then hook.register accepts a "priority" argument. - inline for (modules) |module| { - try module.connect(); + var modules = std.StringArrayHashMap(Module).init(alloc); + defer modules.deinit(); + + const driver = root.nu_driver.driver(); + enable(&modules, driver.module); + inline for (root.nu_modules) |mod| { + enable(&modules, mod.module()); } - hooks.enter.invoke(.{}); + for (modules.values()) |mod| { + std.log.debug("{s}.setup", .{mod.name}); + mod.setup(alloc) catch |err| { + std.debug.panic("Module {s} setup error: {!}", .{ mod.name, err }); + }; + } + defer { + var rev = std.mem.reverseIterator(modules.values()); + while (rev.next()) |mod| { + std.log.debug("{s}.teardown", .{mod.name}); + mod.teardown(); + } + } - while (driver.next()) |events| { - if (events.len > 0) { - hooks.events.invoke(.{events}); + std.log.info("Loaded modules: {s}", .{modules.keys()}); + + while (driver.next()) { + for (modules.values()) |mod| { + if (mod.update) |update| { + update() catch |err| { + std.debug.panic("Module {s} update event error: {!}", .{ mod.name, err }); + }; + } } - // todo frame timer - const frame_results = try hooks.frame.invoke(alloc, .{}); - defer alloc.free(frame_results); - for (frame_results) |result| result catch |err| return err; - - // todo fixed timestep + for (modules.values()) |mod| { + if (mod.frame) |frame| { + frame() catch |err| { + std.debug.panic("Module {s} frame event error: {!}", .{ mod.name, err }); + }; + } + } } - - hooks.close.invoke(.{}); } - -// // todo specify hook type. -// // - special handling for error unions -// // - allow per-hook state somehow declared in the handler -// -// pub fn invoke_hook(comptime name: []const u8, args: anytype) !void { -// inline for (modules) |module| { -// if (@hasDecl(module, name)) { -// try invoke(@field(module, name), args); -// } -// } -// } -// -// fn invoke(func: anytype, args: anytype) !void { -// if (@typeInfo(@TypeOf(func)).Fn.return_type) |R| { -// switch (@typeInfo(R)) { -// .ErrorUnion => try @call(.auto, func, args), -// .Void => @call(.auto, func, args), -// else => { -// @compileLog(func, @typeInfo(R)); -// @compileError("Invalid hook return type. Must be void or !void."); -// }, -// } -// } -// } diff --git a/src/nu/ImGui.zig b/src/nu/ImGui.zig index 66ad5a3..d96a0de 100644 --- a/src/nu/ImGui.zig +++ b/src/nu/ImGui.zig @@ -13,23 +13,33 @@ const Window = @import("Window.zig"); const im = @import("cimgui"); pub usingnamespace im; -pub const Options = struct {}; +pub const Config = struct {}; +const config = nu.config.imgui; -pub const Hooks = struct { - pub const Frame = Hook(fn () void); - frame: Frame, -}; +pub fn module() nu.Module { + return nu.Module{ + .name = "ImGui", + // .dependencies = &.{Render.module()}, + .setup = setup, + .teardown = teardown, + .update = update, + }; +} pub fn loader_wrapper(procname: [*c]const u8, _: ?*anyopaque) callconv(.C) vk.PfnVoidFunction { return au.glfwGetInstanceProcAddress(au.I.handle, procname); } +pub var gui: Hook(fn () void) = undefined; var ctx: *im.ImGuiContext = undefined; var descriptor_pool: vk.DescriptorPool = undefined; -pub var hooks: Hooks = undefined; +pub fn setup(alloc: std.mem.Allocator) !void { + gui = @TypeOf(gui).init(alloc); + errdefer gui.deinit(); + + try Render.present.register(present); -pub fn init(alloc: std.mem.Allocator) !void { ctx = im.igCreateContext(null) orelse { return error.igCreateContextFailed; }; @@ -64,7 +74,7 @@ pub fn init(alloc: std.mem.Allocator) !void { .DescriptorPool = @ptrFromInt(@intFromEnum(descriptor_pool)), .RenderPass = null, .MinImageCount = 2, - .ImageCount = @intCast(nu.options.render.frames_in_flight), + .ImageCount = @intCast(nu.config.render.frames_in_flight), .PipelineRenderingCreateInfo = @bitCast(vk.PipelineRenderingCreateInfo{ .view_mask = 0, .depth_attachment_format = .undefined, @@ -85,38 +95,30 @@ pub fn init(alloc: std.mem.Allocator) !void { if (!im.impl.ImGui_ImplVulkan_CreateFontsTexture()) { return error.igVulkanFontTextureFailed; } - - hooks = .{ - .frame = Hooks.Frame.init(alloc), - }; - errdefer hooks.frame.deinit(); } -pub fn deinit() void { - hooks.frame.deinit(); +pub fn teardown() void { + au.D.deviceWaitIdle() catch |err| std.debug.panic("Device wait failed: {!}", .{err}); + im.impl.ImGui_ImplVulkan_Shutdown(); au.D.destroyDescriptorPool(descriptor_pool, null); im.impl.ImGui_ImplGlfw_Shutdown(); im.igDestroyContext(ctx); + gui.deinit(); } -pub fn connect() !void { - try nu.hooks.frame.register(nu_frame); - try Render.hooks.present.register(nu_render_present); -} - -pub fn nu_frame() !void { +pub fn update() !void { im.impl.ImGui_ImplGlfw_NewFrame(); im.impl.ImGui_ImplVulkan_NewFrame(); im.igNewFrame(); - hooks.frame.invoke(.{}); + gui.invoke(.{}); im.igEndFrame(); im.igRender(); } -pub fn nu_render_present(cmd: au.CommandBufferProxy) void { +pub fn present(cmd: au.CommandBufferProxy) void { im.impl.ImGui_ImplVulkan_RenderDrawData( @ptrCast(im.igGetDrawData()), @ptrFromInt(@intFromEnum(cmd.handle)), diff --git a/src/nu/Render.zig b/src/nu/Render.zig index 697f8c8..3e4ddac 100644 --- a/src/nu/Render.zig +++ b/src/nu/Render.zig @@ -9,9 +9,7 @@ const vk = @import("vk"); const nu = @import("../nu.zig"); const au = @import("Render/au.zig"); -const Hook = @import("hooks.zig").Hook; - -pub const Options = struct { +pub const Config = struct { app_name: [*:0]const u8 = "nu-au-app", app_version: struct { variant: u3 = 0, @@ -27,20 +25,29 @@ pub const Options = struct { patch: u12 = 0, } = .{}, frames_in_flight: u8 = 3, + use_debug_messenger: bool = switch (builtin.mode) { + .Debug, .ReleaseSafe => true, + .ReleaseSmall, .ReleaseFast => false, + }, }; +const config = nu.config.render; -pub const Hooks = struct { - pub const Present = Hook(fn (au.CommandBufferProxy) void); - - present: Present, -}; - -pub var hooks: Hooks = undefined; +pub fn module() nu.Module { + return nu.Module{ + .name = "Render", + .setup = setup, + .teardown = teardown, + .frame = frame, + .dependencies = &.{nu.Window.driver().module}, + }; +} var sc: au.SwapChain = undefined; var flights: au.Flights = undefined; -pub fn init(alloc: std.mem.Allocator) !void { +pub var present: nu.Hook(fn (au.CommandBufferProxy) void) = undefined; + +pub fn setup(alloc: std.mem.Allocator) !void { // todo pick apart au into helpers; not a sub-module filled with its own globals. try au.init(alloc); errdefer au.deinit(); @@ -48,26 +55,22 @@ pub fn init(alloc: std.mem.Allocator) !void { sc = try au.SwapChain.init(alloc); errdefer sc.deinit(); - flights = try au.Flights.init(alloc, nu.options.render.frames_in_flight); + flights = try au.Flights.init(alloc, config.frames_in_flight); errdefer flights.deinit(); - hooks = .{ .present = Hooks.Present.init(alloc) }; - errdefer hooks.present.deinit(); + present = @TypeOf(present).init(alloc); + errdefer present.deinit(); } -pub fn deinit() void { - hooks.present.deinit(); +pub fn teardown() void { + au.D.deviceWaitIdle() catch |err| std.debug.panic("Device wait failed: {!}", .{err}); + errdefer present.deinit(); flights.deinit(); sc.deinit(); au.deinit(); } -pub fn connect() !void { - try nu.hooks.frame.register(nu_frame); - try nu.hooks.close.register(nu_close); -} - -pub fn nu_frame() !void { +pub fn frame() !void { const flight: au.Flights.Flight = flights.next(); try flight.wait(); @@ -95,7 +98,8 @@ pub fn nu_frame() !void { // todo manage frame in flight state for each hook; pass the current flight in as context. // will need some comptime -> anytype mapping. - hooks.present.invoke(.{cmd}); + + present.invoke(.{cmd}); target.end_rendering(cmd); try cmd.endCommandBuffer(); @@ -126,9 +130,3 @@ pub fn nu_frame() !void { } } } - -pub fn nu_close() void { - au.D.deviceWaitIdle() catch |err| { - std.debug.panic("Device wait failed: {!}", .{err}); - }; -} diff --git a/src/nu/Render/au.zig b/src/nu/Render/au.zig index 2b54aa9..e12cbf5 100644 --- a/src/nu/Render/au.zig +++ b/src/nu/Render/au.zig @@ -8,10 +8,7 @@ pub const SwapChain = @import("au/SwapChain.zig"); pub const Flights = @import("au/Flights.zig"); pub const VkAllocator = @import("au/VkAllocator.zig"); -pub const use_debug_messenger = switch (builtin.mode) { - .Debug, .ReleaseSafe => true, - .ReleaseSmall, .ReleaseFast => false, -}; +const config = nu.config.render; pub const apis: []const vk.ApiInfo = &.{ vk.features.version_1_0, @@ -21,7 +18,7 @@ pub const apis: []const vk.ApiInfo = &.{ vk.extensions.khr_surface, vk.extensions.khr_swapchain, vk.extensions.khr_dynamic_rendering, - if (use_debug_messenger) vk.extensions.ext_debug_utils else .{}, + if (config.use_debug_messenger) vk.extensions.ext_debug_utils else .{}, }; pub const device_extensions: []const [*:0]const u8 = &.{ @@ -82,7 +79,7 @@ fn init_base() !void { if (glfwVulkanSupported() != nu.Window.c.GLFW_TRUE) return error.glfwNoVulkan; - if (use_debug_messenger) { + if (config.use_debug_messenger) { _bw = try BaseWrapper.load(glfwGetInstanceProcAddress); } else { _bw = BaseWrapper.loadNoFail(glfwGetInstanceProcAddress); @@ -98,7 +95,7 @@ fn init_instance(alloc: std.mem.Allocator) !void { var layers = std.ArrayList([*:0]const u8).init(alloc); defer layers.deinit(); - if (use_debug_messenger) { + if (config.use_debug_messenger) { try extensions.appendSlice(&.{ vk.extensions.ext_debug_utils.name, }); @@ -132,19 +129,19 @@ fn init_instance(alloc: std.mem.Allocator) !void { _instance = try B.createInstance(&.{ .p_application_info = &.{ - .p_application_name = nu.options.render.app_name, + .p_application_name = config.app_name, .application_version = vk.makeApiVersion( - nu.options.render.app_version.variant, - nu.options.render.app_version.major, - nu.options.render.app_version.minor, - nu.options.render.app_version.patch, + config.app_version.variant, + config.app_version.major, + config.app_version.minor, + config.app_version.patch, ), - .p_engine_name = nu.options.render.engine_name, + .p_engine_name = config.engine_name, .engine_version = vk.makeApiVersion( - nu.options.render.engine_version.variant, - nu.options.render.engine_version.major, - nu.options.render.engine_version.minor, - nu.options.render.engine_version.patch, + config.engine_version.variant, + config.engine_version.major, + config.engine_version.minor, + config.engine_version.patch, ), .api_version = vk.API_VERSION_1_3, }, @@ -152,10 +149,10 @@ fn init_instance(alloc: std.mem.Allocator) !void { .pp_enabled_extension_names = extensions.items.ptr, .enabled_layer_count = @intCast(layers.items.len), .pp_enabled_layer_names = layers.items.ptr, - .p_next = if (use_debug_messenger) &mci else null, + .p_next = if (config.use_debug_messenger) &mci else null, }, null); - if (use_debug_messenger) { + if (config.use_debug_messenger) { _iw = try InstanceWrapper.load(_instance, _bw.dispatch.vkGetInstanceProcAddr); } else { _iw = InstanceWrapper.loadNoFail(_instance, _bw.dispatch.vkGetInstanceProcAddr); @@ -306,7 +303,7 @@ fn init_device(alloc: std.mem.Allocator) !void { }, }, null); - if (use_debug_messenger) { + if (config.use_debug_messenger) { _dw = try DeviceWrapper.load(_device, _iw.dispatch.vkGetDeviceProcAddr); } else { _dw = DeviceWrapper.loadNoFail(_device, _iw.dispatch.vkGetDeviceProcAddr); diff --git a/src/nu/Window.zig b/src/nu/Window.zig index 303bc5b..bc13dc9 100644 --- a/src/nu/Window.zig +++ b/src/nu/Window.zig @@ -13,61 +13,62 @@ pub const c = @cImport({ pub const Bus = @import("Bus.zig"); -pub const Options = struct { +pub const Config = struct { title: [*:0]const u8 = "Hello World", width: u32 = 1280, height: u32 = 720, x11_class_name: [*:0]const u8 = "floating_window", x11_instance_name: [*:0]const u8 = "floating_window", + unfocused_wait: f32 = 1.0 / 20.0, }; +const config: Config = nu.config.window; -var bus: Bus = undefined; // todo bus should probably move to engine. -pub var handle: *c.GLFWwindow = undefined; -var unfocused_rate: f32 = 1.0 / 20.0; - -pub fn init(alloc: std.mem.Allocator) !void { - if (c.glfwInit() != c.GLFW_TRUE) - return error.glfwInitFailed; - errdefer c.glfwTerminate(); - - bus = Bus.init(alloc); - errdefer bus.deinit(); - - c.glfwWindowHint(c.GLFW_CLIENT_API, c.GLFW_NO_API); - c.glfwWindowHintString(c.GLFW_X11_CLASS_NAME, nu.options.window.x11_class_name); - c.glfwWindowHintString(c.GLFW_X11_INSTANCE_NAME, nu.options.window.x11_instance_name); - - handle = c.glfwCreateWindow( - @intCast(nu.options.window.width), - @intCast(nu.options.window.height), - nu.options.window.title, - null, - null, - ) orelse - return error.glfWCreateWindowFailed; - errdefer c.glfwDestroyWindow(handle); - - bus.connect(handle); - errdefer bus.disconnect(handle); +pub fn driver() nu.Driver { + return nu.Driver{ + .module = .{ + .name = "Window", + .dependencies = &.{}, // todo bus + .setup = setup, + .teardown = teardown, + }, + .next = next, + }; } -pub fn deinit() void { - bus.deinit(); +pub var handle: *c.GLFWwindow = undefined; + +pub fn setup(_: std.mem.Allocator) !void { + if (c.glfwInit() != c.GLFW_TRUE) std.debug.panic("GLFW Init Failed", .{}); + + c.glfwWindowHint(c.GLFW_CLIENT_API, c.GLFW_NO_API); + c.glfwWindowHintString(c.GLFW_X11_CLASS_NAME, config.x11_class_name); + c.glfwWindowHintString(c.GLFW_X11_INSTANCE_NAME, config.x11_instance_name); + handle = c.glfwCreateWindow( + @intCast(config.width), + @intCast(config.height), + config.title, + null, + null, + ) orelse std.debug.panic("GLFW Create Window Failed", .{}); + + // bus.connect(handle); + // errdefer bus.disconnect(handle); +} + +pub fn teardown() void { c.glfwDestroyWindow(handle); c.glfwTerminate(); } -pub fn next() ?[]Bus.Event { - bus.clear(); - +pub fn next() bool { if (c.glfwWindowShouldClose(handle) == c.GLFW_TRUE) - return null; + return false; if (c.glfwGetWindowAttrib(handle, c.GLFW_FOCUSED) == c.GLFW_TRUE) { c.glfwPollEvents(); } else { - c.glfwWaitEventsTimeout(unfocused_rate); + c.glfwWaitEventsTimeout(config.unfocused_wait); } - return bus.events.items; + return true; } diff --git a/src/nu/hooks.zig b/src/nu/hooks.zig index ca8fef8..8e6b4e1 100644 --- a/src/nu/hooks.zig +++ b/src/nu/hooks.zig @@ -28,7 +28,7 @@ pub fn Hook(ftype: type) type { _ = self.handlers.orderedRemove(f); } - fn invoke(self: Self, args: anytype) void { + pub fn invoke(self: Self, args: anytype) void { for (self.handlers.keys()) |handler| { @call(.auto, handler, args); } @@ -57,7 +57,7 @@ pub fn Hook(ftype: type) type { _ = self.handlers.orderedRemove(f); } - fn invoke(self: Self, alloc: std.mem.Allocator, args: anytype) ![]Result { + pub fn invoke(self: Self, alloc: std.mem.Allocator, args: anytype) ![]Result { const results = try alloc.alloc(Result, self.handlers.count()); for (self.handlers.keys(), results) |handler, *result| { result.* = @call(.auto, handler, args);