@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.
@@ -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 runtimeLayer = fromIndex ? await sharedRuntime(fromIndex, options.log) : undefined;
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
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@volter/blender-engine",
3
3
  "author": "Volter AI, Inc.",
4
4
  "license": "GPL-3.0-or-later",
5
- "version": "0.1.21",
5
+ "version": "0.1.22",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },