Backlog/engine/core/src/extern/externModule.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") orelse return error.BadModule;
mod.shutdown = lib.lookup(@TypeOf(mod.shutdown), "shutdown") 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");