refactor : 重构代码

This commit is contained in:
何冠峰
2026-01-26 15:03:33 +08:00
parent 781e5950b1
commit 48a121f961
483 changed files with 2062 additions and 2971 deletions

View File

@@ -194,9 +194,9 @@ namespace YooAsset.Editor
int playerId = args.playerId;
var debugReport = DiagnosticReport.Deserialize(args.data);
if (debugReport.DebuggerVersion != DiagnosticSystemDefine.DebuggerVersion)
if (debugReport.ProtocolVersion != DiagnosticSystemDefine.ProtocolVersion)
{
Debug.LogWarning($"Debugger versions are inconsistent : {debugReport.DebuggerVersion} != {DiagnosticSystemDefine.DebuggerVersion}");
Debug.LogWarning($"Debugger versions are inconsistent : {debugReport.ProtocolVersion} != {DiagnosticSystemDefine.ProtocolVersion}");
return;
}
@@ -254,10 +254,10 @@ namespace YooAsset.Editor
private void OnRecordToggleValueChange(ChangeEvent<bool> evt)
{
// 发送采集数据的命令
RemoteDebugCommand command = new RemoteDebugCommand();
command.CommandType = (int)EDebugCommandType.AutoSampling;
DiagnosticCommand command = new DiagnosticCommand();
command.CommandType = (int)EDiagnosticCommandType.AutoSampling;
command.Parameter = evt.newValue ? "open" : "close";
byte[] data = RemoteDebugCommand.Serialize(command);
byte[] data = DiagnosticCommand.Serialize(command);
EditorConnection.instance.Send(DiagnosticSystemDefine.EditorToPlayerMessageId, data);
MockEditorConnection.Instance.Send(DiagnosticSystemDefine.EditorToPlayerMessageId, data);
}
@@ -265,10 +265,10 @@ namespace YooAsset.Editor
private void SampleBtn_onClick()
{
// 发送采集数据的命令
RemoteDebugCommand command = new RemoteDebugCommand();
command.CommandType = (int)EDebugCommandType.SampleOnce;
DiagnosticCommand command = new DiagnosticCommand();
command.CommandType = (int)EDiagnosticCommandType.SampleOnce;
command.Parameter = string.Empty;
byte[] data = RemoteDebugCommand.Serialize(command);
byte[] data = DiagnosticCommand.Serialize(command);
EditorConnection.instance.Send(DiagnosticSystemDefine.EditorToPlayerMessageId, data);
MockEditorConnection.Instance.Send(DiagnosticSystemDefine.EditorToPlayerMessageId, data);
}
@@ -289,7 +289,7 @@ namespace YooAsset.Editor
packageData.ProviderInfos.Sort();
foreach (var providerInfo in packageData.ProviderInfos)
{
providerInfo.DependentBundles.Sort();
providerInfo.Dependencies.Sort();
}
}

View File

