Merge pull request #4052 from Snarling/ns1wrapper

NETSCRIPT: FIX #4037 ns1 wraps deeper layers correctly.
This commit is contained in:
hydroflame 2022-08-29 11:14:32 -03:00 committed by GitHub
commit c2796e164a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 59 additions and 146 deletions

@ -94,11 +94,6 @@ export class WorkerScript {
*/ */
ramUsage = 0; ramUsage = 0;
/**
* Whether or not this workerScript is currently running
*/
running = false;
/** /**
* Reference to underlying RunningScript object * Reference to underlying RunningScript object
*/ */

@ -702,7 +702,7 @@ const base: InternalAPI<NS> = {
throw helpers.makeRuntimeErrorMsg(ctx, "Could not find server. This is a bug. Report to dev."); throw helpers.makeRuntimeErrorMsg(ctx, "Could not find server. This is a bug. Report to dev.");
} }
return runScriptFromScript(Player, "run", scriptServer, scriptname, args, ctx.workerScript, threads); return runScriptFromScript("run", scriptServer, scriptname, args, ctx.workerScript, threads);
}, },
exec: exec:
(ctx: NetscriptContext) => (ctx: NetscriptContext) =>
@ -718,7 +718,7 @@ const base: InternalAPI<NS> = {
throw helpers.makeRuntimeErrorMsg(ctx, `Invalid thread count. Must be numeric and > 0, is ${threads}`); throw helpers.makeRuntimeErrorMsg(ctx, `Invalid thread count. Must be numeric and > 0, is ${threads}`);
} }
const server = helpers.getServer(ctx, hostname); const server = helpers.getServer(ctx, hostname);
return runScriptFromScript(Player, "exec", server, scriptname, args, ctx.workerScript, threads); return runScriptFromScript("exec", server, scriptname, args, ctx.workerScript, threads);
}, },
spawn: spawn:
(ctx: NetscriptContext) => (ctx: NetscriptContext) =>
@ -740,12 +740,11 @@ const base: InternalAPI<NS> = {
throw helpers.makeRuntimeErrorMsg(ctx, "Could not find server. This is a bug. Report to dev"); throw helpers.makeRuntimeErrorMsg(ctx, "Could not find server. This is a bug. Report to dev");
} }
return runScriptFromScript(Player, "spawn", scriptServer, scriptname, args, ctx.workerScript, threads); return runScriptFromScript("spawn", scriptServer, scriptname, args, ctx.workerScript, threads);
}, spawnDelay * 1e3); }, spawnDelay * 1e3);
helpers.log(ctx, () => `Will execute '${scriptname}' in ${spawnDelay} seconds`); helpers.log(ctx, () => `Will execute '${scriptname}' in ${spawnDelay} seconds`);
ctx.workerScript.running = false; // Prevent workerScript from "finishing execution naturally"
if (killWorkerScript(ctx.workerScript)) { if (killWorkerScript(ctx.workerScript)) {
helpers.log(ctx, () => "Exiting..."); helpers.log(ctx, () => "Exiting...");
} }
@ -820,7 +819,6 @@ const base: InternalAPI<NS> = {
return scriptsKilled > 0; return scriptsKilled > 0;
}, },
exit: (ctx: NetscriptContext) => (): void => { exit: (ctx: NetscriptContext) => (): void => {
ctx.workerScript.running = false; // Prevent workerScript from "finishing execution naturally"
if (killWorkerScript(ctx.workerScript)) { if (killWorkerScript(ctx.workerScript)) {
helpers.log(ctx, () => "Exiting..."); helpers.log(ctx, () => "Exiting...");
} else { } else {

@ -37,7 +37,7 @@ export function NetscriptCodingContract(): InternalAPI<ICodingContract> {
throw new Error("The answer provided was not a number, string, or array"); throw new Error("The answer provided was not a number, string, or array");
// Convert answer to string. // Convert answer to string.
const answerStr = typeof answer === 'string' ? answer : JSON.stringify(answer); const answerStr = typeof answer === "string" ? answer : JSON.stringify(answer);
const creward = contract.reward; const creward = contract.reward;
if (creward === null) throw new Error("Somehow solved a contract that didn't have a reward"); if (creward === null) throw new Error("Somehow solved a contract that didn't have a reward");

@ -92,7 +92,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
} }
const runningScriptObj = new RunningScript(script, []); // No args const runningScriptObj = new RunningScript(script, []); // No args
runningScriptObj.threads = 1; // Only 1 thread runningScriptObj.threads = 1; // Only 1 thread
startWorkerScript(player, runningScriptObj, home); startWorkerScript(runningScriptObj, home);
} }
} }
}; };
@ -235,8 +235,6 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
runAfterReset(cbScript); runAfterReset(cbScript);
}, 0); }, 0);
// Prevent ctx.workerScript from "finishing execution naturally"
ctx.workerScript.running = false;
killWorkerScript(ctx.workerScript); killWorkerScript(ctx.workerScript);
}, },
installAugmentations: (ctx: NetscriptContext) => installAugmentations: (ctx: NetscriptContext) =>
@ -255,7 +253,6 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
runAfterReset(cbScript); runAfterReset(cbScript);
}, 0); }, 0);
ctx.workerScript.running = false; // Prevent ctx.workerScript from "finishing execution naturally"
killWorkerScript(ctx.workerScript); killWorkerScript(ctx.workerScript);
return true; return true;
}, },

@ -34,10 +34,8 @@ import { simple as walksimple } from "acorn-walk";
import { areFilesEqual } from "./Terminal/DirectoryHelpers"; import { areFilesEqual } from "./Terminal/DirectoryHelpers";
import { Player } from "./Player"; import { Player } from "./Player";
import { Terminal } from "./Terminal"; import { Terminal } from "./Terminal";
import { IPlayer } from "./PersonObjects/IPlayer";
import { ScriptArg } from "./Netscript/ScriptArg"; import { ScriptArg } from "./Netscript/ScriptArg";
import { helpers } from "./Netscript/NetscriptHelpers"; import { helpers } from "./Netscript/NetscriptHelpers";
import { NS } from "./ScriptEditor/NetscriptDefinitions";
// Netscript Ports are instantiated here // Netscript Ports are instantiated here
export const NetscriptPorts: IPort[] = []; export const NetscriptPorts: IPort[] = [];
@ -61,10 +59,9 @@ export function prestigeWorkerScripts(): void {
// JS script promises need a little massaging to have the same guarantees as netscript // 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 // 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. // that resolves or rejects when the corresponding worker script is done.
function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Promise<void> { function startNetscript2Script(workerScript: WorkerScript): Promise<void> {
workerScript.running = true;
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
executeJSScript(player, workerScript.getServer().scripts, workerScript) executeJSScript(Player, workerScript.getServer().scripts, workerScript)
.then(() => { .then(() => {
resolve(); resolve();
}) })
@ -98,7 +95,6 @@ function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Pro
function startNetscript1Script(workerScript: WorkerScript): Promise<void> { function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
const code = workerScript.code; const code = workerScript.code;
workerScript.running = true;
//Process imports //Process imports
let codeWithImports, codeLineOffset; let codeWithImports, codeLineOffset;
@ -109,120 +105,62 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
} catch (e: unknown) { } catch (e: unknown) {
dialogBoxCreate("Error processing Imports in " + workerScript.name + ":<br>" + String(e)); dialogBoxCreate("Error processing Imports in " + workerScript.name + ":<br>" + String(e));
workerScript.env.stopFlag = true; workerScript.env.stopFlag = true;
workerScript.running = false;
killWorkerScript(workerScript); killWorkerScript(workerScript);
return Promise.resolve(); return Promise.resolve();
} }
const interpreterInitialization = function (int: Interpreter, scope: unknown): void { function wrapNS1Layer(int: Interpreter, intLayer: unknown, path: string[] = []) {
interface NS1 extends NS { //TODO: Better typing layers of interpreter scope and ns
interface BasicObject {
[key: string]: any; [key: string]: any;
} }
//Add the Netscript environment const nsLayer = path.reduce((prev, newPath) => prev[newPath], workerScript.env.vars as BasicObject);
const ns = NetscriptFunctions(workerScript) as NS1; for (const [name, entry] of Object.entries(nsLayer)) {
for (const name of Object.keys(ns)) {
const entry = ns[name];
if (typeof entry === "function") { if (typeof entry === "function") {
//Async functions need to be wrapped. See JS-Interpreter documentation // Async functions need to be wrapped. See JS-Interpreter documentation
const asyncFuncs = ["hack", "grow", "weaken", "sleep", "prompt", "manualHack", "share", "wget"]; const wrapper = async (...args: unknown[]) => {
// This async wrapper is sent a resolver function as an extra arg.
if (asyncFuncs.includes(name)) { // See JSInterpreter.js:3209
const tempWrapper = function (...args: unknown[]): void { try {
const fnArgs = []; const callback = args.pop() as (value: unknown) => void;
const result = await entry(...args.map(int.pseudoToNative));
//All of the Object/array elements are in JSInterpreter format, so return callback(int.nativeToPseudo(result));
//we have to convert them back to native format to pass them to these fns } catch (e: unknown) {
for (let i = 0; i < args.length - 1; ++i) { // TODO: Unify error handling, this was stolen from previous async handler
if (typeof args[i] === "object" || Array.isArray(args[i])) { if (typeof e === "string") {
fnArgs.push(int.pseudoToNative(args[i])); console.error(e);
} else { const errorTextArray = e.split("|DELIMITER|");
fnArgs.push(args[i]); const hostname = errorTextArray[1];
} const scriptName = errorTextArray[2];
const errorMsg = errorTextArray[3];
let msg = `${scriptName}@${hostname}<br>`;
msg += "<br>";
msg += errorMsg;
dialogBoxCreate(msg);
workerScript.env.stopFlag = true;
killWorkerScript(workerScript);
return;
} }
const callb = args[args.length - 1]; }
const fnPromise = entry(...fnArgs); };
fnPromise int.setProperty(intLayer, name, int.createAsyncFunction(wrapper));
.then(function (res: unknown) { } else if (Array.isArray(entry) || typeof entry !== "object") {
if (typeof callb === "function") { // args, strings on enums, etc
callb(res); int.setProperty(intLayer, name, int.nativeToPseudo(entry));
}
})
.catch(function (err: unknown) {
if (typeof err === "string") {
console.error(err);
const errorTextArray = err.split("|DELIMITER|");
const hostname = errorTextArray[1];
const scriptName = errorTextArray[2];
const errorMsg = errorTextArray[3];
let msg = `${scriptName}@${hostname}<br>`;
msg += "<br>";
msg += errorMsg;
dialogBoxCreate(msg);
workerScript.env.stopFlag = true;
workerScript.running = false;
killWorkerScript(workerScript);
return Promise.resolve();
}
});
};
int.setProperty(scope, name, int.createAsyncFunction(tempWrapper));
} else if (
name === "sprintf" ||
name === "vsprintf" ||
name === "scp" ||
name == "write" ||
name === "tryWritePort" ||
name === "run" ||
name === "exec"
) {
const tempWrapper = function (...args: unknown[]): void {
const fnArgs = [];
//All of the Object/array elements are in JSInterpreter format, so
//we have to convert them back to native format to pass them to these fns
for (let i = 0; i < args.length; ++i) {
if (typeof args[i] === "object" || Array.isArray(args[i])) {
fnArgs.push(int.pseudoToNative(args[i]));
} else {
fnArgs.push(args[i]);
}
}
return entry(...fnArgs);
};
int.setProperty(scope, name, int.createNativeFunction(tempWrapper));
} else {
const tempWrapper = function (...args: unknown[]): unknown {
const res = entry(...args);
if (res == null) {
return res;
} else if (res.constructor === Array || res === Object(res)) {
//Objects and Arrays must be converted to the interpreter's format
return int.nativeToPseudo(res);
} else {
return res;
}
};
int.setProperty(scope, name, int.createNativeFunction(tempWrapper));
}
} else { } else {
//bladeburner, or anything else // new object layer, e.g. bladeburner
int.setProperty(scope, name, int.nativeToPseudo(entry)); int.setProperty(intLayer, name, int.nativeToPseudo({}));
wrapNS1Layer(int, (intLayer as BasicObject).properties[name], [...path, name]);
} }
} }
}
//Add the arguments
int.setProperty(scope, "args", int.nativeToPseudo(workerScript.args));
};
let interpreter: Interpreter; let interpreter: Interpreter;
try { try {
interpreter = new Interpreter(codeWithImports, interpreterInitialization, codeLineOffset); interpreter = new Interpreter(codeWithImports, wrapNS1Layer, codeLineOffset);
} catch (e: unknown) { } catch (e: unknown) {
dialogBoxCreate("Syntax ERROR in " + workerScript.name + ":<br>" + String(e)); dialogBoxCreate("Syntax ERROR in " + workerScript.name + ":<br>" + String(e));
workerScript.env.stopFlag = true; workerScript.env.stopFlag = true;
workerScript.running = false;
killWorkerScript(workerScript); killWorkerScript(workerScript);
return Promise.resolve(); return Promise.resolve();
} }
@ -421,13 +359,8 @@ function processNetscript1Imports(code: string, workerScript: WorkerScript): { c
* corresponding WorkerScript), and add the RunningScript to the server on which * corresponding WorkerScript), and add the RunningScript to the server on which
* it is active * it is active
*/ */
export function startWorkerScript( export function startWorkerScript(runningScript: RunningScript, server: BaseServer, parent?: WorkerScript): number {
player: IPlayer, if (createAndAddWorkerScript(runningScript, server, parent)) {
runningScript: RunningScript,
server: BaseServer,
parent?: WorkerScript,
): number {
if (createAndAddWorkerScript(player, runningScript, server, parent)) {
// Push onto runningScripts. // Push onto runningScripts.
// This has to come after createAndAddWorkerScript() because that fn updates RAM usage // This has to come after createAndAddWorkerScript() because that fn updates RAM usage
server.runScript(runningScript); server.runScript(runningScript);
@ -447,12 +380,7 @@ export function startWorkerScript(
* @param {Server} server - Server on which the script is to be run * @param {Server} server - Server on which the script is to be run
* returns {boolean} indicating whether or not the workerScript was successfully added * returns {boolean} indicating whether or not the workerScript was successfully added
*/ */
function createAndAddWorkerScript( function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseServer, parent?: WorkerScript): boolean {
player: IPlayer,
runningScriptObj: RunningScript,
server: BaseServer,
parent?: WorkerScript,
): boolean {
// Update server's ram usage // Update server's ram usage
let threads = 1; let threads = 1;
if (runningScriptObj.threads && !isNaN(runningScriptObj.threads)) { if (runningScriptObj.threads && !isNaN(runningScriptObj.threads)) {
@ -496,7 +424,7 @@ function createAndAddWorkerScript(
// Start the script's execution // Start the script's execution
let scriptExecution: Promise<void> | null = null; // Script's resulting promise let scriptExecution: Promise<void> | null = null; // Script's resulting promise
if (workerScript.name.endsWith(".js")) { if (workerScript.name.endsWith(".js")) {
scriptExecution = startNetscript2Script(player, workerScript); scriptExecution = startNetscript2Script(workerScript);
} else { } else {
scriptExecution = startNetscript1Script(workerScript); scriptExecution = startNetscript1Script(workerScript);
if (!(scriptExecution instanceof Promise)) { if (!(scriptExecution instanceof Promise)) {
@ -508,14 +436,11 @@ function createAndAddWorkerScript(
// running status to false // running status to false
scriptExecution scriptExecution
.then(function () { .then(function () {
workerScript.running = false;
workerScript.env.stopFlag = true; workerScript.env.stopFlag = true;
// On natural death, the earnings are transfered to the parent if it still exists. // On natural death, the earnings are transfered to the parent if it still exists.
if (parent !== undefined) { if (parent !== undefined && !parent.env.stopFlag) {
if (parent.running) { parent.scriptRef.onlineExpGained += runningScriptObj.onlineExpGained;
parent.scriptRef.onlineExpGained += runningScriptObj.onlineExpGained; parent.scriptRef.onlineMoneyMade += runningScriptObj.onlineMoneyMade;
parent.scriptRef.onlineMoneyMade += runningScriptObj.onlineMoneyMade;
}
} }
killWorkerScript(workerScript); killWorkerScript(workerScript);
@ -583,7 +508,7 @@ export function updateOnlineScriptTimes(numCycles = 1): void {
* Called when the game is loaded. Loads all running scripts (from all servers) * Called when the game is loaded. Loads all running scripts (from all servers)
* into worker scripts so that they will start running * into worker scripts so that they will start running
*/ */
export function loadAllRunningScripts(player: IPlayer): void { export function loadAllRunningScripts(): void {
const skipScriptLoad = window.location.href.toLowerCase().indexOf("?noscripts") !== -1; const skipScriptLoad = window.location.href.toLowerCase().indexOf("?noscripts") !== -1;
if (skipScriptLoad) { if (skipScriptLoad) {
Terminal.warn("Skipped loading player scripts during startup"); Terminal.warn("Skipped loading player scripts during startup");
@ -604,7 +529,7 @@ export function loadAllRunningScripts(player: IPlayer): void {
} else { } else {
for (let j = 0; j < server.runningScripts.length; ++j) { for (let j = 0; j < server.runningScripts.length; ++j) {
const fileName = server.runningScripts[j].filename; const fileName = server.runningScripts[j].filename;
createAndAddWorkerScript(player, server.runningScripts[j], server); createAndAddWorkerScript(server.runningScripts[j], server);
if (!server.runningScripts[j]) { if (!server.runningScripts[j]) {
// createAndAddWorkerScript can modify the server.runningScripts array if a script is invalid // createAndAddWorkerScript can modify the server.runningScripts array if a script is invalid
@ -623,7 +548,6 @@ export function loadAllRunningScripts(player: IPlayer): void {
* Run a script from inside another script (run(), exec(), spawn(), etc.) * Run a script from inside another script (run(), exec(), spawn(), etc.)
*/ */
export function runScriptFromScript( export function runScriptFromScript(
player: IPlayer,
caller: string, caller: string,
server: BaseServer, server: BaseServer,
scriptname: string, scriptname: string,
@ -698,7 +622,7 @@ export function runScriptFromScript(
runningScriptObj.threads = threads; runningScriptObj.threads = threads;
runningScriptObj.server = server.hostname; runningScriptObj.server = server.hostname;
return startWorkerScript(player, runningScriptObj, server, workerScript); return startWorkerScript(runningScriptObj, server, workerScript);
} }
workerScript.log(caller, () => `Could not find script '${scriptname}' on '${server.hostname}'`); workerScript.log(caller, () => `Could not find script '${scriptname}' on '${server.hostname}'`);

@ -76,7 +76,7 @@ export function runScript(
const runningScript = new RunningScript(script, args); const runningScript = new RunningScript(script, args);
runningScript.threads = numThreads; runningScript.threads = numThreads;
const success = startWorkerScript(player, runningScript, server); const success = startWorkerScript(runningScript, server);
if (!success) { if (!success) {
terminal.error(`Failed to start script`); terminal.error(`Failed to start script`);
return; return;

@ -278,7 +278,7 @@ const Engine: {
Player.gainMoney(offlineHackingIncome, "hacking"); Player.gainMoney(offlineHackingIncome, "hacking");
// Process offline progress // Process offline progress
loadAllRunningScripts(Player); // This also takes care of offline production for those scripts loadAllRunningScripts(); // This also takes care of offline production for those scripts
if (Player.currentWork !== null) { if (Player.currentWork !== null) {
Player.focus = true; Player.focus = true;

@ -15,7 +15,6 @@ import { workerScripts } from "../../Netscript/WorkerScripts";
import { startWorkerScript } from "../../NetscriptWorker"; import { startWorkerScript } from "../../NetscriptWorker";
import { GetServer } from "../../Server/AllServers"; import { GetServer } from "../../Server/AllServers";
import { findRunningScript } from "../../Script/ScriptHelpers"; import { findRunningScript } from "../../Script/ScriptHelpers";
import { Player } from "../../Player";
import { debounce } from "lodash"; import { debounce } from "lodash";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { ANSIITypography } from "./ANSIITypography"; import { ANSIITypography } from "./ANSIITypography";
@ -159,7 +158,7 @@ function LogWindow(props: IProps): React.ReactElement {
if (server === null) return; if (server === null) return;
const s = findRunningScript(script.filename, script.args, server); const s = findRunningScript(script.filename, script.args, server);
if (s === null) { if (s === null) {
startWorkerScript(Player, script, server); startWorkerScript(script, server);
} else { } else {
setScript(s); setScript(s);
} }