Refactored 'workerScripts' array and killWorkerScript() fn to be their own modules in TypeScript

This commit is contained in:
danielyxie 2019-05-15 23:05:36 -07:00
parent b1248521f3
commit 42804b0cd3
16 changed files with 413 additions and 203 deletions

119
css/activescripts.scss Normal file

@ -0,0 +1,119 @@
@import "theme";
.active-scripts-list {
list-style-type: none;
}
#active-scripts-container {
position: fixed;
padding-top: 10px;
> p {
width: 70%;
margin: 6px;
padding: 4px;
}
}
.active-scripts-server-header {
background-color: #444;
font-size: $defaultFontSize * 1.25;
color: #fff;
margin: 6px 6px 0 6px;
padding: 6px;
cursor: pointer;
width: 60%;
text-align: left;
border: none;
outline: none;
}
.active-scripts-server-header.active,
.active-scripts-server-header:hover {
background-color: #555;
}
.active-scripts-server-header.active:hover {
background-color: #666;
}
.active-scripts-server-header:after {
content: '\02795'; /* "plus" sign (+) */
font-size: $defaultFontSize * 0.8125;
color: #fff;
float: right;
margin-left: 5px;
}
.active-scripts-server-header.active:after {
content: "\2796"; /* "minus" sign (-) */
font-size: $defaultFontSize * 0.8125;
color: #fff;
float: right;
margin-left: 5px;
}
.active-scripts-server-panel {
margin: 0 6px 6px 6px;
padding: 0 6px 6px 6px;
width: 55%;
margin-left: 5%;
display: none;
}
.active-scripts-server-panel div,
.active-scripts-server-panel ul,
.active-scripts-server-panel ul > li {
background-color: #555;
}
.active-scripts-script-header {
background-color: #555;
color: var(--my-font-color);
padding: 4px 25px 4px 10px;
cursor: pointer;
width: auto;
text-align: left;
border: none;
outline: none;
position: relative;
&:after {
content: '\02795'; /* "plus" sign (+) */
font-size: $defaultFontSize * 0.8125;
float: right;
margin-left: 5px;
color: transparent;
text-shadow: 0 0 0 var(--my-font-color);
position: absolute;
bottom: 4px;
}
&.active:after {
content: "\2796"; /* "minus" sign (-) */
}
&:hover,
&.active:hover {
background-color: #666;
}
&.active {
background-color: #555;
}
}
.active-scripts-script-panel {
padding: 0 18px;
background-color: #555;
width: auto;
display: none;
margin-bottom: 6px;
p, h2, ul, li {
background-color: #555;
width: auto;
color: #fff;
margin-left: 5%;
}
}

@ -18,126 +18,6 @@
position: fixed;
}
/* Active scripts */
.active-scripts-list {
list-style-type: none;
}
#active-scripts-container {
position: fixed;
padding-top: 10px;
}
#active-scripts-text,
#active-scripts-total-prod {
width: 70%;
margin: 6px;
padding: 4px;
}
.active-scripts-server-header {
background-color: #444;
font-size: $defaultFontSize * 1.25;
color: #fff;
margin: 6px 6px 0 6px;
padding: 6px;
cursor: pointer;
width: 60%;
text-align: left;
border: none;
outline: none;
}
.active-scripts-server-header.active,
.active-scripts-server-header:hover {
background-color: #555;
}
.active-scripts-server-header.active:hover {
background-color: #666;
}
.active-scripts-server-header:after {
content: '\02795'; /* "plus" sign (+) */
font-size: $defaultFontSize * 0.8125;
color: #fff;
float: right;
margin-left: 5px;
}
.active-scripts-server-header.active:after {
content: "\2796"; /* "minus" sign (-) */
font-size: $defaultFontSize * 0.8125;
color: #fff;
float: right;
margin-left: 5px;
}
.active-scripts-server-panel {
margin: 0 6px 6px 6px;
padding: 0 6px 6px 6px;
width: 55%;
margin-left: 5%;
display: none;
}
.active-scripts-server-panel div,
.active-scripts-server-panel ul,
.active-scripts-server-panel ul > li {
background-color: #555;
}
.active-scripts-script-header {
background-color: #555;
color: var(--my-font-color);
padding: 4px 25px 4px 10px;
cursor: pointer;
width: auto;
text-align: left;
border: none;
outline: none;
position: relative;
&:after {
content: '\02795'; /* "plus" sign (+) */
font-size: $defaultFontSize * 0.8125;
float: right;
margin-left: 5px;
color: transparent;
text-shadow: 0 0 0 var(--my-font-color);
position: absolute;
bottom: 4px;
}
&.active:after {
content: "\2796"; /* "minus" sign (-) */
}
&:hover,
&.active:hover {
background-color: #666;
}
&.active {
background-color: #555;
}
}
.active-scripts-script-panel {
padding: 0 18px;
background-color: #555;
width: auto;
display: none;
margin-bottom: 6px;
p, h2, ul, li {
background-color: #555;
width: auto;
color: #fff;
margin-left: 5%;
}
}
/* World */
#world-container {
position: fixed;

