Unknown state [2025-08-04]
This commit is contained in:
95
hotswap/src.bak/core.zig
Normal file
95
hotswap/src.bak/core.zig
Normal file
@@ -0,0 +1,95 @@
|
||||
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);
|
Reference in New Issue
Block a user