236 lines
8.2 KiB
Zig
236 lines
8.2 KiB
Zig
// modules for managing loading dynamic libraries
|
|
//
|
|
|
|
const ModuleInterface = struct {
|
|
startup: *const fn (*anyopaque, ?*anyopaque) callconv(.C) bool = undefined,
|
|
shutdown: *const fn () callconv(.C) void = undefined,
|
|
};
|
|
|
|
pub const ModuleLoaderArgs = extern struct {
|
|
firstLoad: bool,
|
|
engine: *core.Engine,
|
|
nameRegistry: *p2.NameRegistry,
|
|
packerFS: *core.PackerFS,
|
|
// luaState: *anyopaque,
|
|
// luaAllocator: *anyopaque,
|
|
debugDrawInterface: *debug.DebugDrawInterface,
|
|
};
|
|
|
|
pub const LoadedModule = struct {
|
|
baseModulePath: []const u8,
|
|
moduleName: []const u8,
|
|
stagedPaths: std.ArrayListUnmanaged([]u8) = .{},
|
|
loaded: std.ArrayListUnmanaged(ModuleInterface) = .{},
|
|
activePath: ?[]const u8 = null,
|
|
stagingCount: u32 = 0,
|
|
lastLoad: i64 = 0,
|
|
lastModification: i64 = 0,
|
|
|
|
pdbPath: ?[]const u8 = null,
|
|
|
|
startOnLoad: bool = true,
|
|
started: bool = false,
|
|
initialLoad: bool = true,
|
|
autoLoad: bool = true,
|
|
loadNextFrame: bool = false,
|
|
|
|
pub fn getInterface(self: @This()) ?ModuleInterface {
|
|
return self.loaded.getLastOrNull();
|
|
}
|
|
|
|
pub fn stageModule(self: *@This(), allocator: std.mem.Allocator) !void {
|
|
//const stagingDirectory = ".modulecache/<name>/0/";
|
|
const stagingDirectoryPath = try std.fmt.allocPrint(allocator, ".modulecache/{d}/{s}/{d}", .{
|
|
core.getSessionStamp(),
|
|
self.moduleName,
|
|
self.stagedPaths.items.len,
|
|
});
|
|
defer allocator.free(stagingDirectoryPath);
|
|
|
|
try std.fs.cwd().makePath(stagingDirectoryPath);
|
|
|
|
const stagedPath = try std.fmt.allocPrint(allocator, "{s}/{s}", .{
|
|
stagingDirectoryPath,
|
|
try p2.sharedLibNameAlloc(allocator, self.moduleName),
|
|
});
|
|
|
|
try std.fs.cwd().copyFile(self.baseModulePath, std.fs.cwd(), stagedPath, .{});
|
|
|
|
if (self.pdbPath) |pdbPath| {
|
|
const stagedPdbPath = try std.fmt.allocPrint(allocator, "{s}/{s}.pdb", .{ stagingDirectoryPath, self.moduleName });
|
|
try std.fs.cwd().copyFile(pdbPath, std.fs.cwd(), stagedPdbPath, .{});
|
|
}
|
|
|
|
self.stagedPaths.append(allocator, stagedPath) catch unreachable;
|
|
}
|
|
|
|
pub fn loadStagedModule(self: *@This(), allocator: std.mem.Allocator) !void {
|
|
const stagedPath = self.stagedPaths.getLast();
|
|
var lib = try std.DynLib.open(stagedPath);
|
|
var mod = ModuleInterface{};
|
|
mod.startup = lib.lookup(@TypeOf(mod.startup), "startup_module") orelse return error.BadModule;
|
|
mod.shutdown = lib.lookup(@TypeOf(mod.shutdown), "shutdown_module") orelse return error.BadModule;
|
|
|
|
try self.loaded.append(allocator, mod);
|
|
}
|
|
|
|
pub fn load(self: *@This(), allocator: std.mem.Allocator) !void {
|
|
core.engine_log("[ModuleLoader] loading module: ", .{});
|
|
self.lastLoad = std.time.microTimestamp();
|
|
|
|
try self.stageModule(allocator);
|
|
try self.loadStagedModule(allocator);
|
|
}
|
|
|
|
// logic for testing if we should stage and load
|
|
//
|
|
// 1. modification check time > last load time
|
|
// 2. current time > modification check time + debounce
|
|
pub fn maybeStageAndLoad(self: *@This(), allocator: std.mem.Allocator) !bool {
|
|
var shouldLoad: bool = false;
|
|
|
|
if (self.lastModification > self.lastLoad) {
|
|
const currentTime = std.time.microTimestamp();
|
|
|
|
// give 100ms for debouncing
|
|
if (currentTime > self.lastModification + 50_000) {
|
|
shouldLoad = true;
|
|
}
|
|
}
|
|
|
|
if (self.initialLoad) {
|
|
self.initialLoad = false;
|
|
shouldLoad = true;
|
|
}
|
|
|
|
if (shouldLoad and (self.autoLoad or self.loadNextFrame)) {
|
|
self.loadNextFrame = false;
|
|
try self.load(allocator);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
};
|
|
|
|
fn dllChangedCallback(pathChanged: []const u8, ctx: ?*anyopaque) void {
|
|
const loaded: *LoadedModule = @ptrCast(@alignCast(ctx.?));
|
|
loaded.lastModification = std.time.microTimestamp();
|
|
|
|
core.engine_log("{s} change detected", .{pathChanged});
|
|
}
|
|
|
|
pub const ModuleLoader = struct {
|
|
backingAllocator: std.mem.Allocator,
|
|
arena: std.heap.ArenaAllocator,
|
|
loadedModules: std.ArrayListUnmanaged(*LoadedModule) = .{},
|
|
watchInitialized: bool = false,
|
|
|
|
pub var NeonObjectTable = core.EngineObjectVTable.from(@This(), "core.ModuleLoader");
|
|
pub fn create(allocator: std.mem.Allocator) !*@This() {
|
|
const self = try allocator.create(@This());
|
|
|
|
self.* = .{
|
|
.backingAllocator = allocator,
|
|
.arena = std.heap.ArenaAllocator.init(allocator),
|
|
};
|
|
|
|
return self;
|
|
}
|
|
|
|
pub fn addModule(self: *@This(), moduleName: []const u8) !void {
|
|
// modules are always looked for under zig-out/modules
|
|
if (core.BuildOption("static_build")) {
|
|
core.engine_log("[ModuleLoader]: skipping dynamic load, hot loading is not available", .{});
|
|
return;
|
|
}
|
|
|
|
core.engine_log("[ModuleLoader]: adding module to watch '{s}'", .{moduleName});
|
|
const libFileName = try p2.sharedLibNameAlloc(self.arena.allocator(), moduleName);
|
|
const loaded = try self.arena.allocator().create(LoadedModule);
|
|
|
|
if (self.watchInitialized == false) {
|
|
core.fs().watchPath("zig-out/modules/");
|
|
self.watchInitialized = true;
|
|
}
|
|
|
|
loaded.* = LoadedModule{
|
|
.baseModulePath = try std.fmt.allocPrint(self.arena.allocator(), "zig-out/modules/{s}", .{libFileName}),
|
|
.moduleName = moduleName,
|
|
.lastModification = std.time.microTimestamp(),
|
|
};
|
|
|
|
if (@import("builtin").os.tag == .windows) {
|
|
loaded.pdbPath = try std.fmt.allocPrint(self.arena.allocator(), "zig-out/modules/{s}.pdb", .{libFileName[0 .. libFileName.len - 4]});
|
|
}
|
|
|
|
try core.fs().addFileChangedCallback(libFileName, dllChangedCallback, loaded);
|
|
|
|
try self.loadedModules.append(self.arena.allocator(), loaded);
|
|
}
|
|
|
|
pub fn activateModuleVersion(self: *@This(), mod: *LoadedModule, index: usize) void {
|
|
mod.autoLoad = false; // once we load a specific version we don't want to autoload anymore
|
|
//
|
|
if (index < mod.loaded.items.len) {
|
|
var a = self.backingAllocator;
|
|
var args = getModuleLoaderArgs(false);
|
|
if (mod.loaded.items[index].startup(&a, &args)) {
|
|
core.engine_log("module startup done", .{});
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn tick(self: *@This(), dt: f64) void {
|
|
_ = dt;
|
|
for (self.loadedModules.items) |loaded| {
|
|
const didLoad = loaded.maybeStageAndLoad(self.arena.allocator()) catch |err| {
|
|
core.engine_log("unable to load module error: {any}", .{err});
|
|
if (@errorReturnTrace()) |trace| {
|
|
std.debug.dumpStackTrace(trace.*);
|
|
}
|
|
continue;
|
|
};
|
|
|
|
if (didLoad) {
|
|
const i = loaded.getInterface();
|
|
|
|
if (i) |interface| {
|
|
if (loaded.startOnLoad) {
|
|
if (!loaded.started) {
|
|
var a = self.backingAllocator;
|
|
var args = getModuleLoaderArgs(loaded.loaded.items.len == 1);
|
|
if (interface.startup(&a, &args)) {
|
|
core.engine_log("[ModuleLoader] module startup done", .{});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn destroy(self: *@This()) void {
|
|
// std.fs.cwd().deleteTree(".modulecache") catch {};
|
|
self.arena.deinit();
|
|
self.backingAllocator.destroy(self);
|
|
}
|
|
};
|
|
|
|
pub fn getModuleLoaderArgs(first: bool) ModuleLoaderArgs {
|
|
return .{
|
|
.firstLoad = first,
|
|
.engine = core.getEngine(),
|
|
.nameRegistry = core.names.gRegistry,
|
|
.packerFS = core.fs(),
|
|
// .luaState = @ptrCast(core.script.gLuaState.l),
|
|
// .luaAllocator = &core.script.gLuaAllocator,
|
|
.debugDrawInterface = debug.gDebugDrawInterface.?,
|
|
};
|
|
}
|
|
|
|
const std = @import("std");
|
|
const core = @import("../core.zig");
|
|
const p2 = @import("p2");
|
|
const debug = @import("../debug_draw.zig");
|