1312 lines
49 KiB
Plaintext
1312 lines
49 KiB
Plaintext
|
#if UNITY_2022_1_OR_NEWER
|
||
|
#define UNLOAD_BUNDLE_ASYNC
|
||
|
#endif
|
||
|
|
||
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.ComponentModel;
|
||
|
using System.IO;
|
||
|
using System.Threading;
|
||
|
using UnityEngine.Networking;
|
||
|
using UnityEngine.ResourceManagement.AsyncOperations;
|
||
|
using UnityEngine.ResourceManagement.Exceptions;
|
||
|
using UnityEngine.ResourceManagement.ResourceLocations;
|
||
|
using UnityEngine.ResourceManagement.Util;
|
||
|
using TTSDK;
|
||
|
|
||
|
namespace UnityEngine.ResourceManagement
|
||
|
{
|
||
|
internal class TTWebRequestQueueOperation
|
||
|
{
|
||
|
private bool m_Completed = false;
|
||
|
public UnityWebRequestAsyncOperation Result;
|
||
|
public Action<UnityWebRequestAsyncOperation> OnComplete;
|
||
|
public bool IsDone
|
||
|
{
|
||
|
get { return m_Completed || Result != null; }
|
||
|
}
|
||
|
|
||
|
internal UnityWebRequest m_WebRequest;
|
||
|
|
||
|
public UnityWebRequest WebRequest
|
||
|
{
|
||
|
get { return m_WebRequest; }
|
||
|
internal set { m_WebRequest = value; }
|
||
|
}
|
||
|
|
||
|
public TTWebRequestQueueOperation(UnityWebRequest request)
|
||
|
{
|
||
|
m_WebRequest = request;
|
||
|
}
|
||
|
|
||
|
internal void Complete(UnityWebRequestAsyncOperation asyncOp)
|
||
|
{
|
||
|
m_Completed = true;
|
||
|
Result = asyncOp;
|
||
|
OnComplete?.Invoke(Result);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
internal static class WebRequestQueue
|
||
|
{
|
||
|
internal static int s_MaxRequest = 512;
|
||
|
internal static Queue<TTWebRequestQueueOperation> s_QueuedOperations = new Queue<TTWebRequestQueueOperation>();
|
||
|
internal static List<UnityWebRequestAsyncOperation> s_ActiveRequests = new List<UnityWebRequestAsyncOperation>();
|
||
|
|
||
|
public static void SetMaxConcurrentRequests(int maxRequests)
|
||
|
{
|
||
|
if (maxRequests < 1)
|
||
|
throw new ArgumentException("MaxRequests must be 1 or greater.", "maxRequests");
|
||
|
s_MaxRequest = maxRequests;
|
||
|
}
|
||
|
|
||
|
public static TTWebRequestQueueOperation QueueRequest(UnityWebRequest request)
|
||
|
{
|
||
|
TTWebRequestQueueOperation queueOperation = new TTWebRequestQueueOperation(request);
|
||
|
if (s_ActiveRequests.Count < s_MaxRequest)
|
||
|
BeginWebRequest(queueOperation);
|
||
|
else
|
||
|
s_QueuedOperations.Enqueue(queueOperation);
|
||
|
|
||
|
return queueOperation;
|
||
|
}
|
||
|
|
||
|
internal static void WaitForRequestToBeActive(TTWebRequestQueueOperation request, int millisecondsTimeout)
|
||
|
{
|
||
|
var completedRequests = new List<UnityWebRequestAsyncOperation>();
|
||
|
while (s_QueuedOperations.Contains(request))
|
||
|
{
|
||
|
completedRequests.Clear();
|
||
|
foreach (UnityWebRequestAsyncOperation webRequestAsyncOp in s_ActiveRequests)
|
||
|
{
|
||
|
if (TTUnityWebRequestUtilities.IsAssetBundleDownloaded(webRequestAsyncOp))
|
||
|
completedRequests.Add(webRequestAsyncOp);
|
||
|
}
|
||
|
|
||
|
foreach (UnityWebRequestAsyncOperation webRequestAsyncOp in completedRequests)
|
||
|
{
|
||
|
bool requestIsActive = s_QueuedOperations.Peek() == request;
|
||
|
webRequestAsyncOp.completed -= OnWebAsyncOpComplete;
|
||
|
OnWebAsyncOpComplete(webRequestAsyncOp);
|
||
|
if (requestIsActive)
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Thread.Sleep(millisecondsTimeout);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal static void DequeueRequest(UnityWebRequestAsyncOperation operation)
|
||
|
{
|
||
|
operation.completed -= OnWebAsyncOpComplete;
|
||
|
OnWebAsyncOpComplete(operation);
|
||
|
}
|
||
|
|
||
|
private static void OnWebAsyncOpComplete(AsyncOperation operation)
|
||
|
{
|
||
|
OnWebAsyncOpComplete(operation as UnityWebRequestAsyncOperation);
|
||
|
}
|
||
|
|
||
|
private static void OnWebAsyncOpComplete(UnityWebRequestAsyncOperation operation)
|
||
|
{
|
||
|
if (s_ActiveRequests.Remove(operation) && s_QueuedOperations.Count > 0)
|
||
|
{
|
||
|
var nextQueuedOperation = s_QueuedOperations.Dequeue();
|
||
|
BeginWebRequest(nextQueuedOperation);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void BeginWebRequest(TTWebRequestQueueOperation queueOperation)
|
||
|
{
|
||
|
var request = queueOperation.m_WebRequest;
|
||
|
UnityWebRequestAsyncOperation webRequestAsyncOp = null;
|
||
|
try
|
||
|
{
|
||
|
webRequestAsyncOp = request.SendWebRequest();
|
||
|
if (webRequestAsyncOp != null)
|
||
|
{
|
||
|
s_ActiveRequests.Add(webRequestAsyncOp);
|
||
|
|
||
|
if (webRequestAsyncOp.isDone)
|
||
|
OnWebAsyncOpComplete(webRequestAsyncOp);
|
||
|
else
|
||
|
webRequestAsyncOp.completed += OnWebAsyncOpComplete;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
OnWebAsyncOpComplete(null);
|
||
|
}
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
Debug.LogError(e.Message);
|
||
|
}
|
||
|
|
||
|
queueOperation.Complete(webRequestAsyncOp);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
namespace UnityEngine.ResourceManagement.Util
|
||
|
{
|
||
|
[Flags]
|
||
|
internal enum BundleSource
|
||
|
{
|
||
|
None = 0,
|
||
|
Local = 1,
|
||
|
Cache = 2,
|
||
|
Download = 4,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
namespace UnityEngine.ResourceManagement.ResourceProviders
|
||
|
{
|
||
|
|
||
|
[DisplayName("Assets from TTBundles Provider")]
|
||
|
public class TTBundledAssetProvider : ResourceProviderBase
|
||
|
{
|
||
|
internal class InternalOp
|
||
|
{
|
||
|
AssetBundle m_AssetBundle;
|
||
|
AssetBundleRequest m_PreloadRequest;
|
||
|
AssetBundleRequest m_RequestOperation;
|
||
|
object m_Result;
|
||
|
ProvideHandle m_ProvideHandle;
|
||
|
string subObjectName = null;
|
||
|
|
||
|
internal static T LoadBundleFromDependecies<T>(IList<object> results) where T : class, IAssetBundleResource
|
||
|
{
|
||
|
if (results == null || results.Count == 0)
|
||
|
return default(T);
|
||
|
|
||
|
IAssetBundleResource bundle = null;
|
||
|
bool firstBundleWrapper = true;
|
||
|
for (int i = 0; i < results.Count; i++)
|
||
|
{
|
||
|
var abWrapper = results[i] as IAssetBundleResource;
|
||
|
if (abWrapper != null)
|
||
|
{
|
||
|
//only use the first asset bundle, even if it is invalid
|
||
|
abWrapper.GetAssetBundle();
|
||
|
if (firstBundleWrapper)
|
||
|
bundle = abWrapper;
|
||
|
firstBundleWrapper = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return bundle as T;
|
||
|
}
|
||
|
|
||
|
public void Start(ProvideHandle provideHandle)
|
||
|
{
|
||
|
provideHandle.SetProgressCallback(ProgressCallback);
|
||
|
provideHandle.SetWaitForCompletionCallback(WaitForCompletionHandler);
|
||
|
subObjectName = null;
|
||
|
m_ProvideHandle = provideHandle;
|
||
|
m_RequestOperation = null;
|
||
|
List<object> deps = new List<object>();
|
||
|
m_ProvideHandle.GetDependencies(deps);
|
||
|
var bundleResource = LoadBundleFromDependecies<IAssetBundleResource>(deps);
|
||
|
if (bundleResource == null)
|
||
|
{
|
||
|
m_ProvideHandle.Complete<AssetBundle>(null, false, new Exception("Unable to load dependent bundle from location " + m_ProvideHandle.Location));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_AssetBundle = bundleResource.GetAssetBundle();
|
||
|
if (m_AssetBundle == null)
|
||
|
{
|
||
|
m_ProvideHandle.Complete<AssetBundle>(null, false, new Exception("Unable to load dependent bundle from location " + m_ProvideHandle.Location));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var assetBundleResource = bundleResource as TTAssetBundleResource;
|
||
|
if (assetBundleResource != null)
|
||
|
m_PreloadRequest = assetBundleResource.GetAssetPreloadRequest();
|
||
|
if (m_PreloadRequest == null || m_PreloadRequest.isDone)
|
||
|
BeginAssetLoad();
|
||
|
else
|
||
|
m_PreloadRequest.completed += operation => BeginAssetLoad();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void BeginAssetLoad()
|
||
|
{
|
||
|
if (m_AssetBundle == null)
|
||
|
{
|
||
|
m_ProvideHandle.Complete<AssetBundle>(null, false, new Exception("Unable to load dependent bundle from location " + m_ProvideHandle.Location));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var assetPath = m_ProvideHandle.ResourceManager.TransformInternalId(m_ProvideHandle.Location);
|
||
|
if (m_ProvideHandle.Type.IsArray)
|
||
|
{
|
||
|
#if !UNITY_2021_1_OR_NEWER
|
||
|
if (AsyncOperationHandle.IsWaitingForCompletion)
|
||
|
{
|
||
|
GetArrayResult(m_AssetBundle.LoadAssetWithSubAssets(assetPath, m_ProvideHandle.Type.GetElementType()));
|
||
|
CompleteOperation();
|
||
|
}
|
||
|
else
|
||
|
#endif
|
||
|
m_RequestOperation = m_AssetBundle.LoadAssetWithSubAssetsAsync(assetPath, m_ProvideHandle.Type.GetElementType());
|
||
|
}
|
||
|
else if (m_ProvideHandle.Type.IsGenericType && typeof(IList<>) == m_ProvideHandle.Type.GetGenericTypeDefinition())
|
||
|
{
|
||
|
#if !UNITY_2021_1_OR_NEWER
|
||
|
if (AsyncOperationHandle.IsWaitingForCompletion)
|
||
|
{
|
||
|
GetListResult(m_AssetBundle.LoadAssetWithSubAssets(assetPath, m_ProvideHandle.Type.GetGenericArguments()[0]));
|
||
|
CompleteOperation();
|
||
|
}
|
||
|
else
|
||
|
#endif
|
||
|
m_RequestOperation = m_AssetBundle.LoadAssetWithSubAssetsAsync(assetPath, m_ProvideHandle.Type.GetGenericArguments()[0]);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (ExtractKeyAndSubKey(assetPath, out string mainPath, out string subKey))
|
||
|
{
|
||
|
subObjectName = subKey;
|
||
|
#if !UNITY_2021_1_OR_NEWER
|
||
|
if (AsyncOperationHandle.IsWaitingForCompletion)
|
||
|
{
|
||
|
GetAssetSubObjectResult(m_AssetBundle.LoadAssetWithSubAssets(mainPath, m_ProvideHandle.Type));
|
||
|
CompleteOperation();
|
||
|
}
|
||
|
else
|
||
|
#endif
|
||
|
m_RequestOperation = m_AssetBundle.LoadAssetWithSubAssetsAsync(mainPath, m_ProvideHandle.Type);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
#if !UNITY_2021_1_OR_NEWER
|
||
|
if (AsyncOperationHandle.IsWaitingForCompletion)
|
||
|
{
|
||
|
GetAssetResult(m_AssetBundle.LoadAsset(assetPath, m_ProvideHandle.Type));
|
||
|
CompleteOperation();
|
||
|
}
|
||
|
else
|
||
|
#endif
|
||
|
m_RequestOperation = m_AssetBundle.LoadAssetAsync(assetPath, m_ProvideHandle.Type);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (m_RequestOperation != null)
|
||
|
{
|
||
|
if (m_RequestOperation.isDone)
|
||
|
ActionComplete(m_RequestOperation);
|
||
|
else
|
||
|
{
|
||
|
#if ENABLE_ADDRESSABLE_PROFILER
|
||
|
if (UnityEngine.Profiling.Profiler.enabled && m_ProvideHandle.IsValid)
|
||
|
Profiling.ProfilerRuntime.AddAssetOperation(m_ProvideHandle, Profiling.ContentStatus.Loading);
|
||
|
#endif
|
||
|
m_RequestOperation.completed += ActionComplete;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private bool WaitForCompletionHandler()
|
||
|
{
|
||
|
if (m_PreloadRequest != null && !m_PreloadRequest.isDone)
|
||
|
return m_PreloadRequest.asset == null;
|
||
|
if (m_Result != null)
|
||
|
return true;
|
||
|
if (m_RequestOperation == null)
|
||
|
return false;
|
||
|
if (m_RequestOperation.isDone)
|
||
|
return true;
|
||
|
return m_RequestOperation.asset != null;
|
||
|
}
|
||
|
|
||
|
private void ActionComplete(AsyncOperation obj)
|
||
|
{
|
||
|
if (m_RequestOperation != null)
|
||
|
{
|
||
|
if (m_ProvideHandle.Type.IsArray)
|
||
|
GetArrayResult(m_RequestOperation.allAssets);
|
||
|
else if (m_ProvideHandle.Type.IsGenericType && typeof(IList<>) == m_ProvideHandle.Type.GetGenericTypeDefinition())
|
||
|
GetListResult(m_RequestOperation.allAssets);
|
||
|
else if (string.IsNullOrEmpty(subObjectName))
|
||
|
GetAssetResult(m_RequestOperation.asset);
|
||
|
else
|
||
|
GetAssetSubObjectResult(m_RequestOperation.allAssets);
|
||
|
}
|
||
|
|
||
|
CompleteOperation();
|
||
|
}
|
||
|
|
||
|
private void GetArrayResult(Object[] allAssets)
|
||
|
{
|
||
|
m_Result = ResourceManagerConfig.CreateArrayResult(m_ProvideHandle.Type, allAssets);
|
||
|
}
|
||
|
|
||
|
private void GetListResult(Object[] allAssets)
|
||
|
{
|
||
|
m_Result = ResourceManagerConfig.CreateListResult(m_ProvideHandle.Type, allAssets);
|
||
|
}
|
||
|
|
||
|
private void GetAssetResult(Object asset)
|
||
|
{
|
||
|
m_Result = (asset != null && m_ProvideHandle.Type.IsAssignableFrom(asset.GetType())) ? asset : null;
|
||
|
}
|
||
|
|
||
|
private void GetAssetSubObjectResult(Object[] allAssets)
|
||
|
{
|
||
|
foreach (var o in allAssets)
|
||
|
{
|
||
|
if (o.name == subObjectName)
|
||
|
{
|
||
|
if (m_ProvideHandle.Type.IsAssignableFrom(o.GetType()))
|
||
|
{
|
||
|
m_Result = o;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CompleteOperation()
|
||
|
{
|
||
|
#if ENABLE_ADDRESSABLE_PROFILER
|
||
|
if (UnityEngine.Profiling.Profiler.enabled && m_Result != null && m_ProvideHandle.IsValid)
|
||
|
Profiling.ProfilerRuntime.AddAssetOperation(m_ProvideHandle, Profiling.ContentStatus.Active);
|
||
|
#endif
|
||
|
|
||
|
Exception e = m_Result == null
|
||
|
? new Exception($"Unable to load asset of type {m_ProvideHandle.Type} from location {m_ProvideHandle.Location}.")
|
||
|
: null;
|
||
|
m_ProvideHandle.Complete(m_Result, m_Result != null, e);
|
||
|
}
|
||
|
|
||
|
public float ProgressCallback()
|
||
|
{
|
||
|
return m_RequestOperation != null ? m_RequestOperation.progress : 0.0f;
|
||
|
}
|
||
|
|
||
|
/// <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>
|
||
|
internal 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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override void Provide(ProvideHandle provideHandle)
|
||
|
{
|
||
|
new InternalOp().Start(provideHandle);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public class TTAssetBundleResource : IAssetBundleResource, IUpdateReceiver
|
||
|
{
|
||
|
public enum LoadType
|
||
|
{
|
||
|
None,
|
||
|
Local,
|
||
|
Web
|
||
|
}
|
||
|
|
||
|
AssetBundle m_AssetBundle;
|
||
|
DownloadHandlerTTAssetBundle m_downloadHandler;
|
||
|
AsyncOperation m_RequestOperation;
|
||
|
internal TTWebRequestQueueOperation m_WebRequestQueueOperation;
|
||
|
internal ProvideHandle m_ProvideHandle;
|
||
|
internal AssetBundleRequestOptions m_Options;
|
||
|
|
||
|
[NonSerialized]
|
||
|
bool m_RequestCompletedCallbackCalled = false;
|
||
|
|
||
|
int m_Retries;
|
||
|
BundleSource m_Source = BundleSource.None;
|
||
|
long m_BytesToDownload;
|
||
|
long m_DownloadedBytes;
|
||
|
bool m_Completed = false;
|
||
|
#if UNLOAD_BUNDLE_ASYNC
|
||
|
AssetBundleUnloadOperation m_UnloadOperation;
|
||
|
#endif
|
||
|
const int k_WaitForWebRequestMainThreadSleep = 1;
|
||
|
string m_TransformedInternalId;
|
||
|
AssetBundleRequest m_PreloadRequest;
|
||
|
bool m_PreloadCompleted = false;
|
||
|
ulong m_LastDownloadedByteCount = 0;
|
||
|
float m_TimeoutTimer = 0;
|
||
|
int m_TimeoutOverFrames = 0;
|
||
|
|
||
|
private bool HasTimedOut => m_TimeoutTimer >= m_Options.Timeout && m_TimeoutOverFrames > 5;
|
||
|
|
||
|
internal long BytesToDownload
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (m_BytesToDownload == -1)
|
||
|
{
|
||
|
if (m_Options != null)
|
||
|
m_BytesToDownload = m_Options.ComputeSize(m_ProvideHandle.Location, m_ProvideHandle.ResourceManager);
|
||
|
else
|
||
|
m_BytesToDownload = 0;
|
||
|
}
|
||
|
|
||
|
return m_BytesToDownload;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal UnityWebRequest CreateWebRequest(IResourceLocation loc)
|
||
|
{
|
||
|
var url = m_ProvideHandle.ResourceManager.TransformInternalId(loc);
|
||
|
return CreateWebRequest(url);
|
||
|
}
|
||
|
|
||
|
internal UnityWebRequest CreateWebRequest(string url)
|
||
|
{
|
||
|
string sanitizedUrl = Uri.UnescapeDataString(url);
|
||
|
|
||
|
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
|
||
|
Uri uri = new Uri(sanitizedUrl.Replace(" ", "%20"));
|
||
|
#else
|
||
|
Uri uri = new Uri(Uri.EscapeUriString(sanitizedUrl));
|
||
|
#endif
|
||
|
|
||
|
if (m_Options == null)
|
||
|
{
|
||
|
m_Source = BundleSource.Download;
|
||
|
#if ENABLE_ADDRESSABLE_PROFILER
|
||
|
AddBundleToProfiler(Profiling.ContentStatus.Downloading, m_Source);
|
||
|
#endif
|
||
|
return TTAssetBundle.GetAssetBundle(url);
|
||
|
}
|
||
|
|
||
|
UnityWebRequest webRequest;
|
||
|
if (!string.IsNullOrEmpty(m_Options.Hash))
|
||
|
{
|
||
|
CachedAssetBundle cachedBundle = new CachedAssetBundle(m_Options.BundleName, Hash128.Parse(m_Options.Hash));
|
||
|
#if ENABLE_CACHING
|
||
|
bool cached = Caching.IsVersionCached(cachedBundle);
|
||
|
m_Source = cached ? BundleSource.Cache : BundleSource.Download;
|
||
|
if (m_Options.UseCrcForCachedBundle || m_Source == BundleSource.Download)
|
||
|
webRequest = UnityWebRequestAssetBundle.GetAssetBundle(uri, cachedBundle, m_Options.Crc);
|
||
|
else
|
||
|
webRequest = UnityWebRequestAssetBundle.GetAssetBundle(uri, cachedBundle);
|
||
|
#else
|
||
|
webRequest = UnityWebRequestAssetBundle.GetAssetBundle(uri, cachedBundle, m_Options.Crc);
|
||
|
#endif
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_Source = BundleSource.Download;
|
||
|
webRequest = TTAssetBundle.GetAssetBundle(url, m_Options.Crc);
|
||
|
}
|
||
|
|
||
|
if (m_Options.RedirectLimit > 0)
|
||
|
webRequest.redirectLimit = m_Options.RedirectLimit;
|
||
|
if (m_ProvideHandle.ResourceManager.CertificateHandlerInstance != null)
|
||
|
{
|
||
|
webRequest.certificateHandler = m_ProvideHandle.ResourceManager.CertificateHandlerInstance;
|
||
|
webRequest.disposeCertificateHandlerOnDispose = false;
|
||
|
}
|
||
|
|
||
|
m_ProvideHandle.ResourceManager.WebRequestOverride?.Invoke(webRequest);
|
||
|
return webRequest;
|
||
|
}
|
||
|
|
||
|
public AssetBundleRequest GetAssetPreloadRequest()
|
||
|
{
|
||
|
if (m_PreloadCompleted || GetAssetBundle() == null)
|
||
|
return null;
|
||
|
|
||
|
if (m_Options.AssetLoadMode == AssetLoadMode.AllPackedAssetsAndDependencies)
|
||
|
{
|
||
|
#if !UNITY_2021_1_OR_NEWER
|
||
|
if (AsyncOperationHandle.IsWaitingForCompletion)
|
||
|
{
|
||
|
m_AssetBundle.LoadAllAssets();
|
||
|
m_PreloadCompleted = true;
|
||
|
return null;
|
||
|
}
|
||
|
#endif
|
||
|
if (m_PreloadRequest == null)
|
||
|
{
|
||
|
m_PreloadRequest = m_AssetBundle.LoadAllAssetsAsync();
|
||
|
m_PreloadRequest.completed += operation => m_PreloadCompleted = true;
|
||
|
}
|
||
|
|
||
|
return m_PreloadRequest;
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
float PercentComplete()
|
||
|
{
|
||
|
return m_RequestOperation != null ? m_RequestOperation.progress : 0.0f;
|
||
|
}
|
||
|
|
||
|
DownloadStatus GetDownloadStatus()
|
||
|
{
|
||
|
if (m_Options == null)
|
||
|
return default;
|
||
|
var status = new DownloadStatus() {TotalBytes = BytesToDownload, IsDone = PercentComplete() >= 1f};
|
||
|
if (BytesToDownload > 0)
|
||
|
{
|
||
|
if (m_WebRequestQueueOperation != null && string.IsNullOrEmpty(m_WebRequestQueueOperation.m_WebRequest.error))
|
||
|
m_DownloadedBytes = (long)(m_WebRequestQueueOperation.m_WebRequest.downloadedBytes);
|
||
|
else if (m_RequestOperation != null && m_RequestOperation is UnityWebRequestAsyncOperation operation && string.IsNullOrEmpty(operation.webRequest.error))
|
||
|
m_DownloadedBytes = (long)operation.webRequest.downloadedBytes;
|
||
|
}
|
||
|
|
||
|
status.DownloadedBytes = m_DownloadedBytes;
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
public AssetBundle GetAssetBundle()
|
||
|
{
|
||
|
if (m_AssetBundle == null)
|
||
|
{
|
||
|
if (m_downloadHandler != null)
|
||
|
{
|
||
|
m_AssetBundle = m_downloadHandler.assetBundle;
|
||
|
m_downloadHandler.Dispose();
|
||
|
m_downloadHandler = null;
|
||
|
} else if (m_RequestOperation is TTAssetBundleRequest r)
|
||
|
{
|
||
|
m_AssetBundle = r.assetBundle;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return m_AssetBundle;
|
||
|
}
|
||
|
|
||
|
#if ENABLE_ADDRESSABLE_PROFILER
|
||
|
private void AddBundleToProfiler(Profiling.ContentStatus status, BundleSource source)
|
||
|
{
|
||
|
if (!Profiler.enabled)
|
||
|
return;
|
||
|
// if (!m_ProvideHandle.IsValid)
|
||
|
// return;
|
||
|
|
||
|
if (status == Profiling.ContentStatus.Active && m_AssetBundle == null)
|
||
|
Profiling.ProfilerRuntime.BundleReleased(m_Options.BundleName);
|
||
|
else
|
||
|
Profiling.ProfilerRuntime.AddBundleOperation(m_ProvideHandle, m_Options, status, source);
|
||
|
}
|
||
|
|
||
|
private void RemoveBundleFromProfiler()
|
||
|
{
|
||
|
if (m_Options == null)
|
||
|
return;
|
||
|
Profiling.ProfilerRuntime.BundleReleased(m_Options.BundleName);
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
#if UNLOAD_BUNDLE_ASYNC
|
||
|
void OnUnloadOperationComplete(AsyncOperation op)
|
||
|
{
|
||
|
m_UnloadOperation = null;
|
||
|
BeginOperation();
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
#if UNLOAD_BUNDLE_ASYNC
|
||
|
public void Start(ProvideHandle provideHandle, AssetBundleUnloadOperation unloadOp)
|
||
|
#else
|
||
|
public void Start(ProvideHandle provideHandle)
|
||
|
#endif
|
||
|
{
|
||
|
m_Retries = 0;
|
||
|
m_AssetBundle = null;
|
||
|
m_RequestOperation = null;
|
||
|
m_ProvideHandle = provideHandle;
|
||
|
m_Options = m_ProvideHandle.Location.Data as AssetBundleRequestOptions;
|
||
|
m_BytesToDownload = -1;
|
||
|
m_ProvideHandle.SetProgressCallback(PercentComplete);
|
||
|
m_ProvideHandle.SetDownloadProgressCallbacks(GetDownloadStatus);
|
||
|
m_ProvideHandle.SetWaitForCompletionCallback(WaitForCompletionHandler);
|
||
|
#if UNLOAD_BUNDLE_ASYNC
|
||
|
m_UnloadOperation = unloadOp;
|
||
|
if (m_UnloadOperation != null && !m_UnloadOperation.isDone)
|
||
|
m_UnloadOperation.completed += OnUnloadOperationComplete;
|
||
|
else
|
||
|
#endif
|
||
|
BeginOperation();
|
||
|
}
|
||
|
|
||
|
private bool WaitForCompletionHandler()
|
||
|
{
|
||
|
#if UNLOAD_BUNDLE_ASYNC
|
||
|
if (m_UnloadOperation != null && !m_UnloadOperation.isDone)
|
||
|
{
|
||
|
m_UnloadOperation.completed -= OnUnloadOperationComplete;
|
||
|
m_UnloadOperation.WaitForCompletion();
|
||
|
m_UnloadOperation = null;
|
||
|
BeginOperation();
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if (m_RequestOperation == null)
|
||
|
{
|
||
|
if (m_WebRequestQueueOperation == null)
|
||
|
return false;
|
||
|
else
|
||
|
WebRequestQueue.WaitForRequestToBeActive(m_WebRequestQueueOperation, k_WaitForWebRequestMainThreadSleep);
|
||
|
}
|
||
|
|
||
|
//We don't want to wait for request op to complete if it's a LoadFromFileAsync. Only UWR will complete in a tight loop like this.
|
||
|
if (m_RequestOperation is UnityWebRequestAsyncOperation op)
|
||
|
{
|
||
|
while (!TTUnityWebRequestUtilities.IsAssetBundleDownloaded(op))
|
||
|
Thread.Sleep(k_WaitForWebRequestMainThreadSleep);
|
||
|
#if ENABLE_ASYNC_ASSETBUNDLE_UWR
|
||
|
if (m_Source == BundleSource.Cache)
|
||
|
{
|
||
|
var downloadHandler = (DownloadHandlerTTAssetBundle)op?.webRequest?.downloadHandler;
|
||
|
if (downloadHandler.autoLoadAssetBundle)
|
||
|
m_AssetBundle = downloadHandler.assetBundle;
|
||
|
}
|
||
|
#endif
|
||
|
WebRequestQueue.DequeueRequest(op);
|
||
|
|
||
|
if (!m_RequestCompletedCallbackCalled)
|
||
|
{
|
||
|
m_RequestOperation.completed -= WebRequestOperationCompleted;
|
||
|
WebRequestOperationCompleted(m_RequestOperation);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!m_Completed && m_Source == BundleSource.Local)
|
||
|
{
|
||
|
// we don't have to check for done with local files as calling
|
||
|
// m_requestOperation.assetBundle is blocking and will wait for the file to load
|
||
|
if (!m_RequestCompletedCallbackCalled)
|
||
|
{
|
||
|
m_RequestOperation.completed -= LocalRequestOperationCompleted;
|
||
|
LocalRequestOperationCompleted(m_RequestOperation);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!m_Completed && m_RequestOperation.isDone)
|
||
|
{
|
||
|
m_ProvideHandle.Complete(this, m_AssetBundle != null, null);
|
||
|
m_Completed = true;
|
||
|
}
|
||
|
|
||
|
return m_Completed;
|
||
|
}
|
||
|
|
||
|
void AddCallbackInvokeIfDone(AsyncOperation operation, Action<AsyncOperation> callback)
|
||
|
{
|
||
|
if (operation.isDone)
|
||
|
callback(operation);
|
||
|
else
|
||
|
operation.completed += callback;
|
||
|
}
|
||
|
|
||
|
public static void GetLoadInfo(ProvideHandle handle, out LoadType loadType, out string path)
|
||
|
{
|
||
|
GetLoadInfo(handle.Location, handle.ResourceManager, out loadType, out path);
|
||
|
}
|
||
|
|
||
|
internal static void GetLoadInfo(IResourceLocation location, ResourceManager resourceManager, out LoadType loadType, out string path)
|
||
|
{
|
||
|
var options = location?.Data as AssetBundleRequestOptions;
|
||
|
if (options == null)
|
||
|
{
|
||
|
loadType = LoadType.None;
|
||
|
path = null;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
path = resourceManager.TransformInternalId(location);
|
||
|
if (Application.platform == RuntimePlatform.Android && path.StartsWith("jar:", StringComparison.Ordinal))
|
||
|
loadType = options.UseUnityWebRequestForLocalBundles ? LoadType.Web : LoadType.Local;
|
||
|
else if (ResourceManagerConfig.ShouldPathUseWebRequest(path))
|
||
|
loadType = LoadType.Web;
|
||
|
else if (options.UseUnityWebRequestForLocalBundles)
|
||
|
{
|
||
|
path = "file:///" + Path.GetFullPath(path);
|
||
|
loadType = LoadType.Web;
|
||
|
}
|
||
|
else
|
||
|
loadType = LoadType.Local;
|
||
|
|
||
|
if (loadType == LoadType.Web)
|
||
|
path = path.Replace('\\', '/');
|
||
|
}
|
||
|
|
||
|
private void BeginOperation()
|
||
|
{
|
||
|
// retrying a failed request will call BeginOperation multiple times. Any member variables
|
||
|
// should be reset at the beginning of the operation
|
||
|
m_DownloadedBytes = 0;
|
||
|
m_RequestCompletedCallbackCalled = false;
|
||
|
GetLoadInfo(m_ProvideHandle, out LoadType loadType, out m_TransformedInternalId);
|
||
|
|
||
|
if (loadType == LoadType.Local)
|
||
|
{
|
||
|
LoadLocalBundle();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (loadType == LoadType.Web)
|
||
|
{
|
||
|
m_WebRequestQueueOperation = EnqueueWebRequest(m_TransformedInternalId);
|
||
|
AddBeginWebRequestHandler(m_WebRequestQueueOperation);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
m_Source = BundleSource.None;
|
||
|
m_RequestOperation = null;
|
||
|
m_ProvideHandle.Complete<TTAssetBundleResource>(null, false,
|
||
|
new RemoteProviderException(string.Format("Invalid path in AssetBundleProvider: '{0}'.", m_TransformedInternalId), m_ProvideHandle.Location));
|
||
|
m_Completed = true;
|
||
|
}
|
||
|
|
||
|
private void LoadLocalBundle()
|
||
|
{
|
||
|
m_Source = BundleSource.Local;
|
||
|
#if !UNITY_2021_1_OR_NEWER
|
||
|
if (AsyncOperationHandle.IsWaitingForCompletion)
|
||
|
CompleteBundleLoad(AssetBundle.LoadFromFile(m_TransformedInternalId, m_Options == null ? 0 : m_Options.Crc));
|
||
|
else
|
||
|
#endif
|
||
|
{
|
||
|
m_RequestOperation = AssetBundle.LoadFromFileAsync(m_TransformedInternalId, m_Options == null ? 0 : m_Options.Crc);
|
||
|
#if ENABLE_ADDRESSABLE_PROFILER
|
||
|
AddBundleToProfiler(Profiling.ContentStatus.Loading, m_Source);
|
||
|
#endif
|
||
|
AddCallbackInvokeIfDone(m_RequestOperation, LocalRequestOperationCompleted);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal TTWebRequestQueueOperation EnqueueWebRequest(string internalId)
|
||
|
{
|
||
|
var req = CreateWebRequest(internalId);
|
||
|
#if ENABLE_ASYNC_ASSETBUNDLE_UWR
|
||
|
((DownloadHandlerAssetBundle)req.downloadHandler).autoLoadAssetBundle = true;
|
||
|
#endif
|
||
|
req.disposeDownloadHandlerOnDispose = false;
|
||
|
|
||
|
return WebRequestQueue.QueueRequest(req);
|
||
|
}
|
||
|
|
||
|
internal void AddBeginWebRequestHandler(TTWebRequestQueueOperation webRequestQueueOperation)
|
||
|
{
|
||
|
if (webRequestQueueOperation.IsDone)
|
||
|
{
|
||
|
BeginWebRequestOperation(webRequestQueueOperation.Result);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
#if ENABLE_ADDRESSABLE_PROFILER
|
||
|
AddBundleToProfiler(Profiling.ContentStatus.Queue, m_Source);
|
||
|
#endif
|
||
|
webRequestQueueOperation.OnComplete += asyncOp => BeginWebRequestOperation(asyncOp);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void BeginWebRequestOperation(AsyncOperation asyncOp)
|
||
|
{
|
||
|
m_TimeoutTimer = 0;
|
||
|
m_TimeoutOverFrames = 0;
|
||
|
m_LastDownloadedByteCount = 0;
|
||
|
m_RequestOperation = asyncOp;
|
||
|
if (m_RequestOperation == null || m_RequestOperation.isDone)
|
||
|
WebRequestOperationCompleted(m_RequestOperation);
|
||
|
else
|
||
|
{
|
||
|
if (m_Options.Timeout > 0)
|
||
|
m_ProvideHandle.ResourceManager.AddUpdateReceiver(this);
|
||
|
#if ENABLE_ADDRESSABLE_PROFILER
|
||
|
AddBundleToProfiler(m_Source == BundleSource.Cache ? Profiling.ContentStatus.Loading : Profiling.ContentStatus.Downloading, m_Source);
|
||
|
#endif
|
||
|
m_RequestOperation.completed += WebRequestOperationCompleted;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void Update(float unscaledDeltaTime)
|
||
|
{
|
||
|
if (m_RequestOperation != null && m_RequestOperation is UnityWebRequestAsyncOperation operation && !operation.isDone)
|
||
|
{
|
||
|
if (m_LastDownloadedByteCount != operation.webRequest.downloadedBytes)
|
||
|
{
|
||
|
m_TimeoutTimer = 0;
|
||
|
m_TimeoutOverFrames = 0;
|
||
|
m_LastDownloadedByteCount = operation.webRequest.downloadedBytes;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_TimeoutTimer += unscaledDeltaTime;
|
||
|
if (HasTimedOut)
|
||
|
operation.webRequest.Abort();
|
||
|
m_TimeoutOverFrames++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void LocalRequestOperationCompleted(AsyncOperation op)
|
||
|
{
|
||
|
if (m_RequestCompletedCallbackCalled)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
m_RequestCompletedCallbackCalled = true;
|
||
|
CompleteBundleLoad((op as TTAssetBundleRequest).assetBundle);
|
||
|
}
|
||
|
|
||
|
private void CompleteBundleLoad(AssetBundle bundle)
|
||
|
{
|
||
|
m_AssetBundle = bundle;
|
||
|
#if ENABLE_ADDRESSABLE_PROFILER
|
||
|
AddBundleToProfiler(Profiling.ContentStatus.Active, m_Source);
|
||
|
#endif
|
||
|
if (m_AssetBundle != null)
|
||
|
m_ProvideHandle.Complete(this, true, null);
|
||
|
else
|
||
|
m_ProvideHandle.Complete<TTAssetBundleResource>(null, false,
|
||
|
new RemoteProviderException(string.Format("Invalid path in AssetBundleProvider: '{0}'.", m_TransformedInternalId), m_ProvideHandle.Location));
|
||
|
m_Completed = true;
|
||
|
}
|
||
|
|
||
|
private void WebRequestOperationCompleted(AsyncOperation op)
|
||
|
{
|
||
|
if (m_RequestCompletedCallbackCalled)
|
||
|
return;
|
||
|
|
||
|
m_RequestCompletedCallbackCalled = true;
|
||
|
|
||
|
if (m_Options.Timeout > 0)
|
||
|
m_ProvideHandle.ResourceManager.RemoveUpdateReciever(this);
|
||
|
|
||
|
UnityWebRequestAsyncOperation remoteReq = op as UnityWebRequestAsyncOperation;
|
||
|
var webReq = remoteReq?.webRequest;
|
||
|
var downloadHandler = webReq?.downloadHandler as DownloadHandlerTTAssetBundle;
|
||
|
TTUnityWebRequestResult uwrResult = null;
|
||
|
if (webReq != null && !TTUnityWebRequestUtilities.RequestHasErrors(webReq, out uwrResult))
|
||
|
{
|
||
|
if (!m_Completed)
|
||
|
{
|
||
|
#if ENABLE_ADDRESSABLE_PROFILER
|
||
|
AddBundleToProfiler(Profiling.ContentStatus.Active, m_Source);
|
||
|
#endif
|
||
|
m_AssetBundle = downloadHandler.assetBundle;
|
||
|
downloadHandler.Dispose();
|
||
|
downloadHandler = null;
|
||
|
m_ProvideHandle.Complete(this, true, null);
|
||
|
m_Completed = true;
|
||
|
}
|
||
|
#if ENABLE_CACHING
|
||
|
if (!string.IsNullOrEmpty(m_Options.Hash) && m_Options.ClearOtherCachedVersionsWhenLoaded)
|
||
|
Caching.ClearOtherCachedVersions(m_Options.BundleName, Hash128.Parse(m_Options.Hash));
|
||
|
#endif
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (HasTimedOut)
|
||
|
uwrResult.Error = "Request timeout";
|
||
|
webReq = m_WebRequestQueueOperation.m_WebRequest;
|
||
|
if (uwrResult == null)
|
||
|
uwrResult = new TTUnityWebRequestResult(m_WebRequestQueueOperation.m_WebRequest);
|
||
|
|
||
|
downloadHandler = webReq.downloadHandler as DownloadHandlerTTAssetBundle;
|
||
|
downloadHandler.Dispose();
|
||
|
downloadHandler = null;
|
||
|
bool forcedRetry = false;
|
||
|
string message = $"Web request failed, retrying ({m_Retries}/{m_Options.RetryCount})...\n{uwrResult}";
|
||
|
#if ENABLE_CACHING
|
||
|
if (!string.IsNullOrEmpty(m_Options.Hash))
|
||
|
{
|
||
|
#if ENABLE_ADDRESSABLE_PROFILER
|
||
|
if (m_Source == BundleSource.Cache)
|
||
|
#endif
|
||
|
{
|
||
|
message = $"Web request failed to load from cache. The cached AssetBundle will be cleared from the cache and re-downloaded. Retrying...\n{uwrResult}";
|
||
|
Caching.ClearCachedVersion(m_Options.BundleName, Hash128.Parse(m_Options.Hash));
|
||
|
// When attempted to load from cache we always retry on first attempt and failed
|
||
|
if (m_Retries == 0 && uwrResult.ShouldRetryDownloadError())
|
||
|
{
|
||
|
Debug.LogFormat(message);
|
||
|
BeginOperation();
|
||
|
m_Retries++; //Will prevent us from entering an infinite loop of retrying if retry count is 0
|
||
|
forcedRetry = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
if (!forcedRetry)
|
||
|
{
|
||
|
if (m_Retries < m_Options.RetryCount && uwrResult.ShouldRetryDownloadError())
|
||
|
{
|
||
|
m_Retries++;
|
||
|
Debug.LogFormat(message);
|
||
|
BeginOperation();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var exception = new TTRemoteProviderException($"Unable to load asset bundle from : {webReq.url}", m_ProvideHandle.Location, uwrResult);
|
||
|
m_ProvideHandle.Complete<TTAssetBundleResource>(null, false, exception);
|
||
|
m_Completed = true;
|
||
|
#if ENABLE_ADDRESSABLE_PROFILER
|
||
|
RemoveBundleFromProfiler();
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
webReq.Dispose();
|
||
|
}
|
||
|
|
||
|
#if UNLOAD_BUNDLE_ASYNC
|
||
|
public bool Unload(out AssetBundleUnloadOperation unloadOp)
|
||
|
#else
|
||
|
public void Unload()
|
||
|
#endif
|
||
|
{
|
||
|
#if UNLOAD_BUNDLE_ASYNC
|
||
|
unloadOp = null;
|
||
|
if (m_AssetBundle != null)
|
||
|
{
|
||
|
unloadOp = m_AssetBundle.UnloadAsync(true);
|
||
|
m_AssetBundle = null;
|
||
|
}
|
||
|
#else
|
||
|
if (m_AssetBundle != null)
|
||
|
{
|
||
|
m_AssetBundle.TTUnload(true);
|
||
|
m_AssetBundle = null;
|
||
|
}
|
||
|
#endif
|
||
|
m_RequestOperation = null;
|
||
|
#if ENABLE_ADDRESSABLE_PROFILER
|
||
|
RemoveBundleFromProfiler();
|
||
|
#endif
|
||
|
#if UNLOAD_BUNDLE_ASYNC
|
||
|
return unloadOp != null;
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[DisplayName("TTAssetBundle Provider")]
|
||
|
public class TTAssetBundleProvider : ResourceProviderBase
|
||
|
{
|
||
|
#if UNLOAD_BUNDLE_ASYNC
|
||
|
internal static Dictionary<string, AssetBundleUnloadOperation> m_UnloadingBundles = new Dictionary<string, AssetBundleUnloadOperation>();
|
||
|
|
||
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||
|
private static void Init()
|
||
|
{
|
||
|
m_UnloadingBundles = new Dictionary<string, AssetBundleUnloadOperation>();
|
||
|
}
|
||
|
|
||
|
protected internal static Dictionary<string, AssetBundleUnloadOperation> UnloadingBundles
|
||
|
{
|
||
|
get { return m_UnloadingBundles; }
|
||
|
internal set { m_UnloadingBundles = value; }
|
||
|
}
|
||
|
|
||
|
internal static int UnloadingAssetBundleCount => m_UnloadingBundles.Count;
|
||
|
internal static int AssetBundleCount => AssetBundle.GetAllLoadedAssetBundles().Count() - UnloadingAssetBundleCount;
|
||
|
internal static void WaitForAllUnloadingBundlesToComplete()
|
||
|
{
|
||
|
if (UnloadingAssetBundleCount > 0)
|
||
|
{
|
||
|
var bundles = m_UnloadingBundles.Values.ToArray();
|
||
|
foreach (var b in bundles)
|
||
|
b.WaitForCompletion();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#else
|
||
|
internal static void WaitForAllUnloadingBundlesToComplete()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
public override void Provide(ProvideHandle providerInterface)
|
||
|
{
|
||
|
#if UNLOAD_BUNDLE_ASYNC
|
||
|
if (m_UnloadingBundles.TryGetValue(providerInterface.Location.InternalId, out var unloadOp))
|
||
|
{
|
||
|
if (unloadOp.isDone)
|
||
|
unloadOp = null;
|
||
|
}
|
||
|
new TTAssetBundleResource().Start(providerInterface, unloadOp);
|
||
|
#else
|
||
|
new TTAssetBundleResource().Start(providerInterface);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
public override Type GetDefaultType(IResourceLocation location)
|
||
|
{
|
||
|
return typeof(IAssetBundleResource);
|
||
|
}
|
||
|
|
||
|
public override void Release(IResourceLocation location, object asset)
|
||
|
{
|
||
|
if (location == null)
|
||
|
throw new ArgumentNullException("location");
|
||
|
if (asset == null)
|
||
|
{
|
||
|
Debug.LogWarningFormat("Releasing null asset bundle from location {0}. This is an indication that the bundle failed to load.", location);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var bundle = asset as TTAssetBundleResource;
|
||
|
if (bundle != null)
|
||
|
{
|
||
|
#if UNLOAD_BUNDLE_ASYNC
|
||
|
if (bundle.Unload(out var unloadOp))
|
||
|
{
|
||
|
m_UnloadingBundles.Add(location.InternalId, unloadOp);
|
||
|
unloadOp.completed += op => m_UnloadingBundles.Remove(location.InternalId);
|
||
|
}
|
||
|
#else
|
||
|
bundle.Unload();
|
||
|
#endif
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
namespace UnityEngine.ResourceManagement.Exceptions
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Class representing an error occured during an operation that remotely fetch data.
|
||
|
/// </summary>
|
||
|
public class TTRemoteProviderException : 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 TTRemoteProviderException(string message, IResourceLocation location = null,
|
||
|
TTUnityWebRequestResult uwrResult = null, Exception innerException = null)
|
||
|
: base(message, location, innerException)
|
||
|
{
|
||
|
WebRequestResult = uwrResult;
|
||
|
}
|
||
|
|
||
|
///<inheritdoc/>
|
||
|
public override string Message => this.ToString();
|
||
|
|
||
|
/// <summary>
|
||
|
/// The result of the unity web request, if any.
|
||
|
/// </summary>
|
||
|
public TTUnityWebRequestResult 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();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
namespace UnityEngine.ResourceManagement.Util
|
||
|
{
|
||
|
internal class TTUnityWebRequestUtilities
|
||
|
{
|
||
|
public static bool RequestHasErrors(UnityWebRequest webReq, out TTUnityWebRequestResult 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 TTUnityWebRequestResult(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
|
||
|
}
|
||
|
|
||
|
internal 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 TTUnityWebRequestResult
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Creates a new instance of <see cref="UnityWebRequestResult"/>.
|
||
|
/// </summary>
|
||
|
/// <param name="request">The unity web request.</param>
|
||
|
public TTUnityWebRequestResult(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 System.Text.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; internal 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; }
|
||
|
|
||
|
internal 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"
|
||
|
)
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|