refactor : 重构代码

This commit is contained in:
何冠峰
2026-01-12 11:09:27 +08:00
parent d228e41df7
commit 781e5950b1
479 changed files with 7679 additions and 13329 deletions

View File

@@ -19,7 +19,7 @@ namespace YooAsset.Editor
buildParameters.BuildOutputRoot = AssetBundleBuilderHelper.GetDefaultBuildOutputRoot();
buildParameters.BuildinFileRoot = AssetBundleBuilderHelper.GetStreamingAssetsRoot();
buildParameters.BuildPipeline = EBuildPipeline.EditorSimulateBuildPipeline.ToString();
buildParameters.BuildBundleType = (int)EBuildBundleType.VirtualBundle;
buildParameters.BuildBundleType = (int)EBundleType.VirtualBundle;
buildParameters.BuildTarget = EditorUserBuildSettings.activeBuildTarget;
buildParameters.PackageName = packageName;
buildParameters.PackageVersion = "Simulate";

View File

@@ -96,7 +96,7 @@ namespace YooAsset.Editor
/// <summary>
/// 资源包加密服务类
/// </summary>
public IEncryptionServices EncryptionServices;
public IBundleEncryptionServices EncryptionServices;
/// <summary>
/// 资源清单加密服务类
@@ -146,7 +146,7 @@ namespace YooAsset.Editor
string message = BuildLogger.GetErrorMessage(ErrorCode.BuildPipelineIsNullOrEmpty, "Build pipeline is null or empty !");
throw new Exception(message);
}
if (BuildBundleType == (int)EBuildBundleType.Unknown)
if (BuildBundleType == (int)EBundleType.Unknown)
{
string message = BuildLogger.GetErrorMessage(ErrorCode.BuildBundleTypeIsUnknown, $"Build bundle type is unknown {BuildBundleType} !");
throw new Exception(message);

View File

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

View File

@@ -31,7 +31,7 @@ namespace YooAsset.Editor
// 创建新补丁清单
PackageManifest manifest = new PackageManifest();
manifest.FileVersion = ManifestDefine.FileVersion;
manifest.FileVersion = PackageManifestDefine.FileVersion;
manifest.EnableAddressable = buildMapContext.Command.EnableAddressable;
manifest.SupportExtensionless = buildMapContext.Command.SupportExtensionless;
manifest.LocationToLower = buildMapContext.Command.LocationToLower;
@@ -71,7 +71,7 @@ namespace YooAsset.Editor
{
string fileName = YooAssetSettingsData.GetManifestJsonFileName(buildParameters.PackageName, buildParameters.PackageVersion);
string filePath = $"{packageOutputDirectory}/{fileName}";
ManifestTools.SerializeToJson(filePath, manifest);
PackageManifestTools.SerializeToJson(filePath, manifest);
BuildLogger.Log($"Create package manifest file: {filePath}");
}
@@ -81,7 +81,7 @@ namespace YooAsset.Editor
{
string fileName = YooAssetSettingsData.GetManifestBinaryFileName(buildParameters.PackageName, buildParameters.PackageVersion);
packagePath = $"{packageOutputDirectory}/{fileName}";
ManifestTools.SerializeToBinary(packagePath, manifest, buildParameters.ManifestProcessServices);
PackageManifestTools.SerializeToBinary(packagePath, manifest, buildParameters.ManifestProcessServices);
packageHash = HashUtility.FileCRC32(packagePath);
BuildLogger.Log($"Create package manifest file: {packagePath}");
}
@@ -106,7 +106,7 @@ namespace YooAsset.Editor
{
ManifestContext manifestContext = new ManifestContext();
byte[] bytesData = FileUtility.ReadAllBytes(packagePath);
manifestContext.Manifest = ManifestTools.DeserializeFromBinary(bytesData, buildParameters.ManifestRestoreServices);
manifestContext.Manifest = PackageManifestTools.DeserializeFromBinary(bytesData, buildParameters.ManifestRestoreServices);
context.SetContextObject(manifestContext);
}
}

View File

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

View File

@@ -54,8 +54,8 @@ namespace YooAsset.Editor
{
string bundleName = bundleInfo.BundleName;
string fileHash = bundleInfo.PackageFileHash;
string fileExtension = ManifestTools.GetRemoteBundleFileExtension(bundleName);
string fileName = ManifestTools.GetRemoteBundleFileName(outputNameStyle, bundleName, fileExtension, fileHash);
string fileExtension = PackageManifestTools.GetRemoteBundleFileExtension(bundleName);
string fileName = PackageManifestTools.GetRemoteBundleFileName(outputNameStyle, bundleName, fileExtension, fileHash);
bundleInfo.PackageDestFilePath = $"{packageOutputDirectory}/{fileName}";
}
}

View File

@@ -1,9 +1,9 @@