@@ -315,9 +315,9 @@ namespace YooAsset.Editor
rowData.ProviderInfo = providerInfo;
rowData.AddAssetPathCell("PackageName", packageData.PackageName);
rowData.AddStringValueCell("AssetPath", providerInfo.AssetPath);
rowData.AddStringValueCell("SpawnScene", providerInfo.OriginScene);
rowData.AddStringValueCell("SpawnScene", providerInfo.SpawnScene);
rowData.AddStringValueCell("StartTime", providerInfo.StartTime);
rowData.AddLongValueCell("LoadingTime", providerInfo.ElapsedMS);
rowData.AddLongValueCell("LoadingTime", providerInfo.ElapsedMilliseconds);
rowData.AddLongValueCell("RefCount", providerInfo.ReferenceCount);
rowData.AddStringValueCell("Status", providerInfo.Status.ToString());
_sourceDatas.Add(rowData);
@@ -378,8 +378,8 @@ namespace YooAsset.Editor
DiagnosticProviderInfo providerInfo = providerTableData.ProviderInfo;
// 填充依赖数据
var sourceDatas = new List<ITableData>(providerInfo.DependentBundles.Count);
foreach (var bundleName in providerInfo.DependentBundles)
var sourceDatas = new List<ITableData>(providerInfo.Dependencies.Count);
foreach (var bundleName in providerInfo.Dependencies)
{
var dependBundleInfo = packageData.GetBundleInfo(bundleName);
var rowData = new DependTableData();

View File

@@ -446,14 +446,14 @@ namespace YooAsset.Editor
var sourceDatas = new List<ITableData>(1000);
foreach (var providerInfo in packageData.ProviderInfos)
{
foreach (var dependBundleName in providerInfo.DependentBundles)
foreach (var dependBundleName in providerInfo.Dependencies)
{
if (dependBundleName == selectBundleInfo.BundleName)
{
var rowData = new UsingTableData();
rowData.ProviderInfo = providerInfo;
rowData.AddStringValueCell("UsingAssets", providerInfo.AssetPath);
rowData.AddStringValueCell("SpawnScene", providerInfo.OriginScene);
rowData.AddStringValueCell("SpawnScene", providerInfo.SpawnScene);
rowData.AddStringValueCell("StartTime", providerInfo.StartTime);
rowData.AddLongValueCell("RefCount", providerInfo.ReferenceCount);
rowData.AddStringValueCell("Status", providerInfo.Status);
@@ -469,7 +469,7 @@ namespace YooAsset.Editor
// 填充ReferenceTableView
{
var sourceDatas = new List<ITableData>(1000);
foreach (string referenceBundleName in selectBundleInfo.ReferencedByBundles)
foreach (string referenceBundleName in selectBundleInfo.Referencers)
{
var bundleInfo = packageData.GetBundleInfo(referenceBundleName);
var rowData = new ReferenceTableData();

View File

@@ -321,7 +321,7 @@ namespace YooAsset.Editor
rowData.AddLongValueCell("Priority", operationInfo.Priority);
rowData.AddDoubleValueCell("Progress", operationInfo.Progress);
rowData.AddStringValueCell("StartTime", operationInfo.StartTime);
rowData.AddLongValueCell("ElapsedMS", operationInfo.ElapsedMS);
rowData.AddLongValueCell("ElapsedMS", operationInfo.ElapsedMilliseconds);
rowData.AddStringValueCell("Status", operationInfo.Status.ToString());
rowData.AddStringValueCell("Desc", operationInfo.OperationDesc);
_sourceDatas.Add(rowData);
@@ -474,7 +474,7 @@ namespace YooAsset.Editor
// ElapsedMS
{
var label = container.Q<Label>("ElapsedMS");
label.text = operationInfo.ElapsedMS.ToString();
label.text = operationInfo.ElapsedMilliseconds.ToString();
}
// Status

View File

@@ -13,13 +13,13 @@ namespace YooAsset
public abstract class AsyncOperationBase : IEnumerator, IComparable<AsyncOperationBase>
{
private List<AsyncOperationBase> _children;
private Action<AsyncOperationBase> _completedCallbacks;
private Action<AsyncOperationBase> _onCompleted;
private uint _priority;
/// <summary>
/// 是否正处于同步等待状态
/// </summary>
internal bool IsWaitingForAsyncComplete { get; private set; }
internal bool IsWaitForCompletion { get; private set; }
/// <summary>
/// 标记脏(用于调度器检测并重排)
@@ -38,9 +38,9 @@ namespace YooAsset
{
get
{
if (IsWaitingForAsyncComplete)
if (IsWaitForCompletion)
return false;
return OperationSystem.IsBusy;
return AsyncOperationSystem.IsBusy;
}
}
@@ -84,7 +84,7 @@ namespace YooAsset
{
get
{
return Status == EOperationStatus.Succeed || Status == EOperationStatus.Failed || Status == EOperationStatus.Aborted;
return Status == EOperationStatus.Succeeded || Status == EOperationStatus.Failed || Status == EOperationStatus.Aborted;
}
}
@@ -112,12 +112,12 @@ namespace YooAsset
}
else
{
_completedCallbacks += value;
_onCompleted += value;
}
}
remove
{
_completedCallbacks -= value;
_onCompleted -= value;
}
}
@@ -160,9 +160,9 @@ namespace YooAsset
/// 内部同步等待方法(子类可选实现)
/// 默认抛出异常,如果异步操作需要支持,子类应重写以支持同步等待
/// </summary>
internal virtual void InternalWaitForAsyncComplete()
internal virtual void InternalWaitForCompletion()
{
throw new YooInternalException($"InternalWaitForAsyncComplete() not implemented : {this.GetType().Name}");
throw new YooInternalException($"InternalWaitForCompletion not implemented : {this.GetType().Name}");
}
/// <summary>
@@ -321,9 +321,9 @@ namespace YooAsset
// 结束记录
DebugEndRecording();
if (_completedCallbacks != null)
if (_onCompleted != null)
{
var invocationList = _completedCallbacks.GetInvocationList();
var invocationList = _onCompleted.GetInvocationList();
foreach (var handler in invocationList)
{
try
@@ -337,7 +337,7 @@ namespace YooAsset
}
}
_completedCallbacks = null;
_onCompleted = null;
if (_taskCompletionSource != null)
_taskCompletionSource.TrySetResult(null);
}
@@ -346,7 +346,7 @@ namespace YooAsset
/// <summary>
/// 执行一次更新逻辑
/// </summary>
protected void RunOnceExecution()
protected void ExecuteOnce()
{
if (IsDone)
return;
@@ -361,7 +361,7 @@ namespace YooAsset
/// <remarks>
/// 用于需要快速完成但又不想完全阻塞主线程的场景。
/// </remarks>
protected void RunBatchExecution(int count = 1000)
protected void ExecuteBatch(int count = 1000)
{
if (IsDone)
return;
@@ -386,7 +386,7 @@ namespace YooAsset
/// 注意:该方法会阻塞主线程
/// </summary>
/// <param name="sleepMS">休眠时长</param>
protected void RunUntilCompletion(int sleepMS = 1)
protected void ExecuteUntilComplete(int sleepMS = 1)
{
if (IsDone)
return;
@@ -405,7 +405,7 @@ namespace YooAsset
/// <summary>
/// 同步等待异步执行完毕(会阻塞当前线程)
/// </summary>
public void WaitForAsyncComplete()
public void WaitForCompletion()
{
//注意:防止异步操作被挂起陷入无限死循环!
if (Status == EOperationStatus.None)
@@ -413,12 +413,12 @@ namespace YooAsset
StartOperation();
}
if (IsWaitingForAsyncComplete == false)
if (IsWaitForCompletion == false)
{
IsWaitingForAsyncComplete = true;
IsWaitForCompletion = true;
if (IsDone == false)
InternalWaitForAsyncComplete();
InternalWaitForCompletion();
if (IsDone == false)
{
@@ -441,7 +441,7 @@ namespace YooAsset
/// <summary>
/// 处理耗时(单位:毫秒)
/// </summary>
public long ElapsedMS { get; protected set; }
public long ElapsedMilliseconds { get; protected set; }
/// <summary>
/// 任务耗时计时器
@@ -463,7 +463,7 @@ namespace YooAsset
{
if (_stopwatch != null)
{
ElapsedMS = _stopwatch.ElapsedMilliseconds;
ElapsedMilliseconds = _stopwatch.ElapsedMilliseconds;
}
}
@@ -472,7 +472,7 @@ namespace YooAsset
{
if (_stopwatch != null)
{
ElapsedMS = _stopwatch.ElapsedMilliseconds;
ElapsedMilliseconds = _stopwatch.ElapsedMilliseconds;
_stopwatch = null;
}
}
@@ -535,7 +535,7 @@ namespace YooAsset
/// 获取调试信息
/// 注意:递归构建子树存在深度风险
/// </summary>
internal DiagnosticOperationInfo GetDebugOperationInfo()
internal DiagnosticOperationInfo GetDiagnosticInfo()
{
var operationInfo = new DiagnosticOperationInfo();
operationInfo.OperationName = this.GetType().Name;
@@ -543,7 +543,7 @@ namespace YooAsset
operationInfo.Priority = Priority;
operationInfo.Progress = Progress;
operationInfo.StartTime = StartTime;
operationInfo.ElapsedMS = ElapsedMS;
operationInfo.ElapsedMilliseconds = ElapsedMilliseconds;
operationInfo.Status = Status.ToString();
if (_children == null)
@@ -555,7 +555,7 @@ namespace YooAsset
operationInfo.Children = new List<DiagnosticOperationInfo>(_children.Count);
foreach (var child in _children)
{
var childInfo = child.GetDebugOperationInfo();
var childInfo = child.GetDiagnosticInfo();
operationInfo.Children.Add(childInfo);
}
}

View File

@@ -6,9 +6,9 @@ namespace YooAsset
/// <summary>
/// 异步操作调度器
/// </summary>
internal class OperationScheduler : IComparable<OperationScheduler>
internal class AsyncOperationScheduler : IComparable<AsyncOperationScheduler>
{
private readonly List<AsyncOperationBase> _operations = new List<AsyncOperationBase>(100);
private readonly List<AsyncOperationBase> _runningOperations = new List<AsyncOperationBase>(100);
private readonly List<AsyncOperationBase> _pendingOperations = new List<AsyncOperationBase>(100);
private uint _priority;
@@ -44,7 +44,7 @@ namespace YooAsset
public int CreationOrder { get; private set; }
public OperationScheduler(string packageName, int creationOrder)
public AsyncOperationScheduler(string packageName, int creationOrder)
{
PackageName = packageName;
CreationOrder = creationOrder;
@@ -69,25 +69,25 @@ namespace YooAsset
public void Update()
{
// 移除已经完成的异步操作
for (int i = _operations.Count - 1; i >= 0; i--)
for (int i = _runningOperations.Count - 1; i >= 0; i--)
{
var operation = _operations[i];
var operation = _runningOperations[i];
if (operation.IsFinished)
{
_operations.RemoveAt(i);
_runningOperations.RemoveAt(i);
}
}
// 添加新增的异步操作
if (_pendingOperations.Count > 0)
{
_operations.AddRange(_pendingOperations);
_runningOperations.AddRange(_pendingOperations);
_pendingOperations.Clear();
}
// 检测是否需要执行排序
bool isDirty = false;
foreach (var operation in _operations)
foreach (var operation in _runningOperations)
{
if (operation.IsDirty)
{
@@ -97,17 +97,17 @@ namespace YooAsset
}
if (isDirty)
{
_operations.Sort();
_runningOperations.Sort();
}
// 更新进行中的异步操作
for (int i = 0; i < _operations.Count; i++)
for (int i = 0; i < _runningOperations.Count; i++)
{
// 检查全局时间切片预算
if (OperationSystem.IsBusy)
if (AsyncOperationSystem.IsBusy)
break;
var operation = _operations[i];
var operation = _runningOperations[i];
if (operation.IsFinished)
continue;
@@ -116,9 +116,9 @@ namespace YooAsset
}
/// <summary>
/// 清空并中止所有任务
/// 中止所有任务
/// </summary>
public void ClearAll()
public void AbortAll()
{
// 终止临时队列里的任务
foreach (var operation in _pendingOperations)
@@ -128,32 +128,32 @@ namespace YooAsset
_pendingOperations.Clear();
// 终止正在进行的任务
foreach (var operation in _operations)
foreach (var operation in _runningOperations)
{
operation.AbortOperation();
}
_operations.Clear();
_runningOperations.Clear();
}
/// <summary>
/// 获取调试信息
/// </summary>
public List<DiagnosticOperationInfo> GetDebugOperationInfos()
public List<DiagnosticOperationInfo> GetDiagnosticInfos()
{
int totalCount = _operations.Count + _pendingOperations.Count;
int totalCount = _runningOperations.Count + _pendingOperations.Count;
List<DiagnosticOperationInfo> result = new List<DiagnosticOperationInfo>(totalCount);
// 包含正在执行的任务
foreach (var operation in _operations)
foreach (var operation in _runningOperations)
{
var operationInfo = operation.GetDebugOperationInfo();
var operationInfo = operation.GetDiagnosticInfo();
result.Add(operationInfo);
}
// 包含待处理的新任务
foreach (var operation in _pendingOperations)
{
var operationInfo = operation.GetDebugOperationInfo();
var operationInfo = operation.GetDiagnosticInfo();
result.Add(operationInfo);
}
@@ -161,7 +161,7 @@ namespace YooAsset
}
#region
public int CompareTo(OperationScheduler other)
public int CompareTo(AsyncOperationScheduler other)
{
// 优先级高的排前面
int result = other.Priority.CompareTo(this.Priority);

View File

@@ -9,7 +9,7 @@ namespace YooAsset
/// 异步操作系统(静态调度器)
/// 负责管理所有包裹的调度器,提供时间切片执行机制
/// </summary>
internal static class OperationSystem
internal static class AsyncOperationSystem
{
#if UNITY_EDITOR
[UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)]
@@ -22,14 +22,14 @@ namespace YooAsset
public const string GlobalSchedulerName = "YOOASSET_GLOBAL_SCHEDULER"; // 全局调度器名称
private const long MinTimeSlice = 10; // 最小时间片(毫秒)
private static readonly Dictionary<string, OperationScheduler> _schedulerDict = new Dictionary<string, OperationScheduler>(100);
private static readonly List<OperationScheduler> _schedulerList = new List<OperationScheduler>(100);
private static readonly Dictionary<string, AsyncOperationScheduler> _schedulerDict = new Dictionary<string, AsyncOperationScheduler>(100);
private static readonly List<AsyncOperationScheduler> _schedulerList = new List<AsyncOperationScheduler>(100);
private static bool _isInitialized;
private static int _nextCreationIndex;
private static int _nextCreationOrder;
// 计时器相关
private static Stopwatch _systemStopwatch;
private static long _frameTime;
private static Stopwatch _stopwatch;
private static long _frameStartTime;
private static long _maxTimeSlice = long.MaxValue;
/// <summary>
@@ -62,14 +62,14 @@ namespace YooAsset
{
get
{
if (_systemStopwatch == null)
if (_stopwatch == null)
return false;
if (_maxTimeSlice == long.MaxValue)
return false;
// 注意 : 单次调用开销约1微秒
return _systemStopwatch.ElapsedMilliseconds - _frameTime >= _maxTimeSlice;
return _stopwatch.ElapsedMilliseconds - _frameStartTime >= _maxTimeSlice;
}
}
@@ -86,7 +86,7 @@ namespace YooAsset
}
_isInitialized = true;
_systemStopwatch = Stopwatch.StartNew();
_stopwatch = Stopwatch.StartNew();
// 创建全局调度器
CreatePackageScheduler(GlobalSchedulerName, uint.MaxValue);
@@ -116,7 +116,7 @@ namespace YooAsset
}
// 更新帧时间
_frameTime = _systemStopwatch.ElapsedMilliseconds;
_frameStartTime = _stopwatch.ElapsedMilliseconds;
// 更新调度器
for (int i = 0; i < _schedulerList.Count; i++)
@@ -138,30 +138,30 @@ namespace YooAsset
// 清空所有调度器
foreach (var scheduler in _schedulerList)
{
scheduler.ClearAll();
scheduler.AbortAll();
}
_schedulerDict.Clear();
_schedulerList.Clear();
_nextCreationIndex = 0;
_nextCreationOrder = 0;
_systemStopwatch = null;
_frameTime = 0;
_stopwatch = null;
_frameStartTime = 0;
_maxTimeSlice = long.MaxValue;
}
/// <summary>
/// 创建包裹调度器
/// </summary>
public static OperationScheduler CreatePackageScheduler(string packageName, uint priority)
public static AsyncOperationScheduler CreatePackageScheduler(string packageName, uint priority)
{
DebugEnsureInitialized(packageName);
DebugCheckInitialized(packageName);
if (_schedulerDict.ContainsKey(packageName))
{
throw new YooInternalException($"Package scheduler already exists: {packageName}");
}
var scheduler = new OperationScheduler(packageName, _nextCreationIndex++);
var scheduler = new AsyncOperationScheduler(packageName, _nextCreationOrder++);
_schedulerDict.Add(packageName, scheduler);
_schedulerList.Add(scheduler);
scheduler.Priority = priority;
@@ -173,7 +173,7 @@ namespace YooAsset
/// </summary>
public static void DestroyPackageScheduler(string packageName)
{
DebugEnsureInitialized(packageName);
DebugCheckInitialized(packageName);
// 不允许销毁默认调度器
if (packageName == GlobalSchedulerName)
@@ -183,7 +183,7 @@ namespace YooAsset
if (_schedulerDict.TryGetValue(packageName, out var scheduler))
{
scheduler.ClearAll();
scheduler.AbortAll();
_schedulerDict.Remove(packageName);
_schedulerList.Remove(scheduler);
}
@@ -194,10 +194,10 @@ namespace YooAsset
/// </summary>
public static void ClearPackageOperations(string packageName)
{
DebugEnsureInitialized(packageName);
DebugCheckInitialized(packageName);
var scheduler = GetScheduler(packageName);
scheduler.ClearAll();
scheduler.AbortAll();
}
/// <summary>
@@ -205,7 +205,7 @@ namespace YooAsset
/// </summary>
public static void StartOperation(string packageName, AsyncOperationBase operation)
{
DebugEnsureInitialized(packageName);
DebugCheckInitialized(packageName);
var scheduler = GetScheduler(packageName);
scheduler.StartOperation(operation);
@@ -216,7 +216,7 @@ namespace YooAsset
/// </summary>
public static void SetSchedulerPriority(string packageName, uint priority)
{
DebugEnsureInitialized(packageName);
DebugCheckInitialized(packageName);
var scheduler = GetScheduler(packageName);
scheduler.Priority = priority;
@@ -227,7 +227,7 @@ namespace YooAsset
/// </summary>
public static uint GetSchedulerPriority(string packageName)
{
DebugEnsureInitialized(packageName);
DebugCheckInitialized(packageName);
var scheduler = GetScheduler(packageName);
return scheduler.Priority;
@@ -236,7 +236,7 @@ namespace YooAsset
/// <summary>
/// 获取调度器(严格模式)
/// </summary>
private static OperationScheduler GetScheduler(string packageName)
private static AsyncOperationScheduler GetScheduler(string packageName)
{
if (_schedulerDict.TryGetValue(packageName, out var scheduler))
{
@@ -248,24 +248,24 @@ namespace YooAsset
}
#region
internal static List<DiagnosticOperationInfo> GetDebugOperationInfos(string packageName)
internal static List<DiagnosticOperationInfo> GetDiagnosticInfos(string packageName)
{
DebugEnsureInitialized(packageName);
DebugCheckInitialized(packageName);
var scheduler = GetScheduler(packageName);
return scheduler.GetDebugOperationInfos();
return scheduler.GetDiagnosticInfos();
}
#endregion
#region
[Conditional("DEBUG")]
private static void DebugEnsureInitialized(string packageName)
private static void DebugCheckInitialized(string packageName)
{
if (string.IsNullOrWhiteSpace(packageName))
throw new YooInternalException("Package name is null or empty.");
if (_isInitialized == false)
throw new YooInternalException($"{nameof(OperationSystem)} not initialized.");
throw new YooInternalException($"{nameof(AsyncOperationSystem)} not initialized.");
}
#endregion
}

View File

@@ -19,7 +19,7 @@ namespace YooAsset
/// <summary>
/// 已成功
/// </summary>
Succeed,
Succeeded,
/// <summary>
/// 已失败

View File

@@ -5,10 +5,10 @@ using UnityEngine.Networking.PlayerConnection;
namespace YooAsset
{
/// <summary>
/// 远程调试行为组件
/// 诊断行为组件
/// 负责接收 Editor 命令并发送诊断数据
/// </summary>
internal class RemoteDebugBehaviour : MonoBehaviour
internal class DiagnosticBehaviour : MonoBehaviour
{
#if UNITY_EDITOR
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
@@ -62,13 +62,13 @@ namespace YooAsset
private static void HandleEditorMessage(MessageEventArgs args)
{
var command = RemoteDebugCommand.Deserialize(args.data);
YooLogger.Log($"Handle remote command: {command.CommandType} Param: {command.Parameter}");
if (command.CommandType == (int)EDebugCommandType.SampleOnce)
var command = DiagnosticCommand.Deserialize(args.data);
YooLogger.Log($"[{nameof(DiagnosticBehaviour)}] Handle command: Type={command.CommandType}, Param={command.Parameter}");
if (command.CommandType == (int)EDiagnosticCommandType.SampleOnce)
{
_sampleOnce = true;
}
else if (command.CommandType == (int)EDebugCommandType.AutoSampling)
else if (command.CommandType == (int)EDiagnosticCommandType.AutoSampling)
{
if (command.Parameter == "open")
_autoSampling = true;
@@ -77,7 +77,7 @@ namespace YooAsset
}
else
{
throw new NotImplementedException(command.CommandType.ToString());
throw new NotImplementedException($"Unknown diagnostic command type: {command.CommandType}");
}
}
}

View File

@@ -5,11 +5,11 @@ using UnityEngine;
namespace YooAsset
{
/// <summary>
/// 远程调试命令
/// 用于 Editor 向 Player 发送调试指令
/// 诊断命令
/// 用于 Editor 向 Player 发送诊断指令
/// </summary>
[Serializable]
internal class RemoteDebugCommand
internal class DiagnosticCommand
{
/// <summary>
/// 命令类型
@@ -25,9 +25,9 @@ namespace YooAsset
/// <summary>
/// 序列化命令为字节数组
/// </summary>
/// <param name="command">要序列化的远程调试命令</param>
/// <param name="command">要序列化的诊断命令</param>
/// <returns>UTF-8 编码的 JSON 字节数组</returns>
public static byte[] Serialize(RemoteDebugCommand command)
public static byte[] Serialize(DiagnosticCommand command)
{
return Encoding.UTF8.GetBytes(JsonUtility.ToJson(command));
}
@@ -36,10 +36,10 @@ namespace YooAsset
/// 从字节数组反序列化命令
/// </summary>
/// <param name="data">UTF-8 编码的 JSON 字节数组</param>
/// <returns>反序列化后的远程调试命令</returns>
public static RemoteDebugCommand Deserialize(byte[] data)
/// <returns>反序列化后的诊断命令</returns>
public static DiagnosticCommand Deserialize(byte[] data)
{
return JsonUtility.FromJson<RemoteDebugCommand>(Encoding.UTF8.GetString(data));
return JsonUtility.FromJson<DiagnosticCommand>(Encoding.UTF8.GetString(data));
}
}
}

View File

@@ -1,6 +1,7 @@
fileFormatVersion: 2
guid: 2c65d3ba8da65004b8d36dbeecc6c6be
TextScriptImporter:
guid: c137052d50eadfa4caf6b2157ebeeaad
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:

View File

@@ -26,9 +26,9 @@ namespace YooAsset
public string Status;
/// <summary>
/// 该资源包被谁引用
/// 引用该资源包的其他资源包列表
/// </summary>
public List<string> ReferencedByBundles;
public List<string> Referencers;
public int CompareTo(DiagnosticBundleInfo other)
{

View File

@@ -33,7 +33,7 @@ namespace YooAsset
/// <summary>
/// 处理耗时(单位:毫秒)
/// </summary>
public long ElapsedMS;
public long ElapsedMilliseconds;
/// <summary>
/// 异步操作的执行进度

View File

@@ -10,6 +10,9 @@ namespace YooAsset
[Serializable]
internal class DiagnosticPackageData
{
private readonly Dictionary<string, DiagnosticBundleInfo> _bundleInfoDict = new Dictionary<string, DiagnosticBundleInfo>();
private bool _isParsed = false;
/// <summary>
/// 包裹名称
/// </summary>
@@ -30,15 +33,11 @@ namespace YooAsset
/// </summary>
public List<DiagnosticOperationInfo> OperationInfos = new List<DiagnosticOperationInfo>(1000);
private readonly Dictionary<string, DiagnosticBundleInfo> _bundleInfoDict = new Dictionary<string, DiagnosticBundleInfo>();
private bool _isParsed = false;
/// <summary>
/// 获取资源包的诊断信息
/// </summary>
public DiagnosticBundleInfo GetBundleInfo(string bundleName)
{
// 解析数据
if (_isParsed == false)
{
_isParsed = true;
@@ -57,7 +56,7 @@ namespace YooAsset
}
else
{
UnityEngine.Debug.LogError($"Cannot find {nameof(DiagnosticBundleInfo)} : {bundleName}");
YooLogger.Error($"[{nameof(DiagnosticPackageData)}] Bundle info not found: {bundleName}");
return default;
}
}

View File

@@ -16,10 +16,10 @@ namespace YooAsset
public string AssetPath;
/// <summary>
/// 资源加载时所在的场景
/// 资源加载时的活跃场景
/// </summary>
public string OriginScene;
public string SpawnScene;
/// <summary>
/// 资源加载开始时间
/// </summary>
@@ -28,7 +28,7 @@ namespace YooAsset
/// <summary>
/// 加载耗时(单位:毫秒)
/// </summary>
public long ElapsedMS;
public long ElapsedMilliseconds;
/// <summary>
/// 引用计数
@@ -41,9 +41,9 @@ namespace YooAsset
public string Status;
/// <summary>
/// 依赖的资源包列表
/// 资源依赖的资源包列表
/// </summary>
public List<string> DependentBundles;
public List<string> Dependencies;
public int CompareTo(DiagnosticProviderInfo other)
{

View File

@@ -13,9 +13,9 @@ namespace YooAsset
internal class DiagnosticReport
{
/// <summary>
/// 调试器版本
/// 通信协议版本
/// </summary>
public string DebuggerVersion = DiagnosticSystemDefine.DebuggerVersion;
public string ProtocolVersion;
/// <summary>
/// 报告发生的游戏帧
@@ -27,6 +27,18 @@ namespace YooAsset
/// </summary>
public List<DiagnosticPackageData> PackageDataList = new List<DiagnosticPackageData>(10);
/// <summary>
/// 创建新的诊断报告
/// </summary>
public static DiagnosticReport Create()
{
var report = new DiagnosticReport();
report.ProtocolVersion = DiagnosticSystemDefine.ProtocolVersion;
report.FrameCount = Time.frameCount;
return report;
}
/// <summary>
/// 序列化诊断报告为字节数组
/// </summary>

View File

@@ -8,9 +8,9 @@ namespace YooAsset
internal class DiagnosticSystemDefine
{
/// <summary>
/// 调试器版本号
/// 通信协议版本号
/// </summary>
public const string DebuggerVersion = "1.0";
public const string ProtocolVersion = "1.0";
/// <summary>
/// Player 向 Editor 发送消息的标识符

View File

@@ -2,9 +2,9 @@
namespace YooAsset
{
/// <summary>
/// 远程调试命令类型
/// 诊断命令类型
/// </summary>
internal enum EDebugCommandType
internal enum EDiagnosticCommandType
{
/// <summary>
/// 采样一次

View File

@@ -53,7 +53,7 @@ namespace YooAsset
public void Register(Guid messageID, UnityAction<MessageEventArgs> callback)
{
if (messageID == Guid.Empty)
throw new ArgumentException("messageID is empty.");
throw new ArgumentException("Message ID cannot be empty.", nameof(messageID));
if (_messageHandlers.ContainsKey(messageID) == false)
_messageHandlers.Add(messageID, callback);
@@ -77,7 +77,7 @@ namespace YooAsset
public void Send(Guid messageID, byte[] data)
{
if (messageID == Guid.Empty)
throw new ArgumentException("messageID is empty.");
throw new ArgumentException("Message ID cannot be empty.", nameof(messageID));
// 接收对方的消息
MockPlayerConnection.Instance.HandleEditorMessage(messageID, data);

View File

@@ -53,7 +53,7 @@ namespace YooAsset
public void Register(Guid messageID, UnityAction<MessageEventArgs> callback)
{
if (messageID == Guid.Empty)
throw new ArgumentException("messageID is empty.");
throw new ArgumentException("Message ID cannot be empty.", nameof(messageID));
if (_messageHandlers.ContainsKey(messageID) == false)
_messageHandlers.Add(messageID, callback);
@@ -77,7 +77,7 @@ namespace YooAsset
public void Send(Guid messageID, byte[] data)
{
if (messageID == Guid.Empty)
throw new ArgumentException("messageID is empty.");
throw new ArgumentException("Message ID cannot be empty.", nameof(messageID));
// 接收对方的消息
MockEditorConnection.Instance.HandlePlayerMessage(messageID, data);

View File

@@ -1,478 +0,0 @@
# DiagnosticSystem 诊断模块
## 模块概述
DiagnosticSystem 是 YooAsset 资源管理系统的**运行时诊断模块**,负责收集和传输资源加载的实时状态信息。该模块支持 Editor 与运行时 Player 之间的双向通信,为 AssetBundleDebugger 窗口提供数据支持。
### 可见性说明
DiagnosticSystem 属于 YooAsset Runtime 的内部基础模块,目录内大多数类型为 `internal`(仅供 YooAsset Runtime 内部程序集使用)。业务层建议通过 `YooAssets.GetDebugReport()` 等上层接口获取诊断数据,避免直接依赖本模块的内部类型。
### 核心职责
- 资源加载状态采集
- Provider/Bundle/Operation 诊断信息汇总
- Editor 与 Player 的远程通信
- 诊断数据的序列化与传输
---
## 边界与上层协作
DiagnosticSystem 的职责是提供"诊断数据采集 + 远程通信 + 序列化传输"的基础能力:
- **本模块不负责数据展示**:数据展示由 Editor 端的 AssetBundleDebugger 窗口实现。
- **本模块不负责数据持久化**:诊断数据为实时采集,不进行本地存储。
- **本模块不负责数据分析**:数据分析和统计由上层调试工具实现。
---
## 设计目标
| 目标 | 说明 |
|------|------|
| **实时性** | 支持单次采样和持续采样两种模式 |
| **低侵入** | 仅在需要时采集数据,不影响正常运行性能 |
| **跨平台** | 支持 Editor 模拟和真机远程调试 |
| **可扩展性** | 诊断数据结构支持版本控制 |
---
## 架构概念
### 分层架构
```
┌─────────────────────────────────────────────────────────┐
│ AssetBundleDebugger (Editor) │
│ (数据展示层) │
└─────────────────────────┬───────────────────────────────┘
┌─────────────────────────▼───────────────────────────────┐
│ MockEditorConnection (Editor) │
│ EditorConnection (Runtime) │
│ (通信层) │
└─────────────────────────┬───────────────────────────────┘
│ 双向消息
┌─────────────────────────▼───────────────────────────────┐
│ MockPlayerConnection (Editor) │
│ PlayerConnection (Runtime) │
│ (通信层) │
└─────────────────────────┬───────────────────────────────┘
┌─────────────────────────▼───────────────────────────────┐
│ RemoteDebugBehaviour │
│ (运行时组件) │
│ 接收命令、采集数据、发送报告 │
└─────────────────────────┬───────────────────────────────┘
┌─────────────────────────▼───────────────────────────────┐
│ DiagnosticReport │
│ (数据模型) │
│ DiagnosticPackageData / ProviderInfo / BundleInfo │
└─────────────────────────────────────────────────────────┘
```
### 核心组件
- **数据模型层**: 定义诊断数据的结构DiagnosticReport、DiagnosticPackageData 等)
- **通信层**: 处理 Editor 与 Player 之间的消息传递
- **行为组件层**: 运行时 MonoBehaviour负责采集和发送诊断数据
---
## 文件结构
```
DiagnosticSystem/
├── DiagnosticBundleInfo.cs # 资源包诊断信息
├── DiagnosticOperationInfo.cs # 异步操作诊断信息
├── DiagnosticPackageData.cs # 包裹诊断数据容器
├── DiagnosticProviderInfo.cs # 资源加载诊断信息
├── DiagnosticReport.cs # 诊断报告(顶层数据结构)
├── DiagnosticSystemDefine.cs # 诊断系统常量定义
├── EDebugCommandType.cs # 调试命令类型枚举
├── RemoteDebugCommand.cs # 远程调试命令
├── RemoteDebugBehaviour.cs # 运行时调试组件
├── MockEditorConnection.cs # 模拟 Editor 连接Editor 用)
└── MockPlayerConnection.cs # 模拟 Player 连接Editor 用)
```
---
## 数据模型
### DiagnosticReport诊断报告
顶层数据结构,包含所有包裹的诊断数据。
```csharp
[Serializable]
internal class DiagnosticReport
{
/// <summary>
/// 调试器版本
/// </summary>
public string DebuggerVersion;
/// <summary>
/// 报告发生的游戏帧
/// </summary>
public int FrameCount;
/// <summary>
/// 包裹数据列表
/// </summary>
public List<DiagnosticPackageData> PackageDataList;
// 序列化/反序列化方法
public static byte[] Serialize(DiagnosticReport report);
public static DiagnosticReport Deserialize(byte[] data);
}
```
### DiagnosticPackageData包裹诊断数据
单个包裹的诊断数据容器。
```csharp
[Serializable]
internal class DiagnosticPackageData
{
/// <summary>
/// 包裹名称
/// </summary>
public string PackageName;
/// <summary>
/// 资源加载的诊断信息列表
/// </summary>
public List<DiagnosticProviderInfo> ProviderInfos;
/// <summary>
/// 资源包的诊断信息列表
/// </summary>
public List<DiagnosticBundleInfo> BundleInfos;
/// <summary>
/// 异步操作的诊断信息列表
/// </summary>
public List<DiagnosticOperationInfo> OperationInfos;
// 快速查找方法
public DiagnosticBundleInfo GetBundleInfo(string bundleName);
}
```
### DiagnosticProviderInfo资源加载诊断信息
描述单个资源加载操作的状态。
```csharp
[Serializable]
internal struct DiagnosticProviderInfo
{
public string AssetPath; // 资源对象路径
public string OriginScene; // 资源加载时所在的场景
public string StartTime; // 资源加载开始时间
public long ElapsedMS; // 加载耗时(毫秒)
public int ReferenceCount; // 引用计数
public string Status; // 资源的加载状态
public List<string> DependentBundles; // 依赖的资源包列表
}
```
### DiagnosticBundleInfo资源包诊断信息
描述单个 AssetBundle 的状态。
```csharp
[Serializable]
internal struct DiagnosticBundleInfo
{
public string BundleName; // 资源包名称
public int ReferenceCount; // 引用计数
public string Status; // 资源包的加载状态
public List<string> ReferencedByBundles; // 该资源包被谁引用
}
```
### DiagnosticOperationInfo异步操作诊断信息
描述异步操作的执行状态。
```csharp
[Serializable]
internal struct DiagnosticOperationInfo
{
public string OperationName; // 异步操作的名称
public string OperationDesc; // 异步操作的说明
public uint Priority; // 异步操作的优先级
public string StartTime; // 开始的时间
public long ElapsedMS; // 处理耗时(毫秒)
public float Progress; // 异步操作的执行进度
public string Status; // 异步操作的执行状态
public List<DiagnosticOperationInfo> Children; // 子任务列表
}
```
---
## 通信协议
### 命令类型
```csharp
/// <summary>
/// 远程调试命令类型
/// </summary>
internal enum EDebugCommandType
{
/// <summary>
/// 采样一次
/// </summary>
SampleOnce = 0,
/// <summary>
/// 持续采样
/// </summary>
AutoSampling = 1,
}
```
### RemoteDebugCommand远程调试命令
Editor 向 Player 发送的调试指令。
```csharp
[Serializable]
internal class RemoteDebugCommand
{
/// <summary>
/// 命令类型
/// </summary>
public int CommandType;
/// <summary>
/// 命令附加参数
/// </summary>
public string Parameter;
// 序列化/反序列化方法
public static byte[] Serialize(RemoteDebugCommand command);
public static RemoteDebugCommand Deserialize(byte[] data);
}
```
### 消息标识符
```csharp
internal class DiagnosticSystemDefine
{
/// <summary>
/// 调试器版本号
/// </summary>
public const string DebuggerVersion = "1.0";
/// <summary>
/// Player 向 Editor 发送消息的标识符
/// </summary>
public static readonly Guid PlayerToEditorMessageId;
/// <summary>
/// Editor 向 Player 发送消息的标识符
/// </summary>
public static readonly Guid EditorToPlayerMessageId;
}
```
---
## 核心类说明
### RemoteDebugBehaviour
运行时调试组件,负责接收 Editor 命令并发送诊断数据。
**职责:**
- 监听 Editor 发送的调试命令
- 根据命令采集诊断数据
- 将诊断报告发送回 Editor
**采样模式:**
- `SampleOnce`: 单次采样,采集一帧数据后停止
- `AutoSampling`: 持续采样,每帧自动采集并发送数据
**生命周期:**
```
Awake() ──► 初始化连接
OnEnable() ──► 注册消息处理器
LateUpdate()──► 检查采样标志,采集并发送数据
OnDisable() ──► 注销消息处理器
```
### MockEditorConnection / MockPlayerConnection
Editor 模式下的模拟连接,用于本地调试。
**特性:**
- 在 Editor 中模拟 `EditorConnection``PlayerConnection` 的行为
- 实现消息的本地传递,无需真机连接
- 支持消息注册、注销和发送
```csharp
// 注册消息处理器
MockPlayerConnection.Instance.Register(messageId, callback);
// 发送消息
MockPlayerConnection.Instance.Send(messageId, data);
// 注销消息处理器
MockPlayerConnection.Instance.Unregister(messageId);
```
---
## 通信流程
### Editor 模式(本地调试)
```
┌───────────────────┐ ┌───────────────────┐
│ Debugger Window │ │ RemoteDebugBehaviour│
│ (Editor) │ │ (PlayMode) │
└────────┬──────────┘ └────────┬──────────┘
│ │
│ 1. 发送采样命令 │
│ ─────────────────────────────►
│ MockEditorConnection.Send() │
│ │
│ │ 2. 接收命令
│ │ HandleEditorMessage()
│ │
│ │ 3. 采集诊断数据
│ │ YooAssets.GetDebugReport()
│ │
│ 4. 返回诊断报告 │
│ ◄─────────────────────────────
│ MockPlayerConnection.Send() │
│ │
│ 5. 解析并展示数据 │
▼ ▼
```
### 真机模式(远程调试)
```
┌───────────────────┐ ┌───────────────────┐
│ Debugger Window │ │ RemoteDebugBehaviour│
│ (Editor) │ USB │ (Device) │
└────────┬──────────┘ ════ └────────┬──────────┘
│ │
│ 1. 发送采样命令 │
│ ─────────────────────────────►
│ EditorConnection.Send() │
│ │
│ │ 2. 接收命令
│ │ PlayerConnection.Register()
│ │
│ │ 3. 采集诊断数据
│ │
│ 4. 返回诊断报告 │
│ ◄─────────────────────────────
│ PlayerConnection.Send() │
│ │
│ 5. 解析并展示数据 │
▼ ▼
```
---
## 设计模式
### 单例模式
`MockEditorConnection``MockPlayerConnection` 使用单例模式:
```csharp
public static MockPlayerConnection Instance
{
get
{
if (_instance == null)
_instance = new MockPlayerConnection();
return _instance;
}
}
```
### 观察者模式
通过消息注册机制实现观察者模式:
```
Register(messageId, callback) ──► 订阅消息
Unregister(messageId) ──► 取消订阅
HandleXxxMessage(messageId, data) ──► 通知订阅者
```
### 命令模式
`RemoteDebugCommand` 封装调试指令:
```
Editor Player
│ │
│ RemoteDebugCommand │
│ ┌──────────────────┐ │
│ │ CommandType: 0 │ │
│ │ Parameter: "" │ ──────►│ 执行采样
│ └──────────────────┘ │
│ │
│ RemoteDebugCommand │
│ ┌──────────────────┐ │
│ │ CommandType: 1 │ │
│ │ Parameter: "open"│ ──────►│ 开启持续采样
│ └──────────────────┘ │
```
---
## 类关系图
```
DiagnosticReport (诊断报告)
└── List<DiagnosticPackageData> (包裹数据)
├── List<DiagnosticProviderInfo> (资源加载信息)
├── List<DiagnosticBundleInfo> (资源包信息)
└── List<DiagnosticOperationInfo> (异步操作信息)
└── List<DiagnosticOperationInfo> (子任务)
RemoteDebugBehaviour (运行时组件)
├── 接收 ──► RemoteDebugCommand
└── 发送 ──► DiagnosticReport
MockEditorConnection ◄────► MockPlayerConnection (Editor 模拟通信)
EditorConnection ◄────► PlayerConnection (真机通信)
```
---
## 注意事项
1. **采样性能**:持续采样模式会每帧采集数据,可能影响性能,建议仅在调试时使用
2. **数据序列化**:使用 Unity 的 `JsonUtility` 进行序列化,受其限制(如不支持 Dictionary
3. **版本兼容**`DebuggerVersion` 用于版本控制Editor 和 Player 版本不匹配时可能无法正常工作
4. **子任务深度**`DiagnosticOperationInfo.Children` 存在序列化深度限制10层
5. **Editor 模式**:在 Editor 模式下使用 Mock 连接进行本地通信,无需真机
6. **真机调试**:真机调试需要通过 USB 连接,使用 Unity 的 `PlayerConnection` API
7. **线程安全**:所有操作应在主线程进行
8. **资源释放**`MockEditorConnection``MockPlayerConnection` 在 Domain Reload 时会自动重置

View File

@@ -1,607 +0,0 @@
# DownloadSystem 下载模块
## 模块概述
DownloadSystem 是 YooAsset 资源管理系统的**底层网络下载层**,负责处理所有 HTTP 网络请求。该模块提供了统一的下载接口抽象,支持文件下载、断点续传、并发请求(由上层调度)、看门狗监控等功能。
### 可见性说明
DownloadSystem 属于 YooAsset Runtime 的内部基础模块,目录内大多数类型为 `internal`(仅供 YooAsset Runtime 内部程序集使用)。本文示例以"模块内部调用方式"展示;业务层建议优先通过 `ResourcePackage / FileSystem / ResourceManager` 等上层接口使用下载能力,避免直接依赖本模块的内部类型。
### 核心职责
- HTTP/HTTPS 文件下载
- 断点续传支持
- 看门狗超时保护
- 多种下载类型(文件/字节/文本/AssetBundle
- 可插拔的网络库后端
---
## 边界与上层协作
DownloadSystem 的职责是提供"可替换后端 + 统一请求接口 + 轮询式生命周期"的基础能力:
- **本模块不负责并发队列/限流调度**:并发通常由上层同时创建多个 request 并自行控制并发数。
- **本模块不负责重试/回退策略**:失败后的重试、切换 CDN、降级等策略通常由上层系统实现。
- **本模块不负责持久化下载任务**:断点续传依赖本地已有文件与 `Range` 请求头,并由上层管理断点信息。
---
## 设计目标
| 目标 | 说明 |
|------|------|
| **可扩展性** | 支持可插拔的网络库后端UnityWebRequest/BestHTTP/自研) |
| **鲁棒性** | 看门狗超时保护、自动清理失败文件、完整错误信息 |
| **高性能** | 轮询模式无阻塞、支持并发请求(并发数由上层调度) |
| **易用性** | 流畅的参数构建 API、清晰的状态转换 |
---
## 架构概念
### 分层架构
```
┌─────────────────────────────────────────────────────────┐
│ 上层调用者 │
│ (FileSystem / ResourceManager) │
└─────────────────────────┬───────────────────────────────┘
┌─────────────────────────▼───────────────────────────────┐
│ IDownloadBackend │
│ (后端接口) │
│ 定义网络库合约,工厂模式创建请求 │
└─────────────────────────┬───────────────────────────────┘
┌─────────────────────────▼───────────────────────────────┐
│ IDownloadRequest │
│ (请求接口) │
│ 轮询式生命周期管理,状态机驱动 │
└─────────────────────────┬───────────────────────────────┘
┌─────────────────────────▼───────────────────────────────┐
│ UnityWebRequest / 其他网络库 │
│ (底层实现) │
└─────────────────────────────────────────────────────────┘
```
### 核心组件
- **后端层 (IDownloadBackend)**: 定义网络库实现合约,通过工厂方法创建各类请求
- **请求层 (IDownloadRequest)**: 统一的请求生命周期管理,支持轮询驱动
- **参数层 (Args 结构体)**: 配置下载行为(超时、断点续传、看门狗等)
---
## 文件结构
```
DownloadSystem/
├── Interface/ # 接口定义
│ ├── IDownloadBackend.cs # 后端接口(工厂模式)
│ └── IDownloadRequest.cs # 请求接口层次结构
├── UnityWebBackend/ # 默认后端实现
│ ├── UnityWebRequestBackend.cs # UnityWebRequest 后端
│ └── UnityWebRequestCreator.cs # UnityWebRequest 创建委托
├── UnityWebRequest/ # 默认请求实现
│ ├── UnityWebRequestBase.cs # 基础下载器(抽象类)
│ ├── UnityWebRequestFile.cs # 文件下载器
│ ├── UnityWebRequestHead.cs # HEAD 请求器
│ ├── UnityWebRequestBytes.cs # 字节下载器
│ ├── UnityWebRequestText.cs # 文本下载器
│ ├── UnityWebRequestAssetBundle.cs # AssetBundle 下载器
│ └── SimulateRequestFile.cs # 模拟下载器(编辑器用)
├── EDownloadRequestStatus.cs # 下载请求状态枚举
├── DownloadRequestArgs.cs # 请求参数结构体定义
├── DownloadSystemTools.cs # 工具函数
└── DownloadFailureCounter.cs # 请求失败计数器
```
---
## 接口说明
### IDownloadBackend后端接口
定义网络库实现的合约,通过工厂方法创建各类下载请求。
```csharp
internal interface IDownloadBackend : IDisposable
{
/// <summary>
/// 后端标识名称(用于日志/调试)
/// </summary>
string Name { get; }
/// <summary>
/// 定期驱动更新(部分第三方库需要)
/// </summary>
void Update();
// 工厂方法 - 创建各类请求
IDownloadHeadRequest CreateHeadRequest(DownloadDataRequestArgs args);
IDownloadFileRequest CreateFileRequest(DownloadFileRequestArgs args);
IDownloadBytesRequest CreateBytesRequest(DownloadDataRequestArgs args);
IDownloadTextRequest CreateTextRequest(DownloadDataRequestArgs args);
IDownloadAssetBundleRequest CreateAssetBundleRequest(DownloadAssetBundleRequestArgs args);
IDownloadFileRequest CreateSimulateRequest(DownloadSimulateRequestArgs args);
}
```
### IDownloadRequest基础请求接口
所有下载请求的通用接口,定义生命周期和状态管理。
```csharp
internal interface IDownloadRequest : IDisposable
{
// 元信息
string URL { get; }
// 生命周期
bool IsDone { get; } // 访问时自动调用 PollingRequest()
EDownloadRequestStatus Status { get; }
// 进度跟踪
float DownloadProgress { get; } // 0f - 1f
long DownloadedBytes { get; } // 本次请求新增字节数
// 诊断信息
long HttpCode { get; }
string Error { get; }
// 生命周期方法
void SendRequest(); // 发起请求
void PollingRequest(); // 轮询状态
void AbortRequest(); // 中止请求
}
```
### 专化请求接口
| 接口 | 用途 | 特有能力 |
|------|------|----------|
| `IDownloadHeadRequest` | HEAD 请求,获取响应头 | `ETag`, `LastModified`, `ContentLength`, `ContentType`, `GetResponseHeader(name)` |
| `IDownloadFileRequest` | 文件下载到本地 | `SavePath` |
| `IDownloadBytesRequest` | 下载到内存(字节数组) | `byte[] Result` |
| `IDownloadTextRequest` | 下载文本内容 | `string Result` |
| `IDownloadAssetBundleRequest` | 下载并加载 AssetBundle | `AssetBundle Result` |
---
## 结构体定义
### 请求状态枚举
```csharp
internal enum EDownloadRequestStatus
{
None, // 未开始
Running, // 进行中
Succeed, // 已成功
Failed, // 已失败
Aborted // 已中止(用户中止或看门狗超时)
}
```
### 请求参数结构体
#### DownloadFileRequestArgs文件下载参数
```csharp
internal struct DownloadFileRequestArgs
{
public readonly string URL; // 请求地址
public readonly int Timeout; // 响应超时0=不应用超时
public readonly int WatchdogTimeout; // 看门狗超时0=禁用
public readonly string SavePath; // 文件保存路径
public readonly bool AppendToFile; // 追加写入(断点续传)
public readonly bool RemoveFileOnAbort; // 中止时删除文件
public readonly long ResumeOffset; // 断点续传起始位置(>0 时推荐由后端设置 Range 头)
public Dictionary<string, string> Headers; // 自定义请求头(可选)
public DownloadFileRequestArgs(
string url,
string savePath,
int timeout,
int watchdogTimeout,
bool appendToFile = false,
bool removeFileOnAbort = true,
long resumeOffset = 0);
/// <summary>
/// 添加请求头(注意:相同 key 重复添加会抛异常)
/// </summary>
public void AddRequestHeader(string name, string value);
}
```
#### DownloadDataRequestArgs数据下载参数
```csharp
internal struct DownloadDataRequestArgs
{
public readonly string URL; // 请求地址
public readonly int Timeout; // 响应超时0=不应用超时
public readonly int WatchdogTimeout; // 看门狗超时0=禁用
public Dictionary<string, string> Headers; // 自定义请求头(可选)
public DownloadDataRequestArgs(string url, int timeout, int watchdogTimeout);
/// <summary>
/// 添加请求头(注意:相同 key 重复添加会抛异常)
/// </summary>
public void AddRequestHeader(string name, string value);
}
```
#### DownloadAssetBundleRequestArgsAssetBundle 下载参数)
```csharp
internal struct DownloadAssetBundleRequestArgs
{
public readonly string URL; // 请求地址
public readonly int Timeout; // 响应超时0=不应用超时
public readonly int WatchdogTimeout; // 看门狗超时0=禁用
public readonly bool DisableUnityWebCache; // 禁用 Unity 缓存(默认 true
public readonly string FileHash; // 文件哈希(缓存启用时需要,且不能为空)
public readonly uint UnityCRC; // Unity CRC 校验值
public Dictionary<string, string> Headers; // 自定义请求头(可选)
public DownloadAssetBundleRequestArgs(
string url,
int timeout,
int watchdogTimeout,
bool disableUnityWebCache = true,
string fileHash = null,
uint unityCrc = 0);
/// <summary>
/// 添加请求头(注意:相同 key 重复添加会抛异常)
/// </summary>
public void AddRequestHeader(string name, string value);
}
```
#### DownloadSimulateRequestArgs模拟下载参数
```csharp
internal struct DownloadSimulateRequestArgs
{
public readonly string URL; // 标识符
public readonly long FileSize; // 模拟文件大小
public readonly long DownloadSpeed; // 模拟速度(字节/秒),默认 1MB/s
public DownloadSimulateRequestArgs(string url, long fileSize, long downloadSpeed = 1024 * 1024);
}
```
---
## 核心类说明
### UnityWebRequestBackend
默认的后端实现,基于 Unity 的 UnityWebRequest API。
**特性:**
- 支持自定义 UnityWebRequest 创建方式(证书验证、代理等)
- 无需手动调用 Update()UnityWebRequest 自动驱动
```csharp
// 自定义 UnityWebRequest 创建(建议通过 backend 构造函数传入)
UnityWebRequestCreator creator = (url, method) =>
{
var request = new UnityWebRequest(url, method);
// 自定义配置...
return request;
};
IDownloadBackend backend = new UnityWebRequestBackend(creator);
```
### UnityWebRequestBase
抽象基类,封装所有下载器的通用逻辑。
**职责:**
- 管理请求生命周期和状态转换
- 实现看门狗监控机制
- 追踪下载进度和字节数
- 处理超时和错误
**生命周期:**
```
None ──► SendRequest() ──► Running ──► PollingRequest() ──┬──► Succeed
├──► Failed
└──► Aborted
```
### 具体下载器
| 下载器 | 实现接口 | 使用场景 |
|--------|----------|----------|
| `UnityWebRequestFile` | `IDownloadFileRequest` | 大文件下载到本地 |
| `UnityWebRequestHead` | `IDownloadHeadRequest` | 检查资源信息 |
| `UnityWebRequestBytes` | `IDownloadBytesRequest` | 小文件内存加载 |
| `UnityWebRequestText` | `IDownloadTextRequest` | 文本文件下载 |
| `UnityWebRequestAssetBundle` | `IDownloadAssetBundleRequest` | AB 包下载加载 |
| `SimulateRequestFile` | `IDownloadFileRequest` | 编辑器模拟下载 |
---
## 使用示例
### 基础文件下载
```csharp
IDownloadBackend backend = new UnityWebRequestBackend();
IDownloadFileRequest request = null;
try
{
// 1. 创建请求
var args = new DownloadFileRequestArgs(
url: "https://example.com/file.zip",
savePath: "/path/to/save/file.zip",
timeout: 30,
watchdogTimeout: 0);
request = backend.CreateFileRequest(args);
// 2. 发起并轮询
request.SendRequest();
while (!request.IsDone)
{
await Task.Yield();
float progress = request.DownloadProgress;
}
// 3. 检查结果
if (request.Status == EDownloadRequestStatus.Succeed)
Debug.Log("下载成功");
else
Debug.LogError($"下载失败: {request.Error}");
}
finally
{
// 4. 清理资源
request?.Dispose();
backend.Dispose();
}
```
### 断点续传
```csharp
// 获取已下载的文件大小
long existingFileSize = new FileInfo(savePath).Length;
var args = new DownloadFileRequestArgs(
url: url,
savePath: savePath,
timeout: 30,
watchdogTimeout: 0,
appendToFile: true, // 追加写入
removeFileOnAbort: false, // 中止时保留文件
resumeOffset: existingFileSize); // 断点位置
IDownloadFileRequest request = backend.CreateFileRequest(args);
request.SendRequest();
// ... 轮询等待完成
```
### 看门狗保护
```csharp
var args = new DownloadFileRequestArgs(
url: url,
savePath: path,
timeout: 30,
watchdogTimeout: 30); // 30秒无数据自动中止
IDownloadFileRequest request = backend.CreateFileRequest(args);
request.SendRequest();
while (!request.IsDone)
{
await Task.Yield();
}
// 检查是否因看门狗超时而中止
if (request.Status == EDownloadRequestStatus.Aborted)
{
Debug.LogWarning("下载超时,已自动中止");
}
```
### HEAD 请求获取文件信息
```csharp
var args = new DownloadDataRequestArgs(
url: "https://example.com/file.zip",
timeout: 30,
watchdogTimeout: 0);
IDownloadHeadRequest request = backend.CreateHeadRequest(args);
request.SendRequest();
while (!request.IsDone)
{
await Task.Yield();
}
if (request.Status == EDownloadRequestStatus.Succeed)
{
long fileSize = request.ContentLength;
string etag = request.ETag;
string lastModified = request.LastModified;
Debug.Log($"文件大小: {fileSize}, ETag: {etag}");
}
```
### 下载字节数据
```csharp
var args = new DownloadDataRequestArgs(
url: "https://example.com/data.json",
timeout: 30,
watchdogTimeout: 0);
IDownloadBytesRequest request = backend.CreateBytesRequest(args);
request.SendRequest();
while (!request.IsDone)
{
await Task.Yield();
}
if (request.Status == EDownloadRequestStatus.Succeed)
{
byte[] data = request.Result;
// 处理数据...
}
```
---
## 设计模式
### 工厂模式
`IDownloadBackend` 作为工厂接口,创建各类下载请求对象:
```
IDownloadBackend
├── CreateHeadRequest() ──► IDownloadHeadRequest
├── CreateFileRequest() ──► IDownloadFileRequest
├── CreateBytesRequest() ──► IDownloadBytesRequest
├── CreateTextRequest() ──► IDownloadTextRequest
├── CreateAssetBundleRequest() ──► IDownloadAssetBundleRequest
└── CreateSimulateRequest() ──► IDownloadFileRequest
```
### 策略模式
通过实现 `IDownloadBackend` 接口,可以替换底层网络库:
```
IDownloadBackend (接口)
├── UnityWebRequestBackend (默认实现)
├── BestHTTPBackend (可扩展)
└── CustomBackend (自定义)
```
### 状态机模式
请求生命周期通过状态机管理:
```
┌──────┐ SendRequest() ┌─────────┐
│ None │ ──────────────────► │ Running │
└──────┘ └────┬────┘
│ PollingRequest()
┌─────────────┼─────────────┐
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌─────────┐
│ Succeed │ │ Failed │ │ Aborted │
└─────────┘ └──────────┘ └─────────┘
```
### 看门狗模式
监控数据接收,防止网络卡顿导致请求无限等待:
```
每帧轮询 PollingRequest()
├── 收到新数据 ──► 重置计时器
└── 未收到数据 ──► 计时器累加
└── 超过 WatchdogTimeout ──► AbortRequest()
```
---
## 类继承关系
```
IDownloadRequest (基础接口)
├── IDownloadHeadRequest (HEAD 请求)
├── IDownloadFileRequest (文件下载)
├── IDownloadBytesRequest (字节下载)
├── IDownloadTextRequest (文本下载)
└── IDownloadAssetBundleRequest (AssetBundle 下载)
UnityWebRequestBase (抽象基类)
├── UnityWebRequestFile ──► IDownloadFileRequest
├── UnityWebRequestHead ──► IDownloadHeadRequest
├── UnityWebRequestBytes ──► IDownloadBytesRequest
├── UnityWebRequestText ──► IDownloadTextRequest
└── UnityWebRequestAssetBundle ──► IDownloadAssetBundleRequest
SimulateRequestFile (独立实现) ──► IDownloadFileRequest
```
---
## 工具类
### DownloadSystemTools
提供跨平台的 URL 转换和判断功能:
| 方法 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| `ToLocalURL(path)` | `string path` 本地文件路径 | 可用于 UnityWebRequest 的文件协议 URL | 转换本地路径为文件协议 URL自动处理特殊字符 |
| `IsLocalFileURL(url)` | `string url` 要判断的 URL | `bool` 是否为本地文件 URL | 判断 URL 是否为 `file:``jar:file:` 协议 |
### DownloadFailureCounter
网络请求失败计数器(诊断用):
- **线程安全**:内部使用 `Dictionary` 且未加锁,约定只在 Unity 主线程调用;如需在多线程/回调线程调用,请在外层加锁或改为并发容器实现
- **Key 格式**`$"{packageName}_{eventName}"`
- **统计口径****仅统计网络请求失败**`IDownloadRequest.Status != Succeed` 时记录),不统计内容为空、校验失败、解析失败等业务层失败
| 方法 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| `RecordFailure(packageName, eventName)` | `string packageName` 资源包名称, `string eventName` 事件名称 | `void` | 记录一次失败 |
| `GetFailureCount(packageName, eventName)` | `string packageName` 资源包名称, `string eventName` 事件名称 | `int` 失败次数(未记录过返回 0 | 获取失败次数 |
```csharp
// 记录失败
DownloadFailureCounter.RecordFailure(packageName, eventName);
// 查询失败次数
int count = DownloadFailureCounter.GetFailureCount(packageName, eventName);
```
---
## 注意事项
1. **资源释放**:使用完毕后务必调用 `Dispose()` 释放资源
- `AbortRequest()` 仅用于中止请求与切换状态,不等同于释放资源;无论成功/失败/中止都需要 `Dispose()`
- 第三方 `IDownloadBackend` 可能持有原生资源/线程/连接池等,上层在不再使用时也应调用 `backend.Dispose()`
- 推荐使用 `try/finally` 确保释放(尤其是上层可能提前中止的场景)
2. **断点续传**:需要服务器支持 `Range` 请求头和 `206 Partial Content` 响应
- 若服务端不支持 Range 仍返回 200全量内容可能会被追加写入导致文件损坏
3. **看门狗超时**:设置为 0 表示禁用,建议根据网络环境设置合理值
4. **内存下载**`IDownloadBytesRequest` 会将整个响应体加载到内存,不适合大文件
5. **驱动更新**:部分第三方网络库实现的 backend 可能需要每帧调用 `IDownloadBackend.Update()` 进行驱动
6. **中止语义**`Aborted` 可能来自用户主动 `AbortRequest()` 或看门狗超时;中止场景下 `HttpCode/Error` 可能为默认值(例如 0/空)
7. **线程安全**:所有下载请求的创建和轮询应在主线程进行
8. **模拟下载器**`SimulateRequestFile` 仅用于模拟进度,不会落盘,且 `SavePath` 始终为 `null`;成功时 `HttpCode` 固定为 `200`

View File

@@ -1,6 +1,7 @@
fileFormatVersion: 2
guid: 5fa2b66c20800124c8dd5cb77e854ce3
TextScriptImporter:
guid: d348d244f16bb9a4c9537f045524af44
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:

View File

@@ -1,6 +1,7 @@
fileFormatVersion: 2
guid: 2afe3d5ffb611b241919112b40d9c7d0
TextScriptImporter:
guid: 7c9bb74a55a8cbc4788da87c55b3a541
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:

View File

@@ -17,7 +17,7 @@ namespace YooAsset
Middle = 2,
/// <summary>
/// 验证文件大小和CRC
/// 验证文件CRC
/// </summary>
High = 3,
}

View File

@@ -8,22 +8,25 @@ namespace YooAsset
/// <summary>
/// 文件校验
/// </summary>
public static EFileVerifyResult FileVerify(string filePath, long fileSize, uint fileCRC, EFileVerifyLevel verifyLevel)
public static EFileVerifyResult FileVerify(string filePath, long fileSize, uint fileCRC)
{
try
{
if (File.Exists(filePath) == false)
return EFileVerifyResult.DataFileNotExisted;
// 验证文件大小
long size = FileUtility.GetFileSize(filePath);
if (size < fileSize)
return EFileVerifyResult.FileNotComplete;
else if (size > fileSize)
return EFileVerifyResult.FileOverflow;
// 验证文件大小
if (fileSize > 0)
{
long size = FileUtility.GetFileSize(filePath);
if (size < fileSize)
return EFileVerifyResult.FileNotComplete;
else if (size > fileSize)
return EFileVerifyResult.FileOverflow;
}
// 验证文件CRC
if (verifyLevel == EFileVerifyLevel.High)
// 验证文件CRC
if (fileCRC > 0)
{
uint crc = HashUtility.FileCRC32Value(filePath);
if (crc == fileCRC)
@@ -36,8 +39,9 @@ namespace YooAsset
return EFileVerifyResult.Succeed;
}
}
catch (Exception)
catch (Exception ex)
{
YooLogger.Error($"File verify exception : {ex.Message}");
return EFileVerifyResult.Exception;
}
}

View File

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

View File

@@ -0,0 +1,14 @@
namespace YooAsset
{
/// <summary>
/// 缓存记录接口
/// </summary>
internal interface ICacheEntry
{
/// <summary>
/// Bundle唯一标识
/// </summary>
string BundleGUID { get; }
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: ae2176c4ea6fbb3478bf8757def34cd7
guid: 0f4a2df973e802b489c3df5917c45714
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -0,0 +1,67 @@
using System.Collections.Generic;
namespace YooAsset
{
/// <summary>
/// 文件缓存系统接口
/// </summary>
/// <typeparam name="TEntry">缓存记录类型</typeparam>
internal interface IFileCache<TEntry> where TEntry : ICacheEntry
{
#region
/// <summary>
/// 包裹名称
/// </summary>
string PackageName { get; }
/// <summary>
/// 缓存根目录
/// </summary>
string RootPath { get; }
/// <summary>
/// 只读属性
/// </summary>
bool IsReadOnly { get; }
/// <summary>
/// 已占用空间(字节)
/// </summary>
long SpaceOccupied { get; }
#endregion
#region
/// <summary>
/// 初始化缓存
/// </summary>
FCInitializeOperation InitializeAsync(FCInitializeOptions options);
/// <summary>
/// 存储缓存文件
/// </summary>
FCStoreCacheOperation StoreCacheAsync(FCStoreCacheOptions options);
/// <summary>
/// 清理缓存文件
/// </summary>
FCClearCacheOperation ClearCacheAsync(FCClearCacheOptions options);
#endregion
#region
/// <summary>
/// 是否已缓存指定 Bundle
/// </summary>
bool IsCached(string bundleGUID);
/// <summary>
/// 获取缓存记录
/// </summary>
TEntry GetEntry(string bundleGUID);
/// <summary>
/// 获取所有的缓存记录
/// </summary>
IReadOnlyCollection<TEntry> GetAllEntries();
#endregion
}
}

View File

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

View File

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

View File

@@ -0,0 +1,78 @@
using System.Collections.Generic;
using System.Linq;
namespace YooAsset
{
internal class FCClearCacheOperation : AsyncOperationBase
{
private enum ESteps
{
None,
CheckOptions,
ClearCache,
Done,
}
private BundleCache _cache;
private FCClearCacheOptions _options;
private int _clearFileTotalCount;
private List<string> _bundleGUIDs;
private ESteps _steps = ESteps.None;
public FCClearCacheOperation(BundleCache cache, FCClearCacheOptions options)
{
_cache = cache;
_options = options;
}
internal override void InternalStart()
{
_steps = ESteps.CheckOptions;
}
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.CheckOptions)
{
if (_options.BundleGUIDs == null || _options.BundleGUIDs.Count == 0)
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
return;
}
_bundleGUIDs = _options.BundleGUIDs.ToList();
_clearFileTotalCount = _options.BundleGUIDs.Count;
_steps = ESteps.ClearCache;
}
if (_steps == ESteps.ClearCache)
{
for (int i = _bundleGUIDs.Count - 1; i >= 0; i--)
{
string bundleGUID = _bundleGUIDs[i];
_cache.RemoveEntry(bundleGUID);
_bundleGUIDs.RemoveAt(i);
if (IsBusy)
break;
}
if (_clearFileTotalCount == 0)
Progress = 1.0f;
else
Progress = 1.0f - ((float)_bundleGUIDs.Count / _clearFileTotalCount);
if (_bundleGUIDs.Count == 0)
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
}
}
}
internal override void InternalWaitForCompletion()
{
ExecuteBatch();
}
}
}

View File

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

View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace YooAsset
{
internal struct FCClearCacheOptions
{
/// <summary>
/// 清理指定缓存列表
/// </summary>
public IReadOnlyList<string> BundleGUIDs { get; set; }
}
}

View File

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

View File

@@ -0,0 +1,79 @@
namespace YooAsset
{
internal class FCInitializeOperation : AsyncOperationBase
{
private enum ESteps
{
None,
SearchCacheFiles,
VerifyCacheFiles,
Done,
}
private readonly BundleCache _cache;
private readonly FCInitializeOptions _options;
private SearchCacheFilesOperation _searchCacheFilesOp;
private VerifyCacheFilesOperation _verifyCacheFilesOp;
private ESteps _steps = ESteps.None;
public FCInitializeOperation(BundleCache cache, FCInitializeOptions options)
{
_cache = cache;
_options = options;
}
internal override void InternalStart()
{
_steps = ESteps.SearchCacheFiles;
}
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.SearchCacheFiles)
{
if (_searchCacheFilesOp == null)
{
_searchCacheFilesOp = new SearchCacheFilesOperation(_cache);
_searchCacheFilesOp.StartOperation();
AddChildOperation(_searchCacheFilesOp);
}
_searchCacheFilesOp.UpdateOperation();
Progress = _searchCacheFilesOp.Progress;
if (_searchCacheFilesOp.IsDone == false)
return;
_steps = ESteps.VerifyCacheFiles;
}
if (_steps == ESteps.VerifyCacheFiles)
{
if (_verifyCacheFilesOp == null)
{
_verifyCacheFilesOp = new VerifyCacheFilesOperation(_cache, _options.FileVerifyLevel, _options.FileVerifyMaxConcurrency, _searchCacheFilesOp.Result);
_verifyCacheFilesOp.StartOperation();
AddChildOperation(_verifyCacheFilesOp);
}
_verifyCacheFilesOp.UpdateOperation();
Progress = _verifyCacheFilesOp.Progress;
if (_verifyCacheFilesOp.IsDone == false)
return;
if (_verifyCacheFilesOp.Status == EOperationStatus.Succeeded)
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _verifyCacheFilesOp.Error;
}
}
}
}
}

View File

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

View File

@@ -0,0 +1,16 @@
namespace YooAsset
{
internal struct FCInitializeOptions
{
/// <summary>
/// 文件校验最大并发数
/// </summary>
public int FileVerifyMaxConcurrency { get; set; }
/// <summary>
/// 文件校验级别
/// </summary>
public EFileVerifyLevel FileVerifyLevel { get; set; }
}
}

View File

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

View File

@@ -0,0 +1,135 @@
using System;
using System.IO;
namespace YooAsset
{
internal class FCStoreCacheOperation : AsyncOperationBase
{
private enum ESteps
{
None,
Check,
VerifyFile,
CacheFile,
Done,
}
private BundleCache _cache;
private readonly FCStoreCacheOptions _options;
private VerifyTempFileOperation _verifyOperation;
private ESteps _steps = ESteps.None;
public FCStoreCacheOperation(BundleCache cache, FCStoreCacheOptions options)
{
_cache = cache;
_options = options;
}
internal override void InternalStart()
{
_steps = ESteps.Check;
}
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.Check)
{
if (_cache.IsReadOnly)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"{nameof(BundleCache)} is readonly.";
return;
}
if (_cache.IsCached(_options.Bundle.BundleGUID))
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = "The bundle is cached.";
}
else
{
_steps = ESteps.VerifyFile;
}
}
if (_steps == ESteps.VerifyFile)
{
if (_verifyOperation == null)
{
var element = new TempFileInfo(_options.FilePath, _options.Bundle.FileCRC, _options.Bundle.FileSize);
_verifyOperation = new VerifyTempFileOperation(element);
_verifyOperation.StartOperation();
AddChildOperation(_verifyOperation);
}
if (IsWaitForCompletion)
_verifyOperation.WaitForCompletion();
_verifyOperation.UpdateOperation();
if (_verifyOperation.IsDone == false)
return;
if (_verifyOperation.Status == EOperationStatus.Succeeded)
{
_steps = ESteps.CacheFile;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _verifyOperation.Error;
}
}
if (_steps == ESteps.CacheFile)
{
string infoFilePath = _cache.GetInfoFilePath(_options.Bundle);
string dataFilePath = _cache.GetDataFilePath(_options.Bundle);
try
{
if (File.Exists(infoFilePath))
File.Delete(infoFilePath);
if (File.Exists(dataFilePath))
File.Delete(dataFilePath);
// 拷贝数据文件
FileUtility.CreateFileDirectory(dataFilePath);
FileInfo fileInfo = new FileInfo(_options.FilePath);
fileInfo.CopyTo(dataFilePath, true);
// 写入信息文件
FileUtility.CreateFileDirectory(infoFilePath);
using (FileStream fs = new FileStream(infoFilePath, FileMode.Create, FileAccess.Write, FileShare.Read))
{
_cache.SharedBuffer.Clear();
_cache.SharedBuffer.WriteUInt32(_options.Bundle.FileCRC);
_cache.SharedBuffer.WriteInt64(_options.Bundle.FileSize);
_cache.SharedBuffer.WriteToStream(fs);
fs.Flush();
}
}
catch (Exception ex)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Failed to write cache file. Error: {ex.Message}";
YooLogger.Error(Error);
return; //失败后直接返回
}
var cacheEntry = new BundleCacheEntry(_options.Bundle.BundleGUID, infoFilePath, dataFilePath, _options.Bundle.FileCRC, _options.Bundle.FileSize);
_cache.AddEntry(_options.Bundle.BundleGUID, cacheEntry);
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
}
}
internal override void InternalWaitForCompletion()
{
ExecuteBatch();
}
}
}

View File

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

View File

@@ -0,0 +1,16 @@
namespace YooAsset
{
internal struct FCStoreCacheOptions
{
/// <summary>
/// 要缓存的资源包
/// </summary>
public PackageBundle Bundle { get; set; }
/// <summary>
/// 要缓存的文件路径
/// </summary>
public string FilePath { get; set; }
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,190 @@
using System.Collections.Generic;
using System.IO;
namespace YooAsset
{
internal class BundleCache : IFileCache<BundleCacheEntry>
{
private const int HashFolderLength = 2;
// 缓存索引
private readonly Dictionary<string, BundleCacheEntry> _caches = new Dictionary<string, BundleCacheEntry>(10000);
// 路径缓存
private readonly Dictionary<string, string> _dataFilePathMapping = new Dictionary<string, string>(10000);
private readonly Dictionary<string, string> _infoFilePathMapping = new Dictionary<string, string>(10000);
// 共享缓冲区
internal readonly BufferWriter SharedBuffer = new BufferWriter(1024);
/// <summary>
/// 包裹名称
/// </summary>
public string PackageName { get; }
/// <summary>
/// 缓存根目录
/// </summary>
public string RootPath { get; }
/// <summary>
/// 追加文件扩展名
/// </summary>
public readonly bool AppendFileExtension;
/// <summary>
/// 只读属性
/// </summary>
public bool IsReadOnly { get; }
/// <summary>
/// 缓存文件数量
/// </summary>
public int FileCount
{
get
{
return _caches.Count;
}
}
/// <summary>
/// 已占用空间
/// 说明:按缓存索引累计
/// </summary>
public long SpaceOccupied { get; private set; }
public BundleCache(string packageName, string rootPath, bool appendFileExtension)
{
PackageName = packageName;
RootPath = rootPath;
AppendFileExtension = appendFileExtension;
IsReadOnly = false;
}
/// <summary>
/// 初始化缓存
/// </summary>
public FCInitializeOperation InitializeAsync(FCInitializeOptions options)
{
var operation = new FCInitializeOperation(this, options);
return operation;
}
/// <summary>
/// 存储缓存文件
/// </summary>
public FCStoreCacheOperation StoreCacheAsync(FCStoreCacheOptions options)
{
var operation = new FCStoreCacheOperation(this, options);
return operation;
}
/// <summary>
/// 清理缓存文件
/// </summary>
public FCClearCacheOperation ClearCacheAsync(FCClearCacheOptions options)
{
var operation = new FCClearCacheOperation(this, options);
return operation;
}
/// <summary>
/// 是否已缓存指定 Bundle
/// </summary>
public bool IsCached(string bundleGUID)
{
return _caches.ContainsKey(bundleGUID);
}
/// <summary>
/// 获取缓存记录
/// </summary>
public BundleCacheEntry GetEntry(string bundleGUID)
{
if (_caches.TryGetValue(bundleGUID, out BundleCacheEntry entry))
return entry;
else
return null;
}
/// <summary>
/// 获取所有的缓存记录
/// </summary>
public IReadOnlyCollection<BundleCacheEntry> GetAllEntries()
{
return _caches.Values;
}
#region
/// <summary>
/// 获取 Bundle 数据文件路径
/// </summary>
internal string GetDataFilePath(PackageBundle bundle)
{
if (_dataFilePathMapping.TryGetValue(bundle.BundleGUID, out string filePath) == false)
{
string folderName = GetHashFolderName(bundle.FileHash);
filePath = PathUtility.Combine(RootPath, folderName, bundle.BundleGUID, BundleCacheDefine.BundleDataFileName);
if (AppendFileExtension)
filePath += bundle.FileExtension;
_dataFilePathMapping.Add(bundle.BundleGUID, filePath);
}
return filePath;
}
/// <summary>
/// 获取 Bundle 信息文件路径
/// </summary>
internal string GetInfoFilePath(PackageBundle bundle)
{
if (_infoFilePathMapping.TryGetValue(bundle.BundleGUID, out string filePath) == false)
{
string folderName = GetHashFolderName(bundle.FileHash);
filePath = PathUtility.Combine(RootPath, folderName, bundle.BundleGUID, BundleCacheDefine.BundleInfoFileName);
_infoFilePathMapping.Add(bundle.BundleGUID, filePath);
}
return filePath;
}
/// <summary>
/// 添加指定缓存
/// </summary>
internal void AddEntry(string bundleGUID, BundleCacheEntry entry)
{
if (_caches.ContainsKey(bundleGUID))
throw new YooInternalException($"Cache already existed: {bundleGUID}");
_caches.Add(bundleGUID, entry);
SpaceOccupied += entry.DataFileSize;
}
/// <summary>
/// 删除指定缓存
/// </summary>
internal void RemoveEntry(string bundleGUID)
{
if (_caches.TryGetValue(bundleGUID, out BundleCacheEntry entry))
{
_caches.Remove(bundleGUID);
_dataFilePathMapping.Remove(bundleGUID);
_infoFilePathMapping.Remove(bundleGUID);
SpaceOccupied -= entry.DataFileSize;
entry.Delete();
}
}
private string GetHashFolderName(string fileHash)
{
if (string.IsNullOrEmpty(fileHash))
throw new YooInternalException();
if (fileHash.Length <= HashFolderLength)
return fileHash;
return fileHash.Substring(0, HashFolderLength);
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,16 @@

namespace YooAsset
{
internal class BundleCacheDefine
{
/// <summary>
/// 数据文件名称
/// </summary>
public const string BundleDataFileName = "__data";
/// <summary>
/// 信息文件名称
/// </summary>
public const string BundleInfoFileName = "__info";
}
}

View File

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

View File

@@ -1,17 +1,19 @@
using System;
using System;
using System.IO;
namespace YooAsset
{
internal class FileCacheEntry
internal class BundleCacheEntry : ICacheEntry
{
public string InfoFilePath { private set; get; }
public string DataFilePath { private set; get; }
public uint DataFileCRC { private set; get; }
public long DataFileSize { private set; get; }
public string BundleGUID { get; private set; }
public string InfoFilePath { get; private set; }
public string DataFilePath { get; private set; }
public uint DataFileCRC { get; private set; }
public long DataFileSize { get; private set; }
public FileCacheEntry(string infoFilePath, string dataFilePath, uint dataFileCRC, long dataFileSize)
public BundleCacheEntry(string bundleGUID, string infoFilePath, string dataFilePath, uint dataFileCRC, long dataFileSize)
{
BundleGUID = bundleGUID;
InfoFilePath = infoFilePath;
DataFilePath = dataFilePath;
DataFileCRC = dataFileCRC;
@@ -19,22 +21,23 @@ namespace YooAsset
}
/// <summary>
/// 修正内容
/// 移动文件
/// </summary>
public void Modify(string dataFilePath)
public void MoveFile(string destFilePath)
{
DataFilePath = dataFilePath;
File.Move(DataFilePath, destFilePath);
DataFilePath = destFilePath;
}
/// <summary>
/// 删除记录文件
/// </summary>
public bool DeleteFolder()
public bool Delete()
{
try
{
string directory = Path.GetDirectoryName(InfoFilePath);
DirectoryInfo directoryInfo = new DirectoryInfo(directory);
var directoryInfo = new DirectoryInfo(directory);
if (directoryInfo.Exists)
{
directoryInfo.Delete(true);

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace YooAsset
{
@@ -15,7 +16,8 @@ namespace YooAsset
Done,
}
private readonly CacheFileSystem _fileSystem;
private readonly BundleCache _cache;
private readonly bool _appendFileExtension;
private IEnumerator<string> _filesEnumerator = null;
private double _verifyStartTime;
private ESteps _steps = ESteps.None;
@@ -26,14 +28,14 @@ namespace YooAsset
public readonly List<VerifyFileInfo> Result = new List<VerifyFileInfo>(5000);
internal SearchCacheFilesOperation(CacheFileSystem fileSystem)
internal SearchCacheFilesOperation(BundleCache cache)
{
_fileSystem = fileSystem;
_cache = cache;
_appendFileExtension = cache.AppendFileExtension;
}
internal override void InternalStart()
{
_steps = ESteps.Prepare;
_verifyStartTime = TimeUtility.RealtimeSinceStartup;
}
internal override void InternalUpdate()
{
@@ -42,13 +44,18 @@ namespace YooAsset
if (_steps == ESteps.Prepare)
{
string rootDirectory = _fileSystem.GetCacheBundleFilesRoot();
if (Directory.Exists(rootDirectory))
if (Directory.Exists(_cache.RootPath))
{
var directories = Directory.EnumerateDirectories(rootDirectory);
var directories = Directory.EnumerateDirectories(_cache.RootPath);
_filesEnumerator = directories.GetEnumerator();
_verifyStartTime = TimeUtility.RealtimeSinceStartup;
_steps = ESteps.SearchFiles;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
}
_steps = ESteps.SearchFiles;
}
if (_steps == ESteps.SearchFiles)
@@ -56,8 +63,11 @@ namespace YooAsset
if (SearchFiles())
return;
_filesEnumerator.Dispose();
_filesEnumerator = null;
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
Status = EOperationStatus.Succeeded;
double costTime = TimeUtility.RealtimeSinceStartup - _verifyStartTime;
YooLogger.Log($"Search cache files elapsed time {costTime:f1} seconds");
}
@@ -65,9 +75,6 @@ namespace YooAsset
private bool SearchFiles()
{
if (_filesEnumerator == null)
return false;
bool isFindItem;
while (true)
{
@@ -80,25 +87,25 @@ namespace YooAsset
foreach (var chidDirectory in childDirectories)
{
string bundleGUID = Path.GetFileName(chidDirectory);
if (_fileSystem.IsRecordBundleFile(bundleGUID))
if (_cache.IsCached(bundleGUID))
continue;
// 创建验证元素类
string fileRootPath = chidDirectory;
string dataFilePath = $"{fileRootPath}/{DefaultCacheFileSystemDefine.BundleDataFileName}";
string infoFilePath = $"{fileRootPath}/{DefaultCacheFileSystemDefine.BundleInfoFileName}";
string dataFilePath = PathUtility.Combine(fileRootPath, BundleCacheDefine.BundleDataFileName);
string infoFilePath = PathUtility.Combine(fileRootPath, BundleCacheDefine.BundleInfoFileName);
// 存储的数据文件追加文件格式
if (_fileSystem.AppendFileExtension)
if (_appendFileExtension)
{
string dataFileExtension = FindDataFileExtension(chidDirectory);
if (string.IsNullOrEmpty(dataFileExtension) == false)
string realFilePath = FindRealDataFilePath(chidDirectory);
if (string.IsNullOrEmpty(realFilePath) == false)
{
dataFilePath += dataFileExtension;
dataFilePath = realFilePath;
}
}
var element = new VerifyFileInfo(_fileSystem.PackageName, bundleGUID, fileRootPath, dataFilePath, infoFilePath);
var element = new VerifyFileInfo(_cache.PackageName, bundleGUID, fileRootPath, dataFilePath, infoFilePath);
Result.Add(element);
}
@@ -108,17 +115,11 @@ namespace YooAsset
return isFindItem;
}
private string FindDataFileExtension(string directory)
private string FindRealDataFilePath(string directory)
{
string dataFileExtension = string.Empty;
string searchPattern = DefaultCacheFileSystemDefine.BundleDataFileName + "*";
var dataFiles = Directory.EnumerateFiles(directory, searchPattern);
foreach (var filePath in dataFiles)
{
dataFileExtension = Path.GetExtension(filePath);
break;
}
return dataFileExtension;
string searchPattern = BundleCacheDefine.BundleDataFileName + "*";
var searchFiles = Directory.EnumerateFiles(directory, searchPattern);
return searchFiles.FirstOrDefault();
}
}
}

View File

@@ -0,0 +1,170 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading;
namespace YooAsset
{
/// <summary>
/// 缓存文件验证(线程版)
/// </summary>
internal sealed class VerifyCacheFilesOperation : AsyncOperationBase
{
private enum ESteps
{
None,
InitVerify,
UpdateVerify,
Done,
}
private readonly BundleCache _cache;
private readonly EFileVerifyLevel _verifyLevel;
private readonly int _fileVerifyMaxConcurrency;
private readonly List<VerifyFileInfo> _waitingList;
private List<VerifyFileInfo> _verifyingList;
private int _verifyMaxNum;
private int _verifyTotalCount;
private double _verifyStartTime;
private int _succeedCount;
private int _failedCount;
private ESteps _steps = ESteps.None;
internal VerifyCacheFilesOperation(BundleCache cache, EFileVerifyLevel verifyLevel, int fileVerifyMaxConcurrency, List<VerifyFileInfo> elements)
{
_cache = cache;
_verifyLevel = verifyLevel;
_fileVerifyMaxConcurrency = fileVerifyMaxConcurrency;
_waitingList = elements;
}
internal override void InternalStart()
{
_steps = ESteps.InitVerify;
}
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.InitVerify)
{
// 设置同时验证的最大数
int processorCount = Environment.ProcessorCount * 2 + 1;
_verifyMaxNum = Math.Min(processorCount, _fileVerifyMaxConcurrency);
if (_verifyMaxNum < 1)
_verifyMaxNum = 1;
YooLogger.Log($"Verify max concurrency : {_verifyMaxNum}");
_verifyingList = new List<VerifyFileInfo>(_verifyMaxNum);
_verifyStartTime = TimeUtility.RealtimeSinceStartup;
_verifyTotalCount = _waitingList.Count;
_steps = ESteps.UpdateVerify;
}
if (_steps == ESteps.UpdateVerify)
{
// 检测校验结果
for (int i = _verifyingList.Count - 1; i >= 0; i--)
{
var verifyElement = _verifyingList[i];
int result = verifyElement.Result;
if (result != 0)
{
_verifyingList.RemoveAt(i);
if (verifyElement.Result == (int)EFileVerifyResult.Succeed)
{
_succeedCount++;
var cacheEntry = new BundleCacheEntry(verifyElement.BundleGUID, verifyElement.InfoFilePath, verifyElement.DataFilePath, verifyElement.DataFileCRC, verifyElement.DataFileSize);
_cache.AddEntry(verifyElement.BundleGUID, cacheEntry);
}
else
{
_failedCount++;
YooLogger.Warning($"Failed to verify file {verifyElement.Result} and delete files : {verifyElement.FolderPath}");
verifyElement.DeleteFiles();
}
}
}
Progress = GetProgress();
if (_waitingList.Count == 0 && _verifyingList.Count == 0)
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
double costTime = TimeUtility.RealtimeSinceStartup - _verifyStartTime;
YooLogger.Log($"Verify cache files elapsed time {costTime:f1} seconds");
}
for (int i = _waitingList.Count - 1; i >= 0; i--)
{
if (IsBusy)
break;
if (_verifyingList.Count >= _verifyMaxNum)
break;
var element = _waitingList[i];
bool succeed = ThreadPool.QueueUserWorkItem(new WaitCallback(VerifyFileInThread), element);
if (succeed == false)
VerifyFileInThread(element);
_waitingList.RemoveAt(i);
_verifyingList.Add(element);
}
}
}
private float GetProgress()
{
if (_verifyTotalCount == 0)
return 1f;
return (float)(_succeedCount + _failedCount) / _verifyTotalCount;
}
// 验证缓存文件(子线程内操作)
private void VerifyFileInThread(object obj)
{
VerifyFileInfo element = (VerifyFileInfo)obj;
int verifyResult = (int)VerifyFile(element, _verifyLevel);
element.Result = verifyResult;
}
private EFileVerifyResult VerifyFile(VerifyFileInfo element, EFileVerifyLevel verifyLevel)
{
try
{
if (File.Exists(element.InfoFilePath) == false)
return EFileVerifyResult.InfoFileNotExisted;
if (File.Exists(element.DataFilePath) == false)
return EFileVerifyResult.DataFileNotExisted;
// 解析信息文件填充验证数据
// 注意:验证数据在后续流程会被使用。
byte[] binaryData = FileUtility.ReadAllBytes(element.InfoFilePath);
BufferReader buffer = new BufferReader(binaryData);
uint dataFileCRC = buffer.ReadUInt32();
long dataFileSize = buffer.ReadInt64();
element.SetDataFileInfo(dataFileCRC, dataFileSize);
if (verifyLevel == EFileVerifyLevel.Low)
{
return EFileVerifyResult.Succeed;
}
else
{
if (verifyLevel == EFileVerifyLevel.Middle)
return FileVerifyTools.FileVerify(element.DataFilePath, dataFileSize, 0);
else if (verifyLevel == EFileVerifyLevel.High)
return FileVerifyTools.FileVerify(element.DataFilePath, dataFileSize, dataFileCRC);
else
throw new System.NotImplementedException(verifyLevel.ToString());
}
}
catch (Exception ex)
{
YooLogger.Error($"File verify exception : {ex.Message}");
return EFileVerifyResult.Exception;
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More