From eed6979868573df6786406c568204bf02d01a27d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 16 Feb 2023 08:52:40 -0800 Subject: [PATCH] apprt: start embedded implement, make App API available to C --- include/ghostty.h | 21 +++++++++++++++++++++ src/App.zig | 40 +++++++++++++++++++++++++++++++++++++-- src/DevMode.zig | 4 +++- src/apprt.zig | 10 ++++++---- src/apprt/embedded.zig | 43 ++++++++++++++++++++++++++++++++++++++++++ src/apprt/glfw.zig | 4 +++- src/build_config.zig | 11 +++++++++-- src/main.zig | 2 +- src/main_c.zig | 7 +++++++ 9 files changed, 131 insertions(+), 11 deletions(-) create mode 100644 src/apprt/embedded.zig diff --git a/include/ghostty.h b/include/ghostty.h index ea8df672d..a256b7364 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -16,14 +16,35 @@ extern "C" { #define GHOSTTY_SUCCESS 0 +//------------------------------------------------------------------- +// Types + +// Fully defined types. This MUST be kept in sync with equivalent Zig +// structs. To find the Zig struct, grep for this type name. The documentation +// for all of these types is available in the Zig source. +typedef void (*ghostty_runtime_wakeup_cb)(void *); +typedef struct { + void *userdata; + ghostty_runtime_wakeup_cb wakeup_cb; +} ghostty_runtime_config_s; + +// Opaque types +typedef void *ghostty_app_t; typedef void *ghostty_config_t; +//------------------------------------------------------------------- +// Published API + int ghostty_init(void); + ghostty_config_t ghostty_config_new(); void ghostty_config_free(ghostty_config_t); void ghostty_config_load_string(ghostty_config_t, const char *, uintptr_t); void ghostty_config_finalize(ghostty_config_t); +ghostty_app_t ghostty_app_new(ghostty_runtime_config_s *, ghostty_config_t); +void ghostty_app_free(ghostty_app_t); + #ifdef __cplusplus } #endif diff --git a/src/App.zig b/src/App.zig index 78fccbdec..731a6ee6b 100644 --- a/src/App.zig +++ b/src/App.zig @@ -65,9 +65,13 @@ pub const Darwin = struct { /// Initialize the main app instance. This creates the main window, sets /// up the renderer state, compiles the shaders, etc. This is the primary /// "startup" logic. -pub fn create(alloc: Allocator, config: *const Config) !*App { +pub fn create( + alloc: Allocator, + rt_opts: apprt.runtime.App.Options, + config: *const Config, +) !*App { // Initialize app runtime - var app_backend = try apprt.runtime.App.init(); + var app_backend = try apprt.runtime.App.init(rt_opts); errdefer app_backend.terminate(); // The mailbox for messaging this thread @@ -303,3 +307,35 @@ pub const Wasm = if (!builtin.target.isWasm()) struct {} else struct { // } // } }; + +// C API +pub const CAPI = struct { + const global = &@import("main.zig").state; + + /// Create a new app. + export fn ghostty_app_new( + opts: *const apprt.runtime.App.Options, + config: *const Config, + ) ?*App { + return app_new_(opts, config) catch |err| { + log.err("error initializing app err={}", .{err}); + return null; + }; + } + + fn app_new_( + opts: *const apprt.runtime.App.Options, + config: *const Config, + ) !*App { + const app = try App.create(global.alloc, opts.*, config); + errdefer app.destroy(); + return app; + } + + export fn ghostty_app_free(ptr: ?*App) void { + if (ptr) |v| { + v.destroy(); + v.alloc.destroy(v); + } + } +}; diff --git a/src/DevMode.zig b/src/DevMode.zig index 2df187b82..2b037ce2a 100644 --- a/src/DevMode.zig +++ b/src/DevMode.zig @@ -4,6 +4,7 @@ const DevMode = @This(); const std = @import("std"); const builtin = @import("builtin"); +const build_config = @import("build_config.zig"); const imgui = @import("imgui"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; @@ -15,7 +16,8 @@ const Config = @import("config.zig").Config; /// If this is false, the rest of the terminal will be compiled without /// dev mode support at all. -pub const enabled = !builtin.target.isWasm(); +/// TODO: remove this and use build_config everywhere +pub const enabled = build_config.devmode_enabled; /// The global DevMode instance that can be used app-wide. Assume all functions /// are NOT thread-safe unless otherwise noted. diff --git a/src/apprt.zig b/src/apprt.zig index 4246d5a3c..36e2a2571 100644 --- a/src/apprt.zig +++ b/src/apprt.zig @@ -9,20 +9,22 @@ //! logic as possible, and to only reach out to platform-specific implementation //! code when absolutely necessary. const builtin = @import("builtin"); +const build_config = @import("build_config.zig"); pub usingnamespace @import("apprt/structs.zig"); pub const glfw = @import("apprt/glfw.zig"); pub const browser = @import("apprt/browser.zig"); +pub const embedded = @import("apprt/embedded.zig"); pub const Window = @import("apprt/Window.zig"); /// The implementation to use for the app runtime. This is comptime chosen /// so that every build has exactly one application runtime implementation. /// Note: it is very rare to use Runtime directly; most usage will use /// Window or something. -pub const runtime = if (builtin.target.isWasm()) - browser -else switch (builtin.os.tag) { - else => glfw, +pub const runtime = switch (build_config.artifact) { + .exe => glfw, + .lib => embedded, + .wasm_module => browser, }; test { diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig new file mode 100644 index 000000000..fa61e85e5 --- /dev/null +++ b/src/apprt/embedded.zig @@ -0,0 +1,43 @@ +//! Application runtime for the embedded version of Ghostty. The embedded +//! version is when Ghostty is embedded within a parent host application, +//! rather than owning the application lifecycle itself. This is used for +//! example for the macOS build of Ghostty so that we can use a native +//! Swift+XCode-based application. + +pub const App = struct { + /// Because we only expect the embedding API to be used in embedded + /// environments, the options are extern so that we can expose it + /// directly to a C callconv and not pay for any translation costs. + /// + /// C type: ghostty_runtime_config_s + pub const Options = extern struct { + /// Userdata that is passed to all the callbacks. + userdata: ?*anyopaque = null, + + /// Callback called to wakeup the event loop. This should trigger + /// a full tick of the app loop. + wakeup: *const fn (?*anyopaque) callconv(.C) void, + }; + + pub fn init(_: Options) !App { + return .{}; + } + + pub fn terminate(self: App) void { + _ = self; + } + + pub fn wakeup(self: App) !void { + _ = self; + } + + pub fn wait(self: App) !void { + _ = self; + } +}; + +pub const Window = struct { + pub fn deinit(self: *Window) void { + _ = self; + } +}; diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index 0cb5fab8e..f64ad200d 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -26,7 +26,9 @@ const glfwNative = glfw.Native(.{ const log = std.log.scoped(.glfw); pub const App = struct { - pub fn init() !App { + pub const Options = struct {}; + + pub fn init(_: Options) !App { if (!glfw.init(.{})) return error.GlfwInitFailed; return .{}; } diff --git a/src/build_config.zig b/src/build_config.zig index 1d1f538e9..2baaa48b8 100644 --- a/src/build_config.zig +++ b/src/build_config.zig @@ -10,6 +10,10 @@ const assert = std.debug.assert; /// building a standalone exe, an embedded lib, etc. pub const artifact = Artifact.detect(); +/// Whether our devmode UI is enabled or not. This requires imgui to be +/// compiled. +pub const devmode_enabled = artifact == .exe; + pub const Artifact = enum { /// Standalone executable exe, @@ -29,8 +33,11 @@ pub const Artifact = enum { return switch (builtin.output_mode) { .Exe => .exe, - .Obj => .lib, - else => unreachable, + .Lib => .lib, + else => { + @compileLog(builtin.output_mode); + @compileError("unsupported artifact output mode"); + }, }; } }; diff --git a/src/main.zig b/src/main.zig index 24f0b76d5..8ad3186bf 100644 --- a/src/main.zig +++ b/src/main.zig @@ -92,7 +92,7 @@ pub fn main() !void { glfw.setErrorCallback(glfwErrorCallback); // Run our app with a single initial window to start. - var app = try App.create(alloc, &config); + var app = try App.create(alloc, .{}, &config); defer app.destroy(); try app.run(); } diff --git a/src/main_c.zig b/src/main_c.zig index a6823260b..344d477bb 100644 --- a/src/main_c.zig +++ b/src/main_c.zig @@ -11,10 +11,17 @@ const assert = std.debug.assert; const builtin = @import("builtin"); const main = @import("main.zig"); +// Some comptime assertions that our C API depends on. +comptime { + const apprt = @import("apprt.zig"); + assert(apprt.runtime == apprt.embedded); +} + /// Global options so we can log. This is identical to main. pub const std_options = main.std_options; pub usingnamespace @import("config.zig").CAPI; +pub usingnamespace @import("App.zig").CAPI; /// Initialize ghostty global state. It is possible to have more than /// one global state but it has zero practical benefit.