664 lines
26 KiB
C#
664 lines
26 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.Networking;
|
|
using UnityEngine.SceneManagement;
|
|
using UnityEngine.Profiling;
|
|
|
|
namespace CustomAssetBundleSystem
|
|
{
|
|
/// <summary>
|
|
/// Custom AssetBundle manager - replaces Unity Addressables
|
|
/// Full manual control, no Unity 6 bugs, lightweight and fast
|
|
/// </summary>
|
|
public class RemoteAssetBundleManager : MonoBehaviour
|
|
{
|
|
private static RemoteAssetBundleManager _instance;
|
|
public static RemoteAssetBundleManager Instance
|
|
{
|
|
get
|
|
{
|
|
if (_instance == null)
|
|
{
|
|
var go = new GameObject("[RemoteAssetBundleManager]");
|
|
_instance = go.AddComponent<RemoteAssetBundleManager>();
|
|
DontDestroyOnLoad(go);
|
|
}
|
|
return _instance;
|
|
}
|
|
}
|
|
|
|
// Configuration
|
|
[Header("Remote Catalog Settings")]
|
|
public string catalogUrl = "https://assetbundle.uncaptured.co/catalog.json";
|
|
public int downloadTimeout = 900; // seconds (15 minutes) for large bundles
|
|
public int maxRetries = 3;
|
|
[Tooltip("If progress does not change for this many seconds, abort and retry.")]
|
|
public float downloadStallTimeout = 45f;
|
|
[Tooltip("Base delay between retries (multiplied by attempt index).")]
|
|
public float retryBackoffSeconds = 2f;
|
|
|
|
[Header("Memory Management")]
|
|
[Tooltip("Unloads the scene bundle and its dependencies once the scene is loaded to free GPU/CPU memory (recommended on mobile).")]
|
|
public bool unloadSceneBundlesAfterLoad = true;
|
|
[Tooltip("Calls Resources.UnloadUnusedAssets and GC.Collect before and after heavy scene loads.")]
|
|
public bool runMemoryCleanupAroundSceneLoad = true;
|
|
[Tooltip("Logs runtime memory usage after cleanup steps.")]
|
|
public bool logMemoryStats = false;
|
|
|
|
// State
|
|
private RemoteAssetCatalog _catalog;
|
|
private Dictionary<string, AssetBundle> _loadedBundles = new Dictionary<string, AssetBundle>();
|
|
private Dictionary<string, Coroutine> _activeDownloads = new Dictionary<string, Coroutine>();
|
|
private bool _isInitialized = false;
|
|
|
|
// Events
|
|
public event Action<bool, string> OnCatalogLoaded;
|
|
public event Action<string, float> OnBundleDownloadProgress;
|
|
public event Action<string, bool> OnBundleLoaded;
|
|
|
|
void Awake()
|
|
{
|
|
if (_instance != null && _instance != this)
|
|
{
|
|
Destroy(gameObject);
|
|
return;
|
|
}
|
|
_instance = this;
|
|
DontDestroyOnLoad(gameObject);
|
|
}
|
|
|
|
#region Initialization
|
|
|
|
/// <summary>
|
|
/// Initialize the system by downloading the remote catalog
|
|
/// </summary>
|
|
public IEnumerator InitializeAsync()
|
|
{
|
|
if (_isInitialized)
|
|
{
|
|
Debug.Log("[AssetBundleManager] Already initialized");
|
|
yield break;
|
|
}
|
|
|
|
Debug.Log($"[AssetBundleManager] Downloading catalog from: {catalogUrl}");
|
|
|
|
bool success = false;
|
|
string errorMessage = "";
|
|
|
|
for (int attempt = 1; attempt <= maxRetries; attempt++)
|
|
{
|
|
using (UnityWebRequest www = UnityWebRequest.Get(catalogUrl))
|
|
{
|
|
www.timeout = downloadTimeout;
|
|
yield return www.SendWebRequest();
|
|
|
|
if (www.result == UnityWebRequest.Result.Success)
|
|
{
|
|
try
|
|
{
|
|
string jsonText = www.downloadHandler.text;
|
|
_catalog = JsonUtility.FromJson<RemoteAssetCatalog>(jsonText);
|
|
|
|
if (_catalog != null && _catalog.bundles != null)
|
|
{
|
|
Debug.Log($"[AssetBundleManager] ✓ Catalog loaded successfully!");
|
|
Debug.Log($" Version: {_catalog.version}");
|
|
Debug.Log($" Base URL: {_catalog.baseUrl}");
|
|
Debug.Log($" Bundles: {_catalog.bundles.Count}");
|
|
|
|
_isInitialized = true;
|
|
success = true;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
errorMessage = "Catalog JSON is invalid or empty";
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errorMessage = $"JSON parsing failed: {ex.Message}";
|
|
Debug.LogError($"[AssetBundleManager] {errorMessage}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
errorMessage = $"Download failed: {www.error}";
|
|
Debug.LogWarning($"[AssetBundleManager] Attempt {attempt}/{maxRetries}: {errorMessage}");
|
|
|
|
if (attempt < maxRetries)
|
|
{
|
|
yield return new WaitForSeconds(2f); // Wait before retry
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
OnCatalogLoaded?.Invoke(success, errorMessage);
|
|
|
|
if (!success)
|
|
{
|
|
Debug.LogError($"[AssetBundleManager] ✗ Failed to load catalog after {maxRetries} attempts: {errorMessage}");
|
|
}
|
|
}
|
|
|
|
public bool IsInitialized => _isInitialized;
|
|
|
|
#endregion
|
|
|
|
#region Bundle Loading
|
|
|
|
/// <summary>
|
|
/// Load an AssetBundle by name (with dependencies)
|
|
/// </summary>
|
|
public IEnumerator LoadBundleAsync(string bundleName, Action<AssetBundle> onComplete = null)
|
|
{
|
|
if (!_isInitialized)
|
|
{
|
|
Debug.LogError("[AssetBundleManager] Not initialized! Call InitializeAsync() first.");
|
|
onComplete?.Invoke(null);
|
|
yield break;
|
|
}
|
|
|
|
// Check if already loaded
|
|
if (_loadedBundles.TryGetValue(bundleName, out AssetBundle cachedBundle))
|
|
{
|
|
Debug.Log($"[AssetBundleManager] Bundle '{bundleName}' already loaded (using cached)");
|
|
onComplete?.Invoke(cachedBundle);
|
|
yield break;
|
|
}
|
|
|
|
// Find bundle entry in catalog
|
|
var bundleEntry = _catalog.FindBundleByName(bundleName);
|
|
if (bundleEntry == null)
|
|
{
|
|
Debug.LogError($"[AssetBundleManager] Bundle '{bundleName}' not found in catalog!");
|
|
onComplete?.Invoke(null);
|
|
yield break;
|
|
}
|
|
|
|
// Load dependencies first
|
|
if (bundleEntry.dependencies != null && bundleEntry.dependencies.Count > 0)
|
|
{
|
|
Debug.Log($"[AssetBundleManager] Loading {bundleEntry.dependencies.Count} dependencies for '{bundleName}'...");
|
|
|
|
foreach (var depName in bundleEntry.dependencies)
|
|
{
|
|
if (!_loadedBundles.ContainsKey(depName))
|
|
{
|
|
bool depLoaded = false;
|
|
yield return LoadBundleAsync(depName, (bundle) => { depLoaded = bundle != null; });
|
|
|
|
if (!depLoaded)
|
|
{
|
|
Debug.LogError($"[AssetBundleManager] Failed to load dependency '{depName}' for '{bundleName}'");
|
|
onComplete?.Invoke(null);
|
|
yield break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Download and load the bundle WITH CACHING
|
|
string bundleUrl = bundleEntry.GetFullUrl(_catalog.baseUrl);
|
|
|
|
// Check if bundle is already cached
|
|
bool isCached = Caching.IsVersionCached(bundleUrl, Hash128.Parse(bundleEntry.hash));
|
|
if (isCached)
|
|
{
|
|
Debug.Log($"[AssetBundleManager] ✓ Bundle '{bundleName}' found in cache (INSTANT LOAD)");
|
|
}
|
|
else
|
|
{
|
|
Debug.Log($"[AssetBundleManager] Downloading bundle: {bundleUrl}");
|
|
Debug.Log($" Size: {bundleEntry.size / 1024f / 1024f:F2} MB");
|
|
}
|
|
|
|
AssetBundle downloadedBundle = null;
|
|
string lastError = null;
|
|
|
|
for (int attempt = 1; attempt <= maxRetries; attempt++)
|
|
{
|
|
if (Application.internetReachability == NetworkReachability.NotReachable)
|
|
{
|
|
lastError = "No internet connection detected";
|
|
Debug.LogWarning($"[AssetBundleManager] Internet not reachable while downloading '{bundleName}'. Attempt {attempt}/{maxRetries}.");
|
|
yield return new WaitForSeconds(retryBackoffSeconds * attempt);
|
|
continue;
|
|
}
|
|
|
|
using (UnityWebRequest www = UnityWebRequestAssetBundle.GetAssetBundle(bundleUrl, Hash128.Parse(bundleEntry.hash), 0))
|
|
{
|
|
www.timeout = downloadTimeout;
|
|
www.disposeDownloadHandlerOnDispose = true;
|
|
|
|
var operation = www.SendWebRequest();
|
|
float lastProgress = -1f;
|
|
float stalledTimer = 0f;
|
|
|
|
// Report download progress (only shows progress if downloading)
|
|
while (!operation.isDone)
|
|
{
|
|
float progress = Mathf.Clamp01(www.downloadProgress);
|
|
if (!isCached)
|
|
{
|
|
OnBundleDownloadProgress?.Invoke(bundleName, progress);
|
|
}
|
|
|
|
if (!isCached && downloadStallTimeout > 0f)
|
|
{
|
|
if (Mathf.Approximately(progress, lastProgress))
|
|
{
|
|
stalledTimer += Time.unscaledDeltaTime;
|
|
if (stalledTimer >= downloadStallTimeout)
|
|
{
|
|
Debug.LogWarning($"[AssetBundleManager] Download stalled for '{bundleName}' (progress {progress:P0}) after {stalledTimer:F1}s. Aborting attempt {attempt}/{maxRetries}.");
|
|
www.Abort();
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lastProgress = progress;
|
|
stalledTimer = 0f;
|
|
}
|
|
}
|
|
|
|
yield return null;
|
|
}
|
|
|
|
if (www.result == UnityWebRequest.Result.Success)
|
|
{
|
|
downloadedBundle = DownloadHandlerAssetBundle.GetContent(www);
|
|
|
|
if (downloadedBundle != null)
|
|
{
|
|
_loadedBundles[bundleName] = downloadedBundle;
|
|
|
|
if (isCached)
|
|
{
|
|
Debug.Log($"[AssetBundleManager] ✓ Bundle '{bundleName}' loaded from cache!");
|
|
}
|
|
else
|
|
{
|
|
Debug.Log($"[AssetBundleManager] ✓ Bundle '{bundleName}' downloaded and cached!");
|
|
Debug.Log($" Cached location: {Caching.defaultCache.path}");
|
|
}
|
|
|
|
OnBundleLoaded?.Invoke(bundleName, true);
|
|
onComplete?.Invoke(downloadedBundle);
|
|
yield break;
|
|
}
|
|
|
|
lastError = "Bundle extraction returned null";
|
|
Debug.LogError($"[AssetBundleManager] ✗ Failed to extract bundle from download: {bundleName}");
|
|
}
|
|
else
|
|
{
|
|
lastError = string.IsNullOrEmpty(www.error) ? www.result.ToString() : www.error;
|
|
Debug.LogWarning($"[AssetBundleManager] Attempt {attempt}/{maxRetries} failed for '{bundleName}': {lastError}");
|
|
}
|
|
}
|
|
|
|
if (attempt < maxRetries)
|
|
{
|
|
float delay = retryBackoffSeconds * attempt;
|
|
Debug.Log($"[AssetBundleManager] Retrying '{bundleName}' in {delay:F1}s...");
|
|
yield return new WaitForSeconds(delay);
|
|
}
|
|
}
|
|
|
|
Debug.LogError($"[AssetBundleManager] ✗ Failed to download bundle '{bundleName}': {lastError}");
|
|
OnBundleLoaded?.Invoke(bundleName, false);
|
|
onComplete?.Invoke(null);
|
|
yield break;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load a scene from a remote AssetBundle by scene name
|
|
/// </summary>
|
|
public IEnumerator LoadSceneAsync(string sceneName, Action<bool> onComplete = null)
|
|
{
|
|
if (!_isInitialized)
|
|
{
|
|
Debug.LogError("[AssetBundleManager] Not initialized! Call InitializeAsync() first.");
|
|
onComplete?.Invoke(false);
|
|
yield break;
|
|
}
|
|
|
|
// Find which bundle contains this scene
|
|
var bundleEntry = _catalog.FindBundleByScene(sceneName);
|
|
if (bundleEntry == null)
|
|
{
|
|
Debug.LogError($"[AssetBundleManager] Scene '{sceneName}' not found in any bundle!");
|
|
onComplete?.Invoke(false);
|
|
yield break;
|
|
}
|
|
|
|
Debug.Log($"[AssetBundleManager] Scene '{sceneName}' found in bundle '{bundleEntry.bundleName}'");
|
|
var dependencyEntries = _catalog.GetAllDependencies(bundleEntry);
|
|
|
|
if (runMemoryCleanupAroundSceneLoad)
|
|
{
|
|
yield return RunMemoryCleanupAsync($"[AssetBundleManager] Pre-load memory cleanup before '{sceneName}'");
|
|
}
|
|
|
|
// Load the bundle (and dependencies)
|
|
bool bundleLoaded = false;
|
|
yield return LoadBundleAsync(bundleEntry.bundleName, (bundle) => { bundleLoaded = bundle != null; });
|
|
|
|
if (!bundleLoaded)
|
|
{
|
|
Debug.LogError($"[AssetBundleManager] Failed to load bundle for scene '{sceneName}'");
|
|
onComplete?.Invoke(false);
|
|
yield break;
|
|
}
|
|
|
|
// CRITICAL FIX: Load scene in SINGLE mode to avoid Unity bug with additive loading
|
|
// The bug causes scene loading to hang at 0.2% when using Additive mode from AssetBundles
|
|
Debug.Log($"[AssetBundleManager] Loading scene '{sceneName}' from bundle (SINGLE mode to avoid Unity hang bug)...");
|
|
|
|
// Extra instrumentation: timestamp, loaded scenes, memory
|
|
Debug.Log($"[AssetBundleManager] StartTime: {DateTime.UtcNow:O}");
|
|
Debug.Log($"[AssetBundleManager] Current loaded scenes: {SceneManager.sceneCount}");
|
|
for (int si = 0; si < SceneManager.sceneCount; si++)
|
|
{
|
|
var s = SceneManager.GetSceneAt(si);
|
|
Debug.Log($"[AssetBundleManager] Scene[{si}] name={s.name} isLoaded={s.isLoaded}");
|
|
}
|
|
|
|
var sceneLoadOperation = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
|
|
if (sceneLoadOperation == null)
|
|
{
|
|
Debug.LogError($"[AssetBundleManager] Failed to start scene load: {sceneName}");
|
|
onComplete?.Invoke(false);
|
|
yield break;
|
|
}
|
|
|
|
// Allow scene to activate immediately (no manual activation needed)
|
|
sceneLoadOperation.allowSceneActivation = true;
|
|
|
|
// Wait for scene to load with timeout protection and stall detection
|
|
float timeout = 30f; // 30 second timeout
|
|
float elapsed = 0f;
|
|
|
|
// Track progress samples to detect stalls (common symptom: stuck at ~0.2)
|
|
float lastProgress = -1f;
|
|
float progressUnchangedTime = 0f;
|
|
const float stallThresholdSeconds = 8f; // if progress unchanged for this long, treat as stall
|
|
bool attemptedRetry = false;
|
|
|
|
while (!sceneLoadOperation.isDone && elapsed < timeout)
|
|
{
|
|
float progress = sceneLoadOperation.progress; // 0..1
|
|
|
|
// Log only when progress changes or at periodic intervals to avoid log spam
|
|
if (Mathf.Approximately(progress, lastProgress))
|
|
{
|
|
progressUnchangedTime += Time.deltaTime;
|
|
}
|
|
else
|
|
{
|
|
lastProgress = progress;
|
|
progressUnchangedTime = 0f;
|
|
Debug.Log($"[AssetBundleManager] Scene load progress: {progress * 100:F1}% (elapsed {elapsed:F2}s)");
|
|
}
|
|
|
|
// If we detect a stall near 0.2 (historical hang), attempt a single retry
|
|
if (!attemptedRetry && progress <= 0.21f && progressUnchangedTime > stallThresholdSeconds)
|
|
{
|
|
Debug.LogWarning($"[AssetBundleManager] Detected scene-load stall at {progress * 100:F1}% for {progressUnchangedTime:F1}s. Attempting one retry...");
|
|
|
|
// Attempt to unload bundle and retry once after small delay
|
|
try
|
|
{
|
|
string retryBundle = bundleEntry.bundleName;
|
|
if (_loadedBundles.ContainsKey(retryBundle))
|
|
{
|
|
Debug.Log($"[AssetBundleManager] Unloading bundle '{retryBundle}' to attempt retry...");
|
|
UnloadBundle(retryBundle, true);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogException(ex);
|
|
}
|
|
|
|
// wait a short moment and restart the load operation
|
|
yield return new WaitForSeconds(0.5f);
|
|
|
|
attemptedRetry = true;
|
|
elapsed = 0f;
|
|
lastProgress = -1f;
|
|
progressUnchangedTime = 0f;
|
|
|
|
Debug.Log("[AssetBundleManager] Starting retry of SceneManager.LoadSceneAsync...");
|
|
sceneLoadOperation = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
|
|
if (sceneLoadOperation == null)
|
|
{
|
|
Debug.LogError($"[AssetBundleManager] Retry failed to start scene load: {sceneName}");
|
|
onComplete?.Invoke(false);
|
|
yield break;
|
|
}
|
|
sceneLoadOperation.allowSceneActivation = true;
|
|
}
|
|
|
|
elapsed += Time.deltaTime;
|
|
yield return null;
|
|
}
|
|
|
|
if (elapsed >= timeout && !sceneLoadOperation.isDone)
|
|
{
|
|
Debug.LogError($"[AssetBundleManager] Scene load timed out after {timeout} seconds! progress={sceneLoadOperation.progress * 100:F1}% attemptedRetry={attemptedRetry}");
|
|
// dump some diagnostic info
|
|
Debug.Log($"[AssetBundleManager] Loaded bundles: {string.Join(",", GetLoadedBundleNames())}");
|
|
Debug.Log($"[AssetBundleManager] Memory stats: {GetMemoryStats()}");
|
|
onComplete?.Invoke(false);
|
|
yield break;
|
|
}
|
|
|
|
Debug.Log($"[AssetBundleManager] ✓ Scene '{sceneName}' loaded successfully in {elapsed:F2} seconds! attemptedRetry={attemptedRetry}");
|
|
|
|
if (unloadSceneBundlesAfterLoad)
|
|
{
|
|
yield return null; // ensure the scene has a frame to settle before we release bundles
|
|
ReleaseSceneBundles(bundleEntry, dependencyEntries);
|
|
}
|
|
|
|
if (runMemoryCleanupAroundSceneLoad)
|
|
{
|
|
yield return RunMemoryCleanupAsync($"[AssetBundleManager] Post-load memory cleanup after '{sceneName}'");
|
|
}
|
|
|
|
onComplete?.Invoke(true);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Cache Management
|
|
|
|
/// <summary>
|
|
/// Check if a bundle is cached on disk
|
|
/// </summary>
|
|
public bool IsBundleCached(string bundleName)
|
|
{
|
|
if (!_isInitialized || _catalog == null)
|
|
return false;
|
|
|
|
var bundleEntry = _catalog.FindBundleByName(bundleName);
|
|
if (bundleEntry == null)
|
|
return false;
|
|
|
|
string bundleUrl = bundleEntry.GetFullUrl(_catalog.baseUrl);
|
|
return Caching.IsVersionCached(bundleUrl, Hash128.Parse(bundleEntry.hash));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get cache information
|
|
/// </summary>
|
|
public void GetCacheInfo()
|
|
{
|
|
Debug.Log($"[AssetBundleManager] Cache Info:");
|
|
Debug.Log($" Cache path: {Caching.defaultCache.path}");
|
|
Debug.Log($" Cache space used: {Caching.defaultCache.spaceOccupied / 1024f / 1024f:F2} MB");
|
|
Debug.Log($" Max cache size: {Caching.defaultCache.maximumAvailableStorageSpace / 1024f / 1024f / 1024f:F2} GB");
|
|
Debug.Log($" Caching ready: {Caching.ready}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear all cached bundles (forces re-download on next load)
|
|
/// </summary>
|
|
public bool ClearCache()
|
|
{
|
|
bool success = Caching.ClearCache();
|
|
if (success)
|
|
{
|
|
Debug.Log($"[AssetBundleManager] ✓ Cache cleared successfully!");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError($"[AssetBundleManager] ✗ Failed to clear cache");
|
|
}
|
|
return success;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear only old versions of cached bundles (keeps current version)
|
|
/// </summary>
|
|
public void CleanupOldCachedVersions()
|
|
{
|
|
bool success = Caching.ClearAllCachedVersions(Caching.defaultCache.path);
|
|
if (success)
|
|
{
|
|
Debug.Log($"[AssetBundleManager] ✓ Cleaned up old cached versions");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"[AssetBundleManager] Failed to cleanup old cached versions");
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Memory Management
|
|
|
|
/// <summary>
|
|
/// Unload a specific bundle
|
|
/// </summary>
|
|
public void UnloadBundle(string bundleName, bool unloadAllLoadedObjects = false)
|
|
{
|
|
if (_loadedBundles.TryGetValue(bundleName, out AssetBundle bundle))
|
|
{
|
|
bundle.Unload(unloadAllLoadedObjects);
|
|
_loadedBundles.Remove(bundleName);
|
|
Debug.Log($"[AssetBundleManager] Unloaded bundle: {bundleName}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unload all bundles
|
|
/// </summary>
|
|
public void UnloadAllBundles(bool unloadAllLoadedObjects = false)
|
|
{
|
|
foreach (var kvp in _loadedBundles)
|
|
{
|
|
kvp.Value.Unload(unloadAllLoadedObjects);
|
|
}
|
|
_loadedBundles.Clear();
|
|
Debug.Log($"[AssetBundleManager] Unloaded all bundles");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get memory usage stats
|
|
/// </summary>
|
|
public string GetMemoryStats()
|
|
{
|
|
long totalMemory = 0;
|
|
foreach (var kvp in _loadedBundles)
|
|
{
|
|
// AssetBundle doesn't expose size directly, but we can estimate
|
|
totalMemory += kvp.Value.GetAllAssetNames().Length * 1024; // Rough estimate
|
|
}
|
|
|
|
return $"Loaded Bundles: {_loadedBundles.Count}, Estimated Memory: {totalMemory / 1024f / 1024f:F2} MB";
|
|
}
|
|
|
|
private void ReleaseSceneBundles(RemoteAssetCatalog.BundleEntry rootBundle, List<RemoteAssetCatalog.BundleEntry> dependencyEntries)
|
|
{
|
|
if (rootBundle == null)
|
|
return;
|
|
|
|
var bundlesToRelease = new List<RemoteAssetCatalog.BundleEntry> { rootBundle };
|
|
if (dependencyEntries != null)
|
|
{
|
|
bundlesToRelease.AddRange(dependencyEntries);
|
|
}
|
|
|
|
foreach (var entry in bundlesToRelease)
|
|
{
|
|
if (entry == null)
|
|
continue;
|
|
|
|
if (_loadedBundles.ContainsKey(entry.bundleName))
|
|
{
|
|
Debug.Log($"[AssetBundleManager] Releasing bundle '{entry.bundleName}' after scene load");
|
|
UnloadBundle(entry.bundleName, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
private IEnumerator RunMemoryCleanupAsync(string reason)
|
|
{
|
|
Debug.Log(reason);
|
|
|
|
// Give the UI a frame to update before we start releasing memory heavy objects.
|
|
yield return null;
|
|
|
|
yield return Resources.UnloadUnusedAssets();
|
|
GC.Collect();
|
|
|
|
if (logMemoryStats)
|
|
{
|
|
Debug.Log($"[AssetBundleManager] Runtime memory usage: {GetRuntimeMemoryStats()}");
|
|
}
|
|
}
|
|
|
|
private string GetRuntimeMemoryStats()
|
|
{
|
|
long allocated = Profiler.GetTotalAllocatedMemoryLong();
|
|
long reserved = Profiler.GetTotalReservedMemoryLong();
|
|
long unused = Profiler.GetTotalUnusedReservedMemoryLong();
|
|
|
|
return $"Allocated: {allocated / (1024f * 1024f):F1} MB | Reserved: {reserved / (1024f * 1024f):F1} MB | Unused Reserved: {unused / (1024f * 1024f):F1} MB | Bundles Cached: {GetMemoryStats()}";
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Catalog Info
|
|
|
|
/// <summary>
|
|
/// Get the loaded catalog (null if not initialized)
|
|
/// </summary>
|
|
public RemoteAssetCatalog GetCatalog() => _catalog;
|
|
|
|
/// <summary>
|
|
/// Check if a bundle is loaded
|
|
/// </summary>
|
|
public bool IsBundleLoaded(string bundleName) => _loadedBundles.ContainsKey(bundleName);
|
|
|
|
/// <summary>
|
|
/// Get list of all loaded bundle names
|
|
/// </summary>
|
|
public List<string> GetLoadedBundleNames() => new List<string>(_loadedBundles.Keys);
|
|
|
|
#endregion
|
|
|
|
void OnDestroy()
|
|
{
|
|
UnloadAllBundles(true);
|
|
}
|
|
}
|
|
}
|