Files
zig-experiments/hotswap/src.bak/core.zig

96 lines
3.5 KiB
Zig

const std = @import("std");
// Notes:
//
// "init" is an overloaded term. Try not to use it. Instead, have hooks:
// - startup / shutdown. executed once each per program lifecycle.
// - load / unload. executed once per module load cycle.
//
// So the flow is:
// - startup. prepares long-lived state.
// - load. deserializes and prepares short-lived state.
// - unload. serializes and releases short-lived state.
// - shutdown. releases long-lived state.
//
// So when the hotplug system reloads the module, it performs an unload/load cycle.
// Normal operation without any reloads includes all four stages.
//
// Some considerations:
// - a module should be able to indicate specific failure:
// - startup (preparing state)
// - load (deserialization or preparing state)
// - unload (serialization)
// - a module should be able to indicate it cannot be reloaded.
// - serialization data might come from disk, or from a reload, or be missing.
//
// On reloading and rollback:
// - when loading a module, copy the dynlib to a versioned temporary file, and load that temporary file.
// - procedure for reloading a module:
// - current old version unload (and serialize)
// - attempt new version load (and deserialize)
// - if success: release old version and delete lib
// - if failure:
// - release new version and delete lib.
// - attempt old version load (and deserialize) (catch fatal error)
// - Configure signal handlers to always clean up the temporary files on errors like segfault?
// - temp files needs to gracefully handle multiple instances of the app running at once with shared hotloading.
//
// Things should be done in a thread-safe manner. Signal to a module that it should unload gracefully, and the
// module signal when it can safely be unloaded.
pub fn Slot(Function: type) type {
const Arguments = std.meta.ArgsTuple(Function);
const Return = @typeInfo(Function).@"fn".return_type.?;
return extern struct {
ptr: *const fn (*const Arguments, *Return) callconv(.c) void,
pub fn wrap(impl: Function) @This() {
return .{
.ptr = &struct {
pub fn cimpl(args: *const Arguments, ret: *Return) callconv(.c) void {
ret.* = @call(.auto, impl, args.*);
}
}.cimpl,
};
}
pub fn invoke(self: @This(), args: Arguments) Return {
var ret: Return = undefined;
self.ptr(&args, &ret);
return ret;
}
};
}
pub fn Module(State: type) type {
return extern struct {
pub const Error = std.mem.Allocator.Error;
pub const StartupError = Error;
pub const UnloadError = Error;
pub const ReloadError = Error;
const Startup = Slot(fn (std.mem.Allocator) StartupError!*State);
const Unload = Slot(fn (*State, std.mem.Allocator) UnloadError![]u8);
const Reload = Slot(fn (*State, std.mem.Allocator, []u8) ReloadError!void);
const Shutdown = Slot(fn (*State, std.mem.Allocator) void);
startup: Startup,
unload: Unload,
reload: Reload,
shutdown: Shutdown,
};
}
pub fn module(State: type) Module(State) {
return .{
.startup = Module(State).Startup.wrap(State.startup),
.unload = Module(State).Unload.wrap(State.unload),
.reload = Module(State).Reload.wrap(State.reload),
.shutdown = Module(State).Shutdown.wrap(State.shutdown),
};
}
pub const AnyModule = Module(anyopaque);