diff --git a/libmach/README.md b/libmach/README.md index a30f077c..3344952c 100644 --- a/libmach/README.md +++ b/libmach/README.md @@ -4,8 +4,9 @@ Build the `libmach` dynamic library by running `make` (or running `zig build` in The resulting binary should be located in `libmach/build/`. Test the functionality of `libmach` using `make test_c` and `make test_lisp`. -These commands use C and Lisp to call into `libmach`, and both should show a blank window for exactly 1 second. +These commands use C and Lisp to call into `libmach`, and both should show a blank window for 5 seconds. +If you resize the window, it should print the new dimensions to the standard output. -Note: `make test_lisp` requires a relatively recent version of Steel Bank Common Lisp (`sbcl`) to be installed. +Note: `make test_lisp` requires a relatively recent version of Steel Bank Common Lisp (`sbcl`) to be installed, plus Quicklisp. -You can find the Zig source code for `libmach` in `src/bindings.zig`. +You can find the Zig source code for `libmach` in `src/platform/libmach.zig`. diff --git a/libmach/test.c b/libmach/test.c index 4583a45b..07b1e67b 100644 --- a/libmach/test.c +++ b/libmach/test.c @@ -1,40 +1,46 @@ #include #include +#include -typedef void mach_core_callback(void*); +typedef void resize_callback(void*, uint32_t, uint32_t); // `libmach` exported API bindings -void mach_core_set_init(mach_core_callback); -void mach_core_set_update(mach_core_callback); -void mach_core_set_deinit(mach_core_callback); -void mach_run(void); -void core_set_should_close(void*); -float core_delta_time(void*); +void* mach_init_core(void); +void mach_deinit(void*); +void mach_set_should_close(void*); +bool mach_window_should_close(void*); +int mach_update(void*, resize_callback); +float mach_delta_time(void*); + +void resize_fn(void* core, uint32_t width, uint32_t height) { + printf("Resize callback: %u %u\n", width, height); +} static float elapsed = 0; -void my_init(void* core) { - printf("My init!\n"); -} - -void my_update(void* core) { - float dt = core_delta_time(core); - if (elapsed < 1.0) { - elapsed += dt; - } else { - core_set_should_close(core); - } - printf("My update! total time = %f\n", elapsed); -} - -void my_deinit(void* core) { - printf("My deinit!\n"); -} - int main() { - mach_core_set_init(my_init); - mach_core_set_update(my_update); - mach_core_set_deinit(my_deinit); - mach_run(); + void* core = mach_init_core(); + + if (core == 0) { + printf("Error instantiating mach core\n"); + return 0; + } + + while (!mach_window_should_close(core)) { + if (mach_update(core, resize_fn) == 0) { + printf("Error updating Mach\n"); + break; + }; + + elapsed += mach_delta_time(core); + if (elapsed > 5.0) { + mach_set_should_close(core); + } + + // printf("Elapsed: %f\n", elapsed); + } + + mach_deinit(core); + return 0; } diff --git a/libmach/test.lisp b/libmach/test.lisp index 9423b4ba..d51c5ac4 100644 --- a/libmach/test.lisp +++ b/libmach/test.lisp @@ -15,50 +15,51 @@ ;; Note: CFFI automatically translates C_style names into lispier kebab-case ones -;; typedef void mach_core_callback(void*); -(defctype mach-core-callback :pointer) +;; void* mach_init(void); +(defcfun "mach_init_core" :pointer) +;; for some reason, calling "mach_init" always returns a null pointer, and I have no clue why... +;; So I renamed the API function name to "mach_init_core" instead -;; void mach_core_set_init(mach_core_callback); -(defcfun "mach_core_set_init" :void - (callback mach-core-callback)) +;; int mach_update(void*, resize_callback); +(defcfun "mach_update" :int + (core :pointer) (resize-fn :pointer)) -;; void mach_core_set_update(mach_core_callback); -(defcfun "mach_core_set_update" :void - (callback mach-core-callback)) +;; void mach_deinit(void*); +(defcfun "mach_deinit" :void + (core :pointer)) -;; void mach_core_set_deinit(mach_core_callback); -(defcfun "mach_core_set_deinit" :void - (callback mach-core-callback)) +;; void mach_set_should_close(void*); +(defcfun "mach_set_should_close" :void + (core :pointer)) -;; void mach_run(void); -(defcfun "mach_run" :void) +;; float mach_delta_time(void*); +(defcfun "mach_delta_time" :float + (core :pointer)) -;; void core_set_should_close(void*); -(defcfun "core_set_should_close" :void (core :pointer)) - -;; float core_delta_time(void*); -(defcfun "core_delta_time" :float (core :pointer)) - -(defcallback my-init :void ((core :pointer)) - (format t "Hello from my-init!~%")) +;; bool mach_window_should_close(void*); +(defcfun "mach_window_should_close" :bool + (core :pointer)) +;; main (defvar *elapsed* 0.0) -(defcallback my-update :void ((core :pointer)) - (format t "Hello from my-update ~a~%" *elapsed*) - (if (< *elapsed* 1.0) - (incf *elapsed* (core-delta-time core)) - (core-set-should-close core))) +(defcallback resize-fn :void ((core :pointer) (width :unsigned-int) (height :unsigned-int)) + (format t "Resize Callback: ~S ~S~%" width height)) -(defcallback my-deinit :void ((core :pointer)) - (format t "Hello from my-deinit!~%")) +(setf core (mach-init-core)) -(mach-core-set-init (callback my-init)) +(format t "Core: ~S~%" core) -(mach-core-set-update (callback my-update)) +(when (pointer-eq core (null-pointer)) + (format t "Failed to initialize mach core~%") + (sb-ext:exit)) -(mach-core-set-deinit (callback my-deinit)) - -(mach-run) +(loop while (not (mach-window-should-close core)) + do (progn + (when (= 0 (mach-update core (callback resize-fn))) + (format t "Error updating mach~%") + (sb-ext:exit)) + (when (> (incf *elapsed* (mach-delta-time core)) 5.0) + (mach-set-should-close core)))) (sb-ext:exit) diff --git a/src/bindings.zig b/src/bindings.zig deleted file mode 100644 index 71f5e78c..00000000 --- a/src/bindings.zig +++ /dev/null @@ -1,52 +0,0 @@ -const std = @import("std"); -const gpu = @import("gpu"); -const Core = @import("Core.zig"); -const libmach = @import("platform/libmach.zig"); -const native = @import("platform/native.zig"); - -// Current Limitations: -// 1. Currently, ecs seems to be using some weird compile-time type trickery, so I'm not exactly sure how -// `engine` should be integrated into the C API -// 2. Core might need to expose more state so more API functions can be exposed (for example, the WebGPU API) -// 3. Be very careful about arguments, types, memory, etc - any mismatch will result in undefined behavior - -pub const App = libmach; - -pub export fn mach_core_set_init(core_init: libmach.CoreCallback) void { - std.debug.print("mach core set init\n", .{}); - libmach.core_callbacks.core_init = core_init; -} - -pub export fn mach_core_set_deinit(core_deinit: libmach.CoreCallback) void { - std.debug.print("mach core set deinit\n", .{}); - libmach.core_callbacks.core_deinit = core_deinit; -} - -pub export fn mach_core_set_update(core_update: libmach.CoreCallback) void { - std.debug.print("mach core set update\n", .{}); - libmach.core_callbacks.core_update = core_update; -} - -pub export fn mach_run() void { - if (libmach.core_callbacks.core_init == null) { - std.debug.print("Did not provide a core_init callback\n", .{}); - return; - } - if (libmach.core_callbacks.core_update == null) { - std.debug.print("Did not provide a core_update callback\n", .{}); - return; - } - if (libmach.core_callbacks.core_deinit == null) { - std.debug.print("Did not provide a core_deinit callback\n", .{}); - return; - } - native.main() catch unreachable; -} - -pub export fn core_set_should_close(core: *Core) void { - core.*.setShouldClose(true); -} - -pub export fn core_delta_time(core: *Core) f32 { - return core.*.delta_time; -} diff --git a/src/platform/libmach.zig b/src/platform/libmach.zig index d521cf07..03ed2160 100644 --- a/src/platform/libmach.zig +++ b/src/platform/libmach.zig @@ -2,34 +2,106 @@ const std = @import("std"); const Core = @import("../Core.zig"); const gpu = @import("gpu"); const ecs = @import("ecs"); +const glfw = @import("glfw"); pub const App = @This(); -// Zig says that *App has a size of 0 bits, and it won't compile if -// pub const core_callback_t = fn (*App, *Core) callconv(.C) void; -// What is *App needed for anyway? -pub const CoreCallback = fn (*Core) callconv(.C) void; +// Dummy init, deinit, and update functions +pub fn init(_: *App, _: *Core) !void { } -pub const CoreCallbacks = struct { - core_init: ?CoreCallback, - core_update: ?CoreCallback, - core_deinit: ?CoreCallback, -}; +pub fn deinit(_: *App, _: *Core) void { } -pub var core_callbacks = CoreCallbacks { - .core_init = null, - .core_update = null, - .core_deinit = null, -}; +pub fn update(_: *App, _: *Core) !void { } -pub fn init(_: *App, core: *Core) !void { - core_callbacks.core_init.?(core); +// Current Limitations: +// 1. Currently, ecs seems to be using some weird compile-time type trickery, so I'm not exactly sure how +// `engine` should be integrated into the C API +// 2. Core might need to expose more state so more API functions can be exposed (for example, the WebGPU API) +// 3. Be very careful about arguments, types, memory, etc - any mismatch will result in undefined behavior + +pub export fn mach_set_should_close(core: *Core) void { + core.setShouldClose(true); } -pub fn deinit(_: *App, core: *Core) void { - core_callbacks.core_deinit.?(core); +pub export fn mach_delta_time(core: *Core) f32 { + return core.delta_time; } -pub fn update(_: *App, core: *Core) !void { - core_callbacks.core_update.?(core); +var gpa = std.heap.GeneralPurposeAllocator(.{}){}; +const allocator = gpa.allocator(); + +// Initializes and returns Mach's core structure +// Uses an optional type so it is valid to return 0 (on an error) +// TODO: come up with a better error reporting system +pub export fn mach_init_core() ?*Core { + const core: *Core = allocator.create(Core) catch { + return @intToPtr(?*Core, 0); // on error, return null pointer + }; + core.* = Core.init(allocator) catch { + return @intToPtr(?*Core, 0); // on error, return null pointer + }; + + // // Glfw specific: initialize the user pointer used in callbacks + core.*.internal.initCallback(); + + return core; +} + +// Deinitializes mach core structure +pub export fn mach_deinit(core: *Core) void { + core.internal.deinit(); + allocator.destroy(core); +} + +pub export fn mach_window_should_close(core: *Core) bool { + return core.internal.window.shouldClose(); +} + +pub const CoreCallback = fn (*Core, u32, u32) callconv(.C) void; + +// Adapted from native.zig +pub export fn mach_update(core: *Core, resize_fn: ?CoreCallback) i32 { + if (core.internal.wait_event_timeout > 0.0) { + if (core.internal.wait_event_timeout == std.math.inf(f64)) { + // Wait for an event + glfw.waitEvents() catch { + return 0; + }; + } else { + // Wait for an event with a timeout + glfw.waitEventsTimeout(core.internal.wait_event_timeout) catch { + return 0; + }; + } + } else { + // Don't wait for events + glfw.pollEvents() catch { + return 0; + }; + } + + core.delta_time_ns = core.timer.lapPrecise(); + core.delta_time = @intToFloat(f32, core.delta_time_ns) / @intToFloat(f32, std.time.ns_per_s); + + var framebuffer_size = core.getFramebufferSize(); + core.target_desc.width = framebuffer_size.width; + core.target_desc.height = framebuffer_size.height; + + if (core.swap_chain == null or !core.current_desc.equal(&core.target_desc)) { + const use_legacy_api = core.surface == null; + if (!use_legacy_api) { + core.swap_chain = core.device.nativeCreateSwapChain(core.surface, &core.target_desc); + } else core.swap_chain.?.configure( + core.swap_chain_format, + .{ .render_attachment = true }, + core.target_desc.width, + core.target_desc.height, + ); + + if (resize_fn != null) { + resize_fn.?(core, core.target_desc.width, core.target_desc.height); + } + core.current_desc = core.target_desc; + } + return 1; } diff --git a/src/platform/native.zig b/src/platform/native.zig index 4e7f2c07..8848946b 100644 --- a/src/platform/native.zig +++ b/src/platform/native.zig @@ -222,7 +222,7 @@ pub const Platform = struct { platform.events.append(node); } - fn initCallback(platform: *Platform) void { + pub fn initCallback(platform: *Platform) void { platform.user_ptr = UserPtr{ .platform = platform }; platform.window.setUserPointer(&platform.user_ptr);