From 5798c4c7d311f8792e74b49a58963b1c173451bd Mon Sep 17 00:00:00 2001 From: Snarling <84951833+Snarling@users.noreply.github.com> Date: Sun, 28 Aug 2022 05:33:38 -0400 Subject: [PATCH] Unify error handling --- src/Augmentation/AugmentationHelpers.tsx | 6 +- src/Casino/Game.tsx | 4 +- src/Corporation/Corporation.tsx | 2 +- src/Corporation/Industry.ts | 35 +--- .../ui/modals/BribeFactionModal.tsx | 4 +- .../ui/modals/IssueNewSharesModal.tsx | 13 +- src/Corporation/ui/modals/ResearchModal.tsx | 4 +- src/Corporation/ui/modals/ThrowPartyModal.tsx | 3 +- src/Faction/FactionHelpers.tsx | 9 +- src/Locations/LocationsHelpers.tsx | 4 +- src/Locations/ui/TravelAgencyRoot.tsx | 2 +- .../{MessageHelpers.ts => MessageHelpers.tsx} | 64 ++++--- src/Netscript/APIWrapper.ts | 2 +- src/Netscript/NetscriptHelpers.ts | 53 +++--- src/Netscript/killWorkerScript.ts | 9 +- src/NetscriptJSEvaluator.ts | 9 +- src/NetscriptWorker.ts | 170 ++++-------------- src/SaveObject.tsx | 23 +-- src/Script/ScriptModule.ts | 2 +- src/UncaughtPromiseHandler.ts | 25 +-- src/ui/React/DialogBox.tsx | 30 +++- 21 files changed, 149 insertions(+), 324 deletions(-) rename src/Message/{MessageHelpers.ts => MessageHelpers.tsx} (82%) diff --git a/src/Augmentation/AugmentationHelpers.tsx b/src/Augmentation/AugmentationHelpers.tsx index a8ffa8e83..c388356f6 100644 --- a/src/Augmentation/AugmentationHelpers.tsx +++ b/src/Augmentation/AugmentationHelpers.tsx @@ -126,15 +126,15 @@ function installAugmentations(force?: boolean): boolean { if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) { level = ` - ${ownedAug.level}`; } - augmentationList += aug.name + level + "
"; + augmentationList += aug.name + level + "\n"; } Player.queuedAugmentations = []; if (!force) { dialogBoxCreate( "You slowly drift to sleep as scientists put you under in order " + - "to install the following Augmentations:
" + + "to install the following Augmentations:\n" + augmentationList + - "
You wake up in your home...you feel different...", + "\nYou wake up in your home...you feel different...", ); } prestigeAugmentation(); diff --git a/src/Casino/Game.tsx b/src/Casino/Game.tsx index 1da783d28..b20eda813 100644 --- a/src/Casino/Game.tsx +++ b/src/Casino/Game.tsx @@ -11,7 +11,7 @@ export function win(p: IPlayer, n: number): void { export function reachedLimit(p: IPlayer): boolean { const reached = p.getCasinoWinnings() > gainLimit; if (reached) { - dialogBoxCreate(<>Alright cheater get out of here. You're not allowed here anymore.); + dialogBoxCreate("Alright cheater get out of here. You're not allowed here anymore."); } return reached; } @@ -24,7 +24,7 @@ export class Game extends React.Component { reachedLimit(p: IPlayer): boolean { const reached = p.getCasinoWinnings() > gainLimit; if (reached) { - dialogBoxCreate(<>Alright cheater get out of here. You're not allowed here anymore.); + dialogBoxCreate("Alright cheater get out of here. You're not allowed here anymore."); } return reached; } diff --git a/src/Corporation/Corporation.tsx b/src/Corporation/Corporation.tsx index cf6ac9125..d9643eddf 100644 --- a/src/Corporation/Corporation.tsx +++ b/src/Corporation/Corporation.tsx @@ -120,7 +120,7 @@ export class Corporation { if (isNaN(this.funds) || this.funds === Infinity || this.funds === -Infinity) { dialogBoxCreate( "There was an error calculating your Corporations funds and they got reset to 0. " + - "This is a bug. Please report to game developer.

" + + "This is a bug. Please report to game developer.\n\n" + "(Your funds have been set to $150b for the inconvenience)", ); this.funds = 150e9; diff --git a/src/Corporation/Industry.ts b/src/Corporation/Industry.ts index 8ec68f26f..affcf04e2 100644 --- a/src/Corporation/Industry.ts +++ b/src/Corporation/Industry.ts @@ -825,14 +825,7 @@ export class Industry implements IIndustry { sellAmt = eval(tmp); } catch (e) { dialogBoxCreate( - "Error evaluating your sell amount for material " + - mat.name + - " in " + - this.name + - "'s " + - city + - " office. The sell amount " + - "is being set to zero", + `Error evaluating your sell amount for material ${mat.name} in ${this.name}'s ${city} office. The sell amount is being set to zero`, ); sellAmt = 0; } @@ -879,27 +872,13 @@ export class Industry implements IIndustry { amt = eval(amtStr); } catch (e) { dialogBoxCreate( - "Calculating export for " + - mat.name + - " in " + - this.name + - "'s " + - city + - " division failed with " + - "error: " + - e, + `Calculating export for ${mat.name} in ${this.name}'s ${city} division failed with error: ${e}`, ); continue; } if (isNaN(amt)) { dialogBoxCreate( - "Error calculating export amount for " + - mat.name + - " in " + - this.name + - "'s " + - city + - " division.", + `Error calculating export amount for ${mat.name} in ${this.name}'s ${city} division.`, ); continue; } @@ -1172,13 +1151,7 @@ export class Industry implements IIndustry { tmp = eval(tmp); } catch (e) { dialogBoxCreate( - "Error evaluating your sell price expression for " + - product.name + - " in " + - this.name + - "'s " + - city + - " office. Sell price is being set to MAX", + `Error evaluating your sell price expression for ${product.name} in ${this.name}'s ${city} office. Sell price is being set to MAX`, ); tmp = product.maxsll; } diff --git a/src/Corporation/ui/modals/BribeFactionModal.tsx b/src/Corporation/ui/modals/BribeFactionModal.tsx index 87d20105d..341c56772 100644 --- a/src/Corporation/ui/modals/BribeFactionModal.tsx +++ b/src/Corporation/ui/modals/BribeFactionModal.tsx @@ -60,9 +60,7 @@ export function BribeFactionModal(props: IProps): React.ReactElement { const fac = Factions[selectedFaction]; if (disabled) return; const rep = repGain(money); - dialogBoxCreate( - "You gained " + numeralWrapper.formatReputation(rep) + " reputation with " + fac.name + " by bribing them.", - ); + dialogBoxCreate(`You gained ${numeralWrapper.formatReputation(rep)} reputation with ${fac.name} by bribing them.`); fac.playerReputation += rep; corp.funds = corp.funds - money; props.onClose(); diff --git a/src/Corporation/ui/modals/IssueNewSharesModal.tsx b/src/Corporation/ui/modals/IssueNewSharesModal.tsx index 85c7f8b97..944d58203 100644 --- a/src/Corporation/ui/modals/IssueNewSharesModal.tsx +++ b/src/Corporation/ui/modals/IssueNewSharesModal.tsx @@ -89,14 +89,11 @@ export function IssueNewSharesModal(props: IProps): React.ReactElement { let dialogContents = `Issued ${numeralWrapper.format(newShares, "0.000a")} new shares` + - ` and raised ${numeralWrapper.formatMoney(profit)}.`; - if (privateShares > 0) { - dialogContents += `
${numeralWrapper.format( - privateShares, - "0.000a", - )} of these shares were bought by private investors.`; - } - dialogContents += `

Stock price decreased to ${numeralWrapper.formatMoney(corp.sharePrice)}`; + ` and raised ${numeralWrapper.formatMoney(profit)}.` + + (privateShares > 0) + ? "\n" + numeralWrapper.format(privateShares, "0.000a") + "of these shares were bought by private investors." + : ""; + dialogContents += `\n\nStock price decreased to ${numeralWrapper.formatMoney(corp.sharePrice)}`; dialogBoxCreate(dialogContents); } diff --git a/src/Corporation/ui/modals/ResearchModal.tsx b/src/Corporation/ui/modals/ResearchModal.tsx index 40570aba5..d0c47d3aa 100644 --- a/src/Corporation/ui/modals/ResearchModal.tsx +++ b/src/Corporation/ui/modals/ResearchModal.tsx @@ -42,9 +42,7 @@ function Upgrade({ n, division }: INodeProps): React.ReactElement { } dialogBoxCreate( - `Researched ${n.text}. It may take a market cycle ` + - `(~${CorporationConstants.SecsPerMarketCycle} seconds) before the effects of ` + - `the Research apply.`, + `Researched ${n.text}. It may take a market cycle (~${CorporationConstants.SecsPerMarketCycle} seconds) before the effects of the Research apply.`, ); } diff --git a/src/Corporation/ui/modals/ThrowPartyModal.tsx b/src/Corporation/ui/modals/ThrowPartyModal.tsx index 8bbd9d3f7..4190e6b85 100644 --- a/src/Corporation/ui/modals/ThrowPartyModal.tsx +++ b/src/Corporation/ui/modals/ThrowPartyModal.tsx @@ -41,8 +41,7 @@ export function ThrowPartyModal(props: IProps): React.ReactElement { if (mult > 0) { dialogBoxCreate( - "You threw a party for the office! The morale and happiness " + - "of each employee increased by " + + "You threw a party for the office! The morale and happiness of each employee increased by " + numeralWrapper.formatPercentage(mult - 1), ); } diff --git a/src/Faction/FactionHelpers.tsx b/src/Faction/FactionHelpers.tsx index 882cba6b7..6e7d9509b 100644 --- a/src/Faction/FactionHelpers.tsx +++ b/src/Faction/FactionHelpers.tsx @@ -94,12 +94,9 @@ export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = fal return "You purchased " + aug.name; } else if (!Settings.SuppressBuyAugmentationConfirmation) { dialogBoxCreate( - "You purchased " + - aug.name + - ". Its enhancements will not take " + - "effect until they are installed. To install your augmentations, go to the " + - "'Augmentations' tab on the left-hand navigation menu. Purchasing additional " + - "augmentations will now be more expensive.", + `You purchased ${aug.name}. Its enhancements will not take effect until they are installed.` + + "To install your augmentations, go to the 'Augmentations' tab on the left-hand navigation menu." + + "Purchasing additional augmentations will now be more expensive.", ); } } else { diff --git a/src/Locations/LocationsHelpers.tsx b/src/Locations/LocationsHelpers.tsx index 30bdabe5c..86e449f2e 100644 --- a/src/Locations/LocationsHelpers.tsx +++ b/src/Locations/LocationsHelpers.tsx @@ -33,8 +33,8 @@ export function purchaseTorRouter(p: IPlayer): void { p.getHomeComputer().serversOnNetwork.push(darkweb.hostname); darkweb.serversOnNetwork.push(p.getHomeComputer().hostname); dialogBoxCreate( - "You have purchased a TOR router!
" + - "You now have access to the dark web from your home computer.
" + + "You have purchased a TOR router!\n" + + "You now have access to the dark web from your home computer.\n" + "Use the scan/scan-analyze commands to search for the dark web connection.", ); } diff --git a/src/Locations/ui/TravelAgencyRoot.tsx b/src/Locations/ui/TravelAgencyRoot.tsx index 9e3b58b06..6711b8857 100644 --- a/src/Locations/ui/TravelAgencyRoot.tsx +++ b/src/Locations/ui/TravelAgencyRoot.tsx @@ -35,7 +35,7 @@ function travel(p: IPlayer, router: IRouter, to: CityName): void { p.loseMoney(cost, "other"); p.travel(to); - dialogBoxCreate(<>You are now in {to}!); + dialogBoxCreate(`You are now in ${to}!`); router.toCity(); } diff --git a/src/Message/MessageHelpers.ts b/src/Message/MessageHelpers.tsx similarity index 82% rename from src/Message/MessageHelpers.ts rename to src/Message/MessageHelpers.tsx index 26764b72c..dc6d87d95 100644 --- a/src/Message/MessageHelpers.ts +++ b/src/Message/MessageHelpers.tsx @@ -1,3 +1,4 @@ +import React from "react"; import { Message } from "./Message"; import { AugmentationNames } from "../Augmentation/data/AugmentationNames"; import { Router } from "../ui/GameRoot"; @@ -22,15 +23,12 @@ function sendMessage(msg: Message, forced = false): void { function showMessage(name: MessageFilenames): void { const msg = Messages[name]; if (!(msg instanceof Message)) throw new Error("trying to display unexistent message"); - const txt = - "Message received from unknown sender:

" + - "" + - msg.msg + - "

" + - "This message was saved as " + - msg.filename + - " onto your home computer."; - dialogBoxCreate(txt); + dialogBoxCreate( + <> + Message received from unknown sender:{msg.msg}This message was saved as {msg.filename} onto your home + computer. + , + ); } //Adds a message to a server @@ -127,20 +125,20 @@ const Messages: Record = { MessageFilenames.Jumper0, "I know you can sense it. I know you're searching for it. " + "It's why you spend night after " + - "night at your computer.

It's real, I've seen it. And I can " + - "help you find it. But not right now. You're not ready yet.

" + - "Use this program to track your progress

" + - "The fl1ght.exe program was added to your home computer

" + + "night at your computer. \n\nIt's real, I've seen it. And I can " + + "help you find it. But not right now. You're not ready yet.\n\n" + + "Use this program to track your progress\n\n" + + "The fl1ght.exe program was added to your home computer\n\n" + "-jump3R", ), [MessageFilenames.Jumper1]: new Message( MessageFilenames.Jumper1, `Soon you will be contacted by a hacking group known as ${FactionNames.NiteSec}. ` + - "They can help you with your search.

" + + "They can help you with your search. \n\n" + "You should join them, garner their favor, and " + "exploit them for their Augmentations. But do not trust them. " + - "They are not what they seem. No one is.

" + + "They are not what they seem. No one is.\n\n" + "-jump3R", ), @@ -148,21 +146,21 @@ const Messages: Record = { MessageFilenames.Jumper2, "Do not try to save the world. There is no world to save. If " + "you want to find the truth, worry only about yourself. Ethics and " + - `morals will get you killed.

Watch out for a hacking group known as ${FactionNames.NiteSec}.` + - "

-jump3R", + `morals will get you killed. \n\nWatch out for a hacking group known as ${FactionNames.NiteSec}.` + + "\n\n-jump3R", ), [MessageFilenames.Jumper3]: new Message( MessageFilenames.Jumper3, "You must learn to walk before you can run. And you must " + - `run before you can fly. Look for ${FactionNames.TheBlackHand}.

` + - "I.I.I.I

-jump3R", + `run before you can fly. Look for ${FactionNames.TheBlackHand}. \n\n` + + "I.I.I.I \n\n-jump3R", ), [MessageFilenames.Jumper4]: new Message( MessageFilenames.Jumper4, "To find what you are searching for, you must understand the bits. " + - "The bits are all around us. The runners will help you.

" + + "The bits are all around us. The runners will help you.\n\n" + "-jump3R", ), @@ -171,8 +169,8 @@ const Messages: Record = { MessageFilenames.CyberSecTest, "We've been watching you. Your skills are very impressive. But you're wasting " + "your talents. If you join us, you can put your skills to good use and change " + - "the world for the better. If you join us, we can unlock your full potential.

" + - "But first, you must pass our test. Find and install the backdoor on our server.

" + + "the world for the better. If you join us, we can unlock your full potential. \n\n" + + "But first, you must pass our test. Find and install the backdoor on our server. \n\n" + `-${FactionNames.CyberSec}`, ), @@ -181,17 +179,17 @@ const Messages: Record = { "People say that the corrupted governments and corporations rule the world. " + "Yes, maybe they do. But do you know who everyone really fears? People " + "like us. Because they can't hide from us. Because they can't fight shadows " + - "and ideas with bullets.

" + - "Join us, and people will fear you, too.

" + + "and ideas with bullets. \n\n" + + "Join us, and people will fear you, too. \n\n" + "Find and install the backdoor on our server, avmnite-02h. Then, we will contact you again." + - `

-${FactionNames.NiteSec}`, + `\n\n-${FactionNames.NiteSec}`, ), [MessageFilenames.BitRunnersTest]: new Message( MessageFilenames.BitRunnersTest, "We know what you are doing. We know what drives you. We know " + - "what you are looking for.

" + - "We can help you find the answers.

" + + "what you are looking for. \n\n " + + "We can help you find the answers.\n\n" + "run4theh111z", ), @@ -199,18 +197,18 @@ const Messages: Record = { [MessageFilenames.TruthGazer]: new Message( MessageFilenames.TruthGazer, //"THE TRUTH CAN NO LONGER ESCAPE YOUR GAZE" - "@&*($#@&__TH3__#@A&#@*)__TRU1H__(*)&*)($#@&()E&R)W&
" + - "%@*$^$()@&$)$*@__CAN__()(@^#)@&@)#__N0__(#@&#)@&@&(
" + - "*(__LON6ER__^#)@)(()*#@)@__ESCAP3__)#(@(#@*@()@(#*$
" + + "@&*($#@&__TH3__#@A&#@*)__TRU1H__(*)&*)($#@&()E&R)W&\n" + + "%@*$^$()@&$)$*@__CAN__()(@^#)@&@)#__N0__(#@&#)@&@&(\n" + + "*(__LON6ER__^#)@)(()*#@)@__ESCAP3__)#(@(#@*@()@(#*$\n" + "()@)#$*%)$#()$#__Y0UR__(*)$#()%(&(%)*!)($__GAZ3__#(", ), [MessageFilenames.RedPill]: new Message( MessageFilenames.RedPill, //"FIND THE-CAVE" - "@)(#V%*N)@(#*)*C)@#%*)*V)@#(*%V@)(#VN%*)@#(*%
" + - ")@B(*#%)@)M#B*%V)____FIND___#$@)#%(B*)@#(*%B)
" + - "@_#(%_@#M(BDSPOMB__THE-CAVE_#)$(*@#$)@#BNBEGB
" + + "@)(#V%*N)@(#*)*C)@#%*)*V)@#(*%V@)(#VN%*)@#(*%\n" + + ")@B(*#%)@)M#B*%V)____FIND___#$@)#%(B*)@#(*%B)\n" + + "@_#(%_@#M(BDSPOMB__THE-CAVE_#)$(*@#$)@#BNBEGB\n" + "DFLSMFVMV)#@($*)@#*$MV)@#(*$V)M#(*$)M@(#*VM$)", ), }; diff --git a/src/Netscript/APIWrapper.ts b/src/Netscript/APIWrapper.ts index 3e1522e90..0bd5817d4 100644 --- a/src/Netscript/APIWrapper.ts +++ b/src/Netscript/APIWrapper.ts @@ -41,7 +41,7 @@ function wrapFunction( const functionPath = tree.join("."); const functionName = tree.pop(); if (typeof functionName !== "string") { - throw helpers.makeRuntimeRejectMsg(workerScript, "Failure occured while wrapping netscript api"); + throw helpers.makeBasicErrorMsg(workerScript, "Failure occured while wrapping netscript api"); } const ctx = { workerScript, diff --git a/src/Netscript/NetscriptHelpers.ts b/src/Netscript/NetscriptHelpers.ts index 017463737..0ca89db76 100644 --- a/src/Netscript/NetscriptHelpers.ts +++ b/src/Netscript/NetscriptHelpers.ts @@ -3,7 +3,6 @@ import { WorkerScript } from "./WorkerScript"; import { GetAllServers, GetServer } from "../Server/AllServers"; import { Player } from "../Player"; import { ScriptDeath } from "./ScriptDeath"; -import { isString } from "../utils/helpers/isString"; import { numeralWrapper } from "../ui/numeralFormat"; import { ScriptArg } from "./ScriptArg"; import { CityName } from "../Locations/data/CityNames"; @@ -40,8 +39,7 @@ export const helpers = { number, scriptArgs, argsToString, - isScriptErrorMessage, - makeRuntimeRejectMsg, + makeBasicErrorMsg, makeRuntimeErrorMsg, resolveNetscriptRequestedThreads, checkEnvFlags, @@ -114,15 +112,6 @@ function scriptArgs(ctx: NetscriptContext, args: unknown) { return args; } -/** Determines if the given msg string is an error created by makeRuntimeRejectMsg. */ -function isScriptErrorMessage(msg: string): boolean { - if (!isString(msg)) { - return false; - } - const splitMsg = msg.split("|DELIMITER|"); - return splitMsg.length == 4; -} - /** Convert multiple arguments for tprint or print into a single string. */ function argsToString(args: unknown[]): string { let out = ""; @@ -143,12 +132,11 @@ function argsToString(args: unknown[]): string { } /** Creates an error message string containing hostname, scriptname, and the error message msg */ -function makeRuntimeRejectMsg(workerScript: WorkerScript, msg: string): string { +function makeBasicErrorMsg(workerScript: WorkerScript, msg: string): string { for (const scriptUrl of workerScript.scriptRef.dependencies) { msg = msg.replace(new RegExp(scriptUrl.url, "g"), scriptUrl.filename); } - - return "|DELIMITER|" + workerScript.hostname + "|DELIMITER|" + workerScript.name + "|DELIMITER|" + msg; + return msg; } /** Creates an error message string with a stack trace. */ @@ -156,9 +144,9 @@ function makeRuntimeErrorMsg(ctx: NetscriptContext, msg: string): string { const errstack = new Error().stack; if (errstack === undefined) throw new Error("how did we not throw an error?"); const stack = errstack.split("\n").slice(1); - const workerScript = ctx.workerScript; - const caller = ctx.function; - const scripts = workerScript.getServer().scripts; + const ws = ctx.workerScript; + const caller = ctx.functionPath; + const scripts = ws.getServer().scripts; const userstack = []; for (const stackline of stack) { let filename; @@ -216,10 +204,10 @@ function makeRuntimeErrorMsg(ctx: NetscriptContext, msg: string): string { userstack.push(`${filename}:L${call.line}@${call.func}`); } - workerScript.log(caller, () => msg); + log(ctx, () => msg); let rejectMsg = `${caller}: ${msg}`; if (userstack.length !== 0) rejectMsg += `

Stack:
${userstack.join("
")}`; - return makeRuntimeRejectMsg(workerScript, rejectMsg); + return makeBasicErrorMsg(ws, rejectMsg); } /** Validate requested number of threads for h/g/w options */ @@ -230,14 +218,14 @@ function resolveNetscriptRequestedThreads(ctx: NetscriptContext, requestedThread } const requestedThreadsAsInt = requestedThreads | 0; if (isNaN(requestedThreads) || requestedThreadsAsInt < 1) { - throw makeRuntimeRejectMsg( - ctx.workerScript, + throw makeRuntimeErrorMsg( + ctx, `Invalid thread count passed to ${ctx.function}: ${requestedThreads}. Threads must be a positive number.`, ); } if (requestedThreadsAsInt > threads) { - throw makeRuntimeRejectMsg( - ctx.workerScript, + throw makeRuntimeErrorMsg( + ctx, `Too many threads requested by ${ctx.function}. Requested: ${requestedThreads}. Has: ${threads}.`, ); } @@ -258,17 +246,21 @@ function checkSingularityAccess(ctx: NetscriptContext): void { /** Create an error if a script is dead or if concurrent ns function calls are made */ function checkEnvFlags(ctx: NetscriptContext): void { const ws = ctx.workerScript; - if (ws.env.stopFlag) throw new ScriptDeath(ws); + if (ws.env.stopFlag) { + log(ctx, () => "Failed to run due to script being killed."); + throw new ScriptDeath(ws); + } if (ws.env.runningFn && ctx.function !== "asleep") { - ws.errorMessage = makeRuntimeRejectMsg( + //This one has no error message so it will not create a dialog + if (ws.delayReject) ws.delayReject(new ScriptDeath(ws)); + ws.errorMessage = makeBasicErrorMsg( ws, `Concurrent calls to Netscript functions are not allowed! Did you forget to await hack(), grow(), or some other promise-returning function? Currently running: ${ws.env.runningFn} tried to run: ${ctx.function}`, ); - if (ws.delayReject) ws.delayReject(new ScriptDeath(ws)); - throw new ScriptDeath(ws); //No idea if this is the right thing to throw + throw new ScriptDeath(ws); } } @@ -302,7 +294,8 @@ function updateDynamicRam(ctx: NetscriptContext, ramCost: number): void { } ws.dynamicRamUsage += ramCost; if (ws.dynamicRamUsage > 1.01 * ws.ramUsage) { - ws.errorMessage = makeRuntimeRejectMsg( + log(ctx, () => "Insufficient static ram available."); + ws.errorMessage = makeBasicErrorMsg( ws, `Dynamic RAM usage calculated to be greater than initial RAM usage on fn: ${fnName}. This is probably because you somehow circumvented the static RAM calculation. @@ -605,7 +598,7 @@ function getRunningScriptByArgs( scriptArgs: ScriptArg[], ): RunningScript | null { if (!Array.isArray(scriptArgs)) { - throw helpers.makeRuntimeRejectMsg( + throw helpers.makeBasicErrorMsg( ctx.workerScript, `Invalid scriptArgs argument passed into getRunningScript() from ${ctx.function}(). ` + `This is probably a bug. Please report to game developer`, diff --git a/src/Netscript/killWorkerScript.ts b/src/Netscript/killWorkerScript.ts index b90ded508..f41e179c0 100644 --- a/src/Netscript/killWorkerScript.ts +++ b/src/Netscript/killWorkerScript.ts @@ -10,7 +10,7 @@ import { WorkerScriptStartStopEventEmitter } from "./WorkerScriptStartStopEventE import { RunningScript } from "../Script/RunningScript"; import { GetServer } from "../Server/AllServers"; -import { dialogBoxCreate } from "../ui/React/DialogBox"; +import { errorDialog } from "../ui/React/DialogBox"; import { AddRecentScript } from "./RecentScripts"; import { Player } from "../Player"; import { ITutorial } from "../InteractiveTutorial"; @@ -66,12 +66,7 @@ function stopAndCleanUpWorkerScript(ws: WorkerScript): void { ws.env.stopFlag = false; ws.atExit(); } catch (e: unknown) { - let message = e instanceof ScriptDeath ? e.errorMessage : String(e); - message = message.replace(/.*\|DELIMITER\|/, ""); - dialogBoxCreate( - `Error trying to call atExit for script ${[ws.name, ...ws.args].join(" ")} on ${ws.hostname}\n ${message}`, - ); - console.error(e); + errorDialog(e, `Error during atExit ${ws.name}@${ws.hostname} (PID - ${ws.pid}\n\n`); } ws.atExit = undefined; } diff --git a/src/NetscriptJSEvaluator.ts b/src/NetscriptJSEvaluator.ts index 8e5f00861..9b384fe65 100644 --- a/src/NetscriptJSEvaluator.ts +++ b/src/NetscriptJSEvaluator.ts @@ -71,7 +71,7 @@ export async function executeJSScript( const ns = workerScript.env.vars; if (!loadedModule) { - throw helpers.makeRuntimeRejectMsg( + throw helpers.makeBasicErrorMsg( workerScript, `${script.filename} cannot be run because the script module won't load`, ); @@ -79,18 +79,19 @@ export async function executeJSScript( // TODO: putting await in a non-async function yields unhelpful // "SyntaxError: unexpected reserved word" with no line number information. if (!loadedModule.main) { - throw helpers.makeRuntimeRejectMsg( + throw helpers.makeBasicErrorMsg( workerScript, `${script.filename} cannot be run because it does not have a main function.`, ); } if (!ns) { - throw helpers.makeRuntimeRejectMsg( + throw helpers.makeBasicErrorMsg( workerScript, `${script.filename} cannot be run because the NS object hasn't been constructed properly.`, ); } - return loadedModule.main(ns); + await loadedModule.main(ns); + return; } function isDependencyOutOfDate(filename: string, scripts: Script[], scriptModuleSequenceNumber: number): boolean { diff --git a/src/NetscriptWorker.ts b/src/NetscriptWorker.ts index 7f808adc4..1d05043aa 100644 --- a/src/NetscriptWorker.ts +++ b/src/NetscriptWorker.ts @@ -24,10 +24,9 @@ import { Settings } from "./Settings/Settings"; import { generate } from "escodegen"; -import { dialogBoxCreate } from "./ui/React/DialogBox"; +import { dialogBoxCreate, errorDialog } from "./ui/React/DialogBox"; import { arrayToString } from "./utils/helpers/arrayToString"; import { roundToTwo } from "./utils/helpers/roundToTwo"; -import { isString } from "./utils/helpers/isString"; import { parse } from "acorn"; import { simple as walksimple } from "acorn-walk"; @@ -35,7 +34,6 @@ import { areFilesEqual } from "./Terminal/DirectoryHelpers"; import { Player } from "./Player"; import { Terminal } from "./Terminal"; import { ScriptArg } from "./Netscript/ScriptArg"; -import { helpers } from "./Netscript/NetscriptHelpers"; export const NetscriptPorts: Map = new Map(); @@ -54,39 +52,8 @@ export function prestigeWorkerScripts(): void { // JS script promises need a little massaging to have the same guarantees as netscript // promises. This does said massaging and kicks the script off. It returns a promise // that resolves or rejects when the corresponding worker script is done. -function startNetscript2Script(workerScript: WorkerScript): Promise { - return new Promise((resolve, reject) => { - executeJSScript(Player, workerScript.getServer().scripts, workerScript) - .then(() => { - resolve(); - }) - .catch((e) => reject(e)); - }).catch((e) => { - if (e instanceof Error) { - if (e instanceof SyntaxError) { - workerScript.errorMessage = helpers.makeRuntimeRejectMsg( - workerScript, - e.message + " (sorry we can't be more helpful)", - ); - } else { - workerScript.errorMessage = helpers.makeRuntimeRejectMsg( - workerScript, - e.message + ((e.stack && "\nstack:\n" + e.stack.toString()) || ""), - ); - } - throw new ScriptDeath(workerScript); - } else if (helpers.isScriptErrorMessage(e)) { - workerScript.errorMessage = e; - throw new ScriptDeath(workerScript); - } else if (e instanceof ScriptDeath) { - throw e; - } - - // Don't know what to do with it, let's try making an error message out of it - workerScript.errorMessage = helpers.makeRuntimeRejectMsg(workerScript, "" + e); - throw new ScriptDeath(workerScript); - }); -} +const startNetscript2Script = (workerScript: WorkerScript): Promise => + executeJSScript(Player, workerScript.getServer().scripts, workerScript); function startNetscript1Script(workerScript: WorkerScript): Promise { const code = workerScript.code; @@ -98,18 +65,16 @@ function startNetscript1Script(workerScript: WorkerScript): Promise { codeWithImports = importProcessingRes.code; codeLineOffset = importProcessingRes.lineOffset; } catch (e: unknown) { - dialogBoxCreate("Error processing Imports in " + workerScript.name + ":
" + String(e)); + dialogBoxCreate(`Error processing Imports in ${workerScript.name} on ${workerScript.hostname}:\n\n${e}`); workerScript.env.stopFlag = true; killWorkerScript(workerScript); return Promise.resolve(); } - function wrapNS1Layer(int: Interpreter, intLayer: unknown, path: string[] = []) { - //TODO: Better typing layers of interpreter scope and ns - interface BasicObject { - [key: string]: any; - } - const nsLayer = path.reduce((prev, newPath) => prev[newPath], workerScript.env.vars as BasicObject); + interface BasicObject { + [key: string]: any; + } + function wrapNS1Layer(int: Interpreter, intLayer: unknown, nsLayer = workerScript.env.vars as BasicObject) { for (const [name, entry] of Object.entries(nsLayer)) { if (typeof entry === "function") { // Async functions need to be wrapped. See JS-Interpreter documentation @@ -121,21 +86,11 @@ function startNetscript1Script(workerScript: WorkerScript): Promise { const result = await entry(...args.map((arg) => int.pseudoToNative(arg))); return callback(int.nativeToPseudo(result)); } catch (e: unknown) { - // TODO: Unify error handling, this was stolen from previous async handler - if (typeof e === "string") { - console.error(e); - const errorTextArray = e.split("|DELIMITER|"); - const hostname = errorTextArray[1]; - const scriptName = errorTextArray[2]; - const errorMsg = errorTextArray[3]; - let msg = `${scriptName}@${hostname}
`; - msg += "
"; - msg += errorMsg; - dialogBoxCreate(msg); - workerScript.env.stopFlag = true; - killWorkerScript(workerScript); - return; - } + // NS1 interpreter doesn't cleanly handle throwing. Need to show dialog here. + errorDialog(e, `RUNTIME ERROR:\n${workerScript.name}@${workerScript.hostname}\n\n`); + workerScript.env.stopFlag = true; + killWorkerScript(workerScript); + return; } }; int.setProperty(intLayer, name, int.createAsyncFunction(wrapper)); @@ -145,7 +100,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise { } else { // new object layer, e.g. bladeburner int.setProperty(intLayer, name, int.nativeToPseudo({})); - wrapNS1Layer(int, (intLayer as BasicObject).properties[name], [...path, name]); + wrapNS1Layer(int, (intLayer as BasicObject).properties[name], nsLayer[name]); } } } @@ -154,54 +109,30 @@ function startNetscript1Script(workerScript: WorkerScript): Promise { try { interpreter = new Interpreter(codeWithImports, wrapNS1Layer, codeLineOffset); } catch (e: unknown) { - dialogBoxCreate("Syntax ERROR in " + workerScript.name + ":
" + String(e)); + dialogBoxCreate(`Syntax ERROR in ${workerScript.name} on ${workerScript.hostname}:\n\n${String(e)}`); workerScript.env.stopFlag = true; killWorkerScript(workerScript); return Promise.resolve(); } - return new Promise(function (resolve, reject) { + return new Promise((resolve) => { function runInterpreter(): void { - try { - if (workerScript.env.stopFlag) { - return reject(new ScriptDeath(workerScript)); - } + if (workerScript.env.stopFlag) resolve(); - let more = true; - let i = 0; - while (i < 3 && more) { - more = more && interpreter.step(); - i++; - } - - if (more) { - setTimeout(runInterpreter, Settings.CodeInstructionRunTime); - } else { - resolve(); - } - } catch (_e: unknown) { - let e = String(_e); - if (!helpers.isScriptErrorMessage(e)) { - e = helpers.makeRuntimeRejectMsg(workerScript, e); - } - workerScript.errorMessage = e; - return reject(new ScriptDeath(workerScript)); + let more = true; + let i = 0; + while (i < 3 && more) { + more = more && interpreter.step(); + i++; } - } - try { - runInterpreter(); - } catch (e: unknown) { - if (isString(e)) { - workerScript.errorMessage = e; - return reject(new ScriptDeath(workerScript)); - } else if (e instanceof ScriptDeath) { - return reject(e); + if (more) { + setTimeout(runInterpreter, Settings.CodeInstructionRunTime); } else { - console.error(e); - return reject(new ScriptDeath(workerScript)); + resolve(); } } + runInterpreter(); }); } @@ -430,58 +361,19 @@ function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseS // running status to false scriptExecution .then(function () { - workerScript.env.stopFlag = true; // On natural death, the earnings are transfered to the parent if it still exists. - if (parent !== undefined && !parent.env.stopFlag) { + if (parent && !parent.env.stopFlag) { parent.scriptRef.onlineExpGained += runningScriptObj.onlineExpGained; parent.scriptRef.onlineMoneyMade += runningScriptObj.onlineMoneyMade; } - killWorkerScript(workerScript); workerScript.log("", () => "Script finished running"); }) .catch(function (e) { - if (e 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: " + e.toString()); - return; - } else if (e instanceof ScriptDeath) { - if (helpers.isScriptErrorMessage(workerScript.errorMessage)) { - const errorTextArray = workerScript.errorMessage.split("|DELIMITER|"); - if (errorTextArray.length != 4) { - console.error("ERROR: Something wrong with Error text in evaluator..."); - console.error("Error text: " + workerScript.errorMessage); - return; - } - const hostname = errorTextArray[1]; - const scriptName = errorTextArray[2]; - const errorMsg = errorTextArray[3]; - - let msg = `RUNTIME ERROR
${scriptName}@${hostname} (PID - ${workerScript.pid})
`; - if (workerScript.args.length > 0) { - msg += `Args: ${arrayToString(workerScript.args)}
`; - } - msg += "
"; - msg += errorMsg; - - dialogBoxCreate(msg); - workerScript.log("", () => "Script crashed with runtime error"); - } else { - workerScript.log("", () => "Script killed"); - return; // Already killed, so stop here - } - } else if (helpers.isScriptErrorMessage(e)) { - dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer"); - console.error( - "ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " + - e.toString(), - ); - return; - } else { - dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev"); - console.error(e); - } - + errorDialog(e, `RUNTIME ERROR\n${workerScript.name}@${workerScript.hostname} (PID - ${workerScript.pid})\n\n`); + let logText = "Script crashed due to an error."; + if (e instanceof ScriptDeath) logText = "Script killed."; + workerScript.log("", () => logText); killWorkerScript(workerScript); }); diff --git a/src/SaveObject.tsx b/src/SaveObject.tsx index b1cf7a872..451198ec1 100755 --- a/src/SaveObject.tsx +++ b/src/SaveObject.tsx @@ -27,7 +27,6 @@ import { AwardNFG, v1APIBreak } from "./utils/v1APIBreak"; import { AugmentationNames } from "./Augmentation/data/AugmentationNames"; import { PlayerOwnedAugmentation } from "./Augmentation/PlayerOwnedAugmentation"; import { LocationName } from "./Locations/data/LocationNames"; -import { SxProps } from "@mui/system"; import { PlayerObject } from "./PersonObjects/Player/PlayerObject"; import { pushGameSaved } from "./Electron"; import { defaultMonacoTheme } from "./ScriptEditor/ui/themes"; @@ -759,27 +758,14 @@ function createScamUpdateText(): void { } } -const resets: SxProps = { - "& h1, & h2, & h3, & h4, & p, & a, & ul": { - margin: 0, - color: Settings.theme.primary, - whiteSpace: "initial", - }, - "& ul": { - paddingLeft: "1.5em", - lineHeight: 1.5, - }, -}; - function createNewUpdateText(): void { setTimeout( () => dialogBoxCreate( - "New update!
" + + "New update!\n" + "Please report any bugs/issues through the GitHub repository " + - "or the Bitburner subreddit (reddit.com/r/bitburner).

" + + "or the Bitburner subreddit (reddit.com/r/bitburner).\n\n" + CONSTANTS.LatestUpdate, - resets, ), 1000, ); @@ -788,11 +774,10 @@ function createNewUpdateText(): void { function createBetaUpdateText(): void { dialogBoxCreate( "You are playing on the beta environment! This branch of the game " + - "features the latest developments in the game. This version may be unstable.
" + + "features the latest developments in the game. This version may be unstable.\n" + "Please report any bugs/issues through the github repository (https://github.com/danielyxie/bitburner/issues) " + - "or the Bitburner subreddit (reddit.com/r/bitburner).

" + + "or the Bitburner subreddit (reddit.com/r/bitburner).\n\n" + CONSTANTS.LatestUpdate, - resets, ); } diff --git a/src/Script/ScriptModule.ts b/src/Script/ScriptModule.ts index f5a3ee425..96da00616 100644 --- a/src/Script/ScriptModule.ts +++ b/src/Script/ScriptModule.ts @@ -1,6 +1,6 @@ import { AutocompleteData, NS } from "../ScriptEditor/NetscriptDefinitions"; export interface ScriptModule { - main?: (ns: NS) => Promise; + main?: (ns: NS) => unknown; autocomplete?: (data: AutocompleteData, flags: string[]) => unknown; } diff --git a/src/UncaughtPromiseHandler.ts b/src/UncaughtPromiseHandler.ts index e37404a33..a786c384a 100644 --- a/src/UncaughtPromiseHandler.ts +++ b/src/UncaughtPromiseHandler.ts @@ -1,24 +1,7 @@ -import { ScriptDeath } from "./Netscript/ScriptDeath"; -import { helpers } from "./Netscript/NetscriptHelpers"; -import { dialogBoxCreate } from "./ui/React/DialogBox"; +import { errorDialog } from "./ui/React/DialogBox"; export function setupUncaughtPromiseHandler(): void { - window.addEventListener("unhandledrejection", function (e) { - if (helpers.isScriptErrorMessage(e.reason)) { - const errorTextArray = e.reason.split("|DELIMITER|"); - const hostname = errorTextArray[1]; - const scriptName = errorTextArray[2]; - const errorMsg = errorTextArray[3]; - - let msg = `UNCAUGHT PROMISE ERROR
You forgot to await a promise
${scriptName}@${hostname}
`; - msg += "
"; - msg += errorMsg; - dialogBoxCreate(msg); - } else if (e.reason instanceof ScriptDeath) { - const msg = - `UNCAUGHT PROMISE ERROR
You forgot to await a promise
${e.reason.name}@${e.reason.hostname} (PID - ${e.reason.pid})
` + - `Maybe hack / grow / weaken ?`; - dialogBoxCreate(msg); - } - }); + window.addEventListener("unhandledrejection", (e) => + errorDialog(e.reason, "UNCAUGHT PROMISE ERROR\nYou forgot to await a promise\nmaybe hack / grow / weaken ?\n"), + ); } diff --git a/src/ui/React/DialogBox.tsx b/src/ui/React/DialogBox.tsx index 9ae8035f6..f4fe5216d 100644 --- a/src/ui/React/DialogBox.tsx +++ b/src/ui/React/DialogBox.tsx @@ -1,13 +1,29 @@ import { AlertEvents } from "./AlertManager"; import React from "react"; -import { SxProps } from "@mui/system"; import { Typography } from "@mui/material"; +import { ScriptDeath } from "../../Netscript/ScriptDeath"; -export function dialogBoxCreate(txt: string | JSX.Element, styles?: SxProps): void { - if (typeof txt !== "string") { - AlertEvents.emit(txt); - } else { - AlertEvents.emit(); - } +export function dialogBoxCreate(txt: string | JSX.Element): void { + AlertEvents.emit(typeof txt === "string" ? {txt} : txt); +} + +export function errorDialog(e: unknown, initialText = "") { + let errorText = ""; + if (typeof e === "string") errorText = e; + else if (e instanceof ScriptDeath) { + if (!e.errorMessage) return; //No need for a dialog for an empty ScriptDeath + errorText = e.errorMessage; + } else if (e instanceof SyntaxError) errorText = e.message + " (sorry we can't be more helpful)"; + else if (e instanceof Error) errorText = e.message + (e.stack ? `\nstack:\n${e.stack.toString()}` : ""); + else { + errorText = "An unknown error was thrown, see console."; + console.error(e); + } + + if (!initialText) { + if (e instanceof ScriptDeath) initialText = `${e.name}@${e.hostname} (PID - ${e.pid})\n\n`; + } + + dialogBoxCreate(initialText + errorText); }