diff --git a/src/ActiveScriptsUI.js b/src/ActiveScriptsUI.js deleted file mode 100644 index 998614fb4..000000000 --- a/src/ActiveScriptsUI.js +++ /dev/null @@ -1,337 +0,0 @@ -// TODO - Convert this to React -import { workerScripts, killWorkerScript } from "./NetscriptWorker"; -import { Player } from "./Player"; -import { getServer } from "./Server/ServerHelpers"; - -import { Page, routing } from "./ui/navigationTracking"; -import { numeralWrapper } from "./ui/numeralFormat"; - -import { dialogBoxCreate } from "../utils/DialogBox"; -import { logBoxCreate } from "../utils/LogBox"; -import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions"; -import { arrayToString } from "../utils/helpers/arrayToString"; -import { createProgressBarText } from "../utils/helpers/createProgressBarText"; -import { exceptionAlert } from "../utils/helpers/exceptionAlert"; -import { roundToTwo } from "../utils/helpers/roundToTwo"; -import { createAccordionElement } from "../utils/uiHelpers/createAccordionElement"; -import { createElement } from "../utils/uiHelpers/createElement"; -import { getElementById } from "../utils/uiHelpers/getElementById"; -import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement"; -import { removeElement } from "../utils/uiHelpers/removeElement"; - - -/** - * { - * serverName: { - * header: Server Header Element - * panel: Server Panel List (ul) element - * scripts: { - * script id: Ref to Script information - * } - * } - * ... - */ -const ActiveScriptsUI = {}; -const ActiveScriptsTasks = []; // Sequentially schedule the creation/deletion of UI elements - -const getHeaderHtml = (server) => { - // TODO: calculate the longest hostname length rather than hard coding it - const longestHostnameLength = 18; - const paddedName = `${server.hostname}${" ".repeat(longestHostnameLength)}`.slice(0, Math.max(server.hostname.length, longestHostnameLength)); - const barOptions = { - progress: server.ramUsed / server.maxRam, - totalTicks: 30 - }; - return `${paddedName} ${createProgressBarText(barOptions)}`.replace(/\s/g, ' '); -}; - -const updateHeaderHtml = (server) => { - const accordion = ActiveScriptsUI[server.hostname]; - if (accordion === null || accordion === undefined) { - return; - } - - // Convert it to a string, as that's how it's stored it will come out of the data attributes - const ramPercentage = '' + roundToTwo(server.ramUsed / server.maxRam); - if (accordion.header.dataset.ramPercentage !== ramPercentage) { - accordion.header.dataset.ramPercentage = ramPercentage; - accordion.header.innerHTML = getHeaderHtml(server); - } -} - -function createActiveScriptsServerPanel(server) { - let hostname = server.hostname; - - var activeScriptsList = document.getElementById("active-scripts-list"); - - let res = createAccordionElement({ - hdrText: getHeaderHtml(server) - }); - let li = res[0]; - var hdr = res[1]; - let panel = res[2]; - - if (ActiveScriptsUI[hostname] != null) { - console.log("WARNING: Tried to create already-existing Active Scripts Server panel. This is most likely fine. It probably means many scripts just got started up on a new server. Aborting"); - return; - } - - var panelScriptList = createElement("ul"); - panel.appendChild(panelScriptList); - activeScriptsList.appendChild(li); - - ActiveScriptsUI[hostname] = { - header: hdr, - panel: panel, - panelList: panelScriptList, - scripts: {}, // Holds references to li elements for each active script - scriptHdrs: {}, // Holds references to header elements for each active script - scriptStats: {}, // Holds references to the p elements containing text for each active script - }; - - return li; -} - -/** - * Deletes the info for a particular server (Dropdown header + Panel with all info) - * in the Active Scripts page if it exists - */ -function deleteActiveScriptsServerPanel(server) { - let hostname = server.hostname; - if (ActiveScriptsUI[hostname] == null) { - console.log("WARNING: Tried to delete non-existent Active Scripts Server panel. Aborting"); - return; - } - - // Make sure it's empty - if (Object.keys(ActiveScriptsUI[hostname].scripts).length > 0) { - console.warn("Tried to delete Active Scripts Server panel that still has scripts. Aborting"); - return; - } - - removeElement(ActiveScriptsUI[hostname].panel); - removeElement(ActiveScriptsUI[hostname].header); - delete ActiveScriptsUI[hostname]; -} - -function addActiveScriptsItem(workerscript) { - var server = getServer(workerscript.serverIp); - if (server == null) { - console.warn("Invalid server IP for workerscript in addActiveScriptsItem()"); - return; - } - let hostname = server.hostname; - - ActiveScriptsTasks.push(function(workerscript, hostname) { - if (ActiveScriptsUI[hostname] == null) { - createActiveScriptsServerPanel(server); - } - - // Create the unique identifier (key) for this script - var itemNameArray = ["active", "scripts", hostname, workerscript.name]; - for (var i = 0; i < workerscript.args.length; ++i) { - itemNameArray.push(String(workerscript.args[i])); - } - var itemName = itemNameArray.join("-"); - - let res = createAccordionElement({hdrText:workerscript.name}); - let li = res[0]; - let hdr = res[1]; - let panel = res[2]; - - hdr.classList.remove("accordion-header"); - hdr.classList.add("active-scripts-script-header"); - panel.classList.remove("accordion-panel"); - panel.classList.add("active-scripts-script-panel"); - - /** - * Handle the constant elements on the panel that don't change after creation: - * Threads, args, kill/log button - */ - panel.appendChild(createElement("p", { - innerHTML: "Threads: " + workerscript.scriptRef.threads + "
" + - "Args: " + arrayToString(workerscript.args) - })); - var panelText = createElement("p", { - innerText: "Loading...", - fontSize: "14px", - }); - panel.appendChild(panelText); - panel.appendChild(createElement("br")); - panel.appendChild(createElement("span", { - innerText: "Log", - class: "accordion-button", - margin: "4px", - padding: "4px", - clickListener: () => { - logBoxCreate(workerscript.scriptRef); - return false; - } - })); - panel.appendChild(createElement("span", { - innerText: "Kill Script", - class: "accordion-button", - margin: "4px", - padding: "4px", - clickListener: () => { - killWorkerScript(workerscript.scriptRef, workerscript.scriptRef.server); - dialogBoxCreate("Killing script, may take a few minutes to complete..."); - return false; - } - })); - - // Append element to list - ActiveScriptsUI[hostname]["panelList"].appendChild(li); - ActiveScriptsUI[hostname].scripts[itemName] = li; - ActiveScriptsUI[hostname].scriptHdrs[itemName] = hdr; - ActiveScriptsUI[hostname].scriptStats[itemName] = panelText; - }.bind(null, workerscript, hostname)); -} - -function deleteActiveScriptsItem(workerscript) { - ActiveScriptsTasks.push(function(workerscript) { - var server = getServer(workerscript.serverIp); - if (server == null) { - throw new Error("ERROR: Invalid server IP for workerscript. This most likely occurred because " + - "you tried to delete a large number of scripts and also deleted servers at the " + - "same time. It's not a big deal, just save and refresh the game."); - return; - } - let hostname = server.hostname; - if (ActiveScriptsUI[hostname] == null) { - console.log("ERROR: Trying to delete Active Script UI Element with a hostname that cant be found in ActiveScriptsUI: " + hostname); - return; - } - - var itemNameArray = ["active", "scripts", server.hostname, workerscript.name]; - for (var i = 0; i < workerscript.args.length; ++i) { - itemNameArray.push(String(workerscript.args[i])); - } - var itemName = itemNameArray.join("-"); - - let li = ActiveScriptsUI[hostname].scripts[itemName]; - if (li == null) { - console.log("ERROR: Cannot find Active Script UI element for workerscript: "); - console.log(workerscript); - return; - } - removeElement(li); - delete ActiveScriptsUI[hostname].scripts[itemName]; - delete ActiveScriptsUI[hostname].scriptHdrs[itemName]; - delete ActiveScriptsUI[hostname].scriptStats[itemName]; - if (Object.keys(ActiveScriptsUI[hostname].scripts).length === 0) { - deleteActiveScriptsServerPanel(server); - } - }.bind(null, workerscript)); -} - -function updateActiveScriptsItems(maxTasks=150) { - /** - * Run tasks that need to be done sequentially (adding items, creating/deleting server panels) - * We'll limit this to 150 at a time for performance (in case someone decides to start a - * bunch of scripts all at once...) - */ - const numTasks = Math.min(maxTasks, ActiveScriptsTasks.length); - for (let i = 0; i < numTasks; ++i) { - let task = ActiveScriptsTasks.shift(); - try { - task(); - } catch(e) { - exceptionAlert(e); - console.log(task); - } - } - - let total = 0; - for (var i = 0; i < workerScripts.length; ++i) { - try { - total += updateActiveScriptsItemContent(workerScripts[i]); - } catch(e) { - exceptionAlert(e); - } - } - - if (!routing.isOn(Page.ActiveScripts)) { return total; } - getElementById("active-scripts-total-production-active").innerText = numeralWrapper.formatMoney(total); - getElementById("active-scripts-total-prod-aug-total").innerText = numeralWrapper.formatMoney(Player.scriptProdSinceLastAug); - getElementById("active-scripts-total-prod-aug-avg").innerText = numeralWrapper.formatMoney(Player.scriptProdSinceLastAug / (Player.playtimeSinceLastAug/1000)); - - return total; -} - -function updateActiveScriptsItemContent(workerscript) { - var server = getServer(workerscript.serverIp); - if (server == null) { - console.log("ERROR: Invalid server IP for workerscript in updateActiveScriptsItemContent()."); - return; - } - let hostname = server.hostname; - if (ActiveScriptsUI[hostname] == null) { - return; // Hasn't been created yet. We'll skip it - } - - updateHeaderHtml(server); - - var itemNameArray = ["active", "scripts", server.hostname, workerscript.name]; - for (var i = 0; i < workerscript.args.length; ++i) { - itemNameArray.push(String(workerscript.args[i])); - } - var itemName = itemNameArray.join("-"); - - if (ActiveScriptsUI[hostname].scriptStats[itemName] == null) { - return; // Hasn't been fully added yet. We'll skip it - } - var item = ActiveScriptsUI[hostname].scriptStats[itemName]; - - // Update the text if necessary. This fn returns the online $/s production - return updateActiveScriptsText(workerscript, item, itemName); -} - -function updateActiveScriptsText(workerscript, item, itemName) { - var server = getServer(workerscript.serverIp); - if (server == null) { - console.log("ERROR: Invalid server IP for workerscript for updateActiveScriptsText()"); - return; - } - let hostname = server.hostname; - if (ActiveScriptsUI[hostname] == null || ActiveScriptsUI[hostname].scriptHdrs[itemName] == null) { - console.log("ERROR: Trying to update Active Script UI Element with a hostname that cant be found in ActiveScriptsUI: " + hostname); - return; - } - - updateHeaderHtml(server); - var onlineMps = workerscript.scriptRef.onlineMoneyMade / workerscript.scriptRef.onlineRunningTime; - - // Only update if the item is visible - if (ActiveScriptsUI[hostname].header.classList.contains("active") === false) {return onlineMps;} - if (ActiveScriptsUI[hostname].scriptHdrs[itemName].classList.contains("active") === false) {return onlineMps;} - - removeChildrenFromElement(item); - - var onlineTime = "Online Time: " + convertTimeMsToTimeElapsedString(workerscript.scriptRef.onlineRunningTime * 1e3); - var offlineTime = "Offline Time: " + convertTimeMsToTimeElapsedString(workerscript.scriptRef.offlineRunningTime * 1e3); - - // Online - var onlineTotalMoneyMade = "Total online production: " + numeralWrapper.formatMoney(workerscript.scriptRef.onlineMoneyMade); - var onlineTotalExpEarned = (Array(26).join(" ") + numeralWrapper.formatBigNumber(workerscript.scriptRef.onlineExpGained) + " hacking exp").replace( / /g, " "); - - var onlineMpsText = "Online production rate: " + numeralWrapper.formatMoney(onlineMps) + " / second"; - var onlineEps = workerscript.scriptRef.onlineExpGained / workerscript.scriptRef.onlineRunningTime; - var onlineEpsText = (Array(25).join(" ") + numeralWrapper.formatBigNumber(onlineEps) + " hacking exp / second").replace( / /g, " "); - - // Offline - var offlineTotalMoneyMade = "Total offline production: " + numeralWrapper.formatMoney(workerscript.scriptRef.offlineMoneyMade); - var offlineTotalExpEarned = (Array(27).join(" ") + numeralWrapper.formatBigNumber(workerscript.scriptRef.offlineExpGained) + " hacking exp").replace( / /g, " "); - - var offlineMps = workerscript.scriptRef.offlineMoneyMade / workerscript.scriptRef.offlineRunningTime; - var offlineMpsText = "Offline production rate: " + numeralWrapper.formatMoney(offlineMps) + " / second"; - var offlineEps = workerscript.scriptRef.offlineExpGained / workerscript.scriptRef.offlineRunningTime; - var offlineEpsText = (Array(26).join(" ") + numeralWrapper.formatBigNumber(offlineEps) + " hacking exp / second").replace( / /g, " "); - - item.innerHTML = onlineTime + "
" + offlineTime + "
" + onlineTotalMoneyMade + "
" + onlineTotalExpEarned + "
" + - onlineMpsText + "
" + onlineEpsText + "
" + offlineTotalMoneyMade + "
" + offlineTotalExpEarned + "
" + - offlineMpsText + "
" + offlineEpsText + "
"; - return onlineMps; -} - -export {addActiveScriptsItem, deleteActiveScriptsItem, updateActiveScriptsItems}; diff --git a/src/Netscript/killWorkerScript.ts b/src/Netscript/killWorkerScript.ts index ee60576f8..4c8c3561d 100644 --- a/src/Netscript/killWorkerScript.ts +++ b/src/Netscript/killWorkerScript.ts @@ -1,5 +1,5 @@ /** - * Function that stops an active script (represented by a WorkerScript object) + * Stops an actively-running script (represented by a WorkerScript object) * and removes it from the global pool of active scripts. */ import { WorkerScript } from "./WorkerScript"; @@ -34,6 +34,8 @@ export function killWorkerScript(script: RunningScript | WorkerScript, serverIp? return false; } else { + console.error(`killWorkerScript() called with invalid argument:`); + console.error(script); return false; } } diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index e41ff542c..394dd0156 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -3,7 +3,6 @@ const vsprintf = require("sprintf-js").vsprintf; import { getRamCost } from "./Netscript/RamCostGenerator"; -import { updateActiveScriptsItems } from "./ActiveScriptsUI"; import { Augmentation } from "./Augmentation/Augmentation"; import { Augmentations } from "./Augmentation/Augmentations"; import { diff --git a/src/NetscriptWorker.js b/src/NetscriptWorker.js index e5548ee57..38da9c654 100644 --- a/src/NetscriptWorker.js +++ b/src/NetscriptWorker.js @@ -2,14 +2,10 @@ * Functions for handling WorkerScripts, which are the underlying mechanism * that allows for scripts to run */ +import { killWorkerScript } from "./Netscript/killWorkerScript"; import { WorkerScript } from "./Netscript/WorkerScript"; import { workerScripts } from "./Netscript/WorkerScripts"; -import { - addActiveScriptsItem, - deleteActiveScriptsItem, - updateActiveScriptsItems -} from "./ActiveScriptsUI"; import { CONSTANTS } from "./Constants"; import { Engine } from "./engine"; import { Interpreter } from "./JSInterpreter"; @@ -54,10 +50,9 @@ export const WorkerScriptEventEmitter = new EventEmitter(); export function prestigeWorkerScripts() { for (var i = 0; i < workerScripts.length; ++i) { - deleteActiveScriptsItem(workerScripts[i]); + // TODO Signal event emitter workerScripts[i].env.stopFlag = true; } - updateActiveScriptsItems(5000); //Force UI to update workerScripts.length = 0; } @@ -415,106 +410,101 @@ function processNetscript1Imports(code, workerScript) { return res; } -// Loop through workerScripts and run every script that is not currently running -export function runScriptsLoop() { - // Run any scripts that haven't been started - for (let i = 0; i < workerScripts.length; i++) { - // If it isn't running, start the script - if (workerScripts[i].running == false && workerScripts[i].env.stopFlag == false) { - let p = null; // p is the script's result promise. - if (workerScripts[i].name.endsWith(".js") || workerScripts[i].name.endsWith(".ns")) { - p = startNetscript2Script(workerScripts[i]); - } else { - p = startNetscript1Script(workerScripts[i]); - if (!(p instanceof Promise)) { continue; } - } - - // Once the code finishes (either resolved or rejected, doesnt matter), set its - // running status to false - p.then(function(w) { - console.log("Stopping script " + w.name + " because it finished running naturally"); - w.running = false; - w.env.stopFlag = true; - w.scriptRef.log("Script finished running"); - }).catch(function(w) { - if (w instanceof Error) { - dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer"); - console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w.toString()); - return; - } else if (w.constructor === Array && w.length === 2 && w[0] === "RETURNSTATEMENT") { - // Script ends with a return statement - console.log("Script returning with value: " + w[1]); - // TODO maybe do something with this in the future - return; - } else if (w instanceof WorkerScript) { - if (isScriptErrorMessage(w.errorMessage)) { - var errorTextArray = w.errorMessage.split("|"); - if (errorTextArray.length != 4) { - console.log("ERROR: Something wrong with Error text in evaluator..."); - console.log("Error text: " + errorText); - return; - } - var serverIp = errorTextArray[1]; - var scriptName = errorTextArray[2]; - var errorMsg = errorTextArray[3]; - - dialogBoxCreate("Script runtime error:
Server Ip: " + serverIp + - "
Script name: " + scriptName + - "
Args:" + arrayToString(w.args) + "
" + errorMsg); - w.scriptRef.log("Script crashed with runtime error"); - } else { - w.scriptRef.log("Script killed"); - } - w.running = false; - w.env.stopFlag = true; - } else if (isScriptErrorMessage(w)) { - dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer"); - console.log("ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " + w.toString()); - return; - } else { - dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev"); - console.log(w); - } - }); - } - } - - setTimeoutRef(runScriptsLoop, 3e3); -} - /** - * Given a RunningScript object, queues that script to be run + * Start a script + * + * Given a RunningScript object, constructs a corresponding WorkerScript, + * adds it to the global 'workerScripts' pool, and begins executing it. + * @param {RunningScript} runningScriptObj - Script that's being run + * @param {Server} server - Server on which the script is to be run */ export function addWorkerScript(runningScriptObj, server) { - var filename = runningScriptObj.filename; + const filename = runningScriptObj.filename; - //Update server's ram usage - var threads = 1; + // Update server's ram usage + let threads = 1; if (runningScriptObj.threads && !isNaN(runningScriptObj.threads)) { threads = runningScriptObj.threads; } else { runningScriptObj.threads = 1; } - var ramUsage = roundToTwo(getRamUsageFromRunningScript(runningScriptObj) * threads); - var ramAvailable = server.maxRam - server.ramUsed; + const ramUsage = roundToTwo(getRamUsageFromRunningScript(runningScriptObj) * threads); + const ramAvailable = server.maxRam - server.ramUsed; if (ramUsage > ramAvailable) { - dialogBoxCreate("Not enough RAM to run script " + runningScriptObj.filename + " with args " + - arrayToString(runningScriptObj.args) + ". This likely occurred because you re-loaded " + - "the game and the script's RAM usage increased (either because of an update to the game or " + - "your changes to the script.)"); + dialogBoxCreate( + `Not enough RAM to run script ${runningScriptObj.filename} with args ` + + `${arrayToString(runningScriptObj.args)}. This likely occurred because you re-loaded ` + + `the game and the script's RAM usage increased (either because of an update to the game or ` + + `your changes to the script.)` + ); return; } server.ramUsed = roundToTwo(server.ramUsed + ramUsage); - //Create the WorkerScript - var s = new WorkerScript(runningScriptObj, NetscriptFunctions); + // Create the WorkerScript + const s = new WorkerScript(runningScriptObj, NetscriptFunctions); s.ramUsage = ramUsage; - //Add the WorkerScript to the Active Scripts list - addActiveScriptsItem(s); + // Start the script's execution + let p = null; // Script's resulting promise + if (s.name.endsWith(".js") || s.name.endsWith(".ns")) { + p = startNetscript2Script(workerScripts[i]); + } else { + p = startNetscript1Script(workerScripts[i]); + if (!(p instanceof Promise)) { return; } + } - //Add the WorkerScript - workerScripts.push(s); + // Once the code finishes (either resolved or rejected, doesnt matter), set its + // running status to false + p.then(function(w) { + console.log("Stopping script " + w.name + " because it finished running naturally"); + killWorkerScript(s); + w.scriptRef.log("Script finished running"); + }).catch(function(w) { + if (w instanceof Error) { + dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer"); + console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w.toString()); + return; + } else if (w.constructor === Array && w.length === 2 && w[0] === "RETURNSTATEMENT") { + // Script ends with a return statement + console.log("Script returning with value: " + w[1]); + // TODO maybe do something with this in the future + return; + } else if (w instanceof WorkerScript) { + if (isScriptErrorMessage(w.errorMessage)) { + var errorTextArray = w.errorMessage.split("|"); + if (errorTextArray.length != 4) { + console.log("ERROR: Something wrong with Error text in evaluator..."); + console.log("Error text: " + errorText); + return; + } + var serverIp = errorTextArray[1]; + var scriptName = errorTextArray[2]; + var errorMsg = errorTextArray[3]; + + dialogBoxCreate("Script runtime error:
Server Ip: " + serverIp + + "
Script name: " + scriptName + + "
Args:" + arrayToString(w.args) + "
" + errorMsg); + w.scriptRef.log("Script crashed with runtime error"); + } else { + w.scriptRef.log("Script killed"); + } + w.running = false; + w.env.stopFlag = true; + } else if (isScriptErrorMessage(w)) { + dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer"); + console.log("ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " + w.toString()); + return; + } else { + dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev"); + console.log(w); + } + + killWorkerScript(s); + }); + + // Add the WorkerScript to the global pool + workerScripts.push(s); return; } diff --git a/src/Prestige.js b/src/Prestige.js index 6e3dc4947..ae875e366 100755 --- a/src/Prestige.js +++ b/src/Prestige.js @@ -1,4 +1,3 @@ -import { deleteActiveScriptsItem } from "./ActiveScriptsUI"; import { Augmentations } from "./Augmentation/Augmentations"; import { augmentationExists, diff --git a/src/engine.jsx b/src/engine.jsx index 28518e377..9ed84ce4f 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -8,7 +8,6 @@ import { replaceAt } from "../utils/StringHelperFunctions"; import { logBoxUpdateText, logBoxOpened } from "../utils/LogBox"; -import { updateActiveScriptsItems } from "./ActiveScriptsUI"; import { Augmentations } from "./Augmentation/Augmentations"; import { initAugmentations, @@ -48,7 +47,6 @@ import { checkForMessagesToSend, initMessages } from "./Message/MessageHelpers"; import { inMission, currMission } from "./Missions"; import { loadAllRunningScripts, - runScriptsLoop, updateOnlineScriptTimes, } from "./NetscriptWorker"; import { Player } from "./Player"; @@ -1520,9 +1518,6 @@ const Engine = { start: function() { // Run main loop Engine.idleTimer(); - - // Script-processing loop - runScriptsLoop(); } }; diff --git a/src/ui/ActiveScripts/Root.tsx b/src/ui/ActiveScripts/Root.tsx index aecacb192..193363017 100644 --- a/src/ui/ActiveScripts/Root.tsx +++ b/src/ui/ActiveScripts/Root.tsx @@ -4,9 +4,14 @@ */ import * as React from "react"; +import { ScriptProduction } from "./ScriptProduction"; +import { ServerAccordions } from "./ServerAccordions"; + import { WorkerScript } from "../../Netscript/WorkerScript"; +import { IPlayer } from "../../PersonObjects/IPlayer"; type IProps = { + p: IPlayer; workerScripts: WorkerScript[]; } @@ -25,6 +30,8 @@ export class ActiveScriptsRoot extends React.Component { the servers on which they are running.

+ + ) } diff --git a/src/ui/ActiveScripts/ScriptProduction.tsx b/src/ui/ActiveScripts/ScriptProduction.tsx new file mode 100644 index 000000000..a348b82cb --- /dev/null +++ b/src/ui/ActiveScripts/ScriptProduction.tsx @@ -0,0 +1,45 @@ +/** + * React Component for displaying the total production and production rate + * of scripts on the 'Active Scripts' UI page + */ +import * as React from "react"; + +import { numeralWrapper } from "../numeralFormat"; + +import { WorkerScript } from "../../Netscript/WorkerScript"; +import { IPlayer } from "../../PersonObjects/IPlayer"; + +type IProps = { + p: IPlayer; + workerScripts: WorkerScript[]; +} + +export function ScriptProduction(props: IProps): React.ReactElement { + const prodRateSinceLastAug = props.p.scriptProdSinceLastAug / (props.p.playtimeSinceLastAug / 1000); + + let onlineProduction = 0; + for (const ws of props.workerScripts) { + onlineProduction += (ws.scriptRef.onlineMoneyMade / ws.scriptRef.onlineRunningTime); + } + + return ( +

+ Total online production of Active scripts: + + + {numeralWrapper.formatMoney(onlineProduction)} + / sec +
+ + Total online production since last Aug installation: + + {numeralWrapper.formatMoney(props.p.scriptProdSinceLastAug)} + + ( + + {numeralWrapper.formatMoney(prodRateSinceLastAug)} + / sec + ) +

+ ) +} diff --git a/src/ui/ActiveScripts/ServerAccordion.tsx b/src/ui/ActiveScripts/ServerAccordion.tsx new file mode 100644 index 000000000..9f49a88f3 --- /dev/null +++ b/src/ui/ActiveScripts/ServerAccordion.tsx @@ -0,0 +1,49 @@ +/** + * React Component for rendering the Accordion element for a single + * server in the 'Active Scripts' UI page + */ +import * as React from "react"; + +import { WorkerScriptAccordion } from "./WorkerScriptAccordion"; +import { Accordion } from "../React/Accordion"; + +import { BaseServer } from "../../Server/BaseServer"; +import { WorkerScript } from "../../Netscript/WorkerScript"; + +import { createProgressBarText } from "../../../utils/helpers/createProgressBarText"; + +type IProps = { + server: BaseServer; + workerScripts: WorkerScript[]; +} + +export function ServerAccordion(props: IProps): React.ReactElement { + const server = props.server; + + // Accordion's header text + // TODO: calculate the longest hostname length rather than hard coding it + const longestHostnameLength = 18; + const paddedName = `${server.hostname}${" ".repeat(longestHostnameLength)}`.slice(0, Math.max(server.hostname.length, longestHostnameLength)); + const barOptions = { + progress: server.ramUsed / server.maxRam, + totalTicks: 30 + }; + const headerTxt = `${paddedName} ${createProgressBarText(barOptions)}`.replace(/\s/g, ' '); + + const scripts = props.workerScripts.map((ws) => { + return ( + + ) + }); + + return ( + {headerTxt}

+ } + panelContent={ +
    {scripts}
+ } + /> + ) +} diff --git a/src/ui/ActiveScripts/ServerAccordions.tsx b/src/ui/ActiveScripts/ServerAccordions.tsx new file mode 100644 index 000000000..1a0dbcd9c --- /dev/null +++ b/src/ui/ActiveScripts/ServerAccordions.tsx @@ -0,0 +1,81 @@ +/** + * React Component for rendering the Accordion elements for all servers + * on which scripts are running + */ +import * as React from "react"; + +import { ServerAccordion } from "./ServerAccordion"; + +import { getServer } from "../../Server/ServerHelpers"; +import { BaseServer } from "../../Server/BaseServer"; +import { WorkerScript } from "../../Netscript/WorkerScript"; + +// Map of server hostname -> all workerscripts on that server for all active scripts +interface IServerData { + server: BaseServer; + workerScripts: WorkerScript[]; +} + +interface IServerToScriptsMap { + [key: string]: IServerData; +} + +type IProps = { + workerScripts: WorkerScript[]; +}; + +export class ServerAccordions extends React.Component { + serverToScriptMap: IServerToScriptsMap = {}; + + constructor(props: IProps) { + super(props); + + this.updateServerToScriptsMap(); + + // TODO + // We subscribe to an event emitter that publishes whenever a script is + // started/stopped. This allows us to only update the map when necessary + } + + updateServerToScriptsMap(): void { + const map: IServerToScriptsMap = {}; + + for (const ws of this.props.workerScripts) { + const server = getServer(ws.serverIp); + if (server == null) { + console.warn(`WorkerScript has invalid IP address: ${ws.serverIp}`); + continue; + } + + if (map[server.hostname] == null) { + map[server.hostname] = { + server: server, + workerScripts: [], + }; + } + + map[server.hostname].workerScripts.push(ws); + } + + this.serverToScriptMap = map; + } + + render() { + const elems = Object.keys(this.serverToScriptMap).map((serverName) => { + const data = this.serverToScriptMap[serverName]; + return ( + + ) + }); + + return ( +
    + {elems} +
+ ) + } +} diff --git a/src/ui/ActiveScripts/WorkerScriptAccordion.tsx b/src/ui/ActiveScripts/WorkerScriptAccordion.tsx index 117c2b045..2f346220b 100644 --- a/src/ui/ActiveScripts/WorkerScriptAccordion.tsx +++ b/src/ui/ActiveScripts/WorkerScriptAccordion.tsx @@ -4,10 +4,16 @@ */ import * as React from "react"; -import { Accordion } from "../React/Accordion"; +import { numeralWrapper } from "../numeralFormat"; +import { Accordion } from "../React/Accordion"; +import { AccordionButton } from "../React/AccordionButton"; + +import { killWorkerScript } from "../../Netscript/killWorkerScript"; import { WorkerScript } from "../../Netscript/WorkerScript"; +import { logBoxCreate } from "../../../utils/LogBox"; +import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions"; import { arrayToString } from "../../../utils/helpers/arrayToString"; type IProps = { @@ -15,7 +21,17 @@ type IProps = { } export function WorkerScriptAccordion(props: IProps): React.ReactElement { + const workerScript = props.workerScript; + const scriptRef = workerScript.scriptRef; + const logClickHandler = logBoxCreate.bind(null, scriptRef); + const killScriptButton = killWorkerScript.bind(null, scriptRef, scriptRef.server); + + // Calculations for script stats + const onlineMps = scriptRef.onlineMoneyMade / scriptRef.onlineRunningTime; + const onlineEps = scriptRef.onlineExpGained / scriptRef.onlineRunningTime; + const offlineMps = scriptRef.offlineMoneyMade / scriptRef.offlineRunningTime; + const offlineEps = scriptRef.offlineExpGained / scriptRef.offlineRunningTime; return ( -

- Threads: {props.workerScript.scriptRef.threads} -

-

- Args: {arrayToString(props.workerScript.args)} -

+

Threads: {props.workerScript.scriptRef.threads}

+

Args: {arrayToString(props.workerScript.args)}

+

Online Time: {convertTimeMsToTimeElapsedString(scriptRef.onlineRunningTime * 1e3)}

+

Offline Time: {convertTimeMsToTimeElapsedString(scriptRef.offlineRunningTime * 1e3)}

+

Total online production: {numeralWrapper.formatMoney(scriptRef.onlineMoneyMade)}

+

{(Array(26).join(" ") + numeralWrapper.formatBigNumber(scriptRef.onlineExpGained) + " hacking exp").replace( / /g, " ")}

+

Online production rate: {numeralWrapper.formatMoney(onlineMps)} / second

+

{(Array(25).join(" ") + numeralWrapper.formatBigNumber(onlineEps) + " hacking exp / second").replace( / /g, " ")}

+

Total offline production: {numeralWrapper.formatMoney(scriptRef.offlineMoneyMade)}

+

{(Array(27).join(" ") + numeralWrapper.formatBigNumber(scriptRef.offlineExpGained) + " hacking exp").replace( / /g, " ")}

+

Offline production rate: {numeralWrapper.formatMoney(offlineMps)} / second

+

{(Array(26).join(" ") + numeralWrapper.formatBigNumber(offlineEps) + " hacking exp / second").replace( / /g, " ")}

+ + + } /> diff --git a/src/ui/React/AccordionButton.tsx b/src/ui/React/AccordionButton.tsx new file mode 100644 index 000000000..f60c6b821 --- /dev/null +++ b/src/ui/React/AccordionButton.tsx @@ -0,0 +1,52 @@ +/** + * Basic stateless button that uses the 'accordion-button' css class. + * This class has a black background so that it does not clash with the default + * accordion coloring + */ +import * as React from "react"; + +interface IProps { + addClasses?: string; + disabled?: boolean; + id?: string; + onClick?: (e: React.MouseEvent) => any; + style?: object; + text: string; + tooltip?: string; +} + +type IInnerHTMLMarkup = { + __html: string; +} + +export function AccordionButton(props: IProps): React.ReactElement { + const hasTooltip = props.tooltip != null && props.tooltip !== ""; + + // TODO Add a disabled class for accordion buttons? + let className = "accordion-button"; + if (hasTooltip) { + className += " tooltip"; + } + + if (typeof props.addClasses === "string") { + className += ` ${props.addClasses}`; + } + + // Tooltip will be set using inner HTML + let tooltipMarkup: IInnerHTMLMarkup | null; + if (hasTooltip) { + tooltipMarkup = { + __html: props.tooltip! + } + } + + return ( + + ) +} diff --git a/utils/LogBox.js b/utils/LogBox.js deleted file mode 100644 index cedbeb967..000000000 --- a/utils/LogBox.js +++ /dev/null @@ -1,68 +0,0 @@ -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) { - logBoxClose(); - } -}); - -function logBoxInit() { - var closeButton = document.getElementById("log-box-close"); - logBoxClose(); - - //Close Dialog box - closeButton.addEventListener("click", function() { - logBoxClose(); - return false; - }); - document.getElementById("log-box-text-header").style.display = "inline-block"; -}; - -document.addEventListener("DOMContentLoaded", logBoxInit, false); - -function logBoxClose() { - logBoxOpened = false; - var logBox = document.getElementById("log-box-container"); - logBox.style.display = "none"; -} - -function logBoxOpen() { - logBoxOpened = true; - - var logBox = document.getElementById("log-box-container"); - logBox.style.display = "block"; -} - - -var logBoxOpened = false; -var logBoxCurrentScript = null; -function logBoxCreate(script) { - logBoxCurrentScript = script; - var killScriptBtn = clearEventListeners("log-box-kill-script"); - killScriptBtn.addEventListener("click", ()=>{ - killWorkerScript(script, script.server); - return false; - }); - document.getElementById('log-box-kill-script').style.display = "inline-block"; - logBoxOpen(); - document.getElementById("log-box-text-header").innerHTML = - logBoxCurrentScript.filename + " " + arrayToString(logBoxCurrentScript.args) + ":

"; - logBoxCurrentScript.logUpd = true; - logBoxUpdateText(); -} - -function logBoxUpdateText() { - var txt = document.getElementById("log-box-text"); - if (logBoxCurrentScript && logBoxOpened && txt && logBoxCurrentScript.logUpd) { - txt.innerHTML = ""; - for (var i = 0; i < logBoxCurrentScript.logs.length; ++i) { - txt.innerHTML += logBoxCurrentScript.logs[i]; - txt.innerHTML += "
"; - } - logBoxCurrentScript.logUpd = false; - } -} - -export {logBoxCreate, logBoxUpdateText, logBoxOpened, logBoxCurrentScript}; diff --git a/utils/LogBox.ts b/utils/LogBox.ts new file mode 100644 index 000000000..c22542958 --- /dev/null +++ b/utils/LogBox.ts @@ -0,0 +1,105 @@ +import { killWorkerScript } from "../src/Netscript/killWorkerScript"; +import { RunningScript } from "../src/Script/RunningScript"; + +import { clearEventListeners } from "./uiHelpers/clearEventListeners"; +import { arrayToString } from "./helpers/arrayToString"; + +import { KEY } from "./helpers/keyCodes"; + +document.addEventListener("keydown", function(event: KeyboardEvent) { + if (logBoxOpened && event.keyCode == KEY.ESC) { + logBoxClose(); + } +}); + +let logBoxContainer: HTMLElement | null; +let textHeader: HTMLElement | null; +let logText: HTMLElement | null; + +function logBoxInit(): void { + // Initialize Close button click listener + const closeButton = document.getElementById("log-box-close"); + if (closeButton == null) { + console.error(`Could not find LogBox's close button`); + return; + } + logBoxClose(); + + closeButton.addEventListener("click", function() { + logBoxClose(); + return false; + }); + + // Initialize text header + textHeader = document.getElementById("log-box-text-header"); + if (textHeader instanceof HTMLElement) { + textHeader.style.display = "inline-block"; + } + + // Initialize references to other DOM elements + logBoxContainer = document.getElementById("log-box-container"); + logText = document.getElementById("log-box-text"); + + document.removeEventListener("DOMContentLoaded", logBoxInit); +}; + +document.addEventListener("DOMContentLoaded", logBoxInit); + +function logBoxClose() { + logBoxOpened = false; + if (logBoxContainer instanceof HTMLElement) { + logBoxContainer.style.display = "none"; + } +} + +function logBoxOpen() { + logBoxOpened = true; + + if (logBoxContainer instanceof HTMLElement) { + logBoxContainer.style.display = "block"; + } +} + + +export let logBoxOpened = false; +let logBoxCurrentScript: RunningScript | null = null; +export function logBoxCreate(script: RunningScript) { + logBoxCurrentScript = script; + + const killScriptBtn = clearEventListeners("log-box-kill-script"); + if (killScriptBtn == null) { + console.error(`Could not find LogBox's 'Kill Script' button`); + return; + } + + killScriptBtn.addEventListener("click", () => { + killWorkerScript(script, script.server); + return false; + }); + + killScriptBtn.style.display = "inline-block"; + + logBoxOpen(); + + if (textHeader instanceof HTMLElement) { + textHeader.innerHTML = `${logBoxCurrentScript.filename} ${arrayToString(logBoxCurrentScript.args)}:

`; + } else { + console.warn(`LogBox's Text Header DOM element is null`); + } + + logBoxCurrentScript.logUpd = true; + logBoxUpdateText(); +} + +export function logBoxUpdateText() { + if (!(logText instanceof HTMLElement)) { return; } + + if (logBoxCurrentScript && logBoxOpened && logBoxCurrentScript.logUpd) { + logText.innerHTML = ""; + for (let i = 0; i < logBoxCurrentScript.logs.length; ++i) { + logText.innerHTML += logBoxCurrentScript.logs[i]; + logText.innerHTML += "
"; + } + logBoxCurrentScript.logUpd = false; + } +}