Unify error handling

This commit is contained in:
Snarling 2022-08-29 02:41:17 -04:00
parent 5798c4c7d3
commit 572c68738f
23 changed files with 156 additions and 255 deletions

@ -106,7 +106,7 @@ function initWebserver(): void {
msg: "Home server does not exist.",
};
}
const { success, overwritten } = home.writeToScriptFile(Player, filename, code);
const { success, overwritten } = home.writeToScriptFile(filename, code);
let script;
if (success) {
script = home.getScript(filename);

@ -19,7 +19,7 @@ import {
import { createRandomIp } from "../utils/IPAddress";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Player } from "../Player";
interface IConstructorParams {
adminRights?: boolean;
@ -123,9 +123,9 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
}
}
updateRamUsed(ram: number, player: IPlayer): void {
super.updateRamUsed(ram, player);
this.updateHashRate(player.mults.hacknet_node_money);
updateRamUsed(ram: number): void {
super.updateRamUsed(ram);
this.updateHashRate(Player.mults.hacknet_node_money);
}
updateHashCapacity(): void {

@ -1,6 +1,5 @@
import { getRamCost } from "./RamCostGenerator";
import type { WorkerScript } from "./WorkerScript";
import { Player } from "../Player";
import { helpers } from "./NetscriptHelpers";
import { ScriptArg } from "./ScriptArg";
import { NSEnums } from "src/ScriptEditor/NetscriptDefinitions";
@ -50,7 +49,7 @@ function wrapFunction(
};
function wrappedFunction(...args: unknown[]): unknown {
helpers.checkEnvFlags(ctx);
helpers.updateDynamicRam(ctx, getRamCost(Player, ...tree, ctx.function));
helpers.updateDynamicRam(ctx, getRamCost(...tree, ctx.function));
return func(ctx)(...args);
}
const parent = getNestedProperty(wrappedAPI, tree);

@ -57,7 +57,6 @@ export const helpers = {
gangMember,
gangTask,
log,
getFunctionNames,
getRunningScript,
getRunningScriptByArgs,
getCannotFindRunningScriptErrorMessage,
@ -205,8 +204,8 @@ function makeRuntimeErrorMsg(ctx: NetscriptContext, msg: string): string {
}
log(ctx, () => msg);
let rejectMsg = `${caller}: ${msg}`;
if (userstack.length !== 0) rejectMsg += `<br><br>Stack:<br>${userstack.join("<br>")}`;
let rejectMsg = `RUNTIME ERROR\n${ws.name}@${ws.hostname} (PID - ${ws.pid})\n\n${caller}: ${msg}`;
if (userstack.length !== 0) rejectMsg += `\n\nStack:\n${userstack.join("\n")}`;
return makeBasicErrorMsg(ws, rejectMsg);
}
@ -253,14 +252,15 @@ function checkEnvFlags(ctx: NetscriptContext): void {
if (ws.env.runningFn && ctx.function !== "asleep") {
//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,
ws.env.stopFlag = true;
log(ctx, () => "Failed to run due to failed concurrency check.");
throw makeRuntimeErrorMsg(
ctx,
`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}`,
);
throw new ScriptDeath(ws);
}
}
@ -295,9 +295,10 @@ function updateDynamicRam(ctx: NetscriptContext, ramCost: number): void {
ws.dynamicRamUsage += ramCost;
if (ws.dynamicRamUsage > 1.01 * ws.ramUsage) {
log(ctx, () => "Insufficient static ram available.");
ws.errorMessage = makeBasicErrorMsg(
ws,
`Dynamic RAM usage calculated to be greater than initial RAM usage on fn: ${fnName}.
ws.env.stopFlag = true;
throw makeRuntimeErrorMsg(
ctx,
`Dynamic RAM usage calculated to be greater than initial RAM usage.
This is probably because you somehow circumvented the static RAM calculation.
Threads: ${threads}
@ -306,14 +307,13 @@ function updateDynamicRam(ctx: NetscriptContext, ramCost: number): void {
One of these could be the reason:
* Using eval() to get a reference to a ns function
&nbsp;&nbsp;const myScan = eval('ns.scan');
\u00a0\u00a0const myScan = eval('ns.scan');
* Using map access to do the same
&nbsp;&nbsp;const myScan = ns['scan'];
\u00a0\u00a0const myScan = ns['scan'];
Sorry :(`,
);
throw new ScriptDeath(ws);
}
}
@ -368,7 +368,7 @@ function isScriptArgs(args: unknown): args is ScriptArg[] {
return Array.isArray(args) && args.every(isScriptArg);
}
async function hack(
function hack(
ctx: NetscriptContext,
hostname: string,
manual: boolean,
@ -454,7 +454,7 @@ async function hack(
if (manual) {
server.backdoorInstalled = true;
}
return Promise.resolve(moneyGained);
return moneyGained;
} else {
// Player only gains 25% exp for failure?
Player.gainHackingExp(expGainedOnFailure);
@ -466,7 +466,7 @@ async function hack(
expGainedOnFailure,
)} exp (t=${numeralWrapper.formatThreads(threads)})`,
);
return Promise.resolve(0);
return 0;
}
});
}
@ -598,10 +598,10 @@ function getRunningScriptByArgs(
scriptArgs: ScriptArg[],
): RunningScript | null {
if (!Array.isArray(scriptArgs)) {
throw helpers.makeBasicErrorMsg(
ctx.workerScript,
`Invalid scriptArgs argument passed into getRunningScript() from ${ctx.function}(). ` +
`This is probably a bug. Please report to game developer`,
throw helpers.makeRuntimeErrorMsg(
ctx,
"Invalid scriptArgs argument passed into getRunningScriptByArgs().\n" +
"This is probably a bug. Please report to game developer",
);
}
@ -619,21 +619,6 @@ function getRunningScriptByArgs(
return ctx.workerScript.scriptRef;
}
/** Provides an array of all function names on a nested object */
function getFunctionNames(obj: object, prefix: string): string[] {
const functionNames: string[] = [];
for (const [key, value] of Object.entries(obj)) {
if (key === "args") {
continue;
} else if (typeof value == "function") {
functionNames.push(prefix + key);
} else if (typeof value == "object") {
functionNames.push(...getFunctionNames(value, key + "."));
}
}
return functionNames;
}
function getRunningScriptByPid(pid: number): RunningScript | null {
for (const server of GetAllServers()) {
const runningScript = findRunningScriptByPid(pid, server);

@ -1,13 +1,12 @@
import { IPlayer } from "src/PersonObjects/IPlayer";
import { Player } from "../Player";
import { IMap } from "../types";
import { NS as INS } from "../ScriptEditor/NetscriptDefinitions";
import { INetscriptExtra } from "../NetscriptFunctions/Extra";
type RamCostTree<API> = {
[Property in keyof API]: API[Property] extends () => void
? number | ((p: IPlayer) => void)
? number | (() => void)
: API[Property] extends object
? RamCostTree<API[Property]>
: never;
@ -89,10 +88,10 @@ export const RamCostConstants: IMap<number> = {
ScriptStanekAcceptGift: 2,
};
function SF4Cost(cost: number): (player: IPlayer) => number {
return (player: IPlayer): number => {
if (player.bitNodeN === 4) return cost;
const sf4 = player.sourceFileLvl(4);
function SF4Cost(cost: number): () => number {
return () => {
if (Player.bitNodeN === 4) return cost;
const sf4 = Player.sourceFileLvl(4);
if (sf4 <= 1) return cost * 16;
if (sf4 === 2) return cost * 4;
return cost;
@ -611,7 +610,7 @@ export const RamCosts: IMap<any> = SourceRamCosts;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _typecheck: RamCostTree<INS & INetscriptExtra> = SourceRamCosts;
export function getRamCost(player: IPlayer, ...args: string[]): number {
export function getRamCost(...args: string[]): number {
if (args.length === 0) {
console.warn(`No arguments passed to getRamCost()`);
return 0;
@ -637,7 +636,7 @@ export function getRamCost(player: IPlayer, ...args: string[]): number {
}
if (typeof curr === "function") {
return curr(player);
return curr();
}
console.warn(`Unexpected type (${curr}) for value [${args}]`);

@ -12,7 +12,6 @@ import { GetServer } from "../Server/AllServers";
import { errorDialog } from "../ui/React/DialogBox";
import { AddRecentScript } from "./RecentScripts";
import { Player } from "../Player";
import { ITutorial } from "../InteractiveTutorial";
import { AlertEvents } from "../ui/React/AlertManager";
@ -102,8 +101,8 @@ function removeWorkerScript(workerScript: WorkerScript): void {
// Recalculate ram used on that server
server.updateRamUsed(0, Player);
for (const rs of server.runningScripts) server.updateRamUsed(server.ramUsed + rs.ramUsage * rs.threads, Player);
server.updateRamUsed(0);
for (const rs of server.runningScripts) server.updateRamUsed(server.ramUsed + rs.ramUsage * rs.threads);
// Delete script from global pool (workerScripts)
workerScripts.delete(workerScript.pid);

@ -931,13 +931,13 @@ const base: InternalAPI<NS> = {
}
// Create new script if it does not already exist
const newScript = new Script(Player, file);
const newScript = new Script(file);
newScript.code = sourceScript.code;
newScript.ramUsage = sourceScript.ramUsage;
newScript.server = destServer.hostname;
destServer.scripts.push(newScript);
helpers.log(ctx, () => `File '${file}' copied over to '${destServer?.hostname}'.`);
newScript.updateRamUsage(Player, destServer.scripts);
newScript.updateRamUsage(destServer.scripts);
}
return noFailures;
@ -1492,12 +1492,12 @@ const base: InternalAPI<NS> = {
let script = ctx.workerScript.getScriptOnServer(fn, server);
if (script == null) {
// Create a new script
script = new Script(Player, fn, String(data), server.hostname, server.scripts);
script = new Script(fn, String(data), server.hostname, server.scripts);
server.scripts.push(script);
return script.updateRamUsage(Player, server.scripts);
return script.updateRamUsage(server.scripts);
}
mode === "w" ? (script.code = String(data)) : (script.code += data);
return script.updateRamUsage(Player, server.scripts);
return script.updateRamUsage(server.scripts);
} else {
// Write to text file
if (!fn.endsWith(".txt")) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid filename: ${fn}`);
@ -1820,7 +1820,7 @@ const base: InternalAPI<NS> = {
function (data) {
let res;
if (isScriptFilename(target)) {
res = s.writeToScriptFile(Player, target, data);
res = s.writeToScriptFile(target, data);
} else {
res = s.writeToTextFile(target, data);
}
@ -1950,4 +1950,18 @@ const ns = {
...NetscriptExtra(),
};
const possibleLogs = Object.fromEntries([...helpers.getFunctionNames(ns, "")].map((a) => [a, true]));
const possibleLogs = Object.fromEntries([...getFunctionNames(ns, "")].map((a) => [a, true]));
/** Provides an array of all function names on a nested object */
function getFunctionNames(obj: object, prefix: string): string[] {
const functionNames: string[] = [];
for (const [key, value] of Object.entries(obj)) {
if (key === "args") {
continue;
} else if (typeof value == "function") {
functionNames.push(prefix + key);
} else if (typeof value == "object") {
functionNames.push(...getFunctionNames(value, key + "."));
}
}
return functionNames;
}

@ -5,12 +5,9 @@
import * as walk from "acorn-walk";
import { parse } from "acorn";
import { helpers } from "./Netscript/NetscriptHelpers";
import { ScriptUrl } from "./Script/ScriptUrl";
import { WorkerScript } from "./Netscript/WorkerScript";
import { Script } from "./Script/Script";
import { areImportsEquals } from "./Terminal/DirectoryHelpers";
import { IPlayer } from "./PersonObjects/IPlayer";
import { ScriptModule } from "./Script/ScriptModule";
// Acorn type def is straight up incomplete so we have to fill with our own.
@ -21,7 +18,7 @@ function makeScriptBlob(code: string): Blob {
return new Blob([code], { type: "text/javascript" });
}
export async function compile(player: IPlayer, script: Script, scripts: Script[]): Promise<ScriptModule> {
export async function compile(script: Script, scripts: Script[]): Promise<ScriptModule> {
//!shouldCompile ensures that script.module is non-null, hence the "as".
if (!shouldCompile(script, scripts)) return script.module as Promise<ScriptModule>;
script.queueCompile = true;
@ -31,7 +28,7 @@ export async function compile(player: IPlayer, script: Script, scripts: Script[]
//If multiple compiles were called on the same script before a compilation could be completed this ensures only one complilation is actually performed.
if (!script.queueCompile) return script.module as Promise<ScriptModule>;
script.queueCompile = false;
script.updateRamUsage(player, scripts);
script.updateRamUsage(scripts);
const uurls = _getScriptUrls(script, scripts, []);
const url = uurls[uurls.length - 1].url;
if (script.url && script.url !== url) URL.revokeObjectURL(script.url);
@ -50,50 +47,6 @@ export async function compile(player: IPlayer, script: Script, scripts: Script[]
return script.module;
}
// Begin executing a user JS script, and return a promise that resolves
// or rejects when the script finishes.
// - script is a script to execute (see Script.js). We depend only on .filename and .code.
// scripts is an array of other scripts on the server.
// env is the global environment that should be visible to all the scripts
// (i.e. hack, grow, etc.).
// When the promise returned by this resolves, we'll have finished
// running the main function of the script.
export async function executeJSScript(
player: IPlayer,
scripts: Script[] = [],
workerScript: WorkerScript,
): Promise<void> {
const script = workerScript.getScript();
if (script === null) throw new Error("script is null");
const loadedModule = await compile(player, script, scripts);
workerScript.ramUsage = script.ramUsage;
const ns = workerScript.env.vars;
if (!loadedModule) {
throw helpers.makeBasicErrorMsg(
workerScript,
`${script.filename} cannot be run because the script module won't load`,
);
}
// TODO: putting await in a non-async function yields unhelpful
// "SyntaxError: unexpected reserved word" with no line number information.
if (!loadedModule.main) {
throw helpers.makeBasicErrorMsg(
workerScript,
`${script.filename} cannot be run because it does not have a main function.`,
);
}
if (!ns) {
throw helpers.makeBasicErrorMsg(
workerScript,
`${script.filename} cannot be run because the NS object hasn't been constructed properly.`,
);
}
await loadedModule.main(ns);
return;
}
function isDependencyOutOfDate(filename: string, scripts: Script[], scriptModuleSequenceNumber: number): boolean {
const depScript = scripts.find((s) => s.filename == filename);

@ -12,7 +12,7 @@ import { generateNextPid } from "./Netscript/Pid";
import { CONSTANTS } from "./Constants";
import { Interpreter } from "./ThirdParty/JSInterpreter";
import { NetscriptFunctions } from "./NetscriptFunctions";
import { executeJSScript, Node } from "./NetscriptJSEvaluator";
import { compile, Node } from "./NetscriptJSEvaluator";
import { IPort } from "./NetscriptPort";
import { RunningScript } from "./Script/RunningScript";
import { getRamUsageFromRunningScript } from "./Script/RunningScriptHelpers";
@ -31,7 +31,6 @@ import { roundToTwo } from "./utils/helpers/roundToTwo";
import { parse } from "acorn";
import { simple as walksimple } from "acorn-walk";
import { areFilesEqual } from "./Terminal/DirectoryHelpers";
import { Player } from "./Player";
import { Terminal } from "./Terminal";
import { ScriptArg } from "./Netscript/ScriptArg";
@ -49,14 +48,26 @@ export function prestigeWorkerScripts(): void {
workerScripts.clear();
}
// 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.
const startNetscript2Script = (workerScript: WorkerScript): Promise<void> =>
executeJSScript(Player, workerScript.getServer().scripts, workerScript);
async function startNetscript2Script(workerScript: WorkerScript): Promise<void> {
const scripts = workerScript.getServer().scripts;
const script = workerScript.getScript();
if (script === null) throw "workerScript had no associated script. This is a bug.";
const loadedModule = await compile(script, scripts);
workerScript.ramUsage = script.ramUsage;
const ns = workerScript.env.vars;
function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
if (!loadedModule) throw `${script.filename} cannot be run because the script module won't load`;
// TODO: Better error for "unexpected reserved word" when using await in non-async function?
if (typeof loadedModule.main !== "function")
throw `${script.filename} cannot be run because it does not have a main function.`;
if (!ns) throw `${script.filename} cannot be run because the NS object hasn't been constructed properly.`;
await loadedModule.main(ns);
return;
}
async function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
const code = workerScript.code;
let errorToThrow: unknown;
//Process imports
let codeWithImports, codeLineOffset;
@ -65,10 +76,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
codeWithImports = importProcessingRes.code;
codeLineOffset = importProcessingRes.lineOffset;
} catch (e: unknown) {
dialogBoxCreate(`Error processing Imports in ${workerScript.name} on ${workerScript.hostname}:\n\n${e}`);
workerScript.env.stopFlag = true;
killWorkerScript(workerScript);
return Promise.resolve();
throw `Error processing Imports in ${workerScript.name}@${workerScript.hostname}:\n\n${e}`;
}
interface BasicObject {
@ -77,20 +85,14 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
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
const wrapper = async (...args: unknown[]) => {
// This async wrapper is sent a resolver function as an extra arg.
// See JSInterpreter.js:3209
try {
// Sent a resolver function as an extra arg. See createAsyncFunction JSInterpreter.js:3209
const callback = args.pop() as (value: unknown) => void;
const result = await entry(...args.map((arg) => int.pseudoToNative(arg)));
return callback(int.nativeToPseudo(result));
} catch (e: unknown) {
// 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;
errorToThrow = e;
}
};
int.setProperty(intLayer, name, int.createAsyncFunction(wrapper));
@ -109,31 +111,16 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
try {
interpreter = new Interpreter(codeWithImports, wrapNS1Layer, codeLineOffset);
} catch (e: unknown) {
dialogBoxCreate(`Syntax ERROR in ${workerScript.name} on ${workerScript.hostname}:\n\n${String(e)}`);
workerScript.env.stopFlag = true;
killWorkerScript(workerScript);
return Promise.resolve();
throw `Syntax ERROR in ${workerScript.name}@${workerScript.hostname}:\n\n${String(e)}`;
}
return new Promise((resolve) => {
function runInterpreter(): void {
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();
}
}
runInterpreter();
});
let more = true;
while (more) {
if (errorToThrow) throw errorToThrow;
if (workerScript.env.stopFlag) return;
for (let i = 0; more && i < 3; i++) more = interpreter.step();
if (more) await new Promise((r) => setTimeout(r, Settings.CodeInstructionRunTime));
}
}
/* Since the JS Interpreter used for Netscript 1.0 only supports ES5, the keyword
@ -326,7 +313,7 @@ function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseS
return false;
}
server.updateRamUsed(roundToTwo(server.ramUsed + ramUsage), Player);
server.updateRamUsed(roundToTwo(server.ramUsed + ramUsage));
// Get the pid
const pid = generateNextPid();
@ -346,20 +333,10 @@ function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseS
workerScripts.set(pid, workerScript);
WorkerScriptStartStopEventEmitter.emit();
// Start the script's execution
let scriptExecution: Promise<void> | null = null; // Script's resulting promise
if (workerScript.name.endsWith(".js")) {
scriptExecution = startNetscript2Script(workerScript);
} else {
scriptExecution = startNetscript1Script(workerScript);
if (!(scriptExecution instanceof Promise)) {
return false;
}
}
// Once the code finishes (either resolved or rejected, doesnt matter), set its
// running status to false
scriptExecution
// Start the script's execution using the correct function for file type
(workerScript.name.endsWith(".js") ? startNetscript2Script : startNetscript1Script)(workerScript)
// Once the code finishes (either resolved or rejected, doesnt matter), set its
// running status to false
.then(function () {
// On natural death, the earnings are transfered to the parent if it still exists.
if (parent && !parent.env.stopFlag) {
@ -370,13 +347,15 @@ function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseS
workerScript.log("", () => "Script finished running");
})
.catch(function (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);
let initialText = `ERROR\n${workerScript.name}@${workerScript.hostname} (PID - ${workerScript.pid})\n\n`;
if (e instanceof SyntaxError) e = `SYNTAX ${initialText}${e.message} (sorry we can't be more helpful)`;
else if (e instanceof Error) {
e = `RUNTIME ${initialText}${e.message}${e.stack ? `\nstack:\n${e.stack.toString()}` : ""}`;
}
errorDialog(e, typeof e === "string" && e.includes(initialText) ? "" : initialText);
workerScript.log("", () => (e instanceof ScriptDeath ? "Script killed." : "Script crashed due to an error."));
killWorkerScript(workerScript);
});
return true;
}

@ -1,4 +1,3 @@
import { Player } from "../Player";
import { isScriptFilename } from "../Script/isScriptFilename";
import { GetServer } from "../Server/AllServers";
import { isValidFilePath } from "../Terminal/DirectoryHelpers";
@ -29,7 +28,7 @@ export const RFARequestHandler: Record<string, (message: RFAMessage) => void | R
const server = GetServer(fileData.server);
if (!server) return error("Server hostname invalid", msg);
if (isScriptFilename(fileData.filename)) server.writeToScriptFile(Player, fileData.filename, fileData.content);
if (isScriptFilename(fileData.filename)) server.writeToScriptFile(fileData.filename, fileData.content);
// Assume it's a text file
else server.writeToTextFile(fileData.filename, fileData.content);

@ -13,7 +13,6 @@ import { RamCalculationErrorCode } from "./RamCalculationErrorCodes";
import { RamCosts, RamCostConstants } from "../Netscript/RamCostGenerator";
import { Script } from "./Script";
import { areImportsEquals } from "../Terminal/DirectoryHelpers";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Node } from "../NetscriptJSEvaluator";
export interface RamUsageEntry {
@ -40,11 +39,8 @@ const memCheckGlobalKey = ".__GLOBAL__";
* Parses code into an AST and walks through it recursively to calculate
* RAM usage. Also accounts for imported modules.
* @param {Script[]} otherScripts - All other scripts on the server. Used to account for imported scripts
* @param {string} codeCopy - The code being parsed
* @param {WorkerScript} workerScript - Object containing RAM costs of Netscript functions. Also used to
* keep track of what functions have/havent been accounted for
*/
function parseOnlyRamCalculate(player: IPlayer, otherScripts: Script[], code: string): RamCalculation {
* @param {string} code - The code being parsed */
function parseOnlyRamCalculate(otherScripts: Script[], code: string): RamCalculation {
try {
/**
* Maps dependent identifiers to their dependencies.
@ -157,11 +153,11 @@ function parseOnlyRamCalculate(player: IPlayer, otherScripts: Script[], code: st
// Check if this identifier is a function in the workerScript environment.
// If it is, then we need to get its RAM cost.
try {
function applyFuncRam(cost: number | ((p: IPlayer) => number)): number {
function applyFuncRam(cost: number | (() => number)): number {
if (typeof cost === "number") {
return cost;
} else if (typeof cost === "function") {
return cost(player);
return cost();
} else {
return 0;
}
@ -178,7 +174,7 @@ function parseOnlyRamCalculate(player: IPlayer, otherScripts: Script[], code: st
prefix: string,
obj: object,
ref: string,
): { func: (p: IPlayer) => number | number; refDetail: string } | undefined => {
): { func: () => number | number; refDetail: string } | undefined => {
if (!obj) return;
const elem = Object.entries(obj).find(([key]) => key === ref);
if (elem !== undefined && (typeof elem[1] === "function" || typeof elem[1] === "number")) {
@ -381,9 +377,9 @@ function parseOnlyCalculateDeps(code: string, currentModule: string): ParseDepsR
* @param {Script[]} otherScripts - All other scripts on the server.
* Used to account for imported scripts
*/
export function calculateRamUsage(player: IPlayer, codeCopy: string, otherScripts: Script[]): RamCalculation {
export function calculateRamUsage(codeCopy: string, otherScripts: Script[]): RamCalculation {
try {
return parseOnlyRamCalculate(player, otherScripts, codeCopy);
return parseOnlyRamCalculate(otherScripts, codeCopy);
} catch (e) {
console.error(`Failed to parse script for RAM calculations:`);
console.error(e);

@ -9,7 +9,6 @@ import { ScriptUrl } from "./ScriptUrl";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver";
import { roundToTwo } from "../utils/helpers/roundToTwo";
import { IPlayer } from "../PersonObjects/IPlayer";
import { ScriptModule } from "./ScriptModule";
let globalModuleSequenceNumber = 0;
@ -52,13 +51,13 @@ export class Script {
// hostname of server that this script is on.
server = "";
constructor(player: IPlayer | null = null, fn = "", code = "", server = "", otherScripts: Script[] = []) {
constructor(fn = "", code = "", server = "", otherScripts: Script[] = []) {
this.filename = fn;
this.code = code;
this.server = server; // hostname of server this script is on
this.moduleSequenceNumber = ++globalModuleSequenceNumber;
if (this.code !== "" && player !== null) {
this.updateRamUsage(player, otherScripts);
if (this.code !== "") {
this.updateRamUsage(otherScripts);
}
}
@ -94,13 +93,13 @@ export class Script {
* @param {string} code - The new contents of the script
* @param {Script[]} otherScripts - Other scripts on the server. Used to process imports
*/
saveScript(player: IPlayer, filename: string, code: string, hostname: string, otherScripts: Script[]): void {
saveScript(filename: string, code: string, hostname: string, otherScripts: Script[]): void {
// Update code and filename
this.code = Script.formatCode(code);
this.filename = filename;
this.server = hostname;
this.updateRamUsage(player, otherScripts);
this.updateRamUsage(otherScripts);
this.markUpdated();
for (const dependent of this.dependents) {
const [dependentScript] = otherScripts.filter(
@ -114,8 +113,8 @@ export class Script {
* Calculates and updates the script's RAM usage based on its code
* @param {Script[]} otherScripts - Other scripts on the server. Used to process imports
*/
updateRamUsage(player: IPlayer, otherScripts: Script[]): void {
const res = calculateRamUsage(player, this.code, otherScripts);
updateRamUsage(otherScripts: Script[]): void {
const res = calculateRamUsage(this.code, otherScripts);
if (res.cost > 0) {
this.ramUsage = roundToTwo(res.cost);
this.ramUsageEntries = res.entries;

@ -252,7 +252,7 @@ export function Root(props: IProps): React.ReactElement {
return;
}
const codeCopy = newCode + "";
const ramUsage = calculateRamUsage(props.player, codeCopy, props.player.getCurrentServer().scripts);
const ramUsage = calculateRamUsage(codeCopy, props.player.getCurrentServer().scripts);
if (ramUsage.cost > 0) {
const entries = ramUsage.entries?.sort((a, b) => b.cost - a.cost) ?? [];
const entriesDisp = [];
@ -466,7 +466,6 @@ export function Root(props: IProps): React.ReactElement {
for (let i = 0; i < server.scripts.length; i++) {
if (scriptToSave.fileName == server.scripts[i].filename) {
server.scripts[i].saveScript(
props.player,
scriptToSave.fileName,
scriptToSave.code,
props.player.currentServer,
@ -480,13 +479,7 @@ export function Root(props: IProps): React.ReactElement {
//If the current script does NOT exist, create a new one
const script = new Script();
script.saveScript(
props.player,
scriptToSave.fileName,
scriptToSave.code,
props.player.currentServer,
server.scripts,
);
script.saveScript(scriptToSave.fileName, scriptToSave.code, props.player.currentServer, server.scripts);
server.scripts.push(script);
} else if (scriptToSave.isTxt) {
for (let i = 0; i < server.textFiles.length; ++i) {
@ -555,7 +548,6 @@ export function Root(props: IProps): React.ReactElement {
for (let i = 0; i < server.scripts.length; i++) {
if (currentScript.fileName == server.scripts[i].filename) {
server.scripts[i].saveScript(
props.player,
currentScript.fileName,
currentScript.code,
props.player.currentServer,
@ -569,13 +561,7 @@ export function Root(props: IProps): React.ReactElement {
//If the current script does NOT exist, create a new one
const script = new Script();
script.saveScript(
props.player,
currentScript.fileName,
currentScript.code,
props.player.currentServer,
server.scripts,
);
script.saveScript(currentScript.fileName, currentScript.code, props.player.currentServer, server.scripts);
server.scripts.push(script);
} else if (currentScript.isTxt) {
for (let i = 0; i < server.textFiles.length; ++i) {

@ -12,7 +12,6 @@ import { isScriptFilename } from "../Script/isScriptFilename";
import { createRandomIp } from "../utils/IPAddress";
import { compareArrays } from "../utils/helpers/compareArrays";
import { IPlayer } from "../PersonObjects/IPlayer";
import { ScriptArg } from "../Netscript/ScriptArg";
interface IConstructorParams {
@ -245,7 +244,7 @@ export class BaseServer {
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
updateRamUsed(ram: number, player: IPlayer): void {
updateRamUsed(ram: number): void {
this.ramUsed = ram;
}
@ -266,7 +265,7 @@ export class BaseServer {
* Write to a script file
* Overwrites existing files. Creates new files if the script does not eixst
*/
writeToScriptFile(player: IPlayer, fn: string, code: string): writeResult {
writeToScriptFile(fn: string, code: string): writeResult {
const ret = { success: false, overwritten: false };
if (!isValidFilePath(fn) || !isScriptFilename(fn)) {
return ret;
@ -277,7 +276,7 @@ export class BaseServer {
if (fn === this.scripts[i].filename) {
const script = this.scripts[i];
script.code = code;
script.updateRamUsage(player, this.scripts);
script.updateRamUsage(this.scripts);
script.markUpdated();
ret.overwritten = true;
ret.success = true;
@ -286,7 +285,7 @@ export class BaseServer {
}
// Otherwise, create a new script
const newScript = new Script(player, fn, code, this.hostname, this.scripts);
const newScript = new Script(fn, code, this.hostname, this.scripts);
this.scripts.push(newScript);
ret.success = true;
return ret;

@ -326,7 +326,7 @@ export function prestigeHomeComputer(player: IPlayer, homeComp: Server): void {
//Update RAM usage on all scripts
homeComp.scripts.forEach(function (script) {
script.updateRamUsage(player, homeComp.scripts);
script.updateRamUsage(homeComp.scripts);
});
homeComp.messages.length = 0; //Remove .lit and .msg files

@ -89,7 +89,7 @@ export function cp(
return;
}
const sRes = server.writeToScriptFile(player, dst, sourceScript.code);
const sRes = server.writeToScriptFile(dst, sourceScript.code);
if (!sRes.success) {
terminal.error(`cp failed`);
return;

@ -94,7 +94,7 @@ export function scp(
return;
}
const sRes = destServer.writeToScriptFile(player, scriptname, sourceScript.code);
const sRes = destServer.writeToScriptFile(scriptname, sourceScript.code);
if (!sRes.success) {
terminal.error(`scp failed`);
return;

@ -28,7 +28,7 @@ export function wget(
function (data: unknown) {
let res;
if (isScriptFilename(target)) {
res = server.writeToScriptFile(player, target, String(data));
res = server.writeToScriptFile(target, String(data));
} else {
res = server.writeToTextFile(target, String(data));
}

@ -286,7 +286,7 @@ export async function determineAllPossibilitiesForTabCompletion(
});
if (!script) return; // Doesn't exist.
//Will return the already compiled module if recompilation not needed.
const loadedModule = await compile(p, script, currServ.scripts);
const loadedModule = await compile(script, currServ.scripts);
if (!loadedModule || !loadedModule.autocomplete) return; // Doesn't have an autocomplete function.
const runArgs = { "--tail": Boolean, "-t": Number };

@ -1,8 +1,8 @@
import React, { useState, useEffect } from "react";
import { IPlayer } from "../PersonObjects/IPlayer";
import { IEngine } from "../IEngine";
import { ITerminal } from "../Terminal/ITerminal";
import { Player } from "../Player";
import { Engine } from "../engine";
import { Terminal } from "../Terminal";
import { installAugmentations } from "../Augmentation/AugmentationHelpers";
import { saveObject } from "../SaveObject";
import { onExport } from "../ExportBonus";
@ -84,12 +84,6 @@ import { V2Modal } from "../utils/V2Modal";
const htmlLocation = location;
interface IProps {
terminal: ITerminal;
player: IPlayer;
engine: IEngine;
}
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
@ -148,20 +142,20 @@ export let Router: IRouter = {
toImportSave: uninitialized,
};
function determineStartPage(player: IPlayer): Page {
function determineStartPage(): Page {
if (RecoveryMode) return Page.Recovery;
if (player.currentWork !== null) return Page.Work;
if (Player.currentWork !== null) return Page.Work;
return Page.Terminal;
}
export function GameRoot({ player, engine, terminal }: IProps): React.ReactElement {
export function GameRoot(): React.ReactElement {
const classes = useStyles();
const [{ files, vim }, setEditorOptions] = useState({ files: {}, vim: false });
const [page, setPage] = useState(determineStartPage(player));
const [page, setPage] = useState(determineStartPage());
const setRerender = useState(0)[1];
const [augPage, setAugPage] = useState<boolean>(false);
const [faction, setFaction] = useState<Faction>(
isFactionWork(player.currentWork) ? Factions[player.currentWork.factionName] : (undefined as unknown as Faction),
isFactionWork(Player.currentWork) ? Factions[Player.currentWork.factionName] : (undefined as unknown as Faction),
);
if (faction === undefined && page === Page.Faction)
throw new Error("Trying to go to a page without the proper setup");
@ -243,7 +237,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
setPage(Page.City);
},
toTravel: () => {
player.gotoLocation(LocationName.TravelAgency);
Player.gotoLocation(LocationName.TravelAgency);
setPage(Page.Travel);
},
toBitVerse: (flume: boolean, quick: boolean) => {
@ -347,7 +341,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
break;
}
case Page.Terminal: {
mainPage = <TerminalRoot terminal={terminal} router={Router} player={player} />;
mainPage = <TerminalRoot terminal={Terminal} router={Router} player={Player} />;
break;
}
case Page.Sleeves: {
@ -366,8 +360,8 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
mainPage = (
<ScriptEditorRoot
files={files}
hostname={player.getCurrentServer().hostname}
player={player}
hostname={Player.getCurrentServer().hostname}
player={Player}
router={Router}
vim={vim}
/>
@ -379,7 +373,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
break;
}
case Page.Hacknet: {
mainPage = <HacknetRoot player={player} />;
mainPage = <HacknetRoot player={Player} />;
break;
}
case Page.CreateProgram: {
@ -387,7 +381,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
break;
}
case Page.Factions: {
mainPage = <FactionsRoot player={player} router={Router} />;
mainPage = <FactionsRoot player={Player} router={Router} />;
break;
}
case Page.Faction: {
@ -395,7 +389,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
break;
}
case Page.Milestones: {
mainPage = <MilestonesRoot player={player} />;
mainPage = <MilestonesRoot player={Player} />;
break;
}
case Page.Tutorial: {
@ -411,7 +405,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
break;
}
case Page.DevMenu: {
mainPage = <DevMenuRoot player={player} engine={engine} router={Router} />;
mainPage = <DevMenuRoot player={Player} engine={Engine} router={Router} />;
break;
}
case Page.Gang: {
@ -431,11 +425,11 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
break;
}
case Page.Travel: {
mainPage = <TravelAgencyRoot p={player} router={Router} />;
mainPage = <TravelAgencyRoot p={Player} router={Router} />;
break;
}
case Page.StockMarket: {
mainPage = <StockMarketRoot p={player} stockMarket={StockMarket} />;
mainPage = <StockMarketRoot p={Player} stockMarket={StockMarket} />;
break;
}
case Page.City: {
@ -450,12 +444,12 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
case Page.Options: {
mainPage = (
<GameOptionsRoot
player={player}
player={Player}
router={Router}
save={() => saveObject.saveGame()}
export={() => {
// Apply the export bonus before saving the game
onExport(player);
onExport(Player);
saveObject.exportGame();
}}
forceKill={killAllScripts}
@ -469,7 +463,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
<AugmentationsRoot
exportGameFn={() => {
// Apply the export bonus before saving the game
onExport(player);
onExport(Player);
saveObject.exportGame();
}}
installAugmentationsFn={() => {
@ -496,7 +490,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
}
return (
<Context.Player.Provider value={player}>
<Context.Player.Provider value={Player}>
<Context.Router.Provider value={Router}>
<ErrorBoundary key={errorBoundaryKey} router={Router} softReset={softReset}>
<BypassWrapper content={bypassGame ? mainPage : null}>
@ -511,7 +505,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
{withSidebar ? (
<Box display="flex" flexDirection="row" width="100%">
<SidebarRoot
player={player}
player={Player}
router={Router}
page={page}
opened={sidebarOpened}

@ -3,9 +3,7 @@ import CircularProgress from "@mui/material/CircularProgress";
import Typography from "@mui/material/Typography";
import Grid from "@mui/material/Grid";
import { Terminal } from "../Terminal";
import { load } from "../db";
import { Player } from "../Player";
import { Engine } from "../engine";
import { GameRoot } from "./GameRoot";
@ -57,7 +55,7 @@ export function LoadingScreen(): React.ReactElement {
}, []);
return loaded ? (
<GameRoot terminal={Terminal} engine={Engine} player={Player} />
<GameRoot />
) : (
<Grid container direction="column" justifyContent="center" alignItems="center" style={{ minHeight: "100vh" }}>
<Grid item>

@ -14,8 +14,10 @@ export function errorDialog(e: unknown, initialText = "") {
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()}` : "");
if (!e.errorMessage.includes(`${e.name}@${e.hostname}`)) {
initialText += `${e.name}@${e.hostname} (PID - ${e.pid})\n\n`;
}
} else if (e instanceof Error) errorText = "Original error message:\n" + e.message;
else {
errorText = "An unknown error was thrown, see console.";
console.error(e);

@ -126,7 +126,7 @@ export function v1APIBreak(): void {
for (const script of server.scripts) {
if (!hasChanges(script.code)) continue;
const prefix = script.filename.includes("/") ? "/BACKUP_" : "BACKUP_";
backups.push(new Script(Player, prefix + script.filename, script.code, script.server));
backups.push(new Script(prefix + script.filename, script.code, script.server));
script.code = convert(script.code);
}
server.scripts = server.scripts.concat(backups);