@ringozz/godot 4.7.1-12 → 4.7.1-13
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/package.json +2 -2
- package/src/index.ts +15 -3
- package/src/load.ts +23 -2
- package/src/runtime.ts +3 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ringozz/godot",
|
|
3
3
|
"author": "Vladimir Davidovich",
|
|
4
|
-
"version": "4.7.1-
|
|
4
|
+
"version": "4.7.1-13",
|
|
5
5
|
"description": "Node-API bindings for Godot Engine — call Godot classes from JavaScript",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"godot"
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"optionalDependencies": {
|
|
39
39
|
"@ringozz/godot-macos-arm64": "^4.7.1-3",
|
|
40
40
|
"@ringozz/godot-windows-x86_64": "^4.7.1-3",
|
|
41
|
-
"@ringozz/godot-web-wasm32": "^4.7.1-
|
|
41
|
+
"@ringozz/godot-web-wasm32": "^4.7.1-12"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"@types/bun": "*"
|
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;
|
|
@@ -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
|
-
|
package/src/load.ts
CHANGED
|
@@ -8,9 +8,25 @@ import { ProjectSettings } from '../gen/classes/ProjectSettings.ts';
|
|
|
8
8
|
import { ResourceLoader, ThreadLoadStatus } from '../gen/classes/ResourceLoader.ts';
|
|
9
9
|
import { ResourceUID } from '../gen/classes/ResourceUID.ts';
|
|
10
10
|
import type { Resource } from '../gen/classes/Resource.ts';
|
|
11
|
-
import { stageFile } from './runtime.ts';
|
|
11
|
+
import { cleanupHooks, stageFile } from './runtime.ts';
|
|
12
12
|
import { decodeCtex } from './web-image.ts';
|
|
13
13
|
|
|
14
|
+
// Each loaded wrapper is released at shutdown via a `cleanupHooks` entry
|
|
15
|
+
// holding a WeakRef: module-scope asset imports (the preload modules' memoized
|
|
16
|
+
// load promises) keep their wrappers reachable, so GC alone can't collect them
|
|
17
|
+
// before the engine stops. WeakRef keeps the entries from pinning resources
|
|
18
|
+
// during the app's lifetime.
|
|
19
|
+
function track<T extends Resource>(res: T): T {
|
|
20
|
+
const ref = new WeakRef<Resource>(res);
|
|
21
|
+
cleanupHooks.add(() => {
|
|
22
|
+
const r = ref.deref();
|
|
23
|
+
if (r) {
|
|
24
|
+
try { r.free(); } catch {}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return res;
|
|
28
|
+
}
|
|
29
|
+
|
|
14
30
|
/**
|
|
15
31
|
* A Godot `Resource` subclass constructor: its `.name` is the registered class
|
|
16
32
|
* name (used as the `ResourceLoader` type hint), and `InstanceType<C>` is the
|
|
@@ -153,6 +169,7 @@ export function loadAsset<C extends ResourceConstructor>(
|
|
|
153
169
|
data: Record<string, unknown> = {},
|
|
154
170
|
): { materialize: Promise<unknown>; load: Promise<InstanceType<C>> } {
|
|
155
171
|
const cached = cachedResource<C>(path);
|
|
172
|
+
if (cached) cached.then(track);
|
|
156
173
|
const materialize = cached ? Promise.resolve() : materializeFiles(files, deps);
|
|
157
174
|
const existing = data[path] as Promise<InstanceType<C>> | undefined;
|
|
158
175
|
const load = existing ?? cached ?? materialize.then(() => loadResourceAsync(path, cls, files));
|
|
@@ -189,6 +206,10 @@ export async function loadResourceAsync<C extends ResourceConstructor>(
|
|
|
189
206
|
break;
|
|
190
207
|
}
|
|
191
208
|
if (status === ThreadLoadStatus.THREAD_LOAD_FAILED || status === ThreadLoadStatus.THREAD_LOAD_INVALID_RESOURCE) {
|
|
209
|
+
// Collect the engine's LoadToken so a failed load doesn't leave it
|
|
210
|
+
// registered (a bare `RefCounted` leaked at exit). Safe no-op when
|
|
211
|
+
// no token exists.
|
|
212
|
+
ResourceLoader.loadThreadedGet(path);
|
|
192
213
|
throw new Error(`loadResourceAsync(${path}): load failed (status ${status})`);
|
|
193
214
|
}
|
|
194
215
|
await nextTick();
|
|
@@ -206,5 +227,5 @@ export async function loadResourceAsync<C extends ResourceConstructor>(
|
|
|
206
227
|
}
|
|
207
228
|
}
|
|
208
229
|
}
|
|
209
|
-
return result;
|
|
230
|
+
return track(result);
|
|
210
231
|
}
|
package/src/runtime.ts
CHANGED
|
@@ -79,3 +79,6 @@ export function _P(signal: any, onfulfilled?: any, onrejected?: any) {
|
|
|
79
79
|
if (res !== 0) reject(new Error('Failed to connect signal: error code ' + res));
|
|
80
80
|
}).then(onfulfilled, onrejected);
|
|
81
81
|
}
|
|
82
|
+
|
|
83
|
+
/** Cleanup callbacks run once by `runGodot` just before the engine is freed; may be async. */
|
|
84
|
+
export const cleanupHooks: Set<() => void | Promise<void>> = new Set();
|