|
|
@ -32,9 +32,11 @@ export class AssetManager { |
|
|
private adapter: AssetRuntimeAdapter; |
|
|
private adapter: AssetRuntimeAdapter; |
|
|
private bundleRefCounts: Map<string, number> = new Map(); |
|
|
private bundleRefCounts: Map<string, number> = new Map(); |
|
|
private bundleCache: Map<string, AssetsBundle> = new Map(); |
|
|
private bundleCache: Map<string, AssetsBundle> = new Map(); |
|
|
|
|
|
private pendingLoads: Map<string, Promise<AssetsBundle | null>> = new Map(); |
|
|
private sessionBundleRefs: Map<string, Map<string, number>> = new Map(); |
|
|
private sessionBundleRefs: Map<string, Map<string, number>> = new Map(); |
|
|
private graph: AssetGraph = new AssetGraph(); |
|
|
private graph: AssetGraph = new AssetGraph(); |
|
|
private sessionIdSeed = 0; |
|
|
private sessionIdSeed = 0; |
|
|
|
|
|
private defaultSessionId = ""; |
|
|
private defaultSession: AssetSession; |
|
|
private defaultSession: AssetSession; |
|
|
|
|
|
|
|
|
constructor(adapter?: AssetRuntimeAdapter) { |
|
|
constructor(adapter?: AssetRuntimeAdapter) { |
|
|
@ -57,6 +59,9 @@ export class AssetManager { |
|
|
const sessionId = `session-${this.sessionIdSeed}`; |
|
|
const sessionId = `session-${this.sessionIdSeed}`; |
|
|
this.graph.registerSession(sessionId, normalizedOwner); |
|
|
this.graph.registerSession(sessionId, normalizedOwner); |
|
|
this.sessionBundleRefs.set(sessionId, new Map()); |
|
|
this.sessionBundleRefs.set(sessionId, new Map()); |
|
|
|
|
|
if (this.defaultSessionId === "") { |
|
|
|
|
|
this.defaultSessionId = sessionId; |
|
|
|
|
|
} |
|
|
return new AssetSession(this, sessionId, normalizedOwner); |
|
|
return new AssetSession(this, sessionId, normalizedOwner); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -68,6 +73,23 @@ export class AssetManager { |
|
|
return this.graph.getSessionBundles(sessionId); |
|
|
return this.graph.getSessionBundles(sessionId); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async destroySession(sessionId: string): Promise<void> { |
|
|
|
|
|
if (!this.graph.hasSession(sessionId)) { |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
if (sessionId === this.defaultSessionId) { |
|
|
|
|
|
throw new Error("AssetManager: default session cannot be destroyed"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const bundles = this.getSessionBundles(sessionId); |
|
|
|
|
|
for (const bundle of bundles) { |
|
|
|
|
|
await this.releaseBySession(sessionId, bundle); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.sessionBundleRefs.delete(sessionId); |
|
|
|
|
|
this.graph.removeSession(sessionId); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
async acquireBySession( |
|
|
async acquireBySession( |
|
|
sessionId: string, |
|
|
sessionId: string, |
|
|
name: string, |
|
|
name: string, |
|
|
@ -86,29 +108,71 @@ export class AssetManager { |
|
|
|
|
|
|
|
|
const totalRefCount = this.bundleRefCounts.get(name) ?? 0; |
|
|
const totalRefCount = this.bundleRefCounts.get(name) ?? 0; |
|
|
const sessionRefCount = sessionRefs.get(name) ?? 0; |
|
|
const sessionRefCount = sessionRefs.get(name) ?? 0; |
|
|
|
|
|
this.bundleRefCounts.set(name, totalRefCount + 1); |
|
|
|
|
|
sessionRefs.set(name, sessionRefCount + 1); |
|
|
|
|
|
this.graph.trackBundle(sessionId, name); |
|
|
|
|
|
|
|
|
|
|
|
const pendingLoad = this.pendingLoads.get(name); |
|
|
|
|
|
if (pendingLoad) { |
|
|
|
|
|
const bundle = await pendingLoad; |
|
|
|
|
|
if (!bundle) { |
|
|
|
|
|
this.rollbackFailedAcquire(sessionId, name); |
|
|
|
|
|
} |
|
|
|
|
|
return bundle; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (totalRefCount > 0) { |
|
|
if (totalRefCount > 0) { |
|
|
this.bundleRefCounts.set(name, totalRefCount + 1); |
|
|
|
|
|
sessionRefs.set(name, sessionRefCount + 1); |
|
|
|
|
|
this.graph.trackBundle(sessionId, name); |
|
|
|
|
|
logger.debug( |
|
|
logger.debug( |
|
|
`AssetManager: reuse bundle "${name}", refCount = ${totalRefCount + 1}` |
|
|
`AssetManager: reuse bundle "${name}", refCount = ${totalRefCount + 1}` |
|
|
); |
|
|
); |
|
|
onProgress?.(1); |
|
|
onProgress?.(1); |
|
|
return this.bundleCache.get(name) ?? this.adapter.loadBundle(name); |
|
|
return this.bundleCache.get(name) ?? null; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
logger.debug(`AssetManager: loading bundle "${name}"`); |
|
|
logger.debug(`AssetManager: loading bundle "${name}"`); |
|
|
try { |
|
|
const loadPromise = this.adapter |
|
|
const bundle = await this.adapter.loadBundle(name, onProgress); |
|
|
.loadBundle(name, onProgress) |
|
|
this.bundleRefCounts.set(name, 1); |
|
|
.then((bundle) => { |
|
|
this.bundleCache.set(name, bundle); |
|
|
this.bundleCache.set(name, bundle); |
|
|
sessionRefs.set(name, 1); |
|
|
return bundle; |
|
|
this.graph.trackBundle(sessionId, name); |
|
|
}) |
|
|
return bundle; |
|
|
.catch((error) => { |
|
|
} catch (error) { |
|
|
logger.error(`AssetManager: Failed to load bundle "${name}":`, error); |
|
|
logger.error(`AssetManager: Failed to load bundle "${name}":`, error); |
|
|
return null; |
|
|
|
|
|
}) |
|
|
|
|
|
.finally(() => { |
|
|
|
|
|
this.pendingLoads.delete(name); |
|
|
|
|
|
}); |
|
|
|
|
|
this.pendingLoads.set(name, loadPromise); |
|
|
|
|
|
|
|
|
|
|
|
const bundle = await loadPromise; |
|
|
|
|
|
if (!bundle) { |
|
|
|
|
|
this.rollbackFailedAcquire(sessionId, name); |
|
|
return null; |
|
|
return null; |
|
|
} |
|
|
} |
|
|
|
|
|
return bundle; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private rollbackFailedAcquire(sessionId: string, name: string): void { |
|
|
|
|
|
const sessionRefs = this.sessionBundleRefs.get(sessionId); |
|
|
|
|
|
if (!sessionRefs) { |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const sessionRefCount = sessionRefs.get(name) ?? 0; |
|
|
|
|
|
if (sessionRefCount <= 1) { |
|
|
|
|
|
sessionRefs.delete(name); |
|
|
|
|
|
this.graph.untrackBundle(sessionId, name); |
|
|
|
|
|
} else { |
|
|
|
|
|
sessionRefs.set(name, sessionRefCount - 1); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const totalRefCount = (this.bundleRefCounts.get(name) ?? 0) - 1; |
|
|
|
|
|
if (totalRefCount <= 0) { |
|
|
|
|
|
this.bundleRefCounts.delete(name); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
this.bundleRefCounts.set(name, totalRefCount); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async releaseBySession(sessionId: string, name: string): Promise<void> { |
|
|
async releaseBySession(sessionId: string, name: string): Promise<void> { |
|
|
@ -181,9 +245,11 @@ export class AssetManager { |
|
|
clearAll(): void { |
|
|
clearAll(): void { |
|
|
this.bundleRefCounts.clear(); |
|
|
this.bundleRefCounts.clear(); |
|
|
this.bundleCache.clear(); |
|
|
this.bundleCache.clear(); |
|
|
|
|
|
this.pendingLoads.clear(); |
|
|
this.sessionBundleRefs.clear(); |
|
|
this.sessionBundleRefs.clear(); |
|
|
this.sessionIdSeed = 0; |
|
|
this.sessionIdSeed = 0; |
|
|
this.graph = new AssetGraph(); |
|
|
this.graph = new AssetGraph(); |
|
|
|
|
|
this.defaultSessionId = ""; |
|
|
this.defaultSession = this.createSession("global"); |
|
|
this.defaultSession = this.createSession("global"); |
|
|
logger.debug("AssetManager: Reference count map cleared"); |
|
|
logger.debug("AssetManager: Reference count map cleared"); |
|
|
} |
|
|
} |
|
|
|