diff --git a/src/Netscript/WorkerScriptStartStopEventEmitter.ts b/src/Netscript/WorkerScriptStartStopEventEmitter.ts index 8fdb922b8..ac1c2ad2b 100644 --- a/src/Netscript/WorkerScriptStartStopEventEmitter.ts +++ b/src/Netscript/WorkerScriptStartStopEventEmitter.ts @@ -3,4 +3,4 @@ */ import { EventEmitter } from "../utils/EventEmitter"; -export const WorkerScriptStartStopEventEmitter = new EventEmitter(); +export const WorkerScriptStartStopEventEmitter = new EventEmitter<[]>(); diff --git a/src/Netscript/killWorkerScript.ts b/src/Netscript/killWorkerScript.ts index d123e848a..e4341709a 100644 --- a/src/Netscript/killWorkerScript.ts +++ b/src/Netscript/killWorkerScript.ts @@ -116,7 +116,7 @@ function removeWorkerScript(workerScript: WorkerScript, rerenderUi = true): void } if (rerenderUi) { - WorkerScriptStartStopEventEmitter.emitEvent(); + WorkerScriptStartStopEventEmitter.emit(); } } else { console.error(`Invalid argument passed into removeWorkerScript():`); diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 1d3447c88..625bfd670 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -1359,7 +1359,7 @@ function NetscriptFunctions(workerScript) { for (let i = server.runningScripts.length - 1; i >= 0; --i) { killWorkerScript(server.runningScripts[i], server.ip, false); } - WorkerScriptStartStopEventEmitter.emitEvent(); + WorkerScriptStartStopEventEmitter.emit(); workerScript.log( "killall", `Killing all scripts on '${server.hostname}'. May take a few minutes for the scripts to die.`, diff --git a/src/NetscriptWorker.js b/src/NetscriptWorker.js index 64c7d1b83..ab3224453 100644 --- a/src/NetscriptWorker.js +++ b/src/NetscriptWorker.js @@ -45,7 +45,7 @@ export function prestigeWorkerScripts() { killWorkerScript(ws); } - WorkerScriptStartStopEventEmitter.emitEvent(); + WorkerScriptStartStopEventEmitter.emit(); workerScripts.clear(); } @@ -501,7 +501,7 @@ export function createAndAddWorkerScript(runningScriptObj, server, parent) { // Add the WorkerScript to the global pool workerScripts.set(pid, s); - WorkerScriptStartStopEventEmitter.emitEvent(); + WorkerScriptStartStopEventEmitter.emit(); // Start the script's execution let p = null; // Script's resulting promise diff --git a/src/StockMarket/StockMarket.tsx b/src/StockMarket/StockMarket.tsx index e1a5e77ad..99631b5ad 100644 --- a/src/StockMarket/StockMarket.tsx +++ b/src/StockMarket/StockMarket.tsx @@ -311,4 +311,4 @@ export function initStockMarketFnForReact(): void { initSymbolToStockMap(); } -export const eventEmitterForUiReset = new EventEmitter(); +export const eventEmitterForUiReset = new EventEmitter<[]>(); diff --git a/src/StockMarket/ui/StockMarketRoot.tsx b/src/StockMarket/ui/StockMarketRoot.tsx index 936f553cd..ef3896a70 100644 --- a/src/StockMarket/ui/StockMarketRoot.tsx +++ b/src/StockMarket/ui/StockMarketRoot.tsx @@ -27,7 +27,7 @@ type IProps = { buyStockLong: txFn; buyStockShort: txFn; cancelOrder: (params: any) => void; - eventEmitterForReset?: EventEmitter; + eventEmitterForReset?: EventEmitter<[]>; initStockMarket: () => void; p: IPlayer; placeOrder: placeOrderFn; diff --git a/src/StockMarket/ui/StockTickers.tsx b/src/StockMarket/ui/StockTickers.tsx index 546dd2072..b9c4772d2 100644 --- a/src/StockMarket/ui/StockTickers.tsx +++ b/src/StockMarket/ui/StockTickers.tsx @@ -31,7 +31,7 @@ type IProps = { buyStockLong: txFn; buyStockShort: txFn; cancelOrder: (params: any) => void; - eventEmitterForReset?: EventEmitter; + eventEmitterForReset?: EventEmitter<[]>; p: IPlayer; placeOrder: placeOrderFn; sellStockLong: txFn; diff --git a/src/Terminal/ITerminal.ts b/src/Terminal/ITerminal.ts index a9bc32f87..c7c842605 100644 --- a/src/Terminal/ITerminal.ts +++ b/src/Terminal/ITerminal.ts @@ -73,7 +73,6 @@ export interface ITerminal { executeCommand(router: IRouter, player: IPlayer, command: string): void; executeCommands(router: IRouter, player: IPlayer, commands: string): void; // If there was any changes, will return true, once. - pollChanges(): boolean; process(router: IRouter, player: IPlayer, cycles: number): void; prestige(): void; getProgressText(): string; diff --git a/src/Terminal/Terminal.ts b/src/Terminal/Terminal.ts index 042a73060..5f2890b1f 100644 --- a/src/Terminal/Terminal.ts +++ b/src/Terminal/Terminal.ts @@ -5,6 +5,7 @@ import { HacknetServer } from "../Hacknet/HacknetServer"; import { BaseServer } from "../Server/BaseServer"; import { Programs } from "../Programs/Programs"; import { CodingContractResult } from "../CodingContracts"; +import { TerminalEvents } from "./TerminalEvents"; import { TextFile } from "../TextFile"; import { Script } from "../Script/Script"; @@ -69,7 +70,6 @@ import { unalias } from "./commands/unalias"; import { wget } from "./commands/wget"; export class Terminal implements ITerminal { - hasChanges = false; // Flags to determine whether the player is currently running a hack or an analyze action: TTimer | null = null; @@ -88,18 +88,10 @@ export class Terminal implements ITerminal { process(router: IRouter, player: IPlayer, cycles: number): void { if (this.action === null) return; this.action.timeLeft -= (CONSTANTS._idleSpeed * cycles) / 1000; - this.hasChanges = true; + TerminalEvents.emit(); if (this.action.timeLeft < 0) this.finishAction(router, player, false); } - pollChanges(): boolean { - if (this.hasChanges) { - this.hasChanges = false; - return true; - } - return false; - } - append(item: Output | Link): void { this.outputHistory.push(item); if (this.outputHistory.length > Settings.MaxTerminalCapacity) { @@ -109,12 +101,12 @@ export class Terminal implements ITerminal { print(s: string): void { this.append(new Output(s, "primary")); - this.hasChanges = true; + TerminalEvents.emit(); } error(s: string): void { this.append(new Output(s, "error")); - this.hasChanges = true; + TerminalEvents.emit(); } startHack(player: IPlayer): void { @@ -327,7 +319,7 @@ export class Terminal implements ITerminal { setcwd(dir: string): void { this.currDir = dir; - this.hasChanges = true; + TerminalEvents.emit(); } async runContract(player: IPlayer, contractName: string): Promise { @@ -490,7 +482,7 @@ export class Terminal implements ITerminal { clear(): void { // TODO: remove this once we figure out the height issue. this.outputHistory = [new Output(`Bitburner v${CONSTANTS.Version}`, "primary")]; - this.hasChanges = true; + TerminalEvents.emit(); } prestige(): void { diff --git a/src/Terminal/TerminalEvents.ts b/src/Terminal/TerminalEvents.ts new file mode 100644 index 000000000..795bbbdb2 --- /dev/null +++ b/src/Terminal/TerminalEvents.ts @@ -0,0 +1,2 @@ +import { EventEmitter } from "../utils/EventEmitter"; +export const TerminalEvents = new EventEmitter<[]>(); diff --git a/src/Terminal/commands/killall.ts b/src/Terminal/commands/killall.ts index 72326c2bb..9b22b7e3e 100644 --- a/src/Terminal/commands/killall.ts +++ b/src/Terminal/commands/killall.ts @@ -9,6 +9,6 @@ export function killall(terminal: ITerminal, router: IRouter, player: IPlayer, s for (let i = server.runningScripts.length - 1; i >= 0; --i) { killWorkerScript(server.runningScripts[i], server.ip, false); } - WorkerScriptStartStopEventEmitter.emitEvent(); + WorkerScriptStartStopEventEmitter.emit(); terminal.print("Killing all running scripts"); } diff --git a/src/Terminal/ui/TerminalRoot.tsx b/src/Terminal/ui/TerminalRoot.tsx index c27f6baeb..ce9005172 100644 --- a/src/Terminal/ui/TerminalRoot.tsx +++ b/src/Terminal/ui/TerminalRoot.tsx @@ -11,6 +11,7 @@ import { ITerminal, Output, Link } from "../ITerminal"; import { IRouter } from "../../ui/Router"; import { IPlayer } from "../../PersonObjects/IPlayer"; import { TerminalInput } from "./TerminalInput"; +import { TerminalEvents } from "../TerminalEvents"; interface IActionTimerProps { terminal: ITerminal; @@ -55,10 +56,7 @@ export function TerminalRoot({ terminal, router, player }: IProps): React.ReactE } useEffect(() => { - const id = setInterval(() => { - if (terminal.pollChanges()) rerender(); - }, 100); - return () => clearInterval(id); + return TerminalEvents.subscribe(rerender); }, []); function doScroll(): void { diff --git a/src/ui/ActiveScripts/ServerAccordions.tsx b/src/ui/ActiveScripts/ServerAccordions.tsx index 6e07ff69c..ef2afc554 100644 --- a/src/ui/ActiveScripts/ServerAccordions.tsx +++ b/src/ui/ActiveScripts/ServerAccordions.tsx @@ -49,11 +49,7 @@ export function ServerAccordions(props: IProps): React.ReactElement { } useEffect(() => { - WorkerScriptStartStopEventEmitter.addSubscriber({ - cb: rerender, - id: subscriberId, - }); - return () => WorkerScriptStartStopEventEmitter.removeSubscriber(subscriberId); + return WorkerScriptStartStopEventEmitter.subscribe(rerender); }, []); const handleChangePage = (event: unknown, newPage: number) => { diff --git a/src/ui/React/ErrorBoundary.tsx b/src/ui/React/ErrorBoundary.tsx index d6f528bec..cea62c202 100644 --- a/src/ui/React/ErrorBoundary.tsx +++ b/src/ui/React/ErrorBoundary.tsx @@ -7,7 +7,7 @@ import * as React from "react"; import { EventEmitter } from "../../utils/EventEmitter"; type IProps = { - eventEmitterForReset?: EventEmitter; + eventEmitterForReset?: EventEmitter<[]>; id?: string; }; @@ -29,6 +29,7 @@ const styleMarkup = { }; export class ErrorBoundary extends React.Component { + unsubscribe: (() => void) | null = null; constructor(props: IProps) { super(props); this.state = { @@ -50,16 +51,13 @@ export class ErrorBoundary extends React.Component { }; if (this.hasEventEmitter()) { - (this.props.eventEmitterForReset as EventEmitter).addSubscriber({ - cb: cb, - id: this.props.id as string, - }); + this.unsubscribe = (this.props.eventEmitterForReset as EventEmitter<[]>).subscribe(cb); } } componentWillUnmount(): void { - if (this.hasEventEmitter()) { - (this.props.eventEmitterForReset as EventEmitter).removeSubscriber(this.props.id as string); + if (this.unsubscribe !== null) { + this.unsubscribe(); } } diff --git a/src/utils/EventEmitter.ts b/src/utils/EventEmitter.ts index e339d31f2..fd36880e0 100644 --- a/src/utils/EventEmitter.ts +++ b/src/utils/EventEmitter.ts @@ -17,33 +17,33 @@ export interface ISubscriber { id: string; } -export class EventEmitter { - /** - * Map of Subscriber name -> Callback function - */ - subscribers: IMap = {}; +function uuidv4(): string { + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { + var r = (Math.random() * 16) | 0, + v = c == "x" ? r : (r & 0x3) | 0x8; + return v.toString(16); + }); +} - constructor(subs?: ISubscriber[]) { - if (Array.isArray(subs)) { - for (const s of subs) { - this.addSubscriber(s); - } - } +export class EventEmitter { + subscribers: { [key: string]: (...args: [...T]) => void | undefined } = {}; + + subscribe(s: (...args: [...T]) => void): () => void { + let uuid = uuidv4(); + while (this.subscribers[uuid] !== undefined) uuid = uuidv4(); + this.subscribers[uuid] = s; + + return () => { + delete this.subscribers[uuid]; + }; } - addSubscriber(s: ISubscriber): void { - this.subscribers[s.id] = s.cb; - } - - emitEvent(...args: any[]): void { + emit(...args: [...T]): void { for (const s in this.subscribers) { const sub = this.subscribers[s]; + if (sub === undefined) continue; - sub(args); + sub(...args); } } - - removeSubscriber(id: string): void { - delete this.subscribers[id]; - } }