namespace YooAsset.Editor
{
public class EncryptionNone : IEncryptionServices
public class EncryptionNone : IBundleEncryptionServices
{
public EncryptResult Encrypt(EncryptFileInfo fileInfo)
public BundleEncryptionResult Encrypt(BundleEncryptionContext fileInfo)
{
throw new System.NotImplementedException();
}

View File

@@ -46,13 +46,13 @@ namespace YooAsset.Editor
/// <summary>
/// 创建资源包加密服务类实例
/// </summary>
protected IEncryptionServices CreateEncryptionServicesInstance()
protected IBundleEncryptionServices CreateEncryptionServicesInstance()
{
var className = AssetBundleBuilderSetting.GetPackageEncyptionServicesClassName(PackageName, PipelineName);
var classTypes = EditorTools.GetAssignableTypes(typeof(IEncryptionServices));
var classTypes = EditorTools.GetAssignableTypes(typeof(IBundleEncryptionServices));
var classType = classTypes.Find(x => x.FullName.Equals(className));
if (classType != null)
return (IEncryptionServices)Activator.CreateInstance(classType);
return (IBundleEncryptionServices)Activator.CreateInstance(classType);
else
return null;
}
@@ -184,7 +184,7 @@ namespace YooAsset.Editor
protected PopupField<Type> CreateEncryptionServicesField(VisualElement container)
{
// 资源包加密服务类
var classTypes = EditorTools.GetAssignableTypes(typeof(IEncryptionServices));
var classTypes = EditorTools.GetAssignableTypes(typeof(IBundleEncryptionServices));
if (classTypes.Count > 0)
{
var className = AssetBundleBuilderSetting.GetPackageEncyptionServicesClassName(PackageName, PipelineName);

View File

@@ -109,7 +109,7 @@ namespace YooAsset.Editor
buildParameters.BuildOutputRoot = AssetBundleBuilderHelper.GetDefaultBuildOutputRoot();
buildParameters.BuildinFileRoot = AssetBundleBuilderHelper.GetStreamingAssetsRoot();
buildParameters.BuildPipeline = PipelineName.ToString();
buildParameters.BuildBundleType = (int)EBuildBundleType.AssetBundle;
buildParameters.BuildBundleType = (int)EBundleType.AssetBundle;
buildParameters.BuildTarget = BuildTarget;
buildParameters.PackageName = PackageName;
buildParameters.PackageVersion = _buildVersionField.value;

View File

@@ -66,7 +66,7 @@ namespace YooAsset.Editor
buildParameters.BuildOutputRoot = AssetBundleBuilderHelper.GetDefaultBuildOutputRoot();
buildParameters.BuildinFileRoot = AssetBundleBuilderHelper.GetStreamingAssetsRoot();
buildParameters.BuildPipeline = PipelineName.ToString();
buildParameters.BuildBundleType = (int)EBuildBundleType.VirtualBundle;
buildParameters.BuildBundleType = (int)EBundleType.VirtualBundle;
buildParameters.BuildTarget = BuildTarget;
buildParameters.PackageName = PackageName;
buildParameters.PackageVersion = _buildVersionField.value;

View File

@@ -103,7 +103,7 @@ namespace YooAsset.Editor
buildParameters.BuildOutputRoot = AssetBundleBuilderHelper.GetDefaultBuildOutputRoot();
buildParameters.BuildinFileRoot = AssetBundleBuilderHelper.GetStreamingAssetsRoot();
buildParameters.BuildPipeline = PipelineName.ToString();
buildParameters.BuildBundleType = (int)EBuildBundleType.RawBundle;
buildParameters.BuildBundleType = (int)EBundleType.RawBundle;
buildParameters.BuildTarget = BuildTarget;
buildParameters.PackageName = PackageName;
buildParameters.PackageVersion = _buildVersionField.value;

View File

@@ -109,7 +109,7 @@ namespace YooAsset.Editor
buildParameters.BuildOutputRoot = AssetBundleBuilderHelper.GetDefaultBuildOutputRoot();
buildParameters.BuildinFileRoot = AssetBundleBuilderHelper.GetStreamingAssetsRoot();
buildParameters.BuildPipeline = PipelineName.ToString();
buildParameters.BuildBundleType = (int)EBuildBundleType.AssetBundle;
buildParameters.BuildBundleType = (int)EBundleType.AssetBundle;
buildParameters.BuildTarget = BuildTarget;
buildParameters.PackageName = PackageName;
buildParameters.PackageVersion = _buildVersionField.value;

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;
@@ -125,13 +125,13 @@ namespace YooAsset.Editor
_providerTableView.AddColumn(column);
}
// BeginTime
// StartTime
{
var columnStyle = new ColumnStyle(100);
columnStyle.Stretchable = false;
columnStyle.Searchable = false;
columnStyle.Sortable = true;
var column = new TableColumn("BeginTime", "Begin Time", columnStyle);
var column = new TableColumn("StartTime", "Start Time", columnStyle);
column.MakeCell = () =>
{
var label = new Label();
@@ -206,7 +206,8 @@ namespace YooAsset.Editor
{
StyleColor textColor;
var providerTableData = data as ProviderTableData;
if (providerTableData.ProviderInfo.Status == EOperationStatus.Failed.ToString())
if (providerTableData.ProviderInfo.Status == EOperationStatus.Failed.ToString() ||
providerTableData.ProviderInfo.Status == EOperationStatus.Aborted.ToString())
textColor = new StyleColor(Color.yellow);
else
textColor = new StyleColor(Color.white);
@@ -280,7 +281,8 @@ namespace YooAsset.Editor
{
StyleColor textColor;
var dependTableData = data as DependTableData;
if (dependTableData.BundleInfo.Status == EOperationStatus.Failed.ToString())
if (dependTableData.BundleInfo.Status == EOperationStatus.Failed.ToString() ||
dependTableData.BundleInfo.Status == EOperationStatus.Aborted.ToString())
textColor = new StyleColor(Color.yellow);
else
textColor = new StyleColor(Color.white);
@@ -296,7 +298,7 @@ namespace YooAsset.Editor
/// <summary>
/// 填充页面数据
/// </summary>
public void FillViewData(DebugReport debugReport)
public void FillViewData(DiagnosticReport debugReport)
{
// 清空旧数据
_providerTableView.ClearAll(false, true);
@@ -304,7 +306,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 +315,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("StartTime", providerInfo.StartTime);
rowData.AddLongValueCell("LoadingTime", providerInfo.ElapsedMS);
rowData.AddLongValueCell("RefCount", providerInfo.ReferenceCount);
rowData.AddStringValueCell("Status", providerInfo.Status.ToString());
_sourceDatas.Add(rowData);
}
@@ -372,18 +374,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;
@@ -151,7 +151,8 @@ namespace YooAsset.Editor
{
StyleColor textColor;
var bundleTableData = data as BundleTableData;
if (bundleTableData.BundleInfo.Status == EOperationStatus.Failed.ToString())
if (bundleTableData.BundleInfo.Status == EOperationStatus.Failed.ToString() ||
bundleTableData.BundleInfo.Status == EOperationStatus.Aborted.ToString())
textColor = new StyleColor(Color.yellow);
else
textColor = new StyleColor(Color.white);
@@ -208,13 +209,13 @@ namespace YooAsset.Editor
_usingTableView.AddColumn(column);
}
// BeginTime
// StartTime
{
var columnStyle = new ColumnStyle(100);
columnStyle.Stretchable = false;
columnStyle.Searchable = false;
columnStyle.Sortable = true;
var column = new TableColumn("BeginTime", "Begin Time", columnStyle);
var column = new TableColumn("StartTime", "Start Time", columnStyle);
column.MakeCell = () =>
{
var label = new Label();
@@ -267,7 +268,8 @@ namespace YooAsset.Editor
{
StyleColor textColor;
var usingTableData = data as UsingTableData;
if (usingTableData.ProviderInfo.Status == EOperationStatus.Failed.ToString())
if (usingTableData.ProviderInfo.Status == EOperationStatus.Failed.ToString() ||
usingTableData.ProviderInfo.Status == EOperationStatus.Aborted.ToString())
textColor = new StyleColor(Color.yellow);
else
textColor = new StyleColor(Color.white);
@@ -341,7 +343,8 @@ namespace YooAsset.Editor
{
StyleColor textColor;
var feferenceTableData = data as ReferenceTableData;
if (feferenceTableData.BundleInfo.Status == EOperationStatus.Failed.ToString())
if (feferenceTableData.BundleInfo.Status == EOperationStatus.Failed.ToString() ||
feferenceTableData.BundleInfo.Status == EOperationStatus.Aborted.ToString())
textColor = new StyleColor(Color.yellow);
else
textColor = new StyleColor(Color.white);
@@ -357,7 +360,7 @@ namespace YooAsset.Editor
/// <summary>
/// 填充页面数据
/// </summary>
public void FillViewData(DebugReport debugReport)
public void FillViewData(DiagnosticReport debugReport)
{
// 清空旧数据
_bundleTableView.ClearAll(false, true);
@@ -366,7 +369,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 +378,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 +446,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("StartTime", providerInfo.StartTime);
rowData.AddLongValueCell("RefCount", providerInfo.ReferenceCount);
rowData.AddStringValueCell("Status", providerInfo.Status);
sourceDatas.Add(rowData);
break;
@@ -466,13 +469,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;
@@ -147,13 +147,13 @@ namespace YooAsset.Editor
_operationTableView.AddColumn(column);
}
// BeginTime
// StartTime
{
var columnStyle = new ColumnStyle(100);
columnStyle.Stretchable = false;
columnStyle.Searchable = false;
columnStyle.Sortable = true;
var column = new TableColumn("BeginTime", "Begin Time", columnStyle);
var column = new TableColumn("StartTime", "Start Time", columnStyle);
column.MakeCell = () =>
{
var label = new Label();
@@ -168,14 +168,14 @@ namespace YooAsset.Editor
_operationTableView.AddColumn(column);
}
// ProcessTime
// ElapsedMS
{
var columnStyle = new ColumnStyle(130);
columnStyle.Stretchable = false;
columnStyle.Searchable = false;
columnStyle.Sortable = true;
columnStyle.Units = "ms";
var column = new TableColumn("ProcessTime", "Process Time", columnStyle);
var column = new TableColumn("ElapsedMS", "Elapsed MS", columnStyle);
column.MakeCell = () =>
{
var label = new Label();
@@ -207,7 +207,8 @@ namespace YooAsset.Editor
{
StyleColor textColor;
var operationTableData = data as OperationTableData;
if (operationTableData.OperationInfo.Status == EOperationStatus.Failed.ToString())
if (operationTableData.OperationInfo.Status == EOperationStatus.Failed.ToString() ||
operationTableData.OperationInfo.Status == EOperationStatus.Aborted.ToString())
textColor = new StyleColor(Color.yellow);
else
textColor = new StyleColor(Color.white);
@@ -259,19 +260,19 @@ namespace YooAsset.Editor
_bottomToolbar.Add(button);
}
// BeginTime
// StartTime
{
ToolbarButton button = new ToolbarButton();
button.text = "BeginTime";
button.text = "StartTime";
button.style.flexGrow = 0;
button.style.width = 100;
_bottomToolbar.Add(button);
}
// ProcessTime
// ElapsedMS
{
ToolbarButton button = new ToolbarButton();
button.text = "ProcessTime (ms)";
button.text = "ElapsedMS";
button.style.flexGrow = 0;
button.style.width = 130;
_bottomToolbar.Add(button);
@@ -299,7 +300,7 @@ namespace YooAsset.Editor
/// <summary>
/// 填充页面数据
/// </summary>
public void FillViewData(DebugReport debugReport)
public void FillViewData(DiagnosticReport debugReport)
{
// 清空旧数据
_operationTableView.ClearAll(false, true);
@@ -308,7 +309,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 +320,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("StartTime", operationInfo.StartTime);
rowData.AddLongValueCell("ElapsedMS", operationInfo.ElapsedMS);
rowData.AddStringValueCell("Status", operationInfo.Status.ToString());
rowData.AddStringValueCell("Desc", operationInfo.OperationDesc);
_sourceDatas.Add(rowData);
@@ -377,8 +378,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);
@@ -408,20 +409,20 @@ namespace YooAsset.Editor
container.Add(label);
}
// BeginTime
// StartTime
{
var label = new Label();
label.name = "BeginTime";
label.name = "StartTime";
label.style.flexGrow = 0f;
label.style.width = 100;
label.style.unityTextAlign = TextAnchor.MiddleLeft;
container.Add(label);
}
// ProcessTime
// ElapsedMS
{
var label = new Label();
label.name = "ProcessTime";
label.name = "ElapsedMS";
label.style.flexGrow = 0f;
label.style.width = 130;
label.style.unityTextAlign = TextAnchor.MiddleLeft;
@@ -450,7 +451,7 @@ namespace YooAsset.Editor
}
private void BindTreeViewItem(VisualElement container, object userData)
{
var operationInfo = (DebugOperationInfo)userData;
var operationInfo = (DiagnosticOperationInfo)userData;
// OperationName
{
@@ -464,22 +465,23 @@ namespace YooAsset.Editor
label.text = operationInfo.Progress.ToString();
}
// BeginTime
// StartTime
{
var label = container.Q<Label>("BeginTime");
label.text = operationInfo.BeginTime;
var label = container.Q<Label>("StartTime");
label.text = operationInfo.StartTime;
}
// ProcessTime
// ElapsedMS
{
var label = container.Q<Label>("ProcessTime");
label.text = operationInfo.ProcessTime.ToString();
var label = container.Q<Label>("ElapsedMS");
label.text = operationInfo.ElapsedMS.ToString();
}
// Status
{
StyleColor textColor;
if (operationInfo.Status == EOperationStatus.Failed.ToString())
if (operationInfo.Status == EOperationStatus.Failed.ToString() ||
operationInfo.Status == EOperationStatus.Aborted.ToString())
textColor = new StyleColor(Color.yellow);
else
textColor = new StyleColor(Color.white);
@@ -495,9 +497,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

@@ -277,7 +277,7 @@ namespace YooAsset.Editor
if (dependTableData.BundleInfo.Encrypted)
return;
if (_buildReport.Summary.BuildBundleType == (int)EBuildBundleType.AssetBundle)
if (_buildReport.Summary.BuildBundleType == (int)EBundleType.AssetBundle)
{
string rootDirectory = Path.GetDirectoryName(_reportFilePath);
string filePath = $"{rootDirectory}/{dependTableData.BundleInfo.FileName}";

View File

@@ -348,7 +348,7 @@ namespace YooAsset.Editor
if (bundleTableData.BundleInfo.Encrypted)
return;
if (_buildReport.Summary.BuildBundleType == (int)EBuildBundleType.AssetBundle)
if (_buildReport.Summary.BuildBundleType == (int)EBundleType.AssetBundle)
{
string rootDirectory = Path.GetDirectoryName(_reportFilePath);
string filePath = $"{rootDirectory}/{bundleTableData.BundleInfo.FileName}";

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

@@ -1,46 +0,0 @@
using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace YooAsset
{
/// <summary>
/// 资源系统调试信息
/// </summary>
[Serializable]
internal class DebugReport
{
/// <summary>
/// 调试器版本
/// </summary>
public string DebuggerVersion = RemoteDebuggerDefine.DebuggerVersion;
/// <summary>
/// 游戏帧
/// </summary>
public int FrameCount;
/// <summary>
/// 调试的包裹数据列表
/// </summary>
public List<DebugPackageData> PackageDatas = new List<DebugPackageData>(10);
/// <summary>
/// 序列化
/// </summary>
public static byte[] Serialize(DebugReport debugReport)
{
return Encoding.UTF8.GetBytes(JsonUtility.ToJson(debugReport));
}
/// <summary>
/// 反序列化
/// </summary>
public static DebugReport Deserialize(byte[] data)
{
return JsonUtility.FromJson<DebugReport>(Encoding.UTF8.GetString(data));
}
}
}

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,41 +4,44 @@ using System.Collections.Generic;
namespace YooAsset
{
/// <summary>
/// 描述异步操作的运行时诊断信息
/// </summary>
[Serializable]
internal struct DebugOperationInfo : IComparer<DebugOperationInfo>, IComparable<DebugOperationInfo>
internal struct DiagnosticOperationInfo : IComparer<DiagnosticOperationInfo>, IComparable<DiagnosticOperationInfo>
{
/// <summary>
/// 任务名称
/// 异步操作的名称
/// </summary>
public string OperationName;
/// <summary>
/// 任务说明
/// 异步操作的说明
/// </summary>
public string OperationDesc;
/// <summary>
/// 优先级
/// 异步操作的优先级
/// </summary>
public uint Priority;
/// <summary>
/// 任务进度
/// </summary>
public float Progress;
/// <summary>
/// 任务开始的时间
/// 开始的时间
/// </summary>
public string BeginTime;
public string StartTime;
/// <summary>
/// 处理耗时(单位:毫秒)
/// </summary>
public long ProcessTime;
public long ElapsedMS;
/// <summary>
/// 任务状态
/// 异步操作的执行进度
/// </summary>
public float Progress;
/// <summary>
/// 异步操作的执行状态
/// </summary>
public string Status;
@@ -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,54 +4,52 @@ 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>
public string AssetPath;
/// <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>
/// 加载状态
/// 资源的加载状态
/// </summary>
public string Status;
/// <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

@@ -0,0 +1,50 @@
using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace YooAsset
{
/// <summary>
/// 资源系统的诊断报告
/// </summary>
[Serializable]
internal class DiagnosticReport
{
/// <summary>
/// 调试器版本
/// </summary>
public string DebuggerVersion = DiagnosticSystemDefine.DebuggerVersion;
/// <summary>
/// 报告发生的游戏帧
/// </summary>
public int FrameCount;
/// <summary>
/// 包裹数据列表
/// </summary>
public List<DiagnosticPackageData> PackageDataList = new List<DiagnosticPackageData>(10);
/// <summary>
/// 序列化诊断报告为字节数组
/// </summary>
/// <param name="report">要序列化的诊断报告</param>
/// <returns>UTF-8 编码的 JSON 字节数组</returns>
public static byte[] Serialize(DiagnosticReport report)
{
return Encoding.UTF8.GetBytes(JsonUtility.ToJson(report));
}
/// <summary>
/// 从字节数组反序列化诊断报告
/// </summary>
/// <param name="data">UTF-8 编码的 JSON 字节数组</param>
/// <returns>反序列化后的诊断报告</returns>
public static DiagnosticReport Deserialize(byte[] 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 = "1.0";
/// <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,19 @@

namespace YooAsset
{
/// <summary>
/// 远程调试命令类型
/// </summary>
internal enum EDebugCommandType
{
/// <summary>
/// 采样一次
/// </summary>
SampleOnce = 0,
/// <summary>
/// 持续采样
/// </summary>
AutoSampling = 1,
}
}

View File

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

View File

@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using UnityEngine.Events;
using UnityEngine.Networking.PlayerConnection;
using UnityEngine;
namespace YooAsset
{
/// <summary>
/// 模拟的 Editor 连接
/// 在 Editor 模式下模拟 EditorConnection 的行为,用于本地调试
/// </summary>
internal class MockEditorConnection
{
#if UNITY_EDITOR
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void OnRuntimeInitialize()
{
_instance = null;
}
#endif
private static MockEditorConnection _instance;
/// <summary>
/// 获取单例实例
/// </summary>
public static MockEditorConnection Instance
{
get
{
if (_instance == null)
_instance = new MockEditorConnection();
return _instance;
}
}
private readonly Dictionary<Guid, UnityAction<MessageEventArgs>> _messageHandlers = new Dictionary<Guid, UnityAction<MessageEventArgs>>();
/// <summary>
/// 初始化连接,清空所有已注册的消息处理器
/// </summary>
public void Initialize()
{
_messageHandlers.Clear();
}
/// <summary>
/// 注册消息处理回调
/// </summary>
/// <param name="messageID">消息标识符</param>
/// <param name="callback">收到消息时的回调函数</param>
public void Register(Guid messageID, UnityAction<MessageEventArgs> callback)
{
if (messageID == Guid.Empty)
throw new ArgumentException("messageID is empty.");
if (_messageHandlers.ContainsKey(messageID) == false)
_messageHandlers.Add(messageID, callback);
}
/// <summary>
/// 注销消息处理回调
/// </summary>
/// <param name="messageID">消息标识符</param>
public void Unregister(Guid messageID)
{
if (_messageHandlers.ContainsKey(messageID))
_messageHandlers.Remove(messageID);
}
/// <summary>
/// 向 Player 端发送消息
/// </summary>
/// <param name="messageID">消息标识符</param>
/// <param name="data">消息数据</param>
public void Send(Guid messageID, byte[] data)
{
if (messageID == Guid.Empty)
throw new ArgumentException("messageID is empty.");
// 接收对方的消息
MockPlayerConnection.Instance.HandleEditorMessage(messageID, data);
}
/// <summary>
/// 处理来自 Player 端的消息
/// </summary>
/// <param name="messageID">消息标识符</param>
/// <param name="data">消息数据</param>
internal void HandlePlayerMessage(Guid messageID, byte[] data)
{
if (_messageHandlers.TryGetValue(messageID, out UnityAction<MessageEventArgs> value))
{
var args = new MessageEventArgs();
args.playerId = 0;
args.data = data;
value?.Invoke(args);
}
}
}
}

View File

@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using UnityEngine.Events;
using UnityEngine.Networking.PlayerConnection;
using UnityEngine;
namespace YooAsset
{
/// <summary>
/// 模拟的 Player 连接
/// 在 Editor 模式下模拟 PlayerConnection 的行为,用于本地调试
/// </summary>
internal class MockPlayerConnection
{
#if UNITY_EDITOR
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void OnRuntimeInitialize()
{
_instance = null;
}
#endif
private static MockPlayerConnection _instance;
/// <summary>
/// 获取单例实例
/// </summary>
public static MockPlayerConnection Instance
{
get
{
if (_instance == null)
_instance = new MockPlayerConnection();
return _instance;
}
}
private readonly Dictionary<Guid, UnityAction<MessageEventArgs>> _messageHandlers = new Dictionary<Guid, UnityAction<MessageEventArgs>>();
/// <summary>
/// 初始化连接,清空所有已注册的消息处理器
/// </summary>
public void Initialize()
{
_messageHandlers.Clear();
}
/// <summary>
/// 注册消息处理回调
/// </summary>
/// <param name="messageID">消息标识符</param>
/// <param name="callback">收到消息时的回调函数</param>
public void Register(Guid messageID, UnityAction<MessageEventArgs> callback)
{
if (messageID == Guid.Empty)
throw new ArgumentException("messageID is empty.");
if (_messageHandlers.ContainsKey(messageID) == false)
_messageHandlers.Add(messageID, callback);
}
/// <summary>
/// 注销消息处理回调
/// </summary>
/// <param name="messageID">消息标识符</param>
public void Unregister(Guid messageID)
{
if (_messageHandlers.ContainsKey(messageID))
_messageHandlers.Remove(messageID);
}
/// <summary>
/// 向 Editor 端发送消息
/// </summary>
/// <param name="messageID">消息标识符</param>
/// <param name="data">消息数据</param>
public void Send(Guid messageID, byte[] data)
{
if (messageID == Guid.Empty)
throw new ArgumentException("messageID is empty.");
// 接收对方的消息
MockEditorConnection.Instance.HandlePlayerMessage(messageID, data);
}
/// <summary>
/// 处理来自 Editor 端的消息
/// </summary>
/// <param name="messageID">消息标识符</param>
/// <param name="data">消息数据</param>
internal void HandleEditorMessage(Guid messageID, byte[] data)
{
if (_messageHandlers.TryGetValue(messageID, out UnityAction<MessageEventArgs> value))
{
var args = new MessageEventArgs();
args.playerId = 0;
args.data = data;
value?.Invoke(args);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,50 +0,0 @@
using System;
using System.Text;
using UnityEngine;
namespace YooAsset
{
internal enum ERemoteCommand
{
/// <summary>
/// 采样一次
/// </summary>
SampleOnce = 0,
/// <summary>
/// 自动采集
/// </summary>
SampleAuto = 1,
}
[Serializable]
internal class RemoteCommand
{
/// <summary>
/// 命令类型
/// </summary>
public int CommandType;
/// <summary>
/// 命令附加参数
/// </summary>
public string CommandParam;
/// <summary>
/// 序列化
/// </summary>
public static byte[] Serialize(RemoteCommand command)
{
return Encoding.UTF8.GetBytes(JsonUtility.ToJson(command));
}
/// <summary>
/// 反序列化
/// </summary>
public static RemoteCommand Deserialize(byte[] data)
{
return JsonUtility.FromJson<RemoteCommand>(Encoding.UTF8.GetString(data));
}
}
}

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

@@ -0,0 +1,45 @@
using System;
using System.Text;
using UnityEngine;
namespace YooAsset
{
/// <summary>
/// 远程调试命令
/// 用于 Editor 向 Player 发送调试指令
/// </summary>
[Serializable]
internal class RemoteDebugCommand
{
/// <summary>
/// 命令类型
/// </summary>
public int CommandType;
/// <summary>
/// 命令附加参数
/// </summary>
public string Parameter;
/// <summary>
/// 序列化命令为字节数组
/// </summary>
/// <param name="command">要序列化的远程调试命令</param>
/// <returns>UTF-8 编码的 JSON 字节数组</returns>
public static byte[] Serialize(RemoteDebugCommand command)
{
return Encoding.UTF8.GetBytes(JsonUtility.ToJson(command));
}
/// <summary>
/// 从字节数组反序列化命令
/// </summary>
/// <param name="data">UTF-8 编码的 JSON 字节数组</param>
/// <returns>反序列化后的远程调试命令</returns>
public static RemoteDebugCommand Deserialize(byte[] 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

@@ -1,69 +0,0 @@
using System;
using System.Collections.Generic;
using UnityEngine.Events;
using UnityEngine.Networking.PlayerConnection;
using UnityEngine;
namespace YooAsset
{
internal class RemoteEditorConnection
{
#if UNITY_EDITOR
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void OnRuntimeInitialize()
{
_instance = null;
}
#endif
private static RemoteEditorConnection _instance;
public static RemoteEditorConnection Instance
{
get
{
if (_instance == null)
_instance = new RemoteEditorConnection();
return _instance;
}
}
private readonly Dictionary<Guid, UnityAction<MessageEventArgs>> _messageCallbacks = new Dictionary<Guid, UnityAction<MessageEventArgs>>();
public void Initialize()
{
_messageCallbacks.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);
}
public void Unregister(Guid messageID)
{
if (_messageCallbacks.ContainsKey(messageID))
_messageCallbacks.Remove(messageID);
}
public void Send(Guid messageID, byte[] data)
{
if (messageID == Guid.Empty)
throw new ArgumentException("messageID is empty !");
// 接收对方的消息
RemotePlayerConnection.Instance.HandleEditorMessage(messageID, data);
}
internal void HandlePlayerMessage(Guid messageID, byte[] data)
{
if (_messageCallbacks.TryGetValue(messageID, out UnityAction<MessageEventArgs> value))
{
var args = new MessageEventArgs();
args.playerId = 0;
args.data = data;
value?.Invoke(args);
}
}
}
}

View File

@@ -1,69 +0,0 @@
using System;
using System.Collections.Generic;
using UnityEngine.Events;
using UnityEngine.Networking.PlayerConnection;
using UnityEngine;
namespace YooAsset
{
internal class RemotePlayerConnection
{
#if UNITY_EDITOR
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void OnRuntimeInitialize()
{
_instance = null;
}
#endif
private static RemotePlayerConnection _instance;
public static RemotePlayerConnection Instance
{
get
{
if (_instance == null)
_instance = new RemotePlayerConnection();
return _instance;
}
}
private readonly Dictionary<Guid, UnityAction<MessageEventArgs>> _messageCallbacks = new Dictionary<Guid, UnityAction<MessageEventArgs>>();
public void Initialize()
{
_messageCallbacks.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);
}
public void Unregister(Guid messageID)
{
if (_messageCallbacks.ContainsKey(messageID))
_messageCallbacks.Remove(messageID);
}
public void Send(Guid messageID, byte[] data)
{
if (messageID == Guid.Empty)
throw new ArgumentException("messageID is empty !");
// 接收对方的消息
RemoteEditorConnection.Instance.HandlePlayerMessage(messageID, data);
}
internal void HandleEditorMessage(Guid messageID, byte[] data)
{
if (_messageCallbacks.TryGetValue(messageID, out UnityAction<MessageEventArgs> value))
{
var args = new MessageEventArgs();
args.playerId = 0;
args.data = data;
value?.Invoke(args);
}
}
}
}

View File

@@ -1,10 +0,0 @@
using UnityEngine.Networking;
using UnityEngine;
namespace YooAsset
{
/// <summary>
/// 自定义下载器的请求委托
/// </summary>
public delegate UnityWebRequest UnityWebRequestCreator(string url, string method);
}

View File

@@ -9,39 +9,47 @@ 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);
/// <remarks>
/// Key 格式:$"{packageName}_{eventName}"
/// </remarks>
private static readonly Dictionary<string, int> _failureRecords = new Dictionary<string, int>(1000);
/// <summary>
/// 记录一次失败
/// </summary>
public static void RecordRequestFailed(string packageName, string eventName)
/// <param name="packageName">资源包名称</param>
/// <param name="eventName">事件名称</param>
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)
/// <param name="packageName">资源包名称</param>
/// <param name="eventName">事件名称</param>
/// <returns>失败次数,如果未记录过则返回 0</returns>
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,153 +2,6 @@ using System.Collections.Generic;
namespace YooAsset
{
/// <summary>
/// 下载器结束
/// </summary>
public struct DownloaderFinishData
{
/// <summary>
/// 所属包裹名称
/// </summary>
public string PackageName;
/// <summary>
/// 是否成功
/// </summary>
public bool Succeed;
}
/// <summary>
/// 下载器相关的更新数据
/// </summary>
public struct DownloadUpdateData
{
/// <summary>
/// 所属包裹名称
/// </summary>
public string PackageName;
/// <summary>
/// 下载进度 (0-1f)
/// </summary>
public float Progress;
/// <summary>
/// 下载文件总数
/// </summary>
public int TotalDownloadCount;
/// <summary>
/// 当前完成的下载文件数量
/// </summary>
public int CurrentDownloadCount;
/// <summary>
/// 下载数据总大小(单位:字节)
/// </summary>
public long TotalDownloadBytes;
/// <summary>
/// 当前完成的下载数据大小(单位:字节)
/// </summary>
public long CurrentDownloadBytes;
}
/// <summary>
/// 下载器相关的错误数据
/// </summary>
public struct DownloadErrorData
{
/// <summary>
/// 所属包裹名称
/// </summary>
public string PackageName;
/// <summary>
/// 下载失败的文件名称
/// </summary>
public string FileName;
/// <summary>
/// 错误信息
/// </summary>
public string ErrorInfo;
}
/// <summary>
/// 下载器相关的文件数据
/// </summary>
public struct DownloadFileData
{
/// <summary>
/// 所属包裹名称
/// </summary>
public string PackageName;
/// <summary>
/// 下载的文件名称
/// </summary>
public string FileName;
/// <summary>
/// 下载的文件大小
/// </summary>
public long FileSize;
}
/// <summary>
/// 导入文件的信息
/// </summary>
public struct ImportFileInfo
{
/// <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>
@@ -182,7 +35,7 @@ namespace YooAsset
/// 2. 每次接收到下载数据时,看门狗计时器会重置。
/// 3. 若在设定的时间范围内未收到任何数据,任务将被自动终止。
/// </remarks>
public readonly int WatchdogTime;
public readonly int WatchdogTimeout;
/// <summary>
/// 文件保存路径
@@ -193,7 +46,7 @@ namespace YooAsset
/// 是否追加写入文件
/// </summary>
/// <remarks>
/// 配合 ResumeFromBytes 使用,用于断点续传场景。
/// 配合 ResumeOffset 使用,用于断点续传场景。
/// </remarks>
public readonly bool AppendToFile;
@@ -206,34 +59,45 @@ namespace YooAsset
/// 断点续传的起始字节(小于等于 0 表示不启用)
/// </summary>
/// <remarks>
/// 推荐由后端自动设置 Range 请求头:"bytes={ResumeFromBytes}-"。
/// 推荐由后端自动设置 Range 请求头:"bytes={ResumeOffset}-"。
/// </remarks>
public readonly long ResumeFromBytes;
public readonly long ResumeOffset;
/// <summary>
/// 自定义请求头(可选)
/// </summary>
/// <remarks>
/// 使用 AddRequestHeader 方法添加请求头。
/// 注意:相同 key 重复添加会抛出异常。
/// </remarks>
public Dictionary<string, string> Headers;
/// <summary>
/// 构造文件下载请求参数
/// </summary>
/// <param name="url">请求地址</param>
/// <param name="savePath">文件保存路径</param>
/// <param name="timeout">响应超时时间0 表示不应用超时</param>
/// <param name="watchdogTimeout">看门狗超时时间0 表示禁用</param>
/// <param name="appendToFile">是否追加写入文件(用于断点续传)</param>
/// <param name="removeFileOnAbort">中止时是否删除目标文件</param>
/// <param name="resumeOffset">断点续传的起始字节位置</param>
public DownloadFileRequestArgs(
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;
}
@@ -281,23 +145,28 @@ namespace YooAsset
/// 2. 每次接收到下载数据时,看门狗计时器会重置。
/// 3. 若在设定的时间范围内未收到任何数据,任务将被自动终止。
/// </remarks>
public readonly int WatchdogTime;
public readonly int WatchdogTimeout;
/// <summary>
/// 自定义请求头(可选)
/// </summary>
/// <remarks>
/// 使用 AddRequestHeader 方法添加请求头。
/// 注意:相同 key 重复添加会抛出异常。
/// </remarks>
public Dictionary<string, string> Headers;
/// <summary>
/// 构造数据下载请求参数
/// </summary>
/// <param name="url">请求地址</param>
/// <param name="options">通用请求参数</param>
public DownloadDataRequestArgs(string url, int timeout, int watchdogTime)
/// <param name="timeout">响应超时时间0 表示不应用超时</param>
/// <param name="watchdogTimeout">看门狗超时时间0 表示禁用</param>
public DownloadDataRequestArgs(string url, int timeout, int watchdogTimeout)
{
URL = url;
Timeout = timeout;
WatchdogTime = watchdogTime;
WatchdogTimeout = watchdogTimeout;
Headers = null;
}
@@ -345,7 +214,7 @@ namespace YooAsset
/// 2. 每次接收到下载数据时,看门狗计时器会重置。
/// 3. 若在设定的时间范围内未收到任何数据,任务将被自动终止。
/// </remarks>
public readonly int WatchdogTime;
public readonly int WatchdogTimeout;
/// <summary>
/// 禁用 Unity 的网络缓存
@@ -368,22 +237,32 @@ namespace YooAsset
/// <summary>
/// 自定义请求头(可选)
/// </summary>
/// <remarks>
/// 使用 AddRequestHeader 方法添加请求头。
/// 注意:相同 key 重复添加会抛出异常。
/// </remarks>
public Dictionary<string, string> Headers;
/// <summary>
/// 构造 AssetBundle 下载请求参数
/// </summary>
/// <param name="url">请求地址</param>
/// <param name="timeout">响应超时时间0 表示不应用超时</param>
/// <param name="watchdogTimeout">看门狗超时时间0 表示禁用</param>
/// <param name="disableUnityWebCache">是否禁用 Unity 内置缓存</param>
/// <param name="fileHash">文件哈希(启用缓存时必须提供)</param>
/// <param name="unityCrc">Unity CRC 校验值</param>
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,20 @@
using UnityEngine.Networking;
using UnityEngine;
namespace YooAsset
{
internal class DownloadSystemHelper
/// <summary>
/// 下载系统工具类
/// </summary>
/// <remarks>
/// 提供跨平台的 URL 转换和判断功能。
/// </remarks>
internal class DownloadSystemTools
{
/// <summary>
/// 获取WWW加载本地资源的路径
/// 转换为本地文件请求地址
/// </summary>
public static string ConvertToWWWPath(string path)
/// <param name="path">本地文件路径</param>
/// <returns>可用于 UnityWebRequest 的文件协议 URL</returns>
public static string ToLocalURL(string path)
{
string url;
@@ -57,17 +63,19 @@ namespace YooAsset
#elif UNITY_STANDALONE_LINUX
url = StringUtility.Format("file:///root/{0}", path);
#else
throw new System.NotSupportedException($"[{nameof(DownloadSystemHelper.ConvertToWWWPath)}] not implemented platform: {UnityEngine.Application.platform}");
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.
// 处理特殊字符:用户设备路径可能包含特殊字符导致 URL 无法正确识别
return url.Replace("+", "%2B").Replace("#", "%23").Replace("?", "%3F");
}
/// <summary>
/// 是否请求的本地文件
/// 判断是否为本地文件 URL
/// </summary>
public static bool IsRequestLocalFile(string url)
/// <param name="url">要判断的 URL</param>
/// <returns>如果是本地文件 URL 返回 true否则返回 false</returns>
public static bool IsLocalFileURL(string url)
{
//TODO UNITY_STANDALONE_OSX平台目前无法确定

View File

@@ -0,0 +1,37 @@
namespace YooAsset
{
/// <summary>
/// 下载请求状态
/// </summary>
internal enum EDownloadRequestStatus
{
/// <summary>
/// 未开始
/// </summary>
None,
/// <summary>
/// 进行中
/// </summary>
Running,
/// <summary>
/// 已成功
/// </summary>
Succeed,
/// <summary>
/// 已失败
/// </summary>
Failed,
/// <summary>
/// 已中止
/// </summary>
/// <remarks>
/// 可能由用户主动调用 AbortRequest() 或看门狗超时触发。
/// </remarks>
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

@@ -6,7 +6,7 @@ namespace YooAsset
/// 可轮询的下载请求接口
/// </summary>
/// <remarks>
/// 上层通常在每帧轮询 Status IsDone并在完成后读取结果。
/// 上层通常在每帧检查 IsDone 属性,完成后读取结果并调用 Dispose() 释放资源
/// </remarks>
internal interface IDownloadRequest : IDisposable
{
@@ -18,6 +18,9 @@ namespace YooAsset
/// <summary>
/// 是否完成(成功/失败/中止)
/// </summary>
/// <remarks>
/// 注意:访问此属性时会自动调用 PollingRequest() 进行轮询。
/// </remarks>
bool IsDone { get; }
/// <summary>

View File

@@ -4,6 +4,10 @@
DownloadSystem 是 YooAsset 资源管理系统的**底层网络下载层**,负责处理所有 HTTP 网络请求。该模块提供了统一的下载接口抽象,支持文件下载、断点续传、并发请求(由上层调度)、看门狗监控等功能。
### 可见性说明
DownloadSystem 属于 YooAsset Runtime 的内部基础模块,目录内大多数类型为 `internal`(仅供 YooAsset Runtime 内部程序集使用)。本文示例以"模块内部调用方式"展示;业务层建议优先通过 `ResourcePackage / FileSystem / ResourceManager` 等上层接口使用下载能力,避免直接依赖本模块的内部类型。
### 核心职责
- HTTP/HTTPS 文件下载
@@ -16,7 +20,7 @@ DownloadSystem 是 YooAsset 资源管理系统的**底层网络下载层**,负
## 边界与上层协作
DownloadSystem 的职责是提供可替换后端 + 统一请求接口 + 轮询式生命周期的基础能力:
DownloadSystem 的职责是提供"可替换后端 + 统一请求接口 + 轮询式生命周期"的基础能力:
- **本模块不负责并发队列/限流调度**:并发通常由上层同时创建多个 request 并自行控制并发数。
- **本模块不负责重试/回退策略**:失败后的重试、切换 CDN、降级等策略通常由上层系统实现。
@@ -41,20 +45,20 @@ DownloadSystem 的职责是提供“可替换后端 + 统一请求接口 + 轮
```
┌─────────────────────────────────────────────────────────┐
│ 上层调用者 │
│ (FileSystem / ResourceManager)
│ 上层调用者
│ (FileSystem / ResourceManager) │
└─────────────────────────┬───────────────────────────────┘
┌─────────────────────────▼───────────────────────────────┐
│ IDownloadBackend
│ IDownloadBackend │
│ (后端接口) │
│ 定义网络库合约,工厂模式创建请求 │
│ 定义网络库合约,工厂模式创建请求
└─────────────────────────┬───────────────────────────────┘
┌─────────────────────────▼───────────────────────────────┐
│ IDownloadRequest
│ IDownloadRequest │
│ (请求接口) │
│ 轮询式生命周期管理,状态机驱动 │
│ 轮询式生命周期管理,状态机驱动
└─────────────────────────┬───────────────────────────────┘
┌─────────────────────────▼───────────────────────────────┐
@@ -79,22 +83,23 @@ DownloadSystem/
│ ├── IDownloadBackend.cs # 后端接口(工厂模式)
│ └── IDownloadRequest.cs # 请求接口层次结构
├── DefaultDownloadBackend/ # 默认后端实现
├── UnityWebBackend/ # 默认后端实现
│ ├── UnityWebRequestBackend.cs # UnityWebRequest 后端
│ └── UnityWebRequestCreator.cs # UnityWebRequest 创建委托
├── DefaultDownloadRequest/ # 默认请求实现
│ ├── UnityWebRequestDownloaderBase.cs # 基础下载器(抽象类)
│ ├── UnityWebRequestFileDownloader.cs # 文件下载器
│ ├── UnityWebRequestHeadDownloader.cs # HEAD 请求器
│ ├── UnityWebRequestBytesDownloader.cs # 字节下载器
│ ├── UnityWebRequestTextDownloader.cs # 文本下载器
│ ├── UnityWebRequestAssetBundleDownloader.cs # AssetBundle 下载器
│ └── VirtualFileDownloader.cs # 模拟下载器(编辑器用)
├── UnityWebRequest/ # 默认请求实现
│ ├── UnityWebRequestBase.cs # 基础下载器(抽象类)
│ ├── UnityWebRequestFile.cs # 文件下载器
│ ├── UnityWebRequestHead.cs # HEAD 请求器
│ ├── UnityWebRequestBytes.cs # 字节下载器
│ ├── UnityWebRequestText.cs # 文本下载器
│ ├── UnityWebRequestAssetBundle.cs # AssetBundle 下载器
│ └── SimulateRequestFile.cs # 模拟下载器(编辑器用)
├── DownloadSystemDefine.cs # 枚举、结构体定义
├── DownloadSystemHelper.cs # 工具函数
── WebRequestCounter.cs # 请求失败计数器
├── EDownloadRequestStatus.cs # 下载请求状态枚举
├── DownloadRequestArgs.cs # 请求参数结构体定义
── DownloadSystemTools.cs # 工具函数
└── DownloadFailureCounter.cs # 请求失败计数器
```
---
@@ -106,7 +111,7 @@ DownloadSystem/
定义网络库实现的合约,通过工厂方法创建各类下载请求。
```csharp
public interface IDownloadBackend
internal interface IDownloadBackend : IDisposable
{
/// <summary>
/// 后端标识名称(用于日志/调试)
@@ -133,13 +138,13 @@ public interface IDownloadBackend
所有下载请求的通用接口,定义生命周期和状态管理。
```csharp
public interface IDownloadRequest : IDisposable
internal interface IDownloadRequest : IDisposable
{
// 元信息
string URL { get; }
// 生命周期
bool IsDone { get; } // 每次访问自动轮询
bool IsDone { get; } // 访问自动调用 PollingRequest()
EDownloadRequestStatus Status { get; }
// 进度跟踪
@@ -159,9 +164,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 +179,7 @@ public interface IDownloadRequest : IDisposable
### 请求状态枚举
```csharp
public enum EDownloadRequestStatus
internal enum EDownloadRequestStatus
{
None, // 未开始
Running, // 进行中
@@ -189,71 +194,97 @@ 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 WatchdogTimeout; // 看门狗超时(秒)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 ResumeOffset; // 断点续传起始位置>0 时推荐由后端设置 Range 头)
public Dictionary<string, string> Headers; // 自定义请求头
public Dictionary<string, string> Headers; // 自定义请求头(可选)
public DownloadFileRequestArgs(
string url,
string savePath,
int timeout,
int watchdogTimeout,
bool appendToFile = false,
bool removeFileOnAbort = true,
long resumeOffset = 0);
/// <summary>
/// 添加请求头(注意:相同 key 重复添加会抛异常)
/// </summary>
public void AddRequestHeader(string name, string value);
}
```
#### DownloadDataRequestArgs数据下载参数
```csharp
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 WatchdogTimeout; // 看门狗超时(秒)0=禁用
public Dictionary<string, string> Headers; // 自定义请求头(可选)
public DownloadDataRequestArgs(string url, int timeout, int watchdogTimeout);
/// <summary>
/// 添加请求头(注意:相同 key 重复添加会抛异常)
/// </summary>
public void AddRequestHeader(string name, string value);
}
```
#### DownloadAssetBundleRequestArgsAssetBundle 下载参数)
```csharp
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 WatchdogTimeout; // 看门狗超时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 watchdogTimeout,
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);
}
```
### 回调数据结构体
| 结构体 | 用途 | 关键字段 |
|--------|------|----------|
| `DownloaderFinishData` | 下载完成回调 | `PackageName`, `Succeed` |
| `DownloadUpdateData` | 进度更新回调 | `Progress`, `TotalDownloadBytes`, `CurrentDownloadBytes` |
| `DownloadErrorData` | 下载错误回调 | `FileName`, `ErrorInfo` |
| `DownloadFileData` | 文件完成回调 | `FileName`, `FileSize` |
| `ImportFileInfo` | 导入文件元数据 | `FilePath`, `BundleName`, `BundleGUID` |
---
## 核心类说明
@@ -278,7 +309,7 @@ UnityWebRequestCreator creator = (url, method) =>
IDownloadBackend backend = new UnityWebRequestBackend(creator);
```
### UnityWebRequestDownloaderBase
### UnityWebRequestBase
抽象基类,封装所有下载器的通用逻辑。
@@ -300,12 +331,12 @@ None ──► SendRequest() ──► Running ──► PollingRequest() ──
| 下载器 | 实现接口 | 使用场景 |
|--------|----------|----------|
| `UnityWebRequestFileDownloader` | `IDownloadFileRequest` | 大文件下载到本地 |
| `UnityWebRequestHeadDownloader` | `IDownloadHeadRequest` | 检查资源信息 |
| `UnityWebRequestBytesDownloader` | `IDownloadBytesRequest` | 小文件内存加载 |
| `UnityWebRequestTextDownloader` | `IDownloadTextRequest` | 文本文件下载 |
| `UnityWebRequestAssetBundleDownloader` | `IDownloadAssetBundleRequest` | AB 包下载加载 |
| `VirtualFileDownloader` | `IDownloadFileRequest` | 编辑器模拟下载 |
| `UnityWebRequestFile` | `IDownloadFileRequest` | 大文件下载到本地 |
| `UnityWebRequestHead` | `IDownloadHeadRequest` | 检查资源信息 |
| `UnityWebRequestBytes` | `IDownloadBytesRequest` | 小文件内存加载 |
| `UnityWebRequestText` | `IDownloadTextRequest` | 文本文件下载 |
| `UnityWebRequestAssetBundle` | `IDownloadAssetBundleRequest` | AB 包下载加载 |
| `SimulateRequestFile` | `IDownloadFileRequest` | 编辑器模拟下载 |
---
@@ -314,36 +345,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,
watchdogTimeout: 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();
}
```
### 断点续传
@@ -356,10 +389,10 @@ var args = new DownloadFileRequestArgs(
url: url,
savePath: savePath,
timeout: 30,
watchdogTime: 0,
appendToFile: true, // 追加写入
removeFileOnAbort: false, // 中止时保留文件
resumeFromBytes: existingFileSize); // 断点位置
watchdogTimeout: 0,
appendToFile: true, // 追加写入
removeFileOnAbort: false, // 中止时保留文件
resumeOffset: existingFileSize); // 断点位置
IDownloadFileRequest request = backend.CreateFileRequest(args);
request.SendRequest();
@@ -373,7 +406,7 @@ var args = new DownloadFileRequestArgs(
url: url,
savePath: path,
timeout: 30,
watchdogTime: 30); // 30秒无数据自动中止
watchdogTimeout: 30); // 30秒无数据自动中止
IDownloadFileRequest request = backend.CreateFileRequest(args);
request.SendRequest();
@@ -396,7 +429,7 @@ if (request.Status == EDownloadRequestStatus.Aborted)
var args = new DownloadDataRequestArgs(
url: "https://example.com/file.zip",
timeout: 30,
watchdogTime: 0);
watchdogTimeout: 0);
IDownloadHeadRequest request = backend.CreateHeadRequest(args);
request.SendRequest();
@@ -422,7 +455,7 @@ if (request.Status == EDownloadRequestStatus.Succeed)
var args = new DownloadDataRequestArgs(
url: "https://example.com/data.json",
timeout: 30,
watchdogTime: 0);
watchdogTimeout: 0);
IDownloadBytesRequest request = backend.CreateBytesRequest(args);
request.SendRequest();
@@ -495,7 +528,7 @@ IDownloadBackend (接口)
└── 未收到数据 ──► 计时器累加
└── 超过 WatchdogTime ──► AbortRequest()
└── 超过 WatchdogTimeout ──► AbortRequest()
```
---
@@ -511,44 +544,49 @@ IDownloadRequest (基础接口)
├── IDownloadTextRequest (文本下载)
└── IDownloadAssetBundleRequest (AssetBundle 下载)
UnityWebRequestDownloaderBase (抽象基类)
UnityWebRequestBase (抽象基类)
├── UnityWebRequestFileDownloader ──► IDownloadFileRequest
├── UnityWebRequestHeadDownloader ──► IDownloadHeadRequest
├── UnityWebRequestBytesDownloader ──► IDownloadBytesRequest
├── UnityWebRequestTextDownloader ──► IDownloadTextRequest
└── UnityWebRequestAssetBundleDownloader ──► IDownloadAssetBundleRequest
├── UnityWebRequestFile ──► IDownloadFileRequest
├── UnityWebRequestHead ──► IDownloadHeadRequest
├── UnityWebRequestBytes ──► IDownloadBytesRequest
├── UnityWebRequestText ──► IDownloadTextRequest
└── UnityWebRequestAssetBundle ──► IDownloadAssetBundleRequest
VirtualFileDownloader (独立实现) ──► IDownloadFileRequest
SimulateRequestFile (独立实现) ──► IDownloadFileRequest
```
---
## 工具类
### DownloadSystemHelper
### DownloadSystemTools
提供跨平台的工具函数
提供跨平台的 URL 转换和判断功能
| 方法 | 说明 |
|------|------|
| `ConvertToWWWPath()` | 转换本地路径为 WWW 协议 URL |
| `IsRequestLocalFile()` | 判断是否本地文件请求 |
| 方法 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| `ToLocalURL(path)` | `string path` 本地文件路径 | 可用于 UnityWebRequest 的文件协议 URL | 转换本地路径为文件协议 URL自动处理特殊字符 |
| `IsLocalFileURL(url)` | `string url` 要判断的 URL | `bool` 是否本地文件 URL | 判断 URL 是否为 `file:``jar:file:` 协议 |
### WebRequestCounter
### DownloadFailureCounter
请求失败计数器,用于诊断统计
网络请求失败计数器(诊断用)
- 线程安全:内部使用 `Dictionary` 且未加锁,约定只在主线程调用;如需多线程统计请在外层加锁或改实现
- Key 规则`$"{packageName}_{eventName}"`
- 统计口径:**仅统计网络请求失败**`IDownloadRequest.Status != Succeed` 时记录),不统计内容为空、校验失败、解析失败等业务层失败
- **线程安全**:内部使用 `Dictionary` 且未加锁,约定只在 Unity 主线程调用;如需多线程/回调线程调用,请在外层加锁或改为并发容器实现
- **Key 格式**`$"{packageName}_{eventName}"`
- **统计口径****仅统计网络请求失败**`IDownloadRequest.Status != Succeed` 时记录),不统计内容为空、校验失败、解析失败等业务层失败
| 方法 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| `RecordFailure(packageName, eventName)` | `string packageName` 资源包名称, `string eventName` 事件名称 | `void` | 记录一次失败 |
| `GetFailureCount(packageName, eventName)` | `string packageName` 资源包名称, `string eventName` 事件名称 | `int` 失败次数(未记录过返回 0 | 获取失败次数 |
```csharp
// 记录失败
WebRequestCounter.RecordRequestFailed(packageName, eventName);
DownloadFailureCounter.RecordFailure(packageName, eventName);
// 查询失败次数
int count = WebRequestCounter.GetRequestFailedCount(packageName, eventName);
int count = DownloadFailureCounter.GetFailureCount(packageName, eventName);
```
---
@@ -557,6 +595,7 @@ int count = WebRequestCounter.GetRequestFailedCount(packageName, eventName);
1. **资源释放**:使用完毕后务必调用 `Dispose()` 释放资源
- `AbortRequest()` 仅用于中止请求与切换状态,不等同于释放资源;无论成功/失败/中止都需要 `Dispose()`
- 第三方 `IDownloadBackend` 可能持有原生资源/线程/连接池等,上层在不再使用时也应调用 `backend.Dispose()`
- 推荐使用 `try/finally` 确保释放(尤其是上层可能提前中止的场景)
2. **断点续传**:需要服务器支持 `Range` 请求头和 `206 Partial Content` 响应
- 若服务端不支持 Range 仍返回 200全量内容可能会被追加写入导致文件损坏
@@ -565,3 +604,4 @@ int count = WebRequestCounter.GetRequestFailedCount(packageName, eventName);
5. **驱动更新**:部分第三方网络库实现的 backend 可能需要每帧调用 `IDownloadBackend.Update()` 进行驱动
6. **中止语义**`Aborted` 可能来自用户主动 `AbortRequest()` 或看门狗超时;中止场景下 `HttpCode/Error` 可能为默认值(例如 0/空)
7. **线程安全**:所有下载请求的创建和轮询应在主线程进行
8. **模拟下载器**`SimulateRequestFile` 仅用于模拟进度,不会落盘,且 `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

@@ -0,0 +1,16 @@
using UnityEngine.Networking;
namespace YooAsset
{
/// <summary>
/// 自定义 UnityWebRequest 创建委托
/// </summary>
/// <remarks>
/// 用于自定义 UnityWebRequest 的创建方式,例如添加证书验证、代理设置等。
/// 通过 UnityWebRequestBackend 构造函数传入。
/// </remarks>
/// <param name="url">请求地址</param>
/// <param name="method">HTTP 方法(如 GET、HEAD</param>
/// <returns>自定义配置的 UnityWebRequest 实例</returns>
public delegate UnityWebRequest UnityWebRequestCreator(string url, string method);
}

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 = GetUnityEngineRealtime();
_lastTickTime = TimeUtility.RealtimeSinceStartup;
}
}
@@ -99,9 +99,9 @@ namespace YooAsset
if (Status != EDownloadRequestStatus.Running)
return;
double currentTime = GetUnityEngineRealtime();
double deltaTime = currentTime - _lastUpdateTime;
_lastUpdateTime = currentTime;
double currentTime = TimeUtility.RealtimeSinceStartup;
double deltaTime = currentTime - _lastTickTime;
_lastTickTime = currentTime;
// 计算本帧下载的字节数
long downloadBytes = (long)(_args.DownloadSpeed * deltaTime);
@@ -137,14 +137,5 @@ namespace YooAsset
public void Dispose()
{
}
private double GetUnityEngineRealtime()
{
#if UNITY_2020_3_OR_NEWER
return UnityEngine.Time.realtimeSinceStartupAsDouble;
#else
return UnityEngine.Time.realtimeSinceStartup;
#endif
}
}
}

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>
@@ -76,7 +76,7 @@ namespace YooAsset
else
{
if (string.IsNullOrEmpty(_args.FileHash))
throw new YooInternalException("File hash is null or empty !");
throw new YooInternalException("File hash is null or empty.");
// 使用 Unity 缓存
// 说明The file hash defining the version of the asset bundle.

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;
@@ -109,7 +109,7 @@ namespace YooAsset
catch (Exception ex)
{
Status = EDownloadRequestStatus.Failed;
Error = $"[{GetType().Name}] Failed to create web request : {ex.Message}";
Error = $"[{GetType().Name}] Failed to create web request: {ex.Message}";
}
}
}
@@ -125,7 +125,7 @@ namespace YooAsset
DownloadProgress = _webRequest.downloadProgress;
DownloadedBytes = (long)_webRequest.downloadedBytes;
CheckWatchdog();
TickWatchdog();
if (_webRequest.isDone == false)
return;
@@ -144,12 +144,12 @@ namespace YooAsset
else
{
Status = EDownloadRequestStatus.Failed;
Error = $"[{GetType().Name}] URL: {URL} - 错误: {_webRequest.error}";
Error = $"[{GetType().Name}] URL: {URL} - Error: {_webRequest.error}";
OnRequestFailed();
}
// 完成后释放
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);
@@ -221,21 +221,21 @@ 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 !");
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,28 +248,23 @@ namespace YooAsset
/// <summary>
/// 检测看门狗
/// </summary>
private void CheckWatchdog()
private void TickWatchdog()
{
if (_watchdogTime == 0)
if (_watchdogTimeout == 0)
return;
if (_watchdogAborted)
return;
#if UNITY_2020_3_OR_NEWER
double realtimeSinceStartup = UnityEngine.Time.realtimeSinceStartupAsDouble;
#else
double realtimeSinceStartup = UnityEngine.Time.realtimeSinceStartup;
#endif
double realtimeSinceStartup = TimeUtility.RealtimeSinceStartup;
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(); //看门狗终止网络请求
@@ -278,9 +273,9 @@ namespace YooAsset
}
/// <summary>
/// 释放资源
/// 清理 WebRequest 资源
/// </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

@@ -36,14 +36,6 @@ namespace YooAsset
{
return _fileSystem.GetBundleFilePath(_packageBundle);
}
public override byte[] ReadBundleFileData()
{
return _fileSystem.ReadBundleFileData(_packageBundle);
}
public override string ReadBundleFileText()
{
return _fileSystem.ReadBundleFileText(_packageBundle);
}
public override FSLoadAssetOperation LoadAssetAsync(AssetInfo assetInfo)
{

View File

@@ -39,7 +39,7 @@ namespace YooAsset
if (_assetBundle == null)
{
_steps = ESteps.Done;
Error = $"The bundle {_packageBundle.BundleName} has been destroyed due to unity engine bugs !";
Error = $"The bundle {_packageBundle.BundleName} has been destroyed due to unity engine bugs.";
Status = EOperationStatus.Failed;
return;
}
@@ -49,7 +49,7 @@ namespace YooAsset
if (_steps == ESteps.LoadAsset)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
if (_assetInfo.AssetType == null)
Result = _assetBundle.LoadAllAssets();
@@ -71,7 +71,7 @@ namespace YooAsset
{
if (_request != null)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
// 强制挂起主线程(注意:该操作会很耗时)
YooLogger.Warning("Suspend the main thread to load unity asset.");
@@ -108,14 +108,7 @@ namespace YooAsset
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
if (ExecuteWhileDone())
{
_steps = ESteps.Done;
break;
}
}
RunBatchExecution();
}
}
}

View File

@@ -39,7 +39,7 @@ namespace YooAsset
if (_assetBundle == null)
{
_steps = ESteps.Done;
Error = $"The bundle {_packageBundle.BundleName} has been destroyed due to unity engine bugs !";
Error = $"The bundle {_packageBundle.BundleName} has been destroyed due to unity engine bugs.";
Status = EOperationStatus.Failed;
return;
}
@@ -49,7 +49,7 @@ namespace YooAsset
if (_steps == ESteps.LoadAsset)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
if (_assetInfo.AssetType == null)
Result = _assetBundle.LoadAsset(_assetInfo.AssetPath);
@@ -71,7 +71,7 @@ namespace YooAsset
{
if (_request != null)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
// 强制挂起主线程(注意:该操作会很耗时)
YooLogger.Warning("Suspend the main thread to load unity asset.");
@@ -93,11 +93,11 @@ namespace YooAsset
error = $"Failed to load asset : {_assetInfo.AssetPath} AssetType : null AssetBundle : {_packageBundle.BundleName}";
else
error = $"Failed to load asset : {_assetInfo.AssetPath} AssetType : {_assetInfo.AssetType} AssetBundle : {_packageBundle.BundleName}";
YooLogger.Error(error);
_steps = ESteps.Done;
Error = error;
Status = EOperationStatus.Failed;
Error = error;
YooLogger.Error(Error);
}
else
{
@@ -108,14 +108,7 @@ namespace YooAsset
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
if (ExecuteWhileDone())
{
_steps = ESteps.Done;
break;
}
}
RunBatchExecution();
}
}
}

View File

@@ -36,7 +36,7 @@ namespace YooAsset
if (_steps == ESteps.LoadScene)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
// 注意:场景同步加载方法不会立即加载场景,而是在下一帧加载。
Result = SceneManager.LoadScene(_assetInfo.AssetPath, _loadParams);
@@ -69,10 +69,10 @@ namespace YooAsset
{
if (_asyncOperation != null)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
//注意:场景加载无法强制异步转同步
YooLogger.Error("The scene is loading asyn !");
YooLogger.Error("The scene is loading asyn.");
}
else
{
@@ -107,7 +107,7 @@ namespace YooAsset
internal override void InternalWaitForAsyncComplete()
{
//注意:场景加载不支持异步转同步,为了支持同步加载方法需要实现该方法!
InternalUpdate();
RunOnceExecution();
}
public override void UnSuspendLoad()
{

View File

@@ -39,7 +39,7 @@ namespace YooAsset
if (_assetBundle == null)
{
_steps = ESteps.Done;
Error = $"The bundle {_packageBundle.BundleName} has been destroyed due to unity engine bugs !";
Error = $"The bundle {_packageBundle.BundleName} has been destroyed due to unity engine bugs.";
Status = EOperationStatus.Failed;
return;
}
@@ -49,7 +49,7 @@ namespace YooAsset
if (_steps == ESteps.LoadAsset)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
if (_assetInfo.AssetType == null)
Result = _assetBundle.LoadAssetWithSubAssets(_assetInfo.AssetPath);
@@ -71,7 +71,7 @@ namespace YooAsset
{
if (_request != null)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
// 强制挂起主线程(注意:该操作会很耗时)
YooLogger.Warning("Suspend the main thread to load unity asset.");
@@ -108,14 +108,7 @@ namespace YooAsset
}
internal override void InternalWaitForAsyncComplete()
{
while (true)
{
if (ExecuteWhileDone())
{
_steps = ESteps.Done;
break;
}
}
RunBatchExecution();
}
}
}

View File

@@ -0,0 +1,416 @@
using System;
using System.IO;
using UnityEngine;
namespace YooAsset
{
/// <summary>
/// 默认的 AssetBundle 加载操作(非加密)
/// 通用实现,适用于 BuiltinFileSystem 和 CacheFileSystem
/// </summary>
public class DefaultLoadAssetBundleOperation : LoadAssetBundleOperation
{
private enum ESteps
{
None,
LoadAssetBundle,
CheckResult,
Done,
}
private AssetBundleCreateRequest _createRequest;
private ESteps _steps = ESteps.None;
public DefaultLoadAssetBundleOperation(LoadAssetBundleOptions opionts) : base(opionts) { }
internal override void InternalStart()
{
_steps = ESteps.LoadAssetBundle;
}
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.LoadAssetBundle)
{
if (IsWaitingForAsyncComplete)
Result = AssetBundle.LoadFromFile(_options.FileLoadPath);
else
_createRequest = AssetBundle.LoadFromFileAsync(_options.FileLoadPath);
_steps = ESteps.CheckResult;
}
if (_steps == ESteps.CheckResult)
{
if (_createRequest != null)
{
if (IsWaitingForAsyncComplete)
{
// 强制挂起主线程(注意:该操作会很耗时)
YooLogger.Warning("Suspend the main thread to load unity bundle.");
Result = _createRequest.assetBundle;
}
else
{
if (_createRequest.isDone == false)
return;
Result = _createRequest.assetBundle;
}
}
if (Result == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Failed to load asset bundle file : {_options.Bundle.BundleName}";
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
}
}
}
internal override void InternalWaitForAsyncComplete()
{
RunBatchExecution();
}
public override AssetBundle LoadFromMemory()
{
byte[] fileData = FileUtility.ReadAllBytes(_options.FileLoadPath);
if (fileData == null || fileData.Length == 0)
return null;
return AssetBundle.LoadFromMemory(fileData);
}
}
/// <summary>
/// 默认的 AssetBundle 加载操作(加密)
/// 通用实现,适用于 BuiltinFileSystem 和 CacheFileSystem
/// </summary>
public abstract class DefaultLoadAssetBundleFromOffsetOperation : LoadAssetBundleOperation
{
private enum ESteps
{
None,
LoadAssetBundle,
CheckResult,
Done,
}
private AssetBundleCreateRequest _createRequest;
private ESteps _steps = ESteps.None;
public DefaultLoadAssetBundleFromOffsetOperation(LoadAssetBundleOptions options) : base(options) { }
internal override void InternalStart()
{
_steps = ESteps.LoadAssetBundle;
}
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.LoadAssetBundle)
{
ulong offset = GetFileOffset();
if (IsWaitingForAsyncComplete)
Result = AssetBundle.LoadFromFile(_options.FileLoadPath, 0, offset);
else
_createRequest = AssetBundle.LoadFromFileAsync(_options.FileLoadPath, 0, offset);
_steps = ESteps.CheckResult;
}
if (_steps == ESteps.CheckResult)
{
if (_createRequest != null)
{
if (IsWaitingForAsyncComplete)
{
// 强制挂起主线程(注意:该操作会很耗时)
YooLogger.Warning("Suspend the main thread to load unity bundle.");
Result = _createRequest.assetBundle;
}
else
{
if (_createRequest.isDone == false)
return;
Result = _createRequest.assetBundle;
}
}
if (Result == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Failed to load asset bundle file : {_options.Bundle.BundleName}";
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
}
}
}
internal override void InternalWaitForAsyncComplete()
{
RunBatchExecution();
}
/// <summary>
/// 获取偏移值
/// </summary>
protected abstract uint GetFileOffset();
public override AssetBundle LoadFromMemory()
{
int offset = (int)GetFileOffset();
byte[] fileData = File.ReadAllBytes(_options.FileLoadPath);
if (fileData == null || fileData.Length <= offset)
return null;
// 跳过偏移量
byte[] bundleData = new byte[fileData.Length - offset];
Buffer.BlockCopy(fileData, offset, bundleData, 0, bundleData.Length);
return AssetBundle.LoadFromMemory(bundleData);
}
}
/// <summary>
/// 默认的 AssetBundle 加载操作(加密)
/// 通用实现,适用于 CacheFileSystem
/// </summary>
public abstract class DefaultLoadAssetBundleFromMemoryOperation : LoadAssetBundleOperation
{
private enum ESteps
{
None,
CheckFilePath,
LoadAssetBundle,
CheckResult,
Done,
}
private AssetBundleCreateRequest _createRequest;
private ESteps _steps = ESteps.None;
public DefaultLoadAssetBundleFromMemoryOperation(LoadAssetBundleOptions options) : base(options) { }
internal override void InternalStart()
{
_steps = ESteps.CheckFilePath;
}
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.CheckFilePath)
{
string filePath = _options.FileLoadPath;
if (IsSupportFileIO(filePath) == false)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"FileIO not supported for builtin path : {filePath}";
}
else
{
_steps = ESteps.LoadAssetBundle;
}
}
if (_steps == ESteps.LoadAssetBundle)
{
byte[] fileData = File.ReadAllBytes(_options.FileLoadPath);
byte[] rawData = DecryptData(fileData);
if (rawData == null || rawData.Length == 0)
{
_steps = ESteps.None;
Status = EOperationStatus.Failed;
Error = "Decrypted raw data is null or empty.";
return;
}
if (IsWaitingForAsyncComplete)
Result = AssetBundle.LoadFromMemory(rawData);
else
_createRequest = AssetBundle.LoadFromMemoryAsync(rawData);
_steps = ESteps.CheckResult;
}
if (_steps == ESteps.CheckResult)
{
if (_createRequest != null)
{
if (IsWaitingForAsyncComplete)
{
// 强制挂起主线程(注意:该操作会很耗时)
YooLogger.Warning("Suspend the main thread to load unity bundle.");
Result = _createRequest.assetBundle;
}
else
{
if (_createRequest.isDone == false)
return;
Result = _createRequest.assetBundle;
}
}
if (Result == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Failed to load asset bundle file : {_options.Bundle.BundleName}";
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
}
}
}
internal override void InternalWaitForAsyncComplete()
{
RunBatchExecution();
}
/// <summary>
/// 文件数据解密
/// </summary>
protected abstract byte[] DecryptData(byte[] data);
public override AssetBundle LoadFromMemory()
{
byte[] fileData = File.ReadAllBytes(_options.FileLoadPath);
byte[] rawData = DecryptData(fileData);
if (rawData == null || rawData.Length == 0)
return null;
return AssetBundle.LoadFromMemory(rawData);
}
}
/// <summary>
/// 默认的 AssetBundle 加载操作(加密)
/// 通用实现,适用于 CacheFileSystem
/// </summary>
public abstract class DefaultLoadAssetBundleFromStreamOperation : LoadAssetBundleOperation
{
private enum ESteps
{
None,
CheckFilePath,
LoadAssetBundle,
CheckResult,
Done,
}
private AssetBundleCreateRequest _createRequest;
private ESteps _steps = ESteps.None;
public DefaultLoadAssetBundleFromStreamOperation(LoadAssetBundleOptions options) : base(options) { }
internal override void InternalStart()
{
_steps = ESteps.CheckFilePath;
}
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.CheckFilePath)
{
string filePath = _options.FileLoadPath;
if (IsSupportFileIO(filePath) == false)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"FileIO not supported for builtin path : {filePath}";
}
else
{
_steps = ESteps.LoadAssetBundle;
}
}
if (_steps == ESteps.LoadAssetBundle)
{
ManagedStream = CreateManagedFileStream();
uint bufferSize = GetManagedReadBufferSize();
if (IsWaitingForAsyncComplete)
Result = AssetBundle.LoadFromStream(ManagedStream, 0, bufferSize);
else
_createRequest = AssetBundle.LoadFromStreamAsync(ManagedStream, 0, bufferSize);
_steps = ESteps.CheckResult;
}
if (_steps == ESteps.CheckResult)
{
if (_createRequest != null)
{
if (IsWaitingForAsyncComplete)
{
// 强制挂起主线程(注意:该操作会很耗时)
YooLogger.Warning("Suspend the main thread to load unity bundle.");
Result = _createRequest.assetBundle;
}
else
{
if (_createRequest.isDone == false)
return;
Result = _createRequest.assetBundle;
}
}
if (Result == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Failed to load asset bundle file : {_options.Bundle.BundleName}";
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
}
}
}
internal override void InternalWaitForAsyncComplete()
{
RunBatchExecution();
}
/// <summary>
/// 获取文件流
/// </summary>
protected abstract FileStream CreateManagedFileStream();
/// <summary>
/// 获取缓冲池大小
/// </summary>
protected abstract uint GetManagedReadBufferSize();
/// <summary>
/// 文件数据解密
/// </summary>
protected abstract byte[] DecryptData(byte[] data);
public override AssetBundle LoadFromMemory()
{
byte[] fileData = File.ReadAllBytes(_options.FileLoadPath);
byte[] rawData = DecryptData(fileData);
if (rawData == null || rawData.Length == 0)
return null;
return AssetBundle.LoadFromMemory(rawData);
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 3b82b4b846083d34b958320b584d8d9b
guid: 067ac2067f265624bac214127575d7d5
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -0,0 +1,249 @@
using UnityEngine;
namespace YooAsset
{
/// <summary>
/// 默认的 AssetBundle 加载操作(非加密)
/// 通用实现,适用于 WebRemoteFileSystem 和 WebServerFileSystem
/// </summary>
public class DefaultLoadWebAssetBundleOperation : LoadWebAssetBundleOperation
{
private enum ESteps
{
None,
CreateRequest,
CheckRequest,
TryAgain,
Done,
}
private IDownloadAssetBundleRequest _downloadAssetBundleRequest;
private ESteps _steps = ESteps.None;
private int _requestCount = 0;
private float _tryAgainTimer = 0;
private int _failedTryAgain;
public DefaultLoadWebAssetBundleOperation(LoadWebAssetBundleOptions opionts) : base(opionts)
{
_failedTryAgain = opionts.FailedTryAgain;
}
internal override void InternalStart()
{
_steps = ESteps.CreateRequest;
}
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
// 创建下载器
if (_steps == ESteps.CreateRequest)
{
string url = GetRequestURL();
var args = new DownloadAssetBundleRequestArgs(url, 0, _options.WatchdogTimeout, _options.DisableUnityWebCache, _options.Bundle.FileHash, _options.Bundle.UnityCRC);
_downloadAssetBundleRequest = _options.DownloadBackend.CreateAssetBundleRequest(args);
_downloadAssetBundleRequest.SendRequest();
_steps = ESteps.CheckRequest;
}
// 检测下载结果
if (_steps == ESteps.CheckRequest)
{
Progress = _downloadAssetBundleRequest.DownloadProgress;
DownloadedBytes = _downloadAssetBundleRequest.DownloadedBytes;
DownloadProgress = _downloadAssetBundleRequest.DownloadProgress;
if (_downloadAssetBundleRequest.IsDone == false)
return;
if (_downloadAssetBundleRequest.Status == EDownloadRequestStatus.Succeed)
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
Result = _downloadAssetBundleRequest.Result;
}
else
{
if (_failedTryAgain > 0)
{
_steps = ESteps.TryAgain;
YooLogger.Warning($"Failed download : {_downloadAssetBundleRequest.URL} Try again.");
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _downloadAssetBundleRequest.Error;
YooLogger.Error(Error);
}
}
// 最终释放请求器
_downloadAssetBundleRequest.Dispose();
}
// 重新尝试下载
if (_steps == ESteps.TryAgain)
{
_tryAgainTimer += Time.unscaledDeltaTime;
if (_tryAgainTimer > 1f)
{
_tryAgainTimer = 0f;
_failedTryAgain--;
Progress = 0f;
DownloadProgress = 0f;
DownloadedBytes = 0;
_steps = ESteps.CreateRequest;
}
}
}
/// <summary>
/// 获取网络请求地址
/// </summary>
protected string GetRequestURL()
{
// 轮流返回请求地址
_requestCount++;
if (_requestCount % 2 == 0)
return _options.FallbackURL;
else
return _options.MainURL;
}
}
/// <summary>
/// 默认的 AssetBundle 加载操作(加密)
/// 通用实现,适用于 WebRemoteFileSystem 和 WebServerFileSystem
/// </summary>
public abstract class DefaultLoadWebAssetBundleFromMemoryOperation : LoadWebAssetBundleOperation
{
private enum ESteps
{
None,
CreateRequest,
CheckRequest,
TryAgain,
Done,
}
private IDownloadBytesRequest _downloadBytesRequest;
private ESteps _steps = ESteps.None;
private int _requestCount = 0;
private float _tryAgainTimer = 0;
private int _failedTryAgain;
public DefaultLoadWebAssetBundleFromMemoryOperation(LoadWebAssetBundleOptions opionts) : base(opionts)
{
_failedTryAgain = opionts.FailedTryAgain;
}
internal override void InternalStart()
{
_steps = ESteps.CreateRequest;
}
internal override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
// 创建下载器
if (_steps == ESteps.CreateRequest)
{
string url = GetRequestURL();
var args = new DownloadDataRequestArgs(url, 0, _options.WatchdogTimeout);
_downloadBytesRequest = _options.DownloadBackend.CreateBytesRequest(args);
_downloadBytesRequest.SendRequest();
_steps = ESteps.CheckRequest;
}
// 检测下载结果
if (_steps == ESteps.CheckRequest)
{
Progress = _downloadBytesRequest.DownloadProgress;
DownloadProgress = _downloadBytesRequest.DownloadProgress;
DownloadedBytes = _downloadBytesRequest.DownloadedBytes;
if (_downloadBytesRequest.IsDone == false)
return;
// 检查网络错误
if (_downloadBytesRequest.Status == EDownloadRequestStatus.Succeed)
{
var rawData = Decryption(_downloadBytesRequest.Result);
if (rawData == null || rawData.Length == 0)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = "AssetBundle raw data is null or empty.";
}
else
{
AssetBundle assetBundle = AssetBundle.LoadFromMemory(rawData);
if (assetBundle == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Failed load encrypted AssetBundle: {_options.Bundle.BundleName}";
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
Result = assetBundle;
}
}
}
else
{
if (_failedTryAgain > 0)
{
_steps = ESteps.TryAgain;
YooLogger.Warning($"Failed download : {_downloadBytesRequest.URL} Try again.");
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _downloadBytesRequest.Error;
YooLogger.Error(Error);
}
}
// 最终释放请求器
_downloadBytesRequest.Dispose();
}
// 重新尝试下载
if (_steps == ESteps.TryAgain)
{
_tryAgainTimer += Time.unscaledDeltaTime;
if (_tryAgainTimer > 1f)
{
_tryAgainTimer = 0f;
_failedTryAgain--;
Progress = 0f;
DownloadProgress = 0f;
DownloadedBytes = 0;
_steps = ESteps.CreateRequest;
}
}
}
/// <summary>
/// 文件数据解密
/// </summary>
protected abstract byte[] Decryption(byte[] data);
/// 获取网络请求地址
/// </summary>
protected string GetRequestURL()
{
// 轮流返回请求地址
_requestCount++;
if (_requestCount % 2 == 0)
return _options.FallbackURL;
else
return _options.MainURL;
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: ff8a96dd005f55346986f8a98aff8c99
guid: 05b4aa37709184a408090202f98b93d3
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -0,0 +1,80 @@
using System.IO;
using UnityEngine;
namespace YooAsset
{
/// <summary>
/// 加载 AssetBundle 的 Operation 工厂委托
/// </summary>
public delegate LoadAssetBundleOperation LoadAssetBundleOperationFactory(bool bundleEncrypted, LoadAssetBundleOptions options);
/// <summary>
/// 加载 AssetBundle 的抽象基类
/// 用户可继承此类实现自定义加载逻辑(如加密解密)
/// </summary>
public abstract class LoadAssetBundleOperation : AsyncOperationBase
{
protected readonly LoadAssetBundleOptions _options;
/// <summary>
/// 加载结果AssetBundle 对象
/// </summary>
public AssetBundle Result { get; protected set; }
/// <summary>
/// 托管流对象(如果使用流加载)
/// 注意:流对象在资源包对象释放的时候会自动释放
/// </summary>
public Stream ManagedStream { get; protected set; }
public LoadAssetBundleOperation(LoadAssetBundleOptions options)
{
_options = options;
}
/// <summary>
/// 后备加载方法:从内存加载 AssetBundle
/// 当主加载方式失败时FileSystem 会调用此方法作为后备机制
/// </summary>
/// <returns>加载成功返回 AssetBundle 对象,失败返回 null</returns>
public abstract AssetBundle LoadFromMemory();
/// <summary>
/// 检查文件路径是否支持 FileIO 读取
/// </summary>
protected static bool IsSupportFileIO(string filePath)
{
if (string.IsNullOrEmpty(filePath))
return true;
if (filePath.StartsWith("jar:") || filePath.StartsWith("content:"))
return false;
return true;
}
}
/// <summary>
/// 立即完成(失败)的 AssetBundle 加载操作
/// 用途:当 Factory 判定某种场景不支持(例如默认实现不支持加密包)时,返回该 Operation
/// </summary>
public sealed class LoadAssetBundleCompleteOperation : LoadAssetBundleOperation
{
private readonly string _error;
public LoadAssetBundleCompleteOperation(string error, LoadAssetBundleOptions options) : base(options)
{
_error = error;
}
internal override void InternalStart()
{
Status = EOperationStatus.Failed;
Error = _error;
}
internal override void InternalUpdate()
{
}
public override AssetBundle LoadFromMemory()
{
return null;
}
}
}

View File

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

View File

@@ -0,0 +1,19 @@
namespace YooAsset
{
/// <summary>
/// 加载 AssetBundle 的上下文信息
/// </summary>
public struct LoadAssetBundleOptions
{
/// <summary>
/// 文件加载路径
/// </summary>
internal string FileLoadPath { get; set; }
/// <summary>
/// 资源包信息
/// </summary>
internal PackageBundle Bundle { get; set; }
}
}

View File

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

View File

@@ -0,0 +1,60 @@
using UnityEngine;
namespace YooAsset
{
/// <summary>
/// 加载 AssetBundle 的 Operation 工厂委托
/// </summary>
public delegate LoadWebAssetBundleOperation LoadWebAssetBundleOperationFactory(bool bundleEncrypted, LoadWebAssetBundleOptions options);
/// <summary>
/// 加载 AssetBundle 的抽象基类
/// 用户可继承此类实现自定义加载逻辑(如加密解密)
/// </summary>
public abstract class LoadWebAssetBundleOperation : AsyncOperationBase
{
protected readonly LoadWebAssetBundleOptions _options;
/// <summary>
/// 加载结果AssetBundle 对象
/// </summary>
public AssetBundle Result { get; protected set; }
/// <summary>
/// 下载进度
/// </summary>
public float DownloadProgress { protected set; get; }
/// <summary>
/// 下载大小
/// </summary>
public long DownloadedBytes { protected set; get; }
public LoadWebAssetBundleOperation(LoadWebAssetBundleOptions options)
{
_options = options;
}
}
/// <summary>
/// 立即完成(失败)的 AssetBundle 加载操作
/// 用途:当 Factory 判定某种场景不支持(例如默认实现不支持加密包)时,返回该 Operation
/// </summary>
public sealed class LoadWebAssetBundleCompleteOperation : LoadWebAssetBundleOperation
{
private readonly string _error;
public LoadWebAssetBundleCompleteOperation(string error, LoadWebAssetBundleOptions options) : base(options)
{
_error = error;
}
internal override void InternalStart()
{
Status = EOperationStatus.Failed;
Error = _error;
}
internal override void InternalUpdate()
{
}
}
}

View File

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

View File

@@ -0,0 +1,44 @@
namespace YooAsset
{
/// <summary>
/// 加载 AssetBundle 的上下文信息
/// </summary>
public struct LoadWebAssetBundleOptions
{
/// <summary>
/// 资源包信息
/// </summary>
internal PackageBundle Bundle { get; set; }
/// <summary>
/// 失败后重试次数
/// </summary>
internal int FailedTryAgain { get; set; }
/// <summary>
/// 看门狗超时时间
/// </summary>
internal int WatchdogTimeout { get; set; }
/// <summary>
/// 下载后台接口
/// </summary>
internal IDownloadBackend DownloadBackend { get; set; }
/// <summary>
/// 禁用Unity的网络缓存
/// </summary>
internal bool DisableUnityWebCache { get; set; }
/// <summary>
/// 主资源地址
/// </summary>
internal string MainURL { get; set; }
/// <summary>
/// 备用资源地址
/// </summary>
internal string FallbackURL { get; set; }
}
}

View File

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

View File

@@ -10,20 +10,10 @@ namespace YooAsset
public abstract void UnloadBundleFile();
/// <summary>
/// 获取资源包文件的路径
/// 获取资源包文件的本地路径
/// </summary>
public abstract string GetBundleFilePath();
/// <summary>
/// 读取资源包文件的二进制数据
/// </summary>
public abstract byte[] ReadBundleFileData();
/// <summary>
/// 读取资源包文件的文本数据
/// </summary>
public abstract string ReadBundleFileText();
/// <summary>
/// 加载资源包内的资源对象

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