2022-03-20 09:32:29 +01:00
|
|
|
import { getRamCost } from "./RamCostGenerator";
|
|
|
|
import type { WorkerScript } from "./WorkerScript";
|
2022-08-08 19:43:41 +02:00
|
|
|
import { helpers } from "./NetscriptHelpers";
|
2022-08-09 21:41:47 +02:00
|
|
|
import { ScriptArg } from "./ScriptArg";
|
2022-11-09 19:46:21 +01:00
|
|
|
import { cloneDeep } from "lodash";
|
2022-03-20 09:32:29 +01:00
|
|
|
|
2022-11-09 19:46:21 +01:00
|
|
|
/** Generic type for an enums object */
|
|
|
|
type Enums = Record<string, Record<string, string>>;
|
|
|
|
/** Permissive type for the documented API functions */
|
|
|
|
type APIFn = (...args: any[]) => void;
|
|
|
|
/** Type for the actual wrapped function given to the player */
|
|
|
|
type WrappedFn = (...args: unknown[]) => unknown;
|
|
|
|
/** Type for internal, unwrapped ctx function that produces an APIFunction */
|
|
|
|
type InternalFn<F extends APIFn> = (ctx: NetscriptContext) => ((...args: unknown[]) => ReturnType<F>) & F;
|
|
|
|
type Key<API> = keyof API & string;
|
2022-08-10 16:02:41 +02:00
|
|
|
|
2022-11-09 19:46:21 +01:00
|
|
|
export type ExternalAPI<API> = {
|
|
|
|
[key in keyof API]: API[key] extends Enums
|
|
|
|
? Enums
|
|
|
|
: key extends "args"
|
|
|
|
? ScriptArg[] // "args" required to be ScriptArg[]
|
|
|
|
: API[key] extends APIFn
|
|
|
|
? WrappedFn
|
|
|
|
: ExternalAPI<API[key]>;
|
2022-04-07 02:00:54 +02:00
|
|
|
};
|
2022-03-30 01:11:13 +02:00
|
|
|
|
|
|
|
export type InternalAPI<API> = {
|
2022-11-09 19:46:21 +01:00
|
|
|
[key in keyof API]: API[key] extends Enums
|
|
|
|
? API[key] & Enums
|
|
|
|
: key extends "args"
|
2022-08-09 21:41:47 +02:00
|
|
|
? ScriptArg[]
|
2022-11-09 19:46:21 +01:00
|
|
|
: API[key] extends APIFn
|
|
|
|
? InternalFn<API[key]>
|
|
|
|
: InternalAPI<API[key]>;
|
2022-04-07 02:00:54 +02:00
|
|
|
};
|
2022-11-09 19:46:21 +01:00
|
|
|
/** Any of the possible values on a internal API layer */
|
|
|
|
type InternalValues = Enums | ScriptArg[] | InternalFn<APIFn> | InternalAPI<unknown>;
|
2022-03-30 01:11:13 +02:00
|
|
|
|
2022-11-28 15:11:55 +01:00
|
|
|
export class StampedLayer {
|
|
|
|
#workerScript: WorkerScript;
|
|
|
|
constructor(ws: WorkerScript, obj: ExternalAPI<unknown>) {
|
|
|
|
this.#workerScript = ws;
|
|
|
|
Object.setPrototypeOf(this, obj);
|
2022-03-20 09:32:29 +01:00
|
|
|
}
|
2022-11-28 15:11:55 +01:00
|
|
|
static wrapFunction<API>(eLayer: ExternalAPI<API>, func: InternalFn<APIFn>, tree: string[], key: Key<API>) {
|
2022-11-09 19:46:21 +01:00
|
|
|
const arrayPath = [...tree, key];
|
|
|
|
const functionPath = arrayPath.join(".");
|
2022-11-28 15:11:55 +01:00
|
|
|
function wrappedFunction(this: StampedLayer, ...args: unknown[]): unknown {
|
2022-12-01 22:07:46 +01:00
|
|
|
if (!this)
|
|
|
|
throw new Error(`
|
|
|
|
ns.${functionPath} called with no this value.
|
|
|
|
ns functions must be bound to ns if placed in a new
|
|
|
|
variable. e.g.
|
|
|
|
|
|
|
|
const ${key} = ns.${functionPath}.bind(ns);
|
|
|
|
${key}(${JSON.stringify(args).replace(/^\[|\]$/g, "")});\n\n`);
|
2022-11-28 15:11:55 +01:00
|
|
|
const ctx = { workerScript: this.#workerScript, function: key, functionPath };
|
2022-11-09 19:46:21 +01:00
|
|
|
helpers.checkEnvFlags(ctx);
|
|
|
|
helpers.updateDynamicRam(ctx, getRamCost(...tree, key));
|
|
|
|
return func(ctx)(...args);
|
|
|
|
}
|
2022-11-28 15:11:55 +01:00
|
|
|
Object.defineProperty(eLayer, key, { value: wrappedFunction, enumerable: true, writable: false });
|
2022-03-20 09:32:29 +01:00
|
|
|
}
|
2022-11-28 15:11:55 +01:00
|
|
|
}
|
|
|
|
Object.defineProperty(StampedLayer.prototype, "constructor", {
|
|
|
|
value: Object,
|
|
|
|
enumerable: false,
|
|
|
|
writable: false,
|
|
|
|
configurable: false,
|
|
|
|
});
|
2022-03-20 09:32:29 +01:00
|
|
|
|
2022-11-28 15:11:55 +01:00
|
|
|
export type NetscriptContext = {
|
|
|
|
workerScript: WorkerScript;
|
|
|
|
function: string;
|
|
|
|
functionPath: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
export function wrapAPILayer<API>(
|
|
|
|
eLayer: ExternalAPI<API>,
|
|
|
|
iLayer: InternalAPI<API>,
|
|
|
|
tree: string[],
|
|
|
|
): ExternalAPI<API> {
|
|
|
|
for (const [key, value] of Object.entries(iLayer) as [Key<API>, InternalValues][]) {
|
|
|
|
if (key === "enums") {
|
|
|
|
const enumObj = Object.freeze(cloneDeep(value as Enums));
|
|
|
|
for (const member of Object.values(enumObj)) Object.freeze(member);
|
|
|
|
(eLayer[key] as Enums) = enumObj;
|
|
|
|
} else if (key === "args") continue;
|
|
|
|
// Args only added on individual instances.
|
|
|
|
else if (typeof value === "function") {
|
|
|
|
StampedLayer.wrapFunction(eLayer, value as InternalFn<APIFn>, tree, key);
|
|
|
|
} else if (typeof value === "object") {
|
|
|
|
wrapAPILayer((eLayer[key] = {} as ExternalAPI<API>[Key<API>]), value, [...tree, key as string]);
|
|
|
|
} else {
|
|
|
|
console.warn(`Unexpected data while wrapping API.`, "tree:", tree, "key:", key, "value:", value);
|
|
|
|
throw new Error("Error while wrapping netscript API. See console.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return eLayer;
|
2022-03-20 09:32:29 +01:00
|
|
|
}
|