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:
parent
7cb3477a90
commit
6c3f91e587
29
electron/package-lock.json
generated
29
electron/package-lock.json
generated
@ -9,6 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"chokidar": "^4.0.3",
|
||||||
"semver": "^7.7.1",
|
"semver": "^7.7.1",
|
||||||
"zod": "^3.24.2"
|
"zod": "^3.24.2"
|
||||||
},
|
},
|
||||||
@ -192,6 +193,21 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/clone-response": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
|
||||||
@ -733,6 +749,19 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/resolve-alpn": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
"start": "tsc && electron ."
|
"start": "tsc && electron ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"chokidar": "^4.0.3",
|
||||||
"semver": "^7.7.1",
|
"semver": "^7.7.1",
|
||||||
"zod": "^3.24.2"
|
"zod": "^3.24.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -56,6 +56,12 @@ function createWindow() {
|
|||||||
ipc.install(window);
|
ipc.install(window);
|
||||||
window.loadURL(pageUrl);
|
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
|
// Redirect any kind of main frame navigation to external applications
|
||||||
window.webContents.on("will-navigate", (ev, url) => {
|
window.webContents.on("will-navigate", (ev, url) => {
|
||||||
if (url === window.webContents.getURL()) {
|
if (url === window.webContents.getURL()) {
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import EventEmitter from "node:events";
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { DevelopmentModLocator, DistroModLocator, ModLocator, UserModLocator } from "./locator.js";
|
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 mods: Mod[] = [];
|
||||||
private readonly locators = new Map<ModSource, ModLocator>();
|
private readonly locators = new Map<ModSource, ModLocator>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
this.locators.set("user", new UserModLocator());
|
this.locators.set("user", new UserModLocator());
|
||||||
this.locators.set("distro", new DistroModLocator());
|
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> {
|
async loadMods(): Promise<void> {
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import chokidar, { FSWatcher } from "chokidar";
|
||||||
import { app } from "electron";
|
import { app } from "electron";
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
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 DISTRO_MODS_DIR = path.join(executableDir, "mods");
|
||||||
|
|
||||||
const DEV_SWITCH = "load-mod";
|
const DEV_SWITCH = "load-mod";
|
||||||
|
const DEV_WATCH_SWITCH = "watch";
|
||||||
const DEV_USER_MOD_PREFIX = "@/";
|
const DEV_USER_MOD_PREFIX = "@/";
|
||||||
|
|
||||||
export interface ModLocator {
|
export interface ModLocator {
|
||||||
@ -153,6 +155,7 @@ export class DistroModLocator extends DirectoryModLocator {
|
|||||||
|
|
||||||
export class DevelopmentModLocator implements ModLocator {
|
export class DevelopmentModLocator implements ModLocator {
|
||||||
readonly priority = 0;
|
readonly priority = 0;
|
||||||
|
readonly fsWatcher: FSWatcher | null = null;
|
||||||
|
|
||||||
private readonly modFiles: string[] = [];
|
private readonly modFiles: string[] = [];
|
||||||
private readonly disabledMods = new Set<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));
|
const resolved = switchValue.split(",").map(f => this.resolveFile(f));
|
||||||
this.modFiles.push(...resolved);
|
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[]> {
|
locateMods(): Promise<string[]> {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user