1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-12-09 16:21:51 +00:00

Basic FS watcher for --load-mod mods

Add a --watch command line flag to be used in tandem with --load-mod.
Chokidar is used to detect file updates, and a page reload is triggered
when there are any changes to watched mod files. Only mods loaded via
--load-mod are affected.

This implementation has a minor issue with how cache is cleared -
removing disk cache is a bit too aggressive - but the only alternative I
could find is to use a non-persistent Electron session, which would get
rid of disk cache entirely (this is not a concern).
This commit is contained in:
Даниїл Григор'єв 2025-06-12 15:22:15 +03:00
parent 7cb3477a90
commit 6c3f91e587
No known key found for this signature in database
GPG Key ID: B890DF16341D8C1D
5 changed files with 70 additions and 2 deletions

View File

@ -9,6 +9,7 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"chokidar": "^4.0.3",
"semver": "^7.7.1",
"zod": "^3.24.2"
},
@ -192,6 +193,21 @@
"node": ">=8"
}
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"license": "MIT",
"dependencies": {
"readdirp": "^4.0.1"
},
"engines": {
"node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/clone-response": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
@ -733,6 +749,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"license": "MIT",
"engines": {
"node": ">= 14.18.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/resolve-alpn": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",

View File

@ -9,6 +9,7 @@
"start": "tsc && electron ."
},
"dependencies": {
"chokidar": "^4.0.3",
"semver": "^7.7.1",
"zod": "^3.24.2"
},

View File

@ -56,6 +56,12 @@ function createWindow() {
ipc.install(window);
window.loadURL(pageUrl);
modLoader.on("forcereload", () => {
// TODO: Find a better way to manage cache when force
// reloading (use a non-persistent session?)
window.webContents.session.clearData({ dataTypes: ["cache"] }).then(() => window.reload());
});
// Redirect any kind of main frame navigation to external applications
window.webContents.on("will-navigate", (ev, url) => {
if (url === window.webContents.getURL()) {

View File

@ -1,3 +1,4 @@
import EventEmitter from "node:events";
import fs from "node:fs/promises";
import path from "node:path";
import { DevelopmentModLocator, DistroModLocator, ModLocator, UserModLocator } from "./locator.js";
@ -48,14 +49,29 @@ class Mod {
}
}
export class ModLoader {
export class ModLoader extends EventEmitter {
private mods: Mod[] = [];
private readonly locators = new Map<ModSource, ModLocator>();
constructor() {
super();
this.locators.set("user", new UserModLocator());
this.locators.set("distro", new DistroModLocator());
this.locators.set("dev", new DevelopmentModLocator());
const devLocator = new DevelopmentModLocator();
this.locators.set("dev", devLocator);
// If requested, restart automatically when dev mods are modified
devLocator.fsWatcher?.on("all", () => this.forceReload());
}
/**
* Resets modloader state and reloads all mods, then triggers page reload.
*/
async forceReload() {
await this.loadMods();
this.emit("forcereload");
}
async loadMods(): Promise<void> {

View File

@ -1,3 +1,4 @@
import chokidar, { FSWatcher } from "chokidar";
import { app } from "electron";
import fs from "node:fs/promises";
import path from "node:path";
@ -10,6 +11,7 @@ const USER_MODS_DIR = path.join(userData, "mods");
const DISTRO_MODS_DIR = path.join(executableDir, "mods");
const DEV_SWITCH = "load-mod";
const DEV_WATCH_SWITCH = "watch";
const DEV_USER_MOD_PREFIX = "@/";
export interface ModLocator {
@ -153,6 +155,7 @@ export class DistroModLocator extends DirectoryModLocator {
export class DevelopmentModLocator implements ModLocator {
readonly priority = 0;
readonly fsWatcher: FSWatcher | null = null;
private readonly modFiles: string[] = [];
private readonly disabledMods = new Set<string>();
@ -166,6 +169,19 @@ export class DevelopmentModLocator implements ModLocator {
const resolved = switchValue.split(",").map(f => this.resolveFile(f));
this.modFiles.push(...resolved);
const watchMode = app.commandLine.hasSwitch(DEV_WATCH_SWITCH);
if (!watchMode || this.modFiles.length === 0) {
// Skip setting up chokidar
return;
}
this.fsWatcher = chokidar.watch(this.modFiles, {
persistent: false,
ignoreInitial: true,
awaitWriteFinish: true,
atomic: true,
});
}
locateMods(): Promise<string[]> {