From 17a7c3846885f5e05b40856aa0c41e1d38146b22 Mon Sep 17 00:00:00 2001 From: Noeri Huisman <8823461+mrxz@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:05:43 +0200 Subject: [PATCH] Use type information of rpcHandlers for worker.call --- src/ExtSplats.ts | 13 +++-- src/PackedSplats.ts | 9 +--- src/SparkRenderer.ts | 45 ++++++---------- src/SplatLoader.ts | 126 +++++++------------------------------------ src/SplatPager.ts | 17 ++++-- src/SplatWorker.ts | 21 +++++--- src/worker.ts | 38 +++++++------ 7 files changed, 86 insertions(+), 183 deletions(-) diff --git a/src/ExtSplats.ts b/src/ExtSplats.ts index 198f9439..7f47c307 100644 --- a/src/ExtSplats.ts +++ b/src/ExtSplats.ts @@ -690,7 +690,10 @@ export class ExtSplats implements SplatSource { : quality ? 1.75 : 1.5; - const extArrays = [this.extArrays[0].slice(), this.extArrays[1].slice()]; + const extArrays = [ + this.extArrays[0].slice(), + this.extArrays[1].slice(), + ] as const; const rgba = rgbaArray ? (await rgbaArray.getArray()).slice() : undefined; const extra = { sh1: this.extra.sh1 ? (this.extra.sh1 as Uint32Array).slice() : undefined, @@ -698,7 +701,7 @@ export class ExtSplats implements SplatSource { sh3: this.extra.sh3 ? (this.extra.sh3 as Uint32Array).slice() : undefined, }; const decoded = await workerPool.withWorker(async (worker) => { - return (await worker.call( + return await worker.call( quality ? "qualityLodExtSplats" : "tinyLodExtSplats", { numSplats: this.numSplats, @@ -707,11 +710,7 @@ export class ExtSplats implements SplatSource { lodBase, rgba, }, - )) as { - numSplats: number; - extArrays: [Uint32Array, Uint32Array]; - extra: Record; - }; + ); }); const lodSplats = new ExtSplats(decoded); diff --git a/src/PackedSplats.ts b/src/PackedSplats.ts index 9d90dcf6..dbcb9e63 100644 --- a/src/PackedSplats.ts +++ b/src/PackedSplats.ts @@ -914,7 +914,7 @@ export class PackedSplats implements SplatSource { sh3: this.extra.sh3 ? (this.extra.sh3 as Uint32Array).slice() : undefined, }; const decoded = await workerPool.withWorker(async (worker) => { - return (await worker.call( + return await worker.call( quality ? "qualityLodPackedSplats" : "tinyLodPackedSplats", { numSplats: this.numSplats, @@ -924,12 +924,7 @@ export class PackedSplats implements SplatSource { rgba, encoding: this.splatEncoding ?? DEFAULT_SPLAT_ENCODING, }, - )) as { - numSplats: number; - packedArray: Uint32Array; - extra: Record; - splatEncoding: SplatEncoding; - }; + ); }); const lodSplats = new PackedSplats(decoded); diff --git a/src/SparkRenderer.ts b/src/SparkRenderer.ts index 1e24ef64..269cd118 100644 --- a/src/SparkRenderer.ts +++ b/src/SparkRenderer.ts @@ -1046,15 +1046,11 @@ export class SparkRenderer extends THREE.Mesh { if (!this.sortWorker) { this.sortWorker = new SplatWorker(); } - const result = (await this.sortWorker.call("sortSplats32", { + const result = await this.sortWorker.call("sortSplats32", { numSplats, readback, ordering, - })) as { - readback: Uint32Array; - ordering: Uint32Array; - activeSplats: number; - }; + }); if (this.sortDelay > 0) { await new Promise((resolve) => setTimeout(resolve, this.sortDelay)); @@ -1250,9 +1246,9 @@ export class SparkRenderer extends THREE.Mesh { numFetchers: this.numLodFetchers, }); - const { lodId } = (await worker.call("newLodTree", { + const { lodId } = await worker.call("newLodTree", { capacity: this.pager.maxSplats, - })) as { lodId: number }; + }); this.pagerId = lodId; } @@ -1346,17 +1342,17 @@ export class SparkRenderer extends THREE.Mesh { splats: PackedSplats | ExtSplats | PagedSplats, ) { if (splats instanceof PackedSplats || splats instanceof ExtSplats) { - const { lodId } = (await worker.call("initLodTree", { + const { lodId } = await worker.call("initLodTree", { numSplats: splats.numSplats ?? 0, lodTree: (splats.extra.lodTree as Uint32Array).slice(), - })) as { lodId: number }; + }); this.lodIds.set(splats, { lodId, lastTouched: performance.now() }); this.lodIdToSplats.set(lodId, splats); // console.log("*** initLodTree", lodId, splats.extra.lodTree, splats); } else { - const { lodId } = (await worker.call("newSharedLodTree", { + const { lodId } = await worker.call("newSharedLodTree", { lodId: this.pagerId, - })) as { lodId: number }; + }); this.lodIds.set(splats, { lodId, lastTouched: performance.now() }); this.lodIdToSplats.set(lodId, splats); // console.log("*** newSharedLodTree", lodId, this.pagerId, splats); @@ -1438,20 +1434,13 @@ export class SparkRenderer extends THREE.Mesh { ); const traverseStart = performance.now(); - const result = (await worker.call("traverseLodTrees", { + const result = await worker.call("traverseLodTrees", { maxSplats, pixelScaleLimit, lastPixelLimit: this.lastPixelLimit, instances, traverseMode: this.lodTraverseMode, - })) as { - keyIndices: Record< - string, - { lodId: number; numSplats: number; indices: Uint32Array } - >; - chunks: [number, number][]; - pixelLimit?: number; - }; + }); this.lastTraverseTime = performance.now() - traverseStart; const { keyIndices, chunks, pixelLimit } = result; @@ -1518,16 +1507,12 @@ export class SparkRenderer extends THREE.Mesh { ) { this.lastLodRaycastTime = performance.now(); const traverseStart = performance.now(); - const result = (await worker.call("traverseLodTrees", { + const result = await worker.call("traverseLodTrees", { maxSplats: Math.min(this.lodRaycast, Math.round(totalLodSplats * 0.1)), pixelScaleLimit, instances, - })) as { - keyIndices: Record< - string, - { lodId: number; numSplats: number; indices: Uint32Array } - >; - }; + traverseMode: this.lodTraverseMode, + }); const raycastTraverseTime = performance.now() - traverseStart; const { keyIndices } = result; @@ -2063,10 +2048,10 @@ export class SparkRenderer extends THREE.Mesh { } const result = await this.ensureLodWorker().exclusive(async (worker) => { - return (await worker.call("getLodTreeLevel", { + return await worker.call("getLodTreeLevel", { lodId: instance.lodId, level, - })) as { indices: Uint32Array }; + }); }); if (splats.packedSplats?.lodSplats) { diff --git a/src/SplatLoader.ts b/src/SplatLoader.ts index b5866099..e813a907 100644 --- a/src/SplatLoader.ts +++ b/src/SplatLoader.ts @@ -4,8 +4,7 @@ import { ExtSplats, type ExtSplatsOptions } from "./ExtSplats"; import { PackedSplats, type PackedSplatsOptions } from "./PackedSplats"; import { SplatMesh } from "./SplatMesh"; import { workerPool } from "./SplatWorker"; -import { type SplatEncoding, SplatFileType } from "./defines"; -import { PlyReader } from "./ply"; +import { type ExtResult, type PackedResult, SplatFileType } from "./defines"; import { decompressPartialGzip, getTextureSize } from "./utils"; // SplatLoader implements the THREE.Loader interface and supports loading a variety @@ -113,19 +112,6 @@ export class SplatLoader extends Loader { nonLod = splatsNonLod; } - // let init: { - // numSplats: number; - // packedArray: Uint32Array; - // extra: Record; - // splatEncoding: SplatEncoding; - // } | null = null; - // let initExt: { - // numSplats: number; - // ext0: Uint32Array; - // ext1: Uint32Array; - // extra: Record; - // } | null = null; - const onStatus = async (data: unknown) => { const { loaded, total } = data as { loaded: number; total: number }; if (loaded !== undefined && onProgress) { @@ -154,47 +140,12 @@ export class SplatLoader extends Loader { } worker.call("nextChunk", { chunk }); } - - // if ((data as { orig?: unknown }).orig) { - // if (extSplats) { - // initExt = (data as { orig?: unknown }).orig as { - // numSplats: number; - // ext0: Uint32Array; - // ext1: Uint32Array; - // extra: Record; - // }; - // extSplats.initialize({ - // numSplats: initExt?.numSplats, - // extArrays: [initExt?.ext0, initExt?.ext1], - // extra: initExt?.extra, - // }); - // calledOnLoad = true; - // onLoad?.(extSplats); - // } else if (packedSplats) { - // init = (data as { orig?: unknown }).orig as { - // numSplats: number; - // packedArray: Uint32Array; - // extra: Record; - // splatEncoding: SplatEncoding; - // }; - // packedSplats.initialize({ - // numSplats: init?.numSplats, - // packedArray: init?.packedArray, - // extra: init?.extra, - // splatEncoding: init?.splatEncoding, - // }); - // calledOnLoad = true; - // onLoad?.(packedSplats); - // } else { - // console.warn("No splats to initialize"); - // } - // } }; const basedUrl = resolvedURL ? new URL(resolvedURL, window.location.href).toString() : undefined; - const decoded = (await worker.call( + const decoded = await worker.call( extSplats ? "loadExtSplats" : "loadPackedSplats", { url: basedUrl, @@ -212,73 +163,36 @@ export class SplatLoader extends Loader { lodAbove, }, { onStatus }, - )) as { - numSplats: number; - packedArray?: Uint32Array; - ext0?: Uint32Array; - ext1?: Uint32Array; - extra: Record; - splatEncoding?: SplatEncoding; - lodSplats?: - | { - numSplats: number; - packedArray?: Uint32Array; - ext0?: Uint32Array; - ext1?: Uint32Array; - extra: Record; - splatEncoding?: SplatEncoding; - } - | PackedSplats - | ExtSplats; - }; + ); - if (decoded.lodSplats) { + // NOTE: Make use of the fact that ExtResult and PackedResult are compatible + // with the ExtSplatsOptions and PackedSplatsOptions types. + const options = decoded as ExtSplatsOptions | PackedSplatsOptions; + + // Convert the lodSplats from the Ext/PackedResult + // into actual ExtSplats or PackedSplats if present + if ("lodSplats" in decoded) { if (extSplats) { - decoded.lodSplats = new ExtSplats({ - ...(decoded.lodSplats as { - numSplats: number; - extArrays: [Uint32Array, Uint32Array]; - extra: Record; - }), + options.lodSplats = new ExtSplats({ + ...(decoded.lodSplats as ExtResult), }); } else { - decoded.lodSplats = new PackedSplats({ - ...(decoded.lodSplats as { - numSplats: number; - packedArray: Uint32Array; - extra: Record; - splatEncoding: SplatEncoding; - }), + options.lodSplats = new PackedSplats({ + ...(decoded.lodSplats as PackedResult), maxSplats: packedSplats?.maxSplats, }); } } + let resultSplats: ExtSplats | PackedSplats; if (extSplats) { - const initExtSplats = { - // ...(initExt ?? {}), - ...decoded, - }; - extSplats.initialize(initExtSplats as ExtSplatsOptions); - // if (!calledOnLoad) { - onLoad?.(extSplats); - // } + resultSplats = extSplats; + extSplats.initialize(options as ExtSplatsOptions); } else { - const initSplats = { - // ...(init ?? {}), - ...decoded, - }; - if (packedSplats) { - packedSplats.initialize(initSplats as PackedSplatsOptions); - // if (!calledOnLoad) { - onLoad?.(packedSplats); - // } - } else { - // if (!calledOnLoad) { - onLoad?.(new PackedSplats(initSplats as PackedSplatsOptions)); - // } - } + resultSplats = packedSplats ?? new PackedSplats(); + resultSplats.initialize(options as PackedSplatsOptions); } + onLoad?.(resultSplats); }) .catch((error) => { this.manager.itemError(resolvedURL ?? ""); diff --git a/src/SplatPager.ts b/src/SplatPager.ts index d9f33d33..0fbe427a 100644 --- a/src/SplatPager.ts +++ b/src/SplatPager.ts @@ -246,13 +246,17 @@ export class PagedSplats implements SplatSource { throw new Error("PagedSplats.pager not set"); } if (!this.pager.extSplats) { - const result = (await worker.call("loadPackedSplats", { + const result = await worker.call("loadPackedSplats", { fileBytes: decodeBytes, pathName: this.chunkUrl(chunk), sh1Codes: this.sh1Codes?.slice(), sh2Codes: this.sh2Codes?.slice(), - sh3Codes: this.sh3Codes?.slice(), - })) as { lodSplats: PackedResult }; + sh3Codes: (this.sh3Codes as Uint32Array | undefined)?.slice(), + }); + if (!("lodSplats" in result)) { + throw new Error("Loaded chunk does not contain LoD splats"); + } + const lodSplats = result.lodSplats; if (!this.splatEncoding) { this.splatEncoding = lodSplats.splatEncoding; @@ -287,7 +291,7 @@ export class PagedSplats implements SplatSource { } const sh3Codes = this.sh3Codes as [Uint32Array, Uint32Array] | undefined; - const result = (await worker.call("loadExtSplats", { + const result = await worker.call("loadExtSplats", { fileBytes: decodeBytes, pathName: this.chunkUrl(chunk), sh1Codes: this.sh1Codes?.slice(), @@ -295,7 +299,10 @@ export class PagedSplats implements SplatSource { sh3Codes: sh3Codes ? [sh3Codes[0].slice(), sh3Codes[1].slice()] : undefined, - })) as { lodSplats: ExtResult }; + }); + if (!("lodSplats" in result)) { + throw new Error("Loaded chunk does not contain LoD splats"); + } const lodSplats = result.lodSplats; if (!this.splatEncoding) { this.splatEncoding = DEFAULT_SPLAT_ENCODING; diff --git a/src/SplatWorker.ts b/src/SplatWorker.ts index 42398e26..7a6c8201 100644 --- a/src/SplatWorker.ts +++ b/src/SplatWorker.ts @@ -1,5 +1,6 @@ import { getTransferable } from "./utils"; import { WASM_MODULE } from "./wasm"; +import type { rpcHandlers } from "./worker"; import BundledWorker from "./worker?worker&inline"; type PromiseRecord = { @@ -68,14 +69,18 @@ export class SplatWorker { } } - async call( - name: string, - args: unknown, + async call>( + name: T, + args: Parameters[0], options: { onStatus?: (data: unknown) => void } = {}, - ): Promise { + ): Promise { const id = ++SplatWorker.currentId; - const promise = new Promise((resolve, reject) => { - this.messages[id] = { resolve, reject, onStatus: options.onStatus }; + const promise = new Promise((resolve, reject) => { + this.messages[id] = { + resolve: resolve as () => void, + reject, + onStatus: options.onStatus, + }; }); this.worker.postMessage( { id, name, args }, @@ -95,7 +100,7 @@ export class SplatWorker { } } -export class NewSplatWorkerPool { +export class SplatWorkerPool { maxWorkers; numWorkers = 0; freelist: SplatWorker[] = []; @@ -150,4 +155,4 @@ export class NewSplatWorkerPool { } } -export const workerPool = new NewSplatWorkerPool(); +export const workerPool = new SplatWorkerPool(); diff --git a/src/worker.ts b/src/worker.ts index 1801a758..5b5d3648 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -39,6 +39,7 @@ const rpcHandlers = { getLodTreeLevel, nextChunk, }; +export type rpcHandlers = typeof rpcHandlers; async function onMessage(event: MessageEvent) { const { @@ -76,8 +77,8 @@ function sortSplats16({ ordering, }: { numSplats: number; - readback: Uint16Array; - ordering: Uint32Array; + readback: Uint16Array; + ordering: Uint32Array; }) { const activeSplats = sort_splats(numSplats, readback, ordering); return { activeSplats, readback, ordering }; @@ -89,8 +90,8 @@ function sortSplats32({ ordering, }: { numSplats: number; - readback: Uint32Array; - ordering: Uint32Array; + readback: Uint32Array; + ordering: Uint32Array; }) { const activeSplats = sort32_splats(numSplats, readback, ordering); return { activeSplats, readback, ordering }; @@ -335,14 +336,9 @@ async function loadPackedSplats( } let result: - | (ReturnType & { - lodSplats?: ReturnType; - }) - | { lodSplats?: ReturnType } = {}; - - // if (nonLod === true) { - // sendStatus({ orig: toPackedResult(packed as DecodedPackedResult) }); - // } else if (nonLod === "wait") { + | (PackedResult & { lodSplats?: PackedResult }) + | { lodSplats?: PackedResult } = {}; + if (nonLod) { // Wait until LoD computation is complete before resolving full PackedSplats result result = toPackedResult(decoded.to_packedsplats() as DecodedPackedResult); @@ -370,7 +366,9 @@ async function loadPackedSplats( const lodPacked = decoded.to_packedsplats_lod(); result.lodSplats = toPackedResult(lodPacked as DecodedPackedResult); - return result; + return result as + | (PackedResult & { lodSplats: PackedResult }) + | { lodSplats: PackedResult }; } type DecodedExtResult = { @@ -495,10 +493,8 @@ async function loadExtSplats( } let result: - | (ReturnType & { - lodSplats?: ReturnType; - }) - | { lodSplats?: ReturnType } = {}; + | (ExtResult & { lodSplats?: ExtResult }) + | { lodSplats?: ExtResult } = {}; if (nonLod) { // Wait until LoD computation is complete before resolving full PackedSplats result @@ -527,7 +523,9 @@ async function loadExtSplats( const lodPacked = decoded.to_extsplats_lod(); result.lodSplats = toExtResult(lodPacked as DecodedExtResult); - return result; + return result as + | (ExtResult & { lodSplats: ExtResult }) + | { lodSplats: ExtResult }; } async function tinyLodPackedSplats({ @@ -642,11 +640,11 @@ async function qualityLodExtSplats({ encoding, }: { numSplats: number; - extArrays: [Uint32Array, Uint32Array]; + extArrays: readonly [Uint32Array, Uint32Array]; extra?: Record; lodBase?: number; rgba?: Uint8Array; - encoding: SplatEncoding; + encoding?: SplatEncoding; }) { const base = Math.max(1.1, Math.min(2.0, lodBase ?? 1.75)); const lodStart = performance.now();