Improve event emitter

This commit is contained in:
Olivier Gagnon 2021-09-18 15:44:39 -04:00
parent 4b6a6300f5
commit 61e3959a25
15 changed files with 46 additions and 61 deletions

@ -3,4 +3,4 @@
*/ */
import { EventEmitter } from "../utils/EventEmitter"; import { EventEmitter } from "../utils/EventEmitter";
export const WorkerScriptStartStopEventEmitter = new EventEmitter(); export const WorkerScriptStartStopEventEmitter = new EventEmitter<[]>();

@ -116,7 +116,7 @@ function removeWorkerScript(workerScript: WorkerScript, rerenderUi = true): void
} }
if (rerenderUi) { if (rerenderUi) {
WorkerScriptStartStopEventEmitter.emitEvent(); WorkerScriptStartStopEventEmitter.emit();
} }
} else { } else {
console.error(`Invalid argument passed into removeWorkerScript():`); console.error(`Invalid argument passed into removeWorkerScript():`);

@ -1359,7 +1359,7 @@ function NetscriptFunctions(workerScript) {
for (let i = server.runningScripts.length - 1; i >= 0; --i) { for (let i = server.runningScripts.length - 1; i >= 0; --i) {
killWorkerScript(server.runningScripts[i], server.ip, false); killWorkerScript(server.runningScripts[i], server.ip, false);
} }
WorkerScriptStartStopEventEmitter.emitEvent(); WorkerScriptStartStopEventEmitter.emit();
workerScript.log( workerScript.log(
"killall", "killall",
`Killing all scripts on '${server.hostname}'. May take a few minutes for the scripts to die.`, `Killing all scripts on '${server.hostname}'. May take a few minutes for the scripts to die.`,

@ -45,7 +45,7 @@ export function prestigeWorkerScripts() {
killWorkerScript(ws); killWorkerScript(ws);
} }
WorkerScriptStartStopEventEmitter.emitEvent(); WorkerScriptStartStopEventEmitter.emit();
workerScripts.clear(); workerScripts.clear();
} }
@ -501,7 +501,7 @@ export function createAndAddWorkerScript(runningScriptObj, server, parent) {
// Add the WorkerScript to the global pool // Add the WorkerScript to the global pool
workerScripts.set(pid, s); workerScripts.set(pid, s);
WorkerScriptStartStopEventEmitter.emitEvent(); WorkerScriptStartStopEventEmitter.emit();
// Start the script's execution // Start the script's execution
let p = null; // Script's resulting promise let p = null; // Script's resulting promise

@ -311,4 +311,4 @@ export function initStockMarketFnForReact(): void {
initSymbolToStockMap(); initSymbolToStockMap();
} }
export const eventEmitterForUiReset = new EventEmitter(); export const eventEmitterForUiReset = new EventEmitter<[]>();

@ -27,7 +27,7 @@ type IProps = {
buyStockLong: txFn; buyStockLong: txFn;
buyStockShort: txFn; buyStockShort: txFn;
cancelOrder: (params: any) => void; cancelOrder: (params: any) => void;
eventEmitterForReset?: EventEmitter; eventEmitterForReset?: EventEmitter<[]>;
initStockMarket: () => void; initStockMarket: () => void;
p: IPlayer; p: IPlayer;
placeOrder: placeOrderFn; placeOrder: placeOrderFn;

@ -31,7 +31,7 @@ type IProps = {
buyStockLong: txFn; buyStockLong: txFn;
buyStockShort: txFn; buyStockShort: txFn;
cancelOrder: (params: any) => void; cancelOrder: (params: any) => void;
eventEmitterForReset?: EventEmitter; eventEmitterForReset?: EventEmitter<[]>;
p: IPlayer; p: IPlayer;
placeOrder: placeOrderFn; placeOrder: placeOrderFn;
sellStockLong: txFn; sellStockLong: txFn;

@ -73,7 +73,6 @@ export interface ITerminal {
executeCommand(router: IRouter, player: IPlayer, command: string): void; executeCommand(router: IRouter, player: IPlayer, command: string): void;
executeCommands(router: IRouter, player: IPlayer, commands: string): void; executeCommands(router: IRouter, player: IPlayer, commands: string): void;
// If there was any changes, will return true, once. // If there was any changes, will return true, once.
pollChanges(): boolean;
process(router: IRouter, player: IPlayer, cycles: number): void; process(router: IRouter, player: IPlayer, cycles: number): void;
prestige(): void; prestige(): void;
getProgressText(): string; getProgressText(): string;

@ -5,6 +5,7 @@ import { HacknetServer } from "../Hacknet/HacknetServer";
import { BaseServer } from "../Server/BaseServer"; import { BaseServer } from "../Server/BaseServer";
import { Programs } from "../Programs/Programs"; import { Programs } from "../Programs/Programs";
import { CodingContractResult } from "../CodingContracts"; import { CodingContractResult } from "../CodingContracts";
import { TerminalEvents } from "./TerminalEvents";
import { TextFile } from "../TextFile"; import { TextFile } from "../TextFile";
import { Script } from "../Script/Script"; import { Script } from "../Script/Script";
@ -69,7 +70,6 @@ import { unalias } from "./commands/unalias";
import { wget } from "./commands/wget"; import { wget } from "./commands/wget";
export class Terminal implements ITerminal { export class Terminal implements ITerminal {
hasChanges = false;
// Flags to determine whether the player is currently running a hack or an analyze // Flags to determine whether the player is currently running a hack or an analyze
action: TTimer | null = null; action: TTimer | null = null;
@ -88,18 +88,10 @@ export class Terminal implements ITerminal {
process(router: IRouter, player: IPlayer, cycles: number): void { process(router: IRouter, player: IPlayer, cycles: number): void {
if (this.action === null) return; if (this.action === null) return;
this.action.timeLeft -= (CONSTANTS._idleSpeed * cycles) / 1000; this.action.timeLeft -= (CONSTANTS._idleSpeed * cycles) / 1000;
this.hasChanges = true; TerminalEvents.emit();
if (this.action.timeLeft < 0) this.finishAction(router, player, false); 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 { append(item: Output | Link): void {
this.outputHistory.push(item); this.outputHistory.push(item);
if (this.outputHistory.length > Settings.MaxTerminalCapacity) { if (this.outputHistory.length > Settings.MaxTerminalCapacity) {
@ -109,12 +101,12 @@ export class Terminal implements ITerminal {
print(s: string): void { print(s: string): void {
this.append(new Output(s, "primary")); this.append(new Output(s, "primary"));
this.hasChanges = true; TerminalEvents.emit();
} }
error(s: string): void { error(s: string): void {
this.append(new Output(s, "error")); this.append(new Output(s, "error"));
this.hasChanges = true; TerminalEvents.emit();
} }
startHack(player: IPlayer): void { startHack(player: IPlayer): void {
@ -327,7 +319,7 @@ export class Terminal implements ITerminal {
setcwd(dir: string): void { setcwd(dir: string): void {
this.currDir = dir; this.currDir = dir;
this.hasChanges = true; TerminalEvents.emit();
} }
async runContract(player: IPlayer, contractName: string): Promise<void> { async runContract(player: IPlayer, contractName: string): Promise<void> {
@ -490,7 +482,7 @@ export class Terminal implements ITerminal {
clear(): void { clear(): void {
// TODO: remove this once we figure out the height issue. // TODO: remove this once we figure out the height issue.
this.outputHistory = [new Output(`Bitburner v${CONSTANTS.Version}`, "primary")]; this.outputHistory = [new Output(`Bitburner v${CONSTANTS.Version}`, "primary")];
this.hasChanges = true; TerminalEvents.emit();
} }
prestige(): void { prestige(): void {

@ -0,0 +1,2 @@
import { EventEmitter } from "../utils/EventEmitter";
export const TerminalEvents = new EventEmitter<[]>();

@ -9,6 +9,6 @@ export function killall(terminal: ITerminal, router: IRouter, player: IPlayer, s
for (let i = server.runningScripts.length - 1; i >= 0; --i) { for (let i = server.runningScripts.length - 1; i >= 0; --i) {
killWorkerScript(server.runningScripts[i], server.ip, false); killWorkerScript(server.runningScripts[i], server.ip, false);
} }
WorkerScriptStartStopEventEmitter.emitEvent(); WorkerScriptStartStopEventEmitter.emit();
terminal.print("Killing all running scripts"); terminal.print("Killing all running scripts");
} }

@ -11,6 +11,7 @@ import { ITerminal, Output, Link } from "../ITerminal";
import { IRouter } from "../../ui/Router"; import { IRouter } from "../../ui/Router";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { TerminalInput } from "./TerminalInput"; import { TerminalInput } from "./TerminalInput";
import { TerminalEvents } from "../TerminalEvents";
interface IActionTimerProps { interface IActionTimerProps {
terminal: ITerminal; terminal: ITerminal;
@ -55,10 +56,7 @@ export function TerminalRoot({ terminal, router, player }: IProps): React.ReactE
} }
useEffect(() => { useEffect(() => {
const id = setInterval(() => { return TerminalEvents.subscribe(rerender);
if (terminal.pollChanges()) rerender();
}, 100);
return () => clearInterval(id);
}, []); }, []);
function doScroll(): void { function doScroll(): void {

@ -49,11 +49,7 @@ export function ServerAccordions(props: IProps): React.ReactElement {
} }
useEffect(() => { useEffect(() => {
WorkerScriptStartStopEventEmitter.addSubscriber({ return WorkerScriptStartStopEventEmitter.subscribe(rerender);
cb: rerender,
id: subscriberId,
});
return () => WorkerScriptStartStopEventEmitter.removeSubscriber(subscriberId);
}, []); }, []);
const handleChangePage = (event: unknown, newPage: number) => { const handleChangePage = (event: unknown, newPage: number) => {

@ -7,7 +7,7 @@ import * as React from "react";
import { EventEmitter } from "../../utils/EventEmitter"; import { EventEmitter } from "../../utils/EventEmitter";
type IProps = { type IProps = {
eventEmitterForReset?: EventEmitter; eventEmitterForReset?: EventEmitter<[]>;
id?: string; id?: string;
}; };
@ -29,6 +29,7 @@ const styleMarkup = {
}; };
export class ErrorBoundary extends React.Component<IProps, IState> { export class ErrorBoundary extends React.Component<IProps, IState> {
unsubscribe: (() => void) | null = null;
constructor(props: IProps) { constructor(props: IProps) {
super(props); super(props);
this.state = { this.state = {
@ -50,16 +51,13 @@ export class ErrorBoundary extends React.Component<IProps, IState> {
}; };
if (this.hasEventEmitter()) { if (this.hasEventEmitter()) {
(this.props.eventEmitterForReset as EventEmitter).addSubscriber({ this.unsubscribe = (this.props.eventEmitterForReset as EventEmitter<[]>).subscribe(cb);
cb: cb,
id: this.props.id as string,
});
} }
} }
componentWillUnmount(): void { componentWillUnmount(): void {
if (this.hasEventEmitter()) { if (this.unsubscribe !== null) {
(this.props.eventEmitterForReset as EventEmitter).removeSubscriber(this.props.id as string); this.unsubscribe();
} }
} }

@ -17,33 +17,33 @@ export interface ISubscriber {
id: string; id: string;
} }
export class EventEmitter { function uuidv4(): string {
/** return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
* Map of Subscriber name -> Callback function var r = (Math.random() * 16) | 0,
*/ v = c == "x" ? r : (r & 0x3) | 0x8;
subscribers: IMap<cbFn> = {}; return v.toString(16);
});
}
constructor(subs?: ISubscriber[]) { export class EventEmitter<T extends any[]> {
if (Array.isArray(subs)) { subscribers: { [key: string]: (...args: [...T]) => void | undefined } = {};
for (const s of subs) {
this.addSubscriber(s); 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 { emit(...args: [...T]): void {
this.subscribers[s.id] = s.cb;
}
emitEvent(...args: any[]): void {
for (const s in this.subscribers) { for (const s in this.subscribers) {
const sub = this.subscribers[s]; const sub = this.subscribers[s];
if (sub === undefined) continue;
sub(args); sub(...args);
} }
} }
removeSubscriber(id: string): void {
delete this.subscribers[id];
}
} }