@volter/blender-engine 0.1.21 → 0.1.22
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/browser/blender-wali-engine.mts +108 -1
- package/package.json +1 -1
|
@@ -232,6 +232,110 @@ async function sharedRuntime(
|
|
|
232
232
|
return layer;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
+
type StoreLayerFromIndex = (
|
|
236
|
+
root: string,
|
|
237
|
+
files: RuntimeIndex['files'],
|
|
238
|
+
store: string,
|
|
239
|
+
options: { mtimeMs: number; include?: (path: string) => boolean },
|
|
240
|
+
) => unknown;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Blender's runtime tree as a STORE ON THE ORIGIN'S DISK the program reads
|
|
244
|
+
* (browser-substrate ADR-0048): installed once per version by the
|
|
245
|
+
* substrate's installer worker, and read file by file as Blender opens them,
|
|
246
|
+
* a large file by the ranges it reads. The shared layer before it copied all
|
|
247
|
+
* 96 MB into memory before Blender started (0.9 s of each boot) and held it
|
|
248
|
+
* for the run; a boot reads 268 of the 2,082 files (measured 2026-09-28).
|
|
249
|
+
* Undefined, and said, where it cannot be installed: the shared layer then
|
|
250
|
+
* carries the tree as before.
|
|
251
|
+
*/
|
|
252
|
+
async function installedRuntime(
|
|
253
|
+
fromIndex: StoreLayerFromIndex,
|
|
254
|
+
hold: ((projectId: string) => Promise<void>) | undefined,
|
|
255
|
+
log: BlenderEngineOptions['log'],
|
|
256
|
+
): Promise<unknown> {
|
|
257
|
+
const began = performance.now();
|
|
258
|
+
const indexAnswer = await fetch(artifactUrl('runtime.idx'));
|
|
259
|
+
if (!indexAnswer.ok)
|
|
260
|
+
throw new Error(`${ARTIFACT_BASE}/runtime.idx answered ${indexAnswer.status}`);
|
|
261
|
+
const text = await indexAnswer.text();
|
|
262
|
+
const index = JSON.parse(text) as RuntimeIndex;
|
|
263
|
+
// The tree's version is its index: the same files at the same sizes and modes, in the same order,
|
|
264
|
+
// and its newest file's time (`runtimeTag`'s validator), so a file rebuilt at the same size is a new version.
|
|
265
|
+
const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(text)));
|
|
266
|
+
const tag = [...digest.slice(0, 12)].map((byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
267
|
+
const projectId = `${RUNTIME_STORE_FAMILY}${tag}`;
|
|
268
|
+
// THIS PAGE HOLDS ITS TREE IN USE from before it is installed to the end of the session, so
|
|
269
|
+
// another tab installing another version never removes it under this one's runs (ADR-0048).
|
|
270
|
+
if (hold) {
|
|
271
|
+
try {
|
|
272
|
+
await hold(projectId);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
log('log', `blender: the runtime tree stays in memory: it cannot be held in use (${error instanceof Error ? error.message : String(error)})`);
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
let worker: Worker;
|
|
279
|
+
try {
|
|
280
|
+
worker = new Worker(artifactUrl('wali/browser-wali/tree-store-worker.js'), { type: 'module' });
|
|
281
|
+
} catch (error) {
|
|
282
|
+
log('log', `blender: the runtime tree stays in memory: no installer worker (${error instanceof Error ? error.message : String(error)})`);
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
// A BOUNDED STALL, NOT A BOUNDED INSTALL: an install stalled in another tab (a throttled background
|
|
286
|
+
// tab) holds this tree's installer lock, or the network stops. Each batch written restarts the wait,
|
|
287
|
+
// so a slow link still installs. On a stall this boot carries the tree in memory, and the installer
|
|
288
|
+
// is left to finish for the next boot rather than stopped halfway.
|
|
289
|
+
const answer = await new Promise<{ ok: boolean; installed?: boolean; bytes?: number; error?: string }>((resolve) => {
|
|
290
|
+
let settled = false;
|
|
291
|
+
const settle = (value: { ok: boolean; installed?: boolean; bytes?: number; error?: string }): void => {
|
|
292
|
+
if (!settled) { settled = true; resolve(value); }
|
|
293
|
+
};
|
|
294
|
+
let timer = setTimeout(() => settle({ ok: false, error: `no progress in ${INSTALL_STALL_MS / 1000} s` }), INSTALL_STALL_MS);
|
|
295
|
+
worker.onmessage = (event) => {
|
|
296
|
+
clearTimeout(timer);
|
|
297
|
+
if (typeof event.data?.progress === 'number') {
|
|
298
|
+
if (!settled) timer = setTimeout(() => settle({ ok: false, error: `no progress in ${INSTALL_STALL_MS / 1000} s` }), INSTALL_STALL_MS);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
worker.terminate();
|
|
302
|
+
settle(event.data);
|
|
303
|
+
};
|
|
304
|
+
worker.onerror = (event) => {
|
|
305
|
+
clearTimeout(timer);
|
|
306
|
+
worker.terminate();
|
|
307
|
+
settle({ ok: false, error: event.message || 'the installer worker failed to start' });
|
|
308
|
+
};
|
|
309
|
+
worker.postMessage({
|
|
310
|
+
projectId,
|
|
311
|
+
family: RUNTIME_STORE_FAMILY,
|
|
312
|
+
root: RESOURCES,
|
|
313
|
+
files: index.files,
|
|
314
|
+
blobUrl: new URL(artifactUrl('runtime.bin'), location.href).href,
|
|
315
|
+
mtimeMs: Date.now(),
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
if (!answer.ok) {
|
|
319
|
+
log('log', `blender: the runtime tree stays in memory: it could not be installed (${answer.error})`);
|
|
320
|
+
return undefined;
|
|
321
|
+
}
|
|
322
|
+
const layer = fromIndex(RESOURCES, index.files, projectId, {
|
|
323
|
+
mtimeMs: Date.now(),
|
|
324
|
+
include: (path) => !PACK_RENDER_OVERRIDES.has(path),
|
|
325
|
+
});
|
|
326
|
+
log('log', `blender: Blender's ${index.files.length}-file runtime tree is on disk (${answer.installed ? `installed, ${Math.round((answer.bytes ?? 0) / 1e6)} MB` : 'installed before'}) in ${Math.round(performance.now() - began)} ms`);
|
|
327
|
+
return layer;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Installed runtime trees' store names begin so; installing one removes the others that no tab
|
|
332
|
+
* reads. `host-tree.` is the substrate's mark of an installed tree (`TREE_STORE_PREFIX`), which no
|
|
333
|
+
* project may take.
|
|
334
|
+
*/
|
|
335
|
+
const RUNTIME_STORE_FAMILY = 'host-tree.blender-runtime-';
|
|
336
|
+
/** How long a boot waits on an installer that writes nothing before it carries the tree in memory. */
|
|
337
|
+
const INSTALL_STALL_MS = 20_000;
|
|
338
|
+
|
|
235
339
|
/** The program's filesystem, as {@link BlenderFiles}. */
|
|
236
340
|
function programFiles(filesystem: BrowserFileSystemLike): BlenderFiles {
|
|
237
341
|
// One listener for the engine's life, and one waiter per wait: a change ends
|
|
@@ -317,7 +421,10 @@ export async function startWaliBlenderEngine(
|
|
|
317
421
|
])
|
|
318
422
|
filesystem.mkdirSync(directory, { recursive: true });
|
|
319
423
|
const fromIndex = layers['sharedFileLayerFromIndex'] as LayerFromIndex | undefined;
|
|
320
|
-
const
|
|
424
|
+
const fromStore = layers['storeFileLayerFromIndex'] as StoreLayerFromIndex | undefined;
|
|
425
|
+
const holdTree = layers['holdTreeStore'] as ((projectId: string) => Promise<void>) | undefined;
|
|
426
|
+
const runtimeLayer = (fromStore ? await installedRuntime(fromStore, holdTree, options.log) : undefined)
|
|
427
|
+
?? (fromIndex ? await sharedRuntime(fromIndex, options.log) : undefined);
|
|
321
428
|
if (!runtimeLayer) await stageRuntime(filesystem, options.log);
|
|
322
429
|
filesystem.writeFileSync(SESSION_SCRIPT, sessionPython);
|
|
323
430
|
|