import { getRamCost } from "./RamCostGenerator"; import type { WorkerScript } from "./WorkerScript"; import { helpers } from "./NetscriptHelpers"; import { ScriptArg } from "./ScriptArg"; import { cloneDeep } from "lodash"; /** Generic type for an enums object */ type Enums = Record>; /** 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 = (ctx: NetscriptContext) => ((...args: unknown[]) => ReturnType) & F; type Key = keyof API & string; export type ExternalAPI = { [key in keyof API]: API[key] extends Enums ? Enums : key extends "args" ? ScriptArg[] // "args" required to be ScriptArg[] : API[key] extends APIFn ? WrappedFn : ExternalAPI; }; export type InternalAPI = { [key in keyof API]: API[key] extends Enums ? API[key] & Enums : key extends "args" ? ScriptArg[] : API[key] extends APIFn ? InternalFn : InternalAPI; }; /** Any of the possible values on a internal API layer */ type InternalValues = Enums | ScriptArg[] | InternalFn | InternalAPI; export class StampedLayer { #workerScript: WorkerScript; constructor(ws: WorkerScript, obj: ExternalAPI) { this.#workerScript = ws; Object.setPrototypeOf(this, obj); } static wrapFunction(eLayer: ExternalAPI, func: InternalFn, tree: string[], key: Key) { const arrayPath = [...tree, key]; const functionPath = arrayPath.join("."); function wrappedFunction(this: StampedLayer, ...args: unknown[]): unknown { const ctx = { workerScript: this.#workerScript, function: key, functionPath }; helpers.checkEnvFlags(ctx); helpers.updateDynamicRam(ctx, getRamCost(...tree, key)); return func(ctx)(...args); } Object.defineProperty(eLayer, key, { value: wrappedFunction, enumerable: true, writable: false }); } } Object.defineProperty(StampedLayer.prototype, "constructor", { value: Object, enumerable: false, writable: false, configurable: false, }); export type NetscriptContext = { workerScript: WorkerScript; function: string; functionPath: string; }; export function wrapAPILayer( eLayer: ExternalAPI, iLayer: InternalAPI, tree: string[], ): ExternalAPI { for (const [key, value] of Object.entries(iLayer) as [Key, 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, tree, key); } else if (typeof value === "object") { wrapAPILayer((eLayer[key] = {} as ExternalAPI[Key]), 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; }