@ringozz/godot 4.7.1-5 → 4.7.1-560

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 CHANGED
@@ -1,62 +1,128 @@
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 (if a `project.godot` is present at cwd, it is loaded and `res://` still maps to cwd).
14
+
15
+ ## Usage
16
+
17
+ On **desktop**, 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
+ ### Web
35
+
36
+ The wasm engine boots asynchronously, so on web the entry script must boot it **before** importing the app: `@ringozz/godot/boot`'s `preloadGodot()` starts the engine, and `@ringozz/godot/runtime` then reads the booted module synchronously (the package's `exports` map routes `./boot` to the web leaf in browser-targeted bundles). This keeps the shared module graph top-level-await free, which is also what makes Bun's HMR dev server work. On desktop `preloadGodot()` is a no-op — the engine boots at import.
37
+
38
+ ```ts
39
+ // your web entry (referenced by <script type="module"> in the HTML)
40
+ import { preloadGodot } from '@ringozz/godot/boot';
41
+ await preloadGodot();
42
+ await import('./app.ts'); // may now import @ringozz/godot / react-godot freely
43
+ ```
44
+
45
+ (That's exactly what `dev/demo.html.ts` does in this repo. A second `<script type="module">` tag can't replace the dynamic import — Bun's fullstack HTML bundler merges all script entries into one chunk, so the browser's in-order script/TLA serialization never applies.)
46
+
47
+ ### What's exported where
48
+
49
+ | Specifier | Contents |
50
+ |---|---|
51
+ | `@ringozz/godot` | Value types (`Vector3`, `Color`, …), global enums, heap types, utility functions, global constants, `runGodot()`, RAF polyfills |
52
+ | `@ringozz/godot/ClassName` | Classes, one per module — e.g. `@ringozz/godot/Label`, `@ringozz/godot/Node` |
53
+ | `@ringozz/godot/runtime` | Low-level dispatch: `_C`, `_S`, `_get`, `_set`, `_R`, `_G`, `_P`, `getGodot`, `GodotVar` |
54
+ | `@ringozz/godot/boot` | Platform boot: `getNativeModule()`; web `preloadGodot()` (awaited before importing the app; no-op on desktop) |
55
+ | `@ringozz/godot/load` | Async resource loading: `loadResourceAsync` |
56
+ | `@ringozz/godot/debug` | `initDebug()`, `dumpStr`, `snapshot`, `inspect`, `click`, `find`, `gc` |
57
+
58
+ Class-scoped enums use bare names (import `ProcessMode` from `@ringozz/godot/Node`, not `NodeProcessMode`).
59
+
60
+ ### Async resource loading
61
+
62
+ The engine loads pre-baked native resources (`.scn`, `.ctex`, …) through `ResourceLoader`. On desktop these are real files (`res://` = cwd); on web there is no filesystem, so the asset's bundled files — `.import` sidecars as text and imported products (`.scn`/`.ctex`) as emitted asset paths — are staged into Emscripten's in-memory MEMFS (transient, no IndexedDB, no persistent copy) by the generated asset modules, handing the fetched bytes straight to MEMFS via Godot's own `copyToFS` (`FS.createDataFile` with `canOwn` zero copy, JS-heap only). On web, `.ctex` files are decoded first: the embedded PNG/WebP blobs go through the browser's `createImageBitmap` and the container is rebuilt as raw RGBA (`DATA_FORMAT_IMAGE`), so no image codec runs in wasm. The source file itself is never fetched.
63
+
64
+ ```ts
65
+ import { loadResourceAsync } from '@ringozz/godot/load';
66
+ import { PackedScene } from '@ringozz/godot/PackedScene';
67
+ import { Texture2D } from '@ringozz/godot/Texture2D';
68
+
69
+ const scene = await loadResourceAsync('res://models/foo.gltf', PackedScene); // PackedScene
70
+ const node = scene.instantiate();
71
+ const tex = await loadResourceAsync('res://textures/icon.jpg', Texture2D); // Texture2D
72
+ ```
73
+
74
+ - `loadResourceAsync(path, cls, files?)` — generic over the Godot class: `cls.name` is the `ResourceLoader` type hint, and the return type is `InstanceType<typeof cls>`. Loads in the background (`loadThreadedRequest` + status polling). Godot default arguments apply: the napi dispatch truncates trailing `undefined` args, so `loadThreadedRequest` uses its default `CACHE_MODE_REUSE` and the resource stays cached in the engine (scene re-references don't re-read the file). On web it only works for paths already staged into MEMFS (generated asset modules handle this). When the asset's `files` map is passed, the staged product files (`.ctex`/`.scn`/native sources) are deleted from MEMFS after the resource loads — only the `.import` sidecars are kept, since they route imported source paths to their products. The decoded texture bytes don't linger in the JS heap.
75
+ - `loadAsset(path, cls, files, deps, data)` — the generated asset modules' entry point (also usable directly): returns `{ materialize, load }`. It checks the engine's `ResourceCache` first (when the asset is already loaded, `load` resolves to the cached instance and staging is skipped), stages the bundled files via Godot's `copyToFS` (zero-copy handover of the fetched bytes; a no-op on desktop where the files already exist), loads via `loadResourceAsync`, and memoizes `load` on `data` (pass `import.meta.hot.data`) so HMR re-evaluations reuse the same fulfilled promise.
76
+
77
+ Asset imports are resolved by `@ringozz/godot/preload` (a Bun plugin, registered via `bunfig.toml` preload for `bun run`/`bun test` and via `[serve.static] plugins` for `Bun.serve`'s fullstack HTML routes; the module's default export is the single plugin object) into a module per asset. Only **JS imports** are virtualized — HTML asset references (`<link rel="icon">`, `<img src>`) and CSS `url()` refs fall through to Bun's normal (content-hashed) asset handling, so favicons and other web assets co-exist with Godot imports:
78
+
79
+ ```ts
80
+ import foo from './models/foo.gltf';
81
+ const scene = await foo; // PackedScene
82
+ ```
83
+
84
+ Each asset module's default export is its load **promise** (`const { materialize, load } = loadAsset(resPath, Class, files, deps, import.meta.hot.data)` — the default is `load`, memoized on the module's HMR data), started eagerly at module import, and it also exports a `materialize` promise (its own files plus its deps' files). It statically imports its own files Godot reads — `.import` sidecars and native text sources (`tscn`/`tres`/`po`) via the `text` loader, imported products (`.scn`/`.ctex`) via the `file` loader — and imports its **dependencies as generated modules too**, waiting only for their `materialize` before loading itself (dep loads start at module evaluation and run concurrently). Text-based source assets (`.gltf`/`.tscn`/`.tres`/`.obj`) are scanned for references (`res://` paths and relative paths ending in known asset extensions); referenced source assets become dep modules, raw `.mtl` files (OBJ materials) are recursed into, and raw `.bin` buffers are import-time only. The modules are virtual (namespace `godot`), and every import of the same asset resolves to the same one (evaluated once) — so the promise is a stable singleton, read with `await` or React 19's `use()`:
85
+
86
+ ```tsx
87
+ import foo from './models/foo.gltf';
88
+ import { Suspense, use } from 'react';
89
+
90
+ function Foo() {
91
+ const scene = use(foo); // suspends until the scene loads
92
+ return <FooView scene={scene} />;
93
+ }
94
+
95
+ <Suspense fallback={<Label text="Loading…" />}>
96
+ <Foo />
97
+ </Suspense>
98
+ ```
99
+
100
+ Each extension maps to its Godot class (source assets only — `gltf`/`tscn`/`obj` → `PackedScene`, `jpg`/`jpeg`/`png`/`webp` → `CompressedTexture2D`, `svg` → `Texture2D`, `exr`/`hdr` → `TextureLayered`, `wav` → `AudioStreamWAV`, `ogg` → `AudioStreamOggVorbis`, `mp3` → `AudioStreamMP3`, `tres` → `Resource`, `po` → `Translation`), declared in `src/assets.d.ts`. Imported products (`.scn`/`.ctex`) are never imported directly.
101
+
102
+ Textures are shared only through Godot's `ResourceLoader` path cache. The glTF import pipeline may produce per-scene texture instances (same `res://` path, separate objects), so don't assume two scenes referencing the same image share one texture object.
103
+
104
+ ### Debugging
105
+
106
+ ```ts
107
+ import { initDebug } from '@ringozz/godot/debug';
108
+ initDebug();
109
+ // singletons + helpers now on globalThis.$: $.Engine, $.tree, $.dumpStr(node), $.snapshot([pattern]), $.inspect(path), $.click(path), $.find([pattern], [type]), $.paused, $.gc()
110
+ ```
111
+
112
+ - `dumpStr(obj)` renders value types inline (`(1.0, 2.0, 3.0)`, arrays, dictionaries) via Godot's `str()` and dumps objects via Godot's `var_to_str`, with the node's `name` prepended.
113
+ - `snapshot(pattern?, node?)` dumps the scene tree from the root `Window` as `Class "name" (N children)` lines; pass a string (case-insensitive substring) or `RegExp` to filter. `$.snapshot()` with no arg dumps the whole tree.
114
+ - `inspect(path)` resolves a node by absolute NodePath (`/root/Box`) or name/glob pattern (`findChild`) and returns its `dumpStr`.
115
+ - `click(path)` synthesizes a left-click at the center of a `Control` via `Window.pushInput` (device-space coordinates), returning the node's `dumpStr` or `null` if it isn't a `Control`.
116
+ - `find(pattern?, type?)` returns a JS array of nodes matching a name glob and/or class (`$.find('*', 'RigidBody3D')` lists all physics bodies).
117
+ - `$.tree` is the live `SceneTree`; `$.paused = true` freezes the scene tree for inspection; `$.paused = false` resumes.
118
+
119
+ `initDebug` also registers an `uncaughtException` handler that keeps the process alive, polyfills `DOMRect` (not defined in Bun, needed by `getBoundingClientRect()`), and adds `getBoundingClientRect()` to `CanvasItem`/`Node3D`.
120
+
121
+ ## Notes
122
+
123
+ - **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).
124
+ - **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;`).
125
+
126
+ ## Development
127
+
128
+ `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.
@@ -729,7 +729,7 @@ export class DisplayServer extends GodotVar {
729
729
  static screenGetDpi(screen?: number /* = -1 */): number { return _C($(), 10248, screen) as number; }
730
730
 
731
731
  /**
732
- * Returns the scale factor of the specified screen by index. Returns `1.0` if `screen` is invalid.
732
+ * Returns the scale factor of the specified screen by index. Returns `1.0` if `screen` is invalid. See also {@link screenGetMaxScale}.
733
733
  * **Note:** One of the following constants can be used as `screen`: {@link SCREEN_OF_MAIN_WINDOW}, {@link SCREEN_PRIMARY}, {@link SCREEN_WITH_MOUSE_FOCUS}, or {@link SCREEN_WITH_KEYBOARD_FOCUS}.
734
734
  * **Note:** On macOS, the returned value is `2.0` for hiDPI (Retina) screens, and `1.0` for all other cases.
735
735
  * **Note:** On Linux (Wayland), the returned value is accurate only when `screen` is {@link SCREEN_OF_MAIN_WINDOW}. Due to API limitations, passing a direct index will return a rounded-up integer, if the screen has a fractional scale (e.g. `1.25` would get rounded up to `2.0`).
@@ -743,9 +743,8 @@ export class DisplayServer extends GodotVar {
743
743
  static isTouchscreenAvailable(): boolean { return _C($(), 8369) as boolean; }
744
744
 
745
745
  /**
746
- * Returns the greatest scale factor of all screens.
747
- * **Note:** On macOS returned value is `2.0` if there is at least one hiDPI (Retina) screen in the system, and `1.0` in all other cases.
748
- * **Note:** This method is implemented only on macOS.
746
+ * Returns the greatest scale factor of all screens. See also {@link screenGetScale}.
747
+ * **Note:** On macOS, the returned value is `2.0` if there is at least one hiDPI (Retina) screen in the system, and `1.0` in all other cases.
749
748
  */
750
749
  static screenGetMaxScale(): number { return _C($(), 10253) as number; }
751
750
 
@@ -296,12 +296,14 @@ export const BakeMode = {
296
296
  */
297
297
  BAKE_DISABLED: 0,
298
298
  /**
299
- * Light is taken into account in static baking ({@link VoxelGI}, {@link LightmapGI}, SDFGI ({@link Environment.sdfgiEnabled})). The light can be moved around or modified, but its global illumination will not update in real-time. This is suitable for subtle changes (such as flickering torches), but generally not large changes such as toggling a light on and off.
299
+ * Light is taken into account in static baking ({@link VoxelGI}, {@link LightmapGI}, SDFGI ({@link Environment.sdfgiEnabled})). The light can be moved around or modified, but its global illumination will not update in real-time.
300
300
  * **Note:** The light is not baked in {@link LightmapGI} if {@link editorOnly} is `true`.
301
+ * **Note:** When using {@link LightmapGI}, both the direct and indirect light are baked. Since direct light is baked, the light doesn't display a specular lobe on static lightmapped meshes. Shadows on static lightmapped meshes will also look less detailed, but the light still casts shadows that can be displayed on dynamic objects. Since real-time light computations are skipped on static lightmapped meshes, this bake mode improves runtime performance compared to {@link BAKE_DYNAMIC} and {@link BAKE_DISABLED}.
301
302
  */
302
303
  BAKE_STATIC: 1,
303
304
  /**
304
- * Light is taken into account in dynamic baking ({@link VoxelGI} and SDFGI ({@link Environment.sdfgiEnabled}) only). The light can be moved around or modified with global illumination updating in real-time. The light's global illumination appearance will be slightly different compared to {@link BAKE_STATIC}. This has a greater performance cost compared to {@link BAKE_STATIC}. When using SDFGI, the update speed of dynamic lights is affected by {@link ProjectSettings.rendering/globalIllumination/sdfgi/framesToUpdateLights}.
305
+ * Light is taken into account in dynamic baking ({@link VoxelGI} and SDFGI ({@link Environment.sdfgiEnabled})). The light can be moved around or modified with global illumination updating in real-time. The light's global illumination appearance will be slightly different compared to {@link BAKE_STATIC}. This has a greater performance cost compared to {@link BAKE_STATIC}. When using SDFGI, the update speed of dynamic lights is affected by {@link ProjectSettings.rendering/globalIllumination/sdfgi/framesToUpdateLights}.
306
+ * **Note:** When using {@link LightmapGI}, the light's indirect light is baked, but direct light and shadows remain real-time. This mode allows performing *subtle* changes to a light's color, energy, and position while still looking fairly correct. For example, you can use this to create flickering static torches that have their indirect light baked.
305
307
  */
306
308
  BAKE_DYNAMIC: 2,
307
309
  } as const;
@@ -48,9 +48,9 @@ export class RandomNumberGenerator extends RefCounted {
48
48
  randiRange(from: number, to: number): number { return _C(this, 9725, from, to) as number; }
49
49
 
50
50
  /**
51
- * Returns a random integer between `0` and the size of the array that is passed as a parameter. Each value in the array should be a floating-point number that represents the relative likelihood that it will be returned as an index. A higher value means the value is more likely to be returned as an index, while a value of `0` means it will never be returned as an index.
51
+ * Returns a random integer between `0` and the size of the array that is passed as a parameter. Each value in the array should be a non-negative floating-point number that represents the relative likelihood that it will be returned as an index. A higher value means the value is more likely to be returned as an index, while a value of `0` means it will never be returned as an index.
52
52
  * For example, if {@link code skip-lint}{@link 0.5, 1, 1, 2} is passed as a parameter, then the method is twice as likely to return `3` (the index of the value `2`) and twice as unlikely to return `0` (the index of the value `0.5`) compared to the indices `1` and `2`.
53
- * Prints an error and returns `-1` if the array is empty.
53
+ * Prints an error and returns `-1` if the array is empty or contains any negative values.
54
54
  *
55
55
  *
56
56
  * ```gdscript
package/package.json CHANGED
@@ -1,15 +1,27 @@
1
1
  {
2
2
  "name": "@ringozz/godot",
3
3
  "author": "Vladimir Davidovich",
4
- "version": "4.7.1-5",
4
+ "version": "4.7.1-560",
5
5
  "description": "Node-API bindings for Godot Engine — call Godot classes from JavaScript",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "keywords": [
10
+ "godot"
11
+ ],
6
12
  "type": "module",
7
13
  "main": "./src/index.ts",
8
14
  "types": "./gen/index.ts",
9
15
  "exports": {
10
16
  ".": "./src/index.ts",
11
17
  "./runtime": "./src/runtime.ts",
18
+ "./boot": {
19
+ "browser": "./src/boot.browser.ts",
20
+ "default": "./src/boot.ts"
21
+ },
22
+ "./load": "./src/load.ts",
12
23
  "./debug": "./src/debug.ts",
24
+ "./preload": "./src/preload.ts",
13
25
  "./*": "./gen/classes/*.ts"
14
26
  },
15
27
  "files": [
@@ -20,18 +32,18 @@
20
32
  "godot": {
21
33
  "version_major": 4,
22
34
  "version_minor": 7,
23
- "version_patch": 1,
35
+ "version_patch": 2,
24
36
  "version_status": "stable",
25
- "version_build": "official",
26
- "version_full_name": "Godot Engine v4.7.1.stable.official",
37
+ "version_build": "custom_build",
38
+ "version_full_name": "Godot Engine v4.7.2.stable.custom_build",
27
39
  "precision": "single"
28
40
  },
29
41
  "optionalDependencies": {
30
- "@ringozz/godot-macos-arm64": "^4.7.1-2",
31
- "@ringozz/godot-windows-x86_64": "^4.7.1-2",
32
- "@ringozz/godot-web-wasm32": "^4.7.1-3"
42
+ "@ringozz/godot-macos-arm64": "^4.7.1-560",
43
+ "@ringozz/godot-windows-x86_64": "^4.7.1-560",
44
+ "@ringozz/godot-web-wasm32": "^4.7.1-560"
33
45
  },
34
46
  "peerDependencies": {
35
- "@types/node": "*"
47
+ "@types/bun": "*"
36
48
  }
37
49
  }
@@ -0,0 +1,116 @@
1
+ /**********************************************************************
2
+ Copyright (c) Vladimir Davidovich. All rights reserved.
3
+ ***********************************************************************/
4
+
5
+ // Ambient types for Godot asset imports, resolved by `@ringozz/godot/preload`
6
+ // into modules that export a `materialize` promise (files staged into MEMFS) and
7
+ // default to the load promise (`const { materialize, load } = loadAsset(path,
8
+ // Class, files, deps, import.meta.hot.data)` — `load` memoized on the module's
9
+ // HMR data, so re-evaluations return the same fulfilled promise). The default is
10
+ // a stable module singleton, so it can be passed to React's `use()` under
11
+ // `<Suspense>`.
12
+
13
+ declare module '*.tres' {
14
+ import type { Resource } from '@ringozz/godot/Resource';
15
+ const asset: Promise<Resource>;
16
+ export default asset;
17
+ export const materialize: Promise<unknown>;
18
+ }
19
+
20
+ declare module '*.po' {
21
+ import type { Translation } from '@ringozz/godot/Translation';
22
+ const asset: Promise<Translation>;
23
+ export default asset;
24
+ export const materialize: Promise<unknown>;
25
+ }
26
+
27
+ declare module '*.gltf' {
28
+ import type { PackedScene } from '@ringozz/godot/PackedScene';
29
+ const asset: Promise<PackedScene>;
30
+ export default asset;
31
+ export const materialize: Promise<unknown>;
32
+ }
33
+
34
+ declare module '*.tscn' {
35
+ import type { PackedScene } from '@ringozz/godot/PackedScene';
36
+ const asset: Promise<PackedScene>;
37
+ export default asset;
38
+ export const materialize: Promise<unknown>;
39
+ }
40
+
41
+ declare module '*.obj' {
42
+ import type { PackedScene } from '@ringozz/godot/PackedScene';
43
+ const asset: Promise<PackedScene>;
44
+ export default asset;
45
+ export const materialize: Promise<unknown>;
46
+ }
47
+
48
+ declare module '*.jpg' {
49
+ import type { CompressedTexture2D } from '@ringozz/godot/CompressedTexture2D';
50
+ const asset: Promise<CompressedTexture2D>;
51
+ export default asset;
52
+ export const materialize: Promise<unknown>;
53
+ }
54
+
55
+ declare module '*.jpeg' {
56
+ import type { CompressedTexture2D } from '@ringozz/godot/CompressedTexture2D';
57
+ const asset: Promise<CompressedTexture2D>;
58
+ export default asset;
59
+ export const materialize: Promise<unknown>;
60
+ }
61
+
62
+ declare module '*.png' {
63
+ import type { CompressedTexture2D } from '@ringozz/godot/CompressedTexture2D';
64
+ const asset: Promise<CompressedTexture2D>;
65
+ export default asset;
66
+ export const materialize: Promise<unknown>;
67
+ }
68
+
69
+ declare module '*.webp' {
70
+ import type { CompressedTexture2D } from '@ringozz/godot/CompressedTexture2D';
71
+ const asset: Promise<CompressedTexture2D>;
72
+ export default asset;
73
+ export const materialize: Promise<unknown>;
74
+ }
75
+
76
+ declare module '*.svg' {
77
+ import type { Texture2D } from '@ringozz/godot/Texture2D';
78
+ const asset: Promise<Texture2D>;
79
+ export default asset;
80
+ export const materialize: Promise<unknown>;
81
+ }
82
+
83
+ declare module '*.exr' {
84
+ import type { TextureLayered } from '@ringozz/godot/TextureLayered';
85
+ const asset: Promise<TextureLayered>;
86
+ export default asset;
87
+ export const materialize: Promise<unknown>;
88
+ }
89
+
90
+ declare module '*.hdr' {
91
+ import type { TextureLayered } from '@ringozz/godot/TextureLayered';
92
+ const asset: Promise<TextureLayered>;
93
+ export default asset;
94
+ export const materialize: Promise<unknown>;
95
+ }
96
+
97
+ declare module '*.wav' {
98
+ import type { AudioStreamWAV } from '@ringozz/godot/AudioStreamWAV';
99
+ const asset: Promise<AudioStreamWAV>;
100
+ export default asset;
101
+ export const materialize: Promise<unknown>;
102
+ }
103
+
104
+ declare module '*.ogg' {
105
+ import type { AudioStreamOggVorbis } from '@ringozz/godot/AudioStreamOggVorbis';
106
+ const asset: Promise<AudioStreamOggVorbis>;
107
+ export default asset;
108
+ export const materialize: Promise<unknown>;
109
+ }
110
+
111
+ declare module '*.mp3' {
112
+ import type { AudioStreamMP3 } from '@ringozz/godot/AudioStreamMP3';
113
+ const asset: Promise<AudioStreamMP3>;
114
+ export default asset;
115
+ export const materialize: Promise<unknown>;
116
+ }
@@ -0,0 +1,41 @@
1
+ /**********************************************************************
2
+ Copyright (c) Vladimir Davidovich. All rights reserved.
3
+ ***********************************************************************/
4
+
5
+ // Web boot leaf (the `browser`-field replacement for `boot.ts`): boots the
6
+ // Godot wasm engine and hands the module to `runtime.ts` as the native module
7
+ // *namespace* (whose `.default` is the wasm module). This module is a leaf — it
8
+ // imports nothing from the godot class graph, so a web entry can call
9
+ // `preloadGodot()` *before* importing the app without pulling in `runtime.ts`.
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.
15
+
16
+ let nativeModule: unknown = null;
17
+
18
+ /** The cached wasm module import (its namespace), or `null` until `preloadGodot()` completes. */
19
+ export function getNativeModule(): unknown {
20
+ return nativeModule;
21
+ }
22
+
23
+ /**
24
+ * Boots the Godot wasm engine so `@ringozz/godot/runtime` can read the native
25
+ * module synchronously via {@link getNativeModule} — keeping the shared module
26
+ * graph top-level-await free (which is what lets Bun's HMR module loader
27
+ * evaluate sibling importers in order). Returns the wasm module's namespace
28
+ * (the native module is its `.default`, which `runtime.ts` destructures).
29
+ * Idempotent: subsequent calls return the cached import without re-booting.
30
+ *
31
+ * A web entry must `await preloadGodot()` before importing the app:
32
+ *
33
+ * ```ts
34
+ * import { preloadGodot } from '@ringozz/godot/boot';
35
+ * await preloadGodot();
36
+ * await import('./app.ts');
37
+ * ```
38
+ */
39
+ export async function preloadGodot(): Promise<unknown> {
40
+ return nativeModule ??= await import('@ringozz/godot-web-wasm32');
41
+ }
package/src/boot.ts ADDED
@@ -0,0 +1,27 @@
1
+ /**********************************************************************
2
+ Copyright (c) Vladimir Davidovich. All rights reserved.
3
+ ***********************************************************************/
4
+
5
+ // Desktop boot leaf: resolves the native addon for the current host and
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 by package
8
+ // self-reference (`@ringozz/godot/boot`), whose `exports` `./boot` entry's
9
+ // `browser` condition selects `boot.browser.ts` when bundling for
10
+ // `target: 'browser'` — so the Bun-only `import.meta.require` below never
11
+ // reaches a web bundle.
12
+
13
+ const { platform, arch } = globalThis.process ?? {};
14
+ const mapping: any = {
15
+ 'darwin-arm64': 'macos-arm64',
16
+ 'win32-x64': 'windows-x86_64',
17
+ };
18
+
19
+ /** The native addon's module namespace (sync dlopen; engine boots on first call). */
20
+ export function getNativeModule(): unknown {
21
+ return import.meta.require(`@ringozz/godot-${mapping[`${platform}-${arch}`]}`);
22
+ }
23
+
24
+ /** Desktop boots synchronously at import — nothing to preload. */
25
+ export async function preloadGodot(): Promise<unknown> {
26
+ return undefined;
27
+ }