mirror of
https://github.com/tuyoogame/YooAsset.git
synced 2026-05-25 18:20:15 +00:00
Initial commit
This commit is contained in:
8
Assets/YooAsset/Runtime/PatchSystem/Download.meta
Normal file
8
Assets/YooAsset/Runtime/PatchSystem/Download.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 715017c3b0bb2a3429d8f021b0d9a2b2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
146
Assets/YooAsset/Runtime/PatchSystem/Download/DownloadSystem.cs
Normal file
146
Assets/YooAsset/Runtime/PatchSystem/Download/DownloadSystem.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using YooAsset.Utility;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// 1. 保证每一时刻资源文件只存在一个下载器
|
||||
/// 2. 保证下载器下载完成后立刻验证并缓存
|
||||
/// 3. 保证资源文件不会被重复下载
|
||||
/// </summary>
|
||||
internal static class DownloadSystem
|
||||
{
|
||||
private static readonly Dictionary<string, FileDownloader> _downloaderDic = new Dictionary<string, FileDownloader>();
|
||||
private static readonly List<string> _removeList = new List<string>(100);
|
||||
private static readonly Dictionary<string, string> _cachedHashList = new Dictionary<string, string>(1000);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 更新所有下载器
|
||||
/// </summary>
|
||||
public static void Update()
|
||||
{
|
||||
// 更新下载器
|
||||
_removeList.Clear();
|
||||
foreach (var valuePair in _downloaderDic)
|
||||
{
|
||||
var downloader = valuePair.Value;
|
||||
downloader.Update();
|
||||
if (downloader.IsDone())
|
||||
_removeList.Add(valuePair.Key);
|
||||
}
|
||||
|
||||
// 移除下载器
|
||||
foreach (var key in _removeList)
|
||||
{
|
||||
_downloaderDic.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始下载资源文件
|
||||
/// 注意:只有第一次请求的参数才是有效的
|
||||
/// </summary>
|
||||
public static FileDownloader BeginDownload(AssetBundleInfo bundleInfo, int failedTryAgain, int timeout = 60)
|
||||
{
|
||||
// 查询存在的下载器
|
||||
if (_downloaderDic.TryGetValue(bundleInfo.Hash, out var downloader))
|
||||
{
|
||||
return downloader;
|
||||
}
|
||||
|
||||
// 如果资源已经缓存
|
||||
if(ContainsVerifyFile(bundleInfo.Hash))
|
||||
{
|
||||
var newDownloader = new FileDownloader(bundleInfo);
|
||||
newDownloader.SetDone();
|
||||
return newDownloader;
|
||||
}
|
||||
|
||||
// 创建新的下载器
|
||||
{
|
||||
Logger.Log($"Beginning to download file : {bundleInfo.BundleName} URL : {bundleInfo.RemoteMainURL}");
|
||||
FileUtility.CreateFileDirectory(bundleInfo.LocalPath);
|
||||
var newDownloader = new FileDownloader(bundleInfo);
|
||||
newDownloader.SendRequest(failedTryAgain, timeout);
|
||||
_downloaderDic.Add(bundleInfo.Hash, newDownloader);
|
||||
return newDownloader;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取下载器的总数
|
||||
/// </summary>
|
||||
public static int GetDownloaderTotalCount()
|
||||
{
|
||||
return _downloaderDic.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询是否为验证文件
|
||||
/// 注意:被收录的文件完整性是绝对有效的
|
||||
/// </summary>
|
||||
public static bool ContainsVerifyFile(string hash)
|
||||
{
|
||||
if (_cachedHashList.ContainsKey(hash))
|
||||
{
|
||||
string filePath = PatchHelper.MakeSandboxCacheFilePath(hash);
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
string bundleName = _cachedHashList[hash];
|
||||
_cachedHashList.Remove(hash);
|
||||
Logger.Error($"Cache file is missing : {bundleName} Hash : {hash}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 缓存验证过的文件
|
||||
/// </summary>
|
||||
public static void CacheVerifyFile(string hash, string bundleName)
|
||||
{
|
||||
if (_cachedHashList.ContainsKey(hash) == false)
|
||||
{
|
||||
Logger.Log($"Cache verify file : {bundleName} Hash : {hash}");
|
||||
_cachedHashList.Add(hash, bundleName);
|
||||
}
|
||||
}
|
||||
|
||||
// 验证文件完整性
|
||||
public static bool CheckContentIntegrity(AssetBundleInfo bundleInfo)
|
||||
{
|
||||
return CheckContentIntegrity(bundleInfo.LocalPath, bundleInfo.SizeBytes, bundleInfo.CRC);
|
||||
}
|
||||
public static bool CheckContentIntegrity(PatchBundle patchBundle)
|
||||
{
|
||||
string filePath = PatchHelper.MakeSandboxCacheFilePath(patchBundle.Hash);
|
||||
return CheckContentIntegrity(filePath, patchBundle.SizeBytes, patchBundle.CRC);
|
||||
}
|
||||
public static bool CheckContentIntegrity(string filePath, long size, string crc)
|
||||
{
|
||||
if (File.Exists(filePath) == false)
|
||||
return false;
|
||||
|
||||
// 先验证文件大小
|
||||
long fileSize = FileUtility.GetFileSize(filePath);
|
||||
if (fileSize != size)
|
||||
return false;
|
||||
|
||||
// 再验证文件CRC
|
||||
string fileCRC = HashUtility.FileCRC32(filePath);
|
||||
return fileCRC == crc;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d19eae1f43ecf6347925a06730a6c846
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// 下载状态
|
||||
/// </summary>
|
||||
public enum EDownloaderStates
|
||||
{
|
||||
None,
|
||||
Loading,
|
||||
Failed,
|
||||
Succeed,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb052d4c6ef18f54d9407cb48eafdfb9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
234
Assets/YooAsset/Runtime/PatchSystem/Download/FileDownloader.cs
Normal file
234
Assets/YooAsset/Runtime/PatchSystem/Download/FileDownloader.cs
Normal file
@@ -0,0 +1,234 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
internal class FileDownloader
|
||||
{
|
||||
private enum ESteps
|
||||
{
|
||||
None,
|
||||
CreateDownload,
|
||||
CheckDownload,
|
||||
TryAgain,
|
||||
Succeed,
|
||||
Failed,
|
||||
}
|
||||
|
||||
public AssetBundleInfo BundleInfo { private set; get; }
|
||||
private UnityWebRequest _webRequest;
|
||||
private UnityWebRequestAsyncOperation _operationHandle;
|
||||
|
||||
private ESteps _steps = ESteps.None;
|
||||
private string _lastError = string.Empty;
|
||||
|
||||
private int _timeout;
|
||||
private int _failedTryAgain;
|
||||
private int _requestCount;
|
||||
private string _requestURL;
|
||||
|
||||
// 重置变量
|
||||
private bool _isAbort = false;
|
||||
private ulong _latestDownloadBytes;
|
||||
private float _latestDownloadRealtime;
|
||||
private float _tryAgainTimer;
|
||||
|
||||
/// <summary>
|
||||
/// 下载进度(0-100f)
|
||||
/// </summary>
|
||||
public float DownloadProgress { private set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// 已经下载的总字节数
|
||||
/// </summary>
|
||||
public ulong DownloadedBytes { private set; get; }
|
||||
|
||||
|
||||
internal FileDownloader(AssetBundleInfo bundleInfo)
|
||||
{
|
||||
BundleInfo = bundleInfo;
|
||||
}
|
||||
internal void SendRequest(int failedTryAgain, int timeout)
|
||||
{
|
||||
if (string.IsNullOrEmpty(BundleInfo.LocalPath))
|
||||
throw new ArgumentNullException();
|
||||
|
||||
if (_steps == ESteps.None)
|
||||
{
|
||||
_failedTryAgain = failedTryAgain;
|
||||
_timeout = timeout;
|
||||
_steps = ESteps.CreateDownload;
|
||||
}
|
||||
}
|
||||
internal void Update()
|
||||
{
|
||||
if (_steps == ESteps.None)
|
||||
return;
|
||||
if (_steps == ESteps.Failed || _steps == ESteps.Succeed)
|
||||
return;
|
||||
|
||||
// 创建下载器
|
||||
if (_steps == ESteps.CreateDownload)
|
||||
{
|
||||
// 重置变量
|
||||
DownloadProgress = 0f;
|
||||
DownloadedBytes = 0;
|
||||
_isAbort = false;
|
||||
_latestDownloadBytes = 0;
|
||||
_latestDownloadRealtime = Time.realtimeSinceStartup;
|
||||
_tryAgainTimer = 0f;
|
||||
|
||||
_requestCount++;
|
||||
_requestURL = GetRequestURL();
|
||||
_webRequest = new UnityWebRequest(_requestURL, UnityWebRequest.kHttpVerbGET);
|
||||
DownloadHandlerFile handler = new DownloadHandlerFile(BundleInfo.LocalPath);
|
||||
handler.removeFileOnAbort = true;
|
||||
_webRequest.downloadHandler = handler;
|
||||
_webRequest.disposeDownloadHandlerOnDispose = true;
|
||||
_operationHandle = _webRequest.SendWebRequest();
|
||||
_steps = ESteps.CheckDownload;
|
||||
}
|
||||
|
||||
// 检测下载结果
|
||||
if (_steps == ESteps.CheckDownload)
|
||||
{
|
||||
DownloadProgress = _webRequest.downloadProgress * 100f;
|
||||
DownloadedBytes = _webRequest.downloadedBytes;
|
||||
if (_operationHandle.isDone == false)
|
||||
{
|
||||
CheckTimeout();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查网络错误
|
||||
bool isError = false;
|
||||
#if UNITY_2020_3_OR_NEWER
|
||||
if (_webRequest.result != UnityWebRequest.Result.Success)
|
||||
{
|
||||
isError = true;
|
||||
_lastError = _webRequest.error;
|
||||
}
|
||||
#else
|
||||
if (_webRequest.isNetworkError || _webRequest.isHttpError)
|
||||
{
|
||||
isError = true;
|
||||
_lastError = _webRequest.error;
|
||||
}
|
||||
#endif
|
||||
|
||||
// 检查文件完整性
|
||||
if (isError == false)
|
||||
{
|
||||
// 注意:如果文件验证失败需要删除文件
|
||||
if (DownloadSystem.CheckContentIntegrity(BundleInfo) == false)
|
||||
{
|
||||
isError = true;
|
||||
_lastError = $"Verification failed";
|
||||
if (File.Exists(BundleInfo.LocalPath))
|
||||
File.Delete(BundleInfo.LocalPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (isError)
|
||||
{
|
||||
ReportError();
|
||||
if (_failedTryAgain > 0)
|
||||
_steps = ESteps.TryAgain;
|
||||
else
|
||||
_steps = ESteps.Failed;
|
||||
}
|
||||
else
|
||||
{
|
||||
_steps = ESteps.Succeed;
|
||||
DownloadSystem.CacheVerifyFile(BundleInfo.Hash, BundleInfo.BundleName);
|
||||
}
|
||||
|
||||
// 释放下载器
|
||||
DisposeWebRequest();
|
||||
}
|
||||
|
||||
// 重新尝试下载
|
||||
if (_steps == ESteps.TryAgain)
|
||||
{
|
||||
_tryAgainTimer += Time.unscaledDeltaTime;
|
||||
if (_tryAgainTimer > 0.5f)
|
||||
{
|
||||
_failedTryAgain--;
|
||||
_steps = ESteps.CreateDownload;
|
||||
Logger.Warning($"Try again download : {_requestURL}");
|
||||
}
|
||||
}
|
||||
}
|
||||
internal void SetDone()
|
||||
{
|
||||
_steps = ESteps.Succeed;
|
||||
}
|
||||
|
||||
private string GetRequestURL()
|
||||
{
|
||||
// 轮流返回请求地址
|
||||
if (_requestCount % 2 == 0)
|
||||
return BundleInfo.RemoteFallbackURL;
|
||||
else
|
||||
return BundleInfo.RemoteMainURL;
|
||||
}
|
||||
private void CheckTimeout()
|
||||
{
|
||||
// 注意:在连续时间段内无新增下载数据及判定为超时
|
||||
if (_isAbort == false)
|
||||
{
|
||||
if (_latestDownloadBytes != DownloadedBytes)
|
||||
{
|
||||
_latestDownloadBytes = DownloadedBytes;
|
||||
_latestDownloadRealtime = Time.realtimeSinceStartup;
|
||||
}
|
||||
|
||||
float offset = Time.realtimeSinceStartup - _latestDownloadRealtime;
|
||||
if (offset > _timeout)
|
||||
{
|
||||
Logger.Warning($"Web file request timeout : {_requestURL}");
|
||||
_webRequest.Abort();
|
||||
_isAbort = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
private void DisposeWebRequest()
|
||||
{
|
||||
if (_webRequest != null)
|
||||
{
|
||||
_webRequest.Dispose();
|
||||
_webRequest = null;
|
||||
_operationHandle = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测下载器是否已经完成(无论成功或失败)
|
||||
/// </summary>
|
||||
public bool IsDone()
|
||||
{
|
||||
return _steps == ESteps.Succeed || _steps == ESteps.Failed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载过程是否发生错误
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool HasError()
|
||||
{
|
||||
return _steps == ESteps.Failed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 报告错误信息
|
||||
/// </summary>
|
||||
public void ReportError()
|
||||
{
|
||||
Logger.Error($"Failed to download : {_requestURL} Error : {_lastError}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c6e1a8bc8d5e664395395daa772ddd7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
252
Assets/YooAsset/Runtime/PatchSystem/Download/HttpDownloader.cs
Normal file
252
Assets/YooAsset/Runtime/PatchSystem/Download/HttpDownloader.cs
Normal file
@@ -0,0 +1,252 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Threading;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
internal class HttpDownloader
|
||||
{
|
||||
private enum ESteps
|
||||
{
|
||||
None,
|
||||
CreateDownload,
|
||||
CheckDownload,
|
||||
Succeed,
|
||||
Failed,
|
||||
}
|
||||
|
||||
public AssetBundleInfo BundleInfo { private set; get; }
|
||||
private ESteps _steps = ESteps.None;
|
||||
|
||||
// 线程
|
||||
private bool _threadOver = false;
|
||||
private bool _threadResult = false;
|
||||
private string _threadError = string.Empty;
|
||||
private Thread _thread;
|
||||
|
||||
// 保留参数
|
||||
private int _timeout;
|
||||
private int _failedTryAgain;
|
||||
private int _requestCount;
|
||||
|
||||
// 下载结果
|
||||
private string _downloadError = string.Empty;
|
||||
private float _downloadProgress = 0f;
|
||||
private long _downloadBytes = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 下载进度(0-100f)
|
||||
/// </summary>
|
||||
public float DownloadProgress
|
||||
{
|
||||
get
|
||||
{
|
||||
return _downloadProgress;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 已经下载的总字节数
|
||||
/// </summary>
|
||||
public long DownloadedBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
return _downloadBytes;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal void SendRequest(int failedTryAgain, int timeout)
|
||||
{
|
||||
}
|
||||
internal void Update()
|
||||
{
|
||||
if (_steps == ESteps.None)
|
||||
return;
|
||||
if (_steps == ESteps.Succeed || _steps == ESteps.Failed)
|
||||
return;
|
||||
|
||||
if(_steps == ESteps.CreateDownload)
|
||||
{
|
||||
_downloadError = string.Empty;
|
||||
_downloadProgress = 0f;
|
||||
_downloadBytes = 0;
|
||||
|
||||
_threadOver = false;
|
||||
_threadResult = false;
|
||||
_threadError = string.Empty;
|
||||
_thread = new Thread(ThreadRun);
|
||||
_thread.IsBackground = true;
|
||||
_thread.Start();
|
||||
_steps = ESteps.CheckDownload;
|
||||
}
|
||||
|
||||
if(_steps == ESteps.CheckDownload)
|
||||
{
|
||||
if (_threadOver == false)
|
||||
return;
|
||||
|
||||
if(_thread != null)
|
||||
{
|
||||
_thread.Abort();
|
||||
_thread = null;
|
||||
}
|
||||
|
||||
_downloadError = _threadError;
|
||||
if (_threadResult)
|
||||
{
|
||||
DownloadSystem.CacheVerifyFile(BundleInfo.Hash, BundleInfo.BundleName);
|
||||
_steps = ESteps.Succeed;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 失败后重新尝试
|
||||
if(_failedTryAgain > 0)
|
||||
{
|
||||
_failedTryAgain--;
|
||||
_steps = ESteps.CreateDownload;
|
||||
}
|
||||
else
|
||||
{
|
||||
_steps = ESteps.Failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
internal void SetDone()
|
||||
{
|
||||
_steps = ESteps.Succeed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测下载器是否已经完成(无论成功或失败)
|
||||
/// </summary>
|
||||
public bool IsDone()
|
||||
{
|
||||
return _steps == ESteps.Succeed || _steps == ESteps.Failed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载过程是否发生错误
|
||||
/// </summary>
|
||||
public bool HasError()
|
||||
{
|
||||
return _steps == ESteps.Failed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 报告错误信息
|
||||
/// </summary>
|
||||
public void ReportError()
|
||||
{
|
||||
Logger.Error(_downloadError);
|
||||
}
|
||||
|
||||
|
||||
#region 多线程下载
|
||||
public const int BufferSize = 1042 * 4;
|
||||
private void ThreadRun()
|
||||
{
|
||||
string url = GetRequestURL();
|
||||
string savePath = BundleInfo.LocalPath;
|
||||
long fileTotalSize = BundleInfo.SizeBytes;
|
||||
|
||||
FileStream fileStream = null;
|
||||
Stream webStream = null;
|
||||
HttpWebResponse fileResponse = null;
|
||||
|
||||
try
|
||||
{
|
||||
// 创建文件流
|
||||
fileStream = new FileStream(savePath, FileMode.OpenOrCreate, FileAccess.Write);
|
||||
long fileLength = fileStream.Length;
|
||||
|
||||
// 创建HTTP下载请求
|
||||
HttpWebRequest fileRequest = WebRequest.Create(url) as HttpWebRequest;
|
||||
fileRequest.Timeout = _timeout;
|
||||
fileRequest.ReadWriteTimeout = _timeout;
|
||||
fileRequest.ProtocolVersion = HttpVersion.Version10;
|
||||
if (fileLength > 0)
|
||||
{
|
||||
// 注意:设置远端请求文件的起始位置
|
||||
fileRequest.AddRange(fileLength);
|
||||
// 注意:设置本地文件流的起始位置
|
||||
fileStream.Seek(fileLength, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
// 读取下载数据并保存到文件
|
||||
fileResponse = fileRequest.GetResponse() as HttpWebResponse;
|
||||
webStream = fileResponse.GetResponseStream();
|
||||
byte[] buffer = new byte[BufferSize];
|
||||
while (true)
|
||||
{
|
||||
int length = webStream.Read(buffer, 0, buffer.Length);
|
||||
if (length <= 0)
|
||||
break;
|
||||
|
||||
fileStream.Write(buffer, 0, length);
|
||||
|
||||
// 计算下载进度
|
||||
// 注意:原子操作保证数据安全
|
||||
fileLength += length;
|
||||
float progress = (fileLength / fileTotalSize) * 100f;
|
||||
_downloadProgress = progress;
|
||||
_downloadBytes = fileLength;
|
||||
}
|
||||
|
||||
// 验证下载文件完整性
|
||||
bool verfiyResult = DownloadSystem.CheckContentIntegrity(savePath, BundleInfo.SizeBytes, BundleInfo.CRC);
|
||||
if(verfiyResult)
|
||||
{
|
||||
_threadResult = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_threadResult = false;
|
||||
_threadError = $"Verify file content failed : {BundleInfo.Hash}";
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_threadResult = false;
|
||||
_threadError = e.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (webStream != null)
|
||||
{
|
||||
webStream.Close();
|
||||
webStream.Dispose();
|
||||
}
|
||||
|
||||
if (fileResponse != null)
|
||||
{
|
||||
fileResponse.Close();
|
||||
}
|
||||
|
||||
if (fileStream != null)
|
||||
{
|
||||
fileStream.Close();
|
||||
fileStream.Dispose();
|
||||
}
|
||||
|
||||
_threadOver = true;
|
||||
}
|
||||
}
|
||||
private string GetRequestURL()
|
||||
{
|
||||
// 轮流返回请求地址
|
||||
_requestCount++;
|
||||
if (_requestCount % 2 == 0)
|
||||
return BundleInfo.RemoteFallbackURL;
|
||||
else
|
||||
return BundleInfo.RemoteMainURL;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 057a358d2d0c92b47add59dac8bef783
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
175
Assets/YooAsset/Runtime/PatchSystem/Download/PatchDownloader.cs
Normal file
175
Assets/YooAsset/Runtime/PatchSystem/Download/PatchDownloader.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// 补丁下载器
|
||||
/// </summary>
|
||||
public class PatchDownloader : IEnumerator
|
||||
{
|
||||
private const int MAX_LOADER_COUNT = 64;
|
||||
|
||||
public delegate void OnDownloadOver(bool isSucceed);
|
||||
public delegate void OnDownloadProgress(int totalDownloadCount, int currentDownloadCoun, long totalDownloadBytes, long currentDownloadBytes);
|
||||
public delegate void OnPatchFileDownloadFailed(string fileName);
|
||||
|
||||
private readonly HostPlayModeImpl _playModeImpl;
|
||||
private readonly int _fileLoadingMaxNumber;
|
||||
private readonly int _failedTryAgain;
|
||||
private readonly List<AssetBundleInfo> _downloadList;
|
||||
private readonly List<AssetBundleInfo> _loadFailedList = new List<AssetBundleInfo>();
|
||||
private readonly List<FileDownloader> _downloaders = new List<FileDownloader>();
|
||||
private readonly List<FileDownloader> _removeList = new List<FileDownloader>(MAX_LOADER_COUNT);
|
||||
|
||||
// 数据相关
|
||||
public EDownloaderStates DownloadStates { private set; get; }
|
||||
public int TotalDownloadCount { private set; get; }
|
||||
public long TotalDownloadBytes { private set; get; }
|
||||
public int CurrentDownloadCount { private set; get; }
|
||||
public long CurrentDownloadBytes { private set; get; }
|
||||
private long _lastDownloadBytes = 0;
|
||||
private int _lastDownloadCount = 0;
|
||||
|
||||
// 委托相关
|
||||
public OnDownloadOver OnDownloadOverCallback { set; get; }
|
||||
public OnDownloadProgress OnDownloadProgressCallback { set; get; }
|
||||
public OnPatchFileDownloadFailed OnPatchFileDownloadFailedCallback { set; get; }
|
||||
|
||||
|
||||
private PatchDownloader()
|
||||
{
|
||||
}
|
||||
internal PatchDownloader(HostPlayModeImpl playModeImpl, List<AssetBundleInfo> downloadList, int fileLoadingMaxNumber, int failedTryAgain)
|
||||
{
|
||||
_playModeImpl = playModeImpl;
|
||||
_downloadList = downloadList;
|
||||
_fileLoadingMaxNumber = UnityEngine.Mathf.Clamp(fileLoadingMaxNumber, 1, MAX_LOADER_COUNT); ;
|
||||
_failedTryAgain = failedTryAgain;
|
||||
|
||||
DownloadStates = EDownloaderStates.None;
|
||||
TotalDownloadCount = downloadList.Count;
|
||||
foreach (var patchBundle in downloadList)
|
||||
{
|
||||
TotalDownloadBytes += patchBundle.SizeBytes;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否完毕,无论成功或失败
|
||||
/// </summary>
|
||||
public bool IsDone()
|
||||
{
|
||||
return DownloadStates == EDownloaderStates.Failed || DownloadStates == EDownloaderStates.Succeed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始下载
|
||||
/// </summary>
|
||||
public void Download()
|
||||
{
|
||||
if (DownloadStates != EDownloaderStates.None)
|
||||
{
|
||||
Logger.Warning($"{nameof(PatchDownloader)} is already running.");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Log($"Begine to download : {TotalDownloadCount} files and {TotalDownloadBytes} bytes");
|
||||
DownloadStates = EDownloaderStates.Loading;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新下载器
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
if (DownloadStates != EDownloaderStates.Loading)
|
||||
return;
|
||||
|
||||
// 检测下载器结果
|
||||
_removeList.Clear();
|
||||
long downloadBytes = CurrentDownloadBytes;
|
||||
foreach (var downloader in _downloaders)
|
||||
{
|
||||
downloadBytes += (long)downloader.DownloadedBytes;
|
||||
if (downloader.IsDone() == false)
|
||||
continue;
|
||||
|
||||
AssetBundleInfo bundleInfo = downloader.BundleInfo;
|
||||
|
||||
// 检测是否下载失败
|
||||
if (downloader.HasError())
|
||||
{
|
||||
downloader.ReportError();
|
||||
_removeList.Add(downloader);
|
||||
_loadFailedList.Add(bundleInfo);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 下载成功
|
||||
_removeList.Add(downloader);
|
||||
CurrentDownloadCount++;
|
||||
CurrentDownloadBytes += bundleInfo.SizeBytes;
|
||||
}
|
||||
|
||||
// 移除已经完成的下载器(无论成功或失败)
|
||||
foreach (var loader in _removeList)
|
||||
{
|
||||
_downloaders.Remove(loader);
|
||||
}
|
||||
|
||||
// 如果下载进度发生变化
|
||||
if (_lastDownloadBytes != downloadBytes || _lastDownloadCount != CurrentDownloadCount)
|
||||
{
|
||||
_lastDownloadBytes = downloadBytes;
|
||||
_lastDownloadCount = CurrentDownloadCount;
|
||||
OnDownloadProgressCallback?.Invoke(TotalDownloadCount, _lastDownloadCount, TotalDownloadBytes, _lastDownloadBytes);
|
||||
}
|
||||
|
||||
// 动态创建新的下载器到最大数量限制
|
||||
// 注意:如果期间有下载失败的文件,暂停动态创建下载器
|
||||
if (_downloadList.Count > 0 && _loadFailedList.Count == 0)
|
||||
{
|
||||
if (_downloaders.Count < _fileLoadingMaxNumber)
|
||||
{
|
||||
int index = _downloadList.Count - 1;
|
||||
var operation = DownloadSystem.BeginDownload(_downloadList[index], _failedTryAgain);
|
||||
_downloaders.Add(operation);
|
||||
_downloadList.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
// 下载结算
|
||||
if (_downloaders.Count == 0)
|
||||
{
|
||||
if (_loadFailedList.Count > 0)
|
||||
{
|
||||
DownloadStates = EDownloaderStates.Failed;
|
||||
OnPatchFileDownloadFailedCallback?.Invoke(_loadFailedList[0].BundleName);
|
||||
OnDownloadOverCallback?.Invoke(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 结算成功
|
||||
DownloadStates = EDownloaderStates.Succeed;
|
||||
OnDownloadOverCallback?.Invoke(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region 异步相关
|
||||
bool IEnumerator.MoveNext()
|
||||
{
|
||||
return !IsDone();
|
||||
}
|
||||
void IEnumerator.Reset()
|
||||
{
|
||||
}
|
||||
object IEnumerator.Current
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e6483a640bdc429459305f148a863397
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// 同步其它线程里的回调到主线程里
|
||||
/// 注意:Unity3D中需要设置Scripting Runtime Version为.NET4.6
|
||||
/// </summary>
|
||||
internal sealed class ThreadSyncContext : SynchronizationContext
|
||||
{
|
||||
private readonly ConcurrentQueue<Action> _safeQueue = new ConcurrentQueue<Action>();
|
||||
|
||||
/// <summary>
|
||||
/// 更新同步队列
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (_safeQueue.TryDequeue(out Action action) == false)
|
||||
return;
|
||||
action.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向同步队列里投递一个回调方法
|
||||
/// </summary>
|
||||
public override void Post(SendOrPostCallback callback, object state)
|
||||
{
|
||||
Action action = new Action(() => { callback(state); });
|
||||
_safeQueue.Enqueue(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c8ce2c52a3e9964fa50a9c031e4e593
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// 下载器
|
||||
/// 说明:UnityWebRequest(UWR) supports reading streaming assets since 2017.1
|
||||
/// </summary>
|
||||
internal class UnityWebRequester
|
||||
{
|
||||
protected UnityWebRequest _webRequest;
|
||||
protected UnityWebRequestAsyncOperation _operationHandle;
|
||||
|
||||
/// <summary>
|
||||
/// 请求URL地址
|
||||
/// </summary>
|
||||
public string URL { private set; get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 发送GET请求
|
||||
/// </summary>
|
||||
/// <param name="timeout">超时:从请求开始计时</param>
|
||||
public void SendRequest(string url, int timeout = 0)
|
||||
{
|
||||
if (_webRequest == null)
|
||||
{
|
||||
URL = url;
|
||||
_webRequest = new UnityWebRequest(URL, UnityWebRequest.kHttpVerbGET);
|
||||
DownloadHandlerBuffer handler = new DownloadHandlerBuffer();
|
||||
_webRequest.downloadHandler = handler;
|
||||
_webRequest.disposeDownloadHandlerOnDispose = true;
|
||||
_webRequest.timeout = timeout;
|
||||
_operationHandle = _webRequest.SendWebRequest();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取下载的字节数据
|
||||
/// </summary>
|
||||
public byte[] GetData()
|
||||
{
|
||||
if (_webRequest != null && IsDone())
|
||||
return _webRequest.downloadHandler.data;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取下载的文本数据
|
||||
/// </summary>
|
||||
public string GetText()
|
||||
{
|
||||
if (_webRequest != null && IsDone())
|
||||
return _webRequest.downloadHandler.text;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放下载器
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_webRequest != null)
|
||||
{
|
||||
_webRequest.Dispose();
|
||||
_webRequest = null;
|
||||
_operationHandle = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否完毕(无论成功失败)
|
||||
/// </summary>
|
||||
public bool IsDone()
|
||||
{
|
||||
if (_operationHandle == null)
|
||||
return false;
|
||||
return _operationHandle.isDone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载是否发生错误
|
||||
/// </summary>
|
||||
public bool HasError()
|
||||
{
|
||||
#if UNITY_2020_3_OR_NEWER
|
||||
return _webRequest.result != UnityWebRequest.Result.Success;
|
||||
#else
|
||||
if (_webRequest.isNetworkError || _webRequest.isHttpError)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取错误信息
|
||||
/// </summary>
|
||||
public string GetError()
|
||||
{
|
||||
if (_webRequest != null)
|
||||
{
|
||||
return $"URL : {URL} Error : {_webRequest.error}";
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b7557eb146572de49a1ec9b3f3c0b706
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/YooAsset/Runtime/PatchSystem/Operations.meta
Normal file
8
Assets/YooAsset/Runtime/PatchSystem/Operations.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4812fd895ef2d7e4a97956be2c6f8170
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,197 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化操作
|
||||
/// </summary>
|
||||
public abstract class InitializationOperation : AsyncOperationBase
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 编辑器下模拟运行的初始化操作
|
||||
/// </summary>
|
||||
internal class EditorModeInitializationOperation : InitializationOperation
|
||||
{
|
||||
internal override void Start()
|
||||
{
|
||||
Status = EOperationStatus.Succeed;
|
||||
}
|
||||
internal override void Update()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 离线模式的初始化操作
|
||||
/// </summary>
|
||||
internal class OfflinePlayModeInitializationOperation : InitializationOperation
|
||||
{
|
||||
private enum ESteps
|
||||
{
|
||||
Idle,
|
||||
LoadAppManifest,
|
||||
CheckAppManifest,
|
||||
Done,
|
||||
}
|
||||
|
||||
private OfflinePlayModeImpl _impl;
|
||||
private ESteps _steps = ESteps.Idle;
|
||||
private UnityWebRequester _downloader;
|
||||
private string _downloadURL;
|
||||
|
||||
internal OfflinePlayModeInitializationOperation(OfflinePlayModeImpl impl)
|
||||
{
|
||||
_impl = impl;
|
||||
}
|
||||
internal override void Start()
|
||||
{
|
||||
_steps = ESteps.LoadAppManifest;
|
||||
}
|
||||
internal override void Update()
|
||||
{
|
||||
if (_steps == ESteps.Idle)
|
||||
return;
|
||||
|
||||
if (_steps == ESteps.LoadAppManifest)
|
||||
{
|
||||
string filePath = AssetPathHelper.MakeStreamingLoadPath(ResourceSettingData.Setting.PatchManifestFileName);
|
||||
_downloadURL = AssetPathHelper.ConvertToWWWPath(filePath);
|
||||
_downloader = new UnityWebRequester();
|
||||
_downloader.SendRequest(_downloadURL);
|
||||
_steps = ESteps.CheckAppManifest;
|
||||
}
|
||||
|
||||
if (_steps == ESteps.CheckAppManifest)
|
||||
{
|
||||
if (_downloader.IsDone() == false)
|
||||
return;
|
||||
|
||||
if (_downloader.HasError())
|
||||
{
|
||||
Error = _downloader.GetError();
|
||||
Status = EOperationStatus.Failed;
|
||||
_downloader.Dispose();
|
||||
_steps = ESteps.Done;
|
||||
throw new System.Exception($"Fatal error : Failed load application patch manifest file : {_downloadURL}");
|
||||
}
|
||||
|
||||
// 解析APP里的补丁清单
|
||||
_impl.AppPatchManifest = PatchManifest.Deserialize(_downloader.GetText());
|
||||
_downloader.Dispose();
|
||||
_steps = ESteps.Done;
|
||||
Status = EOperationStatus.Succeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 网络模式的初始化操作
|
||||
/// </summary>
|
||||
internal class HostPlayModeInitializationOperation : InitializationOperation
|
||||
{
|
||||
private enum ESteps
|
||||
{
|
||||
Idle,
|
||||
InitCache,
|
||||
LoadAppManifest,
|
||||
CheckAppManifest,
|
||||
LoadSandboxManifest,
|
||||
Done,
|
||||
}
|
||||
|
||||
private HostPlayModeImpl _impl;
|
||||
private ESteps _steps = ESteps.Idle;
|
||||
private UnityWebRequester _downloader;
|
||||
private string _downloadURL;
|
||||
|
||||
internal HostPlayModeInitializationOperation(HostPlayModeImpl impl)
|
||||
{
|
||||
_impl = impl;
|
||||
}
|
||||
internal override void Start()
|
||||
{
|
||||
_steps = ESteps.InitCache;
|
||||
}
|
||||
internal override void Update()
|
||||
{
|
||||
if (_steps == ESteps.Idle)
|
||||
return;
|
||||
|
||||
if (_steps == ESteps.InitCache)
|
||||
{
|
||||
// 每次启动时比对APP版本号是否一致
|
||||
PatchCache cache = PatchCache.LoadCache();
|
||||
if (cache.CacheAppVersion != Application.version)
|
||||
{
|
||||
Logger.Warning($"Cache is dirty ! Cache app version is {cache.CacheAppVersion}, Current app version is {Application.version}");
|
||||
|
||||
// 注意:在覆盖安装的时候,会保留APP沙盒目录,可以选择清空缓存目录
|
||||
if (_impl.ClearCacheWhenDirty)
|
||||
{
|
||||
Logger.Warning("Clear cache files.");
|
||||
PatchHelper.DeleteSandboxCacheFolder();
|
||||
}
|
||||
|
||||
// 删除清单文件
|
||||
PatchHelper.DeleteSandboxPatchManifestFile();
|
||||
// 更新缓存文件
|
||||
PatchCache.UpdateCache();
|
||||
}
|
||||
_steps = ESteps.LoadAppManifest;
|
||||
}
|
||||
|
||||
if (_steps == ESteps.LoadAppManifest)
|
||||
{
|
||||
// 加载APP内的补丁清单
|
||||
Logger.Log($"Load application patch manifest.");
|
||||
string filePath = AssetPathHelper.MakeStreamingLoadPath(ResourceSettingData.Setting.PatchManifestFileName);
|
||||
_downloadURL = AssetPathHelper.ConvertToWWWPath(filePath);
|
||||
_downloader = new UnityWebRequester();
|
||||
_downloader.SendRequest(_downloadURL);
|
||||
_steps = ESteps.CheckAppManifest;
|
||||
}
|
||||
|
||||
if (_steps == ESteps.CheckAppManifest)
|
||||
{
|
||||
if (_downloader.IsDone() == false)
|
||||
return;
|
||||
|
||||
if (_downloader.HasError())
|
||||
{
|
||||
Error = _downloader.GetError();
|
||||
Status = EOperationStatus.Failed;
|
||||
_downloader.Dispose();
|
||||
_steps = ESteps.Done;
|
||||
throw new System.Exception($"Fatal error : Failed load application patch manifest file : {_downloadURL}");
|
||||
}
|
||||
|
||||
// 解析补丁清单
|
||||
string jsonData = _downloader.GetText();
|
||||
_impl.AppPatchManifest = PatchManifest.Deserialize(jsonData);
|
||||
_impl.LocalPatchManifest = _impl.AppPatchManifest;
|
||||
_downloader.Dispose();
|
||||
_steps = ESteps.LoadSandboxManifest;
|
||||
}
|
||||
|
||||
if (_steps == ESteps.LoadSandboxManifest)
|
||||
{
|
||||
// 加载沙盒内的补丁清单
|
||||
if (PatchHelper.CheckSandboxPatchManifestFileExist())
|
||||
{
|
||||
Logger.Log($"Load sandbox patch manifest.");
|
||||
string filePath = AssetPathHelper.MakePersistentLoadPath(ResourceSettingData.Setting.PatchManifestFileName);
|
||||
string jsonData = File.ReadAllText(filePath);
|
||||
_impl.LocalPatchManifest = PatchManifest.Deserialize(jsonData);
|
||||
}
|
||||
|
||||
_steps = ESteps.Done;
|
||||
Status = EOperationStatus.Succeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a44ad228b117d0047ab80e7a442459b4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,308 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// 更新清单操作
|
||||
/// </summary>
|
||||
public abstract class UpdateManifestOperation : AsyncOperationBase
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 编辑器下模拟运行的更新清单操作
|
||||
/// </summary>
|
||||
internal class EditorModeUpdateManifestOperation : UpdateManifestOperation
|
||||
{
|
||||
internal override void Start()
|
||||
{
|
||||
Status = EOperationStatus.Succeed;
|
||||
}
|
||||
internal override void Update()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 离线模式的更新清单操作
|
||||
/// </summary>
|
||||
internal class OfflinePlayModeUpdateManifestOperation : UpdateManifestOperation
|
||||
{
|
||||
internal override void Start()
|
||||
{
|
||||
Status = EOperationStatus.Succeed;
|
||||
}
|
||||
internal override void Update()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 网络模式的更新清单操作
|
||||
/// </summary>
|
||||
internal class HostPlayModeUpdateManifestOperation : UpdateManifestOperation
|
||||
{
|
||||
private enum ESteps
|
||||
{
|
||||
Idle,
|
||||
LoadWebManifestHash,
|
||||
CheckWebManifestHash,
|
||||
LoadWebManifest,
|
||||
CheckWebManifest,
|
||||
InitPrepareCache,
|
||||
UpdatePrepareCache,
|
||||
Done,
|
||||
}
|
||||
|
||||
private static int RequestCount = 0;
|
||||
|
||||
private readonly HostPlayModeImpl _impl;
|
||||
private readonly int _updateResourceVersion;
|
||||
private readonly int _timeout;
|
||||
private ESteps _steps = ESteps.Idle;
|
||||
private UnityWebRequester _downloaderHash;
|
||||
private UnityWebRequester _downloaderManifest;
|
||||
private float _verifyTime;
|
||||
|
||||
public HostPlayModeUpdateManifestOperation(HostPlayModeImpl impl, int updateResourceVersion, int timeout)
|
||||
{
|
||||
_impl = impl;
|
||||
_updateResourceVersion = updateResourceVersion;
|
||||
_timeout = timeout;
|
||||
}
|
||||
internal override void Start()
|
||||
{
|
||||
RequestCount++;
|
||||
_steps = ESteps.LoadWebManifestHash;
|
||||
|
||||
if (_impl.IgnoreResourceVersion && _updateResourceVersion > 0)
|
||||
{
|
||||
Logger.Warning($"Update resource version {_updateResourceVersion} is invalid when ignore resource version.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log($"Update patch manifest : update resource version is {_updateResourceVersion}");
|
||||
}
|
||||
}
|
||||
internal override void Update()
|
||||
{
|
||||
if (_steps == ESteps.Idle)
|
||||
return;
|
||||
|
||||
if (_steps == ESteps.LoadWebManifestHash)
|
||||
{
|
||||
string webURL = GetPatchManifestRequestURL(_updateResourceVersion, ResourceSettingData.Setting.PatchManifestHashFileName);
|
||||
Logger.Log($"Beginning to request patch manifest hash : {webURL}");
|
||||
_downloaderHash = new UnityWebRequester();
|
||||
_downloaderHash.SendRequest(webURL, _timeout);
|
||||
_steps = ESteps.CheckWebManifestHash;
|
||||
}
|
||||
|
||||
if (_steps == ESteps.CheckWebManifestHash)
|
||||
{
|
||||
if (_downloaderHash.IsDone() == false)
|
||||
return;
|
||||
|
||||
// Check fatal
|
||||
if (_downloaderHash.HasError())
|
||||
{
|
||||
Error = _downloaderHash.GetError();
|
||||
Status = EOperationStatus.Failed;
|
||||
_downloaderHash.Dispose();
|
||||
_steps = ESteps.Done;
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取补丁清单文件的哈希值
|
||||
string webManifestHash = _downloaderHash.GetText();
|
||||
_downloaderHash.Dispose();
|
||||
|
||||
// 如果补丁清单文件的哈希值相同
|
||||
string currentFileHash = PatchHelper.GetSandboxPatchManifestFileHash();
|
||||
if (currentFileHash == webManifestHash)
|
||||
{
|
||||
Logger.Log($"Patch manifest file hash is not change : {webManifestHash}");
|
||||
_steps = ESteps.InitPrepareCache;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log($"Patch manifest hash is change : {webManifestHash} -> {currentFileHash}");
|
||||
_steps = ESteps.LoadWebManifest;
|
||||
}
|
||||
}
|
||||
|
||||
if (_steps == ESteps.LoadWebManifest)
|
||||
{
|
||||
string webURL = GetPatchManifestRequestURL(_updateResourceVersion, ResourceSettingData.Setting.PatchManifestFileName);
|
||||
Logger.Log($"Beginning to request patch manifest : {webURL}");
|
||||
_downloaderManifest = new UnityWebRequester();
|
||||
_downloaderManifest.SendRequest(webURL, _timeout);
|
||||
_steps = ESteps.CheckWebManifest;
|
||||
}
|
||||
|
||||
if (_steps == ESteps.CheckWebManifest)
|
||||
{
|
||||
if (_downloaderManifest.IsDone() == false)
|
||||
return;
|
||||
|
||||
// Check fatal
|
||||
if (_downloaderManifest.HasError())
|
||||
{
|
||||
Error = _downloaderManifest.GetError();
|
||||
Status = EOperationStatus.Failed;
|
||||
_downloaderManifest.Dispose();
|
||||
_steps = ESteps.Done;
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析补丁清单
|
||||
ParseAndSaveRemotePatchManifest(_downloaderManifest.GetText());
|
||||
_downloaderManifest.Dispose();
|
||||
_steps = ESteps.InitPrepareCache;
|
||||
}
|
||||
|
||||
if (_steps == ESteps.InitPrepareCache)
|
||||
{
|
||||
InitPrepareCache();
|
||||
_verifyTime = UnityEngine.Time.realtimeSinceStartup;
|
||||
_steps = ESteps.UpdatePrepareCache;
|
||||
}
|
||||
|
||||
if (_steps == ESteps.UpdatePrepareCache)
|
||||
{
|
||||
if (UpdatePrepareCache())
|
||||
{
|
||||
_steps = ESteps.Done;
|
||||
Status = EOperationStatus.Succeed;
|
||||
float costTime = UnityEngine.Time.realtimeSinceStartup - _verifyTime;
|
||||
Logger.Log($"Verify files total time : {costTime}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetPatchManifestRequestURL(int updateResourceVersion, string fileName)
|
||||
{
|
||||
string url;
|
||||
|
||||
// 轮流返回请求地址
|
||||
if (RequestCount % 2 == 0)
|
||||
url = _impl.GetPatchDownloadFallbackURL(updateResourceVersion, fileName);
|
||||
else
|
||||
url = _impl.GetPatchDownloadMainURL(updateResourceVersion, fileName);
|
||||
|
||||
// 注意:在URL末尾添加时间戳
|
||||
if (_impl.IgnoreResourceVersion)
|
||||
url = $"{url}?{System.DateTime.UtcNow.Ticks}";
|
||||
|
||||
return url;
|
||||
}
|
||||
private void ParseAndSaveRemotePatchManifest(string content)
|
||||
{
|
||||
_impl.LocalPatchManifest = PatchManifest.Deserialize(content);
|
||||
|
||||
// 注意:这里会覆盖掉沙盒内的补丁清单文件
|
||||
Logger.Log("Save remote patch manifest file.");
|
||||
string savePath = AssetPathHelper.MakePersistentLoadPath(ResourceSettingData.Setting.PatchManifestFileName);
|
||||
PatchManifest.Serialize(savePath, _impl.LocalPatchManifest);
|
||||
}
|
||||
|
||||
#region 多线程相关
|
||||
private class ThreadInfo
|
||||
{
|
||||
public bool Result = false;
|
||||
public string FilePath { private set; get; }
|
||||
public PatchBundle Bundle { private set; get; }
|
||||
public ThreadInfo(string filePath, PatchBundle bundle)
|
||||
{
|
||||
FilePath = filePath;
|
||||
Bundle = bundle;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<PatchBundle> _cacheList = new List<PatchBundle>(1000);
|
||||
private readonly List<PatchBundle> _verifyList = new List<PatchBundle>(100);
|
||||
private readonly ThreadSyncContext _syncContext = new ThreadSyncContext();
|
||||
private const int VerifyMaxCount = 32;
|
||||
|
||||
private void InitPrepareCache()
|
||||
{
|
||||
// 遍历所有文件然后验证并缓存合法文件
|
||||
foreach (var patchBundle in _impl.LocalPatchManifest.BundleList)
|
||||
{
|
||||
// 忽略缓存文件
|
||||
if (DownloadSystem.ContainsVerifyFile(patchBundle.Hash))
|
||||
continue;
|
||||
|
||||
// 忽略APP资源
|
||||
// 注意:如果是APP资源并且哈希值相同,则不需要下载
|
||||
if (_impl.AppPatchManifest.Bundles.TryGetValue(patchBundle.BundleName, out PatchBundle appPatchBundle))
|
||||
{
|
||||
if (appPatchBundle.IsBuildin && appPatchBundle.Hash == patchBundle.Hash)
|
||||
continue;
|
||||
}
|
||||
|
||||
// 查看文件是否存在
|
||||
string filePath = PatchHelper.MakeSandboxCacheFilePath(patchBundle.Hash);
|
||||
if (File.Exists(filePath) == false)
|
||||
continue;
|
||||
|
||||
_cacheList.Add(patchBundle);
|
||||
}
|
||||
}
|
||||
private bool UpdatePrepareCache()
|
||||
{
|
||||
_syncContext.Update();
|
||||
|
||||
if (_cacheList.Count == 0 && _verifyList.Count == 0)
|
||||
return true;
|
||||
|
||||
if (_verifyList.Count >= VerifyMaxCount)
|
||||
return false;
|
||||
|
||||
for (int i = _cacheList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (_verifyList.Count >= VerifyMaxCount)
|
||||
break;
|
||||
|
||||
var patchBundle = _cacheList[i];
|
||||
if (RunThread(patchBundle))
|
||||
{
|
||||
_cacheList.RemoveAt(i);
|
||||
_verifyList.Add(patchBundle);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning("Failed to run verify thread.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
private bool RunThread(PatchBundle patchBundle)
|
||||
{
|
||||
string filePath = PatchHelper.MakeSandboxCacheFilePath(patchBundle.Hash);
|
||||
ThreadInfo info = new ThreadInfo(filePath, patchBundle);
|
||||
return ThreadPool.QueueUserWorkItem(new WaitCallback(VerifyFile), info);
|
||||
}
|
||||
private void VerifyFile(object infoObj)
|
||||
{
|
||||
// 验证沙盒内的文件
|
||||
ThreadInfo info = (ThreadInfo)infoObj;
|
||||
info.Result = DownloadSystem.CheckContentIntegrity(info.FilePath, info.Bundle.SizeBytes, info.Bundle.CRC);
|
||||
_syncContext.Post(VerifyCallback, info);
|
||||
}
|
||||
private void VerifyCallback(object obj)
|
||||
{
|
||||
ThreadInfo info = (ThreadInfo)obj;
|
||||
if (info.Result)
|
||||
DownloadSystem.CacheVerifyFile(info.Bundle.Hash, info.Bundle.BundleName);
|
||||
_verifyList.Remove(info.Bundle);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 250c5da7ab03e724f8c328de5238e433
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
23
Assets/YooAsset/Runtime/PatchSystem/PatchAsset.cs
Normal file
23
Assets/YooAsset/Runtime/PatchSystem/PatchAsset.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
[Serializable]
|
||||
public class PatchAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// 资源路径
|
||||
/// </summary>
|
||||
public string AssetPath;
|
||||
|
||||
/// <summary>
|
||||
/// 所属资源包ID
|
||||
/// </summary>
|
||||
public int BundleID;
|
||||
|
||||
/// <summary>
|
||||
/// 依赖的资源包ID列表
|
||||
/// </summary>
|
||||
public int[] DependIDs;
|
||||
}
|
||||
}
|
||||
11
Assets/YooAsset/Runtime/PatchSystem/PatchAsset.cs.meta
Normal file
11
Assets/YooAsset/Runtime/PatchSystem/PatchAsset.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df359cc7c21634d41b5d05f09ca59cf2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
129
Assets/YooAsset/Runtime/PatchSystem/PatchBundle.cs
Normal file
129
Assets/YooAsset/Runtime/PatchSystem/PatchBundle.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using YooAsset.Utility;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
[Serializable]
|
||||
public class PatchBundle
|
||||
{
|
||||
/// <summary>
|
||||
/// 资源包名称
|
||||
/// </summary>
|
||||
public string BundleName;
|
||||
|
||||
/// <summary>
|
||||
/// 文件哈希值
|
||||
/// </summary>
|
||||
public string Hash;
|
||||
|
||||
/// <summary>
|
||||
/// 文件校验码
|
||||
/// </summary>
|
||||
public string CRC;
|
||||
|
||||
/// <summary>
|
||||
/// 文件大小(字节数)
|
||||
/// </summary>
|
||||
public long SizeBytes;
|
||||
|
||||
/// <summary>
|
||||
/// 文件版本
|
||||
/// </summary>
|
||||
public int Version;
|
||||
|
||||
/// <summary>
|
||||
/// Tags
|
||||
/// </summary>
|
||||
public string[] Tags;
|
||||
|
||||
/// <summary>
|
||||
/// Flags
|
||||
/// </summary>
|
||||
public int Flags;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 是否为加密文件
|
||||
/// </summary>
|
||||
public bool IsEncrypted { private set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为内置文件
|
||||
/// </summary>
|
||||
public bool IsBuildin { private set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为原生文件
|
||||
/// </summary>
|
||||
public bool IsRawFile { private set; get; }
|
||||
|
||||
|
||||
|
||||
public PatchBundle(string bundleName, string hash, string crc, long sizeBytes, int version, string[] tags)
|
||||
{
|
||||
BundleName = bundleName;
|
||||
Hash = hash;
|
||||
CRC = crc;
|
||||
SizeBytes = sizeBytes;
|
||||
Version = version;
|
||||
Tags = tags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置Flags
|
||||
/// </summary>
|
||||
public void SetFlagsValue(bool isEncrypted, bool isBuildin, bool isRawFile)
|
||||
{
|
||||
IsEncrypted = isEncrypted;
|
||||
IsBuildin = isBuildin;
|
||||
IsRawFile = isRawFile;
|
||||
|
||||
BitMask32 mask = new BitMask32(0);
|
||||
if (isEncrypted) mask.Open(0);
|
||||
if (isBuildin) mask.Open(1);
|
||||
if (isRawFile) mask.Open(2);
|
||||
Flags = mask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析Flags
|
||||
/// </summary>
|
||||
public void ParseFlagsValue()
|
||||
{
|
||||
BitMask32 value = Flags;
|
||||
IsEncrypted = value.Test(0);
|
||||
IsBuildin = value.Test(1);
|
||||
IsRawFile = value.Test(2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否包含Tag
|
||||
/// </summary>
|
||||
public bool HasTag(string[] tags)
|
||||
{
|
||||
if (tags == null || tags.Length == 0)
|
||||
return false;
|
||||
if (Tags == null || Tags.Length == 0)
|
||||
return false;
|
||||
|
||||
foreach (var tag in tags)
|
||||
{
|
||||
if (Tags.Contains(tag))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否为纯内置资源(不带任何Tag的资源)
|
||||
/// </summary>
|
||||
public bool IsPureBuildin()
|
||||
{
|
||||
if (Tags == null || Tags.Length == 0)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/YooAsset/Runtime/PatchSystem/PatchBundle.cs.meta
Normal file
11
Assets/YooAsset/Runtime/PatchSystem/PatchBundle.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70ff08c6c9b0f5e4c993ec4c91954467
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
55
Assets/YooAsset/Runtime/PatchSystem/PatchCache.cs
Normal file
55
Assets/YooAsset/Runtime/PatchSystem/PatchCache.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using YooAsset.Utility;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
[Serializable]
|
||||
internal sealed class PatchCache
|
||||
{
|
||||
/// <summary>
|
||||
/// 缓存的APP内置版本
|
||||
/// </summary>
|
||||
public string CacheAppVersion = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 读取缓存文件
|
||||
/// 注意:如果文件不存在则创建新的缓存文件
|
||||
/// </summary>
|
||||
public static PatchCache LoadCache()
|
||||
{
|
||||
if (PatchHelper.CheckSandboxCacheFileExist())
|
||||
{
|
||||
Logger.Log("Load patch cache from disk.");
|
||||
string filePath = PatchHelper.GetSandboxCacheFilePath();
|
||||
string jsonData = FileUtility.ReadFile(filePath);
|
||||
return JsonUtility.FromJson<PatchCache>(jsonData);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log($"Create patch cache to disk : {Application.version}");
|
||||
PatchCache cache = new PatchCache();
|
||||
cache.CacheAppVersion = Application.version;
|
||||
string filePath = PatchHelper.GetSandboxCacheFilePath();
|
||||
string jsonData = JsonUtility.ToJson(cache);
|
||||
FileUtility.CreateFile(filePath, jsonData);
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新缓存文件
|
||||
/// </summary>
|
||||
public static void UpdateCache()
|
||||
{
|
||||
Logger.Log($"Update patch cache to disk : {Application.version}");
|
||||
PatchCache cache = new PatchCache();
|
||||
cache.CacheAppVersion = Application.version;
|
||||
string filePath = PatchHelper.GetSandboxCacheFilePath();
|
||||
string jsonData = JsonUtility.ToJson(cache);
|
||||
FileUtility.CreateFile(filePath, jsonData);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/YooAsset/Runtime/PatchSystem/PatchCache.cs.meta
Normal file
11
Assets/YooAsset/Runtime/PatchSystem/PatchCache.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 37a7daecdb1361140b44ba4724e8866e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
101
Assets/YooAsset/Runtime/PatchSystem/PatchHelper.cs
Normal file
101
Assets/YooAsset/Runtime/PatchSystem/PatchHelper.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using YooAsset.Utility;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
internal static class PatchHelper
|
||||
{
|
||||
private const string StrCacheFileName = "Cache.bytes";
|
||||
private const string StrCacheFolderName = "CacheFiles";
|
||||
|
||||
/// <summary>
|
||||
/// 清空沙盒目录
|
||||
/// </summary>
|
||||
public static void ClearSandbox()
|
||||
{
|
||||
string directoryPath = AssetPathHelper.MakePersistentLoadPath(string.Empty);
|
||||
if (Directory.Exists(directoryPath))
|
||||
Directory.Delete(directoryPath, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除沙盒内补丁清单文件
|
||||
/// </summary>
|
||||
public static void DeleteSandboxPatchManifestFile()
|
||||
{
|
||||
string filePath = AssetPathHelper.MakePersistentLoadPath(ResourceSettingData.Setting.PatchManifestFileName);
|
||||
if (File.Exists(filePath))
|
||||
File.Delete(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除沙盒内的缓存文件
|
||||
/// </summary>
|
||||
public static void DeleteSandboxCacheFile()
|
||||
{
|
||||
string filePath = GetSandboxCacheFilePath();
|
||||
if (File.Exists(filePath))
|
||||
File.Delete(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除沙盒内的缓存文件夹
|
||||
/// </summary>
|
||||
public static void DeleteSandboxCacheFolder()
|
||||
{
|
||||
string directoryPath = AssetPathHelper.MakePersistentLoadPath(StrCacheFolderName);
|
||||
if (Directory.Exists(directoryPath))
|
||||
Directory.Delete(directoryPath, true);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取沙盒内缓存文件的路径
|
||||
/// </summary>
|
||||
public static string GetSandboxCacheFilePath()
|
||||
{
|
||||
return AssetPathHelper.MakePersistentLoadPath(StrCacheFileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测沙盒内缓存文件是否存在
|
||||
/// </summary>
|
||||
public static bool CheckSandboxCacheFileExist()
|
||||
{
|
||||
string filePath = GetSandboxCacheFilePath();
|
||||
return File.Exists(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测沙盒内补丁清单文件是否存在
|
||||
/// </summary>
|
||||
public static bool CheckSandboxPatchManifestFileExist()
|
||||
{
|
||||
string filePath = AssetPathHelper.MakePersistentLoadPath(ResourceSettingData.Setting.PatchManifestFileName);
|
||||
return File.Exists(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取沙盒内补丁清单文件的哈希值
|
||||
/// 注意:如果沙盒内补丁清单文件不存在,返回空字符串
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetSandboxPatchManifestFileHash()
|
||||
{
|
||||
string filePath = AssetPathHelper.MakePersistentLoadPath(ResourceSettingData.Setting.PatchManifestFileName);
|
||||
if (File.Exists(filePath))
|
||||
return HashUtility.FileMD5(filePath);
|
||||
else
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取缓存文件的存储路径
|
||||
/// </summary>
|
||||
public static string MakeSandboxCacheFilePath(string fileName)
|
||||
{
|
||||
return AssetPathHelper.MakePersistentLoadPath($"{StrCacheFolderName}/{fileName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/YooAsset/Runtime/PatchSystem/PatchHelper.cs.meta
Normal file
11
Assets/YooAsset/Runtime/PatchSystem/PatchHelper.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 887179b2bb92ca04988d1091b3b8f8e2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
162
Assets/YooAsset/Runtime/PatchSystem/PatchManifest.cs
Normal file
162
Assets/YooAsset/Runtime/PatchSystem/PatchManifest.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using YooAsset.Utility;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// 补丁清单文件
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class PatchManifest
|
||||
{
|
||||
/// <summary>
|
||||
/// 资源版本号
|
||||
/// </summary>
|
||||
public int ResourceVersion;
|
||||
|
||||
/// <summary>
|
||||
/// 内置资源的标记列表
|
||||
/// </summary>
|
||||
public string BuildinTags;
|
||||
|
||||
/// <summary>
|
||||
/// 资源列表(主动收集的资源列表)
|
||||
/// </summary>
|
||||
public List<PatchAsset> AssetList = new List<PatchAsset>();
|
||||
|
||||
/// <summary>
|
||||
/// 资源包列表
|
||||
/// </summary>
|
||||
public List<PatchBundle> BundleList = new List<PatchBundle>();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 资源包集合(提供BundleName获取PatchBundle)
|
||||
/// </summary>
|
||||
[NonSerialized]
|
||||
public readonly Dictionary<string, PatchBundle> Bundles = new Dictionary<string, PatchBundle>();
|
||||
|
||||
/// <summary>
|
||||
/// 资源映射集合(提供AssetPath获取PatchAsset)
|
||||
/// </summary>
|
||||
[NonSerialized]
|
||||
public readonly Dictionary<string, PatchAsset> Assets = new Dictionary<string, PatchAsset>();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取内置资源标记列表
|
||||
/// </summary>
|
||||
public string[] GetBuildinTags()
|
||||
{
|
||||
return StringUtility.StringToStringList(BuildinTags, ';').ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取资源依赖列表
|
||||
/// </summary>
|
||||
public string[] GetAllDependencies(string assetPath)
|
||||
{
|
||||
if (Assets.TryGetValue(assetPath, out PatchAsset patchAsset))
|
||||
{
|
||||
List<string> result = new List<string>(patchAsset.DependIDs.Length);
|
||||
foreach (var dependID in patchAsset.DependIDs)
|
||||
{
|
||||
if (dependID >= 0 && dependID < BundleList.Count)
|
||||
{
|
||||
var dependPatchBundle = BundleList[dependID];
|
||||
result.Add(dependPatchBundle.BundleName);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Invalid depend id : {dependID} Asset path : {assetPath}");
|
||||
}
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning($"Not found asset path in patch manifest : {assetPath}");
|
||||
return new string[] { };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取资源包名称
|
||||
/// </summary>
|
||||
public string GetAssetBundleName(string assetPath)
|
||||
{
|
||||
if (Assets.TryGetValue(assetPath, out PatchAsset patchAsset))
|
||||
{
|
||||
int bundleID = patchAsset.BundleID;
|
||||
if (bundleID >= 0 && bundleID < BundleList.Count)
|
||||
{
|
||||
var patchBundle = BundleList[bundleID];
|
||||
return patchBundle.BundleName;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Invalid depend id : {bundleID} Asset path : {assetPath}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning($"Not found asset path in patch manifest : {assetPath}");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 序列化
|
||||
/// </summary>
|
||||
public static void Serialize(string savePath, PatchManifest patchManifest)
|
||||
{
|
||||
string json = JsonUtility.ToJson(patchManifest);
|
||||
FileUtility.CreateFile(savePath, json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 反序列化
|
||||
/// </summary>
|
||||
public static PatchManifest Deserialize(string jsonData)
|
||||
{
|
||||
PatchManifest patchManifest = JsonUtility.FromJson<PatchManifest>(jsonData);
|
||||
|
||||
// BundleList
|
||||
foreach (var patchBundle in patchManifest.BundleList)
|
||||
{
|
||||
patchBundle.ParseFlagsValue();
|
||||
patchManifest.Bundles.Add(patchBundle.BundleName, patchBundle);
|
||||
}
|
||||
|
||||
// AssetList
|
||||
foreach (var patchAsset in patchManifest.AssetList)
|
||||
{
|
||||
string assetPath = patchAsset.AssetPath;
|
||||
|
||||
// 添加原始路径
|
||||
// 注意:我们不允许原始路径存在重名
|
||||
if (patchManifest.Assets.ContainsKey(assetPath))
|
||||
throw new Exception($"Asset path have existed : {assetPath}");
|
||||
else
|
||||
patchManifest.Assets.Add(assetPath, patchAsset);
|
||||
|
||||
// 添加去掉后缀名的路径
|
||||
if (Path.HasExtension(assetPath))
|
||||
{
|
||||
string assetPathWithoutExtension = StringUtility.RemoveExtension(assetPath);
|
||||
if (patchManifest.Assets.ContainsKey(assetPathWithoutExtension))
|
||||
Logger.Warning($"Asset path have existed : {assetPathWithoutExtension}");
|
||||
else
|
||||
patchManifest.Assets.Add(assetPathWithoutExtension, patchAsset);
|
||||
}
|
||||
}
|
||||
|
||||
return patchManifest;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/YooAsset/Runtime/PatchSystem/PatchManifest.cs.meta
Normal file
11
Assets/YooAsset/Runtime/PatchSystem/PatchManifest.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0bb3469358a90d41a3a4b284a3ddc8f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/YooAsset/Runtime/PatchSystem/PlayMode.meta
Normal file
8
Assets/YooAsset/Runtime/PatchSystem/PlayMode.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9d6cb1ce5d510645866ad7c122abfab
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
internal class EditorPlayModeImpl : IBundleServices
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步初始化
|
||||
/// </summary>
|
||||
public InitializationOperation InitializeAsync()
|
||||
{
|
||||
var operation = new EditorModeInitializationOperation();
|
||||
OperationUpdater.ProcessOperaiton(operation);
|
||||
return operation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取资源版本号
|
||||
/// </summary>
|
||||
public int GetResourceVersion()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取内置资源标记列表
|
||||
/// </summary>
|
||||
public string[] GetManifestBuildinTags()
|
||||
{
|
||||
return new string[0];
|
||||
}
|
||||
|
||||
#region IBundleServices接口
|
||||
AssetBundleInfo IBundleServices.GetAssetBundleInfo(string bundleName)
|
||||
{
|
||||
Logger.Warning($"Editor play mode can not get asset bundle info.");
|
||||
AssetBundleInfo bundleInfo = new AssetBundleInfo(bundleName, bundleName);
|
||||
return bundleInfo;
|
||||
}
|
||||
string IBundleServices.GetAssetBundleName(string assetPath)
|
||||
{
|
||||
return assetPath;
|
||||
}
|
||||
string[] IBundleServices.GetAllDependencies(string assetPath)
|
||||
{
|
||||
return new string[] { };
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2fb1b9a2a91f1af4a86acfcfac424e0b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
252
Assets/YooAsset/Runtime/PatchSystem/PlayMode/HostPlayModeImpl.cs
Normal file
252
Assets/YooAsset/Runtime/PatchSystem/PlayMode/HostPlayModeImpl.cs
Normal file
@@ -0,0 +1,252 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
internal class HostPlayModeImpl : IBundleServices
|
||||
{
|
||||
// 补丁清单
|
||||
internal PatchManifest AppPatchManifest;
|
||||
internal PatchManifest LocalPatchManifest;
|
||||
|
||||
// 参数相关
|
||||
internal bool ClearCacheWhenDirty { private set; get; }
|
||||
internal bool IgnoreResourceVersion { private set; get; }
|
||||
private string _defaultHostServer;
|
||||
private string _fallbackHostServer;
|
||||
|
||||
/// <summary>
|
||||
/// 异步初始化
|
||||
/// </summary>
|
||||
public InitializationOperation InitializeAsync(bool clearCacheWhenDirty, bool ignoreResourceVersion,
|
||||
string defaultHostServer, string fallbackHostServer)
|
||||
{
|
||||
ClearCacheWhenDirty = clearCacheWhenDirty;
|
||||
IgnoreResourceVersion = ignoreResourceVersion;
|
||||
_defaultHostServer = defaultHostServer;
|
||||
_fallbackHostServer = fallbackHostServer;
|
||||
|
||||
var operation = new HostPlayModeInitializationOperation(this);
|
||||
OperationUpdater.ProcessOperaiton(operation);
|
||||
return operation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步更新补丁清单
|
||||
/// </summary>
|
||||
public UpdateManifestOperation UpdatePatchManifestAsync(int updateResourceVersion, int timeout)
|
||||
{
|
||||
var operation = new HostPlayModeUpdateManifestOperation(this, updateResourceVersion, timeout);
|
||||
OperationUpdater.ProcessOperaiton(operation);
|
||||
return operation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取资源版本号
|
||||
/// </summary>
|
||||
public int GetResourceVersion()
|
||||
{
|
||||
if (LocalPatchManifest == null)
|
||||
return 0;
|
||||
return LocalPatchManifest.ResourceVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取内置资源标记列表
|
||||
/// </summary>
|
||||
public string[] GetManifestBuildinTags()
|
||||
{
|
||||
if (LocalPatchManifest == null)
|
||||
return new string[0];
|
||||
return LocalPatchManifest.GetBuildinTags();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建补丁下载器
|
||||
/// </summary>
|
||||
public PatchDownloader CreateDLCDownloader(string[] dlcTags, int fileLoadingMaxNumber, int failedTryAgain)
|
||||
{
|
||||
List<AssetBundleInfo> downloadList = GetDLCDownloadList(dlcTags);
|
||||
PatchDownloader downlader = new PatchDownloader(this, downloadList, fileLoadingMaxNumber, failedTryAgain);
|
||||
return downlader;
|
||||
}
|
||||
private List<AssetBundleInfo> GetDLCDownloadList(string[] dlcTags)
|
||||
{
|
||||
List<PatchBundle> downloadList = new List<PatchBundle>(1000);
|
||||
foreach (var patchBundle in LocalPatchManifest.BundleList)
|
||||
{
|
||||
// 忽略缓存文件
|
||||
if (DownloadSystem.ContainsVerifyFile(patchBundle.Hash))
|
||||
continue;
|
||||
|
||||
// 忽略APP资源
|
||||
// 注意:如果是APP资源并且哈希值相同,则不需要下载
|
||||
if (AppPatchManifest.Bundles.TryGetValue(patchBundle.BundleName, out PatchBundle appPatchBundle))
|
||||
{
|
||||
if (appPatchBundle.IsBuildin && appPatchBundle.Hash == patchBundle.Hash)
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果是纯内置资源,则统一下载
|
||||
// 注意:可能是新增的或者变化的内置资源
|
||||
// 注意:可能是由热更资源转换的内置资源
|
||||
if (patchBundle.IsPureBuildin())
|
||||
{
|
||||
downloadList.Add(patchBundle);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 查询DLC资源
|
||||
if (patchBundle.HasTag(dlcTags))
|
||||
{
|
||||
downloadList.Add(patchBundle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ConvertToDownloadList(downloadList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建补丁下载器
|
||||
/// </summary>
|
||||
public PatchDownloader CreateBundleDownloader(List<string> assetPaths, int fileLoadingMaxNumber, int failedTryAgain)
|
||||
{
|
||||
List<AssetBundleInfo> downloadList = GetBundleDownloadList(assetPaths);
|
||||
PatchDownloader downlader = new PatchDownloader(this, downloadList, fileLoadingMaxNumber, failedTryAgain);
|
||||
return downlader;
|
||||
}
|
||||
private List<AssetBundleInfo> GetBundleDownloadList(List<string> assetPaths)
|
||||
{
|
||||
// 获取资源对象的资源包和所有依赖资源包
|
||||
List<PatchBundle> checkList = new List<PatchBundle>();
|
||||
foreach (var assetPath in assetPaths)
|
||||
{
|
||||
string mainBundleName = LocalPatchManifest.GetAssetBundleName(assetPath);
|
||||
if (string.IsNullOrEmpty(mainBundleName) == false)
|
||||
{
|
||||
if (LocalPatchManifest.Bundles.TryGetValue(mainBundleName, out PatchBundle mainBundle))
|
||||
{
|
||||
if (checkList.Contains(mainBundle) == false)
|
||||
checkList.Add(mainBundle);
|
||||
}
|
||||
}
|
||||
|
||||
string[] dependBundleNames = LocalPatchManifest.GetAllDependencies(assetPath);
|
||||
foreach (var dependBundleName in dependBundleNames)
|
||||
{
|
||||
if (LocalPatchManifest.Bundles.TryGetValue(dependBundleName, out PatchBundle dependBundle))
|
||||
{
|
||||
if (checkList.Contains(dependBundle) == false)
|
||||
checkList.Add(dependBundle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<PatchBundle> downloadList = new List<PatchBundle>(1000);
|
||||
foreach (var patchBundle in checkList)
|
||||
{
|
||||
// 忽略缓存文件
|
||||
if (DownloadSystem.ContainsVerifyFile(patchBundle.Hash))
|
||||
continue;
|
||||
|
||||
// 忽略APP资源
|
||||
// 注意:如果是APP资源并且哈希值相同,则不需要下载
|
||||
if (AppPatchManifest.Bundles.TryGetValue(patchBundle.BundleName, out PatchBundle appPatchBundle))
|
||||
{
|
||||
if (appPatchBundle.IsBuildin && appPatchBundle.Hash == patchBundle.Hash)
|
||||
continue;
|
||||
}
|
||||
|
||||
downloadList.Add(patchBundle);
|
||||
}
|
||||
|
||||
return ConvertToDownloadList(downloadList);
|
||||
}
|
||||
|
||||
// WEB相关
|
||||
internal string GetPatchDownloadMainURL(int resourceVersion, string fileName)
|
||||
{
|
||||
if (IgnoreResourceVersion)
|
||||
return $"{_defaultHostServer}/{fileName}";
|
||||
else
|
||||
return $"{_defaultHostServer}/{resourceVersion}/{fileName}";
|
||||
}
|
||||
internal string GetPatchDownloadFallbackURL(int resourceVersion, string fileName)
|
||||
{
|
||||
if (IgnoreResourceVersion)
|
||||
return $"{_fallbackHostServer}/{fileName}";
|
||||
else
|
||||
return $"{_fallbackHostServer}/{resourceVersion}/{fileName}";
|
||||
}
|
||||
|
||||
// 下载相关
|
||||
private AssetBundleInfo ConvertToDownloadInfo(PatchBundle patchBundle)
|
||||
{
|
||||
// 注意:资源版本号只用于确定下载路径
|
||||
string sandboxPath = PatchHelper.MakeSandboxCacheFilePath(patchBundle.Hash);
|
||||
string remoteMainURL = GetPatchDownloadMainURL(patchBundle.Version, patchBundle.Hash);
|
||||
string remoteFallbackURL = GetPatchDownloadFallbackURL(patchBundle.Version, patchBundle.Hash);
|
||||
AssetBundleInfo bundleInfo = new AssetBundleInfo(patchBundle, sandboxPath, remoteMainURL, remoteFallbackURL);
|
||||
return bundleInfo;
|
||||
}
|
||||
private List<AssetBundleInfo> ConvertToDownloadList(List<PatchBundle> downloadList)
|
||||
{
|
||||
List<AssetBundleInfo> result = new List<AssetBundleInfo>(downloadList.Count);
|
||||
foreach (var patchBundle in downloadList)
|
||||
{
|
||||
var bundleInfo = ConvertToDownloadInfo(patchBundle);
|
||||
result.Add(bundleInfo);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#region IBundleServices接口
|
||||
AssetBundleInfo IBundleServices.GetAssetBundleInfo(string bundleName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(bundleName))
|
||||
return new AssetBundleInfo(string.Empty, string.Empty);
|
||||
|
||||
if (LocalPatchManifest.Bundles.TryGetValue(bundleName, out PatchBundle patchBundle))
|
||||
{
|
||||
// 查询APP资源
|
||||
if (AppPatchManifest.Bundles.TryGetValue(bundleName, out PatchBundle appPatchBundle))
|
||||
{
|
||||
if (appPatchBundle.IsBuildin && appPatchBundle.Hash == patchBundle.Hash)
|
||||
{
|
||||
string appLoadPath = AssetPathHelper.MakeStreamingLoadPath(appPatchBundle.Hash);
|
||||
AssetBundleInfo bundleInfo = new AssetBundleInfo(appPatchBundle, appLoadPath);
|
||||
return bundleInfo;
|
||||
}
|
||||
}
|
||||
|
||||
// 查询沙盒资源
|
||||
if (DownloadSystem.ContainsVerifyFile(patchBundle.Hash))
|
||||
{
|
||||
string sandboxLoadPath = PatchHelper.MakeSandboxCacheFilePath(patchBundle.Hash);
|
||||
AssetBundleInfo bundleInfo = new AssetBundleInfo(patchBundle, sandboxLoadPath);
|
||||
return bundleInfo;
|
||||
}
|
||||
|
||||
// 从服务端下载
|
||||
return ConvertToDownloadInfo(patchBundle);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning($"Not found bundle in patch manifest : {bundleName}");
|
||||
AssetBundleInfo bundleInfo = new AssetBundleInfo(bundleName, string.Empty);
|
||||
return bundleInfo;
|
||||
}
|
||||
}
|
||||
string IBundleServices.GetAssetBundleName(string assetPath)
|
||||
{
|
||||
return LocalPatchManifest.GetAssetBundleName(assetPath);
|
||||
}
|
||||
string[] IBundleServices.GetAllDependencies(string assetPath)
|
||||
{
|
||||
return LocalPatchManifest.GetAllDependencies(assetPath);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84c10fd3507a1c24a9043aebb72db5f5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
internal class OfflinePlayModeImpl : IBundleServices
|
||||
{
|
||||
internal PatchManifest AppPatchManifest;
|
||||
|
||||
/// <summary>
|
||||
/// 异步初始化
|
||||
/// </summary>
|
||||
public InitializationOperation InitializeAsync()
|
||||
{
|
||||
var operation = new OfflinePlayModeInitializationOperation(this);
|
||||
OperationUpdater.ProcessOperaiton(operation);
|
||||
return operation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取资源版本号
|
||||
/// </summary>
|
||||
public int GetResourceVersion()
|
||||
{
|
||||
if (AppPatchManifest == null)
|
||||
return 0;
|
||||
return AppPatchManifest.ResourceVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取内置资源标记列表
|
||||
/// </summary>
|
||||
public string[] GetManifestBuildinTags()
|
||||
{
|
||||
if (AppPatchManifest == null)
|
||||
return new string[0];
|
||||
return AppPatchManifest.GetBuildinTags();
|
||||
}
|
||||
|
||||
#region IBundleServices接口
|
||||
AssetBundleInfo IBundleServices.GetAssetBundleInfo(string bundleName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(bundleName))
|
||||
return new AssetBundleInfo(string.Empty, string.Empty);
|
||||
|
||||
if (AppPatchManifest.Bundles.TryGetValue(bundleName, out PatchBundle patchBundle))
|
||||
{
|
||||
string localPath = AssetPathHelper.MakeStreamingLoadPath(patchBundle.Hash);
|
||||
AssetBundleInfo bundleInfo = new AssetBundleInfo(patchBundle, localPath);
|
||||
return bundleInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning($"Not found bundle in patch manifest : {bundleName}");
|
||||
AssetBundleInfo bundleInfo = new AssetBundleInfo(bundleName, string.Empty);
|
||||
return bundleInfo;
|
||||
}
|
||||
}
|
||||
string IBundleServices.GetAssetBundleName(string assetPath)
|
||||
{
|
||||
return AppPatchManifest.GetAssetBundleName(assetPath);
|
||||
}
|
||||
string[] IBundleServices.GetAllDependencies(string assetPath)
|
||||
{
|
||||
return AppPatchManifest.GetAllDependencies(assetPath);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc10550f17aaeb14795135a51444de1c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user