@al8b/runtime 0.1.0
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 +87 -0
- package/dist/assets/constructors.d.mts +6 -0
- package/dist/assets/constructors.d.ts +6 -0
- package/dist/assets/constructors.js +40 -0
- package/dist/assets/constructors.js.map +1 -0
- package/dist/assets/constructors.mjs +12 -0
- package/dist/assets/constructors.mjs.map +1 -0
- package/dist/assets/index.d.mts +11 -0
- package/dist/assets/index.d.ts +11 -0
- package/dist/assets/index.js +276 -0
- package/dist/assets/index.js.map +1 -0
- package/dist/assets/index.mjs +247 -0
- package/dist/assets/index.mjs.map +1 -0
- package/dist/assets/loader.d.mts +83 -0
- package/dist/assets/loader.d.ts +83 -0
- package/dist/assets/loader.js +260 -0
- package/dist/assets/loader.js.map +1 -0
- package/dist/assets/loader.mjs +237 -0
- package/dist/assets/loader.mjs.map +1 -0
- package/dist/browser/index.js +16599 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/index.min.js +171 -0
- package/dist/constants.d.mts +16 -0
- package/dist/constants.d.ts +16 -0
- package/dist/constants.js +49 -0
- package/dist/constants.js.map +1 -0
- package/dist/constants.mjs +18 -0
- package/dist/constants.mjs.map +1 -0
- package/dist/core/api-factory.d.mts +63 -0
- package/dist/core/api-factory.d.ts +63 -0
- package/dist/core/api-factory.js +239 -0
- package/dist/core/api-factory.js.map +1 -0
- package/dist/core/api-factory.mjs +214 -0
- package/dist/core/api-factory.mjs.map +1 -0
- package/dist/core/assets-registry.d.mts +14 -0
- package/dist/core/assets-registry.d.ts +14 -0
- package/dist/core/assets-registry.js +64 -0
- package/dist/core/assets-registry.js.map +1 -0
- package/dist/core/assets-registry.mjs +41 -0
- package/dist/core/assets-registry.mjs.map +1 -0
- package/dist/core/controller.d.mts +109 -0
- package/dist/core/controller.d.ts +109 -0
- package/dist/core/controller.js +1782 -0
- package/dist/core/controller.js.map +1 -0
- package/dist/core/controller.mjs +1758 -0
- package/dist/core/controller.mjs.map +1 -0
- package/dist/core/debug-logger.d.mts +35 -0
- package/dist/core/debug-logger.d.ts +35 -0
- package/dist/core/debug-logger.js +177 -0
- package/dist/core/debug-logger.js.map +1 -0
- package/dist/core/debug-logger.mjs +154 -0
- package/dist/core/debug-logger.mjs.map +1 -0
- package/dist/core/error-handler.d.mts +25 -0
- package/dist/core/error-handler.d.ts +25 -0
- package/dist/core/error-handler.js +106 -0
- package/dist/core/error-handler.js.map +1 -0
- package/dist/core/error-handler.mjs +81 -0
- package/dist/core/error-handler.mjs.map +1 -0
- package/dist/core/index.d.mts +14 -0
- package/dist/core/index.d.ts +14 -0
- package/dist/core/index.js +1782 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/index.mjs +1757 -0
- package/dist/core/index.mjs.map +1 -0
- package/dist/hot-reload/index.d.mts +7 -0
- package/dist/hot-reload/index.d.ts +7 -0
- package/dist/hot-reload/index.js +103 -0
- package/dist/hot-reload/index.js.map +1 -0
- package/dist/hot-reload/index.mjs +78 -0
- package/dist/hot-reload/index.mjs.map +1 -0
- package/dist/hot-reload/updater.d.mts +33 -0
- package/dist/hot-reload/updater.d.ts +33 -0
- package/dist/hot-reload/updater.js +101 -0
- package/dist/hot-reload/updater.js.map +1 -0
- package/dist/hot-reload/updater.mjs +78 -0
- package/dist/hot-reload/updater.mjs.map +1 -0
- package/dist/index.d.mts +24 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +1859 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1817 -0
- package/dist/index.mjs.map +1 -0
- package/dist/input/index.d.mts +2 -0
- package/dist/input/index.d.ts +2 -0
- package/dist/input/index.js +79 -0
- package/dist/input/index.js.map +1 -0
- package/dist/input/index.mjs +54 -0
- package/dist/input/index.mjs.map +1 -0
- package/dist/input/manager.d.mts +37 -0
- package/dist/input/manager.d.ts +37 -0
- package/dist/input/manager.js +77 -0
- package/dist/input/manager.js.map +1 -0
- package/dist/input/manager.mjs +54 -0
- package/dist/input/manager.mjs.map +1 -0
- package/dist/loop/game-loop.d.mts +63 -0
- package/dist/loop/game-loop.d.ts +63 -0
- package/dist/loop/game-loop.js +156 -0
- package/dist/loop/game-loop.js.map +1 -0
- package/dist/loop/game-loop.mjs +131 -0
- package/dist/loop/game-loop.mjs.map +1 -0
- package/dist/loop/index.d.mts +1 -0
- package/dist/loop/index.d.ts +1 -0
- package/dist/loop/index.js +156 -0
- package/dist/loop/index.js.map +1 -0
- package/dist/loop/index.mjs +131 -0
- package/dist/loop/index.mjs.map +1 -0
- package/dist/storage/index.d.mts +1 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.js +31 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/index.mjs +6 -0
- package/dist/storage/index.mjs.map +1 -0
- package/dist/system/api.d.mts +28 -0
- package/dist/system/api.d.ts +28 -0
- package/dist/system/api.js +126 -0
- package/dist/system/api.js.map +1 -0
- package/dist/system/api.mjs +101 -0
- package/dist/system/api.mjs.map +1 -0
- package/dist/system/index.d.mts +2 -0
- package/dist/system/index.d.ts +2 -0
- package/dist/system/index.js +126 -0
- package/dist/system/index.js.map +1 -0
- package/dist/system/index.mjs +101 -0
- package/dist/system/index.mjs.map +1 -0
- package/dist/types/assets.d.mts +43 -0
- package/dist/types/assets.d.ts +43 -0
- package/dist/types/assets.js +19 -0
- package/dist/types/assets.js.map +1 -0
- package/dist/types/assets.mjs +1 -0
- package/dist/types/assets.mjs.map +1 -0
- package/dist/types/bridge.d.mts +66 -0
- package/dist/types/bridge.d.ts +66 -0
- package/dist/types/bridge.js +19 -0
- package/dist/types/bridge.js.map +1 -0
- package/dist/types/bridge.mjs +1 -0
- package/dist/types/bridge.mjs.map +1 -0
- package/dist/types/index.d.mts +6 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.js +19 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/index.mjs +1 -0
- package/dist/types/index.mjs.map +1 -0
- package/dist/types/runtime.d.mts +71 -0
- package/dist/types/runtime.d.ts +71 -0
- package/dist/types/runtime.js +19 -0
- package/dist/types/runtime.js.map +1 -0
- package/dist/types/runtime.mjs +1 -0
- package/dist/types/runtime.mjs.map +1 -0
- package/dist/types/vm.d.mts +1 -0
- package/dist/types/vm.d.ts +1 -0
- package/dist/types/vm.js +19 -0
- package/dist/types/vm.js.map +1 -0
- package/dist/types/vm.mjs +1 -0
- package/dist/types/vm.mjs.map +1 -0
- package/dist/utils/deep-clone.d.mts +14 -0
- package/dist/utils/deep-clone.d.ts +14 -0
- package/dist/utils/deep-clone.js +42 -0
- package/dist/utils/deep-clone.js.map +1 -0
- package/dist/utils/deep-clone.mjs +19 -0
- package/dist/utils/deep-clone.mjs.map +1 -0
- package/dist/utils/index.d.mts +3 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.js +156 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/index.mjs +129 -0
- package/dist/utils/index.mjs.map +1 -0
- package/dist/utils/object-pool.d.mts +66 -0
- package/dist/utils/object-pool.d.ts +66 -0
- package/dist/utils/object-pool.js +113 -0
- package/dist/utils/object-pool.js.map +1 -0
- package/dist/utils/object-pool.mjs +90 -0
- package/dist/utils/object-pool.mjs.map +1 -0
- package/dist/utils/shallow-equal.d.mts +15 -0
- package/dist/utils/shallow-equal.d.ts +15 -0
- package/dist/utils/shallow-equal.js +53 -0
- package/dist/utils/shallow-equal.js.map +1 -0
- package/dist/utils/shallow-equal.mjs +30 -0
- package/dist/utils/shallow-equal.mjs.map +1 -0
- package/dist/vm/index.d.mts +1 -0
- package/dist/vm/index.d.ts +1 -0
- package/dist/vm/index.js +37 -0
- package/dist/vm/index.js.map +1 -0
- package/dist/vm/index.mjs +9 -0
- package/dist/vm/index.mjs.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,1757 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/core/controller.ts
|
|
5
|
+
import { AudioCore } from "@al8b/audio";
|
|
6
|
+
import { PlayerService } from "@al8b/player";
|
|
7
|
+
import { SceneManager } from "@al8b/scene";
|
|
8
|
+
import { Screen } from "@al8b/screen";
|
|
9
|
+
import { StatePlayer, TimeMachine } from "@al8b/time";
|
|
10
|
+
import { L8BVM } from "@al8b/vm";
|
|
11
|
+
|
|
12
|
+
// src/assets/constructors.ts
|
|
13
|
+
import { Sound } from "@al8b/audio";
|
|
14
|
+
import { TileMap } from "@al8b/map";
|
|
15
|
+
import { Image } from "@al8b/image";
|
|
16
|
+
import { Sprite } from "@al8b/sprites";
|
|
17
|
+
|
|
18
|
+
// src/assets/loader.ts
|
|
19
|
+
import { Sound as Sound2, Music } from "@al8b/audio";
|
|
20
|
+
|
|
21
|
+
// src/constants.ts
|
|
22
|
+
var DEFAULT_FPS = 60;
|
|
23
|
+
var DEFAULT_UPDATE_RATE = 60;
|
|
24
|
+
var FRAME_TIME_MS = 1e3 / DEFAULT_FPS;
|
|
25
|
+
var PAUSE_THRESHOLD_MS = 160;
|
|
26
|
+
var DEFAULT_BLOCK_SIZE = 16;
|
|
27
|
+
var LOADING_BAR_THROTTLE_MS = 16;
|
|
28
|
+
var ASSET_LOAD_TIMEOUT_MS = 3e4;
|
|
29
|
+
|
|
30
|
+
// src/assets/loader.ts
|
|
31
|
+
import { LoadMap } from "@al8b/map";
|
|
32
|
+
import { LoadSprite } from "@al8b/sprites";
|
|
33
|
+
function withTimeout(promise, ms) {
|
|
34
|
+
return Promise.race([
|
|
35
|
+
promise,
|
|
36
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`Asset load timed out after ${ms}ms`)), ms))
|
|
37
|
+
]);
|
|
38
|
+
}
|
|
39
|
+
__name(withTimeout, "withTimeout");
|
|
40
|
+
var AssetLoader = class {
|
|
41
|
+
static {
|
|
42
|
+
__name(this, "AssetLoader");
|
|
43
|
+
}
|
|
44
|
+
url;
|
|
45
|
+
resources;
|
|
46
|
+
collections;
|
|
47
|
+
loadingBarTime = null;
|
|
48
|
+
audioCore;
|
|
49
|
+
listener;
|
|
50
|
+
constructor(url, resources, audioCore, listener) {
|
|
51
|
+
this.url = url;
|
|
52
|
+
this.resources = resources;
|
|
53
|
+
this.audioCore = audioCore;
|
|
54
|
+
this.listener = listener;
|
|
55
|
+
this.collections = {
|
|
56
|
+
sprites: {},
|
|
57
|
+
maps: {},
|
|
58
|
+
sounds: {},
|
|
59
|
+
music: {},
|
|
60
|
+
assets: {}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Load all assets
|
|
65
|
+
*/
|
|
66
|
+
async loadAll() {
|
|
67
|
+
await Promise.all([
|
|
68
|
+
this.loadSprites(),
|
|
69
|
+
this.loadMaps(),
|
|
70
|
+
this.loadSounds(),
|
|
71
|
+
this.loadMusic(),
|
|
72
|
+
this.loadGenericAssets()
|
|
73
|
+
]);
|
|
74
|
+
return this.collections;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Load a set of callback-based assets (sprites or maps) with timeout and placeholder fallback.
|
|
78
|
+
*/
|
|
79
|
+
async loadCallbackAssets(resources, urlPrefix, collectionKey, load, createPlaceholder) {
|
|
80
|
+
if (!resources) return;
|
|
81
|
+
const promises = resources.map((res) => new Promise((resolve) => {
|
|
82
|
+
const name = res.file.split(".")[0].replace(/-/g, "/");
|
|
83
|
+
const url = `${this.url}${urlPrefix}/${res.file}?v=${res.version || 0}`;
|
|
84
|
+
try {
|
|
85
|
+
const inner = new Promise((onReady) => {
|
|
86
|
+
this.collections[collectionKey][name] = load(url, res, onReady);
|
|
87
|
+
});
|
|
88
|
+
withTimeout(inner, ASSET_LOAD_TIMEOUT_MS).then(resolve).catch((err) => {
|
|
89
|
+
this.listener?.log?.(`[AssetLoader] Failed to load ${collectionKey.slice(0, -1)} "${name}": ${String(err)}`);
|
|
90
|
+
this.collections[collectionKey][name] = createPlaceholder(name, res);
|
|
91
|
+
resolve();
|
|
92
|
+
});
|
|
93
|
+
} catch (err) {
|
|
94
|
+
this.listener?.log?.(`[AssetLoader] Failed to load ${collectionKey.slice(0, -1)} "${name}": ${String(err)}`);
|
|
95
|
+
this.collections[collectionKey][name] = createPlaceholder(name, res);
|
|
96
|
+
resolve();
|
|
97
|
+
}
|
|
98
|
+
}));
|
|
99
|
+
await Promise.all(promises);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Load sprites
|
|
103
|
+
*/
|
|
104
|
+
async loadSprites() {
|
|
105
|
+
await this.loadCallbackAssets(this.resources.images, "sprites", "sprites", (url, img, onReady) => LoadSprite(url, img.properties, onReady), (name, img) => ({
|
|
106
|
+
name,
|
|
107
|
+
ready: false,
|
|
108
|
+
frames: [],
|
|
109
|
+
fps: img.properties?.fps || 5,
|
|
110
|
+
width: 0,
|
|
111
|
+
height: 0
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Load maps
|
|
116
|
+
*/
|
|
117
|
+
async loadMaps() {
|
|
118
|
+
await this.loadCallbackAssets(this.resources.maps, "maps", "maps", (url, _mapRes, onReady) => LoadMap(url, this.collections.sprites, onReady), (name) => ({
|
|
119
|
+
name,
|
|
120
|
+
ready: false,
|
|
121
|
+
width: 0,
|
|
122
|
+
height: 0,
|
|
123
|
+
block_width: DEFAULT_BLOCK_SIZE,
|
|
124
|
+
block_height: DEFAULT_BLOCK_SIZE,
|
|
125
|
+
data: []
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Load sounds
|
|
130
|
+
*/
|
|
131
|
+
async loadSounds() {
|
|
132
|
+
if (!this.resources.sounds) return;
|
|
133
|
+
const promises = this.resources.sounds.map((sound) => {
|
|
134
|
+
return new Promise((resolve) => {
|
|
135
|
+
const name = sound.file.split(".")[0];
|
|
136
|
+
const url = `${this.url}sounds/${sound.file}?v=${sound.version || 0}`;
|
|
137
|
+
try {
|
|
138
|
+
const soundInstance = new Sound2(this.audioCore, url);
|
|
139
|
+
this.collections.sounds[name] = soundInstance;
|
|
140
|
+
resolve();
|
|
141
|
+
} catch (err) {
|
|
142
|
+
this.listener?.log?.(`[AssetLoader] Failed to load sound "${name}": ${String(err)}`);
|
|
143
|
+
this.collections.sounds[name] = new Sound2(this.audioCore, url);
|
|
144
|
+
resolve();
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
await Promise.all(promises);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Load music
|
|
152
|
+
*/
|
|
153
|
+
async loadMusic() {
|
|
154
|
+
if (!this.resources.music) return;
|
|
155
|
+
const promises = this.resources.music.map((mus) => {
|
|
156
|
+
return new Promise((resolve) => {
|
|
157
|
+
const name = mus.file.split(".")[0];
|
|
158
|
+
const url = `${this.url}music/${mus.file}?v=${mus.version || 0}`;
|
|
159
|
+
try {
|
|
160
|
+
const musicInstance = new Music(this.audioCore, url);
|
|
161
|
+
this.collections.music[name] = musicInstance;
|
|
162
|
+
resolve();
|
|
163
|
+
} catch (err) {
|
|
164
|
+
this.listener?.log?.(`[AssetLoader] Failed to load music "${name}": ${String(err)}`);
|
|
165
|
+
this.collections.music[name] = new Music(this.audioCore, url);
|
|
166
|
+
resolve();
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
await Promise.all(promises);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Load generic assets
|
|
174
|
+
*/
|
|
175
|
+
async loadGenericAssets() {
|
|
176
|
+
if (!this.resources.assets) return;
|
|
177
|
+
for (const asset of this.resources.assets) {
|
|
178
|
+
const name = asset.file.split(".")[0].replace(/-/g, "/");
|
|
179
|
+
this.collections.assets[name] = {
|
|
180
|
+
name,
|
|
181
|
+
file: asset.file,
|
|
182
|
+
version: asset.version
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Check if all assets are ready
|
|
188
|
+
*/
|
|
189
|
+
isReady() {
|
|
190
|
+
return this.countReady() === this.countTotal();
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Get loading progress (0-1)
|
|
194
|
+
*/
|
|
195
|
+
getProgress() {
|
|
196
|
+
const total = this.countTotal();
|
|
197
|
+
if (total === 0) return 1;
|
|
198
|
+
return this.countReady() / total;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Count total assets
|
|
202
|
+
*/
|
|
203
|
+
countTotal() {
|
|
204
|
+
let count = 0;
|
|
205
|
+
count += Object.keys(this.collections.sprites).length;
|
|
206
|
+
count += Object.keys(this.collections.maps).length;
|
|
207
|
+
count += Object.keys(this.collections.sounds).length;
|
|
208
|
+
count += Object.keys(this.collections.music).length;
|
|
209
|
+
return count;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Count ready assets
|
|
213
|
+
*/
|
|
214
|
+
countReady() {
|
|
215
|
+
let ready = 0;
|
|
216
|
+
for (const sprite of Object.values(this.collections.sprites)) {
|
|
217
|
+
if (sprite.ready) ready++;
|
|
218
|
+
}
|
|
219
|
+
for (const map of Object.values(this.collections.maps)) {
|
|
220
|
+
if (map.ready) ready++;
|
|
221
|
+
}
|
|
222
|
+
for (const sound of Object.values(this.collections.sounds)) {
|
|
223
|
+
if (sound.ready) ready++;
|
|
224
|
+
}
|
|
225
|
+
for (const mus of Object.values(this.collections.music)) {
|
|
226
|
+
if (mus.ready) ready++;
|
|
227
|
+
}
|
|
228
|
+
return ready;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Show loading bar on screen
|
|
232
|
+
*/
|
|
233
|
+
showLoadingBar(screenInterface) {
|
|
234
|
+
if (this.loadingBarTime && Date.now() < this.loadingBarTime + LOADING_BAR_THROTTLE_MS) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
this.loadingBarTime = Date.now();
|
|
238
|
+
const progress = this.getProgress();
|
|
239
|
+
screenInterface.clear("#000");
|
|
240
|
+
screenInterface.drawRect(0, 0, 100, 10, "#DDD");
|
|
241
|
+
screenInterface.fillRect(-(1 - progress) * 48, 0, progress * 96, 6, "#DDD");
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Get loaded collections
|
|
245
|
+
*/
|
|
246
|
+
getCollections() {
|
|
247
|
+
return this.collections;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
// src/hot-reload/updater.ts
|
|
252
|
+
var SourceUpdater = class {
|
|
253
|
+
static {
|
|
254
|
+
__name(this, "SourceUpdater");
|
|
255
|
+
}
|
|
256
|
+
vm;
|
|
257
|
+
listener;
|
|
258
|
+
audio;
|
|
259
|
+
screen;
|
|
260
|
+
reportWarnings;
|
|
261
|
+
emitBridgeEvent;
|
|
262
|
+
updateMemory = {};
|
|
263
|
+
previousInit = null;
|
|
264
|
+
constructor(vm, listener, audio, screen, reportWarnings2, emitBridgeEvent) {
|
|
265
|
+
this.vm = vm;
|
|
266
|
+
this.listener = listener;
|
|
267
|
+
this.audio = audio;
|
|
268
|
+
this.screen = screen;
|
|
269
|
+
this.reportWarnings = reportWarnings2;
|
|
270
|
+
this.emitBridgeEvent = emitBridgeEvent;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Update source code (hot reload)
|
|
274
|
+
*/
|
|
275
|
+
updateSource(file, src, reinit = false) {
|
|
276
|
+
if (!this.vm) return false;
|
|
277
|
+
if (src === this.updateMemory[file]) return false;
|
|
278
|
+
this.updateMemory[file] = src;
|
|
279
|
+
if (this.audio) {
|
|
280
|
+
this.audio.cancelBeeps();
|
|
281
|
+
}
|
|
282
|
+
if (this.screen) {
|
|
283
|
+
this.screen.clear();
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
this.vm.run(src, 3e3, file);
|
|
287
|
+
if (this.emitBridgeEvent) {
|
|
288
|
+
this.emitBridgeEvent("compile_success", {
|
|
289
|
+
file
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
if (this.reportWarnings) {
|
|
293
|
+
this.reportWarnings();
|
|
294
|
+
}
|
|
295
|
+
if (this.vm.error_info) {
|
|
296
|
+
const err = Object.assign({}, this.vm.error_info);
|
|
297
|
+
err.type = "init";
|
|
298
|
+
err.file = file;
|
|
299
|
+
this.listener.reportError?.(err);
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
if (this.vm.runner?.getFunctionSource) {
|
|
303
|
+
const init = this.vm.runner.getFunctionSource("init");
|
|
304
|
+
if (init && init !== this.previousInit && reinit) {
|
|
305
|
+
this.previousInit = init;
|
|
306
|
+
this.vm.call("init");
|
|
307
|
+
if (this.vm.error_info) {
|
|
308
|
+
const err = Object.assign({}, this.vm.error_info);
|
|
309
|
+
err.type = "init";
|
|
310
|
+
this.listener.reportError?.(err);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return true;
|
|
315
|
+
} catch (err) {
|
|
316
|
+
err.file = file;
|
|
317
|
+
this.listener.reportError?.(err);
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
// src/input/manager.ts
|
|
324
|
+
import { GamepadInput, KeyboardInput, MouseInput, TouchInput } from "@al8b/input";
|
|
325
|
+
var InputManager = class {
|
|
326
|
+
static {
|
|
327
|
+
__name(this, "InputManager");
|
|
328
|
+
}
|
|
329
|
+
keyboard;
|
|
330
|
+
mouse;
|
|
331
|
+
touch;
|
|
332
|
+
gamepad;
|
|
333
|
+
constructor(canvas) {
|
|
334
|
+
this.keyboard = new KeyboardInput();
|
|
335
|
+
this.mouse = new MouseInput();
|
|
336
|
+
this.touch = new TouchInput(this.mouse);
|
|
337
|
+
this.gamepad = new GamepadInput();
|
|
338
|
+
if (canvas) {
|
|
339
|
+
this.attachCanvas(canvas);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Attach input systems to canvas
|
|
344
|
+
*/
|
|
345
|
+
attachCanvas(canvas) {
|
|
346
|
+
this.mouse.setCanvas(canvas);
|
|
347
|
+
this.touch.setCanvas(canvas);
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Update all input systems (call each frame)
|
|
351
|
+
*/
|
|
352
|
+
update() {
|
|
353
|
+
this.keyboard.update();
|
|
354
|
+
this.mouse.update();
|
|
355
|
+
this.touch.update();
|
|
356
|
+
this.gamepad.update();
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Get input states for game code
|
|
360
|
+
*/
|
|
361
|
+
getStates() {
|
|
362
|
+
return {
|
|
363
|
+
keyboard: this.keyboard.state,
|
|
364
|
+
mouse: this.mouse.state,
|
|
365
|
+
touch: this.touch.state,
|
|
366
|
+
gamepad: this.gamepad.status
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// src/loop/game-loop.ts
|
|
372
|
+
var GameLoop = class {
|
|
373
|
+
static {
|
|
374
|
+
__name(this, "GameLoop");
|
|
375
|
+
}
|
|
376
|
+
callbacks;
|
|
377
|
+
state;
|
|
378
|
+
stopped = false;
|
|
379
|
+
animationFrameId = null;
|
|
380
|
+
constructor(callbacks) {
|
|
381
|
+
this.callbacks = callbacks;
|
|
382
|
+
this.state = {
|
|
383
|
+
currentFrame: 0,
|
|
384
|
+
floatingFrame: 0,
|
|
385
|
+
dt: FRAME_TIME_MS,
|
|
386
|
+
lastTime: performance.now(),
|
|
387
|
+
fps: DEFAULT_FPS,
|
|
388
|
+
updateRate: DEFAULT_UPDATE_RATE
|
|
389
|
+
};
|
|
390
|
+
this.loop = this.loop.bind(this);
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Start the game loop
|
|
394
|
+
*/
|
|
395
|
+
start() {
|
|
396
|
+
this.stopped = false;
|
|
397
|
+
this.state.lastTime = performance.now();
|
|
398
|
+
this.state.currentFrame = 0;
|
|
399
|
+
this.state.floatingFrame = 0;
|
|
400
|
+
this.loop();
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Stop the game loop
|
|
404
|
+
*/
|
|
405
|
+
stop() {
|
|
406
|
+
this.stopped = true;
|
|
407
|
+
if (this.animationFrameId !== null) {
|
|
408
|
+
cancelAnimationFrame(this.animationFrameId);
|
|
409
|
+
this.animationFrameId = null;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Resume the game loop
|
|
414
|
+
*/
|
|
415
|
+
resume() {
|
|
416
|
+
if (!this.stopped) return;
|
|
417
|
+
this.stopped = false;
|
|
418
|
+
this.state.lastTime = performance.now();
|
|
419
|
+
this.loop();
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Main game loop
|
|
423
|
+
*/
|
|
424
|
+
loop() {
|
|
425
|
+
if (this.stopped) return;
|
|
426
|
+
this.animationFrameId = requestAnimationFrame(this.loop);
|
|
427
|
+
const time = performance.now();
|
|
428
|
+
if (Math.abs(time - this.state.lastTime) > PAUSE_THRESHOLD_MS) {
|
|
429
|
+
this.state.lastTime = time - LOADING_BAR_THROTTLE_MS;
|
|
430
|
+
}
|
|
431
|
+
const dt = time - this.state.lastTime;
|
|
432
|
+
this.state.dt = this.state.dt * 0.9 + dt * 0.1;
|
|
433
|
+
this.state.lastTime = time;
|
|
434
|
+
const fps = Math.round(1e3 / this.state.dt);
|
|
435
|
+
this.state.fps = fps;
|
|
436
|
+
if (this.callbacks.setFPS) {
|
|
437
|
+
this.callbacks.setFPS(fps);
|
|
438
|
+
}
|
|
439
|
+
let updateRate = this.state.updateRate;
|
|
440
|
+
if (this.callbacks.getUpdateRate) {
|
|
441
|
+
const rate = this.callbacks.getUpdateRate();
|
|
442
|
+
if (rate != null && rate > 0 && Number.isFinite(rate)) {
|
|
443
|
+
updateRate = rate;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
this.state.floatingFrame += this.state.dt * updateRate / 1e3;
|
|
447
|
+
let ds = Math.min(10, Math.round(this.state.floatingFrame - this.state.currentFrame));
|
|
448
|
+
if ((ds === 0 || ds === 2) && updateRate === DEFAULT_UPDATE_RATE && Math.abs(fps - DEFAULT_FPS) < 2) {
|
|
449
|
+
ds = 1;
|
|
450
|
+
this.state.floatingFrame = this.state.currentFrame + 1;
|
|
451
|
+
}
|
|
452
|
+
for (let i = 1; i <= ds; i++) {
|
|
453
|
+
this.callbacks.onUpdate();
|
|
454
|
+
if (i < ds && this.callbacks.onTick) {
|
|
455
|
+
this.callbacks.onTick();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
this.state.currentFrame += ds;
|
|
459
|
+
this.callbacks.onDraw();
|
|
460
|
+
if (this.callbacks.onTick) {
|
|
461
|
+
this.callbacks.onTick();
|
|
462
|
+
}
|
|
463
|
+
if (ds > 0 && this.callbacks.onWatchStep) {
|
|
464
|
+
this.callbacks.onWatchStep();
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Get current state
|
|
469
|
+
*/
|
|
470
|
+
getState() {
|
|
471
|
+
return this.state;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Set update rate
|
|
475
|
+
*/
|
|
476
|
+
setUpdateRate(rate) {
|
|
477
|
+
if (rate > 0 && Number.isFinite(rate)) {
|
|
478
|
+
this.state.updateRate = rate;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Get FPS
|
|
483
|
+
*/
|
|
484
|
+
getFPS() {
|
|
485
|
+
return this.state.fps;
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
// src/system/api.ts
|
|
490
|
+
var System = class {
|
|
491
|
+
static {
|
|
492
|
+
__name(this, "System");
|
|
493
|
+
}
|
|
494
|
+
systemAPI;
|
|
495
|
+
constructor() {
|
|
496
|
+
this.systemAPI = {
|
|
497
|
+
// Time
|
|
498
|
+
get time() {
|
|
499
|
+
return Date.now();
|
|
500
|
+
},
|
|
501
|
+
// FPS
|
|
502
|
+
fps: DEFAULT_FPS,
|
|
503
|
+
// CPU load
|
|
504
|
+
cpu_load: 0,
|
|
505
|
+
// Update rate
|
|
506
|
+
update_rate: DEFAULT_UPDATE_RATE,
|
|
507
|
+
// Language
|
|
508
|
+
get language() {
|
|
509
|
+
return typeof navigator !== "undefined" ? navigator.language : "en";
|
|
510
|
+
},
|
|
511
|
+
// Input availability
|
|
512
|
+
inputs: {
|
|
513
|
+
get keyboard() {
|
|
514
|
+
return 1;
|
|
515
|
+
},
|
|
516
|
+
get mouse() {
|
|
517
|
+
return 1;
|
|
518
|
+
},
|
|
519
|
+
get touch() {
|
|
520
|
+
return typeof window !== "undefined" && "ontouchstart" in window ? 1 : 0;
|
|
521
|
+
},
|
|
522
|
+
get gamepad() {
|
|
523
|
+
return typeof navigator !== "undefined" && typeof navigator.getGamepads === "function" ? 1 : 0;
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
// Loading progress
|
|
527
|
+
loading: 0,
|
|
528
|
+
// Utility functions
|
|
529
|
+
prompt: /* @__PURE__ */ __name((text, callback) => {
|
|
530
|
+
if (typeof window !== "undefined") {
|
|
531
|
+
const result = window.prompt(text);
|
|
532
|
+
if (result !== null && callback) {
|
|
533
|
+
callback(result);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}, "prompt"),
|
|
537
|
+
say: /* @__PURE__ */ __name((text) => {
|
|
538
|
+
if (typeof window !== "undefined") {
|
|
539
|
+
window.alert(text);
|
|
540
|
+
}
|
|
541
|
+
}, "say"),
|
|
542
|
+
// File drop support
|
|
543
|
+
file: {
|
|
544
|
+
dropped: 0
|
|
545
|
+
},
|
|
546
|
+
// JavaScript interop (placeholder for future use)
|
|
547
|
+
javascript: {},
|
|
548
|
+
// Additional flags
|
|
549
|
+
disable_autofullscreen: 0,
|
|
550
|
+
preemptive: 1
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Get system API for game code
|
|
555
|
+
*/
|
|
556
|
+
getAPI() {
|
|
557
|
+
return this.systemAPI;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Update FPS
|
|
561
|
+
*/
|
|
562
|
+
setFPS(fps) {
|
|
563
|
+
this.systemAPI.fps = fps;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Update CPU load
|
|
567
|
+
*/
|
|
568
|
+
setCPULoad(load) {
|
|
569
|
+
this.systemAPI.cpu_load = load;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Update loading progress
|
|
573
|
+
*/
|
|
574
|
+
setLoading(progress) {
|
|
575
|
+
this.systemAPI.loading = progress;
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
// src/core/debug-logger.ts
|
|
580
|
+
var DebugLogger = class {
|
|
581
|
+
static {
|
|
582
|
+
__name(this, "DebugLogger");
|
|
583
|
+
}
|
|
584
|
+
lastInputDebug;
|
|
585
|
+
lastScreenDebug;
|
|
586
|
+
/**
|
|
587
|
+
* Log input state changes (deduplication via shallow compare)
|
|
588
|
+
*/
|
|
589
|
+
debugInputs(input, debug) {
|
|
590
|
+
if (!debug?.input) return;
|
|
591
|
+
const snapshot = this.createInputSnapshot(input, debug.input);
|
|
592
|
+
if (!snapshot) return;
|
|
593
|
+
if (this.lastInputDebug && shallowEqual(snapshot, this.lastInputDebug)) return;
|
|
594
|
+
this.lastInputDebug = snapshot;
|
|
595
|
+
console.debug("[@al8b/runtime][input]", snapshot);
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Log screen dimension changes
|
|
599
|
+
*/
|
|
600
|
+
debugScreen(screen, debug) {
|
|
601
|
+
if (!debug?.screen) return;
|
|
602
|
+
const canvas = screen.getCanvas();
|
|
603
|
+
const current = {
|
|
604
|
+
width: screen.width,
|
|
605
|
+
height: screen.height,
|
|
606
|
+
canvasWidth: canvas.width,
|
|
607
|
+
canvasHeight: canvas.height
|
|
608
|
+
};
|
|
609
|
+
if (this.lastScreenDebug && current.width === this.lastScreenDebug.width && current.height === this.lastScreenDebug.height && current.canvasWidth === this.lastScreenDebug.canvasWidth && current.canvasHeight === this.lastScreenDebug.canvasHeight) {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
this.lastScreenDebug = current;
|
|
613
|
+
console.debug("[@al8b/runtime][screen]", {
|
|
614
|
+
screen: {
|
|
615
|
+
width: screen.width,
|
|
616
|
+
height: screen.height
|
|
617
|
+
},
|
|
618
|
+
canvas: {
|
|
619
|
+
width: canvas.width,
|
|
620
|
+
height: canvas.height,
|
|
621
|
+
clientWidth: canvas.clientWidth,
|
|
622
|
+
clientHeight: canvas.clientHeight,
|
|
623
|
+
style: {
|
|
624
|
+
width: canvas.style.width,
|
|
625
|
+
height: canvas.style.height
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Create a snapshot of current input state based on enabled channels
|
|
632
|
+
*/
|
|
633
|
+
createInputSnapshot(input, setting) {
|
|
634
|
+
const channels = getEnabledInputChannels(setting);
|
|
635
|
+
if (channels.length === 0) return null;
|
|
636
|
+
const states = input.getStates();
|
|
637
|
+
const snapshot = {};
|
|
638
|
+
if (channels.includes("touch")) {
|
|
639
|
+
snapshot.touch = {
|
|
640
|
+
touching: states.touch.touching,
|
|
641
|
+
press: states.touch.press,
|
|
642
|
+
release: states.touch.release,
|
|
643
|
+
x: Number(states.touch.x?.toFixed?.(2) ?? states.touch.x),
|
|
644
|
+
y: Number(states.touch.y?.toFixed?.(2) ?? states.touch.y),
|
|
645
|
+
count: states.touch.touches?.length ?? 0
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
if (channels.includes("mouse")) {
|
|
649
|
+
snapshot.mouse = {
|
|
650
|
+
pressed: states.mouse.pressed,
|
|
651
|
+
left: states.mouse.left,
|
|
652
|
+
x: Number(states.mouse.x?.toFixed?.(2) ?? states.mouse.x),
|
|
653
|
+
y: Number(states.mouse.y?.toFixed?.(2) ?? states.mouse.y),
|
|
654
|
+
wheel: states.mouse.wheel
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
if (channels.includes("keyboard")) {
|
|
658
|
+
snapshot.keyboard = {
|
|
659
|
+
UP: states.keyboard.UP,
|
|
660
|
+
DOWN: states.keyboard.DOWN,
|
|
661
|
+
LEFT: states.keyboard.LEFT,
|
|
662
|
+
RIGHT: states.keyboard.RIGHT,
|
|
663
|
+
press: states.keyboard.press,
|
|
664
|
+
release: states.keyboard.release
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
if (channels.includes("gamepad")) {
|
|
668
|
+
snapshot.gamepad = {
|
|
669
|
+
count: input.gamepad.count,
|
|
670
|
+
A: states.gamepad.A,
|
|
671
|
+
B: states.gamepad.B,
|
|
672
|
+
UP: states.gamepad.UP,
|
|
673
|
+
DOWN: states.gamepad.DOWN,
|
|
674
|
+
LEFT: states.gamepad.LEFT,
|
|
675
|
+
RIGHT: states.gamepad.RIGHT
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
return Object.keys(snapshot).length === 0 ? null : snapshot;
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
function getEnabledInputChannels(setting) {
|
|
682
|
+
if (typeof setting === "boolean") {
|
|
683
|
+
return setting ? [
|
|
684
|
+
"keyboard",
|
|
685
|
+
"mouse",
|
|
686
|
+
"touch",
|
|
687
|
+
"gamepad"
|
|
688
|
+
] : [];
|
|
689
|
+
}
|
|
690
|
+
const channels = [];
|
|
691
|
+
if (setting.keyboard) channels.push("keyboard");
|
|
692
|
+
if (setting.mouse) channels.push("mouse");
|
|
693
|
+
if (setting.touch) channels.push("touch");
|
|
694
|
+
if (setting.gamepad) channels.push("gamepad");
|
|
695
|
+
return channels;
|
|
696
|
+
}
|
|
697
|
+
__name(getEnabledInputChannels, "getEnabledInputChannels");
|
|
698
|
+
function shallowEqual(obj1, obj2) {
|
|
699
|
+
if (obj1 === obj2) return true;
|
|
700
|
+
if (!obj1 || !obj2 || typeof obj1 !== "object" || typeof obj2 !== "object") return false;
|
|
701
|
+
const keys1 = Object.keys(obj1);
|
|
702
|
+
const keys2 = Object.keys(obj2);
|
|
703
|
+
if (keys1.length !== keys2.length) return false;
|
|
704
|
+
for (const key of keys1) {
|
|
705
|
+
const val1 = obj1[key];
|
|
706
|
+
const val2 = obj2[key];
|
|
707
|
+
if (val1 === val2) continue;
|
|
708
|
+
if (val1 == null || val2 == null) {
|
|
709
|
+
if (val1 !== val2) return false;
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
if (typeof val1 === "object" && typeof val2 === "object") {
|
|
713
|
+
const keys1Nested = Object.keys(val1);
|
|
714
|
+
const keys2Nested = Object.keys(val2);
|
|
715
|
+
if (keys1Nested.length !== keys2Nested.length) return false;
|
|
716
|
+
for (const nestedKey of keys1Nested) {
|
|
717
|
+
if (val1[nestedKey] !== val2[nestedKey]) return false;
|
|
718
|
+
}
|
|
719
|
+
} else {
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return true;
|
|
724
|
+
}
|
|
725
|
+
__name(shallowEqual, "shallowEqual");
|
|
726
|
+
|
|
727
|
+
// src/core/error-handler.ts
|
|
728
|
+
import { createDiagnostic, formatForBrowser } from "@al8b/diagnostics";
|
|
729
|
+
function formatRuntimeError(error) {
|
|
730
|
+
if (error.code || error.context || error.suggestions) {
|
|
731
|
+
return error;
|
|
732
|
+
}
|
|
733
|
+
const code = error.code || "E2005";
|
|
734
|
+
const diagnostic = createDiagnostic(code, {
|
|
735
|
+
file: error.file,
|
|
736
|
+
line: error.line,
|
|
737
|
+
column: error.column,
|
|
738
|
+
context: error.context,
|
|
739
|
+
suggestions: error.suggestions,
|
|
740
|
+
related: error.related,
|
|
741
|
+
stackTrace: error.stackTrace,
|
|
742
|
+
data: {
|
|
743
|
+
error: error.error || error.message
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
const formattedMessage = formatForBrowser(diagnostic);
|
|
747
|
+
return {
|
|
748
|
+
...error,
|
|
749
|
+
...diagnostic,
|
|
750
|
+
formatted: formattedMessage
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
__name(formatRuntimeError, "formatRuntimeError");
|
|
754
|
+
function reportError(listener, error) {
|
|
755
|
+
if (listener.reportError) {
|
|
756
|
+
const formatted = formatRuntimeError(error);
|
|
757
|
+
listener.reportError(formatted);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
__name(reportError, "reportError");
|
|
761
|
+
function reportWarnings(vm, listener) {
|
|
762
|
+
if (!vm) return;
|
|
763
|
+
const warnings = vm.context?.warnings;
|
|
764
|
+
if (!warnings) return;
|
|
765
|
+
if (warnings.invoking_non_function) {
|
|
766
|
+
for (const value of Object.values(warnings.invoking_non_function)) {
|
|
767
|
+
const warning = value;
|
|
768
|
+
if (!warning.reported) {
|
|
769
|
+
warning.reported = true;
|
|
770
|
+
reportError(listener, {
|
|
771
|
+
error: "",
|
|
772
|
+
type: "non_function",
|
|
773
|
+
expression: warning.expression,
|
|
774
|
+
line: warning.line,
|
|
775
|
+
column: warning.column,
|
|
776
|
+
file: warning.file
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
if (warnings.using_undefined_variable) {
|
|
782
|
+
for (const value of Object.values(warnings.using_undefined_variable)) {
|
|
783
|
+
const warning = value;
|
|
784
|
+
if (!warning.reported) {
|
|
785
|
+
warning.reported = true;
|
|
786
|
+
reportError(listener, {
|
|
787
|
+
error: "",
|
|
788
|
+
type: "undefined_variable",
|
|
789
|
+
expression: warning.expression,
|
|
790
|
+
line: warning.line,
|
|
791
|
+
column: warning.column,
|
|
792
|
+
file: warning.file
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
__name(reportWarnings, "reportWarnings");
|
|
799
|
+
|
|
800
|
+
// src/core/assets-registry.ts
|
|
801
|
+
var RuntimeAssetsRegistry = class {
|
|
802
|
+
static {
|
|
803
|
+
__name(this, "RuntimeAssetsRegistry");
|
|
804
|
+
}
|
|
805
|
+
collections = {
|
|
806
|
+
sprites: {},
|
|
807
|
+
maps: {},
|
|
808
|
+
sounds: {},
|
|
809
|
+
music: {},
|
|
810
|
+
assets: {}
|
|
811
|
+
};
|
|
812
|
+
replace(collections) {
|
|
813
|
+
this.collections = collections;
|
|
814
|
+
}
|
|
815
|
+
getCollections() {
|
|
816
|
+
return this.collections;
|
|
817
|
+
}
|
|
818
|
+
get sprites() {
|
|
819
|
+
return this.collections.sprites;
|
|
820
|
+
}
|
|
821
|
+
get maps() {
|
|
822
|
+
return this.collections.maps;
|
|
823
|
+
}
|
|
824
|
+
get sounds() {
|
|
825
|
+
return this.collections.sounds;
|
|
826
|
+
}
|
|
827
|
+
get music() {
|
|
828
|
+
return this.collections.music;
|
|
829
|
+
}
|
|
830
|
+
get assets() {
|
|
831
|
+
return this.collections.assets;
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
|
|
835
|
+
// src/core/api-factory.ts
|
|
836
|
+
import { Palette } from "@al8b/palette";
|
|
837
|
+
import { Random, Routine } from "@al8b/vm";
|
|
838
|
+
|
|
839
|
+
// src/utils/object-pool.ts
|
|
840
|
+
var ObjectPool = class {
|
|
841
|
+
static {
|
|
842
|
+
__name(this, "ObjectPool");
|
|
843
|
+
}
|
|
844
|
+
pool = [];
|
|
845
|
+
factory;
|
|
846
|
+
reset;
|
|
847
|
+
maxSize;
|
|
848
|
+
/**
|
|
849
|
+
* Create a new object pool
|
|
850
|
+
*
|
|
851
|
+
* @param factory - Function to create new objects
|
|
852
|
+
* @param reset - Function to reset object state for reuse
|
|
853
|
+
* @param maxSize - Maximum pool size (default: 100)
|
|
854
|
+
*/
|
|
855
|
+
constructor(factory, reset, maxSize = 100) {
|
|
856
|
+
this.factory = factory;
|
|
857
|
+
this.reset = reset;
|
|
858
|
+
this.maxSize = maxSize;
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Acquire an object from the pool
|
|
862
|
+
*
|
|
863
|
+
* Returns a new object if pool is empty, otherwise reuses a pooled object.
|
|
864
|
+
*
|
|
865
|
+
* @returns Object from pool or newly created
|
|
866
|
+
*/
|
|
867
|
+
acquire() {
|
|
868
|
+
if (this.pool.length > 0) {
|
|
869
|
+
return this.pool.pop();
|
|
870
|
+
}
|
|
871
|
+
return this.factory();
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Release an object back to the pool
|
|
875
|
+
*
|
|
876
|
+
* Resets the object and adds it back to the pool for reuse.
|
|
877
|
+
* If pool is at max size, the object is discarded.
|
|
878
|
+
*
|
|
879
|
+
* @param obj - Object to release
|
|
880
|
+
*/
|
|
881
|
+
release(obj) {
|
|
882
|
+
if (this.pool.length >= this.maxSize) {
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
this.reset(obj);
|
|
886
|
+
this.pool.push(obj);
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Clear all objects from the pool
|
|
890
|
+
*/
|
|
891
|
+
clear() {
|
|
892
|
+
this.pool = [];
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Get current pool size
|
|
896
|
+
*
|
|
897
|
+
* @returns Number of objects in pool
|
|
898
|
+
*/
|
|
899
|
+
size() {
|
|
900
|
+
return this.pool.length;
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Get maximum pool size
|
|
904
|
+
*
|
|
905
|
+
* @returns Maximum pool size
|
|
906
|
+
*/
|
|
907
|
+
getMaxSize() {
|
|
908
|
+
return this.maxSize;
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Set maximum pool size
|
|
912
|
+
*
|
|
913
|
+
* @param maxSize - New maximum pool size
|
|
914
|
+
*/
|
|
915
|
+
setMaxSize(maxSize) {
|
|
916
|
+
this.maxSize = maxSize;
|
|
917
|
+
if (this.pool.length > maxSize) {
|
|
918
|
+
this.pool = this.pool.slice(0, maxSize);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
// src/core/api-factory.ts
|
|
924
|
+
function createRuntimeMeta(context) {
|
|
925
|
+
return {
|
|
926
|
+
print: /* @__PURE__ */ __name((text) => {
|
|
927
|
+
const vm = context.getVM();
|
|
928
|
+
if ((typeof text === "object" || typeof text === "function") && vm) {
|
|
929
|
+
text = vm.toString(text);
|
|
930
|
+
}
|
|
931
|
+
if (context.listener.log) {
|
|
932
|
+
context.listener.log(String(text));
|
|
933
|
+
} else {
|
|
934
|
+
console.log(text);
|
|
935
|
+
}
|
|
936
|
+
}, "print")
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
__name(createRuntimeMeta, "createRuntimeMeta");
|
|
940
|
+
function createRuntimeGlobalApi(context) {
|
|
941
|
+
const inputStates = context.input.getStates();
|
|
942
|
+
const session = {
|
|
943
|
+
user: /* @__PURE__ */ __name(() => cloneValue(context.getSessionSnapshot()?.user ?? null), "user"),
|
|
944
|
+
player: /* @__PURE__ */ __name(() => cloneValue(context.getSessionSnapshot()?.player ?? null), "player"),
|
|
945
|
+
game: /* @__PURE__ */ __name(() => cloneValue(context.getSessionSnapshot()?.game ?? null), "game"),
|
|
946
|
+
room: /* @__PURE__ */ __name(() => cloneValue(context.getSessionSnapshot()?.room ?? null), "room")
|
|
947
|
+
};
|
|
948
|
+
const host = {
|
|
949
|
+
emit: /* @__PURE__ */ __name((name, payload) => {
|
|
950
|
+
context.sendHostEvent({
|
|
951
|
+
type: name,
|
|
952
|
+
payload,
|
|
953
|
+
source: "host"
|
|
954
|
+
});
|
|
955
|
+
}, "emit"),
|
|
956
|
+
request: /* @__PURE__ */ __name((name, payload, callback) => context.sendHostRequest(name, payload, callback), "request")
|
|
957
|
+
};
|
|
958
|
+
const memory = {
|
|
959
|
+
export: /* @__PURE__ */ __name(() => context.exportSnapshot(), "export"),
|
|
960
|
+
import: /* @__PURE__ */ __name((snapshot) => context.importSnapshot(snapshot), "import"),
|
|
961
|
+
reset: /* @__PURE__ */ __name((options) => context.resetRuntime(options), "reset"),
|
|
962
|
+
save: /* @__PURE__ */ __name((meta, callback) => context.saveSnapshot(meta, callback), "save"),
|
|
963
|
+
load: /* @__PURE__ */ __name((meta, callback) => context.loadSnapshot(meta, callback), "load")
|
|
964
|
+
};
|
|
965
|
+
return {
|
|
966
|
+
screen: context.screen.getInterface(),
|
|
967
|
+
audio: context.audio.getInterface(),
|
|
968
|
+
keyboard: inputStates.keyboard,
|
|
969
|
+
mouse: inputStates.mouse,
|
|
970
|
+
touch: inputStates.touch,
|
|
971
|
+
gamepad: inputStates.gamepad,
|
|
972
|
+
sprites: context.assets.sprites,
|
|
973
|
+
maps: context.assets.maps,
|
|
974
|
+
sounds: context.assets.sounds,
|
|
975
|
+
music: context.assets.music,
|
|
976
|
+
assets: context.assets.assets,
|
|
977
|
+
player: context.playerService.getInterface(),
|
|
978
|
+
host,
|
|
979
|
+
session,
|
|
980
|
+
memory,
|
|
981
|
+
system: context.system.getAPI(),
|
|
982
|
+
scene: /* @__PURE__ */ __name((name, definition) => {
|
|
983
|
+
const convertedDefinition = convertSceneDefinition(asSceneDefinition(definition), context.getVM(), context.listener);
|
|
984
|
+
context.sceneManager.registerScene(name, convertedDefinition);
|
|
985
|
+
}, "scene"),
|
|
986
|
+
route: /* @__PURE__ */ __name((path, sceneName) => context.sceneManager.registerRoute(path, sceneName), "route"),
|
|
987
|
+
router: context.sceneManager.router.getInterface(),
|
|
988
|
+
Image,
|
|
989
|
+
Sprite,
|
|
990
|
+
TileMap,
|
|
991
|
+
Sound,
|
|
992
|
+
Palette,
|
|
993
|
+
Random,
|
|
994
|
+
ObjectPool
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
__name(createRuntimeGlobalApi, "createRuntimeGlobalApi");
|
|
998
|
+
function convertSceneDefinition(definition, vm, listener) {
|
|
999
|
+
if (!vm?.runner?.main_thread?.processor) {
|
|
1000
|
+
listener.log?.("[RuntimeController] VM not ready for scene conversion. Scene functions may not work correctly.");
|
|
1001
|
+
return definition;
|
|
1002
|
+
}
|
|
1003
|
+
const processor = vm.runner.main_thread.processor;
|
|
1004
|
+
const context = vm.context;
|
|
1005
|
+
const converted = {};
|
|
1006
|
+
for (const [key, value] of Object.entries(definition)) {
|
|
1007
|
+
if (value instanceof Routine) {
|
|
1008
|
+
converted[key] = processor.routineAsFunction(value, context);
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
1012
|
+
converted[key] = convertSceneDefinition(value, vm, listener);
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
converted[key] = value;
|
|
1016
|
+
}
|
|
1017
|
+
return converted;
|
|
1018
|
+
}
|
|
1019
|
+
__name(convertSceneDefinition, "convertSceneDefinition");
|
|
1020
|
+
function asSceneDefinition(definition) {
|
|
1021
|
+
if (!definition || typeof definition !== "object" || Array.isArray(definition)) {
|
|
1022
|
+
throw new Error("Scene definition must be an object.");
|
|
1023
|
+
}
|
|
1024
|
+
return definition;
|
|
1025
|
+
}
|
|
1026
|
+
__name(asSceneDefinition, "asSceneDefinition");
|
|
1027
|
+
function cloneValue(value) {
|
|
1028
|
+
if (value == null) {
|
|
1029
|
+
return value;
|
|
1030
|
+
}
|
|
1031
|
+
return JSON.parse(JSON.stringify(value));
|
|
1032
|
+
}
|
|
1033
|
+
__name(cloneValue, "cloneValue");
|
|
1034
|
+
|
|
1035
|
+
// src/core/controller.ts
|
|
1036
|
+
function createRuntime(options = {}) {
|
|
1037
|
+
return new RuntimeControllerImpl(options);
|
|
1038
|
+
}
|
|
1039
|
+
__name(createRuntime, "createRuntime");
|
|
1040
|
+
var RuntimeControllerImpl = class {
|
|
1041
|
+
static {
|
|
1042
|
+
__name(this, "RuntimeControllerImpl");
|
|
1043
|
+
}
|
|
1044
|
+
options;
|
|
1045
|
+
listener;
|
|
1046
|
+
assetRegistry = new RuntimeAssetsRegistry();
|
|
1047
|
+
assetLoader;
|
|
1048
|
+
debugLogger = new DebugLogger();
|
|
1049
|
+
DEBUG_UPDATE_FREQUENCY = 10;
|
|
1050
|
+
snapshotRestorer = new StatePlayer();
|
|
1051
|
+
bridgeUnsubscribe = null;
|
|
1052
|
+
sourceUpdater = null;
|
|
1053
|
+
gameLoop = null;
|
|
1054
|
+
frameCount = 0;
|
|
1055
|
+
lastUpdateRate = -1;
|
|
1056
|
+
isStopped = false;
|
|
1057
|
+
preserveStorageOnNextBoot;
|
|
1058
|
+
sessionSnapshot;
|
|
1059
|
+
screen;
|
|
1060
|
+
audio;
|
|
1061
|
+
input;
|
|
1062
|
+
system;
|
|
1063
|
+
playerService;
|
|
1064
|
+
sceneManager;
|
|
1065
|
+
vm = null;
|
|
1066
|
+
timeMachine = null;
|
|
1067
|
+
constructor(options = {}) {
|
|
1068
|
+
this.options = options;
|
|
1069
|
+
this.listener = options.listener || {};
|
|
1070
|
+
this.preserveStorageOnNextBoot = options.preserveStorage || false;
|
|
1071
|
+
this.sessionSnapshot = options.initialSession || null;
|
|
1072
|
+
this.screen = new Screen({
|
|
1073
|
+
runtime: this,
|
|
1074
|
+
canvas: options.canvas,
|
|
1075
|
+
width: options.width || 400,
|
|
1076
|
+
height: options.height || 400
|
|
1077
|
+
});
|
|
1078
|
+
this.audio = new AudioCore(this);
|
|
1079
|
+
this.input = new InputManager(this.screen.getCanvas());
|
|
1080
|
+
this.system = new System();
|
|
1081
|
+
this.playerService = new PlayerService({
|
|
1082
|
+
pause: /* @__PURE__ */ __name(() => this.stop(), "pause"),
|
|
1083
|
+
resume: /* @__PURE__ */ __name(() => this.resume(), "resume"),
|
|
1084
|
+
postMessage: /* @__PURE__ */ __name((message) => this.emitPlayerMessage(message), "postMessage"),
|
|
1085
|
+
getFps: /* @__PURE__ */ __name(() => this.system.getAPI().fps, "getFps"),
|
|
1086
|
+
getUpdateRate: /* @__PURE__ */ __name(() => this.system.getAPI().update_rate, "getUpdateRate"),
|
|
1087
|
+
setUpdateRate: /* @__PURE__ */ __name((rate) => {
|
|
1088
|
+
this.system.getAPI().update_rate = rate;
|
|
1089
|
+
}, "setUpdateRate")
|
|
1090
|
+
});
|
|
1091
|
+
this.sceneManager = new SceneManager();
|
|
1092
|
+
this.assetLoader = new AssetLoader(options.url || "", options.resources || {}, this.audio, this.listener);
|
|
1093
|
+
this.logStep("RuntimeController constructed", {
|
|
1094
|
+
width: this.screen.width,
|
|
1095
|
+
height: this.screen.height,
|
|
1096
|
+
resources: {
|
|
1097
|
+
images: options.resources?.images?.length ?? 0,
|
|
1098
|
+
sounds: options.resources?.sounds?.length ?? 0,
|
|
1099
|
+
music: options.resources?.music?.length ?? 0
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
get sprites() {
|
|
1104
|
+
return this.assetRegistry.sprites;
|
|
1105
|
+
}
|
|
1106
|
+
get maps() {
|
|
1107
|
+
return this.assetRegistry.maps;
|
|
1108
|
+
}
|
|
1109
|
+
get sounds() {
|
|
1110
|
+
return this.assetRegistry.sounds;
|
|
1111
|
+
}
|
|
1112
|
+
get music() {
|
|
1113
|
+
return this.assetRegistry.music;
|
|
1114
|
+
}
|
|
1115
|
+
get assets() {
|
|
1116
|
+
return this.assetRegistry.assets;
|
|
1117
|
+
}
|
|
1118
|
+
get stopped() {
|
|
1119
|
+
return this.isStopped;
|
|
1120
|
+
}
|
|
1121
|
+
getSession() {
|
|
1122
|
+
return this.sessionSnapshot ? cloneSnapshot(this.sessionSnapshot) : null;
|
|
1123
|
+
}
|
|
1124
|
+
async start() {
|
|
1125
|
+
this.logStep("startup: begin");
|
|
1126
|
+
await this.hydrateSession();
|
|
1127
|
+
this.ensureBridgeSubscription();
|
|
1128
|
+
this.logStep("startup: loading assets");
|
|
1129
|
+
await this.loadAssets();
|
|
1130
|
+
this.logStep("startup: assets loaded", {
|
|
1131
|
+
sprites: Object.keys(this.sprites).length,
|
|
1132
|
+
maps: Object.keys(this.maps).length,
|
|
1133
|
+
sounds: Object.keys(this.sounds).length,
|
|
1134
|
+
music: Object.keys(this.music).length,
|
|
1135
|
+
assets: Object.keys(this.assets).length
|
|
1136
|
+
});
|
|
1137
|
+
this.logStep("startup: waiting for asset readiness");
|
|
1138
|
+
await this.waitForAssetsReady();
|
|
1139
|
+
this.logStep("startup: assets ready");
|
|
1140
|
+
this.logStep("startup: initializing VM");
|
|
1141
|
+
this.initializeVM();
|
|
1142
|
+
this.logStep("startup: VM ready", {
|
|
1143
|
+
sourceFiles: Object.keys(this.options.sources || {}).length
|
|
1144
|
+
});
|
|
1145
|
+
this.logStep("startup: starting game loop");
|
|
1146
|
+
this.startGameLoop();
|
|
1147
|
+
this.isStopped = false;
|
|
1148
|
+
this.logStep("startup: completed");
|
|
1149
|
+
}
|
|
1150
|
+
stop() {
|
|
1151
|
+
this.logStep("lifecycle: stop requested");
|
|
1152
|
+
this.isStopped = true;
|
|
1153
|
+
this.gameLoop?.stop();
|
|
1154
|
+
this.audio.stopAll();
|
|
1155
|
+
}
|
|
1156
|
+
resume() {
|
|
1157
|
+
this.logStep("lifecycle: resume requested");
|
|
1158
|
+
this.isStopped = false;
|
|
1159
|
+
this.gameLoop?.resume();
|
|
1160
|
+
}
|
|
1161
|
+
async reset(options = {}) {
|
|
1162
|
+
this.logStep("lifecycle: reset requested", options);
|
|
1163
|
+
const preservedSnapshot = options.preserveSnapshot ? this.exportSnapshot() : null;
|
|
1164
|
+
const preserveSession = options.preserveSession ?? true;
|
|
1165
|
+
const preserveStorage = options.preserveStorage ?? this.options.preserveStorage ?? false;
|
|
1166
|
+
this.stop();
|
|
1167
|
+
this.teardownRuntimeState();
|
|
1168
|
+
if (!preserveSession) {
|
|
1169
|
+
this.sessionSnapshot = null;
|
|
1170
|
+
}
|
|
1171
|
+
this.preserveStorageOnNextBoot = preserveStorage;
|
|
1172
|
+
await this.start();
|
|
1173
|
+
if (preservedSnapshot) {
|
|
1174
|
+
await this.importSnapshot(preservedSnapshot);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
exportSnapshot() {
|
|
1178
|
+
const global = this.vm?.context?.global;
|
|
1179
|
+
const routerState = this.sceneManager.router.getState();
|
|
1180
|
+
return {
|
|
1181
|
+
version: 1,
|
|
1182
|
+
global: global ? serializeGlobalSnapshot(global) : {},
|
|
1183
|
+
session: this.getSession(),
|
|
1184
|
+
router: {
|
|
1185
|
+
path: routerState.path,
|
|
1186
|
+
sceneName: this.sceneManager.getCurrentSceneName()
|
|
1187
|
+
},
|
|
1188
|
+
system: {
|
|
1189
|
+
updateRate: this.system.getAPI().update_rate
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
async importSnapshot(snapshot) {
|
|
1194
|
+
if (!this.vm?.context?.global) {
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
this.snapshotRestorer.restoreState(this.vm.context.global, snapshot.global);
|
|
1198
|
+
this.system.getAPI().update_rate = snapshot.system.updateRate;
|
|
1199
|
+
this.updateGameLoopUpdateRate();
|
|
1200
|
+
if (snapshot.session) {
|
|
1201
|
+
this.sessionSnapshot = cloneSnapshot(snapshot.session);
|
|
1202
|
+
}
|
|
1203
|
+
if (snapshot.router.path) {
|
|
1204
|
+
this.sceneManager.router.replace(snapshot.router.path);
|
|
1205
|
+
} else if (snapshot.router.sceneName) {
|
|
1206
|
+
this.sceneManager.setActiveScene(snapshot.router.sceneName);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
updateSource(file, src, reinit = false) {
|
|
1210
|
+
if (!this.sourceUpdater) return false;
|
|
1211
|
+
return this.sourceUpdater.updateSource(file, src, reinit);
|
|
1212
|
+
}
|
|
1213
|
+
handleMessage(message) {
|
|
1214
|
+
if (!message) {
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
if (typeof message === "object" && "type" in message && typeof message.type === "string") {
|
|
1218
|
+
this.sendHostEvent(message);
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
if (message.name === "time_machine" && this.timeMachine) {
|
|
1222
|
+
this.timeMachine.messageReceived(message);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
sendHostEvent(event) {
|
|
1226
|
+
this.handleHostEvent(event);
|
|
1227
|
+
}
|
|
1228
|
+
getCanvas() {
|
|
1229
|
+
return this.screen.getCanvas();
|
|
1230
|
+
}
|
|
1231
|
+
async loadAssets() {
|
|
1232
|
+
const collections = await this.assetLoader.loadAll();
|
|
1233
|
+
this.assetRegistry.replace(collections);
|
|
1234
|
+
}
|
|
1235
|
+
async waitForAssetsReady() {
|
|
1236
|
+
return new Promise((resolve) => {
|
|
1237
|
+
const checkReady = /* @__PURE__ */ __name(() => {
|
|
1238
|
+
if (this.assetLoader.isReady()) {
|
|
1239
|
+
this.system.setLoading(100);
|
|
1240
|
+
resolve();
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
const progress = this.assetLoader.getProgress();
|
|
1244
|
+
this.system.setLoading(Math.floor(progress * 100));
|
|
1245
|
+
this.assetLoader.showLoadingBar(this.screen.getInterface());
|
|
1246
|
+
requestAnimationFrame(checkReady);
|
|
1247
|
+
}, "checkReady");
|
|
1248
|
+
checkReady();
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
initializeVM() {
|
|
1252
|
+
this.logStep("vm: building meta/global APIs");
|
|
1253
|
+
const apiContext = {
|
|
1254
|
+
listener: this.listener,
|
|
1255
|
+
options: this.options,
|
|
1256
|
+
screen: this.screen,
|
|
1257
|
+
audio: this.audio,
|
|
1258
|
+
input: this.input,
|
|
1259
|
+
system: this.system,
|
|
1260
|
+
playerService: this.playerService,
|
|
1261
|
+
sceneManager: this.sceneManager,
|
|
1262
|
+
assets: this.assetRegistry,
|
|
1263
|
+
bridge: this.options.bridge,
|
|
1264
|
+
getVM: /* @__PURE__ */ __name(() => this.vm, "getVM"),
|
|
1265
|
+
getSessionSnapshot: /* @__PURE__ */ __name(() => this.getSession(), "getSessionSnapshot"),
|
|
1266
|
+
sendHostEvent: /* @__PURE__ */ __name((event) => this.emitBridgeEvent(event.type, event.payload), "sendHostEvent"),
|
|
1267
|
+
sendHostRequest: /* @__PURE__ */ __name((name, payload, callback) => this.sendBridgeRequest(name, payload, callback), "sendHostRequest"),
|
|
1268
|
+
exportSnapshot: /* @__PURE__ */ __name(() => this.exportSnapshot(), "exportSnapshot"),
|
|
1269
|
+
importSnapshot: /* @__PURE__ */ __name((snapshot) => this.importSnapshot(snapshot), "importSnapshot"),
|
|
1270
|
+
resetRuntime: /* @__PURE__ */ __name((options) => this.reset(options), "resetRuntime"),
|
|
1271
|
+
saveSnapshot: /* @__PURE__ */ __name((meta2, callback) => this.saveSnapshot(meta2, callback), "saveSnapshot"),
|
|
1272
|
+
loadSnapshot: /* @__PURE__ */ __name((meta2, callback) => this.loadSnapshot(meta2, callback), "loadSnapshot")
|
|
1273
|
+
};
|
|
1274
|
+
const meta = createRuntimeMeta(apiContext);
|
|
1275
|
+
const global = createRuntimeGlobalApi(apiContext);
|
|
1276
|
+
this.vm = new L8BVM(meta, global, this.options.namespace || "/l8b", this.preserveStorageOnNextBoot);
|
|
1277
|
+
this.sourceUpdater = new SourceUpdater(this.vm, this.listener, this.audio, this.screen, () => reportWarnings(this.vm, this.listener), (name, payload) => this.emitBridgeEvent(name, payload));
|
|
1278
|
+
this.timeMachine = new TimeMachine(this);
|
|
1279
|
+
this.timeMachine.onStatus((status) => {
|
|
1280
|
+
this.emitBridgeEvent("time_machine_status", {
|
|
1281
|
+
status
|
|
1282
|
+
});
|
|
1283
|
+
});
|
|
1284
|
+
this.loadPrograms();
|
|
1285
|
+
this.initializeScenesAndRouter();
|
|
1286
|
+
this.emitBridgeEvent("runtime.started", {});
|
|
1287
|
+
}
|
|
1288
|
+
loadPrograms() {
|
|
1289
|
+
if (!this.vm) {
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
const compiledRoutines = this.options.compiledRoutines || {};
|
|
1293
|
+
const sources = this.options.sources || {};
|
|
1294
|
+
if (Object.keys(compiledRoutines).length > 0) {
|
|
1295
|
+
this.logStep("vm: loading compiled routines", {
|
|
1296
|
+
files: Object.keys(compiledRoutines)
|
|
1297
|
+
});
|
|
1298
|
+
for (const [file, routine] of Object.entries(compiledRoutines)) {
|
|
1299
|
+
try {
|
|
1300
|
+
this.vm.loadRoutine(routine, file);
|
|
1301
|
+
} catch (err) {
|
|
1302
|
+
reportError(this.listener, {
|
|
1303
|
+
error: err.message || String(err),
|
|
1304
|
+
type: "compile",
|
|
1305
|
+
stack: err.stack,
|
|
1306
|
+
file
|
|
1307
|
+
});
|
|
1308
|
+
this.logStep("vm: routine load error", {
|
|
1309
|
+
file,
|
|
1310
|
+
message: err?.message || String(err)
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
} else if (Object.keys(sources).length > 0) {
|
|
1315
|
+
this.logStep("vm: executing sources", {
|
|
1316
|
+
files: Object.keys(sources)
|
|
1317
|
+
});
|
|
1318
|
+
for (const [file, src] of Object.entries(sources)) {
|
|
1319
|
+
this.sourceUpdater?.updateSource(file, src, false);
|
|
1320
|
+
}
|
|
1321
|
+
} else {
|
|
1322
|
+
this.logStep("vm: no sources or compiled routines provided");
|
|
1323
|
+
}
|
|
1324
|
+
try {
|
|
1325
|
+
this.vm.call("init");
|
|
1326
|
+
this.vm.runner.tick();
|
|
1327
|
+
this.logStep("vm: init() executed");
|
|
1328
|
+
} catch (err) {
|
|
1329
|
+
reportError(this.listener, {
|
|
1330
|
+
error: err.message || String(err),
|
|
1331
|
+
type: "init",
|
|
1332
|
+
stack: err.stack
|
|
1333
|
+
});
|
|
1334
|
+
this.logStep("vm: init() error", {
|
|
1335
|
+
message: err?.message || String(err)
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
initializeScenesAndRouter() {
|
|
1340
|
+
const registeredScenes = this.sceneManager.registry.getNames();
|
|
1341
|
+
this.logStep("router: initializing", {
|
|
1342
|
+
registeredScenes: registeredScenes.length,
|
|
1343
|
+
sceneNames: registeredScenes
|
|
1344
|
+
});
|
|
1345
|
+
this.sceneManager.router.init();
|
|
1346
|
+
const activeScene = this.sceneManager.hasActiveScene() ? this.sceneManager.getCurrentSceneName?.() || "unknown" : null;
|
|
1347
|
+
const routerState = this.sceneManager.router.getState();
|
|
1348
|
+
this.logStep("router: initialized", {
|
|
1349
|
+
activeScene: activeScene || "none",
|
|
1350
|
+
path: routerState.path,
|
|
1351
|
+
hasActiveScene: this.sceneManager.hasActiveScene()
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
startGameLoop() {
|
|
1355
|
+
this.logStep("loop: creating game loop");
|
|
1356
|
+
this.gameLoop = new GameLoop({
|
|
1357
|
+
onUpdate: /* @__PURE__ */ __name(() => this.handleUpdate(), "onUpdate"),
|
|
1358
|
+
onDraw: /* @__PURE__ */ __name(() => this.handleDraw(), "onDraw"),
|
|
1359
|
+
onTick: /* @__PURE__ */ __name(() => this.handleTick(), "onTick"),
|
|
1360
|
+
onWatchStep: /* @__PURE__ */ __name(() => this.handleWatchStep(), "onWatchStep"),
|
|
1361
|
+
getUpdateRate: /* @__PURE__ */ __name(() => {
|
|
1362
|
+
if (!this.vm) return void 0;
|
|
1363
|
+
try {
|
|
1364
|
+
return this.vm.context?.global?.system?.update_rate;
|
|
1365
|
+
} catch {
|
|
1366
|
+
return void 0;
|
|
1367
|
+
}
|
|
1368
|
+
}, "getUpdateRate"),
|
|
1369
|
+
setFPS: /* @__PURE__ */ __name((fps) => {
|
|
1370
|
+
if (!this.vm) return;
|
|
1371
|
+
try {
|
|
1372
|
+
if (this.vm.context?.global?.system) {
|
|
1373
|
+
this.vm.context.global.system.fps = fps;
|
|
1374
|
+
}
|
|
1375
|
+
} catch {
|
|
1376
|
+
}
|
|
1377
|
+
}, "setFPS")
|
|
1378
|
+
});
|
|
1379
|
+
this.gameLoop.start();
|
|
1380
|
+
this.logStep("loop: started");
|
|
1381
|
+
}
|
|
1382
|
+
updateGameLoopUpdateRate() {
|
|
1383
|
+
if (!this.vm || !this.gameLoop) return;
|
|
1384
|
+
try {
|
|
1385
|
+
const updateRate = this.vm.context?.global?.system?.update_rate;
|
|
1386
|
+
const rate = updateRate != null && updateRate > 0 && Number.isFinite(updateRate) ? updateRate : 60;
|
|
1387
|
+
if (rate !== this.lastUpdateRate) {
|
|
1388
|
+
this.lastUpdateRate = rate;
|
|
1389
|
+
this.gameLoop.setUpdateRate(rate);
|
|
1390
|
+
}
|
|
1391
|
+
} catch {
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
handleUpdate() {
|
|
1395
|
+
if (!this.vm) return;
|
|
1396
|
+
this.frameCount++;
|
|
1397
|
+
this.input.update();
|
|
1398
|
+
if (this.frameCount % this.DEBUG_UPDATE_FREQUENCY === 0) {
|
|
1399
|
+
this.debugLogger.debugInputs(this.input, this.options.debug);
|
|
1400
|
+
this.debugLogger.debugScreen(this.screen, this.options.debug);
|
|
1401
|
+
}
|
|
1402
|
+
if (this.gameLoop) {
|
|
1403
|
+
this.system.setFPS(this.gameLoop.getFPS());
|
|
1404
|
+
this.updateGameLoopUpdateRate();
|
|
1405
|
+
}
|
|
1406
|
+
try {
|
|
1407
|
+
if (this.sceneManager.hasActiveScene()) {
|
|
1408
|
+
this.sceneManager.update();
|
|
1409
|
+
} else {
|
|
1410
|
+
this.vm.call("update");
|
|
1411
|
+
this.vm.runner.tick();
|
|
1412
|
+
}
|
|
1413
|
+
if (this.vm.error_info) {
|
|
1414
|
+
const err = Object.assign({}, this.vm.error_info);
|
|
1415
|
+
err.type = "update";
|
|
1416
|
+
reportError(this.listener, err);
|
|
1417
|
+
}
|
|
1418
|
+
} catch (err) {
|
|
1419
|
+
reportError(this.listener, {
|
|
1420
|
+
error: err.message || String(err),
|
|
1421
|
+
type: "update",
|
|
1422
|
+
stack: err.stack
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
handleDraw() {
|
|
1427
|
+
if (!this.vm) return;
|
|
1428
|
+
try {
|
|
1429
|
+
this.screen.initDraw();
|
|
1430
|
+
this.screen.updateInterface();
|
|
1431
|
+
if (this.sceneManager.hasActiveScene()) {
|
|
1432
|
+
this.sceneManager.draw();
|
|
1433
|
+
} else {
|
|
1434
|
+
this.vm.call("draw");
|
|
1435
|
+
this.vm.runner.tick();
|
|
1436
|
+
}
|
|
1437
|
+
reportWarnings(this.vm, this.listener);
|
|
1438
|
+
if (this.vm.error_info) {
|
|
1439
|
+
const err = Object.assign({}, this.vm.error_info);
|
|
1440
|
+
err.type = "draw";
|
|
1441
|
+
reportError(this.listener, err);
|
|
1442
|
+
}
|
|
1443
|
+
} catch (err) {
|
|
1444
|
+
reportError(this.listener, {
|
|
1445
|
+
error: err.message || String(err),
|
|
1446
|
+
type: "draw",
|
|
1447
|
+
stack: err.stack
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
this.timeMachine?.step();
|
|
1451
|
+
}
|
|
1452
|
+
handleTick() {
|
|
1453
|
+
if (this.vm?.runner) {
|
|
1454
|
+
this.vm.runner.tick?.();
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
handleWatchStep() {
|
|
1458
|
+
this.timeMachine?.loopStep();
|
|
1459
|
+
}
|
|
1460
|
+
ensureBridgeSubscription() {
|
|
1461
|
+
if (this.bridgeUnsubscribe || !this.options.bridge?.subscribe) {
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
const maybeUnsubscribe = this.options.bridge.subscribe((event) => this.handleHostEvent(event));
|
|
1465
|
+
this.bridgeUnsubscribe = typeof maybeUnsubscribe === "function" ? maybeUnsubscribe : null;
|
|
1466
|
+
}
|
|
1467
|
+
handleHostEvent(event) {
|
|
1468
|
+
switch (event.type) {
|
|
1469
|
+
case "session.update":
|
|
1470
|
+
this.mergeSession(event.payload);
|
|
1471
|
+
break;
|
|
1472
|
+
case "runtime.reset":
|
|
1473
|
+
void this.reset(asRecord(event.payload));
|
|
1474
|
+
break;
|
|
1475
|
+
case "runtime.import_snapshot":
|
|
1476
|
+
if (isRuntimeSnapshot(event.payload)) {
|
|
1477
|
+
void this.importSnapshot(event.payload);
|
|
1478
|
+
}
|
|
1479
|
+
break;
|
|
1480
|
+
case "runtime.export_snapshot":
|
|
1481
|
+
this.emitBridgeEvent("runtime.snapshot", this.exportSnapshot());
|
|
1482
|
+
break;
|
|
1483
|
+
case "runtime.stop":
|
|
1484
|
+
case "runtime.pause":
|
|
1485
|
+
this.stop();
|
|
1486
|
+
break;
|
|
1487
|
+
case "runtime.resume":
|
|
1488
|
+
this.resume();
|
|
1489
|
+
break;
|
|
1490
|
+
case "time_machine":
|
|
1491
|
+
if (this.timeMachine && isRecord(event.payload)) {
|
|
1492
|
+
const command = event.payload.command;
|
|
1493
|
+
if (typeof command === "string") {
|
|
1494
|
+
this.timeMachine.messageReceived({
|
|
1495
|
+
name: "time_machine",
|
|
1496
|
+
command,
|
|
1497
|
+
position: typeof event.payload.position === "number" ? event.payload.position : void 0
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
break;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
emitPlayerMessage(message) {
|
|
1505
|
+
if (isRecord(message) && typeof message.type === "string") {
|
|
1506
|
+
this.emitBridgeEvent(message.type, message);
|
|
1507
|
+
} else {
|
|
1508
|
+
this.emitBridgeEvent("player.message", message);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
emitBridgeEvent(name, payload) {
|
|
1512
|
+
this.options.bridge?.emit?.(name, payload);
|
|
1513
|
+
}
|
|
1514
|
+
sendBridgeRequest(name, payload, callback) {
|
|
1515
|
+
const request = this.options.bridge?.request;
|
|
1516
|
+
if (!request) {
|
|
1517
|
+
callback?.({
|
|
1518
|
+
ok: false,
|
|
1519
|
+
error: `No runtime bridge request handler registered for "${name}"`
|
|
1520
|
+
});
|
|
1521
|
+
return null;
|
|
1522
|
+
}
|
|
1523
|
+
const requestId = createRequestId(name);
|
|
1524
|
+
try {
|
|
1525
|
+
const result = request(name, payload);
|
|
1526
|
+
if (isPromiseLike(result)) {
|
|
1527
|
+
void result.then((value) => callback?.(value)).catch((error) => callback?.({
|
|
1528
|
+
ok: false,
|
|
1529
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1530
|
+
}));
|
|
1531
|
+
} else {
|
|
1532
|
+
callback?.(result);
|
|
1533
|
+
}
|
|
1534
|
+
return requestId;
|
|
1535
|
+
} catch (error) {
|
|
1536
|
+
callback?.({
|
|
1537
|
+
ok: false,
|
|
1538
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1539
|
+
});
|
|
1540
|
+
return requestId;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
saveSnapshot(meta, callback) {
|
|
1544
|
+
const snapshot = this.exportSnapshot();
|
|
1545
|
+
const save = this.options.bridge?.saveSnapshot;
|
|
1546
|
+
if (!save) {
|
|
1547
|
+
const fallback = {
|
|
1548
|
+
ok: false,
|
|
1549
|
+
error: "No runtime bridge saveSnapshot handler registered",
|
|
1550
|
+
snapshot
|
|
1551
|
+
};
|
|
1552
|
+
callback?.(fallback);
|
|
1553
|
+
return fallback;
|
|
1554
|
+
}
|
|
1555
|
+
try {
|
|
1556
|
+
const result = save(snapshot, meta);
|
|
1557
|
+
if (isPromiseLike(result)) {
|
|
1558
|
+
void result.then(() => callback?.({
|
|
1559
|
+
ok: true,
|
|
1560
|
+
snapshot
|
|
1561
|
+
})).catch((error) => callback?.({
|
|
1562
|
+
ok: false,
|
|
1563
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1564
|
+
}));
|
|
1565
|
+
return null;
|
|
1566
|
+
}
|
|
1567
|
+
callback?.({
|
|
1568
|
+
ok: true,
|
|
1569
|
+
snapshot
|
|
1570
|
+
});
|
|
1571
|
+
return {
|
|
1572
|
+
ok: true,
|
|
1573
|
+
snapshot
|
|
1574
|
+
};
|
|
1575
|
+
} catch (error) {
|
|
1576
|
+
const failure = {
|
|
1577
|
+
ok: false,
|
|
1578
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1579
|
+
};
|
|
1580
|
+
callback?.(failure);
|
|
1581
|
+
return failure;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
loadSnapshot(meta, callback) {
|
|
1585
|
+
const load = this.options.bridge?.loadSnapshot;
|
|
1586
|
+
if (!load) {
|
|
1587
|
+
const fallback = {
|
|
1588
|
+
ok: false,
|
|
1589
|
+
error: "No runtime bridge loadSnapshot handler registered"
|
|
1590
|
+
};
|
|
1591
|
+
callback?.(fallback);
|
|
1592
|
+
return fallback;
|
|
1593
|
+
}
|
|
1594
|
+
try {
|
|
1595
|
+
const result = load(meta);
|
|
1596
|
+
if (isPromiseLike(result)) {
|
|
1597
|
+
void result.then(async (snapshot) => {
|
|
1598
|
+
if (snapshot) {
|
|
1599
|
+
await this.importSnapshot(snapshot);
|
|
1600
|
+
}
|
|
1601
|
+
callback?.({
|
|
1602
|
+
ok: true,
|
|
1603
|
+
snapshot
|
|
1604
|
+
});
|
|
1605
|
+
}).catch((error) => callback?.({
|
|
1606
|
+
ok: false,
|
|
1607
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1608
|
+
}));
|
|
1609
|
+
return null;
|
|
1610
|
+
}
|
|
1611
|
+
if (result) {
|
|
1612
|
+
void this.importSnapshot(result);
|
|
1613
|
+
}
|
|
1614
|
+
callback?.({
|
|
1615
|
+
ok: true,
|
|
1616
|
+
snapshot: result
|
|
1617
|
+
});
|
|
1618
|
+
return result;
|
|
1619
|
+
} catch (error) {
|
|
1620
|
+
const failure = {
|
|
1621
|
+
ok: false,
|
|
1622
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1623
|
+
};
|
|
1624
|
+
callback?.(failure);
|
|
1625
|
+
return failure;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
async hydrateSession() {
|
|
1629
|
+
if (this.sessionSnapshot) {
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1632
|
+
const getSession = this.options.bridge?.getSession;
|
|
1633
|
+
if (!getSession) {
|
|
1634
|
+
this.sessionSnapshot = this.options.initialSession || null;
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
try {
|
|
1638
|
+
const session = getSession();
|
|
1639
|
+
this.sessionSnapshot = isPromiseLike(session) ? await session : session;
|
|
1640
|
+
} catch {
|
|
1641
|
+
this.sessionSnapshot = this.options.initialSession || null;
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
mergeSession(payload) {
|
|
1645
|
+
if (!isRecord(payload)) {
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
const current = this.sessionSnapshot || {};
|
|
1649
|
+
this.sessionSnapshot = {
|
|
1650
|
+
...current,
|
|
1651
|
+
...payload
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
teardownRuntimeState() {
|
|
1655
|
+
this.gameLoop = null;
|
|
1656
|
+
this.sourceUpdater = null;
|
|
1657
|
+
this.vm = null;
|
|
1658
|
+
this.timeMachine = null;
|
|
1659
|
+
this.frameCount = 0;
|
|
1660
|
+
this.lastUpdateRate = -1;
|
|
1661
|
+
this.isStopped = false;
|
|
1662
|
+
this.sceneManager.registry.clear();
|
|
1663
|
+
this.sceneManager.routeManager.clear();
|
|
1664
|
+
}
|
|
1665
|
+
logStep(message, payload) {
|
|
1666
|
+
if (!this.options.debug?.lifecycle) return;
|
|
1667
|
+
const prefix = "[@al8b/runtime][lifecycle]";
|
|
1668
|
+
if (payload !== void 0) {
|
|
1669
|
+
console.info(`${prefix} ${message}`, payload);
|
|
1670
|
+
} else {
|
|
1671
|
+
console.info(`${prefix} ${message}`);
|
|
1672
|
+
}
|
|
1673
|
+
if (this.listener.log) {
|
|
1674
|
+
try {
|
|
1675
|
+
const serialized = payload === void 0 ? "" : ` ${JSON.stringify(payload)}`;
|
|
1676
|
+
this.listener.log(`${prefix} ${message}${serialized}`);
|
|
1677
|
+
} catch {
|
|
1678
|
+
this.listener.log(`${prefix} ${message}`);
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
};
|
|
1683
|
+
function serializeGlobalSnapshot(global) {
|
|
1684
|
+
const globalRecord = global;
|
|
1685
|
+
const excluded = [
|
|
1686
|
+
globalRecord.random,
|
|
1687
|
+
globalRecord.screen,
|
|
1688
|
+
globalRecord.audio,
|
|
1689
|
+
globalRecord.keyboard,
|
|
1690
|
+
globalRecord.mouse,
|
|
1691
|
+
globalRecord.touch,
|
|
1692
|
+
globalRecord.gamepad,
|
|
1693
|
+
globalRecord.system,
|
|
1694
|
+
globalRecord.storage,
|
|
1695
|
+
globalRecord.host,
|
|
1696
|
+
globalRecord.session,
|
|
1697
|
+
globalRecord.memory
|
|
1698
|
+
].filter((value) => value != null);
|
|
1699
|
+
return deepCloneValue(globalRecord, excluded);
|
|
1700
|
+
}
|
|
1701
|
+
__name(serializeGlobalSnapshot, "serializeGlobalSnapshot");
|
|
1702
|
+
function deepCloneValue(value, excluded = []) {
|
|
1703
|
+
if (value == null) {
|
|
1704
|
+
return value;
|
|
1705
|
+
}
|
|
1706
|
+
if (excluded.includes(value)) {
|
|
1707
|
+
return null;
|
|
1708
|
+
}
|
|
1709
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
1710
|
+
return value;
|
|
1711
|
+
}
|
|
1712
|
+
if (Array.isArray(value)) {
|
|
1713
|
+
return value.map((entry) => deepCloneValue(entry, excluded));
|
|
1714
|
+
}
|
|
1715
|
+
if (typeof value === "object") {
|
|
1716
|
+
const clone = {};
|
|
1717
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1718
|
+
clone[key] = deepCloneValue(entry, excluded);
|
|
1719
|
+
}
|
|
1720
|
+
return clone;
|
|
1721
|
+
}
|
|
1722
|
+
return null;
|
|
1723
|
+
}
|
|
1724
|
+
__name(deepCloneValue, "deepCloneValue");
|
|
1725
|
+
function cloneSnapshot(value) {
|
|
1726
|
+
return deepCloneValue(value);
|
|
1727
|
+
}
|
|
1728
|
+
__name(cloneSnapshot, "cloneSnapshot");
|
|
1729
|
+
function createRequestId(name) {
|
|
1730
|
+
return `${name}:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`;
|
|
1731
|
+
}
|
|
1732
|
+
__name(createRequestId, "createRequestId");
|
|
1733
|
+
function isPromiseLike(value) {
|
|
1734
|
+
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
|
|
1735
|
+
}
|
|
1736
|
+
__name(isPromiseLike, "isPromiseLike");
|
|
1737
|
+
function isRecord(value) {
|
|
1738
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1739
|
+
}
|
|
1740
|
+
__name(isRecord, "isRecord");
|
|
1741
|
+
function asRecord(value) {
|
|
1742
|
+
return isRecord(value) ? value : void 0;
|
|
1743
|
+
}
|
|
1744
|
+
__name(asRecord, "asRecord");
|
|
1745
|
+
function isRuntimeSnapshot(value) {
|
|
1746
|
+
if (!isRecord(value)) return false;
|
|
1747
|
+
if (value.version !== 1) return false;
|
|
1748
|
+
if (!isRecord(value.global)) return false;
|
|
1749
|
+
if (!("router" in value)) return false;
|
|
1750
|
+
if (!("session" in value)) return false;
|
|
1751
|
+
return true;
|
|
1752
|
+
}
|
|
1753
|
+
__name(isRuntimeSnapshot, "isRuntimeSnapshot");
|
|
1754
|
+
export {
|
|
1755
|
+
createRuntime
|
|
1756
|
+
};
|
|
1757
|
+
//# sourceMappingURL=index.mjs.map
|