Compare commits

..

43 Commits

Author SHA1 Message Date
何冠峰
bfb166ad91 Update CHANGELOG.md 2026-06-18 14:39:28 +08:00
何冠峰
f31e24bf59 Update package.json 2026-06-18 14:38:38 +08:00
何冠峰
868a186199 update AssetInfo 2026-06-18 14:14:50 +08:00
何冠峰
092af257d7 feat : optimize package manifest 2026-06-18 14:01:14 +08:00
何冠峰
d39d9b59ef fix: correct decryptor enum names
修正解密器枚举名称与微信文件系统类型名
2026-06-03 18:23:35 +08:00
何冠峰
31c09d53bb fix #751 2026-06-03 18:01:18 +08:00
何冠峰
a6299268d3 feat: refactor AssetReference
将资源弱引用的公共逻辑抽象到基类中,并新增对 Texture2D 的支持。
2026-06-03 17:30:03 +08:00
何冠峰
89b516942e Merge pull request #752 from absences/wx
Refactor file system class string assignment
2026-06-03 10:31:51 +08:00
absences
8c49c27264 Refactor file system class string assignment 2026-06-02 21:22:06 +08:00
何冠峰
51c8c00604 fix #660
修复 IL2CPP 逆变委托崩溃
2026-06-02 17:24:59 +08:00
何冠峰
cb96767f2d feat : add editable Collector Inspector extension for bundle collector 2026-06-01 19:22:05 +08:00
何冠峰
55d98dde02 update Mini Game
update Mini Game
2026-05-29 11:23:32 +08:00
何冠峰
f631d67db3 Update CHANGELOG.md 2026-05-28 22:12:28 +08:00
何冠峰
a2fa8ff6fc Update package.json 2026-05-28 22:12:19 +08:00
何冠峰
477954377d Update EPlayMode.cs 2026-05-28 21:48:11 +08:00
何冠峰
513c1d0b18 feat: support ArchiveBundle loading in Web file systems 2026-05-28 21:42:07 +08:00
何冠峰
289dee326b feat : support encrypted ArchiveBundle 2026-05-28 20:43:22 +08:00
何冠峰
d5a9b9f0f4 Add explicit YooAsset macro gates 2026-05-28 11:03:28 +08:00
何冠峰
aff50317cb feat : add IBuiltinFileAccessor 2026-05-28 10:37:42 +08:00
何冠峰
9e969d34f6 feat : add IBundleUnpackPolicy 2026-05-27 14:33:31 +08:00
何冠峰
926f72d129 update space shooter 2026-05-27 10:36:35 +08:00
何冠峰
72c5a9588e feat : add WebNetwork file system 2026-05-27 10:31:35 +08:00
何冠峰
af7aecbf0b change : remove legacy WebGame and WebRemote file systems 2026-05-27 10:31:15 +08:00
何冠峰
d337a86e68 feat : add WebNetwork file system 2026-05-26 19:18:39 +08:00
何冠峰
9c05f3514e fix #696 2026-05-25 19:44:31 +08:00
何冠峰
b31bb1bcfb feat: add collect asset search to Bundle Collector 2026-05-25 19:38:17 +08:00
何冠峰
92e34fb8b5 feat : web remote file system support raw bundle 2026-05-21 22:08:13 +08:00
何冠峰
687dc1d9e9 feat : web server file system support raw bundle 2026-05-21 22:07:44 +08:00
何冠峰
7842235af6 feat : web game file system support raw bundle 2026-05-21 21:44:51 +08:00
何冠峰
0142828b91 docs: unify runtime options comments 2026-05-21 18:43:22 +08:00
何冠峰
a36a7cc6d2 feat : kuaishou file system support 2026-05-21 17:30:59 +08:00
何冠峰
485ed5198e feat : vivo file system support 2026-05-21 17:17:50 +08:00
何冠峰
2170156216 feat : oppo file system support 2026-05-21 17:17:37 +08:00
何冠峰
99b975554a refactor: remove WebGame platform cache query APIs
移除 WebGame 平台缓存查询接口
2026-05-21 14:34:07 +08:00
何冠峰
2f8355c3f4 feat : instant asset build pipeline 2026-05-21 14:06:26 +08:00
何冠峰
f05f794461 Update CHANGELOG.md 2026-05-19 16:32:45 +08:00
何冠峰
a5ec8e8eb9 Update package.json 2026-05-19 16:21:32 +08:00
何冠峰
c0c3bb406d feat: add bundle type dropdown for editor simulate pipeline 2026-05-19 16:00:33 +08:00
何冠峰
656b8f18e2 feat: complete compatibility layer 2026-05-19 15:30:38 +08:00
何冠峰
429a7395ca update test sample 2026-05-19 14:26:25 +08:00
何冠峰
f8f2dd8faf feat : add editor bundle cache markers 2026-05-19 14:16:23 +08:00
何冠峰
45ecd8baff feat: add EnsureBundleFileAsync operation 2026-05-15 18:37:01 +08:00
何冠峰
322c4a9847 refactor: rename LoadRawFile API to LoadBundleFile with BundleFileHandle 2026-05-13 17:42:07 +08:00
448 changed files with 8740 additions and 3238 deletions

View File

@@ -2,6 +2,138 @@
All notable changes to this package will be documented in this file.
## [3.0.3-beta] - 2026-06-18
本版本重点优化资源清单二进制结构,显著减小清单体积、同时降低运行时内存占用并提升资源定位查询效率。。
### Added
- 新增 `AssetInfo.Tags` 资源标签访问接口
支持通过 `AssetInfo` 直接访问资源标签集合,便于在资源查询结果中判断和遍历标签。
- 新增 Bundle Collector 可编辑 Inspector 扩展
支持在 Inspector 面板中扩展和编辑 Bundle Collector 相关配置,方便自定义收集器配置界面。
### Changed
- 优化资源清单二进制结构
减小资源清单文件体积,降低运行时内存占用,并提升资源定位查询效率。
小型清单体积约减少 20%,重度项目预计可减少 35%~50%。
- 重构 `AssetReference`
调整资源引用相关实现,提升接口一致性和维护性,新增对 `Texture2D` 的支持。
- 调整编辑器缓存目录位置
编辑器模式下的缓存根目录调整到项目 `Library` 目录内,避免缓存文件直接生成在项目根目录,减少对工程目录的污染。
### Fixed
- (#752) 修复小游戏扩展类解密器枚举命名错误。
- (#660) 修复了UniTask扩展类里 IL2CPP 逆变委托崩溃。
## [3.0.2-beta] - 2026-05-28
### Added
- 新增 `WebNetworkFileSystem` 文件系统
用于统一 WebGL 远程加载和 Mini Game 平台资源加载流程,并通过 `IWebPlatformStrategy` 适配不同平台的 AssetBundle 请求、提取和卸载行为。
- 新增 Web 文件系统对 `RawBundle``ArchiveBundle` 的加载支持
`WebServerFileSystem``WebNetworkFileSystem` 支持在 WebGL 平台加载原始文件包和归档资源包。
- 新增加密 `ArchiveBundle` 构建和加载支持
`ArchiveFileBuildPipeline` 支持加密归档资源包,运行时可通过 `EFileSystemParameter.ArchiveBundleDecryptor` 配置对应解密器。
- 新增 `IBundleUnpackPolicy` 内置资源包解包策略
支持根据资源包名称、文件名、类型、加密状态和标签,自定义哪些内置资源包需要解包。
- 新增 `IBuiltinFileAccessor` 内置文件访问器
支持为 `StreamingAssets` 文件提供自定义存在检测和字节读取能力,方便 Android 等平台接入第三方同步读取方案。
- 新增 Bundle Collector 资源搜索功能
支持在收集器窗口输入或拖入资源路径,定位资源所在的分组和收集器,并高亮命中的资源配置。
- 新增 Mini Game 平台示例
补充 OPPO、vivo、快手小游戏文件系统示例。
### Changed
- WebGL 运行模式文件系统结构调整
移除旧版 `WebGameFileSystem``WebRemoteFileSystem`,相关能力已合并到新的文件系统 `WebNetworkFileSystem`
### Fixed
- 修复微信小游戏示例宏兼容问题
微信小游戏文件系统同时支持 `WEIXINMINIGAME``UNITY_WECHATMINIGAME` 宏。
## [3.0.1-beta] - 2026-05-19
### Added
- 新增归档文件构建管线 `ArchiveFileBuildPipeline`
支持将同一资源包内的多个原始文件合并为 `ArchiveBundle`,并提供构建参数、构建任务、编辑器构建界面和运行时加载支持。
- 新增 `EnsureBundleFileOperation` 操作
用于确保资源包文件已就绪,并通过 `EnsureBundleFileOperation.Detail` 获取资源包名称、本地文件路径、资源包类型和加密状态。
**代码示例**
```csharp
var package = YooAssets.GetPackage("DefaultPackage");
// 确保资源所在的资源包文件已经就绪。
// 如果资源包未缓存,会自动触发下载或解压流程。
var operation = package.EnsureBundleFileAsync(new EnsureBundleFileOptions("asset_location"));
yield return operation;
if (operation.Status == EOperationStatus.Succeeded)
{
var detail = operation.Detail;
string bundleName = detail.BundleName;
string bundleFilePath = detail.BundleFilePath;
int bundleType = detail.BundleType;
bool isEncrypted = detail.IsEncrypted;
UnityEngine.Debug.Log($"BundleName: {bundleName}");
UnityEngine.Debug.Log($"BundleFilePath: {bundleFilePath}");
UnityEngine.Debug.Log($"BundleType: {bundleType}");
UnityEngine.Debug.Log($"IsEncrypted: {isEncrypted}");
}
else
{
UnityEngine.Debug.LogError(operation.Error);
}
```
- 新增编辑器模拟模式下的 Bundle Cache 标记文件机制
通过记录缓存状态,提升虚拟下载模式下缓存扫描、恢复和清理的可靠性。
### Changed
- `LoadRawFile` API 重命名为 `LoadBundleFile`
对应句柄由 `RawFileHandle` 重命名为 `BundleFileHandle`,语义从“原生文件”调整为“资源包文件”。
## [3.0.0-beta] - 2026-05-09
beta released.

View File

@@ -216,19 +216,19 @@ namespace YooAsset.Editor
}
/// <summary>
/// 创建PackageBundle类
/// 创建BuildBundle类
/// </summary>
internal PackageBundle CreatePackageBundle()
internal BuildBundle CreateBuildBundle()
{
PackageBundle packageBundle = new PackageBundle();
packageBundle.BundleName = BundleName;
packageBundle.UnityCrc = PackageUnityCRC;
packageBundle.FileHash = PackageFileHash;
packageBundle.FileCrc = PackageFileCRC;
packageBundle.FileSize = PackageFileSize;
packageBundle.IsEncrypted = Encrypted;
packageBundle.DependentBundleIDs = Array.Empty<int>();
return packageBundle;
BuildBundle buildBundle = new BuildBundle();
buildBundle.BundleName = BundleName;
buildBundle.UnityCrc = PackageUnityCRC;
buildBundle.FileHash = PackageFileHash;
buildBundle.FileCrc = PackageFileCRC;
buildBundle.FileSize = PackageFileSize;
buildBundle.IsEncrypted = Encrypted;
buildBundle.DependentBundleIDs = Array.Empty<int>();
return buildBundle;
}
}
}

View File

