initial commit

This commit is contained in:
Jo 2025-01-07 02:06:59 +01:00
parent 6715289efe
commit 788c3389af
37645 changed files with 2526849 additions and 80 deletions

View file

@ -0,0 +1,855 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
namespace UnityEngine.ResourceManagement.Util
{
internal class BinaryStorageBuffer
{
class BuiltinTypesSerializer :
ISerializationAdapter<int>,
ISerializationAdapter<bool>,
ISerializationAdapter<long>,
ISerializationAdapter<string>,
ISerializationAdapter<Hash128>
{
public IEnumerable<ISerializationAdapter> Dependencies => null;
struct ObjectToStringRemap
{
public uint stringId;
public char separator;
}
public object Deserialize(Reader reader, Type t, uint offset)
{
if (offset == uint.MaxValue)
return null;
if (t == typeof(int)) return reader.ReadValue<int>(offset);
if (t == typeof(bool)) return reader.ReadValue<bool>(offset);
if (t == typeof(long)) return reader.ReadValue<long>(offset);
if (t == typeof(Hash128)) return reader.ReadValue<Hash128>(offset);
if (t == typeof(string))
{
var remap = reader.ReadValue<ObjectToStringRemap>(offset);
return reader.ReadString(remap.stringId, remap.separator, false);
}
return null;
}
char FindBestSeparator(string str, params char[] seps)
{
int bestCount = 0;
char bestSep = (char)0;
foreach (var s in seps)
{
var sepCount = str.Count(c => c == s);
if (sepCount > bestCount)
{
bestCount = sepCount;
bestSep = s;
}
}
var parts = str.Split(bestSep);
int validParts = 0;
foreach (var p in parts)
if (p.Length > 8)
validParts++;
if (validParts < 2)
bestSep = (char)0;
return bestSep;
}
public uint Serialize(Writer writer, object val)
{
if (val == null)
return uint.MaxValue;
var t = val.GetType();
if (t == typeof(int)) return writer.Write((int)val);
if (t == typeof(bool)) return writer.Write((bool)val);
if (t == typeof(long)) return writer.Write((long)val);
if (t == typeof(Hash128)) return writer.Write((Hash128)val);
if (t == typeof(string))
{
var str = val as string;
var bestSep = FindBestSeparator(str, '/', '.', '-', '_', '\\', ',');
return writer.Write(new ObjectToStringRemap { stringId = writer.WriteString((string)val), separator = bestSep });
}
return uint.MaxValue;
}
}
class TypeSerializer :
ISerializationAdapter<Type>
{
struct Data
{
public uint assemblyId;
public uint classId;
}
public IEnumerable<BinaryStorageBuffer.ISerializationAdapter> Dependencies => null;
public object Deserialize(Reader reader, Type type, uint offset)
{
try
{
var d = reader.ReadValue<Data>(offset);
var assemblyName = reader.ReadString(d.assemblyId, '.');
var className = reader.ReadString(d.classId, '.');
var assembly = Assembly.Load(assemblyName);
return assembly == null ? null : assembly.GetType(className);
}
catch (Exception e)
{
Debug.LogException(e);
return null;
}
}
public uint Serialize(Writer writer, object val)
{
if (val == null)
return uint.MaxValue;
var t = val as Type;
return writer.Write(new Data
{
assemblyId = writer.WriteString(t.Assembly.FullName, '.'),
classId = writer.WriteString(t.FullName, '.')
});
}
}
private unsafe static void ComputeHash(void* pData, ulong size, Hash128* hash)
{
if (pData == null || size == 0)
{
*hash = default;
return;
}
HashUnsafeUtilities.ComputeHash128(pData, size, hash);
}
static void AddSerializationAdapter(Dictionary<Type, ISerializationAdapter> serializationAdapters, ISerializationAdapter adapter, bool forceOverride = false)
{
bool added = false;
foreach (var i in adapter.GetType().GetInterfaces())
{
if (i.IsGenericType && typeof(ISerializationAdapter).IsAssignableFrom(i))
{
var aType = i.GenericTypeArguments[0];
if (serializationAdapters.ContainsKey(aType))
{
if (forceOverride)
{
var prevAdapter = serializationAdapters[aType];
serializationAdapters.Remove(aType);
// serializationAdapters.Add(aType, adapter);
serializationAdapters[aType] = adapter;
added = true;
Debug.Log($"Replacing adapter for type {aType}: {prevAdapter} -> {adapter}");
}
else
{
Debug.Log($"Failed to register adapter for type {aType}: {adapter}, {serializationAdapters[aType]} is already registered.");
}
}
else
{
serializationAdapters[aType] = adapter;
added = true;
}
}
}
if (added)
{
var deps = adapter.Dependencies;
if (deps != null)
foreach (var d in deps)
AddSerializationAdapter(serializationAdapters, d);
}
}
static bool GetSerializationAdapter(Dictionary<Type, ISerializationAdapter> serializationAdapters, Type t, out ISerializationAdapter adapter)
{
if (!serializationAdapters.TryGetValue(t, out adapter))
{
foreach (var k in serializationAdapters)
if (k.Key.IsAssignableFrom(t))
return (adapter = k.Value) != null;
Debug.LogError($"Unable to find serialization adapter for type {t}.");
}
return adapter != null;
}
const uint kUnicodeStringFlag = 0x80000000;
const uint kDynamicStringFlag = 0x40000000;
const uint kClearFlagsMask = 0x3fffffff;
struct DynamicString
{
public uint stringId;
public uint nextId;
}
struct ObjectTypeData
{
public uint typeId;
public uint objectId;
}
public interface ISerializationAdapter
{
IEnumerable<ISerializationAdapter> Dependencies { get; }
uint Serialize(Writer writer, object val);
object Deserialize(Reader reader, Type t, uint offset);
}
public interface ISerializationAdapter<T> : ISerializationAdapter
{
}
public unsafe class Reader
{
byte[] m_Buffer;
Dictionary<Type, ISerializationAdapter> m_Adapters;
LRUCache<uint, object> m_Cache;
StringBuilder stringBuilder;
private void Init(byte[] data, int maxCachedObjects, params ISerializationAdapter[] adapters)
{
m_Buffer = data;
stringBuilder = new StringBuilder(1024);
m_Cache = new LRUCache<uint, object>(maxCachedObjects);
m_Adapters = new Dictionary<Type, ISerializationAdapter>();
foreach (var a in adapters)
BinaryStorageBuffer.AddSerializationAdapter(m_Adapters, a);
BinaryStorageBuffer.AddSerializationAdapter(m_Adapters, new TypeSerializer());
BinaryStorageBuffer.AddSerializationAdapter(m_Adapters, new BuiltinTypesSerializer());
}
public void AddSerializationAdapter(ISerializationAdapter a)
{
BinaryStorageBuffer.AddSerializationAdapter(m_Adapters, a);
}
public Reader(byte[] data, int maxCachedObjects = 1024, params ISerializationAdapter[] adapters)
{
Init(data, maxCachedObjects, adapters);
}
public Reader(Stream inputStream, uint bufferSize, int maxCachedObjects, params ISerializationAdapter[] adapters)
{
var data = new byte[bufferSize == 0 ? inputStream.Length : bufferSize];
inputStream.Read(data, 0, data.Length);
Init(data, maxCachedObjects, adapters);
}
bool TryGetCachedValue<T>(uint offset, out T val)
{
if(m_Cache.TryGet(offset, out var obj))
{
val = (T)obj;
return true;
}
val = default;
return false;
}
public object[] ReadObjectArray(uint id, bool cacheValues = true)
{
if (id == uint.MaxValue)
return null;
var ids = ReadValueArray<uint>(id, cacheValues);
var objs = new object[ids.Length];
for (int i = 0; i < ids.Length; i++)
objs[i] = ReadObject(ids[i], cacheValues);
return objs;
}
public object[] ReadObjectArray(Type t, uint id, bool cacheValues = true)
{
if (id == uint.MaxValue)
return null;
var ids = ReadValueArray<uint>(id, cacheValues);
var objs = new object[ids.Length];
for (int i = 0; i < ids.Length; i++)
objs[i] = ReadObject(t, ids[i], cacheValues);
return objs;
}
public T[] ReadObjectArray<T>(uint id, bool cacheValues = true)
{
if (id == uint.MaxValue)
return null;
var ids = ReadValueArray<uint>(id, cacheValues);
var objs = new T[ids.Length];
for (int i = 0; i < ids.Length; i++)
objs[i] = ReadObject<T>(ids[i], cacheValues);
return objs;
}
public object ReadObject(uint id, bool cacheValue = true)
{
if (id == uint.MaxValue)
return null;
var td = ReadValue<ObjectTypeData>(id);
var type = ReadObject<Type>(td.typeId);
return ReadObject(type, td.objectId, cacheValue);
}
public T ReadObject<T>(uint offset, bool cacheValue = true) => (T)ReadObject(typeof(T), offset, cacheValue);
public object ReadObject(Type t, uint id, bool cacheValue = true)
{
if (id == uint.MaxValue)
return null;
if (TryGetCachedValue<object>(id, out var val))
return val;
if (!GetSerializationAdapter(m_Adapters, t, out var adapter))
return null;
object res = default;
try
{
res = adapter.Deserialize(this, t, id);
}
catch (Exception e)
{
Debug.LogException(e);
return null;
}
if (cacheValue && res != null)
m_Cache.TryAdd(id, res);
return res;
}
public T[] ReadValueArray<T>(uint id, bool cacheValue = true) where T : unmanaged
{
if (id == uint.MaxValue)
return null;
if (id - sizeof(uint) >= m_Buffer.Length)
throw new Exception($"Data offset {id} is out of bounds of buffer with length of {m_Buffer.Length}.");
fixed (byte* pData = &m_Buffer[id - sizeof(uint)])
{
if (TryGetCachedValue<T[]>(id, out var vals))
return vals;
uint size = 0;
UnsafeUtility.MemCpy(&size, pData, sizeof(uint));
if((id + size) > m_Buffer.Length)
throw new Exception($"Data size {size} is out of bounds of buffer with length of {m_Buffer.Length}.");
var elCount = size / sizeof(T);
var valsT = new T[elCount];
fixed (T* pVals = valsT)
UnsafeUtility.MemCpy(pVals, &pData[sizeof(uint)], size);
if(cacheValue)
m_Cache.TryAdd(id, valsT);
return valsT;
}
}
public T ReadValue<T>(uint id) where T : unmanaged
{
if (id == uint.MaxValue)
return default;
if (id >= m_Buffer.Length)
throw new Exception($"Data offset {id} is out of bounds of buffer with length of {m_Buffer.Length}.");
fixed (byte *pData = m_Buffer)
return *(T*)&pData[id];
}
public string ReadString(uint id, char sep = (char)0, bool cacheValue = true)
{
if (id == uint.MaxValue)
return null;
if (sep == (char)0)
return ReadAutoEncodedString(id, cacheValue);
return ReadDynamicString(id, sep, cacheValue);
}
string ReadStringInternal(uint offset, Encoding enc, bool cacheValue = true)
{
if (offset - sizeof(uint) >= m_Buffer.Length)
throw new Exception($"Data offset {offset} is out of bounds of buffer with length of {m_Buffer.Length}.");
if (TryGetCachedValue<string>(offset, out var val))
return val;
fixed (byte* pData = m_Buffer)
{
var strDataLength = *(uint*)&pData[offset - sizeof(uint)];
if (offset + strDataLength > m_Buffer.Length)
throw new Exception($"Data offset {offset}, len {strDataLength} is out of bounds of buffer with length of {m_Buffer.Length}.");
var valStr = enc.GetString(&pData[offset], (int)strDataLength);
if(cacheValue)
m_Cache.TryAdd(offset, valStr);
return valStr;
}
}
string ReadAutoEncodedString(uint id, bool cacheValue)
{
if ((id & kUnicodeStringFlag) == kUnicodeStringFlag)
return ReadStringInternal((uint)(id & kClearFlagsMask), Encoding.Unicode, cacheValue);
return ReadStringInternal(id, Encoding.ASCII, cacheValue);
}
string ReadDynamicString(uint id, char sep, bool cacheValue)
{
if ((id & kDynamicStringFlag) == kDynamicStringFlag)
{
if (!TryGetCachedValue<string>(id, out var str))
{
var ds = ReadValue<DynamicString>((uint)(id & kClearFlagsMask));
stringBuilder.Append(ReadAutoEncodedString(ds.stringId, cacheValue));
while (ds.nextId != uint.MaxValue)
{
ds = ReadValue<DynamicString>(ds.nextId);
stringBuilder.Append(sep);
stringBuilder.Append(ReadAutoEncodedString(ds.stringId, cacheValue));
}
str = stringBuilder.ToString();
stringBuilder.Clear();
if (cacheValue)
m_Cache.TryAdd(id, str);
}
return str;
}
else
{
return ReadAutoEncodedString(id, cacheValue);
}
}
}
public unsafe class Writer
{
class Chunk
{
public uint position;
public byte[] data;
}
uint totalBytes;
uint defaulChunkSize;
List<Chunk> chunks;
Dictionary<Hash128, uint> existingValues;
Dictionary<Type, ISerializationAdapter> serializationAdapters;
public uint Length => totalBytes;
public Writer(int chunkSize = 1024*1024, params ISerializationAdapter[] adapters)
{
defaulChunkSize = (uint)(chunkSize > 0 ? chunkSize : 1024 * 1024);
existingValues = new Dictionary<Hash128, uint>();
chunks = new List<Chunk>(10);
chunks.Add(new Chunk { position = 0 });
serializationAdapters = new Dictionary<Type, ISerializationAdapter>();
AddSerializationAdapter(serializationAdapters, new TypeSerializer());
AddSerializationAdapter(serializationAdapters, new BuiltinTypesSerializer());
foreach (var a in adapters)
AddSerializationAdapter(serializationAdapters, a, true);
}
Chunk FindChunkWithSpace(uint length)
{
var chunk = chunks[chunks.Count - 1];
if (chunk.data == null)
chunk.data = new byte[length > defaulChunkSize ? length : defaulChunkSize];
if (length > chunk.data.Length - chunk.position)
{
chunk = new Chunk { position = 0, data = new byte[length > defaulChunkSize ? length : defaulChunkSize] };
chunks.Add(chunk);
}
return chunk;
}
uint WriteInternal(void* pData, uint dataSize, bool prefixSize)
{
Hash128 hash;
ComputeHash(pData, (ulong)dataSize, &hash);
if (existingValues.TryGetValue(hash, out var existingOffset))
return existingOffset;
var addedBytes = prefixSize ? dataSize + sizeof(uint) : dataSize;
var chunk = FindChunkWithSpace(addedBytes);
fixed (byte* pChunkData = &chunk.data[chunk.position])
{
var id = totalBytes;
if (prefixSize)
{
UnsafeUtility.MemCpy(pChunkData, &dataSize, sizeof(uint));
if(dataSize > 0)
UnsafeUtility.MemCpy(&pChunkData[sizeof(uint)], pData, dataSize);
id += sizeof(uint);
}
else
{
if (dataSize == 0)
return uint.MaxValue;
UnsafeUtility.MemCpy(pChunkData, pData, dataSize);
}
totalBytes += addedBytes;
chunk.position += addedBytes;
existingValues[hash] = id;
return id;
}
}
uint ReserveInternal(uint dataSize, bool prefixSize)
{
//reserved data cannot reuse previously hashed values, but it can be reused for future writes
var addedBytes = prefixSize ? dataSize + sizeof(uint) : dataSize;
var chunk = FindChunkWithSpace(addedBytes);
totalBytes += addedBytes;
chunk.position += addedBytes;
return totalBytes - dataSize;
}
void WriteInternal(uint id, void* pData, uint dataSize, bool prefixSize)
{
//reserved data cannot reuse previously hashed values, but it can be reused for future writes
var addedBytes = prefixSize ? dataSize + sizeof(uint) : dataSize;
Hash128 hash;
ComputeHash(pData, (ulong)dataSize, &hash);
existingValues[hash] = id;
var chunkOffset = id;
foreach (var c in chunks)
{
if (chunkOffset < c.position)
{
fixed (byte* pChunkData = c.data)
{
if (prefixSize)
UnsafeUtility.MemCpy(&pChunkData[chunkOffset - sizeof(uint)], &dataSize, sizeof(uint));
UnsafeUtility.MemCpy(&pChunkData[chunkOffset], pData, dataSize);
return;
}
}
chunkOffset -= c.position;
}
}
public uint Reserve<T>() where T : unmanaged => ReserveInternal((uint)sizeof(T), false);
public uint Write<T>(in T val) where T : unmanaged
{
fixed (T* pData = &val)
return WriteInternal(pData, (uint)sizeof(T), false);
}
public uint Write<T>(T val) where T : unmanaged
{
return WriteInternal(&val, (uint)sizeof(T), false);
}
public uint Write<T>(uint offset, in T val) where T : unmanaged
{
fixed (T* pData = &val)
WriteInternal(offset, pData, (uint)sizeof(T), false);
return offset;
}
public uint Write<T>(uint offset, T val) where T : unmanaged
{
WriteInternal(offset, &val, (uint)sizeof(T), false);
return offset;
}
public uint Reserve<T>(uint count) where T : unmanaged => ReserveInternal((uint)sizeof(T) * count, true);
public uint Write<T>(T[] values, bool hashElements = true) where T : unmanaged
{
fixed (T* pData = values)
{
uint size = (uint)(values.Length * sizeof(T));
Hash128 hash;
ComputeHash(pData, (ulong)size, &hash);
if (existingValues.TryGetValue(hash, out var existingOffset))
return existingOffset;
var chunk = FindChunkWithSpace(size + sizeof(uint));
fixed (byte* pChunkData = &chunk.data[chunk.position])
{
var id = totalBytes + sizeof(uint);
UnsafeUtility.MemCpy(pChunkData, &size, sizeof(uint));
UnsafeUtility.MemCpy(&pChunkData[sizeof(uint)], pData, size);
var addedBytes = size + sizeof(uint);
totalBytes += addedBytes;
chunk.position += addedBytes;
existingValues[hash] = id;
if (hashElements && sizeof(T) > sizeof(uint))
{
for (int i = 0; i < values.Length; i++)
{
hash = default;
ComputeHash(&pData[i], (ulong)sizeof(T), &hash);
existingValues[hash] = id + (uint)(i * sizeof(T));
}
}
return id;
}
}
}
public uint Write<T>(uint offset, T[] values, bool hashElements = true) where T : unmanaged
{
var dataSize = (uint)(values.Length * sizeof(T));
var chunkOffset = offset;
fixed (T* pValues = values)
{
foreach (var c in chunks)
{
if (chunkOffset < c.position)
{
fixed (byte* pChunkData = c.data)
{
UnsafeUtility.MemCpy(&pChunkData[chunkOffset - sizeof(uint)], &dataSize, sizeof(uint));
UnsafeUtility.MemCpy(&pChunkData[chunkOffset], pValues, dataSize);
if (hashElements && sizeof(T) > sizeof(uint))
{
for (int i = 0; i < values.Length; i++)
{
Hash128 hash;
var v = values[i];
ComputeHash(&v, (ulong)sizeof(T), &hash);
existingValues[hash] = offset + (uint)i * (uint)sizeof(T);
}
}
return offset;
}
}
chunkOffset -= c.position;
}
}
return uint.MaxValue;
}
public uint WriteObjects<T>(IEnumerable<T> objs, bool serizalizeTypeData)
{
if (objs == null)
return uint.MaxValue;
var count = objs.Count();
var ids = new uint[count];
var index = 0;
foreach (var o in objs)
ids[index++] = WriteObject(o, serizalizeTypeData);
return Write(ids);
}
public uint WriteObject(object obj, bool serializeTypeData)
{
if (obj == null)
return uint.MaxValue;
var objType = obj.GetType();
if (!GetSerializationAdapter(serializationAdapters, objType, out var adapter))
return uint.MaxValue;
var id = adapter.Serialize(this, obj);
if (serializeTypeData)
id = Write(new ObjectTypeData { typeId = WriteObject(objType, false), objectId = id });
return id;
}
public uint WriteString(string str, char sep = (char)0)
{
if (str == null)
return uint.MaxValue;
return sep == (char)0 ? WriteAutoEncodedString(str) : WriteDynamicString(str, sep);
}
uint WriteStringInternal(string val, Encoding enc)
{
if (val == null)
return uint.MaxValue;
byte[] tmp = enc.GetBytes(val);
fixed (byte* pBytes = tmp)
return WriteInternal(pBytes, (uint)tmp.Length, true);
}
public byte[] SerializeToByteArray()
{
var data = new byte[totalBytes];
fixed (byte* pData = data)
{
uint offset = 0;
foreach (var c in chunks)
{
fixed (byte* pChunk = c.data)
UnsafeUtility.MemCpy(&pData[offset], pChunk, c.position);
offset += c.position;
}
}
return data;
}
public uint SerializeToStream(Stream str)
{
foreach (var c in chunks)
str.Write(c.data, 0, (int)c.position);
return totalBytes;
}
static bool IsUnicode(string str)
{
for (int i = 0; i < str.Length; i++)
if (str[i] > 255)
return true;
return false;
}
uint WriteAutoEncodedString(string str)
{
if (str == null)
return uint.MaxValue;
if (IsUnicode(str))
return WriteUnicodeString(str);
else
return WriteStringInternal(str, Encoding.ASCII);
}
uint WriteUnicodeString(string str)
{
var id = WriteStringInternal(str, Encoding.Unicode);
return (kUnicodeStringFlag | id);
}
static uint ComputeStringSize(string str, out bool isUnicode)
{
if (isUnicode = IsUnicode(str))
return (uint)Encoding.Unicode.GetByteCount(str);
return (uint)Encoding.ASCII.GetByteCount(str);
}
uint RecurseDynamicStringParts(StringParts[] parts, int index, char sep, uint minSize)
{
while (index < parts.Length - 1)
{
var currPartSize = parts[index].dataSize;
var nextPartSize = parts[index + 1].dataSize;
if (currPartSize < minSize || nextPartSize < minSize)
{
parts[index + 1].str = $"{parts[index].str}{sep}{parts[index + 1].str}";
index++;
}
else
{
break;
}
}
var strId = parts[index].isUnicode ? WriteUnicodeString(parts[index].str) : WriteStringInternal(parts[index].str, Encoding.ASCII);
var nxtId = (index < parts.Length - 1 ? RecurseDynamicStringParts(parts, index + 1, sep, minSize) : uint.MaxValue);
var id = Write(new DynamicString { stringId = strId, nextId = nxtId });
return id;
}
struct StringParts
{
public string str;
public uint dataSize;
public bool isUnicode;
}
unsafe uint WriteDynamicString(string str, char sep)
{
if (str == null)
return uint.MaxValue;
var minSize = (uint)sizeof(DynamicString);
var split = str.Split(sep);
var parts = new StringParts[split.Length];
for (int i = 0; i < parts.Length; i++)
{
var partSize = ComputeStringSize(split[i], out var isUnicode);
parts[i] = new StringParts { str = split[i], dataSize = partSize, isUnicode = isUnicode };
}
if (parts.Length < 2 || (parts.Length == 2 && (parts[0].dataSize + parts[1].dataSize) < minSize))
{
return WriteAutoEncodedString(str);
}
else
{
return (kDynamicStringFlag | RecurseDynamicStringParts(parts, 0, sep, minSize));
}
}
}
}
internal struct LRUCache<TKey, TValue> where TKey : IEquatable<TKey>
{
public struct Entry : IEquatable<Entry>
{
public LinkedListNode<TKey> lruNode;
public TValue Value;
public bool Equals(Entry other) => Value.Equals(other);
public override int GetHashCode() => Value.GetHashCode();
}
int entryLimit;
Dictionary<TKey, Entry> cache;
LinkedList<TKey> lru;
public LRUCache(int limit)
{
entryLimit = limit;
cache = new Dictionary<TKey, Entry>();
lru = new LinkedList<TKey>();
}
public bool TryAdd(TKey id, TValue obj)
{
if (obj == null || entryLimit <= 0)
return false;
cache.Add(id, new Entry { Value = obj, lruNode = lru.AddFirst(id) });
while (lru.Count > entryLimit)
{
cache.Remove(lru.Last.Value);
lru.RemoveLast();
}
return true;
}
public bool TryGet(TKey offset, out TValue val)
{
if (cache.TryGetValue(offset, out var entry))
{
val = entry.Value;
if (entry.lruNode.Previous != null)
{
lru.Remove(entry.lruNode);
lru.AddFirst(entry.lruNode);
}
return true;
}
val = default;
return false;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 25cef95ed1a3d0a47bb7058b90931cd0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,125 @@
using UnityEditor;
namespace UnityEngine.ResourceManagement.Util
{
/// <summary>
/// Creates a singleton.
/// </summary>
/// <typeparam name="T">The singleton type.</typeparam>
[ExecuteInEditMode]
public abstract class ComponentSingleton<T> : MonoBehaviour where T : ComponentSingleton<T>
{
static T s_Instance;
/// <summary>
/// Indicates whether or not there is an existing instance of the singleton.
/// </summary>
public static bool Exists => s_Instance != null;
/// <summary>
/// Stores the instance of the singleton.
/// </summary>
public static T Instance
{
get
{
if (s_Instance == null)
{
s_Instance = FindInstance() ?? CreateNewSingleton();
}
return s_Instance;
}
}
static T FindInstance()
{
#if UNITY_EDITOR
foreach (T cb in Resources.FindObjectsOfTypeAll(typeof(T)))
{
var go = cb.gameObject;
if (!EditorUtility.IsPersistent(go.transform.root.gameObject) && !(go.hideFlags == HideFlags.NotEditable || go.hideFlags == HideFlags.HideAndDontSave))
return cb;
}
return null;
#else
return FindObjectOfType<T>();
#endif
}
/// <summary>
/// Retrieves the name of the object.
/// </summary>
/// <returns>Returns the name of the object.</returns>
protected virtual string GetGameObjectName() => typeof(T).Name;
static T CreateNewSingleton()
{
var go = new GameObject();
if (Application.isPlaying)
{
DontDestroyOnLoad(go);
go.hideFlags = HideFlags.DontSave;
}
else
{
go.hideFlags = HideFlags.HideAndDontSave;
}
var instance = go.AddComponent<T>();
go.name = instance.GetGameObjectName();
return instance;
}
private void Awake()
{
if (s_Instance != null && s_Instance != this)
{
DestroyImmediate(gameObject);
return;
}
s_Instance = this as T;
}
/// <summary>
/// Destroys the singleton.
/// </summary>
public static void DestroySingleton()
{
if (Exists)
{
DestroyImmediate(Instance.gameObject);
s_Instance = null;
}
}
#if UNITY_EDITOR
void OnEnable()
{
EditorApplication.playModeStateChanged += PlayModeChanged;
}
void OnDisable()
{
EditorApplication.playModeStateChanged -= PlayModeChanged;
}
void PlayModeChanged(PlayModeStateChange state)
{
if (state == PlayModeStateChange.ExitingPlayMode)
{
if (Exists)
{
DestroyImmediate(Instance.gameObject);
s_Instance = null;
}
}
}
#endif
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 11643b1a70376ce4e807aa759c0f4dbd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,201 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEditor;
namespace UnityEngine.ResourceManagement.Util
{
internal class DelayedActionManager : ComponentSingleton<DelayedActionManager>
{
struct DelegateInfo
{
static int s_Id;
int m_Id;
Delegate m_Delegate;
object[] m_Target;
public DelegateInfo(Delegate d, float invocationTime, params object[] p)
{
m_Delegate = d;
m_Id = s_Id++;
m_Target = p;
InvocationTime = invocationTime;
}
public float InvocationTime { get; private set; }
public override string ToString()
{
if (m_Delegate == null || m_Delegate.Method.DeclaringType == null)
return "Null m_delegate for " + m_Id;
var n = m_Id + " (target=" + m_Delegate.Target + ") " + m_Delegate.Method.DeclaringType.Name + "." + m_Delegate.Method.Name + "(";
var sep = "";
foreach (var o in m_Target)
{
n += sep + o;
sep = ", ";
}
return n + ") @" + InvocationTime;
}
public void Invoke()
{
try
{
m_Delegate.DynamicInvoke(m_Target);
}
catch (Exception e)
{
Debug.LogErrorFormat("Exception thrown in DynamicInvoke: {0} {1}", e, this);
}
}
}
List<DelegateInfo>[] m_Actions = {new List<DelegateInfo>(), new List<DelegateInfo>()};
LinkedList<DelegateInfo> m_DelayedActions = new LinkedList<DelegateInfo>();
Stack<LinkedListNode<DelegateInfo>> m_NodeCache = new Stack<LinkedListNode<DelegateInfo>>(10);
int m_CollectionIndex;
bool m_DestroyOnCompletion;
LinkedListNode<DelegateInfo> GetNode(ref DelegateInfo del)
{
if (m_NodeCache.Count > 0)
{
var node = m_NodeCache.Pop();
node.Value = del;
return node;
}
return new LinkedListNode<DelegateInfo>(del);
}
public static void Clear()
{
if (Exists)
Instance.DestroyWhenComplete();
}
void DestroyWhenComplete()
{
m_DestroyOnCompletion = true;
}
public static void AddAction(Delegate action, float delay = 0, params object[] parameters)
{
Instance.AddActionInternal(action, delay, parameters);
}
void AddActionInternal(Delegate action, float delay, params object[] parameters)
{
var del = new DelegateInfo(action, Time.unscaledTime + delay, parameters);
if (delay > 0)
{
if (m_DelayedActions.Count == 0)
{
m_DelayedActions.AddFirst(GetNode(ref del));
}
else
{
var n = m_DelayedActions.Last;
while (n != null && n.Value.InvocationTime > del.InvocationTime)
n = n.Previous;
if (n == null)
m_DelayedActions.AddFirst(GetNode(ref del));
else
m_DelayedActions.AddBefore(n, GetNode(ref del));
}
}
else
m_Actions[m_CollectionIndex].Add(del);
}
#if UNITY_EDITOR
void Awake()
{
if (!Application.isPlaying)
{
// Debug.Log("DelayedActionManager called outside of play mode, registering with EditorApplication.update.");
EditorApplication.update += LateUpdate;
}
}
#endif
public static bool IsActive
{
get
{
if (!Exists)
return false;
if (Instance.m_DelayedActions.Count > 0)
return true;
for (int i = 0; i < Instance.m_Actions.Length; i++)
if (Instance.m_Actions[i].Count > 0)
return true;
return false;
}
}
public static bool Wait(float timeout = 0, float timeAdvanceAmount = 0)
{
if (!IsActive)
return true;
var timer = new Stopwatch();
timer.Start();
var t = Time.unscaledTime;
do
{
Instance.InternalLateUpdate(t);
if (timeAdvanceAmount >= 0)
t += timeAdvanceAmount;
else
t = Time.unscaledTime;
} while (IsActive && (timeout <= 0 || timer.Elapsed.TotalSeconds < timeout));
return !IsActive;
}
void LateUpdate()
{
InternalLateUpdate(Time.unscaledTime);
}
void InternalLateUpdate(float t)
{
int iterationCount = 0;
while (m_DelayedActions.Count > 0 && m_DelayedActions.First.Value.InvocationTime <= t)
{
m_Actions[m_CollectionIndex].Add(m_DelayedActions.First.Value);
m_NodeCache.Push(m_DelayedActions.First);
m_DelayedActions.RemoveFirst();
}
do
{
int invokeIndex = m_CollectionIndex;
m_CollectionIndex = (m_CollectionIndex + 1) % 2;
var list = m_Actions[invokeIndex];
if (list.Count > 0)
{
for (int i = 0; i < list.Count; i++)
list[i].Invoke();
list.Clear();
}
iterationCount++;
Debug.Assert(iterationCount < 100);
} while (m_Actions[m_CollectionIndex].Count > 0);
if (m_DestroyOnCompletion && !IsActive)
Destroy(gameObject);
}
private void OnApplicationQuit()
{
if (Exists)
Destroy(Instance.gameObject);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: caa3431363af216479bf77b9d16e8c72
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using UnityEngine.ResourceManagement.Util;
internal class DelegateList<T>
{
Func<Action<T>, LinkedListNode<Action<T>>> m_acquireFunc;
Action<LinkedListNode<Action<T>>> m_releaseFunc;
LinkedList<Action<T>> m_callbacks;
bool m_invoking = false;
public DelegateList(Func<Action<T>, LinkedListNode<Action<T>>> acquireFunc, Action<LinkedListNode<Action<T>>> releaseFunc)
{
if (acquireFunc == null)
throw new ArgumentNullException("acquireFunc");
if (releaseFunc == null)
throw new ArgumentNullException("releaseFunc");
m_acquireFunc = acquireFunc;
m_releaseFunc = releaseFunc;
}
public int Count
{
get { return m_callbacks == null ? 0 : m_callbacks.Count; }
}
public void Add(Action<T> action)
{
var node = m_acquireFunc(action);
if (m_callbacks == null)
m_callbacks = new LinkedList<Action<T>>();
m_callbacks.AddLast(node);
}
public void Remove(Action<T> action)
{
if (m_callbacks == null)
return;
var node = m_callbacks.First;
while (node != null)
{
if (node.Value == action)
{
if (m_invoking)
{
node.Value = null;
}
else
{
m_callbacks.Remove(node);
m_releaseFunc(node);
}
return;
}
node = node.Next;
}
}
public void Invoke(T res)
{
if (m_callbacks == null)
return;
m_invoking = true;
var node = m_callbacks.First;
while (node != null)
{
if (node.Value != null)
{
try
{
node.Value(res);
}
catch (Exception ex)
{
UnityEngine.Debug.LogException(ex);
}
}
node = node.Next;
}
m_invoking = false;
var r = m_callbacks.First;
while (r != null)
{
var next = r.Next;
if (r.Value == null)
{
m_callbacks.Remove(r);
m_releaseFunc(r);
}
r = next;
}
}
public void Clear()
{
if (m_callbacks == null)
return;
var node = m_callbacks.First;
while (node != null)
{
var next = node.Next;
m_callbacks.Remove(node);
m_releaseFunc(node);
node = next;
}
}
public static DelegateList<T> CreateWithGlobalCache()
{
if (!GlobalLinkedListNodeCache<Action<T>>.CacheExists)
GlobalLinkedListNodeCache<Action<T>>.SetCacheSize(32);
return new DelegateList<T>(GlobalLinkedListNodeCache<Action<T>>.Acquire, GlobalLinkedListNodeCache<Action<T>>.Release);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a6ea1740a8df96b44bd23845119733cf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,201 @@
using System;
using System.Runtime.Serialization;
using UnityEngine.ResourceManagement.ResourceLocations;
using UnityEngine.ResourceManagement.Util;
namespace UnityEngine.ResourceManagement.Exceptions
{
/// <summary>
/// Base class for all ResourceManager related exceptions.
/// </summary>
public class ResourceManagerException : Exception
{
/// <summary>
/// Construct a new ResourceManagerException.
/// </summary>
public ResourceManagerException()
{
}
/// <summary>
/// Construct a new ResourceManagerException.
/// </summary>
/// <param name="message">Message to describe the exception.</param>
public ResourceManagerException(string message) : base(message)
{
}
/// <summary>
/// Construct a new ResourceManagerException.
/// </summary>
/// <param name="message">Message to describe the exception.</param>
/// <param name="innerException">Inner exception that caused this exception.</param>
public ResourceManagerException(string message, Exception innerException) : base(message, innerException)
{
}
/// <summary>
/// Construct a new ResourceManagerException.
/// </summary>
/// <param name="message">Message to describe the exception.</param>
/// <param name="context">Context related to the exception.</param>
protected ResourceManagerException(SerializationInfo message, StreamingContext context) : base(message, context)
{
}
/// <summary>Provides a new string object describing the exception.</summary>
/// <returns>A newly allocated managed string.</returns>
public override string ToString() => $"{GetType().Name} : {base.Message}\n{InnerException}";
}
/// <summary>
/// Exception returned when the IResourceProvider is not found for a location.
/// </summary>
public class UnknownResourceProviderException : ResourceManagerException
{
/// <summary>
/// The location that contains the provider id that was not found.
/// </summary>
public IResourceLocation Location { get; private set; }
/// <summary>
/// Construct a new UnknownResourceProviderException
/// </summary>
/// <param name="location">The location that caused the exception to be created.</param>
public UnknownResourceProviderException(IResourceLocation location)
{
Location = location;
}
/// <summary>
/// Construct a new UnknownResourceProviderException
/// </summary>
public UnknownResourceProviderException()
{
}
/// <summary>
/// Construct a new UnknownResourceProviderException
/// </summary>
/// <param name="message">Message to describe the exception.</param>
public UnknownResourceProviderException(string message) : base(message)
{
}
/// <summary>
/// Construct a new UnknownResourceProviderException
/// </summary>
/// <param name="message">Message to describe the exception.</param>
/// <param name="innerException">Inner exception that caused this exception.</param>
public UnknownResourceProviderException(string message, Exception innerException) : base(message, innerException)
{
}
/// <summary>
/// Construct a new UnknownResourceProviderException
/// </summary>
/// <param name="message">Message to describe the exception.</param>
/// <param name="context">Context related to the exception.</param>
protected UnknownResourceProviderException(SerializationInfo message, StreamingContext context) : base(message, context)
{
}
/// <summary>
/// Returns a string describing this exception.
/// </summary>
public override string Message
{
get { return base.Message + ", ProviderId=" + Location.ProviderId + ", Location=" + Location; }
}
/// <summary>
/// Returns string representation of exception.
/// </summary>
/// <returns>String representation of exception.</returns>
public override string ToString()
{
return Message;
}
}
/// <summary>
/// Class that represent an error that occured during an AsyncOperation.
/// </summary>
public class OperationException : Exception
{
/// <summary>
/// Creates a new instance of <see cref="OperationException"/>.
/// </summary>
/// <param name="message">A message describing the error.</param>
/// <param name="innerException">The exception that caused the error, if any.</param>
public OperationException(string message, Exception innerException = null) : base(message, innerException)
{
}
/// <summary>Provides a new string object describing the exception.</summary>
/// <returns>A newly allocated managed string.</returns>
public override string ToString() => $"{GetType().Name} : {base.Message}\n{InnerException}";
}
/// <summary>
/// Class that represent an error that occured during a ProviderOperation.
/// </summary>
public class ProviderException : OperationException
{
/// <summary>
/// Creates a new instance of <see cref="ProviderException"/>.
/// </summary>
/// <param name="message">A message describing the error.</param>
/// <param name="location">The resource location that the operation was trying to provide.</param>
/// <param name="innerException">The exception that caused the error, if any.</param>
public ProviderException(string message, IResourceLocation location = null, Exception innerException = null)
: base(message, innerException)
{
Location = location;
}
/// <summary>
/// The resource location that the operation was trying to provide.
/// </summary>
public IResourceLocation Location { get; }
}
/// <summary>
/// Class representing an error occured during an operation that remotely fetch data.
/// </summary>
public class RemoteProviderException : ProviderException
{
/// <summary>
/// Creates a new instance of <see cref="ProviderException"/>.
/// </summary>
/// <param name="message">A message describing the error.</param>
/// <param name="location">The resource location that the operation was trying to provide.</param>
/// <param name="uwrResult">The result of the unity web request, if any.</param>
/// <param name="innerException">The exception that caused the error, if any.</param>
public RemoteProviderException(string message, IResourceLocation location = null, UnityWebRequestResult uwrResult = null, Exception innerException = null)
: base(message, location, innerException)
{
WebRequestResult = uwrResult;
}
/// <summary>
/// Returns a string describing this exception.
/// </summary>
public override string Message => this.ToString();
/// <summary>
/// The result of the unity web request, if any.
/// </summary>
public UnityWebRequestResult WebRequestResult { get; }
/// <summary>Provides a new string object describing the exception.</summary>
/// <returns>A newly allocated managed string.</returns>
public override string ToString()
{
if (WebRequestResult != null)
return $"{GetType().Name} : {base.Message}\nUnityWebRequest result : {WebRequestResult}\n{InnerException}";
else
return base.ToString();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ba5033d0e43aacc4aa1fe4bed1f637e6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,106 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
internal class ListWithEvents<T> : IList<T>
{
private List<T> m_List = new List<T>();
public event Action<T> OnElementAdded;
public event Action<T> OnElementRemoved;
private void InvokeAdded(T element)
{
if (OnElementAdded != null)
OnElementAdded(element);
}
private void InvokeRemoved(T element)
{
if (OnElementRemoved != null)
OnElementRemoved(element);
}
public T this[int index]
{
get { return m_List[index]; }
set
{
T oldElement = m_List[index];
m_List[index] = value;
InvokeRemoved(oldElement);
InvokeAdded(value);
}
}
public int Count
{
get { return m_List.Count; }
}
public bool IsReadOnly
{
get { return ((IList<T>)m_List).IsReadOnly; }
}
public void Add(T item)
{
m_List.Add(item);
InvokeAdded(item);
}
public void Clear()
{
foreach (T obj in m_List)
InvokeRemoved(obj);
m_List.Clear();
}
public bool Contains(T item)
{
return m_List.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
m_List.CopyTo(array, arrayIndex);
}
public IEnumerator<T> GetEnumerator()
{
return m_List.GetEnumerator();
}
public int IndexOf(T item)
{
return m_List.IndexOf(item);
}
public void Insert(int index, T item)
{
m_List.Insert(index, item);
InvokeAdded(item);
}
public bool Remove(T item)
{
bool ret = m_List.Remove(item);
if (ret)
InvokeRemoved(item);
return ret;
}
public void RemoveAt(int index)
{
T item = m_List[index];
m_List.RemoveAt(index);
InvokeRemoved(item);
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)m_List).GetEnumerator();
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 84f724d4704815c4e96da7a3308f0be7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,36 @@
using System;
using UnityEngine;
using UnityEngine.ResourceManagement.Util;
internal class MonoBehaviourCallbackHooks : ComponentSingleton<MonoBehaviourCallbackHooks>
{
internal Action<float> m_OnUpdateDelegate;
internal Action<float> m_OnLateUpdateDelegate;
public event Action<float> OnUpdateDelegate
{
add { m_OnUpdateDelegate += value; }
remove { m_OnUpdateDelegate -= value; }
}
internal event Action<float> OnLateUpdateDelegate
{
add { m_OnLateUpdateDelegate += value; }
remove { m_OnLateUpdateDelegate -= value; }
}
protected override string GetGameObjectName() => "ResourceManagerCallbacks";
// Update is called once per frame
internal void Update()
{
m_OnUpdateDelegate?.Invoke(Time.unscaledDeltaTime);
}
internal void LateUpdate()
{
m_OnLateUpdateDelegate?.Invoke(Time.unscaledDeltaTime);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c3a73b848f18b6b498c76e82b2551f0d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,157 @@
using System;
using System.Collections.Generic;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceLocations;
namespace UnityEngine.ResourceManagement.Util
{
internal interface IOperationCacheKey : IEquatable<IOperationCacheKey>
{
}
/// <summary>
/// Used to compare cachable operation based solely on a single string id
/// </summary>
internal sealed class IdCacheKey : IOperationCacheKey
{
public string ID;
public IdCacheKey(string id)
{
ID = id;
}
bool Equals(IdCacheKey other)
{
if (ReferenceEquals(this, other)) return true;
if (ReferenceEquals(other, null)) return false;
return other.ID == ID;
}
public override int GetHashCode()
{
return ID.GetHashCode();
}
public override bool Equals(object obj) => Equals(obj as IdCacheKey);
public bool Equals(IOperationCacheKey other) => Equals(other as IdCacheKey);
}
internal sealed class LocationCacheKey : IOperationCacheKey
{
readonly IResourceLocation m_Location;
readonly Type m_DesiredType;
public LocationCacheKey(IResourceLocation location, Type desiredType)
{
if (location == null)
throw new NullReferenceException($"Resource location cannot be null.");
if (desiredType == null)
throw new NullReferenceException($"Desired type cannot be null.");
m_Location = location;
m_DesiredType = desiredType;
}
public override int GetHashCode()
{
return m_Location.Hash(m_DesiredType);
}
public override bool Equals(object obj) => Equals(obj as LocationCacheKey);
public bool Equals(IOperationCacheKey other) => Equals(other as LocationCacheKey);
bool Equals(LocationCacheKey other)
{
if (ReferenceEquals(this, other)) return true;
if (ReferenceEquals(other, null)) return false;
return LocationUtils.LocationEquals(m_Location, other.m_Location) && Equals(m_DesiredType, other.m_DesiredType);
}
}
internal sealed class DependenciesCacheKey : IOperationCacheKey
{
readonly IList<IResourceLocation> m_Dependencies;
readonly int m_DependenciesHash;
public DependenciesCacheKey(IList<IResourceLocation> dependencies, int dependenciesHash)
{
m_Dependencies = dependencies;
m_DependenciesHash = dependenciesHash;
}
public override int GetHashCode()
{
return m_DependenciesHash;
}
public override bool Equals(object obj) => Equals(obj as DependenciesCacheKey);
public bool Equals(IOperationCacheKey other) => Equals(other as DependenciesCacheKey);
bool Equals(DependenciesCacheKey other)
{
if (ReferenceEquals(this, other)) return true;
if (ReferenceEquals(other, null)) return false;
return LocationUtils.DependenciesEqual(m_Dependencies, other.m_Dependencies);
}
}
internal sealed class AsyncOpHandlesCacheKey : IOperationCacheKey
{
readonly HashSet<AsyncOperationHandle> m_Handles;
public AsyncOpHandlesCacheKey(IList<AsyncOperationHandle> handles)
{
m_Handles = new HashSet<AsyncOperationHandle>(handles);
}
public override int GetHashCode()
{
return m_Handles.GetHashCode();
}
public override bool Equals(object obj) => Equals(obj as AsyncOpHandlesCacheKey);
public bool Equals(IOperationCacheKey other) => Equals(other as AsyncOpHandlesCacheKey);
bool Equals(AsyncOpHandlesCacheKey other)
{
if (ReferenceEquals(this, other)) return true;
if (ReferenceEquals(other, null)) return false;
return m_Handles.SetEquals(other.m_Handles);
}
}
internal static class LocationUtils
{
// TODO : Added equality methods here to prevent a minor version bump since we intend to stay on v1.18.x for a while.
// Ideally this should have been the Equals() implementation of IResourceLocation
public static bool LocationEquals(IResourceLocation loc1, IResourceLocation loc2)
{
if (ReferenceEquals(loc1, loc2)) return true;
if (ReferenceEquals(loc1, null)) return false;
if (ReferenceEquals(loc2, null)) return false;
return (loc1.InternalId.Equals(loc2.InternalId)
&& loc1.ProviderId.Equals(loc2.ProviderId)
&& loc1.ResourceType.Equals(loc2.ResourceType));
}
public static bool DependenciesEqual(IList<IResourceLocation> deps1, IList<IResourceLocation> deps2)
{
if (ReferenceEquals(deps1, deps2)) return true;
if (ReferenceEquals(deps1, null)) return false;
if (ReferenceEquals(deps2, null)) return false;
if (deps1.Count != deps2.Count)
return false;
for (int i = 0; i < deps1.Count; i++)
{
if (!LocationEquals(deps1[i], deps2[i]))
return false;
}
return true;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2d597abbd594bb545be7bc273326f95c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,10 @@
namespace UnityEngine.ResourceManagement.Util
{
internal class PlatformUtilities
{
internal static bool PlatformUsesMultiThreading(RuntimePlatform platform)
{
return platform != RuntimePlatform.WebGLPlayer;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 86ae0199f263f5e42adb6d8581811534
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,736 @@
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
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c8cf226ae39e82446b1bb827024f582c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,195 @@
using System;
using System.Text;
using UnityEngine.Networking;
namespace UnityEngine.ResourceManagement.Util
{
/// <summary>
/// Utility class for extracting information from UnityWebRequests.
/// </summary>
public class UnityWebRequestUtilities
{
/// <summary>
/// Determines if a web request resulted in an error.
/// </summary>
/// <param name="webReq">The web request.</param>
/// <param name="result"></param>
/// <returns>True if a web request resulted in an error.</returns>
public static bool RequestHasErrors(UnityWebRequest webReq, out UnityWebRequestResult result)
{
result = null;
if (webReq == null || !webReq.isDone)
return false;
#if UNITY_2020_1_OR_NEWER
switch (webReq.result)
{
case UnityWebRequest.Result.InProgress:
case UnityWebRequest.Result.Success:
return false;
case UnityWebRequest.Result.ConnectionError:
case UnityWebRequest.Result.ProtocolError:
case UnityWebRequest.Result.DataProcessingError:
result = new UnityWebRequestResult(webReq);
return true;
default:
throw new NotImplementedException($"Cannot determine whether UnityWebRequest succeeded or not from result : {webReq.result}");
}
#else
var isError = webReq.isHttpError || webReq.isNetworkError;
if (isError)
result = new UnityWebRequestResult(webReq);
return isError;
#endif
}
/// <summary>
/// Indicates if the requested AssetBundle is downloaded.
/// </summary>
/// <param name="op">The object returned from sending the web request.</param>
/// <returns>Returns true if the AssetBundle is downloaded.</returns>
public static bool IsAssetBundleDownloaded(UnityWebRequestAsyncOperation op)
{
#if ENABLE_ASYNC_ASSETBUNDLE_UWR
var handler = (DownloadHandlerAssetBundle)op.webRequest.downloadHandler;
if (handler != null && handler.autoLoadAssetBundle)
return handler.isDownloadComplete;
#endif
return op.isDone;
}
}
/// <summary>
/// Container class for the result of a unity web request.
/// </summary>
public class UnityWebRequestResult
{
/// <summary>
/// Creates a new instance of <see cref="UnityWebRequestResult"/>.
/// </summary>
/// <param name="request">The unity web request.</param>
public UnityWebRequestResult(UnityWebRequest request)
{
string error = request.error;
#if UNITY_2020_1_OR_NEWER
if (request.result == UnityWebRequest.Result.DataProcessingError && request.downloadHandler != null)
{
// https://docs.unity3d.com/ScriptReference/Networking.DownloadHandler-error.html
// When a UnityWebRequest ends with the result, UnityWebRequest.Result.DataProcessingError, the message describing the error is in the download handler
error = $"{error} : {request.downloadHandler.error}";
}
Result = request.result;
#endif
Error = error;
ResponseCode = request.responseCode;
Method = request.method;
Url = request.url;
}
/// <summary>Provides a new string object describing the result.</summary>
/// <returns>A newly allocated managed string.</returns>
public override string ToString()
{
var sb = new StringBuilder();
#if UNITY_2020_1_OR_NEWER
sb.AppendLine($"{Result} : {Error}");
#else
if (!string.IsNullOrEmpty(Error))
sb.AppendLine(Error);
#endif
if (ResponseCode > 0)
sb.AppendLine($"ResponseCode : {ResponseCode}, Method : {Method}");
sb.AppendLine($"url : {Url}");
return sb.ToString();
}
/// <summary>
/// A string explaining the error that occured.
/// </summary>
public string Error { get; set; }
/// <summary>
/// The numeric HTTP response code returned by the server, if any.
/// See <a href="https://docs.unity3d.com/ScriptReference/Networking.UnityWebRequest-responseCode.html">documentation</a> for more details.
/// </summary>
public long ResponseCode { get; }
#if UNITY_2020_1_OR_NEWER
/// <summary>
/// The outcome of the request.
/// </summary>
public UnityWebRequest.Result Result { get; }
#endif
/// <summary>
/// The HTTP verb used by this UnityWebRequest, such as GET or POST.
/// </summary>
public string Method { get; }
/// <summary>
/// The target url of the request.
/// </summary>
public string Url { get; }
/// <summary>
/// Determines if the web request can be sent again based on its error.
/// </summary>
/// <returns>Returns true if the web request can be sent again.</returns>
public bool ShouldRetryDownloadError()
{
if (string.IsNullOrEmpty(Error))
return true;
if (Error == "Request aborted" ||
Error == "Unable to write data" ||
Error == "Malformed URL" ||
Error == "Out of memory" ||
Error == "Encountered invalid redirect (missing Location header?)" ||
Error == "Cannot modify request at this time" ||
Error == "Unsupported Protocol" ||
Error == "Destination host has an erroneous SSL certificate" ||
Error == "Unable to load SSL Cipher for verification" ||
Error == "SSL CA certificate error" ||
Error == "Unrecognized content-encoding" ||
Error == "Request already transmitted" ||
Error == "Invalid HTTP Method" ||
Error == "Header name contains invalid characters" ||
Error == "Header value contains invalid characters" ||
Error == "Cannot override system-specified headers"
#if UNITY_2022_1_OR_NEWER
|| Error == "Insecure connection not allowed"
#endif
)
return false;
/* Errors that can be retried:
"Unknown Error":
"No Internet Connection"
"Backend Initialization Error":
"Cannot resolve proxy":
"Cannot resolve destination host":
"Cannot connect to destination host":
"Access denied":
"Generic/unknown HTTP error":
"Unable to read data":
"Request timeout":
"Error during HTTP POST transmission":
"Unable to complete SSL connection":
"Redirect limit exceeded":
"Received no data in response":
"Destination host does not support SSL":
"Failed to transmit data":
"Failed to receive data":
"Login failed":
"SSL shutdown failed":
"Redirect limit is invalid":
"Not implemented":
"Data Processing Error, see Download Handler error":
*/
return true;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4fd966b3a8792f64b8089a68443e19e9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: