From c3125d2f05032c3b342a2cdfdb771845bae5dcb8 Mon Sep 17 00:00:00 2001 From: CatLover <152669316+catloversg@users.noreply.github.com> Date: Thu, 24 Oct 2024 00:54:20 +0700 Subject: [PATCH 1/2] BUGFIX: Running scripts may be loaded before main UI --- src/NetscriptWorker.ts | 64 +++++++++++++++++++++++-------------- src/Script/ScriptHelpers.ts | 10 ++++-- src/engine.tsx | 4 +-- src/ui/GameRoot.tsx | 7 ++++ src/ui/UIEventEmitter.ts | 7 ++++ src/utils/EventEmitter.ts | 2 +- 6 files changed, 64 insertions(+), 30 deletions(-) create mode 100644 src/ui/UIEventEmitter.ts diff --git a/src/NetscriptWorker.ts b/src/NetscriptWorker.ts index ba98c13ec..72fb0f73e 100644 --- a/src/NetscriptWorker.ts +++ b/src/NetscriptWorker.ts @@ -36,6 +36,8 @@ import { CompleteRunOptions, getRunningScriptsByArgs } from "./Netscript/Netscri import { handleUnknownError } from "./Netscript/ErrorMessages"; import { isLegacyScript, legacyScriptExtension, resolveScriptFilePath, ScriptFilePath } from "./Paths/ScriptFilePath"; import { root } from "./Paths/Directory"; +import { Player } from "./Player"; +import { UIEventEmitter, UIEventType } from "./ui/UIEventEmitter"; export const NetscriptPorts = new Map(); @@ -398,33 +400,47 @@ function createAutoexec(server: BaseServer): RunningScript | null { * into worker scripts so that they will start running */ export function loadAllRunningScripts(): void { - const skipScriptLoad = window.location.href.toLowerCase().includes("?noscripts"); - if (skipScriptLoad) { - Terminal.warn("Skipped loading player scripts during startup"); - console.info("Skipping the load of any scripts during startup"); - } - for (const server of GetAllServers()) { - // Reset each server's RAM usage to 0 - server.ramUsed = 0; - - const rsList = server.savedScripts; - server.savedScripts = undefined; - if (skipScriptLoad || !rsList) { - // Start game with no scripts - continue; + /** + * While loading the save data, the game engine calls this function to load all running scripts. With each script, we + * calculate the offline data, so we need the current "lastUpdate" and "playtimeSinceLastAug" from the save data. + * After the main UI is loaded and the logic of this function starts executing, those info in the Player object might be + * overwritten, so we need to save them here and use them later in "scriptCalculateOfflineProduction". + */ + const playerLastUpdate = Player.lastUpdate; + const playerPlaytimeSinceLastAug = Player.playtimeSinceLastAug; + const unsubscribe = UIEventEmitter.subscribe((event) => { + if (event !== UIEventType.MainUILoaded) { + return; } - if (server.hostname === "home") { - // Push autoexec script onto the front of the list - const runningScript = createAutoexec(server); - if (runningScript) { - rsList.unshift(runningScript); - } + unsubscribe(); + const skipScriptLoad = window.location.href.toLowerCase().includes("?noscripts"); + if (skipScriptLoad) { + Terminal.warn("Skipped loading player scripts during startup"); + console.info("Skipping the load of any scripts during startup"); } - for (const runningScript of rsList) { - startWorkerScript(runningScript, server); - scriptCalculateOfflineProduction(runningScript); + for (const server of GetAllServers()) { + // Reset each server's RAM usage to 0 + server.ramUsed = 0; + + const rsList = server.savedScripts; + server.savedScripts = undefined; + if (skipScriptLoad || !rsList) { + // Start game with no scripts + continue; + } + if (server.hostname === "home") { + // Push autoexec script onto the front of the list + const runningScript = createAutoexec(server); + if (runningScript) { + rsList.unshift(runningScript); + } + } + for (const runningScript of rsList) { + startWorkerScript(runningScript, server); + scriptCalculateOfflineProduction(runningScript, playerLastUpdate, playerPlaytimeSinceLastAug); + } } - } + }); } /** Run a script from inside another script (run(), exec(), spawn(), etc.) */ diff --git a/src/Script/ScriptHelpers.ts b/src/Script/ScriptHelpers.ts index 5bff465e8..bf766b253 100644 --- a/src/Script/ScriptHelpers.ts +++ b/src/Script/ScriptHelpers.ts @@ -11,10 +11,14 @@ import { scriptKey } from "../utils/helpers/scriptKey"; import type { ScriptFilePath } from "../Paths/ScriptFilePath"; -export function scriptCalculateOfflineProduction(runningScript: RunningScript): void { +export function scriptCalculateOfflineProduction( + runningScript: RunningScript, + playerLastUpdate: number, + playerPlaytimeSinceLastAug: number, +): void { //The Player object stores the last update time from when we were online const thisUpdate = new Date().getTime(); - const lastUpdate = Player.lastUpdate; + const lastUpdate = playerLastUpdate; const timePassed = Math.max((thisUpdate - lastUpdate) / 1000, 0); //Seconds //Calculate the "confidence" rating of the script's true production. This is based @@ -55,7 +59,7 @@ export function scriptCalculateOfflineProduction(runningScript: RunningScript): Player.gainHackingExp(expGain); const moneyGain = - (runningScript.onlineMoneyMade / Player.playtimeSinceLastAug) * timePassed * CONSTANTS.OfflineHackingIncome; + (runningScript.onlineMoneyMade / playerPlaytimeSinceLastAug) * timePassed * CONSTANTS.OfflineHackingIncome; // money is given to player during engine load Player.scriptProdSinceLastAug += moneyGain; diff --git a/src/engine.tsx b/src/engine.tsx index 3bc0fe9f6..551f9dfab 100644 --- a/src/engine.tsx +++ b/src/engine.tsx @@ -291,10 +291,10 @@ const Engine: { const offlineHackingIncome = (Player.moneySourceA.hacking / Player.playtimeSinceLastAug) * timeOffline * CONSTANTS.OfflineHackingIncome; Player.gainMoney(offlineHackingIncome, "hacking"); - // Process offline progress loadAllRunningScripts(); // This also takes care of offline production for those scripts + // Process offline progress if (Player.currentWork !== null) { Player.focus = true; Player.processWork(numCyclesOffline); @@ -447,7 +447,7 @@ const Engine: { Engine._lastUpdate = _thisUpdate - offset; Player.lastUpdate = _thisUpdate - offset; Engine.updateGame(diff); - if (GameCycleEvents.hasSubscibers()) { + if (GameCycleEvents.hasSubscribers()) { ReactDOM.unstable_batchedUpdates(() => { GameCycleEvents.emit(); }); diff --git a/src/ui/GameRoot.tsx b/src/ui/GameRoot.tsx index 962331e9a..bfc5b6e2c 100644 --- a/src/ui/GameRoot.tsx +++ b/src/ui/GameRoot.tsx @@ -75,6 +75,7 @@ import { HistoryProvider } from "./React/Documentation"; import { GoRoot } from "../Go/ui/GoRoot"; import { Settings } from "../Settings/Settings"; import { isBitNodeFinished } from "../BitNode/BitNodeUtils"; +import { UIEventEmitter, UIEventType } from "./UIEventEmitter"; const htmlLocation = location; @@ -381,9 +382,15 @@ export function GameRoot(): React.ReactElement { mainPage = ; withSidebar = false; bypassGame = true; + break; } } + // Emit an event to notify subscribers that the main UI is loaded. + useEffect(() => { + UIEventEmitter.emit(UIEventType.MainUILoaded); + }, []); + return ( diff --git a/src/ui/UIEventEmitter.ts b/src/ui/UIEventEmitter.ts new file mode 100644 index 000000000..434228c1c --- /dev/null +++ b/src/ui/UIEventEmitter.ts @@ -0,0 +1,7 @@ +import { EventEmitter } from "../utils/EventEmitter"; + +export enum UIEventType { + MainUILoaded, +} + +export const UIEventEmitter = new EventEmitter(); diff --git a/src/utils/EventEmitter.ts b/src/utils/EventEmitter.ts index feef01a5e..3b2af8bdc 100644 --- a/src/utils/EventEmitter.ts +++ b/src/utils/EventEmitter.ts @@ -18,7 +18,7 @@ export class EventEmitter { } } - hasSubscibers(): boolean { + hasSubscribers(): boolean { return this.subscribers.size > 0; } } From 61f38c89595e4f31102f9b6d206a614856584146 Mon Sep 17 00:00:00 2001 From: CatLover <152669316+catloversg@users.noreply.github.com> Date: Thu, 24 Oct 2024 01:28:40 +0700 Subject: [PATCH 2/2] Fix Jest test --- test/jest/Save.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/jest/Save.test.ts b/test/jest/Save.test.ts index 65f4f7f38..f7b7adc46 100644 --- a/test/jest/Save.test.ts +++ b/test/jest/Save.test.ts @@ -5,6 +5,7 @@ import { loadAllRunningScripts } from "../../src/NetscriptWorker"; import { Settings } from "../../src/Settings/Settings"; import { Player, setPlayer } from "../../src/Player"; import { PlayerObject } from "../../src/PersonObjects/Player/PlayerObject"; +import { UIEventEmitter, UIEventType } from "../../src/ui/UIEventEmitter"; jest.useFakeTimers(); // Direct tests of loading and saving. @@ -146,6 +147,7 @@ function loadStandardServers() { } }`); // Fix confused highlighting ` loadAllRunningScripts(); + UIEventEmitter.emit(UIEventType.MainUILoaded); } test("load/saveAllServers", () => {