@@ -15,6 +15,7 @@ const RUNTIME_MAGIC = 0x53475254; // SGRT
1515const RUNTIME_VERSION = 2 ;
1616const HEADER_BYTE_LENGTH = 22 * 4 ;
1717export const DEFAULT_SPLAT_BASE_LOD_MAX_SPLATS = 65536 ;
18+ export const DEFAULT_PREPARED_SPLAT_RUNTIME_CACHE_MAX_BYTES = 256 * 1024 * 1024 ;
1819
1920export interface PreparedSplatRuntime {
2021 runtimeKey : string ;
@@ -54,7 +55,10 @@ interface RuntimeRequestOptions extends RuntimeSourceOptions {
5455const assetPromiseCache = new Map < string , Promise < GaussianSplatAsset > > ( ) ;
5556const runtimePromiseCache = new Map < string , Promise < PreparedSplatRuntime > > ( ) ;
5657const runtimeValueCache = new Map < string , PreparedSplatRuntime > ( ) ;
58+ const runtimeValueSizeCache = new Map < string , number > ( ) ;
5759const idlePrewarmKeys = new Set < string > ( ) ;
60+ let preparedRuntimeCacheMaxBytes = DEFAULT_PREPARED_SPLAT_RUNTIME_CACHE_MAX_BYTES ;
61+ let preparedRuntimeCacheBytes = 0 ;
5862
5963function normalizeRequestedMaxSplats ( requestedMaxSplats : number | undefined ) : number {
6064 const normalized = Math . floor ( requestedMaxSplats ?? 0 ) ;
@@ -119,6 +123,76 @@ function cloneViewBytes(view: Float32Array): ArrayBuffer {
119123 return bytes . buffer as ArrayBuffer ;
120124}
121125
126+ function getPreparedRuntimeByteLength ( runtime : PreparedSplatRuntime ) : number {
127+ return runtime . centers . byteLength
128+ + runtime . centerOpacityTextureData . byteLength
129+ + runtime . colorTextureData . byteLength
130+ + runtime . axisXTextureData . byteLength
131+ + runtime . axisYTextureData . byteLength
132+ + runtime . axisZTextureData . byteLength
133+ + runtime . orderTemplateData . byteLength ;
134+ }
135+
136+ function deletePreparedRuntime ( runtimeKey : string ) : void {
137+ const size = runtimeValueSizeCache . get ( runtimeKey ) ;
138+ if ( typeof size === 'number' ) {
139+ preparedRuntimeCacheBytes = Math . max ( 0 , preparedRuntimeCacheBytes - size ) ;
140+ runtimeValueSizeCache . delete ( runtimeKey ) ;
141+ }
142+ runtimeValueCache . delete ( runtimeKey ) ;
143+ }
144+
145+ function touchPreparedRuntime ( runtimeKey : string ) : PreparedSplatRuntime | null {
146+ const runtime = runtimeValueCache . get ( runtimeKey ) ;
147+ if ( ! runtime ) {
148+ return null ;
149+ }
150+
151+ runtimeValueCache . delete ( runtimeKey ) ;
152+ runtimeValueCache . set ( runtimeKey , runtime ) ;
153+ return runtime ;
154+ }
155+
156+ function trimPreparedRuntimeCache ( ) : void {
157+ while ( preparedRuntimeCacheBytes > preparedRuntimeCacheMaxBytes && runtimeValueCache . size > 0 ) {
158+ const oldestKey = runtimeValueCache . keys ( ) . next ( ) . value ;
159+ if ( oldestKey === undefined ) {
160+ break ;
161+ }
162+ deletePreparedRuntime ( oldestKey ) ;
163+ }
164+ }
165+
166+ function storePreparedRuntime ( runtime : PreparedSplatRuntime ) : PreparedSplatRuntime {
167+ const size = getPreparedRuntimeByteLength ( runtime ) ;
168+ deletePreparedRuntime ( runtime . runtimeKey ) ;
169+
170+ if ( size > preparedRuntimeCacheMaxBytes ) {
171+ log . debug ( 'Skipping prepared gaussian splat runtime cache for oversize entry' , {
172+ runtimeKey : runtime . runtimeKey ,
173+ size,
174+ preparedRuntimeCacheMaxBytes,
175+ } ) ;
176+ return runtime ;
177+ }
178+
179+ runtimeValueCache . set ( runtime . runtimeKey , runtime ) ;
180+ runtimeValueSizeCache . set ( runtime . runtimeKey , size ) ;
181+ preparedRuntimeCacheBytes += size ;
182+ trimPreparedRuntimeCache ( ) ;
183+ return touchPreparedRuntime ( runtime . runtimeKey ) ?? runtime ;
184+ }
185+
186+ function getRuntimeKeyForOptions ( options : RuntimeRequestOptions ) : string {
187+ const requestedMaxSplats = normalizeRequestedMaxSplats ( options . requestedMaxSplats ) ;
188+ return buildRuntimeKey (
189+ options . cacheKey ,
190+ options . variant ,
191+ requestedMaxSplats ,
192+ options . gaussianSplatSequence ,
193+ ) ;
194+ }
195+
122196function gcd ( a : number , b : number ) : number {
123197 let x = Math . abs ( Math . floor ( a ) ) ;
124198 let y = Math . abs ( Math . floor ( b ) ) ;
@@ -199,7 +273,11 @@ async function loadAsset(options: RuntimeSourceOptions): Promise<GaussianSplatAs
199273 } ) ( ) ;
200274
201275 assetPromiseCache . set ( options . cacheKey , promise ) ;
202- void promise . catch ( ( ) => {
276+ void promise . then ( ( ) => {
277+ if ( assetPromiseCache . get ( options . cacheKey ) === promise ) {
278+ assetPromiseCache . delete ( options . cacheKey ) ;
279+ }
280+ } , ( ) => {
203281 if ( assetPromiseCache . get ( options . cacheKey ) === promise ) {
204282 assetPromiseCache . delete ( options . cacheKey ) ;
205283 }
@@ -504,8 +582,7 @@ async function loadRuntimeFromProjectCache(
504582 try {
505583 const buffer = await file . arrayBuffer ( ) ;
506584 const runtime = deserializeRuntime ( runtimeKey , buffer ) ;
507- runtimeValueCache . set ( runtimeKey , runtime ) ;
508- return runtime ;
585+ return storePreparedRuntime ( runtime ) ;
509586 } catch ( error ) {
510587 log . warn ( 'Failed to read gaussian splat runtime cache file' , {
511588 fileHash,
@@ -542,13 +619,8 @@ async function persistRuntimeToProjectCache(
542619
543620async function ensurePreparedRuntime ( options : RuntimeRequestOptions ) : Promise < PreparedSplatRuntime > {
544621 const requestedMaxSplats = normalizeRequestedMaxSplats ( options . requestedMaxSplats ) ;
545- const runtimeKey = buildRuntimeKey (
546- options . cacheKey ,
547- options . variant ,
548- requestedMaxSplats ,
549- options . gaussianSplatSequence ,
550- ) ;
551- const existing = runtimeValueCache . get ( runtimeKey ) ;
622+ const runtimeKey = getRuntimeKeyForOptions ( options ) ;
623+ const existing = touchPreparedRuntime ( runtimeKey ) ;
552624 if ( existing ) return existing ;
553625
554626 const existingPromise = runtimePromiseCache . get ( runtimeKey ) ;
@@ -574,13 +646,17 @@ async function ensurePreparedRuntime(options: RuntimeRequestOptions): Promise<Pr
574646 requestedMaxSplats ,
575647 normalizationBounds ,
576648 ) ;
577- runtimeValueCache . set ( runtimeKey , runtime ) ;
649+ storePreparedRuntime ( runtime ) ;
578650 void persistRuntimeToProjectCache ( options . fileHash , runtime ) ;
579651 return runtime ;
580652 } ) ( ) ;
581653
582654 runtimePromiseCache . set ( runtimeKey , promise ) ;
583- void promise . catch ( ( ) => {
655+ void promise . then ( ( ) => {
656+ if ( runtimePromiseCache . get ( runtimeKey ) === promise ) {
657+ runtimePromiseCache . delete ( runtimeKey ) ;
658+ }
659+ } , ( ) => {
584660 if ( runtimePromiseCache . get ( runtimeKey ) === promise ) {
585661 runtimePromiseCache . delete ( runtimeKey ) ;
586662 }
@@ -591,10 +667,34 @@ async function ensurePreparedRuntime(options: RuntimeRequestOptions): Promise<Pr
591667export function getPreparedSplatRuntimeSync (
592668 options : RuntimeRequestOptions ,
593669) : PreparedSplatRuntime | null {
594- const requestedMaxSplats = normalizeRequestedMaxSplats ( options . requestedMaxSplats ) ;
595- return runtimeValueCache . get (
596- buildRuntimeKey ( options . cacheKey , options . variant , requestedMaxSplats , options . gaussianSplatSequence ) ,
597- ) ?? null ;
670+ return touchPreparedRuntime ( getRuntimeKeyForOptions ( options ) ) ;
671+ }
672+
673+ export function clearPreparedSplatRuntimeCache ( ) : void {
674+ assetPromiseCache . clear ( ) ;
675+ runtimePromiseCache . clear ( ) ;
676+ runtimeValueCache . clear ( ) ;
677+ runtimeValueSizeCache . clear ( ) ;
678+ idlePrewarmKeys . clear ( ) ;
679+ preparedRuntimeCacheBytes = 0 ;
680+ }
681+
682+ export function setPreparedSplatRuntimeCacheMaxBytes ( maxBytes : number ) : void {
683+ const normalized = Math . max ( 0 , Math . floor ( maxBytes ) ) ;
684+ preparedRuntimeCacheMaxBytes = normalized ;
685+ trimPreparedRuntimeCache ( ) ;
686+ }
687+
688+ export function getPreparedSplatRuntimeCacheStats ( ) : {
689+ runtimeCount : number ;
690+ totalBytes : number ;
691+ maxBytes : number ;
692+ } {
693+ return {
694+ runtimeCount : runtimeValueCache . size ,
695+ totalBytes : preparedRuntimeCacheBytes ,
696+ maxBytes : preparedRuntimeCacheMaxBytes ,
697+ } ;
598698}
599699
600700export async function resolvePreparedSplatRuntime (
0 commit comments