initial commit
This commit is contained in:
parent
6715289efe
commit
788c3389af
37645 changed files with 2526849 additions and 80 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 25cef95ed1a3d0a47bb7058b90931cd0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 11643b1a70376ce4e807aa759c0f4dbd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: caa3431363af216479bf77b9d16e8c72
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a6ea1740a8df96b44bd23845119733cf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ba5033d0e43aacc4aa1fe4bed1f637e6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 84f724d4704815c4e96da7a3308f0be7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c3a73b848f18b6b498c76e82b2551f0d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2d597abbd594bb545be7bc273326f95c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,10 @@
|
|||
namespace UnityEngine.ResourceManagement.Util
|
||||
{
|
||||
internal class PlatformUtilities
|
||||
{
|
||||
internal static bool PlatformUsesMultiThreading(RuntimePlatform platform)
|
||||
{
|
||||
return platform != RuntimePlatform.WebGLPlayer;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 86ae0199f263f5e42adb6d8581811534
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c8cf226ae39e82446b1bb827024f582c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4fd966b3a8792f64b8089a68443e19e9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Add table
Add a link
Reference in a new issue