Something went wrong. Try again.
[PRE-ALPHA] an open source web-based 2D/3D graphing calculator cartesium.org
Something went wrong. Try again.
TypeScript
at mathquill
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160import { buildScene } from './cartesium';import { createLivePreviewDocument, needsWholeSceneRefinement, refineDelayMs, shouldPresentScene} from './rendering';import type { ScenePresentationPlan } from './scene-demand';import { recordScenePublication } from './scene-telemetry';import type { GraphDocument2d, RenderScene2d } from './types';
interface ScenePresentationControllerOptions { publish: (scene: RenderScene2d, document: GraphDocument2d) => void; setBusy: (busy: boolean) => void; setError: (error: string | null) => void; build?: (document: GraphDocument2d) => Promise<RenderScene2d>; setTimer?: (callback: () => void, delayMs: number) => number; clearTimer?: (handle: number) => void;}
/** Owns preview/refinement request mechanics while the shell owns render policy. */export class ScenePresentationController { readonly #publish: (scene: RenderScene2d, document: GraphDocument2d) => void; readonly #setBusy: (busy: boolean) => void; readonly #setError: (error: string | null) => void; readonly #build: (document: GraphDocument2d) => Promise<RenderScene2d>; readonly #setTimer: (callback: () => void, delayMs: number) => number; readonly #clearTimer: (handle: number) => void; #requestRevision = 0; #nextSceneSequence = 0; #presentedSceneSequence = 0; #settledContentEpoch = 0; #currentContentSignature: string | undefined; #refineTimer: number | undefined;
constructor(options: ScenePresentationControllerOptions) { this.#publish = options.publish; this.#setBusy = options.setBusy; this.#setError = options.setError; this.#build = options.build ?? buildScene; // Delay browser-global lookup until an effect actually schedules work so // the controller remains safe to construct during server rendering. this.#setTimer = options.setTimer ?? ((callback, delayMs) => window.setTimeout(callback, delayMs)); this.#clearTimer = options.clearTimer ?? ((handle) => window.clearTimeout(handle)); }
setPlan(plan: ScenePresentationPlan | undefined) { const requestRevision = ++this.#requestRevision; this.#clearRefineTimer();
if (!plan) { this.#setBusy(false);
return; }
const contentChanged = plan.contentSignature !== this.#currentContentSignature;
// Live model requests may complete a little behind animation/drag input so // rendering continues to make progress. Once that model settles, however, // no completion from the preceding live stream may replace the committed // value. A content epoch provides that one-way admission fence while still // allowing camera-only work for the same model to trail a gesture. if (contentChanged && !plan.interactive) this.#settledContentEpoch += 1; this.#currentContentSignature = plan.contentSignature; const settledContentEpoch = this.#settledContentEpoch;
this.#setBusy(true);
if (!plan.preserveCameraSceneOnSettle) { // Dispatch immediately. The worker/client queue already coalesces this // stream to one active request and the newest request waiting to start. void this.#request( createLivePreviewDocument(plan.document, plan.interactive), plan.document, requestRevision, settledContentEpoch, !plan.interactive ); }
if (plan.interactive) return;
if (plan.preserveCameraSceneOnSettle && plan.retainedSceneNeedsRefinement) { this.#scheduleRefinement(plan.document, requestRevision, settledContentEpoch, true); } else if (plan.preserveCameraSceneOnSettle) { // The retained scene already satisfies the settled camera. No request // will run a completion handler, so clear the progress state here. this.#setBusy(false); } }
dispose() { this.#requestRevision += 1; this.#clearRefineTimer(); }
async #request( requestDocument: GraphDocument2d, presentationDocument: GraphDocument2d, requestRevision: number, settledContentEpoch: number, refineAfterPreview = false ) { const sceneSequence = ++this.#nextSceneSequence;
try { const scene = await this.#build(requestDocument);
if (settledContentEpoch !== this.#settledContentEpoch) return;
// Whole-scene geometry may safely trail a camera gesture. Sequence order, // rather than current-plan identity, prevents an older completion from // replacing a scene that was requested and presented later. if (!shouldPresentScene(sceneSequence, this.#presentedSceneSequence)) return; recordScenePublication(scene); this.#publish(scene, presentationDocument); this.#presentedSceneSequence = sceneSequence; this.#setError(null);
if ( refineAfterPreview && requestRevision === this.#requestRevision && needsWholeSceneRefinement(scene) ) { this.#scheduleRefinement( presentationDocument, requestRevision, settledContentEpoch, false ); } } catch (error) { if (requestRevision !== this.#requestRevision) return; this.#setError(error instanceof Error ? error.message : String(error)); } finally { if (requestRevision === this.#requestRevision) this.#setBusy(false); } }
#scheduleRefinement( document: GraphDocument2d, requestRevision: number, settledContentEpoch: number, preservingCameraScene: boolean ) { this.#refineTimer = this.#setTimer(() => { this.#refineTimer = undefined; void this.#request(document, document, requestRevision, settledContentEpoch); }, refineDelayMs(preservingCameraScene)); }
#clearRefineTimer() { if (this.#refineTimer === undefined) return; this.#clearTimer(this.#refineTimer); this.#refineTimer = undefined; }}