@ringozz/godot 4.7.1-9 → 4.7.2-579
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 +12 -7
- package/gen/classes/AnimationNode.ts +1 -1
- package/gen/classes/AnimationNodeBlendSpace2D.ts +1 -1
- package/gen/classes/ArrayOccluder3D.ts +3 -2
- package/gen/classes/AudioStreamMP3.ts +1 -1
- package/gen/classes/AudioStreamOggVorbis.ts +1 -1
- package/gen/classes/AudioStreamWAV.ts +2 -2
- package/gen/classes/BitMap.ts +1 -1
- package/gen/classes/CPUParticles2D.ts +3 -3
- package/gen/classes/CPUParticles3D.ts +3 -3
- package/gen/classes/CSGPolygon3D.ts +2 -1
- package/gen/classes/CharFXTransform.ts +1 -1
- package/gen/classes/CodeEdit.ts +6 -6
- package/gen/classes/CodeHighlighter.ts +3 -3
- package/gen/classes/CollisionPolygon2D.ts +1 -1
- package/gen/classes/CollisionPolygon3D.ts +2 -2
- package/gen/classes/ColorPalette.ts +2 -1
- package/gen/classes/Compositor.ts +1 -1
- package/gen/classes/ConcavePolygonShape2D.ts +2 -1
- package/gen/classes/ConcavePolygonShape3D.ts +2 -1
- package/gen/classes/Control.ts +4 -4
- package/gen/classes/ConvexPolygonShape2D.ts +2 -1
- package/gen/classes/ConvexPolygonShape3D.ts +2 -1
- package/gen/classes/DPITexture.ts +1 -1
- package/gen/classes/DisplayServer.ts +3 -4
- package/gen/classes/FileDialog.ts +1 -1
- package/gen/classes/Font.ts +1 -1
- package/gen/classes/FontFile.ts +2 -2
- package/gen/classes/FontVariation.ts +4 -4
- package/gen/classes/GLTFAccessor.ts +2 -2
- package/gen/classes/GLTFMesh.ts +2 -2
- package/gen/classes/GLTFNode.ts +1 -1
- package/gen/classes/GLTFObjectModelProperty.ts +2 -2
- package/gen/classes/GLTFSkeleton.ts +4 -4
- package/gen/classes/GLTFSkin.ts +7 -7
- package/gen/classes/GLTFState.ts +19 -19
- package/gen/classes/Gradient.ts +2 -2
- package/gen/classes/GraphEdit.ts +2 -2
- package/gen/classes/HeightMapShape3D.ts +1 -1
- package/gen/classes/Image.ts +1 -1
- package/gen/classes/Label.ts +2 -2
- package/gen/classes/Label3D.ts +1 -1
- package/gen/classes/Light3D.ts +4 -2
- package/gen/classes/LightmapGIData.ts +5 -5
- package/gen/classes/Line2D.ts +1 -1
- package/gen/classes/LineEdit.ts +1 -1
- package/gen/classes/LinkButton.ts +1 -1
- package/gen/classes/MultiMesh.ts +6 -6
- package/gen/classes/NavigationMesh.ts +2 -2
- package/gen/classes/NavigationMeshSourceGeometryData2D.ts +3 -3
- package/gen/classes/NavigationMeshSourceGeometryData3D.ts +3 -3
- package/gen/classes/NavigationObstacle2D.ts +1 -1
- package/gen/classes/NavigationObstacle3D.ts +1 -1
- package/gen/classes/NavigationPathQueryParameters2D.ts +2 -2
- package/gen/classes/NavigationPathQueryParameters3D.ts +2 -2
- package/gen/classes/NavigationPathQueryResult2D.ts +5 -4
- package/gen/classes/NavigationPathQueryResult3D.ts +5 -4
- package/gen/classes/NavigationPolygon.ts +3 -3
- package/gen/classes/OccluderPolygon2D.ts +2 -1
- package/gen/classes/OggPacketSequence.ts +2 -2
- package/gen/classes/OpenXRAction.ts +1 -1
- package/gen/classes/OpenXRActionMap.ts +2 -2
- package/gen/classes/OpenXRActionSet.ts +1 -1
- package/gen/classes/OpenXRIPBinding.ts +2 -2
- package/gen/classes/OpenXRInteractionProfile.ts +2 -2
- package/gen/classes/PhysicsPointQueryParameters2D.ts +1 -1
- package/gen/classes/PhysicsPointQueryParameters3D.ts +1 -1
- package/gen/classes/PhysicsRayQueryParameters2D.ts +1 -1
- package/gen/classes/PhysicsRayQueryParameters3D.ts +1 -1
- package/gen/classes/PhysicsShapeQueryParameters2D.ts +1 -1
- package/gen/classes/PhysicsShapeQueryParameters3D.ts +1 -1
- package/gen/classes/PhysicsTestMotionParameters2D.ts +2 -2
- package/gen/classes/PhysicsTestMotionParameters3D.ts +2 -2
- package/gen/classes/Polygon2D.ts +5 -5
- package/gen/classes/PolygonOccluder3D.ts +2 -1
- package/gen/classes/PolygonPathFinder.ts +1 -1
- package/gen/classes/RDFramebufferPass.ts +4 -4
- package/gen/classes/RDPipelineColorBlendState.ts +1 -1
- package/gen/classes/RDPipelineMultisampleState.ts +1 -1
- package/gen/classes/RDPipelineShader.ts +1 -1
- package/gen/classes/RDShaderSPIRV.ts +10 -10
- package/gen/classes/RandomNumberGenerator.ts +2 -2
- package/gen/classes/ResourcePreloader.ts +1 -1
- package/gen/classes/RichTextLabel.ts +3 -3
- package/gen/classes/Shortcut.ts +1 -1
- package/gen/classes/SplitContainer.ts +1 -1
- package/gen/classes/SpriteFrames.ts +1 -1
- package/gen/classes/StreamPeerBuffer.ts +1 -1
- package/gen/classes/SystemFont.ts +1 -1
- package/gen/classes/TextEdit.ts +1 -1
- package/gen/classes/TextMesh.ts +1 -1
- package/gen/classes/TileMapLayer.ts +1 -1
- package/gen/classes/Translation.ts +1 -1
- package/gen/classes/VisualShaderNode.ts +2 -2
- package/gen/classes/VisualShaderNodeFrame.ts +1 -1
- package/gen/classes/VisualShaderNodeIntParameter.ts +1 -1
- package/gen/classes/WebSocketMultiplayerPeer.ts +2 -2
- package/gen/classes/WebSocketPeer.ts +2 -2
- package/gen/classes/Window.ts +1 -1
- package/gen/classes/XRFaceTracker.ts +1 -1
- package/gen/heap-types/GodotArray.ts +4 -1
- package/gen/heap-types/GodotDictionary.ts +6 -1
- package/package.json +16 -10
- package/src/assets.d.ts +14 -0
- package/src/boot.browser.ts +5 -3
- package/src/boot.ts +3 -2
- package/src/debug.ts +236 -118
- package/src/index.ts +15 -3
- package/src/load.ts +23 -2
- package/src/preload.ts +19 -1
- package/src/runtime.ts +17 -4
package/gen/classes/Window.ts
CHANGED
|
@@ -542,7 +542,7 @@ export class Window extends Viewport {
|
|
|
542
542
|
* **Note:** This property is implemented on Linux (X11), macOS and Windows.
|
|
543
543
|
*/
|
|
544
544
|
get mousePassthroughPolygon(): PackedVector2Array { return _C(this, 5590) as PackedVector2Array; }
|
|
545
|
-
set mousePassthroughPolygon(v: PackedVector2Array) { _C(this, 12096, v); }
|
|
545
|
+
set mousePassthroughPolygon(v: PackedVector2Array | Vector2[]) { _C(this, 12096, v); }
|
|
546
546
|
|
|
547
547
|
/**
|
|
548
548
|
* If `true`, the window is visible.
|
|
@@ -26,7 +26,7 @@ export class XRFaceTracker extends XRTracker {
|
|
|
26
26
|
* The array of face blend shape weights with indices corresponding to the {@link BlendShapeEntry} enum.
|
|
27
27
|
*/
|
|
28
28
|
get blendShapes(): PackedFloat32Array { return _C(this, 3942) as PackedFloat32Array; }
|
|
29
|
-
set blendShapes(v: PackedFloat32Array) { _C(this, 10685, v); }
|
|
29
|
+
set blendShapes(v: PackedFloat32Array | number[]) { _C(this, 10685, v); }
|
|
30
30
|
|
|
31
31
|
static readonly name = _R(980, XRFaceTracker);
|
|
32
32
|
}
|
|
@@ -6,7 +6,10 @@ import type { Callable, StringName, Variant } from './index.ts';
|
|
|
6
6
|
|
|
7
7
|
/** Godot's Array type — lazy, indexed container. */
|
|
8
8
|
export class GodotArray<T = any> extends GodotVar implements Iterable<T> {
|
|
9
|
-
constructor(
|
|
9
|
+
constructor(from?: Iterable<T>) {
|
|
10
|
+
super(28);
|
|
11
|
+
if (from != null) for (const x of from) _C(this, 9630, x);
|
|
12
|
+
}
|
|
10
13
|
|
|
11
14
|
get length(): number { return _C(this, 13608) as number; }
|
|
12
15
|
|
|
@@ -6,7 +6,12 @@ import type { GodotArray, StringName, Variant } from './index.ts';
|
|
|
6
6
|
|
|
7
7
|
/** Godot's Dictionary type — lazy, keyed container. */
|
|
8
8
|
export class GodotDictionary<K = any, V = any> extends GodotVar implements Iterable<[K, V]> {
|
|
9
|
-
constructor(
|
|
9
|
+
constructor(from?: Record<string, V> | GodotDictionary<K, V>) {
|
|
10
|
+
super(27);
|
|
11
|
+
if (from == null) return;
|
|
12
|
+
if (from instanceof GodotDictionary) { this.assign(from); return; }
|
|
13
|
+
for (const [k, v] of Object.entries(from)) this.set(k as K, v);
|
|
14
|
+
}
|
|
10
15
|
|
|
11
16
|
*[Symbol.iterator](): Iterator<[K, V]> {
|
|
12
17
|
const ks = _C(this, 8530) as any;
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ringozz/godot",
|
|
3
3
|
"author": "Vladimir Davidovich",
|
|
4
|
-
"version": "4.7.
|
|
4
|
+
"version": "4.7.2-579",
|
|
5
5
|
"description": "Node-API bindings for Godot Engine — call Godot classes from JavaScript",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
6
9
|
"keywords": [
|
|
7
10
|
"godot"
|
|
8
11
|
],
|
|
@@ -11,16 +14,19 @@
|
|
|
11
14
|
"types": "./gen/index.ts",
|
|
12
15
|
"exports": {
|
|
13
16
|
".": "./src/index.ts",
|
|
17
|
+
"./assets": {
|
|
18
|
+
"types": "./src/assets.d.ts"
|
|
19
|
+
},
|
|
14
20
|
"./runtime": "./src/runtime.ts",
|
|
15
|
-
"./boot":
|
|
21
|
+
"./boot": {
|
|
22
|
+
"browser": "./src/boot.browser.ts",
|
|
23
|
+
"default": "./src/boot.ts"
|
|
24
|
+
},
|
|
16
25
|
"./load": "./src/load.ts",
|
|
17
26
|
"./debug": "./src/debug.ts",
|
|
18
27
|
"./preload": "./src/preload.ts",
|
|
19
28
|
"./*": "./gen/classes/*.ts"
|
|
20
29
|
},
|
|
21
|
-
"browser": {
|
|
22
|
-
"./src/boot.ts": "./src/boot.browser.ts"
|
|
23
|
-
},
|
|
24
30
|
"files": [
|
|
25
31
|
"src/",
|
|
26
32
|
"gen/",
|
|
@@ -29,16 +35,16 @@
|
|
|
29
35
|
"godot": {
|
|
30
36
|
"version_major": 4,
|
|
31
37
|
"version_minor": 7,
|
|
32
|
-
"version_patch":
|
|
38
|
+
"version_patch": 2,
|
|
33
39
|
"version_status": "stable",
|
|
34
40
|
"version_build": "official",
|
|
35
|
-
"version_full_name": "Godot Engine v4.7.
|
|
41
|
+
"version_full_name": "Godot Engine v4.7.2.stable.official",
|
|
36
42
|
"precision": "single"
|
|
37
43
|
},
|
|
38
44
|
"optionalDependencies": {
|
|
39
|
-
"@ringozz/godot-macos-arm64": "^4.7.
|
|
40
|
-
"@ringozz/godot-windows-x86_64": "^4.7.
|
|
41
|
-
"@ringozz/godot-web-wasm32": "^4.7.
|
|
45
|
+
"@ringozz/godot-macos-arm64": "^4.7.2-579",
|
|
46
|
+
"@ringozz/godot-windows-x86_64": "^4.7.2-579",
|
|
47
|
+
"@ringozz/godot-web-wasm32": "^4.7.2-579"
|
|
42
48
|
},
|
|
43
49
|
"peerDependencies": {
|
|
44
50
|
"@types/bun": "*"
|
package/src/assets.d.ts
CHANGED
|
@@ -101,6 +101,20 @@ declare module '*.wav' {
|
|
|
101
101
|
export const materialize: Promise<unknown>;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
declare module '*.ttf' {
|
|
105
|
+
import type { FontFile } from '@ringozz/godot/FontFile';
|
|
106
|
+
const asset: Promise<FontFile>;
|
|
107
|
+
export default asset;
|
|
108
|
+
export const materialize: Promise<unknown>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
declare module '*.otf' {
|
|
112
|
+
import type { FontFile } from '@ringozz/godot/FontFile';
|
|
113
|
+
const asset: Promise<FontFile>;
|
|
114
|
+
export default asset;
|
|
115
|
+
export const materialize: Promise<unknown>;
|
|
116
|
+
}
|
|
117
|
+
|
|
104
118
|
declare module '*.ogg' {
|
|
105
119
|
import type { AudioStreamOggVorbis } from '@ringozz/godot/AudioStreamOggVorbis';
|
|
106
120
|
const asset: Promise<AudioStreamOggVorbis>;
|
package/src/boot.browser.ts
CHANGED
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
// *namespace* (whose `.default` is the wasm module). This module is a leaf — it
|
|
8
8
|
// imports nothing from the godot class graph, so a web entry can call
|
|
9
9
|
// `preloadGodot()` *before* importing the app without pulling in `runtime.ts`.
|
|
10
|
-
// `runtime.ts` statically imports this module too
|
|
11
|
-
//
|
|
12
|
-
//
|
|
10
|
+
// `runtime.ts` statically imports this module too — by package self-reference
|
|
11
|
+
// (`@ringozz/godot/boot`, whose `exports` `./boot` entry's `browser` condition
|
|
12
|
+
// selects this file), the same resolution as the entrypoint's import, so both
|
|
13
|
+
// sides are guaranteed the same module instance and `nativeModule` below is the
|
|
14
|
+
// handoff — no global needed.
|
|
13
15
|
|
|
14
16
|
let nativeModule: unknown = null;
|
|
15
17
|
|
package/src/boot.ts
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
// Desktop boot leaf: resolves the native addon for the current host and
|
|
6
6
|
// hands it to `runtime.ts` as the native module *namespace* (whose `.default`
|
|
7
|
-
// is the addon's `module.exports`). `runtime.ts` imports this module
|
|
8
|
-
//
|
|
7
|
+
// is the addon's `module.exports`). `runtime.ts` imports this module by package
|
|
8
|
+
// self-reference (`@ringozz/godot/boot`), whose `exports` `./boot` entry's
|
|
9
|
+
// `browser` condition selects `boot.browser.ts` when bundling for
|
|
9
10
|
// `target: 'browser'` — so the Bun-only `import.meta.require` below never
|
|
10
11
|
// reaches a web bundle.
|
|
11
12
|
|
package/src/debug.ts
CHANGED
|
@@ -2,16 +2,27 @@
|
|
|
2
2
|
Copyright (c) Vladimir Davidovich. All rights reserved.
|
|
3
3
|
***********************************************************************/
|
|
4
4
|
|
|
5
|
-
import { Rect2, str, varToStr, 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 { Viewport } from '../gen/classes/Viewport.ts';
|
|
18
|
+
import type { Window } from '../gen/classes/Window.ts';
|
|
19
|
+
|
|
20
|
+
const tree = Engine.getMainLoop() as SceneTree;
|
|
21
|
+
|
|
22
|
+
/** Does a snapshot line match a string (case-insensitive substring) or `RegExp` pattern? */
|
|
23
|
+
function matches(pattern: string | RegExp | undefined, line: string): boolean {
|
|
24
|
+
return !pattern || (pattern instanceof RegExp ? pattern.test(line) : line.toLowerCase().includes(pattern.toLowerCase()));
|
|
25
|
+
}
|
|
15
26
|
|
|
16
27
|
/**
|
|
17
28
|
* Dump a Godot value or object as a string.
|
|
@@ -35,42 +46,95 @@ export function dumpStr(obj: unknown): string {
|
|
|
35
46
|
}
|
|
36
47
|
|
|
37
48
|
/**
|
|
38
|
-
* Serialize a scene subtree as a tree-formatted string.
|
|
49
|
+
* Serialize a scene subtree as a tree-formatted string, optionally filtered.
|
|
50
|
+
*
|
|
51
|
+
* Emits one line per node: `Class "name" (N children)`. When `pattern` is a
|
|
52
|
+
* string, only lines containing it (case-insensitive) are kept; a `RegExp`
|
|
53
|
+
* matches against the full line.
|
|
39
54
|
*
|
|
40
|
-
* @param
|
|
55
|
+
* @param pattern - Optional substring or `RegExp` to filter the lines
|
|
56
|
+
* @param node - Root node to start from (defaults to the scene tree's root Window)
|
|
41
57
|
* @param depth - Max depth (default 99)
|
|
42
58
|
*/
|
|
43
|
-
export function
|
|
59
|
+
export function snapshot(pattern?: string | RegExp, node?: Node, depth = 99): string {
|
|
60
|
+
const root = node ?? tree.root;
|
|
44
61
|
const lines: string[] = [];
|
|
45
62
|
(function walk(n: Node, d: number, ind: string) {
|
|
46
63
|
if (d <= 0) return;
|
|
47
|
-
|
|
48
|
-
|
|
64
|
+
const count = n.getChildCount();
|
|
65
|
+
const line = `${ind}${n.getClass()} "${n.name}" (${count} children)`;
|
|
66
|
+
if (matches(pattern, line)) lines.push(line);
|
|
67
|
+
for (let i = 0; i < count; i++) {
|
|
49
68
|
const c = n.getChild(i);
|
|
50
69
|
if (c) walk(c as Node, d - 1, ind + ' ');
|
|
51
70
|
}
|
|
52
|
-
})(
|
|
71
|
+
})(root, depth, '');
|
|
53
72
|
return lines.join('\n');
|
|
54
73
|
}
|
|
55
74
|
|
|
56
75
|
/**
|
|
57
|
-
*
|
|
76
|
+
* Resolve a node from the scene root by absolute NodePath (`/root/...`) or
|
|
77
|
+
* name glob (`findChild`). Returns `null` when not found.
|
|
78
|
+
*/
|
|
79
|
+
function resolveNode(path: string, root: Window): Node | null {
|
|
80
|
+
if (path.startsWith('/')) return root.getNode(path);
|
|
81
|
+
return root.findChild(path, true, false);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Resolve a node by path (absolute NodePath or name glob) from the scene root
|
|
86
|
+
* and dump it via {@link dumpStr}.
|
|
87
|
+
*
|
|
88
|
+
* @param path - An absolute NodePath like `/root/Box`, or a `findChild` name/glob pattern
|
|
89
|
+
*/
|
|
90
|
+
export function inspect(path: string): string | null {
|
|
91
|
+
const node = resolveNode(path, tree.root);
|
|
92
|
+
return node ? dumpStr(node) : null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Synthesize a left-click at the center of a {@link Control} matched by `path`.
|
|
97
|
+
*
|
|
98
|
+
* Press and release are pushed through `Window.pushInput` (the GUI-routing
|
|
99
|
+
* path — `Input.parseInputEvent` doesn't reliably reach `Control.guiInput` on
|
|
100
|
+
* the wasm build). Positions are in device space (logical × `contentScaleFactor`).
|
|
101
|
+
*
|
|
102
|
+
* @param path - An absolute NodePath like `/root/...`, or a `findChild` name/glob pattern
|
|
103
|
+
* @returns The node's `dumpStr`, or `null` if not found or not a `Control`
|
|
104
|
+
*/
|
|
105
|
+
export function click(path: string): string | null {
|
|
106
|
+
const root = tree.root;
|
|
107
|
+
const node = resolveNode(path, root);
|
|
108
|
+
if (!(node instanceof Control)) return null;
|
|
109
|
+
const rect = node.getGlobalRect();
|
|
110
|
+
const scale = root.contentScaleFactor;
|
|
111
|
+
const position = new Vector2(
|
|
112
|
+
(rect.position.x + rect.size.x / 2) * scale,
|
|
113
|
+
(rect.position.y + rect.size.y / 2) * scale,
|
|
114
|
+
);
|
|
115
|
+
const press = (down: boolean) => {
|
|
116
|
+
const event = new InputEventMouseButton();
|
|
117
|
+
event.position = position;
|
|
118
|
+
event.globalPosition = position;
|
|
119
|
+
event.buttonIndex = MouseButton.MOUSE_BUTTON_LEFT;
|
|
120
|
+
event.pressed = down;
|
|
121
|
+
root.pushInput(event);
|
|
122
|
+
};
|
|
123
|
+
press(true);
|
|
124
|
+
setTimeout(() => press(false), 50);
|
|
125
|
+
return dumpStr(node);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Enumerate nodes in the scene by name pattern and/or class, in tree order.
|
|
58
130
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
131
|
+
* @param pattern - Name glob matched by `findChildren` (default `"*"`)
|
|
132
|
+
* @param type - Class name to filter by, e.g. `"RigidBody3D"` (default `""` = any)
|
|
133
|
+
* @param node - Root node to search under (defaults to the scene tree's root Window)
|
|
62
134
|
*/
|
|
63
|
-
export function
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return [
|
|
67
|
-
`Frames: ${Engine.getProcessFrames()}`,
|
|
68
|
-
`FPS: ${Engine.getFramesPerSecond()}`,
|
|
69
|
-
`Physics: ${Engine.getPhysicsFrames()}`,
|
|
70
|
-
`Nodes: ${tree?.getNodeCount?.() ?? 'N/A'}`,
|
|
71
|
-
`Time scale: ${Engine.timeScale}`,
|
|
72
|
-
`Main loop: ${tree ? tree.getClass() : 'N/A'}`,
|
|
73
|
-
].join('\n');
|
|
135
|
+
export function find(pattern = '*', type = '', node?: Node): Node[] {
|
|
136
|
+
const root = node ?? tree.root;
|
|
137
|
+
return [...root.findChildren(pattern, type, true, false)];
|
|
74
138
|
}
|
|
75
139
|
|
|
76
140
|
/**
|
|
@@ -117,6 +181,150 @@ function ensureDOMRect(): void {
|
|
|
117
181
|
};
|
|
118
182
|
}
|
|
119
183
|
|
|
184
|
+
type Push = (x: number, y: number) => void;
|
|
185
|
+
type BoundsCollect = (node: any, push: Push, viewport: Viewport) => void;
|
|
186
|
+
|
|
187
|
+
/** Affine 2D point mapper from a transform's basis/origin, optionally scaled. */
|
|
188
|
+
function makeAffine(t: any, scale = 1): (x: number, y: number) => [number, number] {
|
|
189
|
+
const tox = t.origin.x, toy = t.origin.y;
|
|
190
|
+
const txx = t.x.x, txy = t.x.y;
|
|
191
|
+
const tyx = t.y.x, tyy = t.y.y;
|
|
192
|
+
return (x, y) => [(tox + txx * x + tyx * y) * scale, (toy + txy * x + tyy * y) * scale];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Axis-aligned 2D bounds accumulator in CSS pixels. Non-finite points (e.g.
|
|
197
|
+
* geometry behind the camera) are dropped; `rect()` is `null` when no point
|
|
198
|
+
* was added.
|
|
199
|
+
*/
|
|
200
|
+
function cssBounds(): { add(x: number, y: number): void; rect(): [number, number, number, number] | null } {
|
|
201
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
202
|
+
let found = false;
|
|
203
|
+
return {
|
|
204
|
+
add(x, y) {
|
|
205
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) return;
|
|
206
|
+
if (x < minX) minX = x; if (y < minY) minY = y;
|
|
207
|
+
if (x > maxX) maxX = x; if (y > maxY) maxY = y;
|
|
208
|
+
found = true;
|
|
209
|
+
},
|
|
210
|
+
rect() {
|
|
211
|
+
return found ? [minX, minY, maxX - minX, maxY - minY] : null;
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** World-space AABB (min, max) of a local AABB under its global transform, or `null` when degenerate. */
|
|
217
|
+
function worldAabb(aabb: any, gt: any): [number, number, number, number, number, number] | null {
|
|
218
|
+
if (aabb.size.x === 0 && aabb.size.y === 0 && aabb.size.z === 0) return null;
|
|
219
|
+
const bx = gt.basis.x, by = gt.basis.y, bz = gt.basis.z;
|
|
220
|
+
const cx = aabb.position.x + aabb.size.x / 2;
|
|
221
|
+
const cy = aabb.position.y + aabb.size.y / 2;
|
|
222
|
+
const cz = aabb.position.z + aabb.size.z / 2;
|
|
223
|
+
const ex = Math.hypot(bx.x, bx.y, bx.z) * aabb.size.x / 2;
|
|
224
|
+
const ey = Math.hypot(by.x, by.y, by.z) * aabb.size.y / 2;
|
|
225
|
+
const ez = Math.hypot(bz.x, bz.y, bz.z) * aabb.size.z / 2;
|
|
226
|
+
const ox = gt.origin.x, oy = gt.origin.y, oz = gt.origin.z;
|
|
227
|
+
const wx = ox + bx.x * cx + by.x * cy + bz.x * cz;
|
|
228
|
+
const wy = oy + bx.y * cx + by.y * cy + bz.y * cz;
|
|
229
|
+
const wz = oz + bx.z * cx + by.z * cy + bz.z * cz;
|
|
230
|
+
return [wx - ex, wy - ey, wz - ez, wx + ex, wy + ey, wz + ez];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* World-space AABB of a node's visual content: the union of its own AABB (when
|
|
235
|
+
* it is a `VisualInstance3D`) and every descendant `VisualInstance3D`'s, each in
|
|
236
|
+
* its own global transform — mirrors the editor's `_calculate_spatial_bounds`.
|
|
237
|
+
* Returns `null` when there is no non-degenerate visual.
|
|
238
|
+
*/
|
|
239
|
+
function worldBounds(node: Node3D): [number, number, number, number, number, number] | null {
|
|
240
|
+
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
241
|
+
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
|
242
|
+
const add = (aabb: any, gt: any) => {
|
|
243
|
+
const box = worldAabb(aabb, gt);
|
|
244
|
+
if (!box) return;
|
|
245
|
+
if (box[0] < minX) minX = box[0]; if (box[3] > maxX) maxX = box[3];
|
|
246
|
+
if (box[1] < minY) minY = box[1]; if (box[4] > maxY) maxY = box[4];
|
|
247
|
+
if (box[2] < minZ) minZ = box[2]; if (box[5] > maxZ) maxZ = box[5];
|
|
248
|
+
};
|
|
249
|
+
if (typeof (node as any).getAabb === 'function') {
|
|
250
|
+
add((node as any).getAabb(), node.globalTransform);
|
|
251
|
+
}
|
|
252
|
+
for (const child of node.findChildren('*', 'VisualInstance3D', true, false) as unknown as any[]) {
|
|
253
|
+
if (child && typeof child.getAabb === 'function') {
|
|
254
|
+
add(child.getAabb(), child.globalTransform);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return minX === Infinity ? null : [minX, minY, minZ, maxX, maxY, maxZ];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Compute a node's bounding rect in CSS pixels. The callback pushes points in
|
|
262
|
+
* viewport logical units; they're mapped through the viewport's screen transform
|
|
263
|
+
* (content scale, stretch, SubViewport/child-window placement), then divided by
|
|
264
|
+
* screenGetScale to reach CSS pixels. Never throws: React DevTools calls this on
|
|
265
|
+
* every commit and must not see a throw (e.g. a wrapper freed by an unmount).
|
|
266
|
+
*/
|
|
267
|
+
function boundsForViewport(node: Node, collect: (push: Push, viewport: Viewport) => void): DOMRect | null {
|
|
268
|
+
try {
|
|
269
|
+
const viewport = node.getViewport();
|
|
270
|
+
if (!viewport) return null;
|
|
271
|
+
const xform = makeAffine(viewport.getScreenTransform(), 1 / DisplayServer.screenGetScale());
|
|
272
|
+
const bounds = cssBounds();
|
|
273
|
+
collect((x, y) => bounds.add(...xform(x, y)), viewport);
|
|
274
|
+
const r = bounds.rect();
|
|
275
|
+
return r ? new DOMRect(r[0], r[1], r[2], r[3]) : null;
|
|
276
|
+
} catch {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** Install `getBoundingClientRect` on a node prototype. */
|
|
282
|
+
function defineBoundingClientRect(proto: object, collect: BoundsCollect): void {
|
|
283
|
+
Object.defineProperty(proto, 'getBoundingClientRect', {
|
|
284
|
+
configurable: true,
|
|
285
|
+
value(this: Node): DOMRect | null {
|
|
286
|
+
return boundsForViewport(this, (push, viewport) => collect(this, push, viewport));
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Push the four corners of an axis-aligned rect through `emit`. */
|
|
292
|
+
function pushRect(emit: (x: number, y: number) => void, x0: number, y0: number, x1: number, y1: number): void {
|
|
293
|
+
for (const px of [x0, x1]) {
|
|
294
|
+
for (const py of [y0, y1]) {
|
|
295
|
+
emit(px, py);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const canvasItemBounds: BoundsCollect = (node, push) => {
|
|
301
|
+
if (node instanceof Control) {
|
|
302
|
+
const rc = node.getGlobalRect();
|
|
303
|
+
pushRect(push, rc.position.x, rc.position.y, rc.position.x + rc.size.x, rc.position.y + rc.size.y);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (typeof node.getRect !== 'function') return;
|
|
307
|
+
const rect = node.getRect() as Rect2;
|
|
308
|
+
const xform = makeAffine(node.getScreenTransform());
|
|
309
|
+
pushRect((px, py) => push(...xform(px, py)), rect.position.x, rect.position.y, rect.position.x + rect.size.x, rect.position.y + rect.size.y);
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const node3dBounds: BoundsCollect = (node, push, viewport) => {
|
|
313
|
+
const camera = viewport.getCamera3d();
|
|
314
|
+
if (!camera) return;
|
|
315
|
+
const box = worldBounds(node);
|
|
316
|
+
if (!box) return;
|
|
317
|
+
const [minX, minY, minZ, maxX, maxY, maxZ] = box;
|
|
318
|
+
for (let i = 0; i < 8; i++) {
|
|
319
|
+
const s = camera.unprojectPosition(new Vector3(
|
|
320
|
+
i & 1 ? maxX : minX,
|
|
321
|
+
i & 2 ? maxY : minY,
|
|
322
|
+
i & 4 ? maxZ : minZ,
|
|
323
|
+
));
|
|
324
|
+
push(s.x, s.y);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
120
328
|
/**
|
|
121
329
|
* Bootstrap the debugging environment.
|
|
122
330
|
*
|
|
@@ -133,104 +341,14 @@ export function initDebug(): void {
|
|
|
133
341
|
});
|
|
134
342
|
|
|
135
343
|
ensureDOMRect();
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
configurable: true,
|
|
139
|
-
value(this: CanvasItem): DOMRect | null {
|
|
140
|
-
const scale = DisplayServer.screenGetScale();
|
|
141
|
-
if (this instanceof Control) {
|
|
142
|
-
const rc = (this as Control).getGlobalRect();
|
|
143
|
-
return new DOMRect(
|
|
144
|
-
rc.position.x / scale, rc.position.y / scale,
|
|
145
|
-
rc.size.x / scale, rc.size.y / scale,
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (typeof (this as any).getRect !== 'function') return null;
|
|
150
|
-
|
|
151
|
-
const rect = (this as any).getRect() as Rect2;
|
|
152
|
-
const t = this.getScreenTransform();
|
|
153
|
-
|
|
154
|
-
const pos = rect.position;
|
|
155
|
-
const sz = rect.size;
|
|
156
|
-
const tx = t.x;
|
|
157
|
-
const ty = t.y;
|
|
158
|
-
const to = t.origin;
|
|
159
|
-
|
|
160
|
-
const x0 = pos.x, y0 = pos.y;
|
|
161
|
-
const x1 = x0 + sz.x, y1 = y0 + sz.y;
|
|
162
|
-
const txx = tx.x, txy = tx.y;
|
|
163
|
-
const tyx = ty.x, tyy = ty.y;
|
|
164
|
-
const tox = to.x, toy = to.y;
|
|
165
|
-
|
|
166
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
167
|
-
const accum = (px: number, py: number) => {
|
|
168
|
-
if (px < minX) minX = px; if (py < minY) minY = py;
|
|
169
|
-
if (px > maxX) maxX = px; if (py > maxY) maxY = py;
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
accum(txx * x0 + tyx * y0 + tox, txy * x0 + tyy * y0 + toy);
|
|
173
|
-
accum(txx * x1 + tyx * y0 + tox, txy * x1 + tyy * y0 + toy);
|
|
174
|
-
accum(txx * x0 + tyx * y1 + tox, txy * x0 + tyy * y1 + toy);
|
|
175
|
-
accum(txx * x1 + tyx * y1 + tox, txy * x1 + tyy * y1 + toy);
|
|
176
|
-
|
|
177
|
-
return new DOMRect(
|
|
178
|
-
minX / scale, minY / scale,
|
|
179
|
-
(maxX - minX) / scale, (maxY - minY) / scale,
|
|
180
|
-
);
|
|
181
|
-
},
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
Object.defineProperty(Node3D.prototype, 'getBoundingClientRect', {
|
|
185
|
-
configurable: true,
|
|
186
|
-
value(this: Node3D): DOMRect | null {
|
|
187
|
-
if (typeof (this as any).getAabb !== 'function') return null;
|
|
188
|
-
|
|
189
|
-
const aabb = (this as any).getAabb();
|
|
190
|
-
const gt = this.globalTransform;
|
|
191
|
-
const viewport = this.getViewport();
|
|
192
|
-
if (!viewport) return null;
|
|
193
|
-
const camera = viewport.getCamera3d();
|
|
194
|
-
if (!camera) return null;
|
|
195
|
-
const scale = DisplayServer.screenGetScale();
|
|
196
|
-
|
|
197
|
-
const b = gt.basis;
|
|
198
|
-
const bx = b.x, by = b.y, bz = b.z;
|
|
199
|
-
const o = gt.origin;
|
|
200
|
-
const p = aabb.position, e = aabb.end;
|
|
201
|
-
|
|
202
|
-
const [px, py, pz] = [p.x, p.y, p.z];
|
|
203
|
-
const [ex, ey, ez] = [e.x, e.y, e.z];
|
|
204
|
-
const [bxx, bxy, bxz] = [bx.x, bx.y, bx.z];
|
|
205
|
-
const [byx, byy, byz] = [by.x, by.y, by.z];
|
|
206
|
-
const [bzx, bzy, bzz] = [bz.x, bz.y, bz.z];
|
|
207
|
-
const [ox, oy, oz] = [o.x, o.y, o.z];
|
|
208
|
-
|
|
209
|
-
const corners = [
|
|
210
|
-
[px, py, pz], [ex, py, pz], [px, ey, pz], [ex, ey, pz],
|
|
211
|
-
[px, py, ez], [ex, py, ez], [px, ey, ez], [ex, ey, ez],
|
|
212
|
-
];
|
|
213
|
-
|
|
214
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
215
|
-
|
|
216
|
-
for (const [cx, cy, cz] of corners) {
|
|
217
|
-
const wx = bxx * cx + byx * cy + bzx * cz + ox;
|
|
218
|
-
const wy = bxy * cx + byy * cy + bzy * cz + oy;
|
|
219
|
-
const wz = bxz * cx + byz * cy + bzz * cz + oz;
|
|
220
|
-
const s = camera.unprojectPosition(new Vector3(wx, wy, wz));
|
|
221
|
-
if (s.x < minX) minX = s.x; if (s.y < minY) minY = s.y;
|
|
222
|
-
if (s.x > maxX) maxX = s.x; if (s.y > maxY) maxY = s.y;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return new DOMRect(
|
|
226
|
-
minX / scale, minY / scale,
|
|
227
|
-
(maxX - minX) / scale, (maxY - minY) / scale,
|
|
228
|
-
);
|
|
229
|
-
},
|
|
230
|
-
});
|
|
344
|
+
defineBoundingClientRect(CanvasItem.prototype, canvasItemBounds);
|
|
345
|
+
defineBoundingClientRect(Node3D.prototype, node3dBounds);
|
|
231
346
|
|
|
232
347
|
(globalThis as any).$ = {
|
|
233
348
|
Engine, OS, ClassDB, Time,
|
|
234
|
-
dumpStr,
|
|
349
|
+
dumpStr, snapshot, inspect, click, find, gc,
|
|
350
|
+
get tree(): SceneTree { return tree; },
|
|
351
|
+
get paused(): boolean { return tree.paused; },
|
|
352
|
+
set paused(v: boolean) { tree.paused = v; },
|
|
235
353
|
};
|
|
236
354
|
}
|
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
|
}
|