retiam 1.0.0 → 1.0.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/bin/export.js CHANGED
@@ -1,323 +1,323 @@
1
- #!/usr/bin/env node
2
-
3
- // tools/builder/build-embed.js
4
- import { build } from 'vite';
5
- import path from 'path';
6
- import fs from 'fs';
7
-
8
- const rootDir = process.cwd();
9
-
10
- const args = process.argv.slice(2);
11
- const isHelp = args.includes('--help') || args.includes('-h') || args.includes('help');
12
- if (isHelp) {
13
- console.log(`
14
- 📦 Retiam WebGPU Engine - Web Embed Bundle Exporter
15
-
16
- Usage:
17
- npx retiam export [file] [outputDir] [options]
18
-
19
- Arguments:
20
- [file] Source scene file to bundle (default: src/main.ts)
21
- [outputDir] Target distribution folder (default: outputs/embed)
22
-
23
- Options:
24
- -o, --out <dir> Target distribution folder (default: outputs/embed)
25
- --help, -h Show this help message and exit
26
-
27
- Description:
28
- Compiles the scene and Retiam engine into a self-contained, standalone
29
- JavaScript web component (<retiam-scene>) for embedding into web pages,
30
- blogs, and documentation with zero external dependencies.
31
-
32
- Examples:
33
- npx retiam export src/scenes/AStarScene.ts
34
- npx retiam export src/scenes/AStarScene.ts ./outputs/embed
35
- npx retiam export src/scenes/AStarScene.ts -o ./public/embeds
36
- `);
37
- process.exit(0);
38
- }
39
-
40
- let targetOutDir = 'outputs/embed';
41
- let inputPath = null;
42
-
43
- for (let i = 0; i < args.length; i++) {
44
- if (args[i] === '-o' || args[i] === '--out') {
45
- targetOutDir = args[++i];
46
- } else if (!args[i].startsWith('-')) {
47
- if (!inputPath) {
48
- inputPath = args[i];
49
- } else {
50
- targetOutDir = args[i];
51
- }
52
- }
53
- }
54
-
55
- inputPath = inputPath || 'src/main.ts';
56
- const fullInputPath = path.resolve(rootDir, inputPath);
57
-
58
- if (!fs.existsSync(fullInputPath)) {
59
- console.error(`❌ Error: Scene file not found at "${inputPath}"`);
60
- console.log('Run "npx retiam export --help" for more details.');
61
- process.exit(1);
62
- }
63
-
64
- const sceneName = path.basename(inputPath, path.extname(inputPath)).toLowerCase();
65
- const outputFile = `${sceneName}.js`;
66
-
67
- const tempEntryPath = path.resolve(rootDir, 'src/_temp_embed_entry.ts');
68
- let rel = path.relative(path.dirname(tempEntryPath), fullInputPath).replace(/\\/g, '/');
69
- if (!rel.startsWith('.')) rel = './' + rel;
70
- const relativeScenePath = rel;
71
-
72
- const entryCode = `
73
- import { Scene } from "./engine/_core/Scene";
74
- import * as SceneModule from "${relativeScenePath}";
75
-
76
- export class RetiamElement extends HTMLElement {
77
- private canvas;
78
- public sceneInstance;
79
- private resizeObserver;
80
-
81
- async connectedCallback() {
82
- if (!navigator.gpu) {
83
- this.innerHTML = '<div style="color:red;padding:20px;">WebGPU not supported</div>';
84
- return;
85
- }
86
-
87
- // Base styles if not specified
88
- if (!this.style.display) this.style.display = "block";
89
- if (!this.style.position) this.style.position = "relative";
90
-
91
- this.canvas = document.createElement("canvas");
92
- this.canvas.style.display = "block";
93
- this.canvas.style.width = "100%";
94
- this.canvas.style.height = "100%";
95
- this.appendChild(this.canvas);
96
-
97
- const updateCanvasSize = () => {
98
- if (!this.canvas) return;
99
- const rect = this.getBoundingClientRect();
100
- const dpr = window.devicePixelRatio || 1;
101
- const w = Math.floor((rect.width || 800) * dpr);
102
- const h = Math.floor((rect.height || 450) * dpr);
103
- if (w > 0 && h > 0 && (this.canvas.width !== w || this.canvas.height !== h)) {
104
- this.canvas.width = w;
105
- this.canvas.height = h;
106
- }
107
- };
108
-
109
- updateCanvasSize();
110
-
111
- this.resizeObserver = new ResizeObserver(() => {
112
- updateCanvasSize();
113
- });
114
- this.resizeObserver.observe(this);
115
-
116
- try {
117
- // 1. Search exports for an actual Scene class (e.g., SubnetRouteGame)
118
- let SceneClass = null;
119
- for (const key of Object.keys(SceneModule)) {
120
- const item = (SceneModule as any)[key];
121
- if (item && item.prototype && item.prototype instanceof Scene) {
122
- SceneClass = item;
123
- break;
124
- }
125
- }
126
-
127
- if (SceneClass) {
128
- // Instantiate class directly with the component's canvas
129
- this.sceneInstance = new (SceneClass as any)();
130
- await this.sceneInstance.start(this.canvas);
131
- } else {
132
- // 2. Fallback: If no Scene class was exported, pass canvas to default export
133
- const defaultFn = (SceneModule as any).default;
134
- if (typeof defaultFn === 'function') {
135
- const origGetElementById = document.getElementById.bind(document);
136
- const compCanvas = this.canvas;
137
- const origStart = Scene.prototype.start;
138
- const self = this;
139
-
140
- // Intercept Scene.prototype.start to capture the instantiated Scene
141
- Scene.prototype.start = async function(...args: any[]) {
142
- self.sceneInstance = this;
143
- return await origStart.apply(this, args);
144
- };
145
-
146
- document.getElementById = function(id) {
147
- if (id === 'canvas') return compCanvas;
148
- return origGetElementById(id);
149
- };
150
-
151
- try {
152
- await defaultFn(this.canvas);
153
- } finally {
154
- document.getElementById = origGetElementById;
155
- Scene.prototype.start = origStart;
156
- }
157
- }
158
- }
159
-
160
- if (this.sceneInstance) {
161
- (window as any).retiamScene = this.sceneInstance;
162
- (window as any).retiamElement = this;
163
- this.dispatchEvent(new CustomEvent("retiam-ready", {
164
- detail: { scene: this.sceneInstance, element: this },
165
- bubbles: true
166
- }));
167
- this.dispatchEvent(new CustomEvent("retiam-scene-ready", {
168
- detail: { scene: this.sceneInstance, element: this },
169
- bubbles: true
170
- }));
171
- }
172
- } catch (err) {
173
- console.error("[Retiam Embed] Scene execution error:", err);
174
- }
175
- }
176
-
177
- disconnectedCallback() {
178
- if (this.resizeObserver) {
179
- this.resizeObserver.disconnect();
180
- this.resizeObserver = null;
181
- }
182
- if (this.sceneInstance) {
183
- this.sceneInstance.destroy();
184
- this.sceneInstance = null;
185
- }
186
- if ((window as any).retiamScene === this.sceneInstance) {
187
- (window as any).retiamScene = null;
188
- }
189
- }
190
- }
191
-
192
- export const RetiamSceneElement = RetiamElement;
193
-
194
- try {
195
- if (!customElements.get("retiam")) {
196
- customElements.define("retiam", RetiamElement);
197
- }
198
- } catch (e) {}
199
-
200
- if (!customElements.get("retiam-scene")) {
201
- customElements.define("retiam-scene", RetiamElement);
202
- }
203
-
204
- function autoMountRetiamTags() {
205
- const nodes = document.querySelectorAll("retiam, Retiam");
206
- nodes.forEach(node => {
207
- if (!(node as any).__retiamMounted) {
208
- (node as any).__retiamMounted = true;
209
- const elem = document.createElement("retiam-scene");
210
- for (let i = 0; i < node.attributes.length; i++) {
211
- const attr = node.attributes[i];
212
- elem.setAttribute(attr.name, attr.value);
213
- }
214
- node.replaceWith(elem);
215
- }
216
- });
217
- }
218
-
219
- if (typeof document !== "undefined") {
220
- const styleId = "retiam-embed-default-style";
221
- if (!document.getElementById(styleId)) {
222
- const style = document.createElement("style");
223
- style.id = styleId;
224
- style.textContent = "retiam, retiam-scene { display: block; position: relative; }";
225
- document.head?.appendChild(style);
226
- }
227
-
228
- if (document.readyState === "loading") {
229
- document.addEventListener("DOMContentLoaded", autoMountRetiamTags);
230
- } else {
231
- autoMountRetiamTags();
232
- }
233
-
234
- try {
235
- const obs = new MutationObserver(() => autoMountRetiamTags());
236
- obs.observe(document.documentElement || document.body, { childList: true, subtree: true });
237
- } catch (e) {}
238
- }
239
- `;
240
-
241
- fs.writeFileSync(tempEntryPath, entryCode);
242
-
243
- function minifyWGSL(code) {
244
- return code
245
- .replace(/\/\*[\s\S]*?\*\//g, '') // Remove block comments
246
- .replace(/\/\/.*/g, '') // Remove line comments
247
- .split('\n') // Process line by line
248
- .map(line => line.trim()) // Trim whitespace per line
249
- .filter(line => line.length > 0) // Remove empty lines
250
- .join('\n'); // Preserve line breaks for WebGPU debugging
251
- }
252
-
253
- const wgslPlugin = () => ({
254
- name: 'wgsl-loader',
255
- transform(code, id) {
256
- if (id.endsWith('.wgsl')) {
257
- const minified = minifyWGSL(code);
258
- return { code: `export default ${JSON.stringify(minified)};`, map: null };
259
- }
260
- }
261
- });
262
-
263
- const tzaPlugin = () => ({
264
- name: 'tza-loader',
265
- enforce: 'pre',
266
- load(id) {
267
- if (/\.(tza|ttf|png)$/i.test(id) || id.endsWith('.json?url')) {
268
- const filePath = id.replace(/\?url$/, '');
269
- const source = fs.readFileSync(filePath);
270
- const referenceId = this.emitFile({
271
- type: 'asset',
272
- fileName: `assets/${path.basename(filePath)}`,
273
- source: source
274
- });
275
- return `export default import.meta.ROLLUP_FILE_URL_${referenceId};`;
276
- }
277
- return null;
278
- }
279
- });
280
-
281
- console.log(`🚀 Building standalone embed for: ${inputPath} -> ${targetOutDir}/${outputFile}`);
282
-
283
- try {
284
- await build({
285
- configFile: false,
286
- publicDir: false,
287
- assetsInclude: ['**/*.tza', '**/*.ttf', '**/*.png'],
288
- plugins: [tzaPlugin(), wgslPlugin()],
289
- resolve: {
290
- alias: [
291
- { find: '../../index', replacement: path.resolve(rootDir, 'src/engine/_core/Constants.ts') },
292
- { find: '../index', replacement: path.resolve(rootDir, 'src/engine/_core/Constants.ts') }
293
- ]
294
- },
295
- build: {
296
- outDir: targetOutDir,
297
- emptyOutDir: false,
298
- lib: {
299
- entry: tempEntryPath,
300
- name: 'RetiamEmbed',
301
- fileName: () => outputFile,
302
- formats: ['iife']
303
- },
304
- rollupOptions: {
305
- output: { inlineDynamicImports: true }
306
- },
307
- minify: 'esbuild'
308
- },
309
- esbuild: {
310
- drop: ['debugger'],
311
- legalComments: 'none',
312
- treeShaking: true
313
- }
314
- });
315
-
316
- console.log(`✅ Build successful! Output: ${targetOutDir}/${outputFile}`);
317
- } catch (e) {
318
- console.error("❌ Build failed:", e);
319
- } finally {
320
- if (fs.existsSync(tempEntryPath)) {
321
- fs.unlinkSync(tempEntryPath);
322
- }
1
+ #!/usr/bin/env node
2
+
3
+ // tools/builder/build-embed.js
4
+ import { build } from 'vite';
5
+ import path from 'path';
6
+ import fs from 'fs';
7
+
8
+ const rootDir = process.cwd();
9
+
10
+ const args = process.argv.slice(2);
11
+ const isHelp = args.includes('--help') || args.includes('-h') || args.includes('help');
12
+ if (isHelp) {
13
+ console.log(`
14
+ 📦 Retiam WebGPU Engine - Web Embed Bundle Exporter
15
+
16
+ Usage:
17
+ npx retiam export [file] [outputDir] [options]
18
+
19
+ Arguments:
20
+ [file] Source scene file to bundle (default: src/main.ts)
21
+ [outputDir] Target distribution folder (default: outputs/embed)
22
+
23
+ Options:
24
+ -o, --out <dir> Target distribution folder (default: outputs/embed)
25
+ --help, -h Show this help message and exit
26
+
27
+ Description:
28
+ Compiles the scene and Retiam engine into a self-contained, standalone
29
+ JavaScript web component (<retiam-scene>) for embedding into web pages,
30
+ blogs, and documentation with zero external dependencies.
31
+
32
+ Examples:
33
+ npx retiam export src/scenes/AStarScene.ts
34
+ npx retiam export src/scenes/AStarScene.ts ./outputs/embed
35
+ npx retiam export src/scenes/AStarScene.ts -o ./public/embeds
36
+ `);
37
+ process.exit(0);
38
+ }
39
+
40
+ let targetOutDir = 'outputs/embed';
41
+ let inputPath = null;
42
+
43
+ for (let i = 0; i < args.length; i++) {
44
+ if (args[i] === '-o' || args[i] === '--out') {
45
+ targetOutDir = args[++i];
46
+ } else if (!args[i].startsWith('-')) {
47
+ if (!inputPath) {
48
+ inputPath = args[i];
49
+ } else {
50
+ targetOutDir = args[i];
51
+ }
52
+ }
53
+ }
54
+
55
+ inputPath = inputPath || 'src/main.ts';
56
+ const fullInputPath = path.resolve(rootDir, inputPath);
57
+
58
+ if (!fs.existsSync(fullInputPath)) {
59
+ console.error(`❌ Error: Scene file not found at "${inputPath}"`);
60
+ console.log('Run "npx retiam export --help" for more details.');
61
+ process.exit(1);
62
+ }
63
+
64
+ const sceneName = path.basename(inputPath, path.extname(inputPath)).toLowerCase();
65
+ const outputFile = `${sceneName}.js`;
66
+
67
+ const tempEntryPath = path.resolve(rootDir, 'src/_temp_embed_entry.ts');
68
+ let rel = path.relative(path.dirname(tempEntryPath), fullInputPath).replace(/\\/g, '/');
69
+ if (!rel.startsWith('.')) rel = './' + rel;
70
+ const relativeScenePath = rel;
71
+
72
+ const entryCode = `
73
+ import { Scene } from "./engine/_core/Scene";
74
+ import * as SceneModule from "${relativeScenePath}";
75
+
76
+ export class RetiamElement extends HTMLElement {
77
+ private canvas;
78
+ public sceneInstance;
79
+ private resizeObserver;
80
+
81
+ async connectedCallback() {
82
+ if (!navigator.gpu) {
83
+ this.innerHTML = '<div style="color:red;padding:20px;">WebGPU not supported</div>';
84
+ return;
85
+ }
86
+
87
+ // Base styles if not specified
88
+ if (!this.style.display) this.style.display = "block";
89
+ if (!this.style.position) this.style.position = "relative";
90
+
91
+ this.canvas = document.createElement("canvas");
92
+ this.canvas.style.display = "block";
93
+ this.canvas.style.width = "100%";
94
+ this.canvas.style.height = "100%";
95
+ this.appendChild(this.canvas);
96
+
97
+ const updateCanvasSize = () => {
98
+ if (!this.canvas) return;
99
+ const rect = this.getBoundingClientRect();
100
+ const dpr = window.devicePixelRatio || 1;
101
+ const w = Math.floor((rect.width || 800) * dpr);
102
+ const h = Math.floor((rect.height || 450) * dpr);
103
+ if (w > 0 && h > 0 && (this.canvas.width !== w || this.canvas.height !== h)) {
104
+ this.canvas.width = w;
105
+ this.canvas.height = h;
106
+ }
107
+ };
108
+
109
+ updateCanvasSize();
110
+
111
+ this.resizeObserver = new ResizeObserver(() => {
112
+ updateCanvasSize();
113
+ });
114
+ this.resizeObserver.observe(this);
115
+
116
+ try {
117
+ // 1. Search exports for an actual Scene class (e.g., SubnetRouteGame)
118
+ let SceneClass = null;
119
+ for (const key of Object.keys(SceneModule)) {
120
+ const item = (SceneModule as any)[key];
121
+ if (item && item.prototype && item.prototype instanceof Scene) {
122
+ SceneClass = item;
123
+ break;
124
+ }
125
+ }
126
+
127
+ if (SceneClass) {
128
+ // Instantiate class directly with the component's canvas
129
+ this.sceneInstance = new (SceneClass as any)();
130
+ await this.sceneInstance.start(this.canvas);
131
+ } else {
132
+ // 2. Fallback: If no Scene class was exported, pass canvas to default export
133
+ const defaultFn = (SceneModule as any).default;
134
+ if (typeof defaultFn === 'function') {
135
+ const origGetElementById = document.getElementById.bind(document);
136
+ const compCanvas = this.canvas;
137
+ const origStart = Scene.prototype.start;
138
+ const self = this;
139
+
140
+ // Intercept Scene.prototype.start to capture the instantiated Scene
141
+ Scene.prototype.start = async function(...args: any[]) {
142
+ self.sceneInstance = this;
143
+ return await origStart.apply(this, args);
144
+ };
145
+
146
+ document.getElementById = function(id) {
147
+ if (id === 'canvas') return compCanvas;
148
+ return origGetElementById(id);
149
+ };
150
+
151
+ try {
152
+ await defaultFn(this.canvas);
153
+ } finally {
154
+ document.getElementById = origGetElementById;
155
+ Scene.prototype.start = origStart;
156
+ }
157
+ }
158
+ }
159
+
160
+ if (this.sceneInstance) {
161
+ (window as any).retiamScene = this.sceneInstance;
162
+ (window as any).retiamElement = this;
163
+ this.dispatchEvent(new CustomEvent("retiam-ready", {
164
+ detail: { scene: this.sceneInstance, element: this },
165
+ bubbles: true
166
+ }));
167
+ this.dispatchEvent(new CustomEvent("retiam-scene-ready", {
168
+ detail: { scene: this.sceneInstance, element: this },
169
+ bubbles: true
170
+ }));
171
+ }
172
+ } catch (err) {
173
+ console.error("[Retiam Embed] Scene execution error:", err);
174
+ }
175
+ }
176
+
177
+ disconnectedCallback() {
178
+ if (this.resizeObserver) {
179
+ this.resizeObserver.disconnect();
180
+ this.resizeObserver = null;
181
+ }
182
+ if (this.sceneInstance) {
183
+ this.sceneInstance.destroy();
184
+ this.sceneInstance = null;
185
+ }
186
+ if ((window as any).retiamScene === this.sceneInstance) {
187
+ (window as any).retiamScene = null;
188
+ }
189
+ }
190
+ }
191
+
192
+ export const RetiamSceneElement = RetiamElement;
193
+
194
+ try {
195
+ if (!customElements.get("retiam")) {
196
+ customElements.define("retiam", RetiamElement);
197
+ }
198
+ } catch (e) {}
199
+
200
+ if (!customElements.get("retiam-scene")) {
201
+ customElements.define("retiam-scene", RetiamElement);
202
+ }
203
+
204
+ function autoMountRetiamTags() {
205
+ const nodes = document.querySelectorAll("retiam, Retiam");
206
+ nodes.forEach(node => {
207
+ if (!(node as any).__retiamMounted) {
208
+ (node as any).__retiamMounted = true;
209
+ const elem = document.createElement("retiam-scene");
210
+ for (let i = 0; i < node.attributes.length; i++) {
211
+ const attr = node.attributes[i];
212
+ elem.setAttribute(attr.name, attr.value);
213
+ }
214
+ node.replaceWith(elem);
215
+ }
216
+ });
217
+ }
218
+
219
+ if (typeof document !== "undefined") {
220
+ const styleId = "retiam-embed-default-style";
221
+ if (!document.getElementById(styleId)) {
222
+ const style = document.createElement("style");
223
+ style.id = styleId;
224
+ style.textContent = "retiam, retiam-scene { display: block; position: relative; }";
225
+ document.head?.appendChild(style);
226
+ }
227
+
228
+ if (document.readyState === "loading") {
229
+ document.addEventListener("DOMContentLoaded", autoMountRetiamTags);
230
+ } else {
231
+ autoMountRetiamTags();
232
+ }
233
+
234
+ try {
235
+ const obs = new MutationObserver(() => autoMountRetiamTags());
236
+ obs.observe(document.documentElement || document.body, { childList: true, subtree: true });
237
+ } catch (e) {}
238
+ }
239
+ `;
240
+
241
+ fs.writeFileSync(tempEntryPath, entryCode);
242
+
243
+ function minifyWGSL(code) {
244
+ return code
245
+ .replace(/\/\*[\s\S]*?\*\//g, '') // Remove block comments
246
+ .replace(/\/\/.*/g, '') // Remove line comments
247
+ .split('\n') // Process line by line
248
+ .map(line => line.trim()) // Trim whitespace per line
249
+ .filter(line => line.length > 0) // Remove empty lines
250
+ .join('\n'); // Preserve line breaks for WebGPU debugging
251
+ }
252
+
253
+ const wgslPlugin = () => ({
254
+ name: 'wgsl-loader',
255
+ transform(code, id) {
256
+ if (id.endsWith('.wgsl')) {
257
+ const minified = minifyWGSL(code);
258
+ return { code: `export default ${JSON.stringify(minified)};`, map: null };
259
+ }
260
+ }
261
+ });
262
+
263
+ const tzaPlugin = () => ({
264
+ name: 'tza-loader',
265
+ enforce: 'pre',
266
+ load(id) {
267
+ if (/\.(tza|ttf|png)$/i.test(id) || id.endsWith('.json?url')) {
268
+ const filePath = id.replace(/\?url$/, '');
269
+ const source = fs.readFileSync(filePath);
270
+ const referenceId = this.emitFile({
271
+ type: 'asset',
272
+ fileName: `assets/${path.basename(filePath)}`,
273
+ source: source
274
+ });
275
+ return `export default import.meta.ROLLUP_FILE_URL_${referenceId};`;
276
+ }
277
+ return null;
278
+ }
279
+ });
280
+
281
+ console.log(`🚀 Building standalone embed for: ${inputPath} -> ${targetOutDir}/${outputFile}`);
282
+
283
+ try {
284
+ await build({
285
+ configFile: false,
286
+ publicDir: false,
287
+ assetsInclude: ['**/*.tza', '**/*.ttf', '**/*.png'],
288
+ plugins: [tzaPlugin(), wgslPlugin()],
289
+ resolve: {
290
+ alias: [
291
+ { find: '../../index', replacement: path.resolve(rootDir, 'src/engine/_core/Constants.ts') },
292
+ { find: '../index', replacement: path.resolve(rootDir, 'src/engine/_core/Constants.ts') }
293
+ ]
294
+ },
295
+ build: {
296
+ outDir: targetOutDir,
297
+ emptyOutDir: false,
298
+ lib: {
299
+ entry: tempEntryPath,
300
+ name: 'RetiamEmbed',
301
+ fileName: () => outputFile,
302
+ formats: ['iife']
303
+ },
304
+ rollupOptions: {
305
+ output: { inlineDynamicImports: true }
306
+ },
307
+ minify: 'esbuild'
308
+ },
309
+ esbuild: {
310
+ drop: ['debugger'],
311
+ legalComments: 'none',
312
+ treeShaking: true
313
+ }
314
+ });
315
+
316
+ console.log(`✅ Build successful! Output: ${targetOutDir}/${outputFile}`);
317
+ } catch (e) {
318
+ console.error("❌ Build failed:", e);
319
+ } finally {
320
+ if (fs.existsSync(tempEntryPath)) {
321
+ fs.unlinkSync(tempEntryPath);
322
+ }
323
323
  }