using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Reflection; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.Serialization; namespace UnityEngine.ResourceManagement.Util { /// <summary> /// Interface for objects that support post construction initialization via an id and byte array. /// </summary> public interface IInitializableObject { /// <summary> /// Initialize a constructed object. /// </summary> /// <param name="id">The id of the object.</param> /// <param name="data">Serialized data for the object.</param> /// <returns>The result of the initialization.</returns> bool Initialize(string id, string data); /// <summary> /// Async operation for initializing a constructed object. /// </summary> /// <param name="rm">The current instance of Resource Manager.</param> /// <param name="id">The id of the object.</param> /// <param name="data">Serialized data for the object.</param> /// <returns>Async operation</returns> AsyncOperationHandle<bool> InitializeAsync(ResourceManager rm, string id, string data); } /// <summary> /// Interface for objects that can create object initialization data. /// </summary> public interface IObjectInitializationDataProvider { /// <summary> /// The name used in the GUI for this provider /// </summary> string Name { get; } /// <summary> /// Construct initialization data for runtime. /// </summary> /// <returns>Init data that will be deserialized at runtime.</returns> ObjectInitializationData CreateObjectInitializationData(); } /// <summary> /// Allocation strategy for managing heap allocations /// </summary> public interface IAllocationStrategy { /// <summary> /// Create a new object of type t. /// </summary> /// <param name="type">The type to return.</param> /// <param name="typeHash">The hash code of the type.</param> /// <returns>The new object.</returns> object New(Type type, int typeHash); /// <summary> /// Release an object. /// </summary> /// <param name="typeHash">The hash code of the type.</param> /// <param name="obj">The object to release.</param> void Release(int typeHash, object obj); } /// <summary> /// Default allocator that relies in garbace collection /// </summary> public class DefaultAllocationStrategy : IAllocationStrategy { /// <inheritdoc/> public object New(Type type, int typeHash) { return Activator.CreateInstance(type); } /// <inheritdoc/> public void Release(int typeHash, object obj) { } } /// <summary> /// Allocation strategy that uses internal pools of objects to avoid allocations that can trigger GC calls. /// </summary> public class LRUCacheAllocationStrategy : IAllocationStrategy { int m_poolMaxSize; int m_poolInitialCapacity; int m_poolCacheMaxSize; List<List<object>> m_poolCache = new List<List<object>>(); Dictionary<int, List<object>> m_cache = new Dictionary<int, List<object>>(); /// <summary> /// Create a new LRUAllocationStrategy objct. /// </summary> /// <param name="poolMaxSize">The max size of each pool.</param> /// <param name="poolCapacity">The initial capacity to create each pool list with.</param> /// <param name="poolCacheMaxSize">The max size of the internal pool cache.</param> /// <param name="initialPoolCacheCapacity">The initial number of pools to create.</param> public LRUCacheAllocationStrategy(int poolMaxSize, int poolCapacity, int poolCacheMaxSize, int initialPoolCacheCapacity) { m_poolMaxSize = poolMaxSize; m_poolInitialCapacity = poolCapacity; m_poolCacheMaxSize = poolCacheMaxSize; for (int i = 0; i < initialPoolCacheCapacity; i++) m_poolCache.Add(new List<object>(m_poolInitialCapacity)); } List<object> GetPool() { int count = m_poolCache.Count; if (count == 0) return new List<object>(m_poolInitialCapacity); var pool = m_poolCache[count - 1]; m_poolCache.RemoveAt(count - 1); return pool; } void ReleasePool(List<object> pool) { if (m_poolCache.Count < m_poolCacheMaxSize) m_poolCache.Add(pool); } /// <inheritdoc/> public object New(Type type, int typeHash) { List<object> pool; if (m_cache.TryGetValue(typeHash, out pool)) { var count = pool.Count; var v = pool[count - 1]; pool.RemoveAt(count - 1); if (count == 1) { m_cache.Remove(typeHash); ReleasePool(pool); } return v; } return Activator.CreateInstance(type); } /// <inheritdoc/> public void Release(int typeHash, object obj) { List<object> pool; if (!m_cache.TryGetValue(typeHash, out pool)) m_cache.Add(typeHash, pool = GetPool()); if (pool.Count < m_poolMaxSize) pool.Add(obj); } } /// <summary> /// Attribute for restricting which types can be assigned to a SerializedType /// </summary> public class SerializedTypeRestrictionAttribute : Attribute { /// <summary> /// The type to restrict a serialized type to. /// </summary> public Type type; } /// <summary> /// Cache for nodes of LinkedLists. This can be used to eliminate GC allocations. /// </summary> /// <typeparam name="T">The type of node.</typeparam> public class LinkedListNodeCache<T> { int m_NodesCreated = 0; LinkedList<T> m_NodeCache; /// <summary> /// Creates or returns a LinkedListNode of the requested type and set the value. /// </summary> /// <param name="val">The value to set to returned node to.</param> /// <returns>A LinkedListNode with the value set to val.</returns> public LinkedListNode<T> Acquire(T val) { if (m_NodeCache != null) { var n = m_NodeCache.First; if (n != null) { m_NodeCache.RemoveFirst(); n.Value = val; return n; } } m_NodesCreated++; return new LinkedListNode<T>(val); } /// <summary> /// Release the linked list node for later use. /// </summary> /// <param name="node"></param> public void Release(LinkedListNode<T> node) { if (m_NodeCache == null) m_NodeCache = new LinkedList<T>(); node.Value = default(T); m_NodeCache.AddLast(node); } internal int CreatedNodeCount { get { return m_NodesCreated; } } internal int CachedNodeCount { get { return m_NodeCache == null ? 0 : m_NodeCache.Count; } set { if (m_NodeCache == null) m_NodeCache = new LinkedList<T>(); while (value < m_NodeCache.Count) m_NodeCache.RemoveLast(); while (value > m_NodeCache.Count) m_NodeCache.AddLast(new LinkedListNode<T>(default)); } } } internal static class GlobalLinkedListNodeCache<T> { static LinkedListNodeCache<T> m_globalCache; public static bool CacheExists => m_globalCache != null; public static void SetCacheSize(int length) { if (m_globalCache == null) m_globalCache = new LinkedListNodeCache<T>(); m_globalCache.CachedNodeCount = length; } public static LinkedListNode<T> Acquire(T val) { if (m_globalCache == null) m_globalCache = new LinkedListNodeCache<T>(); return m_globalCache.Acquire(val); } public static void Release(LinkedListNode<T> node) { if (m_globalCache == null) m_globalCache = new LinkedListNodeCache<T>(); m_globalCache.Release(node); } } /// <summary> /// Wrapper for serializing types for runtime. /// </summary> [Serializable] public struct SerializedType { [FormerlySerializedAs("m_assemblyName")] [SerializeField] string m_AssemblyName; /// <summary> /// The assembly name of the type. /// </summary> public string AssemblyName { get { return m_AssemblyName; } } [FormerlySerializedAs("m_className")] [SerializeField] string m_ClassName; /// <summary> /// The name of the type. /// </summary> public string ClassName { get { return m_ClassName; } } Type m_CachedType; /// <summary> /// Converts information about the serialized type to a formatted string. /// </summary> /// <returns>Returns information about the serialized type.</returns> public override string ToString() { return Value == null ? "<none>" : Value.Name; } /// <summary> /// Get and set the serialized type. /// </summary> public Type Value { get { try { if (string.IsNullOrEmpty(m_AssemblyName) || string.IsNullOrEmpty(m_ClassName)) return null; if (m_CachedType == null) { var assembly = Assembly.Load(m_AssemblyName); if (assembly != null) m_CachedType = assembly.GetType(m_ClassName); } return m_CachedType; } catch (Exception ex) { //file not found is most likely an editor only type, we can ignore error. if (ex.GetType() != typeof(FileNotFoundException)) Debug.LogException(ex); return null; } } set { if (value != null) { m_AssemblyName = value.Assembly.FullName; m_ClassName = value.FullName; } else { m_AssemblyName = m_ClassName = null; } } } /// <summary> /// Used for multi-object editing. Indicates whether or not property value was changed. /// </summary> public bool ValueChanged { get; set; } } /// <summary> /// Contains data used to construct and initialize objects at runtime. /// </summary> [Serializable] public struct ObjectInitializationData { #pragma warning disable 0649 [FormerlySerializedAs("m_id")] [SerializeField] string m_Id; /// <summary> /// The object id. /// </summary> public string Id { get { return m_Id; } } [FormerlySerializedAs("m_objectType")] [SerializeField] SerializedType m_ObjectType; /// <summary> /// The object type that will be created. /// </summary> public SerializedType ObjectType { get { return m_ObjectType; } } [FormerlySerializedAs("m_data")] [SerializeField] string m_Data; /// <summary> /// String representation of the data that will be passed to the IInitializableObject.Initialize method of the created object. This is usually a JSON string of the serialized data object. /// </summary> public string Data { get { return m_Data; } } #pragma warning restore 0649 #if ENABLE_BINARY_CATALOG internal class Serializer : BinaryStorageBuffer.ISerializationAdapter<ObjectInitializationData> { struct Data { public uint id; public uint type; public uint data; } public IEnumerable<BinaryStorageBuffer.ISerializationAdapter> Dependencies => null; public object Deserialize(BinaryStorageBuffer.Reader reader, Type t, uint offset) { var d = reader.ReadValue<Data>(offset); return new ObjectInitializationData { m_Id = reader.ReadString(d.id), m_ObjectType = new SerializedType { Value = reader.ReadObject<Type>(d.type) }, m_Data = reader.ReadString(d.data) }; } public uint Serialize(BinaryStorageBuffer.Writer writer, object val) { var oid = (ObjectInitializationData)val; var d = new Data { id = writer.WriteString(oid.m_Id), type = writer.WriteObject(oid.ObjectType.Value, false), data = writer.WriteString(oid.m_Data) }; return writer.Write(d); } } #endif /// <summary> /// Converts information about the initialization data to a formatted string. /// </summary> /// <returns>Returns information about the initialization data.</returns> public override string ToString() { return string.Format("ObjectInitializationData: id={0}, type={1}", m_Id, m_ObjectType); } /// <summary> /// Create an instance of the defined object. Initialize will be called on it with the id and data if it implements the IInitializableObject interface. /// </summary> /// <typeparam name="TObject">The instance type.</typeparam> /// <param name="idOverride">Optional id to assign to the created object. This only applies to objects that inherit from IInitializableObject.</param> /// <returns>Constructed object. This object will already be initialized with its serialized data and id.</returns> public TObject CreateInstance<TObject>(string idOverride = null) { try { var objType = m_ObjectType.Value; if (objType == null) return default(TObject); var obj = Activator.CreateInstance(objType, true); var serObj = obj as IInitializableObject; if (serObj != null) { if (!serObj.Initialize(idOverride == null ? m_Id : idOverride, m_Data)) return default(TObject); } return (TObject)obj; } catch (Exception ex) { Debug.LogException(ex); return default(TObject); } } /// <summary> /// Create an instance of the defined object. This will get the AsyncOperationHandle for the async Initialization operation if the object implements the IInitializableObject interface. /// </summary> /// <param name="rm">The current instance of Resource Manager</param> /// <param name="idOverride">Optional id to assign to the created object. This only applies to objects that inherit from IInitializableObject.</param> /// <returns>AsyncOperationHandle for the async initialization operation if the defined type implements IInitializableObject, otherwise returns a default AsyncOperationHandle.</returns> public AsyncOperationHandle GetAsyncInitHandle(ResourceManager rm, string idOverride = null) { try { var objType = m_ObjectType.Value; if (objType == null) return default(AsyncOperationHandle); var obj = Activator.CreateInstance(objType, true); var serObj = obj as IInitializableObject; if (serObj != null) return serObj.InitializeAsync(rm, idOverride == null ? m_Id : idOverride, m_Data); return default(AsyncOperationHandle); } catch (Exception ex) { Debug.LogException(ex); return default(AsyncOperationHandle); } } #if UNITY_EDITOR Type[] m_RuntimeTypes; /// <summary> /// Construct a serialized data for the object. /// </summary> /// <param name="objectType">The type of object to create.</param> /// <param name="id">The object id.</param> /// <param name="dataObject">The serializable object that will be saved into the Data string via the JSONUtility.ToJson method.</param> /// <returns>Contains data used to construct and initialize an object at runtime.</returns> public static ObjectInitializationData CreateSerializedInitializationData(Type objectType, string id = null, object dataObject = null) { return new ObjectInitializationData { m_ObjectType = new SerializedType {Value = objectType}, m_Id = string.IsNullOrEmpty(id) ? objectType.FullName : id, m_Data = dataObject == null ? null : JsonUtility.ToJson(dataObject), m_RuntimeTypes = dataObject == null ? null : new[] {dataObject.GetType()} }; } /// <summary> /// Construct a serialized data for the object. /// </summary> /// <typeparam name="T">The type of object to create.</typeparam> /// <param name="id">The ID for the object</param> /// <param name="dataObject">The serializable object that will be saved into the Data string via the JSONUtility.ToJson method.</param> /// <returns>Contains data used to construct and initialize an object at runtime.</returns> public static ObjectInitializationData CreateSerializedInitializationData<T>(string id = null, object dataObject = null) { return CreateSerializedInitializationData(typeof(T), id, dataObject); } /// <summary> /// Get the set of runtime types need to deserialized this object. This is used to ensure that types are not stripped from player builds. /// </summary> /// <returns></returns> public Type[] GetRuntimeTypes() { return m_RuntimeTypes; } #endif } /// <summary> /// Resource Manager Config utility class. /// </summary> public static class ResourceManagerConfig { /// <summary> /// Extracts main and subobject keys if properly formatted /// </summary> /// <param name="keyObj">The key as an object.</param> /// <param name="mainKey">The key of the main asset. This will be set to null if a sub key is not found.</param> /// <param name="subKey">The key of the sub object. This will be set to null if not found.</param> /// <returns>Returns true if properly formatted keys are extracted.</returns> public static bool ExtractKeyAndSubKey(object keyObj, out string mainKey, out string subKey) { var key = keyObj as string; if (key != null) { var i = key.IndexOf('['); if (i > 0) { var j = key.LastIndexOf(']'); if (j > i) { mainKey = key.Substring(0, i); subKey = key.Substring(i + 1, j - (i + 1)); return true; } } } mainKey = null; subKey = null; return false; } /// <summary> /// Check to see if a path is remote or not. /// </summary> /// <param name="path">The path to check.</param> /// <returns>Returns true if path is remote.</returns> public static bool IsPathRemote(string path) { return path != null && path.StartsWith("http", StringComparison.Ordinal); } /// <summary> /// Strips the query parameters of an url. /// </summary> /// <param name="path">The path to check.</param> /// <returns>Returns the path without query parameters.</returns> public static string StripQueryParameters(string path) { if (path != null) { var idx = path.IndexOf('?'); if (idx >= 0) return path.Substring(0, idx); } return path; } /// <summary> /// Check if path should use WebRequest. A path should use WebRequest for remote paths and platforms that require WebRequest to load locally. /// </summary> /// <param name="path">The path to check.</param> /// <returns>Returns true if path should use WebRequest.</returns> public static bool ShouldPathUseWebRequest(string path) { if (PlatformCanLoadLocallyFromUrlPath() && File.Exists(path)) return false; return path != null && path.Contains("://"); } /// <summary> /// Checks if the current platform can use urls for load loads. /// </summary> /// <returns>True if the current platform can use urls for local loads, false otherwise.</returns> private static bool PlatformCanLoadLocallyFromUrlPath() { //For something so simple, this is pretty over engineered. But, if more platforms come up that do this, it'll be easy to account for them. //Just add runtime platforms to this list that do the same thing Android does. List<RuntimePlatform> platformsThatUseUrlForLocalLoads = new List<RuntimePlatform>() { RuntimePlatform.Android }; return platformsThatUseUrlForLocalLoads.Contains((Application.platform)); } /// <summary> /// Used to create an operation result that has multiple items. /// </summary> /// <param name="type">The type of the result.</param> /// <param name="allAssets">The result objects.</param> /// <returns>Returns Array object with result items.</returns> public static Array CreateArrayResult(Type type, Object[] allAssets) { var elementType = type.GetElementType(); if (elementType == null) return null; int length = 0; foreach (var asset in allAssets) { if (elementType.IsAssignableFrom(asset.GetType())) length++; } var array = Array.CreateInstance(elementType, length); int index = 0; foreach (var asset in allAssets) { if (elementType.IsAssignableFrom(asset.GetType())) array.SetValue(asset, index++); } return array; } /// <summary> /// Used to create an operation result that has multiple items. /// </summary> /// <typeparam name="TObject">The type of the result.</typeparam> /// <param name="allAssets">The result objects.</param> /// <returns>Returns result Array as TObject.</returns> public static TObject CreateArrayResult<TObject>(Object[] allAssets) where TObject : class { return CreateArrayResult(typeof(TObject), allAssets) as TObject; } /// <summary> /// Used to create an operation result that has multiple items. /// </summary> /// <param name="type">The type of the result objects.</param> /// <param name="allAssets">The result objects.</param> /// <returns>An IList of the resulting objects.</returns> public static IList CreateListResult(Type type, Object[] allAssets) { var genArgs = type.GetGenericArguments(); var listType = typeof(List<>).MakeGenericType(genArgs); var list = Activator.CreateInstance(listType) as IList; var elementType = genArgs[0]; if (list == null) return null; foreach (var a in allAssets) { if (elementType.IsAssignableFrom(a.GetType())) list.Add(a); } return list; } /// <summary> /// Used to create an operation result that has multiple items. /// </summary> /// <typeparam name="TObject">The type of the result.</typeparam> /// <param name="allAssets">The result objects.</param> /// <returns>An IList of the resulting objects converted to TObject.</returns> public static TObject CreateListResult<TObject>(Object[] allAssets) { return (TObject)CreateListResult(typeof(TObject), allAssets); } /// <summary> /// Check if one type is an instance of another type. /// </summary> /// <typeparam name="T1">Expected base type.</typeparam> /// <typeparam name="T2">Expected child type.</typeparam> /// <returns>Returns true if T2 is a base type of T1.</returns> public static bool IsInstance<T1, T2>() { var tA = typeof(T1); var tB = typeof(T2); #if !UNITY_EDITOR && UNITY_WSA_10_0 && ENABLE_DOTNET return tB.GetTypeInfo().IsAssignableFrom(tA.GetTypeInfo()); #else return tB.IsAssignableFrom(tA); #endif } } [System.Flags] internal enum BundleSource { None = 0, Local = 1, Cache = 2, Download = 4 } }