mirror of
https://github.com/tuyoogame/YooAsset.git
synced 2026-05-25 18:20:15 +00:00
Compare commits
75 Commits
2.3.16
...
0a1c81faf1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a1c81faf1 | ||
|
|
e10888de93 | ||
|
|
da222c6179 | ||
|
|
3e3661ad95 | ||
|
|
611ac886b3 | ||
|
|
b0e85017d2 | ||
|
|
2b6e7856e7 | ||
|
|
8d461056e4 | ||
|
|
f188cc715a | ||
|
|
329cae1441 | ||
|
|
c8c74b8c20 | ||
|
|
b3d024743c | ||
|
|
246a62a675 | ||
|
|
354ca5197f | ||
|
|
ce4d6911db | ||
|
|
b796b1a44e | ||
|
|
7198e639d9 | ||
|
|
294fa18fec | ||
|
|
a3f689d815 | ||
|
|
d228e41df7 | ||
|
|
23032cc269 | ||
|
|
72f02bd73f | ||
|
|
a37663a8c2 | ||
|
|
f375d45bd6 | ||
|
|
3dd3d4ef76 | ||
|
|
f0563cce0b | ||
|
|
9b83dcf723 | ||
|
|
ee67a55c0f | ||
|
|
454afc9ba6 | ||
|
|
539ca3523e | ||
|
|
c87efdb509 | ||
|
|
1884fab0c2 | ||
|
|
e5d0a856a5 | ||
|
|
5da8c6baf8 | ||
|
|
33356cb270 | ||
|
|
4b6a8ca406 | ||
|
|
c8e45a6cae | ||
|
|
1fbc9d26a6 | ||
|
|
abb087b02e | ||
|
|
aeaf03011f | ||
|
|
78d24ad3a6 | ||
|
|
aab2c4625e | ||
|
|
079ef75605 | ||
|
|
75881b55f6 | ||
|
|
2020c7d508 | ||
|
|
088d939346 | ||
|
|
fa50e91c9f | ||
|
|
2e7b992a4b | ||
|
|
29fbbd97ff | ||
|
|
abb150e33f | ||
|
|
78e20f434e | ||
|
|
304222b788 | ||
|
|
101236db8a | ||
|
|
37de007b3f | ||
|
|
d570ba8d74 | ||
|
|
2a956099ae | ||
|
|
74037a5a29 | ||
|
|
861f850a32 | ||
|
|
8b1b5f988a | ||
|
|
efafd1173f | ||
|
|
03e49ff1fb | ||
|
|
ea34be1f00 | ||
|
|
831a9981e3 | ||
|
|
9de4aaa658 | ||
|
|
3579a23bd5 | ||
|
|
c865ddc7f2 | ||
|
|
0bde506aec | ||
|
|
15005b3d30 | ||
|
|
4caf733ac6 | ||
|
|
014b17f5cb | ||
|
|
f3ebda0c04 | ||
|
|
5602addaca | ||
|
|
c4ae67aa8e | ||
|
|
bbcc3bf971 | ||
|
|
8b0e75b9b3 |
@@ -2,6 +2,115 @@
|
||||
|
||||
All notable changes to this package will be documented in this file.
|
||||
|
||||
## [2.3.18] - 2025-12-04
|
||||
|
||||
### Fixed
|
||||
|
||||
- (#676) 修复了UniTask扩展包的编译报错。
|
||||
- (#684) 修复了资源配置窗口Group列表数量过多的时候,添加和删除按钮会变小的问题。
|
||||
- (#700) [**严重**] 修复了小游戏扩展库的下载器再失败后重试逻辑不起效的问题。
|
||||
|
||||
### Added
|
||||
|
||||
- (#683) 新增了内置文件系统类初始化参数:UNPACK_FILE_SYSTEM_ROOT
|
||||
|
||||
```csharp
|
||||
class FileSystemParametersDefine
|
||||
{
|
||||
// 指定解压文件的根目录
|
||||
public const string UNPACK_FILE_SYSTEM_ROOT = "UNPACK_FILE_SYSTEM_ROOT";
|
||||
}
|
||||
```
|
||||
|
||||
- (#682) 原生文件构建管线新增构建参数:IncludePathInHash
|
||||
|
||||
```csharp
|
||||
class RawFileBuildParameters : BuildParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件哈希值计算包含路径信息
|
||||
/// </summary>
|
||||
public bool IncludePathInHash = false;
|
||||
}
|
||||
```
|
||||
|
||||
- (#671) 新增扩展工具,可以生成空的包裹内置资源目录文件。
|
||||
|
||||
```csharp
|
||||
public class CreateEmptyCatalogWindow : EditorWindow
|
||||
```
|
||||
|
||||
- (#694) 新增资源清理方式:ClearBundleFilesByLocations
|
||||
|
||||
```csharp
|
||||
public enum EFileClearMode
|
||||
{
|
||||
/// <summary>
|
||||
/// 清理指定地址的文件
|
||||
/// 说明:需要指定参数,可选:string, string[], List<string>
|
||||
/// </summary>
|
||||
ClearBundleFilesByLocations,
|
||||
}
|
||||
```
|
||||
|
||||
## [2.3.17] - 2025-10-30
|
||||
|
||||
**非常重要**:修复了#627优化导致的资源清单CRC值为空的问题。
|
||||
|
||||
该问题会导致下载的损坏文件验证通过。
|
||||
|
||||
影响范围:v2.3.15版本,v2.3.16版本。
|
||||
|
||||
**非常重要**:(#661) 修复了Package销毁过程中,遇到正在加载的AssetBundle会导致无法卸载的问题。
|
||||
|
||||
该问题是偶现,引擎会提示AssetBundle已经加载,无法加载新的文件,导致资源对象加载失败!
|
||||
|
||||
影响范围:所有版本!
|
||||
|
||||
### Fixed
|
||||
|
||||
- (#645) 修复了着色器变种收集工具,在极端情况下变种收集不完整的问题。
|
||||
- (#646) 修复了EditorSimulateMode模式下开启模拟下载tag不生效的问题。
|
||||
- (#667) 修复了所有编辑器窗口针对中文IME的输入问题。
|
||||
- (#670) 修复了Catalog文件生成过程中白名单未考虑自定义清单前缀名。
|
||||
|
||||
### Improvements
|
||||
|
||||
- 重构并统一了资源清单的反序列化逻辑。
|
||||
- (#650) 解决互相依赖的资源包无法卸载的问题。需要开启宏定义:YOOASSET_EXPERIMENTAL
|
||||
- (#655) 优化了初始化的时候,缓存文件搜索效率。安卓平台性能提升1倍,IOS平台性能提升3倍。
|
||||
|
||||
### Added
|
||||
|
||||
- (#643) 新增构建参数,可以节省资源清单运行时内存
|
||||
|
||||
```csharp
|
||||
class ScriptableBuildParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用可寻址地址代替资源路径
|
||||
/// 说明:开启此项可以节省运行时清单占用的内存!
|
||||
/// </summary>
|
||||
public bool ReplaceAssetPathWithAddress = false;
|
||||
}
|
||||
```
|
||||
|
||||
- (#648) 新增初始化参数,可以自动释放引用计数为零的资源包
|
||||
|
||||
```csharp
|
||||
class InitializeParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// 当资源引用计数为零的时候自动释放资源包
|
||||
/// </summary>
|
||||
public bool AutoUnloadBundleWhenUnused = false;
|
||||
}
|
||||
```
|
||||
|
||||
### Changed
|
||||
|
||||
- 程序集宏定义代码转移到扩展工程。参考MacroSupport文件夹。
|
||||
|
||||
## [2.3.16] - 2025-09-17
|
||||
|
||||
### Improvements
|
||||
|
||||
@@ -261,14 +261,14 @@ namespace YooAsset.Editor
|
||||
catch (System.Exception e)
|
||||
{
|
||||
_reportCombiner = null;
|
||||
_titleLabel.text = "导入报告失败!";
|
||||
_titleLabel.text = "Failed to import report!";
|
||||
_descLabel.text = e.Message;
|
||||
UnityEngine.Debug.LogError(e.StackTrace);
|
||||
}
|
||||
}
|
||||
private void FixAllBtn_clicked()
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("提示", "修复全部资源(排除白名单和隐藏元素)", "Yes", "No"))
|
||||
if (EditorUtility.DisplayDialog("Info", "Fix all resources (excluding whitelist and hidden elements)", "Yes", "No"))
|
||||
{
|
||||
if (_reportCombiner != null)
|
||||
_reportCombiner.FixAll();
|
||||
@@ -276,7 +276,7 @@ namespace YooAsset.Editor
|
||||
}
|
||||
private void FixSelectBtn_clicked()
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("提示", "修复勾选资源(包含白名单和隐藏元素)", "Yes", "No"))
|
||||
if (EditorUtility.DisplayDialog("Info", "Fix selected resources (including whitelist and hidden elements)", "Yes", "No"))
|
||||
{
|
||||
if (_reportCombiner != null)
|
||||
_reportCombiner.FixSelect();
|
||||
@@ -302,7 +302,7 @@ namespace YooAsset.Editor
|
||||
}
|
||||
private void ExportFilesBtn_clicked()
|
||||
{
|
||||
string selectFolderPath = EditorUtility.OpenFolderPanel("导入所有选中资源", EditorTools.GetProjectPath(), string.Empty);
|
||||
string selectFolderPath = EditorUtility.OpenFolderPanel("Export all selected resources", EditorTools.GetProjectPath(), string.Empty);
|
||||
if (string.IsNullOrEmpty(selectFolderPath) == false)
|
||||
{
|
||||
if (_reportCombiner != null)
|
||||
|
||||
@@ -32,17 +32,10 @@ namespace YooAsset.Editor
|
||||
|
||||
// 开始扫描工作
|
||||
ScanReport report = scanner.RunScanner();
|
||||
|
||||
// 检测报告合法性
|
||||
report.CheckError();
|
||||
|
||||
// 保存扫描结果
|
||||
string saveDirectory = scanner.SaveDirectory;
|
||||
if (string.IsNullOrEmpty(saveDirectory))
|
||||
saveDirectory = "Assets/";
|
||||
string filePath = $"{saveDirectory}/{scanner.ScannerName}_{scanner.ScannerDesc}.json";
|
||||
ScanReportConfig.ExportJsonConfig(filePath, report);
|
||||
return new ScannerResult(filePath, report);
|
||||
// 返回扫描结果
|
||||
return new ScannerResult(report);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -240,7 +240,7 @@ namespace YooAsset.Editor
|
||||
}
|
||||
private void ScanAllBtn_clicked()
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("提示", $"开始全面扫描!", "Yes", "No"))
|
||||
if (EditorUtility.DisplayDialog("Info", $"Start full scan!", "Yes", "No"))
|
||||
{
|
||||
string searchKeyWord = _scannerSearchField.value;
|
||||
AssetArtScannerSettingData.ScanAll(searchKeyWord);
|
||||
@@ -248,7 +248,7 @@ namespace YooAsset.Editor
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("全面扫描已经取消");
|
||||
Debug.LogWarning("Full scan has been canceled.");
|
||||
}
|
||||
}
|
||||
private void ScanBtn_clicked()
|
||||
|
||||
@@ -3,11 +3,6 @@ namespace YooAsset.Editor
|
||||
{
|
||||
public class ScannerResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 生成的报告文件路径
|
||||
/// </summary>
|
||||
public string ReprotFilePath { private set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// 报告对象
|
||||
/// </summary>
|
||||
@@ -43,11 +38,9 @@ namespace YooAsset.Editor
|
||||
ErrorInfo = error;
|
||||
ErrorStack = stack;
|
||||
}
|
||||
public ScannerResult(string filePath, ScanReport report)
|
||||
public ScannerResult(ScanReport report)
|
||||
{
|
||||
ReprotFilePath = filePath;
|
||||
Report = report;
|
||||
ErrorInfo = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -61,5 +54,19 @@ namespace YooAsset.Editor
|
||||
reproterWindow.ImportSingleReprotFile(Report);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存报告文件
|
||||
/// </summary>
|
||||
public void SaveReportFile(string saveDirectory)
|
||||
{
|
||||
if (Report == null)
|
||||
throw new System.Exception("Scan report is invalid !");
|
||||
|
||||
if (string.IsNullOrEmpty(saveDirectory))
|
||||
saveDirectory = "Assets/";
|
||||
string filePath = $"{saveDirectory}/{Report.ReportName}_{Report.ReportDesc}.json";
|
||||
ScanReportConfig.ExportJsonConfig(filePath, Report);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -106,7 +106,27 @@ namespace YooAsset.Editor
|
||||
/// </summary>
|
||||
public string[] GetAllPackAssetPaths()
|
||||
{
|
||||
return AllPackAssets.Select(t => t.AssetInfo.AssetPath).ToArray();
|
||||
List<string> results = new List<string>(AllPackAssets.Count);
|
||||
for (int i = 0; i < AllPackAssets.Count; i++)
|
||||
{
|
||||
var packAsset = AllPackAssets[i];
|
||||
results.Add(packAsset.AssetInfo.AssetPath);
|
||||
}
|
||||
return results.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取构建的资源可寻址列表
|
||||
/// </summary>
|
||||
public string[] GetAllPackAssetAddress()
|
||||
{
|
||||
List<string> results = new List<string>(AllPackAssets.Count);
|
||||
for (int i = 0; i < AllPackAssets.Count; i++)
|
||||
{
|
||||
var packAsset = AllPackAssets[i];
|
||||
results.Add(packAsset.Address);
|
||||
}
|
||||
return results.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -153,13 +173,15 @@ namespace YooAsset.Editor
|
||||
/// <summary>
|
||||
/// 创建AssetBundleBuild类
|
||||
/// </summary>
|
||||
public UnityEditor.AssetBundleBuild CreatePipelineBuild()
|
||||
public UnityEditor.AssetBundleBuild CreatePipelineBuild(bool replaceAssetPathWithAddress)
|
||||
{
|
||||
// 注意:我们不再支持AssetBundle的变种机制
|
||||
AssetBundleBuild build = new AssetBundleBuild();
|
||||
build.assetBundleName = BundleName;
|
||||
build.assetBundleVariant = string.Empty;
|
||||
build.assetNames = GetAllPackAssetPaths();
|
||||
if (replaceAssetPathWithAddress)
|
||||
build.addressableNames = GetAllPackAssetAddress();
|
||||
return build;
|
||||
}
|
||||
|
||||
|
||||
@@ -96,12 +96,12 @@ namespace YooAsset.Editor
|
||||
/// <summary>
|
||||
/// 获取构建管线里需要的数据
|
||||
/// </summary>
|
||||
public UnityEditor.AssetBundleBuild[] GetPipelineBuilds()
|
||||
public UnityEditor.AssetBundleBuild[] GetPipelineBuilds(bool replaceAssetPathWithAddres)
|
||||
{
|
||||
List<UnityEditor.AssetBundleBuild> builds = new List<UnityEditor.AssetBundleBuild>(_bundleInfoDic.Count);
|
||||
foreach (var bundleInfo in _bundleInfoDic.Values)
|
||||
{
|
||||
builds.Add(bundleInfo.CreatePipelineBuild());
|
||||
builds.Add(bundleInfo.CreatePipelineBuild(replaceAssetPathWithAddres));
|
||||
}
|
||||
return builds.ToArray();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
CatalogFileHelper.CreateFile(manifestServices, buildPackageName, buildinRootDirectory);
|
||||
|
||||
// 刷新目录
|
||||
AssetDatabase.Refresh();
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace YooAsset.Editor
|
||||
/// <summary>
|
||||
/// 创建补丁清单文件到输出目录
|
||||
/// </summary>
|
||||
protected void CreateManifestFile(bool processBundleDepends, bool processBundleTags, BuildContext context)
|
||||
protected void CreateManifestFile(bool processBundleDepends, bool processBundleTags, bool replaceAssetPathWithAddress, BuildContext context)
|
||||
{
|
||||
var buildMapContext = context.GetContextObject<BuildMapContext>();
|
||||
var buildParametersContext = context.GetContextObject<BuildParametersContext>();
|
||||
@@ -31,11 +31,12 @@ 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;
|
||||
manifest.IncludeAssetGUID = buildMapContext.Command.IncludeAssetGUID;
|
||||
manifest.ReplaceAssetPathWithAddress = replaceAssetPathWithAddress;
|
||||
manifest.OutputNameStyle = (int)buildParameters.FileNameStyle;
|
||||
manifest.BuildBundleType = buildParameters.BuildBundleType;
|
||||
manifest.BuildPipeline = buildParameters.BuildPipeline;
|
||||
@@ -58,13 +59,19 @@ namespace YooAsset.Editor
|
||||
|
||||
// 4. 处理内置资源包
|
||||
if (processBundleDepends)
|
||||
{
|
||||
// 注意:初始化资源清单建立引用关系
|
||||
manifest.Initialize();
|
||||
|
||||
ProcessBuiltinBundleDependency(context, manifest);
|
||||
}
|
||||
|
||||
|
||||
// 创建资源清单文本文件
|
||||
{
|
||||
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}");
|
||||
}
|
||||
|
||||
@@ -74,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}");
|
||||
}
|
||||
@@ -99,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);
|
||||
}
|
||||
}
|
||||
@@ -302,9 +309,6 @@ namespace YooAsset.Editor
|
||||
#region YOOASSET_LEGACY_DEPENDENCY
|
||||
private void ProcessBuiltinBundleDependency(BuildContext context, PackageManifest manifest)
|
||||
{
|
||||
// 注意:初始化资源清单建立引用关系
|
||||
ManifestTools.InitManifest(manifest);
|
||||
|
||||
// 注意:如果是可编程构建管线,需要补充内置资源包
|
||||
// 注意:该步骤依赖前面的操作!
|
||||
var buildResultContext = context.TryGetContextObject<TaskBuilding_SBP.BuildResultContext>();
|
||||
|
||||
@@ -54,6 +54,7 @@ namespace YooAsset.Editor
|
||||
buildReport.Summary.CompressOption = builtinBuildParameters.CompressOption;
|
||||
buildReport.Summary.DisableWriteTypeTree = builtinBuildParameters.DisableWriteTypeTree;
|
||||
buildReport.Summary.IgnoreTypeTreeChanges = builtinBuildParameters.IgnoreTypeTreeChanges;
|
||||
buildReport.Summary.ReplaceAssetPathWithAddress = builtinBuildParameters.ReplaceAssetPathWithAddress;
|
||||
}
|
||||
else if (buildParameters is ScriptableBuildParameters)
|
||||
{
|
||||
@@ -61,6 +62,7 @@ namespace YooAsset.Editor
|
||||
buildReport.Summary.CompressOption = scriptableBuildParameters.CompressOption;
|
||||
buildReport.Summary.DisableWriteTypeTree = scriptableBuildParameters.DisableWriteTypeTree;
|
||||
buildReport.Summary.IgnoreTypeTreeChanges = scriptableBuildParameters.IgnoreTypeTreeChanges;
|
||||
buildReport.Summary.ReplaceAssetPathWithAddress = scriptableBuildParameters.ReplaceAssetPathWithAddress;
|
||||
buildReport.Summary.WriteLinkXML = scriptableBuildParameters.WriteLinkXML;
|
||||
buildReport.Summary.CacheServerHost = scriptableBuildParameters.CacheServerHost;
|
||||
buildReport.Summary.CacheServerPort = scriptableBuildParameters.CacheServerPort;
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace YooAsset.Editor
|
||||
string pipelineOutputDirectory = buildParametersContext.GetPipelineOutputDirectory();
|
||||
foreach (var bundleInfo in buildMapContext.Collection)
|
||||
{
|
||||
EncryptFileInfo fileInfo = new EncryptFileInfo();
|
||||
EncryptBundleInfo fileInfo = new EncryptBundleInfo();
|
||||
fileInfo.BundleName = bundleInfo.BundleName;
|
||||
fileInfo.FileLoadPath = $"{pipelineOutputDirectory}/{bundleInfo.BundleName}";
|
||||
var encryptResult = encryptionServices.Encrypt(fileInfo);
|
||||
|
||||
@@ -44,9 +44,9 @@ namespace YooAsset.Editor
|
||||
{
|
||||
bundleInfo.PackageUnityHash = GetUnityHash(bundleInfo, context);
|
||||
bundleInfo.PackageUnityCRC = GetUnityCRC(bundleInfo, context);
|
||||
bundleInfo.PackageFileHash = GetBundleFileHash(bundleInfo, buildParametersContext);
|
||||
bundleInfo.PackageFileCRC = GetBundleFileCRC(bundleInfo, buildParametersContext);
|
||||
bundleInfo.PackageFileSize = GetBundleFileSize(bundleInfo, buildParametersContext);
|
||||
bundleInfo.PackageFileHash = GetBundleFileHash(bundleInfo, context);
|
||||
bundleInfo.PackageFileCRC = GetBundleFileCRC(bundleInfo, context);
|
||||
bundleInfo.PackageFileSize = GetBundleFileSize(bundleInfo, context);
|
||||
}
|
||||
|
||||
// 4.更新补丁包输出的文件路径
|
||||
@@ -54,16 +54,16 @@ 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}";
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract string GetUnityHash(BuildBundleInfo bundleInfo, BuildContext context);
|
||||
protected abstract uint GetUnityCRC(BuildBundleInfo bundleInfo, BuildContext context);
|
||||
protected abstract string GetBundleFileHash(BuildBundleInfo bundleInfo, BuildParametersContext buildParametersContext);
|
||||
protected abstract uint GetBundleFileCRC(BuildBundleInfo bundleInfo, BuildParametersContext buildParametersContext);
|
||||
protected abstract long GetBundleFileSize(BuildBundleInfo bundleInfo, BuildParametersContext buildParametersContext);
|
||||
protected abstract string GetBundleFileHash(BuildBundleInfo bundleInfo, BuildContext context);
|
||||
protected abstract uint GetBundleFileCRC(BuildBundleInfo bundleInfo, BuildContext context);
|
||||
protected abstract long GetBundleFileSize(BuildBundleInfo bundleInfo, BuildContext context);
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,8 @@ namespace YooAsset.Editor
|
||||
// 开始构建
|
||||
string pipelineOutputDirectory = buildParametersContext.GetPipelineOutputDirectory();
|
||||
BuildAssetBundleOptions buildOptions = builtinBuildParameters.GetBundleBuildOptions();
|
||||
AssetBundleManifest unityManifest = BuildPipeline.BuildAssetBundles(pipelineOutputDirectory, buildMapContext.GetPipelineBuilds(), buildOptions, buildParametersContext.Parameters.BuildTarget);
|
||||
var bundleBuilds = buildMapContext.GetPipelineBuilds(builtinBuildParameters.ReplaceAssetPathWithAddress);
|
||||
AssetBundleManifest unityManifest = BuildPipeline.BuildAssetBundles(pipelineOutputDirectory, bundleBuilds, buildOptions, buildParametersContext.Parameters.BuildTarget);
|
||||
if (unityManifest == null)
|
||||
{
|
||||
string message = BuildLogger.GetErrorMessage(ErrorCode.UnityEngineBuildFailed, "UnityEngine build failed !");
|
||||
|
||||
@@ -11,7 +11,10 @@ namespace YooAsset.Editor
|
||||
|
||||
void IBuildTask.Run(BuildContext context)
|
||||
{
|
||||
CreateManifestFile(true, true, context);
|
||||
var buildParametersContext = context.GetContextObject<BuildParametersContext>();
|
||||
var builtinBuildParameters = buildParametersContext.Parameters as BuiltinBuildParameters;
|
||||
bool replaceAssetPathWithAddress = builtinBuildParameters.ReplaceAssetPathWithAddress;
|
||||
CreateManifestFile(true, true, replaceAssetPathWithAddress, context);
|
||||
}
|
||||
|
||||
protected override string[] GetBundleDepends(BuildContext context, string bundleName)
|
||||
|
||||
@@ -40,17 +40,17 @@ namespace YooAsset.Editor
|
||||
throw new Exception(message);
|
||||
}
|
||||
}
|
||||
protected override string GetBundleFileHash(BuildBundleInfo bundleInfo, BuildParametersContext buildParametersContext)
|
||||
protected override string GetBundleFileHash(BuildBundleInfo bundleInfo, BuildContext context)
|
||||
{
|
||||
string filePath = bundleInfo.PackageSourceFilePath;
|
||||
return HashUtility.FileMD5(filePath);
|
||||
}
|
||||
protected override uint GetBundleFileCRC(BuildBundleInfo bundleInfo, BuildParametersContext buildParametersContext)
|
||||
protected override uint GetBundleFileCRC(BuildBundleInfo bundleInfo, BuildContext context)
|
||||
{
|
||||
string filePath = bundleInfo.PackageSourceFilePath;
|
||||
return HashUtility.FileCRC32Value(filePath);
|
||||
}
|
||||
protected override long GetBundleFileSize(BuildBundleInfo bundleInfo, BuildParametersContext buildParametersContext)
|
||||
protected override long GetBundleFileSize(BuildBundleInfo bundleInfo, BuildContext context)
|
||||
{
|
||||
string filePath = bundleInfo.PackageSourceFilePath;
|
||||
return FileUtility.GetFileSize(filePath);
|
||||
|
||||
@@ -27,6 +27,12 @@ namespace YooAsset.Editor
|
||||
/// </summary>
|
||||
public bool IgnoreTypeTreeChanges = true;
|
||||
|
||||
/// <summary>
|
||||
/// 使用可寻址地址代替资源路径
|
||||
/// 说明:开启此项可以节省运行时清单占用的内存!
|
||||
/// </summary>
|
||||
public bool ReplaceAssetPathWithAddress = false;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取内置构建管线的构建选项
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace YooAsset.Editor
|
||||
{
|
||||
void IBuildTask.Run(BuildContext context)
|
||||
{
|
||||
CreateManifestFile(false, false, context);
|
||||
CreateManifestFile(false, true, false, context);
|
||||
}
|
||||
|
||||
protected override string[] GetBundleDepends(BuildContext context, string bundleName)
|
||||
|
||||
@@ -19,16 +19,16 @@ namespace YooAsset.Editor
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
protected override string GetBundleFileHash(BuildBundleInfo bundleInfo, BuildParametersContext buildParametersContext)
|
||||
protected override string GetBundleFileHash(BuildBundleInfo bundleInfo, BuildContext context)
|
||||
{
|
||||
string filePath = bundleInfo.PackageSourceFilePath;
|
||||
return GetFilePathTempHash(filePath);
|
||||
}
|
||||
protected override uint GetBundleFileCRC(BuildBundleInfo bundleInfo, BuildParametersContext buildParametersContext)
|
||||
protected override uint GetBundleFileCRC(BuildBundleInfo bundleInfo, BuildContext context)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
protected override long GetBundleFileSize(BuildBundleInfo bundleInfo, BuildParametersContext buildParametersContext)
|
||||
protected override long GetBundleFileSize(BuildBundleInfo bundleInfo, BuildContext context)
|
||||
{
|
||||
return GetBundleTempSize(bundleInfo);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace YooAsset.Editor
|
||||
{
|
||||
void IBuildTask.Run(BuildContext context)
|
||||
{
|
||||
CreateManifestFile(false, true, context);
|
||||
CreateManifestFile(false, true, false, context);
|
||||
}
|
||||
|
||||
protected override string[] GetBundleDepends(BuildContext context, string bundleName)
|
||||
|
||||
@@ -15,27 +15,55 @@ namespace YooAsset.Editor
|
||||
|
||||
protected override string GetUnityHash(BuildBundleInfo bundleInfo, BuildContext context)
|
||||
{
|
||||
string filePath = bundleInfo.PackageSourceFilePath;
|
||||
return HashUtility.FileMD5(filePath);
|
||||
var buildParametersContext = context.GetContextObject<BuildParametersContext>();
|
||||
var rawFileBuildParameters = buildParametersContext.Parameters as RawFileBuildParameters;
|
||||
if (rawFileBuildParameters.IncludePathInHash)
|
||||
{
|
||||
string filePath = bundleInfo.PackageSourceFilePath;
|
||||
return GetFileMD5IncludePath(filePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
string filePath = bundleInfo.PackageSourceFilePath;
|
||||
return HashUtility.FileMD5(filePath);
|
||||
}
|
||||
}
|
||||
protected override uint GetUnityCRC(BuildBundleInfo bundleInfo, BuildContext context)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
protected override string GetBundleFileHash(BuildBundleInfo bundleInfo, BuildParametersContext buildParametersContext)
|
||||
protected override string GetBundleFileHash(BuildBundleInfo bundleInfo, BuildContext context)
|
||||
{
|
||||
string filePath = bundleInfo.PackageSourceFilePath;
|
||||
return HashUtility.FileMD5(filePath);
|
||||
var buildParametersContext = context.GetContextObject<BuildParametersContext>();
|
||||
var rawFileBuildParameters = buildParametersContext.Parameters as RawFileBuildParameters;
|
||||
if (rawFileBuildParameters.IncludePathInHash)
|
||||
{
|
||||
string filePath = bundleInfo.PackageSourceFilePath;
|
||||
return GetFileMD5IncludePath(filePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
string filePath = bundleInfo.PackageSourceFilePath;
|
||||
return HashUtility.FileMD5(filePath);
|
||||
}
|
||||
}
|
||||
protected override uint GetBundleFileCRC(BuildBundleInfo bundleInfo, BuildParametersContext buildParametersContext)
|
||||
protected override uint GetBundleFileCRC(BuildBundleInfo bundleInfo, BuildContext context)
|
||||
{
|
||||
string filePath = bundleInfo.PackageSourceFilePath;
|
||||
return HashUtility.FileCRC32Value(filePath);
|
||||
}
|
||||
protected override long GetBundleFileSize(BuildBundleInfo bundleInfo, BuildParametersContext buildParametersContext)
|
||||
protected override long GetBundleFileSize(BuildBundleInfo bundleInfo, BuildContext context)
|
||||
{
|
||||
string filePath = bundleInfo.PackageSourceFilePath;
|
||||
return FileUtility.GetFileSize(filePath);
|
||||
}
|
||||
|
||||
private string GetFileMD5IncludePath(string filePath)
|
||||
{
|
||||
string pathHash = HashUtility.StringMD5(filePath.ToLowerInvariant());
|
||||
string contentHash = HashUtility.FileMD5(filePath);
|
||||
string combined = pathHash + contentHash;
|
||||
return HashUtility.StringMD5(combined);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,5 +6,9 @@ namespace YooAsset.Editor
|
||||
{
|
||||
public class RawFileBuildParameters : BuildParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件哈希值计算包含路径信息
|
||||
/// </summary>
|
||||
public bool IncludePathInHash = false;
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,8 @@ namespace YooAsset.Editor
|
||||
var scriptableBuildParameters = buildParametersContext.Parameters as ScriptableBuildParameters;
|
||||
|
||||
// 构建内容
|
||||
var buildContent = new BundleBuildContent(buildMapContext.GetPipelineBuilds());
|
||||
var bundleBuilds = buildMapContext.GetPipelineBuilds(scriptableBuildParameters.ReplaceAssetPathWithAddress);
|
||||
var buildContent = new BundleBuildContent(bundleBuilds);
|
||||
|
||||
// 开始构建
|
||||
IBundleBuildResults buildResults;
|
||||
|
||||
@@ -13,7 +13,10 @@ namespace YooAsset.Editor
|
||||
|
||||
void IBuildTask.Run(BuildContext context)
|
||||
{
|
||||
CreateManifestFile(true, true, context);
|
||||
var buildParametersContext = context.GetContextObject<BuildParametersContext>();
|
||||
var scriptableBuildParameters = buildParametersContext.Parameters as ScriptableBuildParameters;
|
||||
bool replaceAssetPathWithAddress = scriptableBuildParameters.ReplaceAssetPathWithAddress;
|
||||
CreateManifestFile(true, true, replaceAssetPathWithAddress, context);
|
||||
}
|
||||
|
||||
protected override string[] GetBundleDepends(BuildContext context, string bundleName)
|
||||
|
||||
@@ -40,17 +40,17 @@ namespace YooAsset.Editor
|
||||
throw new Exception(message);
|
||||
}
|
||||
}
|
||||
protected override string GetBundleFileHash(BuildBundleInfo bundleInfo, BuildParametersContext buildParametersContext)
|
||||
protected override string GetBundleFileHash(BuildBundleInfo bundleInfo, BuildContext context)
|
||||
{
|
||||
string filePath = bundleInfo.PackageSourceFilePath;
|
||||
return HashUtility.FileMD5(filePath);
|
||||
}
|
||||
protected override uint GetBundleFileCRC(BuildBundleInfo bundleInfo, BuildParametersContext buildParametersContext)
|
||||
protected override uint GetBundleFileCRC(BuildBundleInfo bundleInfo, BuildContext context)
|
||||
{
|
||||
string filePath = bundleInfo.PackageSourceFilePath;
|
||||
return HashUtility.FileCRC32Value(filePath);
|
||||
}
|
||||
protected override long GetBundleFileSize(BuildBundleInfo bundleInfo, BuildParametersContext buildParametersContext)
|
||||
protected override long GetBundleFileSize(BuildBundleInfo bundleInfo, BuildContext context)
|
||||
{
|
||||
string filePath = bundleInfo.PackageSourceFilePath;
|
||||
return FileUtility.GetFileSize(filePath);
|
||||
|
||||
@@ -29,6 +29,12 @@ namespace YooAsset.Editor
|
||||
/// </summary>
|
||||
public bool IgnoreTypeTreeChanges = true;
|
||||
|
||||
/// <summary>
|
||||
/// 使用可寻址地址代替资源路径
|
||||
/// 说明:开启此项可以节省运行时清单占用的内存!
|
||||
/// </summary>
|
||||
public bool ReplaceAssetPathWithAddress = false;
|
||||
|
||||
/// <summary>
|
||||
/// 自动建立资源对象对图集的依赖关系
|
||||
/// </summary>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
|
||||
namespace YooAsset.Editor
|
||||
{
|
||||
public class EncryptionNone : IEncryptionServices
|
||||
public class EncryptionNone : IBundleEncryptionServices
|
||||
{
|
||||
public EncryptResult Encrypt(EncryptFileInfo fileInfo)
|
||||
public EncryptResult Encrypt(EncryptBundleInfo fileInfo)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -82,14 +82,14 @@ namespace YooAsset.Editor
|
||||
}
|
||||
private void BuildButton_clicked()
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("提示", $"开始构建资源包[{PackageName}]!", "Yes", "No"))
|
||||
if (EditorUtility.DisplayDialog("Info", $"Start building resource package [{PackageName}]!", "Yes", "No"))
|
||||
{
|
||||
EditorTools.ClearUnityConsole();
|
||||
EditorApplication.delayCall += ExecuteBuild;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[Build] 打包已经取消");
|
||||
Debug.LogWarning("[Build] Packaging has been canceled.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -42,14 +42,14 @@ namespace YooAsset.Editor
|
||||
}
|
||||
private void BuildButton_clicked()
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("提示", $"开始构建资源包[{PackageName}]!", "Yes", "No"))
|
||||
if (EditorUtility.DisplayDialog("Info", $"Start building resource package [{PackageName}]!", "Yes", "No"))
|
||||
{
|
||||
EditorTools.ClearUnityConsole();
|
||||
EditorApplication.delayCall += ExecuteBuild;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[Build] 打包已经取消");
|
||||
Debug.LogWarning("[Build] Packaging has been canceled.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -77,14 +77,14 @@ namespace YooAsset.Editor
|
||||
}
|
||||
private void BuildButton_clicked()
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("提示", $"开始构建资源包[{PackageName}]!", "Yes", "No"))
|
||||
if (EditorUtility.DisplayDialog("Info", $"Start building resource package [{PackageName}]!", "Yes", "No"))
|
||||
{
|
||||
EditorTools.ClearUnityConsole();
|
||||
EditorApplication.delayCall += ExecuteBuild;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[Build] 打包已经取消");
|
||||
Debug.LogWarning("[Build] Packaging has been canceled.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -82,14 +82,14 @@ namespace YooAsset.Editor
|
||||
}
|
||||
private void BuildButton_clicked()
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("提示", $"开始构建资源包[{PackageName}]!", "Yes", "No"))
|
||||
if (EditorUtility.DisplayDialog("Info", $"Start building resource package [{PackageName}]!", "Yes", "No"))
|
||||
{
|
||||
EditorTools.ClearUnityConsole();
|
||||
EditorApplication.delayCall += ExecuteBuild;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[Build] 打包已经取消");
|
||||
Debug.LogWarning("[Build] Packaging has been canceled.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -236,6 +236,7 @@ namespace YooAsset.Editor
|
||||
|
||||
// 包裹名称
|
||||
_packageNameTxt = root.Q<TextField>("PackageName");
|
||||
_packageNameTxt.isDelayed = true;
|
||||
_packageNameTxt.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
var selectPackage = _packageListView.selectedItem as AssetBundleCollectorPackage;
|
||||
@@ -249,6 +250,7 @@ namespace YooAsset.Editor
|
||||
|
||||
// 包裹备注
|
||||
_packageDescTxt = root.Q<TextField>("PackageDesc");
|
||||
_packageDescTxt.isDelayed = true;
|
||||
_packageDescTxt.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
var selectPackage = _packageListView.selectedItem as AssetBundleCollectorPackage;
|
||||
@@ -286,6 +288,7 @@ namespace YooAsset.Editor
|
||||
|
||||
// 分组名称
|
||||
_groupNameTxt = root.Q<TextField>("GroupName");
|
||||
_groupNameTxt.isDelayed = true;
|
||||
_groupNameTxt.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
var selectPackage = _packageListView.selectedItem as AssetBundleCollectorPackage;
|
||||
@@ -300,6 +303,7 @@ namespace YooAsset.Editor
|
||||
|
||||
// 分组备注
|
||||
_groupDescTxt = root.Q<TextField>("GroupDesc");
|
||||
_groupDescTxt.isDelayed = true;
|
||||
_groupDescTxt.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
var selectPackage = _packageListView.selectedItem as AssetBundleCollectorPackage;
|
||||
@@ -314,6 +318,7 @@ namespace YooAsset.Editor
|
||||
|
||||
// 分组的资源标签
|
||||
_groupTagsTxt = root.Q<TextField>("GroupTags");
|
||||
_groupTagsTxt.isDelayed = true;
|
||||
_groupTagsTxt.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
var selectPackage = _packageListView.selectedItem as AssetBundleCollectorPackage;
|
||||
@@ -817,6 +822,7 @@ namespace YooAsset.Editor
|
||||
var textField = new TextField();
|
||||
textField.name = "TextField0";
|
||||
textField.label = "User Data";
|
||||
textField.isDelayed = true;
|
||||
textField.style.width = 200;
|
||||
elementBottom.Add(textField);
|
||||
var label = textField.Q<Label>();
|
||||
@@ -826,6 +832,7 @@ namespace YooAsset.Editor
|
||||
var textField = new TextField();
|
||||
textField.name = "TextField1";
|
||||
textField.label = "Asset Tags";
|
||||
textField.isDelayed = true;
|
||||
textField.style.width = 100;
|
||||
textField.style.marginLeft = 20;
|
||||
textField.style.flexGrow = 1;
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<ui:VisualElement name="PackageContainer" style="width: 200px; flex-grow: 0; background-color: rgb(67, 67, 67); border-left-width: 5px; border-right-width: 5px; border-top-width: 5px; border-bottom-width: 5px;">
|
||||
<ui:Label text="Packages" display-tooltip-when-elided="true" name="PackageTitle" style="background-color: rgb(89, 89, 89); -unity-text-align: upper-center; -unity-font-style: bold; border-left-width: 5px; border-right-width: 5px; border-top-width: 5px; border-bottom-width: 5px; font-size: 12px;" />
|
||||
<ui:ListView focusable="true" name="PackageListView" item-height="20" virtualization-method="DynamicHeight" reorderable="true" reorder-mode="Animated" style="flex-grow: 1;" />
|
||||
<ui:VisualElement name="PackageAddContainer" style="height: 20px; flex-direction: row; justify-content: center;">
|
||||
<ui:VisualElement name="PackageAddContainer" style="height: 20px; flex-direction: row; justify-content: center; flex-shrink: 0;">
|
||||
<ui:Button text=" - " display-tooltip-when-elided="true" name="RemoveBtn" />
|
||||
<ui:Button text=" + " display-tooltip-when-elided="true" name="AddBtn" />
|
||||
</ui:VisualElement>
|
||||
@@ -40,7 +40,7 @@
|
||||
<ui:TextField picking-mode="Ignore" label="Package Name" name="PackageName" style="flex-direction: column;" />
|
||||
<ui:TextField picking-mode="Ignore" label="Package Desc" name="PackageDesc" style="flex-direction: column;" />
|
||||
<ui:ListView focusable="true" name="GroupListView" item-height="20" virtualization-method="DynamicHeight" reorderable="true" reorder-mode="Animated" style="flex-grow: 1;" />
|
||||
<ui:VisualElement name="GroupAddContainer" style="height: 20px; flex-direction: row; justify-content: center;">
|
||||
<ui:VisualElement name="GroupAddContainer" style="height: 20px; flex-direction: row; justify-content: center; flex-shrink: 0;">
|
||||
<ui:Button text=" - " display-tooltip-when-elided="true" name="RemoveBtn" />
|
||||
<ui:Button text=" + " display-tooltip-when-elided="true" name="AddBtn" />
|
||||
</ui:VisualElement>
|
||||
|
||||
@@ -88,8 +88,8 @@ namespace YooAsset.Editor
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ClearDatabase(true);
|
||||
Debug.LogError($"Failed to load cache database : {ex.Message}");
|
||||
ClearDatabase(true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -169,7 +169,8 @@ namespace YooAsset.Editor
|
||||
File.Delete(_databaseFilePath);
|
||||
}
|
||||
|
||||
_database.Clear();
|
||||
if (_database != null)
|
||||
_database.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -81,6 +81,7 @@ namespace YooAsset.Editor
|
||||
public ECompressOption CompressOption;
|
||||
public bool DisableWriteTypeTree;
|
||||
public bool IgnoreTypeTreeChanges;
|
||||
public bool ReplaceAssetPathWithAddress;
|
||||
public bool WriteLinkXML = true;
|
||||
public string CacheServerHost;
|
||||
public int CacheServerPort;
|
||||
|
||||
@@ -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}";
|
||||
|
||||
@@ -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}";
|
||||
|
||||
@@ -74,6 +74,7 @@ namespace YooAsset.Editor
|
||||
BindListViewItem("CompressOption", $"{buildReport.Summary.CompressOption}");
|
||||
BindListViewItem("DisableWriteTypeTree", $"{buildReport.Summary.DisableWriteTypeTree}");
|
||||
BindListViewItem("IgnoreTypeTreeChanges", $"{buildReport.Summary.IgnoreTypeTreeChanges}");
|
||||
BindListViewItem("ReplaceAssetPathWithAddress", $"{buildReport.Summary.ReplaceAssetPathWithAddress}");
|
||||
BindListViewItem(string.Empty, string.Empty);
|
||||
|
||||
BindListViewHeader("Build Results");
|
||||
|
||||
@@ -41,6 +41,8 @@ namespace YooAsset.Editor
|
||||
Texture,
|
||||
RenderTexture,
|
||||
VideoClip,
|
||||
PlayableAsset,
|
||||
TimelineAsset
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
945
Assets/YooAsset/Runtime/DiagnosticSystem/README.md
Normal file
945
Assets/YooAsset/Runtime/DiagnosticSystem/README.md
Normal file
@@ -0,0 +1,945 @@
|
||||
# DiagnosticSystem 诊断系统
|
||||
|
||||
## 模块概述
|
||||
|
||||
DiagnosticSystem 是 YooAsset 的**远程调试诊断系统**,提供运行时资源管理状态的实时可视化和性能分析能力。该系统通过编辑器窗口与运行时游戏进行双向通信,实时采集和展示资源加载、Bundle 管理、异步操作等调试信息。
|
||||
|
||||
### 核心特性
|
||||
|
||||
- **实时远程调试**:在 Unity 编辑器中查看游戏运行时的资源管理状态
|
||||
- **完整状态快照**:采集所有资源、Bundle、异步操作的实时信息
|
||||
- **历史数据回溯**:缓存最近 500 帧数据,支持时间回溯分析
|
||||
- **双模式采样**:支持单次采样和自动连续采样
|
||||
- **低性能开销**:按需采样,无需连续监控
|
||||
|
||||
### 模块统计
|
||||
|
||||
| 组件 | 职责 |
|
||||
|------|------|
|
||||
| 核心通信 | RemoteDebuggerInRuntime + 双连接层 |
|
||||
| 数据结构 | 5 个调试信息结构体 |
|
||||
| 命令系统 | RemoteCommand 命令定义 |
|
||||
| **总计** | 10 个核心文件,完整的远程诊断框架 |
|
||||
|
||||
---
|
||||
|
||||
## 设计目标
|
||||
|
||||
| 目标 | 说明 |
|
||||
|------|------|
|
||||
| **实时可视化** | 在编辑器中实时查看运行时资源状态 |
|
||||
| **性能监控** | 收集加载耗时、引用计数、下载进度等性能指标 |
|
||||
| **内存诊断** | 追踪资源出生场景、引用计数,识别潜在内存泄漏 |
|
||||
| **操作追踪** | 显示异步操作树,包括嵌套和依赖关系 |
|
||||
| **低开销设计** | 按需采样而非连续监控,DEBUG 模式自动启用 |
|
||||
|
||||
---
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
DiagnosticSystem/
|
||||
├── RemoteDebuggerDefine.cs # 全局定义和常量
|
||||
├── RemoteCommand.cs # 命令定义和序列化
|
||||
├── DebugReport.cs # 调试报告(顶层容器)
|
||||
├── DebugPackageData.cs # 包级调试数据
|
||||
├── DebugProviderInfo.cs # 资源加载器调试信息
|
||||
├── DebugBundleInfo.cs # 资源包调试信息
|
||||
├── DebugOperationInfo.cs # 异步操作调试信息
|
||||
├── RemoteDebuggerInRuntime.cs # 运行时调试器主类
|
||||
├── RemotePlayerConnection.cs # 编辑器模拟连接层
|
||||
└── RemoteEditorConnection.cs # 运行时模拟连接层
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心类说明
|
||||
|
||||
### RemoteDebuggerDefine
|
||||
|
||||
全局定义类,包含调试器版本和通信协议的 GUID 标识符。
|
||||
|
||||
```csharp
|
||||
internal class RemoteDebuggerDefine
|
||||
{
|
||||
// 调试器版本(用于版本校验)
|
||||
public const string DebuggerVersion = "2.3.3";
|
||||
|
||||
// 消息标识符(GUID)
|
||||
public static readonly Guid kMsgPlayerSendToEditor =
|
||||
new Guid("e34a5702dd353724aa315fb8011f08c3"); // 运行时→编辑器
|
||||
|
||||
public static readonly Guid kMsgEditorSendToPlayer =
|
||||
new Guid("4d1926c9df5b052469a1c63448b7609a"); // 编辑器→运行时
|
||||
}
|
||||
```
|
||||
|
||||
### RemoteCommand
|
||||
|
||||
命令定义类,用于编辑器向运行时发送采样指令。
|
||||
|
||||
```csharp
|
||||
internal enum ERemoteCommand
|
||||
{
|
||||
SampleOnce = 0, // 单次采样
|
||||
SampleAuto = 1, // 自动采样(连续)
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class RemoteCommand
|
||||
{
|
||||
public int CommandType; // ERemoteCommand 枚举值
|
||||
public string CommandParam; // 命令参数
|
||||
|
||||
// 序列化/反序列化(JSON 格式)
|
||||
public static byte[] Serialize(RemoteCommand command)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(JsonUtility.ToJson(command));
|
||||
}
|
||||
|
||||
public static RemoteCommand Deserialize(byte[] data)
|
||||
{
|
||||
return JsonUtility.FromJson<RemoteCommand>(Encoding.UTF8.GetString(data));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**命令示例:**
|
||||
|
||||
```json
|
||||
// 单次采样
|
||||
{
|
||||
"CommandType": 0,
|
||||
"CommandParam": ""
|
||||
}
|
||||
|
||||
// 开启自动采样
|
||||
{
|
||||
"CommandType": 1,
|
||||
"CommandParam": "open"
|
||||
}
|
||||
|
||||
// 关闭自动采样
|
||||
{
|
||||
"CommandType": 1,
|
||||
"CommandParam": "close"
|
||||
}
|
||||
```
|
||||
|
||||
### DebugReport
|
||||
|
||||
调试报告容器,包含完整的系统状态快照。
|
||||
|
||||
```csharp
|
||||
[Serializable]
|
||||
internal class DebugReport
|
||||
{
|
||||
// 调试器版本(用于版本校验)
|
||||
public string DebuggerVersion = RemoteDebuggerDefine.DebuggerVersion;
|
||||
|
||||
// 游戏帧数
|
||||
public int FrameCount;
|
||||
|
||||
// 包级调试数据列表(一个游戏可能有多个资源包)
|
||||
public List<DebugPackageData> PackageDatas = new List<DebugPackageData>(10);
|
||||
|
||||
// 序列化/反序列化
|
||||
public static byte[] Serialize(DebugReport debugReport);
|
||||
public static DebugReport Deserialize(byte[] data);
|
||||
}
|
||||
```
|
||||
|
||||
### DebugPackageData
|
||||
|
||||
包级调试数据,包含单个资源包的所有诊断信息。
|
||||
|
||||
```csharp
|
||||
[Serializable]
|
||||
internal class DebugPackageData
|
||||
{
|
||||
// 资源包名称
|
||||
public string PackageName;
|
||||
|
||||
// 资源加载器列表
|
||||
public List<DebugProviderInfo> ProviderInfos = new List<DebugProviderInfo>(1000);
|
||||
|
||||
// 资源包列表
|
||||
public List<DebugBundleInfo> BundleInfos = new List<DebugBundleInfo>(1000);
|
||||
|
||||
// 异步操作列表
|
||||
public List<DebugOperationInfo> OperationInfos = new List<DebugOperationInfo>(1000);
|
||||
|
||||
// 运行时查询字典(非序列化,按需构建)
|
||||
[NonSerialized]
|
||||
public Dictionary<string, DebugBundleInfo> BundleInfoDic;
|
||||
|
||||
// 延迟解析字典
|
||||
public DebugBundleInfo GetBundleInfo(string bundleName);
|
||||
}
|
||||
```
|
||||
|
||||
### DebugProviderInfo
|
||||
|
||||
资源加载器(Provider)的调试信息。
|
||||
|
||||
```csharp
|
||||
[Serializable]
|
||||
internal struct DebugProviderInfo : IComparer<DebugProviderInfo>, IComparable<DebugProviderInfo>
|
||||
{
|
||||
public string PackageName; // 所属包名
|
||||
public string AssetPath; // 资源路径(如 "Assets/Prefabs/Player.prefab")
|
||||
public string SpawnScene; // 资源加载时的活跃场景名
|
||||
public string BeginTime; // 加载开始时间(格式:HH:mm:ss.fff)
|
||||
public long LoadingTime; // 加载耗时(单位:毫秒)
|
||||
public int RefCount; // 引用计数
|
||||
public string Status; // 加载状态(None/Processing/Succeed/Failed)
|
||||
public List<string> DependBundles; // 依赖的资源包名列表
|
||||
|
||||
// 按 AssetPath 字母排序
|
||||
public int CompareTo(DebugProviderInfo other);
|
||||
}
|
||||
```
|
||||
|
||||
**关键诊断价值:**
|
||||
- `SpawnScene`:追踪资源在哪个场景被加载,帮助识别资源泄漏
|
||||
- `LoadingTime`:性能分析,识别加载慢的资源
|
||||
- `RefCount`:引用计数监控,RefCount > 0 表示资源仍在使用
|
||||
- `DependBundles`:依赖分析,理解资源加载的完整依赖链
|
||||
|
||||
### DebugBundleInfo
|
||||
|
||||
资源包(Bundle)的调试信息。
|
||||
|
||||
```csharp
|
||||
[Serializable]
|
||||
internal struct DebugBundleInfo : IComparer<DebugBundleInfo>, IComparable<DebugBundleInfo>
|
||||
{
|
||||
public string BundleName; // 资源包名称
|
||||
public int RefCount; // 引用计数(当前有多少个 Provider 在使用)
|
||||
public string Status; // 加载状态
|
||||
public List<string> ReferenceBundles; // 反向依赖(谁引用了我)
|
||||
|
||||
// 按 BundleName 字母排序
|
||||
public int CompareTo(DebugBundleInfo other);
|
||||
}
|
||||
```
|
||||
|
||||
**关键诊断价值:**
|
||||
- `RefCount`:Bundle 引用计数,为 0 时可以被卸载
|
||||
- `ReferenceBundles`:反向依赖分析,了解 Bundle 被哪些其他 Bundle 依赖
|
||||
|
||||
### DebugOperationInfo
|
||||
|
||||
异步操作的调试信息,支持递归树结构。
|
||||
|
||||
```csharp
|
||||
[Serializable]
|
||||
internal struct DebugOperationInfo : IComparer<DebugOperationInfo>, IComparable<DebugOperationInfo>
|
||||
{
|
||||
public string OperationName; // 操作类名(如 "LoadAssetOperation")
|
||||
public string OperationDesc; // 操作说明(自定义描述)
|
||||
public uint Priority; // 优先级(用于操作排序)
|
||||
public float Progress; // 进度(0.0 - 1.0)
|
||||
public string BeginTime; // 操作开始时间
|
||||
public long ProcessTime; // 处理耗时(单位:毫秒)
|
||||
public string Status; // 操作状态(None/Processing/Succeed/Failed)
|
||||
public List<DebugOperationInfo> Childs; // 子操作列表(支持嵌套树结构)
|
||||
|
||||
public int CompareTo(DebugOperationInfo other);
|
||||
}
|
||||
```
|
||||
|
||||
**递归树结构示例:**
|
||||
```
|
||||
InitializationOperation
|
||||
├─ LoadManifestOperation
|
||||
│ ├─ LoadBundleFileOperation (manifest.bundle)
|
||||
│ └─ DeserializeManifestOperation
|
||||
└─ InitFileSystemOperation
|
||||
```
|
||||
|
||||
**关键诊断价值:**
|
||||
- `OperationName`:操作类型识别
|
||||
- `ProcessTime`:性能瓶颈分析
|
||||
- `Childs`:操作依赖关系可视化
|
||||
|
||||
---
|
||||
|
||||
## 通信系统
|
||||
|
||||
### RemoteDebuggerInRuntime
|
||||
|
||||
运行时调试器主类,负责接收命令、采样数据、发送报告。
|
||||
|
||||
```csharp
|
||||
internal class RemoteDebuggerInRuntime : MonoBehaviour
|
||||
{
|
||||
// 采样控制标志
|
||||
private static bool _sampleOnce = false; // 单次采样
|
||||
private static bool _autoSample = false; // 连续采样
|
||||
|
||||
// 运行时初始化
|
||||
[RuntimeInitializeOnLoadMethod]
|
||||
private static void RuntimeInitializeOnLoad()
|
||||
{
|
||||
_sampleOnce = false;
|
||||
_autoSample = false;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
RemotePlayerConnection.Instance.Initialize();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
// 注册命令接收回调
|
||||
RemotePlayerConnection.Instance.Register(
|
||||
RemoteDebuggerDefine.kMsgEditorSendToPlayer,
|
||||
OnHandleEditorMessage);
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
// 采样逻辑(在一帧的最后执行)
|
||||
if (_autoSample || _sampleOnce)
|
||||
{
|
||||
_sampleOnce = false;
|
||||
var debugReport = YooAssets.GetDebugReport();
|
||||
var data = DebugReport.Serialize(debugReport);
|
||||
|
||||
RemotePlayerConnection.Instance.Send(
|
||||
RemoteDebuggerDefine.kMsgPlayerSendToEditor,
|
||||
data);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnHandleEditorMessage(MessageEventArgs args)
|
||||
{
|
||||
var command = RemoteCommand.Deserialize(args.data);
|
||||
|
||||
if (command.CommandType == (int)ERemoteCommand.SampleOnce)
|
||||
{
|
||||
_sampleOnce = true;
|
||||
}
|
||||
else if (command.CommandType == (int)ERemoteCommand.SampleAuto)
|
||||
{
|
||||
_autoSample = (command.CommandParam == "open");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关键设计点:**
|
||||
1. **LateUpdate 时机**:确保该帧所有资源加载完成后再采样
|
||||
2. **状态重置**:`[RuntimeInitializeOnLoadMethod]` 确保编辑器重新编译时重置状态
|
||||
3. **DEBUG 模式自动启用**:通过 `#if DEBUG` 条件编译自动添加组件
|
||||
|
||||
### 双连接层架构
|
||||
|
||||
YooAsset 支持两种通信模式:
|
||||
|
||||
| 模式 | 使用场景 | 实现方式 |
|
||||
|------|----------|----------|
|
||||
| **编辑器模拟模式** | 开发调试 | `RemotePlayerConnection` + `RemoteEditorConnection`(虚拟连接) |
|
||||
| **发布版本** | 运营期监控 | Unity 的 `PlayerConnection` API(真实网络) |
|
||||
|
||||
#### RemotePlayerConnection(编辑器模拟模式)
|
||||
|
||||
```csharp
|
||||
internal class RemotePlayerConnection
|
||||
{
|
||||
private static RemotePlayerConnection _instance;
|
||||
private readonly Dictionary<Guid, UnityAction<MessageEventArgs>> _messageCallbacks;
|
||||
|
||||
public static RemotePlayerConnection Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
_instance = new RemotePlayerConnection();
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
public void Register(Guid messageID, UnityAction<MessageEventArgs> callback)
|
||||
{
|
||||
_messageCallbacks.Add(messageID, callback);
|
||||
}
|
||||
|
||||
public void Send(Guid messageID, byte[] data)
|
||||
{
|
||||
// 在编辑器模拟模式下,发送给虚拟编辑器连接
|
||||
RemoteEditorConnection.Instance.HandlePlayerMessage(messageID, data);
|
||||
}
|
||||
|
||||
internal void HandleEditorMessage(Guid messageID, byte[] data)
|
||||
{
|
||||
if (_messageCallbacks.TryGetValue(messageID, out var callback))
|
||||
{
|
||||
callback.Invoke(new MessageEventArgs { playerId = 0, data = data });
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### RemoteEditorConnection(运行时模拟模式)
|
||||
|
||||
```csharp
|
||||
internal class RemoteEditorConnection
|
||||
{
|
||||
private static RemoteEditorConnection _instance;
|
||||
private readonly Dictionary<Guid, UnityAction<MessageEventArgs>> _messageCallbacks;
|
||||
|
||||
public static RemoteEditorConnection Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
_instance = new RemoteEditorConnection();
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
public void Register(Guid messageID, UnityAction<MessageEventArgs> callback)
|
||||
{
|
||||
_messageCallbacks.Add(messageID, callback);
|
||||
}
|
||||
|
||||
public void Send(Guid messageID, byte[] data)
|
||||
{
|
||||
// 发送给虚拟运行时连接
|
||||
RemotePlayerConnection.Instance.HandleEditorMessage(messageID, data);
|
||||
}
|
||||
|
||||
internal void HandlePlayerMessage(Guid messageID, byte[] data)
|
||||
{
|
||||
if (_messageCallbacks.TryGetValue(messageID, out var callback))
|
||||
{
|
||||
callback.Invoke(new MessageEventArgs { playerId = 0, data = data });
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 通信协议
|
||||
|
||||
### 协议规范
|
||||
|
||||
**协议版本:** 2.3.3
|
||||
|
||||
**编码格式:**
|
||||
```
|
||||
C# 对象 → JsonUtility.ToJson() → JSON 字符串 → Encoding.UTF8.GetBytes() → byte[]
|
||||
```
|
||||
|
||||
**消息类型:**
|
||||
|
||||
| 方向 | GUID | 数据类型 | 说明 |
|
||||
|------|------|----------|------|
|
||||
| 编辑器→运行时 | `4d1926c9df5b052469a1c63448b7609a` | `RemoteCommand` | 采样命令 |
|
||||
| 运行时→编辑器 | `e34a5702dd353724aa315fb8011f08c3` | `DebugReport` | 调试报告 |
|
||||
|
||||
### 双向通信流程
|
||||
|
||||
```
|
||||
[编辑器 UI]
|
||||
│
|
||||
├─ 用户点击 "Sample" 按钮
|
||||
│ └─ 发送 RemoteCommand (SampleOnce)
|
||||
│
|
||||
└─ 用户开启 "Record" 开关
|
||||
└─ 发送 RemoteCommand (SampleAuto, "open")
|
||||
|
||||
↓ RemoteEditorConnection.Send()
|
||||
↓
|
||||
RemotePlayerConnection.HandleEditorMessage()
|
||||
↓ 触发回调
|
||||
|
||||
[运行时]
|
||||
RemoteDebuggerInRuntime.OnHandleEditorMessage()
|
||||
↓
|
||||
设置采样标志 (_sampleOnce 或 _autoSample)
|
||||
↓
|
||||
LateUpdate 中采样
|
||||
↓
|
||||
YooAssets.GetDebugReport()
|
||||
├─ 收集所有 ResourcePackage 数据
|
||||
├─ DebugProviderInfo[] (从 ProviderDic)
|
||||
├─ DebugBundleInfo[] (从 LoaderDic)
|
||||
└─ DebugOperationInfo[] (从 _operations)
|
||||
↓
|
||||
DebugReport.Serialize()
|
||||
↓
|
||||
RemotePlayerConnection.Send()
|
||||
↓
|
||||
RemoteEditorConnection.HandlePlayerMessage()
|
||||
↓
|
||||
|
||||
[编辑器]
|
||||
AssetBundleDebuggerWindow.OnHandlePlayerMessage()
|
||||
↓
|
||||
版本校验 (DebuggerVersion)
|
||||
↓
|
||||
RemotePlayerSession.AddDebugReport()
|
||||
↓
|
||||
缓存到历史记录 (最多 500 帧)
|
||||
↓
|
||||
UI 刷新显示
|
||||
```
|
||||
|
||||
### 版本校验机制
|
||||
|
||||
```csharp
|
||||
// 编辑器端版本校验
|
||||
private void OnHandlePlayerMessage(MessageEventArgs args)
|
||||
{
|
||||
var debugReport = DebugReport.Deserialize(args.data);
|
||||
|
||||
// 版本校验
|
||||
if (debugReport.DebuggerVersion != RemoteDebuggerDefine.DebuggerVersion)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"Debugger versions are inconsistent : " +
|
||||
$"{debugReport.DebuggerVersion} != {RemoteDebuggerDefine.DebuggerVersion}");
|
||||
return; // 丢弃不兼容的数据
|
||||
}
|
||||
|
||||
// 处理数据...
|
||||
}
|
||||
```
|
||||
|
||||
**设计意图:** 防止编辑器和运行时的调试器版本不一致导致的数据格式错误。
|
||||
|
||||
---
|
||||
|
||||
## 数据收集流程
|
||||
|
||||
### 完整采样流程
|
||||
|
||||
```
|
||||
RemoteDebuggerInRuntime.LateUpdate()
|
||||
↓
|
||||
检查 _sampleOnce 或 _autoSample 标志
|
||||
↓ YES
|
||||
调用 YooAssets.GetDebugReport()
|
||||
│
|
||||
├─ 初始化 DebugReport
|
||||
├─ 设置 FrameCount = Time.frameCount
|
||||
├─ 遍历每个 ResourcePackage:
|
||||
│ │
|
||||
│ └─ package.GetDebugPackageData()
|
||||
│ │
|
||||
│ ├─ 创建 DebugPackageData
|
||||
│ ├─ 设置 PackageName
|
||||
│ │
|
||||
│ ├─ 收集 ProviderInfos:
|
||||
│ │ ResourceManager.GetDebugProviderInfos()
|
||||
│ │ 遍历 ProviderDic:
|
||||
│ │ └─ 每个 ProviderOperation 提供:
|
||||
│ │ - MainAssetInfo.AssetPath
|
||||
│ │ - SpawnScene(场景名)
|
||||
│ │ - BeginTime(开始时间)
|
||||
│ │ - ProcessTime(耗时)
|
||||
│ │ - RefCount(引用计数)
|
||||
│ │ - Status(加载状态)
|
||||
│ │ - GetDebugDependBundles()(依赖列表)
|
||||
│ │
|
||||
│ ├─ 收集 BundleInfos:
|
||||
│ │ ResourceManager.GetDebugBundleInfos()
|
||||
│ │ 遍历 LoaderDic:
|
||||
│ │ └─ 每个 LoadBundleFileOperation 提供:
|
||||
│ │ - BundleName
|
||||
│ │ - RefCount
|
||||
│ │ - Status
|
||||
│ │ - FilterReferenceBundles()(反向依赖)
|
||||
│ │
|
||||
│ └─ 收集 OperationInfos:
|
||||
│ OperationSystem.GetDebugOperationInfos(PackageName)
|
||||
│ 遍历 _operations(按 PackageName 过滤):
|
||||
│ └─ 递归构建操作树:
|
||||
│ - GetType().Name(操作类名)
|
||||
│ - GetOperationDesc()(自定义描述)
|
||||
│ - Priority(优先级)
|
||||
│ - Progress(进度)
|
||||
│ - BeginTime(开始时间)
|
||||
│ - ProcessTime(耗时)
|
||||
│ - Status(状态)
|
||||
│ - Childs(子操作列表)
|
||||
│
|
||||
└─ 返回 DebugReport
|
||||
```
|
||||
|
||||
### 性能指标收集
|
||||
|
||||
#### 资源加载耗时
|
||||
|
||||
```csharp
|
||||
// AsyncOperationBase 中的自动计时
|
||||
private Stopwatch _watch = null;
|
||||
|
||||
internal void InternalStart()
|
||||
{
|
||||
if (_watch == null)
|
||||
{
|
||||
BeginTime = SpawnTimeToString(UnityEngine.Time.realtimeSinceStartup);
|
||||
_watch = Stopwatch.StartNew();
|
||||
}
|
||||
}
|
||||
|
||||
internal void InternalUpdate()
|
||||
{
|
||||
ProcessTime = _watch.ElapsedMilliseconds;
|
||||
// ... 持续计时
|
||||
}
|
||||
```
|
||||
|
||||
#### 场景信息追踪
|
||||
|
||||
```csharp
|
||||
// ProviderOperation.cs
|
||||
[Conditional("DEBUG")] // 仅在 DEBUG 模式下启用
|
||||
public void InitProviderDebugInfo()
|
||||
{
|
||||
SpawnScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
|
||||
}
|
||||
```
|
||||
|
||||
#### 引用计数监控
|
||||
|
||||
```csharp
|
||||
// ProviderOperation 和 LoadBundleFileOperation 都维护 RefCount
|
||||
public int RefCount { get; } // 当前被引用的次数
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 与其他模块的交互
|
||||
|
||||
```
|
||||
YooAssets (全局入口)
|
||||
│
|
||||
├─ 初始化阶段:
|
||||
│ #if DEBUG
|
||||
│ _driver.AddComponent<RemoteDebuggerInRuntime>();
|
||||
│ #endif
|
||||
│
|
||||
└─ 数据收集入口:
|
||||
GetDebugReport()
|
||||
├─ 遍历所有 ResourcePackage
|
||||
└─ 构建 DebugReport
|
||||
|
||||
ResourcePackage (资源包)
|
||||
│
|
||||
└─ GetDebugPackageData()
|
||||
├─ 调用 ResourceManager.GetDebugProviderInfos()
|
||||
├─ 调用 ResourceManager.GetDebugBundleInfos()
|
||||
└─ 调用 OperationSystem.GetDebugOperationInfos()
|
||||
|
||||
ResourceManager (资源管理器)
|
||||
│
|
||||
├─ GetDebugProviderInfos()
|
||||
│ └─ 遍历 ProviderDic (Dictionary<string, ProviderOperation>)
|
||||
│
|
||||
└─ GetDebugBundleInfos()
|
||||
└─ 遍历 LoaderDic (Dictionary<string, LoadBundleFileOperation>)
|
||||
|
||||
OperationSystem (操作系统)
|
||||
│
|
||||
└─ GetDebugOperationInfos(packageName)
|
||||
└─ 遍历 _operations (List<AsyncOperationBase>)
|
||||
└─ 递归收集子操作 (Childs)
|
||||
|
||||
AsyncOperationBase (异步操作基类)
|
||||
│
|
||||
├─ BeginTime:操作开始时间
|
||||
├─ ProcessTime:累计处理耗时
|
||||
├─ Status:操作状态
|
||||
├─ Progress:进度
|
||||
└─ GetOperationDesc():自定义描述
|
||||
|
||||
ProviderOperation (资源提供者)
|
||||
│
|
||||
├─ SpawnScene:加载时的活跃场景
|
||||
└─ GetDebugDependBundles():依赖包列表
|
||||
|
||||
LoadBundleFileOperation (Bundle 加载器)
|
||||
│
|
||||
├─ RefCount:引用计数
|
||||
└─ LoadBundleInfo:Bundle 信息
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 场景 1:运行时资源泄漏诊断
|
||||
|
||||
**问题:** 游戏切换场景后内存持续增长,怀疑有资源未释放。
|
||||
|
||||
**诊断步骤:**
|
||||
1. 打开 AssetBundle Debugger 窗口
|
||||
2. 开启 Record 模式(自动采样)
|
||||
3. 切换场景前后观察 ProviderInfos 列表
|
||||
4. 检查 `RefCount > 0` 且 `SpawnScene` 为旧场景的资源
|
||||
5. 定位未释放的资源和对应的代码位置
|
||||
|
||||
**关键字段:**
|
||||
- `SpawnScene`:资源在哪个场景被加载
|
||||
- `RefCount`:引用计数,应该为 0
|
||||
- `AssetPath`:资源路径,定位具体资源
|
||||
|
||||
### 场景 2:资源加载性能分析
|
||||
|
||||
**问题:** 首次加载场景卡顿严重。
|
||||
|
||||
**诊断步骤:**
|
||||
1. 单次采样(Sample Once)
|
||||
2. 切换到 Asset View
|
||||
3. 按 `LoadingTime` 降序排序
|
||||
4. 识别加载耗时最长的资源
|
||||
5. 分析 `DependBundles` 了解依赖链
|
||||
|
||||
**关键字段:**
|
||||
- `LoadingTime`:加载耗时(毫秒)
|
||||
- `DependBundles`:依赖的 Bundle 列表
|
||||
- `Status`:加载状态
|
||||
|
||||
### 场景 3:Bundle 引用分析
|
||||
|
||||
**问题:** 某个 Bundle 无法被卸载。
|
||||
|
||||
**诊断步骤:**
|
||||
1. 切换到 Bundle View
|
||||
2. 搜索目标 Bundle
|
||||
3. 检查 `RefCount` 和 `ReferenceBundles`
|
||||
4. 追踪哪些资源正在使用该 Bundle
|
||||
5. 定位未释放的资源引用
|
||||
|
||||
**关键字段:**
|
||||
- `RefCount`:Bundle 引用计数
|
||||
- `ReferenceBundles`:反向依赖列表
|
||||
- `Status`:Bundle 加载状态
|
||||
|
||||
### 场景 4:异步操作监控
|
||||
|
||||
**问题:** 复杂的初始化流程卡住,不知道在哪个步骤。
|
||||
|
||||
**诊断步骤:**
|
||||
1. 切换到 Operation View
|
||||
2. 查看操作树结构
|
||||
3. 检查 `Status` 为 `Processing` 的操作
|
||||
4. 分析 `Progress` 了解进度
|
||||
5. 通过 `Childs` 了解操作依赖关系
|
||||
|
||||
**关键字段:**
|
||||
- `OperationName`:操作类型
|
||||
- `OperationDesc`:操作描述
|
||||
- `Progress`:进度(0.0 - 1.0)
|
||||
- `Childs`:子操作列表
|
||||
|
||||
---
|
||||
|
||||
## 数据导出
|
||||
|
||||
### JSON 导出功能
|
||||
|
||||
编辑器窗口支持导出当前帧的完整调试数据为 JSON 文件。
|
||||
|
||||
**导出示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"DebuggerVersion": "2.3.3",
|
||||
"FrameCount": 1234,
|
||||
"PackageDatas": [
|
||||
{
|
||||
"PackageName": "DefaultPackage",
|
||||
"ProviderInfos": [
|
||||
{
|
||||
"PackageName": "DefaultPackage",
|
||||
"AssetPath": "Assets/Prefabs/Player.prefab",
|
||||
"SpawnScene": "GameScene",
|
||||
"BeginTime": "12:34:56.789",
|
||||
"LoadingTime": 45,
|
||||
"RefCount": 1,
|
||||
"Status": "Succeed",
|
||||
"DependBundles": [
|
||||
"assets_prefabs.bundle"
|
||||
]
|
||||
}
|
||||
],
|
||||
"BundleInfos": [
|
||||
{
|
||||
"BundleName": "assets_prefabs.bundle",
|
||||
"RefCount": 1,
|
||||
"Status": "Succeed",
|
||||
"ReferenceBundles": []
|
||||
}
|
||||
],
|
||||
"OperationInfos": [
|
||||
{
|
||||
"OperationName": "LoadAssetOperation",
|
||||
"OperationDesc": "Load assets_prefabs.bundle",
|
||||
"Priority": 0,
|
||||
"Progress": 1.0,
|
||||
"BeginTime": "12:34:56.745",
|
||||
"ProcessTime": 44,
|
||||
"Status": "Succeed",
|
||||
"Childs": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**用途:**
|
||||
- 离线分析和归档
|
||||
- 性能数据对比
|
||||
- 问题复现和追踪
|
||||
|
||||
---
|
||||
|
||||
## 系统架构图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Unity Editor Window │
|
||||
│ AssetBundleDebuggerWindow │
|
||||
│ ┌──────────────────────────────────────────────────────────────┐ │
|
||||
│ │ UI Controls: │ │
|
||||
│ │ - Sample Button (SampleOnce) │ │
|
||||
│ │ - Record Toggle (SampleAuto) │ │
|
||||
│ │ - View Mode Menu (Asset/Bundle/Operation View) │ │
|
||||
│ │ - Frame Slider (历史帧导航) │ │
|
||||
│ │ - Search Field (关键词搜索) │ │
|
||||
│ │ - Export Button (JSON 导出) │ │
|
||||
│ └──────────────────────────────────────────────────────────────┘ │
|
||||
└────────────┬──────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────▼───────────────────────────────────┐
|
||||
│ RemoteEditorConnection (虚拟连接) │
|
||||
│ - Register callbacks │
|
||||
│ - Send/Receive commands & reports │
|
||||
└─────┬──────────────────────────────┬────┘
|
||||
│ │
|
||||
┌────────▼─────────────┐ ┌──────────▼───────────────┐
|
||||
│ RemoteCommand │ │ DebugReport │
|
||||
│ (Serialize) │ │ (Deserialize) │
|
||||
│ ↓ JSON │ │ ← JSON │
|
||||
│ ↓ UTF-8 bytes │ │ ← UTF-8 bytes │
|
||||
└────────┬─────────────┘ └──────────┬───────────────┘
|
||||
│ │
|
||||
│ ═══════════════════════ │
|
||||
│ Internet / Emulation │
|
||||
│ ═══════════════════════ │
|
||||
│ │
|
||||
┌────────▼─────────────┐ ┌──────────▼───────────────┐
|
||||
│ PlayerConnection │ │ RemotePlayerConnection │
|
||||
│ (真实连接) │ │ (虚拟连接) │
|
||||
│ 或模拟连接 │ │ │
|
||||
└────────┬─────────────┘ └──────────┬───────────────┘
|
||||
│ │
|
||||
└──────────────┬───────────────┘
|
||||
│
|
||||
┌──────▼──────────────────────────────┐
|
||||
│ RemoteDebuggerInRuntime │
|
||||
│ (MonoBehaviour) │
|
||||
│ ┌──────────────────────────────┐ │
|
||||
│ │ _sampleOnce (bool) │ │
|
||||
│ │ _autoSample (bool) │ │
|
||||
│ └──────────────────────────────┘ │
|
||||
│ ┌──────────────────────────────┐ │
|
||||
│ │ Awake() - 初始化连接 │ │
|
||||
│ │ OnEnable() - 注册回调 │ │
|
||||
│ │ LateUpdate() - 采样触发 │ │
|
||||
│ │ OnHandleEditorMessage() - 收命令│ │
|
||||
│ └──────────────────────────────┘ │
|
||||
└──────────┬──────────────────────────┘
|
||||
│
|
||||
┌──────▼────────────────────┐
|
||||
│ YooAssets.GetDebugReport() │
|
||||
│ (全系统数据收集入口) │
|
||||
└──────┬────────────────────┘
|
||||
│
|
||||
┌───────────┼───────────┐
|
||||
│ │ │
|
||||
┌──────────▼──┐ ┌─────▼────┐ ┌──▼─────────────┐
|
||||
│ResourcePkg 1│ │ResourcePkg2 │ResourcePackageN│
|
||||
└──────┬──────┘ └────┬─────┘ └──┬──────────┘
|
||||
│ │ │
|
||||
└───────────────┼──────────┘
|
||||
│
|
||||
┌───────────▼──────────┐
|
||||
│ DebugPackageData │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ PackageName │ │
|
||||
│ │ ProviderInfos[] │ │
|
||||
│ │ BundleInfos[] │ │
|
||||
│ │ OperationInfos[]│ │
|
||||
│ └─────────────────┘ │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **DEBUG 模式自动启用**
|
||||
- 诊断系统仅在 `DEBUG` 模式下启用(通过 `#if DEBUG` 条件编译)
|
||||
- Release 构建中不会包含诊断代码,无性能开销
|
||||
|
||||
2. **版本兼容性**
|
||||
- 编辑器和运行时的调试器版本必须一致
|
||||
- 版本不一致的数据会被自动丢弃
|
||||
|
||||
3. **历史数据限制**
|
||||
- 最多缓存 500 帧历史数据(可配置)
|
||||
- 超过限制后,最早的数据会被移除
|
||||
|
||||
4. **JSON 序列化深度限制**
|
||||
- Unity JsonUtility 序列化深度限制为 10 层
|
||||
- 操作树(Childs)嵌套过深可能导致序列化失败
|
||||
|
||||
5. **性能开销**
|
||||
- 单次采样:低开销,仅在需要时采集
|
||||
- 自动采样:每帧采集,有一定性能开销,建议仅在需要时开启
|
||||
|
||||
6. **LateUpdate 时机**
|
||||
- 采样在 LateUpdate 中执行,确保该帧所有操作已更新
|
||||
- 避免在采样过程中资源状态发生变化
|
||||
|
||||
7. **非序列化字典**
|
||||
- `DebugPackageData.BundleInfoDic` 使用 `[NonSerialized]` 标记
|
||||
- 字典在首次查询时才构建,减少序列化开销
|
||||
|
||||
8. **场景追踪条件编译**
|
||||
- `SpawnScene` 字段仅在 DEBUG 模式下赋值(`[Conditional("DEBUG")]`)
|
||||
- Release 构建中该字段为空字符串
|
||||
|
||||
---
|
||||
|
||||
## 性能优化建议
|
||||
|
||||
1. **按需采样**
|
||||
- 优先使用单次采样(Sample Once)
|
||||
- 仅在需要连续监控时开启自动采样(Record)
|
||||
|
||||
2. **及时关闭 Record**
|
||||
- 分析完成后及时关闭自动采样
|
||||
- 避免不必要的性能开销
|
||||
|
||||
3. **合理设置历史缓存**
|
||||
- 根据内存情况调整 `MaxReportCount`
|
||||
- 默认 500 帧已足够大多数分析场景
|
||||
|
||||
4. **导出数据离线分析**
|
||||
- 对于复杂的性能问题,导出 JSON 数据
|
||||
- 在编辑器外使用专业工具进行分析
|
||||
|
||||
5. **Release 构建移除诊断代码**
|
||||
- 确保 Release 构建使用 Release 配置
|
||||
- 诊断代码通过 `#if DEBUG` 自动移除
|
||||
7
Assets/YooAsset/Runtime/DiagnosticSystem/README.md.meta
Normal file
7
Assets/YooAsset/Runtime/DiagnosticSystem/README.md.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5fa2b66c20800124c8dd5cb77e854ce3
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -36,7 +36,7 @@ namespace YooAsset
|
||||
public void Register(Guid messageID, UnityAction<MessageEventArgs> callback)
|
||||
{
|
||||
if (messageID == Guid.Empty)
|
||||
throw new ArgumentException("messageID is empty !");
|
||||
throw new ArgumentException("messageID is empty.");
|
||||
|
||||
if (_messageCallbacks.ContainsKey(messageID) == false)
|
||||
_messageCallbacks.Add(messageID, callback);
|
||||
@@ -49,7 +49,7 @@ namespace YooAsset
|
||||
public void Send(Guid messageID, byte[] data)
|
||||
{
|
||||
if (messageID == Guid.Empty)
|
||||
throw new ArgumentException("messageID is empty !");
|
||||
throw new ArgumentException("messageID is empty.");
|
||||
|
||||
// 接收对方的消息
|
||||
RemotePlayerConnection.Instance.HandleEditorMessage(messageID, data);
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace YooAsset
|
||||
public void Register(Guid messageID, UnityAction<MessageEventArgs> callback)
|
||||
{
|
||||
if (messageID == Guid.Empty)
|
||||
throw new ArgumentException("messageID is empty !");
|
||||
throw new ArgumentException("messageID is empty.");
|
||||
|
||||
if (_messageCallbacks.ContainsKey(messageID) == false)
|
||||
_messageCallbacks.Add(messageID, callback);
|
||||
@@ -49,7 +49,7 @@ namespace YooAsset
|
||||
public void Send(Guid messageID, byte[] data)
|
||||
{
|
||||
if (messageID == Guid.Empty)
|
||||
throw new ArgumentException("messageID is empty !");
|
||||
throw new ArgumentException("messageID is empty.");
|
||||
|
||||
// 接收对方的消息
|
||||
RemoteEditorConnection.Instance.HandlePlayerMessage(messageID, data);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9d6cb1ce5d510645866ad7c122abfab
|
||||
guid: bb70e2274ce5a5d419cfbd7212efdf4a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// UnityWebRequest 下载后台
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 基于 Unity 内置 UnityWebRequest 实现的下载后台。
|
||||
/// 支持自定义 UnityWebRequest 创建方式,例如添加证书验证、代理设置等。
|
||||
/// </remarks>
|
||||
internal sealed class UnityWebRequestBackend : IDownloadBackend
|
||||
{
|
||||
private readonly UnityWebRequestCreator _webRequestCreator;
|
||||
|
||||
/// <summary>
|
||||
/// 后端名称
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get { return nameof(UnityWebRequestBackend); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建 UnityWebRequest 下载后端(使用默认创建方式)
|
||||
/// </summary>
|
||||
public UnityWebRequestBackend() : this(null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建 UnityWebRequest 下载后端
|
||||
/// </summary>
|
||||
/// <param name="webRequestCreator">
|
||||
/// 自定义 UnityWebRequest 创建委托(可选)。
|
||||
/// 如果为 null,则使用默认的 UnityWebRequest 构造方式。
|
||||
/// </param>
|
||||
public UnityWebRequestBackend(UnityWebRequestCreator webRequestCreator)
|
||||
{
|
||||
_webRequestCreator = webRequestCreator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 驱动更新
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// UnityWebRequest 由 Unity 引擎自动驱动,无需额外更新。
|
||||
/// </remarks>
|
||||
public void Update()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
// 无需释放资源
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建 HEAD 请求
|
||||
/// </summary>
|
||||
public IDownloadHeadRequest CreateHeadRequest(DownloadDataRequestArgs args)
|
||||
{
|
||||
return new UnityWebRequestHeadDownloader(args, _webRequestCreator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建文件下载请求
|
||||
/// </summary>
|
||||
public IDownloadFileRequest CreateFileRequest(DownloadFileRequestArgs args)
|
||||
{
|
||||
return new UnityWebRequestFileDownloader(args, _webRequestCreator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建字节下载请求
|
||||
/// </summary>
|
||||
public IDownloadBytesRequest CreateBytesRequest(DownloadDataRequestArgs args)
|
||||
{
|
||||
return new UnityWebRequestBytesDownloader(args, _webRequestCreator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建文本下载请求
|
||||
/// </summary>
|
||||
public IDownloadTextRequest CreateTextRequest(DownloadDataRequestArgs args)
|
||||
{
|
||||
return new UnityWebRequestTextDownloader(args, _webRequestCreator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建 AssetBundle 下载请求
|
||||
/// </summary>
|
||||
public IDownloadAssetBundleRequest CreateAssetBundleRequest(DownloadAssetBundleRequestArgs args)
|
||||
{
|
||||
return new UnityWebRequestAssetBundleDownloader(args, _webRequestCreator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建模拟下载请求
|
||||
/// </summary>
|
||||
public IDownloadFileRequest CreateSimulateRequest(DownloadSimulateRequestArgs args)
|
||||
{
|
||||
return new VirtualFileDownloader(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 932e4c4574244dac8c27d9ddc156f8ff
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
using UnityEngine.Networking;
|
||||
using UnityEngine;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// 自定义下载器的请求委托
|
||||
/// </summary>
|
||||
public delegate UnityWebRequest UnityWebRequestCreator(string url, string method);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0fa3e6346decc4d4db3b03e6ae93e57a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d30af9086de4fb282c2cdd3d26244c0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// UnityWebRequest AssetBundle 下载器
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 下载并加载 Unity AssetBundle 资源包。
|
||||
/// 支持 Unity 内置缓存机制和 CRC 校验。
|
||||
/// </remarks>
|
||||
internal sealed class UnityWebRequestAssetBundleDownloader : UnityWebRequestDownloaderBase, IDownloadAssetBundleRequest
|
||||
{
|
||||
private readonly DownloadAssetBundleRequestArgs _args;
|
||||
private DownloadHandlerAssetBundle _downloadHandler;
|
||||
|
||||
/// <summary>
|
||||
/// 下载结果(AssetBundle 对象)
|
||||
/// </summary>
|
||||
public AssetBundle Result { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造 AssetBundle 下载器
|
||||
/// </summary>
|
||||
/// <param name="args">AssetBundle 下载参数</param>
|
||||
/// <param name="webRequestCreator">UnityWebRequest 创建器(可选)</param>
|
||||
public UnityWebRequestAssetBundleDownloader(DownloadAssetBundleRequestArgs args, UnityWebRequestCreator webRequestCreator)
|
||||
: base(args.URL, webRequestCreator)
|
||||
{
|
||||
_args = args;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建 UnityWebRequest
|
||||
/// </summary>
|
||||
protected override void CreateWebRequest()
|
||||
{
|
||||
_downloadHandler = CreateAssetBundleDownloadHandler();
|
||||
_webRequest = CreateUnityWebRequestGet(URL);
|
||||
_webRequest.downloadHandler = _downloadHandler;
|
||||
_webRequest.disposeDownloadHandlerOnDispose = true;
|
||||
ApplyRequestOptions(_args.Timeout, _args.WatchdogTime, _args.Headers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求成功时的回调
|
||||
/// </summary>
|
||||
protected override void OnRequestSucceed()
|
||||
{
|
||||
AssetBundle assetBundle = _downloadHandler.assetBundle;
|
||||
if (assetBundle == null)
|
||||
{
|
||||
Status = EDownloadRequestStatus.Failed;
|
||||
Error = $"[{GetType().Name}] URL: {URL} - AssetBundle object is null";
|
||||
}
|
||||
else
|
||||
{
|
||||
Result = assetBundle;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建 AssetBundle 下载处理器
|
||||
/// </summary>
|
||||
private DownloadHandlerAssetBundle CreateAssetBundleDownloadHandler()
|
||||
{
|
||||
DownloadHandlerAssetBundle handler;
|
||||
|
||||
if (_args.DisableUnityWebCache)
|
||||
{
|
||||
// 禁用 Unity 缓存
|
||||
handler = new DownloadHandlerAssetBundle(URL, _args.UnityCRC);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(_args.FileHash))
|
||||
throw new YooInternalException("File hash is null or empty.");
|
||||
|
||||
// 使用 Unity 缓存
|
||||
// 说明:The file hash defining the version of the asset bundle.
|
||||
Hash128 fileHash = Hash128.Parse(_args.FileHash);
|
||||
handler = new DownloadHandlerAssetBundle(URL, fileHash, _args.UnityCRC);
|
||||
}
|
||||
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e01cc308d7179a34281087fafe455b42
|
||||
guid: 6f635344c08e2b04295a108c2bcc6a40
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// UnityWebRequest 字节下载器
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 将下载内容保存到内存中的字节数组。
|
||||
/// </remarks>
|
||||
internal sealed class UnityWebRequestBytesDownloader : UnityWebRequestDownloaderBase, IDownloadBytesRequest
|
||||
{
|
||||
private readonly DownloadDataRequestArgs _args;
|
||||
|
||||
/// <summary>
|
||||
/// 下载结果(字节数组)
|
||||
/// </summary>
|
||||
public byte[] Result { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造字节数组下载器
|
||||
/// </summary>
|
||||
/// <param name="args">数据下载参数</param>
|
||||
/// <param name="webRequestCreator">UnityWebRequest 创建器(可选)</param>
|
||||
public UnityWebRequestBytesDownloader(DownloadDataRequestArgs args, UnityWebRequestCreator webRequestCreator)
|
||||
: base(args.URL, webRequestCreator)
|
||||
{
|
||||
_args = args;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建 UnityWebRequest
|
||||
/// </summary>
|
||||
protected override void CreateWebRequest()
|
||||
{
|
||||
var handler = new DownloadHandlerBuffer();
|
||||
_webRequest = CreateUnityWebRequestGet(URL);
|
||||
_webRequest.downloadHandler = handler;
|
||||
_webRequest.disposeDownloadHandlerOnDispose = true;
|
||||
ApplyRequestOptions(_args.Timeout, _args.WatchdogTime, _args.Headers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求成功时的回调
|
||||
/// </summary>
|
||||
protected override void OnRequestSucceed()
|
||||
{
|
||||
Result = _webRequest.downloadHandler.data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6602c4be2ef295546b7bbb328de8fb0c
|
||||
guid: 38884a96e8c2df74082d4e059e2e73ed
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -0,0 +1,288 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// UnityWebRequest 下载器基类
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 封装 UnityWebRequest 的通用下载逻辑,包括状态管理、进度追踪等。
|
||||
/// 子类只需实现 CreateWebRequest 方法来创建特定类型的下载请求。
|
||||
/// </remarks>
|
||||
internal abstract class UnityWebRequestDownloaderBase : IDownloadRequest
|
||||
{
|
||||
private readonly UnityWebRequestCreator _webRequestCreator;
|
||||
protected UnityWebRequest _webRequest;
|
||||
|
||||
// 看门狗相关
|
||||
private int _watchdogTime = 0;
|
||||
private bool _watchdogAborted = false;
|
||||
private long _lastDownloadBytes = -1;
|
||||
private double _lastGetDataTime;
|
||||
|
||||
#region 接口实现
|
||||
/// <summary>
|
||||
/// 请求地址
|
||||
/// </summary>
|
||||
public string URL { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否完成
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 每次调用都会主动轮询请求 PollingRequest
|
||||
/// </remarks>
|
||||
public bool IsDone
|
||||
{
|
||||
get
|
||||
{
|
||||
PollingRequest();
|
||||
return Status == EDownloadRequestStatus.Succeed
|
||||
|| Status == EDownloadRequestStatus.Failed
|
||||
|| Status == EDownloadRequestStatus.Aborted;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求状态
|
||||
/// </summary>
|
||||
public EDownloadRequestStatus Status { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前下载进度(0f - 1f)
|
||||
/// </summary>
|
||||
public float DownloadProgress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前请求已接收的字节数
|
||||
/// </summary>
|
||||
public long DownloadedBytes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// HTTP 返回码
|
||||
/// </summary>
|
||||
public long HttpCode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误信息
|
||||
/// </summary>
|
||||
public string Error { get; protected set; }
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 构造下载器基类
|
||||
/// </summary>
|
||||
/// <param name="url">请求地址</param>
|
||||
/// <param name="webRequestCreator">UnityWebRequest 创建器(可选)</param>
|
||||
protected UnityWebRequestDownloaderBase(string url, UnityWebRequestCreator webRequestCreator)
|
||||
{
|
||||
URL = url;
|
||||
_webRequestCreator = webRequestCreator;
|
||||
Status = EDownloadRequestStatus.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发起请求
|
||||
/// </summary>
|
||||
public void SendRequest()
|
||||
{
|
||||
if (Status == EDownloadRequestStatus.None)
|
||||
{
|
||||
Status = EDownloadRequestStatus.Running;
|
||||
|
||||
try
|
||||
{
|
||||
CreateWebRequest();
|
||||
|
||||
if (_webRequest == null)
|
||||
{
|
||||
Status = EDownloadRequestStatus.Failed;
|
||||
Error = $"[{GetType().Name}] Created web request is null.";
|
||||
}
|
||||
else
|
||||
{
|
||||
_webRequest.SendWebRequest();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Status = EDownloadRequestStatus.Failed;
|
||||
Error = $"[{GetType().Name}] Failed to create web request: {ex.Message}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轮询请求
|
||||
/// </summary>
|
||||
public void PollingRequest()
|
||||
{
|
||||
if (Status != EDownloadRequestStatus.Running)
|
||||
return;
|
||||
|
||||
DownloadProgress = _webRequest.downloadProgress;
|
||||
DownloadedBytes = (long)_webRequest.downloadedBytes;
|
||||
|
||||
CheckWatchdog();
|
||||
if (_webRequest.isDone == false)
|
||||
return;
|
||||
|
||||
HttpCode = _webRequest.responseCode;
|
||||
#if UNITY_2020_3_OR_NEWER
|
||||
bool isSuccess = _webRequest.result == UnityWebRequest.Result.Success;
|
||||
#else
|
||||
bool isSuccess = !_webRequest.isNetworkError && !_webRequest.isHttpError;
|
||||
#endif
|
||||
|
||||
if (isSuccess)
|
||||
{
|
||||
Status = EDownloadRequestStatus.Succeed;
|
||||
OnRequestSucceed();
|
||||
}
|
||||
else
|
||||
{
|
||||
Status = EDownloadRequestStatus.Failed;
|
||||
Error = $"[{GetType().Name}] URL: {URL} - Error: {_webRequest.error}";
|
||||
OnRequestFailed();
|
||||
}
|
||||
|
||||
// 完成后释放
|
||||
DisposeWebRequest();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 中止请求
|
||||
/// </summary>
|
||||
public void AbortRequest()
|
||||
{
|
||||
if (Status == EDownloadRequestStatus.None || Status == EDownloadRequestStatus.Running)
|
||||
{
|
||||
Status = EDownloadRequestStatus.Aborted;
|
||||
if (_webRequest != null)
|
||||
_webRequest.Abort();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeWebRequest();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 创建 UnityWebRequest(子类实现)
|
||||
/// </summary>
|
||||
protected abstract void CreateWebRequest();
|
||||
|
||||
/// <summary>
|
||||
/// 请求成功时的回调(子类可重写)
|
||||
/// </summary>
|
||||
protected virtual void OnRequestSucceed()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求失败时的回调(子类可重写)
|
||||
/// </summary>
|
||||
protected virtual void OnRequestFailed()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 创建 UnityWebRequest GET 请求
|
||||
/// </summary>
|
||||
/// <param name="requestUrl">请求地址</param>
|
||||
/// <returns>UnityWebRequest 实例</returns>
|
||||
protected UnityWebRequest CreateUnityWebRequestGet(string requestUrl)
|
||||
{
|
||||
if (_webRequestCreator != null)
|
||||
return _webRequestCreator.Invoke(requestUrl, UnityWebRequest.kHttpVerbGET);
|
||||
|
||||
return new UnityWebRequest(requestUrl, UnityWebRequest.kHttpVerbGET);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建 UnityWebRequest HEAD 请求
|
||||
/// </summary>
|
||||
/// <param name="requestUrl">请求地址</param>
|
||||
/// <returns>UnityWebRequest 实例</returns>
|
||||
protected UnityWebRequest CreateUnityWebRequestHead(string requestUrl)
|
||||
{
|
||||
if (_webRequestCreator != null)
|
||||
return _webRequestCreator.Invoke(requestUrl, UnityWebRequest.kHttpVerbHEAD);
|
||||
|
||||
return new UnityWebRequest(requestUrl, UnityWebRequest.kHttpVerbHEAD);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用通用请求参数
|
||||
/// </summary>
|
||||
protected void ApplyRequestOptions(int timeout, int watchdogTime, Dictionary<string, string> headers)
|
||||
{
|
||||
if (_webRequest == null)
|
||||
throw new YooInternalException("Web request is null.");
|
||||
|
||||
// 设置看门狗超时时间
|
||||
_watchdogTime = watchdogTime;
|
||||
|
||||
// 设置响应的超时时间
|
||||
if (timeout > 0)
|
||||
_webRequest.timeout = timeout;
|
||||
|
||||
// 设置响应头
|
||||
if (headers != null)
|
||||
{
|
||||
foreach (var header in headers)
|
||||
{
|
||||
_webRequest.SetRequestHeader(header.Key, header.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测看门狗
|
||||
/// </summary>
|
||||
private void CheckWatchdog()
|
||||
{
|
||||
if (_watchdogTime == 0)
|
||||
return;
|
||||
if (_watchdogAborted)
|
||||
return;
|
||||
|
||||
double realtimeSinceStartup = TimeUtility.RealtimeSinceStartup;
|
||||
if (DownloadedBytes != _lastDownloadBytes)
|
||||
{
|
||||
_lastDownloadBytes = DownloadedBytes;
|
||||
_lastGetDataTime = realtimeSinceStartup;
|
||||
}
|
||||
else
|
||||
{
|
||||
double deltaTime = realtimeSinceStartup - _lastGetDataTime;
|
||||
if (deltaTime > _watchdogTime)
|
||||
{
|
||||
_watchdogAborted = true;
|
||||
AbortRequest(); //看门狗终止网络请求
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
private void DisposeWebRequest()
|
||||
{
|
||||
if (_webRequest != null)
|
||||
{
|
||||
//注意:引擎底层会自动调用Abort方法
|
||||
_webRequest.Dispose();
|
||||
_webRequest = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e71e850eded0da43906cb4f7cb75629
|
||||
guid: 76b0712524ed69542914db8e44fa64fd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// UnityWebRequest 文件下载器
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 将下载内容保存到本地文件,支持断点续传和追加写入。
|
||||
/// </remarks>
|
||||
internal sealed class UnityWebRequestFileDownloader : UnityWebRequestDownloaderBase, IDownloadFileRequest
|
||||
{
|
||||
private readonly DownloadFileRequestArgs _args;
|
||||
|
||||
/// <summary>
|
||||
/// 文件保存路径
|
||||
/// </summary>
|
||||
public string SavePath
|
||||
{
|
||||
get { return _args.SavePath; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造文件下载器
|
||||
/// </summary>
|
||||
/// <param name="args">文件下载参数</param>
|
||||
/// <param name="webRequestCreator">UnityWebRequest 创建器(可选)</param>
|
||||
public UnityWebRequestFileDownloader(DownloadFileRequestArgs args, UnityWebRequestCreator webRequestCreator)
|
||||
: base(args.URL, webRequestCreator)
|
||||
{
|
||||
_args = args;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建 UnityWebRequest
|
||||
/// </summary>
|
||||
protected override void CreateWebRequest()
|
||||
{
|
||||
var handler = new DownloadHandlerFile(_args.SavePath, _args.AppendToFile);
|
||||
handler.removeFileOnAbort = _args.RemoveFileOnAbort;
|
||||
|
||||
_webRequest = CreateUnityWebRequestGet(URL);
|
||||
_webRequest.downloadHandler = handler;
|
||||
_webRequest.disposeDownloadHandlerOnDispose = true;
|
||||
|
||||
// 断点续传:设置 Range 请求头
|
||||
if (_args.ResumeFromBytes > 0)
|
||||
{
|
||||
_webRequest.SetRequestHeader("Range", $"bytes={_args.ResumeFromBytes}-");
|
||||
}
|
||||
|
||||
ApplyRequestOptions(_args.Timeout, _args.WatchdogTime, _args.Headers);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 375d88bcf5b9a6146adaf98ceb5369f8
|
||||
guid: 101a4be1bb0a85c4d84ecbf3e74f89e4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// UnityWebRequest HEAD 请求下载器
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 仅获取响应头信息,不下载实际内容。
|
||||
/// 用于检查资源是否存在、获取资源大小、检查缓存有效性等场景。
|
||||
/// </remarks>
|
||||
internal sealed class UnityWebRequestHeadDownloader : UnityWebRequestDownloaderBase, IDownloadHeadRequest
|
||||
{
|
||||
// 注意:缓存响应头(因为 WebRequest 释放后无法获取)
|
||||
private Dictionary<string, string> _cachedResponseHeaders;
|
||||
private readonly DownloadDataRequestArgs _args;
|
||||
|
||||
/// <summary>
|
||||
/// 获取 ETag 响应头
|
||||
/// </summary>
|
||||
public string ETag
|
||||
{
|
||||
get { return GetResponseHeader("ETag"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 Last-Modified 响应头
|
||||
/// </summary>
|
||||
public string LastModified
|
||||
{
|
||||
get { return GetResponseHeader("Last-Modified"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 Content-Type 响应头
|
||||
/// </summary>
|
||||
public string ContentType
|
||||
{
|
||||
get { return GetResponseHeader("Content-Type"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 Content-Length 响应头
|
||||
/// 预期下载的总字节数
|
||||
/// </summary>
|
||||
public long ContentLength
|
||||
{
|
||||
get
|
||||
{
|
||||
string contentLengthStr = GetResponseHeader("Content-Length");
|
||||
if (string.IsNullOrEmpty(contentLengthStr))
|
||||
return -1;
|
||||
|
||||
if (long.TryParse(contentLengthStr, out long contentLength))
|
||||
{
|
||||
return contentLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造 HEAD 请求下载器
|
||||
/// </summary>
|
||||
/// <param name="args">数据下载参数</param>
|
||||
/// <param name="webRequestCreator">UnityWebRequest 创建器(可选)</param>
|
||||
public UnityWebRequestHeadDownloader(DownloadDataRequestArgs args, UnityWebRequestCreator webRequestCreator)
|
||||
: base(args.URL, webRequestCreator)
|
||||
{
|
||||
_args = args;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取响应头信息
|
||||
/// </summary>
|
||||
/// <param name="name">响应头名称(不区分大小写)</param>
|
||||
/// <returns>响应头的值,如果不存在或请求未完成则返回 null</returns>
|
||||
public string GetResponseHeader(string name)
|
||||
{
|
||||
if (_cachedResponseHeaders == null)
|
||||
return null;
|
||||
|
||||
// 注意:UnityWebRequest 的响应头 key 是小写的
|
||||
string lowerName = name.ToLowerInvariant();
|
||||
if (_cachedResponseHeaders.TryGetValue(lowerName, out string value))
|
||||
return value;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建 UnityWebRequest
|
||||
/// </summary>
|
||||
protected override void CreateWebRequest()
|
||||
{
|
||||
_webRequest = CreateUnityWebRequestHead(URL);
|
||||
_webRequest.downloadHandler = null; // HEAD 请求不需要 DownloadHandler
|
||||
ApplyRequestOptions(_args.Timeout, _args.WatchdogTime, _args.Headers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求成功时的回调
|
||||
/// </summary>
|
||||
protected override void OnRequestSucceed()
|
||||
{
|
||||
var headers = _webRequest.GetResponseHeaders();
|
||||
if (headers != null)
|
||||
{
|
||||
_cachedResponseHeaders = new Dictionary<string, string>(headers.Count, StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var kvp in headers)
|
||||
{
|
||||
string name = kvp.Key.ToLowerInvariant();
|
||||
string value = kvp.Value;
|
||||
_cachedResponseHeaders[name] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ed243d707130394aa85e20dee876d8e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// UnityWebRequest 文本下载器
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 将下载内容解析为 UTF-8 文本字符串。
|
||||
/// </remarks>
|
||||
internal sealed class UnityWebRequestTextDownloader : UnityWebRequestDownloaderBase, IDownloadTextRequest
|
||||
{
|
||||
private readonly DownloadDataRequestArgs _args;
|
||||
|
||||
/// <summary>
|
||||
/// 下载结果(文本字符串)
|
||||
/// </summary>
|
||||
public string Result { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造文本下载器
|
||||
/// </summary>
|
||||
/// <param name="args">数据下载参数</param>
|
||||
/// <param name="webRequestCreator">UnityWebRequest 创建器(可选)</param>
|
||||
public UnityWebRequestTextDownloader(DownloadDataRequestArgs args, UnityWebRequestCreator webRequestCreator)
|
||||
: base(args.URL, webRequestCreator)
|
||||
{
|
||||
_args = args;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建 UnityWebRequest
|
||||
/// </summary>
|
||||
protected override void CreateWebRequest()
|
||||
{
|
||||
var handler = new DownloadHandlerBuffer();
|
||||
_webRequest = CreateUnityWebRequestGet(URL);
|
||||
_webRequest.downloadHandler = handler;
|
||||
_webRequest.disposeDownloadHandlerOnDispose = true;
|
||||
ApplyRequestOptions(_args.Timeout, _args.WatchdogTime, _args.Headers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求成功时的回调
|
||||
/// </summary>
|
||||
protected override void OnRequestSucceed()
|
||||
{
|
||||
Result = _webRequest.downloadHandler.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b0bd3024ca5bae4dbdc05c7af7479df
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,141 @@
|
||||
using System;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// 模拟下载器
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 用于编辑器模式下模拟下载进度,不进行实际网络请求。
|
||||
/// 根据配置的下载速度模拟进度变化。
|
||||
/// </remarks>
|
||||
internal sealed class VirtualFileDownloader : IDownloadFileRequest
|
||||
{
|
||||
private readonly DownloadSimulateRequestArgs _args;
|
||||
private double _lastUpdateTime;
|
||||
|
||||
/// <summary>
|
||||
/// 文件保存路径(模拟下载不需要)
|
||||
/// </summary>
|
||||
public string SavePath
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
#region 接口实现
|
||||
/// <summary>
|
||||
/// 请求地址
|
||||
/// </summary>
|
||||
public string URL { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否完成
|
||||
/// </summary>
|
||||
public bool IsDone
|
||||
{
|
||||
get
|
||||
{
|
||||
PollingRequest();
|
||||
return Status == EDownloadRequestStatus.Succeed
|
||||
|| Status == EDownloadRequestStatus.Failed
|
||||
|| Status == EDownloadRequestStatus.Aborted;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求状态
|
||||
/// </summary>
|
||||
public EDownloadRequestStatus Status { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前下载进度(0f - 1f)
|
||||
/// </summary>
|
||||
public float DownloadProgress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前请求已接收的字节数
|
||||
/// </summary>
|
||||
public long DownloadedBytes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// HTTP 返回码(模拟固定返回 200)
|
||||
/// </summary>
|
||||
public long HttpCode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误信息
|
||||
/// </summary>
|
||||
public string Error { get; private set; }
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 构造模拟下载器
|
||||
/// </summary>
|
||||
/// <param name="args">模拟下载参数</param>
|
||||
public VirtualFileDownloader(DownloadSimulateRequestArgs args)
|
||||
{
|
||||
_args = args;
|
||||
URL = args.URL;
|
||||
Status = EDownloadRequestStatus.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发起请求
|
||||
/// </summary>
|
||||
public void SendRequest()
|
||||
{
|
||||
if (Status == EDownloadRequestStatus.None)
|
||||
{
|
||||
Status = EDownloadRequestStatus.Running;
|
||||
_lastUpdateTime = TimeUtility.RealtimeSinceStartup;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轮询请求
|
||||
/// </summary>
|
||||
public void PollingRequest()
|
||||
{
|
||||
if (Status != EDownloadRequestStatus.Running)
|
||||
return;
|
||||
|
||||
double currentTime = TimeUtility.RealtimeSinceStartup;
|
||||
double deltaTime = currentTime - _lastUpdateTime;
|
||||
_lastUpdateTime = currentTime;
|
||||
|
||||
// 计算本帧下载的字节数
|
||||
long downloadBytes = (long)(_args.DownloadSpeed * deltaTime);
|
||||
DownloadedBytes += downloadBytes;
|
||||
|
||||
if (_args.FileSize > 0)
|
||||
DownloadProgress = (float)DownloadedBytes / _args.FileSize;
|
||||
|
||||
// 检查是否完成
|
||||
if (DownloadedBytes >= _args.FileSize)
|
||||
{
|
||||
HttpCode = 200;
|
||||
DownloadProgress = 1f;
|
||||
DownloadedBytes = _args.FileSize;
|
||||
Status = EDownloadRequestStatus.Succeed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 中止请求
|
||||
/// </summary>
|
||||
public void AbortRequest()
|
||||
{
|
||||
if (Status == EDownloadRequestStatus.None || Status == EDownloadRequestStatus.Running)
|
||||
{
|
||||
Status = EDownloadRequestStatus.Aborted;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84daeb1559aadff40b5b5aa5c81d7d64
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,118 +0,0 @@
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
349
Assets/YooAsset/Runtime/DownloadSystem/DownloadSystemDefine.cs
Normal file
349
Assets/YooAsset/Runtime/DownloadSystem/DownloadSystemDefine.cs
Normal file
@@ -0,0 +1,349 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// 导入的资源包信息
|
||||
/// </summary>
|
||||
public struct ImportBundleInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 本地文件路径
|
||||
/// </summary>
|
||||
public string FilePath;
|
||||
|
||||
/// <summary>
|
||||
/// 资源包名称
|
||||
/// </summary>
|
||||
public string BundleName;
|
||||
|
||||
/// <summary>
|
||||
/// 资源包GUID
|
||||
/// </summary>
|
||||
public string BundleGUID;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 下载请求状态
|
||||
/// </summary>
|
||||
internal enum EDownloadRequestStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// 未开始
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// 进行中
|
||||
/// </summary>
|
||||
Running,
|
||||
|
||||
/// <summary>
|
||||
/// 已成功
|
||||
/// </summary>
|
||||
Succeed,
|
||||
|
||||
/// <summary>
|
||||
/// 已失败
|
||||
/// </summary>
|
||||
Failed,
|
||||
|
||||
/// <summary>
|
||||
/// 已中止
|
||||
/// </summary>
|
||||
Aborted,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文件下载请求参数
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 用于将下载内容保存到本地文件的请求配置。
|
||||
/// 支持断点续传和追加写入模式。
|
||||
/// </remarks>
|
||||
internal struct DownloadFileRequestArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 请求地址
|
||||
/// </summary>
|
||||
public readonly string URL;
|
||||
|
||||
/// <summary>
|
||||
/// 响应的超时时间(单位:秒)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 当 Timeout 设置为 0 时,不应用超时。
|
||||
/// 设置的超时值可能应用于Android上的每个URL重定向,这可能会导致响应时间增加。
|
||||
/// </remarks>
|
||||
public readonly int Timeout;
|
||||
|
||||
/// <summary>
|
||||
/// 看门狗超时时间(单位:秒)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 用于监控下载任务的数据接收情况。
|
||||
/// 规则说明:
|
||||
/// 1. 当设置值为 0 时,表示禁用看门狗监控。
|
||||
/// 2. 每次接收到下载数据时,看门狗计时器会重置。
|
||||
/// 3. 若在设定的时间范围内未收到任何数据,任务将被自动终止。
|
||||
/// </remarks>
|
||||
public readonly int WatchdogTime;
|
||||
|
||||
/// <summary>
|
||||
/// 文件保存路径
|
||||
/// </summary>
|
||||
public readonly string SavePath;
|
||||
|
||||
/// <summary>
|
||||
/// 是否追加写入文件
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 配合 ResumeFromBytes 使用,用于断点续传场景。
|
||||
/// </remarks>
|
||||
public readonly bool AppendToFile;
|
||||
|
||||
/// <summary>
|
||||
/// 中止请求时是否删除目标文件
|
||||
/// </summary>
|
||||
public readonly bool RemoveFileOnAbort;
|
||||
|
||||
/// <summary>
|
||||
/// 断点续传的起始字节(小于等于 0 表示不启用)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 推荐由后端自动设置 Range 请求头:"bytes={ResumeFromBytes}-"。
|
||||
/// </remarks>
|
||||
public readonly long ResumeFromBytes;
|
||||
|
||||
/// <summary>
|
||||
/// 自定义请求头(可选)
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Headers;
|
||||
|
||||
/// <summary>
|
||||
/// 构造文件下载请求参数
|
||||
/// </summary>
|
||||
public DownloadFileRequestArgs(
|
||||
string url,
|
||||
string savePath,
|
||||
int timeout,
|
||||
int watchdogTime,
|
||||
bool appendToFile = false,
|
||||
bool removeFileOnAbort = true,
|
||||
long resumeFromBytes = 0)
|
||||
{
|
||||
URL = url;
|
||||
SavePath = savePath;
|
||||
Timeout = timeout;
|
||||
WatchdogTime = watchdogTime;
|
||||
AppendToFile = appendToFile;
|
||||
RemoveFileOnAbort = removeFileOnAbort;
|
||||
ResumeFromBytes = resumeFromBytes;
|
||||
Headers = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加请求头数据
|
||||
/// </summary>
|
||||
public void AddRequestHeader(string name, string value)
|
||||
{
|
||||
if (Headers == null)
|
||||
Headers = new Dictionary<string, string>(10);
|
||||
Headers.Add(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据下载请求参数(通用)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 用于下载到内存的请求配置。
|
||||
/// 可用于字节数组(bytes)或文本(text)下载。
|
||||
/// </remarks>
|
||||
internal struct DownloadDataRequestArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 请求地址
|
||||
/// </summary>
|
||||
public readonly string URL;
|
||||
|
||||
/// <summary>
|
||||
/// 响应的超时时间(单位:秒)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 当 Timeout 设置为 0 时,不应用超时。
|
||||
/// 设置的超时值可能应用于Android上的每个URL重定向,这可能会导致响应时间增加。
|
||||
/// </remarks>
|
||||
public readonly int Timeout;
|
||||
|
||||
/// <summary>
|
||||
/// 看门狗超时时间(单位:秒)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 用于监控下载任务的数据接收情况。
|
||||
/// 规则说明:
|
||||
/// 1. 当设置值为 0 时,表示禁用看门狗监控。
|
||||
/// 2. 每次接收到下载数据时,看门狗计时器会重置。
|
||||
/// 3. 若在设定的时间范围内未收到任何数据,任务将被自动终止。
|
||||
/// </remarks>
|
||||
public readonly int WatchdogTime;
|
||||
|
||||
/// <summary>
|
||||
/// 自定义请求头(可选)
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Headers;
|
||||
|
||||
/// <summary>
|
||||
/// 构造数据下载请求参数
|
||||
/// </summary>
|
||||
/// <param name="url">请求地址</param>
|
||||
/// <param name="options">通用请求参数</param>
|
||||
public DownloadDataRequestArgs(string url, int timeout, int watchdogTime)
|
||||
{
|
||||
URL = url;
|
||||
Timeout = timeout;
|
||||
WatchdogTime = watchdogTime;
|
||||
Headers = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加请求头数据
|
||||
/// </summary>
|
||||
public void AddRequestHeader(string name, string value)
|
||||
{
|
||||
if (Headers == null)
|
||||
Headers = new Dictionary<string, string>(10);
|
||||
Headers.Add(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AssetBundle 下载请求参数
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 用于下载并加载 Unity AssetBundle 的请求配置。
|
||||
/// 支持 Unity 内置缓存机制和 CRC 校验。
|
||||
/// </remarks>
|
||||
internal struct DownloadAssetBundleRequestArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 请求地址
|
||||
/// </summary>
|
||||
public readonly string URL;
|
||||
|
||||
/// <summary>
|
||||
/// 响应的超时时间(单位:秒)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 当 Timeout 设置为 0 时,不应用超时。
|
||||
/// 设置的超时值可能应用于Android上的每个URL重定向,这可能会导致响应时间增加。
|
||||
/// </remarks>
|
||||
public readonly int Timeout;
|
||||
|
||||
/// <summary>
|
||||
/// 看门狗超时时间(单位:秒)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 用于监控下载任务的数据接收情况。
|
||||
/// 规则说明:
|
||||
/// 1. 当设置值为 0 时,表示禁用看门狗监控。
|
||||
/// 2. 每次接收到下载数据时,看门狗计时器会重置。
|
||||
/// 3. 若在设定的时间范围内未收到任何数据,任务将被自动终止。
|
||||
/// </remarks>
|
||||
public readonly int WatchdogTime;
|
||||
|
||||
/// <summary>
|
||||
/// 禁用 Unity 的网络缓存
|
||||
/// </summary>
|
||||
public readonly bool DisableUnityWebCache;
|
||||
|
||||
/// <summary>
|
||||
/// AssetBundle 文件哈希(用于 UnityWebRequest 的缓存)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 仅当 DisableUnityWebCache 为 false 时需要。
|
||||
/// </remarks>
|
||||
public readonly string FileHash;
|
||||
|
||||
/// <summary>
|
||||
/// Unity CRC 校验值
|
||||
/// </summary>
|
||||
public readonly uint UnityCRC;
|
||||
|
||||
/// <summary>
|
||||
/// 自定义请求头(可选)
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Headers;
|
||||
|
||||
/// <summary>
|
||||
/// 构造 AssetBundle 下载请求参数
|
||||
/// </summary>
|
||||
public DownloadAssetBundleRequestArgs(
|
||||
string url,
|
||||
int timeout,
|
||||
int watchdogTime,
|
||||
bool disableUnityWebCache = true,
|
||||
string fileHash = null,
|
||||
uint unityCrc = 0)
|
||||
{
|
||||
URL = url;
|
||||
Timeout = timeout;
|
||||
WatchdogTime = watchdogTime;
|
||||
DisableUnityWebCache = disableUnityWebCache;
|
||||
FileHash = fileHash;
|
||||
UnityCRC = unityCrc;
|
||||
Headers = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加请求头数据
|
||||
/// </summary>
|
||||
public void AddRequestHeader(string name, string value)
|
||||
{
|
||||
if (Headers == null)
|
||||
Headers = new Dictionary<string, string>(10);
|
||||
Headers.Add(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 模拟下载请求参数
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 用于编辑器模式下模拟下载进度,不进行实际网络请求。
|
||||
/// </remarks>
|
||||
internal struct DownloadSimulateRequestArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 请求地址(仅用于标识)
|
||||
/// </summary>
|
||||
public readonly string URL;
|
||||
|
||||
/// <summary>
|
||||
/// 模拟的文件大小(字节)
|
||||
/// </summary>
|
||||
public readonly long FileSize;
|
||||
|
||||
/// <summary>
|
||||
/// 模拟的下载速度(字节/秒)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 用于计算模拟的下载进度。
|
||||
/// </remarks>
|
||||
public readonly long DownloadSpeed;
|
||||
|
||||
/// <summary>
|
||||
/// 构造模拟下载请求参数
|
||||
/// </summary>
|
||||
/// <param name="url">请求地址(仅用于标识)</param>
|
||||
/// <param name="fileSize">模拟的文件大小(字节)</param>
|
||||
/// <param name="downloadSpeed">模拟的下载速度(字节/秒),默认 1MB/s</param>
|
||||
public DownloadSimulateRequestArgs(string url, long fileSize, long downloadSpeed = 1024 * 1024)
|
||||
{
|
||||
URL = url;
|
||||
FileSize = fileSize;
|
||||
DownloadSpeed = downloadSpeed > 0 ? downloadSpeed : 1024 * 1024;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb0480e877454f7c944f66a01646b6d4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -3,32 +3,8 @@ using UnityEngine;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// 自定义下载器的请求委托
|
||||
/// </summary>
|
||||
public delegate UnityWebRequest UnityWebRequestDelegate(string url);
|
||||
|
||||
internal class DownloadSystemHelper
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void OnRuntimeInitialize()
|
||||
{
|
||||
UnityWebRequestCreater = null;
|
||||
}
|
||||
#endif
|
||||
|
||||
public static UnityWebRequestDelegate UnityWebRequestCreater = null;
|
||||
public static UnityWebRequest NewUnityWebRequestGet(string requestURL)
|
||||
{
|
||||
UnityWebRequest webRequest;
|
||||
if (UnityWebRequestCreater != null)
|
||||
webRequest = UnityWebRequestCreater.Invoke(requestURL);
|
||||
else
|
||||
webRequest = new UnityWebRequest(requestURL, UnityWebRequest.kHttpVerbGET);
|
||||
return webRequest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取WWW加载本地资源的路径
|
||||
/// </summary>
|
||||
@@ -81,7 +57,7 @@ namespace YooAsset
|
||||
#elif UNITY_STANDALONE_LINUX
|
||||
url = StringUtility.Format("file:///root/{0}", path);
|
||||
#else
|
||||
throw new System.NotImplementedException();
|
||||
throw new System.NotSupportedException($"[{nameof(DownloadSystemHelper.ConvertToWWWPath)}] 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.
|
||||
@@ -94,12 +70,16 @@ namespace YooAsset
|
||||
public static bool IsRequestLocalFile(string url)
|
||||
{
|
||||
//TODO UNITY_STANDALONE_OSX平台目前无法确定
|
||||
|
||||
// 本地文件传输协议
|
||||
if (url.StartsWith("file:"))
|
||||
return true;
|
||||
|
||||
// JAR文件协议
|
||||
if (url.StartsWith("jar:file:"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e7f5776546411834d9ed949d54a6f241
|
||||
guid: d724672f4d6f24b4db435d10eef6c40d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// 下载后台接口
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 不同网络库(UnityWebRequest / BestHTTP / 自研)实现该接口,用于创建具体下载请求。
|
||||
/// 每个后台实例是独立的,不共享全局状态。
|
||||
/// </remarks>
|
||||
internal interface IDownloadBackend : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 后台名称(用于日志与调试)
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 驱动更新
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 部分第三方网络库需要在 Unity 主线程中周期性调用 Update 进行驱动。
|
||||
/// 不需要驱动的后台可实现为空方法。
|
||||
/// </remarks>
|
||||
void Update();
|
||||
|
||||
/// <summary>
|
||||
/// 创建 HEAD 请求
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 仅获取响应头信息,不下载实际内容。
|
||||
/// 用于检查资源是否存在、获取资源大小、检查缓存有效性等场景。
|
||||
/// </remarks>
|
||||
/// <param name="args">数据请求参数</param>
|
||||
/// <returns>HEAD 请求实例</returns>
|
||||
IDownloadHeadRequest CreateHeadRequest(DownloadDataRequestArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// 创建文件下载请求
|
||||
/// </summary>
|
||||
/// <param name="args">文件下载参数</param>
|
||||
/// <returns>文件下载请求实例</returns>
|
||||
IDownloadFileRequest CreateFileRequest(DownloadFileRequestArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// 创建内存下载请求(字节数组)
|
||||
/// </summary>
|
||||
/// <param name="args">数据下载参数</param>
|
||||
/// <returns>字节下载请求实例</returns>
|
||||
IDownloadBytesRequest CreateBytesRequest(DownloadDataRequestArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// 创建文本下载请求
|
||||
/// </summary>
|
||||
/// <param name="args">数据下载参数</param>
|
||||
/// <returns>文本下载请求实例</returns>
|
||||
IDownloadTextRequest CreateTextRequest(DownloadDataRequestArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// 创建 AssetBundle 下载请求
|
||||
/// </summary>
|
||||
/// <param name="args">AssetBundle 下载参数</param>
|
||||
/// <returns>AssetBundle 下载请求实例</returns>
|
||||
IDownloadAssetBundleRequest CreateAssetBundleRequest(DownloadAssetBundleRequestArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// 创建模拟下载请求
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 用于编辑器模式下模拟下载进度,不进行实际网络请求。
|
||||
/// 可用于测试下载流程和 UI 展示。
|
||||
/// </remarks>
|
||||
/// <param name="args">模拟下载参数</param>
|
||||
/// <returns>模拟下载请求实例</returns>
|
||||
IDownloadFileRequest CreateSimulateRequest(DownloadSimulateRequestArgs args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e88c026ebbfb40258aad52ad1c2feb70
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,194 @@
|
||||
using System;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// 可轮询的下载请求接口
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 上层通常会在每帧轮询 Status IsDone,并在完成后读取结果。
|
||||
/// </remarks>
|
||||
internal interface IDownloadRequest : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 请求地址
|
||||
/// </summary>
|
||||
string URL { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否完成(成功/失败/中止)
|
||||
/// </summary>
|
||||
bool IsDone { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 请求状态
|
||||
/// </summary>
|
||||
EDownloadRequestStatus Status { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前下载进度(0f - 1f)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 部分情况下无法准确获取总长度,可返回 0。
|
||||
/// </remarks>
|
||||
float DownloadProgress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前请求已接收的字节数
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 断点续传场景下,该值仅表示"本次请求新增下载的字节数",不包含已存在的本地文件长度。
|
||||
/// </remarks>
|
||||
long DownloadedBytes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// HTTP 返回码
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 非 HTTP 协议可返回 0。使用 long 类型以兼容各种协议的返回码。
|
||||
/// </remarks>
|
||||
long HttpCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误信息
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 失败时不为空。
|
||||
/// </remarks>
|
||||
string Error { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 发起请求
|
||||
/// </summary>
|
||||
void SendRequest();
|
||||
|
||||
/// <summary>
|
||||
/// 轮询请求
|
||||
/// </summary>
|
||||
void PollingRequest();
|
||||
|
||||
/// <summary>
|
||||
/// 中止请求
|
||||
/// </summary>
|
||||
void AbortRequest();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HEAD 请求接口(仅获取响应头)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 用于检查资源是否存在、获取资源大小、检查缓存有效性等场景。
|
||||
/// 不下载实际内容,仅获取响应头信息。
|
||||
/// </remarks>
|
||||
internal interface IDownloadHeadRequest : IDownloadRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取 ETag 响应头
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 用于缓存验证,如果服务器未返回则为 null。
|
||||
/// </remarks>
|
||||
string ETag { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取 Last-Modified 响应头
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 资源最后修改时间,如果服务器未返回则为 null。
|
||||
/// </remarks>
|
||||
string LastModified { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取 Content-Type 响应头
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 资源的 MIME 类型,如果服务器未返回则为 null。
|
||||
/// </remarks>
|
||||
string ContentType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 预期下载的总字节数(Content-Length)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 从响应头 Content-Length 获取。
|
||||
/// 如果服务器未返回或请求未完成,返回 -1。
|
||||
/// 用于更准确的进度计算。
|
||||
/// </remarks>
|
||||
long ContentLength { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取响应头信息
|
||||
/// </summary>
|
||||
/// <param name="name">响应头名称(不区分大小写)</param>
|
||||
/// <returns>响应头的值,如果不存在或请求未完成则返回 null</returns>
|
||||
/// <remarks>
|
||||
/// 常用响应头:Content-Length、Content-Type、ETag、Last-Modified 等。
|
||||
/// </remarks>
|
||||
string GetResponseHeader(string name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文件下载请求接口
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 将下载内容保存到指定的本地文件路径。
|
||||
/// </remarks>
|
||||
internal interface IDownloadFileRequest : IDownloadRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件保存路径
|
||||
/// </summary>
|
||||
string SavePath { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内存下载请求接口(字节数组)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 将下载内容保存到内存中的字节数组。
|
||||
/// </remarks>
|
||||
internal interface IDownloadBytesRequest : IDownloadRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 下载结果(字节数组)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 仅在请求成功时可用,失败时为 null。
|
||||
/// </remarks>
|
||||
byte[] Result { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文本下载请求接口
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 将下载内容解析为 UTF-8 文本字符串。
|
||||
/// </remarks>
|
||||
internal interface IDownloadTextRequest : IDownloadRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 下载结果(文本字符串)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 仅在请求成功时可用,失败时为 null。
|
||||
/// </remarks>
|
||||
string Result { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AssetBundle 下载请求接口
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 下载并加载 Unity AssetBundle 资源包。
|
||||
/// </remarks>
|
||||
internal interface IDownloadAssetBundleRequest : IDownloadRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 下载结果(AssetBundle 对象)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 仅在请求成功时可用,失败时为 null。
|
||||
/// </remarks>
|
||||
UnityEngine.AssetBundle Result { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a2aafeb309243f4959394a97d66b19f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,113 +0,0 @@
|
||||
using UnityEngine.Networking;
|
||||
using UnityEngine;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
internal class UnityAssetBundleRequestOperation : UnityWebRequestOperation
|
||||
{
|
||||
protected enum ESteps
|
||||
{
|
||||
None,
|
||||
CreateRequest,
|
||||
Download,
|
||||
Done,
|
||||
}
|
||||
|
||||
private UnityWebRequestAsyncOperation _requestOperation;
|
||||
private DownloadHandlerAssetBundle _downloadhandler;
|
||||
private readonly PackageBundle _packageBundle;
|
||||
private readonly bool _disableUnityWebCache;
|
||||
private ESteps _steps = ESteps.None;
|
||||
|
||||
/// <summary>
|
||||
/// 请求结果
|
||||
/// </summary>
|
||||
public AssetBundle Result { private set; get; }
|
||||
|
||||
internal UnityAssetBundleRequestOperation(PackageBundle packageBundle, bool disableUnityWebCache, string url) : base(url)
|
||||
{
|
||||
_packageBundle = packageBundle;
|
||||
_disableUnityWebCache = disableUnityWebCache;
|
||||
}
|
||||
internal override void InternalStart()
|
||||
{
|
||||
_steps = ESteps.CreateRequest;
|
||||
}
|
||||
internal override void InternalUpdate()
|
||||
{
|
||||
if (_steps == ESteps.None || _steps == ESteps.Done)
|
||||
return;
|
||||
|
||||
if (_steps == ESteps.CreateRequest)
|
||||
{
|
||||
CreateWebRequest();
|
||||
_steps = ESteps.Download;
|
||||
}
|
||||
|
||||
if (_steps == ESteps.Download)
|
||||
{
|
||||
DownloadProgress = _webRequest.downloadProgress;
|
||||
DownloadedBytes = (long)_webRequest.downloadedBytes;
|
||||
Progress = _requestOperation.progress;
|
||||
if (_requestOperation.isDone == false)
|
||||
return;
|
||||
|
||||
if (CheckRequestResult())
|
||||
{
|
||||
AssetBundle assetBundle = _downloadhandler.assetBundle;
|
||||
if (assetBundle == null)
|
||||
{
|
||||
_steps = ESteps.Done;
|
||||
Status = EOperationStatus.Failed;
|
||||
Error = $"URL : {_requestURL} Download handler asset bundle object is null !";
|
||||
}
|
||||
else
|
||||
{
|
||||
_steps = ESteps.Done;
|
||||
Result = assetBundle;
|
||||
Status = EOperationStatus.Succeed;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_steps = ESteps.Done;
|
||||
Status = EOperationStatus.Failed;
|
||||
}
|
||||
|
||||
// 注意:最终释放请求器
|
||||
DisposeRequest();
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateWebRequest()
|
||||
{
|
||||
_downloadhandler = CreateWebDownloadHandler();
|
||||
_webRequest = DownloadSystemHelper.NewUnityWebRequestGet(_requestURL);
|
||||
_webRequest.downloadHandler = _downloadhandler;
|
||||
_webRequest.disposeDownloadHandlerOnDispose = true;
|
||||
_requestOperation = _webRequest.SendWebRequest();
|
||||
}
|
||||
private DownloadHandlerAssetBundle CreateWebDownloadHandler()
|
||||
{
|
||||
if (_disableUnityWebCache)
|
||||
{
|
||||
var downloadhandler = new DownloadHandlerAssetBundle(_requestURL, _packageBundle.UnityCRC);
|
||||
#if UNITY_2020_3_OR_NEWER
|
||||
downloadhandler.autoLoadAssetBundle = false;
|
||||
#endif
|
||||
return downloadhandler;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 注意:优先从浏览器缓存里获取文件
|
||||
// The file hash defining the version of the asset bundle.
|
||||
Hash128 fileHash = Hash128.Parse(_packageBundle.FileHash);
|
||||
var downloadhandler = new DownloadHandlerAssetBundle(_requestURL, fileHash, _packageBundle.UnityCRC);
|
||||
#if UNITY_2020_3_OR_NEWER
|
||||
downloadhandler.autoLoadAssetBundle = false;
|
||||
#endif
|
||||
return downloadhandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
using UnityEngine.Networking;
|
||||
using UnityEngine;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
internal class UnityVirtualBundleRequestOperation : UnityWebRequestOperation
|
||||
{
|
||||
protected enum ESteps
|
||||
{
|
||||
None,
|
||||
Download,
|
||||
Done,
|
||||
}
|
||||
|
||||
private readonly PackageBundle _bundle;
|
||||
private readonly int _downloadSpeed;
|
||||
private ESteps _steps = ESteps.None;
|
||||
|
||||
internal UnityVirtualBundleRequestOperation(PackageBundle packageBundle, int downloadSpeed, string url) : base(url)
|
||||
{
|
||||
_bundle = packageBundle;
|
||||
_downloadSpeed = downloadSpeed;
|
||||
}
|
||||
internal override void InternalStart()
|
||||
{
|
||||
_steps = ESteps.Download;
|
||||
}
|
||||
internal override void InternalUpdate()
|
||||
{
|
||||
if (_steps == ESteps.None || _steps == ESteps.Done)
|
||||
return;
|
||||
|
||||
if (_steps == ESteps.Download)
|
||||
{
|
||||
// 模拟下载进度
|
||||
float progress = 0;
|
||||
if (DownloadedBytes > 0)
|
||||
progress = DownloadedBytes / _bundle.FileSize;
|
||||
long downloadBytes = (long)((double)_downloadSpeed * Time.deltaTime);
|
||||
|
||||
Progress = progress;
|
||||
DownloadProgress = progress;
|
||||
DownloadedBytes += downloadBytes;
|
||||
if (DownloadedBytes < _bundle.FileSize)
|
||||
return;
|
||||
|
||||
Progress = 1f;
|
||||
DownloadProgress = 1f;
|
||||
DownloadedBytes = _bundle.FileSize;
|
||||
|
||||
_steps = ESteps.Done;
|
||||
Status = EOperationStatus.Succeed;
|
||||
}
|
||||
}
|
||||
internal override void InternalWaitForAsyncComplete()
|
||||
{
|
||||
if (_steps != ESteps.Done)
|
||||
{
|
||||
_steps = ESteps.Done;
|
||||
Status = EOperationStatus.Failed;
|
||||
Error = $"Try load bundle {_bundle.BundleName} from remote !";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Networking;
|
||||
using UnityEngine;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
internal class UnityWebCacheRequestOperation : UnityWebRequestOperation
|
||||
{
|
||||
protected enum ESteps
|
||||
{
|
||||
None,
|
||||
CreateRequest,
|
||||
Download,
|
||||
Done,
|
||||
}
|
||||
|
||||
private UnityWebRequestAsyncOperation _requestOperation;
|
||||
private readonly Dictionary<string, string> _headers = new Dictionary<string, string>();
|
||||
private ESteps _steps = ESteps.None;
|
||||
|
||||
|
||||
internal UnityWebCacheRequestOperation(string url) : base(url)
|
||||
{
|
||||
}
|
||||
internal override void InternalStart()
|
||||
{
|
||||
_steps = ESteps.CreateRequest;
|
||||
}
|
||||
internal override void InternalUpdate()
|
||||
{
|
||||
if (_steps == ESteps.None || _steps == ESteps.Done)
|
||||
return;
|
||||
|
||||
if (_steps == ESteps.CreateRequest)
|
||||
{
|
||||
CreateWebRequest();
|
||||
_steps = ESteps.Download;
|
||||
}
|
||||
|
||||
if (_steps == ESteps.Download)
|
||||
{
|
||||
DownloadProgress = _webRequest.downloadProgress;
|
||||
DownloadedBytes = (long)_webRequest.downloadedBytes;
|
||||
Progress = _requestOperation.progress;
|
||||
if (_requestOperation.isDone == false)
|
||||
return;
|
||||
|
||||
if (CheckRequestResult())
|
||||
{
|
||||
_steps = ESteps.Done;
|
||||
Status = EOperationStatus.Succeed;
|
||||
}
|
||||
else
|
||||
{
|
||||
_steps = ESteps.Done;
|
||||
Status = EOperationStatus.Failed;
|
||||
}
|
||||
|
||||
// 注意:最终释放请求器
|
||||
DisposeRequest();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置请求头信息
|
||||
/// </summary>
|
||||
public void SetRequestHeader(string name, string value)
|
||||
{
|
||||
_headers.Add(name, value);
|
||||
}
|
||||
|
||||
private void CreateWebRequest()
|
||||
{
|
||||
_webRequest = DownloadSystemHelper.NewUnityWebRequestGet(_requestURL);
|
||||
_webRequest.disposeDownloadHandlerOnDispose = true;
|
||||
|
||||
// 设置消息头
|
||||
foreach (var keyValuePair in _headers)
|
||||
{
|
||||
string name = keyValuePair.Key;
|
||||
string value = keyValuePair.Value;
|
||||
_webRequest.SetRequestHeader(name, value);
|
||||
}
|
||||
|
||||
_requestOperation = _webRequest.SendWebRequest();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
using UnityEngine.Networking;
|
||||
using UnityEngine;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
internal class UnityWebDataRequestOperation : UnityWebRequestOperation
|
||||
{
|
||||
protected enum ESteps
|
||||
{
|
||||
None,
|
||||
CreateRequest,
|
||||
Download,
|
||||
Done,
|
||||
}
|
||||
|
||||
private UnityWebRequestAsyncOperation _requestOperation;
|
||||
private ESteps _steps = ESteps.None;
|
||||
|
||||
/// <summary>
|
||||
/// 响应的超时时间(单位:秒),在经过Timeout的秒数后尝试中止。
|
||||
/// 注意:当Timeout设置为0时,不会应用超时。
|
||||
/// 注意:设置的超时值可能应用于Android上的每个URL重定向,这可能会导致响应时间增加。
|
||||
/// </summary>
|
||||
private readonly int _timeout;
|
||||
|
||||
/// <summary>
|
||||
/// 请求结果
|
||||
/// </summary>
|
||||
public byte[] Result { private set; get; }
|
||||
|
||||
|
||||
internal UnityWebDataRequestOperation(string url, int timeout) : base(url)
|
||||
{
|
||||
_timeout = timeout;
|
||||
}
|
||||
internal override void InternalStart()
|
||||
{
|
||||
_steps = ESteps.CreateRequest;
|
||||
}
|
||||
internal override void InternalUpdate()
|
||||
{
|
||||
if (_steps == ESteps.None || _steps == ESteps.Done)
|
||||
return;
|
||||
|
||||
if (_steps == ESteps.CreateRequest)
|
||||
{
|
||||
CreateWebRequest();
|
||||
_steps = ESteps.Download;
|
||||
}
|
||||
|
||||
if (_steps == ESteps.Download)
|
||||
{
|
||||
DownloadProgress = _webRequest.downloadProgress;
|
||||
DownloadedBytes = (long)_webRequest.downloadedBytes;
|
||||
Progress = _requestOperation.progress;
|
||||
if (_requestOperation.isDone == false)
|
||||
return;
|
||||
|
||||
if (CheckRequestResult())
|
||||
{
|
||||
var fileData = _webRequest.downloadHandler.data;
|
||||
if (fileData == null || fileData.Length == 0)
|
||||
{
|
||||
_steps = ESteps.Done;
|
||||
Status = EOperationStatus.Failed;
|
||||
Error = $"URL : {_requestURL} Download handler data is null or empty !";
|
||||
}
|
||||
else
|
||||
{
|
||||
_steps = ESteps.Done;
|
||||
Result = fileData;
|
||||
Status = EOperationStatus.Succeed;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_steps = ESteps.Done;
|
||||
Status = EOperationStatus.Failed;
|
||||
}
|
||||
|
||||
// 注意:最终释放请求器
|
||||
DisposeRequest();
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateWebRequest()
|
||||
{
|
||||
DownloadHandlerBuffer handler = new DownloadHandlerBuffer();
|
||||
_webRequest = DownloadSystemHelper.NewUnityWebRequestGet(_requestURL);
|
||||
_webRequest.timeout = _timeout;
|
||||
_webRequest.downloadHandler = handler;
|
||||
_webRequest.disposeDownloadHandlerOnDispose = true;
|
||||
_requestOperation = _webRequest.SendWebRequest();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b7557eb146572de49a1ec9b3f3c0b706
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,83 +0,0 @@
|
||||
using UnityEngine.Networking;
|
||||
using UnityEngine;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
internal class UnityWebFileRequestOperation : UnityWebRequestOperation
|
||||
{
|
||||
protected enum ESteps
|
||||
{
|
||||
None,
|
||||
CreateRequest,
|
||||
Download,
|
||||
Done,
|
||||
}
|
||||
|
||||
private UnityWebRequestAsyncOperation _requestOperation;
|
||||
private readonly string _fileSavePath;
|
||||
private ESteps _steps = ESteps.None;
|
||||
|
||||
/// <summary>
|
||||
/// 响应的超时时间(单位:秒),在经过Timeout的秒数后尝试中止。
|
||||
/// 注意:当Timeout设置为0时,不会应用超时。
|
||||
/// 注意:设置的超时值可能应用于Android上的每个URL重定向,这可能会导致响应时间增加。
|
||||
/// </summary>
|
||||
private readonly int _timeout;
|
||||
|
||||
|
||||
internal UnityWebFileRequestOperation(string url, string fileSavePath, int timeout) : base(url)
|
||||
{
|
||||
_fileSavePath = fileSavePath;
|
||||
_timeout = timeout;
|
||||
}
|
||||
internal override void InternalStart()
|
||||
{
|
||||
_steps = ESteps.CreateRequest;
|
||||
}
|
||||
internal override void InternalUpdate()
|
||||
{
|
||||
if (_steps == ESteps.None || _steps == ESteps.Done)
|
||||
return;
|
||||
|
||||
if (_steps == ESteps.CreateRequest)
|
||||
{
|
||||
CreateWebRequest();
|
||||
_steps = ESteps.Download;
|
||||
}
|
||||
|
||||
if (_steps == ESteps.Download)
|
||||
{
|
||||
DownloadProgress = _webRequest.downloadProgress;
|
||||
DownloadedBytes = (long)_webRequest.downloadedBytes;
|
||||
Progress = _requestOperation.progress;
|
||||
if (_requestOperation.isDone == false)
|
||||
return;
|
||||
|
||||
if (CheckRequestResult())
|
||||
{
|
||||
_steps = ESteps.Done;
|
||||
Status = EOperationStatus.Succeed;
|
||||
}
|
||||
else
|
||||
{
|
||||
_steps = ESteps.Done;
|
||||
Status = EOperationStatus.Failed;
|
||||
}
|
||||
|
||||
// 注意:最终释放请求器
|
||||
DisposeRequest();
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateWebRequest()
|
||||
{
|
||||
DownloadHandlerFile handler = new DownloadHandlerFile(_fileSavePath);
|
||||
handler.removeFileOnAbort = true;
|
||||
_webRequest = DownloadSystemHelper.NewUnityWebRequestGet(_requestURL);
|
||||
_webRequest.timeout = _timeout;
|
||||
_webRequest.downloadHandler = handler;
|
||||
_webRequest.disposeDownloadHandlerOnDispose = true;
|
||||
_requestOperation = _webRequest.SendWebRequest();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ab5f2486d06e2642ba7aa9c9623430e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,98 +0,0 @@
|
||||
using System;
|
||||
using UnityEngine.Networking;
|
||||
using UnityEngine;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
internal abstract class UnityWebRequestOperation : AsyncOperationBase
|
||||
{
|
||||
protected UnityWebRequest _webRequest;
|
||||
protected readonly string _requestURL;
|
||||
private bool _isAbort = false;
|
||||
|
||||
/// <summary>
|
||||
/// HTTP返回码
|
||||
/// </summary>
|
||||
public long HttpCode { private set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前下载的字节数
|
||||
/// </summary>
|
||||
public long DownloadedBytes { protected set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前下载进度(0f - 1f)
|
||||
/// </summary>
|
||||
public float DownloadProgress { protected set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// 请求的URL地址
|
||||
/// </summary>
|
||||
public string URL
|
||||
{
|
||||
get { return _requestURL; }
|
||||
}
|
||||
|
||||
internal UnityWebRequestOperation(string url)
|
||||
{
|
||||
_requestURL = url;
|
||||
}
|
||||
internal override void InternalAbort()
|
||||
{
|
||||
//TODO
|
||||
// 1. 编辑器下停止运行游戏的时候主动终止下载任务
|
||||
// 2. 真机上销毁包裹的时候主动终止下载任务
|
||||
if (_isAbort == false)
|
||||
{
|
||||
if (_webRequest != null)
|
||||
{
|
||||
_webRequest.Abort();
|
||||
_isAbort = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放下载器
|
||||
/// </summary>
|
||||
protected void DisposeRequest()
|
||||
{
|
||||
if (_webRequest != null)
|
||||
{
|
||||
//注意:引擎底层会自动调用Abort方法
|
||||
_webRequest.Dispose();
|
||||
_webRequest = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测请求结果
|
||||
/// </summary>
|
||||
protected bool CheckRequestResult()
|
||||
{
|
||||
HttpCode = _webRequest.responseCode;
|
||||
|
||||
#if UNITY_2020_3_OR_NEWER
|
||||
if (_webRequest.result != UnityWebRequest.Result.Success)
|
||||
{
|
||||
Error = $"URL : {_requestURL} Error : {_webRequest.error}";
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
if (_webRequest.isNetworkError || _webRequest.isHttpError)
|
||||
{
|
||||
Error = $"URL : {_requestURL} Error : {_webRequest.error}";
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e5c3c1a1655a8b41b585c6811201583
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,96 +0,0 @@
|
||||
using UnityEngine.Networking;
|
||||
using UnityEngine;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
internal class UnityWebTextRequestOperation : UnityWebRequestOperation
|
||||
{
|
||||
protected enum ESteps
|
||||
{
|
||||
None,
|
||||
CreateRequest,
|
||||
Download,
|
||||
Done,
|
||||
}
|
||||
|
||||
private UnityWebRequestAsyncOperation _requestOperation;
|
||||
private ESteps _steps = ESteps.None;
|
||||
|
||||
/// <summary>
|
||||
/// 响应的超时时间(单位:秒),在经过Timeout的秒数后尝试中止。
|
||||
/// 注意:当Timeout设置为0时,不会应用超时。
|
||||
/// 注意:设置的超时值可能应用于Android上的每个URL重定向,这可能会导致响应时间增加。
|
||||
/// </summary>
|
||||
private readonly int _timeout;
|
||||
|
||||
/// <summary>
|
||||
/// 请求结果
|
||||
/// </summary>
|
||||
public string Result { private set; get; }
|
||||
|
||||
|
||||
internal UnityWebTextRequestOperation(string url, int timeout) : base(url)
|
||||
{
|
||||
_timeout = timeout;
|
||||
}
|
||||
internal override void InternalStart()
|
||||
{
|
||||
_steps = ESteps.CreateRequest;
|
||||
}
|
||||
internal override void InternalUpdate()
|
||||
{
|
||||
if (_steps == ESteps.None || _steps == ESteps.Done)
|
||||
return;
|
||||
|
||||
if (_steps == ESteps.CreateRequest)
|
||||
{
|
||||
CreateWebRequest();
|
||||
_steps = ESteps.Download;
|
||||
}
|
||||
|
||||
if (_steps == ESteps.Download)
|
||||
{
|
||||
DownloadProgress = _webRequest.downloadProgress;
|
||||
DownloadedBytes = (long)_webRequest.downloadedBytes;
|
||||
Progress = _requestOperation.progress;
|
||||
if (_requestOperation.isDone == false)
|
||||
return;
|
||||
|
||||
if (CheckRequestResult())
|
||||
{
|
||||
var fileText = _webRequest.downloadHandler.text;
|
||||
if (string.IsNullOrEmpty(fileText))
|
||||
{
|
||||
_steps = ESteps.Done;
|
||||
Status = EOperationStatus.Failed;
|
||||
Error = $"URL : {_requestURL} Download handler text is null or empty !";
|
||||
}
|
||||
else
|
||||
{
|
||||
_steps = ESteps.Done;
|
||||
Result = fileText;
|
||||
Status = EOperationStatus.Succeed;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_steps = ESteps.Done;
|
||||
Status = EOperationStatus.Failed;
|
||||
}
|
||||
|
||||
// 注意:最终释放请求器
|
||||
DisposeRequest();
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateWebRequest()
|
||||
{
|
||||
DownloadHandlerBuffer handler = new DownloadHandlerBuffer();
|
||||
_webRequest = DownloadSystemHelper.NewUnityWebRequestGet(_requestURL);
|
||||
_webRequest.timeout = _timeout;
|
||||
_webRequest.downloadHandler = handler;
|
||||
_webRequest.disposeDownloadHandlerOnDispose = true;
|
||||
_requestOperation = _webRequest.SendWebRequest();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a488de5dcd6f4c448a47c4b574d5c9bc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
557
Assets/YooAsset/Runtime/DownloadSystem/README.md
Normal file
557
Assets/YooAsset/Runtime/DownloadSystem/README.md
Normal file
@@ -0,0 +1,557 @@
|
||||
# DownloadSystem 下载模块
|
||||
|
||||
## 模块概述
|
||||
|
||||
DownloadSystem 是 YooAsset 资源管理系统的**底层网络下载层**,负责处理所有 HTTP 网络请求。该模块提供了统一的下载接口抽象,支持文件下载、断点续传、并发请求(由上层调度)、看门狗监控等功能。
|
||||
|
||||
### 核心职责
|
||||
|
||||
- HTTP/HTTPS 文件下载
|
||||
- 断点续传支持
|
||||
- 看门狗超时保护
|
||||
- 多种下载类型(文件/字节/文本/AssetBundle)
|
||||
- 可插拔的网络库后端
|
||||
|
||||
---
|
||||
|
||||
## 边界与上层协作
|
||||
|
||||
DownloadSystem 的职责是提供“可替换后端 + 统一请求接口 + 轮询式生命周期”的基础能力:
|
||||
|
||||
- **本模块不负责并发队列/限流调度**:并发通常由上层同时创建多个 request 并自行控制并发数。
|
||||
- **本模块不负责重试/回退策略**:失败后的重试、切换 CDN、降级等策略通常由上层系统实现。
|
||||
- **本模块不负责持久化下载任务**:断点续传依赖本地已有文件与 `Range` 请求头,并由上层管理断点信息。
|
||||
|
||||
---
|
||||
|
||||
## 设计目标
|
||||
|
||||
| 目标 | 说明 |
|
||||
|------|------|
|
||||
| **可扩展性** | 支持可插拔的网络库后端(UnityWebRequest/BestHTTP/自研) |
|
||||
| **鲁棒性** | 看门狗超时保护、自动清理失败文件、完整错误信息 |
|
||||
| **高性能** | 轮询模式无阻塞、支持并发请求(并发数由上层调度) |
|
||||
| **易用性** | 流畅的参数构建 API、清晰的状态转换 |
|
||||
|
||||
---
|
||||
|
||||
## 架构概念
|
||||
|
||||
### 分层架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 上层调用者 │
|
||||
│ (FileSystem / ResourceManager) │
|
||||
└─────────────────────────┬───────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────▼───────────────────────────────┐
|
||||
│ IDownloadBackend │
|
||||
│ (后端接口) │
|
||||
│ 定义网络库合约,工厂模式创建请求 │
|
||||
└─────────────────────────┬───────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────▼───────────────────────────────┐
|
||||
│ IDownloadRequest │
|
||||
│ (请求接口) │
|
||||
│ 轮询式生命周期管理,状态机驱动 │
|
||||
└─────────────────────────┬───────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────▼───────────────────────────────┐
|
||||
│ UnityWebRequest / 其他网络库 │
|
||||
│ (底层实现) │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 核心组件
|
||||
|
||||
- **后端层 (IDownloadBackend)**: 定义网络库实现合约,通过工厂方法创建各类请求
|
||||
- **请求层 (IDownloadRequest)**: 统一的请求生命周期管理,支持轮询驱动
|
||||
- **参数层 (Args 结构体)**: 配置下载行为(超时、断点续传、看门狗等)
|
||||
|
||||
---
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
DownloadSystem/
|
||||
├── Interface/ # 接口定义
|
||||
│ ├── IDownloadBackend.cs # 后端接口(工厂模式)
|
||||
│ └── IDownloadRequest.cs # 请求接口层次结构
|
||||
│
|
||||
├── DefaultDownloadBackend/ # 默认后端实现
|
||||
│ ├── UnityWebRequestBackend.cs # UnityWebRequest 后端
|
||||
│ └── UnityWebRequestCreator.cs # UnityWebRequest 创建委托
|
||||
│
|
||||
├── DefaultDownloadRequest/ # 默认请求实现
|
||||
│ ├── UnityWebRequestDownloaderBase.cs # 基础下载器(抽象类)
|
||||
│ ├── UnityWebRequestFileDownloader.cs # 文件下载器
|
||||
│ ├── UnityWebRequestHeadDownloader.cs # HEAD 请求器
|
||||
│ ├── UnityWebRequestBytesDownloader.cs # 字节下载器
|
||||
│ ├── UnityWebRequestTextDownloader.cs # 文本下载器
|
||||
│ ├── UnityWebRequestAssetBundleDownloader.cs # AssetBundle 下载器
|
||||
│ └── VirtualFileDownloader.cs # 模拟下载器(编辑器用)
|
||||
│
|
||||
├── DownloadSystemDefine.cs # 枚举、结构体定义
|
||||
├── DownloadSystemHelper.cs # 工具函数
|
||||
└── WebRequestCounter.cs # 请求失败计数器
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 接口说明
|
||||
|
||||
### IDownloadBackend(后端接口)
|
||||
|
||||
定义网络库实现的合约,通过工厂方法创建各类下载请求。
|
||||
|
||||
```csharp
|
||||
public interface IDownloadBackend
|
||||
{
|
||||
/// <summary>
|
||||
/// 后端标识名称(用于日志/调试)
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 定期驱动更新(部分第三方库需要)
|
||||
/// </summary>
|
||||
void Update();
|
||||
|
||||
// 工厂方法 - 创建各类请求
|
||||
IDownloadHeadRequest CreateHeadRequest(DownloadDataRequestArgs args);
|
||||
IDownloadFileRequest CreateFileRequest(DownloadFileRequestArgs args);
|
||||
IDownloadBytesRequest CreateBytesRequest(DownloadDataRequestArgs args);
|
||||
IDownloadTextRequest CreateTextRequest(DownloadDataRequestArgs args);
|
||||
IDownloadAssetBundleRequest CreateAssetBundleRequest(DownloadAssetBundleRequestArgs args);
|
||||
IDownloadFileRequest CreateSimulateRequest(DownloadSimulateRequestArgs args);
|
||||
}
|
||||
```
|
||||
|
||||
### IDownloadRequest(基础请求接口)
|
||||
|
||||
所有下载请求的通用接口,定义生命周期和状态管理。
|
||||
|
||||
```csharp
|
||||
public interface IDownloadRequest : IDisposable
|
||||
{
|
||||
// 元信息
|
||||
string URL { get; }
|
||||
|
||||
// 生命周期
|
||||
bool IsDone { get; } // 每次访问自动轮询
|
||||
EDownloadRequestStatus Status { get; }
|
||||
|
||||
// 进度跟踪
|
||||
float DownloadProgress { get; } // 0f - 1f
|
||||
long DownloadedBytes { get; } // 本次请求新增字节数
|
||||
|
||||
// 诊断信息
|
||||
long HttpCode { get; }
|
||||
string Error { get; }
|
||||
|
||||
// 生命周期方法
|
||||
void SendRequest(); // 发起请求
|
||||
void PollingRequest(); // 轮询状态
|
||||
void AbortRequest(); // 中止请求
|
||||
}
|
||||
```
|
||||
|
||||
### 专化请求接口
|
||||
|
||||
| 接口 | 用途 | 特有属性 |
|
||||
|------|------|----------|
|
||||
| `IDownloadHeadRequest` | HEAD 请求,获取响应头 | `ETag`, `LastModified`, `ContentLength`, `ContentType` |
|
||||
| `IDownloadFileRequest` | 文件下载到本地 | `SavePath` |
|
||||
| `IDownloadBytesRequest` | 下载到内存(字节数组) | `byte[] Result` |
|
||||
| `IDownloadTextRequest` | 下载文本内容 | `string Result` |
|
||||
| `IDownloadAssetBundleRequest` | 下载并加载 AssetBundle | `AssetBundle Result` |
|
||||
|
||||
---
|
||||
|
||||
## 结构体定义
|
||||
|
||||
### 请求状态枚举
|
||||
|
||||
```csharp
|
||||
public enum EDownloadRequestStatus
|
||||
{
|
||||
None, // 未开始
|
||||
Running, // 进行中
|
||||
Succeed, // 已成功
|
||||
Failed, // 已失败
|
||||
Aborted // 已中止(用户中止或看门狗超时)
|
||||
}
|
||||
```
|
||||
|
||||
### 请求参数结构体
|
||||
|
||||
#### DownloadFileRequestArgs(文件下载参数)
|
||||
|
||||
```csharp
|
||||
public struct DownloadFileRequestArgs
|
||||
{
|
||||
public string URL; // 请求地址
|
||||
public int Timeout; // 响应超时(秒),0=无限制
|
||||
public int WatchdogTime; // 看门狗超时(秒)
|
||||
|
||||
public string SavePath; // 文件保存路径
|
||||
public bool AppendToFile; // 追加写入(断点续传)
|
||||
public bool RemoveFileOnAbort; // 中止时删除文件
|
||||
public long ResumeFromBytes; // 断点续传起始位置
|
||||
|
||||
public Dictionary<string, string> Headers; // 自定义请求头
|
||||
}
|
||||
```
|
||||
|
||||
#### DownloadDataRequestArgs(数据下载参数)
|
||||
|
||||
```csharp
|
||||
public struct DownloadDataRequestArgs
|
||||
{
|
||||
public string URL; // 请求地址
|
||||
public int Timeout; // 响应超时(秒)
|
||||
public int WatchdogTime; // 看门狗超时(秒)
|
||||
public Dictionary<string, string> Headers; // 自定义请求头
|
||||
}
|
||||
```
|
||||
|
||||
#### DownloadAssetBundleRequestArgs(AssetBundle 下载参数)
|
||||
|
||||
```csharp
|
||||
public struct DownloadAssetBundleRequestArgs
|
||||
{
|
||||
public string URL; // 请求地址
|
||||
public int Timeout; // 响应超时
|
||||
public int WatchdogTime; // 看门狗超时
|
||||
|
||||
public bool DisableUnityWebCache; // 禁用 Unity 缓存(推荐 true)
|
||||
public string FileHash; // 文件哈希(缓存启用时需要)
|
||||
public uint UnityCRC; // Unity CRC 校验值
|
||||
|
||||
public Dictionary<string, string> Headers;
|
||||
}
|
||||
```
|
||||
|
||||
#### DownloadSimulateRequestArgs(模拟下载参数)
|
||||
|
||||
```csharp
|
||||
public struct DownloadSimulateRequestArgs
|
||||
{
|
||||
public string URL; // 标识符
|
||||
public long FileSize; // 模拟文件大小
|
||||
public long DownloadSpeed; // 模拟速度(字节/秒),默认 1MB/s
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心类说明
|
||||
|
||||
### UnityWebRequestBackend
|
||||
|
||||
默认的后端实现,基于 Unity 的 UnityWebRequest API。
|
||||
|
||||
**特性:**
|
||||
- 支持自定义 UnityWebRequest 创建方式(证书验证、代理等)
|
||||
- 无需手动调用 Update(),UnityWebRequest 自动驱动
|
||||
|
||||
```csharp
|
||||
// 自定义 UnityWebRequest 创建(建议通过 backend 构造函数传入)
|
||||
UnityWebRequestCreator creator = (url, method) =>
|
||||
{
|
||||
var request = new UnityWebRequest(url, method);
|
||||
// 自定义配置...
|
||||
return request;
|
||||
};
|
||||
|
||||
IDownloadBackend backend = new UnityWebRequestBackend(creator);
|
||||
```
|
||||
|
||||
### UnityWebRequestDownloaderBase
|
||||
|
||||
抽象基类,封装所有下载器的通用逻辑。
|
||||
|
||||
**职责:**
|
||||
- 管理请求生命周期和状态转换
|
||||
- 实现看门狗监控机制
|
||||
- 追踪下载进度和字节数
|
||||
- 处理超时和错误
|
||||
|
||||
**生命周期:**
|
||||
|
||||
```
|
||||
None ──► SendRequest() ──► Running ──► PollingRequest() ──┬──► Succeed
|
||||
├──► Failed
|
||||
└──► Aborted
|
||||
```
|
||||
|
||||
### 具体下载器
|
||||
|
||||
| 下载器 | 实现接口 | 使用场景 |
|
||||
|--------|----------|----------|
|
||||
| `UnityWebRequestFileDownloader` | `IDownloadFileRequest` | 大文件下载到本地 |
|
||||
| `UnityWebRequestHeadDownloader` | `IDownloadHeadRequest` | 检查资源信息 |
|
||||
| `UnityWebRequestBytesDownloader` | `IDownloadBytesRequest` | 小文件内存加载 |
|
||||
| `UnityWebRequestTextDownloader` | `IDownloadTextRequest` | 文本文件下载 |
|
||||
| `UnityWebRequestAssetBundleDownloader` | `IDownloadAssetBundleRequest` | AB 包下载加载 |
|
||||
| `VirtualFileDownloader` | `IDownloadFileRequest` | 编辑器模拟下载 |
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基础文件下载
|
||||
|
||||
```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)
|
||||
{
|
||||
await Task.Yield();
|
||||
// 可选:显示进度
|
||||
float progress = request.DownloadProgress;
|
||||
}
|
||||
|
||||
// 3. 检查结果
|
||||
if (request.Status == EDownloadRequestStatus.Succeed)
|
||||
{
|
||||
Debug.Log("下载成功");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"下载失败: {request.Error}");
|
||||
}
|
||||
|
||||
// 4. 清理资源
|
||||
request.Dispose();
|
||||
```
|
||||
|
||||
### 断点续传
|
||||
|
||||
```csharp
|
||||
// 获取已下载的文件大小
|
||||
long existingFileSize = new FileInfo(savePath).Length;
|
||||
|
||||
var args = new DownloadFileRequestArgs(
|
||||
url: url,
|
||||
savePath: savePath,
|
||||
timeout: 30,
|
||||
watchdogTime: 0,
|
||||
appendToFile: true, // 追加写入
|
||||
removeFileOnAbort: false, // 中止时保留文件
|
||||
resumeFromBytes: existingFileSize); // 断点位置
|
||||
|
||||
IDownloadFileRequest request = backend.CreateFileRequest(args);
|
||||
request.SendRequest();
|
||||
// ... 轮询等待完成
|
||||
```
|
||||
|
||||
### 看门狗保护
|
||||
|
||||
```csharp
|
||||
var args = new DownloadFileRequestArgs(
|
||||
url: url,
|
||||
savePath: path,
|
||||
timeout: 30,
|
||||
watchdogTime: 30); // 30秒无数据自动中止
|
||||
|
||||
IDownloadFileRequest request = backend.CreateFileRequest(args);
|
||||
request.SendRequest();
|
||||
|
||||
while (!request.IsDone)
|
||||
{
|
||||
await Task.Yield();
|
||||
}
|
||||
|
||||
// 检查是否因看门狗超时而中止
|
||||
if (request.Status == EDownloadRequestStatus.Aborted)
|
||||
{
|
||||
Debug.LogWarning("下载超时,已自动中止");
|
||||
}
|
||||
```
|
||||
|
||||
### HEAD 请求获取文件信息
|
||||
|
||||
```csharp
|
||||
var args = new DownloadDataRequestArgs(
|
||||
url: "https://example.com/file.zip",
|
||||
timeout: 30,
|
||||
watchdogTime: 0);
|
||||
|
||||
IDownloadHeadRequest request = backend.CreateHeadRequest(args);
|
||||
request.SendRequest();
|
||||
|
||||
while (!request.IsDone)
|
||||
{
|
||||
await Task.Yield();
|
||||
}
|
||||
|
||||
if (request.Status == EDownloadRequestStatus.Succeed)
|
||||
{
|
||||
long fileSize = request.ContentLength;
|
||||
string etag = request.ETag;
|
||||
string lastModified = request.LastModified;
|
||||
|
||||
Debug.Log($"文件大小: {fileSize}, ETag: {etag}");
|
||||
}
|
||||
```
|
||||
|
||||
### 下载字节数据
|
||||
|
||||
```csharp
|
||||
var args = new DownloadDataRequestArgs(
|
||||
url: "https://example.com/data.json",
|
||||
timeout: 30,
|
||||
watchdogTime: 0);
|
||||
|
||||
IDownloadBytesRequest request = backend.CreateBytesRequest(args);
|
||||
request.SendRequest();
|
||||
|
||||
while (!request.IsDone)
|
||||
{
|
||||
await Task.Yield();
|
||||
}
|
||||
|
||||
if (request.Status == EDownloadRequestStatus.Succeed)
|
||||
{
|
||||
byte[] data = request.Result;
|
||||
// 处理数据...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 设计模式
|
||||
|
||||
### 工厂模式
|
||||
|
||||
`IDownloadBackend` 作为工厂接口,创建各类下载请求对象:
|
||||
|
||||
```
|
||||
IDownloadBackend
|
||||
├── CreateHeadRequest() ──► IDownloadHeadRequest
|
||||
├── CreateFileRequest() ──► IDownloadFileRequest
|
||||
├── CreateBytesRequest() ──► IDownloadBytesRequest
|
||||
├── CreateTextRequest() ──► IDownloadTextRequest
|
||||
├── CreateAssetBundleRequest() ──► IDownloadAssetBundleRequest
|
||||
└── CreateSimulateRequest() ──► IDownloadFileRequest
|
||||
```
|
||||
|
||||
### 策略模式
|
||||
|
||||
通过实现 `IDownloadBackend` 接口,可以替换底层网络库:
|
||||
|
||||
```
|
||||
IDownloadBackend (接口)
|
||||
├── UnityWebRequestBackend (默认实现)
|
||||
├── BestHTTPBackend (可扩展)
|
||||
└── CustomBackend (自定义)
|
||||
```
|
||||
|
||||
### 状态机模式
|
||||
|
||||
请求生命周期通过状态机管理:
|
||||
|
||||
```
|
||||
┌──────┐ SendRequest() ┌─────────┐
|
||||
│ None │ ──────────────────► │ Running │
|
||||
└──────┘ └────┬────┘
|
||||
│ PollingRequest()
|
||||
┌─────────────┼─────────────┐
|
||||
▼ ▼ ▼
|
||||
┌─────────┐ ┌──────────┐ ┌─────────┐
|
||||
│ Succeed │ │ Failed │ │ Aborted │
|
||||
└─────────┘ └──────────┘ └─────────┘
|
||||
```
|
||||
|
||||
### 看门狗模式
|
||||
|
||||
监控数据接收,防止网络卡顿导致请求无限等待:
|
||||
|
||||
```
|
||||
每帧轮询 PollingRequest()
|
||||
│
|
||||
├── 收到新数据 ──► 重置计时器
|
||||
│
|
||||
└── 未收到数据 ──► 计时器累加
|
||||
│
|
||||
└── 超过 WatchdogTime ──► AbortRequest()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 类继承关系
|
||||
|
||||
```
|
||||
IDownloadRequest (基础接口)
|
||||
│
|
||||
├── IDownloadHeadRequest (HEAD 请求)
|
||||
├── IDownloadFileRequest (文件下载)
|
||||
├── IDownloadBytesRequest (字节下载)
|
||||
├── IDownloadTextRequest (文本下载)
|
||||
└── IDownloadAssetBundleRequest (AssetBundle 下载)
|
||||
|
||||
UnityWebRequestDownloaderBase (抽象基类)
|
||||
│
|
||||
├── UnityWebRequestFileDownloader ──► IDownloadFileRequest
|
||||
├── UnityWebRequestHeadDownloader ──► IDownloadHeadRequest
|
||||
├── UnityWebRequestBytesDownloader ──► IDownloadBytesRequest
|
||||
├── UnityWebRequestTextDownloader ──► IDownloadTextRequest
|
||||
└── UnityWebRequestAssetBundleDownloader ──► IDownloadAssetBundleRequest
|
||||
|
||||
VirtualFileDownloader (独立实现) ──► IDownloadFileRequest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 工具类
|
||||
|
||||
### DownloadSystemHelper
|
||||
|
||||
提供跨平台的工具函数:
|
||||
|
||||
| 方法 | 说明 |
|
||||
|------|------|
|
||||
| `ConvertToWWWPath()` | 转换本地路径为 WWW 协议 URL |
|
||||
| `IsRequestLocalFile()` | 判断是否本地文件请求 |
|
||||
|
||||
### WebRequestCounter
|
||||
|
||||
请求失败计数器,用于诊断统计:
|
||||
|
||||
- 线程安全:内部使用 `Dictionary` 且未加锁,约定只在主线程调用;如需多线程统计请在外层加锁或改造实现
|
||||
- Key 规则:`$"{packageName}_{eventName}"`
|
||||
- 统计口径:**仅统计网络请求失败**(`IDownloadRequest.Status != Succeed` 时记录),不统计内容为空、校验失败、解析失败等业务层失败
|
||||
|
||||
```csharp
|
||||
// 记录失败
|
||||
WebRequestCounter.RecordRequestFailed(packageName, eventName);
|
||||
|
||||
// 查询失败次数
|
||||
int count = WebRequestCounter.GetRequestFailedCount(packageName, eventName);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **资源释放**:使用完毕后务必调用 `Dispose()` 释放资源
|
||||
- `AbortRequest()` 仅用于中止请求与切换状态,不等同于释放资源;无论成功/失败/中止都需要 `Dispose()`
|
||||
- 推荐使用 `try/finally` 确保释放(尤其是上层可能提前中止的场景)
|
||||
2. **断点续传**:需要服务器支持 `Range` 请求头和 `206 Partial Content` 响应
|
||||
- 若服务端不支持 Range 仍返回 200,全量内容可能会被追加写入,导致文件损坏
|
||||
3. **看门狗超时**:设置为 0 表示禁用,建议根据网络环境设置合理值
|
||||
4. **内存下载**:`IDownloadBytesRequest` 会将整个响应体加载到内存,不适合大文件
|
||||
5. **驱动更新**:部分第三方网络库实现的 backend 可能需要每帧调用 `IDownloadBackend.Update()` 进行驱动
|
||||
6. **中止语义**:`Aborted` 可能来自用户主动 `AbortRequest()` 或看门狗超时;中止场景下 `HttpCode/Error` 可能为默认值(例如 0/空)
|
||||
7. **线程安全**:所有下载请求的创建和轮询应在主线程进行
|
||||
7
Assets/YooAsset/Runtime/DownloadSystem/README.md.meta
Normal file
7
Assets/YooAsset/Runtime/DownloadSystem/README.md.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c65d3ba8da65004b8d36dbeecc6c6be
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,9 +1,14 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace YooAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// 网络请求失败计数器(诊断用)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 线程安全:内部使用 Dictionary 且未加锁,约定只在 Unity 主线程调用。
|
||||
/// 如需在多线程/回调线程调用,请在外层加锁或改为并发容器实现。
|
||||
/// </remarks>
|
||||
internal class WebRequestCounter
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
@@ -15,12 +20,12 @@ namespace YooAsset
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 记录网络请求失败事件的次数
|
||||
/// 失败计数记录表(key = $"{packageName}_{eventName}")
|
||||
/// </summary>
|
||||
private static readonly Dictionary<string, int> _requestFailedRecorder = new Dictionary<string, int>(1000);
|
||||
|
||||
/// <summary>
|
||||
/// 记录请求失败事件
|
||||
/// 记录一次失败
|
||||
/// </summary>
|
||||
public static void RecordRequestFailed(string packageName, string eventName)
|
||||
{
|
||||
@@ -31,14 +36,14 @@ namespace YooAsset
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取请求失败的次数
|
||||
/// 获取失败次数
|
||||
/// </summary>
|
||||
public static int GetRequestFailedCount(string packageName, string eventName)
|
||||
{
|
||||
string key = $"{packageName}_{eventName}";
|
||||
if (_requestFailedRecorder.ContainsKey(key) == false)
|
||||
_requestFailedRecorder.Add(key, 0);
|
||||
return _requestFailedRecorder[key];
|
||||
if (_requestFailedRecorder.TryGetValue(key, out int count))
|
||||
return count;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -108,14 +108,7 @@ namespace YooAsset
|
||||
}
|
||||
internal override void InternalWaitForAsyncComplete()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (ExecuteWhileDone())
|
||||
{
|
||||
_steps = ESteps.Done;
|
||||
break;
|
||||
}
|
||||
}
|
||||
RunBatchExecution();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -108,14 +108,7 @@ namespace YooAsset
|
||||
}
|
||||
internal override void InternalWaitForAsyncComplete()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (ExecuteWhileDone())
|
||||
{
|
||||
_steps = ESteps.Done;
|
||||
break;
|
||||
}
|
||||
}
|
||||
RunBatchExecution();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ namespace YooAsset
|
||||
if (IsWaitForAsyncComplete)
|
||||
{
|
||||
//注意:场景加载无法强制异步转同步
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -108,14 +108,7 @@ namespace YooAsset
|
||||
}
|
||||
internal override void InternalWaitForAsyncComplete()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (ExecuteWhileDone())
|
||||
{
|
||||
_steps = ESteps.Done;
|
||||
break;
|
||||
}
|
||||
}
|
||||
RunBatchExecution();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ namespace YooAsset
|
||||
{
|
||||
internal override void InternalStart()
|
||||
{
|
||||
Error = $"{nameof(RawBundleLoadAllAssetsOperation)} not support load all assets !";
|
||||
Error = $"{nameof(RawBundleLoadAllAssetsOperation)} not support load all assets.";
|
||||
Status = EOperationStatus.Failed;
|
||||
}
|
||||
internal override void InternalUpdate()
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace YooAsset
|
||||
{
|
||||
internal override void InternalStart()
|
||||
{
|
||||
Error = $"{nameof(RawBundleLoadAssetOperation)} not support load asset !";
|
||||
Error = $"{nameof(RawBundleLoadAssetOperation)} not support load asset.";
|
||||
Status = EOperationStatus.Failed;
|
||||
}
|
||||
internal override void InternalUpdate()
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace YooAsset
|
||||
{
|
||||
internal override void InternalStart()
|
||||
{
|
||||
Error = $"{nameof(RawBundleLoadSceneOperation)} not support load scene !";
|
||||
Error = $"{nameof(RawBundleLoadSceneOperation)} not support load scene.";
|
||||
Status = EOperationStatus.Failed;
|
||||
}
|
||||
internal override void InternalUpdate()
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace YooAsset
|
||||
{
|
||||
internal override void InternalStart()
|
||||
{
|
||||
Error = $"{nameof(RawBundleLoadSubAssetsOperation)} not support load sub assets !";
|
||||
Error = $"{nameof(RawBundleLoadSubAssetsOperation)} not support load sub assets.";
|
||||
Status = EOperationStatus.Failed;
|
||||
}
|
||||
internal override void InternalUpdate()
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace YooAsset
|
||||
_steps = ESteps.CheckBundle;
|
||||
#else
|
||||
_steps = ESteps.Done;
|
||||
Error = $"{nameof(VirtualBundleLoadAllAssetsOperation)} only support unity editor platform !";
|
||||
Error = $"{nameof(VirtualBundleLoadAllAssetsOperation)} only support unity editor platform.";
|
||||
Status = EOperationStatus.Failed;
|
||||
#endif
|
||||
}
|
||||
@@ -109,14 +109,7 @@ namespace YooAsset
|
||||
}
|
||||
internal override void InternalWaitForAsyncComplete()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (ExecuteWhileDone())
|
||||
{
|
||||
_steps = ESteps.Done;
|
||||
break;
|
||||
}
|
||||
}
|
||||
RunBatchExecution();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ namespace YooAsset
|
||||
_steps = ESteps.CheckBundle;
|
||||
#else
|
||||
_steps = ESteps.Done;
|
||||
Error = $"{nameof(VirtualBundleLoadAssetOperation)} only support unity editor platform !";
|
||||
Error = $"{nameof(VirtualBundleLoadAssetOperation)} only support unity editor platform.";
|
||||
Status = EOperationStatus.Failed;
|
||||
#endif
|
||||
}
|
||||
@@ -88,14 +88,7 @@ namespace YooAsset
|
||||
}
|
||||
internal override void InternalWaitForAsyncComplete()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (ExecuteWhileDone())
|
||||
{
|
||||
_steps = ESteps.Done;
|
||||
break;
|
||||
}
|
||||
}
|
||||
RunBatchExecution();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ namespace YooAsset
|
||||
_steps = ESteps.LoadScene;
|
||||
#else
|
||||
_steps = ESteps.Done;
|
||||
Error = $"{nameof(VirtualBundleLoadSceneOperation)} only support unity editor platform !";
|
||||
Error = $"{nameof(VirtualBundleLoadSceneOperation)} only support unity editor platform.";
|
||||
Status = EOperationStatus.Failed;
|
||||
#endif
|
||||
}
|
||||
@@ -77,7 +77,7 @@ namespace YooAsset
|
||||
if (IsWaitForAsyncComplete)
|
||||
{
|
||||
// 注意:场景加载无法强制异步转同步
|
||||
YooLogger.Error("The scene is loading asyn !");
|
||||
YooLogger.Error("The scene is loading asyn.");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -113,7 +113,7 @@ namespace YooAsset
|
||||
internal override void InternalWaitForAsyncComplete()
|
||||
{
|
||||
//注意:场景加载不支持异步转同步,为了支持同步加载方法需要实现该方法!
|
||||
InternalUpdate();
|
||||
RunOnceExecution();
|
||||
}
|
||||
public override void UnSuspendLoad()
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace YooAsset
|
||||
_steps = ESteps.CheckBundle;
|
||||
#else
|
||||
_steps = ESteps.Done;
|
||||
Error = $"{nameof(VirtualBundleLoadSubAssetsOperation)} only support unity editor platform !";
|
||||
Error = $"{nameof(VirtualBundleLoadSubAssetsOperation)} only support unity editor platform.";
|
||||
Status = EOperationStatus.Failed;
|
||||
#endif
|
||||
}
|
||||
@@ -100,14 +100,7 @@ namespace YooAsset
|
||||
}
|
||||
internal override void InternalWaitForAsyncComplete()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (ExecuteWhileDone())
|
||||
{
|
||||
_steps = ESteps.Done;
|
||||
break;
|
||||
}
|
||||
}
|
||||
RunBatchExecution();
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user