using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using UnityEngine.AddressableAssets.Initialization; using UnityEngine.AddressableAssets.ResourceLocators; using UnityEngine.AddressableAssets.ResourceProviders; using UnityEngine.Networking; using UnityEngine.ResourceManagement; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.ResourceLocations; using UnityEngine.ResourceManagement.ResourceProviders; using UnityEngine.ResourceManagement.Util; using UnityEngine.SceneManagement; namespace UnityEngine.AddressableAssets { internal class AddressablesImpl : IEqualityComparer { ResourceManager m_ResourceManager; IInstanceProvider m_InstanceProvider; int m_CatalogRequestsTimeout; internal const string kCacheDataFolder = "{UnityEngine.Application.persistentDataPath}/com.unity.addressables/"; public IInstanceProvider InstanceProvider { get { return m_InstanceProvider; } set { m_InstanceProvider = value; var rec = m_InstanceProvider as IUpdateReceiver; if (rec != null) m_ResourceManager.AddUpdateReceiver(rec); } } public ISceneProvider SceneProvider; public ResourceManager ResourceManager { get { if (m_ResourceManager == null) m_ResourceManager = new ResourceManager(new DefaultAllocationStrategy()); return m_ResourceManager; } } public int CatalogRequestsTimeout { get { return m_CatalogRequestsTimeout; } set { m_CatalogRequestsTimeout = value; } } public class ResourceLocatorInfo { public IResourceLocator Locator { get; private set; } public string LocalHash { get; private set; } public IResourceLocation CatalogLocation { get; private set; } public bool ContentUpdateAvailable { get; internal set; } public ResourceLocatorInfo(IResourceLocator loc, string localHash, IResourceLocation remoteCatalogLocation) { Locator = loc; LocalHash = localHash; CatalogLocation = remoteCatalogLocation; } public IResourceLocation HashLocation { get { return CatalogLocation.Dependencies[0]; } } public bool CanUpdateContent { get { return !string.IsNullOrEmpty(LocalHash) && CatalogLocation != null && CatalogLocation.HasDependencies && CatalogLocation.Dependencies.Count == 2; } } internal void UpdateContent(IResourceLocator locator, string hash, IResourceLocation loc) { LocalHash = hash; CatalogLocation = loc; Locator = locator; } } internal List m_ResourceLocators = new List(); AsyncOperationHandle m_InitializationOperation; AsyncOperationHandle> m_ActiveCheckUpdateOperation; internal AsyncOperationHandle> m_ActiveUpdateOperation; Action m_OnHandleCompleteAction; Action m_OnSceneHandleCompleteAction; Action m_OnHandleDestroyedAction; Dictionary m_resultToHandle = new Dictionary(); internal HashSet m_SceneInstances = new HashSet(); AsyncOperationHandle m_ActiveCleanBundleCacheOperation; internal int SceneOperationCount { get { return m_SceneInstances.Count; } } internal int TrackedHandleCount { get { return m_resultToHandle.Count; } } internal bool hasStartedInitialization = false; public AddressablesImpl(IAllocationStrategy alloc) { m_ResourceManager = new ResourceManager(alloc); SceneManager.sceneUnloaded += OnSceneUnloaded; } internal void ReleaseSceneManagerOperation() { SceneManager.sceneUnloaded -= OnSceneUnloaded; } public Func InternalIdTransformFunc { get { return ResourceManager.InternalIdTransformFunc; } set { ResourceManager.InternalIdTransformFunc = value; } } public Action WebRequestOverride { get { return ResourceManager.WebRequestOverride; } set { ResourceManager.WebRequestOverride = value; } } public AsyncOperationHandle ChainOperation { get { if (!hasStartedInitialization) return InitializeAsync(); if (m_InitializationOperation.IsValid() && !m_InitializationOperation.IsDone) return m_InitializationOperation; if (m_ActiveUpdateOperation.IsValid() && !m_ActiveUpdateOperation.IsDone) return m_ActiveUpdateOperation; Debug.LogWarning($"{nameof(ChainOperation)} property should not be accessed unless {nameof(ShouldChainRequest)} is true."); return default; } } internal bool ShouldChainRequest { get { if (!hasStartedInitialization) return true; if (m_InitializationOperation.IsValid() && !m_InitializationOperation.IsDone) return true; return m_ActiveUpdateOperation.IsValid() && !m_ActiveUpdateOperation.IsDone; } } internal void OnSceneUnloaded(Scene scene) { foreach (var s in m_SceneInstances) { if (!s.IsValid()) { m_SceneInstances.Remove(s); break; } var sceneHandle = s.Convert(); if (sceneHandle.Result.Scene == scene) { m_SceneInstances.Remove(s); m_resultToHandle.Remove(s.Result); var op = SceneProvider.ReleaseScene(m_ResourceManager, sceneHandle); AutoReleaseHandleOnCompletion(op); break; } } m_ResourceManager.CleanupSceneInstances(scene); } public string StreamingAssetsSubFolder { get { return "aa"; } } public string BuildPath { get { return Addressables.LibraryPath + StreamingAssetsSubFolder + "/" + PlatformMappingService.GetPlatformPathSubFolder(); } } public string PlayerBuildDataPath { get { return Application.streamingAssetsPath + "/" + StreamingAssetsSubFolder; } } public string RuntimePath { get { #if UNITY_EDITOR return BuildPath; #else return PlayerBuildDataPath; #endif } } public void Log(string msg) { Debug.Log(msg); } public void LogFormat(string format, params object[] args) { Debug.LogFormat(format, args); } public void LogWarning(string msg) { Debug.LogWarning(msg); } public void LogWarningFormat(string format, params object[] args) { Debug.LogWarningFormat(format, args); } public void LogError(string msg) { Debug.LogError(msg); } public void LogException(AsyncOperationHandle op, Exception ex) { if (op.Status == AsyncOperationStatus.Failed) { Debug.LogError(ex.ToString()); Addressables.Log($"Failed op : {op.DebugName}"); } else Addressables.Log(ex.ToString()); } public void LogException(Exception ex) { Addressables.Log(ex.ToString()); } public void LogErrorFormat(string format, params object[] args) { Debug.LogErrorFormat(format, args); } public string ResolveInternalId(string id) { var path = AddressablesRuntimeProperties.EvaluateString(id); #if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN || UNITY_XBOXONE if (path.Length >= 260 && path.StartsWith(Application.dataPath)) path = path.Substring(Application.dataPath.Length + 1); #endif return path; } public IEnumerable ResourceLocators { get { return m_ResourceLocators.Select(l => l.Locator); } } public void AddResourceLocator(IResourceLocator loc, string localCatalogHash = null, IResourceLocation remoteCatalogLocation = null) { m_ResourceLocators.Add(new ResourceLocatorInfo(loc, localCatalogHash, remoteCatalogLocation)); } public void RemoveResourceLocator(IResourceLocator loc) { m_ResourceLocators.RemoveAll(l => l.Locator == loc); } public void ClearResourceLocators() { m_ResourceLocators.Clear(); } internal bool GetResourceLocations(object key, Type type, out IList locations) { if (type == null && (key is AssetReference)) type = (key as AssetReference).SubOjbectType; key = EvaluateKey(key); locations = null; HashSet current = null; foreach (var locatorInfo in m_ResourceLocators) { var locator = locatorInfo.Locator; IList locs; if (locator.Locate(key, type, out locs)) { if (locations == null) { //simple, common case, no allocations locations = locs; } else { //less common, need to merge... if (current == null) { current = new HashSet(); foreach (var loc in locations) current.Add(loc); } current.UnionWith(locs); } } } if (current == null) return locations != null; locations = new List(current); return true; } internal bool GetResourceLocations(IEnumerable keys, Type type, Addressables.MergeMode merge, out IList locations) { locations = null; HashSet current = null; foreach (var key in keys) { IList locs; if (GetResourceLocations(key, type, out locs)) { if (locations == null) { locations = locs; if (merge == Addressables.MergeMode.UseFirst) return true; } else { if (current == null) { current = new HashSet(locations, this); } if (merge == Addressables.MergeMode.Intersection) current.IntersectWith(locs); else if (merge == Addressables.MergeMode.Union) current.UnionWith(locs); } } else { //if entries for a key are not found, the intersection is empty if (merge == Addressables.MergeMode.Intersection) { locations = null; return false; } } } if (current == null) return locations != null; if (current.Count == 0) { locations = null; return false; } locations = new List(current); return true; } public AsyncOperationHandle InitializeAsync(string runtimeDataPath, string providerSuffix = null, bool autoReleaseHandle = true) { if (hasStartedInitialization) { if (m_InitializationOperation.IsValid()) return m_InitializationOperation; var completedOperation = ResourceManager.CreateCompletedOperation(m_ResourceLocators[0].Locator, errorMsg: null); if (autoReleaseHandle) AutoReleaseHandleOnCompletion(completedOperation); return completedOperation; } if (ResourceManager.ExceptionHandler == null) { ResourceManager.ExceptionHandler = LogException; } hasStartedInitialization = true; if (m_InitializationOperation.IsValid()) return m_InitializationOperation; //these need to be referenced in order to prevent stripping on IL2CPP platforms. if (string.IsNullOrEmpty(Application.streamingAssetsPath)) Addressables.LogWarning("Application.streamingAssetsPath has been stripped!"); #if !UNITY_SWITCH if (string.IsNullOrEmpty(Application.persistentDataPath)) Addressables.LogWarning("Application.persistentDataPath has been stripped!"); #endif if (string.IsNullOrEmpty(runtimeDataPath)) return ResourceManager.CreateCompletedOperation(null, string.Format("Invalid Key: {0}", runtimeDataPath)); m_OnHandleCompleteAction = OnHandleCompleted; m_OnSceneHandleCompleteAction = OnSceneHandleCompleted; m_OnHandleDestroyedAction = OnHandleDestroyed; #if UNITY_EDITOR Object settingsObject = null; string settingsPath = null; //this indicates that a specific addressables settings asset is being used for the runtime locations if (runtimeDataPath.StartsWith("GUID:")) settingsPath = UnityEditor.AssetDatabase.GUIDToAssetPath(runtimeDataPath.Substring(runtimeDataPath.IndexOf(':') + 1)); var assembly = Assembly.Load("Unity.Addressables.Editor"); if (string.IsNullOrEmpty(settingsPath) && !UnityEditor.EditorApplication.isPlaying) { var rtp = runtimeDataPath.StartsWith("file://") ? runtimeDataPath.Substring("file://".Length) : runtimeDataPath; if(!File.Exists(rtp)) { var defaultSettingsObjectType = assembly.GetType("UnityEditor.AddressableAssets.AddressableAssetSettingsDefaultObject"); var prop = defaultSettingsObjectType.GetProperty("DefaultAssetPath", BindingFlags.Public | BindingFlags.Static); settingsPath = prop.GetValue(null) as string; UnityEditor.EditorApplication.QueuePlayerLoopUpdate(); } } if (!string.IsNullOrEmpty(settingsPath)) { var settingsType = assembly.GetType("UnityEditor.AddressableAssets.Settings.AddressableAssetSettings"); settingsObject = UnityEditor.AssetDatabase.LoadAssetAtPath(settingsPath, settingsType); if (settingsObject != null) { var settingsSetupMethod = settingsType.GetMethod("CreatePlayModeInitializationOperation", BindingFlags.Instance | BindingFlags.NonPublic); m_InitializationOperation = (AsyncOperationHandle)settingsSetupMethod.Invoke(settingsObject, new object[] { this }); } } #endif if(!m_InitializationOperation.IsValid()) m_InitializationOperation = Initialization.InitializationOperation.CreateInitializationOperation(this, runtimeDataPath, providerSuffix); if (autoReleaseHandle) AutoReleaseHandleOnCompletion(m_InitializationOperation); return m_InitializationOperation; } public AsyncOperationHandle InitializeAsync() { var settingsPath = #if UNITY_EDITOR PlayerPrefs.GetString(Addressables.kAddressablesRuntimeDataPath, RuntimePath + "/settings.json"); #else RuntimePath + "/settings.json"; #endif return InitializeAsync(ResolveInternalId(settingsPath)); } public AsyncOperationHandle InitializeAsync(bool autoReleaseHandle) { var settingsPath = #if UNITY_EDITOR PlayerPrefs.GetString(Addressables.kAddressablesRuntimeDataPath, RuntimePath + "/settings.json"); #else RuntimePath + "/settings.json"; #endif return InitializeAsync(ResolveInternalId(settingsPath), null, autoReleaseHandle); } internal ResourceLocationBase CreateCatalogLocationWithHashDependencies(string catalogPath, string hashFilePath) { var catalogLoc = new ResourceLocationBase(catalogPath, catalogPath, typeof(ContentCatalogProvider).FullName, typeof(IResourceLocator)); catalogLoc.Data = new ProviderLoadRequestOptions() { IgnoreFailures = false, WebRequestTimeout = CatalogRequestsTimeout }; if (!string.IsNullOrEmpty(hashFilePath)) { ProviderLoadRequestOptions hashOptions = new ProviderLoadRequestOptions() { IgnoreFailures = true, WebRequestTimeout = CatalogRequestsTimeout }; string tmpPath = hashFilePath; if (ResourceManagerConfig.IsPathRemote(hashFilePath)) { tmpPath = ResourceManagerConfig.StripQueryParameters(hashFilePath); } // The file name of the local cached catalog + hash file is the hash code of the remote hash path, without query parameters (if any). string cacheHashFilePath = ResolveInternalId(kCacheDataFolder + tmpPath.GetHashCode() + ".hash"); var hashResourceLocation = new ResourceLocationBase(hashFilePath, hashFilePath, typeof(TextDataProvider).FullName, typeof(string)); hashResourceLocation.Data = hashOptions.Copy(); catalogLoc.Dependencies.Add(hashResourceLocation); var cacheResourceLocation = new ResourceLocationBase(cacheHashFilePath, cacheHashFilePath, typeof(TextDataProvider).FullName, typeof(string)); cacheResourceLocation.Data = hashOptions.Copy(); catalogLoc.Dependencies.Add(cacheResourceLocation); } return catalogLoc; } [Conditional("UNITY_EDITOR")] void QueueEditorUpdateIfNeeded() { #if UNITY_EDITOR if (!UnityEditor.EditorApplication.isPlaying) UnityEditor.EditorApplication.QueuePlayerLoopUpdate(); #endif } public AsyncOperationHandle LoadContentCatalogAsync(string catalogPath, bool autoReleaseHandle = true, string providerSuffix = null) { string catalogHashPath = catalogPath.Replace(".json", ".hash"); var catalogLoc = CreateCatalogLocationWithHashDependencies(catalogPath, catalogHashPath); if (ShouldChainRequest) return ResourceManager.CreateChainOperation(ChainOperation, op => LoadContentCatalogAsync(catalogPath, autoReleaseHandle, providerSuffix)); var handle = Initialization.InitializationOperation.LoadContentCatalog(this, catalogLoc, providerSuffix); if (autoReleaseHandle) AutoReleaseHandleOnCompletion(handle); QueueEditorUpdateIfNeeded(); return handle; } AsyncOperationHandle TrackHandle(AsyncOperationHandle handle) { handle.Completed += (sceneHandle) => { m_OnSceneHandleCompleteAction(sceneHandle); }; return handle; } AsyncOperationHandle TrackHandle(AsyncOperationHandle handle) { handle.CompletedTypeless += m_OnHandleCompleteAction; return handle; } AsyncOperationHandle TrackHandle(AsyncOperationHandle handle) { handle.Completed += m_OnHandleCompleteAction; return handle; } internal void ClearTrackHandles() { m_resultToHandle.Clear(); } public AsyncOperationHandle LoadAssetAsync(IResourceLocation location) { QueueEditorUpdateIfNeeded(); return TrackHandle(ResourceManager.ProvideResource(location)); } AsyncOperationHandle LoadAssetWithChain(AsyncOperationHandle dep, object key) { return ResourceManager.CreateChainOperation(dep, op => LoadAssetAsync(key)); } public AsyncOperationHandle LoadAssetAsync(object key) { QueueEditorUpdateIfNeeded(); if (ShouldChainRequest) return TrackHandle(LoadAssetWithChain(ChainOperation, key)); key = EvaluateKey(key); IList locs; var t = typeof(TObject); if (t.IsArray) t = t.GetElementType(); else if (t.IsGenericType && typeof(IList<>) == t.GetGenericTypeDefinition()) t = t.GetGenericArguments()[0]; foreach (var locatorInfo in m_ResourceLocators) { var locator = locatorInfo.Locator; if (locator.Locate(key, t, out locs)) { foreach (var loc in locs) { var provider = ResourceManager.GetResourceProvider(typeof(TObject), loc); if (provider != null) return TrackHandle(ResourceManager.ProvideResource(loc)); } } } return ResourceManager.CreateCompletedOperationWithException(default(TObject), new InvalidKeyException(key, t, this)); } class LoadResourceLocationKeyOp : AsyncOperationBase> { object m_Keys; IList m_locations; AddressablesImpl m_Addressables; Type m_ResourceType; protected override string DebugName { get { return m_Keys.ToString(); } } public void Init(AddressablesImpl aa, Type t, object keys) { m_Keys = keys; m_ResourceType = t; m_Addressables = aa; } /// protected override bool InvokeWaitForCompletion() { m_RM?.Update(Time.unscaledDeltaTime); if (!HasExecuted) InvokeExecute(); return true; } protected override void Execute() { m_Addressables.GetResourceLocations(m_Keys, m_ResourceType, out m_locations); if (m_locations == null) m_locations = new List(); Complete(m_locations, true, string.Empty); } } class LoadResourceLocationKeysOp : AsyncOperationBase> { IEnumerable m_Key; Addressables.MergeMode m_MergeMode; IList m_locations; AddressablesImpl m_Addressables; Type m_ResourceType; protected override string DebugName { get { return "LoadResourceLocationKeysOp"; } } public void Init(AddressablesImpl aa, Type t, IEnumerable key, Addressables.MergeMode mergeMode) { m_Key = key; m_ResourceType = t; m_MergeMode = mergeMode; m_Addressables = aa; } protected override void Execute() { m_Addressables.GetResourceLocations(m_Key, m_ResourceType, m_MergeMode, out m_locations); if (m_locations == null) m_locations = new List(); Complete(m_locations, true, string.Empty); } /// protected override bool InvokeWaitForCompletion() { m_RM?.Update(Time.unscaledDeltaTime); if (!HasExecuted) InvokeExecute(); return true; } } public AsyncOperationHandle> LoadResourceLocationsWithChain(AsyncOperationHandle dep, IEnumerable keys, Addressables.MergeMode mode, Type type) { return ResourceManager.CreateChainOperation(dep, op => LoadResourceLocationsAsync(keys, mode, type)); } public AsyncOperationHandle> LoadResourceLocationsAsync(IEnumerable keys, Addressables.MergeMode mode, Type type = null) { QueueEditorUpdateIfNeeded(); if (ShouldChainRequest) return TrackHandle(LoadResourceLocationsWithChain(ChainOperation, keys, mode, type)); var op = new LoadResourceLocationKeysOp(); op.Init(this, type, keys, mode); return TrackHandle(ResourceManager.StartOperation(op, default)); } public AsyncOperationHandle> LoadResourceLocationsWithChain(AsyncOperationHandle dep, object key, Type type) { return ResourceManager.CreateChainOperation(dep, op => LoadResourceLocationsAsync(key, type)); } public AsyncOperationHandle> LoadResourceLocationsAsync(object key, Type type = null) { QueueEditorUpdateIfNeeded(); if (ShouldChainRequest) return TrackHandle(LoadResourceLocationsWithChain(ChainOperation, key, type)); var op = new LoadResourceLocationKeyOp(); op.Init(this, type, key); return TrackHandle(ResourceManager.StartOperation(op, default)); } public AsyncOperationHandle> LoadAssetsAsync(IList locations, Action callback, bool releaseDependenciesOnFailure) { QueueEditorUpdateIfNeeded(); return TrackHandle(ResourceManager.ProvideResources(locations, releaseDependenciesOnFailure, callback)); } AsyncOperationHandle> LoadAssetsWithChain(AsyncOperationHandle dep, IEnumerable keys, Action callback, Addressables.MergeMode mode, bool releaseDependenciesOnFailure) { return ResourceManager.CreateChainOperation(dep, op => LoadAssetsAsync(keys, callback, mode, releaseDependenciesOnFailure)); } public AsyncOperationHandle> LoadAssetsAsync(IEnumerable keys, Action callback, Addressables.MergeMode mode, bool releaseDependenciesOnFailure) { QueueEditorUpdateIfNeeded(); if (ShouldChainRequest) return TrackHandle(LoadAssetsWithChain(ChainOperation, keys, callback, mode, releaseDependenciesOnFailure)); IList locations; if (!GetResourceLocations(keys, typeof(TObject), mode, out locations)) return ResourceManager.CreateCompletedOperationWithException>(null, new InvalidKeyException(keys, typeof(TObject), mode, this)); return LoadAssetsAsync(locations, callback, releaseDependenciesOnFailure); } AsyncOperationHandle> LoadAssetsWithChain(AsyncOperationHandle dep, object key, Action callback, bool releaseDependenciesOnFailure) { return ResourceManager.CreateChainOperation(dep, op2 => LoadAssetsAsync(key, callback, releaseDependenciesOnFailure)); } public AsyncOperationHandle> LoadAssetsAsync(object key, Action callback, bool releaseDependenciesOnFailure) { QueueEditorUpdateIfNeeded(); if (ShouldChainRequest) return TrackHandle(LoadAssetsWithChain(ChainOperation, key, callback, releaseDependenciesOnFailure)); IList locations; if (!GetResourceLocations(key, typeof(TObject), out locations)) return ResourceManager.CreateCompletedOperationWithException>(null, new InvalidKeyException(key, typeof(TObject), this)); return LoadAssetsAsync(locations, callback, releaseDependenciesOnFailure); } void OnHandleDestroyed(AsyncOperationHandle handle) { if (handle.Status == AsyncOperationStatus.Succeeded) { m_resultToHandle.Remove(handle.Result); } } void OnSceneHandleCompleted(AsyncOperationHandle handle) { if (handle.Status == AsyncOperationStatus.Succeeded) { m_SceneInstances.Add(handle); if (!m_resultToHandle.ContainsKey(handle.Result)) { handle.Destroyed += m_OnHandleDestroyedAction; m_resultToHandle.Add(handle.Result, handle); } } } void OnHandleCompleted(AsyncOperationHandle handle) { if (handle.Status == AsyncOperationStatus.Succeeded) { if (!m_resultToHandle.ContainsKey(handle.Result)) { handle.Destroyed += m_OnHandleDestroyedAction; m_resultToHandle.Add(handle.Result, handle); } } } public void Release(TObject obj) { if (obj == null) { LogWarning("Addressables.Release() - trying to release null object."); return; } AsyncOperationHandle handle; if (m_resultToHandle.TryGetValue(obj, out handle)) Release(handle); else { LogError("Addressables.Release was called on an object that Addressables was not previously aware of. Thus nothing is being released"); } } public void Release(AsyncOperationHandle handle) { if (typeof(TObject) == typeof(SceneInstance)) { SceneInstance sceneInstance = (SceneInstance)Convert.ChangeType(handle.Result, typeof(SceneInstance)); if (sceneInstance.Scene.isLoaded && handle.ReferenceCount == 1) { if (SceneOperationCount == 1 && m_SceneInstances.First().Equals(handle)) m_SceneInstances.Clear(); UnloadSceneAsync(handle, UnloadSceneOptions.None, true); } else if (!sceneInstance.Scene.isLoaded && handle.ReferenceCount == 2 && !handle.UnloadSceneOpExcludeReleaseCallback) { AutoReleaseHandleOnCompletion(handle); } } m_ResourceManager.Release(handle); } public void Release(AsyncOperationHandle handle) { m_ResourceManager.Release(handle); } AsyncOperationHandle GetDownloadSizeWithChain(AsyncOperationHandle dep, object key) { return ResourceManager.CreateChainOperation(dep, op => GetDownloadSizeAsync(key)); } AsyncOperationHandle GetDownloadSizeWithChain(AsyncOperationHandle dep, IEnumerable keys) { return ResourceManager.CreateChainOperation(dep, op => GetDownloadSizeAsync(keys)); } public AsyncOperationHandle GetDownloadSizeAsync(object key) { QueueEditorUpdateIfNeeded(); return GetDownloadSizeAsync(new object[] { key }); } public AsyncOperationHandle GetDownloadSizeAsync(IEnumerable keys) { QueueEditorUpdateIfNeeded(); if (ShouldChainRequest) return TrackHandle(GetDownloadSizeWithChain(ChainOperation, keys)); List allLocations = new List(); foreach (object key in keys) { IList locations; if (key is IList) locations = key as IList; else if (key is IResourceLocation) { locations = new List(1) { key as IResourceLocation }; } else if (!GetResourceLocations(key, typeof(object), out locations)) return ResourceManager.CreateCompletedOperationWithException(0, new InvalidKeyException(key, typeof(object), this)); foreach (var loc in locations) { if (loc.HasDependencies) allLocations.AddRange(loc.Dependencies); } } long size = 0; foreach (IResourceLocation location in allLocations.Distinct()) { var sizeData = location.Data as ILocationSizeData; if (sizeData != null) size += sizeData.ComputeSize(location, ResourceManager); } return ResourceManager.CreateCompletedOperation(size, string.Empty); } AsyncOperationHandle DownloadDependenciesAsyncWithChain(AsyncOperationHandle dep, object key, bool autoReleaseHandle) { var handle = ResourceManager.CreateChainOperation(dep, op => DownloadDependenciesAsync(key).Convert>()); if (autoReleaseHandle) AutoReleaseHandleOnCompletion(handle); return handle; } internal static void WrapAsDownloadLocations(List locations) { for (int i = 0; i < locations.Count; i++) locations[i] = new DownloadOnlyLocation(locations[i]); } static List GatherDependenciesFromLocations(IList locations) { var locHash = new HashSet(); foreach (var loc in locations) { if (loc.ResourceType == typeof(IAssetBundleResource)) { locHash.Add(loc); } if (loc.HasDependencies) { foreach (var dep in loc.Dependencies) if (dep.ResourceType == typeof(IAssetBundleResource)) locHash.Add(dep); } } return new List(locHash); } public AsyncOperationHandle DownloadDependenciesAsync(object key, bool autoReleaseHandle = false) { QueueEditorUpdateIfNeeded(); if (ShouldChainRequest) return DownloadDependenciesAsyncWithChain(ChainOperation, key, autoReleaseHandle); IList locations; if (!GetResourceLocations(key, typeof(object), out locations)) { var handle = ResourceManager.CreateCompletedOperationWithException>(null, new InvalidKeyException(key, typeof(object), this)); if (autoReleaseHandle) AutoReleaseHandleOnCompletion(handle); return handle; } else { List dlLocations = GatherDependenciesFromLocations(locations); WrapAsDownloadLocations(dlLocations); var handle = LoadAssetsAsync(dlLocations, null, true); if (autoReleaseHandle) AutoReleaseHandleOnCompletion(handle); return handle; } } AsyncOperationHandle DownloadDependenciesAsyncWithChain(AsyncOperationHandle dep, IList locations, bool autoReleaseHandle) { var handle = ResourceManager.CreateChainOperation(dep, op => DownloadDependenciesAsync(locations).Convert>()); if (autoReleaseHandle) AutoReleaseHandleOnCompletion(handle); return handle; } public AsyncOperationHandle DownloadDependenciesAsync(IList locations, bool autoReleaseHandle = false) { QueueEditorUpdateIfNeeded(); if (ShouldChainRequest) return DownloadDependenciesAsyncWithChain(ChainOperation, locations, autoReleaseHandle); List dlLocations = GatherDependenciesFromLocations(locations); WrapAsDownloadLocations(dlLocations); var handle = LoadAssetsAsync(dlLocations, null, true); if (autoReleaseHandle) AutoReleaseHandleOnCompletion(handle); return handle; } AsyncOperationHandle DownloadDependenciesAsyncWithChain(AsyncOperationHandle dep, IEnumerable keys, Addressables.MergeMode mode, bool autoReleaseHandle) { var handle = ResourceManager.CreateChainOperation(dep, op => DownloadDependenciesAsync(keys, mode).Convert>()); if (autoReleaseHandle) AutoReleaseHandleOnCompletion(handle); return handle; } public AsyncOperationHandle DownloadDependenciesAsync(IEnumerable keys, Addressables.MergeMode mode, bool autoReleaseHandle = false) { QueueEditorUpdateIfNeeded(); if (ShouldChainRequest) return DownloadDependenciesAsyncWithChain(ChainOperation, keys, mode, autoReleaseHandle); IList locations; if (!GetResourceLocations(keys, typeof(object), mode, out locations)) { var handle = ResourceManager.CreateCompletedOperationWithException>(null, new InvalidKeyException(keys, typeof(object), mode, this)); if (autoReleaseHandle) AutoReleaseHandleOnCompletion(handle); return handle; } else { List dlLocations = GatherDependenciesFromLocations(locations); WrapAsDownloadLocations(dlLocations); var handle = LoadAssetsAsync(dlLocations, null, true); if (autoReleaseHandle) AutoReleaseHandleOnCompletion(handle); return handle; } } internal bool ClearDependencyCacheForKey(object key) { bool result = true; #if ENABLE_CACHING IList locations; if (key is IResourceLocation && (key as IResourceLocation).HasDependencies) { foreach (var dep in GatherDependenciesFromLocations((key as IResourceLocation).Dependencies)) { //This should never be false when we get here, if it is there's likely a deeper problem. if (dep.Data is AssetBundleRequestOptions) result = result && Caching.ClearAllCachedVersions((dep.Data as AssetBundleRequestOptions).BundleName); } } else if (GetResourceLocations(key, typeof(object), out locations)) { foreach (var dep in GatherDependenciesFromLocations(locations)) { //This should never be false when we get here, if it is there's likely a deeper problem. if (dep.Data is AssetBundleRequestOptions) result = result && Caching.ClearAllCachedVersions((dep.Data as AssetBundleRequestOptions).BundleName); } } #endif return result; } internal void AutoReleaseHandleOnCompletion(AsyncOperationHandle handle) { handle.Completed += op => Release(op); } internal void AutoReleaseHandleOnCompletion(AsyncOperationHandle handle) { handle.Completed += op => Release(op); } internal void AutoReleaseHandleOnCompletion(AsyncOperationHandle handle, bool unloadSceneOpExcludeReleaseCallback) { handle.Completed += op => { if (unloadSceneOpExcludeReleaseCallback) op.UnloadSceneOpExcludeReleaseCallback = true; Release(op); }; } internal void AutoReleaseHandleOnTypelessCompletion(AsyncOperationHandle handle) { handle.CompletedTypeless += op => Release(op); } public AsyncOperationHandle ClearDependencyCacheAsync(object key, bool autoReleaseHandle) { QueueEditorUpdateIfNeeded(); if (ShouldChainRequest) { var chainOp = ResourceManager.CreateChainOperation(ChainOperation, op => ClearDependencyCacheAsync(key, autoReleaseHandle)); if (autoReleaseHandle) AutoReleaseHandleOnCompletion(chainOp); return chainOp; } bool result = ClearDependencyCacheForKey(key); var completedOp = ResourceManager.CreateCompletedOperation(result, result ? String.Empty : "Unable to clear the cache. AssetBundle's may still be loaded for the given key."); if (autoReleaseHandle) AutoReleaseHandleOnCompletion(completedOp); return completedOp; } public AsyncOperationHandle ClearDependencyCacheAsync(IList locations, bool autoReleaseHandle) { QueueEditorUpdateIfNeeded(); if (ShouldChainRequest) { var chainOp = ResourceManager.CreateChainOperation(ChainOperation, op => ClearDependencyCacheAsync(locations, autoReleaseHandle)); if (autoReleaseHandle) AutoReleaseHandleOnCompletion(chainOp); return chainOp; } bool result = true; foreach (var location in locations) result = result && ClearDependencyCacheForKey(location); var completedOp = ResourceManager.CreateCompletedOperation(result, result ? String.Empty : "Unable to clear the cache. AssetBundle's may still be loaded for the given key(s)."); if (autoReleaseHandle) AutoReleaseHandleOnCompletion(completedOp); return completedOp; } public AsyncOperationHandle ClearDependencyCacheAsync(IEnumerable keys, bool autoReleaseHandle) { QueueEditorUpdateIfNeeded(); if (ShouldChainRequest) { var chainOp = ResourceManager.CreateChainOperation(ChainOperation, op => ClearDependencyCacheAsync(keys, autoReleaseHandle)); if (autoReleaseHandle) AutoReleaseHandleOnCompletion(chainOp); return chainOp; } bool result = true; foreach (var key in keys) result = result && ClearDependencyCacheForKey(key); var completedOp = ResourceManager.CreateCompletedOperation(result, result ? String.Empty : "Unable to clear the cache. AssetBundle's may still be loaded for the given key(s)."); if (autoReleaseHandle) AutoReleaseHandleOnCompletion(completedOp); return completedOp; } public AsyncOperationHandle InstantiateAsync(IResourceLocation location, Transform parent = null, bool instantiateInWorldSpace = false, bool trackHandle = true) { return InstantiateAsync(location, new InstantiationParameters(parent, instantiateInWorldSpace), trackHandle); } public AsyncOperationHandle InstantiateAsync(IResourceLocation location, Vector3 position, Quaternion rotation, Transform parent = null, bool trackHandle = true) { return InstantiateAsync(location, new InstantiationParameters(position, rotation, parent), trackHandle); } public AsyncOperationHandle InstantiateAsync(object key, Transform parent = null, bool instantiateInWorldSpace = false, bool trackHandle = true) { return InstantiateAsync(key, new InstantiationParameters(parent, instantiateInWorldSpace), trackHandle); } public AsyncOperationHandle InstantiateAsync(object key, Vector3 position, Quaternion rotation, Transform parent = null, bool trackHandle = true) { return InstantiateAsync(key, new InstantiationParameters(position, rotation, parent), trackHandle); } AsyncOperationHandle InstantiateWithChain(AsyncOperationHandle dep, object key, InstantiationParameters instantiateParameters, bool trackHandle = true) { var chainOp = ResourceManager.CreateChainOperation(dep, op => InstantiateAsync(key, instantiateParameters, false)); if (trackHandle) chainOp.CompletedTypeless += m_OnHandleCompleteAction; return chainOp; } public AsyncOperationHandle InstantiateAsync(object key, InstantiationParameters instantiateParameters, bool trackHandle = true) { QueueEditorUpdateIfNeeded(); if (ShouldChainRequest) return InstantiateWithChain(ChainOperation, key, instantiateParameters, trackHandle); key = EvaluateKey(key); IList locs; foreach (var locatorInfo in m_ResourceLocators) { var locator = locatorInfo.Locator; if (locator.Locate(key, typeof(GameObject), out locs)) return InstantiateAsync(locs[0], instantiateParameters, trackHandle); } return ResourceManager.CreateCompletedOperationWithException(null, new InvalidKeyException(key, typeof(GameObject), this)); } AsyncOperationHandle InstantiateWithChain(AsyncOperationHandle dep, IResourceLocation location, InstantiationParameters instantiateParameters, bool trackHandle = true) { var chainOp = ResourceManager.CreateChainOperation(dep, op => InstantiateAsync(location, instantiateParameters, false)); if (trackHandle) chainOp.CompletedTypeless += m_OnHandleCompleteAction; return chainOp; } public AsyncOperationHandle InstantiateAsync(IResourceLocation location, InstantiationParameters instantiateParameters, bool trackHandle = true) { QueueEditorUpdateIfNeeded(); if (ShouldChainRequest) return InstantiateWithChain(ChainOperation, location, instantiateParameters, trackHandle); var opHandle = ResourceManager.ProvideInstance(InstanceProvider, location, instantiateParameters); if (!trackHandle) return opHandle; opHandle.CompletedTypeless += m_OnHandleCompleteAction; return opHandle; } public bool ReleaseInstance(GameObject instance) { if (instance == null) { LogWarning("Addressables.ReleaseInstance() - trying to release null object."); return false; } AsyncOperationHandle handle; if (m_resultToHandle.TryGetValue(instance, out handle)) Release(handle); else return false; return true; } internal AsyncOperationHandle LoadSceneWithChain(AsyncOperationHandle dep, object key, LoadSceneMode loadMode = LoadSceneMode.Single, bool activateOnLoad = true, int priority = 100) { return TrackHandle(ResourceManager.CreateChainOperation(dep, op => LoadSceneAsync(key, loadMode, activateOnLoad, priority, false))); } public AsyncOperationHandle LoadSceneAsync(object key, LoadSceneMode loadMode = LoadSceneMode.Single, bool activateOnLoad = true, int priority = 100, bool trackHandle = true) { QueueEditorUpdateIfNeeded(); if (ShouldChainRequest) return LoadSceneWithChain(ChainOperation, key, loadMode, activateOnLoad, priority); IList locations; if (!GetResourceLocations(key, typeof(SceneInstance), out locations)) return ResourceManager.CreateCompletedOperationWithException(default(SceneInstance), new InvalidKeyException(key, typeof(SceneInstance), this)); return LoadSceneAsync(locations[0], loadMode, activateOnLoad, priority, trackHandle); } public AsyncOperationHandle LoadSceneAsync(IResourceLocation location, LoadSceneMode loadMode = LoadSceneMode.Single, bool activateOnLoad = true, int priority = 100, bool trackHandle = true) { var handle = ResourceManager.ProvideScene(SceneProvider, location, loadMode, activateOnLoad, priority); if (trackHandle) return TrackHandle(handle); return handle; } public AsyncOperationHandle UnloadSceneAsync(SceneInstance scene, UnloadSceneOptions unloadOptions = UnloadSceneOptions.None, bool autoReleaseHandle = true) { AsyncOperationHandle handle; if (!m_resultToHandle.TryGetValue(scene, out handle)) { var msg = string.Format("Addressables.UnloadSceneAsync() - Cannot find handle for scene {0}", scene); LogWarning(msg); return ResourceManager.CreateCompletedOperation(scene, msg); } if (handle.m_InternalOp.IsRunning) return CreateUnloadSceneWithChain(handle, unloadOptions, autoReleaseHandle); return UnloadSceneAsync(handle, unloadOptions, autoReleaseHandle); } public AsyncOperationHandle UnloadSceneAsync(AsyncOperationHandle handle, UnloadSceneOptions unloadOptions = UnloadSceneOptions.None, bool autoReleaseHandle = true) { QueueEditorUpdateIfNeeded(); if (handle.m_InternalOp.IsRunning) return CreateUnloadSceneWithChain(handle, unloadOptions, autoReleaseHandle); return UnloadSceneAsync(handle.Convert(), unloadOptions, autoReleaseHandle); } public AsyncOperationHandle UnloadSceneAsync(AsyncOperationHandle handle, UnloadSceneOptions unloadOptions = UnloadSceneOptions.None, bool autoReleaseHandle = true) { if (handle.m_InternalOp.IsRunning) return CreateUnloadSceneWithChain(handle, unloadOptions, autoReleaseHandle); return InternalUnloadScene(handle, unloadOptions, autoReleaseHandle); } internal AsyncOperationHandle CreateUnloadSceneWithChain(AsyncOperationHandle handle, UnloadSceneOptions unloadOptions, bool autoReleaseHandle) { return m_ResourceManager.CreateChainOperation(handle, (completedHandle) => InternalUnloadScene(completedHandle.Convert(), unloadOptions, autoReleaseHandle)); } internal AsyncOperationHandle CreateUnloadSceneWithChain(AsyncOperationHandle handle, UnloadSceneOptions unloadOptions, bool autoReleaseHandle) { return m_ResourceManager.CreateChainOperation(handle, (completedHandle) => InternalUnloadScene(completedHandle, unloadOptions, autoReleaseHandle)); } internal AsyncOperationHandle InternalUnloadScene(AsyncOperationHandle handle, UnloadSceneOptions unloadOptions, bool autoReleaseHandle) { QueueEditorUpdateIfNeeded(); var relOp = SceneProvider.ReleaseScene(ResourceManager, handle, unloadOptions); if (autoReleaseHandle) AutoReleaseHandleOnCompletion(relOp, true); return relOp; } private object EvaluateKey(object obj) { if (obj is IKeyEvaluator) return (obj as IKeyEvaluator).RuntimeKey; return obj; } internal AsyncOperationHandle> CheckForCatalogUpdates(bool autoReleaseHandle = true) { if (ShouldChainRequest) return CheckForCatalogUpdatesWithChain(autoReleaseHandle); if (m_ActiveCheckUpdateOperation.IsValid()) Release(m_ActiveCheckUpdateOperation); m_ActiveCheckUpdateOperation = new CheckCatalogsOperation(this).Start(m_ResourceLocators); if (autoReleaseHandle) AutoReleaseHandleOnTypelessCompletion(m_ActiveCheckUpdateOperation); return m_ActiveCheckUpdateOperation; } internal AsyncOperationHandle> CheckForCatalogUpdatesWithChain(bool autoReleaseHandle) { return ResourceManager.CreateChainOperation(ChainOperation, op => CheckForCatalogUpdates(autoReleaseHandle)); } internal ResourceLocatorInfo GetLocatorInfo(string c) { foreach (var l in m_ResourceLocators) if (l.Locator.LocatorId == c) return l; return null; } internal IEnumerable CatalogsWithAvailableUpdates => m_ResourceLocators.Where(s => s.ContentUpdateAvailable).Select(s => s.Locator.LocatorId); internal AsyncOperationHandle> UpdateCatalogs(IEnumerable catalogIds = null, bool autoReleaseHandle = true, bool autoCleanBundleCache = false) { if (m_ActiveUpdateOperation.IsValid()) return m_ActiveUpdateOperation; if (catalogIds == null && !CatalogsWithAvailableUpdates.Any()) return m_ResourceManager.CreateChainOperation(CheckForCatalogUpdates(), depOp => UpdateCatalogs(CatalogsWithAvailableUpdates, autoReleaseHandle, autoCleanBundleCache)); var op = new UpdateCatalogsOperation(this).Start(catalogIds == null ? CatalogsWithAvailableUpdates : catalogIds, autoCleanBundleCache); if (autoReleaseHandle) AutoReleaseHandleOnTypelessCompletion(op); return op; } //needed for IEqualityComparer interface public bool Equals(IResourceLocation x, IResourceLocation y) { return x.PrimaryKey.Equals(y.PrimaryKey) && x.ResourceType.Equals(y.ResourceType) && x.InternalId.Equals(y.InternalId); } //needed for IEqualityComparer interface public int GetHashCode(IResourceLocation loc) { return loc.PrimaryKey.GetHashCode() * 31 + loc.ResourceType.GetHashCode(); } internal AsyncOperationHandle CleanBundleCache(IEnumerable catalogIds, bool forceSingleThreading) { if (ShouldChainRequest) return CleanBundleCacheWithChain(catalogIds, forceSingleThreading); #if !ENABLE_CACHING return ResourceManager.CreateCompletedOperation(false, "Caching not enabled. There is no bundle cache to modify."); #else if (catalogIds == null) catalogIds = m_ResourceLocators.Select(s => s.Locator.LocatorId); var locations = new List(); foreach (var c in catalogIds) { if (c == null) continue; var loc = GetLocatorInfo(c); if (loc == null || loc.CatalogLocation == null) continue; locations.Add(loc.CatalogLocation); } if (locations.Count == 0) return ResourceManager.CreateCompletedOperation(false, "Provided catalogs do not load data from a catalog file. This can occur when using the \"Use Asset Database (fastest)\" playmode script. Bundle cache was not modified."); return CleanBundleCache(ResourceManager.CreateGroupOperation(locations), forceSingleThreading); #endif } internal AsyncOperationHandle CleanBundleCache(AsyncOperationHandle> depOp, bool forceSingleThreading) { if (ShouldChainRequest) return CleanBundleCacheWithChain(depOp, forceSingleThreading); #if !ENABLE_CACHING return ResourceManager.CreateCompletedOperation(false, "Caching not enabled. There is no bundle cache to modify."); #else if (m_ActiveCleanBundleCacheOperation.IsValid() && !m_ActiveCleanBundleCacheOperation.IsDone) return ResourceManager.CreateCompletedOperation(false, "Bundle cache is already being cleaned."); m_ActiveCleanBundleCacheOperation = new CleanBundleCacheOperation(this, forceSingleThreading).Start(depOp); return m_ActiveCleanBundleCacheOperation; #endif } internal AsyncOperationHandle CleanBundleCacheWithChain(AsyncOperationHandle> depOp, bool forceSingleThreading) { return ResourceManager.CreateChainOperation(ChainOperation, op => CleanBundleCache(depOp, forceSingleThreading)); } internal AsyncOperationHandle CleanBundleCacheWithChain(IEnumerable catalogIds, bool forceSingleThreading) { return ResourceManager.CreateChainOperation(ChainOperation, op => CleanBundleCache(catalogIds, forceSingleThreading)); } } }