initial commit. working prototype.
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/.idea
|
||||||
|
/.envrc
|
||||||
|
|
||||||
|
/zig-out/
|
||||||
|
/.zig-cache/
|
47
build.zig
Normal file
47
build.zig
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn installModule(b: *std.Build, plug: *std.Build.Step.Compile) void {
|
||||||
|
std.debug.assert(plug.isDynamicLibrary());
|
||||||
|
const install = b.addInstallArtifact(plug, .{
|
||||||
|
.dest_dir = .{ .override = .{ .custom = "mods" } },
|
||||||
|
});
|
||||||
|
b.getInstallStep().dependOn(&install.step);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const exedriver = b.addExecutable(.{
|
||||||
|
.name = "driver",
|
||||||
|
.root_source_file = b.path("src/main.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.link_libc = true,
|
||||||
|
});
|
||||||
|
b.installArtifact(exedriver);
|
||||||
|
|
||||||
|
const libfoo = b.addSharedLibrary(.{
|
||||||
|
.name = "foo",
|
||||||
|
.root_source_file = b.path("src/foo.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.link_libc = true,
|
||||||
|
});
|
||||||
|
installModule(b, libfoo);
|
||||||
|
|
||||||
|
const libbar = b.addSharedLibrary(.{
|
||||||
|
.name = "bar",
|
||||||
|
.root_source_file = b.path("src/bar.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.link_libc = true,
|
||||||
|
});
|
||||||
|
installModule(b, libbar);
|
||||||
|
|
||||||
|
const run = b.addRunArtifact(exedriver);
|
||||||
|
run.step.dependOn(b.getInstallStep());
|
||||||
|
run.setEnvironmentVariable("MODS_DIR", b.getInstallPath(.{ .custom = "mods" }, ""));
|
||||||
|
|
||||||
|
b.step("run", "").dependOn(&run.step);
|
||||||
|
}
|
86
build.zig.zon
Normal file
86
build.zig.zon
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
.{
|
||||||
|
// This is the default name used by packages depending on this one. For
|
||||||
|
// example, when a user runs `zig fetch --save <url>`, this field is used
|
||||||
|
// as the key in the `dependencies` table. Although the user can choose a
|
||||||
|
// different name, most users will stick with this provided value.
|
||||||
|
//
|
||||||
|
// It is redundant to include "zig" in this name because it is already
|
||||||
|
// within the Zig package namespace.
|
||||||
|
.name = .zig_hotswap,
|
||||||
|
|
||||||
|
// This is a [Semantic Version](https://semver.org/).
|
||||||
|
// In a future version of Zig it will be used for package deduplication.
|
||||||
|
.version = "0.0.0",
|
||||||
|
|
||||||
|
// Together with name, this represents a globally unique package
|
||||||
|
// identifier. This field is generated by the Zig toolchain when the
|
||||||
|
// package is first created, and then *never changes*. This allows
|
||||||
|
// unambiguous detection of one package being an updated version of
|
||||||
|
// another.
|
||||||
|
//
|
||||||
|
// When forking a Zig project, this id should be regenerated (delete the
|
||||||
|
// field and run `zig build`) if the upstream project is still maintained.
|
||||||
|
// Otherwise, the fork is *hostile*, attempting to take control over the
|
||||||
|
// original project's identity. Thus it is recommended to leave the comment
|
||||||
|
// on the following line intact, so that it shows up in code reviews that
|
||||||
|
// modify the field.
|
||||||
|
.fingerprint = 0xa7823a599d65a583, // Changing this has security and trust implications.
|
||||||
|
|
||||||
|
// Tracks the earliest Zig version that the package considers to be a
|
||||||
|
// supported use case.
|
||||||
|
.minimum_zig_version = "0.14.0",
|
||||||
|
|
||||||
|
// This field is optional.
|
||||||
|
// Each dependency must either provide a `url` and `hash`, or a `path`.
|
||||||
|
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
|
||||||
|
// Once all dependencies are fetched, `zig build` no longer requires
|
||||||
|
// internet connectivity.
|
||||||
|
.dependencies = .{
|
||||||
|
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
|
||||||
|
//.example = .{
|
||||||
|
// // When updating this field to a new URL, be sure to delete the corresponding
|
||||||
|
// // `hash`, otherwise you are communicating that you expect to find the old hash at
|
||||||
|
// // the new URL. If the contents of a URL change this will result in a hash mismatch
|
||||||
|
// // which will prevent zig from using it.
|
||||||
|
// .url = "https://example.com/foo.tar.gz",
|
||||||
|
//
|
||||||
|
// // This is computed from the file contents of the directory of files that is
|
||||||
|
// // obtained after fetching `url` and applying the inclusion rules given by
|
||||||
|
// // `paths`.
|
||||||
|
// //
|
||||||
|
// // This field is the source of truth; packages do not come from a `url`; they
|
||||||
|
// // come from a `hash`. `url` is just one of many possible mirrors for how to
|
||||||
|
// // obtain a package matching this `hash`.
|
||||||
|
// //
|
||||||
|
// // Uses the [multihash](https://multiformats.io/multihash/) format.
|
||||||
|
// .hash = "...",
|
||||||
|
//
|
||||||
|
// // When this is provided, the package is found in a directory relative to the
|
||||||
|
// // build root. In this case the package's hash is irrelevant and therefore not
|
||||||
|
// // computed. This field and `url` are mutually exclusive.
|
||||||
|
// .path = "foo",
|
||||||
|
//
|
||||||
|
// // When this is set to `true`, a package is declared to be lazily
|
||||||
|
// // fetched. This makes the dependency only get fetched if it is
|
||||||
|
// // actually used.
|
||||||
|
// .lazy = false,
|
||||||
|
//},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Specifies the set of files and directories that are included in this package.
|
||||||
|
// Only files and directories listed here are included in the `hash` that
|
||||||
|
// is computed for this package. Only files listed here will remain on disk
|
||||||
|
// when using the zig package manager. As a rule of thumb, one should list
|
||||||
|
// files required for compilation plus any license(s).
|
||||||
|
// Paths are relative to the build root. Use the empty string (`""`) to refer to
|
||||||
|
// the build root itself.
|
||||||
|
// A directory listed here means that all files within, recursively, are included.
|
||||||
|
.paths = .{
|
||||||
|
"build.zig",
|
||||||
|
"build.zig.zon",
|
||||||
|
"src",
|
||||||
|
// For example...
|
||||||
|
//"LICENSE",
|
||||||
|
//"README.md",
|
||||||
|
},
|
||||||
|
}
|
24
src/bar.zig
Normal file
24
src/bar.zig
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const core = @import("core.zig");
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
export const MODULE = core.module(Self);
|
||||||
|
|
||||||
|
pub fn startup(_: std.mem.Allocator) !Self {
|
||||||
|
std.log.debug("!! startup {s} !!", .{@typeName(@This())});
|
||||||
|
return .{};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unload(_: *Self, _: std.mem.Allocator) ![]u8 {
|
||||||
|
std.log.debug("!! unload {s} !!", .{@typeName(@This())});
|
||||||
|
return &.{};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reload(_: *Self, _: std.mem.Allocator, _: []u8) !void {
|
||||||
|
std.log.debug("!! reload {s} !!", .{@typeName(@This())});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown(_: *Self, _: std.mem.Allocator) void {
|
||||||
|
std.log.debug("!! shutdown {s} !!", .{@typeName(@This())});
|
||||||
|
}
|
166
src/core.zig
Normal file
166
src/core.zig
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
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 ModuleType(Self: type) type {
|
||||||
|
return extern struct {
|
||||||
|
pub const Startup = fn (alloc: std.mem.Allocator) anyerror!Self;
|
||||||
|
pub const CStartup = fn (alloc: *const std.mem.Allocator) callconv(.c) ?*anyopaque;
|
||||||
|
|
||||||
|
pub const Unload = fn (self: *Self, alloc: std.mem.Allocator) anyerror![]u8;
|
||||||
|
pub const CUnload = fn (self: *Self, alloc: *const std.mem.Allocator, ptr: *[*]u8, len: *usize) callconv(.c) bool;
|
||||||
|
|
||||||
|
pub const Reload = fn (self: *Self, alloc: std.mem.Allocator, data: []u8) anyerror!void;
|
||||||
|
pub const CReload = fn (self: *Self, alloc: *const std.mem.Allocator, ptr: [*]u8, len: usize) callconv(.c) bool;
|
||||||
|
|
||||||
|
pub const Shutdown = fn (self: *Self, alloc: std.mem.Allocator) void;
|
||||||
|
pub const CShutdown = fn (self: *Self, alloc: *const std.mem.Allocator) callconv(.c) void;
|
||||||
|
|
||||||
|
cstartup: *const CStartup,
|
||||||
|
creload: *const CReload,
|
||||||
|
cunload: *const CUnload,
|
||||||
|
cshutdown: *const CShutdown,
|
||||||
|
|
||||||
|
pub fn startup(mod: @This(), alloc: std.mem.Allocator) !*Self {
|
||||||
|
return mod.cstartup(&alloc) orelse error.ModuleInitFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unload(mod: @This(), self: *Self, alloc: std.mem.Allocator) ![]u8 {
|
||||||
|
var data: []u8 = undefined;
|
||||||
|
if (mod.cunload(self, &alloc, &data.ptr, &data.len)) {
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
return error.ModuleUnloadFailed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reload(mod: @This(), self: *Self, alloc: std.mem.Allocator, data: []u8) !void {
|
||||||
|
if (mod.creload(self, &alloc, data.ptr, data.len)) {} else {
|
||||||
|
return error.ModuleReloadFailed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown(mod: @This(), self: *Self, alloc: std.mem.Allocator) void {
|
||||||
|
mod.cshutdown(self, &alloc);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn module(Self: type) ModuleType(Self) {
|
||||||
|
const Module = ModuleType(Self);
|
||||||
|
|
||||||
|
const impl = struct {
|
||||||
|
pub const startup: Module.Startup = Self.startup;
|
||||||
|
|
||||||
|
pub fn cstartup(
|
||||||
|
alloc: *const std.mem.Allocator,
|
||||||
|
) callconv(.c) ?*anyopaque {
|
||||||
|
const self: *Self = alloc.create(Self) catch |err| {
|
||||||
|
std.log.err(
|
||||||
|
"Module {s} startup failed: {!}",
|
||||||
|
.{ @typeName(Self), err },
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
self.* = startup(alloc.*) catch |err| {
|
||||||
|
alloc.destroy(self);
|
||||||
|
std.log.err(
|
||||||
|
"Module {s} startup failed: {!}",
|
||||||
|
.{ @typeName(Self), err },
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const unload: Module.Unload = Self.unload;
|
||||||
|
|
||||||
|
pub fn cunload(
|
||||||
|
self: *Self,
|
||||||
|
alloc: *const std.mem.Allocator,
|
||||||
|
ptr: *[*]u8,
|
||||||
|
len: *usize,
|
||||||
|
) callconv(.c) bool {
|
||||||
|
const data = unload(self, alloc.*) catch |err| {
|
||||||
|
std.log.err(
|
||||||
|
"Module {s} unload failed: {!}",
|
||||||
|
.{ @typeName(Self), err },
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
ptr.* = data.ptr;
|
||||||
|
len.* = data.len;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const reload: Module.Reload = Self.reload;
|
||||||
|
|
||||||
|
pub fn creload(
|
||||||
|
self: *Self,
|
||||||
|
alloc: *const std.mem.Allocator,
|
||||||
|
ptr: [*]u8,
|
||||||
|
len: usize,
|
||||||
|
) callconv(.c) bool {
|
||||||
|
reload(self, alloc.*, ptr[0..len]) catch |err| {
|
||||||
|
std.log.err(
|
||||||
|
"Module {s} reload failed: {!}",
|
||||||
|
.{ @typeName(Self), err },
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const shutdown: Module.Shutdown = Self.shutdown;
|
||||||
|
|
||||||
|
pub fn cshutdown(
|
||||||
|
self: *Self,
|
||||||
|
alloc: *const std.mem.Allocator,
|
||||||
|
) callconv(.c) void {
|
||||||
|
shutdown(self, alloc.*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.cstartup = &impl.cstartup,
|
||||||
|
.creload = &impl.creload,
|
||||||
|
.cunload = &impl.cunload,
|
||||||
|
.cshutdown = &impl.cshutdown,
|
||||||
|
};
|
||||||
|
}
|
25
src/foo.zig
Normal file
25
src/foo.zig
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const core = @import("core.zig");
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
export const MODULE = core.module(Self);
|
||||||
|
|
||||||
|
pub fn startup(_: std.mem.Allocator) !Self {
|
||||||
|
std.log.debug("!! startup {s} !!", .{@typeName(@This())});
|
||||||
|
return .{};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unload(_: *Self, _: std.mem.Allocator) ![]u8 {
|
||||||
|
std.log.debug("!! unload {s} !!", .{@typeName(@This())});
|
||||||
|
return &.{};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reload(_: *Self, _: std.mem.Allocator, _: []u8) !void {
|
||||||
|
std.log.debug("!! reload {s} !!", .{@typeName(@This())});
|
||||||
|
std.log.debug("!! NEW CONTENT !!", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown(_: *Self, _: std.mem.Allocator) void {
|
||||||
|
std.log.debug("!! shutdown {s} !!", .{@typeName(@This())});
|
||||||
|
}
|
190
src/main.zig
Normal file
190
src/main.zig
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const linux = std.os.linux;
|
||||||
|
const core = @import("core.zig");
|
||||||
|
|
||||||
|
const ModLib = struct {
|
||||||
|
path: []const u8,
|
||||||
|
lib: std.DynLib,
|
||||||
|
mod: *core.ModuleType(anyopaque),
|
||||||
|
|
||||||
|
fn open(realpath: []const u8, version: u32, alloc: std.mem.Allocator) !ModLib {
|
||||||
|
const basename = std.fs.path.basename(realpath);
|
||||||
|
const name = try std.fmt.allocPrint(
|
||||||
|
alloc,
|
||||||
|
".{s}.{d}",
|
||||||
|
.{ basename, version },
|
||||||
|
);
|
||||||
|
defer alloc.free(name);
|
||||||
|
|
||||||
|
const path = if (std.fs.path.dirname(realpath)) |dirname|
|
||||||
|
try std.fs.path.join(alloc, &.{ dirname, name })
|
||||||
|
else
|
||||||
|
try alloc.dupe(u8, name);
|
||||||
|
errdefer alloc.free(path);
|
||||||
|
|
||||||
|
try std.fs.copyFileAbsolute(realpath, path, .{});
|
||||||
|
|
||||||
|
var lib = try std.DynLib.open(path);
|
||||||
|
errdefer lib.close();
|
||||||
|
|
||||||
|
const mod = lib.lookup(*core.ModuleType(anyopaque), "MODULE") orelse
|
||||||
|
return error.MissingModuleDefinition;
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.path = path,
|
||||||
|
.lib = lib,
|
||||||
|
.mod = mod,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(self: *ModLib, alloc: std.mem.Allocator) void {
|
||||||
|
self.lib.close();
|
||||||
|
// std.fs.deleteFileAbsolute(self.path) catch std.log.warn("Failed to delete {s}", .{self.path});
|
||||||
|
alloc.free(self.path);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const DynamicModule = struct {
|
||||||
|
gpa: std.heap.GeneralPurposeAllocator(.{}),
|
||||||
|
realpath: []const u8,
|
||||||
|
curr: ModLib,
|
||||||
|
state: *anyopaque,
|
||||||
|
next: u32,
|
||||||
|
|
||||||
|
pub fn init(path: []const u8, alloc: std.mem.Allocator) !DynamicModule {
|
||||||
|
const realpath = try std.fs.cwd().realpathAlloc(alloc, path);
|
||||||
|
errdefer alloc.free(realpath);
|
||||||
|
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}).init;
|
||||||
|
errdefer if (gpa.deinit() == .leak) {
|
||||||
|
std.log.warn("Module {s} leaked.", .{realpath});
|
||||||
|
};
|
||||||
|
|
||||||
|
var self: DynamicModule = .{
|
||||||
|
.gpa = gpa,
|
||||||
|
.realpath = realpath,
|
||||||
|
.curr = undefined,
|
||||||
|
.state = undefined,
|
||||||
|
.next = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.curr = try ModLib.open(realpath, self.next, alloc);
|
||||||
|
errdefer self.curr.close(alloc);
|
||||||
|
|
||||||
|
self.state = try self.curr.mod.startup(self.gpa.allocator());
|
||||||
|
errdefer self.curr.mod.shutdown(self.state, self.gpa.allocator());
|
||||||
|
|
||||||
|
// todo deserialize from disk.
|
||||||
|
try self.curr.mod.reload(self.state, self.gpa.allocator(), &.{});
|
||||||
|
|
||||||
|
self.next += 1;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reload(self: *@This(), alloc: std.mem.Allocator) !void {
|
||||||
|
var next = try ModLib.open(self.realpath, self.next, alloc);
|
||||||
|
errdefer next.close(alloc);
|
||||||
|
|
||||||
|
const data = try self.curr.mod.unload(self.state, self.gpa.allocator());
|
||||||
|
errdefer self.curr.mod.reload(self.state, self.gpa.allocator(), data) catch
|
||||||
|
std.debug.panic("Failed to rollback to {s}", .{self.curr.path});
|
||||||
|
|
||||||
|
try next.mod.reload(self.state, self.gpa.allocator(), data);
|
||||||
|
|
||||||
|
self.curr.close(alloc);
|
||||||
|
self.next += 1;
|
||||||
|
self.curr = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *DynamicModule, alloc: std.mem.Allocator) void {
|
||||||
|
const data = self.curr.mod.unload(self.state, self.gpa.allocator()) catch
|
||||||
|
std.debug.panic("Failed to unload {s}", .{self.curr.path});
|
||||||
|
|
||||||
|
_ = data; // todo serialize to disk.
|
||||||
|
|
||||||
|
self.curr.mod.shutdown(self.state, self.gpa.allocator());
|
||||||
|
|
||||||
|
self.curr.close(alloc);
|
||||||
|
|
||||||
|
if (self.gpa.deinit() == .leak) {
|
||||||
|
std.log.warn("Module {s} leaked.", .{self.realpath});
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc.free(self.realpath);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}).init;
|
||||||
|
defer _ = gpa.deinit();
|
||||||
|
const alloc = gpa.allocator();
|
||||||
|
|
||||||
|
const self = try std.fs.selfExeDirPathAlloc(alloc);
|
||||||
|
defer alloc.free(self);
|
||||||
|
std.log.debug("running from '{s}'", .{self});
|
||||||
|
|
||||||
|
var env = try std.process.getEnvMap(alloc);
|
||||||
|
defer env.deinit();
|
||||||
|
|
||||||
|
const mods_path = if (env.get("MODS_DIR")) |envvar| blk: {
|
||||||
|
break :blk try alloc.dupe(u8, envvar);
|
||||||
|
} else blk: {
|
||||||
|
const dir = try std.fs.selfExeDirPathAlloc(alloc);
|
||||||
|
defer alloc.free(dir);
|
||||||
|
break :blk try std.fs.path.resolve(alloc, &.{ dir, "..", "mods" });
|
||||||
|
};
|
||||||
|
defer alloc.free(mods_path);
|
||||||
|
|
||||||
|
var mods: std.ArrayListUnmanaged(DynamicModule) = .{};
|
||||||
|
defer {
|
||||||
|
for (mods.items) |*mod| {
|
||||||
|
mod.deinit(alloc);
|
||||||
|
}
|
||||||
|
mods.deinit(alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
std.log.debug("loading mods in '{s}'", .{mods_path});
|
||||||
|
{
|
||||||
|
var dir = try std.fs.openDirAbsolute(mods_path, .{ .iterate = true });
|
||||||
|
defer dir.close();
|
||||||
|
|
||||||
|
// const fd = linux.inotify_init1(linux.IN.NONBLOCK);
|
||||||
|
// defer linux.close(fd);
|
||||||
|
// var fds = [_]linux.pollfd{
|
||||||
|
// .{ .fd = fd, .events = linux.IN.ALL_EVENTS, .revents = undefined },
|
||||||
|
// };
|
||||||
|
// const n = linux.poll(&fds, fds.len, 0);
|
||||||
|
|
||||||
|
var it = dir.iterate();
|
||||||
|
while (try it.next()) |entry| {
|
||||||
|
const mod_path = try std.fs.path.resolve(alloc, &.{ mods_path, entry.name });
|
||||||
|
defer alloc.free(mod_path);
|
||||||
|
|
||||||
|
if (std.mem.startsWith(u8, std.fs.path.basename(mod_path), "."))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var mod = try DynamicModule.init(mod_path, alloc);
|
||||||
|
errdefer mod.deinit(alloc);
|
||||||
|
try mods.append(alloc, mod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std.log.debug("-" ** 80, .{});
|
||||||
|
for (0..10) |x| {
|
||||||
|
std.log.debug("{d}...", .{x});
|
||||||
|
std.time.sleep(std.time.ns_per_s);
|
||||||
|
}
|
||||||
|
std.log.debug("reloading.", .{});
|
||||||
|
std.time.sleep(std.time.ns_per_s);
|
||||||
|
|
||||||
|
for (mods.items) |*mod| {
|
||||||
|
if (mod.reload(alloc)) |_| {
|
||||||
|
std.log.debug("loaded {s}", .{mod.curr.path});
|
||||||
|
} else |_| {
|
||||||
|
std.log.debug("rolled back to {s}", .{mod.curr.path});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std.log.debug("-" ** 80, .{});
|
||||||
|
}
|
Reference in New Issue
Block a user