refactor : 重构网络下载模块

新增通用下载接口,扩展了默认的Unity引擎下载器
This commit is contained in:
何冠峰
2026-01-05 19:44:10 +08:00
parent 1884fab0c2
commit c87efdb509
89 changed files with 2597 additions and 1641 deletions
@@ -23,10 +23,14 @@ namespace YooAsset
protected string _cacheManifestFilesRoot;
/// <summary>
/// 下载中心
/// 说明:当异步操作任务终止的时候,所有下载子任务都会一同被终止!
/// 下载调度器
/// </summary>
public DownloadCenterOperation DownloadCenter { set; get; }
public DownloadSchedulerOperation DownloadScheduler { set; get; }
/// <summary>
/// 下载后台接口
/// </summary>
public IDownloadBackend DownloadBackend { private set; get; }
/// <summary>
/// 包裹名称
@@ -105,7 +109,7 @@ namespace YooAsset
/// <summary>
/// 自定义参数:下载任务的看门狗机制监控时间
/// </summary>
public int DownloadWatchDogTime { private set; get; } = int.MaxValue;
public int DownloadWatchDogTime { private set; get; } = 0;
/// <summary>
/// 自定义参数:启用断点续传的最小尺寸
@@ -288,7 +292,7 @@ namespace YooAsset
else if (name == FileSystemParametersDefine.DOWNLOAD_WATCH_DOG_TIME)
{
int convertValue = Convert.ToInt32(value);
DownloadWatchDogTime = Mathf.Clamp(convertValue, 1, int.MaxValue);
DownloadWatchDogTime = Mathf.Clamp(convertValue, 0, int.MaxValue);
}
else if (name == FileSystemParametersDefine.RESUME_DOWNLOAD_MINMUM_SIZE)
{
@@ -327,13 +331,17 @@ namespace YooAsset
_cacheBundleFilesRoot = PathUtility.Combine(_packageRoot, DefaultCacheFileSystemDefine.BundleFilesFolderName);
_cacheManifestFilesRoot = PathUtility.Combine(_packageRoot, DefaultCacheFileSystemDefine.ManifestFilesFolderName);
_tempFilesRoot = PathUtility.Combine(_packageRoot, DefaultCacheFileSystemDefine.TempFilesFolderName);
// 创建默认的下载后台接口
if (DownloadBackend == null)
DownloadBackend = new UnityWebRequestBackend(DownloadSystemHelper.UnityWebRequestCreater);
}
public virtual void OnDestroy()
{
if (DownloadCenter != null)
if (DownloadScheduler != null)
{
DownloadCenter.AbortOperation();
DownloadCenter = null;
DownloadScheduler.Dispose();
DownloadScheduler = null;
}
}
@@ -9,7 +9,7 @@ namespace YooAsset
CheckAppFootPrint,
SearchCacheFiles,
VerifyCacheFiles,
CreateDownloadCenter,
CreateDownloadScheduler,
Done,
}
@@ -110,7 +110,7 @@ namespace YooAsset
if (_verifyCacheFilesOp.Status == EOperationStatus.Succeed)
{
_steps = ESteps.CreateDownloadCenter;
_steps = ESteps.CreateDownloadScheduler;
YooLogger.Log($"Package '{_fileSystem.PackageName}' '{_fileSystem.GetType().Name}' cached files count : {_fileSystem.FileCount}");
}
else
@@ -121,13 +121,13 @@ namespace YooAsset
}
}
if (_steps == ESteps.CreateDownloadCenter)
if (_steps == ESteps.CreateDownloadScheduler)
{
// 注意:下载中心作为独立任务运行!
if (_fileSystem.DownloadCenter == null)
if (_fileSystem.DownloadScheduler == null)
{
_fileSystem.DownloadCenter = new DownloadCenterOperation(_fileSystem);
OperationSystem.StartOperation(_fileSystem.PackageName, _fileSystem.DownloadCenter);
_fileSystem.DownloadScheduler = new DownloadSchedulerOperation(_fileSystem);
OperationSystem.StartOperation(_fileSystem.PackageName, _fileSystem.DownloadScheduler);
}
_steps = ESteps.Done;
@@ -1,133 +0,0 @@
using System;
using System.Collections.Generic;
namespace YooAsset
{
internal class DownloadCenterOperation : AsyncOperationBase
{
private readonly DefaultCacheFileSystem _fileSystem;
protected readonly Dictionary<string, UnityDownloadFileOperation> _downloaders = new Dictionary<string, UnityDownloadFileOperation>(1000);
protected readonly List<string> _removeList = new List<string>(1000);
public DownloadCenterOperation(DefaultCacheFileSystem fileSystem)
{
_fileSystem = fileSystem;
}
internal override void InternalStart()
{
}
internal override void InternalUpdate()
{
// 获取可移除的下载器集合
_removeList.Clear();
foreach (var valuePair in _downloaders)
{
var downloader = valuePair.Value;
downloader.UpdateOperation();
if (downloader.IsDone)
{
_removeList.Add(valuePair.Key);
continue;
}
// 注意:主动终止引用计数为零的下载任务
if (downloader.RefCount <= 0)
{
_removeList.Add(valuePair.Key);
downloader.AbortOperation();
continue;
}
}
// 移除下载器
foreach (var key in _removeList)
{
if (_downloaders.TryGetValue(key, out var downloader))
{
Childs.Remove(downloader);
_downloaders.Remove(key);
}
}
// 最大并发数检测
int processCount = GetProcessingOperationCount();
if (processCount != _downloaders.Count)
{
if (processCount < _fileSystem.DownloadMaxConcurrency)
{
int startCount = _fileSystem.DownloadMaxConcurrency - processCount;
if (startCount > _fileSystem.DownloadMaxRequestPerFrame)
startCount = _fileSystem.DownloadMaxRequestPerFrame;
foreach (var operationPair in _downloaders)
{
var operation = operationPair.Value;
if (operation.Status == EOperationStatus.None)
{
operation.StartOperation();
startCount--;
if (startCount <= 0)
break;
}
}
}
}
}
/// <summary>
/// 创建下载任务
/// </summary>
public UnityDownloadFileOperation DownloadFileAsync(PackageBundle bundle, string url)
{
// 查询旧的下载器
if (_downloaders.TryGetValue(bundle.BundleGUID, out var oldDownloader))
{
oldDownloader.Reference();
return oldDownloader;
}
// 创建新的下载器
UnityDownloadFileOperation newDownloader;
bool isRequestLocalFile = DownloadSystemHelper.IsRequestLocalFile(url);
if (isRequestLocalFile)
{
newDownloader = new UnityDownloadLocalFileOperation(_fileSystem, bundle, url);
AddChildOperation(newDownloader);
_downloaders.Add(bundle.BundleGUID, newDownloader);
}
else
{
if (bundle.FileSize >= _fileSystem.ResumeDownloadMinimumSize)
{
newDownloader = new UnityDownloadResumeFileOperation(_fileSystem, bundle, url);
AddChildOperation(newDownloader);
_downloaders.Add(bundle.BundleGUID, newDownloader);
}
else
{
newDownloader = new UnityDownloadNormalFileOperation(_fileSystem, bundle, url);
AddChildOperation(newDownloader);
_downloaders.Add(bundle.BundleGUID, newDownloader);
}
}
newDownloader.Reference();
return newDownloader;
}
/// <summary>
/// 获取正在进行中的下载器总数
/// </summary>
private int GetProcessingOperationCount()
{
int count = 0;
foreach (var operationPair in _downloaders)
{
var operation = operationPair.Value;
if (operation.Status != EOperationStatus.None)
count++;
}
return count;
}
}
}
@@ -1,4 +1,5 @@
using UnityEngine;
using System.IO;
using UnityEngine;
namespace YooAsset
{
@@ -17,7 +18,7 @@ namespace YooAsset
// 下载参数
protected readonly DefaultCacheFileSystem _fileSystem;
protected readonly DownloadFileOptions _options;
private UnityDownloadFileOperation _unityDownloadFileOp;
private DownloadAndCacheFileOperation _downloadFileOp;
protected int _requestCount = 0;
protected float _tryAgainTimer = 0;
@@ -67,7 +68,7 @@ namespace YooAsset
}
string url = GetRequestURL();
_unityDownloadFileOp = _fileSystem.DownloadCenter.DownloadFileAsync(Bundle, url);
_downloadFileOp = _fileSystem.DownloadScheduler.DownloadAndCacheFileAsync(Bundle, url);
_steps = ESteps.CheckRequest;
}
@@ -75,20 +76,16 @@ namespace YooAsset
if (_steps == ESteps.CheckRequest)
{
if (IsWaitForAsyncComplete)
_unityDownloadFileOp.WaitForAsyncComplete();
_downloadFileOp.WaitForAsyncComplete();
// 因为并发数量限制,下载器可能被挂起!
if (_unityDownloadFileOp.Status == EOperationStatus.None)
_downloadFileOp.UpdateOperation();
Progress = _downloadFileOp.Progress;
DownloadedBytes = _downloadFileOp.DownloadedBytes;
DownloadProgress = _downloadFileOp.DownloadProgress;
if (_downloadFileOp.IsDone == false)
return;
_unityDownloadFileOp.UpdateOperation();
Progress = _unityDownloadFileOp.Progress;
DownloadedBytes = _unityDownloadFileOp.DownloadedBytes;
DownloadProgress = _unityDownloadFileOp.DownloadProgress;
if (_unityDownloadFileOp.IsDone == false)
return;
if (_unityDownloadFileOp.Status == EOperationStatus.Succeed)
if (_downloadFileOp.Status == EOperationStatus.Succeed)
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
@@ -98,13 +95,13 @@ namespace YooAsset
if (IsWaitForAsyncComplete == false && _failedTryAgain > 0)
{
_steps = ESteps.TryAgain;
YooLogger.Warning($"Failed download : {_unityDownloadFileOp.URL} Try again !");
YooLogger.Warning($"Failed download : {_downloadFileOp.URL} Try again !");
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _unityDownloadFileOp.Error;
Error = _downloadFileOp.Error;
YooLogger.Error(Error);
}
}
@@ -141,9 +138,9 @@ namespace YooAsset
// 注意:取消下载任务的时候引用计数减一
if (_steps != ESteps.Done)
{
if (_unityDownloadFileOp != null)
if (_downloadFileOp != null)
{
_unityDownloadFileOp.Release();
_downloadFileOp.Release();
}
}
}
@@ -15,7 +15,7 @@ namespace YooAsset
private readonly DefaultCacheFileSystem _fileSystem;
private readonly string _packageVersion;
private readonly int _timeout;
private UnityWebFileRequestOperation _webFileRequestOp;
private IDownloadFileRequest _webFileRequestOp;
private int _requestCount = 0;
private ESteps _steps = ESteps.None;
@@ -57,16 +57,16 @@ namespace YooAsset
string savePath = _fileSystem.GetCachePackageHashFilePath(_packageVersion);
string fileName = YooAssetSettingsData.GetPackageHashFileName(_fileSystem.PackageName, _packageVersion);
string webURL = GetWebRequestURL(fileName);
_webFileRequestOp = new UnityWebFileRequestOperation(webURL, savePath, _timeout);
_webFileRequestOp.StartOperation();
AddChildOperation(_webFileRequestOp);
int watchdogTime = _fileSystem.DownloadWatchDogTime;
var args = new DownloadFileRequestArgs(webURL, savePath, _timeout, watchdogTime);
_webFileRequestOp = _fileSystem.DownloadBackend.CreateFileRequest(args);
_webFileRequestOp.SendRequest();
}
_webFileRequestOp.UpdateOperation();
if (_webFileRequestOp.IsDone == false)
return;
if (_webFileRequestOp.Status == EOperationStatus.Succeed)
if (_webFileRequestOp.Status == EDownloadRequestStatus.Succeed)
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
@@ -15,7 +15,7 @@ namespace YooAsset
private readonly DefaultCacheFileSystem _fileSystem;
private readonly string _packageVersion;
private readonly int _timeout;
private UnityWebFileRequestOperation _webFileRequestOp;
private IDownloadFileRequest _webFileRequestOp;
private int _requestCount = 0;
private ESteps _steps = ESteps.None;
@@ -57,16 +57,16 @@ namespace YooAsset
string savePath = _fileSystem.GetCachePackageManifestFilePath(_packageVersion);
string fileName = YooAssetSettingsData.GetManifestBinaryFileName(_fileSystem.PackageName, _packageVersion);
string webURL = GetDownloadRequestURL(fileName);
_webFileRequestOp = new UnityWebFileRequestOperation(webURL, savePath, _timeout);
_webFileRequestOp.StartOperation();
AddChildOperation(_webFileRequestOp);
int watchdogTime = _fileSystem.DownloadWatchDogTime;
var args = new DownloadFileRequestArgs(webURL, savePath, _timeout, watchdogTime);
_webFileRequestOp = _fileSystem.DownloadBackend.CreateFileRequest(args);
_webFileRequestOp.SendRequest();
}
_webFileRequestOp.UpdateOperation();
if (_webFileRequestOp.IsDone == false)
return;
if (_webFileRequestOp.Status == EOperationStatus.Succeed)
if (_webFileRequestOp.Status == EDownloadRequestStatus.Succeed)
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
@@ -13,7 +13,7 @@ namespace YooAsset
private readonly DefaultCacheFileSystem _fileSystem;
private readonly bool _appendTimeTicks;
private readonly int _timeout;
private UnityWebTextRequestOperation _webTextRequestOp;
private IDownloadTextRequest _webTextRequestOp;
private int _requestCount = 0;
private ESteps _steps = ESteps.None;
@@ -45,17 +45,17 @@ namespace YooAsset
{
string fileName = YooAssetSettingsData.GetPackageVersionFileName(_fileSystem.PackageName);
string url = GetWebRequestURL(fileName);
_webTextRequestOp = new UnityWebTextRequestOperation(url, _timeout);
_webTextRequestOp.StartOperation();
AddChildOperation(_webTextRequestOp);
int watchDogTime = _fileSystem.DownloadWatchDogTime;
var args = new DownloadDataRequestArgs(url, _timeout, watchDogTime);
_webTextRequestOp = _fileSystem.DownloadBackend.CreateTextRequest(args);
_webTextRequestOp.SendRequest();
}
_webTextRequestOp.UpdateOperation();
Progress = _webTextRequestOp.Progress;
Progress = _webTextRequestOp.DownloadProgress;
if (_webTextRequestOp.IsDone == false)
return;
if (_webTextRequestOp.Status == EOperationStatus.Succeed)
if (_webTextRequestOp.Status == EDownloadRequestStatus.Succeed)
{
PackageVersion = _webTextRequestOp.Result;
if (string.IsNullOrEmpty(PackageVersion))
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bf9991076b60f0f459846f54b0ca6698
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,51 @@
namespace YooAsset
{
internal abstract class DownloadAndCacheFileOperation : AsyncOperationBase
{
/// <summary>
/// 引用计数
/// </summary>
public int RefCount { private set; get; }
/// <summary>
/// 下载地址
/// </summary>
public readonly string URL;
/// <summary>
/// 下载进度
/// </summary>
public float DownloadProgress { get; protected set; }
/// <summary>
/// 下载字节
/// </summary>
public long DownloadedBytes { get; protected set; }
public DownloadAndCacheFileOperation(string url)
{
URL = url;
}
internal override string InternalGetDesc()
{
return $"RefCount : {RefCount}";
}
/// <summary>
/// 减少引用计数
/// </summary>
public void Release()
{
RefCount--;
}
/// <summary>
/// 增加引用计数
/// </summary>
public void Reference()
{
RefCount++;
}
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: c68640bb1d36552469024324e3357bc2
guid: 2954a64fd419d5a4b9d0a102260d193c
MonoImporter:
externalObjects: {}
serializedVersion: 2
@@ -1,94 +1,79 @@
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
namespace YooAsset
{
internal sealed class UnityDownloadLocalFileOperation : UnityDownloadFileOperation
internal sealed class DownloadAndCacheLocalFileOperation : DownloadAndCacheFileOperation
{
private enum ESteps
{
None,
CheckCopy,
CopyLocalFile,
CreateRequest,
CheckRequest,
VerifyBundleFile,
CacheBundleFile,
Done,
}
private readonly DefaultCacheFileSystem _fileSystem;
private readonly PackageBundle _bundle;
private readonly string _tempFilePath;
private IDownloadRequest _request;
private VerifyTempFileOperation _verifyOperation;
private ESteps _steps = ESteps.None;
internal UnityDownloadLocalFileOperation(DefaultCacheFileSystem fileSystem, PackageBundle bundle, string url)
: base(fileSystem, bundle, url)
internal DownloadAndCacheLocalFileOperation(DefaultCacheFileSystem fileSystem, PackageBundle bundle, string url) : base(url)
{
_fileSystem = fileSystem;
_bundle = bundle;
_tempFilePath = _fileSystem.GetTempFilePath(_bundle);
}
internal override void InternalStart()
{
if (_fileSystem.CopyLocalFileServices != null)
_steps = ESteps.CopyLocalFile;
else
_steps = ESteps.CreateRequest;
_steps = ESteps.CheckCopy;
}
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
// 创建下载器
if (_steps == ESteps.CreateRequest)
// 检测文件拷贝
if (_steps == ESteps.CheckCopy)
{
// 删除历史缓存文件
FileUtility.CreateFileDirectory(_tempFilePath);
if (File.Exists(_tempFilePath))
File.Delete(_tempFilePath);
CreateWebRequest();
_steps = ESteps.Download;
}
// 检测下载结果
if (_steps == ESteps.Download)
{
DownloadProgress = _webRequest.downloadProgress;
DownloadedBytes = (long)_webRequest.downloadedBytes;
Progress = DownloadProgress;
UpdateWatchDog();
if (_webRequest.isDone == false)
return;
// 检查网络错误
if (CheckRequestResult())
{
_steps = ESteps.VerifyFile;
}
if (_fileSystem.CopyLocalFileServices != null)
_steps = ESteps.CopyLocalFile;
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
}
// 注意:最终释放请求器
DisposeRequest();
_steps = ESteps.CreateRequest;
}
// 拷贝内置文件
// 拷贝本地文件
if (_steps == ESteps.CopyLocalFile)
{
FileUtility.CreateFileDirectory(_tempFilePath);
if (File.Exists(_tempFilePath))
File.Delete(_tempFilePath);
try
{
//TODO 团结引擎,在某些机型(红米),拷贝包内文件会小概率失败!需要借助其它方式来拷贝包内文件。
var localFileInfo = new LocalFileInfo();
localFileInfo.PackageName = _fileSystem.PackageName;
localFileInfo.BundleName = _bundle.BundleName;
localFileInfo.SourceFileURL = _requestURL;
localFileInfo.SourceFileURL = URL;
_fileSystem.CopyLocalFileServices.CopyFile(localFileInfo, _tempFilePath);
if (File.Exists(_tempFilePath))
{
DownloadProgress = 1f;
DownloadedBytes = _bundle.FileSize;
Progress = DownloadProgress;
_steps = ESteps.VerifyFile;
_steps = ESteps.VerifyBundleFile;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Failed copy local file : {_requestURL}";
Error = $"Failed copy local file : {URL}";
}
}
catch (System.Exception ex)
@@ -99,8 +84,44 @@ namespace YooAsset
}
}
// 验证下载文件
if (_steps == ESteps.VerifyFile)
// 创建下载请求
if (_steps == ESteps.CreateRequest)
{
int watchdogTime = _fileSystem.DownloadWatchDogTime;
int timeout = 0; //注意:文件下载不做超时检测
var args = new DownloadFileRequestArgs(URL, _tempFilePath, timeout, watchdogTime);
_request = _fileSystem.DownloadBackend.CreateFileRequest(args);
_request.SendRequest();
_steps = ESteps.CheckRequest;
}
// 检测下载结果
if (_steps == ESteps.CheckRequest)
{
DownloadProgress = _request.DownloadProgress;
DownloadedBytes = _request.DownloadedBytes;
Progress = DownloadProgress;
if (_request.IsDone == false)
return;
// 检查网络错误
if (_request.Status == EDownloadRequestStatus.Succeed)
{
_steps = ESteps.VerifyBundleFile;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _request.Error;
}
// 最终释放请求器
_request.Dispose();
}
// 验证下载结果
if (_steps == ESteps.VerifyBundleFile)
{
if (_verifyOperation == null)
{
@@ -119,35 +140,51 @@ namespace YooAsset
if (_verifyOperation.Status == EOperationStatus.Succeed)
{
if (_fileSystem.WriteCacheBundleFile(_bundle, _tempFilePath))
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"{_fileSystem.GetType().FullName} failed to write file !";
}
_steps = ESteps.CacheBundleFile;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _verifyOperation.Error;
// 注意:验证失败后直接删除文件
if (File.Exists(_tempFilePath))
File.Delete(_tempFilePath);
}
}
// 缓存文件
if (_steps == ESteps.CacheBundleFile)
{
if (_fileSystem.WriteCacheBundleFile(_bundle, _tempFilePath))
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"{_fileSystem.GetType().FullName} failed to write file !";
}
// 注意:验证完成后直接删除文件
// 注意:缓存完成后直接删除临时文件
if (File.Exists(_tempFilePath))
File.Delete(_tempFilePath);
}
}
internal override void InternalAbort()
{
if (_request != null)
_request.AbortRequest();
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
//TODO 等待导入或解压本地文件完毕,该操作会挂起主线程!
_fileSystem.DownloadBackend.Update();
InternalUpdate();
if (IsDone)
break;
@@ -156,15 +193,5 @@ namespace YooAsset
System.Threading.Thread.Sleep(1);
}
}
private void CreateWebRequest()
{
DownloadHandlerFile handler = new DownloadHandlerFile(_tempFilePath);
handler.removeFileOnAbort = true;
_webRequest = DownloadSystemHelper.NewUnityWebRequestGet(_requestURL);
_webRequest.downloadHandler = handler;
_webRequest.disposeDownloadHandlerOnDispose = true;
_webRequest.SendWebRequest();
}
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 768ef2df3433df245a26fec28d022e45
guid: 47700ac672942834f8d5a7c59783d88e
MonoImporter:
externalObjects: {}
serializedVersion: 2
@@ -0,0 +1,206 @@
using System.IO;
namespace YooAsset
{
internal sealed class DownloadAndCacheRemoteFileOperation : DownloadAndCacheFileOperation
{
private enum ESteps
{
None,
CreateRequest,
CheckRequest,
VerifyBundleFile,
CacheBundleFile,
Done,
}
private readonly DefaultCacheFileSystem _fileSystem;
private readonly PackageBundle _bundle;
private readonly string _tempFilePath;
private bool _enableResume = false;
private long _fileOriginLength = 0;
private IDownloadRequest _request;
private VerifyTempFileOperation _verifyOperation;
private ESteps _steps = ESteps.None;
internal DownloadAndCacheRemoteFileOperation(DefaultCacheFileSystem fileSystem, PackageBundle bundle, string url) : base(url)
{
_fileSystem = fileSystem;
_bundle = bundle;
_tempFilePath = _fileSystem.GetTempFilePath(_bundle);
}
internal override void InternalStart()
{
_steps = ESteps.CreateRequest;
}
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
// 创建下载请求
if (_steps == ESteps.CreateRequest)
{
FileUtility.CreateFileDirectory(_tempFilePath);
_enableResume = _bundle.FileSize >= _fileSystem.ResumeDownloadMinimumSize;
if (_enableResume)
{
_request = CreateResumeRequest();
_request.SendRequest();
_steps = ESteps.CheckRequest;
}
else
{
_request = CreateNormalRequest();
_request.SendRequest();
_steps = ESteps.CheckRequest;
}
}
// 检测下载结果
if (_steps == ESteps.CheckRequest)
{
DownloadProgress = _request.DownloadProgress;
DownloadedBytes = _fileOriginLength + _request.DownloadedBytes;
Progress = DownloadProgress;
if (_request.IsDone == false)
return;
// 检查网络错误
if (_request.Status == EDownloadRequestStatus.Succeed)
{
_steps = ESteps.VerifyBundleFile;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _request.Error;
}
// 在遇到特殊错误的时候删除文件
if (_enableResume)
ClearTempFileWhenError(_request.HttpCode);
// 最终释放请求器
_request.Dispose();
}
// 验证下载结果
if (_steps == ESteps.VerifyBundleFile)
{
if (_verifyOperation == null)
{
var element = new TempFileElement(_tempFilePath, _bundle.FileCRC, _bundle.FileSize);
_verifyOperation = new VerifyTempFileOperation(element);
_verifyOperation.StartOperation();
AddChildOperation(_verifyOperation);
}
if (IsWaitForAsyncComplete)
_verifyOperation.WaitForAsyncComplete();
_verifyOperation.UpdateOperation();
if (_verifyOperation.IsDone == false)
return;
if (_verifyOperation.Status == EOperationStatus.Succeed)
{
_steps = ESteps.CacheBundleFile;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _verifyOperation.Error;
// 注意:验证失败后直接删除文件
if (File.Exists(_tempFilePath))
File.Delete(_tempFilePath);
}
}
// 缓存文件
if (_steps == ESteps.CacheBundleFile)
{
if (_fileSystem.WriteCacheBundleFile(_bundle, _tempFilePath))
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"{_fileSystem.GetType().FullName} failed to write file !";
}
// 注意:缓存完成后直接删除临时文件
if (File.Exists(_tempFilePath))
File.Delete(_tempFilePath);
}
}
internal override void InternalAbort()
{
if (_request != null)
_request.AbortRequest();
}
internal override void InternalWaitForAsyncComplete()
{
if (_steps != ESteps.Done)
{
// 注意:不中断下载任务,保持后台继续下载
YooLogger.Error($"Try load bundle {_bundle.BundleName} from remote : {URL} !");
}
}
private IDownloadRequest CreateResumeRequest()
{
// 获取下载起始位置
if (File.Exists(_tempFilePath))
{
FileInfo fileInfo = new FileInfo(_tempFilePath);
if (fileInfo.Length >= _bundle.FileSize)
{
File.Delete(_tempFilePath);
}
else
{
_fileOriginLength = fileInfo.Length;
}
}
int watchdogTime = _fileSystem.DownloadWatchDogTime;
int timeout = 0; //注意:文件下载不做超时检测
bool appendToFile = true;
bool removeFileOnAbort = false;
long resumeFromBytes = _fileOriginLength;
var args = new DownloadFileRequestArgs(URL, _tempFilePath, timeout, watchdogTime, appendToFile, removeFileOnAbort, resumeFromBytes);
return _fileSystem.DownloadBackend.CreateFileRequest(args);
}
private IDownloadRequest CreateNormalRequest()
{
// 删除历史缓存文件
if (File.Exists(_tempFilePath))
File.Delete(_tempFilePath);
int watchdogTime = _fileSystem.DownloadWatchDogTime;
int timeout = 0; //注意:文件下载不做超时检测
var args = new DownloadFileRequestArgs(URL, _tempFilePath, timeout, watchdogTime);
return _fileSystem.DownloadBackend.CreateFileRequest(args);
}
private void ClearTempFileWhenError(long httpCode)
{
if (_fileSystem.ResumeDownloadResponseCodes == null)
return;
//说明:如果遇到以下错误返回码,验证失败直接删除文件
if (_fileSystem.ResumeDownloadResponseCodes.Contains(httpCode))
{
if (File.Exists(_tempFilePath))
File.Delete(_tempFilePath);
}
}
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 759967d543776a0469b625eff171d235
guid: ad910a81855976e44b4f1f09051910b0
MonoImporter:
externalObjects: {}
serializedVersion: 2
@@ -0,0 +1,185 @@
using System;
using System.Collections.Generic;
namespace YooAsset
{
/// <summary>
/// 下载调度器
/// </summary>
/// <remarks>
/// 管理所有活跃的下载任务,控制并发数量。
/// </remarks>
internal class DownloadSchedulerOperation : AsyncOperationBase, IDisposable
{
private readonly DefaultCacheFileSystem _fileSystem;
private readonly Dictionary<string, DownloadAndCacheFileOperation> _downloaders = new Dictionary<string, DownloadAndCacheFileOperation>(1000);
private readonly List<string> _removeList = new List<string>(1000);
/// <summary>
/// 是否已暂停
/// </summary>
public bool Paused { get; private set; } = false;
/// <summary>
/// 当前活跃的下载任务数
/// </summary>
public int ActiveDownloadCount { get; private set; }
/// <summary>
/// 当前等待中的下载任务数
/// </summary>
public int PendingDownloadCount
{
get
{
return _downloaders.Count - ActiveDownloadCount;
}
}
/// <summary>
/// 构造下载中心
/// </summary>
public DownloadSchedulerOperation(DefaultCacheFileSystem fileSystem)
{
_fileSystem = fileSystem;
}
internal override void InternalStart()
{
}
internal override void InternalUpdate()
{
// 驱动下载后台
_fileSystem.DownloadBackend.Update();
// 获取可移除的下载器集合
_removeList.Clear();
foreach (var valuePair in _downloaders)
{
var downloader = valuePair.Value;
downloader.UpdateOperation();
if (downloader.IsDone)
{
_removeList.Add(valuePair.Key);
continue;
}
// 注意:主动终止引用计数为零的下载任务
if (downloader.RefCount <= 0)
{
_removeList.Add(valuePair.Key);
downloader.AbortOperation();
continue;
}
}
// 移除下载器
foreach (var key in _removeList)
{
if (_downloaders.TryGetValue(key, out var downloader))
{
RemoveChildOperation(downloader);
_downloaders.Remove(key);
}
}
// 暂停时不启动新任务
if (Paused)
return;
// 最大并发数检测
ActiveDownloadCount = GetProcessingOperationCount();
if (ActiveDownloadCount != _downloaders.Count)
{
int maxConcurrency = _fileSystem.DownloadMaxConcurrency;
int maxRequestPerFrame = _fileSystem.DownloadMaxRequestPerFrame;
if (ActiveDownloadCount < maxConcurrency)
{
int startCount = maxConcurrency - ActiveDownloadCount;
if (startCount > maxRequestPerFrame)
startCount = maxRequestPerFrame;
foreach (var operationPair in _downloaders)
{
var operation = operationPair.Value;
if (operation.Status == EOperationStatus.None)
{
operation.StartOperation();
startCount--;
if (startCount <= 0)
break;
}
}
}
}
}
/// <summary>
/// 中止所有下载任务
/// </summary>
public void AbortAll()
{
foreach (var valuePair in _downloaders)
{
valuePair.Value.AbortOperation();
}
_downloaders.Clear();
}
/// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
AbortAll();
}
/// <summary>
/// 创建下载任务
/// </summary>
/// <param name="bundle">资源包信息</param>
/// <param name="url">下载地址</param>
/// <returns>下载操作</returns>
public DownloadAndCacheFileOperation DownloadAndCacheFileAsync(PackageBundle bundle, string url)
{
// 查询旧的下载器
if (_downloaders.TryGetValue(bundle.BundleGUID, out var oldDownloader))
{
oldDownloader.Reference();
return oldDownloader;
}
// 创建新的下载器
DownloadAndCacheFileOperation newDownloader;
bool isRequestLocalFile = DownloadSystemHelper.IsRequestLocalFile(url);
if (isRequestLocalFile)
{
newDownloader = new DownloadAndCacheLocalFileOperation(_fileSystem, bundle, url);
}
else
{
newDownloader = new DownloadAndCacheRemoteFileOperation(_fileSystem, bundle, url);
}
AddChildOperation(newDownloader);
_downloaders.Add(bundle.BundleGUID, newDownloader);
newDownloader.Reference();
return newDownloader;
}
/// <summary>
/// 获取正在进行中的下载器总数
/// </summary>
private int GetProcessingOperationCount()
{
int count = 0;
foreach (var operationPair in _downloaders)
{
var operation = operationPair.Value;
if (operation.Status != EOperationStatus.None)
count++;
}
return count;
}
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 40bb5e9391f413c42ae70e48ca90c4b7
guid: 73441e829c36a36418f5d677e189e9f3
MonoImporter:
externalObjects: {}
serializedVersion: 2
@@ -1,98 +0,0 @@
namespace YooAsset
{
internal abstract class UnityDownloadFileOperation : UnityWebRequestOperation
{
protected enum ESteps
{
None,
CreateRequest,
Download,
CopyLocalFile,
VerifyFile,
Done,
}
protected readonly DefaultCacheFileSystem _fileSystem;
protected readonly PackageBundle _bundle;
protected readonly string _tempFilePath;
private bool _watchDogInit = false;
private bool _watchDogAborted = false;
private ulong _lastDownloadBytes;
private double _lastGetDataTime;
/// <summary>
/// 引用计数
/// </summary>
public int RefCount { private set; get; }
internal UnityDownloadFileOperation(DefaultCacheFileSystem fileSystem, PackageBundle bundle, string url) : base(url)
{
_fileSystem = fileSystem;
_bundle = bundle;
_tempFilePath = _fileSystem.GetTempFilePath(bundle);
}
internal override string InternalGetDesc()
{
return $"RefCount : {RefCount}";
}
/// <summary>
/// 更新看门狗监测
/// 说明:监控时间范围内,如果没有接收到任何下载数据,那么直接终止任务!
/// </summary>
protected void UpdateWatchDog()
{
if (_fileSystem.DownloadWatchDogTime == int.MaxValue)
return;
if (_watchDogAborted)
return;
#if UNITY_2020_3_OR_NEWER
double realtimeSinceStartup = UnityEngine.Time.realtimeSinceStartupAsDouble;
#else
double realtimeSinceStartup = UnityEngine.Time.realtimeSinceStartup;
#endif
if (_watchDogInit == false)
{
_watchDogInit = true;
_lastDownloadBytes = 0;
_lastGetDataTime = realtimeSinceStartup;
}
if (_webRequest.downloadedBytes != _lastDownloadBytes)
{
_lastDownloadBytes = _webRequest.downloadedBytes;
_lastGetDataTime = realtimeSinceStartup;
}
else
{
double deltaTime = realtimeSinceStartup - _lastGetDataTime;
if (deltaTime > _fileSystem.DownloadWatchDogTime)
{
_watchDogAborted = true;
InternalAbort(); //终止网络请求
}
}
}
/// <summary>
/// 减少引用计数
/// </summary>
public void Release()
{
RefCount--;
}
/// <summary>
/// 增加引用计数
/// </summary>
public void Reference()
{
RefCount++;
}
}
}
@@ -1,124 +0,0 @@
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
namespace YooAsset
{
internal sealed class UnityDownloadNormalFileOperation : UnityDownloadFileOperation
{
private VerifyTempFileOperation _verifyOperation;
private ESteps _steps = ESteps.None;
internal UnityDownloadNormalFileOperation(DefaultCacheFileSystem fileSystem, PackageBundle bundle, string url)
: base(fileSystem, bundle, url)
{
}
internal override void InternalStart()
{
_steps = ESteps.CreateRequest;
}
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
// 创建下载器
if (_steps == ESteps.CreateRequest)
{
FileUtility.CreateFileDirectory(_tempFilePath);
if (File.Exists(_tempFilePath))
File.Delete(_tempFilePath);
CreateWebRequest();
_steps = ESteps.Download;
}
// 检测下载结果
if (_steps == ESteps.Download)
{
DownloadProgress = _webRequest.downloadProgress;
DownloadedBytes = (long)_webRequest.downloadedBytes;
Progress = DownloadProgress;
UpdateWatchDog();
if (_webRequest.isDone == false)
return;
// 检查网络错误
if (CheckRequestResult())
{
_steps = ESteps.VerifyFile;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
}
// 注意:最终释放请求器
DisposeRequest();
}
// 验证下载文件
if (_steps == ESteps.VerifyFile)
{
if (_verifyOperation == null)
{
var element = new TempFileElement(_tempFilePath, _bundle.FileCRC, _bundle.FileSize);
_verifyOperation = new VerifyTempFileOperation(element);
_verifyOperation.StartOperation();
AddChildOperation(_verifyOperation);
}
if (IsWaitForAsyncComplete)
_verifyOperation.WaitForAsyncComplete();
_verifyOperation.UpdateOperation();
if (_verifyOperation.IsDone == false)
return;
if (_verifyOperation.Status == EOperationStatus.Succeed)
{
if (_fileSystem.WriteCacheBundleFile(_bundle, _tempFilePath))
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"{_fileSystem.GetType().FullName} failed to write file ! {_tempFilePath}";
}
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _verifyOperation.Error;
}
// 注意:验证完成后直接删除文件
if (File.Exists(_tempFilePath))
File.Delete(_tempFilePath);
}
}
internal override void InternalWaitForAsyncComplete()
{
if (_steps != ESteps.Done)
{
YooLogger.Error($"Try load bundle {_bundle.BundleName} from remote : {_requestURL} !");
}
}
private void CreateWebRequest()
{
DownloadHandlerFile handler = new DownloadHandlerFile(_tempFilePath);
handler.removeFileOnAbort = true;
_webRequest = DownloadSystemHelper.NewUnityWebRequestGet(_requestURL);
_webRequest.downloadHandler = handler;
_webRequest.disposeDownloadHandlerOnDispose = true;
_webRequest.SendWebRequest();
}
}
}
@@ -1,158 +0,0 @@
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
namespace YooAsset
{
internal sealed class UnityDownloadResumeFileOperation : UnityDownloadFileOperation
{
private VerifyTempFileOperation _verifyOperation;
private long _fileOriginLength = 0;
private ESteps _steps = ESteps.None;
internal UnityDownloadResumeFileOperation(DefaultCacheFileSystem fileSystem, PackageBundle bundle, string url)
: base(fileSystem, bundle, url)
{
}
internal override void InternalStart()
{
_steps = ESteps.CreateRequest;
}
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
// 创建下载器
if (_steps == ESteps.CreateRequest)
{
FileUtility.CreateFileDirectory(_tempFilePath);
// 获取下载起始位置
_fileOriginLength = 0;
long fileBeginLength = -1;
if (File.Exists(_tempFilePath))
{
FileInfo fileInfo = new FileInfo(_tempFilePath);
if (fileInfo.Length >= _bundle.FileSize)
{
File.Delete(_tempFilePath);
}
else
{
fileBeginLength = fileInfo.Length;
_fileOriginLength = fileBeginLength;
DownloadedBytes = _fileOriginLength;
}
}
CreateWebRequest(fileBeginLength);
_steps = ESteps.Download;
}
// 检测下载结果
if (_steps == ESteps.Download)
{
DownloadProgress = _webRequest.downloadProgress;
DownloadedBytes = _fileOriginLength + (long)_webRequest.downloadedBytes;
Progress = DownloadProgress;
UpdateWatchDog();
if (_webRequest.isDone == false)
return;
// 检查网络错误
if (CheckRequestResult())
{
_steps = ESteps.VerifyFile;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
}
// 在遇到特殊错误的时候删除文件
ClearTempFileWhenError();
// 注意:最终释放请求器
DisposeRequest();
}
// 验证下载文件
if (_steps == ESteps.VerifyFile)
{
if (_verifyOperation == null)
{
var element = new TempFileElement(_tempFilePath, _bundle.FileCRC, _bundle.FileSize);
_verifyOperation = new VerifyTempFileOperation(element);
_verifyOperation.StartOperation();
AddChildOperation(_verifyOperation);
}
if (IsWaitForAsyncComplete)
_verifyOperation.WaitForAsyncComplete();
_verifyOperation.UpdateOperation();
if (_verifyOperation.IsDone == false)
return;
if (_verifyOperation.Status == EOperationStatus.Succeed)
{
if (_fileSystem.WriteCacheBundleFile(_bundle, _tempFilePath))
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"{_fileSystem.GetType().FullName} failed to write file !";
}
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _verifyOperation.Error;
}
// 注意:验证完成后直接删除文件
if (File.Exists(_tempFilePath))
File.Delete(_tempFilePath);
}
}
internal override void InternalWaitForAsyncComplete()
{
if (_steps != ESteps.Done)
{
YooLogger.Error($"Try load bundle {_bundle.BundleName} from remote : {_requestURL} !");
}
}
private void ClearTempFileWhenError()
{
if (_fileSystem.ResumeDownloadResponseCodes == null)
return;
//说明:如果遇到以下错误返回码,验证失败直接删除文件
if (_fileSystem.ResumeDownloadResponseCodes.Contains(HttpCode))
{
if (File.Exists(_tempFilePath))
File.Delete(_tempFilePath);
}
}
private void CreateWebRequest(long fileBeginLength)
{
var handler = new DownloadHandlerFile(_tempFilePath, true);
handler.removeFileOnAbort = false;
_webRequest = DownloadSystemHelper.NewUnityWebRequestGet(_requestURL);
_webRequest.downloadHandler = handler;
_webRequest.disposeDownloadHandlerOnDispose = true;
if (fileBeginLength > 0)
_webRequest.SetRequestHeader("Range", $"bytes={fileBeginLength}-");
_webRequest.SendWebRequest();
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 9bfb785912326f942a31dc77762eb16a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: