@ringozz/godot 4.7.1-4 → 4.7.1-555
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -5
- package/package.json +17 -5
- package/src/assets.d.ts +116 -0
- package/src/boot.browser.ts +41 -0
- package/src/boot.ts +27 -0
- package/src/debug.ts +250 -171
- package/src/index.ts +16 -4
- package/src/load.ts +231 -0
- package/src/preload.ts +214 -0
- package/src/runtime.ts +23 -7
- package/src/web-image.ts +118 -0
package/src/debug.ts
CHANGED
|
@@ -2,88 +2,134 @@
|
|
|
2
2
|
Copyright (c) Vladimir Davidovich. All rights reserved.
|
|
3
3
|
***********************************************************************/
|
|
4
4
|
|
|
5
|
-
import { Rect2, Vector3 } from './index.ts';
|
|
5
|
+
import { MouseButton, Rect2, str, varToStr, Vector2, Vector3 } from './index.ts';
|
|
6
6
|
import { CanvasItem } from '../gen/classes/CanvasItem.ts';
|
|
7
7
|
import { ClassDB } from '../gen/classes/ClassDB.ts';
|
|
8
8
|
import { Control } from '../gen/classes/Control.ts';
|
|
9
9
|
import { DisplayServer } from '../gen/classes/DisplayServer.ts';
|
|
10
10
|
import { Engine } from '../gen/classes/Engine.ts';
|
|
11
|
+
import { InputEventMouseButton } from '../gen/classes/InputEventMouseButton.ts';
|
|
11
12
|
import { Node } from '../gen/classes/Node.ts';
|
|
12
13
|
import { Node3D } from '../gen/classes/Node3D.ts';
|
|
13
14
|
import { OS } from '../gen/classes/OS.ts';
|
|
15
|
+
import { SceneTree } from '../gen/classes/SceneTree.ts';
|
|
14
16
|
import { Time } from '../gen/classes/Time.ts';
|
|
17
|
+
import type { Window } from '../gen/classes/Window.ts';
|
|
18
|
+
|
|
19
|
+
const tree = Engine.getMainLoop() as SceneTree;
|
|
15
20
|
|
|
16
21
|
/**
|
|
17
|
-
* Dump a Godot object
|
|
18
|
-
*
|
|
22
|
+
* Dump a Godot value or object as a string.
|
|
23
|
+
* Value types (vectors, arrays, dictionaries, …) render via Godot's `str()`.
|
|
24
|
+
* Objects dump via Godot's `var_to_str` — its own property walk + recursion —
|
|
25
|
+
* with the node's `name` prepended when present.
|
|
19
26
|
*
|
|
20
|
-
* @param obj - Any Godot wrapper (Node, Resource,
|
|
21
|
-
* @param depth - Max recursion depth into sub-objects (default 2)
|
|
22
|
-
* @param maxProps - Max properties to show per object (default 30)
|
|
27
|
+
* @param obj - Any Godot wrapper (Node, Resource, …), value type, or plain value
|
|
23
28
|
*/
|
|
24
|
-
export function dumpStr(obj: unknown
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
let count = 0;
|
|
31
|
-
try {
|
|
32
|
-
const plist = (obj as any).getPropertyList?.();
|
|
33
|
-
if (plist?.size) {
|
|
34
|
-
for (let i = 0; i < plist.size() && count < maxProps; i++) {
|
|
35
|
-
const name = plist.get(i).get('name');
|
|
36
|
-
if (!name || (name + '').startsWith('_')) continue;
|
|
37
|
-
let val: unknown;
|
|
38
|
-
try { val = (obj as any).get(name); } catch { val = '<err>'; }
|
|
39
|
-
if (typeof val === 'object' && val !== null && depth > 1)
|
|
40
|
-
lines.push(` ${name}:\n${indent(dumpStr(val, depth - 1, maxProps), ' ')}`);
|
|
41
|
-
else
|
|
42
|
-
lines.push(` ${name}: ${String(val)}`);
|
|
43
|
-
count++;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
} catch { /* not a GodotVar */ }
|
|
47
|
-
return lines.join('\n');
|
|
48
|
-
}
|
|
29
|
+
export function dumpStr(obj: unknown): string {
|
|
30
|
+
if (obj == null || typeof obj !== 'object') return String(obj);
|
|
31
|
+
|
|
32
|
+
// Value types & heap containers (Vector3, Array, Dictionary, …) have no getClass() —
|
|
33
|
+
// let Godot's str() render them.
|
|
34
|
+
if (!(obj as any).getClass) return str(obj as any);
|
|
49
35
|
|
|
50
|
-
|
|
36
|
+
const dump = varToStr(obj as any);
|
|
37
|
+
let name: unknown;
|
|
38
|
+
try { name = (obj as any).name; } catch { name = undefined; }
|
|
39
|
+
return name === undefined || name === null || name === '' ? dump : `${name}: ${dump}`;
|
|
40
|
+
}
|
|
51
41
|
|
|
52
42
|
/**
|
|
53
|
-
* Serialize a scene subtree as a tree-formatted string.
|
|
43
|
+
* Serialize a scene subtree as a tree-formatted string, optionally filtered.
|
|
44
|
+
*
|
|
45
|
+
* Emits one line per node: `Class "name" (N children)`. When `pattern` is a
|
|
46
|
+
* string, only lines containing it (case-insensitive) are kept; a `RegExp`
|
|
47
|
+
* matches against the full line.
|
|
54
48
|
*
|
|
55
|
-
* @param
|
|
49
|
+
* @param pattern - Optional substring or `RegExp` to filter the lines
|
|
50
|
+
* @param node - Root node to start from (defaults to the scene tree's root Window)
|
|
56
51
|
* @param depth - Max depth (default 99)
|
|
57
52
|
*/
|
|
58
|
-
export function
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
53
|
+
export function snapshot(pattern?: string | RegExp, node?: Node, depth = 99): string {
|
|
54
|
+
const root = node ?? tree.root;
|
|
55
|
+
const lines: string[] = [];
|
|
56
|
+
(function walk(n: Node, d: number, ind: string) {
|
|
57
|
+
if (d <= 0) return;
|
|
58
|
+
const count = n.getChildCount();
|
|
59
|
+
const line = `${ind}${n.getClass()} "${n.name}" (${count} children)`;
|
|
60
|
+
const keep = !pattern || (pattern instanceof RegExp ? pattern.test(line) : line.toLowerCase().includes(pattern.toLowerCase()));
|
|
61
|
+
if (keep) lines.push(line);
|
|
62
|
+
for (let i = 0; i < count; i++) {
|
|
63
|
+
const c = n.getChild(i);
|
|
64
|
+
if (c) walk(c as Node, d - 1, ind + ' ');
|
|
65
|
+
}
|
|
66
|
+
})(root, depth, '');
|
|
67
|
+
return lines.join('\n');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Resolve a node from the scene root by absolute NodePath (`/root/...`) or
|
|
72
|
+
* name glob (`findChild`). Returns `null` when not found.
|
|
73
|
+
*/
|
|
74
|
+
function resolveNode(path: string, root: Window): Node | null {
|
|
75
|
+
if (path.startsWith('/')) return root.getNode(path);
|
|
76
|
+
return root.findChild(path, true, false);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Resolve a node by path (absolute NodePath or name glob) from the scene root
|
|
81
|
+
* and dump it via {@link dumpStr}.
|
|
82
|
+
*
|
|
83
|
+
* @param path - An absolute NodePath like `/root/Box`, or a `findChild` name/glob pattern
|
|
84
|
+
*/
|
|
85
|
+
export function inspect(path: string): string | null {
|
|
86
|
+
const node = resolveNode(path, tree.root);
|
|
87
|
+
return node ? dumpStr(node) : null;
|
|
69
88
|
}
|
|
70
89
|
|
|
71
90
|
/**
|
|
72
|
-
*
|
|
91
|
+
* Synthesize a left-click at the center of a {@link Control} matched by `path`.
|
|
73
92
|
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
93
|
+
* Press and release are pushed through `Window.pushInput` (the GUI-routing
|
|
94
|
+
* path — `Input.parseInputEvent` doesn't reliably reach `Control.guiInput` on
|
|
95
|
+
* the wasm build). Positions are in device space (logical × `contentScaleFactor`).
|
|
96
|
+
*
|
|
97
|
+
* @param path - An absolute NodePath like `/root/...`, or a `findChild` name/glob pattern
|
|
98
|
+
* @returns The node's `dumpStr`, or `null` if not found or not a `Control`
|
|
76
99
|
*/
|
|
77
|
-
export function
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
100
|
+
export function click(path: string): string | null {
|
|
101
|
+
const root = tree.root;
|
|
102
|
+
const node = resolveNode(path, root);
|
|
103
|
+
if (!(node instanceof Control)) return null;
|
|
104
|
+
const rect = node.getGlobalRect();
|
|
105
|
+
const scale = root!.contentScaleFactor;
|
|
106
|
+
const position = new Vector2(
|
|
107
|
+
(rect.position.x + rect.size.x / 2) * scale,
|
|
108
|
+
(rect.position.y + rect.size.y / 2) * scale,
|
|
109
|
+
);
|
|
110
|
+
const press = (down: boolean) => {
|
|
111
|
+
const event = new InputEventMouseButton();
|
|
112
|
+
event.position = position;
|
|
113
|
+
event.globalPosition = position;
|
|
114
|
+
event.buttonIndex = MouseButton.MOUSE_BUTTON_LEFT;
|
|
115
|
+
event.pressed = down;
|
|
116
|
+
root!.pushInput(event);
|
|
117
|
+
};
|
|
118
|
+
press(true);
|
|
119
|
+
setTimeout(() => press(false), 50);
|
|
120
|
+
return dumpStr(node);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Enumerate nodes in the scene by name pattern and/or class, in tree order.
|
|
125
|
+
*
|
|
126
|
+
* @param pattern - Name glob matched by `findChildren` (default `"*"`)
|
|
127
|
+
* @param type - Class name to filter by, e.g. `"RigidBody3D"` (default `""` = any)
|
|
128
|
+
* @param node - Root node to search under (defaults to the scene tree's root Window)
|
|
129
|
+
*/
|
|
130
|
+
export function find(pattern = '*', type = '', node?: Node): Node[] {
|
|
131
|
+
const root = node ?? tree.root;
|
|
132
|
+
return [...root.findChildren(pattern, type, true, false)];
|
|
87
133
|
}
|
|
88
134
|
|
|
89
135
|
/**
|
|
@@ -91,16 +137,43 @@ export function statsStr(): string {
|
|
|
91
137
|
* Requires the `--expose-gc` Node.js flag.
|
|
92
138
|
*/
|
|
93
139
|
export function gc(): string {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
140
|
+
const before = globalThis.process?.memoryUsage?.().heapUsed;
|
|
141
|
+
if (globalThis.Bun)
|
|
142
|
+
globalThis.Bun.gc(true);
|
|
143
|
+
else if (globalThis.gc)
|
|
144
|
+
globalThis.gc({ type: 'major', execution: 'sync' });
|
|
145
|
+
else
|
|
146
|
+
return 'gc() unavailable — need --expose-gc';
|
|
147
|
+
|
|
148
|
+
const after = globalThis.process?.memoryUsage?.().heapUsed;
|
|
149
|
+
const freed = typeof before === 'number' && typeof after === 'number' ? (before - after) / 1024 : 0;
|
|
150
|
+
return `GC: freed ${freed.toFixed(0)} KB`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* `DOMRect` is not a global in Bun (desktop), but `getBoundingClientRect()`
|
|
155
|
+
* constructs one. Install a minimal spec-compatible polyfill on desktop so the
|
|
156
|
+
* helper works identically on desktop and web (browsers already define it).
|
|
157
|
+
*/
|
|
158
|
+
function ensureDOMRect(): void {
|
|
159
|
+
if (typeof (globalThis as any).DOMRect !== 'undefined') return;
|
|
160
|
+
(globalThis as any).DOMRect = class DOMRect {
|
|
161
|
+
x: number;
|
|
162
|
+
y: number;
|
|
163
|
+
width: number;
|
|
164
|
+
height: number;
|
|
165
|
+
constructor(x = 0, y = 0, width = 0, height = 0) {
|
|
166
|
+
this.x = x;
|
|
167
|
+
this.y = y;
|
|
168
|
+
this.width = width;
|
|
169
|
+
this.height = height;
|
|
170
|
+
}
|
|
171
|
+
get left() { return this.x; }
|
|
172
|
+
get top() { return this.y; }
|
|
173
|
+
get right() { return this.x + this.width; }
|
|
174
|
+
get bottom() { return this.y + this.height; }
|
|
175
|
+
toJSON() { return { x: this.x, y: this.y, width: this.width, height: this.height }; }
|
|
176
|
+
};
|
|
104
177
|
}
|
|
105
178
|
|
|
106
179
|
/**
|
|
@@ -111,109 +184,115 @@ export function gc(): string {
|
|
|
111
184
|
* breakpoints in any module.
|
|
112
185
|
* - Registers an `uncaughtException` handler that logs but keeps the process
|
|
113
186
|
* alive for interactive debugging.
|
|
187
|
+
* - Polyfills `DOMRect` (needed by `getBoundingClientRect()` on desktop).
|
|
114
188
|
*/
|
|
115
189
|
export function initDebug(): void {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
190
|
+
globalThis.process?.on?.('uncaughtException', (err, origin) => {
|
|
191
|
+
console.error(`\n✗ ${origin}:`, err.stack);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
ensureDOMRect();
|
|
195
|
+
|
|
196
|
+
Object.defineProperty(CanvasItem.prototype, 'getBoundingClientRect', {
|
|
197
|
+
configurable: true,
|
|
198
|
+
value(this: CanvasItem): DOMRect | null {
|
|
199
|
+
const scale = DisplayServer.screenGetScale();
|
|
200
|
+
if (this instanceof Control) {
|
|
201
|
+
const rc = (this as Control).getGlobalRect();
|
|
202
|
+
return new DOMRect(
|
|
203
|
+
rc.position.x / scale, rc.position.y / scale,
|
|
204
|
+
rc.size.x / scale, rc.size.y / scale,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (typeof (this as any).getRect !== 'function') return null;
|
|
209
|
+
|
|
210
|
+
const rect = (this as any).getRect() as Rect2;
|
|
211
|
+
const t = this.getScreenTransform();
|
|
212
|
+
|
|
213
|
+
const pos = rect.position;
|
|
214
|
+
const sz = rect.size;
|
|
215
|
+
const tx = t.x;
|
|
216
|
+
const ty = t.y;
|
|
217
|
+
const to = t.origin;
|
|
218
|
+
|
|
219
|
+
const x0 = pos.x, y0 = pos.y;
|
|
220
|
+
const x1 = x0 + sz.x, y1 = y0 + sz.y;
|
|
221
|
+
const txx = tx.x, txy = tx.y;
|
|
222
|
+
const tyx = ty.x, tyy = ty.y;
|
|
223
|
+
const tox = to.x, toy = to.y;
|
|
224
|
+
|
|
225
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
226
|
+
const accum = (px: number, py: number) => {
|
|
227
|
+
if (px < minX) minX = px; if (py < minY) minY = py;
|
|
228
|
+
if (px > maxX) maxX = px; if (py > maxY) maxY = py;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
accum(txx * x0 + tyx * y0 + tox, txy * x0 + tyy * y0 + toy);
|
|
232
|
+
accum(txx * x1 + tyx * y0 + tox, txy * x1 + tyy * y0 + toy);
|
|
233
|
+
accum(txx * x0 + tyx * y1 + tox, txy * x0 + tyy * y1 + toy);
|
|
234
|
+
accum(txx * x1 + tyx * y1 + tox, txy * x1 + tyy * y1 + toy);
|
|
235
|
+
|
|
236
|
+
return new DOMRect(
|
|
237
|
+
minX / scale, minY / scale,
|
|
238
|
+
(maxX - minX) / scale, (maxY - minY) / scale,
|
|
239
|
+
);
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
Object.defineProperty(Node3D.prototype, 'getBoundingClientRect', {
|
|
244
|
+
configurable: true,
|
|
245
|
+
value(this: Node3D): DOMRect | null {
|
|
246
|
+
if (typeof (this as any).getAabb !== 'function') return null;
|
|
247
|
+
|
|
248
|
+
const aabb = (this as any).getAabb();
|
|
249
|
+
const gt = this.globalTransform;
|
|
250
|
+
const viewport = this.getViewport();
|
|
251
|
+
if (!viewport) return null;
|
|
252
|
+
const camera = viewport.getCamera3d();
|
|
253
|
+
if (!camera) return null;
|
|
254
|
+
const scale = DisplayServer.screenGetScale();
|
|
255
|
+
|
|
256
|
+
const b = gt.basis;
|
|
257
|
+
const bx = b.x, by = b.y, bz = b.z;
|
|
258
|
+
const o = gt.origin;
|
|
259
|
+
const p = aabb.position, e = aabb.end;
|
|
260
|
+
|
|
261
|
+
const [px, py, pz] = [p.x, p.y, p.z];
|
|
262
|
+
const [ex, ey, ez] = [e.x, e.y, e.z];
|
|
263
|
+
const [bxx, bxy, bxz] = [bx.x, bx.y, bx.z];
|
|
264
|
+
const [byx, byy, byz] = [by.x, by.y, by.z];
|
|
265
|
+
const [bzx, bzy, bzz] = [bz.x, bz.y, bz.z];
|
|
266
|
+
const [ox, oy, oz] = [o.x, o.y, o.z];
|
|
267
|
+
|
|
268
|
+
const corners = [
|
|
269
|
+
[px, py, pz], [ex, py, pz], [px, ey, pz], [ex, ey, pz],
|
|
270
|
+
[px, py, ez], [ex, py, ez], [px, ey, ez], [ex, ey, ez],
|
|
271
|
+
];
|
|
272
|
+
|
|
273
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
274
|
+
|
|
275
|
+
for (const [cx, cy, cz] of corners) {
|
|
276
|
+
const wx = bxx * cx + byx * cy + bzx * cz + ox;
|
|
277
|
+
const wy = bxy * cx + byy * cy + bzy * cz + oy;
|
|
278
|
+
const wz = bxz * cx + byz * cy + bzz * cz + oz;
|
|
279
|
+
const s = camera.unprojectPosition(new Vector3(wx, wy, wz));
|
|
280
|
+
if (s.x < minX) minX = s.x; if (s.y < minY) minY = s.y;
|
|
281
|
+
if (s.x > maxX) maxX = s.x; if (s.y > maxY) maxY = s.y;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return new DOMRect(
|
|
285
|
+
minX / scale, minY / scale,
|
|
286
|
+
(maxX - minX) / scale, (maxY - minY) / scale,
|
|
287
|
+
);
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
(globalThis as any).$ = {
|
|
292
|
+
Engine, OS, ClassDB, Time,
|
|
293
|
+
dumpStr, snapshot, inspect, click, find, gc,
|
|
294
|
+
get tree(): SceneTree { return tree; },
|
|
295
|
+
get paused(): boolean { return tree.paused; },
|
|
296
|
+
set paused(v: boolean) { tree.paused = v; },
|
|
297
|
+
};
|
|
219
298
|
}
|
package/src/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { GodotInstance } from '../gen/classes/GodotInstance.ts';
|
|
|
10
10
|
import { SceneTree } from '../gen/classes/SceneTree.ts';
|
|
11
11
|
import { Window } from '../gen/classes/Window.ts';
|
|
12
12
|
import { gc } from './debug.ts';
|
|
13
|
-
import { cancelAnimationFrame, getGodot, requestAnimationFrame } from './runtime.ts';
|
|
13
|
+
import { cancelAnimationFrame, cleanupHooks, getGodot, requestAnimationFrame } from './runtime.ts';
|
|
14
14
|
|
|
15
15
|
// ---- make sure these classes are not tree-shaked ----
|
|
16
16
|
void ValueTypes;
|
|
@@ -26,7 +26,7 @@ Object.assign(globalThis as any, { requestAnimationFrame, cancelAnimationFrame }
|
|
|
26
26
|
export async function runGodot(signal?: AbortSignal, unmount?: () => PromiseLike<void>) {
|
|
27
27
|
signal?.throwIfAborted();
|
|
28
28
|
signal?.addEventListener('abort', () => {
|
|
29
|
-
(Engine.getMainLoop() as SceneTree)
|
|
29
|
+
(Engine.getMainLoop() as SceneTree)?.quit();
|
|
30
30
|
}, { once: true });
|
|
31
31
|
|
|
32
32
|
const godot = getGodot();
|
|
@@ -34,7 +34,19 @@ export async function runGodot(signal?: AbortSignal, unmount?: () => PromiseLike
|
|
|
34
34
|
await new Promise(requestAnimationFrame);
|
|
35
35
|
|
|
36
36
|
await unmount?.();
|
|
37
|
-
|
|
37
|
+
while (cleanupHooks.size) {
|
|
38
|
+
const hooks = Array.from(cleanupHooks); cleanupHooks.clear();
|
|
39
|
+
await Promise.all(hooks.map((hook) => hook()));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Let pending Napi wrapper finalizers run before tearing down the engine:
|
|
43
|
+
// Bun.gc(true) marks unreachable wrappers but their ~GodotVar/~Variant
|
|
44
|
+
// (which unrefs RefCounted objects) only runs on later macrotasks — and one
|
|
45
|
+
// wrapper's finalizer can free others. Freeing the engine first would report
|
|
46
|
+
// every still-referenced RefCounted as leaked, so drain a few gc+tick rounds.
|
|
47
|
+
for (let i = 0; i < 4; i++) {
|
|
48
|
+
console.log(gc());
|
|
49
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
50
|
+
}
|
|
38
51
|
return godot.free();
|
|
39
52
|
}
|
|
40
|
-
|