using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.Serialization;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.ResourceManagement;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceLocations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.ResourceManagement.Util;
using UnityEngine.SceneManagement;
using System.Collections;
using System.Text;
using UnityEngine.Networking;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UnityEngine.AddressableAssets
{
///
/// Exception to encapsulate invalid key errors.
///
public class InvalidKeyException : Exception
{
///
/// The key used to generate the exception.
///
public object Key { get; private set; }
///
/// The type of the key used to generate the exception.
///
public Type Type { get; private set; }
///
/// MergeMode if used, else null.
///
public Addressables.MergeMode? MergeMode { get; }
///
/// Construct a new InvalidKeyException.
///
/// The key that caused the exception.
public InvalidKeyException(object key) : this(key, typeof(object)) {}
private AddressablesImpl m_Addressables;
///
/// Construct a new InvalidKeyException.
///
/// The key that caused the exception.
/// The type of the key that caused the exception.
public InvalidKeyException(object key, Type type)
{
Key = key;
Type = type;
}
internal InvalidKeyException(object key, Type type, AddressablesImpl addr)
{
Key = key;
Type = type;
m_Addressables = addr;
}
///
/// Construct a new InvalidKeyException.
///
/// The key that caused the exception.
/// The type of the key that caused the exception.
/// The mergeMode of the input that caused the exception.
public InvalidKeyException(object key, Type type, Addressables.MergeMode mergeMode)
{
Key = key;
Type = type;
MergeMode = mergeMode;
}
internal InvalidKeyException(object key, Type type, Addressables.MergeMode mergeMode, AddressablesImpl addr)
{
Key = key;
Type = type;
MergeMode = mergeMode;
m_Addressables = addr;
}
///
public InvalidKeyException() {}
///
public InvalidKeyException(string message) : base(message) {}
///
public InvalidKeyException(string message, Exception innerException) : base(message, innerException) {}
///
protected InvalidKeyException(SerializationInfo message, StreamingContext context) : base(message, context) {}
const string BaseInvalidKeyMessageFormat = "{0}, Key={1}, Type={2}";
///
/// Stores information about the exception.
///
public override string Message
{
get
{
string stringKey = Key as string;
if (!string.IsNullOrEmpty(stringKey))
{
if (m_Addressables == null)
return string.Format(BaseInvalidKeyMessageFormat, base.Message, stringKey, Type);
return GetMessageForSingleKey(stringKey);
}
IEnumerable enumerableKey = Key as IEnumerable;
if (enumerableKey != null)
{
int keyCount = 0;
List stringKeys = new List();
HashSet keyTypeNames = new HashSet();
foreach (object keyObj in enumerableKey)
{
keyCount++;
keyTypeNames.Add(keyObj.GetType().ToString());
if (keyObj is string)
stringKeys.Add(keyObj as string);
}
if (!MergeMode.HasValue)
{
string keysCSV = GetCSVString(stringKeys, "Key=", "Keys=");
return $"{base.Message} No MergeMode is set to merge the multiple keys requested. {keysCSV}, Type={Type}";
}
if (keyCount != stringKeys.Count)
{
string types = GetCSVString(keyTypeNames, "Type=", "Types=");
return $"{base.Message} Enumerable key contains multiple Types. {types}, all Keys are expected to be strings";
}
if (keyCount == 1)
return GetMessageForSingleKey(stringKeys[0]);
return GetMessageforMergeKeys(stringKeys);
}
return string.Format(BaseInvalidKeyMessageFormat, base.Message, Key, Type);
}
}
string GetMessageForSingleKey(string keyString)
{
#if UNITY_EDITOR
string path = AssetDatabase.GUIDToAssetPath(keyString);
if (!string.IsNullOrEmpty(path))
{
Type directType = AssetDatabase.GetMainAssetTypeAtPath(path);
if (directType != null)
return $"{base.Message} Could not load Asset with GUID={keyString}, Path={path}. Asset exists with main Type={directType}, which is not assignable from the requested Type={Type}";
return string.Format(BaseInvalidKeyMessageFormat, base.Message, keyString, Type);
}
#endif
HashSet typesAvailableForKey = GetTypesForKey(keyString);
if (typesAvailableForKey.Count == 0)
return $"{base.Message} No Location found for Key={keyString}";
if (typesAvailableForKey.Count == 1)
{
Type availableType = null;
foreach (Type type in typesAvailableForKey)
availableType = type;
if (availableType == null)
return string.Format(BaseInvalidKeyMessageFormat, base.Message, keyString, Type);
return $"{base.Message} No Asset found with for Key={keyString}. Key exists as Type={availableType}, which is not assignable from the requested Type={Type}";
}
StringBuilder csv = new StringBuilder(512);
int count = 0;
foreach (Type type in typesAvailableForKey)
{
count++;
csv.Append(count > 1 ? $", {type}" : type.ToString());
}
return $"{base.Message} No Asset found with for Key={keyString}. Key exists as multiple Types={csv}, which is not assignable from the requested Type={Type}";
}
string GetMessageforMergeKeys(List keys)
{
string keysCSV = GetCSVString(keys, "Key=", "Keys=");
string NoLocationLineMessage = "\nNo Location found for Key={0}";
StringBuilder messageBuilder = null;
switch (MergeMode)
{
case Addressables.MergeMode.Union:
{
messageBuilder = new StringBuilder($"{base.Message} No {MergeMode.Value} of Assets between {keysCSV} with Type={Type}");
Dictionary> typeToKeys = new Dictionary>();
foreach (string key in keys)
{
if (!GetTypeToKeys(key, typeToKeys))
messageBuilder.Append(string.Format(NoLocationLineMessage, key));
}
foreach (KeyValuePair> pair in typeToKeys)
{
string availableKeysString = GetCSVString(pair.Value, "Key=", "Keys=");
List unavailableKeys = new List();
foreach (string key in keys)
{
if (!pair.Value.Contains(key))
unavailableKeys.Add(key);
}
if (unavailableKeys.Count == 0)
messageBuilder.Append($"\nUnion of Type={pair.Key} found with {availableKeysString}");
else
{
string unavailableKeysString = GetCSVString(unavailableKeys, "Key=", "Keys=");
messageBuilder.Append($"\nUnion of Type={pair.Key} found with {availableKeysString}. Without {unavailableKeysString}");
}
}
}
break;
case Addressables.MergeMode.Intersection:
{
messageBuilder = new StringBuilder($"{base.Message} No {MergeMode.Value} of Assets between {keysCSV} with Type={Type}");
bool hasInvalidKeys = false;
Dictionary> typeToKeys = new Dictionary>();
foreach (string key in keys)
{
if (!GetTypeToKeys(key, typeToKeys))
{
hasInvalidKeys = true;
messageBuilder.Append(string.Format(NoLocationLineMessage, key));
}
}
if (hasInvalidKeys)
break;
foreach (KeyValuePair> pair in typeToKeys)
{
if (pair.Value.Count == keys.Count)
messageBuilder.Append($"\nAn Intersection exists for Type={pair.Key}");
}
}
break;
case Addressables.MergeMode.UseFirst:
{
messageBuilder = new StringBuilder($"{base.Message} No {MergeMode.Value} Asset within {keysCSV} with Type={Type}");
Dictionary> typeToKeys = new Dictionary>();
foreach (string key in keys)
{
if (!GetTypeToKeys(key, typeToKeys))
messageBuilder.Append(string.Format(NoLocationLineMessage, key));
}
string keyCSV;
foreach (KeyValuePair> pair in typeToKeys)
{
keyCSV = GetCSVString(pair.Value, "Key=", "Keys=");
messageBuilder.Append($"\nType={pair.Key} exists for {keyCSV}");
}
}
break;
}
return messageBuilder.ToString();
}
HashSet GetTypesForKey(string keyString)
{
HashSet typesAvailableForKey = new HashSet();
foreach (var locator in m_Addressables.ResourceLocators)
{
if (!locator.Locate(keyString, null, out var locations))
continue;
foreach (IResourceLocation location in locations)
typesAvailableForKey.Add(location.ResourceType);
}
return typesAvailableForKey;
}
bool GetTypeToKeys(string key, Dictionary> typeToKeys)
{
HashSet types = GetTypesForKey(key);
if (types.Count == 0)
return false;
foreach (Type type in types)
{
if (!typeToKeys.TryGetValue(type, out List keysForType))
typeToKeys.Add(type, new List() {key});
else
keysForType.Add(key);
}
return true;
}
string GetCSVString(IEnumerable enumerator, string prefixSingle, string prefixPlural)
{
StringBuilder keysCSVBuilder = new StringBuilder(prefixPlural);
int count = 0;
foreach (var key in enumerator)
{
count++;
keysCSVBuilder.Append(count > 1 ? $", {key}" : key);
}
if (count == 1 && !string.IsNullOrEmpty(prefixPlural) && !string.IsNullOrEmpty(prefixSingle))
keysCSVBuilder.Replace(prefixPlural, prefixSingle);
return keysCSVBuilder.ToString();
}
}
///
/// Entry point for Addressable API, this provides a simpler interface than using ResourceManager directly as it assumes string address type.
///
public static class Addressables
{
internal static bool reinitializeAddressables = true;
internal static AddressablesImpl m_AddressablesInstance = new AddressablesImpl(new LRUCacheAllocationStrategy(1000, 1000, 100, 10));
static AddressablesImpl m_Addressables
{
get
{
#if UNITY_EDITOR
if (EditorSettings.enterPlayModeOptionsEnabled && reinitializeAddressables)
{
reinitializeAddressables = false;
m_AddressablesInstance.ReleaseSceneManagerOperation();
m_AddressablesInstance = new AddressablesImpl(new LRUCacheAllocationStrategy(1000, 1000, 100, 10));
}
#endif
return m_AddressablesInstance;
}
}
///
/// Stores the ResourceManager associated with this Addressables instance.
///
public static ResourceManager ResourceManager { get { return m_Addressables.ResourceManager; } }
internal static AddressablesImpl Instance { get { return m_Addressables; } }
#if UNITY_EDITOR
[InitializeOnLoadMethod]
static void RegisterPlayModeStateChange()
{
EditorApplication.playModeStateChanged += SetAddressablesReInitFlagOnExitPlayMode;
}
static void SetAddressablesReInitFlagOnExitPlayMode(PlayModeStateChange change)
{
if (change == PlayModeStateChange.EnteredEditMode || change == PlayModeStateChange.ExitingPlayMode)
reinitializeAddressables = true;
}
#endif
///
/// The Instance Provider used by the Addressables System.
///
public static IInstanceProvider InstanceProvider { get { return m_Addressables.InstanceProvider; } }
///
/// Used to resolve a string using addressables config values
///
/// The internal id to resolve.
/// Returns the string that the internal id represents.
public static string ResolveInternalId(string id)
{
return m_Addressables.ResolveInternalId(id);
}
///
/// Functor to transform internal ids before being used by the providers.
///
///
/// The calls your transorm function when it looks up an asset,
/// passing the instance for the asset to your function.
/// You can change the property of this instance
/// and return the modified object as the return value of your function.
///
/// See also: [Transforming resource URLs](xref:addressables-api-transform-internal-id)
///
static public Func InternalIdTransformFunc
{
get { return m_Addressables.InternalIdTransformFunc; }
set { m_Addressables.InternalIdTransformFunc = value; }
}
///
/// Delegate that can be used to override the web request options before being sent.
///
///
/// The web request passed to this delegate has already been preconfigured internally. Override at your own risk.
///
public static Action WebRequestOverride
{
get { return m_Addressables.WebRequestOverride; }
set { m_Addressables.WebRequestOverride = value; }
}
///
/// Options for merging the results of requests.
/// If keys (A, B) mapped to results ([1,2,4],[3,4,5])...
/// - UseFirst (or None) takes the results from the first key
/// -- [1,2,4]
/// - Union takes results of each key and collects items that matched any key.
/// -- [1,2,3,4,5]
/// - Intersection takes results of each key, and collects items that matched every key.
/// -- [4]
///
public enum MergeMode
{
///
/// Use to indicate that no merge should occur. The first set of results will be used.
///
None = 0,
///
/// Use to indicate that the merge should take the first set of results.
///
UseFirst = 0,
///
/// Use to indicate that the merge should take the union of the results.
///
Union,
///
/// Use to indicate that the merge should take the intersection of the results.
///
Intersection
}
///
/// The name of the PlayerPrefs value used to set the path to load the addressables runtime data file.
///
public const string kAddressablesRuntimeDataPath = "AddressablesRuntimeDataPath";
const string k_AddressablesLogConditional = "ADDRESSABLES_LOG_ALL";
///
/// The name of the PlayerPrefs value used to set the path to check for build logs that need to be shown in the runtime.
///
public const string kAddressablesRuntimeBuildLogPath = "AddressablesRuntimeBuildLog";
///
/// The subfolder used by the Addressables system for its initialization data.
///
public static string StreamingAssetsSubFolder { get { return m_Addressables.StreamingAssetsSubFolder; } }
///
/// The path to the Addressables Library subfolder
///
public static string LibraryPath = "Library/com.unity.addressables/";
///
/// The path used by the Addressables system for its initialization data.
///
public static string BuildPath { get { return m_Addressables.BuildPath; } }
///
/// The path that addressables player data gets copied to during a player build.
///
public static string PlayerBuildDataPath { get { return m_Addressables.PlayerBuildDataPath; } }
///
/// The path used by the Addressables system to load initialization data.
///
public static string RuntimePath { get { return m_Addressables.RuntimePath; } }
///
/// Gets the collection of configured objects. Resource Locators are used to find objects from user-defined typed keys.
///
/// The resource locators collection.
public static IEnumerable ResourceLocators { get { return m_Addressables.ResourceLocators; } }
[Conditional(k_AddressablesLogConditional)]
internal static void InternalSafeSerializationLog(string msg, LogType logType = LogType.Log)
{
if (m_AddressablesInstance == null)
return;
switch (logType)
{
case LogType.Warning:
m_AddressablesInstance.LogWarning(msg);
break;
case LogType.Error:
m_AddressablesInstance.LogError(msg);
break;
case LogType.Log:
m_AddressablesInstance.Log(msg);
break;
}
}
[Conditional(k_AddressablesLogConditional)]
internal static void InternalSafeSerializationLogFormat(string format, LogType logType = LogType.Log, params object[] args)
{
if (m_AddressablesInstance == null)
return;
switch (logType)
{
case LogType.Warning:
m_AddressablesInstance.LogWarningFormat(format, args);
break;
case LogType.Error:
m_AddressablesInstance.LogErrorFormat(format, args);
break;
case LogType.Log:
m_AddressablesInstance.LogFormat(format, args);
break;
}
}
///
/// Debug.Log wrapper method that is contional on the ADDRESSABLES_LOG_ALL symbol definition. This can be set in the Player preferences in the 'Scripting Define Symbols'.
///
/// The msg to log
[Conditional(k_AddressablesLogConditional)]
public static void Log(string msg)
{
m_Addressables.Log(msg);
}
///
/// Debug.LogFormat wrapper method that is contional on the ADDRESSABLES_LOG_ALL symbol definition. This can be set in the Player preferences in the 'Scripting Define Symbols'.
///
/// The string with format tags.
/// The args used to fill in the format tags.
[Conditional(k_AddressablesLogConditional)]
public static void LogFormat(string format, params object[] args)
{
m_Addressables.LogFormat(format, args);
}
///
/// Debug.LogWarning wrapper method.
///
/// The msg to log
public static void LogWarning(string msg)
{
m_Addressables.LogWarning(msg);
}
///
/// Debug.LogWarningFormat wrapper method.
///
/// The string with format tags.
/// The args used to fill in the format tags.
public static void LogWarningFormat(string format, params object[] args)
{
m_Addressables.LogWarningFormat(format, args);
}
///
/// Debug.LogError wrapper method.
///
/// The msg to log
public static void LogError(string msg)
{
m_Addressables.LogError(msg);
}
///
/// Debug.LogException wrapper method.
///
/// The operation handle.
/// The exception.
public static void LogException(AsyncOperationHandle op, Exception ex)
{
m_Addressables.LogException(op, ex);
}
///
/// Debug.LogException wrapper method.
///
/// The exception.
public static void LogException(Exception ex)
{
m_Addressables.LogException(ex);
}
///
/// Debug.LogErrorFormat wrapper method.
///
/// The string with format tags.
/// The args used to fill in the format tags.
public static void LogErrorFormat(string format, params object[] args)
{
m_Addressables.LogErrorFormat(format, args);
}
///
/// Initialize Addressables system. Addressables will be initialized on the first API call if this is not called explicitly.
///
/// The operation handle for the request.
//[Obsolete("We have added Async to the name of all asynchronous methods (UnityUpgradable) -> InitializeAsync(*)", true)]
[Obsolete]
public static AsyncOperationHandle Initialize()
{
return InitializeAsync();
}
///
/// Initialize the Addressables system, if needed.
///
///
/// The Addressables system initializes itself at runtime the first time you call an Addressables API function.
/// You can call this function explicitly to initialize Addressables earlier. This function does nothing if
/// initialization has already occurred.
///
/// The initialization process:
/// * Sets up the and
/// * Loads the object, which is created by the Addressables build
/// * Executes operations
/// * Optionally, checks for an updated content catalog (`true` by default)
/// * Loads the content catalog
///
/// The `Result` object contained in the returned by this function
/// contains a list of Addressable keys and a method that can be used to gather the
/// instances for a given key and asset type. You must access the `Result` object in a
/// event handler. To access the handle in a coroutine or Task-based function, pass `false` to the
/// overload of this function. Otherwise, the Addressables system
/// releases the object before control returns to your code.
///
/// Initializing Addressables manually can improve performance of your first loading operations since they do not
/// need to wait for initialization to complete. In addition, it can help when debugging early loading operations
/// by separating out the initialization process.
///
/// See also:
/// * [Customizing initialization](xref:addressables-api-initialize-async)
/// * [Managing catalogs at runtime](xref:addressables-api-load-content-catalog-async)
///
/// The operation handle for the request.
public static AsyncOperationHandle InitializeAsync()
{
return m_Addressables.InitializeAsync();
}
///
/// Initialize the Addressables system, if needed.
///
///
/// The Addressables system initializes itself at runtime the first time you call an Addressables API function.
/// You can call this function explicitly to initialize Addressables earlier. This function does nothing if
/// initialization has already occurred.
///
/// The initialization process:
/// * Sets up the and
/// * Loads the object, which is created by the Addressables build
/// * Executes operations
/// * Optionally, checks for an updated content catalog (`true` by default)
/// * Loads the content catalog
///
/// The `Result` object contained in the returned by this function
/// contains a list of Addressable keys and a method that can be used to gather the
/// instances for a given key and asset type. To access the `Result` object, use a
/// event handler or set to `false`.
///
/// Initializing Addressables manually can improve performance of your first loading operations since they do not
/// need to wait for initialization to complete. In addition, it can help when debugging early loading operations
/// by separating out the initialization process.
///
/// See also:
/// * [Customizing initialization](xref:addressables-api-initialize-async)
/// * [Managing catalogs at runtime](xref:addressables-api-load-content-catalog-async)
///
/// If true, the handle is automatically released on completion.
/// The operation handle for the request.
public static AsyncOperationHandle InitializeAsync(bool autoReleaseHandle)
{
return m_Addressables.InitializeAsync(autoReleaseHandle);
}
///
/// Additively load catalogs from runtime data. The settings are not used.
///
/// The path to the runtime data.
/// This value, if not null or empty, will be appended to all provider ids loaded from this data.
/// The operation handle for the request.
//[Obsolete("We have added Async to the name of all asynchronous methods (UnityUpgradable) -> LoadContentCatalogAsync(*)", true)]
[Obsolete]
public static AsyncOperationHandle LoadContentCatalog(string catalogPath, string providerSuffix = null)
{
return LoadContentCatalogAsync(catalogPath, providerSuffix);
}
///
/// Additively load catalogs from runtime data.
///
///
/// You can cache content catalog by providing the hash file created for the catalog by the Addressables content build
/// at the same URL as the catalog JSON file. The Addressables system uses this hash file to determine if the cached catalog
/// needs to be updated. If the value in the hash file has not changed since the last time you loaded the same catalog,
/// this function loads the cached version instead of downloading the catalog. If the hash value has changed or if no
/// hash file is provided, Addressables downloads the catalog from the specified path before loading it into memory.
///
/// See also: [Managing catalogs at runtime](xref:addressables-api-load-content-catalog-async)
///
/// The path to the runtime data.
/// This value, if not null or empty, will be appended to all provider ids loaded from this data.
/// The operation handle for the request.
public static AsyncOperationHandle LoadContentCatalogAsync(string catalogPath, string providerSuffix = null)
{
return m_Addressables.LoadContentCatalogAsync(catalogPath, false, providerSuffix);
}
///
/// Additively load catalogs from runtime data.
///
///
/// You can cache content catalog by providing the hash file created for the catalog by the Addressables content build
/// at the same URL as the catalog JSON file. The Addressables system uses this hash file to determine if the cached catalog
/// needs to be updated. If the value in the hash file has not changed since the last time you loaded the same catalog,
/// this function loads the cached version instead of downloading the catalog. If the hash value has changed or if no
/// hash file is provided, Addressables downloads the catalog from the specified path before loading it into memory.
///
/// See also: [Managing catalogs at runtime](xref:addressables-api-load-content-catalog-async)
///
/// The path to the runtime data.
/// If true, the async operation handle will be automatically released on completion. Typically,
/// there is no reason to hold on to the handle for this operation.
/// This value, if not null or empty, will be appended to all provider ids loaded from this data.
/// The operation handle for the request.
public static AsyncOperationHandle LoadContentCatalogAsync(string catalogPath, bool autoReleaseHandle, string providerSuffix = null)
{
return m_Addressables.LoadContentCatalogAsync(catalogPath, autoReleaseHandle, providerSuffix);
}
///
/// Initialization operation. You can register a callback with this if you need to run code after Addressables is ready. Any requests made before this operaton completes will automatically cahin to its result.
///
[Obsolete]
public static AsyncOperationHandle InitializationOperation => default;
///
/// Load a single asset
///
/// The type of the asset.
/// The location of the asset.
/// Returns the load operation.
//[Obsolete("We have added Async to the name of all asynchronous methods (UnityUpgradable) -> LoadAssetAsync(*)", true)]
[Obsolete]
public static AsyncOperationHandle LoadAsset(IResourceLocation location)
{
return LoadAssetAsync(location);
}
///
/// Load a single asset
///
/// The type of the asset.
/// The key of the location of the asset.
/// Returns the load operation.
//[Obsolete("We have added Async to the name of all asynchronous methods (UnityUpgradable) -> LoadAssetAsync(*)", true)]
[Obsolete]
public static AsyncOperationHandle LoadAsset(object key)
{
return LoadAssetAsync(key);
}
///
/// Loads a single Addressable asset identified by an .
///
///
/// When you load an Addressable asset, the system:
/// * Gathers the asset's dependencies
/// * Downloads any remote AssetBundles needed to load the asset or its dependencies
/// * Loads the AssetBundles into memory
/// * Populates the `Result` object of the instance returned by this function.
///
/// Use the `Result` object to access the loaded assets.
///
/// See [Loading Addressable Assets](xref:addressables-api-load-asset-async) for more information and examples.
///
/// See [Operations](xref:addressables-async-operation-handling) for information on handling the asynchronous operations used
/// to load Addressable assets.
///
/// The type of the asset.
/// The location of the asset.
/// Returns the load operation.
public static AsyncOperationHandle LoadAssetAsync(IResourceLocation location)
{
return m_Addressables.LoadAssetAsync(location);
}
///
/// Loads a single Addressable asset identified by a key such as an address or label.
///
///
/// When you load an Addressable asset, the system:
/// * Gathers the asset's dependencies
/// * Downloads any remote AssetBundles needed to load the asset or its dependencies
/// * Loads the AssetBundles into memory
/// * Populates the `Result` object of the instance returned by this function.
///
/// Use the `Result` object to access the loaded assets.
///
/// Note that if you provide a key, such as a label, that maps to more than one asset, only the first object encountered by the
/// loading operation is returned. Use or one of its overloads
/// to load multiple assets in a single operation.
///
/// See [Loading Addressable Assets](xref:addressables-api-load-asset-async) for more information and examples.
///
/// See [Operations](xref:addressables-async-operation-handling) for information on handling the asynchronous operations used
/// to load Addressable assets.
///
/// The type of the asset.
/// The key of the location of the asset.
/// Returns the load operation.
public static AsyncOperationHandle LoadAssetAsync(object key)
{
return m_Addressables.LoadAssetAsync(key);
}
///
/// Loads the resource locations specified by the keys.
/// The method will always return success, with a valid IList of results. If nothing matches keys, IList will be empty
///
/// The set of keys to use.
/// The mode for merging the results of the found locations.
/// A type restriction for the lookup. Only locations of the provided type (or derived type) will be returned.
/// The operation handle for the request.
//[Obsolete("We have added Async to the name of all asynchronous methods (UnityUpgradable) -> LoadResourceLocationsAsync(*)", true)]
[Obsolete]
public static AsyncOperationHandle> LoadResourceLocations(IList