mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
move allocator to global state
This commit is contained in:
@ -14,13 +14,14 @@ extern "C" {
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
typedef void *ghostty_t;
|
#define GHOSTTY_SUCCESS 0
|
||||||
|
|
||||||
typedef void *ghostty_config_t;
|
typedef void *ghostty_config_t;
|
||||||
|
|
||||||
ghostty_t ghostty_init(void);
|
int ghostty_init(void);
|
||||||
ghostty_config_t ghostty_config_new(ghostty_t);
|
ghostty_config_t ghostty_config_new();
|
||||||
void ghostty_config_free(ghostty_t, ghostty_config_t);
|
void ghostty_config_free(ghostty_config_t);
|
||||||
void ghostty_config_load_string(ghostty_t, ghostty_config_t, const char *, uintptr_t);
|
void ghostty_config_load_string(ghostty_config_t, const char *, uintptr_t);
|
||||||
void ghostty_config_finalize(ghostty_config_t);
|
void ghostty_config_finalize(ghostty_config_t);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
@ -15,6 +15,8 @@ struct GhosttyApp: App {
|
|||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
switch ghostty.readiness {
|
switch ghostty.readiness {
|
||||||
|
case .loading:
|
||||||
|
Text("Loading")
|
||||||
case .error:
|
case .error:
|
||||||
ErrorView()
|
ErrorView()
|
||||||
case .ready:
|
case .ready:
|
||||||
@ -26,28 +28,27 @@ struct GhosttyApp: App {
|
|||||||
|
|
||||||
class GhosttyState: ObservableObject {
|
class GhosttyState: ObservableObject {
|
||||||
enum Readiness {
|
enum Readiness {
|
||||||
case error, ready
|
case loading, error, ready
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The readiness value of the state.
|
/// The readiness value of the state.
|
||||||
var readiness: Readiness { ghostty != nil ? .ready : .error }
|
@Published var readiness: Readiness = .loading
|
||||||
|
|
||||||
/// The ghostty global state.
|
|
||||||
var ghostty: ghostty_t? = nil
|
|
||||||
|
|
||||||
/// The ghostty global configuration.
|
/// The ghostty global configuration.
|
||||||
var config: ghostty_config_t? = nil
|
var config: ghostty_config_t? = nil
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
// Initialize ghostty global state. This happens once per process.
|
// Initialize ghostty global state. This happens once per process.
|
||||||
guard let g = ghostty_init() else {
|
guard ghostty_init() == GHOSTTY_SUCCESS else {
|
||||||
GhosttyApp.logger.critical("ghostty_init failed")
|
GhosttyApp.logger.critical("ghostty_init failed")
|
||||||
|
readiness = .error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the global configuration.
|
// Initialize the global configuration.
|
||||||
guard let cfg = ghostty_config_new(g) else {
|
guard let cfg = ghostty_config_new() else {
|
||||||
GhosttyApp.logger.critical("ghostty_config_new failed")
|
GhosttyApp.logger.critical("ghostty_config_new failed")
|
||||||
|
readiness = .error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,11 +59,11 @@ class GhosttyState: ObservableObject {
|
|||||||
// Finalize will make our defaults available.
|
// Finalize will make our defaults available.
|
||||||
ghostty_config_finalize(cfg)
|
ghostty_config_finalize(cfg)
|
||||||
|
|
||||||
ghostty = g;
|
config = cfg
|
||||||
config = cfg;
|
readiness = .ready
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
ghostty_config_free(ghostty, config)
|
ghostty_config_free(config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
35
src/build_config.zig
Normal file
35
src/build_config.zig
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
//! Build options, available at comptime. Used to configure features. This
|
||||||
|
//! will reproduce some of the fields from builtin and build_options just
|
||||||
|
//! so we can limit the amount of imports we need AND give us the ability
|
||||||
|
//! to shim logic and values into them later.
|
||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
|
/// The artifact we're producing. This can be used to determine if we're
|
||||||
|
/// building a standalone exe, an embedded lib, etc.
|
||||||
|
pub const artifact = Artifact.detect();
|
||||||
|
|
||||||
|
pub const Artifact = enum {
|
||||||
|
/// Standalone executable
|
||||||
|
exe,
|
||||||
|
|
||||||
|
/// Embeddable library
|
||||||
|
lib,
|
||||||
|
|
||||||
|
/// The WASM-targetted module.
|
||||||
|
wasm_module,
|
||||||
|
|
||||||
|
pub fn detect() Artifact {
|
||||||
|
if (builtin.target.isWasm()) {
|
||||||
|
assert(builtin.output_mode == .Obj);
|
||||||
|
assert(builtin.link_mode == .Static);
|
||||||
|
return .wasm_module;
|
||||||
|
}
|
||||||
|
|
||||||
|
return switch (builtin.output_mode) {
|
||||||
|
.Exe => .exe,
|
||||||
|
.Obj => .lib,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
@ -583,19 +583,19 @@ pub const Wasm = if (!builtin.target.isWasm()) struct {} else struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Wasm API.
|
// C API.
|
||||||
pub const CAPI = struct {
|
pub const CAPI = struct {
|
||||||
const Ghostty = @import("main_c.zig").Ghostty;
|
const global = &@import("main.zig").state;
|
||||||
const cli_args = @import("cli_args.zig");
|
const cli_args = @import("cli_args.zig");
|
||||||
|
|
||||||
/// Create a new configuration filled with the initial default values.
|
/// Create a new configuration filled with the initial default values.
|
||||||
export fn ghostty_config_new(g: *Ghostty) ?*Config {
|
export fn ghostty_config_new() ?*Config {
|
||||||
const result = g.alloc.create(Config) catch |err| {
|
const result = global.alloc.create(Config) catch |err| {
|
||||||
log.err("error allocating config err={}", .{err});
|
log.err("error allocating config err={}", .{err});
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
result.* = Config.default(g.alloc) catch |err| {
|
result.* = Config.default(global.alloc) catch |err| {
|
||||||
log.err("error creating config err={}", .{err});
|
log.err("error creating config err={}", .{err});
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
@ -603,30 +603,29 @@ pub const CAPI = struct {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export fn ghostty_config_free(g: *Ghostty, ptr: ?*Config) void {
|
export fn ghostty_config_free(ptr: ?*Config) void {
|
||||||
if (ptr) |v| {
|
if (ptr) |v| {
|
||||||
v.deinit();
|
v.deinit();
|
||||||
g.alloc.destroy(v);
|
global.alloc.destroy(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load the configuration from a string in the same format as
|
/// Load the configuration from a string in the same format as
|
||||||
/// the file-based syntax for the desktop version of the terminal.
|
/// the file-based syntax for the desktop version of the terminal.
|
||||||
export fn ghostty_config_load_string(
|
export fn ghostty_config_load_string(
|
||||||
g: *Ghostty,
|
|
||||||
self: *Config,
|
self: *Config,
|
||||||
str: [*]const u8,
|
str: [*]const u8,
|
||||||
len: usize,
|
len: usize,
|
||||||
) void {
|
) void {
|
||||||
config_load_string_(g, self, str[0..len]) catch |err| {
|
config_load_string_(self, str[0..len]) catch |err| {
|
||||||
log.err("error loading config err={}", .{err});
|
log.err("error loading config err={}", .{err});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn config_load_string_(g: *Ghostty, self: *Config, str: []const u8) !void {
|
fn config_load_string_(self: *Config, str: []const u8) !void {
|
||||||
var fbs = std.io.fixedBufferStream(str);
|
var fbs = std.io.fixedBufferStream(str);
|
||||||
var iter = cli_args.lineIterator(fbs.reader());
|
var iter = cli_args.lineIterator(fbs.reader());
|
||||||
try cli_args.parse(Config, g.alloc, self, &iter);
|
try cli_args.parse(Config, global.alloc, self, &iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
export fn ghostty_config_finalize(self: *Config) void {
|
export fn ghostty_config_finalize(self: *Config) void {
|
||||||
|
89
src/main.zig
89
src/main.zig
@ -3,6 +3,12 @@ const builtin = @import("builtin");
|
|||||||
const options = @import("build_options");
|
const options = @import("build_options");
|
||||||
const glfw = @import("glfw");
|
const glfw = @import("glfw");
|
||||||
const macos = @import("macos");
|
const macos = @import("macos");
|
||||||
|
const tracy = @import("tracy");
|
||||||
|
const internal_os = @import("os/main.zig");
|
||||||
|
const xev = @import("xev");
|
||||||
|
const fontconfig = @import("fontconfig");
|
||||||
|
const harfbuzz = @import("harfbuzz");
|
||||||
|
const renderer = @import("renderer.zig");
|
||||||
const xdg = @import("xdg.zig");
|
const xdg = @import("xdg.zig");
|
||||||
|
|
||||||
const App = @import("App.zig");
|
const App = @import("App.zig");
|
||||||
@ -10,9 +16,14 @@ const cli_args = @import("cli_args.zig");
|
|||||||
const Config = @import("config.zig").Config;
|
const Config = @import("config.zig").Config;
|
||||||
const Ghostty = @import("main_c.zig").Ghostty;
|
const Ghostty = @import("main_c.zig").Ghostty;
|
||||||
|
|
||||||
|
/// Global process state. This is initialized in main() for exe artifacts
|
||||||
|
/// and by ghostty_init() for lib artifacts. This should ONLY be used by
|
||||||
|
/// the C API. The Zig API should NOT use any global state and should
|
||||||
|
/// rely on allocators being passed in as parameters.
|
||||||
|
pub var state: GlobalState = undefined;
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
var state: Ghostty = undefined;
|
state.init();
|
||||||
Ghostty.init(&state);
|
|
||||||
defer state.deinit();
|
defer state.deinit();
|
||||||
const alloc = state.alloc;
|
const alloc = state.alloc;
|
||||||
|
|
||||||
@ -153,6 +164,80 @@ fn glfwErrorCallback(code: glfw.ErrorCode, desc: [:0]const u8) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This represents the global process state. There should only
|
||||||
|
/// be one of these at any given moment. This is extracted into a dedicated
|
||||||
|
/// struct because it is reused by main and the static C lib.
|
||||||
|
pub const GlobalState = struct {
|
||||||
|
const GPA = std.heap.GeneralPurposeAllocator(.{});
|
||||||
|
|
||||||
|
gpa: ?GPA,
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
pub fn init(self: *GlobalState) void {
|
||||||
|
// Output some debug information right away
|
||||||
|
std.log.info("dependency harfbuzz={s}", .{harfbuzz.versionString()});
|
||||||
|
if (options.fontconfig) {
|
||||||
|
std.log.info("dependency fontconfig={d}", .{fontconfig.version()});
|
||||||
|
}
|
||||||
|
std.log.info("renderer={}", .{renderer.Renderer});
|
||||||
|
std.log.info("libxev backend={}", .{xev.backend});
|
||||||
|
|
||||||
|
// First things first, we fix our file descriptors
|
||||||
|
internal_os.fixMaxFiles();
|
||||||
|
|
||||||
|
// We need to make sure the process locale is set properly. Locale
|
||||||
|
// affects a lot of behaviors in a shell.
|
||||||
|
internal_os.ensureLocale();
|
||||||
|
|
||||||
|
// Initialize ourself to nothing so we don't have any extra state.
|
||||||
|
self.* = .{
|
||||||
|
.gpa = null,
|
||||||
|
.alloc = undefined,
|
||||||
|
};
|
||||||
|
errdefer self.deinit();
|
||||||
|
|
||||||
|
self.gpa = gpa: {
|
||||||
|
// Use the libc allocator if it is available beacuse it is WAY
|
||||||
|
// faster than GPA. We only do this in release modes so that we
|
||||||
|
// can get easy memory leak detection in debug modes.
|
||||||
|
if (builtin.link_libc) {
|
||||||
|
if (switch (builtin.mode) {
|
||||||
|
.ReleaseSafe, .ReleaseFast => true,
|
||||||
|
|
||||||
|
// We also use it if we can detect we're running under
|
||||||
|
// Valgrind since Valgrind only instruments the C allocator
|
||||||
|
else => std.valgrind.runningOnValgrind() > 0,
|
||||||
|
}) break :gpa null;
|
||||||
|
}
|
||||||
|
|
||||||
|
break :gpa GPA{};
|
||||||
|
};
|
||||||
|
|
||||||
|
self.alloc = alloc: {
|
||||||
|
const base = if (self.gpa) |*value|
|
||||||
|
value.allocator()
|
||||||
|
else if (builtin.link_libc)
|
||||||
|
std.heap.c_allocator
|
||||||
|
else
|
||||||
|
unreachable;
|
||||||
|
|
||||||
|
// If we're tracing, wrap the allocator
|
||||||
|
if (!tracy.enabled) break :alloc base;
|
||||||
|
var tracy_alloc = tracy.allocator(base, null);
|
||||||
|
break :alloc tracy_alloc.allocator();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cleans up the global state. This doesn't _need_ to be called but
|
||||||
|
/// doing so in dev modes will check for memory leaks.
|
||||||
|
pub fn deinit(self: *GlobalState) void {
|
||||||
|
if (self.gpa) |*value| {
|
||||||
|
// We want to ensure that we deinit the GPA because this is
|
||||||
|
// the point at which it will output if there were safety violations.
|
||||||
|
_ = value.deinit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
test {
|
test {
|
||||||
_ = @import("Pty.zig");
|
_ = @import("Pty.zig");
|
||||||
_ = @import("Command.zig");
|
_ = @import("Command.zig");
|
||||||
|
@ -9,13 +9,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const options = @import("build_options");
|
|
||||||
const fontconfig = @import("fontconfig");
|
|
||||||
const harfbuzz = @import("harfbuzz");
|
|
||||||
const renderer = @import("renderer.zig");
|
|
||||||
const tracy = @import("tracy");
|
|
||||||
const xev = @import("xev");
|
|
||||||
const internal_os = @import("os/main.zig");
|
|
||||||
const main = @import("main.zig");
|
const main = @import("main.zig");
|
||||||
|
|
||||||
/// Global options so we can log. This is identical to main.
|
/// Global options so we can log. This is identical to main.
|
||||||
@ -25,85 +18,8 @@ pub usingnamespace @import("config.zig").CAPI;
|
|||||||
|
|
||||||
/// Initialize ghostty global state. It is possible to have more than
|
/// Initialize ghostty global state. It is possible to have more than
|
||||||
/// one global state but it has zero practical benefit.
|
/// one global state but it has zero practical benefit.
|
||||||
export fn ghostty_init() ?*Ghostty {
|
export fn ghostty_init() c_int {
|
||||||
assert(builtin.link_libc);
|
assert(builtin.link_libc);
|
||||||
const alloc = std.heap.c_allocator;
|
main.state.init();
|
||||||
const g = alloc.create(Ghostty) catch return null;
|
return 0;
|
||||||
Ghostty.init(g);
|
|
||||||
return g;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This represents the global process state. There should only
|
|
||||||
/// be one of these at any given moment. This is extracted into a dedicated
|
|
||||||
/// struct because it is reused by main and the static C lib.
|
|
||||||
///
|
|
||||||
/// init should be one of the first things ever called when using Ghostty.
|
|
||||||
pub const Ghostty = struct {
|
|
||||||
const GPA = std.heap.GeneralPurposeAllocator(.{});
|
|
||||||
|
|
||||||
gpa: ?GPA,
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
|
|
||||||
pub fn init(self: *Ghostty) void {
|
|
||||||
// Output some debug information right away
|
|
||||||
std.log.info("dependency harfbuzz={s}", .{harfbuzz.versionString()});
|
|
||||||
if (options.fontconfig) {
|
|
||||||
std.log.info("dependency fontconfig={d}", .{fontconfig.version()});
|
|
||||||
}
|
|
||||||
std.log.info("renderer={}", .{renderer.Renderer});
|
|
||||||
std.log.info("libxev backend={}", .{xev.backend});
|
|
||||||
|
|
||||||
// First things first, we fix our file descriptors
|
|
||||||
internal_os.fixMaxFiles();
|
|
||||||
|
|
||||||
// We need to make sure the process locale is set properly. Locale
|
|
||||||
// affects a lot of behaviors in a shell.
|
|
||||||
internal_os.ensureLocale();
|
|
||||||
|
|
||||||
// Initialize ourself to nothing so we don't have any extra state.
|
|
||||||
self.* = .{
|
|
||||||
.gpa = null,
|
|
||||||
.alloc = undefined,
|
|
||||||
};
|
|
||||||
errdefer self.deinit();
|
|
||||||
|
|
||||||
self.gpa = gpa: {
|
|
||||||
// Use the libc allocator if it is available beacuse it is WAY
|
|
||||||
// faster than GPA. We only do this in release modes so that we
|
|
||||||
// can get easy memory leak detection in debug modes.
|
|
||||||
if (builtin.link_libc) {
|
|
||||||
if (switch (builtin.mode) {
|
|
||||||
.ReleaseSafe, .ReleaseFast => true,
|
|
||||||
|
|
||||||
// We also use it if we can detect we're running under
|
|
||||||
// Valgrind since Valgrind only instruments the C allocator
|
|
||||||
else => std.valgrind.runningOnValgrind() > 0,
|
|
||||||
}) break :gpa null;
|
|
||||||
}
|
|
||||||
|
|
||||||
break :gpa GPA{};
|
|
||||||
};
|
|
||||||
|
|
||||||
self.alloc = alloc: {
|
|
||||||
const base = if (self.gpa) |*value|
|
|
||||||
value.allocator()
|
|
||||||
else if (builtin.link_libc)
|
|
||||||
std.heap.c_allocator
|
|
||||||
else
|
|
||||||
unreachable;
|
|
||||||
|
|
||||||
// If we're tracing, wrap the allocator
|
|
||||||
if (!tracy.enabled) break :alloc base;
|
|
||||||
var tracy_alloc = tracy.allocator(base, null);
|
|
||||||
break :alloc tracy_alloc.allocator();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Ghostty) void {
|
|
||||||
if (self.gpa) |*value| {
|
|
||||||
// We want to ensure that we deinit the GPA because this is
|
|
||||||
// the point at which it will output if there were safety violations.
|
|
||||||
_ = value.deinit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
Reference in New Issue
Block a user