API: make ns.atExit add the callback to an array instead of setting it (#1059)

This commit is contained in:
Shy 2024-03-06 01:22:45 +01:00 committed by GitHub
parent 4f4c6fe7e5
commit 4aaf845fca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 38 additions and 22 deletions

@ -9,7 +9,7 @@ Add callback function when the script dies
**Signature:** **Signature:**
```typescript ```typescript
atExit(f: () => void): void; atExit(f: () => void, id?: string): void;
``` ```
## Parameters ## Parameters
@ -17,6 +17,7 @@ atExit(f: () => void): void;
| Parameter | Type | Description | | Parameter | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| f | () => void | | | f | () => void | |
| id | string | _(Optional)_ |
**Returns:** **Returns:**

@ -56,7 +56,7 @@ export async function main(ns) {
| --- | --- | | --- | --- |
| [alert(msg)](./bitburner.ns.alert.md) | Open up a message box. | | [alert(msg)](./bitburner.ns.alert.md) | Open up a message box. |
| [asleep(millis)](./bitburner.ns.asleep.md) | Suspends the script for n milliseconds. Doesn't block with concurrent calls. | | [asleep(millis)](./bitburner.ns.asleep.md) | Suspends the script for n milliseconds. Doesn't block with concurrent calls. |
| [atExit(f)](./bitburner.ns.atexit.md) | Add callback function when the script dies | | [atExit(f, id)](./bitburner.ns.atexit.md) | Add callback function when the script dies |
| [brutessh(host)](./bitburner.ns.brutessh.md) | Runs BruteSSH.exe on a server. | | [brutessh(host)](./bitburner.ns.brutessh.md) | Runs BruteSSH.exe on a server. |
| [clear(handle)](./bitburner.ns.clear.md) | Clear data from a file. | | [clear(handle)](./bitburner.ns.clear.md) | Clear data from a file. |
| [clearLog()](./bitburner.ns.clearlog.md) | Clears the scripts logs. | | [clearLog()](./bitburner.ns.clearlog.md) | Clears the scripts logs. |

@ -78,8 +78,8 @@ export class WorkerScript {
/** hostname on which this script is running */ /** hostname on which this script is running */
hostname: string; hostname: string;
/** Function called when the script ends. */ /**Map of functions called when the script ends. */
atExit: (() => void) | undefined = undefined; atExit: Map<string, () => void> = new Map();
constructor(runningScriptObj: RunningScript, pid: number, nsFuncsGenerator?: (ws: WorkerScript) => NSFull) { constructor(runningScriptObj: RunningScript, pid: number, nsFuncsGenerator?: (ws: WorkerScript) => NSFull) {
this.name = runningScriptObj.filename; this.name = runningScriptObj.filename;

@ -42,20 +42,25 @@ function stopAndCleanUpWorkerScript(ws: WorkerScript): void {
if (ws.delay) clearTimeout(ws.delay); if (ws.delay) clearTimeout(ws.delay);
ws.delayReject?.(new ScriptDeath(ws)); ws.delayReject?.(new ScriptDeath(ws));
ws.env.runningFn = ""; ws.env.runningFn = "";
if (typeof ws.atExit === "function") {
try {
const atExit = ws.atExit; const atExit = ws.atExit;
ws.atExit = undefined; //Calling ns.exit inside ns.atExit can lead to recursion
atExit(); //so the map must be cleared before looping
ws.atExit = new Map();
for (const key of atExit.keys()) {
try {
const callback = atExit.get(key);
if (typeof callback == "function") callback();
} catch (e: unknown) { } catch (e: unknown) {
handleUnknownError(e, ws, "Error running atExit function.\n\n"); handleUnknownError(e, ws, "Error running atExit function.\n\n");
} }
}
if (ws.env.stopFlag) { if (ws.env.stopFlag) {
// If atExit() kills the script, we'll already be stopped, don't stop again. // If atExit() kills the script, we'll already be stopped, don't stop again.
return; return;
} }
}
ws.env.stopFlag = true; ws.env.stopFlag = true;
removeWorkerScript(ws); removeWorkerScript(ws);
} }

@ -1708,13 +1708,20 @@ export const ns: InternalAPI<NSFull> = {
sinceInstall: Object.assign({}, Player.moneySourceA), sinceInstall: Object.assign({}, Player.moneySourceA),
sinceStart: Object.assign({}, Player.moneySourceB), sinceStart: Object.assign({}, Player.moneySourceB),
}), }),
atExit: (ctx) => (f) => { atExit:
(ctx) =>
(f, id = "default") => {
if (typeof f !== "function") { if (typeof f !== "function") {
throw helpers.errorMessage(ctx, "argument should be function"); throw helpers.errorMessage(ctx, "argument should be function");
} }
ctx.workerScript.atExit = () => {
if (typeof id !== "string") {
throw helpers.errorMessage(ctx, "id should be a string");
}
ctx.workerScript.atExit.set(id, () => {
f(); f();
}; // Wrap the user function to prevent WorkerScript leaking as 'this' }); // Wrap the user function to prevent WorkerScript leaking as 'this'
}, },
mv: (ctx) => (_host, _source, _destination) => { mv: (ctx) => (_host, _source, _destination) => {
const hostname = helpers.string(ctx, "host", _host); const hostname = helpers.string(ctx, "host", _host);

@ -7246,7 +7246,7 @@ export interface NS {
* *
* Add callback to be executed when the script dies. * Add callback to be executed when the script dies.
*/ */
atExit(f: () => void): void; atExit(f: () => void, id?: string): void;
/** /**
* Move a file on the target server. * Move a file on the target server.

@ -105,7 +105,10 @@ test.each([
// await script death. // await script death.
const ws = workerScripts.get(pid); const ws = workerScripts.get(pid);
expect(ws).toBeDefined(); expect(ws).toBeDefined();
const result = await Promise.race([alerted, new Promise((resolve) => (ws.atExit = resolve))]); const result = await Promise.race([
alerted,
new Promise<void>((resolve) => (ws!.atExit = new Map([["default", resolve]]))),
]);
// If an error alert was thrown, we catch it here. // If an error alert was thrown, we catch it here.
expect(result).not.toBeDefined(); expect(result).not.toBeDefined();
expect(runningScript.logs).toEqual(expectedLog); expect(runningScript.logs).toEqual(expectedLog);