Resource Management System¶
Overview¶
The Resource Management System is a robust framework for loading, caching, and managing game assets in Unity projects built with the LoL Engine. It provides a unified interface for handling different asset types and loading methods, efficient memory management, object pooling, and optional asset versioning and updates.
Key Features¶
- Unified Asset Loading API - Load assets from Resources, AssetBundles, or Addressables with a single API
- AssetReference Support - Type-safe asset loading with Unity's AssetReference system
- Thread-Safe Operations - Production-ready thread safety with ReaderWriterLockSlim for concurrent access
- Handle Leak Prevention - Comprehensive Addressables handle tracking prevents memory leaks
- Addressables Integration - First-class support for Unity Addressables with automatic initialization
- Asynchronous Loading - Full support for asynchronous operations using Tasks
- Reference Counting - Intelligent asset caching with reference counting
- Memory Management - Budget-based memory management with asset unloading
- Object Pooling - Efficient GameObject recycling to reduce instantiation overhead
- Asset Versioning - Track asset versions and dependencies
- Update System - Download and manage asset updates at runtime
- Event Notifications - Integration with the engine's event system
- Editor Tools - Asset versioning and management utilities
AssetReference Features¶
- Type Safety: Compile-time validation of asset references
- Inspector Integration: Drag-and-drop asset assignment in Unity Inspector
- Automatic Validation:
RuntimeKeyIsValid()checks prevent invalid references - Seamless Integration: Works with existing ResourceService caching and events
- Memory Management: Proper release handling for AssetReference-loaded assets
Thread Safety & Memory Management¶
- Thread-Safe Handle Tracking: All Addressables AsyncOperationHandle objects are tracked thread-safely
- Leak Prevention: Comprehensive handle lifecycle management prevents memory leaks
- Concurrent Access: ReaderWriterLockSlim enables multiple concurrent readers for better performance
- Resource Cleanup: Automatic handle cleanup during service shutdown
- Handle Monitoring: Debug handle counts and detect potential leaks early
Architecture¶
The Resource Management System consists of several key components:
- Resource Service - The main entry point for loading and managing assets
- Resource Loaders - Strategy pattern for different loading methods
- Resource Pool - GameObject pooling for efficient reuse
- Version Catalog - Tracks asset versions and updates
- Asset Updater - Handles downloading and applying updates
- Configuration - Configurable settings for resource management
Getting Started¶
Quick Start¶
- Enable Resource Service in your ServiceConfiguration asset:
-
[x] Enable Resource Service
-
Create ResourceManagementConfig asset:
- Right-click in Project → Create → LoLEngine → Config → Resource Management Config
- Save at:
Assets/[YourProject]/Resources/Configs/DefaultResourceManagementConfig.asset -
CRITICAL: Must be in
Resources/Configs/folder for auto-discovery -
Configure ResourceManagementConfig Settings:
- enableAddressables (true) - Use Addressables as primary loading system
- preferredSource (Addressables) - Which loader to try first
- fallbackToResources (true) - Fall back to Resources folder if Addressables fails
- maxMemoryBudgetMB (512f) - Maximum cached assets memory in MB
- VR RECOMMENDATION: Set to 256MB or lower for VR projects
- remoteCatalogUrl - URL for version catalog (for asset updates)
-
assetDownloadSubPath - Where to download updated assets
-
Add ImprovedGameInitializer to your first scene:
- Assign ServiceConfiguration asset
- Services auto-register via ConfigurableServiceInitializer
Configuration Details¶
ResourceManagementConfig Fields (actual implementation):
[Header("Asset Updater Settings")]
public string remoteCatalogUrl = "https://moomoo.games/LoLEngine/version.json";
public string localCatalogFileName = "version_catalog.json";
public string assetDownloadSubPath = "UpdatedAssets";
[Header("Addressables Settings")]
public bool enableAddressables = true;
public ResourceSource preferredSource = ResourceSource.Addressables;
public bool fallbackToResources = true;
[Header("Resource Service Settings")]
public float maxMemoryBudgetMB = 512f; // Set to 0 or negative for unlimited
Path Requirements:
- Config MUST be at: Assets/Resources/Configs/DefaultResourceManagementConfig.asset
- Or configure custom path in ResourcePathConfig asset
- Engine auto-loads config from Resources folder on initialization
Loading Assets¶
String-Based Loading (Traditional)¶
using LoLEngine.Core.ResourceManagement.Interfaces;
using LoLEngine.Core.ServiceManagement.Service;
using LoLEngine.Runtime;
using UnityEngine;
public class MyGameController : MonoBehaviour
{
private IResourceService _resourceService;
private bool _isInitialized = false;
private void Start()
{
if (ServiceAwaiter.AreServicesReady())
OnServicesReady();
else
ServiceAwaiter.WaitForServices(this, OnServicesReady, OnServicesTimeout);
}
private void OnServicesReady()
{
_resourceService = ServiceLocator.Instance.Get<IResourceService>();
_isInitialized = true;
}
private void OnServicesTimeout()
{
Debug.LogError("Timed out waiting for services");
}
// Synchronous loading (simple assets)
public void LoadTexture()
{
Texture2D texture = _resourceService.Load<Texture2D>("Textures/MyTexture");
if (texture != null)
{
// Use the texture
}
}
// Asynchronous loading (recommended)
public async void LoadModelAsync()
{
GameObject model = await _resourceService.LoadAsync<GameObject>("Models/MyModel");
if (model != null)
{
// Use the model
}
}
// With options
public async void LoadWithOptions()
{
var options = new ResourceLoadOptions
{
Priority = ResourceLoadPriority.High,
KeepInMemory = true,
FallbackIds = new[] { "Fallback1", "Fallback2" }
};
AudioClip sound = await _resourceService.LoadAsync<AudioClip>("Sounds/MySound", options);
}
}
AssetReference Loading (Recommended - Type-Safe)¶
using LoLEngine.Core.ResourceManagement.Interfaces;
using LoLEngine.Core.ServiceManagement.Service;
using LoLEngine.Runtime;
using UnityEngine;
#if UNITY_ADDRESSABLES
using UnityEngine.AddressableAssets;
#endif
public class AssetReferenceGameController : MonoBehaviour
{
[Header("Asset References - Drag assets here in Inspector")]
#if UNITY_ADDRESSABLES
[SerializeField] private AssetReference playerPrefabRef;
[SerializeField] private AssetReference backgroundMusicRef;
[SerializeField] private AssetReference uiTextureRef;
#endif
private IResourceService _resourceService;
private bool _isInitialized = false;
private void Start()
{
if (ServiceAwaiter.AreServicesReady())
OnServicesReady();
else
ServiceAwaiter.WaitForServices(this, OnServicesReady, OnServicesTimeout);
}
private void OnServicesReady()
{
_resourceService = ServiceLocator.Instance.Get<IResourceService>();
_isInitialized = true;
}
private void OnServicesTimeout()
{
Debug.LogError("Timed out waiting for services");
}
#if UNITY_ADDRESSABLES
// Type-safe AssetReference loading
public async void LoadPlayerPrefab()
{
if (playerPrefabRef != null && playerPrefabRef.RuntimeKeyIsValid())
{
GameObject playerPrefab = await _resourceService.LoadAsync<GameObject>(playerPrefabRef);
if (playerPrefab != null)
{
// Instantiate player at spawn point
Instantiate(playerPrefab, Vector3.zero, Quaternion.identity);
}
}
}
// AssetReference with options
public async void LoadBackgroundMusic()
{
if (backgroundMusicRef != null && backgroundMusicRef.RuntimeKeyIsValid())
{
var options = new ResourceLoadOptions
{
KeepInMemory = true, // Keep music in memory
Priority = ResourceLoadPriority.High
};
AudioClip music = await _resourceService.LoadAsync<AudioClip>(backgroundMusicRef, options);
if (music != null)
{
// Play background music
var audioSource = GetComponent<AudioSource>();
audioSource.clip = music;
audioSource.Play();
}
}
}
// Proper cleanup with AssetReference
private void OnDestroy()
{
// Release AssetReference resources
if (_resourceService != null)
{
if (playerPrefabRef != null) _resourceService.Release(playerPrefabRef);
if (backgroundMusicRef != null) _resourceService.Release(backgroundMusicRef);
if (uiTextureRef != null) _resourceService.Release(uiTextureRef);
}
}
#endif
}
Object Pooling¶
using LoLEngine.Core.ResourceManagement.Interfaces;
using LoLEngine.Core.ServiceManagement.Service;
using LoLEngine.Runtime;
using UnityEngine;
public class EnemySpawner : MonoBehaviour
{
[SerializeField] private string enemyPrefabId = "Prefabs/Enemy";
private IResourceService _resourceService;
private bool _isInitialized = false;
private void Start()
{
if (ServiceAwaiter.AreServicesReady())
OnServicesReady();
else
ServiceAwaiter.WaitForServices(this, OnServicesReady, OnServicesTimeout);
}
private void OnServicesReady()
{
_resourceService = ServiceLocator.Instance.Get<IResourceService>();
_isInitialized = true;
}
private void OnServicesTimeout()
{
Debug.LogError("Timed out waiting for services");
}
// Spawn an enemy
public void SpawnEnemy(Vector3 position)
{
GameObject enemy = _resourceService.Instantiate(enemyPrefabId, position, Quaternion.identity);
// The enemy is either newly instantiated or recycled from the pool
}
// Return an enemy to the pool
public void RecycleEnemy(GameObject enemy)
{
_resourceService.Recycle(enemy);
// The enemy is now inactive and back in the pool
}
}
Versioning and Updates¶
Note: Asset versioning and updates are optional advanced features. Most games won't need this.
using LoLEngine.Core.ResourceManagement.Interfaces;
using LoLEngine.Core.ServiceManagement.Service;
using LoLEngine.Runtime;
using UnityEngine;
public class GameUpdater : MonoBehaviour
{
private IAssetUpdaterService _assetUpdater;
private bool _isInitialized = false;
private void Start()
{
if (ServiceAwaiter.AreServicesReady())
OnServicesReady();
else
ServiceAwaiter.WaitForServices(this, OnServicesReady, OnServicesTimeout);
}
private void OnServicesReady()
{
// AssetUpdaterService is registered under its interface when enabled in ServiceConfiguration.
_assetUpdater = ServiceLocator.Instance.Get<IAssetUpdaterService>();
if (_assetUpdater != null)
{
_isInitialized = true;
}
else
{
Debug.LogWarning("IAssetUpdaterService not available — enable it in ServiceConfiguration.");
}
}
private void OnServicesTimeout()
{
Debug.LogError("Timed out waiting for services");
}
// Check for updates
public async void CheckForUpdates()
{
int updateCount = await _assetUpdater.CheckForUpdates();
if (updateCount > 0)
{
Debug.Log($"Found {updateCount} updates available");
}
}
// Download all updates
public async void DownloadUpdates(System.IProgress<float> progress = null)
{
bool success = await _assetUpdater.DownloadAllUpdates(progress);
if (success)
{
Debug.Log("Updates downloaded successfully");
}
}
}
Event Handling¶
using LoLEngine.Core.Events.Interfaces;
using LoLEngine.Core.Events.Providers;
using LoLEngine.Core.ResourceManagement.Events;
using LoLEngine.Core.ServiceManagement.Service;
using LoLEngine.Runtime;
using UnityEngine;
public class ResourceMonitor : MonoBehaviour,
IEventListener<ResourceEvents.ResourceLoaded>,
IEventListener<ResourceEvents.ResourceLoadFailed>
{
private bool _isInitialized = false;
private void Start()
{
if (ServiceAwaiter.AreServicesReady())
OnServicesReady();
else
ServiceAwaiter.WaitForServices(this, OnServicesReady, OnServicesTimeout);
}
private void OnServicesReady()
{
this.EventStartListening<ResourceEvents.ResourceLoaded>();
this.EventStartListening<ResourceEvents.ResourceLoadFailed>();
_isInitialized = true;
}
private void OnServicesTimeout()
{
Debug.LogError("Timed out waiting for services");
}
private void OnDisable()
{
this.EventStopListening<ResourceEvents.ResourceLoaded>();
this.EventStopListening<ResourceEvents.ResourceLoadFailed>();
}
public void OnGameEvent(ResourceEvents.ResourceLoaded evt)
{
Debug.Log($"Resource loaded: {evt.ResourceId} of type {evt.AssetType.Name}");
}
public void OnGameEvent(ResourceEvents.ResourceLoadFailed evt)
{
Debug.LogError($"Resource load failed: {evt.ResourceId}, Error: {evt.Error}");
}
}
Integrating in Your Game¶
Automatic Integration (Current System)¶
The Resource Management System integrates automatically via ImprovedGameInitializer and ServiceConfiguration.
No manual service registration needed! Services auto-register when you:
- Enable "Enable Resource Service" in ServiceConfiguration asset
- Create ResourceManagementConfig at
Assets/Resources/Configs/DefaultResourceManagementConfig.asset - Add ImprovedGameInitializer to your scene
That's it! The engine handles all service initialization automatically.
Advanced: Custom Game Init Logic¶
If you need custom logic during initialization:
using LoLEngine.Core.GameInitializer;
using LoLEngine.Core.ResourceManagement.Interfaces;
using LoLEngine.Core.ServiceManagement.Service;
using LoLEngine.Runtime;
using UnityEngine;
public class MyGameController : MonoBehaviour
{
private IResourceService _resourceService;
private bool _isInitialized = false;
private void Start()
{
// Wait for automatic service initialization
if (ServiceAwaiter.AreServicesReady())
OnServicesReady();
else
ServiceAwaiter.WaitForServices(this, OnServicesReady, OnServicesTimeout);
}
private void OnServicesReady()
{
// Access services after automatic initialization
_resourceService = ServiceLocator.Instance.Get<IResourceService>();
_isInitialized = true;
// Your custom initialization logic here
Debug.Log("ResourceService is ready!");
}
private void OnServicesTimeout()
{
Debug.LogError("Services failed to initialize");
}
}
Step 1: Configure Memory Budgets¶
Adjust memory budgets based on your target platform:
Desktop/Console: 512MB-1GB (default 512MB) Mobile: 256MB-384MB VR (Quest): 128MB-256MB CRITICAL for VR VR (PC): 256MB-384MB
Edit in ResourceManagementConfig asset or via code:
private void OnServicesReady()
{
_resourceService = ServiceLocator.Instance.Get<IResourceService>();
// Platform-specific memory budgets
#if UNITY_STANDALONE || UNITY_EDITOR
_resourceService.SetMemoryBudget(512 * 1024 * 1024); // 512MB desktop
#elif UNITY_ANDROID || UNITY_IOS
_resourceService.SetMemoryBudget(256 * 1024 * 1024); // 256MB mobile
#endif
}
Step 2: Organize Your Resources¶
- Put assets in the appropriate folders:
Resourcesfolder for direct loading- AssetBundles for grouped loading
-
Addressables for modern async loading with remote capabilities
-
Set up Addressables (recommended):
- Window > Asset Management > Addressables > Groups
- Mark assets as Addressable
-
Set meaningful Address names (e.g., "Audio/BackgroundMusic01")
-
Use consistent naming conventions for resource IDs
Step 4: Take Advantage of Object Pooling¶
For frequently instantiated and destroyed objects (bullets, enemies, particles, etc.), use the pooling system to improve performance.
Step 5: Set Up Asset Versioning (Optional)¶
If you need asset updates:
- Set up a server to host your version catalog and assets
- Use the Asset Versioning Tool to generate version information
- Implement update checking and downloading in your game
Thread Safety & Handle Management¶
Enhanced Thread Safety¶
The ResourceService and AddressableResourceLoader now provide production-ready thread safety:
// All operations are thread-safe and can be called from any thread
private async Task LoadAssetsFromMultipleThreads()
{
var tasks = new List<Task>();
// Safe to call from multiple threads simultaneously
tasks.Add(Task.Run(async () => await _resourceService.LoadAsync<Texture2D>("UI/Icon1")));
tasks.Add(Task.Run(async () => await _resourceService.LoadAsync<Texture2D>("UI/Icon2")));
tasks.Add(Task.Run(async () => await _resourceService.LoadAsync<AudioClip>("Audio/SFX1")));
await Task.WhenAll(tasks);
}
Handle Leak Prevention¶
All Addressables handles are now automatically tracked and cleaned up:
// Check handle count for debugging
public void MonitorHandles()
{
var stats = _resourceService.GetResourceStats();
// Handle counts are automatically logged in ResourceStats
// Active AssetReference handles: 5
// Active AddressableResourceLoader handles: 12
// Manual cleanup if needed
_resourceService.Shutdown(); // Releases all tracked handles
}
Debugging Handle Leaks¶
Monitor handle usage during development:
public class ResourceMonitor : MonoBehaviour
{
private IResourceService _resourceService;
private void Start()
{
_resourceService = ServiceLocator.Instance.Get<IResourceService>();
// Check handles periodically
InvokeRepeating(nameof(LogHandleStats), 10f, 10f);
}
private void LogHandleStats()
{
// This will log active handle counts to help detect leaks
_resourceService.GetResourceStats();
}
}
Best Practices¶
- Use AssetReferences When Possible - Prefer
AssetReferenceover string-based loading for: - Type Safety: Compile-time validation of asset types
- Inspector Integration: Drag-and-drop asset assignment
- Automatic Dependency Tracking: Unity handles asset dependencies
-
Refactoring Safety: Asset moves don't break references
-
Asynchronous Loading - Prefer async methods for larger assets to avoid frame rate spikes
-
Resource IDs - For string-based loading, use consistent, hierarchical naming (e.g., "Characters/Player", "UI/MainMenu")
-
Memory Management - Release assets when no longer needed:
// For AssetReference _resourceService.Release(myAssetReference); // For string-based loading _resourceService.Release("path/to/asset"); -
Preloading - Preload assets during loading screens to avoid hitches during gameplay
-
Pooling - Use object pooling for frequently instantiated objects
-
Cleanup - Make sure to release resources in OnDestroy or when switching scenes
-
AssetReference Validation - Always check
RuntimeKeyIsValid()before loading:if (assetRef != null && assetRef.RuntimeKeyIsValid()) { var asset = await _resourceService.LoadAsync<T>(assetRef); } -
Handle Monitoring - In development builds, monitor handle counts to detect leaks early:
#if DEVELOPMENT_BUILD || UNITY_EDITOR _resourceService.GetResourceStats(); // Logs handle counts #endif
Common Pitfalls¶
- Memory Leaks - Not releasing assets when they're no longer needed
- Main Thread Blocking - Using synchronous loading for large assets during gameplay
- Path Inconsistencies - Inconsistent casing or slashes in resource paths
- Missing References - Not checking for null when loading assets
- Pool Bloat - Not setting maximum pool sizes for object pools
Addressables Integration¶
Setting Up Addressables¶
-
Initialize Addressables in Unity:
Window > Asset Management > Addressables > Groups → Click "Create Addressables Settings" if not already done -
Mark Assets as Addressable:
- Select assets in Project window
- Check "Addressable" checkbox
-
Set Address to meaningful names (e.g., "Audio/BackgroundMusic01")
-
Configure ResourceManagementConfig:
// Set these values in your ResourceManagementConfig asset enableAddressables = true preferredSource = ResourceSource.Addressables fallbackToResources = true
Loading priority¶
When Addressables is enabled in ResourceManagementConfig, ResourceService tries loaders in this order:
- Addressables (
AddressableResourceLoader) — if enabled and initialized - Resources (
ResourcesLoader) — iffallbackToResourcesis true - AssetBundles (
AssetBundleLoader)
Your game code stays the same — LoadAsync<T>("Audio/MyClip") uses the configured priority automatically. AudioService and other consumers load through IResourceService; no per-service Addressables setup is required.
Editor setup checklist:
Window > Asset Management > Addressables > Groups→ Create Addressables Settings- Mark assets Addressable with meaningful addresses (e.g.
Audio/BackgroundMusic01) - Create Resource Management Config (
Create > LoLEngine > Config > Resource Management Config) - Set
enableAddressables = true,preferredSource = Addressables - Assign config to
ServiceConfigurationand test:
var clip = await ServiceLocator.Instance.Get<IResourceService>()
.LoadAsync<AudioClip>("Audio/YourAddressableAudio");
Addressables Benefits¶
- Async-First: Built for non-blocking asset loading
- Remote Content: Download assets from CDN/servers
- Memory Management: Automatic reference counting and cleanup
- Build Optimization: Assets can be built separately from main app
- Progress Tracking: Built-in loading progress reports
Migration from Resources¶
Replace Resources folder assets with Addressable assets:
// OLD (Resources folder):
// "Resources/Audio/Music.wav" → Resources.Load<AudioClip>("Audio/Music")
// NEW (Addressables):
// Mark "Audio/Music.wav" as Addressable with Address "Audio/Music"
// Same ResourceService code works automatically!
var music = await _resourceService.LoadAsync<AudioClip>("Audio/Music");
Advanced Features¶
Custom asset loaders¶
ResourceService registers built-in loaders during Initialize():
AddressableResourceLoader(when Addressables enabled)ResourcesLoaderAssetBundleLoader
There is no public RegisterLoader() API — custom loading strategies should go through IResourceService configuration or game-layer wrappers. Forking the engine to add IResourceLoader implementations is possible but not a supported extension point for Asset Store projects today.
If you only need a different source priority, use ResourceManagementConfig.preferredSource and fallbackToResources.
Memory Budget Tuning¶
See "Step 1: Configure Memory Budgets" section above for platform-specific recommendations.
Adjust memory budgets dynamically at runtime:
private void OnServicesReady()
{
var resourceService = ServiceLocator.Instance.Get<IResourceService>();
// Set different budgets based on platform
#if UNITY_ANDROID || UNITY_IOS
resourceService.SetMemoryBudget(256 * 1024 * 1024); // 256MB for mobile
#elif UNITY_STANDALONE || UNITY_EDITOR
resourceService.SetMemoryBudget(512 * 1024 * 1024); // 512MB for desktop
#endif
}
VR-SPECIFIC RESOURCE MANAGEMENT RECOMMENDATIONS¶
Critical VR Considerations¶
Memory Constraints - VR requires double rendering (one per eye), leaving significantly less memory for assets:
- Quest 2/3: Set maxMemoryBudgetMB = 128-256 (NOT 512!)
- PC VR: Set maxMemoryBudgetMB = 256-384 depending on GPU VRAM
Asynchronous Loading is MANDATORY - VR cannot afford frame drops:
// NEVER in VR - causes stuttering and nausea
var model = _resourceService.Load<GameObject>("Models/Enemy");
// ALWAYS use async in VR
var model = await _resourceService.LoadAsync<GameObject>("Models/Enemy");
Aggressive Object Pooling - Essential for 90fps minimum:
// Pool EVERYTHING that spawns frequently
// Even small objects add up with VR's strict frame budget
_resourceService.Instantiate(bulletPrefab, pos, rot); // Uses pool automatically
_resourceService.Recycle(bullet); // Return to pool - CRITICAL!
Preloading During Load Screens - Never load assets mid-gameplay in VR:
async void PreloadVRAssets()
{
await _resourceService.LoadAsync<GameObject>("Player/HandModels");
await _resourceService.LoadAsync<GameObject>("UI/VRMenuPrefab");
await _resourceService.LoadAsync<AudioClip>("Audio/UIClick");
// Preload ALL gameplay assets before entering VR scene
}
Texture Compression - Mandatory for VR:
- Use ASTC compression for Quest
- Use BC7 for PC VR
- Enable Addressables texture streaming
- Set preferredSource = ResourceSource.Addressables
Handle Leak Prevention - CRITICAL for long VR sessions:
// Monitor handles in development builds
#if DEVELOPMENT_BUILD || UNITY_EDITOR
void Update()
{
if (Time.frameCount % 300 == 0) // Every 5 seconds at 60fps
{
_resourceService.GetResourceStats(); // Logs handle counts
}
}
#endif
Release Assets Aggressively:
// VR has tight memory limits - clean up immediately
void OnSceneUnload()
{
_resourceService.Release("LevelSpecificAsset");
_resourceService.ReleaseAll(); // When changing levels
}
Troubleshooting¶
Asset Not Found¶
- Check that the resource ID (path) is correct, including case
- Verify the asset is in the correct location
- Check for compilation errors in asset scripts
Memory Issues¶
- Monitor memory usage with
GetResourceStats() - Release unused assets
- Set appropriate memory budgets
- Use object pooling for frequent instantiation
Slow Loading¶
- Use asynchronous loading for large assets
- Preload assets during loading screens
- Consider using asset bundles or addressables for better loading
- Compress textures and audio appropriately
Architecture Diagram¶
+-------------------+ +-------------------+
| | | |
| Game Code |------>| IResourceService |
| | | |
+-------------------+ +--------+----------+
|
| implements
v
+-----------------+ +-------------------+
| | | |
| ResourceEvents |<------>| ResourceService |
| | | |
+-----------------+ +--------+----------+
|
| uses
v
+-------------------+ +-------------------+ +------------------------+
| | | | | |
| ResourcesLoader | | AssetBundleLoader | | AddressableResourceLdr |
| | | | | (NEW - Priority #1) |
+-------------------+ +-------------------+ +------------------------+
|
| manages
v
+-------------------+ +-------------------+ +-------------------+
| | | | | |
| ResourcePool | | AssetUpdaterService | VersionCatalog |
| | | | | |
+-------------------+ +-------------------+ +-------------------+