Initial commit

This commit is contained in:
hevinci
2022-03-01 10:44:12 +08:00
parent 1d54164bfb
commit e66853fd46
237 changed files with 14462 additions and 2 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 715017c3b0bb2a3429d8f021b0d9a2b2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}
}

View File

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

View File

@@ -0,0 +1,14 @@

namespace YooAsset
{
/// <summary>
/// 下载状态
/// </summary>
public enum EDownloaderStates
{
None,
Loading,
Failed,
Succeed,
}
}

View File

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

View 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}");
}
}
}

View File

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

View 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
}
}

View File

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

View 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
}
}

View File

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

View File

@@ -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);
}
}
}

View File

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

View File

@@ -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;
}
}
}

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4812fd895ef2d7e4a97956be2c6f8170
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}
}

View File

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

View File

@@ -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
}
}

View File

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

View 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;
}
}

View File

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

View 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;
}
}
}

View File

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

View 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);
}
}
}

View File

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

View 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}");
}
}
}

View File

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

View 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;
}
}
}

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e9d6cb1ce5d510645866ad7c122abfab
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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
}
}

View File

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

View 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
}
}

View File

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

View File

@@ -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
}
}

View File

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