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