mirror of
https://github.com/tuyoogame/YooAsset.git
synced 2026-05-22 08:20:18 +00:00
refactor : 重构代码
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
|
||||
}
|
||||
@@ -19,7 +19,7 @@ namespace YooAsset
|
||||
/// <summary>
|
||||
/// 已成功
|
||||
/// </summary>
|
||||
Succeed,
|
||||
Succeeded,
|
||||
|
||||
/// <summary>
|
||||
/// 已失败
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c65d3ba8da65004b8d36dbeecc6c6be
|
||||
TextScriptImporter:
|
||||
guid: c137052d50eadfa4caf6b2157ebeeaad
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
@@ -26,9 +26,9 @@ namespace YooAsset
|
||||
public string Status;
|
||||
|
||||
/// <summary>
|
||||
/// 该资源包被谁引用
|
||||
/// 引用该资源包的其他资源包列表
|
||||
/// </summary>
|
||||
public List<string> ReferencedByBundles;
|
||||
public List<string> Referencers;
|
||||
|
||||
public int CompareTo(DiagnosticBundleInfo other)
|
||||
{
|
||||
@@ -33,7 +33,7 @@ namespace YooAsset
|
||||
/// <summary>
|
||||
/// 处理耗时(单位:毫秒)
|
||||
/// </summary>
|
||||
public long ElapsedMS;
|
||||
public long ElapsedMilliseconds;
|
||||
|
||||
/// <summary>
|
||||
/// 异步操作的执行进度
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
@@ -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>
|
||||
@@ -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 发送消息的标识符
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// 远程调试命令类型
|
||||
/// 诊断命令类型
|
||||
/// </summary>
|
||||
internal enum EDebugCommandType
|
||||
internal enum EDiagnosticCommandType
|
||||
{
|
||||
/// <summary>
|
||||
/// 采样一次
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 时会自动重置
|
||||
@@ -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);
|
||||
}
|
||||
```
|
||||
|
||||
#### DownloadAssetBundleRequestArgs(AssetBundle 下载参数)
|
||||
|
||||
```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`
|
||||
@@ -1,6 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5fa2b66c20800124c8dd5cb77e854ce3
|
||||
TextScriptImporter:
|
||||
guid: d348d244f16bb9a4c9537f045524af44
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
@@ -1,6 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2afe3d5ffb611b241919112b40d9c7d0
|
||||
TextScriptImporter:
|
||||
guid: 7c9bb74a55a8cbc4788da87c55b3a541
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
@@ -17,7 +17,7 @@ namespace YooAsset
|
||||
Middle = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 验证文件大小和CRC
|
||||
/// 验证文件CRC
|
||||
/// </summary>
|
||||
High = 3,
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
8
Assets/YooAsset/Runtime/FileCache/Interfaces.meta
Normal file
8
Assets/YooAsset/Runtime/FileCache/Interfaces.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb3085b0049518a4db1d817e1486c879
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
14
Assets/YooAsset/Runtime/FileCache/Interfaces/ICacheEntry.cs
Normal file
14
Assets/YooAsset/Runtime/FileCache/Interfaces/ICacheEntry.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// 缓存记录接口
|
||||
/// </summary>
|
||||
internal interface ICacheEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Bundle唯一标识
|
||||
/// </summary>
|
||||
string BundleGUID { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae2176c4ea6fbb3478bf8757def34cd7
|
||||
guid: 0f4a2df973e802b489c3df5917c45714
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
67
Assets/YooAsset/Runtime/FileCache/Interfaces/IFileCache.cs
Normal file
67
Assets/YooAsset/Runtime/FileCache/Interfaces/IFileCache.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 273f6d8fdd5b38e49be55c5c38daaf66
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/YooAsset/Runtime/FileCache/Operations.meta
Normal file
8
Assets/YooAsset/Runtime/FileCache/Operations.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b80693487b8ebcb49b52686fe907cbf8
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fb3347395a55d3048bcec202aca0e4a7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
internal struct FCClearCacheOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 清理指定缓存列表
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> BundleGUIDs { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 03f8c5e4c7af7df4387f56d045d8d357
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9be990c57fe162845ba3893188a68f8b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
internal struct FCInitializeOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件校验最大并发数
|
||||
/// </summary>
|
||||
public int FileVerifyMaxConcurrency { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件校验级别
|
||||
/// </summary>
|
||||
public EFileVerifyLevel FileVerifyLevel { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75f6c6588c154e145b09fa36cc6f2602
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f5c954b2aaf2524790dbfb2c5251da2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
internal struct FCStoreCacheOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 要缓存的资源包
|
||||
/// </summary>
|
||||
public PackageBundle Bundle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 要缓存的文件路径
|
||||
/// </summary>
|
||||
public string FilePath { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cbe6c6f462ef0c9429568f9d1df559f6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/YooAsset/Runtime/FileCache/Services.meta
Normal file
8
Assets/YooAsset/Runtime/FileCache/Services.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 21f2bb59b8b32e24b96619bea464a48c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd27cacfb758c3546a5015d236e2f539
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dc67a10bc17d3cf4f960251012a2387b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
internal class BundleCacheDefine
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据文件名称
|
||||
/// </summary>
|
||||
public const string BundleDataFileName = "__data";
|
||||
|
||||
/// <summary>
|
||||
/// 信息文件名称
|
||||
/// </summary>
|
||||
public const string BundleInfoFileName = "__info";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af5c9bfdcfeff44468bc464f7daeb946
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 440983279a9c8f04789434e24f0ff10b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51dceae1fa3604044afd0409fe8d6c84
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00391c6495efccb42bacdd0bd9cfa374
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user