@ringozz/godot 4.7.2-614 → 4.7.2-616
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 +148 -146
- package/package.json +7 -4
- package/src/assets.d.ts +130 -130
- package/src/debug.ts +357 -357
- package/src/editor.ts +107 -0
- package/src/index.ts +54 -50
- package/src/load.ts +231 -231
- package/src/preload.ts +253 -253
- package/src/runtime.ts +107 -107
- package/src/web-image.ts +153 -117
package/src/editor.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**********************************************************************
|
|
3
|
+
Copyright (c) Vladimir Davidovich. All rights reserved.
|
|
4
|
+
***********************************************************************/
|
|
5
|
+
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import { chmodSync, copyFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, symlinkSync } from 'node:fs';
|
|
8
|
+
import { homedir, tmpdir } from 'node:os';
|
|
9
|
+
import { delimiter, dirname, join, resolve } from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
|
|
12
|
+
const SELF = realpathSync(fileURLToPath(import.meta.url));
|
|
13
|
+
const IS_WINDOWS = process.platform === 'win32';
|
|
14
|
+
const BIN_DIR = join(homedir(), '.local', 'bin');
|
|
15
|
+
const MANAGED = join(BIN_DIR, `godot${IS_WINDOWS ? '.exe' : ''}`);
|
|
16
|
+
|
|
17
|
+
const { version_major, version_minor, version_patch, version_status } = JSON.parse(
|
|
18
|
+
readFileSync(join(dirname(SELF), '..', 'package.json'), 'utf-8'),
|
|
19
|
+
).godot;
|
|
20
|
+
const TAG = `${version_major}.${version_minor}.${version_patch}-${version_status}`;
|
|
21
|
+
|
|
22
|
+
const TARGETS: Record<string, { asset: string; exe: string }> = {
|
|
23
|
+
'darwin-arm64': { asset: `Godot_v${TAG}_macos.universal.zip`, exe: 'Godot.app/Contents/MacOS/Godot' },
|
|
24
|
+
'darwin-x64': { asset: `Godot_v${TAG}_macos.universal.zip`, exe: 'Godot.app/Contents/MacOS/Godot' },
|
|
25
|
+
'win32-x64': { asset: `Godot_v${TAG}_win64.exe.zip`, exe: `Godot_v${TAG}_win64.exe` },
|
|
26
|
+
'linux-x64': { asset: `Godot_v${TAG}_linux.x86_64.zip`, exe: `Godot_v${TAG}_linux.x86_64` },
|
|
27
|
+
'linux-arm64': { asset: `Godot_v${TAG}_linux.arm64.zip`, exe: `Godot_v${TAG}_linux.arm64` },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const EXTRACTORS: Record<string, (archive: string, dest: string) => [string, string[]]> = {
|
|
31
|
+
darwin: (archive, dest) => ['ditto', ['-x', '-k', archive, dest]],
|
|
32
|
+
win32: (archive, dest) => ['tar', ['-xf', archive, '-C', dest]],
|
|
33
|
+
linux: (archive, dest) => ['unzip', ['-o', '-q', archive, '-d', dest]],
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function systemGodot(): string | null {
|
|
37
|
+
const path = (process.env.PATH ?? '')
|
|
38
|
+
.split(delimiter)
|
|
39
|
+
.filter((dir) => {
|
|
40
|
+
try {
|
|
41
|
+
return realpathSync(join(resolve(dir), `godot${IS_WINDOWS ? '.exe' : ''}`)) !== SELF;
|
|
42
|
+
} catch {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return Bun.which('godot', { PATH: path.join(delimiter) });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function extract(archive: string, dest: string): void {
|
|
50
|
+
const make = EXTRACTORS[process.platform];
|
|
51
|
+
if (!make) throw new Error(`godot: no extractor for ${process.platform}.`);
|
|
52
|
+
const [tool, args] = make(archive, dest);
|
|
53
|
+
const { error, status } = spawnSync(tool, args, { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
54
|
+
if (error) throw new Error(`godot: '${tool}' is required to extract the editor (${error.message}).`);
|
|
55
|
+
if (status !== 0) throw new Error(`godot: '${tool}' failed with exit code ${status}.`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function install(stage: string, source: string): void {
|
|
59
|
+
mkdirSync(BIN_DIR, { recursive: true });
|
|
60
|
+
rmSync(MANAGED, { force: true });
|
|
61
|
+
if (process.platform !== 'darwin') {
|
|
62
|
+
copyFileSync(source, MANAGED);
|
|
63
|
+
if (!IS_WINDOWS) chmodSync(MANAGED, 0o755);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const bundle = join(BIN_DIR, 'Godot.app');
|
|
67
|
+
rmSync(bundle, { recursive: true, force: true });
|
|
68
|
+
cpSync(join(stage, 'Godot.app'), bundle, { recursive: true });
|
|
69
|
+
symlinkSync(join('Godot.app', 'Contents', 'MacOS', 'Godot'), MANAGED);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function provision(): Promise<void> {
|
|
73
|
+
const target = TARGETS[`${process.platform}-${process.arch}`];
|
|
74
|
+
if (!target) throw new Error(`godot: no prebuilt editor for ${process.platform}/${process.arch}; set GODOT_PATH.`);
|
|
75
|
+
const url = `https://github.com/godotengine/godot-builds/releases/download/${TAG}/${target.asset}`;
|
|
76
|
+
const stage = mkdtempSync(join(tmpdir(), 'godot-editor-'));
|
|
77
|
+
try {
|
|
78
|
+
console.error(`godot: downloading ${url}`);
|
|
79
|
+
const response = await fetch(url);
|
|
80
|
+
if (!response.ok) throw new Error(`godot: download failed (${response.status} ${response.statusText}) for ${url}`);
|
|
81
|
+
await Bun.write(join(stage, target.asset), response);
|
|
82
|
+
console.error('godot: extracting');
|
|
83
|
+
extract(join(stage, target.asset), stage);
|
|
84
|
+
const source = join(stage, target.exe);
|
|
85
|
+
if (!existsSync(source)) throw new Error(`godot: expected ${source} after extraction.`);
|
|
86
|
+
install(stage, source);
|
|
87
|
+
console.error(`godot: installed ${MANAGED}`);
|
|
88
|
+
} finally {
|
|
89
|
+
rmSync(stage, { recursive: true, force: true });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function resolveEditor(): Promise<string> {
|
|
94
|
+
if (process.env.GODOT_PATH) return process.env.GODOT_PATH;
|
|
95
|
+
if (!process.env.GODOT_NODE_WRAPPER) {
|
|
96
|
+
const system = systemGodot();
|
|
97
|
+
if (system) return system;
|
|
98
|
+
}
|
|
99
|
+
if (!existsSync(MANAGED)) await provision();
|
|
100
|
+
return MANAGED;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (import.meta.main) {
|
|
104
|
+
const exe = await resolveEditor();
|
|
105
|
+
const result = spawnSync(exe, process.argv.slice(2), { stdio: 'inherit', env: { ...process.env, GODOT_NODE_WRAPPER: '1' } });
|
|
106
|
+
process.exit(result.status ?? 1);
|
|
107
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,50 +1,54 @@
|
|
|
1
|
-
/**********************************************************************
|
|
2
|
-
Copyright (c) Vladimir Davidovich. All rights reserved.
|
|
3
|
-
***********************************************************************/
|
|
4
|
-
|
|
5
|
-
export * from '../gen/index.ts';
|
|
6
|
-
import * as HeapTypes from '../gen/heap-types/index.ts';
|
|
7
|
-
import { Engine } from '../gen/classes/Engine.ts';
|
|
8
|
-
import { GodotInstance } from '../gen/classes/GodotInstance.ts';
|
|
9
|
-
import { SceneTree } from '../gen/classes/SceneTree.ts';
|
|
10
|
-
import { Window } from '../gen/classes/Window.ts';
|
|
11
|
-
import { gc } from './debug.ts';
|
|
12
|
-
import { cancelAnimationFrame, cleanupHooks, getGodot, requestAnimationFrame } from './runtime.ts';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
void
|
|
18
|
-
void
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
while (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
1
|
+
/**********************************************************************
|
|
2
|
+
Copyright (c) Vladimir Davidovich. All rights reserved.
|
|
3
|
+
***********************************************************************/
|
|
4
|
+
|
|
5
|
+
export * from '../gen/index.ts';
|
|
6
|
+
import * as HeapTypes from '../gen/heap-types/index.ts';
|
|
7
|
+
import { Engine } from '../gen/classes/Engine.ts';
|
|
8
|
+
import { GodotInstance } from '../gen/classes/GodotInstance.ts';
|
|
9
|
+
import { SceneTree } from '../gen/classes/SceneTree.ts';
|
|
10
|
+
import { Window } from '../gen/classes/Window.ts';
|
|
11
|
+
import { gc } from './debug.ts';
|
|
12
|
+
import { cancelAnimationFrame, cleanupHooks, getGodot, requestAnimationFrame } from './runtime.ts';
|
|
13
|
+
import { InputEventMouseButton } from '../gen/classes/InputEventMouseButton.ts';
|
|
14
|
+
import { InputEventMouseMotion } from '../gen/classes/InputEventMouseMotion.ts';
|
|
15
|
+
|
|
16
|
+
// ---- make sure these classes are not tree-shaked ----
|
|
17
|
+
void HeapTypes;
|
|
18
|
+
void GodotInstance;
|
|
19
|
+
void SceneTree;
|
|
20
|
+
void Window;
|
|
21
|
+
void InputEventMouseButton;
|
|
22
|
+
void InputEventMouseMotion;
|
|
23
|
+
|
|
24
|
+
// ---- globalThis browser-compat API ----
|
|
25
|
+
Object.assign(globalThis as any, { requestAnimationFrame, cancelAnimationFrame });
|
|
26
|
+
|
|
27
|
+
// ---- GodotInstance frame pump (engine already started by C++ InitModule) ----
|
|
28
|
+
export async function runGodot(signal?: AbortSignal, unmount?: () => PromiseLike<void>) {
|
|
29
|
+
signal?.throwIfAborted();
|
|
30
|
+
signal?.addEventListener('abort', () => {
|
|
31
|
+
(Engine.getMainLoop() as SceneTree)?.quit();
|
|
32
|
+
}, { once: true });
|
|
33
|
+
|
|
34
|
+
const godot = getGodot();
|
|
35
|
+
while (!godot.iteration())
|
|
36
|
+
await new Promise(requestAnimationFrame);
|
|
37
|
+
|
|
38
|
+
await unmount?.();
|
|
39
|
+
while (cleanupHooks.size) {
|
|
40
|
+
const hooks = Array.from(cleanupHooks); cleanupHooks.clear();
|
|
41
|
+
await Promise.all(hooks.map((hook) => hook()));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Let pending Napi wrapper finalizers run before tearing down the engine:
|
|
45
|
+
// Bun.gc(true) marks unreachable wrappers but their ~GodotVar/~Variant
|
|
46
|
+
// (which unrefs RefCounted objects) only runs on later macrotasks — and one
|
|
47
|
+
// wrapper's finalizer can free others. Freeing the engine first would report
|
|
48
|
+
// every still-referenced RefCounted as leaked, so drain a few gc+tick rounds.
|
|
49
|
+
for (let i = 0; i < 4; i++) {
|
|
50
|
+
console.log(gc());
|
|
51
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
52
|
+
}
|
|
53
|
+
return godot.free();
|
|
54
|
+
}
|