using System; using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine.Networking; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.ResourceLocations; using UnityEngine.ResourceManagement.Util; using UnityEngine.Serialization; namespace UnityEngine.ResourceManagement.ResourceProviders { /// /// Wrapper for asset bundles. /// public interface IAssetBundleResource { AssetBundle GetAssetBundle(); } /// /// Contains cache information to be used by the AssetBundleProvider /// [Serializable] public class AssetBundleRequestOptions : ILocationSizeData { [FormerlySerializedAs("m_hash")] [SerializeField] string m_Hash = ""; /// /// Hash value of the asset bundle. /// public string Hash { get { return m_Hash; } set { m_Hash = value; } } [FormerlySerializedAs("m_crc")] [SerializeField] uint m_Crc; /// /// CRC value of the bundle. /// public uint Crc { get { return m_Crc; } set { m_Crc = value; } } [FormerlySerializedAs("m_timeout")] [SerializeField] int m_Timeout; /// /// Sets UnityWebRequest to attempt to abort after the number of seconds in timeout have passed. /// public int Timeout { get { return m_Timeout; } set { m_Timeout = value; } } [FormerlySerializedAs("m_chunkedTransfer")] [SerializeField] bool m_ChunkedTransfer; /// /// Indicates whether the UnityWebRequest system should employ the HTTP/1.1 chunked-transfer encoding method. /// public bool ChunkedTransfer { get { return m_ChunkedTransfer; } set { m_ChunkedTransfer = value; } } [FormerlySerializedAs("m_redirectLimit")] [SerializeField] int m_RedirectLimit = -1; /// /// Indicates the number of redirects which this UnityWebRequest will follow before halting with a “Redirect Limit Exceeded” system error. /// public int RedirectLimit { get { return m_RedirectLimit; } set { m_RedirectLimit = value; } } [FormerlySerializedAs("m_retryCount")] [SerializeField] int m_RetryCount; /// /// Indicates the number of times the request will be retried. /// public int RetryCount { get { return m_RetryCount; } set { m_RetryCount = value; } } [SerializeField] string m_BundleName = null; /// /// The name of the original bundle. This does not contain the appended hash. /// public string BundleName { get { return m_BundleName; } set { m_BundleName = value; } } [SerializeField] long m_BundleSize; /// /// The size of the bundle, in bytes. /// public long BundleSize { get { return m_BundleSize; } set { m_BundleSize = value; } } /// /// Computes the amount of data needed to be downloaded for this bundle. /// /// The location of the bundle. /// The size in bytes of the bundle that is needed to be downloaded. If the local cache contains the bundle or it is a local bundle, 0 will be returned. public virtual long ComputeSize(IResourceLocation loc) { if (!ResourceManagerConfig.IsPathRemote(loc.InternalId)) return 0; var locHash = Hash128.Parse(Hash); #if !UNITY_SWITCH && !UNITY_PS4 var bundleName = Path.GetFileNameWithoutExtension(loc.InternalId); if (locHash.isValid) //If we have a hash, ensure that our desired version is cached. { if (Caching.IsVersionCached(new CachedAssetBundle(bundleName, locHash))) return 0; return BundleSize; } else //If we don't have a hash, any cached version will do. { List versions = new List(); Caching.GetCachedVersions(bundleName, versions); if (versions.Count > 0) return 0; } #endif //!UNITY_SWITCH && !UNITY_PS4 return BundleSize; } } class AssetBundleResource : IAssetBundleResource { AssetBundle m_AssetBundle; DownloadHandlerAssetBundle m_downloadHandler; AsyncOperation m_RequestOperation; ProvideHandle m_ProvideHandle; AssetBundleRequestOptions m_Options; int m_Retries; UnityWebRequest CreateWebRequest(IResourceLocation loc) { if (m_Options == null) return UnityWebRequestAssetBundle.GetAssetBundle(loc.InternalId); var webRequest = !string.IsNullOrEmpty(m_Options.Hash) ? UnityWebRequestAssetBundle.GetAssetBundle(loc.InternalId, Hash128.Parse(m_Options.Hash), m_Options.Crc) : UnityWebRequestAssetBundle.GetAssetBundle(loc.InternalId, m_Options.Crc); if (m_Options.Timeout > 0) webRequest.timeout = m_Options.Timeout; if (m_Options.RedirectLimit > 0) webRequest.redirectLimit = m_Options.RedirectLimit; #if !UNITY_2019_3_OR_NEWER webRequest.chunkedTransfer = m_Options.ChunkedTransfer; #endif if (m_ProvideHandle.ResourceManager.CertificateHandlerInstance != null) { webRequest.certificateHandler = m_ProvideHandle.ResourceManager.CertificateHandlerInstance; webRequest.disposeCertificateHandlerOnDispose = false; } return webRequest; } float PercentComplete() { return m_RequestOperation != null ? m_RequestOperation.progress : 0.0f; } /// /// Get the asset bundle object managed by this resource. This call may force the bundle to load if not already loaded. /// /// The asset bundle. public AssetBundle GetAssetBundle() { if (m_AssetBundle == null && m_downloadHandler != null) { m_AssetBundle = m_downloadHandler.assetBundle; m_downloadHandler.Dispose(); m_downloadHandler = null; } return m_AssetBundle; } internal void Start(ProvideHandle provideHandle) { m_Retries = 0; m_AssetBundle = null; m_downloadHandler = null; m_ProvideHandle = provideHandle; m_Options = m_ProvideHandle.Location.Data as AssetBundleRequestOptions; m_RequestOperation = null; BeginOperation(); provideHandle.SetProgressCallback(PercentComplete); } private void BeginOperation() { string path = m_ProvideHandle.Location.InternalId; if (File.Exists(path)) { m_RequestOperation = AssetBundle.LoadFromFileAsync(path, m_Options == null ? 0 : m_Options.Crc); m_RequestOperation.completed += LocalRequestOperationCompleted; } else if (ResourceManagerConfig.ShouldPathUseWebRequest(path)) { var req = CreateWebRequest(m_ProvideHandle.Location); req.disposeDownloadHandlerOnDispose = false; m_RequestOperation = req.SendWebRequest(); m_RequestOperation.completed += WebRequestOperationCompleted; } else { m_RequestOperation = null; m_ProvideHandle.Complete(null, false, new Exception(string.Format("Invalid path in AssetBundleProvider: '{0}'.", path))); } } private void LocalRequestOperationCompleted(AsyncOperation op) { m_AssetBundle = (op as AssetBundleCreateRequest).assetBundle; m_ProvideHandle.Complete(this, m_AssetBundle != null, null); } private void WebRequestOperationCompleted(AsyncOperation op) { UnityWebRequestAsyncOperation remoteReq = op as UnityWebRequestAsyncOperation; var webReq = remoteReq.webRequest; if (string.IsNullOrEmpty(webReq.error)) { m_downloadHandler = webReq.downloadHandler as DownloadHandlerAssetBundle; m_ProvideHandle.Complete(this, true, null); } else { m_downloadHandler = webReq.downloadHandler as DownloadHandlerAssetBundle; m_downloadHandler.Dispose(); m_downloadHandler = null; if (m_Retries++ < m_Options.RetryCount) { Debug.LogFormat("Web request {0} failed with error '{1}', retrying ({2}/{3})...", webReq.url, webReq.error, m_Retries, m_Options.RetryCount); BeginOperation(); } else { var exception = new Exception(string.Format("RemoteAssetBundleProvider unable to load from url {0}, result='{1}'.", webReq.url, webReq.error)); m_ProvideHandle.Complete(null, false, exception); } } webReq.Dispose(); } /// /// Unloads all resources associated with this asset bundle. /// public void Unload() { if (m_AssetBundle != null) { m_AssetBundle.Unload(true); m_AssetBundle = null; } if (m_downloadHandler != null) { m_downloadHandler.Dispose(); m_downloadHandler = null; } m_RequestOperation = null; } } /// /// IResourceProvider for asset bundles. Loads bundles via UnityWebRequestAssetBundle API if the internalId starts with "http". If not, it will load the bundle via AssetBundle.LoadFromFileAsync. /// public class AssetBundleProvider : ResourceProviderBase { /// public override void Provide(ProvideHandle providerInterface) { new AssetBundleResource().Start(providerInterface); } /// public override Type GetDefaultType(IResourceLocation location) { return typeof(IAssetBundleResource); } /// /// Releases the asset bundle via AssetBundle.Unload(true). /// /// /// /// 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 AssetBundleResource; if (bundle != null) { bundle.Unload(); return; } return; } } }