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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179import { recordTilePublication } from './tile-telemetry';import { mergeSpatialTileResults, retainCompatibleSpatialTiles} from './tile-presentation';import { SpatialTileScheduler, type TileDemand } from './tile-scheduler';import { spatialStyleSignature, type SpatialDemandTarget } from './tiles';import type { SpatialTileResult } from './worker-protocol';
interface TileScheduler { setDemand(demand: TileDemand, onresource: (result: SpatialTileResult) => void): void; clear(): void;}
interface SpatialTilePresentationOptions { read: () => SpatialTileResult[]; publish: (tiles: SpatialTileResult[]) => void; scheduler?: TileScheduler; requestFrame?: (callback: FrameRequestCallback) => number; cancelFrame?: (handle: number) => void;}
/** * Bridges spatial demand and component presentation state. * * The scheduler owns computation and its cache. This controller owns the * main-thread publication lifecycle: compatible-resource retention, stale * callback rejection, and merging all results received during one browser * frame into one state update. */export class SpatialTilePresentationController { readonly #read: () => SpatialTileResult[]; readonly #publish: (tiles: SpatialTileResult[]) => void; readonly #scheduler: TileScheduler; readonly #requestFrame: (callback: FrameRequestCallback) => number; readonly #cancelFrame: (handle: number) => void; #demandSignature = ''; #demandGeneration = 0; #publishFrame: number | undefined; #pendingResults: SpatialTileResult[] = []; #complexMode: boolean | undefined;
constructor(options: SpatialTilePresentationOptions) { this.#read = options.read; this.#publish = options.publish; this.#scheduler = options.scheduler ?? new SpatialTileScheduler(); // Keep browser globals behind callbacks so constructing the shell during // server rendering does not require a Canvas animation-frame environment. this.#requestFrame = options.requestFrame ?? ((callback) => requestAnimationFrame(callback)); this.#cancelFrame = options.cancelFrame ?? ((handle) => cancelAnimationFrame(handle)); }
setDemand(demand: TileDemand | undefined) { if (!demand) { if (this.#demandSignature === '' && this.#read().length === 0) return; this.#demandSignature = ''; this.#demandGeneration += 1; this.#discardPendingResults(); this.#scheduler.clear();
if (this.#read().length > 0) this.#publish([]);
return; }
const signature = tileDemandSignature(demand);
if (signature === this.#demandSignature) return; this.#demandSignature = signature; const generation = ++this.#demandGeneration; this.#discardPendingResults();
const current = this.#read(); const complexMode = demand.document.complex_mode ?? false;
const semanticModeChanged = this.#complexMode !== undefined && this.#complexMode !== complexMode;
this.#complexMode = complexMode;
const compatible = semanticModeChanged ? [] : retainCompatibleSpatialTiles(current, demand.targets, demand.viewport);
if (compatible.length !== current.length) this.#publish(compatible);
let acceptingSynchronousResults = true; const synchronousResults: SpatialTileResult[] = []; this.#scheduler.setDemand(demand, (result) => { if (generation !== this.#demandGeneration) return;
if (!matchesTarget(result, demand.targets)) return;
if (acceptingSynchronousResults) { synchronousResults.push(result);
return; }
this.#enqueueResult(result); }); acceptingSynchronousResults = false;
if (synchronousResults.length > 0) this.#publishResults(synchronousResults); }
/** * Stop serving the current tile demand without withdrawing the tiles that * belong to the scene still on screen. A whole-scene model request can take * a few frames to arrive; clearing its fallback tiles first would expose the * coarse scene in isolation and can briefly reveal discontinuity artifacts. */ suspend() { if (this.#demandSignature === '') return; this.#demandSignature = ''; this.#demandGeneration += 1; this.#discardPendingResults(); this.#scheduler.clear(); }
dispose() { this.#demandSignature = ''; this.#demandGeneration += 1; this.#discardPendingResults(); this.#scheduler.clear(); }
#enqueueResult(result: SpatialTileResult) { this.#pendingResults.push(result);
if (this.#publishFrame !== undefined) return; this.#publishFrame = this.#requestFrame(() => { this.#publishFrame = undefined;
if (this.#pendingResults.length === 0) return; const results = this.#pendingResults; this.#pendingResults = []; this.#publishResults(results); }); }
#publishResults(results: SpatialTileResult[]) { const current = this.#read(); const merged = mergeSpatialTileResults(current, results);
if (merged === current) return; const published = results.filter((candidate) => merged.includes(candidate)); recordTilePublication(published); this.#publish(merged); }
#discardPendingResults() { this.#pendingResults = [];
if (this.#publishFrame === undefined) return; this.#cancelFrame(this.#publishFrame); this.#publishFrame = undefined; }}
function tileDemandSignature(demand: TileDemand) { return JSON.stringify([ demand.document.complex_mode ?? false, demand.targets, demand.viewport, demand.fallbackCoverage ]);}
function matchesTarget(result: SpatialTileResult, targets: SpatialDemandTarget[]) { return targets.some( ({ expressionId, representation, modelRevision, styleSignature }) => expressionId === result.resource.expressionId && representation === result.resource.representation && modelRevision === result.resource.modelRevision && (styleSignature ?? '') === spatialStyleSignature(result.resource) );}