@ringozz/godot 0.1.1 → 4.7.1-1
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 +62 -62
- package/package.json +4 -4
- package/src/debug.ts +219 -219
- package/src/index.ts +36 -36
- package/src/runtime.ts +68 -69
package/README.md
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
# @ringozz/godot
|
|
2
|
-
|
|
3
|
-
Node-API bindings for the Godot Engine — call Godot classes, value types, and utility functions from JavaScript.
|
|
4
|
-
|
|
5
|
-
This is the core runtime package. The React layer is a separate package ([`@ringozz/react-godot`](../react-godot/README.md)).
|
|
6
|
-
|
|
7
|
-
## Installation
|
|
8
|
-
|
|
9
|
-
```sh
|
|
10
|
-
bun add @ringozz/godot
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
The package ships no engine binary itself — it auto-selects a platform addon from its optional dependencies on install: `@ringozz/godot-macos-arm64`, `@ringozz/godot-windows-x86_64`, or `@ringozz/godot-web-wasm32` (the WASM build is installed everywhere and used on web). No `project.godot` is required — the engine boots from engine defaults with `res://` = your working directory.
|
|
14
|
-
|
|
15
|
-
## Usage
|
|
16
|
-
|
|
17
|
-
The engine is already running when you import — `getGodot()` returns the instance synchronously. Pump frames with `runGodot()`:
|
|
18
|
-
|
|
19
|
-
```ts
|
|
20
|
-
import { runGodot } from '@ringozz/godot';
|
|
21
|
-
import { Engine } from '@ringozz/godot/Engine';
|
|
22
|
-
import { SceneTree } from '@ringozz/godot/SceneTree';
|
|
23
|
-
|
|
24
|
-
const tree = Engine.getMainLoop() as SceneTree;
|
|
25
|
-
const root = tree.root;
|
|
26
|
-
|
|
27
|
-
const label = new Label();
|
|
28
|
-
label.text = 'hello from godot-node';
|
|
29
|
-
root.addChild(label);
|
|
30
|
-
|
|
31
|
-
const done = runGodot(); // pumps frames until aborted
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
### What's exported where
|
|
35
|
-
|
|
36
|
-
| Specifier | Contents |
|
|
37
|
-
|---|---|
|
|
38
|
-
| `@ringozz/godot` | Value types (`Vector3`, `Color`, …), global enums, heap types, utility functions, global constants, `runGodot()`, RAF polyfills |
|
|
39
|
-
| `@ringozz/godot/ClassName` | Classes, one per module — e.g. `@ringozz/godot/Label`, `@ringozz/godot/Node` |
|
|
40
|
-
| `@ringozz/godot/runtime` | Low-level dispatch: `_C`, `_S`, `_get`, `_set`, `_R`, `_G`, `_P`, `getGodot`, `GodotVar` |
|
|
41
|
-
| `@ringozz/godot/debug` | `initDebug()`, `dumpStr`, `dumpTreeStr`, `statsStr`, `gc` |
|
|
42
|
-
|
|
43
|
-
Class-scoped enums use bare names (import `ProcessMode` from `@ringozz/godot/Node`, not `NodeProcessMode`).
|
|
44
|
-
|
|
45
|
-
### Debugging
|
|
46
|
-
|
|
47
|
-
```ts
|
|
48
|
-
import { initDebug } from '@ringozz/godot/debug';
|
|
49
|
-
initDebug();
|
|
50
|
-
// singletons + helpers now on globalThis.$: $.Engine, $.dumpTreeStr(root), $.statsStr(), $.gc()
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
`initDebug` also registers an `uncaughtException` handler that keeps the process alive, and adds `getBoundingClientRect()` to `CanvasItem`/`Node3D`.
|
|
54
|
-
|
|
55
|
-
## Notes
|
|
56
|
-
|
|
57
|
-
- **Memory**: non-RefCounted objects (`Node`, `Node2D`, `Node3D`, …) must call `free()` explicitly. RefCounted objects are managed by Godot's ref counting; calling `free()` clears the JS wrapper (decrements the ref).
|
|
58
|
-
- **Class-registration tree-shaking**: `ClassDB.instantiate('X')` requires the JS class to be value-imported (`import { Label } from '@ringozz/godot/Label'`). If a class is only type-used (e.g. `as Label` casts), bundlers may drop its registration side effect, and wrappers fall back to an ancestor class. Render through JSX is unaffected. Workaround: keep a value reference (`void Label;`).
|
|
59
|
-
|
|
60
|
-
## Development
|
|
61
|
-
|
|
62
|
-
`gen/` is generated (and gitignored) by `dev/codegen.ts` from `godot --dump-extension-api-with-docs`. Regenerate + typecheck with `bun run prebuild`. Build the native addon with `bun run build`. See [`AGENTS.md`](../../AGENTS.md) for the full workflow.
|
|
1
|
+
# @ringozz/godot
|
|
2
|
+
|
|
3
|
+
Node-API bindings for the Godot Engine — call Godot classes, value types, and utility functions from JavaScript.
|
|
4
|
+
|
|
5
|
+
This is the core runtime package. The React layer is a separate package ([`@ringozz/react-godot`](../react-godot/README.md)).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
bun add @ringozz/godot
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The package ships no engine binary itself — it auto-selects a platform addon from its optional dependencies on install: `@ringozz/godot-macos-arm64`, `@ringozz/godot-windows-x86_64`, or `@ringozz/godot-web-wasm32` (the WASM build is installed everywhere and used on web). No `project.godot` is required — the engine boots from engine defaults with `res://` = your working directory.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
The engine is already running when you import — `getGodot()` returns the instance synchronously. Pump frames with `runGodot()`:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { runGodot } from '@ringozz/godot';
|
|
21
|
+
import { Engine } from '@ringozz/godot/Engine';
|
|
22
|
+
import { SceneTree } from '@ringozz/godot/SceneTree';
|
|
23
|
+
|
|
24
|
+
const tree = Engine.getMainLoop() as SceneTree;
|
|
25
|
+
const root = tree.root;
|
|
26
|
+
|
|
27
|
+
const label = new Label();
|
|
28
|
+
label.text = 'hello from godot-node';
|
|
29
|
+
root.addChild(label);
|
|
30
|
+
|
|
31
|
+
const done = runGodot(); // pumps frames until aborted
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### What's exported where
|
|
35
|
+
|
|
36
|
+
| Specifier | Contents |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `@ringozz/godot` | Value types (`Vector3`, `Color`, …), global enums, heap types, utility functions, global constants, `runGodot()`, RAF polyfills |
|
|
39
|
+
| `@ringozz/godot/ClassName` | Classes, one per module — e.g. `@ringozz/godot/Label`, `@ringozz/godot/Node` |
|
|
40
|
+
| `@ringozz/godot/runtime` | Low-level dispatch: `_C`, `_S`, `_get`, `_set`, `_R`, `_G`, `_P`, `getGodot`, `GodotVar` |
|
|
41
|
+
| `@ringozz/godot/debug` | `initDebug()`, `dumpStr`, `dumpTreeStr`, `statsStr`, `gc` |
|
|
42
|
+
|
|
43
|
+
Class-scoped enums use bare names (import `ProcessMode` from `@ringozz/godot/Node`, not `NodeProcessMode`).
|
|
44
|
+
|
|
45
|
+
### Debugging
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { initDebug } from '@ringozz/godot/debug';
|
|
49
|
+
initDebug();
|
|
50
|
+
// singletons + helpers now on globalThis.$: $.Engine, $.dumpTreeStr(root), $.statsStr(), $.gc()
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`initDebug` also registers an `uncaughtException` handler that keeps the process alive, and adds `getBoundingClientRect()` to `CanvasItem`/`Node3D`.
|
|
54
|
+
|
|
55
|
+
## Notes
|
|
56
|
+
|
|
57
|
+
- **Memory**: non-RefCounted objects (`Node`, `Node2D`, `Node3D`, …) must call `free()` explicitly. RefCounted objects are managed by Godot's ref counting; calling `free()` clears the JS wrapper (decrements the ref).
|
|
58
|
+
- **Class-registration tree-shaking**: `ClassDB.instantiate('X')` requires the JS class to be value-imported (`import { Label } from '@ringozz/godot/Label'`). If a class is only type-used (e.g. `as Label` casts), bundlers may drop its registration side effect, and wrappers fall back to an ancestor class. Render through JSX is unaffected. Workaround: keep a value reference (`void Label;`).
|
|
59
|
+
|
|
60
|
+
## Development
|
|
61
|
+
|
|
62
|
+
`gen/` is generated (and gitignored) by `dev/codegen.ts` from `godot --dump-extension-api-with-docs`. Regenerate + typecheck with `bun run prebuild`. Build the native addon with `bun run build`. See [`AGENTS.md`](../../AGENTS.md) for the full workflow.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ringozz/godot",
|
|
3
3
|
"author": "Vladimir Davidovich",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.7.1-1",
|
|
5
5
|
"description": "Node-API bindings for Godot Engine — call Godot classes from JavaScript",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./src/index.ts",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"precision": "single"
|
|
28
28
|
},
|
|
29
29
|
"optionalDependencies": {
|
|
30
|
-
"@ringozz/godot-macos-arm64": "^
|
|
31
|
-
"@ringozz/godot-windows-x86_64": "^
|
|
32
|
-
"@ringozz/godot-web-wasm32": "^
|
|
30
|
+
"@ringozz/godot-macos-arm64": "^4.7.1-1",
|
|
31
|
+
"@ringozz/godot-windows-x86_64": "^4.7.1-1",
|
|
32
|
+
"@ringozz/godot-web-wasm32": "^4.7.1-2"
|
|
33
33
|
}
|
|
34
34
|
}
|
package/src/debug.ts
CHANGED
|
@@ -1,219 +1,219 @@
|
|
|
1
|
-
/**********************************************************************
|
|
2
|
-
Copyright (c) Vladimir Davidovich. All rights reserved.
|
|
3
|
-
***********************************************************************/
|
|
4
|
-
|
|
5
|
-
import { Rect2, Vector3 } from './index.ts';
|
|
6
|
-
import { CanvasItem } from '../gen/classes/CanvasItem.ts';
|
|
7
|
-
import { ClassDB } from '../gen/classes/ClassDB.ts';
|
|
8
|
-
import { Control } from '../gen/classes/Control.ts';
|
|
9
|
-
import { DisplayServer } from '../gen/classes/DisplayServer.ts';
|
|
10
|
-
import { Engine } from '../gen/classes/Engine.ts';
|
|
11
|
-
import { Node } from '../gen/classes/Node.ts';
|
|
12
|
-
import { Node3D } from '../gen/classes/Node3D.ts';
|
|
13
|
-
import { OS } from '../gen/classes/OS.ts';
|
|
14
|
-
import { Time } from '../gen/classes/Time.ts';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Dump a Godot object's properties as an indented string.
|
|
18
|
-
* Uses `getPropertyList()` + `get()` to safely introspect — never invokes JS getters.
|
|
19
|
-
*
|
|
20
|
-
* @param obj - Any Godot wrapper (Node, Resource, etc.) or plain value
|
|
21
|
-
* @param depth - Max recursion depth into sub-objects (default 2)
|
|
22
|
-
* @param maxProps - Max properties to show per object (default 30)
|
|
23
|
-
*/
|
|
24
|
-
export function dumpStr(obj: unknown, depth = 2, maxProps = 30): string {
|
|
25
|
-
if (obj == null || typeof obj !== 'object') return String(obj);
|
|
26
|
-
let cls: string;
|
|
27
|
-
try { cls = (obj as any).getClass?.(); } catch { cls = (obj as any).constructor?.name ?? typeof obj; }
|
|
28
|
-
if (!cls) return String(obj);
|
|
29
|
-
const lines = [`[${cls}]`];
|
|
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
|
-
}
|
|
49
|
-
|
|
50
|
-
function indent(s: string, prefix: string): string { return s.split('\n').join('\n' + prefix); }
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Serialize a scene subtree as a tree-formatted string.
|
|
54
|
-
*
|
|
55
|
-
* @param node - Root node to start from
|
|
56
|
-
* @param depth - Max depth (default 99)
|
|
57
|
-
*/
|
|
58
|
-
export function dumpTreeStr(node: Node, depth = 99): string {
|
|
59
|
-
const lines: string[] = [];
|
|
60
|
-
(function walk(n: Node, d: number, ind: string) {
|
|
61
|
-
if (d <= 0) return;
|
|
62
|
-
lines.push(`${ind}${n.getClass()} "${n.name}" (${n.getChildCount()} children)`);
|
|
63
|
-
for (let i = 0; i < n.getChildCount(); i++) {
|
|
64
|
-
const c = n.getChild(i);
|
|
65
|
-
if (c) walk(c as Node, d - 1, ind + ' ');
|
|
66
|
-
}
|
|
67
|
-
})(node, depth, '');
|
|
68
|
-
return lines.join('\n');
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Return a snapshot of engine runtime stats as a formatted string.
|
|
73
|
-
*
|
|
74
|
-
* Includes process frames, FPS, physics frames, node count, time scale,
|
|
75
|
-
* and main-loop class.
|
|
76
|
-
*/
|
|
77
|
-
export function statsStr(): string {
|
|
78
|
-
const tree = Engine.getMainLoop() as any;
|
|
79
|
-
return [
|
|
80
|
-
`Frames: ${Engine.getProcessFrames()}`,
|
|
81
|
-
`FPS: ${Engine.getFramesPerSecond()}`,
|
|
82
|
-
`Physics: ${Engine.getPhysicsFrames()}`,
|
|
83
|
-
`Nodes: ${tree.getNodeCount()}`,
|
|
84
|
-
`Time scale: ${Engine.timeScale}`,
|
|
85
|
-
`Main loop: ${Engine.getMainLoop().getClass()}`,
|
|
86
|
-
].join('\n');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Run `gc()` and report how much heap was freed.
|
|
91
|
-
* Requires the `--expose-gc` Node.js flag.
|
|
92
|
-
*/
|
|
93
|
-
export function gc(): string {
|
|
94
|
-
const before = process.memoryUsage().heapUsed;
|
|
95
|
-
if (typeof Bun !== 'undefined')
|
|
96
|
-
Bun.gc(true);
|
|
97
|
-
else if (globalThis.gc)
|
|
98
|
-
globalThis.gc({ type: 'major', execution: 'sync' });
|
|
99
|
-
else
|
|
100
|
-
return 'gc() unavailable — need --expose-gc';
|
|
101
|
-
|
|
102
|
-
const after = process.memoryUsage().heapUsed;
|
|
103
|
-
return `GC: freed ${((before - after) / 1024).toFixed(0)} KB`;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Bootstrap the debugging environment.
|
|
108
|
-
*
|
|
109
|
-
* - Exposes common singletons (`Engine`, `OS`, `ClassDB`, `Time`) and all
|
|
110
|
-
* debug utility functions on `globalThis.$` so they're reachable from
|
|
111
|
-
* breakpoints in any module.
|
|
112
|
-
* - Registers an `uncaughtException` handler that logs but keeps the process
|
|
113
|
-
* alive for interactive debugging.
|
|
114
|
-
*/
|
|
115
|
-
export function initDebug(): void {
|
|
116
|
-
globalThis.process?.on('uncaughtException', (err, origin) => {
|
|
117
|
-
console.error(`\n✗ ${origin}:`, err.stack);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
Object.defineProperty(CanvasItem.prototype, 'getBoundingClientRect', {
|
|
121
|
-
configurable: true,
|
|
122
|
-
value(this: CanvasItem): DOMRect | null {
|
|
123
|
-
const scale = DisplayServer.screenGetScale();
|
|
124
|
-
if (this instanceof Control) {
|
|
125
|
-
const rc = (this as Control).getGlobalRect();
|
|
126
|
-
return new DOMRect(
|
|
127
|
-
rc.position.x / scale, rc.position.y / scale,
|
|
128
|
-
rc.size.x / scale, rc.size.y / scale,
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (typeof (this as any).getRect !== 'function') return null;
|
|
133
|
-
|
|
134
|
-
const rect = (this as any).getRect() as Rect2;
|
|
135
|
-
const t = this.getScreenTransform();
|
|
136
|
-
|
|
137
|
-
const pos = rect.position;
|
|
138
|
-
const sz = rect.size;
|
|
139
|
-
const tx = t.x;
|
|
140
|
-
const ty = t.y;
|
|
141
|
-
const to = t.origin;
|
|
142
|
-
|
|
143
|
-
const x0 = pos.x, y0 = pos.y;
|
|
144
|
-
const x1 = x0 + sz.x, y1 = y0 + sz.y;
|
|
145
|
-
const txx = tx.x, txy = tx.y;
|
|
146
|
-
const tyx = ty.x, tyy = ty.y;
|
|
147
|
-
const tox = to.x, toy = to.y;
|
|
148
|
-
|
|
149
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
150
|
-
const accum = (px: number, py: number) => {
|
|
151
|
-
if (px < minX) minX = px; if (py < minY) minY = py;
|
|
152
|
-
if (px > maxX) maxX = px; if (py > maxY) maxY = py;
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
accum(txx * x0 + tyx * y0 + tox, txy * x0 + tyy * y0 + toy);
|
|
156
|
-
accum(txx * x1 + tyx * y0 + tox, txy * x1 + tyy * y0 + toy);
|
|
157
|
-
accum(txx * x0 + tyx * y1 + tox, txy * x0 + tyy * y1 + toy);
|
|
158
|
-
accum(txx * x1 + tyx * y1 + tox, txy * x1 + tyy * y1 + toy);
|
|
159
|
-
|
|
160
|
-
return new DOMRect(
|
|
161
|
-
minX / scale, minY / scale,
|
|
162
|
-
(maxX - minX) / scale, (maxY - minY) / scale,
|
|
163
|
-
);
|
|
164
|
-
},
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
Object.defineProperty(Node3D.prototype, 'getBoundingClientRect', {
|
|
168
|
-
configurable: true,
|
|
169
|
-
value(this: Node3D): DOMRect | null {
|
|
170
|
-
if (typeof (this as any).getAabb !== 'function') return null;
|
|
171
|
-
|
|
172
|
-
const aabb = (this as any).getAabb();
|
|
173
|
-
const gt = this.globalTransform;
|
|
174
|
-
const viewport = this.getViewport();
|
|
175
|
-
if (!viewport) return null;
|
|
176
|
-
const camera = viewport.getCamera3d();
|
|
177
|
-
if (!camera) return null;
|
|
178
|
-
const scale = DisplayServer.screenGetScale();
|
|
179
|
-
|
|
180
|
-
const b = gt.basis;
|
|
181
|
-
const bx = b.x, by = b.y, bz = b.z;
|
|
182
|
-
const o = gt.origin;
|
|
183
|
-
const p = aabb.position, e = aabb.end;
|
|
184
|
-
|
|
185
|
-
const [px, py, pz] = [p.x, p.y, p.z];
|
|
186
|
-
const [ex, ey, ez] = [e.x, e.y, e.z];
|
|
187
|
-
const [bxx, bxy, bxz] = [bx.x, bx.y, bx.z];
|
|
188
|
-
const [byx, byy, byz] = [by.x, by.y, by.z];
|
|
189
|
-
const [bzx, bzy, bzz] = [bz.x, bz.y, bz.z];
|
|
190
|
-
const [ox, oy, oz] = [o.x, o.y, o.z];
|
|
191
|
-
|
|
192
|
-
const corners = [
|
|
193
|
-
[px, py, pz], [ex, py, pz], [px, ey, pz], [ex, ey, pz],
|
|
194
|
-
[px, py, ez], [ex, py, ez], [px, ey, ez], [ex, ey, ez],
|
|
195
|
-
];
|
|
196
|
-
|
|
197
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
198
|
-
|
|
199
|
-
for (const [cx, cy, cz] of corners) {
|
|
200
|
-
const wx = bxx * cx + byx * cy + bzx * cz + ox;
|
|
201
|
-
const wy = bxy * cx + byy * cy + bzy * cz + oy;
|
|
202
|
-
const wz = bxz * cx + byz * cy + bzz * cz + oz;
|
|
203
|
-
const s = camera.unprojectPosition(new Vector3(wx, wy, wz));
|
|
204
|
-
if (s.x < minX) minX = s.x; if (s.y < minY) minY = s.y;
|
|
205
|
-
if (s.x > maxX) maxX = s.x; if (s.y > maxY) maxY = s.y;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return new DOMRect(
|
|
209
|
-
minX / scale, minY / scale,
|
|
210
|
-
(maxX - minX) / scale, (maxY - minY) / scale,
|
|
211
|
-
);
|
|
212
|
-
},
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
(globalThis as any).$ = {
|
|
216
|
-
Engine, OS, ClassDB, Time,
|
|
217
|
-
dumpStr, dumpTreeStr, statsStr, gc,
|
|
218
|
-
};
|
|
219
|
-
}
|
|
1
|
+
/**********************************************************************
|
|
2
|
+
Copyright (c) Vladimir Davidovich. All rights reserved.
|
|
3
|
+
***********************************************************************/
|
|
4
|
+
|
|
5
|
+
import { Rect2, Vector3 } from './index.ts';
|
|
6
|
+
import { CanvasItem } from '../gen/classes/CanvasItem.ts';
|
|
7
|
+
import { ClassDB } from '../gen/classes/ClassDB.ts';
|
|
8
|
+
import { Control } from '../gen/classes/Control.ts';
|
|
9
|
+
import { DisplayServer } from '../gen/classes/DisplayServer.ts';
|
|
10
|
+
import { Engine } from '../gen/classes/Engine.ts';
|
|
11
|
+
import { Node } from '../gen/classes/Node.ts';
|
|
12
|
+
import { Node3D } from '../gen/classes/Node3D.ts';
|
|
13
|
+
import { OS } from '../gen/classes/OS.ts';
|
|
14
|
+
import { Time } from '../gen/classes/Time.ts';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Dump a Godot object's properties as an indented string.
|
|
18
|
+
* Uses `getPropertyList()` + `get()` to safely introspect — never invokes JS getters.
|
|
19
|
+
*
|
|
20
|
+
* @param obj - Any Godot wrapper (Node, Resource, etc.) or plain value
|
|
21
|
+
* @param depth - Max recursion depth into sub-objects (default 2)
|
|
22
|
+
* @param maxProps - Max properties to show per object (default 30)
|
|
23
|
+
*/
|
|
24
|
+
export function dumpStr(obj: unknown, depth = 2, maxProps = 30): string {
|
|
25
|
+
if (obj == null || typeof obj !== 'object') return String(obj);
|
|
26
|
+
let cls: string;
|
|
27
|
+
try { cls = (obj as any).getClass?.(); } catch { cls = (obj as any).constructor?.name ?? typeof obj; }
|
|
28
|
+
if (!cls) return String(obj);
|
|
29
|
+
const lines = [`[${cls}]`];
|
|
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
|
+
}
|
|
49
|
+
|
|
50
|
+
function indent(s: string, prefix: string): string { return s.split('\n').join('\n' + prefix); }
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Serialize a scene subtree as a tree-formatted string.
|
|
54
|
+
*
|
|
55
|
+
* @param node - Root node to start from
|
|
56
|
+
* @param depth - Max depth (default 99)
|
|
57
|
+
*/
|
|
58
|
+
export function dumpTreeStr(node: Node, depth = 99): string {
|
|
59
|
+
const lines: string[] = [];
|
|
60
|
+
(function walk(n: Node, d: number, ind: string) {
|
|
61
|
+
if (d <= 0) return;
|
|
62
|
+
lines.push(`${ind}${n.getClass()} "${n.name}" (${n.getChildCount()} children)`);
|
|
63
|
+
for (let i = 0; i < n.getChildCount(); i++) {
|
|
64
|
+
const c = n.getChild(i);
|
|
65
|
+
if (c) walk(c as Node, d - 1, ind + ' ');
|
|
66
|
+
}
|
|
67
|
+
})(node, depth, '');
|
|
68
|
+
return lines.join('\n');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Return a snapshot of engine runtime stats as a formatted string.
|
|
73
|
+
*
|
|
74
|
+
* Includes process frames, FPS, physics frames, node count, time scale,
|
|
75
|
+
* and main-loop class.
|
|
76
|
+
*/
|
|
77
|
+
export function statsStr(): string {
|
|
78
|
+
const tree = Engine.getMainLoop() as any;
|
|
79
|
+
return [
|
|
80
|
+
`Frames: ${Engine.getProcessFrames()}`,
|
|
81
|
+
`FPS: ${Engine.getFramesPerSecond()}`,
|
|
82
|
+
`Physics: ${Engine.getPhysicsFrames()}`,
|
|
83
|
+
`Nodes: ${tree.getNodeCount()}`,
|
|
84
|
+
`Time scale: ${Engine.timeScale}`,
|
|
85
|
+
`Main loop: ${Engine.getMainLoop().getClass()}`,
|
|
86
|
+
].join('\n');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Run `gc()` and report how much heap was freed.
|
|
91
|
+
* Requires the `--expose-gc` Node.js flag.
|
|
92
|
+
*/
|
|
93
|
+
export function gc(): string {
|
|
94
|
+
const before = process.memoryUsage().heapUsed;
|
|
95
|
+
if (typeof Bun !== 'undefined')
|
|
96
|
+
Bun.gc(true);
|
|
97
|
+
else if (globalThis.gc)
|
|
98
|
+
globalThis.gc({ type: 'major', execution: 'sync' });
|
|
99
|
+
else
|
|
100
|
+
return 'gc() unavailable — need --expose-gc';
|
|
101
|
+
|
|
102
|
+
const after = process.memoryUsage().heapUsed;
|
|
103
|
+
return `GC: freed ${((before - after) / 1024).toFixed(0)} KB`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Bootstrap the debugging environment.
|
|
108
|
+
*
|
|
109
|
+
* - Exposes common singletons (`Engine`, `OS`, `ClassDB`, `Time`) and all
|
|
110
|
+
* debug utility functions on `globalThis.$` so they're reachable from
|
|
111
|
+
* breakpoints in any module.
|
|
112
|
+
* - Registers an `uncaughtException` handler that logs but keeps the process
|
|
113
|
+
* alive for interactive debugging.
|
|
114
|
+
*/
|
|
115
|
+
export function initDebug(): void {
|
|
116
|
+
globalThis.process?.on('uncaughtException', (err, origin) => {
|
|
117
|
+
console.error(`\n✗ ${origin}:`, err.stack);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
Object.defineProperty(CanvasItem.prototype, 'getBoundingClientRect', {
|
|
121
|
+
configurable: true,
|
|
122
|
+
value(this: CanvasItem): DOMRect | null {
|
|
123
|
+
const scale = DisplayServer.screenGetScale();
|
|
124
|
+
if (this instanceof Control) {
|
|
125
|
+
const rc = (this as Control).getGlobalRect();
|
|
126
|
+
return new DOMRect(
|
|
127
|
+
rc.position.x / scale, rc.position.y / scale,
|
|
128
|
+
rc.size.x / scale, rc.size.y / scale,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (typeof (this as any).getRect !== 'function') return null;
|
|
133
|
+
|
|
134
|
+
const rect = (this as any).getRect() as Rect2;
|
|
135
|
+
const t = this.getScreenTransform();
|
|
136
|
+
|
|
137
|
+
const pos = rect.position;
|
|
138
|
+
const sz = rect.size;
|
|
139
|
+
const tx = t.x;
|
|
140
|
+
const ty = t.y;
|
|
141
|
+
const to = t.origin;
|
|
142
|
+
|
|
143
|
+
const x0 = pos.x, y0 = pos.y;
|
|
144
|
+
const x1 = x0 + sz.x, y1 = y0 + sz.y;
|
|
145
|
+
const txx = tx.x, txy = tx.y;
|
|
146
|
+
const tyx = ty.x, tyy = ty.y;
|
|
147
|
+
const tox = to.x, toy = to.y;
|
|
148
|
+
|
|
149
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
150
|
+
const accum = (px: number, py: number) => {
|
|
151
|
+
if (px < minX) minX = px; if (py < minY) minY = py;
|
|
152
|
+
if (px > maxX) maxX = px; if (py > maxY) maxY = py;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
accum(txx * x0 + tyx * y0 + tox, txy * x0 + tyy * y0 + toy);
|
|
156
|
+
accum(txx * x1 + tyx * y0 + tox, txy * x1 + tyy * y0 + toy);
|
|
157
|
+
accum(txx * x0 + tyx * y1 + tox, txy * x0 + tyy * y1 + toy);
|
|
158
|
+
accum(txx * x1 + tyx * y1 + tox, txy * x1 + tyy * y1 + toy);
|
|
159
|
+
|
|
160
|
+
return new DOMRect(
|
|
161
|
+
minX / scale, minY / scale,
|
|
162
|
+
(maxX - minX) / scale, (maxY - minY) / scale,
|
|
163
|
+
);
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
Object.defineProperty(Node3D.prototype, 'getBoundingClientRect', {
|
|
168
|
+
configurable: true,
|
|
169
|
+
value(this: Node3D): DOMRect | null {
|
|
170
|
+
if (typeof (this as any).getAabb !== 'function') return null;
|
|
171
|
+
|
|
172
|
+
const aabb = (this as any).getAabb();
|
|
173
|
+
const gt = this.globalTransform;
|
|
174
|
+
const viewport = this.getViewport();
|
|
175
|
+
if (!viewport) return null;
|
|
176
|
+
const camera = viewport.getCamera3d();
|
|
177
|
+
if (!camera) return null;
|
|
178
|
+
const scale = DisplayServer.screenGetScale();
|
|
179
|
+
|
|
180
|
+
const b = gt.basis;
|
|
181
|
+
const bx = b.x, by = b.y, bz = b.z;
|
|
182
|
+
const o = gt.origin;
|
|
183
|
+
const p = aabb.position, e = aabb.end;
|
|
184
|
+
|
|
185
|
+
const [px, py, pz] = [p.x, p.y, p.z];
|
|
186
|
+
const [ex, ey, ez] = [e.x, e.y, e.z];
|
|
187
|
+
const [bxx, bxy, bxz] = [bx.x, bx.y, bx.z];
|
|
188
|
+
const [byx, byy, byz] = [by.x, by.y, by.z];
|
|
189
|
+
const [bzx, bzy, bzz] = [bz.x, bz.y, bz.z];
|
|
190
|
+
const [ox, oy, oz] = [o.x, o.y, o.z];
|
|
191
|
+
|
|
192
|
+
const corners = [
|
|
193
|
+
[px, py, pz], [ex, py, pz], [px, ey, pz], [ex, ey, pz],
|
|
194
|
+
[px, py, ez], [ex, py, ez], [px, ey, ez], [ex, ey, ez],
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
198
|
+
|
|
199
|
+
for (const [cx, cy, cz] of corners) {
|
|
200
|
+
const wx = bxx * cx + byx * cy + bzx * cz + ox;
|
|
201
|
+
const wy = bxy * cx + byy * cy + bzy * cz + oy;
|
|
202
|
+
const wz = bxz * cx + byz * cy + bzz * cz + oz;
|
|
203
|
+
const s = camera.unprojectPosition(new Vector3(wx, wy, wz));
|
|
204
|
+
if (s.x < minX) minX = s.x; if (s.y < minY) minY = s.y;
|
|
205
|
+
if (s.x > maxX) maxX = s.x; if (s.y > maxY) maxY = s.y;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return new DOMRect(
|
|
209
|
+
minX / scale, minY / scale,
|
|
210
|
+
(maxX - minX) / scale, (maxY - minY) / scale,
|
|
211
|
+
);
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
(globalThis as any).$ = {
|
|
216
|
+
Engine, OS, ClassDB, Time,
|
|
217
|
+
dumpStr, dumpTreeStr, statsStr, gc,
|
|
218
|
+
};
|
|
219
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
/**********************************************************************
|
|
2
|
-
Copyright (c) Vladimir Davidovich. All rights reserved.
|
|
3
|
-
***********************************************************************/
|
|
4
|
-
|
|
5
|
-
export * from '../gen/index.ts';
|
|
6
|
-
import { Engine } from '../gen/classes/Engine.ts';
|
|
7
|
-
import { GodotInstance } from '../gen/classes/GodotInstance.ts';
|
|
8
|
-
import { SceneTree } from '../gen/classes/SceneTree.ts';
|
|
9
|
-
import { Window } from '../gen/classes/Window.ts';
|
|
10
|
-
import { gc } from './debug.ts';
|
|
11
|
-
import { cancelAnimationFrame, getGodot, requestAnimationFrame } from './runtime.ts';
|
|
12
|
-
|
|
13
|
-
// ---- make sure these classes are not tree-shaked ----
|
|
14
|
-
void GodotInstance;
|
|
15
|
-
void SceneTree;
|
|
16
|
-
void Window;
|
|
17
|
-
|
|
18
|
-
// ---- globalThis browser-compat API ----
|
|
19
|
-
Object.assign(globalThis as any, { requestAnimationFrame, cancelAnimationFrame });
|
|
20
|
-
|
|
21
|
-
// ---- GodotInstance frame pump (engine already started by C++ InitModule) ----
|
|
22
|
-
export async function runGodot(signal?: AbortSignal, unmount?: () => PromiseLike<void>) {
|
|
23
|
-
signal?.throwIfAborted();
|
|
24
|
-
signal?.addEventListener('abort', () => {
|
|
25
|
-
(Engine.getMainLoop() as SceneTree).quit();
|
|
26
|
-
}, { once: true });
|
|
27
|
-
|
|
28
|
-
const godot = getGodot();
|
|
29
|
-
while (!godot.iteration())
|
|
30
|
-
await new Promise(requestAnimationFrame);
|
|
31
|
-
|
|
32
|
-
await unmount?.();
|
|
33
|
-
console.log(gc());
|
|
34
|
-
return godot.free();
|
|
35
|
-
}
|
|
36
|
-
|
|
1
|
+
/**********************************************************************
|
|
2
|
+
Copyright (c) Vladimir Davidovich. All rights reserved.
|
|
3
|
+
***********************************************************************/
|
|
4
|
+
|
|
5
|
+
export * from '../gen/index.ts';
|
|
6
|
+
import { Engine } from '../gen/classes/Engine.ts';
|
|
7
|
+
import { GodotInstance } from '../gen/classes/GodotInstance.ts';
|
|
8
|
+
import { SceneTree } from '../gen/classes/SceneTree.ts';
|
|
9
|
+
import { Window } from '../gen/classes/Window.ts';
|
|
10
|
+
import { gc } from './debug.ts';
|
|
11
|
+
import { cancelAnimationFrame, getGodot, requestAnimationFrame } from './runtime.ts';
|
|
12
|
+
|
|
13
|
+
// ---- make sure these classes are not tree-shaked ----
|
|
14
|
+
void GodotInstance;
|
|
15
|
+
void SceneTree;
|
|
16
|
+
void Window;
|
|
17
|
+
|
|
18
|
+
// ---- globalThis browser-compat API ----
|
|
19
|
+
Object.assign(globalThis as any, { requestAnimationFrame, cancelAnimationFrame });
|
|
20
|
+
|
|
21
|
+
// ---- GodotInstance frame pump (engine already started by C++ InitModule) ----
|
|
22
|
+
export async function runGodot(signal?: AbortSignal, unmount?: () => PromiseLike<void>) {
|
|
23
|
+
signal?.throwIfAborted();
|
|
24
|
+
signal?.addEventListener('abort', () => {
|
|
25
|
+
(Engine.getMainLoop() as SceneTree).quit();
|
|
26
|
+
}, { once: true });
|
|
27
|
+
|
|
28
|
+
const godot = getGodot();
|
|
29
|
+
while (!godot.iteration())
|
|
30
|
+
await new Promise(requestAnimationFrame);
|
|
31
|
+
|
|
32
|
+
await unmount?.();
|
|
33
|
+
console.log(gc());
|
|
34
|
+
return godot.free();
|
|
35
|
+
}
|
|
36
|
+
|
package/src/runtime.ts
CHANGED
|
@@ -1,69 +1,68 @@
|
|
|
1
|
-
/**********************************************************************
|
|
2
|
-
Copyright (c) Vladimir Davidovich. All rights reserved.
|
|
3
|
-
***********************************************************************/
|
|
4
|
-
|
|
5
|
-
const {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
* - _C(
|
|
28
|
-
* - _C(
|
|
29
|
-
* - _C(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
1
|
+
/**********************************************************************
|
|
2
|
+
Copyright (c) Vladimir Davidovich. All rights reserved.
|
|
3
|
+
***********************************************************************/
|
|
4
|
+
|
|
5
|
+
const { platform, arch } = globalThis.process ?? {};
|
|
6
|
+
const mapping: any = {
|
|
7
|
+
'darwin-arm64': 'macos-arm64',
|
|
8
|
+
'win32-x64': 'windows-x86_64',
|
|
9
|
+
};
|
|
10
|
+
const suffix = mapping[`${platform}-${arch}`];
|
|
11
|
+
const { default: _mod } = await (suffix ? import(`@ringozz/godot-${suffix}`) : import('@ringozz/godot-web-wasm32'));
|
|
12
|
+
|
|
13
|
+
/** Godot instance */
|
|
14
|
+
import type { GodotInstance } from '../gen/classes/GodotInstance.ts';
|
|
15
|
+
export const getGodot = _mod.getGodot as () => GodotInstance;
|
|
16
|
+
|
|
17
|
+
/** Base constructor for all Godot object wrappers. */
|
|
18
|
+
export interface GodotVar {
|
|
19
|
+
free(): void;
|
|
20
|
+
}
|
|
21
|
+
export const GodotVar = _mod.GodotVar as (new (...args: unknown[]) => GodotVar) & {
|
|
22
|
+
assign<T extends GodotVar>(target: T, props: Record<string, unknown>): T;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Unified entry point:
|
|
26
|
+
* - _C(this, methodId, ...args) → instance method
|
|
27
|
+
* - _C(classId, methodId, ...args) → static method
|
|
28
|
+
* - _C(null, methodId, ...args) → singleton lookup (_getInstance) or internal
|
|
29
|
+
* - _C(0xHASHn, methodId, ...args) → utility function (BigInt carries hash + type metadata)
|
|
30
|
+
*/
|
|
31
|
+
export const _C = _mod._C as (...args: unknown[]) => unknown;
|
|
32
|
+
|
|
33
|
+
/** Resolve a string name to its integer handle. */
|
|
34
|
+
export const _S = _mod._S as (name: string) => number;
|
|
35
|
+
|
|
36
|
+
/** Low-level property getter for value type fields. */
|
|
37
|
+
export const _get = _mod._get as (target: GodotVar, nameId: number) => unknown;
|
|
38
|
+
|
|
39
|
+
/** Low-level property setter for value type fields. */
|
|
40
|
+
export const _set = _mod._set as (target: GodotVar, nameId: number, val: unknown) => void;
|
|
41
|
+
|
|
42
|
+
/** Register Godot object wrapper. */
|
|
43
|
+
export const _R = _mod._R as (typeId: number, ctor: Function) => string;
|
|
44
|
+
|
|
45
|
+
/** Get a Signal value from an Object by signal name, or connect a callback when 3rd arg given. */
|
|
46
|
+
export const _G = _mod._G as (target: GodotVar, signalNameId: number, callback?: unknown) => unknown;
|
|
47
|
+
|
|
48
|
+
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/DedicatedWorkerGlobalScope/requestAnimationFrame) */
|
|
49
|
+
export const requestAnimationFrame = _mod.requestAnimationFrame as typeof globalThis.requestAnimationFrame ?? globalThis.requestAnimationFrame;
|
|
50
|
+
|
|
51
|
+
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/DedicatedWorkerGlobalScope/cancelAnimationFrame) */
|
|
52
|
+
export const cancelAnimationFrame = _mod.cancelAnimationFrame as typeof globalThis.cancelAnimationFrame ?? globalThis.cancelAnimationFrame;
|
|
53
|
+
|
|
54
|
+
/** Make a Signal PromiseLike: connects a one-shot callback that resolves with the args tuple. */
|
|
55
|
+
export function _P(signal: any, onfulfilled?: any, onrejected?: any) {
|
|
56
|
+
return new Promise<any>((resolve, reject) => {
|
|
57
|
+
if (signal.isNull()) {
|
|
58
|
+
reject(new Error('Cannot await a null Signal'));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const cb = (...args: any[]) => {
|
|
62
|
+
signal.disconnect(cb);
|
|
63
|
+
resolve(args);
|
|
64
|
+
};
|
|
65
|
+
const res = signal.connect(cb);
|
|
66
|
+
if (res !== 0) reject(new Error('Failed to connect signal: error code ' + res));
|
|
67
|
+
}).then(onfulfilled, onrejected);
|
|
68
|
+
}
|