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 { /// /// Interface for objects that support post construction initialization via an id and byte array. /// public interface IInitializableObject { /// /// Initialize a constructed object. /// /// The id of the object. /// Serialized data for the object. /// The result of the initialization. bool Initialize(string id, string data); /// /// Async operation for initializing a constructed object. /// /// The current instance of Resource Manager. /// The id of the object. /// Serialized data for the object. /// Async operation AsyncOperationHandle InitializeAsync(ResourceManager rm, string id, string data); } /// /// Interface for objects that can create object initialization data. /// public interface IObjectInitializationDataProvider { /// /// The name used in the GUI for this provider /// string Name { get; } /// /// Construct initialization data for runtime. /// /// Init data that will be deserialized at runtime. ObjectInitializationData CreateObjectInitializationData(); } /// /// Allocation strategy for managing heap allocations /// public interface IAllocationStrategy { /// /// Create a new object of type t. /// /// The type to return. /// The hash code of the type. /// The new object. object New(Type type, int typeHash); /// /// Release an object. /// /// The hash code of the type. /// The object to release. void Release(int typeHash, object obj); } /// /// Default allocator that relies in garbace collection /// public class DefaultAllocationStrategy : IAllocationStrategy { /// public object New(Type type, int typeHash) { return Activator.CreateInstance(type); } /// public void Release(int typeHash, object obj) { } } /// /// Allocation strategy that uses internal pools of objects to avoid allocations that can trigger GC calls. /// public class LRUCacheAllocationStrategy : IAllocationStrategy { int m_poolMaxSize; int m_poolInitialCapacity; int m_poolCacheMaxSize; List> m_poolCache = new List>(); Dictionary> m_cache = new Dictionary>(); /// /// Create a new LRUAllocationStrategy objct. /// /// The max size of each pool. /// The initial capacity to create each pool list with. /// The max size of the internal pool cache. /// The initial number of pools to create. 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(m_poolInitialCapacity)); } List GetPool() { int count = m_poolCache.Count; if (count == 0) return new List(m_poolInitialCapacity); var pool = m_poolCache[count - 1]; m_poolCache.RemoveAt(count - 1); return pool; } void ReleasePool(List pool) { if (m_poolCache.Count < m_poolCacheMaxSize) m_poolCache.Add(pool); } /// public object New(Type type, int typeHash) { List 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); } /// public void Release(int typeHash, object obj) { List pool; if (!m_cache.TryGetValue(typeHash, out pool)) m_cache.Add(typeHash, pool = GetPool()); if (pool.Count < m_poolMaxSize) pool.Add(obj); } } /// /// Attribute for restricting which types can be assigned to a SerializedType /// public class SerializedTypeRestrictionAttribute : Attribute { /// /// The type to restrict a serialized type to. /// public Type type; } /// /// Cache for nodes of LinkedLists. This can be used to eliminate GC allocations. /// /// The type of node. public class LinkedListNodeCache { int m_NodesCreated = 0; LinkedList m_NodeCache; /// /// Creates or returns a LinkedListNode of the requested type and set the value. /// /// The value to set to returned node to. /// A LinkedListNode with the value set to val. public LinkedListNode 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(val); } /// /// Release the linked list node for later use. /// /// public void Release(LinkedListNode node) { if (m_NodeCache == null) m_NodeCache = new LinkedList(); 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(); while (value < m_NodeCache.Count) m_NodeCache.RemoveLast(); while (value > m_NodeCache.Count) m_NodeCache.AddLast(new LinkedListNode(default)); } } } internal static class GlobalLinkedListNodeCache { static LinkedListNodeCache m_globalCache; public static bool CacheExists => m_globalCache != null; public static void SetCacheSize(int length) { if (m_globalCache == null) m_globalCache = new LinkedListNodeCache(); m_globalCache.CachedNodeCount = length; } public static LinkedListNode Acquire(T val) { if (m_globalCache == null) m_globalCache = new LinkedListNodeCache(); return m_globalCache.Acquire(val); } public static void Release(LinkedListNode node) { if (m_globalCache == null) m_globalCache = new LinkedListNodeCache(); m_globalCache.Release(node); } } /// /// Wrapper for serializing types for runtime. /// [Serializable] public struct SerializedType { [FormerlySerializedAs("m_assemblyName")] [SerializeField] string m_AssemblyName; /// /// The assembly name of the type. /// public string AssemblyName { get { return m_AssemblyName; } } [FormerlySerializedAs("m_className")] [SerializeField] string m_ClassName; /// /// The name of the type. /// public string ClassName { get { return m_ClassName; } } Type m_CachedType; /// /// Converts information about the serialized type to a formatted string. /// /// Returns information about the serialized type. public override string ToString() { return Value == null ? "" : Value.Name; } /// /// Get and set the serialized type. /// 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; } } } /// /// Used for multi-object editing. Indicates whether or not property value was changed. /// public bool ValueChanged { get; set; } } /// /// Contains data used to construct and initialize objects at runtime. /// [Serializable] public struct ObjectInitializationData { #pragma warning disable 0649 [FormerlySerializedAs("m_id")] [SerializeField] string m_Id; /// /// The object id. /// public string Id { get { return m_Id; } } [FormerlySerializedAs("m_objectType")] [SerializeField] SerializedType m_ObjectType; /// /// The object type that will be created. /// public SerializedType ObjectType { get { return m_ObjectType; } } [FormerlySerializedAs("m_data")] [SerializeField] string m_Data; /// /// 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. /// public string Data { get { return m_Data; } } #pragma warning restore 0649 #if ENABLE_BINARY_CATALOG internal class Serializer : BinaryStorageBuffer.ISerializationAdapter { struct Data { public uint id; public uint type; public uint data; } public IEnumerable Dependencies => null; public object Deserialize(BinaryStorageBuffer.Reader reader, Type t, uint offset) { var d = reader.ReadValue(offset); return new ObjectInitializationData { m_Id = reader.ReadString(d.id), m_ObjectType = new SerializedType { Value = reader.ReadObject(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 /// /// Converts information about the initialization data to a formatted string. /// /// Returns information about the initialization data. public override string ToString() { return string.Format("ObjectInitializationData: id={0}, type={1}", m_Id, m_ObjectType); } /// /// Create an instance of the defined object. Initialize will be called on it with the id and data if it implements the IInitializableObject interface. /// /// The instance type. /// Optional id to assign to the created object. This only applies to objects that inherit from IInitializableObject. /// Constructed object. This object will already be initialized with its serialized data and id. public TObject CreateInstance(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); } } /// /// Create an instance of the defined object. This will get the AsyncOperationHandle for the async Initialization operation if the object implements the IInitializableObject interface. /// /// The current instance of Resource Manager /// Optional id to assign to the created object. This only applies to objects that inherit from IInitializableObject. /// AsyncOperationHandle for the async initialization operation if the defined type implements IInitializableObject, otherwise returns a default AsyncOperationHandle. 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; /// /// Construct a serialized data for the object. /// /// The type of object to create. /// The object id. /// The serializable object that will be saved into the Data string via the JSONUtility.ToJson method. /// Contains data used to construct and initialize an object at runtime. 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()} }; } /// /// Construct a serialized data for the object. /// /// The type of object to create. /// The ID for the object /// The serializable object that will be saved into the Data string via the JSONUtility.ToJson method. /// Contains data used to construct and initialize an object at runtime. public static ObjectInitializationData CreateSerializedInitializationData(string id = null, object dataObject = null) { return CreateSerializedInitializationData(typeof(T), id, dataObject); } /// /// Get the set of runtime types need to deserialized this object. This is used to ensure that types are not stripped from player builds. /// /// public Type[] GetRuntimeTypes() { return m_RuntimeTypes; } #endif } /// /// Resource Manager Config utility class. /// public static class ResourceManagerConfig { /// /// Extracts main and subobject keys if properly formatted /// /// The key as an object. /// The key of the main asset. This will be set to null if a sub key is not found. /// The key of the sub object. This will be set to null if not found. /// Returns true if properly formatted keys are extracted. 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; } /// /// Check to see if a path is remote or not. /// /// The path to check. /// Returns true if path is remote. public static bool IsPathRemote(string path) { return path != null && path.StartsWith("http", StringComparison.Ordinal); } /// /// Strips the query parameters of an url. /// /// The path to check. /// Returns the path without query parameters. public static string StripQueryParameters(string path) { if (path != null) { var idx = path.IndexOf('?'); if (idx >= 0) return path.Substring(0, idx); } return path; } /// /// Check if path should use WebRequest. A path should use WebRequest for remote paths and platforms that require WebRequest to load locally. /// /// The path to check. /// Returns true if path should use WebRequest. public static bool ShouldPathUseWebRequest(string path) { if (PlatformCanLoadLocallyFromUrlPath() && File.Exists(path)) return false; return path != null && path.Contains("://"); } /// /// Checks if the current platform can use urls for load loads. /// /// True if the current platform can use urls for local loads, false otherwise. 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 platformsThatUseUrlForLocalLoads = new List() { RuntimePlatform.Android }; return platformsThatUseUrlForLocalLoads.Contains((Application.platform)); } /// /// Used to create an operation result that has multiple items. /// /// The type of the result. /// The result objects. /// Returns Array object with result items. 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; } /// /// Used to create an operation result that has multiple items. /// /// The type of the result. /// The result objects. /// Returns result Array as TObject. public static TObject CreateArrayResult(Object[] allAssets) where TObject : class { return CreateArrayResult(typeof(TObject), allAssets) as TObject; } /// /// Used to create an operation result that has multiple items. /// /// The type of the result objects. /// The result objects. /// An IList of the resulting objects. 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; } /// /// Used to create an operation result that has multiple items. /// /// The type of the result. /// The result objects. /// An IList of the resulting objects converted to TObject. public static TObject CreateListResult(Object[] allAssets) { return (TObject)CreateListResult(typeof(TObject), allAssets); } /// /// Check if one type is an instance of another type. /// /// Expected base type. /// Expected child type. /// Returns true if T2 is a base type of T1. public static bool IsInstance() { 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 } }