NETSCRIPT: Rework script ram updates (#408)

This commit is contained in:
Snarling 2023-03-05 22:39:42 -05:00 committed by GitHub
parent 14aafbe0a3
commit 759f86d6e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 171 additions and 177 deletions

@ -1,4 +1,4 @@
import { PlayerObject } from "src/PersonObjects/Player/PlayerObject";
import { PlayerObject } from "../PersonObjects/Player/PlayerObject";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { SkillNames } from "../Bladeburner/data/SkillNames";
import { Skills } from "../Bladeburner/Skills";
@ -341,7 +341,7 @@ export const achievements: Record<string, Achievement> = {
SCRIPT_32GB: {
...achievementData["SCRIPT_32GB"],
Icon: "bigcost",
Condition: () => Player.getHomeComputer().scripts.some((s) => s.ramUsage >= 32),
Condition: () => Player.getHomeComputer().scripts.some((s) => (s.ramUsage as number) >= 32),
},
FIRST_HACKNET_NODE: {
...achievementData["FIRST_HACKNET_NODE"],

@ -33,6 +33,7 @@ import { BaseServer } from "../Server/BaseServer";
import { dialogBoxCreate } from "../ui/React/DialogBox";
import { checkEnum } from "../utils/helpers/enum";
import { RamCostConstants } from "./RamCostGenerator";
import { PositiveInteger } from "../types";
export const helpers = {
string,
@ -158,12 +159,12 @@ function number(ctx: NetscriptContext, argName: string, v: unknown): number {
}
/** Convert provided value v for argument argName to a positive integer, throwing if it looks like something else. */
function positiveInteger(ctx: NetscriptContext, argName: string, v: unknown): number {
function positiveInteger(ctx: NetscriptContext, argName: string, v: unknown): PositiveInteger {
const n = number(ctx, argName, v);
if (n < 1 || !Number.isInteger(n)) {
throw makeRuntimeErrorMsg(ctx, `${argName} should be a positive integer, was ${n}`, "TYPE");
}
return n;
return n as PositiveInteger;
}
/** Returns args back if it is a ScriptArg[]. Throws an error if it is not. */
@ -348,11 +349,6 @@ function updateDynamicRam(ctx: NetscriptContext, ramCost: number): void {
if (ws.dynamicLoadedFns[fnName]) return;
ws.dynamicLoadedFns[fnName] = true;
let threads = ws.scriptRef.threads;
if (typeof threads !== "number") {
console.warn(`WorkerScript detected NaN for thread count for ${ws.name} on ${ws.hostname}`);
threads = 1;
}
ws.dynamicRamUsage = Math.min(ws.dynamicRamUsage + ramCost, RamCostConstants.Max);
if (ws.dynamicRamUsage > 1.01 * ws.ramUsage) {
log(ctx, () => "Insufficient static ram available.");
@ -362,7 +358,7 @@ function updateDynamicRam(ctx: NetscriptContext, ramCost: number): void {
`Dynamic RAM usage calculated to be greater than initial RAM usage.
This is probably because you somehow circumvented the static RAM calculation.
Threads: ${threads}
Threads: ${ws.scriptRef.threads}
Dynamic RAM Usage: ${formatRam(ws.dynamicRamUsage)} per thread
Static RAM Usage: ${formatRam(ws.ramUsage)} per thread

@ -72,7 +72,7 @@ export class WorkerScript {
pid: number;
/** Script's Static RAM usage. Equivalent to underlying script's RAM usage */
ramUsage = 0;
ramUsage = RamCostConstants.Base;
/** Reference to underlying RunningScript object */
scriptRef: RunningScript;

@ -36,7 +36,7 @@ import {
} from "./Server/ServerPurchases";
import { Server } from "./Server/Server";
import { influenceStockThroughServerGrow } from "./StockMarket/PlayerInfluencing";
import { isValidFilePath, removeLeadingSlash } from "./Terminal/DirectoryHelpers";
import { areFilesEqual, isValidFilePath, removeLeadingSlash } from "./Terminal/DirectoryHelpers";
import { TextFile, getTextFile, createTextFile } from "./TextFile";
import { runScriptFromScript } from "./NetscriptWorker";
import { killWorkerScript } from "./Netscript/killWorkerScript";
@ -890,7 +890,8 @@ export const ns: InternalAPI<NSFull> = {
continue;
}
destScript.code = sourceScript.code;
destScript.ramUsage = sourceScript.ramUsage;
// Set ramUsage to null in order to force a recalculation prior to next run.
destScript.ramUsage = null;
destScript.markUpdated();
helpers.log(ctx, () => `WARNING: File '${file}' overwritten on '${destServer?.hostname}'`);
continue;
@ -899,11 +900,11 @@ export const ns: InternalAPI<NSFull> = {
// Create new script if it does not already exist
const newScript = new Script(file);
newScript.code = sourceScript.code;
newScript.ramUsage = sourceScript.ramUsage;
// Set ramUsage to null in order to force a recalculation prior to next run.
newScript.ramUsage = null;
newScript.server = destServer.hostname;
destServer.scripts.push(newScript);
helpers.log(ctx, () => `File '${file}' copied over to '${destServer?.hostname}'.`);
newScript.updateRamUsage(destServer.scripts);
}
return noFailures;
@ -1430,12 +1431,14 @@ export const ns: InternalAPI<NSFull> = {
let script = ctx.workerScript.getScriptOnServer(fn, server);
if (script == null) {
// Create a new script
script = new Script(fn, String(data), server.hostname, server.scripts);
script = new Script(fn, String(data), server.hostname);
server.scripts.push(script);
return script.updateRamUsage(server.scripts);
return;
}
mode === "w" ? (script.code = String(data)) : (script.code += data);
return script.updateRamUsage(server.scripts);
// Set ram to null so a recalc is performed the next time ram usage is needed
script.ramUsage = null;
return;
} else {
// Write to text file
if (!fn.endsWith(".txt")) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid filename: ${fn}`);
@ -1566,12 +1569,14 @@ export const ns: InternalAPI<NSFull> = {
const scriptname = helpers.string(ctx, "scriptname", _scriptname);
const hostname = helpers.string(ctx, "hostname", _hostname);
const server = helpers.getServer(ctx, hostname);
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename == scriptname) {
return server.scripts[i].ramUsage;
}
const script = server.scripts.find((serverScript) => areFilesEqual(serverScript.filename, scriptname));
if (!script) return 0;
const ramUsage = script.getRamUsage(server.scripts);
if (!ramUsage) {
helpers.log(ctx, () => `Could not calculate ram usage for ${scriptname} on ${hostname}.`);
return 0;
}
return 0;
return ramUsage;
},
getRunningScript:
(ctx) =>

@ -6,6 +6,7 @@ import { Apr1Events as devMenu } from "../ui/Apr1";
import { InternalAPI } from "../Netscript/APIWrapper";
import { helpers } from "../Netscript/NetscriptHelpers";
import { Terminal } from "../Terminal";
import { RamCostConstants } from "../Netscript/RamCostGenerator";
export type INetscriptExtra = {
heart: {
@ -36,7 +37,7 @@ export function NetscriptExtra(): InternalAPI<INetscriptExtra> {
real_document.completely_unused_field = undefined;
// set one to true and check that it affected the other.
real_document.completely_unused_field = true;
if (d.completely_unused_field && ctx.workerScript.ramUsage === 1.6) {
if (d.completely_unused_field && ctx.workerScript.ramUsage === RamCostConstants.Base) {
Player.giveExploit(Exploit.Bypass);
}
d.completely_unused_field = undefined;

@ -49,6 +49,7 @@ import { canGetBonus, onExport } from "../ExportBonus";
import { saveObject } from "../SaveObject";
import { calculateCrimeWorkStats } from "../Work/Formulas";
import { findEnumMember } from "../utils/helpers/enum";
import { areFilesEqual } from "../Terminal/DirectoryHelpers";
export function NetscriptSingularity(): InternalAPI<ISingularity> {
const getAugmentation = function (ctx: NetscriptContext, name: string): Augmentation {
@ -77,18 +78,17 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
//Run a script after reset
if (!cbScript) return;
const home = Player.getHomeComputer();
for (const script of home.scripts) {
if (script.filename === cbScript) {
const ramUsage = script.ramUsage;
const ramAvailable = home.maxRam - home.ramUsed;
if (ramUsage > ramAvailable + 0.001) {
return; // Not enough RAM
}
const runningScriptObj = new RunningScript(script, []); // No args
runningScriptObj.threads = 1; // Only 1 thread
startWorkerScript(runningScriptObj, home);
}
const script = home.scripts.find((serverScript) => areFilesEqual(serverScript.filename, cbScript));
if (!script) return;
const ramUsage = script.getRamUsage(home.scripts);
if (!ramUsage) {
return Terminal.error(`Attempted to launch ${cbScript} after reset but could not calculate ram usage.`);
}
const ramAvailable = home.maxRam - home.ramUsed;
if (ramUsage > ramAvailable + 0.001) return;
// Start script with no args and 1 thread (default).
const runningScriptObj = new RunningScript(script, ramUsage, []);
startWorkerScript(runningScriptObj, home);
};
const singularityAPI: InternalAPI<ISingularity> = {

@ -28,7 +28,6 @@ export async function compile(script: Script, scripts: Script[]): Promise<Script
//If multiple compiles were called on the same script before a compilation could be completed this ensures only one compilation is actually performed.
if (!script.queueCompile) return script.module as Promise<ScriptModule>;
script.queueCompile = false;
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);

@ -1,12 +1,13 @@
import { Settings } from "./Settings/Settings";
import { NetscriptPort } from "@nsdefs";
import { NetscriptPorts } from "./NetscriptWorker";
import { PositiveInteger } from "./types";
type PortData = string | number;
type Resolver = () => void;
const emptyPortData = "NULL PORT DATA";
/** The object property is for typechecking and is not present at runtime */
export type PortNumber = number & { __PortNumber: true };
export type PortNumber = PositiveInteger & { __PortNumber: true };
/** Gets the numbered port, initializing it if it doesn't already exist.
* Only using for functions that write data/resolvers. Use NetscriptPorts.get(n) for */

@ -15,7 +15,6 @@ import { NetscriptFunctions } from "./NetscriptFunctions";
import { compile, Node } from "./NetscriptJSEvaluator";
import { Port, PortNumber } from "./NetscriptPort";
import { RunningScript } from "./Script/RunningScript";
import { getRamUsageFromRunningScript } from "./Script/RunningScriptHelpers";
import { scriptCalculateOfflineProduction } from "./Script/ScriptHelpers";
import { Script } from "./Script/Script";
import { GetAllServers } from "./Server/AllServers";
@ -32,8 +31,9 @@ import { parse } from "acorn";
import { simple as walksimple } from "acorn-walk";
import { areFilesEqual } from "./Terminal/DirectoryHelpers";
import { Terminal } from "./Terminal";
import { ScriptArg } from "./Netscript/ScriptArg";
import { ScriptArg } from "@nsdefs";
import { handleUnknownError } from "./Netscript/NetscriptHelpers";
import { PositiveInteger } from "./types";
export const NetscriptPorts: Map<PortNumber, Port> = new Map();
@ -53,6 +53,7 @@ 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.";
if (!script.ramUsage) throw "Attempting to start a script with no calculated ram cost. This is a bug.";
const loadedModule = await compile(script, scripts);
workerScript.ramUsage = script.ramUsage;
const ns = workerScript.env.vars;
@ -294,15 +295,7 @@ export function startWorkerScript(runningScript: RunningScript, server: BaseServ
* returns {boolean} indicating whether or not the workerScript was successfully added
*/
function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseServer, parent?: WorkerScript): boolean {
// Update server's ram usage
let threads = 1;
if (runningScriptObj.threads && !isNaN(runningScriptObj.threads)) {
threads = runningScriptObj.threads;
} else {
runningScriptObj.threads = 1;
}
const oneRamUsage = getRamUsageFromRunningScript(runningScriptObj);
const ramUsage = roundToTwo(oneRamUsage * threads);
const ramUsage = roundToTwo(runningScriptObj.ramUsage * runningScriptObj.threads);
const ramAvailable = server.maxRam - server.ramUsed;
if (ramUsage > ramAvailable + 0.001) {
dialogBoxCreate(
@ -327,7 +320,7 @@ function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseS
// Create the WorkerScript. NOTE: WorkerScript ctor will set the underlying
// RunningScript's PID as well
const workerScript = new WorkerScript(runningScriptObj, pid, NetscriptFunctions);
workerScript.ramUsage = oneRamUsage;
workerScript.ramUsage = runningScriptObj.ramUsage;
// Add the WorkerScript to the global pool
workerScripts.set(pid, workerScript);
@ -405,82 +398,61 @@ export function loadAllRunningScripts(): void {
/** Run a script from inside another script (run(), exec(), spawn(), etc.) */
export function runScriptFromScript(
caller: string,
server: BaseServer,
host: BaseServer,
scriptname: string,
args: ScriptArg[],
workerScript: WorkerScript,
threads = 1,
threads = 1 as PositiveInteger,
): number {
// Sanitize arguments
if (!(workerScript instanceof WorkerScript)) {
/* Very inefficient, TODO change data structures so that finding script & checking if it's already running does
* not require iterating through all scripts and comparing names/args. This is a big part of slowdown when
* running a large number of scripts. */
// Find the script, fail if it doesn't exist.
const script = host.scripts.find((serverScript) => areFilesEqual(serverScript.filename, scriptname));
if (!script) {
workerScript.log(caller, () => `Could not find script '${scriptname}' on '${host.hostname}'`);
return 0;
}
if (typeof scriptname !== "string" || !Array.isArray(args)) {
workerScript.log(caller, () => `Invalid arguments: scriptname='${scriptname} args='${args}'`);
console.error(`runScriptFromScript() failed due to invalid arguments`);
// Check if script is already running on server and fail if it is.
if (host.getRunningScript(scriptname, args)) {
workerScript.log(caller, () => `'${scriptname}' is already running on '${host.hostname}'`);
return 0;
}
args.forEach((arg, i) => {
if (typeof arg !== "string" && typeof arg !== "number" && typeof arg !== "boolean")
throw new Error(
"Only strings, numbers, and booleans can be passed as arguments to other scripts.\n" +
`${scriptname} argument index ${i} is of type ${typeof arg} and value ${JSON.stringify(arg)}`,
);
});
// Check if the script is already running
const runningScriptObj = server.getRunningScript(scriptname, args);
if (runningScriptObj != null) {
workerScript.log(caller, () => `'${scriptname}' is already running on '${server.hostname}'`);
const singleRamUsage = script.getRamUsage(host.scripts);
if (!singleRamUsage) {
workerScript.log(caller, () => `Ram usage could not be calculated for ${scriptname}`);
return 0;
}
// 'null/undefined' arguments are not allowed
for (let i = 0; i < args.length; ++i) {
if (args[i] == null) {
workerScript.log(caller, () => "Cannot execute a script with null/undefined as an argument");
return 0;
}
// Check if admin rights on host, fail if not.
if (host.hasAdminRights == false) {
workerScript.log(caller, () => `You do not have root access on '${host.hostname}'`);
return 0;
}
// Check if the script exists and if it does run it
for (let i = 0; i < server.scripts.length; ++i) {
if (!areFilesEqual(server.scripts[i].filename, scriptname)) continue;
// Check for admin rights and that there is enough RAM available to run
const script = server.scripts[i];
let ramUsage = script.ramUsage;
threads = Math.floor(Number(threads));
if (threads === 0) {
return 0;
}
ramUsage = ramUsage * threads;
const ramAvailable = server.maxRam - server.ramUsed;
// Calculate ram usage including thread count
const ramUsage = singleRamUsage * threads;
if (server.hasAdminRights == false) {
workerScript.log(caller, () => `You do not have root access on '${server.hostname}'`);
return 0;
} else if (ramUsage > ramAvailable + 0.001) {
workerScript.log(
caller,
() =>
`Cannot run script '${scriptname}' (t=${threads}) on '${server.hostname}' because there is not enough available RAM!`,
);
return 0;
}
// Able to run script
// Check if there is enough ram to run the script, fail if not.
const ramAvailable = host.maxRam - host.ramUsed;
if (ramUsage > ramAvailable + 0.001) {
workerScript.log(
caller,
() => `'${scriptname}' on '${server.hostname}' with ${threads} threads and args: ${arrayToString(args)}.`,
() =>
`Cannot run script '${scriptname}' (t=${threads}) on '${host.hostname}' because there is not enough available RAM!`,
);
const runningScriptObj = new RunningScript(script, args);
runningScriptObj.threads = threads;
runningScriptObj.server = server.hostname;
return startWorkerScript(runningScriptObj, server, workerScript);
return 0;
}
// Able to run script
workerScript.log(
caller,
() => `'${scriptname}' on '${host.hostname}' with ${threads} threads and args: ${arrayToString(args)}.`,
);
const runningScriptObj = new RunningScript(script, singleRamUsage, args);
runningScriptObj.threads = threads;
workerScript.log(caller, () => `Could not find script '${scriptname}' on '${server.hostname}'`);
return 0;
return startWorkerScript(runningScriptObj, host, workerScript);
}

@ -124,8 +124,8 @@ export const RFARequestHandler: Record<string, (message: RFAMessage) => void | R
if (!isScriptFilename(fileData.filename)) return error("Filename isn't a script filename", msg);
const script = server.getScript(fileData.filename);
if (!script) return error("File doesn't exist", msg);
const ramUsage = script.ramUsage;
const ramUsage = script.getRamUsage(server.scripts);
if (!ramUsage) return error("Ram cost could not be calculated", msg);
return new RFAMessage({ result: ramUsage, id: msg.id });
},

@ -10,7 +10,9 @@ import { Terminal } from "../Terminal";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver";
import { formatTime } from "../utils/helpers/formatTime";
import { ScriptArg } from "../Netscript/ScriptArg";
import { ScriptArg } from "@nsdefs";
import { RamCostConstants } from "../Netscript/RamCostGenerator";
import { PositiveInteger } from "../types";
export class RunningScript {
// Script arguments
@ -52,25 +54,24 @@ export class RunningScript {
pid = -1;
// How much RAM this script uses for ONE thread
ramUsage = 0;
ramUsage = RamCostConstants.Base;
// hostname of the server on which this script is running
server = "";
// Number of threads that this script is running with
threads = 1;
threads = 1 as PositiveInteger;
// Script urls for the current running script for translating urls back to file names in errors
dependencies: ScriptUrl[] = [];
constructor(script: Script | null = null, args: ScriptArg[] = []) {
if (script == null) {
return;
}
constructor(script?: Script, ramUsage?: number, args: ScriptArg[] = []) {
if (!script) return;
if (!ramUsage) throw new Error("Must provide a ramUsage for RunningScript initialization.");
this.filename = script.filename;
this.args = args;
this.server = script.server;
this.ramUsage = script.ramUsage;
this.ramUsage = ramUsage;
this.dependencies = script.dependencies;
}

@ -1,22 +0,0 @@
import { GetServer } from "../Server/AllServers";
import { RunningScript } from "./RunningScript";
export function getRamUsageFromRunningScript(script: RunningScript): number {
if (script.ramUsage != null && script.ramUsage > 0) {
return script.ramUsage; // Use cached value
}
const server = GetServer(script.server);
if (server == null) {
return 0;
}
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === script.filename) {
// Cache the ram usage for the next call
script.ramUsage = server.scripts[i].ramUsage;
return script.ramUsage;
}
}
return 0;
}

@ -10,6 +10,7 @@ import { ScriptUrl } from "./ScriptUrl";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver";
import { roundToTwo } from "../utils/helpers/roundToTwo";
import { ScriptModule } from "./ScriptModule";
import { RamCostConstants } from "../Netscript/RamCostGenerator";
let globalModuleSequenceNumber = 0;
@ -41,8 +42,8 @@ export class Script {
dependencies: ScriptUrl[] = [];
dependents: ScriptReference[] = [];
// Amount of RAM this Script requires to run
ramUsage = 0;
// Amount of RAM this Script requires to run. null indicates an error in calculating ram.
ramUsage: number | null = null;
ramUsageEntries?: RamUsageEntry[];
// Used to deconflict multiple simultaneous compilations.
@ -51,14 +52,11 @@ export class Script {
// hostname of server that this script is on.
server = "";
constructor(fn = "", code = "", server = "", otherScripts: Script[] = []) {
constructor(fn = "", code = "", server = "") {
this.filename = fn;
this.code = code;
this.server = server; // hostname of server this script is on
this.moduleSequenceNumber = ++globalModuleSequenceNumber;
if (this.code !== "") {
this.updateRamUsage(otherScripts);
}
}
/** Download the script as a file */
@ -97,14 +95,24 @@ export class Script {
this.filename = filename;
this.server = hostname;
this.updateRamUsage(otherScripts);
// Null ramUsage forces a recalc next time ramUsage is needed
this.ramUsage = null;
this.markUpdated();
for (const dependent of this.dependents) {
const [dependentScript] = otherScripts.filter(
(s) => s.filename === dependent.filename && s.server == dependent.server,
this.dependents.forEach((dependent) => {
const scriptToUpdate = otherScripts.find(
(otherScript) => otherScript.filename === dependent.filename && otherScript.server === dependent.server,
);
dependentScript?.markUpdated();
}
if (!scriptToUpdate) return;
scriptToUpdate.ramUsage = null;
scriptToUpdate.markUpdated();
});
}
/** Gets the ram usage, while also attempting to update it if it's currently null */
getRamUsage(otherScripts: Script[]): number | null {
if (this.ramUsage) return this.ramUsage;
this.updateRamUsage(otherScripts);
return this.ramUsage;
}
/**
@ -112,11 +120,11 @@ export class Script {
* @param {Script[]} otherScripts - Other scripts on the server. Used to process imports
*/
updateRamUsage(otherScripts: Script[]): void {
const res = calculateRamUsage(this.code, otherScripts);
if (res.cost > 0) {
this.ramUsage = roundToTwo(res.cost);
this.ramUsageEntries = res.entries;
}
const ramCalc = calculateRamUsage(this.code, otherScripts);
if (ramCalc.cost >= RamCostConstants.Base) {
this.ramUsage = roundToTwo(ramCalc.cost);
this.ramUsageEntries = ramCalc.entries;
} else this.ramUsage = null;
this.markUpdated();
}

@ -272,7 +272,8 @@ export abstract class BaseServer {
if (fn === this.scripts[i].filename) {
const script = this.scripts[i];
script.code = code;
script.updateRamUsage(this.scripts);
// Set ramUsage to null in order to force recalculation on next run
script.ramUsage = null;
script.markUpdated();
ret.overwritten = true;
ret.success = true;
@ -281,7 +282,7 @@ export abstract class BaseServer {
}
// Otherwise, create a new script
const newScript = new Script(fn, code, this.hostname, this.scripts);
const newScript = new Script(fn, code, this.hostname);
this.scripts.push(newScript);
ret.success = true;
return ret;

@ -254,9 +254,9 @@ export function prestigeHomeComputer(homeComp: Server): void {
homeComp.programs.push(Programs.BitFlume.name);
}
//Update RAM usage on all scripts
//Reset RAM usage calculation for all scripts
homeComp.scripts.forEach(function (script) {
script.updateRamUsage(homeComp.scripts);
script.ramUsage = null;
});
homeComp.messages.length = 0; //Remove .lit and .msg files

@ -1,8 +1,9 @@
import { Terminal } from "../../Terminal";
import { formatRam } from "../../ui/formatNumber";
import { Settings } from "../../Settings/Settings";
import { BaseServer } from "../../Server/BaseServer";
export function mem(args: (string | number | boolean)[]): void {
export function mem(args: (string | number | boolean)[], server: BaseServer): void {
try {
if (args.length !== 1 && args.length !== 3) {
Terminal.error("Incorrect usage of mem command. usage: mem [scriptname] [-t] [number threads]");
@ -25,7 +26,10 @@ export function mem(args: (string | number | boolean)[]): void {
return;
}
const ramUsage = script.ramUsage * numThreads;
const singleRamUsage = script.getRamUsage(server.scripts);
if (!singleRamUsage) return Terminal.error(`Could not calculate ram usage for ${scriptName}`);
const ramUsage = singleRamUsage * numThreads;
Terminal.print(`This script requires ${formatRam(ramUsage)} of RAM to run for ${numThreads} thread(s)`);

@ -6,6 +6,8 @@ import { RunningScript } from "../../Script/RunningScript";
import { findRunningScript } from "../../Script/ScriptHelpers";
import * as libarg from "arg";
import { formatRam } from "../../ui/formatNumber";
import { ScriptArg } from "@nsdefs";
import { isPositiveInteger } from "../../types";
export function runScript(commandArgs: (string | number | boolean)[], server: BaseServer): void {
if (commandArgs.length < 1) {
@ -24,11 +26,12 @@ export function runScript(commandArgs: (string | number | boolean)[], server: Ba
});
const tailFlag = flags["--tail"] === true;
const numThreads = parseFloat(flags["-t"] ?? 1);
if (numThreads < 1 || !Number.isInteger(numThreads)) {
Terminal.error("Invalid number of threads specified. Number of threads must be an integer greater than 0");
return;
if (!isPositiveInteger(numThreads)) {
return Terminal.error("Invalid number of threads specified. Number of threads must be an integer greater than 0");
}
const args = flags["_"];
// Todo: Switch out arg for something with typescript support
const args = flags["_"] as ScriptArg[];
// Check if this script is already running
if (findRunningScript(scriptName, args, server) != null) {
@ -46,7 +49,9 @@ export function runScript(commandArgs: (string | number | boolean)[], server: Ba
// Check for admin rights and that there is enough RAM available to run
const script = server.scripts[i];
script.server = server.hostname;
const ramUsage = script.ramUsage * numThreads;
const singleRamUsage = script.getRamUsage(server.scripts);
if (!singleRamUsage) return Terminal.error("Error while calculating ram usage for this script.");
const ramUsage = singleRamUsage * numThreads;
const ramAvailable = server.maxRam - server.ramUsed;
if (!server.hasAdminRights) {
@ -64,7 +69,7 @@ export function runScript(commandArgs: (string | number | boolean)[], server: Ba
}
// Able to run script
const runningScript = new RunningScript(script, args);
const runningScript = new RunningScript(script, singleRamUsage, args);
runningScript.threads = numThreads;
const success = startWorkerScript(runningScript, server);

@ -1,6 +1,5 @@
import { Terminal } from "../../Terminal";
import { BaseServer } from "../../Server/BaseServer";
import { getRamUsageFromRunningScript } from "../../Script/RunningScriptHelpers";
import { formatRam } from "../../ui/formatNumber";
export function top(args: (string | number | boolean)[], server: BaseServer): void {
@ -45,7 +44,7 @@ export function top(args: (string | number | boolean)[], server: BaseServer): vo
const spacesThread = " ".repeat(numSpacesThread);
// Calculate and transform RAM usage
const ramUsage = formatRam(getRamUsageFromRunningScript(script) * script.threads);
const ramUsage = formatRam(script.ramUsage * script.threads);
const entry = [script.filename, spacesScript, script.pid, spacesPid, script.threads, spacesThread, ramUsage].join(
"",

@ -1,4 +1,4 @@
import { Person } from "src/PersonObjects/Person";
import { Person } from "../PersonObjects/Person";
import { Player } from "@player";
import { Multipliers } from "../PersonObjects/Multipliers";

@ -1,3 +1,12 @@
// The object properties on these numeric types are for typechecking only and do not exist at runtime. They're just a way to require a typechecking function.
export type Integer = number & { __Integer: true };
export type PositiveNumber = number & { __Positive: true };
export type PositiveInteger = Integer & PositiveNumber;
// Numeric typechecking functions
export const isInteger = (n: unknown): n is Integer => Number.isInteger(n);
export const isPositiveInteger = (n: unknown): n is PositiveInteger => isInteger(n) && n > 0;
/** Status object for functions that return a boolean indicating success/failure
* and an optional message */
export interface IReturnStatus {

@ -20,6 +20,8 @@ import { Settings } from "../../Settings/Settings";
import { ANSIITypography } from "./ANSIITypography";
import { ScriptArg } from "../../Netscript/ScriptArg";
import { useRerender } from "./hooks";
import { areFilesEqual } from "../../Terminal/DirectoryHelpers";
import { dialogBoxCreate } from "./DialogBox";
let layerCounter = 0;
@ -220,7 +222,17 @@ function LogWindow(props: IProps): React.ReactElement {
if (server === null) return;
const s = findRunningScript(script.filename, script.args, server);
if (s === null) {
script.ramUsage = 0;
const baseScript = server.scripts.find((serverScript) => areFilesEqual(serverScript.filename, script.filename));
if (!baseScript) {
return dialogBoxCreate(
`Could not launch script. The script ${script.filename} no longer exists on the server ${server.hostname}.`,
);
}
const ramUsage = baseScript.getRamUsage(server.scripts);
if (!ramUsage) {
return dialogBoxCreate(`Could not calculate ram usage for ${script.filename} on ${server.hostname}.`);
}
script.ramUsage = ramUsage;
startWorkerScript(script, server);
} else {
setScript(s);

@ -2,7 +2,7 @@ import * as React from "react";
import LinearProgress from "@mui/material/LinearProgress";
import { TableCell, Tooltip, Typography } from "@mui/material";
import { characterOverviewStyles } from "./CharacterOverview";
import { ISkillProgress } from "src/PersonObjects/formulas/skill";
import { ISkillProgress } from "../../PersonObjects/formulas/skill";
import { formatExp } from "../formatNumber";
interface IProgressProps {

@ -36,8 +36,11 @@ describe("Netscript RAM Calculation/Generation Tests", function () {
/** Creates a RunningScript object which calculates static ram usage */
function createRunningScript(code: string) {
script.code = code;
script.updateRamUsage([]);
const runningScript = new RunningScript(script);
// Force ram calculation reset
script.ramUsage = null;
const ramUsage = script.getRamUsage([]);
if (!ramUsage) throw new Error("Ram usage should be defined.");
const runningScript = new RunningScript(script, ramUsage);
return runningScript;
}

@ -52,7 +52,7 @@ test.each([
const alerted = new Promise((resolve) => {
alertDelete = AlertEvents.subscribe((x) => resolve(x));
});
server = new Server({ hostname: "home", hasAdminRights: true, maxRam: 8 });
server = new Server({ hostname: "home", adminRights: true, maxRam: 8 });
AddToAllServers(server);
for (const s of scripts) {
expect(server.writeToScriptFile(s.name, s.code)).toEqual({ success: true, overwritten: false });
@ -61,8 +61,8 @@ test.each([
const script = server.scripts[server.scripts.length - 1];
expect(script.filename).toEqual(scripts[scripts.length - 1].name);
const runningScript = new RunningScript(script);
runningScript.threads = 1;
const ramUsage = script.getRamUsage(server.scripts);
const runningScript = new RunningScript(script, ramUsage as number);
expect(startWorkerScript(runningScript, server)).toBeGreaterThan(0);
// We don't care about start, so subscribe after that. Await script death.
const result = await Promise.race([