@volter/blender-engine 0.1.5 → 0.1.7

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.
@@ -27,6 +27,9 @@ type DistributiveOmit<T, K extends keyof T> = T extends unknown ? Omit<T, K> : n
27
27
  type Request = DistributiveOmit<WorkerRequest, 'id'>;
28
28
 
29
29
  export interface BlenderRuntimeOptions {
30
+ /** Operation-only diagnostics, never request payloads. The optional observer
31
+ * must not affect work, even if it throws. End calls balance overlapping work. */
32
+ work?(label: string): () => void;
30
33
  history?(entries: readonly NativeHistoryEntry[]): void;
31
34
  /** Display a frame. A screenshot `capture` has its view REMEMBERED so the
32
35
  * next document capture photographs what the agent asked for; a render
@@ -149,6 +152,7 @@ export class BlenderRuntime {
149
152
  };
150
153
  /** `performance.now()` at the `postMessage` of every outstanding call. */
151
154
  readonly #callStarts = new Map<number, number>();
155
+ readonly #callWork = new Map<number, () => void>();
152
156
  #lastCallMs: number | null = null;
153
157
  #maxCompletedMs = 0;
154
158
  #completedOver5s = 0;
@@ -198,6 +202,11 @@ export class BlenderRuntime {
198
202
  return this.#project;
199
203
  }
200
204
 
205
+ /** The startup document owned by this worker, including while boot is pending. */
206
+ get document(): string | null {
207
+ return this.#document;
208
+ }
209
+
201
210
  /** Idempotent: the first call boots Blender at `project`'s
202
211
  * absolute path; later calls await it (a different path is refused).
203
212
  *
@@ -442,7 +451,8 @@ export class BlenderRuntime {
442
451
  if (this.#terminated) return;
443
452
  this.#terminated = true;
444
453
  globalThis.removeEventListener?.('beforeunload', this.#beforeUnload);
445
- this.#worker.terminate();
454
+ const end = this.#work('terminating worker');
455
+ try { this.#worker.terminate(); } finally { end(); }
446
456
  const error = new Error('The Blender session was terminated');
447
457
  for (const id of [...this.#pending.keys()]) this.#settled(id);
448
458
  for (const pending of this.#pending.values()) pending.reject(error);
@@ -489,6 +499,8 @@ export class BlenderRuntime {
489
499
 
490
500
  /** Close a call's window, whether it answered, threw, or was terminated. */
491
501
  #settled(id: number): void {
502
+ this.#callWork.get(id)?.();
503
+ this.#callWork.delete(id);
492
504
  const started = this.#callStarts.get(id);
493
505
  if (started === undefined) return;
494
506
  this.#callStarts.delete(id);
@@ -510,10 +522,24 @@ export class BlenderRuntime {
510
522
  const started = performance.now();
511
523
  this.#callStarts.set(id, started);
512
524
  this.#lastCallWindow = { start: started, end: null };
513
- this.#worker.postMessage({ ...request, id });
525
+ this.#callWork.set(id, this.#work(`waiting for worker ${request.op}`));
526
+ try {
527
+ this.#worker.postMessage({ ...request, id });
528
+ } catch (error) {
529
+ this.#pending.delete(id);
530
+ this.#settled(id);
531
+ reject(error instanceof Error ? error : new Error(String(error)));
532
+ }
514
533
  });
515
534
  }
516
535
 
536
+ #work(label: string): () => void {
537
+ try {
538
+ const end = this.#options.work?.(label);
539
+ return () => { try { end?.(); } catch { /* diagnostics never fail work */ } };
540
+ } catch { return () => {}; }
541
+ }
542
+
517
543
  async #receive(reply: WorkerReply): Promise<void> {
518
544
  if ('op' in reply) {
519
545
  if (reply.op === 'document-dirty') {
@@ -539,6 +565,7 @@ export class BlenderRuntime {
539
565
  const frame = reply.frame as { session?: unknown; revision?: unknown } | null;
540
566
  if (typeof frame?.session === 'string' && typeof frame.revision === 'number')
541
567
  this.#presented = { session: frame.session, revision: frame.revision };
568
+ const end = this.#work('presenting worker frame');
542
569
  try {
543
570
  const answer = await this.#options.present(reply.frame, reply.description, reply.capture);
544
571
  this.#worker.postMessage({
@@ -555,6 +582,8 @@ export class BlenderRuntime {
555
582
  id: reply.id,
556
583
  error: error instanceof Error ? error.message : String(error),
557
584
  } satisfies WorkerRequest);
585
+ } finally {
586
+ end();
558
587
  }
559
588
  return;
560
589
  }