Fix/Unify NS1 wrapper

This commit is contained in:
Snarling 2022-08-27 20:56:12 -04:00
parent 3ee55b2918
commit 6f36e9cdc5
6 changed files with 53 additions and 113 deletions

@ -702,7 +702,7 @@ const base: InternalAPI<NS> = {
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:
(ctx: NetscriptContext) =>
@ -718,7 +718,7 @@ const base: InternalAPI<NS> = {
throw helpers.makeRuntimeErrorMsg(ctx, `Invalid thread count. Must be numeric and > 0, is ${threads}`);
}
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:
(ctx: NetscriptContext) =>
@ -740,7 +740,7 @@ const base: InternalAPI<NS> = {
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);
helpers.log(ctx, () => `Will execute '${scriptname}' in ${spawnDelay} seconds`);

@ -92,7 +92,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
}
const runningScriptObj = new RunningScript(script, []); // No args
runningScriptObj.threads = 1; // Only 1 thread
startWorkerScript(player, runningScriptObj, home);
startWorkerScript(runningScriptObj, home);
}
}
};

@ -34,10 +34,8 @@ import { simple as walksimple } from "acorn-walk";
import { areFilesEqual } from "./Terminal/DirectoryHelpers";
import { Player } from "./Player";
import { Terminal } from "./Terminal";
import { IPlayer } from "./PersonObjects/IPlayer";
import { ScriptArg } from "./Netscript/ScriptArg";
import { helpers } from "./Netscript/NetscriptHelpers";
import { NS } from "./ScriptEditor/NetscriptDefinitions";
// Netscript Ports are instantiated here
export const NetscriptPorts: IPort[] = [];
@ -61,10 +59,10 @@ export function prestigeWorkerScripts(): void {
// JS script promises need a little massaging to have the same guarantees as netscript
// promises. This does said massaging and kicks the script off. It returns a promise
// that resolves or rejects when the corresponding worker script is done.
function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Promise<void> {
function startNetscript2Script(workerScript: WorkerScript): Promise<void> {
workerScript.running = true;
return new Promise<void>((resolve, reject) => {
executeJSScript(player, workerScript.getServer().scripts, workerScript)
executeJSScript(Player, workerScript.getServer().scripts, workerScript)
.then(() => {
resolve();
})
@ -114,111 +112,56 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
return Promise.resolve();
}
const interpreterInitialization = function (int: Interpreter, scope: unknown): void {
interface NS1 extends NS {
function wrapNS1Layer(int: Interpreter, intLayer: unknown, path: string[] = []) {
//TODO: Better typing layers of interpreter scope and ns
interface BasicObject {
[key: string]: any;
}
//Add the Netscript environment
const ns = NetscriptFunctions(workerScript) as NS1;
for (const name of Object.keys(ns)) {
const entry = ns[name];
const nsLayer = path.reduce((prev, newPath) => prev[newPath], 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 asyncFuncs = ["hack", "grow", "weaken", "sleep", "prompt", "manualHack", "share", "wget"];
if (asyncFuncs.includes(name)) {
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 - 1; ++i) {
if (typeof args[i] === "object" || Array.isArray(args[i])) {
fnArgs.push(int.pseudoToNative(args[i]));
} else {
fnArgs.push(args[i]);
}
// 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 {
const callback = args.pop() as (value: unknown) => void;
const result = await entry(...args.map(int.pseudoToNative));
return callback(int.nativeToPseudo(result));
} catch (e: unknown) {
// TODO: Unify error handling, this was stolen from previous async handler
if (typeof e === "string") {
console.error(e);
const errorTextArray = e.split("|DELIMITER|");
const hostname = errorTextArray[1];
const scriptName = errorTextArray[2];
const errorMsg = errorTextArray[3];
let msg = `${scriptName}@${hostname}<br>`;
msg += "<br>";
msg += errorMsg;
dialogBoxCreate(msg);
workerScript.env.stopFlag = true;
workerScript.running = false;
killWorkerScript(workerScript);
return;
}
const callb = args[args.length - 1];
const fnPromise = entry(...fnArgs);
fnPromise
.then(function (res: unknown) {
if (typeof callb === "function") {
callb(res);
}
})
.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));
}
}
};
int.setProperty(intLayer, name, int.createAsyncFunction(wrapper));
} else if (Array.isArray(entry) || typeof entry !== "object"){
// args, strings on enums, etc
int.setProperty(intLayer, name, int.nativeToPseudo(entry));
} else {
//bladeburner, or anything else
int.setProperty(scope, name, int.nativeToPseudo(entry));
// new object layer, e.g. bladeburner
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;
try {
interpreter = new Interpreter(codeWithImports, interpreterInitialization, codeLineOffset);
interpreter = new Interpreter(codeWithImports, wrapNS1Layer, codeLineOffset);
} catch (e: unknown) {
dialogBoxCreate("Syntax ERROR in " + workerScript.name + ":<br>" + String(e));
workerScript.env.stopFlag = true;
@ -422,12 +365,11 @@ function processNetscript1Imports(code: string, workerScript: WorkerScript): { c
* it is active
*/
export function startWorkerScript(
player: IPlayer,
runningScript: RunningScript,
server: BaseServer,
parent?: WorkerScript,
): number {
if (createAndAddWorkerScript(player, runningScript, server, parent)) {
if (createAndAddWorkerScript(runningScript, server, parent)) {
// Push onto runningScripts.
// This has to come after createAndAddWorkerScript() because that fn updates RAM usage
server.runScript(runningScript);
@ -448,7 +390,6 @@ export function startWorkerScript(
* returns {boolean} indicating whether or not the workerScript was successfully added
*/
function createAndAddWorkerScript(
player: IPlayer,
runningScriptObj: RunningScript,
server: BaseServer,
parent?: WorkerScript,
@ -496,7 +437,7 @@ function createAndAddWorkerScript(
// Start the script's execution
let scriptExecution: Promise<void> | null = null; // Script's resulting promise
if (workerScript.name.endsWith(".js")) {
scriptExecution = startNetscript2Script(player, workerScript);
scriptExecution = startNetscript2Script(workerScript);
} else {
scriptExecution = startNetscript1Script(workerScript);
if (!(scriptExecution instanceof Promise)) {
@ -583,7 +524,7 @@ export function updateOnlineScriptTimes(numCycles = 1): void {
* Called when the game is loaded. Loads all running scripts (from all servers)
* 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;
if (skipScriptLoad) {
Terminal.warn("Skipped loading player scripts during startup");
@ -604,7 +545,7 @@ export function loadAllRunningScripts(player: IPlayer): void {
} else {
for (let j = 0; j < server.runningScripts.length; ++j) {
const fileName = server.runningScripts[j].filename;
createAndAddWorkerScript(player, server.runningScripts[j], server);
createAndAddWorkerScript(server.runningScripts[j], server);
if (!server.runningScripts[j]) {
// createAndAddWorkerScript can modify the server.runningScripts array if a script is invalid
@ -623,7 +564,6 @@ export function loadAllRunningScripts(player: IPlayer): void {
* Run a script from inside another script (run(), exec(), spawn(), etc.)
*/
export function runScriptFromScript(
player: IPlayer,
caller: string,
server: BaseServer,
scriptname: string,
@ -698,7 +638,7 @@ export function runScriptFromScript(
runningScriptObj.threads = threads;
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}'`);

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

@ -278,7 +278,7 @@ const Engine: {
Player.gainMoney(offlineHackingIncome, "hacking");
// 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) {
Player.focus = true;

@ -159,7 +159,7 @@ function LogWindow(props: IProps): React.ReactElement {
if (server === null) return;
const s = findRunningScript(script.filename, script.args, server);
if (s === null) {
startWorkerScript(Player, script, server);
startWorkerScript(script, server);
} else {
setScript(s);
}