mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-18 20:25:45 +01:00
NETSCRIPT: Rework script ram updates (#408)
This commit is contained in:
parent
14aafbe0a3
commit
759f86d6e5
@ -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([
|
||||
|
Loading…
Reference in New Issue
Block a user