@@ -27,14 +27,6 @@ namespace YooAsset.Editor
/// <inheritdoc />
protected override void CheckBuildParametersCore()
{
// ArchiveBundle 不支持资源包加密
if (BundleEncryptor != null)
{
string message = BuildLogger.GetErrorMessage(ErrorCode.BundleEncryptionNotSupported,
$"ArchiveFileBuildPipeline does not support bundle encryption. Please remove the BundleEncryptor configuration.");
throw new NotSupportedException(message);
}
// 校验文件对齐参数范围
if (FileAlignment < 0 || FileAlignment > MaxFileAlignment)
{

View File

@@ -1,4 +1,4 @@
#if TUANJIE_1_8_OR_NEWER
#if TUANJIE_1_8_OR_NEWER && YOOASSET_INSTANT_ASSET_SUPPORT
using System;
using System.Collections.Generic;
using UnityEditor;

View File

@@ -1,4 +1,4 @@
#if TUANJIE_1_8_OR_NEWER
#if TUANJIE_1_8_OR_NEWER && YOOASSET_INSTANT_ASSET_SUPPORT
using UnityEditor;
namespace YooAsset.Editor

View File

@@ -1,4 +1,4 @@
#if TUANJIE_1_8_OR_NEWER
#if TUANJIE_1_8_OR_NEWER && YOOASSET_INSTANT_ASSET_SUPPORT
using System;
namespace YooAsset.Editor

View File

@@ -1,4 +1,4 @@
#if TUANJIE_1_8_OR_NEWER
#if TUANJIE_1_8_OR_NEWER && YOOASSET_INSTANT_ASSET_SUPPORT
namespace YooAsset.Editor
{
/// <summary>

View File

@@ -1,4 +1,4 @@
#if TUANJIE_1_8_OR_NEWER
#if TUANJIE_1_8_OR_NEWER && YOOASSET_INSTANT_ASSET_SUPPORT
using System.Collections.Generic;
namespace YooAsset.Editor

View File

@@ -1,4 +1,4 @@
#if TUANJIE_1_8_OR_NEWER
#if TUANJIE_1_8_OR_NEWER && YOOASSET_INSTANT_ASSET_SUPPORT
namespace YooAsset.Editor
{
/// <summary>

View File

@@ -1,4 +1,4 @@
#if TUANJIE_1_8_OR_NEWER
#if TUANJIE_1_8_OR_NEWER && YOOASSET_INSTANT_ASSET_SUPPORT
namespace YooAsset.Editor
{
/// <summary>

View File

@@ -1,4 +1,4 @@
#if TUANJIE_1_8_OR_NEWER
#if TUANJIE_1_8_OR_NEWER && YOOASSET_INSTANT_ASSET_SUPPORT
namespace YooAsset.Editor
{
/// <summary>

View File

@@ -1,4 +1,4 @@
#if TUANJIE_1_8_OR_NEWER
#if TUANJIE_1_8_OR_NEWER && YOOASSET_INSTANT_ASSET_SUPPORT
using System;
namespace YooAsset.Editor
@@ -68,8 +68,7 @@ namespace YooAsset.Editor
{
string bundleName = bundleInfo.BundleName;
string fileHash = bundleInfo.PackageFileHash;
string fileExtension = PackageManifestHelper.GetRemoteBundleFileExtension(bundleName);
string fileName = PackageManifestHelper.GetRemoteBundleFileName(outputNameStyle, bundleName, fileExtension, fileHash);
string fileName = BundleFileNaming.GetBundleFileName(outputNameStyle, bundleName, fileHash);
bundleInfo.PackageDestFilePath = $"{packageOutputDirectory}/{fileName}";
}
}

View File

@@ -1,4 +1,4 @@
#if TUANJIE_1_8_OR_NEWER
#if TUANJIE_1_8_OR_NEWER && YOOASSET_INSTANT_ASSET_SUPPORT
using System;
using System.Linq;
using System.Collections.Generic;

View File

@@ -1,4 +1,4 @@
#if TUANJIE_1_8_OR_NEWER
#if TUANJIE_1_8_OR_NEWER && YOOASSET_INSTANT_ASSET_SUPPORT
using System;
using System.IO;
using System.Collections.Generic;

View File

@@ -1,4 +1,4 @@
#if TUANJIE_1_8_OR_NEWER
#if TUANJIE_1_8_OR_NEWER && YOOASSET_INSTANT_ASSET_SUPPORT
using UnityEditor;
namespace YooAsset.Editor

View File

@@ -1,4 +1,4 @@
#if TUANJIE_1_8_OR_NEWER
#if TUANJIE_1_8_OR_NEWER && YOOASSET_INSTANT_ASSET_SUPPORT
using System;
using System.Collections.Generic;

View File

@@ -1,4 +1,4 @@
#if TUANJIE_1_8_OR_NEWER
#if TUANJIE_1_8_OR_NEWER && YOOASSET_INSTANT_ASSET_SUPPORT
using System.Text;
using System.IO;
using System.Collections.Generic;

View File

@@ -25,7 +25,7 @@ namespace YooAsset.Editor
/// <summary>
/// 拷贝首包资源文件
/// </summary>
internal void CopyBundledFilesToStreaming(BuildParametersContext buildParametersContext, PackageManifest manifest)
internal void CopyBundledFilesToStreaming(BuildParametersContext buildParametersContext, BuildManifest manifest)
{
EBundledCopyOption copyOption = buildParametersContext.Parameters.BundledCopyOption;
string packageOutputDirectory = buildParametersContext.GetPackageOutputDirectory();
@@ -67,10 +67,10 @@ namespace YooAsset.Editor
// 拷贝文件列表(所有文件)
if (copyOption == EBundledCopyOption.ClearAndCopyAll || copyOption == EBundledCopyOption.OnlyCopyAll)
{
foreach (var packageBundle in manifest.BundleList)
foreach (var buildBundle in manifest.BundleList)
{
string sourcePath = $"{packageOutputDirectory}/{packageBundle.GetFileName()}";
string destPath = $"{bundledRootDirectory}/{packageBundle.GetFileName()}";
string sourcePath = $"{packageOutputDirectory}/{buildBundle.GetFileName(manifest.OutputNameStyle)}";
string destPath = $"{bundledRootDirectory}/{buildBundle.GetFileName(manifest.OutputNameStyle)}";
EditorFileUtility.CopyFile(sourcePath, destPath, true);
}
}
@@ -85,12 +85,12 @@ namespace YooAsset.Editor
throw new InvalidOperationException(message);
}
string[] tags = copyParams.Split(';');
foreach (var packageBundle in manifest.BundleList)
foreach (var buildBundle in manifest.BundleList)
{
if (packageBundle.HasAnyTag(tags) == false)
if (buildBundle.HasAnyTag(tags) == false)
continue;
string sourcePath = $"{packageOutputDirectory}/{packageBundle.GetFileName()}";
string destPath = $"{bundledRootDirectory}/{packageBundle.GetFileName()}";
string sourcePath = $"{packageOutputDirectory}/{buildBundle.GetFileName(manifest.OutputNameStyle)}";
string destPath = $"{bundledRootDirectory}/{buildBundle.GetFileName(manifest.OutputNameStyle)}";
EditorFileUtility.CopyFile(sourcePath, destPath, true);
}
}

View File

@@ -12,7 +12,7 @@ namespace YooAsset.Editor
[ContextObject]
public class ManifestContext
{
internal PackageManifest Manifest;
internal BuildManifest Manifest;
}
/// <summary>
@@ -44,7 +44,7 @@ namespace YooAsset.Editor
CheckBundleHashConflict(buildMapContext);
// 创建新补丁清单
PackageManifest manifest = new PackageManifest();
BuildManifest manifest = new BuildManifest();
manifest.FileVersion = PackageManifestConsts.FileVersion;
manifest.EnableAddressable = buildMapContext.Command.EnableAddressable;
manifest.SupportExtensionless = buildMapContext.Command.SupportExtensionless;
@@ -74,20 +74,15 @@ namespace YooAsset.Editor
// 4. 处理首包资源包
if (processBundleDepends)
{
// 注意:初始化资源清单建立引用关系
manifest.Initialize();
// 注意:建立资源包引用关系
manifest.BuildReferrerGraph();
ProcessBuiltinBundleDependency(context, manifest);
}
// 创建资源清单文本文件
{
string fileName = YooAssetConfiguration.GetManifestJsonFileName(buildParameters.PackageName, buildParameters.PackageVersion);
string filePath = $"{packageOutputDirectory}/{fileName}";
PackageManifestHelper.SerializeManifestToJson(filePath, manifest);
BuildLogger.Log($"Create package manifest file: '{filePath}'.");
}
// 5. 构建全局标签表与目录表
manifest.BuildTagTable();
manifest.BuildDirectoryTable();
// 创建资源清单二进制文件
string packageHash;
@@ -119,8 +114,7 @@ namespace YooAsset.Editor
// 填充上下文
{
ManifestContext manifestContext = new ManifestContext();
byte[] bytesData = FileUtility.ReadAllBytes(packagePath);
manifestContext.Manifest = PackageManifestHelper.DeserializeManifestFromBinary(bytesData, buildParameters.ManifestDecryptor);
manifestContext.Manifest = manifest;
context.SetContextObject(manifestContext);
}
}
@@ -158,21 +152,21 @@ namespace YooAsset.Editor
/// <summary>
/// 创建资源对象列表
/// </summary>
private List<PackageAsset> CreatePackageAssetList(BuildMapContext buildMapContext)
private List<BuildAsset> CreatePackageAssetList(BuildMapContext buildMapContext)
{
List<PackageAsset> result = new List<PackageAsset>(1000);
List<BuildAsset> result = new List<BuildAsset>(1000);
foreach (var bundleInfo in buildMapContext.Collection)
{
var assetInfos = bundleInfo.GetAllManifestAssetInfos();
foreach (var assetInfo in assetInfos)
{
PackageAsset packageAsset = new PackageAsset();
packageAsset.Address = buildMapContext.Command.EnableAddressable ? assetInfo.Address : string.Empty;
packageAsset.AssetPath = assetInfo.AssetInfo.AssetPath;
packageAsset.AssetGuid = buildMapContext.Command.IncludeAssetGUID ? assetInfo.AssetInfo.AssetGUID : string.Empty;
packageAsset.AssetTags = assetInfo.AssetTags.ToArray();
packageAsset.EditorUserData = assetInfo;
result.Add(packageAsset);
BuildAsset buildAsset = new BuildAsset();
buildAsset.Address = buildMapContext.Command.EnableAddressable ? assetInfo.Address : string.Empty;
buildAsset.AssetPath = assetInfo.AssetInfo.AssetPath;
buildAsset.AssetGuid = buildMapContext.Command.IncludeAssetGUID ? assetInfo.AssetInfo.AssetGUID : string.Empty;
buildAsset.Tags = assetInfo.AssetTags.ToArray();
buildAsset.EditorUserData = assetInfo;
result.Add(buildAsset);
}
}
@@ -184,13 +178,13 @@ namespace YooAsset.Editor
/// <summary>
/// 创建资源包列表
/// </summary>
private List<PackageBundle> CreatePackageBundleList(BuildMapContext buildMapContext)
private List<BuildBundle> CreatePackageBundleList(BuildMapContext buildMapContext)
{
List<PackageBundle> result = new List<PackageBundle>(1000);
List<BuildBundle> result = new List<BuildBundle>(1000);
foreach (var bundleInfo in buildMapContext.Collection)
{
var packageBundle = bundleInfo.CreatePackageBundle();
result.Add(packageBundle);
var buildBundle = bundleInfo.CreateBuildBundle();
result.Add(buildBundle);
}
// 按照BundleName排序
@@ -201,7 +195,7 @@ namespace YooAsset.Editor
/// <summary>
/// 处理资源清单的资源对象列表
/// </summary>
private void ProcessPackageAsset(PackageManifest manifest)
private void ProcessPackageAsset(BuildManifest manifest)
{
// 注意:优先缓存资源包索引
for (int index = 0; index < manifest.BundleList.Count; index++)
@@ -211,31 +205,31 @@ namespace YooAsset.Editor
}
// 记录资源对象所属的资源包ID
foreach (var packageAsset in manifest.AssetList)
foreach (var buildAsset in manifest.AssetList)
{
var assetInfo = packageAsset.EditorUserData as BuildAssetInfo;
packageAsset.BundleID = GetCachedBundleIndexID(assetInfo.BundleName);
var assetInfo = buildAsset.EditorUserData as BuildAssetInfo;
buildAsset.BundleID = GetCachedBundleIndexID(assetInfo.BundleName);
}
// 记录资源对象依赖的资源包ID集合
// 注意:依赖关系非引擎构建结果里查询!
foreach (var packageAsset in manifest.AssetList)
foreach (var buildAsset in manifest.AssetList)
{
var mainAssetInfo = packageAsset.EditorUserData as BuildAssetInfo;
packageAsset.DependentBundleIDs = GetAssetDependBundleIDs(mainAssetInfo);
var mainAssetInfo = buildAsset.EditorUserData as BuildAssetInfo;
buildAsset.DependentBundleIDs = GetAssetDependBundleIDs(mainAssetInfo);
}
}
/// <summary>
/// 处理资源包的依赖集合
/// </summary>
private void ProcessBundleDepends(BuildContext context, PackageManifest manifest)
private void ProcessBundleDepends(BuildContext context, BuildManifest manifest)
{
// 查询引擎生成的资源包依赖关系,然后记录到清单
foreach (var packageBundle in manifest.BundleList)
foreach (var buildBundle in manifest.BundleList)
{
int mainBundleID = GetCachedBundleIndexID(packageBundle.BundleName);
string[] dependNames = GetBundleDepends(context, packageBundle.BundleName);
int mainBundleID = GetCachedBundleIndexID(buildBundle.BundleName);
string[] dependNames = GetBundleDepends(context, buildBundle.BundleName);
List<int> dependIDs = new List<int>(dependNames.Length);
foreach (var dependName in dependNames)
{
@@ -246,29 +240,29 @@ namespace YooAsset.Editor
// 排序并填充数据
dependIDs.Sort();
packageBundle.DependentBundleIDs = dependIDs.ToArray();
buildBundle.DependentBundleIDs = dependIDs.ToArray();
}
}
/// <summary>
/// 处理资源包的标签集合
/// </summary>
private void ProcessBundleTags(PackageManifest manifest)
private void ProcessBundleTags(BuildManifest manifest)
{
foreach (var packageBundle in manifest.BundleList)
foreach (var buildBundle in manifest.BundleList)
{
packageBundle.Tags = Array.Empty<string>();
buildBundle.Tags = Array.Empty<string>();
}
// 将主资源的标签信息传染给其依赖的资源包集合
foreach (var packageAsset in manifest.AssetList)
foreach (var buildAsset in manifest.AssetList)
{
var assetTags = packageAsset.AssetTags;
int bundleID = packageAsset.BundleID;
var assetTags = buildAsset.Tags;
int bundleID = buildAsset.BundleID;
CacheBundleTags(bundleID, assetTags);
if (packageAsset.DependentBundleIDs != null)
if (buildAsset.DependentBundleIDs != null)
{
foreach (var dependBundleID in packageAsset.DependentBundleIDs)
foreach (var dependBundleID in buildAsset.DependentBundleIDs)
{
CacheBundleTags(dependBundleID, assetTags);
}
@@ -278,15 +272,15 @@ namespace YooAsset.Editor
// 将缓存的资源标签赋值给资源包
for (int index = 0; index < manifest.BundleList.Count; index++)
{
var packageBundle = manifest.BundleList[index];
var buildBundle = manifest.BundleList[index];
if (_cacheBundleTags.TryGetValue(index, out var value))
{
packageBundle.Tags = value.ToArray();
buildBundle.Tags = value.ToArray();
}
else
{
// 注意SBP构建管线会自动剔除一些冗余资源的引用关系导致游离资源包没有被任何主资源包引用。
string warning = BuildLogger.GetErrorMessage(ErrorCode.FoundStrayBundle, $"Found stray bundle. Bundle ID: {index}, bundle name: '{packageBundle.BundleName}'.");
string warning = BuildLogger.GetErrorMessage(ErrorCode.FoundStrayBundle, $"Found stray bundle. Bundle ID: {index}, bundle name: '{buildBundle.BundleName}'.");
BuildLogger.Warning(warning);
}
}
@@ -324,7 +318,7 @@ namespace YooAsset.Editor
}
#region YOOASSET_LEGACY_DEPENDENCY
private void ProcessBuiltinBundleDependency(BuildContext context, PackageManifest manifest)
private void ProcessBuiltinBundleDependency(BuildContext context, BuildManifest manifest)
{
// 注意:如果是可编程构建管线,需要补充首包资源包
// 注意:该步骤依赖前面的操作!
@@ -356,7 +350,7 @@ namespace YooAsset.Editor
}
}
}
private void ProcessBuiltinBundleReference(PackageManifest manifest, string builtinBundleName)
private void ProcessBuiltinBundleReference(BuildManifest manifest, string builtinBundleName)
{
if (string.IsNullOrEmpty(builtinBundleName))
return;
@@ -367,23 +361,23 @@ namespace YooAsset.Editor
// 获取首包资源包
int builtinBundleID = GetCachedBundleIndexID(builtinBundleName);
var builtinPackageBundle = manifest.BundleList[builtinBundleID];
var builtinBuildBundle = manifest.BundleList[builtinBundleID];
// 更新依赖资源包ID集合
HashSet<int> cacheBundleIDs = new HashSet<int>(builtinPackageBundle.ReferrerBundleIDs);
HashSet<int> cacheBundleIDs = new HashSet<int>(builtinBuildBundle.ReferrerBundleIDs);
HashSet<string> tempTags = new HashSet<string>();
foreach (var packageAsset in manifest.AssetList)
foreach (var buildAsset in manifest.AssetList)
{
if (cacheBundleIDs.Contains(packageAsset.BundleID))
if (cacheBundleIDs.Contains(buildAsset.BundleID))
{
if (packageAsset.DependentBundleIDs.Contains(builtinBundleID) == false)
if (buildAsset.DependentBundleIDs.Contains(builtinBundleID) == false)
{
var tempBundleIDs = new List<int>(packageAsset.DependentBundleIDs);
var tempBundleIDs = new List<int>(buildAsset.DependentBundleIDs);
tempBundleIDs.Add(builtinBundleID);
packageAsset.DependentBundleIDs = tempBundleIDs.ToArray();
buildAsset.DependentBundleIDs = tempBundleIDs.ToArray();
}
foreach (var tag in packageAsset.AssetTags)
foreach (var tag in buildAsset.Tags)
{
if (tempTags.Contains(tag) == false)
tempTags.Add(tag);
@@ -392,12 +386,12 @@ namespace YooAsset.Editor
}
// 更新首包资源包的标签集合
foreach (var tag in builtinPackageBundle.Tags)
foreach (var tag in builtinBuildBundle.Tags)
{
if (tempTags.Contains(tag) == false)
tempTags.Add(tag);
}
builtinPackageBundle.Tags = tempTags.ToArray();
builtinBuildBundle.Tags = tempTags.ToArray();
}
private int[] GetAssetDependBundleIDs(BuildAssetInfo mainAssetInfo)
{

View File

@@ -22,7 +22,7 @@ namespace YooAsset.Editor
var buildParameters = buildParametersContext.Parameters;
string packageOutputDirectory = buildParametersContext.GetPackageOutputDirectory();
PackageManifest manifest = manifestContext.Manifest;
BuildManifest manifest = manifestContext.Manifest;
BuildReport buildReport = new BuildReport();
// 概述信息
@@ -89,36 +89,36 @@ namespace YooAsset.Editor
// 资源对象列表
buildReport.AssetInfos = new List<ReportAssetInfo>(manifest.AssetList.Count);
foreach (var packageAsset in manifest.AssetList)
foreach (var buildAsset in manifest.AssetList)
{
var mainBundle = manifest.BundleList[packageAsset.BundleID];
var mainBundle = manifest.BundleList[buildAsset.BundleID];
ReportAssetInfo reportAssetInfo = new ReportAssetInfo();
reportAssetInfo.Address = packageAsset.Address;
reportAssetInfo.AssetPath = packageAsset.AssetPath;
reportAssetInfo.AssetTags = packageAsset.AssetTags;
reportAssetInfo.AssetGuid = AssetDatabase.AssetPathToGUID(packageAsset.AssetPath);
reportAssetInfo.Address = buildAsset.Address;
reportAssetInfo.AssetPath = buildAsset.AssetPath;
reportAssetInfo.AssetTags = buildAsset.Tags;
reportAssetInfo.AssetGuid = AssetDatabase.AssetPathToGUID(buildAsset.AssetPath);
reportAssetInfo.MainBundleName = mainBundle.BundleName;
reportAssetInfo.MainBundleSize = mainBundle.FileSize;
reportAssetInfo.DependAssets = GetAssetDependAssets(buildMapContext, mainBundle.BundleName, packageAsset.AssetPath);
reportAssetInfo.DependBundles = GetAssetDependBundles(manifest, packageAsset);
reportAssetInfo.DependAssets = GetAssetDependAssets(buildMapContext, mainBundle.BundleName, buildAsset.AssetPath);
reportAssetInfo.DependBundles = GetAssetDependBundles(manifest, buildAsset);
buildReport.AssetInfos.Add(reportAssetInfo);
}
// 资源包列表
buildReport.BundleInfos = new List<ReportBundleInfo>(manifest.BundleList.Count);
foreach (var packageBundle in manifest.BundleList)
foreach (var buildBundle in manifest.BundleList)
{
ReportBundleInfo reportBundleInfo = new ReportBundleInfo();
reportBundleInfo.BundleName = packageBundle.BundleName;
reportBundleInfo.FileName = packageBundle.GetFileName();
reportBundleInfo.FileHash = packageBundle.FileHash;
reportBundleInfo.FileCrc = packageBundle.FileCrc;
reportBundleInfo.FileSize = packageBundle.FileSize;
reportBundleInfo.Encrypted = packageBundle.IsEncrypted;
reportBundleInfo.Tags = packageBundle.Tags;
reportBundleInfo.DependBundles = GetBundleDependBundles(manifest, packageBundle);
reportBundleInfo.ReferenceBundles = GetBundleReferenceBundles(manifest, packageBundle);
reportBundleInfo.BundleContents = GetBundleContents(buildMapContext, packageBundle.BundleName);
reportBundleInfo.BundleName = buildBundle.BundleName;
reportBundleInfo.FileName = buildBundle.GetFileName(manifest.OutputNameStyle);
reportBundleInfo.FileHash = buildBundle.FileHash;
reportBundleInfo.FileCrc = buildBundle.FileCrc;
reportBundleInfo.FileSize = buildBundle.FileSize;
reportBundleInfo.Encrypted = buildBundle.IsEncrypted;
reportBundleInfo.Tags = buildBundle.Tags;
reportBundleInfo.DependBundles = GetBundleDependBundles(manifest, buildBundle);
reportBundleInfo.ReferenceBundles = GetBundleReferenceBundles(manifest, buildBundle);
reportBundleInfo.BundleContents = GetBundleContents(buildMapContext, buildBundle.BundleName);
buildReport.BundleInfos.Add(reportBundleInfo);
}
@@ -151,10 +151,10 @@ namespace YooAsset.Editor
/// <summary>
/// 获取资源对象依赖的资源包集合
/// </summary>
private List<string> GetAssetDependBundles(PackageManifest manifest, PackageAsset packageAsset)
private List<string> GetAssetDependBundles(BuildManifest manifest, BuildAsset buildAsset)
{
List<string> dependBundles = new List<string>(packageAsset.DependentBundleIDs.Length);
foreach (int index in packageAsset.DependentBundleIDs)
List<string> dependBundles = new List<string>(buildAsset.DependentBundleIDs.Length);
foreach (int index in buildAsset.DependentBundleIDs)
{
string dependBundleName = manifest.BundleList[index].BundleName;
dependBundles.Add(dependBundleName);
@@ -166,10 +166,10 @@ namespace YooAsset.Editor
/// <summary>
/// 获取资源包依赖的资源包集合
/// </summary>
private List<string> GetBundleDependBundles(PackageManifest manifest, PackageBundle packageBundle)
private List<string> GetBundleDependBundles(BuildManifest manifest, BuildBundle buildBundle)
{
List<string> dependBundles = new List<string>(packageBundle.DependentBundleIDs.Length);
foreach (int index in packageBundle.DependentBundleIDs)
List<string> dependBundles = new List<string>(buildBundle.DependentBundleIDs.Length);
foreach (int index in buildBundle.DependentBundleIDs)
{
string dependBundleName = manifest.BundleList[index].BundleName;
dependBundles.Add(dependBundleName);
@@ -181,10 +181,10 @@ namespace YooAsset.Editor
/// <summary>
/// 获取引用该资源包的资源包集合
/// </summary>
private List<string> GetBundleReferenceBundles(PackageManifest manifest, PackageBundle packageBundle)
private List<string> GetBundleReferenceBundles(BuildManifest manifest, BuildBundle buildBundle)
{
List<string> referenceBundles = new List<string>(packageBundle.ReferrerBundleIDs.Count);
foreach (int index in packageBundle.ReferrerBundleIDs)
List<string> referenceBundles = new List<string>(buildBundle.ReferrerBundleIDs.Count);
foreach (int index in buildBundle.ReferrerBundleIDs)
{
string dependBundleName = manifest.BundleList[index].BundleName;
referenceBundles.Add(dependBundleName);
@@ -204,15 +204,15 @@ namespace YooAsset.Editor
return result;
}
private int GetMainAssetCount(PackageManifest manifest)
private int GetMainAssetCount(BuildManifest manifest)
{
return manifest.AssetList.Count;
}
private int GetAllBundleCount(PackageManifest manifest)
private int GetAllBundleCount(BuildManifest manifest)
{
return manifest.BundleList.Count;
}
private long GetAllBundleSize(PackageManifest manifest)
private long GetAllBundleSize(BuildManifest manifest)
{
long fileBytes = 0;
foreach (var packageBundle in manifest.BundleList)
@@ -221,7 +221,7 @@ namespace YooAsset.Editor
}
return fileBytes;
}
private int GetEncryptedBundleCount(PackageManifest manifest)
private int GetEncryptedBundleCount(BuildManifest manifest)
{
int fileCount = 0;
foreach (var packageBundle in manifest.BundleList)
@@ -231,7 +231,7 @@ namespace YooAsset.Editor
}
return fileCount;
}
private long GetEncryptedBundleSize(PackageManifest manifest)
private long GetEncryptedBundleSize(BuildManifest manifest)
{
long fileBytes = 0;
foreach (var packageBundle in manifest.BundleList)

View File

@@ -61,8 +61,7 @@ namespace YooAsset.Editor
{
string bundleName = bundleInfo.BundleName;
string fileHash = bundleInfo.PackageFileHash;
string fileExtension = PackageManifestHelper.GetRemoteBundleFileExtension(bundleName);
string fileName = PackageManifestHelper.GetRemoteBundleFileName(outputNameStyle, bundleName, fileExtension, fileHash);
string fileName = BundleFileNaming.GetBundleFileName(outputNameStyle, bundleName, fileHash);
bundleInfo.PackageDestFilePath = $"{packageOutputDirectory}/{fileName}";
}
}

View File

@@ -33,7 +33,7 @@ namespace YooAsset.Editor
/// <summary>
/// 构建资源包类型下拉框
/// </summary>
protected DropdownField _buildBundleTypeField;
protected PopupField<string> _buildBundleTypeField;
public override void CreateView(VisualElement parent)
@@ -56,8 +56,8 @@ namespace YooAsset.Editor
SetBuildVersionField(_buildVersionField);
// 构建资源包类型
_buildBundleTypeField = Root.Q<DropdownField>("BuildBundleType");
SetBuildBundleTypeField(_buildBundleTypeField);
var buildBundleTypeContainer = Root.Q<VisualElement>("BuildBundleType");
_buildBundleTypeField = CreateBuildBundleTypeField(buildBundleTypeContainer);
// 构建按钮
var buildButton = Root.Q<Button>("Build");
@@ -104,7 +104,7 @@ namespace YooAsset.Editor
EditorUtility.RevealInFinder(buildResult.OutputPackageDirectory);
}
private void SetBuildBundleTypeField(DropdownField dropdownField)
private PopupField<string> CreateBuildBundleTypeField(VisualElement container)
{
var bundleTypes = Enum.GetValues(typeof(EBundleType))
.Cast<EBundleType>()
@@ -112,10 +112,17 @@ namespace YooAsset.Editor
.Select(type => type.ToString())
.ToList();
dropdownField.choices = bundleTypes;
dropdownField.SetValueWithoutNotify(EBundleType.VirtualAssetBundle.ToString());
dropdownField.style.width = StyleWidth;
UIElementsTools.SetElementLabelMinWidth(dropdownField, LabelMinWidth);
int defaultIndex = bundleTypes.IndexOf(EBundleType.VirtualAssetBundle.ToString());
if (defaultIndex < 0)
defaultIndex = 0;
var popupField = new PopupField<string>(bundleTypes, defaultIndex);
popupField.label = "Build Bundle Type";
popupField.style.width = StyleWidth;
container.Add(popupField);
UIElementsTools.SetElementLabelMinWidth(popupField, LabelMinWidth);
return popupField;
}
}
}

View File

@@ -2,7 +2,7 @@
<ui:VisualElement name="BuildContainer">
<ui:TextField picking-mode="Ignore" label="Build Output" name="BuildOutput" />
<ui:TextField picking-mode="Ignore" label="Build Version" name="BuildVersion" />
<ui:DropdownField label="Build Bundle Type" name="BuildBundleType" />
<ui:VisualElement name="BuildBundleType" />
<ui:VisualElement name="ExtensionContainer" />
<ui:Button text="Click Build" display-tooltip-when-elided="true" name="Build" style="height: 50px; background-color: rgb(40, 106, 42); margin-top: 10px;" />
</ui:VisualElement>

View File

@@ -1,4 +1,4 @@
#if TUANJIE_1_8_OR_NEWER
#if TUANJIE_1_8_OR_NEWER && YOOASSET_INSTANT_ASSET_SUPPORT
using System;
using UnityEditor;
using UnityEngine;

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: bc4cd72a390c1054a99c49a7c501c8f6
guid: 07c436f0dfa2bcf43b12628aed2aa91c
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -0,0 +1,52 @@
namespace YooAsset.Editor
{
/// <summary>
/// 收集资源搜索结果
/// </summary>
public class CollectAssetSearchResult
{
/// <summary>
/// 命中的收集器分组
/// </summary>
public BundleCollectorGroup Group { get; }
/// <summary>
/// 命中的收集器分组索引
/// </summary>
public int GroupIndex { get; }
/// <summary>
/// 命中的收集器
/// </summary>
public BundleCollector Collector { get; }
/// <summary>
/// 命中的收集器索引
/// </summary>
public int CollectorIndex { get; }
/// <summary>
/// 命中的资源路径
/// </summary>
public string AssetPath { get; }
/// <summary>
/// 构建收集资源搜索结果
/// </summary>
/// <param name="group">命中的收集器分组</param>
/// <param name="groupIndex">命中的收集器分组索引</param>
/// <param name="collector">命中的收集器</param>
/// <param name="collectorIndex">命中的收集器索引</param>
/// <param name="assetPath">命中的资源路径</param>
public CollectAssetSearchResult(BundleCollectorGroup group, int groupIndex,
BundleCollector collector, int collectorIndex, string assetPath)
{
Group = group;
GroupIndex = groupIndex;
Collector = collector;
CollectorIndex = collectorIndex;
AssetPath = assetPath;
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 4c79e03ac8bcbef4aa0e2eede5bf63fb
guid: ff1a09e6aca8b104c9b256885b06130f
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -0,0 +1,148 @@
using System;
using UnityEngine;
using UnityEditor;
namespace YooAsset.Editor
{
/// <summary>
/// 收集资源搜索工具类
/// </summary>
public static class CollectAssetSearchUtility
{
/// <summary>
/// 验证搜索路径
/// </summary>
/// <param name="input">搜索路径</param>
/// <returns>搜索路径错误类型</returns>
public static ECollectAssetSearchError ValidateSearchPath(string input)
{
if (string.IsNullOrEmpty(input))
return ECollectAssetSearchError.InputPathEmpty;
if (input.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase) == false)
return ECollectAssetSearchError.InputPathMissingAssetsPrefix;
if (input.EndsWith("/"))
return ECollectAssetSearchError.InputPathEndsWithSlash;
string fileName = System.IO.Path.GetFileName(input);
if (fileName.Contains(".") == false)
return ECollectAssetSearchError.InputPathMissingExtension;
if (AssetDatabase.IsValidFolder(input))
return ECollectAssetSearchError.InputPathIsFolder;
string guid = AssetDatabase.AssetPathToGUID(input);
if (string.IsNullOrEmpty(guid))
return ECollectAssetSearchError.AssetPathNotExists;
return ECollectAssetSearchError.None;
}
/// <summary>
/// 获取搜索路径错误提示信息
/// </summary>
/// <param name="error">搜索路径错误类型</param>
/// <param name="input">搜索路径</param>
/// <returns>错误提示信息</returns>
public static string GetSearchPathErrorMessage(ECollectAssetSearchError error, string input)
{
switch (error)
{
case ECollectAssetSearchError.InputPathEmpty:
return "Please enter an asset path.";
case ECollectAssetSearchError.InputPathMissingAssetsPrefix:
return "Path must start with Assets/.";
case ECollectAssetSearchError.InputPathEndsWithSlash:
return "Please enter a file path. Do not end with /.";
case ECollectAssetSearchError.InputPathMissingExtension:
return "Path is missing a file extension (e.g. .prefab, .png, .mat).";
case ECollectAssetSearchError.InputPathIsFolder:
return "Please enter an asset file path, not a folder path.";
case ECollectAssetSearchError.AssetPathNotExists:
return $"Asset not found: {input}";
default:
return "Invalid input format.";
}
}
/// <summary>
/// 在指定 Package 中搜索资源路径,找到第一个命中结果即返回
/// </summary>
/// <param name="package">搜索的资源包裹</param>
/// <param name="assetPath">资源路径</param>
/// <returns>搜索结果,如果未找到返回 null</returns>
public static CollectAssetSearchResult SearchAssetPath(BundleCollectorPackage package, string assetPath)
{
if (ValidateSearchPath(assetPath) != ECollectAssetSearchError.None)
return null;
IAssetIgnoreRule ignoreRule = BundleCollectorSettingData.GetAssetIgnoreRuleInstance(package.IgnoreRuleName);
var command = new CollectCommand(package.PackageName, ignoreRule);
command.SetFlag(ECollectFlags.IgnoreGetDependencies, true);
command.UniqueBundleName = BundleCollectorSettingData.Setting.UniqueBundleName;
command.EnableAddressable = package.EnableAddressable;
command.SupportExtensionless = package.SupportExtensionless;
command.LocationToLower = package.LocationToLower;
command.IncludeAssetGUID = package.IncludeAssetGUID;
command.AutoCollectShaders = package.AutoCollectShaders;
for (int groupIndex = 0; groupIndex < package.Groups.Count; groupIndex++)
{
var group = package.Groups[groupIndex];
for (int collectIndex = 0; collectIndex < group.Collectors.Count; collectIndex++)
{
var collector = group.Collectors[collectIndex];
// 判断收集器是否可能收集指定资源
if (IsCandidateCollector(collector, assetPath) == false)
continue;
try
{
// 检测配置是否有效
collector.CheckConfigError();
// 收集有效资源信息
var collectAssets = collector.GetAllCollectAssets(command, group);
foreach (var collectAsset in collectAssets)
{
if (string.Equals(collectAsset.AssetInfo.AssetPath, assetPath, StringComparison.OrdinalIgnoreCase))
{
return new CollectAssetSearchResult(group, groupIndex, collector, collectIndex, assetPath);
}
}
}
catch (Exception e)
{
Debug.LogError($"Invalid collector : {collector.CollectPath}, error: {e.Message}");
}
}
}
// 未找到匹配资源
return null;
}
/// <summary>
/// 判断收集器是否可能收集指定资源
/// </summary>
/// <param name="collector">收集器</param>
/// <param name="assetPath">资源路径</param>
/// <returns>如果收集器可能收集该资源返回 true</returns>
private static bool IsCandidateCollector(BundleCollector collector, string assetPath)
{
if (string.IsNullOrEmpty(collector.CollectPath))
return false;
if (AssetDatabase.IsValidFolder(collector.CollectPath))
{
string folderPath = collector.CollectPath.TrimEnd('/') + "/";
return assetPath.StartsWith(folderPath, StringComparison.OrdinalIgnoreCase);
}
// 注意:资源收集器也可能直接配置的单个资源路径
return string.Equals(assetPath, collector.CollectPath, StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 1cfc5979929f8cd43b58e1fcc541eaa8
guid: 6c8bc2931bda7884198893f1d2e8f364
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -0,0 +1,44 @@
namespace YooAsset.Editor
{
/// <summary>
/// 搜索错误类型
/// </summary>
public enum ECollectAssetSearchError
{
/// <summary>
/// 无错误
/// </summary>
None,
/// <summary>
/// 输入路径为空
/// </summary>
InputPathEmpty,
/// <summary>
/// 输入路径缺少 Assets/ 路径前缀
/// </summary>
InputPathMissingAssetsPrefix,
/// <summary>
/// 输入路径以斜杠结尾
/// </summary>
InputPathEndsWithSlash,
/// <summary>
/// 输入路径缺少文件扩展名
/// </summary>
InputPathMissingExtension,
/// <summary>
/// 输入路径的是文件夹路径
/// </summary>
InputPathIsFolder,
/// <summary>
/// 资源文件不存在
/// </summary>
AssetPathNotExists,
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 5ef65b981b91200439b05f29435e7c92
guid: 9a2c8a878d6041b4d8bad2a2d2f1896d
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -60,21 +60,22 @@ namespace YooAsset.Editor
/// <returns>如果收集器配置有效返回 true</returns>
public bool IsValid()
{
if (AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(CollectPath) == null)
string assetGUID = AssetDatabase.AssetPathToGUID(CollectPath);
if (string.IsNullOrEmpty(assetGUID))
return false;
if (CollectorType == ECollectorType.None)
return false;
if (BundleCollectorSettingData.HasAddressRuleName(AddressRuleName) == false)
return false;
if (BundleCollectorSettingData.HasBundlePackRuleName(PackRuleName) == false)
return false;
if (BundleCollectorSettingData.HasAssetFilterRuleName(FilterRuleName) == false)
return false;
if (BundleCollectorSettingData.HasAddressRuleName(AddressRuleName) == false)
return false;
return true;
}

View File

@@ -175,6 +175,17 @@ namespace YooAsset.Editor
}
}
/// <summary>
/// 工程中是否已存在收集器配置文件
/// </summary>
/// <returns>存在返回 true</returns>
public static bool HasSettingAsset()
{
string typeName = nameof(BundleCollectorSetting);
var guids = AssetDatabase.FindAssets($"t:{typeName}");
return guids != null && guids.Length > 0;
}
/// <summary>
/// 存储配置文件
/// </summary>

View File

@@ -19,12 +19,34 @@ namespace YooAsset.Editor
/// </summary>
[MenuItem("YooAsset/Bundle Collector", false, 101)]
public static void OpenWindow()
{
OpenWindowInternal();
}
/// <summary>
/// 打开资源收集器窗口并定位到指定的收集器
/// </summary>
/// <param name="packageName">包裹名称</param>
/// <param name="groupName">分组名称</param>
/// <param name="collectPath">收集路径</param>
public static void OpenWindow(string packageName, string groupName, string collectPath)
{
BundleCollectorWindow window = OpenWindowInternal();
window.SetFocusCollector(packageName, groupName, collectPath);
window.RefreshWindow();
window.Focus();
}
private static BundleCollectorWindow OpenWindowInternal()
{
Type[] dockedTypes = EditorWindowDefine.GetDockedWindowTypes();
BundleCollectorWindow window = GetWindow<BundleCollectorWindow>("Bundle Collector", true, dockedTypes);
window.minSize = new Vector2(800, 600);
return window;
}
private const string PlaceholderClass = "search-placeholder";
private Button _saveButton;
private List<string> _collectorTypeList;
private List<RuleDisplayName> _groupActiveRuleList;
@@ -35,6 +57,11 @@ namespace YooAsset.Editor
private VisualElement _helpBoxContainer;
private ToolbarSearchField _searchField;
private TextField _searchTextField;
private Button _searchButton;
private Label _searchResultLabel;
private Button _globalSettingsButton;
private Button _packageSettingsButton;
@@ -66,8 +93,11 @@ namespace YooAsset.Editor
private ScrollView _collectorScrollView;
private PopupField<RuleDisplayName> _activeRulePopupField;
private string _highlightAssetPath;
private int _highlightCollectorIndex = -1;
private int _lastModifyPackageIndex = 0;
private int _lastModifyGroupIndex = 0;
private bool _hasFocusCollector = false;
private bool _showGlobalSettings = false;
private bool _showPackageSettings = false;
@@ -219,6 +249,44 @@ namespace YooAsset.Editor
_saveButton = root.Q<Button>("SaveButton");
_saveButton.clicked += OnSaveButtonClicked;
// 搜索相关
_searchField = root.Q<ToolbarSearchField>("SearchField");
_searchTextField = _searchField.Q<TextField>();
_searchTextField.RegisterCallback<FocusInEvent>(evt =>
{
if (_searchTextField.ClassListContains(PlaceholderClass))
{
_searchField.value = string.Empty;
ClearSearchPlaceholder();
}
});
_searchTextField.RegisterCallback<FocusOutEvent>(evt =>
{
if (string.IsNullOrEmpty(_searchField.value))
ApplySearchPlaceholder();
});
_searchField.RegisterCallback<DragUpdatedEvent>(evt =>
{
if (DragAndDrop.objectReferences.Length > 0)
DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
});
_searchField.RegisterCallback<DragPerformEvent>(evt =>
{
if (DragAndDrop.objectReferences.Length > 0)
{
string assetPath = AssetDatabase.GetAssetPath(DragAndDrop.objectReferences[0]);
if (string.IsNullOrEmpty(assetPath) == false)
{
_searchField.value = assetPath;
ClearSearchPlaceholder();
}
}
});
ApplySearchPlaceholder();
_searchButton = root.Q<Button>("SearchButton");
_searchButton.clicked += OnSearchButtonClicked;
_searchResultLabel = root.Q<Label>("SearchResultLabel");
// 包裹容器
_packageContainer = root.Q("PackageContainer");
@@ -423,6 +491,9 @@ namespace YooAsset.Editor
private void RefreshWindow()
{
_highlightAssetPath = null;
if (_hasFocusCollector == false)
_highlightCollectorIndex = -1;
_groupContainer.visible = false;
_collectorContainer.visible = false;
@@ -455,6 +526,55 @@ namespace YooAsset.Editor
{
BundleCollectorSettingData.SaveFile();
}
private void OnSearchButtonClicked()
{
_highlightAssetPath = null;
_highlightCollectorIndex = -1;
_hasFocusCollector = false;
FillCollectorViewData();
string searchInput = GetSearchInput();
var pathError = CollectAssetSearchUtility.ValidateSearchPath(searchInput);
if (pathError != ECollectAssetSearchError.None)
{
string message = CollectAssetSearchUtility.GetSearchPathErrorMessage(pathError, searchInput);
ShowSearchResult(message, new Color(1f, 0.4f, 0.4f));
return;
}
var selectPackage = _packageListView.selectedItem as BundleCollectorPackage;
if (selectPackage == null)
{
string message = "No package selected. Please select a package first.";
ShowSearchResult(message, new Color(1f, 0.8f, 0.3f));
return;
}
var searchResult = CollectAssetSearchUtility.SearchAssetPath(selectPackage, searchInput);
if (searchResult == null)
{
string message = $"No results found in package '{selectPackage.PackageName}'.";
ShowSearchResult(message, new Color(1f, 0.8f, 0.3f));
return;
}
string resultMessage = $"Found in group '{searchResult.Group.GroupName}', collector '{searchResult.Collector.CollectPath}'";
ShowSearchResult(resultMessage, Color.white);
_searchResultLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
_searchResultLabel.style.unityTextAlign = TextAnchor.MiddleLeft;
_highlightAssetPath = searchResult.AssetPath;
_highlightCollectorIndex = searchResult.CollectorIndex;
_groupContainer.visible = true;
_lastModifyGroupIndex = searchResult.GroupIndex;
if (_groupListView.selectedIndex == searchResult.GroupIndex)
FillCollectorViewData();
else
_groupListView.selectedIndex = searchResult.GroupIndex;
}
private void OnGlobalSettingsButtonClicked()
{
_showGlobalSettings = !_showGlobalSettings;
@@ -480,6 +600,85 @@ namespace YooAsset.Editor
return ruleDisplayName.ClassName;
}
// 焦点相关
private void SetFocusCollector(string packageName, string groupName, string collectPath)
{
var packages = BundleCollectorSettingData.Setting.Packages;
int packageIndex = packages.FindIndex(item => item.PackageName == packageName);
if (packageIndex < 0)
{
Debug.LogWarning($"Package not found: '{packageName}'.");
_highlightCollectorIndex = -1;
_hasFocusCollector = false;
return;
}
var package = packages[packageIndex];
int groupIndex = package.Groups.FindIndex(item => item.GroupName == groupName);
if (groupIndex < 0)
{
Debug.LogWarning($"Group not found: '{groupName}' in package '{packageName}'.");
_highlightCollectorIndex = -1;
_hasFocusCollector = false;
return;
}
var group = package.Groups[groupIndex];
int collectorIndex = group.Collectors.FindIndex(item => string.Equals(item.CollectPath, collectPath, StringComparison.OrdinalIgnoreCase));
if (collectorIndex < 0)
{
Debug.LogWarning($"Collector not found: '{collectPath}'.");
_highlightCollectorIndex = -1;
_hasFocusCollector = false;
return;
}
_highlightAssetPath = null;
_highlightCollectorIndex = collectorIndex;
_lastModifyPackageIndex = packageIndex;
_lastModifyGroupIndex = groupIndex;
_hasFocusCollector = true;
}
// 搜索栏相关
private void ShowSearchResult(string message, Color color)
{
_searchResultLabel.text = message;
_searchResultLabel.style.color = color;
_searchResultLabel.style.unityFontStyleAndWeight = FontStyle.Normal;
_searchResultLabel.style.unityTextAlign = TextAnchor.MiddleCenter;
_searchResultLabel.style.display = DisplayStyle.Flex;
}
private void ClearSearchResult()
{
_searchResultLabel.text = string.Empty;
_searchResultLabel.style.display = DisplayStyle.None;
}
private void ApplySearchPlaceholder()
{
_searchField.value = "Drag or enter asset path here (e.g. Assets/Res/icon.png)";
if (_searchTextField.ClassListContains(PlaceholderClass) == false)
_searchTextField.AddToClassList(PlaceholderClass);
var inputElement = _searchTextField.Q("unity-text-input");
inputElement.style.color = new Color(0.7f, 0.7f, 0.7f, 0.6f);
}
private void ClearSearchPlaceholder()
{
if (_searchTextField.ClassListContains(PlaceholderClass))
{
_searchTextField.RemoveFromClassList(PlaceholderClass);
var inputElement = _searchTextField.Q("unity-text-input");
inputElement.style.color = StyleKeyword.Null;
}
}
private string GetSearchInput()
{
if (_searchTextField.ClassListContains(PlaceholderClass))
return string.Empty;
return _searchField.value;
}
// 设置栏相关
private void RefreshSettings()
{
@@ -607,6 +806,8 @@ namespace YooAsset.Editor
}
private void OnPackageListViewSelectionChange(IEnumerable<object> objs)
{
ClearSearchResult();
var selectPackage = _packageListView.selectedItem as BundleCollectorPackage;
if (selectPackage == null)
{
@@ -752,6 +953,16 @@ namespace YooAsset.Editor
BindCollectorListViewItem(element, i);
_collectorScrollView.Add(element);
}
if (_highlightCollectorIndex >= 0 && _highlightCollectorIndex < selectGroup.Collectors.Count)
{
var targetElement = _collectorScrollView[_highlightCollectorIndex];
var foldout = targetElement.Q<Foldout>("Foldout1");
if (foldout != null)
foldout.value = true;
_highlightCollectorIndex = -1;
_hasFocusCollector = false;
}
}
private VisualElement MakeCollectorListViewItem()
{
@@ -1033,13 +1244,6 @@ namespace YooAsset.Editor
// 清空旧元素
foldout.Clear();
// 检测配置是否有效
if (collector.IsValid() == false)
{
collector.CheckConfigError();
return;
}
List<CollectAssetInfo> collectAssetInfos = null;
try
@@ -1055,12 +1259,15 @@ namespace YooAsset.Editor
command.IncludeAssetGUID = _includeAssetGUIDToggle.value;
command.AutoCollectShaders = _autoCollectShadersToggle.value;
// 检测配置是否有效
collector.CheckConfigError();
// 收集有效资源信息
collectAssetInfos = collector.GetAllCollectAssets(command, group);
}
catch (System.Exception e)
catch (Exception e)
{
Debug.LogError(e.ToString());
Debug.LogError($"Invalid collector : {collector.CollectPath}, error: {e.Message}");
}
if (collectAssetInfos != null)
@@ -1085,6 +1292,13 @@ namespace YooAsset.Editor
label.style.width = 300;
label.style.marginLeft = 0;
label.style.flexGrow = 1;
if (string.IsNullOrEmpty(_highlightAssetPath) == false &&
string.Equals(collectAsset.AssetInfo.AssetPath, _highlightAssetPath, StringComparison.OrdinalIgnoreCase))
{
label.style.color = new Color(1f, 0.2f, 0.2f);
}
elementRow.Add(label);
}
}

View File

@@ -5,6 +5,11 @@
<ui:Button text="Import" display-tooltip-when-elided="true" name="ImportButton" style="width: 50px; background-color: rgb(56, 147, 58);" />
<ui:Button text="Fix" display-tooltip-when-elided="true" name="FixButton" style="width: 50px; background-color: rgb(56, 147, 58);" />
</uie:Toolbar>
<uie:Toolbar name="SearchToolbar" style="display: flex; flex-direction: row;">
<uie:ToolbarSearchField focusable="true" name="SearchField" style="flex-grow: 1;" />
<ui:Button text="Search" display-tooltip-when-elided="true" name="SearchButton" style="width: 60px; background-color: rgb(56, 147, 58);" />
</uie:Toolbar>
<ui:Label name="SearchResultLabel" style="display: none; height: 24px; -unity-text-align: middle-center; padding-left: 5px; padding-right: 5px;" />
<ui:VisualElement name="PublicContainer" style="background-color: rgb(79, 79, 79); border-left-width: 5px; border-right-width: 5px; border-top-width: 5px; border-bottom-width: 5px;">
<ui:VisualElement name="HelpBoxContainer" style="flex-grow: 1;" />
<ui:VisualElement name="GlobalSettingsContainer">

View File

@@ -1,57 +0,0 @@
using System.Collections.Generic;
namespace YooAsset
{
/// <summary>
/// 淘汰策略执行结果
/// </summary>
internal readonly struct EvictionResult
{
private readonly bool _initialized;
/// <summary>
/// 错误信息
/// </summary>
public string Error { get; }
/// <summary>
/// 需要清理的资源标识符集合
/// </summary>
public IReadOnlyList<string> BundleGuids { get; }
/// <summary>
/// 是否执行成功
/// </summary>
public bool Succeeded
{
get { return _initialized && Error == null; }
}
private EvictionResult(string error, IReadOnlyList<string> bundleGuids)
{
_initialized = true;
Error = error;
BundleGuids = bundleGuids;
}
/// <summary>
/// 创建表示执行成功的淘汰结果
/// </summary>
/// <param name="bundleGuids">需要清理的资源包标识符列表</param>
/// <returns>携带待清理列表的成功结果</returns>
public static EvictionResult CreateSuccess(IReadOnlyList<string> bundleGuids)
{
return new EvictionResult(null, bundleGuids);
}
/// <summary>
/// 创建表示执行失败的淘汰结果
/// </summary>
/// <param name="error">描述失败原因的错误信息</param>
/// <returns>携带错误信息的失败结果</returns>
public static EvictionResult CreateFailure(string error)
{
return new EvictionResult(error, null);
}
}
}

View File

@@ -73,5 +73,12 @@ namespace YooAsset
/// <param name="bundleGuid">资源包的唯一标识符</param>
/// <returns>如果缓存中存在该资源包则返回 true否则返回 false。</returns>
bool IsCached(string bundleGuid);
/// <summary>
/// 获取已缓存的资源包文件路径
/// </summary>
/// <param name="bundleGuid">资源包的唯一标识符</param>
/// <returns>返回已缓存的资源包文件路径,如果不存在则返回 null。</returns>
string GetCacheFilePath(string bundleGuid);
}
}

View File

@@ -7,7 +7,7 @@ namespace YooAsset
internal interface ICacheEntry
{
/// <summary>
/// Bundle 唯一标识
/// 资源包唯一标识
/// </summary>
string BundleGuid { get; }
}

View File

@@ -2,6 +2,59 @@ using System.Collections.Generic;
namespace YooAsset
{
/// <summary>
/// 淘汰策略执行结果
/// </summary>
internal readonly struct EvictionResult
{
private readonly bool _initialized;
/// <summary>
/// 错误信息
/// </summary>
public string Error { get; }
/// <summary>
/// 需要清理的资源标识符集合
/// </summary>
public IReadOnlyList<string> BundleGuids { get; }
/// <summary>
/// 是否执行成功
/// </summary>
public bool Succeeded
{
get { return _initialized && Error == null; }
}
private EvictionResult(string error, IReadOnlyList<string> bundleGuids)
{
_initialized = true;
Error = error;
BundleGuids = bundleGuids;
}
/// <summary>
/// 创建表示执行成功的淘汰结果
/// </summary>
/// <param name="bundleGuids">需要清理的资源包标识符列表</param>
/// <returns>携带待清理列表的成功结果</returns>
public static EvictionResult CreateSuccess(IReadOnlyList<string> bundleGuids)
{
return new EvictionResult(null, bundleGuids);
}
/// <summary>
/// 创建表示执行失败的淘汰结果
/// </summary>
/// <param name="error">描述失败原因的错误信息</param>
/// <returns>携带错误信息的失败结果</returns>
public static EvictionResult CreateFailure(string error)
{
return new EvictionResult(error, null);
}
}
/// <summary>
/// 缓存淘汰策略接口
/// </summary>

View File

@@ -2,7 +2,7 @@
namespace YooAsset
{
/// <summary>
/// 清理缓存操作选项
/// 清理缓存操作选项
/// </summary>
internal readonly struct BCClearCacheOptions
{

View File

@@ -2,7 +2,7 @@
namespace YooAsset
{
/// <summary>
/// 加载资源包操作选项
/// 加载资源包操作选项
/// </summary>
internal readonly struct BCLoadBundleOptions
{

View File

@@ -2,7 +2,7 @@
namespace YooAsset
{
/// <summary>
/// 验证缓存操作选项
/// 验证缓存操作选项
/// </summary>
internal readonly struct BCVerifyCacheOptions
{

View File

@@ -2,7 +2,7 @@
namespace YooAsset
{
/// <summary>
/// 写入缓存操作选项
/// 写入缓存操作选项
/// </summary>
internal readonly struct BCWriteCacheOptions
{

View File

@@ -31,7 +31,7 @@ namespace YooAsset
/// <summary>
/// 创建加载内置资源目录操作实例
/// </summary>
/// <param name="options">加载内置资源目录的配置选项</param>
/// <param name="options">加载内置资源目录的操作选项</param>
internal LoadBuiltinCatalogOperation(LoadBuiltinCatalogOptions options)
{
_options = options;

View File

@@ -2,7 +2,7 @@
namespace YooAsset
{
/// <summary>
/// 加载内置资源目录操作选项
/// 加载内置资源目录操作选项
/// </summary>
internal readonly struct LoadBuiltinCatalogOptions
{

View File

@@ -22,7 +22,7 @@ namespace YooAsset
/// <summary>
/// 创建本地 ArchiveBundle 加载操作实例
/// </summary>
/// <param name="options">从本地加载 ArchiveBundle 的配置选项</param>
/// <param name="options">从本地加载 ArchiveBundle 的操作选项</param>
public LoadLocalArchiveBundleOperation(LoadLocalArchiveBundleOptions options)
{
_options = options;
@@ -38,26 +38,51 @@ namespace YooAsset
if (_steps == ESteps.LoadBundle)
{
if (_options.Bundle.IsEncrypted)
if (_options.Bundle.IsEncrypted == false)
{
_steps = ESteps.Done;
SetError($"ArchiveBundle encrypted loading is not supported: '{_options.FilePath}'.");
return;
}
if (FileUtility.IsFileIOSupported(_options.FilePath) == false)
{
_steps = ESteps.Done;
SetError($"FileIO is not supported for builtin path: '{_options.FilePath}'.");
return;
}
if (FileUtility.IsFileIOSupported(_options.FilePath) == false)
{
_steps = ESteps.Done;
SetError($"FileIO is not supported for builtin path: '{_options.FilePath}'.");
return;
LoadResult result = LoadFromFile();
if (result.Succeeded == false)
{
_steps = ESteps.Done;
SetError(result.Error);
return;
}
}
LoadResult result = ParseArchiveFile();
if (result.Succeeded == false)
else
{
_steps = ESteps.Done;
SetError(result.Error);
return;
var decryptor = _options.ArchiveBundleDecryptor;
if (decryptor == null)
{
_steps = ESteps.Done;
SetError($"{_options.CacheName} archive bundle decryptor is null.");
return;
}
LoadResult result;
if (decryptor is IBundleMemoryDecryptor memoryDecryptor)
{
result = LoadFromMemory(memoryDecryptor);
}
else
{
_steps = ESteps.Done;
SetError($"{_options.CacheName} does not support '{decryptor.GetType().Name}' for ArchiveBundle.");
return;
}
if (result.Succeeded == false)
{
_steps = ESteps.Done;
SetError(result.Error);
return;
}
}
_steps = ESteps.CheckResult;
@@ -74,7 +99,7 @@ namespace YooAsset
{
_steps = ESteps.Done;
SetResult();
BundleHandle = new ArchiveBundleHandle(_options.FilePath, _options.Bundle, _archiveBundle);
BundleHandle = new ArchiveBundleHandle(_options.Bundle, _archiveBundle);
}
}
}
@@ -83,16 +108,33 @@ namespace YooAsset
ExecuteBatch();
}
private LoadResult ParseArchiveFile()
private LoadResult LoadFromFile()
{
try
{
_archiveBundle = ArchiveBundleHelper.LoadArchiveBundle(_options.FilePath);
_archiveBundle = ArchiveBundleHelper.LoadFromFile(_options.FilePath);
return LoadResult.Default();
}
catch (Exception ex)
{
return LoadResult.Failure($"Failed to parse archive file: {ex.Message}.");
return LoadResult.Failure($"Failed to load archive bundle file: {ex.Message}.");
}
}
private LoadResult LoadFromMemory(IBundleMemoryDecryptor decryptor)
{
try
{
var args = new BundleDecryptArgs(_options.Bundle, null, _options.FilePath);
byte[] binaryData = decryptor.GetDecryptedData(args);
if (binaryData == null)
return LoadResult.Failure($"{_options.CacheName} decryptor returned null data.");
_archiveBundle = ArchiveBundleHelper.LoadFromMemory(binaryData);
return LoadResult.Default();
}
catch (Exception ex)
{
return LoadResult.Failure($"Failed to load archive bundle file from memory: {ex.Message}.");
}
}
}

View File

@@ -2,7 +2,7 @@
namespace YooAsset
{
/// <summary>
/// 加载 ArchiveBundle 的上下文信息
/// 本地加载 ArchiveBundle 的操作选项
/// </summary>
internal readonly struct LoadLocalArchiveBundleOptions
{
@@ -21,11 +21,17 @@ namespace YooAsset
/// </summary>
public string FilePath { get; }
public LoadLocalArchiveBundleOptions(string cacheName, PackageBundle bundle, string filePath)
/// <summary>
/// ArchiveBundle 解密器
/// </summary>
public IBundleDecryptor ArchiveBundleDecryptor { get; }
public LoadLocalArchiveBundleOptions(string cacheName, PackageBundle bundle, string filePath, IBundleDecryptor archiveBundleDecryptor)
{
CacheName = cacheName;
Bundle = bundle;
FilePath = filePath;
ArchiveBundleDecryptor = archiveBundleDecryptor;
}
}
}

View File

@@ -30,7 +30,7 @@ namespace YooAsset
/// <summary>
/// 创建本地 AssetBundle 加载操作实例
/// </summary>
/// <param name="options">从本地加载 AssetBundle 的配置选项</param>
/// <param name="options">从本地加载 AssetBundle 的操作选项</param>
public LoadLocalAssetBundleOperation(LoadLocalAssetBundleOptions options)
{
_options = options;
@@ -56,14 +56,14 @@ namespace YooAsset
if (decryptor == null)
{
_steps = ESteps.Done;
SetError($"{_options.CacheName} decryptor is null.");
SetError($"{_options.CacheName} asset bundle decryptor is null.");
return;
}
LoadResult result;
if (decryptor is IBundleOffsetDecryptor offsetDecryptor)
{
result = LoadFromFileWithOffset(offsetDecryptor);
result = LoadFromFile(offsetDecryptor);
}
else if (decryptor is IBundleMemoryDecryptor memoryDecryptor)
{
@@ -76,7 +76,7 @@ namespace YooAsset
else
{
_steps = ESteps.Done;
SetError($"{_options.CacheName} does not support '{decryptor.GetType().Name}'.");
SetError($"{_options.CacheName} does not support '{decryptor.GetType().Name}' for AssetBundle.");
return;
}
@@ -121,7 +121,7 @@ namespace YooAsset
{
_steps = ESteps.Done;
SetResult();
BundleHandle = new AssetBundleHandle(_options.FilePath, _options.Bundle, _assetBundle, _loadStream);
BundleHandle = new AssetBundleHandle(_options.Bundle, _assetBundle, _loadStream);
}
}
}
@@ -137,18 +137,17 @@ namespace YooAsset
else
_createRequest = AssetBundle.LoadFromFileAsync(_options.FilePath);
}
private LoadResult LoadFromFileWithOffset(IBundleOffsetDecryptor decryptor)
private LoadResult LoadFromFile(IBundleOffsetDecryptor decryptor)
{
var args = new BundleDecryptArgs(_options.Bundle, null, _options.FilePath);
long rawOffset = decryptor.GetFileOffset(args);
if (rawOffset < 0)
return LoadResult.Failure($"{_options.CacheName} decryptor returned negative offset: {rawOffset}.");
ulong offset = (ulong)rawOffset;
long offset = decryptor.GetFileOffset(args);
if (offset < 0)
return LoadResult.Failure($"{_options.CacheName} decryptor returned negative offset: {offset}.");
if (IsWaitForCompletion)
_assetBundle = AssetBundle.LoadFromFile(_options.FilePath, 0, offset);
_assetBundle = AssetBundle.LoadFromFile(_options.FilePath, 0, (ulong)offset);
else
_createRequest = AssetBundle.LoadFromFileAsync(_options.FilePath, 0, offset);
_createRequest = AssetBundle.LoadFromFileAsync(_options.FilePath, 0, (ulong)offset);
return LoadResult.Default();
}

View File

@@ -2,7 +2,7 @@
namespace YooAsset
{
/// <summary>
/// 加载 AssetBundle 的上下文信息
/// 本地加载 AssetBundle 的操作选项
/// </summary>
internal readonly struct LoadLocalAssetBundleOptions
{

View File

@@ -1,5 +1,4 @@
using System;
using System.IO;
namespace YooAsset
{
@@ -23,7 +22,7 @@ namespace YooAsset
/// <summary>
/// 创建本地 RawBundle 加载操作实例
/// </summary>
/// <param name="options">从本地加载 RawBundle 的配置选项</param>
/// <param name="options">从本地加载 RawBundle 的操作选项</param>
public LoadLocalRawBundleOperation(LoadLocalRawBundleOptions options)
{
_options = options;
@@ -62,7 +61,7 @@ namespace YooAsset
if (decryptor == null)
{
_steps = ESteps.Done;
SetError($"{_options.CacheName} decryptor is null.");
SetError($"{_options.CacheName} raw bundle decryptor is null.");
return;
}
@@ -74,7 +73,7 @@ namespace YooAsset
else
{
_steps = ESteps.Done;
SetError($"{_options.CacheName} does not support '{decryptor.GetType().Name}'.");
SetError($"{_options.CacheName} does not support '{decryptor.GetType().Name}' for RawBundle.");
return;
}
@@ -100,7 +99,7 @@ namespace YooAsset
{
_steps = ESteps.Done;
SetResult();
BundleHandle = new RawBundleHandle(_options.FilePath, _options.Bundle, _rawBundle);
BundleHandle = new RawBundleHandle(_options.Bundle, _rawBundle);
}
}
}
@@ -113,13 +112,12 @@ namespace YooAsset
{
try
{
byte[] data = File.ReadAllBytes(_options.FilePath);
_rawBundle = new RawBundle(data);
_rawBundle = RawBundleHelper.LoadFromFile(_options.FilePath);
return LoadResult.Default();
}
catch (Exception ex)
{
return LoadResult.Failure($"Failed to read raw bundle file: {ex.Message}.");
return LoadResult.Failure($"Failed to load raw bundle file: {ex.Message}.");
}
}
private LoadResult LoadFromMemory(IBundleMemoryDecryptor decryptor)
@@ -129,7 +127,7 @@ namespace YooAsset
if (binaryData == null)
return LoadResult.Failure($"{_options.CacheName} decryptor returned null data.");
_rawBundle = new RawBundle(binaryData);
_rawBundle = RawBundleHelper.LoadFromMemory(binaryData);
return LoadResult.Default();
}
}

View File

@@ -2,7 +2,7 @@
namespace YooAsset
{
/// <summary>
/// 加载 RawBundle 的上下文信息
/// 本地加载 RawBundle 的操作选项
/// </summary>
internal readonly struct LoadLocalRawBundleOptions
{

View File

@@ -0,0 +1,216 @@
using System;
namespace YooAsset
{
/// <summary>
/// WebGL 平台加载 ArchiveBundle 操作
/// </summary>
internal sealed class LoadWebArchiveBundleOperation : BCLoadBundleOperation
{
private enum ESteps
{
None,
Prepare,
DataRequest,
CheckRequest,
VerifyData,
LoadBundle,
TryAgain,
Done,
}
private readonly LoadWebArchiveBundleOptions _options;
private readonly DownloadRetryController _downloadRetryController;
private IDownloadBytesRequest _downloadBytesRequest;
private IBundleMemoryDecryptor _decryptor;
private ArchiveBundle _archiveBundle;
private ESteps _steps = ESteps.None;
internal LoadWebArchiveBundleOperation(LoadWebArchiveBundleOptions options)
{
_options = options;
// 注意:网络原因失败后,重新尝试直到成功
_downloadRetryController = new DownloadRetryController(int.MaxValue, options.DownloadRetryPolicy);
}
protected override void InternalStart()
{
_steps = ESteps.Prepare;
}
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.Prepare)
{
if (_options.Bundle.IsEncrypted == false)
{
_steps = ESteps.DataRequest;
}
else
{
var decryptor = _options.ArchiveBundleDecryptor;
if (decryptor == null)
{
_steps = ESteps.Done;
SetError($"{_options.CacheName} archive bundle decryptor is null.");
return;
}
if (decryptor is IBundleMemoryDecryptor)
{
_decryptor = decryptor as IBundleMemoryDecryptor;
_steps = ESteps.DataRequest;
}
else
{
_steps = ESteps.Done;
SetError($"{_options.CacheName} does not support '{decryptor.GetType().Name}' for ArchiveBundle.");
return;
}
}
}
if (_steps == ESteps.DataRequest)
{
string url = _options.DownloadUrlPolicy.SelectUrl(_options.CandidateUrls);
var args = new DownloadDataRequestArgs(
url: url,
timeout: 0,
watchdogTimeout: _options.WatchdogTimeout);
_downloadBytesRequest = _options.DownloadBackend.CreateBytesRequest(args);
_downloadBytesRequest.SendRequest();
_steps = ESteps.CheckRequest;
}
if (_steps == ESteps.CheckRequest)
{
Progress = _downloadBytesRequest.DownloadProgress;
if (_downloadBytesRequest.IsDone == false)
return;
if (_downloadBytesRequest.Status == EDownloadRequestStatus.Succeeded)
{
_options.DownloadUrlPolicy.OnRequestSucceeded(_downloadBytesRequest.Url);
_steps = ESteps.VerifyData;
}
else
{
string url = _downloadBytesRequest.Url;
long httpCode = _downloadBytesRequest.HttpCode;
string httpError = _downloadBytesRequest.HttpError;
_options.DownloadUrlPolicy.OnRequestFailed(url, httpCode, httpError);
if (IsWaitForCompletion == false && _downloadRetryController.CanRetryRequest(url, httpCode, httpError))
{
_downloadRetryController.StartRetryDelay();
_steps = ESteps.TryAgain;
}
else
{
_steps = ESteps.Done;
SetError(_downloadBytesRequest.Error);
YooLogger.LogError(Error);
}
}
}
if (_steps == ESteps.VerifyData)
{
// 注意:网络/代理/服务器异常导致内容不完整但请求仍成功
EFileVerifyResult verifyResult;
if (_options.DownloadVerifyLevel == EFileVerifyLevel.Low || _options.DownloadVerifyLevel == EFileVerifyLevel.Middle)
verifyResult = FileVerifyHelper.VerifyFile(_downloadBytesRequest.Result, _options.Bundle.FileSize, 0);
else if (_options.DownloadVerifyLevel == EFileVerifyLevel.High)
verifyResult = FileVerifyHelper.VerifyFile(_downloadBytesRequest.Result, _options.Bundle.FileSize, _options.Bundle.FileCrc);
else
throw new YooInternalException($"Unexpected verify level: {_options.DownloadVerifyLevel}.");
if (verifyResult == EFileVerifyResult.Succeed)
{
_steps = ESteps.LoadBundle;
}
else
{
string error = $"Verify failed. Url: '{_downloadBytesRequest.Url}' Level: {_options.DownloadVerifyLevel} Result: {verifyResult}.";
YooLogger.LogWarning(error);
if (IsWaitForCompletion == false && _downloadRetryController.HasRetriesRemaining())
{
_downloadRetryController.StartRetryDelay();
_steps = ESteps.TryAgain;
}
else
{
_steps = ESteps.Done;
SetError(error);
}
}
}
if (_steps == ESteps.LoadBundle)
{
LoadResult result = LoadFromMemory(_decryptor, _downloadBytesRequest.Result);
if (result.Succeeded == false)
{
_steps = ESteps.Done;
SetError(result.Error);
return;
}
_steps = ESteps.Done;
SetResult();
BundleHandle = new ArchiveBundleHandle(_options.Bundle, _archiveBundle);
}
if (_steps == ESteps.TryAgain)
{
// 注意:失败后释放网络请求
if (_downloadBytesRequest != null)
{
_downloadBytesRequest.Dispose();
_downloadBytesRequest = null;
}
if (_downloadRetryController.TickRetryDelay())
{
Progress = 0f;
_steps = ESteps.DataRequest;
}
}
}
protected override void InternalDispose()
{
if (_downloadBytesRequest != null)
{
_downloadBytesRequest.Dispose();
_downloadBytesRequest = null;
}
}
private LoadResult LoadFromMemory(IBundleMemoryDecryptor decryptor, byte[] fileData)
{
try
{
if (decryptor != null)
{
var args = new BundleDecryptArgs(_options.Bundle, fileData, null);
var binaryData = decryptor.GetDecryptedData(args);
if (binaryData == null)
return LoadResult.Failure($"{_options.CacheName} decryptor returned null data.");
_archiveBundle = ArchiveBundleHelper.LoadFromMemory(binaryData);
}
else
{
_archiveBundle = ArchiveBundleHelper.LoadFromMemory(fileData);
}
return LoadResult.Default();
}
catch (Exception ex)
{
return LoadResult.Failure($"Failed to load archive bundle from memory: {ex.Message}.");
}
}
}
}

View File

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

View File

@@ -0,0 +1,70 @@
using System.Collections.Generic;
namespace YooAsset
{
/// <summary>
/// WebGL 平台加载 ArchiveBundle 的操作选项
/// </summary>
internal readonly struct LoadWebArchiveBundleOptions
{
/// <summary>
/// 文件缓存名称
/// </summary>
public string CacheName { get; }
/// <summary>
/// 资源包描述
/// </summary>
public PackageBundle Bundle { get; }
/// <summary>
/// 候选下载地址列表
/// </summary>
public IReadOnlyList<string> CandidateUrls { get; }
/// <summary>
/// ArchiveBundle 解密器
/// </summary>
public IBundleDecryptor ArchiveBundleDecryptor { get; }
/// <summary>
/// 下载后台接口
/// </summary>
public IDownloadBackend DownloadBackend { get; }
/// <summary>
/// 下载数据校验级别
/// </summary>
public EFileVerifyLevel DownloadVerifyLevel { get; }
/// <summary>
/// 看门狗超时时间
/// </summary>
public int WatchdogTimeout { get; }
/// <summary>
/// 下载重试判定策略
/// </summary>
public IDownloadRetryPolicy DownloadRetryPolicy { get; }
/// <summary>
/// URL 选择策略
/// </summary>
public IDownloadUrlPolicy DownloadUrlPolicy { get; }
public LoadWebArchiveBundleOptions(string cacheName, PackageBundle bundle, IReadOnlyList<string> candidateUrls,
IBundleDecryptor archiveBundleDecryptor, IDownloadBackend downloadBackend, EFileVerifyLevel downloadVerifyLevel,
int watchdogTimeout, IDownloadRetryPolicy downloadRetryPolicy, IDownloadUrlPolicy downloadUrlPolicy)
{
CacheName = cacheName;
Bundle = bundle;
CandidateUrls = candidateUrls;
ArchiveBundleDecryptor = archiveBundleDecryptor;
DownloadBackend = downloadBackend;
DownloadVerifyLevel = downloadVerifyLevel;
WatchdogTimeout = watchdogTimeout;
DownloadRetryPolicy = downloadRetryPolicy;
DownloadUrlPolicy = downloadUrlPolicy;
}
}
}

View File

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

View File

@@ -3,136 +3,14 @@ using UnityEngine;
namespace YooAsset
{
/// <summary>
/// 从网络加载加密 AssetBundle 操作
/// </summary>
internal sealed class LoadWebNormalAssetBundleOperation : BCLoadBundleOperation
{
private enum ESteps
{
None,
BundleRequest,
CheckRequest,
TryAgain,
Done,
}
private readonly LoadWebAssetBundleOptions _options;
private readonly DownloadRetryController _downloadRetryController;
private IDownloadAssetBundleRequest _downloadAssetBundleRequest;
private ESteps _steps = ESteps.None;
/// <summary>
/// 创建 LoadWebNormalAssetBundleOperation 实例
/// </summary>
/// <param name="options">从网络加载 AssetBundle 的配置选项</param>
public LoadWebNormalAssetBundleOperation(LoadWebAssetBundleOptions options)
{
_options = options;
// 注意:网络原因失败后,重新尝试直到成功
_downloadRetryController = new DownloadRetryController(int.MaxValue, options.DownloadRetryPolicy);
}
protected override void InternalStart()
{
_steps = ESteps.BundleRequest;
}
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.BundleRequest)
{
string url = _options.DownloadUrlPolicy.SelectUrl(_options.CandidateUrls);
var args = new DownloadAssetBundleRequestArgs(
url: url,
timeout: 0,
watchdogTimeout: _options.WatchdogTimeout,
disableUnityWebCache: _options.DisableUnityWebCache,
fileHash: _options.Bundle.FileHash,
unityCrc: _options.Bundle.UnityCrc);
_downloadAssetBundleRequest = _options.DownloadBackend.CreateAssetBundleRequest(args);
_downloadAssetBundleRequest.SendRequest();
_steps = ESteps.CheckRequest;
}
if (_steps == ESteps.CheckRequest)
{
Progress = _downloadAssetBundleRequest.DownloadProgress;
if (_downloadAssetBundleRequest.IsDone == false)
return;
if (_downloadAssetBundleRequest.Status == EDownloadRequestStatus.Succeeded)
{
_options.DownloadUrlPolicy.OnRequestSucceeded(_downloadAssetBundleRequest.Url);
var assetBundle = _downloadAssetBundleRequest.Result;
if (assetBundle == null)
{
_steps = ESteps.Done;
SetError($"Downloaded asset bundle is null. URL: {_downloadAssetBundleRequest.Url}");
}
else
{
_steps = ESteps.Done;
SetResult();
BundleHandle = new AssetBundleHandle(_downloadAssetBundleRequest.Url, _options.Bundle, assetBundle, null);
}
}
else
{
string url = _downloadAssetBundleRequest.Url;
long httpCode = _downloadAssetBundleRequest.HttpCode;
string httpError = _downloadAssetBundleRequest.HttpError;
_options.DownloadUrlPolicy.OnRequestFailed(url, httpCode, httpError);
if (IsWaitForCompletion == false && _downloadRetryController.CanRetryRequest(url, httpCode, httpError))
{
_downloadRetryController.StartRetryDelay();
_steps = ESteps.TryAgain;
}
else
{
_steps = ESteps.Done;
SetError(_downloadAssetBundleRequest.Error);
YooLogger.LogError(Error);
}
}
}
if (_steps == ESteps.TryAgain)
{
// 注意:失败后释放网络请求
if (_downloadAssetBundleRequest != null)
{
_downloadAssetBundleRequest.Dispose();
_downloadAssetBundleRequest = null;
}
if (_downloadRetryController.TickRetryDelay())
{
Progress = 0f;
_steps = ESteps.BundleRequest;
}
}
}
protected override void InternalDispose()
{
if (_downloadAssetBundleRequest != null)
{
_downloadAssetBundleRequest.Dispose();
_downloadAssetBundleRequest = null;
}
}
}
/// <summary>
/// 从网络加载加密的 AssetBundle 操作
/// WebGL 平台加载加密 AssetBundle 操作
/// </summary>
internal sealed class LoadWebEncryptedAssetBundleOperation : BCLoadBundleOperation
{
private enum ESteps
{
None,
Prepare,
DataRequest,
CheckRequest,
VerifyData,
@@ -142,18 +20,14 @@ namespace YooAsset
Done,
}
private readonly LoadWebAssetBundleOptions _options;
private readonly LoadWebEncryptedAssetBundleOptions _options;
private readonly DownloadRetryController _downloadRetryController;
private IDownloadBytesRequest _downloadBytesRequest;
private IBundleMemoryDecryptor _decryptor;
private AssetBundleCreateRequest _createRequest;
private ESteps _steps = ESteps.None;
/// <summary>
/// 创建 LoadWebEncryptedAssetBundleOperation 实例
/// </summary>
/// <param name="options">从网络加载 AssetBundle 的配置选项</param>
public LoadWebEncryptedAssetBundleOperation(LoadWebAssetBundleOptions options)
public LoadWebEncryptedAssetBundleOperation(LoadWebEncryptedAssetBundleOptions options)
{
_options = options;
@@ -162,43 +36,48 @@ namespace YooAsset
}
protected override void InternalStart()
{
_steps = ESteps.DataRequest;
_steps = ESteps.Prepare;
}
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.DataRequest)
if (_steps == ESteps.Prepare)
{
var decryptor = _options.AssetBundleDecryptor;
if (decryptor == null)
{
_steps = ESteps.Done;
SetError($"{_options.CacheName} decryptor is null.");
SetError($"{_options.CacheName} asset bundle decryptor is null.");
return;
}
if (decryptor is IBundleMemoryDecryptor)
{
_decryptor = decryptor as IBundleMemoryDecryptor;
string url = _options.DownloadUrlPolicy.SelectUrl(_options.CandidateUrls);
var args = new DownloadDataRequestArgs(
url: url,
timeout: 0,
watchdogTimeout: _options.WatchdogTimeout);
_downloadBytesRequest = _options.DownloadBackend.CreateBytesRequest(args);
_downloadBytesRequest.SendRequest();
_steps = ESteps.CheckRequest;
_steps = ESteps.DataRequest;
}
else
{
_steps = ESteps.Done;
SetError($"{_options.CacheName} does not support '{decryptor.GetType().Name}'.");
SetError($"{_options.CacheName} does not support '{decryptor.GetType().Name}' for ArchiveBundle.");
return;
}
}
if (_steps == ESteps.DataRequest)
{
string url = _options.DownloadUrlPolicy.SelectUrl(_options.CandidateUrls);
var args = new DownloadDataRequestArgs(
url: url,
timeout: 0,
watchdogTimeout: _options.WatchdogTimeout);
_downloadBytesRequest = _options.DownloadBackend.CreateBytesRequest(args);
_downloadBytesRequest.SendRequest();
_steps = ESteps.CheckRequest;
}
if (_steps == ESteps.CheckRequest)
{
Progress = _downloadBytesRequest.DownloadProgress;
@@ -225,6 +104,7 @@ namespace YooAsset
{
_steps = ESteps.Done;
SetError(_downloadBytesRequest.Error);
YooLogger.LogError(Error);
}
}
}
@@ -246,7 +126,7 @@ namespace YooAsset
}
else
{
string error = $"[WebBundleVerify] Verify failed. Url: '{_downloadBytesRequest.Url}' Level: {_options.DownloadVerifyLevel} Result: {verifyResult}.";
string error = $"Verify failed. Url: '{_downloadBytesRequest.Url}' Level: {_options.DownloadVerifyLevel} Result: {verifyResult}.";
YooLogger.LogWarning(error);
if (IsWaitForCompletion == false && _downloadRetryController.HasRetriesRemaining())
@@ -284,13 +164,13 @@ namespace YooAsset
if (assetBundle == null)
{
_steps = ESteps.Done;
SetError("Unity engine load failed.");
SetError("Unity engine load asset bundle failed.");
}
else
{
_steps = ESteps.Done;
SetResult();
BundleHandle = new AssetBundleHandle(_downloadBytesRequest.Url, _options.Bundle, assetBundle, null);
BundleHandle = new AssetBundleHandle(_options.Bundle, assetBundle, null);
}
}

View File

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

View File

@@ -3,9 +3,9 @@ using System.Collections.Generic;
namespace YooAsset
{
/// <summary>
/// 加载 AssetBundle 的上下文信息
/// WebGL 平台加载加密 AssetBundle 的操作选项
/// </summary>
internal readonly struct LoadWebAssetBundleOptions
internal readonly struct LoadWebEncryptedAssetBundleOptions
{
/// <summary>
/// 文件缓存名称
@@ -42,11 +42,6 @@ namespace YooAsset
/// </summary>
public int WatchdogTimeout { get; }
/// <summary>
/// 禁用 Unity 内置网络缓存
/// </summary>
public bool DisableUnityWebCache { get; }
/// <summary>
/// 下载重试判定策略
/// </summary>
@@ -57,9 +52,9 @@ namespace YooAsset
/// </summary>
public IDownloadUrlPolicy DownloadUrlPolicy { get; }
public LoadWebAssetBundleOptions(string cacheName, PackageBundle bundle, IReadOnlyList<string> candidateUrls,
public LoadWebEncryptedAssetBundleOptions(string cacheName, PackageBundle bundle, IReadOnlyList<string> candidateUrls,
IBundleDecryptor assetBundleDecryptor, IDownloadBackend downloadBackend, EFileVerifyLevel downloadVerifyLevel,
int watchdogTimeout, bool disableUnityWebCache, IDownloadRetryPolicy downloadRetryPolicy, IDownloadUrlPolicy downloadUrlPolicy)
int watchdogTimeout, IDownloadRetryPolicy downloadRetryPolicy, IDownloadUrlPolicy downloadUrlPolicy)
{
CacheName = cacheName;
Bundle = bundle;
@@ -68,7 +63,6 @@ namespace YooAsset
DownloadBackend = downloadBackend;
DownloadVerifyLevel = downloadVerifyLevel;
WatchdogTimeout = watchdogTimeout;
DisableUnityWebCache = disableUnityWebCache;
DownloadRetryPolicy = downloadRetryPolicy;
DownloadUrlPolicy = downloadUrlPolicy;
}

View File

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

View File

@@ -1,11 +1,9 @@
using UnityEngine;
namespace YooAsset
{
/// <summary>
/// WebGL 游戏平台加载非加密 AssetBundle 操作
/// WebGL 平台加载非加密 AssetBundle 操作
/// </summary>
internal sealed class LoadWebGameAssetBundleOperation : BCLoadBundleOperation
internal sealed class LoadWebPlatformAssetBundleOperation : BCLoadBundleOperation
{
private enum ESteps
{
@@ -16,12 +14,12 @@ namespace YooAsset
Done,
}
private readonly LoadWebGameAssetBundleOptions _options;
private readonly LoadWebPlatformAssetBundleOptions _options;
private readonly DownloadRetryController _downloadRetryController;
private IDownloadAssetBundleRequest _downloadAssetBundleRequest;
private ESteps _steps = ESteps.None;
internal LoadWebGameAssetBundleOperation(LoadWebGameAssetBundleOptions options)
internal LoadWebPlatformAssetBundleOperation(LoadWebPlatformAssetBundleOptions options)
{
_options = options;
@@ -40,11 +38,15 @@ namespace YooAsset
if (_steps == ESteps.BundleRequest)
{
string url = _options.DownloadUrlPolicy.SelectUrl(_options.CandidateUrls);
var args = new DownloadRequestArgs(
var args = new DownloadAssetBundleRequestArgs(
url: url,
timeout: 0,
watchdogTimeout: _options.WatchdogTimeout);
_downloadAssetBundleRequest = new WebGameAssetBundleRequest(args, _options.GamePlatform);
watchdogTimeout: _options.WatchdogTimeout,
disableUnityWebCache: _options.DisableUnityWebCache,
fileHash: _options.Bundle.FileHash,
unityCrc: _options.Bundle.UnityCrc,
platformStrategy: _options.PlatformStrategy);
_downloadAssetBundleRequest = _options.DownloadBackend.CreateAssetBundleRequest(args);
_downloadAssetBundleRequest.SendRequest();
_steps = ESteps.CheckRequest;
}
@@ -69,7 +71,7 @@ namespace YooAsset
{
_steps = ESteps.Done;
SetResult();
BundleHandle = new WebGameAssetBundleHandle(_options.CacheFilePath, _options.Bundle, assetBundle, _options.GamePlatform);
BundleHandle = new WebAssetBundleHandle(_options.Bundle, assetBundle, _options.PlatformStrategy);
}
}
else

View File

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

View File

@@ -3,9 +3,9 @@ using System.Collections.Generic;
namespace YooAsset
{
/// <summary>
/// WebGL 游戏平台加载 AssetBundle 的配置选项
/// WebGL 平台加载非加密 AssetBundle 的操作选项
/// </summary>
internal readonly struct LoadWebGameAssetBundleOptions
internal readonly struct LoadWebPlatformAssetBundleOptions
{
/// <summary>
/// 资源包描述
@@ -18,20 +18,25 @@ namespace YooAsset
public IReadOnlyList<string> CandidateUrls { get; }
/// <summary>
/// 游戏平台接口
/// 平台策略
/// </summary>
public IWebGamePlatform GamePlatform { get; }
public IWebPlatformStrategy PlatformStrategy { get; }
/// <summary>
/// 平台侧缓存文件路径
/// 下载后台接口
/// </summary>
public string CacheFilePath { get; }
public IDownloadBackend DownloadBackend { get; }
/// <summary>
/// 看门狗超时时间
/// </summary>
public int WatchdogTimeout { get; }
/// <summary>
/// 禁用 Unity 内置网络缓存
/// </summary>
public bool DisableUnityWebCache { get; }
/// <summary>
/// 下载重试判定策略
/// </summary>
@@ -42,15 +47,16 @@ namespace YooAsset
/// </summary>
public IDownloadUrlPolicy DownloadUrlPolicy { get; }
public LoadWebGameAssetBundleOptions(PackageBundle bundle, IReadOnlyList<string> candidateUrls,
IWebGamePlatform gamePlatform, string cacheFilePath, int watchdogTimeout,
public LoadWebPlatformAssetBundleOptions(PackageBundle bundle, IReadOnlyList<string> candidateUrls,
IWebPlatformStrategy platformStrategy, IDownloadBackend downloadBackend, int watchdogTimeout, bool disableUnityWebCache,
IDownloadRetryPolicy downloadRetryPolicy, IDownloadUrlPolicy downloadUrlPolicy)
{
Bundle = bundle;
CandidateUrls = candidateUrls;
GamePlatform = gamePlatform;
CacheFilePath = cacheFilePath;
PlatformStrategy = platformStrategy;
DownloadBackend = downloadBackend;
WatchdogTimeout = watchdogTimeout;
DisableUnityWebCache = disableUnityWebCache;
DownloadRetryPolicy = downloadRetryPolicy;
DownloadUrlPolicy = downloadUrlPolicy;
}

View File

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

View File

@@ -0,0 +1,216 @@
using System;
namespace YooAsset
{
/// <summary>
/// WebGL 平台加载 RawBundle 操作
/// </summary>
internal sealed class LoadWebRawBundleOperation : BCLoadBundleOperation
{
private enum ESteps
{
None,
Prepare,
DataRequest,
CheckRequest,
VerifyData,
LoadBundle,
TryAgain,
Done,
}
private readonly LoadWebRawBundleOptions _options;
private readonly DownloadRetryController _downloadRetryController;
private IDownloadBytesRequest _downloadBytesRequest;
private IBundleMemoryDecryptor _decryptor;
private RawBundle _rawBundle;
private ESteps _steps = ESteps.None;
internal LoadWebRawBundleOperation(LoadWebRawBundleOptions options)
{
_options = options;
// 注意:网络原因失败后,重新尝试直到成功
_downloadRetryController = new DownloadRetryController(int.MaxValue, options.DownloadRetryPolicy);
}
protected override void InternalStart()
{
_steps = ESteps.Prepare;
}
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.Prepare)
{
if (_options.Bundle.IsEncrypted == false)
{
_steps = ESteps.DataRequest;
}
else
{
var decryptor = _options.RawBundleDecryptor;
if (decryptor == null)
{
_steps = ESteps.Done;
SetError($"{_options.CacheName} raw bundle decryptor is null.");
return;
}
if (decryptor is IBundleMemoryDecryptor)
{
_decryptor = decryptor as IBundleMemoryDecryptor;
_steps = ESteps.DataRequest;
}
else
{
_steps = ESteps.Done;
SetError($"{_options.CacheName} does not support '{decryptor.GetType().Name}' for RawBundle.");
return;
}
}
}
if (_steps == ESteps.DataRequest)
{
string url = _options.DownloadUrlPolicy.SelectUrl(_options.CandidateUrls);
var args = new DownloadDataRequestArgs(
url: url,
timeout: 0,
watchdogTimeout: _options.WatchdogTimeout);
_downloadBytesRequest = _options.DownloadBackend.CreateBytesRequest(args);
_downloadBytesRequest.SendRequest();
_steps = ESteps.CheckRequest;
}
if (_steps == ESteps.CheckRequest)
{
Progress = _downloadBytesRequest.DownloadProgress;
if (_downloadBytesRequest.IsDone == false)
return;
if (_downloadBytesRequest.Status == EDownloadRequestStatus.Succeeded)
{
_options.DownloadUrlPolicy.OnRequestSucceeded(_downloadBytesRequest.Url);
_steps = ESteps.VerifyData;
}
else
{
string url = _downloadBytesRequest.Url;
long httpCode = _downloadBytesRequest.HttpCode;
string httpError = _downloadBytesRequest.HttpError;
_options.DownloadUrlPolicy.OnRequestFailed(url, httpCode, httpError);
if (IsWaitForCompletion == false && _downloadRetryController.CanRetryRequest(url, httpCode, httpError))
{
_downloadRetryController.StartRetryDelay();
_steps = ESteps.TryAgain;
}
else
{
_steps = ESteps.Done;
SetError(_downloadBytesRequest.Error);
YooLogger.LogError(Error);
}
}
}
if (_steps == ESteps.VerifyData)
{
// 注意:网络/代理/服务器异常导致内容不完整但请求仍成功
EFileVerifyResult verifyResult;
if (_options.DownloadVerifyLevel == EFileVerifyLevel.Low || _options.DownloadVerifyLevel == EFileVerifyLevel.Middle)
verifyResult = FileVerifyHelper.VerifyFile(_downloadBytesRequest.Result, _options.Bundle.FileSize, 0);
else if (_options.DownloadVerifyLevel == EFileVerifyLevel.High)
verifyResult = FileVerifyHelper.VerifyFile(_downloadBytesRequest.Result, _options.Bundle.FileSize, _options.Bundle.FileCrc);
else
throw new YooInternalException($"Unexpected verify level: {_options.DownloadVerifyLevel}.");
if (verifyResult == EFileVerifyResult.Succeed)
{
_steps = ESteps.LoadBundle;
}
else
{
string error = $"Verify failed. Url: '{_downloadBytesRequest.Url}' Level: {_options.DownloadVerifyLevel} Result: {verifyResult}.";
YooLogger.LogWarning(error);
if (IsWaitForCompletion == false && _downloadRetryController.HasRetriesRemaining())
{
_downloadRetryController.StartRetryDelay();
_steps = ESteps.TryAgain;
}
else
{
_steps = ESteps.Done;
SetError(error);
}
}
}
if (_steps == ESteps.LoadBundle)
{
LoadResult result = LoadFromMemory(_decryptor, _downloadBytesRequest.Result);
if (result.Succeeded == false)
{
_steps = ESteps.Done;
SetError(result.Error);
return;
}
_steps = ESteps.Done;
SetResult();
BundleHandle = new RawBundleHandle(_options.Bundle, _rawBundle);
}
if (_steps == ESteps.TryAgain)
{
// 注意:失败后释放网络请求
if (_downloadBytesRequest != null)
{
_downloadBytesRequest.Dispose();
_downloadBytesRequest = null;
}
if (_downloadRetryController.TickRetryDelay())
{
Progress = 0f;
_steps = ESteps.DataRequest;
}
}
}
protected override void InternalDispose()
{
if (_downloadBytesRequest != null)
{
_downloadBytesRequest.Dispose();
_downloadBytesRequest = null;
}
}
private LoadResult LoadFromMemory(IBundleMemoryDecryptor decryptor, byte[] fileData)
{
try
{
if (decryptor != null)
{
var args = new BundleDecryptArgs(_options.Bundle, fileData, null);
var binaryData = decryptor.GetDecryptedData(args);
if (binaryData == null)
return LoadResult.Failure($"{_options.CacheName} decryptor returned null data.");
_rawBundle = RawBundleHelper.LoadFromMemory(binaryData);
}
else
{
_rawBundle = RawBundleHelper.LoadFromMemory(fileData);
}
return LoadResult.Default();
}
catch (Exception ex)
{
return LoadResult.Failure($"Failed to load archive bundle from memory: {ex.Message}.");
}
}
}
}

View File

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

View File

@@ -0,0 +1,70 @@
using System.Collections.Generic;
namespace YooAsset
{
/// <summary>
/// WebGL 平台加载 RawBundle 的操作选项
/// </summary>
internal readonly struct LoadWebRawBundleOptions
{
/// <summary>
/// 文件缓存名称
/// </summary>
public string CacheName { get; }
/// <summary>
/// 资源包描述
/// </summary>
public PackageBundle Bundle { get; }
/// <summary>
/// 候选下载地址列表
/// </summary>
public IReadOnlyList<string> CandidateUrls { get; }
/// <summary>
/// RawBundle 解密器
/// </summary>
public IBundleDecryptor RawBundleDecryptor { get; }
/// <summary>
/// 下载后台接口
/// </summary>
public IDownloadBackend DownloadBackend { get; }
/// <summary>
/// 下载数据校验级别
/// </summary>
public EFileVerifyLevel DownloadVerifyLevel { get; }
/// <summary>
/// 看门狗超时时间
/// </summary>
public int WatchdogTimeout { get; }
/// <summary>
/// 下载重试判定策略
/// </summary>
public IDownloadRetryPolicy DownloadRetryPolicy { get; }
/// <summary>
/// URL 选择策略
/// </summary>
public IDownloadUrlPolicy DownloadUrlPolicy { get; }
public LoadWebRawBundleOptions(string cacheName, PackageBundle bundle, IReadOnlyList<string> candidateUrls,
IBundleDecryptor rawBundleDecryptor, IDownloadBackend downloadBackend, EFileVerifyLevel downloadVerifyLevel,
int watchdogTimeout, IDownloadRetryPolicy downloadRetryPolicy, IDownloadUrlPolicy downloadUrlPolicy)
{
CacheName = cacheName;
Bundle = bundle;
CandidateUrls = candidateUrls;
RawBundleDecryptor = rawBundleDecryptor;
DownloadBackend = downloadBackend;
DownloadVerifyLevel = downloadVerifyLevel;
WatchdogTimeout = watchdogTimeout;
DownloadRetryPolicy = downloadRetryPolicy;
DownloadUrlPolicy = downloadUrlPolicy;
}
}
}

View File

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

View File

@@ -33,7 +33,7 @@ namespace YooAsset
{
if (options.Manifest.TryGetPackageBundleByBundleGuid(entry.BundleGuid, out PackageBundle bundle))
{
if (bundle.HasAnyTag(tags))
if (bundle.Tags.HasAnyTag(tags))
bundleGuids.Add(bundle.BundleGuid);
}
}

View File

@@ -20,15 +20,22 @@ namespace YooAsset
/// </summary>
public IBundleDecryptor RawBundleDecryptor { get; }
/// <summary>
/// ArchiveBundle 解密器
/// </summary>
public IBundleDecryptor ArchiveBundleDecryptor { get; }
/// <summary>
/// 下载后台
/// </summary>
public IDownloadBackend DownloadBackend { get; }
public Configuration(IBundleDecryptor assetBundleDecryptor, IBundleDecryptor rawBundleDecryptor, IDownloadBackend downloadBackend)
public Configuration(IBundleDecryptor assetBundleDecryptor, IBundleDecryptor rawBundleDecryptor,
IBundleDecryptor archiveBundleDecryptor, IDownloadBackend downloadBackend)
{
AssetBundleDecryptor = assetBundleDecryptor;
RawBundleDecryptor = rawBundleDecryptor;
ArchiveBundleDecryptor = archiveBundleDecryptor;
DownloadBackend = downloadBackend;
}
}
@@ -135,6 +142,17 @@ namespace YooAsset
{
return _cacheEntries.ContainsKey(bundleGuid);
}
/// <inheritdoc />
public string GetCacheFilePath(string bundleGuid)
{
if (_cacheEntries.TryGetValue(bundleGuid, out BuiltinBundleCacheEntry entry))
{
return entry.FilePath;
}
YooLogger.LogWarning($"Cache file path not found. Bundle GUID: '{bundleGuid}'.");
return null;
}
#region
/// <summary>

View File

@@ -60,7 +60,8 @@ namespace YooAsset
var options = new LoadLocalArchiveBundleOptions(
cacheName: _fileCache.GetType().Name,
bundle: _bundle,
filePath: _cacheEntry.FilePath);
filePath: _cacheEntry.FilePath,
archiveBundleDecryptor: _fileCache.Config.ArchiveBundleDecryptor);
_loadLocalArchiveBundleOp = new LoadLocalArchiveBundleOperation(options);
_loadLocalArchiveBundleOp.StartOperation();
AddChildOperation(_loadLocalArchiveBundleOp);

View File

@@ -80,7 +80,7 @@ namespace YooAsset
PackageName = packageName;
RootPath = rootPath;
Config = config;
IsReadOnly = true;
IsReadOnly = config.VirtualDownloadMode == false;
}
/// <inheritdoc />
@@ -142,10 +142,16 @@ namespace YooAsset
/// <inheritdoc />
public bool IsCached(string bundleGuid)
{
if (Config.VirtualDownloadMode)
return _cacheEntries.ContainsKey(bundleGuid);
else
if (Config.VirtualDownloadMode == false)
return true;
return _cacheEntries.ContainsKey(bundleGuid);
}
/// <inheritdoc />
public string GetCacheFilePath(string bundleGuid)
{
YooLogger.LogWarning($"{nameof(EditorBundleCache)} does not support local cache file path.");
return null;
}
/// <summary>
@@ -202,8 +208,43 @@ namespace YooAsset
if (_cacheEntries.TryGetValue(bundleGuid, out EditorBundleCacheEntry entry))
{
_cacheEntries.Remove(bundleGuid);
entry.Delete();
}
}
/// <summary>
/// 获取 marker 文件路径
/// </summary>
/// <param name="bundle">资源包描述</param>
/// <returns>marker 文件的完整路径</returns>
internal string GetMarkerFilePath(PackageBundle bundle)
{
string bundleGuid = bundle.BundleGuid;
string hashFolder = GetHashFolderName(bundleGuid);
return PathUtility.Combine(RootPath, hashFolder, bundleGuid, EditorBundleCacheConsts.MarkerFileName);
}
/// <summary>
/// 获取 marker 临时文件路径
/// </summary>
/// <param name="bundle">资源包描述</param>
/// <returns>marker 临时文件的完整路径</returns>
internal string GetMarkerTempFilePath(PackageBundle bundle)
{
string bundleGuid = bundle.BundleGuid;
string hashFolder = GetHashFolderName(bundleGuid);
return PathUtility.Combine(RootPath, hashFolder, bundleGuid, EditorBundleCacheConsts.MarkerTempFileName);
}
private string GetHashFolderName(string bundleGuid)
{
if (string.IsNullOrEmpty(bundleGuid))
throw new YooInternalException("Bundle GUID is null or empty.");
if (bundleGuid.Length <= EditorBundleCacheConsts.HashFolderNameLength)
return bundleGuid;
return bundleGuid.Substring(0, EditorBundleCacheConsts.HashFolderNameLength);
}
#endregion
}
}

View File

@@ -0,0 +1,24 @@
namespace YooAsset
{
/// <summary>
/// 编辑器文件缓存常量定义
/// </summary>
internal static class EditorBundleCacheConsts
{
/// <summary>
/// 标记文件名称
/// </summary>
public const string MarkerFileName = "__marker";
/// <summary>
/// 标记临时文件名称
/// </summary>
public const string MarkerTempFileName = "__marker.tmp";
/// <summary>
/// 哈希分片目录前缀长度
/// </summary>
public const int HashFolderNameLength = 2;
}
}

View File

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

View File

@@ -1,3 +1,5 @@
using System;
using System.IO;
namespace YooAsset
{
@@ -12,19 +14,46 @@ namespace YooAsset
public string BundleGuid { get; private set; }
/// <summary>
/// 资源包文件路径
/// 标记文件路径
/// </summary>
public string FilePath { get; private set; }
public string MarkerFilePath { get; private set; }
/// <summary>
/// 创建编辑器文件缓存条目实例
/// </summary>
/// <param name="bundleGuid">资源包唯一标识</param>
/// <param name="filePath">资源包文件路径</param>
public EditorBundleCacheEntry(string bundleGuid, string filePath)
/// <param name="markerFilePath">标记文件路径</param>
public EditorBundleCacheEntry(string bundleGuid, string markerFilePath)
{
BundleGuid = bundleGuid;
FilePath = filePath;
MarkerFilePath = markerFilePath;
}
/// <summary>
/// 删除缓存文件夹及其所有内容
/// </summary>
/// <returns>删除是否成功</returns>
public bool Delete()
{
try
{
string directory = Path.GetDirectoryName(MarkerFilePath);
var directoryInfo = new DirectoryInfo(directory);
if (directoryInfo.Exists)
{
directoryInfo.Delete(true);
return true;
}
else
{
return false;
}
}
catch (Exception ex)
{
YooLogger.LogError($"Failed to delete editor cache file: {ex.Message}.");
return false;
}
}
}
}
}

View File

@@ -46,7 +46,6 @@ namespace YooAsset
{
var cacheEntries = _fileCache.GetAllEntries();
EvictionResult clearResult = _policy.SelectEvictionTargets(cacheEntries, _options);
if (clearResult.Succeeded == false)
{
_steps = ESteps.Done;

View File

@@ -1,4 +1,3 @@
namespace YooAsset
{
/// <summary>
@@ -6,7 +5,17 @@ namespace YooAsset
/// </summary>
internal sealed class EBCInitializeOperation : BCInitializeOperation
{
private enum ESteps
{
None,
ScanMarkerFiles,
AddCacheEntries,
Done,
}
private readonly EditorBundleCache _fileCache;
private ScanMarkerFilesOperation _scanMarkerFilesOp;
private ESteps _steps = ESteps.None;
public EBCInitializeOperation(EditorBundleCache cache)
{
@@ -14,10 +23,58 @@ namespace YooAsset
}
protected override void InternalStart()
{
SetResult();
if (_fileCache.Config.VirtualDownloadMode == false)
{
SetResult();
return;
}
_steps = ESteps.ScanMarkerFiles;
}
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.ScanMarkerFiles)
{
if (_scanMarkerFilesOp == null)
{
_scanMarkerFilesOp = new ScanMarkerFilesOperation(_fileCache);
_scanMarkerFilesOp.StartOperation();
AddChildOperation(_scanMarkerFilesOp);
}
_scanMarkerFilesOp.UpdateOperation();
Progress = _scanMarkerFilesOp.Progress;
if (_scanMarkerFilesOp.IsDone == false)
return;
if (_scanMarkerFilesOp.Status == EOperationStatus.Succeeded)
{
_steps = ESteps.AddCacheEntries;
}
else
{
_steps = ESteps.Done;
SetError(_scanMarkerFilesOp.Error);
}
}
if (_steps == ESteps.AddCacheEntries)
{
foreach (var fileInfo in _scanMarkerFilesOp.Result)
{
if (_fileCache.IsCached(fileInfo.BundleGuid) == false)
{
var cacheEntry = new EditorBundleCacheEntry(fileInfo.BundleGuid, fileInfo.MarkerFilePath);
_fileCache.AddEntry(fileInfo.BundleGuid, cacheEntry);
}
}
_steps = ESteps.Done;
SetResult();
}
}
}
}

View File

@@ -1,5 +1,3 @@
using System;
using System.IO;
namespace YooAsset
{
@@ -13,15 +11,8 @@ namespace YooAsset
protected override void CreateBundleHandle()
{
string editorFilePath = EditorFileSystemHelper.GetEditorFilePath(_bundle);
if (string.IsNullOrEmpty(editorFilePath))
{
SetError($"Editor file path is null. Bundle: '{_bundle.BundleName}'.");
return;
}
SetResult();
BundleHandle = new VirtualAssetBundleHandle(editorFilePath, _bundle);
BundleHandle = new VirtualAssetBundleHandle(_bundle);
}
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.IO;
namespace YooAsset
{
@@ -22,11 +21,10 @@ namespace YooAsset
try
{
byte[] data = File.ReadAllBytes(editorFilePath);
var rawBundle = new RawBundle(data);
var rawBundle = RawBundleHelper.LoadFromFile(editorFilePath);
SetResult();
BundleHandle = new VirtualRawBundleHandle(editorFilePath, _bundle, rawBundle);
BundleHandle = new VirtualRawBundleHandle(_bundle, rawBundle);
}
catch (Exception ex)
{

View File

@@ -1,3 +1,5 @@
using System;
using System.IO;
namespace YooAsset
{
@@ -52,7 +54,38 @@ namespace YooAsset
if (_steps == ESteps.CacheFile)
{
var cacheEntry = new EditorBundleCacheEntry(_options.Bundle.BundleGuid, _options.FilePath);
string markerFilePath = _fileCache.GetMarkerFilePath(_options.Bundle);
string markerTempPath = _fileCache.GetMarkerTempFilePath(_options.Bundle);
try
{
// 阶段A准备目标目录清理可能存在的残留临时文件
FileUtility.EnsureParentDirectoryExists(markerFilePath);
DeleteFileSafely(markerTempPath);
// 阶段B写入临时文件内容仅用于人工调试
string debugContent = $"BundleName={_options.Bundle.BundleName}\n"
+ $"BundleGuid={_options.Bundle.BundleGuid}\n";
File.WriteAllText(markerTempPath, debugContent);
// 阶段C原子提交
if (File.Exists(markerFilePath))
File.Delete(markerFilePath);
File.Move(markerTempPath, markerFilePath);
}
catch (Exception ex)
{
_steps = ESteps.Done;
SetError($"Failed to write marker file: {ex.Message}.");
YooLogger.LogError(Error);
// 回滚:清理临时文件,正式文件不受影响
DeleteFileSafely(markerTempPath);
return;
}
// 阶段D注册内存缓存条目
var cacheEntry = new EditorBundleCacheEntry(_options.Bundle.BundleGuid, markerFilePath);
_fileCache.AddEntry(_options.Bundle.BundleGuid, cacheEntry);
_steps = ESteps.Done;
SetResult();
@@ -62,5 +95,18 @@ namespace YooAsset
{
ExecuteBatch();
}
private static void DeleteFileSafely(string filePath)
{
try
{
if (File.Exists(filePath))
File.Delete(filePath);
}
catch (Exception ex)
{
YooLogger.LogWarning($"Failed to delete file '{filePath}': {ex.Message}.");
}
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 9c86f68bc316ce54ca9396e6e6c2dd8f
guid: 2e9a70495d8ca514caa7647851844101
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -0,0 +1,114 @@
using System.IO;
using System.Collections.Generic;
namespace YooAsset
{
/// <summary>
/// 扫描标记文件操作
/// </summary>
internal sealed class ScanMarkerFilesOperation : AsyncOperationBase
{
private enum ESteps
{
None,
Prepare,
ScanFiles,
Done,
}
private readonly EditorBundleCache _fileCache;
private IEnumerator<string> _shardEnumerator = null;
private double _scanStartTime;
private ESteps _steps = ESteps.None;
/// <summary>
/// 扫描到的标记件信息
/// </summary>
public readonly List<ScanFileInfo> Result = new List<ScanFileInfo>(5000);
/// <summary>
/// 创建操作实例
/// </summary>
/// <param name="fileCache">编辑器文件缓存系统</param>
internal ScanMarkerFilesOperation(EditorBundleCache fileCache)
{
_fileCache = fileCache;
}
protected override void InternalStart()
{
_steps = ESteps.Prepare;
}
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.Prepare)
{
if (Directory.Exists(_fileCache.RootPath))
{
var directories = Directory.EnumerateDirectories(_fileCache.RootPath);
_shardEnumerator = directories.GetEnumerator();
_scanStartTime = TimeUtility.RealtimeSinceStartup;
_steps = ESteps.ScanFiles;
}
else
{
_steps = ESteps.Done;
SetResult();
}
}
if (_steps == ESteps.ScanFiles)
{
if (ScanFiles())
return;
_shardEnumerator.Dispose();
_shardEnumerator = null;
_steps = ESteps.Done;
SetResult();
double costTime = TimeUtility.RealtimeSinceStartup - _scanStartTime;
YooLogger.Log($"Marker file scan completed in {costTime:f1} seconds. Found {Result.Count} marker files.");
}
}
protected override void InternalDispose()
{
if (_shardEnumerator != null)
{
_shardEnumerator.Dispose();
_shardEnumerator = null;
}
}
private bool ScanFiles()
{
bool hasMore;
while (true)
{
hasMore = _shardEnumerator.MoveNext();
if (hasMore == false)
break;
string shardFolder = _shardEnumerator.Current;
var childDirectories = Directory.EnumerateDirectories(shardFolder);
foreach (string childDirectory in childDirectories)
{
string bundleGuid = Path.GetFileName(childDirectory);
string markerFilePath = PathUtility.Combine(childDirectory, EditorBundleCacheConsts.MarkerFileName);
if (File.Exists(markerFilePath))
{
var fileInfo = new ScanFileInfo(bundleGuid, markerFilePath);
Result.Add(fileInfo);
}
}
if (IsBusy)
break;
}
return hasMore;
}
}
}

View File

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

View File

@@ -0,0 +1,30 @@
namespace YooAsset
{
/// <summary>
/// 扫描到的标记文件信息
/// </summary>
internal class ScanFileInfo
{
/// <summary>
/// 资源包唯一标识
/// </summary>
public string BundleGuid { get; private set; }
/// <summary>
/// 标记文件路径
/// </summary>
public string MarkerFilePath { get; private set; }
/// <summary>
/// 创建扫描文件信息实例
/// </summary>
/// <param name="bundleGuid">资源包唯一标识</param>
/// <param name="markerFilePath">标记文件路径</param>
public ScanFileInfo(string bundleGuid, string markerFilePath)
{
BundleGuid = bundleGuid;
MarkerFilePath = markerFilePath;
}
}
}

View File

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

View File

@@ -19,7 +19,7 @@ namespace YooAsset
}
private readonly SandboxBundleCache _fileCache;
private IEnumerator<string> _filesEnumerator = null;
private IEnumerator<string> _shardEnumerator = null;
private double _verifyStartTime;
private ESteps _steps = ESteps.None;
@@ -50,7 +50,7 @@ namespace YooAsset
if (Directory.Exists(_fileCache.RootPath))
{
var directories = Directory.EnumerateDirectories(_fileCache.RootPath);
_filesEnumerator = directories.GetEnumerator();
_shardEnumerator = directories.GetEnumerator();
_verifyStartTime = TimeUtility.RealtimeSinceStartup;
_steps = ESteps.SearchFiles;
}
@@ -66,35 +66,35 @@ namespace YooAsset
if (SearchFiles())
return;
_filesEnumerator.Dispose();
_filesEnumerator = null;
_shardEnumerator.Dispose();
_shardEnumerator = null;
_steps = ESteps.Done;
SetResult();
double costTime = TimeUtility.RealtimeSinceStartup - _verifyStartTime;
YooLogger.Log($"Cache file search completed in {costTime:f1} seconds.");
YooLogger.Log($"Cache file search completed in {costTime:f1} seconds. Found {Result.Count} cache files.");
}
}
protected override void InternalDispose()
{
if (_filesEnumerator != null)
if (_shardEnumerator != null)
{
_filesEnumerator.Dispose();
_filesEnumerator = null;
_shardEnumerator.Dispose();
_shardEnumerator = null;
}
}
private bool SearchFiles()
{
bool isFindItem;
bool hasMore;
while (true)
{
isFindItem = _filesEnumerator.MoveNext();
if (isFindItem == false)
hasMore = _shardEnumerator.MoveNext();
if (hasMore == false)
break;
var rootFolder = _filesEnumerator.Current;
var childDirectories = Directory.EnumerateDirectories(rootFolder);
var shardFolder = _shardEnumerator.Current;
var childDirectories = Directory.EnumerateDirectories(shardFolder);
foreach (var childDirectory in childDirectories)
{
string bundleGuid = Path.GetFileName(childDirectory);
@@ -113,7 +113,7 @@ namespace YooAsset
break;
}
return isFindItem;
return hasMore;
}
}
}

View File

@@ -61,7 +61,8 @@ namespace YooAsset
var options = new LoadLocalArchiveBundleOptions(
cacheName: _fileCache.GetType().Name,
bundle: _bundle,
filePath: _cacheEntry.DataFilePath);
filePath: _cacheEntry.DataFilePath,
archiveBundleDecryptor: _fileCache.Config.ArchiveBundleDecryptor);
_loadLocalArchiveBundleOp = new LoadLocalArchiveBundleOperation(options);
_loadLocalArchiveBundleOp.StartOperation();
AddChildOperation(_loadLocalArchiveBundleOp);

View File

@@ -170,7 +170,7 @@ namespace YooAsset
{
_steps = ESteps.Done;
SetResult();
BundleHandle = new AssetBundleHandle(_cacheEntry.DataFilePath, _bundle, assetBundle, null);
BundleHandle = new AssetBundleHandle(_bundle, assetBundle, null);
}
}
}

View File

@@ -30,23 +30,29 @@ namespace YooAsset
/// </summary>
public IBundleDecryptor RawBundleDecryptor { get; }
/// <summary>
/// ArchiveBundle 解密器
/// </summary>
public IBundleDecryptor ArchiveBundleDecryptor { get; }
/// <summary>
/// AssetBundle 备用解密器
/// </summary>
public IBundleMemoryDecryptor AssetBundleFallbackDecryptor { get; }
public Configuration(int fileVerifyMaxConcurrency, EFileVerifyLevel fileVerifyLevel,
IBundleDecryptor assetBundleDecryptor, IBundleDecryptor rawBundleDecryptor, IBundleMemoryDecryptor assetBundleFallbackDecryptor)
IBundleDecryptor assetBundleDecryptor, IBundleDecryptor rawBundleDecryptor,
IBundleDecryptor archiveBundleDecryptor, IBundleMemoryDecryptor assetBundleFallbackDecryptor)
{
FileVerifyMaxConcurrency = fileVerifyMaxConcurrency;
FileVerifyLevel = fileVerifyLevel;
AssetBundleDecryptor = assetBundleDecryptor;
RawBundleDecryptor = rawBundleDecryptor;
ArchiveBundleDecryptor = archiveBundleDecryptor;
AssetBundleFallbackDecryptor = assetBundleFallbackDecryptor;
}
}
private const int HashFolderNameLength = 2;
private readonly Dictionary<string, SandboxBundleCacheEntry> _cacheEntries = new Dictionary<string, SandboxBundleCacheEntry>(10000);
private readonly Dictionary<string, string> _dataFilePathMapping = new Dictionary<string, string>(10000);
private readonly Dictionary<string, string> _infoFilePathMapping = new Dictionary<string, string>(10000);
@@ -154,6 +160,17 @@ namespace YooAsset
{
return _cacheEntries.ContainsKey(bundleGuid);
}
/// <inheritdoc />
public string GetCacheFilePath(string bundleGuid)
{
if (_cacheEntries.TryGetValue(bundleGuid, out SandboxBundleCacheEntry entry))
{
return entry.DataFilePath;
}
YooLogger.LogWarning($"Cache file path not found. Bundle GUID: '{bundleGuid}'.");
return null;
}
/// <summary>
/// 根据 ClearMethod 创建对应的淘汰策略实例
@@ -185,11 +202,12 @@ namespace YooAsset
/// <returns>数据文件的完整路径</returns>
internal string GetDataFilePath(PackageBundle bundle)
{
if (_dataFilePathMapping.TryGetValue(bundle.BundleGuid, out string filePath) == false)
string bundleGuid = bundle.BundleGuid;
if (_dataFilePathMapping.TryGetValue(bundleGuid, out string filePath) == false)
{
string folderName = GetHashFolderName(bundle.FileHash);
filePath = PathUtility.Combine(RootPath, folderName, bundle.BundleGuid, SandboxBundleCacheConsts.BundleDataFileName);
_dataFilePathMapping.Add(bundle.BundleGuid, filePath);
string folderName = GetHashFolderName(bundleGuid);
filePath = PathUtility.Combine(RootPath, folderName, bundleGuid, SandboxBundleCacheConsts.BundleDataFileName);
_dataFilePathMapping.Add(bundleGuid, filePath);
}
return filePath;
}
@@ -201,11 +219,12 @@ namespace YooAsset
/// <returns>信息文件的完整路径</returns>
internal string GetInfoFilePath(PackageBundle bundle)
{
if (_infoFilePathMapping.TryGetValue(bundle.BundleGuid, out string filePath) == false)
string bundleGuid = bundle.BundleGuid;
if (_infoFilePathMapping.TryGetValue(bundleGuid, out string filePath) == false)
{
string folderName = GetHashFolderName(bundle.FileHash);
filePath = PathUtility.Combine(RootPath, folderName, bundle.BundleGuid, SandboxBundleCacheConsts.BundleInfoFileName);
_infoFilePathMapping.Add(bundle.BundleGuid, filePath);
string folderName = GetHashFolderName(bundleGuid);
filePath = PathUtility.Combine(RootPath, folderName, bundleGuid, SandboxBundleCacheConsts.BundleInfoFileName);
_infoFilePathMapping.Add(bundleGuid, filePath);
}
return filePath;
}
@@ -217,8 +236,9 @@ namespace YooAsset
/// <returns>数据临时文件的完整路径</returns>
internal string GetDataTempFilePath(PackageBundle bundle)
{
string folderName = GetHashFolderName(bundle.FileHash);
return PathUtility.Combine(RootPath, folderName, bundle.BundleGuid, SandboxBundleCacheConsts.BundleDataTempFileName);
string bundleGuid = bundle.BundleGuid;
string folderName = GetHashFolderName(bundleGuid);
return PathUtility.Combine(RootPath, folderName, bundleGuid, SandboxBundleCacheConsts.BundleDataTempFileName);
}
/// <summary>
@@ -228,8 +248,9 @@ namespace YooAsset
/// <returns>信息临时文件的完整路径</returns>
internal string GetInfoTempFilePath(PackageBundle bundle)
{
string folderName = GetHashFolderName(bundle.FileHash);
return PathUtility.Combine(RootPath, folderName, bundle.BundleGuid, SandboxBundleCacheConsts.BundleInfoTempFileName);
string bundleGuid = bundle.BundleGuid;
string folderName = GetHashFolderName(bundleGuid);
return PathUtility.Combine(RootPath, folderName, bundleGuid, SandboxBundleCacheConsts.BundleInfoTempFileName);
}
/// <summary>
@@ -286,14 +307,14 @@ namespace YooAsset
}
}
private string GetHashFolderName(string fileHash)
private string GetHashFolderName(string bundleGuid)
{
if (string.IsNullOrEmpty(fileHash))
throw new YooInternalException("File hash is null or empty.");
if (string.IsNullOrEmpty(bundleGuid))
throw new YooInternalException("Bundle GUID is null or empty.");
if (fileHash.Length <= HashFolderNameLength)
return fileHash;
return fileHash.Substring(0, HashFolderNameLength);
if (bundleGuid.Length <= SandboxBundleCacheConsts.HashFolderNameLength)
return bundleGuid;
return bundleGuid.Substring(0, SandboxBundleCacheConsts.HashFolderNameLength);
}
#endregion
}

View File

@@ -40,5 +40,10 @@ namespace YooAsset
/// 信息文件预期大小(字节)
/// </summary>
public const int InfoFileExpectedSize = 36;
/// <summary>
/// 哈希分片目录前缀长度
/// </summary>
public const int HashFolderNameLength = 2;
}
}

View File

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

View File

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

View File

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

View File

@@ -1,182 +0,0 @@
using System.Collections.Generic;
namespace YooAsset
{
/// <summary>
/// WebGL 游戏平台缓存系统
/// </summary>
internal class WebGameBundleCache : IBundleCache
{
internal readonly struct Configuration
{
/// <summary>
/// 游戏平台接口
/// </summary>
public IWebGamePlatform GamePlatform { get; }
/// <summary>
/// 看门狗超时时间
/// </summary>
public int WatchdogTimeout { get; }
/// <summary>
/// 禁用 Unity 内置网络缓存
/// </summary>
public bool DisableUnityWebCache { get; }
/// <summary>
/// 下载数据校验级别
/// </summary>
public EFileVerifyLevel DownloadVerifyLevel { get; }
/// <summary>
/// AssetBundle 解密器
/// </summary>
public IBundleDecryptor AssetBundleDecryptor { get; }
/// <summary>
/// 远程服务接口
/// </summary>
public IRemoteService RemoteService { get; }
/// <summary>
/// 下载后台接口
/// </summary>
public IDownloadBackend DownloadBackend { get; }
/// <summary>
/// 下载重试判定策略
/// </summary>
public IDownloadRetryPolicy DownloadRetryPolicy { get; }
/// <summary>
/// URL 选择策略
/// </summary>
public IDownloadUrlPolicy DownloadUrlPolicy { get; }
public Configuration(IWebGamePlatform gamePlatform,
int watchdogTimeout, bool disableUnityWebCache,
EFileVerifyLevel downloadVerifyLevel, IBundleDecryptor assetBundleDecryptor, IRemoteService remoteService,
IDownloadBackend downloadBackend, IDownloadRetryPolicy downloadRetryPolicy, IDownloadUrlPolicy downloadUrlPolicy)
{
GamePlatform = gamePlatform;
WatchdogTimeout = watchdogTimeout;
DisableUnityWebCache = disableUnityWebCache;
DownloadVerifyLevel = downloadVerifyLevel;
AssetBundleDecryptor = assetBundleDecryptor;
RemoteService = remoteService;
DownloadBackend = downloadBackend;
DownloadRetryPolicy = downloadRetryPolicy;
DownloadUrlPolicy = downloadUrlPolicy;
}
}
private readonly Dictionary<string, WebGameBundleCacheEntry> _cacheEntries = new Dictionary<string, WebGameBundleCacheEntry>(10000);
/// <summary>
/// 缓存配置
/// </summary>
internal readonly Configuration Config;
#region
/// <inheritdoc/>
public string PackageName { get; }
/// <inheritdoc/>
public string RootPath { get; }
/// <inheritdoc/>
public bool IsReadOnly { get; }
/// <inheritdoc/>
public int FileCount => _cacheEntries.Count;
/// <inheritdoc/>
public long SpaceOccupied => 0;
#endregion
/// <summary>
/// 创建 WebGameBundleCache 实例
/// </summary>
/// <param name="packageName">包裹名称</param>
/// <param name="rootPath">缓存根目录</param>
/// <param name="config">缓存配置</param>
public WebGameBundleCache(string packageName, string rootPath, Configuration config)
{
PackageName = packageName;
RootPath = rootPath;
Config = config;
IsReadOnly = true;
}
/// <inheritdoc/>
public void Dispose()
{
}
/// <inheritdoc/>
public BCInitializeOperation InitializeAsync()
{
var operation = new WGBCInitializeOperation();
return operation;
}
/// <inheritdoc/>
public BCWriteCacheOperation WriteCacheAsync(BCWriteCacheOptions options)
{
var operation = new BCWriteCacheCompleteOperation($"{nameof(WebGameBundleCache)} is readonly.");
return operation;
}
/// <inheritdoc/>
public BCClearCacheOperation ClearCacheAsync(BCClearCacheOptions options)
{
var operation = new BCClearCacheCompleteOperation();
return operation;
}
/// <inheritdoc/>
public BCVerifyCacheOperation VerifyCacheAsync(BCVerifyCacheOptions options)
{
var operation = new BCVerifyCacheCompleteOperation();
return operation;
}
/// <inheritdoc/>
public BCLoadBundleOperation LoadBundleAsync(BCLoadBundleOptions options)
{
if (options.Bundle.GetBundleType() == (int)EBundleType.AssetBundle)
{
var operation = new WGBCLoadAssetBundleOperation(this, options);
return operation;
}
else
{
string error = $"{nameof(WebGameBundleCache)} does not support bundle type: {options.Bundle.GetBundleType()}.";
var operation = new BCLoadBundleErrorOperation(error);
return operation;
}
}
/// <inheritdoc/>
public bool IsCached(string bundleGuid)
{
if (_cacheEntries.TryGetValue(bundleGuid, out var entry))
return Config.GamePlatform.IsCached(entry.CacheFilePath);
return false;
}
#region
/// <summary>
/// 获取或创建指定资源包的缓存条目
/// </summary>
/// <param name="bundle">资源包描述</param>
/// <returns>已存在则返回对应条目;否则创建并登记后返回新条目。</returns>
internal WebGameBundleCacheEntry GetEntry(PackageBundle bundle)
{
if (_cacheEntries.TryGetValue(bundle.BundleGuid, out var entry))
return entry;
var urls = Config.RemoteService.GetRemoteUrls(bundle.GetFileName());
var filePath = Config.GamePlatform.GetCacheFilePath(RootPath, bundle);
var newEntry = new WebGameBundleCacheEntry(bundle.BundleGuid, urls, filePath);
_cacheEntries.Add(bundle.BundleGuid, newEntry);
return newEntry;
}
#endregion
}
}

View File

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

View File

@@ -1,38 +0,0 @@
using System.Collections.Generic;
namespace YooAsset
{
/// <summary>
/// WebGL 游戏平台缓存条目
/// </summary>
internal class WebGameBundleCacheEntry : ICacheEntry
{
/// <summary>
/// 资源包唯一标识
/// </summary>
public string BundleGuid { get; }
/// <summary>
/// 候选下载地址列表
/// </summary>
public IReadOnlyList<string> Urls { get; }
/// <summary>
/// 平台侧缓存文件路径
/// </summary>
public string CacheFilePath { get; }
/// <summary>
/// 创建 WebGL 游戏平台缓存条目实例
/// </summary>
/// <param name="bundleGuid">资源包唯一标识</param>
/// <param name="urls">候选下载地址列表</param>
/// <param name="cacheFilePath">平台侧缓存文件路径</param>
public WebGameBundleCacheEntry(string bundleGuid, IReadOnlyList<string> urls, string cacheFilePath)
{
BundleGuid = bundleGuid;
Urls = urls;
CacheFilePath = cacheFilePath;
}
}
}

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: ead67fdda05e99747b03f49accc1526a
guid: 9494c5da1a02a7b43b40729e88fbda21
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -2,9 +2,9 @@
namespace YooAsset
{
/// <summary>
/// WebGL 游戏平台缓存系统初始化操作
/// WebGL 平台网络缓存初始化操作
/// </summary>
internal sealed class WGBCInitializeOperation : BCInitializeOperation
internal sealed class WNBCInitializeOperation : BCInitializeOperation
{
protected override void InternalStart()
{

View File

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

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