AssetManager
AssetManager is a singleton that handles loading, caching, and lifecycle management of game assets. It supports GLTF models, textures, audio, JSON, and binary data with concurrency-limited loading, priority ordering, LRU cache eviction, and retry with exponential backoff.
Unlike other managers, AssetManager is not mounted automatically by Game. Use AssetLoader for declarative preloading, or access the manager directly for imperative control.
import { getAssetManager, destroyAssetManager, detectAssetType } from "@carverjs/core/systems";How It Works #
Manifest parsing: Asset entries are normalized — relative URLs are resolved against
baseUrl, types are auto-detected from file extensions, and entries are sorted by priority.Concurrency-limited loading: A worker pool pattern loads assets in parallel (default: 6 concurrent). Each worker pulls the next task from a shared queue.
Retry with backoff: Failed loads retry up to 3 times with exponential backoff and jitter to avoid thundering herd.
Caching: Loaded assets are stored in an in-memory LRU cache (256 MB default). Reference counting prevents eviction of assets in active use.
Progress tracking: Real-time progress is available via
useSyncExternalStore(used byuseAssetProgress).Resource disposal: On eviction or explicit unload, Three.js resources (textures, geometries, materials) are properly disposed.
API #
getAssetManager() #
Returns the singleton AssetManager instance. Creates one if it doesn't exist.
destroyAssetManager() #
Clears all caches, disposes Three.js resources, and destroys the singleton.
detectAssetType(url) #
Detect asset type from a URL's file extension. Returns AssetType | undefined.
Loading #
| Method | Description |
|---|---|
load<T>(url, options?) | Load a single asset. Returns cached result if available. Deduplicates concurrent requests |
loadManifest(manifest, callbacks?) | Load all non-lazy assets from a manifest with progress tracking |
loadGroup(name) | Load all assets in a named group |
preload(urls) | Fire-and-forget preload for one or more URLs |
load() Options #
| Option | Type | Description |
|---|---|---|
type | AssetType | Override auto-detection |
key | string | Cache key (defaults to URL) |
loaderOptions | Record<string, unknown> | Passed to the underlying loader |
loadManifest() Callbacks #
| Callback | Description |
|---|---|
onProgress(progress) | Called on each progress update |
onComplete() | Called when all assets finish loading |
onError(errors) | Called when any asset fails after all retries |
Cache Access #
| Method | Description |
|---|---|
get<T>(key) | Get a cached asset synchronously. Returns undefined if not cached |
has(key) | Check if an asset is cached |
isLoading(key) | Check if an asset is currently loading |
Cache Management #
| Method | Description |
|---|---|
unload(key) | Unload a specific asset, disposing Three.js resources |
unloadGroup(group) | Unload all assets in a group |
clearAll() | Clear all cached assets |
getMemoryUsage() | Get current cache size in bytes |
retain(key) | Increment reference count (prevents LRU eviction) |
release(key) | Decrement reference count |
Configuration #
const manager = getAssetManager();
manager.configure({
maxConcurrent: 8, // Parallel loads (default: 6)
maxCacheBytes: 512 * 1024 * 1024, // Cache limit (default: 256 MB)
retries: 5, // Retry attempts (default: 3)
retryDelay: 2000, // Base retry delay in ms (default: 1000)
timeout: 60000, // Per-asset timeout in ms (default: 30000)
});Custom Loaders #
Register custom loaders for new asset types or override built-in ones:
const manager = getAssetManager();
manager.registerLoader("shader", async (url, options) => {
const response = await fetch(url);
return response.text();
});
// Now loadable:
const shader = await manager.load("/shaders/glow.glsl", { type: "shader" });Built-in Loaders #
| Type | Loader | Output |
|---|---|---|
"gltf" | GLTFLoader (three-stdlib, lazy-imported) | GLTF scene object |
"texture" | TextureLoader (three, lazy-imported) | THREE.Texture |
"audio" | fetch → arrayBuffer() | ArrayBuffer |
"json" | fetch → json() | Parsed object |
"binary" | fetch → arrayBuffer() | ArrayBuffer |
GLTF supports Draco and Meshopt decompression via loaderOptions:
await manager.load("/model.glb", {
type: "gltf",
loaderOptions: { draco: true, meshopt: false },
});Usage #
Most game code should use AssetLoader + useAssets. Direct manager access is useful for imperative scenarios:
import { getAssetManager } from "@carverjs/core/systems";
// Preload assets before a level transition
async function preloadLevel(level: number) {
const manager = getAssetManager();
await manager.loadManifest([
{ url: `/maps/level-${level}.json` },
{ url: `/audio/level-${level}.mp3` },
]);
}
// Access cached data
function getLevelConfig(level: number) {
return getAssetManager().get(`/maps/level-${level}.json`);
}Memory Management #
The cache uses LRU (Least Recently Used) eviction with reference counting:
LRU: When the cache exceeds
maxCacheBytes, the least-recently-accessed asset withrefCount === 0is evicted.Reference counting:
useAssetsretains assets on mount and releases on unmount. Retained assets are never evicted.Disposal: Evicted Three.js resources (textures, geometries, materials) are properly disposed via
.dispose().
Type Definitions #
See Types for AssetType, AssetEntry, AssetManifest, AssetGroupConfig, LoadingProgress, AssetLoadError, and CacheEntry.