refactor : 重构代码

This commit is contained in:
何冠峰
2026-01-20 17:10:13 +08:00
parent b0678906af
commit 5df021204d
318 changed files with 1387 additions and 6001 deletions

View File

@@ -15,7 +15,7 @@ namespace YooAsset.Editor
string buildinRootDirectory = buildParametersContext.GetBuildinRootDirectory();
string buildPackageName = buildParametersContext.Parameters.PackageName;
var manifestServices = buildParametersContext.Parameters.ManifestRestoreServices;
CatalogFileHelper.CreateFile(manifestServices, buildPackageName, buildinRootDirectory);
CatalogFileTools.CreateFile(manifestServices, buildPackageName, buildinRootDirectory);
// 刷新目录
AssetDatabase.Refresh();

View File

@@ -24,7 +24,7 @@ namespace YooAsset.Editor
string pipelineOutputDirectory = buildParametersContext.GetPipelineOutputDirectory();
foreach (var bundleInfo in buildMapContext.Collection)
{
EncryptBundleInfo fileInfo = new EncryptBundleInfo();
BundleEncryptionContext fileInfo = new BundleEncryptionContext();
fileInfo.BundleName = bundleInfo.BundleName;
fileInfo.FileLoadPath = $"{pipelineOutputDirectory}/{bundleInfo.BundleName}";
var encryptResult = encryptionServices.Encrypt(fileInfo);

View File

@@ -3,7 +3,7 @@ namespace YooAsset.Editor
{
public class EncryptionNone : IBundleEncryptionServices
{
public EncryptResult Encrypt(EncryptBundleInfo fileInfo)
public BundleEncryptionResult Encrypt(BundleEncryptionContext fileInfo)
{
throw new System.NotImplementedException();
}

View File

@@ -57,7 +57,7 @@ namespace YooAsset.Editor
private EViewMode _viewMode;
private string _searchKeyWord;
private DebugReport _currentReport;
private DiagnosticReport _currentReport;
private RemotePlayerSession _currentPlayerSession;
private double _lastRepaintTime = 0;
@@ -147,9 +147,9 @@ namespace YooAsset.Editor
EditorConnection.instance.Initialize();
EditorConnection.instance.RegisterConnection(OnHandleConnectionEvent);
EditorConnection.instance.RegisterDisconnection(OnHandleDisconnectionEvent);
EditorConnection.instance.Register(RemoteDebuggerDefine.kMsgPlayerSendToEditor, OnHandlePlayerMessage);
RemoteEditorConnection.Instance.Initialize();
RemoteEditorConnection.Instance.Register(RemoteDebuggerDefine.kMsgPlayerSendToEditor, OnHandlePlayerMessage);
EditorConnection.instance.Register(DiagnosticSystemDefine.PlayerToEditorMessageId, OnHandlePlayerMessage);
MockEditorConnection.Instance.Initialize();
MockEditorConnection.Instance.Register(DiagnosticSystemDefine.PlayerToEditorMessageId, OnHandlePlayerMessage);
}
catch (Exception e)
{
@@ -161,8 +161,8 @@ namespace YooAsset.Editor
// 远程调试
EditorConnection.instance.UnregisterConnection(OnHandleConnectionEvent);
EditorConnection.instance.UnregisterDisconnection(OnHandleDisconnectionEvent);
EditorConnection.instance.Unregister(RemoteDebuggerDefine.kMsgPlayerSendToEditor, OnHandlePlayerMessage);
RemoteEditorConnection.Instance.Unregister(RemoteDebuggerDefine.kMsgPlayerSendToEditor);
EditorConnection.instance.Unregister(DiagnosticSystemDefine.PlayerToEditorMessageId, OnHandlePlayerMessage);
MockEditorConnection.Instance.Unregister(DiagnosticSystemDefine.PlayerToEditorMessageId);
_playerSessions.Clear();
}
public void Update()
@@ -192,11 +192,11 @@ namespace YooAsset.Editor
private void OnHandlePlayerMessage(MessageEventArgs args)
{
int playerId = args.playerId;
var debugReport = DebugReport.Deserialize(args.data);
var debugReport = DiagnosticReport.Deserialize(args.data);
if (debugReport.DebuggerVersion != RemoteDebuggerDefine.DebuggerVersion)
if (debugReport.DebuggerVersion != DiagnosticSystemDefine.DebuggerVersion)
{
Debug.LogWarning($"Debugger versions are inconsistent : {debugReport.DebuggerVersion} != {RemoteDebuggerDefine.DebuggerVersion}");
Debug.LogWarning($"Debugger versions are inconsistent : {debugReport.DebuggerVersion} != {DiagnosticSystemDefine.DebuggerVersion}");
return;
}
@@ -254,23 +254,23 @@ namespace YooAsset.Editor
private void OnRecordToggleValueChange(ChangeEvent<bool> evt)
{
// 发送采集数据的命令
RemoteCommand command = new RemoteCommand();
command.CommandType = (int)ERemoteCommand.SampleAuto;
command.CommandParam = evt.newValue ? "open" : "close";
byte[] data = RemoteCommand.Serialize(command);
EditorConnection.instance.Send(RemoteDebuggerDefine.kMsgEditorSendToPlayer, data);
RemoteEditorConnection.Instance.Send(RemoteDebuggerDefine.kMsgEditorSendToPlayer, data);
RemoteDebugCommand command = new RemoteDebugCommand();
command.CommandType = (int)EDebugCommandType.AutoSampling;
command.Parameter = evt.newValue ? "open" : "close";
byte[] data = RemoteDebugCommand.Serialize(command);
EditorConnection.instance.Send(DiagnosticSystemDefine.EditorToPlayerMessageId, data);
MockEditorConnection.Instance.Send(DiagnosticSystemDefine.EditorToPlayerMessageId, data);
}
private void SampleBtn_onClick()
{
// 发送采集数据的命令
RemoteCommand command = new RemoteCommand();
command.CommandType = (int)ERemoteCommand.SampleOnce;
command.CommandParam = string.Empty;
byte[] data = RemoteCommand.Serialize(command);
EditorConnection.instance.Send(RemoteDebuggerDefine.kMsgEditorSendToPlayer, data);
RemoteEditorConnection.Instance.Send(RemoteDebuggerDefine.kMsgEditorSendToPlayer, data);
RemoteDebugCommand command = new RemoteDebugCommand();
command.CommandType = (int)EDebugCommandType.SampleOnce;
command.Parameter = string.Empty;
byte[] data = RemoteDebugCommand.Serialize(command);
EditorConnection.instance.Send(DiagnosticSystemDefine.EditorToPlayerMessageId, data);
MockEditorConnection.Instance.Send(DiagnosticSystemDefine.EditorToPlayerMessageId, data);
}
private void ExportBtn_clicked()
{
@@ -284,16 +284,16 @@ namespace YooAsset.Editor
if (resultPath != null)
{
// 注意:排序保证生成配置的稳定性
foreach (var packageData in _currentReport.PackageDatas)
foreach (var packageData in _currentReport.PackageDataList)
{
packageData.ProviderInfos.Sort();
foreach (var providerInfo in packageData.ProviderInfos)
{
providerInfo.DependBundles.Sort();
providerInfo.DependentBundles.Sort();
}
}
string filePath = $"{resultPath}/{nameof(DebugReport)}_{_currentReport.FrameCount}.json";
string filePath = $"{resultPath}/{nameof(DiagnosticReport)}_{_currentReport.FrameCount}.json";
string fileContent = JsonUtility.ToJson(_currentReport, true);
FileUtility.WriteAllText(filePath, fileContent);
Debug.Log($"Debug report file saved : {filePath}");

View File

@@ -7,7 +7,7 @@ namespace YooAsset.Editor
{
internal class RemotePlayerSession
{
private readonly Queue<DebugReport> _reports = new Queue<DebugReport>();
private readonly Queue<DiagnosticReport> _reports = new Queue<DiagnosticReport>();
/// <summary>
/// 用户ID
@@ -55,7 +55,7 @@ namespace YooAsset.Editor
/// <summary>
/// 添加一个调试报告
/// </summary>
public void AddDebugReport(DebugReport report)
public void AddDebugReport(DiagnosticReport report)
{
if (report == null)
Debug.LogWarning("Invalid debug report data !");
@@ -68,7 +68,7 @@ namespace YooAsset.Editor
/// <summary>
/// 获取调试报告
/// </summary>
public DebugReport GetDebugReport(int rangeIndex)
public DiagnosticReport GetDebugReport(int rangeIndex)
{
if (_reports.Count == 0)
return null;

View File

@@ -13,12 +13,12 @@ namespace YooAsset.Editor
{
private class ProviderTableData : DefaultTableData
{
public DebugPackageData PackageData;
public DebugProviderInfo ProviderInfo;
public DiagnosticPackageData PackageData;
public DiagnosticProviderInfo ProviderInfo;
}
private class DependTableData : DefaultTableData
{
public DebugBundleInfo BundleInfo;
public DiagnosticBundleInfo BundleInfo;
}
private VisualTreeAsset _visualAsset;
@@ -296,7 +296,7 @@ namespace YooAsset.Editor
/// <summary>
/// 填充页面数据
/// </summary>
public void FillViewData(DebugReport debugReport)
public void FillViewData(DiagnosticReport debugReport)
{
// 清空旧数据
_providerTableView.ClearAll(false, true);
@@ -304,7 +304,7 @@ namespace YooAsset.Editor
// 填充数据源
_sourceDatas = new List<ITableData>(1000);
foreach (var packageData in debugReport.PackageDatas)
foreach (var packageData in debugReport.PackageDataList)
{
foreach (var providerInfo in packageData.ProviderInfos)
{
@@ -313,10 +313,10 @@ namespace YooAsset.Editor
rowData.ProviderInfo = providerInfo;
rowData.AddAssetPathCell("PackageName", packageData.PackageName);
rowData.AddStringValueCell("AssetPath", providerInfo.AssetPath);
rowData.AddStringValueCell("SpawnScene", providerInfo.SpawnScene);
rowData.AddStringValueCell("BeginTime", providerInfo.BeginTime);
rowData.AddLongValueCell("LoadingTime", providerInfo.LoadingTime);
rowData.AddLongValueCell("RefCount", providerInfo.RefCount);
rowData.AddStringValueCell("SpawnScene", providerInfo.OriginScene);
rowData.AddStringValueCell("BeginTime", providerInfo.StartTime);
rowData.AddLongValueCell("LoadingTime", providerInfo.ElapsedMS);
rowData.AddLongValueCell("RefCount", providerInfo.ReferenceCount);
rowData.AddStringValueCell("Status", providerInfo.Status.ToString());
_sourceDatas.Add(rowData);
}
@@ -372,18 +372,18 @@ namespace YooAsset.Editor
private void OnProviderTableViewSelectionChanged(ITableData data)
{
var providerTableData = data as ProviderTableData;
DebugPackageData packageData = providerTableData.PackageData;
DebugProviderInfo providerInfo = providerTableData.ProviderInfo;
DiagnosticPackageData packageData = providerTableData.PackageData;
DiagnosticProviderInfo providerInfo = providerTableData.ProviderInfo;
// 填充依赖数据
var sourceDatas = new List<ITableData>(providerInfo.DependBundles.Count);
foreach (var bundleName in providerInfo.DependBundles)
var sourceDatas = new List<ITableData>(providerInfo.DependentBundles.Count);
foreach (var bundleName in providerInfo.DependentBundles)
{
var dependBundleInfo = packageData.GetBundleInfo(bundleName);
var rowData = new DependTableData();
rowData.BundleInfo = dependBundleInfo;
rowData.AddStringValueCell("DependBundles", dependBundleInfo.BundleName);
rowData.AddLongValueCell("RefCount", dependBundleInfo.RefCount);
rowData.AddLongValueCell("RefCount", dependBundleInfo.ReferenceCount);
rowData.AddStringValueCell("Status", dependBundleInfo.Status.ToString());
sourceDatas.Add(rowData);
}

View File

@@ -13,16 +13,16 @@ namespace YooAsset.Editor
{
private class BundleTableData : DefaultTableData
{
public DebugPackageData PackageData;
public DebugBundleInfo BundleInfo;
public DiagnosticPackageData PackageData;
public DiagnosticBundleInfo BundleInfo;
}
private class UsingTableData : DefaultTableData
{
public DebugProviderInfo ProviderInfo;
public DiagnosticProviderInfo ProviderInfo;
}
private class ReferenceTableData : DefaultTableData
{
public DebugBundleInfo BundleInfo;
public DiagnosticBundleInfo BundleInfo;
}
private VisualTreeAsset _visualAsset;
@@ -357,7 +357,7 @@ namespace YooAsset.Editor
/// <summary>
/// 填充页面数据
/// </summary>
public void FillViewData(DebugReport debugReport)
public void FillViewData(DiagnosticReport debugReport)
{
// 清空旧数据
_bundleTableView.ClearAll(false, true);
@@ -366,7 +366,7 @@ namespace YooAsset.Editor
// 填充数据源
_sourceDatas = new List<ITableData>(1000);
foreach (var packageData in debugReport.PackageDatas)
foreach (var packageData in debugReport.PackageDataList)
{
foreach (var bundleInfo in packageData.BundleInfos)
{
@@ -375,7 +375,7 @@ namespace YooAsset.Editor
rowData.BundleInfo = bundleInfo;
rowData.AddAssetPathCell("PackageName", packageData.PackageName);
rowData.AddStringValueCell("BundleName", bundleInfo.BundleName);
rowData.AddLongValueCell("RefCount", bundleInfo.RefCount);
rowData.AddLongValueCell("RefCount", bundleInfo.ReferenceCount);
rowData.AddStringValueCell("Status", bundleInfo.Status.ToString());
_sourceDatas.Add(rowData);
}
@@ -443,16 +443,16 @@ namespace YooAsset.Editor
var sourceDatas = new List<ITableData>(1000);
foreach (var providerInfo in packageData.ProviderInfos)
{
foreach (var dependBundleName in providerInfo.DependBundles)
foreach (var dependBundleName in providerInfo.DependentBundles)
{
if (dependBundleName == selectBundleInfo.BundleName)
{
var rowData = new UsingTableData();
rowData.ProviderInfo = providerInfo;
rowData.AddStringValueCell("UsingAssets", providerInfo.AssetPath);
rowData.AddStringValueCell("SpawnScene", providerInfo.SpawnScene);
rowData.AddStringValueCell("BeginTime", providerInfo.BeginTime);
rowData.AddLongValueCell("RefCount", providerInfo.RefCount);
rowData.AddStringValueCell("SpawnScene", providerInfo.OriginScene);
rowData.AddStringValueCell("BeginTime", providerInfo.StartTime);
rowData.AddLongValueCell("RefCount", providerInfo.ReferenceCount);
rowData.AddStringValueCell("Status", providerInfo.Status);
sourceDatas.Add(rowData);
break;
@@ -466,13 +466,13 @@ namespace YooAsset.Editor
// 填充ReferenceTableView
{
var sourceDatas = new List<ITableData>(1000);
foreach (string referenceBundleName in selectBundleInfo.ReferenceBundles)
foreach (string referenceBundleName in selectBundleInfo.ReferencedByBundles)
{
var bundleInfo = packageData.GetBundleInfo(referenceBundleName);
var rowData = new ReferenceTableData();
rowData.BundleInfo = bundleInfo;
rowData.AddStringValueCell("BundleName", bundleInfo.BundleName);
rowData.AddLongValueCell("RefCount", bundleInfo.RefCount);
rowData.AddLongValueCell("RefCount", bundleInfo.ReferenceCount);
rowData.AddStringValueCell("Status", bundleInfo.Status.ToString());
sourceDatas.Add(rowData);
}

View File

@@ -13,8 +13,8 @@ namespace YooAsset.Editor
{
private class OperationTableData : DefaultTableData
{
public DebugPackageData PackageData;
public DebugOperationInfo OperationInfo;
public DiagnosticPackageData PackageData;
public DiagnosticOperationInfo OperationInfo;
}
private VisualTreeAsset _visualAsset;
@@ -299,7 +299,7 @@ namespace YooAsset.Editor
/// <summary>
/// 填充页面数据
/// </summary>
public void FillViewData(DebugReport debugReport)
public void FillViewData(DiagnosticReport debugReport)
{
// 清空旧数据
_operationTableView.ClearAll(false, true);
@@ -308,7 +308,7 @@ namespace YooAsset.Editor
// 填充数据源
_sourceDatas = new List<ITableData>(1000);
foreach (var packageData in debugReport.PackageDatas)
foreach (var packageData in debugReport.PackageDataList)
{
foreach (var operationInfo in packageData.OperationInfos)
{
@@ -319,8 +319,8 @@ namespace YooAsset.Editor
rowData.AddStringValueCell("OperationName", operationInfo.OperationName);
rowData.AddLongValueCell("Priority", operationInfo.Priority);
rowData.AddDoubleValueCell("Progress", operationInfo.Progress);
rowData.AddStringValueCell("BeginTime", operationInfo.BeginTime);
rowData.AddLongValueCell("LoadingTime", operationInfo.ProcessTime);
rowData.AddStringValueCell("BeginTime", operationInfo.StartTime);
rowData.AddLongValueCell("LoadingTime", operationInfo.ElapsedMS);
rowData.AddStringValueCell("Status", operationInfo.Status.ToString());
rowData.AddStringValueCell("Desc", operationInfo.OperationDesc);
_sourceDatas.Add(rowData);
@@ -377,8 +377,8 @@ namespace YooAsset.Editor
private void OnOperationTableViewSelectionChanged(ITableData data)
{
var operationTableData = data as OperationTableData;
DebugPackageData packageData = operationTableData.PackageData;
DebugOperationInfo operationInfo = operationTableData.OperationInfo;
DiagnosticPackageData packageData = operationTableData.PackageData;
DiagnosticOperationInfo operationInfo = operationTableData.OperationInfo;
TreeNode rootNode = new TreeNode(operationInfo);
FillTreeData(operationInfo, rootNode);
@@ -450,7 +450,7 @@ namespace YooAsset.Editor
}
private void BindTreeViewItem(VisualElement container, object userData)
{
var operationInfo = (DebugOperationInfo)userData;
var operationInfo = (DiagnosticOperationInfo)userData;
// OperationName
{
@@ -467,13 +467,13 @@ namespace YooAsset.Editor
// BeginTime
{
var label = container.Q<Label>("BeginTime");
label.text = operationInfo.BeginTime;
label.text = operationInfo.StartTime;
}
// ProcessTime
{
var label = container.Q<Label>("ProcessTime");
label.text = operationInfo.ProcessTime.ToString();
label.text = operationInfo.ElapsedMS.ToString();
}
// Status
@@ -495,9 +495,9 @@ namespace YooAsset.Editor
label.text = operationInfo.OperationDesc;
}
}
private void FillTreeData(DebugOperationInfo parentOperation, TreeNode rootNode)
private void FillTreeData(DiagnosticOperationInfo parentOperation, TreeNode rootNode)
{
foreach (var childOperation in parentOperation.Childs)
foreach (var childOperation in parentOperation.Children)
{
var childNode = new TreeNode(childOperation);
rootNode.AddChild(childNode);

View File

@@ -1,54 +0,0 @@
using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;
namespace YooAsset
{
[Serializable]
internal class DebugPackageData
{
/// <summary>
/// 包裹名称
/// </summary>
public string PackageName;
public List<DebugProviderInfo> ProviderInfos = new List<DebugProviderInfo>(1000);
public List<DebugBundleInfo> BundleInfos = new List<DebugBundleInfo>(1000);
public List<DebugOperationInfo> OperationInfos = new List<DebugOperationInfo>(1000);
[NonSerialized]
public Dictionary<string, DebugBundleInfo> BundleInfoDic = new Dictionary<string, DebugBundleInfo>();
private bool _isParse = false;
/// <summary>
/// 获取调试资源包信息类
/// </summary>
public DebugBundleInfo GetBundleInfo(string bundleName)
{
// 解析数据
if (_isParse == false)
{
_isParse = true;
foreach (var bundleInfo in BundleInfos)
{
if (BundleInfoDic.ContainsKey(bundleInfo.BundleName) == false)
{
BundleInfoDic.Add(bundleInfo.BundleName, bundleInfo);
}
}
}
if (BundleInfoDic.TryGetValue(bundleName, out DebugBundleInfo value))
{
return value;
}
else
{
UnityEngine.Debug.LogError($"Can not found {nameof(DebugBundleInfo)} : {bundleName}");
return default;
}
}
}
}

View File

@@ -4,8 +4,11 @@ using System.Collections.Generic;
namespace YooAsset
{
/// <summary>
/// 描述资源包的运行时诊断信息
/// </summary>
[Serializable]
internal struct DebugBundleInfo : IComparer<DebugBundleInfo>, IComparable<DebugBundleInfo>
internal struct DiagnosticBundleInfo : IComparer<DiagnosticBundleInfo>, IComparable<DiagnosticBundleInfo>
{
/// <summary>
/// 资源包名称
@@ -15,23 +18,23 @@ namespace YooAsset
/// <summary>
/// 引用计数
/// </summary>
public int RefCount;
public int ReferenceCount;
/// <summary>
/// 加载状态
/// 当前状态
/// </summary>
public string Status;
/// <summary>
/// 谁引用了该资源包
/// 该资源包被谁引用
/// </summary>
public List<string> ReferenceBundles;
public List<string> ReferencedByBundles;
public int CompareTo(DebugBundleInfo other)
public int CompareTo(DiagnosticBundleInfo other)
{
return Compare(this, other);
}
public int Compare(DebugBundleInfo a, DebugBundleInfo b)
public int Compare(DiagnosticBundleInfo a, DiagnosticBundleInfo b)
{
return string.CompareOrdinal(a.BundleName, b.BundleName);
}

View File

@@ -4,8 +4,11 @@ using System.Collections.Generic;
namespace YooAsset
{
/// <summary>
/// 描述异步操作的运行时诊断信息
/// </summary>
[Serializable]
internal struct DebugOperationInfo : IComparer<DebugOperationInfo>, IComparable<DebugOperationInfo>
internal struct DiagnosticOperationInfo : IComparer<DiagnosticOperationInfo>, IComparable<DiagnosticOperationInfo>
{
/// <summary>
/// 任务名称
@@ -21,7 +24,7 @@ namespace YooAsset
/// 优先级
/// </summary>
public uint Priority;
/// <summary>
/// 任务进度
/// </summary>
@@ -30,12 +33,12 @@ namespace YooAsset
/// <summary>
/// 任务开始的时间
/// </summary>
public string BeginTime;
public string StartTime;
/// <summary>
/// 处理耗时(单位:毫秒)
/// </summary>
public long ProcessTime;
public long ElapsedMS;
/// <summary>
/// 任务状态
@@ -46,13 +49,13 @@ namespace YooAsset
/// 子任务列表
/// TODO : Serialization depth limit 10 exceeded
/// </summary>
public List<DebugOperationInfo> Childs;
public List<DiagnosticOperationInfo> Children;
public int CompareTo(DebugOperationInfo other)
public int CompareTo(DiagnosticOperationInfo other)
{
return Compare(this, other);
}
public int Compare(DebugOperationInfo a, DebugOperationInfo b)
public int Compare(DiagnosticOperationInfo a, DiagnosticOperationInfo b)
{
return string.CompareOrdinal(a.OperationName, b.OperationName);
}

View File

@@ -0,0 +1,65 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace YooAsset
{
/// <summary>
/// 包裹的诊断数据容器
/// </summary>
[Serializable]
internal class DiagnosticPackageData
{
/// <summary>
/// 包裹名称
/// </summary>
public string PackageName;
/// <summary>
/// 资源加载的诊断信息列表
/// </summary>
public List<DiagnosticProviderInfo> ProviderInfos = new List<DiagnosticProviderInfo>(1000);
/// <summary>
/// 资源包的诊断信息列表
/// </summary>
public List<DiagnosticBundleInfo> BundleInfos = new List<DiagnosticBundleInfo>(1000);
/// <summary>
/// 异步操作的诊断信息列表
/// </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;
foreach (var bundleInfo in BundleInfos)
{
if (_bundleInfoDict.ContainsKey(bundleInfo.BundleName) == false)
{
_bundleInfoDict.Add(bundleInfo.BundleName, bundleInfo);
}
}
}
if (_bundleInfoDict.TryGetValue(bundleName, out DiagnosticBundleInfo value))
{
return value;
}
else
{
UnityEngine.Debug.LogError($"Cannot find {nameof(DiagnosticBundleInfo)} : {bundleName}");
return default;
}
}
}
}

View File

@@ -4,14 +4,12 @@ using System.Collections.Generic;
namespace YooAsset
{
/// <summary>
/// 描述资源加载的运行时诊断信息
/// </summary>
[Serializable]
internal struct DebugProviderInfo : IComparer<DebugProviderInfo>, IComparable<DebugProviderInfo>
internal struct DiagnosticProviderInfo : IComparer<DiagnosticProviderInfo>, IComparable<DiagnosticProviderInfo>
{
/// <summary>
/// 包裹名
/// </summary>
public string PackageName { set; get; }
/// <summary>
/// 资源对象路径
/// </summary>
@@ -20,22 +18,22 @@ namespace YooAsset
/// <summary>
/// 资源出生的场景
/// </summary>
public string SpawnScene;
public string OriginScene;
/// <summary>
/// 资源加载开始时间
/// </summary>
public string BeginTime;
public string StartTime;
/// <summary>
/// 加载耗时(单位:毫秒)
/// </summary>
public long LoadingTime;
public long ElapsedMS;
/// <summary>
/// 引用计数
/// </summary>
public int RefCount;
public int ReferenceCount;
/// <summary>
/// 加载状态
@@ -45,13 +43,13 @@ namespace YooAsset
/// <summary>
/// 依赖的资源包列表
/// </summary>
public List<string> DependBundles;
public List<string> DependentBundles;
public int CompareTo(DebugProviderInfo other)
public int CompareTo(DiagnosticProviderInfo other)
{
return Compare(this, other);
}
public int Compare(DebugProviderInfo a, DebugProviderInfo b)
public int Compare(DiagnosticProviderInfo a, DiagnosticProviderInfo b)
{
return string.CompareOrdinal(a.AssetPath, b.AssetPath);
}

View File

@@ -7,15 +7,15 @@ using UnityEngine;
namespace YooAsset
{
/// <summary>
/// 资源系统调试信息
/// 资源系统的诊断报告
/// </summary>
[Serializable]
internal class DebugReport
internal class DiagnosticReport
{
/// <summary>
/// 调试器版本
/// </summary>
public string DebuggerVersion = RemoteDebuggerDefine.DebuggerVersion;
public string DebuggerVersion = DiagnosticSystemDefine.DebuggerVersion;
/// <summary>
/// 游戏帧
@@ -25,22 +25,22 @@ namespace YooAsset
/// <summary>
/// 调试的包裹数据列表
/// </summary>
public List<DebugPackageData> PackageDatas = new List<DebugPackageData>(10);
public List<DiagnosticPackageData> PackageDataList = new List<DiagnosticPackageData>(10);
/// <summary>
/// 序列化
/// </summary>
public static byte[] Serialize(DebugReport debugReport)
public static byte[] Serialize(DiagnosticReport report)
{
return Encoding.UTF8.GetBytes(JsonUtility.ToJson(debugReport));
return Encoding.UTF8.GetBytes(JsonUtility.ToJson(report));
}
/// <summary>
/// 反序列化
/// </summary>
public static DebugReport Deserialize(byte[] data)
public static DiagnosticReport Deserialize(byte[] data)
{
return JsonUtility.FromJson<DebugReport>(Encoding.UTF8.GetString(data));
return JsonUtility.FromJson<DiagnosticReport>(Encoding.UTF8.GetString(data));
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
namespace YooAsset
{
/// <summary>
/// 诊断系统的常量定义
/// </summary>
internal class DiagnosticSystemDefine
{
/// <summary>
/// 调试器版本号
/// </summary>
public const string DebuggerVersion = "2026.1.20";
/// <summary>
/// Player 向 Editor 发送消息的标识符
/// </summary>
public static readonly Guid PlayerToEditorMessageId = new Guid("e34a5702dd353724aa315fb8011f08c3");
/// <summary>
/// Editor 向 Player 发送消息的标识符
/// </summary>
public static readonly Guid EditorToPlayerMessageId = new Guid("4d1926c9df5b052469a1c63448b7609a");
}
}

View File

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

namespace YooAsset
{
internal enum EDebugCommandType
{
/// <summary>
/// 采样一次
/// </summary>
SampleOnce = 0,
/// <summary>
/// 持续采样
/// </summary>
AutoSampling = 1,
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: d6d4ba58bf1a1bb4db469b2cdd741ad0
guid: 773cefd0e4f22f745b88d25c930f6493
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -6,7 +6,11 @@ using UnityEngine;
namespace YooAsset
{
internal class RemoteEditorConnection
/// <summary>
/// 模拟的 Editor 连接
/// 在 Editor 模式下模拟 EditorConnection 的行为,用于本地调试
/// </summary>
internal class MockEditorConnection
{
#if UNITY_EDITOR
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
@@ -16,35 +20,35 @@ namespace YooAsset
}
#endif
private static RemoteEditorConnection _instance;
public static RemoteEditorConnection Instance
private static MockEditorConnection _instance;
public static MockEditorConnection Instance
{
get
{
if (_instance == null)
_instance = new RemoteEditorConnection();
_instance = new MockEditorConnection();
return _instance;
}
}
private readonly Dictionary<Guid, UnityAction<MessageEventArgs>> _messageCallbacks = new Dictionary<Guid, UnityAction<MessageEventArgs>>();
private readonly Dictionary<Guid, UnityAction<MessageEventArgs>> _messageHandlers = new Dictionary<Guid, UnityAction<MessageEventArgs>>();
public void Initialize()
{
_messageCallbacks.Clear();
_messageHandlers.Clear();
}
public void Register(Guid messageID, UnityAction<MessageEventArgs> callback)
{
if (messageID == Guid.Empty)
throw new ArgumentException("messageID is empty.");
if (_messageCallbacks.ContainsKey(messageID) == false)
_messageCallbacks.Add(messageID, callback);
if (_messageHandlers.ContainsKey(messageID) == false)
_messageHandlers.Add(messageID, callback);
}
public void Unregister(Guid messageID)
{
if (_messageCallbacks.ContainsKey(messageID))
_messageCallbacks.Remove(messageID);
if (_messageHandlers.ContainsKey(messageID))
_messageHandlers.Remove(messageID);
}
public void Send(Guid messageID, byte[] data)
{
@@ -52,12 +56,12 @@ namespace YooAsset
throw new ArgumentException("messageID is empty.");
// 接收对方的消息
RemotePlayerConnection.Instance.HandleEditorMessage(messageID, data);
MockPlayerConnection.Instance.HandleEditorMessage(messageID, data);
}
internal void HandlePlayerMessage(Guid messageID, byte[] data)
{
if (_messageCallbacks.TryGetValue(messageID, out UnityAction<MessageEventArgs> value))
if (_messageHandlers.TryGetValue(messageID, out UnityAction<MessageEventArgs> value))
{
var args = new MessageEventArgs();
args.playerId = 0;

View File

@@ -6,7 +6,11 @@ using UnityEngine;
namespace YooAsset
{
internal class RemotePlayerConnection
/// <summary>
/// 模拟的 Player 连接
/// 在 Editor 模式下模拟 PlayerConnection 的行为,用于本地调试
/// </summary>
internal class MockPlayerConnection
{
#if UNITY_EDITOR
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
@@ -16,35 +20,35 @@ namespace YooAsset
}
#endif
private static RemotePlayerConnection _instance;
public static RemotePlayerConnection Instance
private static MockPlayerConnection _instance;
public static MockPlayerConnection Instance
{
get
{
if (_instance == null)
_instance = new RemotePlayerConnection();
_instance = new MockPlayerConnection();
return _instance;
}
}
private readonly Dictionary<Guid, UnityAction<MessageEventArgs>> _messageCallbacks = new Dictionary<Guid, UnityAction<MessageEventArgs>>();
private readonly Dictionary<Guid, UnityAction<MessageEventArgs>> _messageHandlers = new Dictionary<Guid, UnityAction<MessageEventArgs>>();
public void Initialize()
{
_messageCallbacks.Clear();
_messageHandlers.Clear();
}
public void Register(Guid messageID, UnityAction<MessageEventArgs> callback)
{
if (messageID == Guid.Empty)
throw new ArgumentException("messageID is empty.");
if (_messageCallbacks.ContainsKey(messageID) == false)
_messageCallbacks.Add(messageID, callback);
if (_messageHandlers.ContainsKey(messageID) == false)
_messageHandlers.Add(messageID, callback);
}
public void Unregister(Guid messageID)
{
if (_messageCallbacks.ContainsKey(messageID))
_messageCallbacks.Remove(messageID);
if (_messageHandlers.ContainsKey(messageID))
_messageHandlers.Remove(messageID);
}
public void Send(Guid messageID, byte[] data)
{
@@ -52,12 +56,12 @@ namespace YooAsset
throw new ArgumentException("messageID is empty.");
// 接收对方的消息
RemoteEditorConnection.Instance.HandlePlayerMessage(messageID, data);
MockEditorConnection.Instance.HandlePlayerMessage(messageID, data);
}
internal void HandleEditorMessage(Guid messageID, byte[] data)
{
if (_messageCallbacks.TryGetValue(messageID, out UnityAction<MessageEventArgs> value))
if (_messageHandlers.TryGetValue(messageID, out UnityAction<MessageEventArgs> value))
{
var args = new MessageEventArgs();
args.playerId = 0;

View File

@@ -1,945 +0,0 @@
# DiagnosticSystem 诊断系统
## 模块概述
DiagnosticSystem 是 YooAsset 的**远程调试诊断系统**提供运行时资源管理状态的实时可视化和性能分析能力。该系统通过编辑器窗口与运行时游戏进行双向通信实时采集和展示资源加载、Bundle 管理、异步操作等调试信息。
### 核心特性
- **实时远程调试**:在 Unity 编辑器中查看游戏运行时的资源管理状态
- **完整状态快照**采集所有资源、Bundle、异步操作的实时信息
- **历史数据回溯**:缓存最近 500 帧数据,支持时间回溯分析
- **双模式采样**:支持单次采样和自动连续采样
- **低性能开销**:按需采样,无需连续监控
### 模块统计
| 组件 | 职责 |
|------|------|
| 核心通信 | RemoteDebuggerInRuntime + 双连接层 |
| 数据结构 | 5 个调试信息结构体 |
| 命令系统 | RemoteCommand 命令定义 |
| **总计** | 10 个核心文件,完整的远程诊断框架 |
---
## 设计目标
| 目标 | 说明 |
|------|------|
| **实时可视化** | 在编辑器中实时查看运行时资源状态 |
| **性能监控** | 收集加载耗时、引用计数、下载进度等性能指标 |
| **内存诊断** | 追踪资源出生场景、引用计数,识别潜在内存泄漏 |
| **操作追踪** | 显示异步操作树,包括嵌套和依赖关系 |
| **低开销设计** | 按需采样而非连续监控DEBUG 模式自动启用 |
---
## 文件结构
```
DiagnosticSystem/
├── RemoteDebuggerDefine.cs # 全局定义和常量
├── RemoteCommand.cs # 命令定义和序列化
├── DebugReport.cs # 调试报告(顶层容器)
├── DebugPackageData.cs # 包级调试数据
├── DebugProviderInfo.cs # 资源加载器调试信息
├── DebugBundleInfo.cs # 资源包调试信息
├── DebugOperationInfo.cs # 异步操作调试信息
├── RemoteDebuggerInRuntime.cs # 运行时调试器主类
├── RemotePlayerConnection.cs # 编辑器模拟连接层
└── RemoteEditorConnection.cs # 运行时模拟连接层
```
---
## 核心类说明
### RemoteDebuggerDefine
全局定义类,包含调试器版本和通信协议的 GUID 标识符。
```csharp
internal class RemoteDebuggerDefine
{
// 调试器版本(用于版本校验)
public const string DebuggerVersion = "2.3.3";
// 消息标识符GUID
public static readonly Guid kMsgPlayerSendToEditor =
new Guid("e34a5702dd353724aa315fb8011f08c3"); // 运行时→编辑器
public static readonly Guid kMsgEditorSendToPlayer =
new Guid("4d1926c9df5b052469a1c63448b7609a"); // 编辑器→运行时
}
```
### RemoteCommand
命令定义类,用于编辑器向运行时发送采样指令。
```csharp
internal enum ERemoteCommand
{
SampleOnce = 0, // 单次采样
SampleAuto = 1, // 自动采样(连续)
}
[Serializable]
internal class RemoteCommand
{
public int CommandType; // ERemoteCommand 枚举值
public string CommandParam; // 命令参数
// 序列化/反序列化JSON 格式)
public static byte[] Serialize(RemoteCommand command)
{
return Encoding.UTF8.GetBytes(JsonUtility.ToJson(command));
}
public static RemoteCommand Deserialize(byte[] data)
{
return JsonUtility.FromJson<RemoteCommand>(Encoding.UTF8.GetString(data));
}
}
```
**命令示例:**
```json
// 单次采样
{
"CommandType": 0,
"CommandParam": ""
}
// 开启自动采样
{
"CommandType": 1,
"CommandParam": "open"
}
// 关闭自动采样
{
"CommandType": 1,
"CommandParam": "close"
}
```
### DebugReport
调试报告容器,包含完整的系统状态快照。
```csharp
[Serializable]
internal class DebugReport
{
// 调试器版本(用于版本校验)
public string DebuggerVersion = RemoteDebuggerDefine.DebuggerVersion;
// 游戏帧数
public int FrameCount;
// 包级调试数据列表(一个游戏可能有多个资源包)
public List<DebugPackageData> PackageDatas = new List<DebugPackageData>(10);
// 序列化/反序列化
public static byte[] Serialize(DebugReport debugReport);
public static DebugReport Deserialize(byte[] data);
}
```
### DebugPackageData
包级调试数据,包含单个资源包的所有诊断信息。
```csharp
[Serializable]
internal class DebugPackageData
{
// 资源包名称
public string PackageName;
// 资源加载器列表
public List<DebugProviderInfo> ProviderInfos = new List<DebugProviderInfo>(1000);
// 资源包列表
public List<DebugBundleInfo> BundleInfos = new List<DebugBundleInfo>(1000);
// 异步操作列表
public List<DebugOperationInfo> OperationInfos = new List<DebugOperationInfo>(1000);
// 运行时查询字典(非序列化,按需构建)
[NonSerialized]
public Dictionary<string, DebugBundleInfo> BundleInfoDic;
// 延迟解析字典
public DebugBundleInfo GetBundleInfo(string bundleName);
}
```
### DebugProviderInfo
资源加载器Provider的调试信息。
```csharp
[Serializable]
internal struct DebugProviderInfo : IComparer<DebugProviderInfo>, IComparable<DebugProviderInfo>
{
public string PackageName; // 所属包名
public string AssetPath; // 资源路径(如 "Assets/Prefabs/Player.prefab"
public string SpawnScene; // 资源加载时的活跃场景名
public string BeginTime; // 加载开始时间格式HH:mm:ss.fff
public long LoadingTime; // 加载耗时(单位:毫秒)
public int RefCount; // 引用计数
public string Status; // 加载状态None/Processing/Succeed/Failed
public List<string> DependBundles; // 依赖的资源包名列表
// 按 AssetPath 字母排序
public int CompareTo(DebugProviderInfo other);
}
```
**关键诊断价值:**
- `SpawnScene`:追踪资源在哪个场景被加载,帮助识别资源泄漏
- `LoadingTime`:性能分析,识别加载慢的资源
- `RefCount`引用计数监控RefCount > 0 表示资源仍在使用
- `DependBundles`:依赖分析,理解资源加载的完整依赖链
### DebugBundleInfo
资源包Bundle的调试信息。
```csharp
[Serializable]
internal struct DebugBundleInfo : IComparer<DebugBundleInfo>, IComparable<DebugBundleInfo>
{
public string BundleName; // 资源包名称
public int RefCount; // 引用计数(当前有多少个 Provider 在使用)
public string Status; // 加载状态
public List<string> ReferenceBundles; // 反向依赖(谁引用了我)
// 按 BundleName 字母排序
public int CompareTo(DebugBundleInfo other);
}
```
**关键诊断价值:**
- `RefCount`Bundle 引用计数,为 0 时可以被卸载
- `ReferenceBundles`:反向依赖分析,了解 Bundle 被哪些其他 Bundle 依赖
### DebugOperationInfo
异步操作的调试信息,支持递归树结构。
```csharp
[Serializable]
internal struct DebugOperationInfo : IComparer<DebugOperationInfo>, IComparable<DebugOperationInfo>
{
public string OperationName; // 操作类名(如 "LoadAssetOperation"
public string OperationDesc; // 操作说明(自定义描述)
public uint Priority; // 优先级(用于操作排序)
public float Progress; // 进度0.0 - 1.0
public string BeginTime; // 操作开始时间
public long ProcessTime; // 处理耗时(单位:毫秒)
public string Status; // 操作状态None/Processing/Succeed/Failed
public List<DebugOperationInfo> Childs; // 子操作列表(支持嵌套树结构)
public int CompareTo(DebugOperationInfo other);
}
```
**递归树结构示例:**
```
InitializationOperation
├─ LoadManifestOperation
│ ├─ LoadBundleFileOperation (manifest.bundle)
│ └─ DeserializeManifestOperation
└─ InitFileSystemOperation
```
**关键诊断价值:**
- `OperationName`:操作类型识别
- `ProcessTime`:性能瓶颈分析
- `Childs`:操作依赖关系可视化
---
## 通信系统
### RemoteDebuggerInRuntime
运行时调试器主类,负责接收命令、采样数据、发送报告。
```csharp
internal class RemoteDebuggerInRuntime : MonoBehaviour
{
// 采样控制标志
private static bool _sampleOnce = false; // 单次采样
private static bool _autoSample = false; // 连续采样
// 运行时初始化
[RuntimeInitializeOnLoadMethod]
private static void RuntimeInitializeOnLoad()
{
_sampleOnce = false;
_autoSample = false;
}
private void Awake()
{
RemotePlayerConnection.Instance.Initialize();
}
private void OnEnable()
{
// 注册命令接收回调
RemotePlayerConnection.Instance.Register(
RemoteDebuggerDefine.kMsgEditorSendToPlayer,
OnHandleEditorMessage);
}
private void LateUpdate()
{
// 采样逻辑(在一帧的最后执行)
if (_autoSample || _sampleOnce)
{
_sampleOnce = false;
var debugReport = YooAssets.GetDebugReport();
var data = DebugReport.Serialize(debugReport);
RemotePlayerConnection.Instance.Send(
RemoteDebuggerDefine.kMsgPlayerSendToEditor,
data);
}
}
private static void OnHandleEditorMessage(MessageEventArgs args)
{
var command = RemoteCommand.Deserialize(args.data);
if (command.CommandType == (int)ERemoteCommand.SampleOnce)
{
_sampleOnce = true;
}
else if (command.CommandType == (int)ERemoteCommand.SampleAuto)
{
_autoSample = (command.CommandParam == "open");
}
}
}
```
**关键设计点:**
1. **LateUpdate 时机**:确保该帧所有资源加载完成后再采样
2. **状态重置**`[RuntimeInitializeOnLoadMethod]` 确保编辑器重新编译时重置状态
3. **DEBUG 模式自动启用**:通过 `#if DEBUG` 条件编译自动添加组件
### 双连接层架构
YooAsset 支持两种通信模式:
| 模式 | 使用场景 | 实现方式 |
|------|----------|----------|
| **编辑器模拟模式** | 开发调试 | `RemotePlayerConnection` + `RemoteEditorConnection`(虚拟连接) |
| **发布版本** | 运营期监控 | Unity 的 `PlayerConnection` API真实网络 |
#### RemotePlayerConnection编辑器模拟模式
```csharp
internal class RemotePlayerConnection
{
private static RemotePlayerConnection _instance;
private readonly Dictionary<Guid, UnityAction<MessageEventArgs>> _messageCallbacks;
public static RemotePlayerConnection Instance
{
get
{
if (_instance == null)
_instance = new RemotePlayerConnection();
return _instance;
}
}
public void Register(Guid messageID, UnityAction<MessageEventArgs> callback)
{
_messageCallbacks.Add(messageID, callback);
}
public void Send(Guid messageID, byte[] data)
{
// 在编辑器模拟模式下,发送给虚拟编辑器连接
RemoteEditorConnection.Instance.HandlePlayerMessage(messageID, data);
}
internal void HandleEditorMessage(Guid messageID, byte[] data)
{
if (_messageCallbacks.TryGetValue(messageID, out var callback))
{
callback.Invoke(new MessageEventArgs { playerId = 0, data = data });
}
}
}
```
#### RemoteEditorConnection运行时模拟模式
```csharp
internal class RemoteEditorConnection
{
private static RemoteEditorConnection _instance;
private readonly Dictionary<Guid, UnityAction<MessageEventArgs>> _messageCallbacks;
public static RemoteEditorConnection Instance
{
get
{
if (_instance == null)
_instance = new RemoteEditorConnection();
return _instance;
}
}
public void Register(Guid messageID, UnityAction<MessageEventArgs> callback)
{
_messageCallbacks.Add(messageID, callback);
}
public void Send(Guid messageID, byte[] data)
{
// 发送给虚拟运行时连接
RemotePlayerConnection.Instance.HandleEditorMessage(messageID, data);
}
internal void HandlePlayerMessage(Guid messageID, byte[] data)
{
if (_messageCallbacks.TryGetValue(messageID, out var callback))
{
callback.Invoke(new MessageEventArgs { playerId = 0, data = data });
}
}
}
```
---
## 通信协议
### 协议规范
**协议版本:** 2.3.3
**编码格式:**
```
C# 对象 → JsonUtility.ToJson() → JSON 字符串 → Encoding.UTF8.GetBytes() → byte[]
```
**消息类型:**
| 方向 | GUID | 数据类型 | 说明 |
|------|------|----------|------|
| 编辑器→运行时 | `4d1926c9df5b052469a1c63448b7609a` | `RemoteCommand` | 采样命令 |
| 运行时→编辑器 | `e34a5702dd353724aa315fb8011f08c3` | `DebugReport` | 调试报告 |
### 双向通信流程
```
[编辑器 UI]
├─ 用户点击 "Sample" 按钮
│ └─ 发送 RemoteCommand (SampleOnce)
└─ 用户开启 "Record" 开关
└─ 发送 RemoteCommand (SampleAuto, "open")
↓ RemoteEditorConnection.Send()
RemotePlayerConnection.HandleEditorMessage()
↓ 触发回调
[运行时]
RemoteDebuggerInRuntime.OnHandleEditorMessage()
设置采样标志 (_sampleOnce 或 _autoSample)
LateUpdate 中采样
YooAssets.GetDebugReport()
├─ 收集所有 ResourcePackage 数据
├─ DebugProviderInfo[] (从 ProviderDic)
├─ DebugBundleInfo[] (从 LoaderDic)
└─ DebugOperationInfo[] (从 _operations)
DebugReport.Serialize()
RemotePlayerConnection.Send()
RemoteEditorConnection.HandlePlayerMessage()
[编辑器]
AssetBundleDebuggerWindow.OnHandlePlayerMessage()
版本校验 (DebuggerVersion)
RemotePlayerSession.AddDebugReport()
缓存到历史记录 (最多 500 帧)
UI 刷新显示
```
### 版本校验机制
```csharp
// 编辑器端版本校验
private void OnHandlePlayerMessage(MessageEventArgs args)
{
var debugReport = DebugReport.Deserialize(args.data);
// 版本校验
if (debugReport.DebuggerVersion != RemoteDebuggerDefine.DebuggerVersion)
{
Debug.LogWarning(
$"Debugger versions are inconsistent : " +
$"{debugReport.DebuggerVersion} != {RemoteDebuggerDefine.DebuggerVersion}");
return; // 丢弃不兼容的数据
}
// 处理数据...
}
```
**设计意图:** 防止编辑器和运行时的调试器版本不一致导致的数据格式错误。
---
## 数据收集流程
### 完整采样流程
```
RemoteDebuggerInRuntime.LateUpdate()
检查 _sampleOnce 或 _autoSample 标志
↓ YES
调用 YooAssets.GetDebugReport()
├─ 初始化 DebugReport
├─ 设置 FrameCount = Time.frameCount
├─ 遍历每个 ResourcePackage
│ │
│ └─ package.GetDebugPackageData()
│ │
│ ├─ 创建 DebugPackageData
│ ├─ 设置 PackageName
│ │
│ ├─ 收集 ProviderInfos
│ │ ResourceManager.GetDebugProviderInfos()
│ │ 遍历 ProviderDic
│ │ └─ 每个 ProviderOperation 提供:
│ │ - MainAssetInfo.AssetPath
│ │ - SpawnScene场景名
│ │ - BeginTime开始时间
│ │ - ProcessTime耗时
│ │ - RefCount引用计数
│ │ - Status加载状态
│ │ - GetDebugDependBundles()(依赖列表)
│ │
│ ├─ 收集 BundleInfos
│ │ ResourceManager.GetDebugBundleInfos()
│ │ 遍历 LoaderDic
│ │ └─ 每个 LoadBundleFileOperation 提供:
│ │ - BundleName
│ │ - RefCount
│ │ - Status
│ │ - FilterReferenceBundles()(反向依赖)
│ │
│ └─ 收集 OperationInfos
│ OperationSystem.GetDebugOperationInfos(PackageName)
│ 遍历 _operations按 PackageName 过滤):
│ └─ 递归构建操作树:
│ - GetType().Name操作类名
│ - GetOperationDesc()(自定义描述)
│ - Priority优先级
│ - Progress进度
│ - BeginTime开始时间
│ - ProcessTime耗时
│ - Status状态
│ - Childs子操作列表
└─ 返回 DebugReport
```
### 性能指标收集
#### 资源加载耗时
```csharp
// AsyncOperationBase 中的自动计时
private Stopwatch _watch = null;
internal void InternalStart()
{
if (_watch == null)
{
BeginTime = SpawnTimeToString(UnityEngine.Time.realtimeSinceStartup);
_watch = Stopwatch.StartNew();
}
}
internal void InternalUpdate()
{
ProcessTime = _watch.ElapsedMilliseconds;
// ... 持续计时
}
```
#### 场景信息追踪
```csharp
// ProviderOperation.cs
[Conditional("DEBUG")] // 仅在 DEBUG 模式下启用
public void InitProviderDebugInfo()
{
SpawnScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
}
```
#### 引用计数监控
```csharp
// ProviderOperation 和 LoadBundleFileOperation 都维护 RefCount
public int RefCount { get; } // 当前被引用的次数
```
---
## 与其他模块的交互
```
YooAssets (全局入口)
├─ 初始化阶段:
│ #if DEBUG
│ _driver.AddComponent<RemoteDebuggerInRuntime>();
│ #endif
└─ 数据收集入口:
GetDebugReport()
├─ 遍历所有 ResourcePackage
└─ 构建 DebugReport
ResourcePackage (资源包)
└─ GetDebugPackageData()
├─ 调用 ResourceManager.GetDebugProviderInfos()
├─ 调用 ResourceManager.GetDebugBundleInfos()
└─ 调用 OperationSystem.GetDebugOperationInfos()
ResourceManager (资源管理器)
├─ GetDebugProviderInfos()
│ └─ 遍历 ProviderDic (Dictionary<string, ProviderOperation>)
└─ GetDebugBundleInfos()
└─ 遍历 LoaderDic (Dictionary<string, LoadBundleFileOperation>)
OperationSystem (操作系统)
└─ GetDebugOperationInfos(packageName)
└─ 遍历 _operations (List<AsyncOperationBase>)
└─ 递归收集子操作 (Childs)
AsyncOperationBase (异步操作基类)
├─ BeginTime操作开始时间
├─ ProcessTime累计处理耗时
├─ Status操作状态
├─ Progress进度
└─ GetOperationDesc():自定义描述
ProviderOperation (资源提供者)
├─ SpawnScene加载时的活跃场景
└─ GetDebugDependBundles():依赖包列表
LoadBundleFileOperation (Bundle 加载器)
├─ RefCount引用计数
└─ LoadBundleInfoBundle 信息
```
---
## 使用场景
### 场景 1运行时资源泄漏诊断
**问题:** 游戏切换场景后内存持续增长,怀疑有资源未释放。
**诊断步骤:**
1. 打开 AssetBundle Debugger 窗口
2. 开启 Record 模式(自动采样)
3. 切换场景前后观察 ProviderInfos 列表
4. 检查 `RefCount > 0``SpawnScene` 为旧场景的资源
5. 定位未释放的资源和对应的代码位置
**关键字段:**
- `SpawnScene`:资源在哪个场景被加载
- `RefCount`:引用计数,应该为 0
- `AssetPath`:资源路径,定位具体资源
### 场景 2资源加载性能分析
**问题:** 首次加载场景卡顿严重。
**诊断步骤:**
1. 单次采样Sample Once
2. 切换到 Asset View
3.`LoadingTime` 降序排序
4. 识别加载耗时最长的资源
5. 分析 `DependBundles` 了解依赖链
**关键字段:**
- `LoadingTime`:加载耗时(毫秒)
- `DependBundles`:依赖的 Bundle 列表
- `Status`:加载状态
### 场景 3Bundle 引用分析
**问题:** 某个 Bundle 无法被卸载。
**诊断步骤:**
1. 切换到 Bundle View
2. 搜索目标 Bundle
3. 检查 `RefCount``ReferenceBundles`
4. 追踪哪些资源正在使用该 Bundle
5. 定位未释放的资源引用
**关键字段:**
- `RefCount`Bundle 引用计数
- `ReferenceBundles`:反向依赖列表
- `Status`Bundle 加载状态
### 场景 4异步操作监控
**问题:** 复杂的初始化流程卡住,不知道在哪个步骤。
**诊断步骤:**
1. 切换到 Operation View
2. 查看操作树结构
3. 检查 `Status``Processing` 的操作
4. 分析 `Progress` 了解进度
5. 通过 `Childs` 了解操作依赖关系
**关键字段:**
- `OperationName`:操作类型
- `OperationDesc`:操作描述
- `Progress`进度0.0 - 1.0
- `Childs`:子操作列表
---
## 数据导出
### JSON 导出功能
编辑器窗口支持导出当前帧的完整调试数据为 JSON 文件。
**导出示例:**
```json
{
"DebuggerVersion": "2.3.3",
"FrameCount": 1234,
"PackageDatas": [
{
"PackageName": "DefaultPackage",
"ProviderInfos": [
{
"PackageName": "DefaultPackage",
"AssetPath": "Assets/Prefabs/Player.prefab",
"SpawnScene": "GameScene",
"BeginTime": "12:34:56.789",
"LoadingTime": 45,
"RefCount": 1,
"Status": "Succeed",
"DependBundles": [
"assets_prefabs.bundle"
]
}
],
"BundleInfos": [
{
"BundleName": "assets_prefabs.bundle",
"RefCount": 1,
"Status": "Succeed",
"ReferenceBundles": []
}
],
"OperationInfos": [
{
"OperationName": "LoadAssetOperation",
"OperationDesc": "Load assets_prefabs.bundle",
"Priority": 0,
"Progress": 1.0,
"BeginTime": "12:34:56.745",
"ProcessTime": 44,
"Status": "Succeed",
"Childs": []
}
]
}
]
}
```
**用途:**
- 离线分析和归档
- 性能数据对比
- 问题复现和追踪
---
## 系统架构图
```
┌─────────────────────────────────────────────────────────────────────┐
│ Unity Editor Window │
│ AssetBundleDebuggerWindow │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ UI Controls: │ │
│ │ - Sample Button (SampleOnce) │ │
│ │ - Record Toggle (SampleAuto) │ │
│ │ - View Mode Menu (Asset/Bundle/Operation View) │ │
│ │ - Frame Slider (历史帧导航) │ │
│ │ - Search Field (关键词搜索) │ │
│ │ - Export Button (JSON 导出) │ │
│ └──────────────────────────────────────────────────────────────┘ │
└────────────┬──────────────────────────────────────────────────────────┘
┌─────▼───────────────────────────────────┐
│ RemoteEditorConnection (虚拟连接) │
│ - Register callbacks │
│ - Send/Receive commands & reports │
└─────┬──────────────────────────────┬────┘
│ │
┌────────▼─────────────┐ ┌──────────▼───────────────┐
│ RemoteCommand │ │ DebugReport │
│ (Serialize) │ │ (Deserialize) │
│ ↓ JSON │ │ ← JSON │
│ ↓ UTF-8 bytes │ │ ← UTF-8 bytes │
└────────┬─────────────┘ └──────────┬───────────────┘
│ │
│ ═══════════════════════ │
│ Internet / Emulation │
│ ═══════════════════════ │
│ │
┌────────▼─────────────┐ ┌──────────▼───────────────┐
│ PlayerConnection │ │ RemotePlayerConnection │
│ (真实连接) │ │ (虚拟连接) │
│ 或模拟连接 │ │ │
└────────┬─────────────┘ └──────────┬───────────────┘
│ │
└──────────────┬───────────────┘
┌──────▼──────────────────────────────┐
│ RemoteDebuggerInRuntime │
│ (MonoBehaviour) │
│ ┌──────────────────────────────┐ │
│ │ _sampleOnce (bool) │ │
│ │ _autoSample (bool) │ │
│ └──────────────────────────────┘ │
│ ┌──────────────────────────────┐ │
│ │ Awake() - 初始化连接 │ │
│ │ OnEnable() - 注册回调 │ │
│ │ LateUpdate() - 采样触发 │ │
│ │ OnHandleEditorMessage() - 收命令│ │
│ └──────────────────────────────┘ │
└──────────┬──────────────────────────┘
┌──────▼────────────────────┐
│ YooAssets.GetDebugReport() │
│ (全系统数据收集入口) │
└──────┬────────────────────┘
┌───────────┼───────────┐
│ │ │
┌──────────▼──┐ ┌─────▼────┐ ┌──▼─────────────┐
│ResourcePkg 1│ │ResourcePkg2 │ResourcePackageN│
└──────┬──────┘ └────┬─────┘ └──┬──────────┘
│ │ │
└───────────────┼──────────┘
┌───────────▼──────────┐
│ DebugPackageData │
│ ┌─────────────────┐ │
│ │ PackageName │ │
│ │ ProviderInfos[] │ │
│ │ BundleInfos[] │ │
│ │ OperationInfos[]│ │
│ └─────────────────┘ │
└─────────────────────┘
```
---
## 注意事项
1. **DEBUG 模式自动启用**
- 诊断系统仅在 `DEBUG` 模式下启用(通过 `#if DEBUG` 条件编译)
- Release 构建中不会包含诊断代码,无性能开销
2. **版本兼容性**
- 编辑器和运行时的调试器版本必须一致
- 版本不一致的数据会被自动丢弃
3. **历史数据限制**
- 最多缓存 500 帧历史数据(可配置)
- 超过限制后,最早的数据会被移除
4. **JSON 序列化深度限制**
- Unity JsonUtility 序列化深度限制为 10 层
- 操作树Childs嵌套过深可能导致序列化失败
5. **性能开销**
- 单次采样:低开销,仅在需要时采集
- 自动采样:每帧采集,有一定性能开销,建议仅在需要时开启
6. **LateUpdate 时机**
- 采样在 LateUpdate 中执行,确保该帧所有操作已更新
- 避免在采样过程中资源状态发生变化
7. **非序列化字典**
- `DebugPackageData.BundleInfoDic` 使用 `[NonSerialized]` 标记
- 字典在首次查询时才构建,减少序列化开销
8. **场景追踪条件编译**
- `SpawnScene` 字段仅在 DEBUG 模式下赋值(`[Conditional("DEBUG")]`
- Release 构建中该字段为空字符串
---
## 性能优化建议
1. **按需采样**
- 优先使用单次采样Sample Once
- 仅在需要连续监控时开启自动采样Record
2. **及时关闭 Record**
- 分析完成后及时关闭自动采样
- 避免不必要的性能开销
3. **合理设置历史缓存**
- 根据内存情况调整 `MaxReportCount`
- 默认 500 帧已足够大多数分析场景
4. **导出数据离线分析**
- 对于复杂的性能问题,导出 JSON 数据
- 在编辑器外使用专业工具进行分析
5. **Release 构建移除诊断代码**
- 确保 Release 构建使用 Release 配置
- 诊断代码通过 `#if DEBUG` 自动移除

View File

@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 5fa2b66c20800124c8dd5cb77e854ce3
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,84 @@
using System;
using UnityEngine;
using UnityEngine.Networking.PlayerConnection;
namespace YooAsset
{
/// <summary>
/// 远程调试行为组件
/// 负责接收 Editor 命令并发送诊断数据
/// </summary>
internal class RemoteDebugBehaviour : MonoBehaviour
{
#if UNITY_EDITOR
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void OnRuntimeInitialize()
{
_sampleOnce = false;
_autoSampling = false;
}
#endif
private static bool _sampleOnce = false;
private static bool _autoSampling = false;
private void Awake()
{
#if UNITY_EDITOR
MockPlayerConnection.Instance.Initialize();
#endif
}
private void OnEnable()
{
#if UNITY_EDITOR
MockPlayerConnection.Instance.Register(DiagnosticSystemDefine.EditorToPlayerMessageId, HandleEditorMessage);
#else
PlayerConnection.instance.Register(DiagnosticSystemDefine.EditorToPlayerMessageId, HandleEditorMessage);
#endif
}
private void OnDisable()
{
#if UNITY_EDITOR
MockPlayerConnection.Instance.Unregister(DiagnosticSystemDefine.EditorToPlayerMessageId);
#else
PlayerConnection.instance.Unregister(DiagnosticSystemDefine.EditorToPlayerMessageId, HandleEditorMessage);
#endif
}
private void LateUpdate()
{
if (_autoSampling || _sampleOnce)
{
_sampleOnce = false;
var debugReport = YooAssets.GetDebugReport();
var data = DiagnosticReport.Serialize(debugReport);
#if UNITY_EDITOR
MockPlayerConnection.Instance.Send(DiagnosticSystemDefine.PlayerToEditorMessageId, data);
#else
PlayerConnection.instance.Send(DiagnosticSystemDefine.PlayerToEditorMessageId, data);
#endif
}
}
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)
{
_sampleOnce = true;
}
else if (command.CommandType == (int)EDebugCommandType.AutoSampling)
{
if (command.Parameter == "open")
_autoSampling = true;
else
_autoSampling = false;
}
else
{
throw new NotImplementedException(command.CommandType.ToString());
}
}
}
}

View File

@@ -4,21 +4,12 @@ using UnityEngine;
namespace YooAsset
{
internal enum ERemoteCommand
{
/// <summary>
/// 采样一次
/// </summary>
SampleOnce = 0,
/// <summary>
/// 自动采集
/// </summary>
SampleAuto = 1,
}
/// <summary>
/// 远程调试命令
/// 用于 Editor 向 Player 发送调试指令
/// </summary>
[Serializable]
internal class RemoteCommand
internal class RemoteDebugCommand
{
/// <summary>
/// 命令类型
@@ -28,13 +19,13 @@ namespace YooAsset
/// <summary>
/// 命令附加参数
/// </summary>
public string CommandParam;
public string Parameter;
/// <summary>
/// 序列化
/// </summary>
public static byte[] Serialize(RemoteCommand command)
public static byte[] Serialize(RemoteDebugCommand command)
{
return Encoding.UTF8.GetBytes(JsonUtility.ToJson(command));
}
@@ -42,9 +33,9 @@ namespace YooAsset
/// <summary>
/// 反序列化
/// </summary>
public static RemoteCommand Deserialize(byte[] data)
public static RemoteDebugCommand Deserialize(byte[] data)
{
return JsonUtility.FromJson<RemoteCommand>(Encoding.UTF8.GetString(data));
return JsonUtility.FromJson<RemoteDebugCommand>(Encoding.UTF8.GetString(data));
}
}
}

View File

@@ -1,11 +0,0 @@
using System;
namespace YooAsset
{
internal class RemoteDebuggerDefine
{
public const string DebuggerVersion = "2.3.3";
public static readonly Guid kMsgPlayerSendToEditor = new Guid("e34a5702dd353724aa315fb8011f08c3");
public static readonly Guid kMsgEditorSendToPlayer = new Guid("4d1926c9df5b052469a1c63448b7609a");
}
}

View File

@@ -1,81 +0,0 @@
using System;
using System.Text;
using UnityEngine;
using UnityEngine.Networking.PlayerConnection;
namespace YooAsset
{
internal class RemoteDebuggerInRuntime : MonoBehaviour
{
#if UNITY_EDITOR
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void OnRuntimeInitialize()
{
_sampleOnce = false;
_autoSample = false;
}
#endif
private static bool _sampleOnce = false;
private static bool _autoSample = false;
private void Awake()
{
#if UNITY_EDITOR
RemotePlayerConnection.Instance.Initialize();
#endif
}
private void OnEnable()
{
#if UNITY_EDITOR
RemotePlayerConnection.Instance.Register(RemoteDebuggerDefine.kMsgEditorSendToPlayer, OnHandleEditorMessage);
#else
PlayerConnection.instance.Register(RemoteDebuggerDefine.kMsgEditorSendToPlayer, OnHandleEditorMessage);
#endif
}
private void OnDisable()
{
#if UNITY_EDITOR
RemotePlayerConnection.Instance.Unregister(RemoteDebuggerDefine.kMsgEditorSendToPlayer);
#else
PlayerConnection.instance.Unregister(RemoteDebuggerDefine.kMsgEditorSendToPlayer, OnHandleEditorMessage);
#endif
}
private void LateUpdate()
{
if (_autoSample || _sampleOnce)
{
_sampleOnce = false;
var debugReport = YooAssets.GetDebugReport();
var data = DebugReport.Serialize(debugReport);
#if UNITY_EDITOR
RemotePlayerConnection.Instance.Send(RemoteDebuggerDefine.kMsgPlayerSendToEditor, data);
#else
PlayerConnection.instance.Send(RemoteDebuggerDefine.kMsgPlayerSendToEditor, data);
#endif
}
}
private static void OnHandleEditorMessage(MessageEventArgs args)
{
var command = RemoteCommand.Deserialize(args.data);
YooLogger.Log($"On handle remote command : {command.CommandType} Param : {command.CommandParam}");
if (command.CommandType == (int)ERemoteCommand.SampleOnce)
{
_sampleOnce = true;
}
else if (command.CommandType == (int)ERemoteCommand.SampleAuto)
{
if (command.CommandParam == "open")
_autoSample = true;
else
_autoSample = false;
}
else
{
throw new NotImplementedException(command.CommandType.ToString());
}
}
}
}

View File

@@ -9,39 +9,39 @@ namespace YooAsset
/// 线程安全:内部使用 Dictionary 且未加锁,约定只在 Unity 主线程调用。
/// 如需在多线程/回调线程调用,请在外层加锁或改为并发容器实现。
/// </remarks>
internal class WebRequestCounter
internal class DownloadFailureCounter
{
#if UNITY_EDITOR
[UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)]
private static void OnRuntimeInitialize()
{
_requestFailedRecorder.Clear();
_failureRecords.Clear();
}
#endif
/// <summary>
/// 失败计数记录表key = $"{packageName}_{eventName}"
/// </summary>
private static readonly Dictionary<string, int> _requestFailedRecorder = new Dictionary<string, int>(1000);
private static readonly Dictionary<string, int> _failureRecords = new Dictionary<string, int>(1000);
/// <summary>
/// 记录一次失败
/// </summary>
public static void RecordRequestFailed(string packageName, string eventName)
public static void RecordFailure(string packageName, string eventName)
{
string key = $"{packageName}_{eventName}";
if (_requestFailedRecorder.ContainsKey(key) == false)
_requestFailedRecorder.Add(key, 0);
_requestFailedRecorder[key]++;
if (_failureRecords.ContainsKey(key) == false)
_failureRecords.Add(key, 0);
_failureRecords[key]++;
}
/// <summary>
/// 获取失败次数
/// </summary>
public static int GetRequestFailedCount(string packageName, string eventName)
public static int GetFailureCount(string packageName, string eventName)
{
string key = $"{packageName}_{eventName}";
if (_requestFailedRecorder.TryGetValue(key, out int count))
if (_failureRecords.TryGetValue(key, out int count))
return count;
return 0;
}

View File

@@ -2,59 +2,6 @@ using System.Collections.Generic;
namespace YooAsset
{
/// <summary>
/// 导入的资源包信息
/// </summary>
public struct ImportBundleInfo
{
/// <summary>
/// 本地文件路径
/// </summary>
public string FilePath;
/// <summary>
/// 资源包名称
/// </summary>
public string BundleName;
/// <summary>
/// 资源包GUID
/// </summary>
public string BundleGUID;
}
/// <summary>
/// 下载请求状态
/// </summary>
internal enum EDownloadRequestStatus
{
/// <summary>
/// 未开始
/// </summary>
None,
/// <summary>
/// 进行中
/// </summary>
Running,
/// <summary>
/// 已成功
/// </summary>
Succeed,
/// <summary>
/// 已失败
/// </summary>
Failed,
/// <summary>
/// 已中止
/// </summary>
Aborted,
}
/// <summary>
/// 文件下载请求参数
/// </summary>
@@ -88,7 +35,7 @@ namespace YooAsset
/// 2. 每次接收到下载数据时,看门狗计时器会重置。
/// 3. 若在设定的时间范围内未收到任何数据,任务将被自动终止。
/// </remarks>
public readonly int WatchdogTime;
public readonly int WatchdogTimeout;
/// <summary>
/// 文件保存路径
@@ -99,7 +46,7 @@ namespace YooAsset
/// 是否追加写入文件
/// </summary>
/// <remarks>
/// 配合 ResumeFromBytes 使用,用于断点续传场景。
/// 配合 ResumeOffset 使用,用于断点续传场景。
/// </remarks>
public readonly bool AppendToFile;
@@ -112,9 +59,9 @@ namespace YooAsset
/// 断点续传的起始字节(小于等于 0 表示不启用)
/// </summary>
/// <remarks>
/// 推荐由后端自动设置 Range 请求头:"bytes={ResumeFromBytes}-"。
/// 推荐由后端自动设置 Range 请求头:"bytes={ResumeOffset}-"。
/// </remarks>
public readonly long ResumeFromBytes;
public readonly long ResumeOffset;
/// <summary>
/// 自定义请求头(可选)
@@ -128,18 +75,18 @@ namespace YooAsset
string url,
string savePath,
int timeout,
int watchdogTime,
int watchdogTimeout,
bool appendToFile = false,
bool removeFileOnAbort = true,
long resumeFromBytes = 0)
long resumeOffset = 0)
{
URL = url;
SavePath = savePath;
Timeout = timeout;
WatchdogTime = watchdogTime;
WatchdogTimeout = watchdogTimeout;
AppendToFile = appendToFile;
RemoveFileOnAbort = removeFileOnAbort;
ResumeFromBytes = resumeFromBytes;
ResumeOffset = resumeOffset;
Headers = null;
}
@@ -187,7 +134,7 @@ namespace YooAsset
/// 2. 每次接收到下载数据时,看门狗计时器会重置。
/// 3. 若在设定的时间范围内未收到任何数据,任务将被自动终止。
/// </remarks>
public readonly int WatchdogTime;
public readonly int WatchdogTimeout;
/// <summary>
/// 自定义请求头(可选)
@@ -197,13 +144,11 @@ namespace YooAsset
/// <summary>
/// 构造数据下载请求参数
/// </summary>
/// <param name="url">请求地址</param>
/// <param name="options">通用请求参数</param>
public DownloadDataRequestArgs(string url, int timeout, int watchdogTime)
public DownloadDataRequestArgs(string url, int timeout, int watchdogTimeout)
{
URL = url;
Timeout = timeout;
WatchdogTime = watchdogTime;
WatchdogTimeout = watchdogTimeout;
Headers = null;
}
@@ -251,7 +196,7 @@ namespace YooAsset
/// 2. 每次接收到下载数据时,看门狗计时器会重置。
/// 3. 若在设定的时间范围内未收到任何数据,任务将被自动终止。
/// </remarks>
public readonly int WatchdogTime;
public readonly int WatchdogTimeout;
/// <summary>
/// 禁用 Unity 的网络缓存
@@ -282,14 +227,14 @@ namespace YooAsset
public DownloadAssetBundleRequestArgs(
string url,
int timeout,
int watchdogTime,
int watchdogTimeout,
bool disableUnityWebCache = true,
string fileHash = null,
uint unityCrc = 0)
{
URL = url;
Timeout = timeout;
WatchdogTime = watchdogTime;
WatchdogTimeout = watchdogTimeout;
DisableUnityWebCache = disableUnityWebCache;
FileHash = fileHash;
UnityCRC = unityCrc;

View File

@@ -1,14 +1,12 @@
using UnityEngine.Networking;
using UnityEngine;

namespace YooAsset
{
internal class DownloadSystemHelper
internal class DownloadSystemTools
{
/// <summary>
/// 获取WWW加载本地资源的路径
/// 转换为本地文件请求地址
/// </summary>
public static string ConvertToWWWPath(string path)
public static string ToLocalURL(string path)
{
string url;
@@ -57,7 +55,7 @@ namespace YooAsset
#elif UNITY_STANDALONE_LINUX
url = StringUtility.Format("file:///root/{0}", path);
#else
throw new System.NotSupportedException($"[{nameof(DownloadSystemHelper.ConvertToWWWPath)}] Platform '{UnityEngine.Application.platform}' is not supported.");
throw new System.NotSupportedException($"Platform '{UnityEngine.Application.platform}' is not supported.");
#endif
// For some special cases when users have special characters in their devices, url paths can not be identified correctly.
@@ -67,7 +65,7 @@ namespace YooAsset
/// <summary>
/// 是否请求的本地文件
/// </summary>
public static bool IsRequestLocalFile(string url)
public static bool IsLocalFileURL(string url)
{
//TODO UNITY_STANDALONE_OSX平台目前无法确定

View File

@@ -0,0 +1,34 @@
namespace YooAsset
{
/// <summary>
/// 下载请求状态
/// </summary>
internal enum EDownloadRequestStatus
{
/// <summary>
/// 未开始
/// </summary>
None,
/// <summary>
/// 进行中
/// </summary>
Running,
/// <summary>
/// 已成功
/// </summary>
Succeed,
/// <summary>
/// 已失败
/// </summary>
Failed,
/// <summary>
/// 已中止
/// </summary>
Aborted,
}
}

View File

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

View File

@@ -4,6 +4,10 @@
DownloadSystem 是 YooAsset 资源管理系统的**底层网络下载层**,负责处理所有 HTTP 网络请求。该模块提供了统一的下载接口抽象,支持文件下载、断点续传、并发请求(由上层调度)、看门狗监控等功能。
### 可见性说明
DownloadSystem 属于 YooAsset Runtime 的内部基础模块,目录内大多数类型为 `internal`(仅供 YooAsset Runtime 内部程序集使用)。本文示例以“模块内部调用方式”展示;业务层建议优先通过 `ResourcePackage / FileSystem / ResourceManager` 等上层接口使用下载能力,避免直接依赖本模块的内部类型。
### 核心职责
- HTTP/HTTPS 文件下载
@@ -106,7 +110,7 @@ DownloadSystem/
定义网络库实现的合约,通过工厂方法创建各类下载请求。
```csharp
public interface IDownloadBackend
internal interface IDownloadBackend : IDisposable
{
/// <summary>
/// 后端标识名称(用于日志/调试)
@@ -133,7 +137,7 @@ public interface IDownloadBackend
所有下载请求的通用接口,定义生命周期和状态管理。
```csharp
public interface IDownloadRequest : IDisposable
internal interface IDownloadRequest : IDisposable
{
// 元信息
string URL { get; }
@@ -159,9 +163,9 @@ public interface IDownloadRequest : IDisposable
### 专化请求接口
| 接口 | 用途 | 特有属性 |
| 接口 | 用途 | 特有能力 |
|------|------|----------|
| `IDownloadHeadRequest` | HEAD 请求,获取响应头 | `ETag`, `LastModified`, `ContentLength`, `ContentType` |
| `IDownloadHeadRequest` | HEAD 请求,获取响应头 | `ETag`, `LastModified`, `ContentLength`, `ContentType`, `GetResponseHeader(name)` |
| `IDownloadFileRequest` | 文件下载到本地 | `SavePath` |
| `IDownloadBytesRequest` | 下载到内存(字节数组) | `byte[] Result` |
| `IDownloadTextRequest` | 下载文本内容 | `string Result` |
@@ -174,7 +178,7 @@ public interface IDownloadRequest : IDisposable
### 请求状态枚举
```csharp
public enum EDownloadRequestStatus
internal enum EDownloadRequestStatus
{
None, // 未开始
Running, // 进行中
@@ -189,58 +193,107 @@ public enum EDownloadRequestStatus
#### DownloadFileRequestArgs文件下载参数
```csharp
public struct DownloadFileRequestArgs
internal struct DownloadFileRequestArgs
{
public string URL; // 请求地址
public int Timeout; // 响应超时0=无限制
public int WatchdogTime; // 看门狗超时(秒)
public readonly string URL; // 请求地址
public readonly int Timeout; // 响应超时0=不应用超时
public readonly int WatchdogTime; // 看门狗超时(秒)0=禁用
public string SavePath; // 文件保存路径
public bool AppendToFile; // 追加写入(断点续传)
public bool RemoveFileOnAbort; // 中止时删除文件
public long ResumeFromBytes; // 断点续传起始位置
public readonly string SavePath; // 文件保存路径
public readonly bool AppendToFile; // 追加写入(断点续传)
public readonly bool RemoveFileOnAbort; // 中止时删除文件
public readonly long ResumeFromBytes; // 断点续传起始位置>0 时推荐由后端设置 Range 头)
public Dictionary<string, string> Headers; // 自定义请求头
public Dictionary<string, string> Headers; // 自定义请求头(可选)
public DownloadFileRequestArgs(
string url,
string savePath,
int timeout,
int watchdogTime,
bool appendToFile = false,
bool removeFileOnAbort = true,
long resumeFromBytes = 0);
/// <summary>
/// 添加请求头(注意:相同 key 重复添加会抛异常)
/// </summary>
public void AddRequestHeader(string name, string value);
}
```
#### DownloadDataRequestArgs数据下载参数
```csharp
public struct DownloadDataRequestArgs
internal struct DownloadDataRequestArgs
{
public string URL; // 请求地址
public int Timeout; // 响应超时(秒)
public int WatchdogTime; // 看门狗超时(秒)
public Dictionary<string, string> Headers; // 自定义请求头
public readonly string URL; // 请求地址
public readonly int Timeout; // 响应超时(秒)0=不应用超时
public readonly int WatchdogTime; // 看门狗超时(秒)0=禁用
public Dictionary<string, string> Headers; // 自定义请求头(可选)
public DownloadDataRequestArgs(string url, int timeout, int watchdogTime);
/// <summary>
/// 添加请求头(注意:相同 key 重复添加会抛异常)
/// </summary>
public void AddRequestHeader(string name, string value);
}
```
#### DownloadAssetBundleRequestArgsAssetBundle 下载参数)
```csharp
public struct DownloadAssetBundleRequestArgs
internal struct DownloadAssetBundleRequestArgs
{
public string URL; // 请求地址
public int Timeout; // 响应超时
public int WatchdogTime; // 看门狗超时
public readonly string URL; // 请求地址
public readonly int Timeout; // 响应超时0=不应用超时
public readonly int WatchdogTime; // 看门狗超时0=禁用
public bool DisableUnityWebCache; // 禁用 Unity 缓存(推荐 true
public string FileHash; // 文件哈希(缓存启用时需要)
public uint UnityCRC; // Unity CRC 校验值
public readonly bool DisableUnityWebCache; // 禁用 Unity 缓存(默认 true
public readonly string FileHash; // 文件哈希(缓存启用时需要,且不能为空
public readonly uint UnityCRC; // Unity CRC 校验值
public Dictionary<string, string> Headers;
public Dictionary<string, string> Headers; // 自定义请求头(可选)
public DownloadAssetBundleRequestArgs(
string url,
int timeout,
int watchdogTime,
bool disableUnityWebCache = true,
string fileHash = null,
uint unityCrc = 0);
/// <summary>
/// 添加请求头(注意:相同 key 重复添加会抛异常)
/// </summary>
public void AddRequestHeader(string name, string value);
}
```
#### DownloadSimulateRequestArgs模拟下载参数
```csharp
public struct DownloadSimulateRequestArgs
internal struct DownloadSimulateRequestArgs
{
public string URL; // 标识符
public long FileSize; // 模拟文件大小
public long DownloadSpeed; // 模拟速度(字节/秒),默认 1MB/s
public readonly string URL; // 标识符
public readonly long FileSize; // 模拟文件大小
public readonly long DownloadSpeed; // 模拟速度(字节/秒),默认 1MB/s
public DownloadSimulateRequestArgs(string url, long fileSize, long downloadSpeed = 1024 * 1024);
}
```
#### ImportBundleInfo导入资源包信息
> 说明:该结构体位于 `DownloadSystemDefine.cs`,主要用于资源包导入(离线文件导入)相关流程,并不参与具体的网络请求参数配置。
```csharp
public struct ImportBundleInfo
{
public string FilePath; // 本地文件路径
public string BundleName; // 资源包名称
public string BundleGUID; // 资源包GUID
}
```
@@ -304,36 +357,38 @@ None ──► SendRequest() ──► Running ──► PollingRequest() ──
### 基础文件下载
```csharp
// 1. 创建后端和请求
IDownloadBackend backend = new UnityWebRequestBackend();
var args = new DownloadFileRequestArgs(
url: "https://example.com/file.zip",
savePath: "/path/to/save/file.zip",
timeout: 30,
watchdogTime: 0);
IDownloadFileRequest request = backend.CreateFileRequest(args);
// 2. 发起并轮询
request.SendRequest();
while (!request.IsDone)
IDownloadFileRequest request = null;
try
{
await Task.Yield();
// 可选:显示进度
float progress = request.DownloadProgress;
}
// 1. 创建请求
var args = new DownloadFileRequestArgs(
url: "https://example.com/file.zip",
savePath: "/path/to/save/file.zip",
timeout: 30,
watchdogTime: 0);
request = backend.CreateFileRequest(args);
// 3. 检查结果
if (request.Status == EDownloadRequestStatus.Succeed)
{
Debug.Log("下载成功");
}
else
{
Debug.LogError($"下载失败: {request.Error}");
}
// 2. 发起并轮询
request.SendRequest();
while (!request.IsDone)
{
await Task.Yield();
float progress = request.DownloadProgress;
}
// 4. 清理资源
request.Dispose();
// 3. 检查结果
if (request.Status == EDownloadRequestStatus.Succeed)
Debug.Log("下载成功");
else
Debug.LogError($"下载失败: {request.Error}");
}
finally
{
// 4. 清理资源
request?.Dispose();
backend.Dispose();
}
```
### 断点续传
@@ -547,6 +602,7 @@ int count = WebRequestCounter.GetRequestFailedCount(packageName, eventName);
1. **资源释放**:使用完毕后务必调用 `Dispose()` 释放资源
- `AbortRequest()` 仅用于中止请求与切换状态,不等同于释放资源;无论成功/失败/中止都需要 `Dispose()`
- 第三方 `IDownloadBackend` 可能持有原生资源/线程/连接池等,上层在不再使用时也应调用 `backend.Dispose()`
- 推荐使用 `try/finally` 确保释放(尤其是上层可能提前中止的场景)
2. **断点续传**:需要服务器支持 `Range` 请求头和 `206 Partial Content` 响应
- 若服务端不支持 Range 仍返回 200全量内容可能会被追加写入导致文件损坏
@@ -555,3 +611,4 @@ int count = WebRequestCounter.GetRequestFailedCount(packageName, eventName);
5. **驱动更新**:部分第三方网络库实现的 backend 可能需要每帧调用 `IDownloadBackend.Update()` 进行驱动
6. **中止语义**`Aborted` 可能来自用户主动 `AbortRequest()` 或看门狗超时;中止场景下 `HttpCode/Error` 可能为默认值(例如 0/空)
7. **线程安全**:所有下载请求的创建和轮询应在主线程进行
8. **模拟下载器**`VirtualFileDownloader` 仅用于模拟进度,不会落盘,且 `SavePath` 始终为 `null`;成功时 `HttpCode` 固定为 `200`

View File

@@ -64,7 +64,7 @@ namespace YooAsset
/// </summary>
public IDownloadHeadRequest CreateHeadRequest(DownloadDataRequestArgs args)
{
return new UnityWebRequestHeadDownloader(args, _webRequestCreator);
return new UnityWebRequestHead(args, _webRequestCreator);
}
/// <summary>
@@ -72,7 +72,7 @@ namespace YooAsset
/// </summary>
public IDownloadFileRequest CreateFileRequest(DownloadFileRequestArgs args)
{
return new UnityWebRequestFileDownloader(args, _webRequestCreator);
return new UnityWebRequestFile(args, _webRequestCreator);
}
/// <summary>
@@ -80,7 +80,7 @@ namespace YooAsset
/// </summary>
public IDownloadBytesRequest CreateBytesRequest(DownloadDataRequestArgs args)
{
return new UnityWebRequestBytesDownloader(args, _webRequestCreator);
return new UnityWebRequestBytes(args, _webRequestCreator);
}
/// <summary>
@@ -88,7 +88,7 @@ namespace YooAsset
/// </summary>
public IDownloadTextRequest CreateTextRequest(DownloadDataRequestArgs args)
{
return new UnityWebRequestTextDownloader(args, _webRequestCreator);
return new UnityWebRequestText(args, _webRequestCreator);
}
/// <summary>
@@ -96,7 +96,7 @@ namespace YooAsset
/// </summary>
public IDownloadAssetBundleRequest CreateAssetBundleRequest(DownloadAssetBundleRequestArgs args)
{
return new UnityWebRequestAssetBundleDownloader(args, _webRequestCreator);
return new UnityWebRequestAssetBundle(args, _webRequestCreator);
}
/// <summary>
@@ -104,7 +104,7 @@ namespace YooAsset
/// </summary>
public IDownloadFileRequest CreateSimulateRequest(DownloadSimulateRequestArgs args)
{
return new VirtualFileDownloader(args);
return new SimulateRequestFile(args);
}
}
}

View File

@@ -9,10 +9,10 @@ namespace YooAsset
/// 用于编辑器模式下模拟下载进度,不进行实际网络请求。
/// 根据配置的下载速度模拟进度变化。
/// </remarks>
internal sealed class VirtualFileDownloader : IDownloadFileRequest
internal sealed class SimulateRequestFile : IDownloadFileRequest
{
private readonly DownloadSimulateRequestArgs _args;
private double _lastUpdateTime;
private double _lastTickTime;
/// <summary>
/// 文件保存路径(模拟下载不需要)
@@ -72,7 +72,7 @@ namespace YooAsset
/// 构造模拟下载器
/// </summary>
/// <param name="args">模拟下载参数</param>
public VirtualFileDownloader(DownloadSimulateRequestArgs args)
public SimulateRequestFile(DownloadSimulateRequestArgs args)
{
_args = args;
URL = args.URL;
@@ -87,7 +87,7 @@ namespace YooAsset
if (Status == EDownloadRequestStatus.None)
{
Status = EDownloadRequestStatus.Running;
_lastUpdateTime = TimeUtility.RealtimeSinceStartup;
_lastTickTime = TimeUtility.RealtimeSinceStartup;
}
}
@@ -100,8 +100,8 @@ namespace YooAsset
return;
double currentTime = TimeUtility.RealtimeSinceStartup;
double deltaTime = currentTime - _lastUpdateTime;
_lastUpdateTime = currentTime;
double deltaTime = currentTime - _lastTickTime;
_lastTickTime = currentTime;
// 计算本帧下载的字节数
long downloadBytes = (long)(_args.DownloadSpeed * deltaTime);

View File

@@ -11,7 +11,7 @@ namespace YooAsset
/// 下载并加载 Unity AssetBundle 资源包。
/// 支持 Unity 内置缓存机制和 CRC 校验。
/// </remarks>
internal sealed class UnityWebRequestAssetBundleDownloader : UnityWebRequestDownloaderBase, IDownloadAssetBundleRequest
internal sealed class UnityWebRequestAssetBundle : UnityWebRequestBase, IDownloadAssetBundleRequest
{
private readonly DownloadAssetBundleRequestArgs _args;
private DownloadHandlerAssetBundle _downloadHandler;
@@ -26,7 +26,7 @@ namespace YooAsset
/// </summary>
/// <param name="args">AssetBundle 下载参数</param>
/// <param name="webRequestCreator">UnityWebRequest 创建器(可选)</param>
public UnityWebRequestAssetBundleDownloader(DownloadAssetBundleRequestArgs args, UnityWebRequestCreator webRequestCreator)
public UnityWebRequestAssetBundle(DownloadAssetBundleRequestArgs args, UnityWebRequestCreator webRequestCreator)
: base(args.URL, webRequestCreator)
{
_args = args;
@@ -38,10 +38,10 @@ namespace YooAsset
protected override void CreateWebRequest()
{
_downloadHandler = CreateAssetBundleDownloadHandler();
_webRequest = CreateUnityWebRequestGet(URL);
_webRequest = CreateGetRequest(URL);
_webRequest.downloadHandler = _downloadHandler;
_webRequest.disposeDownloadHandlerOnDispose = true;
ApplyRequestOptions(_args.Timeout, _args.WatchdogTime, _args.Headers);
ConfigureRequest(_args.Timeout, _args.WatchdogTimeout, _args.Headers);
}
/// <summary>

View File

@@ -11,16 +11,16 @@ namespace YooAsset
/// 封装 UnityWebRequest 的通用下载逻辑,包括状态管理、进度追踪等。
/// 子类只需实现 CreateWebRequest 方法来创建特定类型的下载请求。
/// </remarks>
internal abstract class UnityWebRequestDownloaderBase : IDownloadRequest
internal abstract class UnityWebRequestBase : IDownloadRequest
{
private readonly UnityWebRequestCreator _webRequestCreator;
protected UnityWebRequest _webRequest;
// 看门狗相关
private int _watchdogTime = 0;
private int _watchdogTimeout = 0;
private bool _watchdogAborted = false;
private long _lastDownloadBytes = -1;
private double _lastGetDataTime;
private double _lastDataReceivedTime;
#region
/// <summary>
@@ -76,7 +76,7 @@ namespace YooAsset
/// </summary>
/// <param name="url">请求地址</param>
/// <param name="webRequestCreator">UnityWebRequest 创建器(可选)</param>
protected UnityWebRequestDownloaderBase(string url, UnityWebRequestCreator webRequestCreator)
protected UnityWebRequestBase(string url, UnityWebRequestCreator webRequestCreator)
{
URL = url;
_webRequestCreator = webRequestCreator;
@@ -125,7 +125,7 @@ namespace YooAsset
DownloadProgress = _webRequest.downloadProgress;
DownloadedBytes = (long)_webRequest.downloadedBytes;
CheckWatchdog();
TickWatchdog();
if (_webRequest.isDone == false)
return;
@@ -149,7 +149,7 @@ namespace YooAsset
}
// 完成后释放
DisposeWebRequest();
CleanupWebRequest();
}
/// <summary>
@@ -170,7 +170,7 @@ namespace YooAsset
/// </summary>
public void Dispose()
{
DisposeWebRequest();
CleanupWebRequest();
}
@@ -199,7 +199,7 @@ namespace YooAsset
/// </summary>
/// <param name="requestUrl">请求地址</param>
/// <returns>UnityWebRequest 实例</returns>
protected UnityWebRequest CreateUnityWebRequestGet(string requestUrl)
protected UnityWebRequest CreateGetRequest(string requestUrl)
{
if (_webRequestCreator != null)
return _webRequestCreator.Invoke(requestUrl, UnityWebRequest.kHttpVerbGET);
@@ -212,7 +212,7 @@ namespace YooAsset
/// </summary>
/// <param name="requestUrl">请求地址</param>
/// <returns>UnityWebRequest 实例</returns>
protected UnityWebRequest CreateUnityWebRequestHead(string requestUrl)
protected UnityWebRequest CreateHeadRequest(string requestUrl)
{
if (_webRequestCreator != null)
return _webRequestCreator.Invoke(requestUrl, UnityWebRequest.kHttpVerbHEAD);
@@ -223,19 +223,19 @@ namespace YooAsset
/// <summary>
/// 应用通用请求参数
/// </summary>
protected void ApplyRequestOptions(int timeout, int watchdogTime, Dictionary<string, string> headers)
protected void ConfigureRequest(int timeout, int watchdogTimeout, Dictionary<string, string> headers)
{
if (_webRequest == null)
throw new YooInternalException("Web request is null.");
// 设置看门狗超时时间
_watchdogTime = watchdogTime;
_watchdogTimeout = watchdogTimeout;
// 设置响应的超时时间
if (timeout > 0)
_webRequest.timeout = timeout;
// 设置响应
// 设置请求
if (headers != null)
{
foreach (var header in headers)
@@ -248,9 +248,9 @@ namespace YooAsset
/// <summary>
/// 检测看门狗
/// </summary>
private void CheckWatchdog()
private void TickWatchdog()
{
if (_watchdogTime == 0)
if (_watchdogTimeout == 0)
return;
if (_watchdogAborted)
return;
@@ -259,12 +259,12 @@ namespace YooAsset
if (DownloadedBytes != _lastDownloadBytes)
{
_lastDownloadBytes = DownloadedBytes;
_lastGetDataTime = realtimeSinceStartup;
_lastDataReceivedTime = realtimeSinceStartup;
}
else
{
double deltaTime = realtimeSinceStartup - _lastGetDataTime;
if (deltaTime > _watchdogTime)
double deltaTime = realtimeSinceStartup - _lastDataReceivedTime;
if (deltaTime > _watchdogTimeout)
{
_watchdogAborted = true;
AbortRequest(); //看门狗终止网络请求
@@ -275,7 +275,7 @@ namespace YooAsset
/// <summary>
/// 释放资源
/// </summary>
private void DisposeWebRequest()
private void CleanupWebRequest()
{
if (_webRequest != null)
{

View File

@@ -9,7 +9,7 @@ namespace YooAsset
/// <remarks>
/// 将下载内容保存到内存中的字节数组。
/// </remarks>
internal sealed class UnityWebRequestBytesDownloader : UnityWebRequestDownloaderBase, IDownloadBytesRequest
internal sealed class UnityWebRequestBytes : UnityWebRequestBase, IDownloadBytesRequest
{
private readonly DownloadDataRequestArgs _args;
@@ -23,7 +23,7 @@ namespace YooAsset
/// </summary>
/// <param name="args">数据下载参数</param>
/// <param name="webRequestCreator">UnityWebRequest 创建器(可选)</param>
public UnityWebRequestBytesDownloader(DownloadDataRequestArgs args, UnityWebRequestCreator webRequestCreator)
public UnityWebRequestBytes(DownloadDataRequestArgs args, UnityWebRequestCreator webRequestCreator)
: base(args.URL, webRequestCreator)
{
_args = args;
@@ -35,10 +35,10 @@ namespace YooAsset
protected override void CreateWebRequest()
{
var handler = new DownloadHandlerBuffer();
_webRequest = CreateUnityWebRequestGet(URL);
_webRequest = CreateGetRequest(URL);
_webRequest.downloadHandler = handler;
_webRequest.disposeDownloadHandlerOnDispose = true;
ApplyRequestOptions(_args.Timeout, _args.WatchdogTime, _args.Headers);
ConfigureRequest(_args.Timeout, _args.WatchdogTimeout, _args.Headers);
}
/// <summary>

View File

@@ -9,7 +9,7 @@ namespace YooAsset
/// <remarks>
/// 将下载内容保存到本地文件,支持断点续传和追加写入。
/// </remarks>
internal sealed class UnityWebRequestFileDownloader : UnityWebRequestDownloaderBase, IDownloadFileRequest
internal sealed class UnityWebRequestFile : UnityWebRequestBase, IDownloadFileRequest
{
private readonly DownloadFileRequestArgs _args;
@@ -26,7 +26,7 @@ namespace YooAsset
/// </summary>
/// <param name="args">文件下载参数</param>
/// <param name="webRequestCreator">UnityWebRequest 创建器(可选)</param>
public UnityWebRequestFileDownloader(DownloadFileRequestArgs args, UnityWebRequestCreator webRequestCreator)
public UnityWebRequestFile(DownloadFileRequestArgs args, UnityWebRequestCreator webRequestCreator)
: base(args.URL, webRequestCreator)
{
_args = args;
@@ -40,17 +40,17 @@ namespace YooAsset
var handler = new DownloadHandlerFile(_args.SavePath, _args.AppendToFile);
handler.removeFileOnAbort = _args.RemoveFileOnAbort;
_webRequest = CreateUnityWebRequestGet(URL);
_webRequest = CreateGetRequest(URL);
_webRequest.downloadHandler = handler;
_webRequest.disposeDownloadHandlerOnDispose = true;
// 断点续传:设置 Range 请求头
if (_args.ResumeFromBytes > 0)
if (_args.ResumeOffset > 0)
{
_webRequest.SetRequestHeader("Range", $"bytes={_args.ResumeFromBytes}-");
_webRequest.SetRequestHeader("Range", $"bytes={_args.ResumeOffset}-");
}
ApplyRequestOptions(_args.Timeout, _args.WatchdogTime, _args.Headers);
ConfigureRequest(_args.Timeout, _args.WatchdogTimeout, _args.Headers);
}
}
}

View File

@@ -11,7 +11,7 @@ namespace YooAsset
/// 仅获取响应头信息,不下载实际内容。
/// 用于检查资源是否存在、获取资源大小、检查缓存有效性等场景。
/// </remarks>
internal sealed class UnityWebRequestHeadDownloader : UnityWebRequestDownloaderBase, IDownloadHeadRequest
internal sealed class UnityWebRequestHead : UnityWebRequestBase, IDownloadHeadRequest
{
// 注意:缓存响应头(因为 WebRequest 释放后无法获取)
private Dictionary<string, string> _cachedResponseHeaders;
@@ -69,7 +69,7 @@ namespace YooAsset
/// </summary>
/// <param name="args">数据下载参数</param>
/// <param name="webRequestCreator">UnityWebRequest 创建器(可选)</param>
public UnityWebRequestHeadDownloader(DownloadDataRequestArgs args, UnityWebRequestCreator webRequestCreator)
public UnityWebRequestHead(DownloadDataRequestArgs args, UnityWebRequestCreator webRequestCreator)
: base(args.URL, webRequestCreator)
{
_args = args;
@@ -98,9 +98,9 @@ namespace YooAsset
/// </summary>
protected override void CreateWebRequest()
{
_webRequest = CreateUnityWebRequestHead(URL);
_webRequest = CreateHeadRequest(URL);
_webRequest.downloadHandler = null; // HEAD 请求不需要 DownloadHandler
ApplyRequestOptions(_args.Timeout, _args.WatchdogTime, _args.Headers);
ConfigureRequest(_args.Timeout, _args.WatchdogTimeout, _args.Headers);
}
/// <summary>

View File

@@ -9,7 +9,7 @@ namespace YooAsset
/// <remarks>
/// 将下载内容解析为 UTF-8 文本字符串。
/// </remarks>
internal sealed class UnityWebRequestTextDownloader : UnityWebRequestDownloaderBase, IDownloadTextRequest
internal sealed class UnityWebRequestText : UnityWebRequestBase, IDownloadTextRequest
{
private readonly DownloadDataRequestArgs _args;
@@ -23,7 +23,7 @@ namespace YooAsset
/// </summary>
/// <param name="args">数据下载参数</param>
/// <param name="webRequestCreator">UnityWebRequest 创建器(可选)</param>
public UnityWebRequestTextDownloader(DownloadDataRequestArgs args, UnityWebRequestCreator webRequestCreator)
public UnityWebRequestText(DownloadDataRequestArgs args, UnityWebRequestCreator webRequestCreator)
: base(args.URL, webRequestCreator)
{
_args = args;
@@ -35,10 +35,10 @@ namespace YooAsset
protected override void CreateWebRequest()
{
var handler = new DownloadHandlerBuffer();
_webRequest = CreateUnityWebRequestGet(URL);
_webRequest = CreateGetRequest(URL);
_webRequest.downloadHandler = handler;
_webRequest.disposeDownloadHandlerOnDispose = true;
ApplyRequestOptions(_args.Timeout, _args.WatchdogTime, _args.Headers);
ConfigureRequest(_args.Timeout, _args.WatchdogTimeout, _args.Headers);
}
/// <summary>

View File

@@ -1,89 +0,0 @@

namespace YooAsset
{
internal class DBFSLoadPackageManifestOperation : FSLoadPackageManifestOperation
{
private enum ESteps
{
None,
RequestBuildinPackageHash,
LoadBuildinPackageManifest,
Done,
}
private readonly DefaultBuildinFileSystem _fileSystem;
private readonly string _packageVersion;
private RequestBuildinPackageHashOperation _requestBuildinPackageHashOp;
private LoadBuildinPackageManifestOperation _loadBuildinPackageManifestOp;
private ESteps _steps = ESteps.None;
public DBFSLoadPackageManifestOperation(DefaultBuildinFileSystem fileSystem, string packageVersion)
{
_fileSystem = fileSystem;
_packageVersion = packageVersion;
}
internal override void InternalStart()
{
_steps = ESteps.RequestBuildinPackageHash;
}
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.RequestBuildinPackageHash)
{
if (_requestBuildinPackageHashOp == null)
{
_requestBuildinPackageHashOp = new RequestBuildinPackageHashOperation(_fileSystem, _packageVersion);
_requestBuildinPackageHashOp.StartOperation();
AddChildOperation(_requestBuildinPackageHashOp);
}
_requestBuildinPackageHashOp.UpdateOperation();
if (_requestBuildinPackageHashOp.IsDone == false)
return;
if (_requestBuildinPackageHashOp.Status == EOperationStatus.Succeed)
{
_steps = ESteps.LoadBuildinPackageManifest;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _requestBuildinPackageHashOp.Error;
}
}
if (_steps == ESteps.LoadBuildinPackageManifest)
{
if (_loadBuildinPackageManifestOp == null)
{
string packageHash = _requestBuildinPackageHashOp.PackageHash;
_loadBuildinPackageManifestOp = new LoadBuildinPackageManifestOperation(_fileSystem, _packageVersion, packageHash);
_loadBuildinPackageManifestOp.StartOperation();
AddChildOperation(_loadBuildinPackageManifestOp);
}
_loadBuildinPackageManifestOp.UpdateOperation();
if (_loadBuildinPackageManifestOp.IsDone == false)
return;
if (_loadBuildinPackageManifestOp.Status == EOperationStatus.Succeed)
{
_steps = ESteps.Done;
Manifest = _loadBuildinPackageManifestOp.Manifest;
Status = EOperationStatus.Succeed;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _loadBuildinPackageManifestOp.Error;
}
}
}
}
}

View File

@@ -1,488 +0,0 @@
# DefaultBuildinFileSystem 内置文件系统
## 模块概述
DefaultBuildinFileSystem 是 YooAsset 的**内置资源文件系统**用于管理打包到应用程序中的资源文件StreamingAssets。该文件系统支持 AssetBundle 和原生文件的加载,并内置解压文件系统以处理 Android/OpenHarmony 平台的特殊需求。
### 核心特性
- **内置资源管理**:管理 StreamingAssets 目录下的资源文件
- **Catalog 目录系统**:使用目录文件快速查询内置资源
- **自动解压机制**Android/OpenHarmony 平台自动解压加密和原生文件
- **清单拷贝功能**:支持将内置清单拷贝到沙盒目录
- **加密资源支持**:通过解密服务接口支持加密资源加载
---
## 设计目标
| 目标 | 说明 |
|------|------|
| **跨平台支持** | 统一处理各平台 StreamingAssets 的访问差异 |
| **高效查询** | 通过 Catalog 文件快速判断资源是否内置 |
| **自动解压** | 自动处理 Android 平台无法直接访问的资源 |
| **灵活配置** | 支持多种参数配置适应不同需求 |
---
## 文件结构
```
DefaultBuildinFileSystem/
├── DefaultBuildinFileSystem.cs # 文件系统主类
├── DefaultBuildinFileSystemDefine.cs # 常量定义
├── DefaultBuildinFileCatalog.cs # 内置资源目录结构
├── CatalogDefine.cs # Catalog 文件格式定义
├── CatalogTools.cs # Catalog 序列化工具
└── Operation/ # 操作类
├── DBFSInitializeOperation.cs # 初始化操作
├── DBFSRequestPackageVersionOperation.cs # 请求版本操作
├── DBFSLoadPackageManifestOperation.cs # 加载清单操作
├── DBFSLoadBundleOperation.cs # 加载资源包操作
└── internal/ # 内部操作类
├── CopyBuildinFileOperation.cs # 拷贝内置文件操作
├── LoadBuildinCatalogFileOperation.cs # 加载 Catalog 文件操作
├── LoadBuildinPackageManifestOperation.cs# 加载清单文件操作
├── RequestBuildinPackageHashOperation.cs # 请求哈希文件操作
└── RequestBuildinPackageVersionOperation.cs # 请求版本文件操作
```
---
## 核心类说明
### DefaultBuildinFileSystem
内置文件系统的主类,实现 `IFileSystem` 接口。
#### 基本属性
| 属性 | 类型 | 说明 |
|------|------|------|
| `PackageName` | `string` | 包裹名称 |
| `FileRoot` | `string` | 文件根目录StreamingAssets 下的包裹目录) |
| `FileCount` | `int` | 已记录的内置文件数量 |
| `DownloadBackend` | `IDownloadBackend` | 下载后台接口 |
#### 自定义参数
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `InstallClearMode` | `EOverwriteInstallClearMode` | `ClearAllManifestFiles` | 覆盖安装时的缓存清理模式 |
| `FileVerifyLevel` | `EFileVerifyLevel` | `Middle` | 文件校验级别 |
| `FileVerifyMaxConcurrency` | `int` | `32` | 文件校验最大并发数 |
| `AppendFileExtension` | `bool` | `false` | 是否追加文件扩展名 |
| `DisableCatalogFile` | `bool` | `false` | 禁用 Catalog 目录文件 |
| `CopyBuildinPackageManifest` | `bool` | `false` | 是否拷贝内置清单到沙盒 |
| `CopyBuildinPackageManifestDestRoot` | `string` | `null` | 清单拷贝目标目录 |
| `UnpackFileSystemRoot` | `string` | `null` | 解压文件系统根目录 |
| `DecryptionServices` | `IDecryptionServices` | `null` | 解密服务接口 |
| `ManifestServices` | `IManifestRestoreServices` | `null` | 清单恢复服务接口 |
| `CopyLocalFileServices` | `ICopyLocalFileServices` | `null` | 本地文件拷贝服务接口 |
#### 核心方法
```csharp
// 生命周期
void OnCreate(string packageName, string packageRoot);
void OnDestroy();
void SetParameter(string name, object value);
// 异步操作
FSInitializeFileSystemOperation InitializeFileSystemAsync();
FSRequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks, int timeout);
FSLoadPackageManifestOperation LoadPackageManifestAsync(string packageVersion, int timeout);
FSLoadBundleOperation LoadBundleFile(PackageBundle bundle);
FSDownloadFileOperation DownloadFileAsync(PackageBundle bundle, DownloadFileOptions options);
FSClearCacheFilesOperation ClearCacheFilesAsync(PackageManifest manifest, ClearCacheFilesOptions options);
// 文件查询
bool Belong(PackageBundle bundle); // 检查是否属于内置文件
bool Exists(PackageBundle bundle); // 检查文件是否存在
bool NeedDownload(PackageBundle bundle);// 始终返回 false
bool NeedUnpack(PackageBundle bundle); // 检查是否需要解压
bool NeedImport(PackageBundle bundle); // 始终返回 false
// 文件访问
string GetBundleFilePath(PackageBundle bundle);
byte[] ReadBundleFileData(PackageBundle bundle);
string ReadBundleFileText(PackageBundle bundle);
```
---
## Catalog 目录系统
### DefaultBuildinFileCatalog
内置资源目录结构,记录所有内置资源文件的信息。
```csharp
[Serializable]
internal class DefaultBuildinFileCatalog
{
[Serializable]
public class FileWrapper
{
public string BundleGUID; // 资源包 GUID
public string FileName; // 文件名
}
public string FileVersion; // 文件版本
public string PackageName; // 包裹名称
public string PackageVersion; // 包裹版本
public List<FileWrapper> Wrappers; // 文件列表
}
```
### CatalogDefine
Catalog 文件格式常量定义。
```csharp
internal class CatalogDefine
{
public const int FileMaxSize = 104857600; // 文件极限大小100MB
public const uint FileSign = 0x133C5EE; // 文件头标记
public const string FileVersion = "1.0.0"; // 文件格式版本
}
```
### CatalogTools
Catalog 文件的序列化和反序列化工具。
| 方法 | 说明 |
|------|------|
| `CreateCatalogFile()` | 生成包裹的内置资源目录文件(编辑器) |
| `CreateEmptyCatalogFile()` | 生成空的内置资源目录文件(编辑器) |
| `SerializeToJson()` | 序列化为 JSON 文件 |
| `DeserializeFromJson()` | 从 JSON 文件反序列化 |
| `SerializeToBinary()` | 序列化为二进制文件 |
| `DeserializeFromBinary()` | 从二进制文件反序列化 |
---
## 操作类说明
### DBFSInitializeOperation
初始化操作,执行以下步骤:
```
状态流程:
┌─────────────────────────────────────────────────────────────┐
│ CopyBuildinPackageManifest = true ? │
│ │ │
│ ├── Yes ──► LoadBuildinPackageVersion │
│ │ └── RequestBuildinPackageVersionOp │
│ │ ↓ │
│ │ CopyBuildinPackageHash │
│ │ └── CopyBuildinFileOperation │
│ │ ↓ │
│ │ CopyBuildinPackageManifest │
│ │ └── CopyBuildinFileOperation │
│ │ ↓ │
│ └── No ─────────────────┘ │
│ ↓ │
│ InitUnpackFileSystem │
│ └── DefaultUnpackFileSystem.Init │
│ ↓ │
│ DisableCatalogFile = true ? │
│ ├── Yes ──► Done (Succeed) │
│ └── No ──► LoadCatalogFile │
│ └── LoadBuildinCatalog │
│ ↓ │
│ RecordCatalogFile │
│ ↓ │
│ Done (Succeed) │
└─────────────────────────────────────────────────────────────┘
```
### DBFSLoadBundleOperation
加载资源包操作,支持多种资源类型。
#### DBFSLoadAssetBundleOperation
加载 AssetBundle 文件。
```
状态流程:
LoadAssetBundle
├── 加密资源 ──► DecryptionServices.LoadAssetBundle[Async]
└── 普通资源 ──► AssetBundle.LoadFromFile[Async]
CheckResult
├── 成功 ──► AssetBundleResult
└── 失败 ──► Error
```
#### DBFSLoadRawBundleOperation
加载原生文件。
```
状态流程:
LoadBuildinRawBundle
├── Android 平台 ──► Error不支持直接读取
└── 其他平台 ──► RawBundleResult
```
#### DBFSLoadInstantBundleOperation
加载团结引擎Tuanjie专用资源包需要 `TUANJIE_1_7_OR_NEWER` 宏)。
---
## 内部操作类
### LoadBuildinCatalogFileOperation
加载 Catalog 目录文件。
```
状态流程:
TryLoadFileData
├── 文件存在 ──► File.ReadAllBytes
└── 文件不存在 ──► RequestFileData (UnityWebRequest)
LoadCatalog
└── CatalogTools.DeserializeFromBinary
```
### CopyBuildinFileOperation
拷贝内置文件到目标路径。
```
状态流程:
CheckFileExist
├── 目标已存在 ──► Done (Succeed)
└── 目标不存在 ──► TryCopyFile
TryCopyFile
├── 源文件存在 ──► File.Copy
└── 源文件不存在 ──► UnpackFile (UnityWebRequest)
```
---
## 解压机制
### 自动解压条件
在 Android/OpenHarmony 平台上,以下情况需要解压到沙盒:
```csharp
protected virtual bool IsUnpackBundleFile(PackageBundle bundle)
{
#if UNITY_ANDROID || UNITY_OPENHARMONY
if (bundle.Encrypted) // 加密资源
return true;
if (bundle.BundleType == RawBundle) // 原生文件
return true;
return false;
#else
return false;
#endif
}
```
### 解压文件系统
内置文件系统在创建时会自动创建一个 `DefaultUnpackFileSystem` 实例:
```csharp
public virtual void OnCreate(string packageName, string packageRoot)
{
// 创建解压文件系统
var remoteServices = new DefaultUnpackRemoteServices(_packageRoot);
_unpackFileSystem = new DefaultUnpackFileSystem();
_unpackFileSystem.SetParameter(REMOTE_SERVICES, remoteServices);
_unpackFileSystem.SetParameter(FILE_VERIFY_LEVEL, FileVerifyLevel);
// ... 其他参数
_unpackFileSystem.OnCreate(packageName, UnpackFileSystemRoot);
}
```
---
## 平台差异处理
### Android 平台限制
```
┌─────────────────────────────────────────────────────────────┐
│ Android 平台特殊处理 │
├─────────────────────────────────────────────────────────────┤
│ StreamingAssets 文件位于 APK 压缩包内,无法直接访问: │
│ │
│ ✓ AssetBundle.LoadFromFile 支持Unity 内部处理) │
│ ✗ File.ReadAllBytes 不支持 │
│ ✗ File.Exists 不支持 │
│ ✓ UnityWebRequest 支持jar:file:// 协议) │
│ │
│ 解决方案: │
│ 1. 加密资源 → 自动解压到沙盒 │
│ 2. 原生文件 → 自动解压到沙盒 │
│ 3. Catalog → 使用 UnityWebRequest 读取 │
└─────────────────────────────────────────────────────────────┘
```
### WebGL 平台
```csharp
#if UNITY_WEBGL
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"{nameof(DefaultBuildinFileSystem)} is not support WEBGL platform !";
#endif
```
WebGL 平台不支持 DefaultBuildinFileSystem应使用 `DefaultWebServerFileSystem`
---
## 使用示例
### 基础配置
```csharp
// 创建内置文件系统参数
var buildinParams = FileSystemParameters.CreateDefaultBuildinFileSystemParameters();
// 初始化包裹
var initParams = new OfflinePlayModeParameters();
initParams.BuildinFileSystemParameters = buildinParams;
var initOp = package.InitializeAsync(initParams);
```
### 配置解密服务
```csharp
var buildinParams = FileSystemParameters.CreateDefaultBuildinFileSystemParameters();
// 设置解密服务
buildinParams.AddParameter(
FileSystemParametersDefine.DECRYPTION_SERVICES,
new MyDecryptionServices()
);
```
### 配置清单拷贝
```csharp
var buildinParams = FileSystemParameters.CreateDefaultBuildinFileSystemParameters();
// 启用清单拷贝(用于离线模式切换到联机模式)
buildinParams.AddParameter(
FileSystemParametersDefine.COPY_BUILDIN_PACKAGE_MANIFEST,
true
);
// 可选:指定拷贝目标目录
buildinParams.AddParameter(
FileSystemParametersDefine.COPY_BUILDIN_PACKAGE_MANIFEST_DEST_ROOT,
"/custom/path"
);
```
### 禁用 Catalog 文件
```csharp
var buildinParams = FileSystemParameters.CreateDefaultBuildinFileSystemParameters();
// 禁用 Catalog所有资源视为内置
buildinParams.AddParameter(
FileSystemParametersDefine.DISABLE_CATALOG_FILE,
true
);
```
### 配置解压文件系统根目录
```csharp
var buildinParams = FileSystemParameters.CreateDefaultBuildinFileSystemParameters();
// 设置解压文件系统的根目录
buildinParams.AddParameter(
FileSystemParametersDefine.UNPACK_FILE_SYSTEM_ROOT,
"/custom/unpack/path"
);
```
---
## 参数常量
```csharp
// 安装清理
FileSystemParametersDefine.INSTALL_CLEAR_MODE // EOverwriteInstallClearMode
// 文件校验
FileSystemParametersDefine.FILE_VERIFY_LEVEL // EFileVerifyLevel
FileSystemParametersDefine.FILE_VERIFY_MAX_CONCURRENCY // int
// 文件配置
FileSystemParametersDefine.APPEND_FILE_EXTENSION // bool
FileSystemParametersDefine.DISABLE_CATALOG_FILE // bool
// 清单拷贝
FileSystemParametersDefine.COPY_BUILDIN_PACKAGE_MANIFEST // bool
FileSystemParametersDefine.COPY_BUILDIN_PACKAGE_MANIFEST_DEST_ROOT // string
// 解压配置
FileSystemParametersDefine.UNPACK_FILE_SYSTEM_ROOT // string
// 服务接口
FileSystemParametersDefine.DECRYPTION_SERVICES // IDecryptionServices
FileSystemParametersDefine.MANIFEST_SERVICES // IManifestRestoreServices
FileSystemParametersDefine.COPY_LOCAL_FILE_SERVICES // ICopyLocalFileServices
```
---
## 类继承关系
```
IFileSystem
└── DefaultBuildinFileSystem
└── (内部持有) DefaultUnpackFileSystem
FSInitializeFileSystemOperation
└── DBFSInitializeOperation
FSRequestPackageVersionOperation
└── DBFSRequestPackageVersionOperation
FSLoadPackageManifestOperation
└── DBFSLoadPackageManifestOperation
FSLoadBundleOperation
├── DBFSLoadAssetBundleOperation
├── DBFSLoadRawBundleOperation
└── DBFSLoadInstantBundleOperation (Tuanjie)
AsyncOperationBase
├── LoadBuildinCatalogFileOperation
├── CopyBuildinFileOperation
├── LoadBuildinPackageManifestOperation
├── RequestBuildinPackageHashOperation
└── RequestBuildinPackageVersionOperation
BundleResult
├── AssetBundleResult ← AssetBundle 资源
└── RawBundleResult ← 原生文件
```
---
## 注意事项
1. **WebGL 不支持**DefaultBuildinFileSystem 不支持 WebGL 平台
2. **Android 限制**Android 平台无法直接读取 StreamingAssets 中的原生文件
3. **Catalog 文件**:构建时需要生成 Catalog 文件,否则需要禁用 Catalog 功能
4. **解压目录**:解压的文件存储在 `UnpackFileSystemRoot` 指定的目录
5. **加密资源**:加密资源在 Android/OpenHarmony 平台会自动解压到沙盒
6. **清单拷贝**:启用清单拷贝可以支持从离线模式平滑切换到联机模式

View File

@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 7ec531875d1515b4496e0e9035e63661
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,791 +0,0 @@
# DefaultCacheFileSystem 缓存文件系统
## 模块概述
DefaultCacheFileSystem 是 YooAsset 的**缓存文件系统**负责管理从远程服务器下载并缓存到本地沙盒的资源文件。该文件系统是联机运行模式HostPlayMode的核心组件提供完整的下载、验证、缓存和加载功能。
### 核心特性
- **智能缓存管理**:基于 GUID 的文件索引,支持增量更新
- **断点续传**:大文件下载支持从断点继续
- **多线程验证**:后台线程验证文件完整性,不阻塞主线程
- **并发下载**:可配置的下载并发数和请求速率
- **覆盖安装检测**App 版本变更时自动清理过期缓存
- **加密支持**:支持加密资源包的解密加载
---
## 设计目标
| 目标 | 说明 |
|------|------|
| **高性能** | 多线程验证、并发下载、路径缓存优化 |
| **高可靠** | CRC/Hash 验证、损坏文件自动清理、加载失败重试 |
| **可扩展** | 支持自定义解密服务、远程服务、本地拷贝服务 |
| **易配置** | 丰富的参数配置,适应不同网络环境 |
---
## 文件结构
```
DefaultCacheFileSystem/
├── DefaultCacheFileSystem.cs # 文件系统主类
├── DefaultCacheFileSystemDefine.cs # 常量定义
├── EOverwriteInstallClearMode.cs # 覆盖安装清理模式枚举
├── ApplicationFootPrint.cs # 应用版本足迹
├── Elements/ # 元素类
│ ├── RecordFileElement.cs # 缓存文件记录元素
│ ├── TempFileElement.cs # 临时文件元素
│ └── VerifyFileElement.cs # 验证文件元素
└── Operation/ # 操作类
├── DCFSInitializeOperation.cs # 初始化操作
├── DCFSRequestPackageVersionOperation.cs # 请求版本操作
├── DCFSLoadPackageManifestOperation.cs # 加载清单操作
├── DCFSLoadBundleOperation.cs # 加载资源包操作
└── internal/ # 内部操作类
├── SearchCacheFilesOperation.cs # 搜索缓存文件
├── VerifyCacheFilesOperation.cs # 验证缓存文件
├── VerifyTempFileOperation.cs # 验证临时文件
├── DownloadPackageHashOperation.cs # 下载哈希文件
├── DownloadPackageManifestOperation.cs # 下载清单文件
├── DownloadPackageBundleOperation.cs # 下载资源包
├── LoadCachePackageHashOperation.cs # 加载缓存哈希
├── LoadCachePackageManifestOperation.cs # 加载缓存清单
├── ClearAllCacheBundleFilesOperation.cs # 清理所有缓存
├── ClearUnusedCacheBundleFilesOperation.cs # 清理未使用缓存
├── ClearCacheBundleFilesByTagsOperaiton.cs # 按标签清理
├── ClearCacheBundleFilesByLocationsOperaiton.cs # 按位置清理
├── ClearAllCacheManifestFilesOperation.cs # 清理所有清单
├── ClearUnusedCacheManifestFilesOperation.cs # 清理未使用清单
└── Scheduler/ # 下载调度器
├── DownloadSchedulerOperation.cs # 下载调度器
├── DownloadAndCacheFileOperation.cs # 下载并缓存基类
├── DownloadAndCacheRemoteFileOperation.cs # 远程文件下载
└── DownloadAndCacheLocalFileOperation.cs # 本地文件拷贝
```
---
## 核心类说明
### DefaultCacheFileSystem
缓存文件系统的主类,实现 `IFileSystem` 接口。
#### 基本属性
| 属性 | 类型 | 说明 |
|------|------|------|
| `PackageName` | `string` | 包裹名称 |
| `FileRoot` | `string` | 缓存根目录 |
| `FileCount` | `int` | 已缓存文件数量 |
| `DownloadBackend` | `IDownloadBackend` | 下载后台接口 |
| `DownloadScheduler` | `DownloadSchedulerOperation` | 下载调度器 |
#### 自定义参数
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `RemoteServices` | `IRemoteServices` | - | 远程服务接口(必需) |
| `InstallClearMode` | `EOverwriteInstallClearMode` | `ClearAllManifestFiles` | 覆盖安装缓存清理模式 |
| `FileVerifyLevel` | `EFileVerifyLevel` | `Middle` | 初始化时文件校验级别 |
| `FileVerifyMaxConcurrency` | `int` | `32` | 文件校验最大并发数1-256 |
| `AppendFileExtension` | `bool` | `false` | 数据文件追加文件扩展名 |
| `DisableOnDemandDownload` | `bool` | `false` | 禁用边玩边下机制 |
| `DownloadMaxConcurrency` | `int` | `10` | 最大并发下载数1-64 |
| `DownloadMaxRequestPerFrame` | `int` | `5` | 每帧最大请求数1-20 |
| `DownloadWatchDogTime` | `int` | `0` | 下载看门狗超时时间(秒) |
| `ResumeDownloadMinimumSize` | `long` | `long.MaxValue` | 启用断点续传的最小文件大小 |
| `ResumeDownloadResponseCodes` | `List<long>` | `null` | 断点续传关注的HTTP错误码 |
| `DecryptionServices` | `IDecryptionServices` | `null` | 解密服务接口 |
| `ManifestServices` | `IManifestRestoreServices` | `null` | 清单服务接口 |
| `CopyLocalFileServices` | `ICopyLocalFileServices` | `null` | 本地文件拷贝服务 |
#### 核心方法
```csharp
// 生命周期
void OnCreate(string packageName, string packageRoot);
void OnDestroy();
void SetParameter(string name, object value);
// 异步操作
FSInitializeFileSystemOperation InitializeFileSystemAsync();
FSRequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks, int timeout);
FSLoadPackageManifestOperation LoadPackageManifestAsync(string packageVersion, int timeout);
FSLoadBundleOperation LoadBundleFile(PackageBundle bundle);
FSDownloadFileOperation DownloadFileAsync(PackageBundle bundle, DownloadFileOptions options);
FSClearCacheFilesOperation ClearCacheFilesAsync(PackageManifest manifest, ClearCacheFilesOptions options);
// 文件查询
bool Belong(PackageBundle bundle); // 始终返回 true保底加载
bool Exists(PackageBundle bundle); // 检查文件是否已缓存
bool NeedDownload(PackageBundle bundle); // 检查是否需要下载
bool NeedUnpack(PackageBundle bundle); // 始终返回 false
bool NeedImport(PackageBundle bundle); // 检查是否需要导入
// 文件访问
string GetBundleFilePath(PackageBundle bundle);
byte[] ReadBundleFileData(PackageBundle bundle);
string ReadBundleFileText(PackageBundle bundle);
```
---
## 缓存目录结构
```
{CacheRoot}/{PackageName}/
├── BundleFiles/ # 资源包文件目录
│ ├── {Hash[0:2]}/ # 哈希前两位分组256个目录
│ │ ├── {BundleGUID}/ # 资源包 GUID 目录
│ │ │ ├── __data # 数据文件(或 __data.bundle
│ │ │ └── __info # 信息文件CRC + Size
│ │ └── ...
│ └── ...
├── ManifestFiles/ # 清单文件目录
│ ├── {PackageName}_{Version}.bytes # 清单二进制文件
│ ├── {PackageName}_{Version}.hash # 清单哈希文件
│ └── __app_footprint.txt # 应用版本足迹文件
└── TempFiles/ # 临时文件目录
├── {BundleGUID} # 下载中的临时文件
└── ...
```
### 信息文件格式__info
```
| 字段 | 类型 | 大小 | 说明 |
|------|------|------|------|
| DataFileCRC | uint32 | 4 bytes | 数据文件 CRC |
| DataFileSize | int64 | 8 bytes | 数据文件大小 |
```
---
## 操作类说明
### DCFSInitializeOperation
初始化操作,执行完整的缓存系统初始化流程。
```
状态流程:
CheckAppFootPrint
├── 版本相同 → 继续
└── 版本不同 → 根据 InstallClearMode 清理缓存
SearchCacheFiles
└── SearchCacheFilesOperation
└── 遍历 BundleFiles 目录
└── 收集需要验证的文件
VerifyCacheFiles
└── VerifyCacheFilesOperation多线程
├── 验证成功 → 记录到 _records
└── 验证失败 → 删除损坏文件
CreateDownloadScheduler
└── 创建 DownloadSchedulerOperation
Done → Status = Succeed
```
#### 状态机枚举
```csharp
private enum ESteps
{
None,
CheckAppFootPrint, // 检查应用版本足迹
SearchCacheFiles, // 搜索缓存文件
VerifyCacheFiles, // 验证缓存文件
CreateDownloadScheduler,// 创建下载调度器
Done // 完成
}
```
### DCFSLoadAssetBundleOperation
加载 AssetBundle 操作,支持按需下载和多重容错机制。
```
状态流程:
CheckExist
├── 已缓存 → LoadAssetBundle
└── 未缓存 → 检查 DisableOnDemandDownload
├── 禁用 → Failed
└── 启用 → DownloadFile
DownloadFile
└── DownloadFileAsync()
├── 下载成功 → LoadAssetBundle
└── 下载失败 → Failed
LoadAssetBundle
├── 未加密 → AssetBundle.LoadFromFile[Async]
└── 已加密 → DecryptionServices.LoadAssetBundle[Async]
CheckResult
├── 加载成功 → AssetBundleResult → Succeed
└── 加载失败 → 验证文件完整性
├── 验证通过 → LoadFromMemory 重试
└── 验证失败 → 删除损坏文件 → Failed
```
#### 移动平台容错机制
```csharp
// 注意:在安卓移动平台,华为和三星真机上有极小概率加载资源包失败。
// 说明:大多数情况在首次安装下载资源到沙盒内,游戏过程中切换到后台再回到游戏内有很大概率触发!
string filePath = _fileSystem.GetCacheBundleFileLoadPath(_bundle);
byte[] fileData = FileUtility.ReadAllBytes(filePath);
if (fileData != null && fileData.Length > 0)
{
_assetBundle = AssetBundle.LoadFromMemory(fileData);
// ...
}
```
### DCFSLoadRawBundleOperation
加载原生资源包操作,处理文件格式变更场景。
```csharp
// 注意:缓存的原生文件的格式,可能会在业务端根据需求发生变动!
// 注意:这里需要校验文件格式,如果不一致对本地文件进行修正!
if (File.Exists(filePath) == false)
{
var recordFileElement = _fileSystem.GetRecordFileElement(_bundle);
File.Move(recordFileElement.DataFilePath, filePath);
}
```
---
## 下载调度器
### DownloadSchedulerOperation
管理所有活跃的下载任务,控制并发数量。
#### 核心属性
| 属性 | 类型 | 说明 |
|------|------|------|
| `Paused` | `bool` | 是否已暂停 |
| `ActiveDownloadCount` | `int` | 当前活跃的下载任务数 |
| `PendingDownloadCount` | `int` | 当前等待中的下载任务数 |
#### 工作原理
```
InternalUpdate()
├── 1. 驱动下载后台 _fileSystem.DownloadBackend.Update()
├── 2. 遍历下载器集合
│ ├── 已完成 → 加入移除列表
│ └── RefCount <= 0 → 中止并移除
├── 3. 移除已完成/中止的下载器
└── 4. 启动新下载任务(如未暂停)
├── 计算可启动数量 = min(maxConcurrency - active, maxRequestPerFrame)
└── 启动等待中的下载器
```
#### 引用计数机制
```csharp
// 查询旧的下载器(复用)
if (_downloaders.TryGetValue(bundle.BundleGUID, out var oldDownloader))
{
oldDownloader.Reference(); // 引用计数 +1
return oldDownloader;
}
// 创建新的下载器
DownloadAndCacheFileOperation newDownloader;
// ...
newDownloader.Reference(); // 引用计数 +1
```
### DownloadAndCacheRemoteFileOperation
远程文件下载操作,支持断点续传。
```
状态流程:
CreateRequest
├── 文件大小 >= ResumeDownloadMinimumSize
│ └── CreateResumeRequest断点续传
└── 文件大小 < ResumeDownloadMinimumSize
└── CreateNormalRequest普通下载
CheckRequest
├── 下载成功 → VerifyBundleFile
└── 下载失败 → ClearTempFileWhenError → Failed
VerifyBundleFile
└── VerifyTempFileOperation多线程验证
├── 验证成功 → CacheBundleFile
└── 验证失败 → 删除临时文件 → Failed
CacheBundleFile
└── WriteCacheBundleFile()
├── 成功 → 删除临时文件 → Succeed
└── 失败 → Failed
```
#### 断点续传实现
```csharp
private IDownloadRequest CreateResumeRequest()
{
// 获取下载起始位置
if (File.Exists(_tempFilePath))
{
FileInfo fileInfo = new FileInfo(_tempFilePath);
if (fileInfo.Length >= _bundle.FileSize)
{
File.Delete(_tempFilePath); // 文件已完整,删除重下
}
else
{
_fileOriginLength = fileInfo.Length; // 记录已下载大小
}
}
var args = new DownloadFileRequestArgs(
URL, _tempFilePath, timeout, watchdogTime,
appendToFile: true, // 追加写入
removeFileOnAbort: false, // 中止时保留文件
resumeFromBytes: _fileOriginLength // 断点位置
);
return _fileSystem.DownloadBackend.CreateFileRequest(args);
}
```
---
## 缓存验证系统
### VerifyCacheFilesOperation
多线程缓存文件验证,在初始化时执行。
#### 验证流程
```
InitVerify
├── 获取系统线程池信息
└── 计算实际并发数 = min(threads, FileVerifyMaxConcurrency)
UpdateVerify循环
├── 检测已完成的验证任务
│ ├── 验证成功 → RecordBundleFile
│ └── 验证失败 → DeleteFiles
└── 启动新的验证任务
└── ThreadPool.QueueUserWorkItem(VerifyInThread)
```
#### 验证级别
```csharp
private EFileVerifyResult VerifyingCacheFile(VerifyFileElement element, EFileVerifyLevel verifyLevel)
{
if (verifyLevel == EFileVerifyLevel.Low)
{
// Low仅检查文件存在
if (File.Exists(element.InfoFilePath) == false)
return EFileVerifyResult.InfoFileNotExisted;
if (File.Exists(element.DataFilePath) == false)
return EFileVerifyResult.DataFileNotExisted;
return EFileVerifyResult.Succeed;
}
else
{
// Middle/High检查文件存在 + CRC/Size 验证
_fileSystem.ReadBundleInfoFile(element.InfoFilePath, out element.DataFileCRC, out element.DataFileSize);
return FileVerifyHelper.FileVerify(element.DataFilePath, element.DataFileSize, element.DataFileCRC, verifyLevel);
}
}
```
### VerifyTempFileOperation
下载文件验证,在线程池中执行。
```csharp
private void VerifyInThread(object obj)
{
TempFileElement element = (TempFileElement)obj;
int result = (int)FileVerifyHelper.FileVerify(
element.TempFilePath,
element.TempFileSize,
element.TempFileCRC,
EFileVerifyLevel.High // 始终使用高级验证
);
element.Result = result; // 线程安全的结果设置
}
```
---
## 覆盖安装检测
### ApplicationFootPrint
应用版本足迹,用于检测 App 覆盖安装。
```csharp
public static bool IsDirty(DefaultCacheFileSystem fileSystem)
{
string filePath = fileSystem.GetSandboxAppFootPrintFilePath();
if (File.Exists(filePath))
{
string footPrint = FileUtility.ReadAllText(filePath);
return IsValidVersion(footPrint) == false; // 版本不同
}
return true; // 文件不存在
}
```
### EOverwriteInstallClearMode
覆盖安装时的缓存清理模式。
| 枚举值 | 说明 |
|--------|------|
| `NeverClear` | 不清理任何缓存 |
| `ClearAllManifestFiles` | 清理所有清单文件(默认) |
| `ClearAllBundleAndManifestFiles` | 清理所有资源包和清单文件 |
---
## 缓存清理操作
### 清理模式对照表
| 清理模式 | 操作类 | 说明 |
|----------|--------|------|
| `ClearAllBundleFiles` | `ClearAllCacheBundleFilesOperation` | 清理所有缓存资源包 |
| `ClearUnusedBundleFiles` | `ClearUnusedCacheBundleFilesOperation` | 清理不在清单中的资源包 |
| `ClearBundleFilesByTags` | `ClearCacheBundleFilesByTagsOperaiton` | 按标签清理 |
| `ClearBundleFilesByLocations` | `ClearCacheBundleFilesByLocationsOperaiton` | 按资源路径清理 |
| `ClearAllManifestFiles` | `ClearAllCacheManifestFilesOperation` | 清理所有清单文件 |
| `ClearUnusedManifestFiles` | `ClearUnusedCacheManifestFilesOperation` | 清理未使用的清单文件 |
### 时间切片清理
```csharp
for (int i = _allBundleGUIDs.Count - 1; i >= 0; i--)
{
string bundleGUID = _allBundleGUIDs[i];
_fileSystem.DeleteCacheBundleFile(bundleGUID);
_allBundleGUIDs.RemoveAt(i);
// 检查操作系统是否繁忙,避免阻塞主线程
if (OperationSystem.IsBusy)
break;
}
```
---
## 元素类说明
### RecordFileElement
缓存文件记录元素,存储已验证的缓存文件信息。
```csharp
internal class RecordFileElement
{
public readonly string InfoFilePath; // 信息文件路径
public readonly string DataFilePath; // 数据文件路径
public readonly uint DataFileCRC; // 数据文件 CRC
public readonly long DataFileSize; // 数据文件大小
public bool DeleteFolder(); // 删除整个文件夹
}
```
### TempFileElement
临时文件元素,用于下载文件验证。
```csharp
internal class TempFileElement
{
public readonly string TempFilePath; // 临时文件路径
public readonly uint TempFileCRC; // 预期 CRC
public readonly long TempFileSize; // 预期文件大小
private int _result = 0;
public int Result // 线程安全的验证结果
{
get => Interlocked.CompareExchange(ref _result, 0, 0);
set => Interlocked.Exchange(ref _result, value);
}
}
```
### VerifyFileElement
验证文件元素,用于缓存文件批量验证。
```csharp
internal class VerifyFileElement
{
public readonly string PackageName; // 包裹名称
public readonly string BundleGUID; // 资源包 GUID
public readonly string FileRootPath; // 文件根目录
public readonly string DataFilePath; // 数据文件路径
public readonly string InfoFilePath; // 信息文件路径
public uint DataFileCRC; // 数据文件 CRC
public long DataFileSize; // 数据文件大小
private int _result = 0;
public int Result // 线程安全的验证结果
{
get => Interlocked.CompareExchange(ref _result, 0, 0);
set => Interlocked.Exchange(ref _result, value);
}
public void DeleteFiles(); // 删除所有相关文件
}
```
---
## 使用示例
### 基础配置
```csharp
// 创建远程服务接口
class GameRemoteServices : IRemoteServices
{
public string GetRemoteMainURL(string fileName)
{
return $"https://cdn.example.com/bundles/{fileName}";
}
public string GetRemoteFallbackURL(string fileName)
{
return $"https://cdn-backup.example.com/bundles/{fileName}";
}
}
// 创建缓存文件系统参数
var cacheParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters(
remoteServices: new GameRemoteServices()
);
// 初始化包裹
var initParams = new HostPlayModeParameters();
initParams.BuildinFileSystemParameters = buildinParams;
initParams.CacheFileSystemParameters = cacheParams;
var initOp = package.InitializeAsync(initParams);
```
### 配置下载参数
```csharp
var cacheParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters(
remoteServices: new GameRemoteServices()
);
// 设置下载并发数
cacheParams.AddParameter(FileSystemParametersDefine.DOWNLOAD_MAX_CONCURRENCY, 8);
// 设置每帧最大请求数
cacheParams.AddParameter(FileSystemParametersDefine.DOWNLOAD_MAX_REQUEST_PER_FRAME, 3);
// 设置下载看门狗时间(秒)
cacheParams.AddParameter(FileSystemParametersDefine.DOWNLOAD_WATCH_DOG_TIME, 30);
```
### 配置断点续传
```csharp
var cacheParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters(
remoteServices: new GameRemoteServices()
);
// 启用断点续传的最小文件大小1MB
cacheParams.AddParameter(FileSystemParametersDefine.RESUME_DOWNLOAD_MINMUM_SIZE, 1024 * 1024);
// 断点续传关注的HTTP错误码这些错误码时删除临时文件重新下载
var responseCodes = new List<long> { 416 }; // Range Not Satisfiable
cacheParams.AddParameter(FileSystemParametersDefine.RESUME_DOWNLOAD_RESPONSE_CODES, responseCodes);
```
### 配置文件验证
```csharp
var cacheParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters(
remoteServices: new GameRemoteServices()
);
// 设置验证级别
cacheParams.AddParameter(FileSystemParametersDefine.FILE_VERIFY_LEVEL, EFileVerifyLevel.High);
// 设置验证并发数
cacheParams.AddParameter(FileSystemParametersDefine.FILE_VERIFY_MAX_CONCURRENCY, 64);
```
### 配置加密支持
```csharp
// 自定义解密服务
class GameDecryptionServices : IDecryptionServices
{
public DecryptResult LoadAssetBundle(DecryptFileInfo fileInfo)
{
// 实现解密逻辑
byte[] data = DecryptFile(fileInfo.FileLoadPath);
AssetBundle bundle = AssetBundle.LoadFromMemory(data);
return new DecryptResult { Result = bundle };
}
// ...
}
var cacheParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters(
remoteServices: new GameRemoteServices()
);
// 设置解密服务
cacheParams.AddParameter(FileSystemParametersDefine.DECRYPTION_SERVICES, new GameDecryptionServices());
```
### 配置覆盖安装行为
```csharp
var cacheParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters(
remoteServices: new GameRemoteServices()
);
// 覆盖安装时清理所有资源包和清单
cacheParams.AddParameter(
FileSystemParametersDefine.INSTALL_CLEAR_MODE,
EOverwriteInstallClearMode.ClearAllBundleAndManifestFiles
);
```
### 清理缓存
```csharp
// 清理所有缓存
var clearOp = package.ClearCacheFilesAsync(EFileClearMode.ClearAllBundleFiles);
await clearOp.ToTask();
// 清理未使用的缓存
var clearOp = package.ClearCacheFilesAsync(EFileClearMode.ClearUnusedBundleFiles);
await clearOp.ToTask();
// 按标签清理
var clearOp = package.ClearCacheFilesAsync(EFileClearMode.ClearBundleFilesByTags, "dlc1");
await clearOp.ToTask();
```
---
## 参数常量
```csharp
// 远程服务
FileSystemParametersDefine.REMOTE_SERVICES // IRemoteServices: 远程服务接口
// 覆盖安装
FileSystemParametersDefine.INSTALL_CLEAR_MODE // EOverwriteInstallClearMode: 清理模式
// 文件验证
FileSystemParametersDefine.FILE_VERIFY_LEVEL // EFileVerifyLevel: 验证级别
FileSystemParametersDefine.FILE_VERIFY_MAX_CONCURRENCY // int: 验证并发数
// 文件格式
FileSystemParametersDefine.APPEND_FILE_EXTENSION // bool: 追加文件扩展名
// 下载控制
FileSystemParametersDefine.DISABLE_ONDEMAND_DOWNLOAD // bool: 禁用边玩边下
FileSystemParametersDefine.DOWNLOAD_MAX_CONCURRENCY // int: 下载并发数
FileSystemParametersDefine.DOWNLOAD_MAX_REQUEST_PER_FRAME // int: 每帧请求数
FileSystemParametersDefine.DOWNLOAD_WATCH_DOG_TIME // int: 看门狗时间
// 断点续传
FileSystemParametersDefine.RESUME_DOWNLOAD_MINMUM_SIZE // long: 最小文件大小
FileSystemParametersDefine.RESUME_DOWNLOAD_RESPONSE_CODES // List<long>: 关注错误码
// 服务接口
FileSystemParametersDefine.DECRYPTION_SERVICES // IDecryptionServices: 解密服务
FileSystemParametersDefine.MANIFEST_SERVICES // IManifestRestoreServices: 清单服务
FileSystemParametersDefine.COPY_LOCAL_FILE_SERVICES // ICopyLocalFileServices: 本地拷贝服务
```
---
## 类继承关系
```
IFileSystem
└── DefaultCacheFileSystem
FSInitializeFileSystemOperation
└── DCFSInitializeOperation
FSRequestPackageVersionOperation
└── DCFSRequestPackageVersionOperation
FSLoadPackageManifestOperation
└── DCFSLoadPackageManifestOperation
FSLoadBundleOperation
├── DCFSLoadAssetBundleOperation
└── DCFSLoadRawBundleOperation
FSDownloadFileOperation
└── DownloadPackageBundleOperation
FSClearCacheFilesOperation
├── ClearAllCacheBundleFilesOperation
├── ClearUnusedCacheBundleFilesOperation
├── ClearCacheBundleFilesByTagsOperaiton
├── ClearCacheBundleFilesByLocationsOperaiton
├── ClearAllCacheManifestFilesOperation
└── ClearUnusedCacheManifestFilesOperation
AsyncOperationBase
├── DownloadSchedulerOperation
├── DownloadAndCacheFileOperation (abstract)
│ ├── DownloadAndCacheRemoteFileOperation
│ └── DownloadAndCacheLocalFileOperation
├── SearchCacheFilesOperation
├── VerifyCacheFilesOperation
├── VerifyTempFileOperation
├── DownloadPackageHashOperation
├── DownloadPackageManifestOperation
├── LoadCachePackageHashOperation
├── LoadCachePackageManifestOperation
└── RequestRemotePackageVersionOperation
BundleResult
├── AssetBundleResult ← AssetBundle 资源
└── RawBundleResult ← 原生文件资源
```
---
## 注意事项
1. **远程服务必需**:使用缓存文件系统必须配置 `IRemoteServices` 接口
2. **保底加载**:缓存文件系统的 `Belong()` 始终返回 true作为资源加载的保底方案
3. **线程安全**:文件验证和下载使用后台线程,但核心逻辑仍在主线程执行
4. **并发限制**:合理设置下载和验证的并发数,避免系统过载
5. **移动平台**Android 平台存在极小概率的 AssetBundle 加载失败,系统会自动尝试 LoadFromMemory 作为备选方案
6. **断点续传**:启用断点续传时,需要合理设置 `ResumeDownloadMinimumSize``ResumeDownloadResponseCodes`
7. **覆盖安装**App 版本更新时会自动检测并根据配置清理缓存,避免旧缓存导致问题

View File

@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 8292f707fbf60854e852e1a75824d892
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,7 +0,0 @@

namespace YooAsset
{
internal class DefaultEditorFileSystemDefine
{
}
}

View File

@@ -1,424 +0,0 @@
# DefaultEditorFileSystem 编辑器模拟文件系统
## 模块概述
DefaultEditorFileSystem 是 YooAsset 的**编辑器模拟文件系统**,专为 Unity 编辑器开发环境设计。该文件系统无需构建实际的 AssetBundle 文件,直接使用 Unity 的 AssetDatabase API 加载资源,实现快速迭代开发。
### 核心特性
- **无需构建资源包**:直接使用 AssetDatabase 加载资源
- **模拟下载流程**:支持模拟网络下载行为(用于 UI 调试)
- **模拟异步延迟**:可配置异步加载的模拟帧数
- **WebGL 模式模拟**:支持模拟 WebGL 平台行为
---
## 设计目标
| 目标 | 说明 |
|------|------|
| **快速迭代** | 无需构建 AssetBundle修改资源后立即生效 |
| **行为模拟** | 模拟真实环境的下载和加载行为 |
| **调试友好** | 支持 UI 进度条等功能的调试 |
| **零配置** | 开箱即用,最小化配置需求 |
---
## 文件结构
```
DefaultEditorFileSystem/
├── DefaultEditorFileSystem.cs # 文件系统主类
├── DefaultEditorFileSystemDefine.cs # 常量定义(预留)
└── Operation/ # 操作类
├── DEFSInitializeOperation.cs # 初始化操作
├── DEFSRequestPackageVersionOperation.cs # 请求版本操作
├── DEFSLoadPackageManifestOperation.cs # 加载清单操作
├── DEFSLoadBundleOperation.cs # 加载资源包操作
└── internal/ # 内部操作类
├── DownloadVirutalBundleOperation.cs # 虚拟下载操作
├── LoadEditorPackageVersionOperation.cs # 加载版本文件操作
├── LoadEditorPackageHashOperation.cs # 加载哈希文件操作
└── LoadEditorPackageManifestOperation.cs # 加载清单文件操作
```
---
## 核心类说明
### DefaultEditorFileSystem
编辑器模拟文件系统的主类,实现 `IFileSystem` 接口。
#### 基本属性
| 属性 | 类型 | 说明 |
|------|------|------|
| `PackageName` | `string` | 包裹名称 |
| `FileRoot` | `string` | 文件根目录(清单文件所在目录) |
| `FileCount` | `int` | 文件数量(始终返回 0 |
| `DownloadBackend` | `IDownloadBackend` | 下载后台接口 |
#### 自定义参数
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `VirtualWebGLMode` | `bool` | `false` | 模拟 WebGL 平台模式 |
| `VirtualDownloadMode` | `bool` | `false` | 模拟虚拟下载模式 |
| `VirtualDownloadSpeed` | `int` | `1024` | 模拟下载速度(字节/秒) |
| `AsyncSimulateMinFrame` | `int` | `1` | 异步加载最小模拟帧数 |
| `AsyncSimulateMaxFrame` | `int` | `1` | 异步加载最大模拟帧数 |
#### 核心方法
```csharp
// 生命周期
void OnCreate(string packageName, string packageRoot);
void OnDestroy();
void SetParameter(string name, object value);
// 异步操作
FSInitializeFileSystemOperation InitializeFileSystemAsync();
FSRequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks, int timeout);
FSLoadPackageManifestOperation LoadPackageManifestAsync(string packageVersion, int timeout);
FSLoadBundleOperation LoadBundleFile(PackageBundle bundle);
FSDownloadFileOperation DownloadFileAsync(PackageBundle bundle, DownloadFileOptions options);
FSClearCacheFilesOperation ClearCacheFilesAsync(PackageManifest manifest, ClearCacheFilesOptions options);
// 文件查询
bool Belong(PackageBundle bundle); // 始终返回 true
bool Exists(PackageBundle bundle); // VirtualDownloadMode 时检查记录
bool NeedDownload(PackageBundle bundle);// VirtualDownloadMode 时返回未记录的文件
bool NeedUnpack(PackageBundle bundle); // 始终返回 false
bool NeedImport(PackageBundle bundle); // 始终返回 false
// 文件访问
string GetBundleFilePath(PackageBundle bundle); // 返回资源路径
byte[] ReadBundleFileData(PackageBundle bundle); // 读取文件二进制
string ReadBundleFileText(PackageBundle bundle); // 读取文件文本
```
---
## 操作类说明
### DEFSInitializeOperation
初始化操作,立即完成(无需任何初始化工作)。
```
状态流程InternalStart() → Status = Succeed
```
### DEFSRequestPackageVersionOperation
请求包裹版本操作,从本地版本文件读取版本号。
```
状态流程:
LoadPackageVersion
└── LoadEditorPackageVersionOperation
└── 读取 {PackageName}_Version.txt
├── 成功 → PackageVersion = 文件内容
└── 失败 → Error
```
### DEFSLoadPackageManifestOperation
加载资源清单操作,从本地清单文件加载并解析清单。
```
状态流程:
LoadEditorPackageHash
└── LoadEditorPackageHashOperation
└── 读取 {PackageName}_{Version}.hash
├── 成功 → PackageHash
└── 失败 → Error
LoadEditorPackageManifest
└── LoadEditorPackageManifestOperation
├── 读取 {PackageName}_{Version}.bytes
├── 验证哈希值
└── 反序列化清单
├── 成功 → Manifest
└── 失败 → Error
```
### DEFSLoadBundleOperation
加载资源包操作,支持虚拟下载模式和异步模拟延迟。
```
状态流程:
CheckExist
├── 文件存在 → LoadAssetBundle
└── 文件不存在 → DownloadFile
DownloadFile
└── DownloadVirtualBundleOperation
├── 模拟下载进度
└── 记录下载完成
LoadAssetBundle
└── 等待模拟帧数
CheckResult
└── 创建 VirtualBundleResult → Status = Succeed
```
#### 状态机枚举
```csharp
private enum ESteps
{
None,
CheckExist, // 检查文件是否存在
DownloadFile, // 下载文件(虚拟下载)
AbortDownload, // 中断下载
LoadAssetBundle, // 加载资源包(模拟延迟)
CheckResult, // 检查结果
Done // 完成
}
```
### DownloadVirtualBundleOperation
虚拟下载操作,模拟网络下载行为。
**特性:**
- 使用 `VirtualFileDownloader` 模拟下载进度
- 支持失败重试机制
- 下载完成后记录到 `_records` 字典
```
状态流程:
CheckExists
├── 文件已记录 → Status = Succeed
└── 文件未记录 → CreateRequest
CreateRequest
└── DownloadSimulateRequestArgs
├── URL = BundleName
├── FileSize = Bundle.FileSize
└── DownloadSpeed = VirtualDownloadSpeed
CheckRequest
├── 下载成功 → RecordDownloadFile() → Status = Succeed
└── 下载失败 → TryAgain 或 Status = Failed
```
---
## 内部操作类
### LoadEditorPackageVersionOperation
从本地文件加载包裹版本号。
| 属性 | 说明 |
|------|------|
| `PackageVersion` | 读取到的版本号字符串 |
### LoadEditorPackageHashOperation
从本地文件加载包裹哈希值。
| 属性 | 说明 |
|------|------|
| `PackageHash` | 读取到的哈希值字符串 |
### LoadEditorPackageManifestOperation
加载并反序列化资源清单。
| 属性 | 说明 |
|------|------|
| `Manifest` | 反序列化后的清单对象 |
**处理流程:**
1. 读取清单二进制文件
2. 使用哈希值验证文件完整性
3. 反序列化为 `PackageManifest` 对象
---
## 工作原理
### 资源加载机制
```
用户请求加载资源
DefaultEditorFileSystem.LoadBundleFile()
DEFSLoadBundleOperation
创建 VirtualBundleResult
VirtualBundleResult.LoadAssetAsync()
VirtualBundleLoadAssetOperation
AssetDatabase.LoadAssetAtPath() ← Unity 编辑器 API
返回资源对象
```
### 虚拟下载模式
`VirtualDownloadMode = true` 时:
1. **首次加载**:资源被视为"未下载",需要执行虚拟下载
2. **虚拟下载**:使用 `VirtualFileDownloader` 模拟下载进度
3. **记录完成**:下载完成后将 BundleGUID 记录到 `_records` 字典
4. **后续加载**:检查 `_records` 字典,已记录的资源直接加载
```csharp
// 记录下载完成的文件
protected readonly Dictionary<string, string> _records;
// 检查文件是否存在
public virtual bool Exists(PackageBundle bundle)
{
if (VirtualDownloadMode)
return _records.ContainsKey(bundle.BundleGUID);
else
return true;
}
```
### 异步模拟延迟
通过 `AsyncSimulateMinFrame``AsyncSimulateMaxFrame` 参数模拟异步加载延迟:
```csharp
// 获取随机模拟帧数
public int GetAsyncSimulateFrame()
{
return UnityEngine.Random.Range(AsyncSimulateMinFrame, AsyncSimulateMaxFrame + 1);
}
// 在 DEFSLoadBundleOperation 中等待
if (_steps == ESteps.LoadAssetBundle)
{
if (_asyncSimulateFrame <= 0)
_steps = ESteps.CheckResult;
else
_asyncSimulateFrame--;
}
```
---
## 使用示例
### 基础配置
```csharp
// 创建编辑器文件系统参数
var editorParams = FileSystemParameters.CreateDefaultEditorFileSystemParameters(
packageRoot: "Assets/GameRes/Bundles/DefaultPackage"
);
// 初始化包裹
var initParams = new EditorSimulateModeParameters();
initParams.EditorFileSystemParameters = editorParams;
var initOp = package.InitializeAsync(initParams);
```
### 启用虚拟下载模式
```csharp
var editorParams = FileSystemParameters.CreateDefaultEditorFileSystemParameters(
packageRoot: "Assets/GameRes/Bundles/DefaultPackage"
);
// 启用虚拟下载模式
editorParams.AddParameter(FileSystemParametersDefine.VIRTUAL_DOWNLOAD_MODE, true);
editorParams.AddParameter(FileSystemParametersDefine.VIRTUAL_DOWNLOAD_SPEED, 1024 * 100); // 100KB/s
```
### 配置异步模拟延迟
```csharp
var editorParams = FileSystemParameters.CreateDefaultEditorFileSystemParameters(
packageRoot: "Assets/GameRes/Bundles/DefaultPackage"
);
// 设置异步加载延迟 1-3 帧
editorParams.AddParameter(FileSystemParametersDefine.ASYNC_SIMULATE_MIN_FRAME, 1);
editorParams.AddParameter(FileSystemParametersDefine.ASYNC_SIMULATE_MAX_FRAME, 3);
```
### 模拟 WebGL 模式
```csharp
var editorParams = FileSystemParameters.CreateDefaultEditorFileSystemParameters(
packageRoot: "Assets/GameRes/Bundles/DefaultPackage"
);
// 启用 WebGL 模拟模式
editorParams.AddParameter(FileSystemParametersDefine.VIRTUAL_WEBGL_MODE, true);
```
---
## 参数常量
```csharp
// 模拟模式参数
FileSystemParametersDefine.VIRTUAL_WEBGL_MODE // bool: 模拟 WebGL 模式
FileSystemParametersDefine.VIRTUAL_DOWNLOAD_MODE // bool: 模拟下载模式
FileSystemParametersDefine.VIRTUAL_DOWNLOAD_SPEED // int: 模拟下载速度(字节/秒)
FileSystemParametersDefine.ASYNC_SIMULATE_MIN_FRAME // int: 异步模拟最小帧数
FileSystemParametersDefine.ASYNC_SIMULATE_MAX_FRAME // int: 异步模拟最大帧数
```
---
## 类继承关系
```
IFileSystem
└── DefaultEditorFileSystem
FSInitializeFileSystemOperation
└── DEFSInitializeOperation
FSRequestPackageVersionOperation
└── DEFSRequestPackageVersionOperation
FSLoadPackageManifestOperation
└── DEFSLoadPackageManifestOperation
FSLoadBundleOperation
└── DEFSLoadBundleOperation
FSDownloadFileOperation
└── DownloadVirtualBundleOperation
AsyncOperationBase
├── LoadEditorPackageVersionOperation
├── LoadEditorPackageHashOperation
└── LoadEditorPackageManifestOperation
BundleResult
└── VirtualBundleResult ← 编辑器模式专用
```
---
## 注意事项
1. **仅限编辑器**:此文件系统仅在 Unity 编辑器中有效
2. **需要构建清单**:虽然不需要构建 AssetBundle但需要构建资源清单文件
3. **VirtualBundle 类型**:只支持 `EBuildBundleType.VirtualBundle` 类型的资源包
4. **WebGL 模式限制**`VirtualWebGLMode` 下不支持同步加载(`WaitForAsyncComplete`
5. **性能差异**:编辑器模式下的加载性能与真机不同,仅供开发调试使用

View File

@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 10ca037f4f07977458b8e94e4ea0f32c
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,385 +0,0 @@
# DefaultUnpackFileSystem 解压文件系统
## 模块概述
DefaultUnpackFileSystem 是 YooAsset 的**解压文件系统**,专为处理 Android 和 OpenHarmony 平台的内置资源解压需求而设计。该文件系统继承自 `DefaultCacheFileSystem`,复用其完整的下载、验证、缓存功能,仅重定义存储目录结构。
### 核心特性
- **继承复用**:完全继承 DefaultCacheFileSystem 的所有功能
- **独立存储**:使用独立的目录存储解压后的资源
- **本地下载**:通过 WWW 路径从 StreamingAssets "下载"资源
- **平台适配**:解决 Android APK 内文件无法直接访问的问题
---
## 设计目标
| 目标 | 说明 |
|------|------|
| **平台兼容** | 解决 Android/OpenHarmony 平台 APK 内文件访问限制 |
| **代码复用** | 继承 DefaultCacheFileSystem避免重复实现 |
| **资源隔离** | 解压资源与下载缓存分开存储,便于管理 |
| **透明集成** | 作为 DefaultBuildinFileSystem 的内部组件工作 |
---
## 文件结构
```
DefaultUnpackFileSystem/
├── DefaultUnpackFileSystem.cs # 解压文件系统主类
├── DefaultUnpackFileSystemDefine.cs # 常量定义
└── DefaultUnpackRemoteServices.cs # 本地资源服务接口
```
---
## 核心类说明
### DefaultUnpackFileSystem
解压文件系统主类,继承自 `DefaultCacheFileSystem`
```csharp
internal class DefaultUnpackFileSystem : DefaultCacheFileSystem
{
public override void OnCreate(string packageName, string rootDirectory)
{
base.OnCreate(packageName, rootDirectory);
// 重写保存根目录和临时目录
_cacheBundleFilesRoot = PathUtility.Combine(_packageRoot, "UnpackBundleFiles");
_cacheManifestFilesRoot = PathUtility.Combine(_packageRoot, "UnpackManifestFiles");
_tempFilesRoot = PathUtility.Combine(_packageRoot, "UnpackTempFiles");
}
}
```
#### 继承的功能
由于继承自 `DefaultCacheFileSystem`DefaultUnpackFileSystem 拥有以下完整功能:
| 功能 | 说明 |
|------|------|
| 文件下载 | 通过 DownloadScheduler 下载资源 |
| 断点续传 | 支持大文件断点续传 |
| 文件验证 | 多线程 CRC/Hash 验证 |
| 缓存管理 | 记录已解压文件,避免重复解压 |
| 加密支持 | 支持 IDecryptionServices 解密 |
| 覆盖安装检测 | App 版本变更时清理解压缓存 |
### DefaultUnpackFileSystemDefine
常量定义类,定义解压目录名称。
```csharp
internal class DefaultUnpackFileSystemDefine
{
/// <summary>
/// 保存的资源文件的文件夹名称
/// </summary>
public const string SaveBundleFilesFolderName = "UnpackBundleFiles";
/// <summary>
/// 保存的清单文件的文件夹名称
/// </summary>
public const string SaveManifestFilesFolderName = "UnpackManifestFiles";
/// <summary>
/// 下载的临时文件的文件夹名称
/// </summary>
public const string TempFilesFolderName = "UnpackTempFiles";
}
```
### DefaultUnpackRemoteServices
本地资源服务接口,将 StreamingAssets 路径转换为 WWW 请求路径。
```csharp
internal class DefaultUnpackRemoteServices : IRemoteServices
{
private readonly string _buildinPackageRoot;
protected readonly Dictionary<string, string> _mapping = new Dictionary<string, string>(10000);
public DefaultUnpackRemoteServices(string buildinPackRoot)
{
_buildinPackageRoot = buildinPackRoot;
}
// 主地址和备用地址相同(本地文件)
string IRemoteServices.GetRemoteMainURL(string fileName)
{
return GetFileLoadURL(fileName);
}
string IRemoteServices.GetRemoteFallbackURL(string fileName)
{
return GetFileLoadURL(fileName);
}
private string GetFileLoadURL(string fileName)
{
if (_mapping.TryGetValue(fileName, out string url) == false)
{
string filePath = PathUtility.Combine(_buildinPackageRoot, fileName);
url = DownloadSystemHelper.ConvertToWWWPath(filePath);
_mapping.Add(fileName, url);
}
return url;
}
}
```
#### 路径转换示例
```
输入文件名: bundle_abc123.bundle
StreamingAssets 路径:
{Application.streamingAssetsPath}/DefaultPackage/bundle_abc123.bundle
WWW 请求路径 (Android):
jar:file:///data/app/com.example.game.apk!/assets/DefaultPackage/bundle_abc123.bundle
```
---
## 目录结构
### 与 DefaultCacheFileSystem 对比
| 目录类型 | DefaultCacheFileSystem | DefaultUnpackFileSystem |
|----------|----------------------|------------------------|
| 资源文件 | `BundleFiles/` | `UnpackBundleFiles/` |
| 清单文件 | `ManifestFiles/` | `UnpackManifestFiles/` |
| 临时文件 | `TempFiles/` | `UnpackTempFiles/` |
### 实际目录结构
```
{SandboxRoot}/{PackageName}/
├── BundleFiles/ # DefaultCacheFileSystem 下载缓存
│ └── ...
├── ManifestFiles/ # DefaultCacheFileSystem 清单缓存
│ └── ...
├── UnpackBundleFiles/ # DefaultUnpackFileSystem 解压缓存
│ ├── {Hash[0:2]}/
│ │ └── {BundleGUID}/
│ │ ├── __data
│ │ └── __info
│ └── ...
├── UnpackManifestFiles/ # DefaultUnpackFileSystem 清单
│ └── ...
└── UnpackTempFiles/ # DefaultUnpackFileSystem 临时文件
└── ...
```
---
## 工作原理
### 解压触发条件
`DefaultBuildinFileSystem` 中,以下情况会触发解压:
```csharp
protected virtual bool IsUnpackBundleFile(PackageBundle bundle)
{
if (Belong(bundle) == false)
return false;
#if UNITY_ANDROID || UNITY_OPENHARMONY
// Android/OpenHarmony 平台
if (bundle.Encrypted)
return true; // 加密资源需要解压
if (bundle.BundleType == (int)EBuildBundleType.RawBundle)
return true; // 原生文件需要解压
return false; // 普通 AssetBundle 不需要解压
#else
return false; // 其他平台不需要解压
#endif
}
```
### 解压流程
```
DefaultBuildinFileSystem
├── NeedUnpack(bundle) 检查
│ └── IsUnpackBundleFile(bundle)
│ ├── Android/OpenHarmony 平台
│ │ ├── 加密资源 → true
│ │ └── RawBundle → true
│ └── 其他平台 → false
├── 需要解压时
│ └── _unpackFileSystem.DownloadFileAsync(bundle, options)
│ └── DefaultUnpackRemoteServices.GetRemoteMainURL()
│ └── 返回 StreamingAssets 的 WWW 路径
│ ↓
│ └── DownloadAndCacheRemoteFileOperation
│ ├── 从 APK 内 "下载" 资源
│ ├── 验证文件完整性
│ └── 保存到 UnpackBundleFiles 目录
└── 加载资源时
└── _unpackFileSystem.LoadBundleFile(bundle)
└── 从 UnpackBundleFiles 加载
```
### 资源加载委托
```csharp
// DefaultBuildinFileSystem.LoadBundleFile()
public virtual FSLoadBundleOperation LoadBundleFile(PackageBundle bundle)
{
// 需要解压的资源,委托给解压文件系统加载
if (IsUnpackBundleFile(bundle))
{
return _unpackFileSystem.LoadBundleFile(bundle);
}
// 普通资源直接从 StreamingAssets 加载
// ...
}
```
---
## 与 DefaultBuildinFileSystem 集成
### 初始化流程
```csharp
// DefaultBuildinFileSystem.OnCreate()
public virtual void OnCreate(string packageName, string packageRoot)
{
// ... 基础初始化 ...
// 创建解压文件系统
var remoteServices = new DefaultUnpackRemoteServices(_packageRoot);
_unpackFileSystem = new DefaultUnpackFileSystem();
// 传递配置参数
_unpackFileSystem.SetParameter(REMOTE_SERVICES, remoteServices);
_unpackFileSystem.SetParameter(INSTALL_CLEAR_MODE, InstallClearMode);
_unpackFileSystem.SetParameter(FILE_VERIFY_LEVEL, FileVerifyLevel);
_unpackFileSystem.SetParameter(FILE_VERIFY_MAX_CONCURRENCY, FileVerifyMaxConcurrency);
_unpackFileSystem.SetParameter(APPEND_FILE_EXTENSION, AppendFileExtension);
_unpackFileSystem.SetParameter(DECRYPTION_SERVICES, DecryptionServices);
_unpackFileSystem.SetParameter(COPY_LOCAL_FILE_SERVICES, CopyLocalFileServices);
// 使用指定的解压根目录
_unpackFileSystem.OnCreate(packageName, UnpackFileSystemRoot);
}
```
### 方法委托关系
| DefaultBuildinFileSystem 方法 | 解压资源时的委托目标 |
|------------------------------|-------------------|
| `LoadBundleFile()` | `_unpackFileSystem.LoadBundleFile()` |
| `GetBundleFilePath()` | `_unpackFileSystem.GetBundleFilePath()` |
| `ReadBundleFileData()` | `_unpackFileSystem.ReadBundleFileData()` |
| `ReadBundleFileText()` | `_unpackFileSystem.ReadBundleFileText()` |
| `DownloadFileAsync()` | `_unpackFileSystem.DownloadFileAsync()` |
| `ClearCacheFilesAsync()` | `_unpackFileSystem.ClearCacheFilesAsync()` |
---
## 使用场景
### 场景 1Android 加密资源
```
问题Android 平台无法直接从 APK 内读取文件进行解密
解决:先解压到沙盒,再从沙盒读取并解密
流程:
1. 检测到加密资源 → IsUnpackBundleFile() = true
2. 首次加载 → NeedUnpack() = true
3. 执行解压 → DownloadFileAsync() 从 APK 复制到沙盒
4. 后续加载 → NeedUnpack() = false直接从沙盒加载
```
### 场景 2Android 原生文件
```
问题RawBundle 需要通过文件路径访问APK 内路径不可直接访问
解决:解压到沙盒,返回沙盒内的文件路径
流程:
1. 检测到 RawBundle → IsUnpackBundleFile() = true
2. 首次访问 → 解压到 UnpackBundleFiles
3. GetBundleFilePath() 返回沙盒路径
4. 业务代码使用标准文件 API 访问
```
### 场景 3普通 AssetBundle
```
情况Android 平台的普通未加密AssetBundle
处理不需要解压Unity 可以直接从 APK 内加载
流程:
1. IsUnpackBundleFile() = false
2. 直接使用 AssetBundle.LoadFromFile() 加载
3. Unity 内部处理 APK 访问
```
---
## 类继承关系
```
IFileSystem
└── DefaultCacheFileSystem
└── DefaultUnpackFileSystem ← 仅重写目录名称
IRemoteServices
└── DefaultUnpackRemoteServices ← 本地 WWW 路径服务
```
---
## 配置参数
DefaultUnpackFileSystem 继承 DefaultCacheFileSystem 的所有参数:
| 参数 | 类型 | 说明 |
|------|------|------|
| `REMOTE_SERVICES` | `IRemoteServices` | 由 DefaultUnpackRemoteServices 提供 |
| `INSTALL_CLEAR_MODE` | `EOverwriteInstallClearMode` | 覆盖安装清理模式 |
| `FILE_VERIFY_LEVEL` | `EFileVerifyLevel` | 文件验证级别 |
| `FILE_VERIFY_MAX_CONCURRENCY` | `int` | 验证并发数 |
| `APPEND_FILE_EXTENSION` | `bool` | 追加文件扩展名 |
| `DECRYPTION_SERVICES` | `IDecryptionServices` | 解密服务 |
| `COPY_LOCAL_FILE_SERVICES` | `ICopyLocalFileServices` | 本地拷贝服务 |
---
## 注意事项
1. **内部组件**DefaultUnpackFileSystem 是 DefaultBuildinFileSystem 的内部组件,不建议单独使用
2. **平台限定**:解压功能仅在 Android 和 OpenHarmony 平台生效
3. **存储占用**:解压会额外占用设备存储空间(相当于资源的两份拷贝)
4. **首次加载**:需要解压的资源首次加载会有额外耗时
5. **自动管理**:解压缓存由系统自动管理,包括覆盖安装时的清理
6. **继承完整性**:继承了 DefaultCacheFileSystem 的所有功能,包括断点续传、多线程验证等
---
## 与其他文件系统对比
| 特性 | DefaultUnpackFileSystem | DefaultCacheFileSystem |
|------|------------------------|----------------------|
| 数据来源 | StreamingAssets (本地) | 远程服务器 |
| 主/备用地址 | 相同(本地路径) | 不同CDN 地址) |
| 存储目录 | UnpackXxxFiles | XxxFiles |
| 使用方式 | 作为内部组件 | 独立使用 |
| 继承关系 | 子类 | 父类 |

View File

@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 402224e40b04b0d458f12f5925d229b7
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,504 +0,0 @@
# DefaultWebRemoteFileSystem Web远程文件系统
## 模块概述
DefaultWebRemoteFileSystem 是 YooAsset 的 **Web 远程文件系统**,专为从远程服务器直接加载资源而设计。该文件系统不缓存文件到本地,每次都从远程 URL 加载资源,适用于 WebGL 平台的跨域资源加载或特殊的网络资源场景。
### 核心特性
- **无本地缓存**:直接从远程 URL 加载,不写入本地文件
- **跨域支持**:通过 `IRemoteServices` 支持跨域资源下载
- **Unity 缓存控制**:可选择禁用 Unity 的 Web 请求缓存
- **加密支持**:支持 `IWebDecryptionServices` 解密 Web 资源
- **失败重试**:内置下载失败自动重试机制
---
## 设计目标
| 目标 | 说明 |
|------|------|
| **轻量级** | 无缓存管理,结构简洁 |
| **即时加载** | 每次从远程获取最新资源 |
| **跨域兼容** | 支持 WebGL 平台的跨域限制处理 |
| **可配置** | 支持 Unity Web 缓存控制和自定义解密 |
---
## 文件结构
```
DefaultWebRemoteFileSystem/
├── DefaultWebRemoteFileSystem.cs # 文件系统主类
└── Operation/ # 操作类
├── DWRFSInitializeOperation.cs # 初始化操作
├── DWRFSRequestPackageVersionOperation.cs # 请求版本操作
├── DWRFSLoadPackageManifestOperation.cs # 加载清单操作
└── DWRFSLoadBundleOperation.cs # 加载资源包操作
```
### 依赖的共享模块
DefaultWebRemoteFileSystem 依赖 `WebGame` 目录下的共享操作类:
```
FileSystem/WebGame/Operation/
├── LoadWebAssetBundleOperation.cs # Web 资源包加载基类
├── LoadWebNormalAssetBundleOperation.cs # 普通资源包加载
├── LoadWebEncryptAssetBundleOperation.cs # 加密资源包加载
├── RequestWebPackageVersionOperation.cs # 请求版本文件
├── RequestWebPackageHashOperation.cs # 请求哈希文件
└── LoadWebPackageManifestOperation.cs # 加载清单文件
```
---
## 核心类说明
### DefaultWebRemoteFileSystem
Web 远程文件系统的主类,实现 `IFileSystem` 接口。
#### 基本属性
| 属性 | 类型 | 说明 |
|------|------|------|
| `PackageName` | `string` | 包裹名称 |
| `FileRoot` | `string` | 始终返回空字符串(无本地存储) |
| `FileCount` | `int` | 始终返回 0无本地文件 |
| `DownloadBackend` | `IDownloadBackend` | 下载后台接口 |
#### 自定义参数
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `DisableUnityWebCache` | `bool` | `false` | 禁用 Unity 的网络缓存 |
| `RemoteServices` | `IRemoteServices` | - | 远程服务接口(必需) |
| `DecryptionServices` | `IWebDecryptionServices` | `null` | Web 解密服务接口 |
| `ManifestServices` | `IManifestRestoreServices` | `null` | 清单服务接口 |
#### 核心方法
```csharp
// 生命周期
void OnCreate(string packageName, string packageRoot);
void OnDestroy();
void SetParameter(string name, object value);
// 异步操作
FSInitializeFileSystemOperation InitializeFileSystemAsync();
FSRequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks, int timeout);
FSLoadPackageManifestOperation LoadPackageManifestAsync(string packageVersion, int timeout);
FSLoadBundleOperation LoadBundleFile(PackageBundle bundle);
FSClearCacheFilesOperation ClearCacheFilesAsync(...); // 直接返回完成
// 不支持的操作
FSDownloadFileOperation DownloadFileAsync(...); // 抛出 NotImplementedException
string GetBundleFilePath(...); // 抛出 NotImplementedException
byte[] ReadBundleFileData(...); // 抛出 NotImplementedException
string ReadBundleFileText(...); // 抛出 NotImplementedException
// 文件查询(固定返回值)
bool Belong(PackageBundle bundle); // 始终返回 true
bool Exists(PackageBundle bundle); // 始终返回 true
bool NeedDownload(PackageBundle bundle);// 始终返回 false
bool NeedUnpack(PackageBundle bundle); // 始终返回 false
bool NeedImport(PackageBundle bundle); // 始终返回 false
```
---
## 操作类说明
### DWRFSInitializeOperation
初始化操作,立即完成(无需任何初始化工作)。
```csharp
internal override void InternalStart()
{
Status = EOperationStatus.Succeed; // 直接成功
}
```
### DWRFSRequestPackageVersionOperation
请求包裹版本操作,从远程服务器获取版本文件。
```
状态流程:
RequestPackageVersion
└── RequestWebPackageVersionOperation
└── 请求 {PackageName}_Version.txt
├── 成功 → PackageVersion = 文件内容 → Succeed
└── 失败 → Failed
```
#### 请求地址轮换
```csharp
// 轮流使用主地址和备用地址
if (_requestCount % 2 == 0)
url = _remoteServices.GetRemoteMainURL(fileName);
else
url = _remoteServices.GetRemoteFallbackURL(fileName);
// 可选:添加时间戳防止缓存
if (_appendTimeTicks)
return $"{url}?{System.DateTime.UtcNow.Ticks}";
```
### DWRFSLoadPackageManifestOperation
加载资源清单操作,从远程下载并解析清单。
```
状态流程:
RequestWebPackageHash
└── RequestWebPackageHashOperation
└── 请求 {PackageName}_{Version}.hash
├── 成功 → PackageHash
└── 失败 → Failed
LoadWebPackageManifest
└── LoadWebPackageManifestOperation
└── 请求 {PackageName}_{Version}.bytes
├── 验证哈希
└── 反序列化清单
├── 成功 → Manifest → Succeed
└── 失败 → Failed
```
### DWRFSLoadAssetBundleOperation
加载资源包操作,从远程 URL 直接加载 AssetBundle。
```
状态流程:
LoadWebAssetBundle
├── 未加密 → LoadWebNormalAssetBundleOperation
│ └── UnityWebRequestAssetBundle.GetAssetBundle()
│ ├── 成功 → AssetBundle → AssetBundleResult
│ └── 失败 → TryAgain 或 Failed
└── 已加密 → LoadWebEncryptAssetBundleOperation
└── DownloadBytesRequest
└── 下载原始字节
└── IWebDecryptionServices.LoadAssetBundle()
├── 成功 → AssetBundle → AssetBundleResult
└── 失败 → TryAgain 或 Failed
```
#### 状态机枚举
```csharp
private enum ESteps
{
None,
LoadWebAssetBundle, // 加载 Web 资源包
Done // 完成
}
```
#### 同步加载限制
```csharp
internal override void InternalWaitForAsyncComplete()
{
if (_steps != ESteps.Done)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = "WebGL platform not support sync load method !";
UnityEngine.Debug.LogError(Error);
}
}
```
---
## 共享 Web 操作类
### LoadWebNormalAssetBundleOperation
普通未加密AssetBundle 的 Web 加载操作。
```
CreateRequest
└── DownloadAssetBundleRequest
├── URL: 主地址或备用地址(轮换)
├── DisableUnityWebCache: 是否禁用缓存
├── FileHash: 用于 Unity 缓存键
└── UnityCRC: CRC 验证
CheckRequest
├── 成功 → Result = AssetBundle
└── 失败 → TryAgain重试或 Failed
```
#### Unity Web 缓存机制
```csharp
// 使用 Unity 的内置缓存
var args = new DownloadAssetBundleRequestArgs(
url,
timeout: 0,
watchdogTime: 0,
disableUnityWebCache: _disableUnityWebCache,
cacheHash: _bundle.FileHash, // 缓存键
unityCRC: _bundle.UnityCRC // CRC 验证
);
```
### LoadWebEncryptAssetBundleOperation
加密 AssetBundle 的 Web 加载操作。
```
CreateRequest
└── 检查 DecryptionServices
├── null → Failed
└── 有效 → DownloadBytesRequest
CheckRequest
├── 下载成功 → LoadEncryptedAssetBundle()
│ └── IWebDecryptionServices.LoadAssetBundle(fileData)
│ ├── 解密成功 → Result = AssetBundle
│ └── 解密失败 → Failed
└── 下载失败 → TryAgain 或 Failed
```
#### 加密资源加载
```csharp
private AssetBundle LoadEncryptedAssetBundle(byte[] fileData)
{
var fileInfo = new WebDecryptFileInfo();
fileInfo.BundleName = _bundle.BundleName;
fileInfo.FileLoadCRC = _bundle.UnityCRC;
fileInfo.FileData = fileData; // 下载的原始字节
var decryptResult = _decryptionServices.LoadAssetBundle(fileInfo);
return decryptResult.Result;
}
```
---
## 失败重试机制
Web 加载操作内置失败重试机制:
```csharp
// 检测下载结果
if (_unityAssetBundleRequestOp.Status == EDownloadRequestStatus.Succeed)
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
Result = _unityAssetBundleRequestOp.Result;
}
else
{
if (_failedTryAgain > 0)
{
_steps = ESteps.TryAgain;
YooLogger.Warning($"Failed download : {url} Try again !");
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _unityAssetBundleRequestOp.Error;
}
}
// 重新尝试下载1秒后
if (_steps == ESteps.TryAgain)
{
_tryAgainTimer += Time.unscaledDeltaTime;
if (_tryAgainTimer > 1f)
{
_tryAgainTimer = 0f;
_failedTryAgain--;
_steps = ESteps.CreateRequest; // 重新创建请求
}
}
```
---
## 使用示例
### 基础配置
```csharp
// 创建远程服务接口
class GameRemoteServices : IRemoteServices
{
public string GetRemoteMainURL(string fileName)
{
return $"https://cdn.example.com/bundles/{fileName}";
}
public string GetRemoteFallbackURL(string fileName)
{
return $"https://cdn-backup.example.com/bundles/{fileName}";
}
}
// 创建 Web 远程文件系统参数
var webRemoteParams = FileSystemParameters.CreateDefaultWebRemoteFileSystemParameters(
remoteServices: new GameRemoteServices()
);
// 初始化包裹WebGL 模式)
var initParams = new WebPlayModeParameters();
initParams.WebServerFileSystemParameters = webServerParams;
initParams.WebRemoteFileSystemParameters = webRemoteParams;
var initOp = package.InitializeAsync(initParams);
```
### 禁用 Unity Web 缓存
```csharp
var webRemoteParams = FileSystemParameters.CreateDefaultWebRemoteFileSystemParameters(
remoteServices: new GameRemoteServices()
);
// 禁用 Unity 的 Web 请求缓存(始终获取最新资源)
webRemoteParams.AddParameter(FileSystemParametersDefine.DISABLE_UNITY_WEB_CACHE, true);
```
### 配置 Web 解密服务
```csharp
// 自定义 Web 解密服务
class GameWebDecryptionServices : IWebDecryptionServices
{
public WebDecryptResult LoadAssetBundle(WebDecryptFileInfo fileInfo)
{
// 解密下载的字节数据
byte[] decryptedData = Decrypt(fileInfo.FileData);
AssetBundle bundle = AssetBundle.LoadFromMemory(decryptedData);
return new WebDecryptResult { Result = bundle };
}
}
var webRemoteParams = FileSystemParameters.CreateDefaultWebRemoteFileSystemParameters(
remoteServices: new GameRemoteServices()
);
// 设置 Web 解密服务
webRemoteParams.AddParameter(
FileSystemParametersDefine.DECRYPTION_SERVICES,
new GameWebDecryptionServices()
);
```
### 跨域资源加载
```csharp
// 跨域远程服务
class CrossDomainRemoteServices : IRemoteServices
{
private readonly string _mainDomain;
private readonly string _fallbackDomain;
public CrossDomainRemoteServices(string mainDomain, string fallbackDomain)
{
_mainDomain = mainDomain;
_fallbackDomain = fallbackDomain;
}
public string GetRemoteMainURL(string fileName)
{
// 主 CDN 域名
return $"https://{_mainDomain}/assets/{fileName}";
}
public string GetRemoteFallbackURL(string fileName)
{
// 备用 CDN 域名
return $"https://{_fallbackDomain}/assets/{fileName}";
}
}
// 使用跨域服务
var remoteServices = new CrossDomainRemoteServices(
mainDomain: "cdn-us.example.com",
fallbackDomain: "cdn-eu.example.com"
);
var webRemoteParams = FileSystemParameters.CreateDefaultWebRemoteFileSystemParameters(
remoteServices: remoteServices
);
```
---
## 参数常量
```csharp
// Unity 缓存控制
FileSystemParametersDefine.DISABLE_UNITY_WEB_CACHE // bool: 禁用 Unity Web 缓存
// 服务接口
FileSystemParametersDefine.REMOTE_SERVICES // IRemoteServices: 远程服务接口
FileSystemParametersDefine.DECRYPTION_SERVICES // IWebDecryptionServices: Web 解密服务
FileSystemParametersDefine.MANIFEST_SERVICES // IManifestRestoreServices: 清单服务
```
---
## 类继承关系
```
IFileSystem
└── DefaultWebRemoteFileSystem
FSInitializeFileSystemOperation
└── DWRFSInitializeOperation
FSRequestPackageVersionOperation
└── DWRFSRequestPackageVersionOperation
FSLoadPackageManifestOperation
└── DWRFSLoadPackageManifestOperation
FSLoadBundleOperation
└── DWRFSLoadAssetBundleOperation
AsyncOperationBase
├── LoadWebAssetBundleOperation (abstract)
│ ├── LoadWebNormalAssetBundleOperation
│ └── LoadWebEncryptAssetBundleOperation
├── RequestWebPackageVersionOperation
├── RequestWebPackageHashOperation
└── LoadWebPackageManifestOperation
BundleResult
└── AssetBundleResult
```
---
## 与其他文件系统对比
| 特性 | DefaultWebRemoteFileSystem | DefaultCacheFileSystem | DefaultWebServerFileSystem |
|------|---------------------------|------------------------|---------------------------|
| 本地缓存 | ❌ 无 | ✅ 有 | ❌ 无 |
| 支持 RawBundle | ❌ | ✅ | ❌ |
| 同步加载 | ❌ | ✅ | ❌ |
| 断点续传 | ❌ | ✅ | ❌ |
| 跨域支持 | ✅ | ✅ | ✅ |
| 适用场景 | WebGL 跨域 | 常规游戏 | WebGL 同域 |
---
## 注意事项
1. **仅支持 AssetBundle**:不支持 RawBundle 类型的资源加载
2. **不支持同步加载**WebGL 平台限制,`WaitForAsyncComplete()` 会直接返回失败
3. **无本地缓存**:每次加载都从远程获取,注意网络流量
4. **部分方法未实现**`DownloadFileAsync``GetBundleFilePath``ReadBundleFileData``ReadBundleFileText` 会抛出异常
5. **远程服务必需**:必须配置 `IRemoteServices` 接口
6. **Unity 缓存**:默认使用 Unity 的 Web 请求缓存,可通过参数禁用
7. **加密资源**:加密资源需要配置 `IWebDecryptionServices`(注意是 Web 专用接口,非 `IDecryptionServices`

View File

@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 9f6744d0bcd43f84ea4279925784afb2
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,525 +0,0 @@
# DefaultWebServerFileSystem Web服务器文件系统
## 模块概述
DefaultWebServerFileSystem 是 YooAsset 的 **Web 服务器文件系统**,专为 WebGL 平台的**同域资源加载**而设计。该文件系统从与 WebGL 构建相同的服务器StreamingAssets 目录)加载资源,通过 Catalog 目录文件管理内置资源清单。
### 核心特性
- **同域加载**:从 WebGL 构建所在服务器加载资源
- **Catalog 管理**:通过目录文件追踪可用资源
- **路径映射**:自动将本地路径转换为 WWW 路径
- **Unity 缓存控制**:可选择禁用 Unity 的 Web 请求缓存
- **加密支持**:支持 `IWebDecryptionServices` 解密 Web 资源
---
## 设计目标
| 目标 | 说明 |
|------|------|
| **WebGL 内置资源** | 加载与 WebGL 构建一起部署的资源 |
| **资源追踪** | 通过 Catalog 文件精确知道哪些资源可用 |
| **无跨域问题** | 从同一服务器加载,避免 CORS 问题 |
| **与 Buildin 对应** | WebGL 版本的 DefaultBuildinFileSystem |
---
## 文件结构
```
DefaultWebServerFileSystem/
├── DefaultWebServerFileSystem.cs # 文件系统主类
└── Operation/ # 操作类
├── DWSFSInitializeOperation.cs # 初始化操作
├── DWSFSRequestPackageVersionOperation.cs # 请求版本操作
├── DWSFSLoadPackageManifestOperation.cs # 加载清单操作
├── DWSFSLoadBundleOperation.cs # 加载资源包操作
└── internal/ # 内部操作类
├── LoadWebServerCatalogFileOperation.cs # 加载目录文件
├── RequestWebServerPackageVersionOperation.cs # 请求版本文件
├── RequestWebServerPackageHashOperation.cs # 请求哈希文件
└── LoadWebServerPackageManifestOperation.cs # 加载清单文件
```
### 依赖的共享模块
DefaultWebServerFileSystem 依赖 `WebGame` 目录下的共享操作类:
```
FileSystem/WebGame/Operation/
├── LoadWebAssetBundleOperation.cs # Web 资源包加载基类
├── LoadWebNormalAssetBundleOperation.cs # 普通资源包加载
└── LoadWebEncryptAssetBundleOperation.cs # 加密资源包加载
```
---
## 核心类说明
### DefaultWebServerFileSystem
Web 服务器文件系统的主类,实现 `IFileSystem` 接口。
#### 内部类
```csharp
public class FileWrapper
{
public string FileName { private set; get; }
public FileWrapper(string fileName)
{
FileName = fileName;
}
}
```
#### 基本属性
| 属性 | 类型 | 说明 |
|------|------|------|
| `PackageName` | `string` | 包裹名称 |
| `FileRoot` | `string` | Web 包裹根目录 |
| `FileCount` | `int` | 始终返回 0 |
| `DownloadBackend` | `IDownloadBackend` | 下载后台接口 |
#### 自定义参数
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `DisableUnityWebCache` | `bool` | `false` | 禁用 Unity 的网络缓存 |
| `DecryptionServices` | `IWebDecryptionServices` | `null` | Web 解密服务接口 |
| `ManifestServices` | `IManifestRestoreServices` | `null` | 清单服务接口 |
#### 核心方法
```csharp
// 生命周期
void OnCreate(string packageName, string packageRoot);
void OnDestroy();
void SetParameter(string name, object value);
// 异步操作
FSInitializeFileSystemOperation InitializeFileSystemAsync();
FSRequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks, int timeout);
FSLoadPackageManifestOperation LoadPackageManifestAsync(string packageVersion, int timeout);
FSLoadBundleOperation LoadBundleFile(PackageBundle bundle);
FSClearCacheFilesOperation ClearCacheFilesAsync(...); // 直接返回完成
// 不支持的操作
FSDownloadFileOperation DownloadFileAsync(...); // 抛出 NotImplementedException
string GetBundleFilePath(...); // 抛出 NotImplementedException
byte[] ReadBundleFileData(...); // 抛出 NotImplementedException
string ReadBundleFileText(...); // 抛出 NotImplementedException
// 文件查询(基于 Catalog
bool Belong(PackageBundle bundle); // 检查是否在 _wrappers 字典中
bool Exists(PackageBundle bundle); // 检查是否在 _wrappers 字典中
bool NeedDownload(PackageBundle bundle);// 始终返回 false
bool NeedUnpack(PackageBundle bundle); // 始终返回 false
bool NeedImport(PackageBundle bundle); // 始终返回 false
```
#### 内部方法
```csharp
// 路径获取
string GetDefaultWebPackageRoot(string packageName); // 默认包裹根目录
string GetWebFileLoadPath(PackageBundle bundle); // 资源文件加载路径
string GetWebPackageVersionFilePath(); // 版本文件路径
string GetWebPackageHashFilePath(string packageVersion); // 哈希文件路径
string GetWebPackageManifestFilePath(string packageVersion); // 清单文件路径
string GetCatalogBinaryFileLoadPath(); // Catalog 文件路径
// Catalog 管理
bool RecordCatalogFile(string bundleGUID, FileWrapper wrapper);
```
---
## Catalog 目录系统
DefaultWebServerFileSystem 使用 Catalog 文件追踪可用的内置资源,这与 DefaultBuildinFileSystem 的机制相同。
### Catalog 文件
| 文件 | 路径 | 说明 |
|------|------|------|
| Catalog 二进制文件 | `{PackageRoot}/{PackageName}_buildin.bytes` | 资源目录信息 |
### Catalog 数据结构
```csharp
// _wrappers 字典BundleGUID → FileWrapper
protected readonly Dictionary<string, FileWrapper> _wrappers;
// FileWrapper 包含文件名信息
public class FileWrapper
{
public string FileName { private set; get; }
}
```
### Belong 与 Exists 判断
```csharp
public virtual bool Belong(PackageBundle bundle)
{
// 检查 Catalog 中是否包含该资源
return _wrappers.ContainsKey(bundle.BundleGUID);
}
public virtual bool Exists(PackageBundle bundle)
{
// 同样基于 Catalog 判断
return _wrappers.ContainsKey(bundle.BundleGUID);
}
```
---
## 操作类说明
### DWSFSInitializeOperation
初始化操作,加载 Catalog 目录文件。
```
状态流程:
LoadCatalogFile
└── LoadWebServerCatalogFileOperation
└── 请求 {PackageName}_buildin.bytes
├── 下载二进制数据
└── 反序列化 Catalog
├── 验证 PackageName
└── 遍历 Wrappers
└── RecordCatalogFile()
├── 成功 → Succeed
└── 失败 → Failed
```
### LoadWebServerCatalogFileOperation
加载 Web 服务器 Catalog 文件的内部操作。
```csharp
// 关键流程
if (_steps == ESteps.LoadCatalog)
{
var catalog = CatalogTools.DeserializeFromBinary(_webDataRequestOp.Result);
// 验证包裹名称
if (catalog.PackageName != _fileSystem.PackageName)
{
Error = $"Catalog file package name {catalog.PackageName} cannot match...";
return;
}
// 记录所有内置资源
foreach (var wrapper in catalog.Wrappers)
{
var fileWrapper = new DefaultWebServerFileSystem.FileWrapper(wrapper.FileName);
_fileSystem.RecordCatalogFile(wrapper.BundleGUID, fileWrapper);
}
}
```
### DWSFSRequestPackageVersionOperation
请求包裹版本操作,从 Web 服务器获取版本文件。
```
状态流程:
RequestPackageVersion
└── RequestWebServerPackageVersionOperation
└── 请求 {FileRoot}/{PackageName}_Version.txt
├── 转换为 WWW 路径
└── 下载文本内容
├── 成功 → PackageVersion
└── 失败 → Failed
```
### DWSFSLoadPackageManifestOperation
加载资源清单操作,从 Web 服务器加载并解析清单。
```
状态流程:
RequestWebPackageHash
└── RequestWebServerPackageHashOperation
└── 请求 {PackageName}_{Version}.hash
├── 成功 → PackageHash
└── 失败 → Failed
LoadWebPackageManifest
└── LoadWebServerPackageManifestOperation
├── 请求 {PackageName}_{Version}.bytes
├── 验证哈希
└── 反序列化清单
├── 成功 → Manifest
└── 失败 → Failed
```
### LoadWebServerPackageManifestOperation
加载清单文件的内部操作,包含哈希验证。
```
状态流程:
RequestFileData
└── DownloadBytesRequest
└── 下载清单二进制数据
VerifyFileData
└── ManifestTools.VerifyManifestData()
├── 验证成功 → LoadManifest
└── 验证失败 → Failed
LoadManifest
└── DeserializeManifestOperation
├── 反序列化成功 → Manifest → Succeed
└── 反序列化失败 → Failed
```
### DWSFSLoadAssetBundleOperation
加载资源包操作,从 Web 服务器加载 AssetBundle。
```
状态流程:
LoadWebAssetBundle
├── 获取文件路径 → 转换为 WWW 路径
├── 未加密 → LoadWebNormalAssetBundleOperation
│ └── UnityWebRequestAssetBundle
│ ├── 成功 → AssetBundleResult
│ └── 失败 → Failed
└── 已加密 → LoadWebEncryptAssetBundleOperation
└── DownloadBytesRequest
└── IWebDecryptionServices.LoadAssetBundle()
├── 成功 → AssetBundleResult
└── 失败 → Failed
```
#### 路径转换
```csharp
// 获取本地文件路径
string fileLoadPath = _fileSystem.GetWebFileLoadPath(_bundle);
// 转换为 WWW 请求路径
string mainURL = DownloadSystemHelper.ConvertToWWWPath(fileLoadPath);
// 主 URL 和备用 URL 相同(同域加载)
DownloadFileOptions options = new DownloadFileOptions(int.MaxValue);
options.SetURL(mainURL, mainURL);
```
#### 同步加载限制
```csharp
internal override void InternalWaitForAsyncComplete()
{
if (_steps != ESteps.Done)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = "WebGL platform not support sync load method !";
UnityEngine.Debug.LogError(Error);
}
}
```
---
## 路径映射机制
DefaultWebServerFileSystem 使用路径缓存优化性能:
```csharp
// 文件路径缓存
protected readonly Dictionary<string, string> _webFilePathMapping = new Dictionary<string, string>(10000);
public string GetWebFileLoadPath(PackageBundle bundle)
{
if (_webFilePathMapping.TryGetValue(bundle.BundleGUID, out string filePath) == false)
{
// 组合路径:{WebPackageRoot}/{FileName}
filePath = PathUtility.Combine(_webPackageRoot, bundle.FileName);
_webFilePathMapping.Add(bundle.BundleGUID, filePath);
}
return filePath;
}
```
### 默认路径
```csharp
protected string GetDefaultWebPackageRoot(string packageName)
{
// 使用默认的内置资源根目录StreamingAssets
string rootDirectory = YooAssetSettingsData.GetYooDefaultBuildinRoot();
return PathUtility.Combine(rootDirectory, packageName);
}
```
---
## 与 DefaultWebRemoteFileSystem 对比
| 特性 | DefaultWebServerFileSystem | DefaultWebRemoteFileSystem |
|------|---------------------------|---------------------------|
| 用途 | WebGL 同域内置资源 | WebGL 跨域远程资源 |
| Catalog 系统 | ✅ 有 | ❌ 无 |
| Belong/Exists | 基于 Catalog 判断 | 始终返回 true |
| 远程服务 | ❌ 不需要 | ✅ 需要 IRemoteServices |
| URL 生成 | 本地路径转 WWW | 远程服务接口生成 |
| 主/备用地址 | 相同(同域) | 不同(可配置) |
---
## 使用示例
### 基础配置
```csharp
// 创建 Web 服务器文件系统参数
var webServerParams = FileSystemParameters.CreateDefaultWebServerFileSystemParameters();
// 初始化包裹WebGL 模式)
var initParams = new WebPlayModeParameters();
initParams.WebServerFileSystemParameters = webServerParams;
initParams.WebRemoteFileSystemParameters = webRemoteParams; // 可选:跨域资源
var initOp = package.InitializeAsync(initParams);
```
### 自定义包裹根目录
```csharp
// 创建参数时指定自定义路径
var webServerParams = FileSystemParameters.CreateDefaultWebServerFileSystemParameters(
packageRoot: "Assets/StreamingAssets/MyCustomPath/DefaultPackage"
);
```
### 禁用 Unity Web 缓存
```csharp
var webServerParams = FileSystemParameters.CreateDefaultWebServerFileSystemParameters();
// 禁用 Unity 的 Web 请求缓存
webServerParams.AddParameter(FileSystemParametersDefine.DISABLE_UNITY_WEB_CACHE, true);
```
### 配置 Web 解密服务
```csharp
// 自定义 Web 解密服务
class GameWebDecryptionServices : IWebDecryptionServices
{
public WebDecryptResult LoadAssetBundle(WebDecryptFileInfo fileInfo)
{
byte[] decryptedData = Decrypt(fileInfo.FileData);
AssetBundle bundle = AssetBundle.LoadFromMemory(decryptedData);
return new WebDecryptResult { Result = bundle };
}
}
var webServerParams = FileSystemParameters.CreateDefaultWebServerFileSystemParameters();
// 设置 Web 解密服务
webServerParams.AddParameter(
FileSystemParametersDefine.DECRYPTION_SERVICES,
new GameWebDecryptionServices()
);
```
### WebGL 双文件系统配置
```csharp
// WebGL 典型配置:内置 + 远程
var webServerParams = FileSystemParameters.CreateDefaultWebServerFileSystemParameters();
var webRemoteParams = FileSystemParameters.CreateDefaultWebRemoteFileSystemParameters(
remoteServices: new GameRemoteServices()
);
var initParams = new WebPlayModeParameters();
initParams.WebServerFileSystemParameters = webServerParams; // 同域内置资源
initParams.WebRemoteFileSystemParameters = webRemoteParams; // 跨域热更资源
var initOp = package.InitializeAsync(initParams);
```
---
## 参数常量
```csharp
// Unity 缓存控制
FileSystemParametersDefine.DISABLE_UNITY_WEB_CACHE // bool: 禁用 Unity Web 缓存
// 服务接口
FileSystemParametersDefine.DECRYPTION_SERVICES // IWebDecryptionServices: Web 解密服务
FileSystemParametersDefine.MANIFEST_SERVICES // IManifestRestoreServices: 清单服务
```
---
## 类继承关系
```
IFileSystem
└── DefaultWebServerFileSystem
FSInitializeFileSystemOperation
└── DWSFSInitializeOperation
FSRequestPackageVersionOperation
└── DWSFSRequestPackageVersionOperation
FSLoadPackageManifestOperation
└── DWSFSLoadPackageManifestOperation
FSLoadBundleOperation
└── DWSFSLoadAssetBundleOperation
AsyncOperationBase
├── LoadWebServerCatalogFileOperation
├── RequestWebServerPackageVersionOperation
├── RequestWebServerPackageHashOperation
├── LoadWebServerPackageManifestOperation
└── LoadWebAssetBundleOperation (共享)
├── LoadWebNormalAssetBundleOperation
└── LoadWebEncryptAssetBundleOperation
BundleResult
└── AssetBundleResult
```
---
## 与 DefaultBuildinFileSystem 对比
| 特性 | DefaultWebServerFileSystem | DefaultBuildinFileSystem |
|------|---------------------------|-------------------------|
| 平台 | WebGL | 非 WebGL移动端、PC |
| 加载方式 | UnityWebRequest | AssetBundle.LoadFromFile |
| Catalog 系统 | ✅ 相同机制 | ✅ 相同机制 |
| 同步加载 | ❌ 不支持 | ✅ 支持 |
| 解包机制 | ❌ 无 | ✅ 有Android APK |
| 文件访问 | WWW 路径 | 本地文件路径 |
---
## 注意事项
1. **WebGL 专用**:此文件系统专为 WebGL 平台设计,非 WebGL 平台应使用 DefaultBuildinFileSystem
2. **仅支持 AssetBundle**:不支持 RawBundle 类型的资源加载
3. **不支持同步加载**WebGL 平台限制,`WaitForAsyncComplete()` 会直接返回失败
4. **Catalog 必需**:初始化时必须成功加载 Catalog 文件,否则无法确定资源归属
5. **同域加载**:资源文件必须与 WebGL 构建在同一服务器上
6. **部分方法未实现**`DownloadFileAsync``GetBundleFilePath``ReadBundleFileData``ReadBundleFileText` 会抛出异常
7. **加密资源**:加密资源需要配置 `IWebDecryptionServices`Web 专用接口)
8. **路径缓存**:内部使用字典缓存路径映射,提升重复访问性能

View File

@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: ac4e3eb80a469df408bb2af7d57b3f8c
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -73,7 +73,7 @@ namespace YooAsset
/// </summary>
public static FileSystemParameters CreateDefaultEditorFileSystemParameters(string packageRoot)
{
string fileSystemClass = typeof(DefaultEditorFileSystem).FullName;
string fileSystemClass = typeof(EditorFileSystem).FullName;
var fileSystemParams = new FileSystemParameters(fileSystemClass, packageRoot);
return fileSystemParams;
}
@@ -85,7 +85,7 @@ namespace YooAsset
/// <param name="packageRoot">文件系统的根目录</param>
public static FileSystemParameters CreateDefaultBuildinFileSystemParameters(IBundleDecryptionServices decryptionServices = null, string packageRoot = null)
{
string fileSystemClass = typeof(DefaultBuildinFileSystem).FullName;
string fileSystemClass = typeof(BuiltinFileSystem).FullName;
var fileSystemParams = new FileSystemParameters(fileSystemClass, packageRoot);
fileSystemParams.AddParameter(FileSystemParametersDefine.BUNDLE_DECRYPTION_SERVICES, decryptionServices);
return fileSystemParams;
@@ -99,7 +99,7 @@ namespace YooAsset
/// <param name="packageRoot">文件系统的根目录</param>
public static FileSystemParameters CreateDefaultCacheFileSystemParameters(IRemoteServices remoteServices, IBundleDecryptionServices decryptionServices = null, string packageRoot = null)
{
string fileSystemClass = typeof(DefaultCacheFileSystem).FullName;
string fileSystemClass = typeof(CacheFileSystem).FullName;
var fileSystemParams = new FileSystemParameters(fileSystemClass, packageRoot);
fileSystemParams.AddParameter(FileSystemParametersDefine.REMOTE_SERVICES, remoteServices);
fileSystemParams.AddParameter(FileSystemParametersDefine.BUNDLE_DECRYPTION_SERVICES, decryptionServices);
@@ -113,7 +113,7 @@ namespace YooAsset
/// <param name="disableUnityWebCache">禁用Unity的网络缓存</param>
public static FileSystemParameters CreateDefaultWebServerFileSystemParameters(IWebBundleDecryptionServices decryptionServices = null, bool disableUnityWebCache = false)
{
string fileSystemClass = typeof(DefaultWebServerFileSystem).FullName;
string fileSystemClass = typeof(WebServerFileSystem).FullName;
var fileSystemParams = new FileSystemParameters(fileSystemClass, null);
fileSystemParams.AddParameter(FileSystemParametersDefine.BUNDLE_DECRYPTION_SERVICES, decryptionServices);
fileSystemParams.AddParameter(FileSystemParametersDefine.DISABLE_UNITY_WEB_CACHE, disableUnityWebCache);
@@ -128,7 +128,7 @@ namespace YooAsset
/// <param name="disableUnityWebCache">禁用Unity的网络缓存</param>
public static FileSystemParameters CreateDefaultWebRemoteFileSystemParameters(IRemoteServices remoteServices, IWebBundleDecryptionServices decryptionServices = null, bool disableUnityWebCache = false)
{
string fileSystemClass = typeof(DefaultWebRemoteFileSystem).FullName;
string fileSystemClass = typeof(WebRemoteFileSystem).FullName;
var fileSystemParams = new FileSystemParameters(fileSystemClass, null);
fileSystemParams.AddParameter(FileSystemParametersDefine.REMOTE_SERVICES, remoteServices);
fileSystemParams.AddParameter(FileSystemParametersDefine.BUNDLE_DECRYPTION_SERVICES, decryptionServices);

View File

@@ -124,7 +124,7 @@ namespace YooAsset
public const string COPY_BUILDIN_PACKAGE_MANIFEST_DEST_ROOT = "COPY_BUILDIN_PACKAGE_MANIFEST_DEST_ROOT";
/// <summary>
/// 拷贝内置文件接口的实例类 <see cref=ICopyLocalFileServices>
/// 拷贝内置文件接口的实例类 <see cref=ILocalFileCopyServices>
/// </summary>
public const string COPY_LOCAL_FILE_SERVICES = "COPY_LOCAL_FILE_SERVICES";

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: da6402a4b93d31943b26fb99cebc0dfd
guid: e5d12b3682189a047b23ef6b5251a8fb
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -8,7 +8,7 @@ namespace YooAsset
/// 内置资源清单目录
/// </summary>
[Serializable]
internal class DefaultBuildinFileCatalog
internal class BuiltinFileCatalog
{
[Serializable]
public class FileWrapper

View File

@@ -8,7 +8,7 @@ namespace YooAsset
/// <summary>
/// 内置文件系统
/// </summary>
internal class DefaultBuildinFileSystem : IFileSystem
internal class BuiltinFileSystem : IFileSystem
{
public class FileWrapper
{
@@ -21,7 +21,7 @@ namespace YooAsset
}
protected readonly Dictionary<string, FileWrapper> _wrappers = new Dictionary<string, FileWrapper>(10000);
protected readonly Dictionary<string, string> _buildinFilePathMapping = new Dictionary<string, string>(10000);
protected readonly Dictionary<string, string> _builtinFilePathMapping = new Dictionary<string, string>(10000);
protected IFileSystem _unpackFileSystem;
protected string _packageRoot;
@@ -117,36 +117,36 @@ namespace YooAsset
/// <summary>
/// 自定义参数:拷贝内置文件接口的实例类
/// </summary>
public ICopyLocalFileServices CopyLocalFileServices { private set; get; }
public ILocalFileCopyServices CopyLocalFileServices { private set; get; }
#endregion
public DefaultBuildinFileSystem()
public BuiltinFileSystem()
{
}
public virtual FSInitializeFileSystemOperation InitializeFileSystemAsync()
public virtual FSInitializeOperation InitializeAsync()
{
var operation = new DBFSInitializeOperation(this);
return operation;
}
public virtual FSRequestPackageVersionOperation RequestPackageVersionAsync(RequestPackageVersionOptions options)
public virtual FSRequestVersionOperation RequestVersionAsync(RequestVersionOptions options)
{
var operation = new DBFSRequestPackageVersionOperation(this);
var operation = new BFSRequestVersionOperation(this);
return operation;
}
public virtual FSLoadPackageManifestOperation LoadPackageManifestAsync(LoadPackageManifestOptions options)
public virtual FSLoadManifestOperation LoadManifestAsync(LoadManifestOptions options)
{
var operation = new DBFSLoadPackageManifestOperation(this, options.PackageVersion);
var operation = new BFSLoadManifestOperation(this, options.PackageVersion);
return operation;
}
public virtual FSClearCacheFilesOperation ClearCacheFilesAsync(ClearCacheFilesOptions options)
public virtual FSClearCacheOperation ClearCacheAsync(ClearCacheOptions options)
{
return _unpackFileSystem.ClearCacheFilesAsync(options);
return _unpackFileSystem.ClearCacheAsync(options);
}
public virtual FSDownloadFileOperation DownloadFileAsync(DownloadFileOptions options)
{
// 注意:业务层的解压器会依赖该方法
options.ImportFilePath = GetBuildinFileLoadPath(options.Bundle);
options.ImportFilePath = GetBuiltinFileLoadPath(options.Bundle);
return _unpackFileSystem.DownloadFileAsync(options);
}
public virtual FSLoadBundleOperation LoadBundleAsync(LoadBundleOptions options)
@@ -159,24 +159,24 @@ namespace YooAsset
if (bundle.BundleType == (int)EBundleType.AssetBundle)
{
var operation = new DBFSLoadAssetBundleOperation(this, bundle);
var operation = new BFSLoadAssetBundleOperation(this, bundle);
return operation;
}
else if (bundle.BundleType == (int)EBundleType.RawBundle)
{
var operation = new DBFSLoadRawBundleOperation(this, bundle);
var operation = new BFSLoadRawBundleOperation(this, bundle);
return operation;
}
#if TUANJIE_1_7_OR_NEWER
else if (bundle.BundleType == (int)EBuildBundleType.InstantBundle)
else if (bundle.BundleType == (int)EBundleType.InstantBundle)
{
var operation = new DBFSLoadInstantBundleOperation(this, bundle);
var operation = new BFSLoadInstantBundleOperation(this, bundle);
return operation;
}
#endif
else
{
string error = $"{nameof(DefaultBuildinFileSystem)} not support load bundle type : {bundle.BundleType}";
string error = $"{nameof(BuiltinFileSystem)} not support load bundle type : {bundle.BundleType}";
var operation = new FSLoadBundleCompleteOperation(error);
return operation;
}
@@ -235,7 +235,7 @@ namespace YooAsset
}
else if (name == FileSystemParametersDefine.COPY_LOCAL_FILE_SERVICES)
{
CopyLocalFileServices = (ICopyLocalFileServices)value;
CopyLocalFileServices = (ILocalFileCopyServices)value;
}
else
{
@@ -247,7 +247,7 @@ namespace YooAsset
PackageName = packageName;
if (string.IsNullOrEmpty(packageRoot))
_packageRoot = GetDefaultBuildinPackageRoot(packageName);
_packageRoot = GetDefaultBuiltinPackageRoot(packageName);
else
_packageRoot = packageRoot;
@@ -256,8 +256,8 @@ namespace YooAsset
DownloadBackend = new UnityWebRequestBackend(WebRequestCreator);
// 创建解压文件系统
var remoteServices = new DefaultUnpackRemoteServices(_packageRoot);
_unpackFileSystem = new DefaultUnpackFileSystem();
var remoteServices = new UnpackRemoteService(_packageRoot);
_unpackFileSystem = new UnpackFileSystem();
_unpackFileSystem.SetParameter(FileSystemParametersDefine.REMOTE_SERVICES, remoteServices);
_unpackFileSystem.SetParameter(FileSystemParametersDefine.DOWNLOAD_BACKEND, DownloadBackend);
_unpackFileSystem.SetParameter(FileSystemParametersDefine.UNITY_WEB_REQUEST_CREATOR, WebRequestCreator);
@@ -321,7 +321,7 @@ namespace YooAsset
if (IsUnpackBundleFile(bundle))
return _unpackFileSystem.GetBundleFilePath(bundle);
return GetBuildinFileLoadPath(bundle);
return GetBuiltinFileLoadPath(bundle);
}
public virtual byte[] ReadBundleFileData(PackageBundle bundle)
{
@@ -420,38 +420,38 @@ namespace YooAsset
}
#region
protected string GetDefaultBuildinPackageRoot(string packageName)
protected string GetDefaultBuiltinPackageRoot(string packageName)
{
string rootDirectory = YooAssetSettingsData.GetYooDefaultBuildinRoot();
return PathUtility.Combine(rootDirectory, packageName);
}
public string GetBuildinFileLoadPath(PackageBundle bundle)
public string GetBuiltinFileLoadPath(PackageBundle bundle)
{
if (_buildinFilePathMapping.TryGetValue(bundle.BundleGUID, out string filePath) == false)
if (_builtinFilePathMapping.TryGetValue(bundle.BundleGUID, out string filePath) == false)
{
filePath = PathUtility.Combine(_packageRoot, bundle.FileName);
_buildinFilePathMapping.Add(bundle.BundleGUID, filePath);
_builtinFilePathMapping.Add(bundle.BundleGUID, filePath);
}
return filePath;
}
public string GetBuildinPackageVersionFilePath()
public string GetBuiltinPackageVersionFilePath()
{
string fileName = YooAssetSettingsData.GetPackageVersionFileName(PackageName);
return PathUtility.Combine(_packageRoot, fileName);
}
public string GetBuildinPackageHashFilePath(string packageVersion)
public string GetBuiltinPackageHashFilePath(string packageVersion)
{
string fileName = YooAssetSettingsData.GetPackageHashFileName(PackageName, packageVersion);
return PathUtility.Combine(_packageRoot, fileName);
}
public string GetBuildinPackageManifestFilePath(string packageVersion)
public string GetBuiltinPackageManifestFilePath(string packageVersion)
{
string fileName = YooAssetSettingsData.GetManifestBinaryFileName(PackageName, packageVersion);
return PathUtility.Combine(_packageRoot, fileName);
}
public string GetCatalogBinaryFileLoadPath()
{
return PathUtility.Combine(_packageRoot, DefaultBuildinFileSystemDefine.BuildinCatalogBinaryFileName);
return PathUtility.Combine(_packageRoot, BuiltinFileSystemConstants.BuiltinCatalogBinaryFileName);
}
/// <summary>
@@ -461,7 +461,7 @@ namespace YooAsset
{
if (_wrappers.ContainsKey(bundleGUID))
{
YooLogger.Error($"{nameof(DefaultBuildinFileSystem)} has element : {bundleGUID}");
YooLogger.Error($"{nameof(BuiltinFileSystem)} has element : {bundleGUID}");
return false;
}
@@ -472,18 +472,18 @@ namespace YooAsset
/// <summary>
/// 初始化解压文件系统
/// </summary>
public FSInitializeFileSystemOperation InitializeUpackFileSystem()
public FSInitializeOperation InitializeUnpackFileSystem()
{
return _unpackFileSystem.InitializeFileSystemAsync();
return _unpackFileSystem.InitializeAsync();
}
/// <summary>
/// 加载加密的资源文件
/// </summary>
public DecryptSyncResult LoadEncryptedBundleSync(PackageBundle bundle)
public BundleDecryptionSyncResult LoadEncryptedBundleSync(PackageBundle bundle)
{
string filePath = GetBuildinFileLoadPath(bundle);
var bundleInfo = new DecryptBundleInfo()
string filePath = GetBuiltinFileLoadPath(bundle);
var bundleInfo = new BundleDecryptionContext()
{
BundleName = bundle.BundleName,
FileLoadCRC = bundle.UnityCRC,
@@ -495,10 +495,10 @@ namespace YooAsset
/// <summary>
/// 加载加密的资源文件
/// </summary>
public DecryptAsyncResult LoadEncryptedBundleAsync(PackageBundle bundle)
public BundleDecryptionAsyncResult LoadEncryptedBundleAsync(PackageBundle bundle)
{
string filePath = GetBuildinFileLoadPath(bundle);
var bundleInfo = new DecryptBundleInfo()
string filePath = GetBuiltinFileLoadPath(bundle);
var bundleInfo = new BundleDecryptionContext()
{
BundleName = bundle.BundleName,
FileLoadCRC = bundle.UnityCRC,

View File

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

namespace YooAsset
{
internal class DefaultBuildinFileSystemDefine
internal class BuiltinFileSystemConstants
{
/// <summary>
/// 内置清单JSON文件名称
/// </summary>
public const string BuildinCatalogJsonFileName = "BuildinCatalog.json";
public const string BuiltinCatalogJsonFileName = "BuiltinCatalog.json";
/// <summary>
/// 内置清单二进制文件名称
/// </summary>
public const string BuildinCatalogBinaryFileName = "BuildinCatalog.bytes";
public const string BuiltinCatalogBinaryFileName = "BuiltinCatalog.bytes";
}
}

View File

@@ -1,7 +1,7 @@

namespace YooAsset
{
internal class CatalogFileDefine
internal class CatalogFileConstants
{
/// <summary>
/// 文件极限大小100MB

View File

@@ -5,7 +5,7 @@ using UnityEngine;
namespace YooAsset
{
internal static class CatalogFileHelper
internal static class CatalogFileTools
{
#if UNITY_EDITOR
/// <summary>
@@ -53,8 +53,8 @@ namespace YooAsset
}
// 创建内置清单实例
var buildinFileCatalog = new DefaultBuildinFileCatalog();
buildinFileCatalog.FileVersion = CatalogFileDefine.FileVersion;
var buildinFileCatalog = new BuiltinFileCatalog();
buildinFileCatalog.FileVersion = CatalogFileConstants.FileVersion;
buildinFileCatalog.PackageName = packageName;
buildinFileCatalog.PackageVersion = packageVersion;
@@ -63,8 +63,8 @@ namespace YooAsset
{
"link.xml",
"buildlogtep.json",
DefaultBuildinFileSystemDefine.BuildinCatalogJsonFileName,
DefaultBuildinFileSystemDefine.BuildinCatalogBinaryFileName
BuiltinFileSystemConstants.BuiltinCatalogJsonFileName,
BuiltinFileSystemConstants.BuiltinCatalogBinaryFileName
};
string packageVersionFileName = YooAssetSettingsData.GetPackageVersionFileName(packageName);
string packageHashFileName = YooAssetSettingsData.GetPackageHashFileName(packageName, packageVersion);
@@ -91,7 +91,7 @@ namespace YooAsset
string fileName = fileInfo.Name;
if (fileMapping.TryGetValue(fileName, out string bundleGUID))
{
var wrapper = new DefaultBuildinFileCatalog.FileWrapper();
var wrapper = new BuiltinFileCatalog.FileWrapper();
wrapper.BundleGUID = bundleGUID;
wrapper.FileName = fileName;
buildinFileCatalog.Wrappers.Add(wrapper);
@@ -103,13 +103,13 @@ namespace YooAsset
}
// 创建输出文件
string jsonFilePath = $"{packageDirectory}/{DefaultBuildinFileSystemDefine.BuildinCatalogJsonFileName}";
string jsonFilePath = $"{packageDirectory}/{BuiltinFileSystemConstants.BuiltinCatalogJsonFileName}";
if (File.Exists(jsonFilePath))
File.Delete(jsonFilePath);
SerializeToJson(jsonFilePath, buildinFileCatalog);
// 创建输出文件
string binaryFilePath = $"{packageDirectory}/{DefaultBuildinFileSystemDefine.BuildinCatalogBinaryFileName}";
string binaryFilePath = $"{packageDirectory}/{BuiltinFileSystemConstants.BuiltinCatalogBinaryFileName}";
if (File.Exists(binaryFilePath))
File.Delete(binaryFilePath);
SerializeToBinary(binaryFilePath, buildinFileCatalog);
@@ -125,19 +125,19 @@ namespace YooAsset
public static bool CreateEmptyFile(string packageName, string packageVersion, string outputPath)
{
// 创建内置清单实例
var buildinFileCatalog = new DefaultBuildinFileCatalog();
buildinFileCatalog.FileVersion = CatalogFileDefine.FileVersion;
var buildinFileCatalog = new BuiltinFileCatalog();
buildinFileCatalog.FileVersion = CatalogFileConstants.FileVersion;
buildinFileCatalog.PackageName = packageName;
buildinFileCatalog.PackageVersion = packageVersion;
// 创建输出文件
string jsonFilePath = $"{outputPath}/{DefaultBuildinFileSystemDefine.BuildinCatalogJsonFileName}";
string jsonFilePath = $"{outputPath}/{BuiltinFileSystemConstants.BuiltinCatalogJsonFileName}";
if (File.Exists(jsonFilePath))
File.Delete(jsonFilePath);
SerializeToJson(jsonFilePath, buildinFileCatalog);
// 创建输出文件
string binaryFilePath = $"{outputPath}/{DefaultBuildinFileSystemDefine.BuildinCatalogBinaryFileName}";
string binaryFilePath = $"{outputPath}/{BuiltinFileSystemConstants.BuiltinCatalogBinaryFileName}";
if (File.Exists(binaryFilePath))
File.Delete(binaryFilePath);
SerializeToBinary(binaryFilePath, buildinFileCatalog);
@@ -151,7 +151,7 @@ namespace YooAsset
/// <summary>
/// 序列化JSON文件
/// </summary>
public static void SerializeToJson(string savePath, DefaultBuildinFileCatalog catalog)
public static void SerializeToJson(string savePath, BuiltinFileCatalog catalog)
{
string json = JsonUtility.ToJson(catalog, true);
FileUtility.WriteAllText(savePath, json);
@@ -160,26 +160,26 @@ namespace YooAsset
/// <summary>
/// 反序列化JSON文件
/// </summary>
public static DefaultBuildinFileCatalog DeserializeFromJson(string jsonContent)
public static BuiltinFileCatalog DeserializeFromJson(string jsonContent)
{
return JsonUtility.FromJson<DefaultBuildinFileCatalog>(jsonContent);
return JsonUtility.FromJson<BuiltinFileCatalog>(jsonContent);
}
/// <summary>
/// 序列化(二进制文件)
/// </summary>
public static void SerializeToBinary(string savePath, DefaultBuildinFileCatalog catalog)
public static void SerializeToBinary(string savePath, BuiltinFileCatalog catalog)
{
using (FileStream fs = new FileStream(savePath, FileMode.Create))
{
// 创建缓存器
BufferWriter buffer = new BufferWriter(CatalogFileDefine.FileMaxSize);
BufferWriter buffer = new BufferWriter(CatalogFileConstants.FileMaxSize);
// 写入文件标记
buffer.WriteUInt32(CatalogFileDefine.FileSign);
buffer.WriteUInt32(CatalogFileConstants.FileSign);
// 写入文件版本
buffer.WriteUTF8(CatalogFileDefine.FileVersion);
buffer.WriteUTF8(CatalogFileConstants.FileVersion);
// 写入文件头信息
buffer.WriteUTF8(catalog.PackageName);
@@ -203,7 +203,7 @@ namespace YooAsset
/// <summary>
/// 反序列化(二进制文件)
/// </summary>
public static DefaultBuildinFileCatalog DeserializeFromBinary(byte[] binaryData)
public static BuiltinFileCatalog DeserializeFromBinary(byte[] binaryData)
{
if (binaryData == null || binaryData.Length == 0)
throw new Exception("Catalog file data is null or empty.");
@@ -213,15 +213,15 @@ namespace YooAsset
// 读取文件标记
uint fileSign = buffer.ReadUInt32();
if (fileSign != CatalogFileDefine.FileSign)
if (fileSign != CatalogFileConstants.FileSign)
throw new Exception("Invalid catalog file.");
// 读取文件版本
string fileVersion = buffer.ReadUTF8();
if (fileVersion != CatalogFileDefine.FileVersion)
throw new Exception($"The catalog file version are not compatible : {fileVersion} != {CatalogFileDefine.FileVersion}");
if (fileVersion != CatalogFileConstants.FileVersion)
throw new Exception($"The catalog file version are not compatible : {fileVersion} != {CatalogFileConstants.FileVersion}");
DefaultBuildinFileCatalog catalog = new DefaultBuildinFileCatalog();
BuiltinFileCatalog catalog = new BuiltinFileCatalog();
{
// 读取文件头信息
catalog.FileVersion = fileVersion;
@@ -230,10 +230,10 @@ namespace YooAsset
// 读取资源包列表
int fileCount = buffer.ReadInt32();
catalog.Wrappers = new List<DefaultBuildinFileCatalog.FileWrapper>(fileCount);
catalog.Wrappers = new List<BuiltinFileCatalog.FileWrapper>(fileCount);
for (int i = 0; i < fileCount; i++)
{
var fileWrapper = new DefaultBuildinFileCatalog.FileWrapper();
var fileWrapper = new BuiltinFileCatalog.FileWrapper();
fileWrapper.BundleGUID = buffer.ReadUTF8();
fileWrapper.FileName = buffer.ReadUTF8();
catalog.Wrappers.Add(fileWrapper);

View File

@@ -3,7 +3,7 @@ using System.IO;
namespace YooAsset
{
internal class DBFSInitializeOperation : FSInitializeFileSystemOperation
internal class DBFSInitializeOperation : FSInitializeOperation
{
private enum ESteps
{
@@ -16,15 +16,15 @@ namespace YooAsset
Done,
}
private readonly DefaultBuildinFileSystem _fileSystem;
private RequestBuildinPackageVersionOperation _requestBuildinPackageVersionOp;
private CopyBuildinFileOperation _copyBuildinHashFileOp;
private CopyBuildinFileOperation _copyBuildinManifestFileOp;
private FSInitializeFileSystemOperation _initUnpackFIleSystemOp;
private LoadBuildinCatalogFileOperation _loadBuildinCatalogFileOp;
private readonly BuiltinFileSystem _fileSystem;
private RequestBuiltinPackageVersionOperation _requestBuildinPackageVersionOp;
private CopyBuiltinFileOperation _copyBuildinHashFileOp;
private CopyBuiltinFileOperation _copyBuildinManifestFileOp;
private FSInitializeOperation _initUnpackFIleSystemOp;
private LoadBuiltinCatalogFileOperation _loadBuildinCatalogFileOp;
private ESteps _steps = ESteps.None;
internal DBFSInitializeOperation(DefaultBuildinFileSystem fileSystem)
internal DBFSInitializeOperation(BuiltinFileSystem fileSystem)
{
_fileSystem = fileSystem;
}
@@ -50,7 +50,7 @@ namespace YooAsset
{
if (_requestBuildinPackageVersionOp == null)
{
_requestBuildinPackageVersionOp = new RequestBuildinPackageVersionOperation(_fileSystem);
_requestBuildinPackageVersionOp = new RequestBuiltinPackageVersionOperation(_fileSystem);
_requestBuildinPackageVersionOp.StartOperation();
AddChildOperation(_requestBuildinPackageVersionOp);
}
@@ -77,8 +77,8 @@ namespace YooAsset
{
string packageVersion = _requestBuildinPackageVersionOp.PackageVersion;
string destFilePath = GetCopyPackageHashDestPath(packageVersion);
string sourceFilePath = _fileSystem.GetBuildinPackageHashFilePath(packageVersion);
_copyBuildinHashFileOp = new CopyBuildinFileOperation(_fileSystem, sourceFilePath, destFilePath);
string sourceFilePath = _fileSystem.GetBuiltinPackageHashFilePath(packageVersion);
_copyBuildinHashFileOp = new CopyBuiltinFileOperation(_fileSystem, sourceFilePath, destFilePath);
_copyBuildinHashFileOp.StartOperation();
AddChildOperation(_copyBuildinHashFileOp);
}
@@ -105,8 +105,8 @@ namespace YooAsset
{
string packageVersion = _requestBuildinPackageVersionOp.PackageVersion;
string destFilePath = GetCopyPackageManifestDestPath(packageVersion);
string sourceFilePath = _fileSystem.GetBuildinPackageManifestFilePath(packageVersion);
_copyBuildinManifestFileOp = new CopyBuildinFileOperation(_fileSystem, sourceFilePath, destFilePath);
string sourceFilePath = _fileSystem.GetBuiltinPackageManifestFilePath(packageVersion);
_copyBuildinManifestFileOp = new CopyBuiltinFileOperation(_fileSystem, sourceFilePath, destFilePath);
_copyBuildinManifestFileOp.StartOperation();
AddChildOperation(_copyBuildinManifestFileOp);
}
@@ -131,7 +131,7 @@ namespace YooAsset
{
if (_initUnpackFIleSystemOp == null)
{
_initUnpackFIleSystemOp = _fileSystem.InitializeUpackFileSystem();
_initUnpackFIleSystemOp = _fileSystem.InitializeUnpackFileSystem();
_initUnpackFIleSystemOp.StartOperation();
AddChildOperation(_initUnpackFIleSystemOp);
}
@@ -165,7 +165,7 @@ namespace YooAsset
{
if (_loadBuildinCatalogFileOp == null)
{
_loadBuildinCatalogFileOp = new LoadBuildinCatalogFileOperation(_fileSystem);
_loadBuildinCatalogFileOp = new LoadBuiltinCatalogFileOperation(_fileSystem);
_loadBuildinCatalogFileOp.StartOperation();
AddChildOperation(_loadBuildinCatalogFileOp);
}
@@ -195,7 +195,7 @@ namespace YooAsset
foreach (var wrapper in catalog.Wrappers)
{
var fileWrapper = new DefaultBuildinFileSystem.FileWrapper(wrapper.FileName);
var fileWrapper = new BuiltinFileSystem.FileWrapper(wrapper.FileName);
_fileSystem.RecordCatalogFile(wrapper.BundleGUID, fileWrapper);
}

View File

@@ -6,7 +6,7 @@ namespace YooAsset
/// <summary>
/// 加载AssetBundle文件
/// </summary>
internal class DBFSLoadAssetBundleOperation : FSLoadBundleOperation
internal class BFSLoadAssetBundleOperation : FSLoadBundleOperation
{
private enum ESteps
{
@@ -16,7 +16,7 @@ namespace YooAsset
Done,
}
private readonly DefaultBuildinFileSystem _fileSystem;
private readonly BuiltinFileSystem _fileSystem;
private readonly PackageBundle _bundle;
private AssetBundleCreateRequest _createRequest;
private AssetBundle _assetBundle;
@@ -24,7 +24,7 @@ namespace YooAsset
private ESteps _steps = ESteps.None;
internal DBFSLoadAssetBundleOperation(DefaultBuildinFileSystem fileSystem, PackageBundle bundle)
internal BFSLoadAssetBundleOperation(BuiltinFileSystem fileSystem, PackageBundle bundle)
{
_fileSystem = fileSystem;
_bundle = bundle;
@@ -64,7 +64,7 @@ namespace YooAsset
}
else
{
string filePath = _fileSystem.GetBuildinFileLoadPath(_bundle);
string filePath = _fileSystem.GetBuiltinFileLoadPath(_bundle);
_assetBundle = AssetBundle.LoadFromFile(filePath);
}
}
@@ -78,7 +78,7 @@ namespace YooAsset
}
else
{
string filePath = _fileSystem.GetBuildinFileLoadPath(_bundle);
string filePath = _fileSystem.GetBuiltinFileLoadPath(_bundle);
_createRequest = AssetBundle.LoadFromFileAsync(filePath);
}
}
@@ -138,21 +138,21 @@ namespace YooAsset
/// <summary>
/// 加载原生文件
/// </summary>
internal class DBFSLoadRawBundleOperation : FSLoadBundleOperation
internal class BFSLoadRawBundleOperation : FSLoadBundleOperation
{
private enum ESteps
{
None,
LoadBuildinRawBundle,
LoadBuiltinRawBundle,
Done,
}
private readonly DefaultBuildinFileSystem _fileSystem;
private readonly BuiltinFileSystem _fileSystem;
private readonly PackageBundle _bundle;
private ESteps _steps = ESteps.None;
internal DBFSLoadRawBundleOperation(DefaultBuildinFileSystem fileSystem, PackageBundle bundle)
internal BFSLoadRawBundleOperation(BuiltinFileSystem fileSystem, PackageBundle bundle)
{
_fileSystem = fileSystem;
_bundle = bundle;
@@ -161,16 +161,16 @@ namespace YooAsset
{
DownloadProgress = 1f;
DownloadedBytes = _bundle.FileSize;
_steps = ESteps.LoadBuildinRawBundle;
_steps = ESteps.LoadBuiltinRawBundle;
}
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.LoadBuildinRawBundle)
if (_steps == ESteps.LoadBuiltinRawBundle)
{
string filePath = _fileSystem.GetBuildinFileLoadPath(_bundle);
string filePath = _fileSystem.GetBuiltinFileLoadPath(_bundle);
#if UNITY_ANDROID
//TODO : 安卓平台内置文件属于APK压缩包内的文件。
@@ -205,7 +205,7 @@ namespace YooAsset
/// <summary>
/// 加载团结文件
/// </summary>
internal class DBFSLoadInstantBundleOperation : FSLoadBundleOperation
internal class BFSLoadInstantBundleOperation : FSLoadBundleOperation
{
private enum ESteps
{
@@ -223,7 +223,7 @@ namespace YooAsset
private ESteps _steps = ESteps.None;
internal DBFSLoadInstantBundleOperation(DefaultBuildinFileSystem fileSystem, PackageBundle bundle)
internal BFSLoadInstantBundleOperation(DefaultBuildinFileSystem fileSystem, PackageBundle bundle)
{
_fileSystem = fileSystem;
_bundle = bundle;

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