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-03-20 09:32:29 +01:00
|
|
|
|
2022-11-09 19:46:21 +01:00
|
|
|
/** Permissive type for the documented API functions */
|
2023-01-06 02:41:24 +01:00
|
|
|
type APIFn = (...args: any[]) => unknown;
|
2022-11-09 19:46:21 +01:00
|
|
|
/** Type for internal, unwrapped ctx function that produces an APIFunction */
|
|
|
|
type InternalFn<F extends APIFn> = (ctx: NetscriptContext) => ((...args: unknown[]) => ReturnType<F>) & F;
|
2023-01-06 02:41:24 +01:00
|
|
|
/** Type constraint for an API layer. They must all fit this "shape". */
|
2023-02-11 19:18:50 +01:00
|
|
|
type GenericAPI<T> = { [key in keyof T]: APIFn | GenericAPI<T[key]> };
|
2022-08-10 16:02:41 +02:00
|
|
|
|
2023-01-04 15:23:20 +01:00
|
|
|
// args, enums, and pid are excluded from the API for typing purposes via the definition of NSFull.
|
|
|
|
// They do in fact exist on the external API (but are absent on the internal API and ramcost tree)
|
2022-03-30 01:11:13 +02:00
|
|
|
export type InternalAPI<API> = {
|
2023-01-04 15:23:20 +01:00
|
|
|
[key in keyof API]: API[key] extends APIFn ? InternalFn<API[key]> : InternalAPI<API[key]>;
|
2022-04-07 02:00:54 +02:00
|
|
|
};
|
2022-03-20 09:32:29 +01:00
|
|
|
|
2023-05-05 09:55:59 +02:00
|
|
|
export interface NetscriptContext {
|
2022-11-28 15:11:55 +01:00
|
|
|
workerScript: WorkerScript;
|
|
|
|
function: string;
|
|
|
|
functionPath: string;
|
2023-05-05 09:55:59 +02:00
|
|
|
}
|
2022-11-28 15:11:55 +01:00
|
|
|
|
2023-02-11 19:18:50 +01:00
|
|
|
class NSProxyHandler<API extends GenericAPI<API>> {
|
2023-01-06 02:41:24 +01:00
|
|
|
ns: API;
|
|
|
|
ws: WorkerScript;
|
|
|
|
tree: string[];
|
|
|
|
additionalData: Record<string, unknown>;
|
|
|
|
memoed: API = {} as API;
|
2023-01-04 14:56:29 +01:00
|
|
|
|
2023-01-06 02:41:24 +01:00
|
|
|
constructor(ws: WorkerScript, ns: API, tree: string[], additionalData: Record<string, unknown>) {
|
|
|
|
this.ns = ns;
|
|
|
|
this.ws = ws;
|
|
|
|
this.tree = tree;
|
|
|
|
this.additionalData = additionalData;
|
|
|
|
Object.assign(this.memoed, additionalData);
|
|
|
|
}
|
2023-01-04 14:56:29 +01:00
|
|
|
|
2023-01-06 02:41:24 +01:00
|
|
|
has(__target: unknown, key: string): boolean {
|
|
|
|
return Reflect.has(this.ns, key) || Reflect.has(this.additionalData, key);
|
|
|
|
}
|
2023-01-04 14:56:29 +01:00
|
|
|
|
2023-01-06 02:41:24 +01:00
|
|
|
ownKeys(__target: unknown): (string | symbol)[] {
|
|
|
|
return [...Reflect.ownKeys(this.ns), ...Reflect.ownKeys(this.additionalData)];
|
|
|
|
}
|
|
|
|
|
|
|
|
getOwnPropertyDescriptor(__target: unknown, key: keyof API & string): PropertyDescriptor | undefined {
|
|
|
|
if (!this.has(__target, key)) return undefined;
|
2023-08-07 08:38:38 +02:00
|
|
|
if (Object.hasOwn(this.memoed, key)) return Object.getOwnPropertyDescriptor(this.memoed, key);
|
|
|
|
this.get(__target, key, this);
|
|
|
|
return Object.getOwnPropertyDescriptor(this.memoed, key);
|
2023-01-06 02:41:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
defineProperty(__target: unknown, __key: unknown, __attrs: unknown): boolean {
|
|
|
|
throw new TypeError("ns instances are not modifiable!");
|
|
|
|
}
|
2023-01-04 14:56:29 +01:00
|
|
|
|
2023-01-06 02:41:24 +01:00
|
|
|
set(__target: unknown, __key: unknown, __attrs: unknown): boolean {
|
|
|
|
// Redundant with defineProperty, but we'll be explicit
|
|
|
|
throw new TypeError("ns instances are not modifiable!");
|
|
|
|
}
|
|
|
|
|
|
|
|
get(__target: unknown, key: keyof API & string, __receiver: any) {
|
|
|
|
const ours = this.memoed[key];
|
|
|
|
if (ours) return ours;
|
|
|
|
|
2023-08-07 08:38:38 +02:00
|
|
|
const descriptor = Object.getOwnPropertyDescriptor(this.ns, key);
|
|
|
|
if (!descriptor) return descriptor;
|
|
|
|
const field = descriptor.value;
|
2023-01-06 02:41:24 +01:00
|
|
|
|
|
|
|
if (typeof field === "function") {
|
|
|
|
const arrayPath = [...this.tree, key];
|
|
|
|
const functionPath = arrayPath.join(".");
|
|
|
|
const ctx = { workerScript: this.ws, function: key, functionPath };
|
|
|
|
// Only do the context-binding once, instead of each time the function
|
|
|
|
// is called.
|
|
|
|
const func: any = field(ctx);
|
|
|
|
const wrappedFunction = function (...args: unknown[]): unknown {
|
|
|
|
// What remains *must* be called every time.
|
|
|
|
helpers.checkEnvFlags(ctx);
|
2023-08-07 08:38:38 +02:00
|
|
|
helpers.updateDynamicRam(ctx, getRamCost(arrayPath));
|
2023-01-06 02:41:24 +01:00
|
|
|
return func(...args);
|
|
|
|
};
|
2023-08-07 08:38:38 +02:00
|
|
|
Object.defineProperty(this.memoed, key, { ...descriptor, value: wrappedFunction });
|
|
|
|
return wrappedFunction;
|
2023-01-06 02:41:24 +01:00
|
|
|
}
|
|
|
|
if (typeof field === "object") {
|
2023-02-11 19:18:50 +01:00
|
|
|
return ((this.memoed[key] as GenericAPI<API[keyof API]>) = NSProxy(
|
|
|
|
this.ws,
|
|
|
|
field as InternalAPI<GenericAPI<API[keyof API]>>,
|
|
|
|
[...this.tree, key],
|
|
|
|
));
|
2023-01-06 02:41:24 +01:00
|
|
|
}
|
|
|
|
console.warn(`Unexpected data while wrapping API.`, "tree:", this.tree, "key:", key, "field:", field);
|
|
|
|
throw new Error("Error while wrapping netscript API. See console.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-11 19:18:50 +01:00
|
|
|
export function NSProxy<API extends GenericAPI<API>>(
|
2023-01-06 02:41:24 +01:00
|
|
|
ws: WorkerScript,
|
|
|
|
ns: InternalAPI<API>,
|
|
|
|
tree: string[],
|
|
|
|
additionalData: Record<string, unknown> = {},
|
|
|
|
): API {
|
|
|
|
const handler = new NSProxyHandler(ws, ns, tree, additionalData);
|
2023-01-04 14:56:29 +01:00
|
|
|
// We target an empty Object, so that unproxied methods don't do anything.
|
|
|
|
// We *can't* freeze the target, because it would break invariants on ownKeys.
|
2023-01-06 02:41:24 +01:00
|
|
|
return new Proxy({}, handler) as API;
|
2022-03-20 09:32:29 +01:00
|
|
|
}
|
2022-12-30 02:28:53 +01:00
|
|
|
|
|
|
|
/** Specify when a function was removed from the game, and its replacement function. */
|
2023-08-07 08:38:38 +02:00
|
|
|
interface RemovedFunctionInfo {
|
|
|
|
/** The version in which the function was removed */
|
|
|
|
version: string;
|
|
|
|
/** The replacement function to use, or the entire replacement message if replaceMsg is true. */
|
|
|
|
replacement: string;
|
|
|
|
/** If set, replacement is treated as a full replacement message. */
|
|
|
|
replaceMsg?: true;
|
|
|
|
}
|
|
|
|
export function setRemovedFunctions(api: object, infos: Record<string, RemovedFunctionInfo>) {
|
|
|
|
for (const [key, { version, replacement, replaceMsg }] of Object.entries(infos)) {
|
|
|
|
Object.defineProperty(api, key, {
|
|
|
|
value: (ctx: NetscriptContext) => () => {
|
2024-02-26 14:05:10 +01:00
|
|
|
throw helpers.errorMessage(
|
2023-08-07 08:38:38 +02:00
|
|
|
ctx,
|
|
|
|
`Function removed in ${version}. ${replaceMsg ? replacement : `Please use ${replacement} instead.`}`,
|
|
|
|
"REMOVED FUNCTION",
|
|
|
|
);
|
|
|
|
},
|
|
|
|
configurable: true,
|
|
|
|
enumerable: false,
|
|
|
|
});
|
|
|
|
}
|
2022-12-30 02:28:53 +01:00
|
|
|
}
|