@ -32,6 +32,11 @@ export class WorkerScript {
*/
delay: number | null = null;
/**
* Holds the Promise resolve() function for when the script is "blocked" by an async op
*/
delayResolve?: () => void;
/**
* Stores names of all functions that have logging disabled
*/

@ -0,0 +1,6 @@
/**
* Global pool of all active scripts (scripts that are currently running)
*/
import { WorkerScript } from "./WorkerScript";
export const workerScripts: WorkerScript[] = [];

@ -0,0 +1,123 @@
/**
* Function that stops an active script (represented by a WorkerScript object)
* and removes it from the global pool of active scripts.
*/
import { WorkerScript } from "./WorkerScript";
import { workerScripts } from "./WorkerScripts";
import { RunningScript } from "../Script/RunningScript";
import { AllServers } from "../Server/AllServers";
import { compareArrays } from "../../utils/helpers/compareArrays";
import { roundToTwo } from "../../utils/helpers/roundToTwo";
export function killWorkerScript(runningScriptObj: RunningScript, serverIp: string): boolean;
export function killWorkerScript(workerScript: WorkerScript): boolean;
export function killWorkerScript(script: RunningScript | WorkerScript, serverIp?: string): boolean {
if (script instanceof WorkerScript) {
script.env.stopFlag = true;
killNetscriptDelay(script);
removeWorkerScript(script);
return true;
} else if (script instanceof RunningScript && typeof serverIp === "string") {
for (let i = 0; i < workerScripts.length; i++) {
if (workerScripts[i].name == script.filename && workerScripts[i].serverIp == serverIp &&
compareArrays(workerScripts[i].args, script.args)) {
workerScripts[i].env.stopFlag = true;
killNetscriptDelay(workerScripts[i]);
removeWorkerScript(workerScripts[i]);
return true;
}
}
return false;
} else {
return false;
}
}
/**
* Helper function that removes the script being killed from the global pool.
* Also handles other cleanup-time operations
*
* @param {WorkerScript | number} - Identifier for WorkerScript. Either the object itself, or
* its index in the global workerScripts array
*/
function removeWorkerScript(id: WorkerScript | number): void {
// Get a reference to the WorkerScript and its index in the global pool
let workerScript: WorkerScript;
let index: number | null = null;
if (typeof id === "number") {
if (id < 0 || id >= workerScripts.length) {
console.error(`Too high of an index passed into removeWorkerScript(): ${id}`);
return;
}
workerScript = workerScripts[id];
index = id;
} else if (id instanceof WorkerScript) {
workerScript = id;
for (let i = 0; i < workerScripts.length; ++i) {
if (workerScripts[i] == id) {
index = i;
break;
}
}
if (index == null) {
console.error(`Could not find WorkerScript in global pool:`);
console.error(workerScript);
}
} else {
console.error(`Invalid argument passed into removeWorkerScript(): ${id}`);
return;
}
const ip = workerScript.serverIp;
const name = workerScript.name;
// Get the server on which the script runs
const server = AllServers[ip];
if (server == null) {
console.error(`Could not find server on which this script is running: ${ip}`);
return;
}
// Recalculate ram used on that server
server.ramUsed = roundToTwo(server.ramUsed - workerScript.ramUsage);
if (server.ramUsed < 0) {
console.warn(`Server RAM usage went negative (if it's due to floating pt imprecision, it's okay): ${server.ramUsed}`);
server.ramUsed = 0;
}
// Delete the RunningScript object from that server
for (let i = 0; i < server.runningScripts.length; ++i) {
const runningScript = server.runningScripts[i];
if (runningScript.filename === name && compareArrays(runningScript.args, workerScript.args)) {
server.runningScripts.splice(i, 1);
break;
}
}
// Delete script from global pool (workerScripts)
workerScripts.splice(<number>index, 1);
}
/**
* Helper function that interrupts a script's delay if it is in the middle of a
* timed, blocked operation (like hack(), sleep(), etc.). This allows scripts to
* be killed immediately even if they're in the middle of one of those long operations
*/
function killNetscriptDelay(workerScript: WorkerScript) {
if (workerScript instanceof WorkerScript) {
if (workerScript.delay) {
clearTimeout(workerScript.delay);
if (workerScript.delayResolve) {
workerScript.delayResolve();
}
}
}
}

@ -1,21 +1,9 @@
import { WorkerScript } from "./Netscript/WorkerScript";
import { getServer } from "./Server/ServerHelpers";
import { setTimeoutRef } from "./utils/SetTimeoutRef";
import { parse, Node } from "../utils/acorn";
import { isValidIPAddress } from "../utils/helpers/isValidIPAddress";
import { isString } from "../utils/helpers/isString";
export function killNetscriptDelay(workerScript) {
if (workerScript instanceof WorkerScript) {
if (workerScript.delay) {
clearTimeout(workerScript.delay);
workerScript.delayResolve();
}
}
}
export function netscriptDelay(time, workerScript) {
return new Promise(function(resolve, reject) {
workerScript.delay = setTimeoutRef(() => {

@ -120,11 +120,11 @@ import {
} from "./NetscriptBladeburner";
import * as nsGang from "./NetscriptGang";
import {
workerScripts,
killWorkerScript,
NetscriptPorts,
runScriptFromScript,
} from "./NetscriptWorker";
import { killWorkerScript } from "./Netscript/killWorkerScript";
import { workerScripts } from "./Netscript/WorkerScripts";
import {
makeRuntimeRejectMsg,
netscriptDelay,

@ -3,6 +3,7 @@
* that allows for scripts to run
*/
import { WorkerScript } from "./Netscript/WorkerScript";
import { workerScripts } from "./Netscript/WorkerScripts";
import {
addActiveScriptsItem,
@ -15,7 +16,6 @@ import { Interpreter } from "./JSInterpreter";
import {
isScriptErrorMessage,
makeRuntimeRejectMsg,
killNetscriptDelay
} from "./NetscriptEvaluator";
import { NetscriptFunctions } from "./NetscriptFunctions";
import { executeJSScript } from "./NetscriptJSEvaluator";
@ -29,6 +29,7 @@ import {
} from "./Script/ScriptHelpers";
import { AllServers } from "./Server/AllServers";
import { Settings } from "./Settings/Settings";
import { EventEmitter } from "./utils/EventEmitter";
import { setTimeoutRef } from "./utils/SetTimeoutRef";
import { generate } from "escodegen";
@ -42,14 +43,15 @@ import { isString } from "../utils/StringHelperFunctions";
const walk = require("acorn/dist/walk");
//Array containing all scripts that are running across all servers, to easily run them all
export const workerScripts = [];
// Netscript Ports are instantiated here
export const NetscriptPorts = [];
for (var i = 0; i < CONSTANTS.NumNetscriptPorts; ++i) {
NetscriptPorts.push(new NetscriptPort());
}
// WorkerScript-related event emitter. Used for the UI
export const WorkerScriptEventEmitter = new EventEmitter();
export function prestigeWorkerScripts() {
for (var i = 0; i < workerScripts.length; ++i) {
deleteActiveScriptsItem(workerScripts[i]);
@ -415,46 +417,6 @@ function processNetscript1Imports(code, workerScript) {
// Loop through workerScripts and run every script that is not currently running
export function runScriptsLoop() {
let scriptDeleted = false;
// Delete any scripts that finished or have been killed. Loop backwards bc removing items screws up indexing
for (let i = workerScripts.length - 1; i >= 0; i--) {
if (workerScripts[i].running == false && workerScripts[i].env.stopFlag == true) {
scriptDeleted = true;
// Delete script from the runningScripts array on its host serverIp
const ip = workerScripts[i].serverIp;
const name = workerScripts[i].name;
// Recalculate ram used
AllServers[ip].ramUsed = 0;
for (let j = 0; j < workerScripts.length; j++) {
if (workerScripts[j].serverIp !== ip) {
continue;
}
if (j === i) { // not this one
continue;
}
AllServers[ip].ramUsed += workerScripts[j].ramUsage;
}
// Delete script from Active Scripts
deleteActiveScriptsItem(workerScripts[i]);
for (let j = 0; j < AllServers[ip].runningScripts.length; j++) {
if (AllServers[ip].runningScripts[j].filename == name &&
compareArrays(AllServers[ip].runningScripts[j].args, workerScripts[i].args)) {
AllServers[ip].runningScripts.splice(j, 1);
break;
}
}
// Delete script from workerScripts
workerScripts.splice(i, 1);
}
}
if (scriptDeleted) { updateActiveScriptsItems(); } // Force Update
// Run any scripts that haven't been started
for (let i = 0; i < workerScripts.length; i++) {
// If it isn't running, start the script
@ -520,24 +482,6 @@ export function runScriptsLoop() {
setTimeoutRef(runScriptsLoop, 3e3);
}
/**
* Queues a script to be killed by setting its stop flag to true. This
* kills and timed/blocking Netscript functions (like hack(), sleep(), etc.) and
* prevents any further execution of Netscript functions.
* The runScriptsLoop() handles the actual deletion of the WorkerScript
*/
export function killWorkerScript(runningScriptObj, serverIp) {
for (var i = 0; i < workerScripts.length; i++) {
if (workerScripts[i].name == runningScriptObj.filename && workerScripts[i].serverIp == serverIp &&
compareArrays(workerScripts[i].args, runningScriptObj.args)) {
workerScripts[i].env.stopFlag = true;
killNetscriptDelay(workerScripts[i]);
return true;
}
}
return false;
}
/**
* Given a RunningScript object, queues that script to be run
*/

@ -53,7 +53,8 @@ import {
import { showLiterature } from "./Literature";
import { Message } from "./Message/Message";
import { showMessage } from "./Message/MessageHelpers";
import { killWorkerScript, addWorkerScript } from "./NetscriptWorker";
import { addWorkerScript } from "./NetscriptWorker";
import { killWorkerScript } from "./Netscript/killWorkerScript";
import { Player } from "./Player";
import { hackWorldDaemon } from "./RedPill";
import { RunningScript } from "./Script/RunningScript";

@ -1,3 +1,8 @@
/**
* Game engine. Handles the main game loop as well as the main UI pages
*
* TODO: Separate UI functionality into its own component
*/
import {
convertTimeMsToTimeElapsedString,
replaceAt

@ -9,6 +9,7 @@ import "../css/characteroverview.scss";
import "../css/terminal.scss";
import "../css/scripteditor.scss";
import "../css/codemirror-overrides.scss";
import "../css/activescripts.scss";
import "../css/hacknetnodes.scss";
import "../css/menupages.scss";
import "../css/augmentations.scss";

@ -0,0 +1,31 @@
/**
* Root React Component for the "Active Scripts" UI page. This page displays
* and provides information about all of the player's scripts that are currently running
*/
import * as React from "react";
import { WorkerScript } from "../../Netscript/WorkerScript";
type IProps = {
workerScripts: WorkerScript[];
}
export class ActiveScriptsRoot extends React.Component<IProps, any> {
constructor(props: IProps) {
super(props);
}
render() {
return (
<>
<p>
This page displays a list of all of your scripts that are currently
running across every machine. It also provides information about each
script's production. The scripts are categorized by the hostname of
the servers on which they are running.
</p>
</>
)
}
}

@ -0,0 +1,40 @@
/**
* React Component for displaying a single WorkerScript's info as an
* Accordion element
*/
import * as React from "react";
import { Accordion } from "../React/Accordion";
import { WorkerScript } from "../../Netscript/WorkerScript";
import { arrayToString } from "../../../utils/helpers/arrayToString";
type IProps = {
workerScript: WorkerScript;
}
export function WorkerScriptAccordion(props: IProps): React.ReactElement {
return (
<Accordion
headerClass="active-scripts-script-header"
headerContent={
<>
</>
}
panelClass="active-scripts-script-panel"
panelContent={
<>
<p>
Threads: {props.workerScript.scriptRef.threads}
</p>
<p>
Args: {arrayToString(props.workerScript.args)}
</p>
</>
}
/>
)
}

@ -4,7 +4,9 @@
import * as React from "react";
type IProps = {
headerClass?: string; // Override default class
headerContent: React.ReactElement;
panelClass?: string; // Override default class
panelContent: React.ReactElement;
panelInitiallyOpened?: boolean;
}
@ -44,12 +46,21 @@ export class Accordion extends React.Component<IProps, IState> {
}
render() {
let className = "accordion-header";
if (typeof this.props.headerClass === "string") {
className = this.props.headerClass;
}
return (
<>
<button className={"accordion-header"} onClick={this.handleHeaderClick}>
<button className={className} onClick={this.handleHeaderClick}>
{this.props.headerContent}
</button>
<AccordionPanel opened={this.state.panelOpened} panelContent={this.props.panelContent} />
<AccordionPanel
opened={this.state.panelOpened}
panelClass={this.props.panelClass}
panelContent={this.props.panelContent}
/>
</>
)
}
@ -57,6 +68,7 @@ export class Accordion extends React.Component<IProps, IState> {
type IPanelProps = {
opened: boolean;
panelClass?: string; // Override default class
panelContent: React.ReactElement;
}
@ -66,8 +78,13 @@ class AccordionPanel extends React.Component<IPanelProps, any> {
}
render() {
let className = "accordion-panel"
if (typeof this.props.panelClass === "string") {
className = this.props.panelClass;
}
return (
<div className={"accordion-panel"}>
<div className={className}>
{this.props.panelContent}
</div>
)

50
src/utils/EventEmitter.ts Normal file

@ -0,0 +1,50 @@
/**
* Generic Event Emitter class following a subscribe/publish paradigm.
*/
import { IMap } from "../types";
type cbFn = (...args: any[]) => any;
export interface ISubscriber {
/**
* Callback function that will be run when an event is emitted
*/
cb: cbFn;
/**
* Name/identifier for this subscriber
*/
id: string;
}
export class EventEmitter {
/**
* Map of Subscriber name -> Callback function
*/
subscribers: IMap<cbFn> = {};
constructor(subs?: ISubscriber[]) {
if (Array.isArray(subs)) {
for (const s of subs) {
this.addSubscriber(s);
}
}
}
addSubscriber(s: ISubscriber) {
this.subscribers[s.id] = s.cb;
}
emitEvent(...args: any[]): void {
for (const s in this.subscribers) {
const cb = this.subscribers[s];
cb(args);
}
}
removeSubscriber(id: string) {
delete this.subscribers[id];
}
}

@ -1,6 +1,6 @@
import {killWorkerScript} from "../src/NetscriptWorker";
import {clearEventListeners} from "./uiHelpers/clearEventListeners";
import {arrayToString} from "./helpers/arrayToString";
import { killWorkerScript } from "../src/Netscript/killWorkerScript";
import { clearEventListeners } from "./uiHelpers/clearEventListeners";
import { arrayToString } from "./helpers/arrayToString";
$(document).keydown(function(event) {
if (logBoxOpened && event.keyCode == 27) {