@@ -31,6 +31,15 @@ interface CachedModelEntry {
|
||||
lastUsedAt: number
|
||||
}
|
||||
|
||||
type GuideModelDiagnosticEvent =
|
||||
| 'cache-hit'
|
||||
| 'cache-miss'
|
||||
| 'in-flight-hit'
|
||||
| 'load-start'
|
||||
| 'load-complete'
|
||||
| 'load-failed'
|
||||
| 'preload-skip'
|
||||
|
||||
// 当前 SDK 数据包含 8 个可导览楼层;缓存需覆盖多层展开、全楼模型和相邻预加载余量,避免加载中途淘汰刚加载过的单层模型。
|
||||
const defaultMaxModelSourceEntries = 10
|
||||
|
||||
@@ -53,6 +62,30 @@ const emptyResources = (): GuideModelResourceSet => ({
|
||||
textures: new Set()
|
||||
})
|
||||
|
||||
const getNow = () => (
|
||||
typeof performance !== 'undefined' && typeof performance.now === 'function'
|
||||
? performance.now()
|
||||
: Date.now()
|
||||
)
|
||||
|
||||
const isModelDiagnosticsEnabled = () => {
|
||||
if (!import.meta.env.DEV) return false
|
||||
if (typeof window === 'undefined') return true
|
||||
|
||||
const diagnosticsWindow = window as unknown as {
|
||||
__GUIDE_3D_MODEL_DIAGNOSTICS__?: boolean
|
||||
}
|
||||
return diagnosticsWindow.__GUIDE_3D_MODEL_DIAGNOSTICS__ !== false
|
||||
}
|
||||
|
||||
const logModelDiagnostic = (
|
||||
event: GuideModelDiagnosticEvent,
|
||||
payload: Record<string, unknown>
|
||||
) => {
|
||||
if (!isModelDiagnosticsEnabled()) return
|
||||
console.debug(`[GuideModelLoadManager] ${event}`, payload)
|
||||
}
|
||||
|
||||
const collectMaterialTextures = (material: THREE.Material) => {
|
||||
const textures = new Set<THREE.Texture>()
|
||||
const materialRecord = material as unknown as Record<string, unknown>
|
||||
@@ -295,7 +328,16 @@ export class GuideModelLoadManager {
|
||||
const candidates = options.urls.map((url) => url.trim()).filter(Boolean)
|
||||
for (const url of candidates) {
|
||||
const key = this.createCacheKey(url, options.semanticKey)
|
||||
if (!key || this.sourceCache.has(key) || this.inFlightCache.has(key)) return
|
||||
if (!key || this.sourceCache.has(key) || this.inFlightCache.has(key)) {
|
||||
logModelDiagnostic('preload-skip', {
|
||||
label: options.label,
|
||||
url,
|
||||
key,
|
||||
reason: !key ? 'empty-key' : this.sourceCache.has(key) ? 'cache-hit' : 'in-flight-hit',
|
||||
diagnostics: this.getDiagnostics()
|
||||
})
|
||||
return
|
||||
}
|
||||
await this.loadSource(url, options)
|
||||
return
|
||||
}
|
||||
@@ -315,23 +357,60 @@ export class GuideModelLoadManager {
|
||||
if (cached) {
|
||||
cached.lastUsedAt = Date.now()
|
||||
this.diagnostics.hits += 1
|
||||
logModelDiagnostic('cache-hit', {
|
||||
label: options.label,
|
||||
url,
|
||||
key,
|
||||
diagnostics: this.getDiagnostics()
|
||||
})
|
||||
return cached
|
||||
}
|
||||
|
||||
this.diagnostics.misses += 1
|
||||
logModelDiagnostic('cache-miss', {
|
||||
label: options.label,
|
||||
url,
|
||||
key,
|
||||
diagnostics: this.getDiagnostics()
|
||||
})
|
||||
|
||||
let inFlight = this.inFlightCache.get(key)
|
||||
const startedAt = getNow()
|
||||
if (inFlight) {
|
||||
this.diagnostics.inFlightHits += 1
|
||||
logModelDiagnostic('in-flight-hit', {
|
||||
label: options.label,
|
||||
url,
|
||||
key,
|
||||
diagnostics: this.getDiagnostics()
|
||||
})
|
||||
} else {
|
||||
logModelDiagnostic('load-start', {
|
||||
label: options.label,
|
||||
url,
|
||||
key
|
||||
})
|
||||
inFlight = options.loadSource(url)
|
||||
this.inFlightCache.set(key, inFlight)
|
||||
}
|
||||
|
||||
const gltf = await inFlight.finally(() => {
|
||||
let gltf: GLTF
|
||||
try {
|
||||
gltf = await inFlight
|
||||
} catch (error) {
|
||||
logModelDiagnostic('load-failed', {
|
||||
label: options.label,
|
||||
url,
|
||||
key,
|
||||
elapsedMs: Math.round(getNow() - startedAt),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
})
|
||||
throw error
|
||||
} finally {
|
||||
if (this.inFlightCache.get(key) === inFlight) {
|
||||
this.inFlightCache.delete(key)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const entry: CachedModelEntry = {
|
||||
key,
|
||||
@@ -341,6 +420,13 @@ export class GuideModelLoadManager {
|
||||
}
|
||||
this.sourceCache.set(key, entry)
|
||||
this.trim()
|
||||
logModelDiagnostic('load-complete', {
|
||||
label: options.label,
|
||||
url,
|
||||
key,
|
||||
elapsedMs: Math.round(getNow() - startedAt),
|
||||
diagnostics: this.getDiagnostics()
|
||||
})
|
||||
return entry
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user