@ringozz/godot 4.7.2-597 → 4.7.2-612
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 +3 -3
- package/package.json +4 -4
- package/src/assets.d.ts +7 -0
- package/src/preload.ts +62 -25
package/README.md
CHANGED
|
@@ -99,7 +99,7 @@ import foo from './models/foo.gltf';
|
|
|
99
99
|
const scene = await foo; // PackedScene
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
-
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).
|
|
102
|
+
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). The files Godot reads are bundled into the module — `.import` sidecars and native text sources (`tscn`/`tres`/`po`/`gd`) are inlined as text, imported products (`.scn`/`.ctex`) are emitted as files — and the module 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`/`.gd`) are scanned for references (`res://` paths and relative paths ending in known asset extensions); referenced loadable assets become dep modules, native-text leaves are staged as raw text so the engine loads them by `res://` on web, 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()`:
|
|
103
103
|
|
|
104
104
|
```tsx
|
|
105
105
|
import foo from './models/foo.gltf';
|
|
@@ -115,7 +115,7 @@ function Foo() {
|
|
|
115
115
|
</Suspense>
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
-
Each extension maps to its Godot class (source assets only — `gltf`/`tscn`/`obj` → `PackedScene`, `
|
|
118
|
+
Each extension maps to its Godot class (source assets only — `gltf`/`tscn`/`obj` → `PackedScene`, `gd` → `GDScript`, `jpg`/`jpeg`/`png`/`webp`/`svg` → `CompressedTexture2D`, `exr`/`hdr` → `TextureLayered`, `wav` → `AudioStreamWAV`, `ogg` → `AudioStreamOggVorbis`, `mp3` → `AudioStreamMP3`, `ttf`/`otf` → `FontFile`, `tres` → `Resource`, `po` → `Translation`), declared in `src/assets.d.ts`. Imported products (`.scn`/`.ctex`) are never imported directly. `.gd` scripts import as `Promise<GDScript>`, so `import script from './x.gd'; const gd = await script;` works like any other resource — and when a `.tscn` references the same `.gd`, Godot's `ResourceLoader` cache returns the same instance (parsed once).
|
|
119
119
|
|
|
120
120
|
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.
|
|
121
121
|
|
|
@@ -143,4 +143,4 @@ initDebug();
|
|
|
143
143
|
|
|
144
144
|
## Development
|
|
145
145
|
|
|
146
|
-
`gen/` is generated (and gitignored) by `dev/codegen.ts` from `godot --dump-extension-api-with-docs`.
|
|
146
|
+
`gen/` is generated (and gitignored) by `dev/codegen.ts` from `godot --dump-extension-api-with-docs`. From the repo root: `bun run prebuild` (codegen + typecheck), `bun run predev` (imports `dev/assets/` via the editor), `bun run build` (native addon), `bun run test`, and `bun run dev` (web dev server). 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.7.2-
|
|
4
|
+
"version": "4.7.2-612",
|
|
5
5
|
"description": "Node-API bindings for Godot Engine — call Godot classes from JavaScript",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -58,9 +58,9 @@
|
|
|
58
58
|
"precision": "single"
|
|
59
59
|
},
|
|
60
60
|
"optionalDependencies": {
|
|
61
|
-
"@ringozz/godot-macos-arm64": "^4.7.2-
|
|
62
|
-
"@ringozz/godot-windows-x86_64": "^4.7.2-
|
|
63
|
-
"@ringozz/godot-web-wasm32": "^4.7.2-
|
|
61
|
+
"@ringozz/godot-macos-arm64": "^4.7.2-612",
|
|
62
|
+
"@ringozz/godot-windows-x86_64": "^4.7.2-612",
|
|
63
|
+
"@ringozz/godot-web-wasm32": "^4.7.2-612"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
66
|
"@types/bun": "*"
|
package/src/assets.d.ts
CHANGED
|
@@ -10,6 +10,13 @@
|
|
|
10
10
|
// a stable module singleton, so it can be passed to React's `use()` under
|
|
11
11
|
// `<Suspense>`.
|
|
12
12
|
|
|
13
|
+
declare module '*.gd' {
|
|
14
|
+
import type { GDScript } from '@ringozz/godot/GDScript';
|
|
15
|
+
const asset: Promise<GDScript>;
|
|
16
|
+
export default asset;
|
|
17
|
+
export const materialize: Promise<unknown>;
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
declare module '*.tres' {
|
|
14
21
|
import type { Resource } from '@ringozz/godot/Resource';
|
|
15
22
|
const asset: Promise<Resource>;
|
package/src/preload.ts
CHANGED
|
@@ -32,6 +32,7 @@ const FORMATS: Record<string, { cls?: string; scan?: boolean; native?: boolean }
|
|
|
32
32
|
gltf: { cls: 'PackedScene', scan: true },
|
|
33
33
|
tscn: { cls: 'PackedScene', scan: true, native: true },
|
|
34
34
|
obj: { cls: 'PackedScene', scan: true },
|
|
35
|
+
gd: { cls: 'GDScript', scan: true, native: true },
|
|
35
36
|
jpg: { cls: 'CompressedTexture2D' },
|
|
36
37
|
jpeg: { cls: 'CompressedTexture2D' },
|
|
37
38
|
png: { cls: 'CompressedTexture2D' },
|
|
@@ -78,24 +79,36 @@ function parseSidecarDest(text: string): string | null {
|
|
|
78
79
|
return remap ? remap[1] : null;
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
// Reference extraction for text-based source assets: absolute `res://` paths
|
|
83
|
+
// (Godot scenes/resources) and relative path tokens ending in a known asset
|
|
84
|
+
// extension (glTF uris, OBJ/MTL textures). The literal dot before the extension
|
|
85
|
+
// avoids matching MIME types inside `data:` URIs. Regexes are built fresh per
|
|
86
|
+
// call: a `/g` instance is stateful, and reusing one module-level regex across
|
|
87
|
+
// many files misbehaves under the dev server's bundler (a stale lastIndex
|
|
88
|
+
// shifted a scan's results). The `//` fragment right after `res:` is a mere
|
|
89
|
+
// path remainder of the `res://` match, not a real relative ref, so it's
|
|
90
|
+
// skipped (otherwise it mis-resolves as a Windows UNC `\\…` path).
|
|
91
|
+
//
|
|
92
|
+
// The extension must TERMINATE a token: `(?![\w.])` rejects matches where the
|
|
93
|
+
// extension is only the head of a longer identifier or a dotted sub-field, e.g.
|
|
94
|
+
// `X.po` in `X.position`, `foo.gd` in `foo.gd.position`, or `scene.tscn2`. A
|
|
95
|
+
// real relative ref (`foo.po`, `x.gd`, `ui/scene.tscn`) always ends its token.
|
|
96
|
+
// `String.raw` keeps the `\w` escapes intact — a plain template literal would
|
|
97
|
+
// drop the backslashes, turning `\w` into `w` (a wrong guard `(?![w.])`).
|
|
98
|
+
const REL_TERM = String.raw`(?![\w.])`;
|
|
99
|
+
const REL_PATTERN = String.raw`([\w./\\-]+\.(?:` + Object.keys(FORMATS).join('|') + `))` + REL_TERM;
|
|
88
100
|
function extractRefs(text: string): string[] {
|
|
89
101
|
const refs: string[] = [];
|
|
90
|
-
for (const m of text.matchAll(
|
|
91
|
-
|
|
102
|
+
for (const m of text.matchAll(/res:\/\/[\w./\\-]+/g)) refs.push(m[0]);
|
|
103
|
+
const rel = new RegExp(REL_PATTERN, 'g');
|
|
104
|
+
for (const m of text.matchAll(rel)) if (!m[1].startsWith('/')) refs.push(m[1]);
|
|
92
105
|
return refs;
|
|
93
106
|
}
|
|
94
107
|
|
|
95
108
|
/**
|
|
96
109
|
* Adds a source asset's own files to `files`: imported assets contribute their
|
|
97
110
|
* `.import` sidecar (text) + imported product (file); native text formats
|
|
98
|
-
* (tscn/tres/po) contribute the file itself (text).
|
|
111
|
+
* (tscn/tres/po/gd) contribute the file itself (text).
|
|
99
112
|
*/
|
|
100
113
|
function addSource(srcAbs: string, files: Map<string, 'text' | 'file'>): void {
|
|
101
114
|
const impAbs = srcAbs + '.import';
|
|
@@ -106,16 +119,23 @@ function addSource(srcAbs: string, files: Map<string, 'text' | 'file'>): void {
|
|
|
106
119
|
files.set(dest, 'file');
|
|
107
120
|
}
|
|
108
121
|
} else if (isNativeText(srcAbs)) {
|
|
109
|
-
|
|
122
|
+
stageNativeText(srcAbs, files);
|
|
110
123
|
}
|
|
111
124
|
}
|
|
112
125
|
|
|
126
|
+
/** Stages a native text source (`res://` path → `text`) so web staging + inlining picks it up. */
|
|
127
|
+
function stageNativeText(srcAbs: string, files: Map<string, 'text' | 'file'>): void {
|
|
128
|
+
files.set(resOf(srcAbs), 'text');
|
|
129
|
+
}
|
|
130
|
+
|
|
113
131
|
/**
|
|
114
|
-
* Collects the referenced source assets (deps) of a text source:
|
|
115
|
-
*
|
|
116
|
-
*
|
|
132
|
+
* Collects the referenced source assets (deps) of a text source: every referenced
|
|
133
|
+
* source asset is staged (native text → `files`, products → dep modules), and
|
|
134
|
+
* loadable source assets also become dep modules so their own files + transitive
|
|
135
|
+
* deps are handled. Raw text formats without a sidecar (`.mtl`) are recursed into.
|
|
136
|
+
* Buffers (`.bin`) are skipped.
|
|
117
137
|
*/
|
|
118
|
-
function collectDeps(abs: string, visited: Set<string>, deps: string[]): void {
|
|
138
|
+
function collectDeps(abs: string, visited: Set<string>, deps: string[], files: Map<string, 'text' | 'file'>): void {
|
|
119
139
|
if (visited.has(abs)) {
|
|
120
140
|
return;
|
|
121
141
|
}
|
|
@@ -130,10 +150,22 @@ function collectDeps(abs: string, visited: Set<string>, deps: string[]): void {
|
|
|
130
150
|
if (visited.has(refAbs)) {
|
|
131
151
|
continue;
|
|
132
152
|
}
|
|
153
|
+
// Stage native text (e.g. `.gd`, nested `.tscn`) so the engine loads it
|
|
154
|
+
// by `res://` on web (idempotent); loadable assets also become dep
|
|
155
|
+
// modules for their own files + transitive deps.
|
|
133
156
|
if (isSourceAsset(refAbs)) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
157
|
+
if (isNativeText(refAbs)) {
|
|
158
|
+
visited.add(refAbs);
|
|
159
|
+
stageNativeText(refAbs, files);
|
|
160
|
+
}
|
|
161
|
+
if (CLASS_BY_EXT[extOf(refAbs)]) {
|
|
162
|
+
deps.push(refAbs);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Any scannable ref (a source's own text, or a raw leaf like `.mtl`) is
|
|
166
|
+
// recursed into so its internal `res://` refs are traced/staged too.
|
|
167
|
+
if (isScannableText(refAbs)) {
|
|
168
|
+
collectDeps(refAbs, visited, deps, files);
|
|
137
169
|
}
|
|
138
170
|
}
|
|
139
171
|
}
|
|
@@ -142,7 +174,7 @@ function analyze(abs: string): { files: Map<string, 'text' | 'file'>; deps: stri
|
|
|
142
174
|
const files = new Map<string, 'text' | 'file'>();
|
|
143
175
|
const deps: string[] = [];
|
|
144
176
|
addSource(abs, files);
|
|
145
|
-
collectDeps(abs, new Set(), deps);
|
|
177
|
+
collectDeps(abs, new Set(), deps, files);
|
|
146
178
|
return { files, deps };
|
|
147
179
|
}
|
|
148
180
|
|
|
@@ -156,15 +188,20 @@ function ensureModule(abs: string): string {
|
|
|
156
188
|
`import { ${cls} } from '@ringozz/godot/${cls}';`,
|
|
157
189
|
];
|
|
158
190
|
const map: string[] = [];
|
|
159
|
-
let ti = 0;
|
|
160
191
|
let fi = 0;
|
|
192
|
+
// Text files (own `.tscn`/`.tres`/`.po`, `.import` sidecars, staged `.gd`
|
|
193
|
+
// leaves) are inlined as string literals — Bun's `with { type: 'text' }`
|
|
194
|
+
// import resolves to a null/default under the dev server's HMR wrapper for
|
|
195
|
+
// a module's own path; products (`.scn`/`.ctex`) are fetched by path.
|
|
161
196
|
for (const [resPath, kind] of files) {
|
|
162
197
|
const absP = resolve(ROOT, resPath.slice('res://'.length));
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
198
|
+
if (kind === 'text') {
|
|
199
|
+
map.push(` ${JSON.stringify(resPath)}: { content: ${JSON.stringify(readFileSync(absP, 'utf8'))} },`);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const v = `f${fi++}`;
|
|
203
|
+
imports.push(`import ${v} from ${JSON.stringify(absP)} with { type: 'file' };`);
|
|
204
|
+
map.push(` ${JSON.stringify(resPath)}: { path: ${v} },`);
|
|
168
205
|
}
|
|
169
206
|
deps.forEach((dep, i) => imports.push(`import * as dep${i} from ${JSON.stringify(dep)};`));
|
|
170
207
|
|