wip polymorphic modules

This commit is contained in:
David Allemang
2024-07-10 17:27:40 -04:00
parent 53cbe35a97
commit 0efc931006
8 changed files with 208 additions and 219 deletions

View File

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

View File

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

View File

@@ -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 main() void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = gpa.allocator();
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());
}
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 });
};
}
pub fn deinit() void {
hooks.enter.deinit();
hooks.events.deinit();
hooks.frame.deinit();
hooks.close.deinit();
defer {
var rev = std.mem.reverseIterator(modules.values());
while (rev.next()) |mod| {
std.log.debug("{s}.teardown", .{mod.name});
mod.teardown();
}
}
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();
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 });
};
}
}
hooks.enter.invoke(.{});
while (driver.next()) |events| {
if (events.len > 0) {
hooks.events.invoke(.{events});
for (modules.values()) |mod| {
if (mod.frame) |frame| {
frame() catch |err| {
std.debug.panic("Module {s} frame 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
}
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.");
// },
// }
// }
// }

View File

@@ -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)),

View File

@@ -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 fn module() nu.Module {
return nu.Module{
.name = "Render",
.setup = setup,
.teardown = teardown,
.frame = frame,
.dependencies = &.{nu.Window.driver().module},
};
pub var hooks: Hooks = undefined;
}
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});
};
}

View File

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

View File

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

View File

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