mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-19 12:45:45 +01:00
Move error functionality to new wrapper
This commit is contained in:
parent
74f3d6507f
commit
3a2e676c9b
@ -13,6 +13,8 @@ import { INetscriptHelper, ScriptIdentifier } from "../NetscriptFunctions/INetsc
|
|||||||
import { GangMember } from "../Gang/GangMember";
|
import { GangMember } from "../Gang/GangMember";
|
||||||
import { GangMemberTask } from "../Gang/GangMemberTask";
|
import { GangMemberTask } from "../Gang/GangMemberTask";
|
||||||
import { ScriptArg } from "./ScriptArg";
|
import { ScriptArg } from "./ScriptArg";
|
||||||
|
import { ScriptDeath } from "./ScriptDeath";
|
||||||
|
import { sprintf } from "sprintf-js";
|
||||||
|
|
||||||
type ExternalFunction = (...args: unknown[]) => unknown;
|
type ExternalFunction = (...args: unknown[]) => unknown;
|
||||||
export type ExternalAPI = {
|
export type ExternalAPI = {
|
||||||
@ -63,6 +65,22 @@ type WrappedNetscriptHelpers = {
|
|||||||
gangTask: (t: unknown) => GangMemberTask;
|
gangTask: (t: unknown) => GangMemberTask;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function checkEnv(ctx: NetscriptContext) {
|
||||||
|
if (ctx.workerScript.env.stopFlag) throw new ScriptDeath(ctx.workerScript);
|
||||||
|
if (ctx.workerScript.env.runningFn && ctx.function !== "asleep") {
|
||||||
|
if (ctx.workerScript.delayReject) ctx.workerScript.delayReject();
|
||||||
|
const msg =
|
||||||
|
"Concurrent calls to Netscript functions are not allowed!\n" +
|
||||||
|
"Did you forget to await hack(), grow(), or some other\n" +
|
||||||
|
"promise-returning function? (Currently running: %s tried to run: %s)";
|
||||||
|
ctx.workerScript.errorMessage = makeRuntimeRejectMsg(
|
||||||
|
ctx.workerScript,
|
||||||
|
sprintf(msg, ctx.workerScript.env.runningFn, ctx.function),
|
||||||
|
);
|
||||||
|
throw new ScriptDeath(ctx.workerScript);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function wrapFunction(
|
function wrapFunction(
|
||||||
helpers: INetscriptHelper,
|
helpers: INetscriptHelper,
|
||||||
wrappedAPI: ExternalAPI,
|
wrappedAPI: ExternalAPI,
|
||||||
@ -107,10 +125,11 @@ function wrapFunction(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
function wrappedFunction(...args: unknown[]): unknown {
|
function wrappedFunction(...args: unknown[]): unknown {
|
||||||
|
checkEnv(ctx);
|
||||||
helpers.updateDynamicRam(ctx.function, getRamCost(Player, ...tree, ctx.function));
|
helpers.updateDynamicRam(ctx.function, getRamCost(Player, ...tree, ctx.function));
|
||||||
return func(ctx)(...args);
|
return func(ctx)(...args);
|
||||||
}
|
}
|
||||||
const parent = getNestedProperty(wrappedAPI, ...tree);
|
const parent = getNestedProperty(wrappedAPI, tree);
|
||||||
Object.defineProperty(parent, functionName, {
|
Object.defineProperty(parent, functionName, {
|
||||||
value: wrappedFunction,
|
value: wrappedFunction,
|
||||||
writable: true,
|
writable: true,
|
||||||
@ -129,7 +148,7 @@ export function wrapAPI(
|
|||||||
if (typeof value === "function") {
|
if (typeof value === "function") {
|
||||||
wrapFunction(helpers, wrappedAPI, workerScript, value, ...tree, key);
|
wrapFunction(helpers, wrappedAPI, workerScript, value, ...tree, key);
|
||||||
} else if (Array.isArray(value)) {
|
} else if (Array.isArray(value)) {
|
||||||
setNestedProperty(wrappedAPI, value, key);
|
setNestedProperty(wrappedAPI, value.slice(), key);
|
||||||
} else if (typeof value === "object") {
|
} else if (typeof value === "object") {
|
||||||
wrapAPI(helpers, wrappedAPI, workerScript, value, ...tree, key);
|
wrapAPI(helpers, wrappedAPI, workerScript, value, ...tree, key);
|
||||||
} else {
|
} else {
|
||||||
@ -139,28 +158,21 @@ export function wrapAPI(
|
|||||||
return wrappedAPI;
|
return wrappedAPI;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This doesn't even work properly.
|
function setNestedProperty(root: any, value: unknown, ...tree: string[]): void {
|
||||||
function setNestedProperty(root: object, value: unknown, ...tree: string[]): void {
|
|
||||||
let target = root;
|
let target = root;
|
||||||
const key = tree.pop();
|
const key = tree.pop();
|
||||||
if (!key) {
|
if (!key) throw new Error("Failure occured while wrapping netscript api (setNestedProperty)");
|
||||||
throw new Error("Failure occured while wrapping netscript api (setNestedProperty)");
|
for (const branch of tree) {
|
||||||
|
target[branch] ??= {};
|
||||||
|
target = target[branch];
|
||||||
}
|
}
|
||||||
for (let branch of Object.values(target)) {
|
target[key] = value;
|
||||||
if (branch === undefined) {
|
|
||||||
branch = {};
|
|
||||||
}
|
|
||||||
target = branch;
|
|
||||||
}
|
|
||||||
Object.assign(target, { [key]: value });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNestedProperty(root: any, ...tree: string[]): unknown {
|
function getNestedProperty(root: any, tree: string[]): unknown {
|
||||||
let target = root;
|
let target = root;
|
||||||
for (const branch of tree) {
|
for (const branch of tree) {
|
||||||
if (target[branch] === undefined) {
|
target[branch] ??= {};
|
||||||
target[branch] = {};
|
|
||||||
}
|
|
||||||
target = target[branch];
|
target = target[branch];
|
||||||
}
|
}
|
||||||
return target;
|
return target;
|
||||||
|
@ -10,6 +10,12 @@ export class Environment {
|
|||||||
*/
|
*/
|
||||||
stopFlag = false;
|
stopFlag = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently running function
|
||||||
|
*/
|
||||||
|
|
||||||
|
runningFn = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Environment variables (currently only Netscript functions)
|
* Environment variables (currently only Netscript functions)
|
||||||
*/
|
*/
|
||||||
|
@ -4,20 +4,21 @@ import { ScriptDeath } from "./Netscript/ScriptDeath";
|
|||||||
import { WorkerScript } from "./Netscript/WorkerScript";
|
import { WorkerScript } from "./Netscript/WorkerScript";
|
||||||
import { NetscriptContext } from "./Netscript/APIWrapper";
|
import { NetscriptContext } from "./Netscript/APIWrapper";
|
||||||
|
|
||||||
export function netscriptDelay(time: number, workerScript: WorkerScript): Promise<void> {
|
export function netscriptDelay(fnName: string, time: number, workerScript: WorkerScript): Promise<void> {
|
||||||
// Cancel any pre-existing netscriptDelay'ed function call
|
// Cancel any pre-existing netscriptDelay'ed function call
|
||||||
// TODO: the rejection almost certainly ends up in the uncaught rejection handler.
|
// TODO: This can probably be removed. Concurrency check should error out before this in APIWrapper.ts
|
||||||
// Maybe reject with a stack-trace'd error message?
|
|
||||||
if (workerScript.delayReject) workerScript.delayReject();
|
if (workerScript.delayReject) workerScript.delayReject();
|
||||||
|
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
workerScript.delay = window.setTimeout(() => {
|
workerScript.delay = window.setTimeout(() => {
|
||||||
workerScript.delay = null;
|
workerScript.delay = null;
|
||||||
workerScript.delayReject = undefined;
|
workerScript.delayReject = undefined;
|
||||||
|
workerScript.env.runningFn = "";
|
||||||
if (workerScript.env.stopFlag) reject(new ScriptDeath(workerScript));
|
if (workerScript.env.stopFlag) reject(new ScriptDeath(workerScript));
|
||||||
else resolve();
|
else resolve();
|
||||||
}, time);
|
}, time);
|
||||||
workerScript.delayReject = reject;
|
workerScript.delayReject = reject;
|
||||||
|
workerScript.env.runningFn = fnName;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -394,7 +394,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
|||||||
)} (t=${numeralWrapper.formatThreads(threads)})`,
|
)} (t=${numeralWrapper.formatThreads(threads)})`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return netscriptDelay(hackingTime * 1000, workerScript).then(function () {
|
return netscriptDelay("hack", hackingTime * 1000, workerScript).then(function () {
|
||||||
const hackChance = calculateHackingChance(server, Player);
|
const hackChance = calculateHackingChance(server, Player);
|
||||||
const rand = Math.random();
|
const rand = Math.random();
|
||||||
let expGainedOnSuccess = calculateHackingExpGain(server, Player) * threads;
|
let expGainedOnSuccess = calculateHackingExpGain(server, Player) * threads;
|
||||||
@ -757,7 +757,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
|||||||
throw ctx.makeRuntimeErrorMsg("Takes 1 argument.");
|
throw ctx.makeRuntimeErrorMsg("Takes 1 argument.");
|
||||||
}
|
}
|
||||||
ctx.log(() => `Sleeping for ${time} milliseconds`);
|
ctx.log(() => `Sleeping for ${time} milliseconds`);
|
||||||
return netscriptDelay(time, workerScript).then(function () {
|
return netscriptDelay("sleep", time, workerScript).then(function () {
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -798,7 +798,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
|||||||
true,
|
true,
|
||||||
)} (t=${numeralWrapper.formatThreads(threads)}).`,
|
)} (t=${numeralWrapper.formatThreads(threads)}).`,
|
||||||
);
|
);
|
||||||
return netscriptDelay(growTime * 1000, workerScript).then(function () {
|
return netscriptDelay("grow", growTime * 1000, workerScript).then(function () {
|
||||||
const moneyBefore = server.moneyAvailable <= 0 ? 1 : server.moneyAvailable;
|
const moneyBefore = server.moneyAvailable <= 0 ? 1 : server.moneyAvailable;
|
||||||
processSingleServerGrowth(server, threads, Player, host.cpuCores);
|
processSingleServerGrowth(server, threads, Player, host.cpuCores);
|
||||||
const moneyAfter = server.moneyAvailable;
|
const moneyAfter = server.moneyAvailable;
|
||||||
@ -892,7 +892,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
|||||||
true,
|
true,
|
||||||
)} (t=${numeralWrapper.formatThreads(threads)})`,
|
)} (t=${numeralWrapper.formatThreads(threads)})`,
|
||||||
);
|
);
|
||||||
return netscriptDelay(weakenTime * 1000, workerScript).then(function () {
|
return netscriptDelay("weaken", weakenTime * 1000, workerScript).then(function () {
|
||||||
const host = GetServer(workerScript.hostname);
|
const host = GetServer(workerScript.hostname);
|
||||||
if (host === null) {
|
if (host === null) {
|
||||||
ctx.log(() => "Server is null, did it die?");
|
ctx.log(() => "Server is null, did it die?");
|
||||||
@ -926,7 +926,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
|
|||||||
const end = StartSharing(
|
const end = StartSharing(
|
||||||
workerScript.scriptRef.threads * calculateIntelligenceBonus(Player.skills.intelligence, 2),
|
workerScript.scriptRef.threads * calculateIntelligenceBonus(Player.skills.intelligence, 2),
|
||||||
);
|
);
|
||||||
return netscriptDelay(10000, workerScript).finally(function () {
|
return netscriptDelay("share", 10000, workerScript).finally(function () {
|
||||||
ctx.log(() => "Finished sharing this computer.");
|
ctx.log(() => "Finished sharing this computer.");
|
||||||
end();
|
end();
|
||||||
});
|
});
|
||||||
|
@ -637,7 +637,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
|
|||||||
() => `Installing backdoor on '${server.hostname}' in ${convertTimeMsToTimeElapsedString(installTime, true)}`,
|
() => `Installing backdoor on '${server.hostname}' in ${convertTimeMsToTimeElapsedString(installTime, true)}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return netscriptDelay(installTime, workerScript).then(function () {
|
return netscriptDelay("singularity.installBackdoor", installTime, workerScript).then(function () {
|
||||||
_ctx.log(() => `Successfully installed backdoor on '${server.hostname}'`);
|
_ctx.log(() => `Successfully installed backdoor on '${server.hostname}'`);
|
||||||
|
|
||||||
server.backdoorInstalled = true;
|
server.backdoorInstalled = true;
|
||||||
|
@ -57,7 +57,7 @@ export function NetscriptStanek(
|
|||||||
}
|
}
|
||||||
//Charge the fragment
|
//Charge the fragment
|
||||||
const time = staneksGift.inBonus() ? 200 : 1000;
|
const time = staneksGift.inBonus() ? 200 : 1000;
|
||||||
return netscriptDelay(time, workerScript).then(function () {
|
return netscriptDelay("stanek.charge", time, workerScript).then(function () {
|
||||||
staneksGift.charge(player, fragment, workerScript.scriptRef.threads);
|
staneksGift.charge(player, fragment, workerScript.scriptRef.threads);
|
||||||
_ctx.log(() => `Charged fragment with ${_ctx.workerScript.scriptRef.threads} threads.`);
|
_ctx.log(() => `Charged fragment with ${_ctx.workerScript.scriptRef.threads} threads.`);
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
@ -29,7 +29,6 @@ import { dialogBoxCreate } from "./ui/React/DialogBox";
|
|||||||
import { arrayToString } from "./utils/helpers/arrayToString";
|
import { arrayToString } from "./utils/helpers/arrayToString";
|
||||||
import { roundToTwo } from "./utils/helpers/roundToTwo";
|
import { roundToTwo } from "./utils/helpers/roundToTwo";
|
||||||
import { isString } from "./utils/helpers/isString";
|
import { isString } from "./utils/helpers/isString";
|
||||||
import { sprintf } from "sprintf-js";
|
|
||||||
|
|
||||||
import { parse } from "acorn";
|
import { parse } from "acorn";
|
||||||
import { simple as walksimple } from "acorn-walk";
|
import { simple as walksimple } from "acorn-walk";
|
||||||
@ -63,83 +62,6 @@ export function prestigeWorkerScripts(): void {
|
|||||||
// 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(player: IPlayer, workerScript: WorkerScript): Promise<void> {
|
||||||
workerScript.running = true;
|
workerScript.running = true;
|
||||||
|
|
||||||
// The name of the currently running netscript function, to prevent concurrent
|
|
||||||
// calls to hack, grow, etc.
|
|
||||||
let runningFn: string | null = null;
|
|
||||||
|
|
||||||
// We need to go through the environment and wrap each function in such a way that it
|
|
||||||
// can be called at most once at a time. This will prevent situations where multiple
|
|
||||||
// hack promises are outstanding, for example.
|
|
||||||
function wrap(propName: string, f: (...args: unknown[]) => Promise<void>): (...args: unknown[]) => Promise<void> {
|
|
||||||
// This function unfortunately cannot be an async function, because we don't
|
|
||||||
// know if the original one was, and there's no way to tell.
|
|
||||||
return function (...args: unknown[]) {
|
|
||||||
// Wrap every netscript function with a check for the stop flag.
|
|
||||||
// This prevents cases where we never stop because we are only calling
|
|
||||||
// netscript functions that don't check this.
|
|
||||||
// This is not a problem for legacy Netscript because it also checks the
|
|
||||||
// stop flag in the evaluator.
|
|
||||||
if (workerScript.env.stopFlag) {
|
|
||||||
throw new ScriptDeath(workerScript);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (propName === "asleep") return f(...args); // OK for multiple simultaneous calls to sleep.
|
|
||||||
|
|
||||||
const msg =
|
|
||||||
"Concurrent calls to Netscript functions are not allowed! " +
|
|
||||||
"Did you forget to await hack(), grow(), or some other " +
|
|
||||||
"promise-returning function? (Currently running: %s tried to run: %s)";
|
|
||||||
if (runningFn) {
|
|
||||||
workerScript.errorMessage = makeRuntimeRejectMsg(workerScript, sprintf(msg, runningFn, propName));
|
|
||||||
throw new ScriptDeath(workerScript);
|
|
||||||
}
|
|
||||||
runningFn = propName;
|
|
||||||
|
|
||||||
// If the function throws an error, clear the runningFn flag first, and then re-throw it
|
|
||||||
// This allows people to properly catch errors thrown by NS functions without getting
|
|
||||||
// the concurrent call error above
|
|
||||||
let result;
|
|
||||||
try {
|
|
||||||
result = f(...args);
|
|
||||||
} catch (e: unknown) {
|
|
||||||
runningFn = null;
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result && result.finally !== undefined) {
|
|
||||||
return result.finally(function () {
|
|
||||||
runningFn = null;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
runningFn = null;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function wrapObject(vars: unknown, ...tree: string[]): void {
|
|
||||||
const isObject = (x: unknown): x is { [key: string]: unknown } => typeof x === "object";
|
|
||||||
if (!isObject(vars)) throw new Error("wrong argument sent to wrapObject");
|
|
||||||
for (const prop of Object.keys(vars)) {
|
|
||||||
let e = vars[prop];
|
|
||||||
switch (typeof e) {
|
|
||||||
case "function": {
|
|
||||||
e = wrap([...tree, prop].join("."), e as any);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "object": {
|
|
||||||
if (Array.isArray(e)) continue;
|
|
||||||
wrapObject(e, ...tree, prop);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wrapObject(workerScript.env.vars);
|
|
||||||
|
|
||||||
// Note: the environment that we pass to the JS script only needs to contain the functions visible
|
|
||||||
// to that script, which env.vars does at this point.
|
|
||||||
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(() => {
|
||||||
|
Loading…
Reference in New Issue
Block a user