From 092af257d78fc7666628578aa0d1a3b9e2aec865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=95=E5=86=A0=E5=B3=B0?= <33317070@qq.com> Date: Wed, 10 Jun 2026 10:36:49 +0800 Subject: [PATCH] feat : optimize package manifest --- .../Editor/BundleBuilder/BuildBundleInfo.cs | 22 +- .../BuildTasks/TaskUpdateBundleInfo_IABP.cs | 3 +- .../BuildTasks/TaskCopyBundledFiles.cs | 16 +- .../BuildTasks/TaskCreateManifest.cs | 118 ++++---- .../BuildTasks/TaskCreateReport.cs | 68 ++--- .../BuildTasks/TaskUpdateBundleInfo.cs | 3 +- .../Policies/EvictionByTagsPolicy.cs | 2 +- .../Runtime/Interfaces/IBundleUnpackPolicy.cs | 34 +-- .../Runtime/ResourcePackage/BuildModel.meta | 8 + .../ResourcePackage/BuildModel/BuildAsset.cs | 44 +++ .../BuildModel/BuildAsset.cs.meta | 11 + .../ResourcePackage/BuildModel/BuildBundle.cs | 84 ++++++ .../BuildModel/BuildBundle.cs.meta | 11 + .../BuildModel/BuildManifest.cs | 259 ++++++++++++++++++ .../BuildModel/BuildManifest.cs.meta | 11 + .../ResourcePackage/BundleFileNaming.cs | 50 ++++ .../ResourcePackage/BundleFileNaming.cs.meta | 11 + .../Runtime/ResourcePackage/FileSystemHost.cs | 4 +- .../Internal/DeserializeManifestOperation.cs | 198 ++++++++++--- .../Internal/SerializeManifestOperation.cs | 153 +++++++++++ .../SerializeManifestOperation.cs.meta | 11 + .../Runtime/ResourcePackage/PackageAsset.cs | 30 +- .../Runtime/ResourcePackage/PackageBundle.cs | 92 +------ .../Runtime/ResourcePackage/PackageDetails.cs | 104 +++++-- .../ResourcePackage/PackageManifest.cs | 135 ++------- .../ResourcePackage/PackageManifestConsts.cs | 6 +- .../ResourcePackage/PackageManifestHelper.cs | 151 +--------- .../Runtime/ResourcePackage/PackageTags.cs | 86 ++++++ .../ResourcePackage/PackageTags.cs.meta | 11 + .../YooAsset/Runtime/Utility/BufferReader.cs | 39 +++ .../YooAsset/Runtime/Utility/BufferWriter.cs | 52 ++++ 31 files changed, 1244 insertions(+), 583 deletions(-) create mode 100644 Assets/YooAsset/Runtime/ResourcePackage/BuildModel.meta create mode 100644 Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildAsset.cs create mode 100644 Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildAsset.cs.meta create mode 100644 Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildBundle.cs create mode 100644 Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildBundle.cs.meta create mode 100644 Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildManifest.cs create mode 100644 Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildManifest.cs.meta create mode 100644 Assets/YooAsset/Runtime/ResourcePackage/BundleFileNaming.cs create mode 100644 Assets/YooAsset/Runtime/ResourcePackage/BundleFileNaming.cs.meta create mode 100644 Assets/YooAsset/Runtime/ResourcePackage/Operations/Internal/SerializeManifestOperation.cs create mode 100644 Assets/YooAsset/Runtime/ResourcePackage/Operations/Internal/SerializeManifestOperation.cs.meta create mode 100644 Assets/YooAsset/Runtime/ResourcePackage/PackageTags.cs create mode 100644 Assets/YooAsset/Runtime/ResourcePackage/PackageTags.cs.meta diff --git a/Assets/YooAsset/Editor/BundleBuilder/BuildBundleInfo.cs b/Assets/YooAsset/Editor/BundleBuilder/BuildBundleInfo.cs index a5b09b9d..4e0f54fa 100644 --- a/Assets/YooAsset/Editor/BundleBuilder/BuildBundleInfo.cs +++ b/Assets/YooAsset/Editor/BundleBuilder/BuildBundleInfo.cs @@ -216,19 +216,19 @@ namespace YooAsset.Editor } /// - /// 创建PackageBundle类 + /// 创建BuildBundle类 /// - 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(); - 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(); + return buildBundle; } } } \ No newline at end of file diff --git a/Assets/YooAsset/Editor/BundleBuilder/BuildPipeline/InstantAssetBuildPipeline/BuildTasks/TaskUpdateBundleInfo_IABP.cs b/Assets/YooAsset/Editor/BundleBuilder/BuildPipeline/InstantAssetBuildPipeline/BuildTasks/TaskUpdateBundleInfo_IABP.cs index 00aedac5..e451f5c5 100644 --- a/Assets/YooAsset/Editor/BundleBuilder/BuildPipeline/InstantAssetBuildPipeline/BuildTasks/TaskUpdateBundleInfo_IABP.cs +++ b/Assets/YooAsset/Editor/BundleBuilder/BuildPipeline/InstantAssetBuildPipeline/BuildTasks/TaskUpdateBundleInfo_IABP.cs @@ -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}"; } } diff --git a/Assets/YooAsset/Editor/BundleBuilder/BuildTasks/TaskCopyBundledFiles.cs b/Assets/YooAsset/Editor/BundleBuilder/BuildTasks/TaskCopyBundledFiles.cs index c93e1e55..a4964ffa 100644 --- a/Assets/YooAsset/Editor/BundleBuilder/BuildTasks/TaskCopyBundledFiles.cs +++ b/Assets/YooAsset/Editor/BundleBuilder/BuildTasks/TaskCopyBundledFiles.cs @@ -25,7 +25,7 @@ namespace YooAsset.Editor /// /// 拷贝首包资源文件 /// - 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); } } diff --git a/Assets/YooAsset/Editor/BundleBuilder/BuildTasks/TaskCreateManifest.cs b/Assets/YooAsset/Editor/BundleBuilder/BuildTasks/TaskCreateManifest.cs index b4ee6589..240981c5 100644 --- a/Assets/YooAsset/Editor/BundleBuilder/BuildTasks/TaskCreateManifest.cs +++ b/Assets/YooAsset/Editor/BundleBuilder/BuildTasks/TaskCreateManifest.cs @@ -12,7 +12,7 @@ namespace YooAsset.Editor [ContextObject] public class ManifestContext { - internal PackageManifest Manifest; + internal BuildManifest Manifest; } /// @@ -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 /// /// 创建资源对象列表 /// - private List CreatePackageAssetList(BuildMapContext buildMapContext) + private List CreatePackageAssetList(BuildMapContext buildMapContext) { - List result = new List(1000); + List result = new List(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 /// /// 创建资源包列表 /// - private List CreatePackageBundleList(BuildMapContext buildMapContext) + private List CreatePackageBundleList(BuildMapContext buildMapContext) { - List result = new List(1000); + List result = new List(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 /// /// 处理资源清单的资源对象列表 /// - 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); } } /// /// 处理资源包的依赖集合 /// - 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 dependIDs = new List(dependNames.Length); foreach (var dependName in dependNames) { @@ -246,29 +240,29 @@ namespace YooAsset.Editor // 排序并填充数据 dependIDs.Sort(); - packageBundle.DependentBundleIDs = dependIDs.ToArray(); + buildBundle.DependentBundleIDs = dependIDs.ToArray(); } } /// /// 处理资源包的标签集合 /// - 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(); + buildBundle.Tags = Array.Empty(); } // 将主资源的标签信息传染给其依赖的资源包集合 - 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 cacheBundleIDs = new HashSet(builtinPackageBundle.ReferrerBundleIDs); + HashSet cacheBundleIDs = new HashSet(builtinBuildBundle.ReferrerBundleIDs); HashSet tempTags = new HashSet(); - 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(packageAsset.DependentBundleIDs); + var tempBundleIDs = new List(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) { diff --git a/Assets/YooAsset/Editor/BundleBuilder/BuildTasks/TaskCreateReport.cs b/Assets/YooAsset/Editor/BundleBuilder/BuildTasks/TaskCreateReport.cs index d32560d4..8482670c 100644 --- a/Assets/YooAsset/Editor/BundleBuilder/BuildTasks/TaskCreateReport.cs +++ b/Assets/YooAsset/Editor/BundleBuilder/BuildTasks/TaskCreateReport.cs @@ -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(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(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 /// /// 获取资源对象依赖的资源包集合 /// - private List GetAssetDependBundles(PackageManifest manifest, PackageAsset packageAsset) + private List GetAssetDependBundles(BuildManifest manifest, BuildAsset buildAsset) { - List dependBundles = new List(packageAsset.DependentBundleIDs.Length); - foreach (int index in packageAsset.DependentBundleIDs) + List dependBundles = new List(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 /// /// 获取资源包依赖的资源包集合 /// - private List GetBundleDependBundles(PackageManifest manifest, PackageBundle packageBundle) + private List GetBundleDependBundles(BuildManifest manifest, BuildBundle buildBundle) { - List dependBundles = new List(packageBundle.DependentBundleIDs.Length); - foreach (int index in packageBundle.DependentBundleIDs) + List dependBundles = new List(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 /// /// 获取引用该资源包的资源包集合 /// - private List GetBundleReferenceBundles(PackageManifest manifest, PackageBundle packageBundle) + private List GetBundleReferenceBundles(BuildManifest manifest, BuildBundle buildBundle) { - List referenceBundles = new List(packageBundle.ReferrerBundleIDs.Count); - foreach (int index in packageBundle.ReferrerBundleIDs) + List referenceBundles = new List(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) diff --git a/Assets/YooAsset/Editor/BundleBuilder/BuildTasks/TaskUpdateBundleInfo.cs b/Assets/YooAsset/Editor/BundleBuilder/BuildTasks/TaskUpdateBundleInfo.cs index bb3db8dd..a8ae8d10 100644 --- a/Assets/YooAsset/Editor/BundleBuilder/BuildTasks/TaskUpdateBundleInfo.cs +++ b/Assets/YooAsset/Editor/BundleBuilder/BuildTasks/TaskUpdateBundleInfo.cs @@ -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}"; } } diff --git a/Assets/YooAsset/Runtime/BundleCache/Policies/EvictionByTagsPolicy.cs b/Assets/YooAsset/Runtime/BundleCache/Policies/EvictionByTagsPolicy.cs index 3001fbde..e4dbb215 100644 --- a/Assets/YooAsset/Runtime/BundleCache/Policies/EvictionByTagsPolicy.cs +++ b/Assets/YooAsset/Runtime/BundleCache/Policies/EvictionByTagsPolicy.cs @@ -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); } } diff --git a/Assets/YooAsset/Runtime/Interfaces/IBundleUnpackPolicy.cs b/Assets/YooAsset/Runtime/Interfaces/IBundleUnpackPolicy.cs index 016aa893..7de92637 100644 --- a/Assets/YooAsset/Runtime/Interfaces/IBundleUnpackPolicy.cs +++ b/Assets/YooAsset/Runtime/Interfaces/IBundleUnpackPolicy.cs @@ -29,44 +29,14 @@ namespace YooAsset public bool IsEncrypted => _bundle.IsEncrypted; /// - /// 分类标签数量 + /// 分类标签集合 /// - public int TagCount - { - get - { - return _bundle.Tags == null ? 0 : _bundle.Tags.Length; - } - } + public PackageTags Tags => _bundle.Tags; internal BundleUnpackInfo(PackageBundle bundle) { _bundle = bundle; } - - /// - /// 获取指定索引的分类标签 - /// - public string GetTag(int index) - { - return _bundle.Tags[index]; - } - - /// - /// 是否包含指定的单个标签 - /// - public bool HasTag(string tag) - { - return _bundle.HasTag(tag); - } - - /// - /// 是否包含指定标签数组中的任意一个 - /// - public bool HasAnyTag(string[] tags) - { - return _bundle.HasAnyTag(tags); - } } /// diff --git a/Assets/YooAsset/Runtime/ResourcePackage/BuildModel.meta b/Assets/YooAsset/Runtime/ResourcePackage/BuildModel.meta new file mode 100644 index 00000000..acdaed6e --- /dev/null +++ b/Assets/YooAsset/Runtime/ResourcePackage/BuildModel.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b097205d69d7e8348968898bc0067ee6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildAsset.cs b/Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildAsset.cs new file mode 100644 index 00000000..b78fbdfc --- /dev/null +++ b/Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildAsset.cs @@ -0,0 +1,44 @@ +namespace YooAsset +{ + /// + /// 构建期专用的资源描述 + /// + internal class BuildAsset + { + /// + /// 可寻址地址 + /// + public string Address; + + /// + /// 资源路径 + /// + public string AssetPath; + + /// + /// 资源GUID + /// + public string AssetGuid; + + /// + /// 所属资源包ID + /// + public int BundleID; + + /// + /// 依赖的资源包ID集合 + /// 说明:框架层收集查询结果 + /// + public int[] DependentBundleIDs; + + /// + /// 资源的分类标签 + /// + public string[] Tags; + + /// + /// 临时数据对象 + /// + public object EditorUserData; + } +} diff --git a/Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildAsset.cs.meta b/Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildAsset.cs.meta new file mode 100644 index 00000000..5aa58b02 --- /dev/null +++ b/Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildAsset.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ecfb4d29b9f6c534191ca4d6e3de242a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildBundle.cs b/Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildBundle.cs new file mode 100644 index 00000000..0f01e12d --- /dev/null +++ b/Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildBundle.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using System.Linq; + +namespace YooAsset +{ + /// + /// 构建期专用的资源包描述 + /// + internal class BuildBundle + { + /// + /// 资源包名称 + /// + public string BundleName; + + /// + /// Unity引擎计算的内容校验码 + /// + public uint UnityCrc; + + /// + /// 文件哈希值 + /// + public string FileHash; + + /// + /// 文件校验码 + /// + public uint FileCrc; + + /// + /// 文件大小(字节数) + /// + public long FileSize; + + /// + /// 是否为加密文件 + /// + public bool IsEncrypted; + + /// + /// 依赖的资源包ID集合 + /// 注意:引擎层构建查询结果 + /// + public int[] DependentBundleIDs; + + /// + /// 资源包的分类标签 + /// + public string[] Tags; + + /// + /// 引用该资源包的资源包ID集合 + /// + public readonly List ReferrerBundleIDs = new List(); + + + /// + /// 获取资源包文件名称 + /// + /// 文件名称样式 + /// 返回根据命名样式生成的远端文件名 + public string GetFileName(int outputNameStyle) + { + return BundleFileNaming.GetBundleFileName(outputNameStyle, BundleName, FileHash); + } + + /// + /// 是否包含指定标签数组中的任意一个 + /// + public bool HasAnyTag(string[] tags) + { + if (tags == null || tags.Length == 0 || Tags == null || Tags.Length == 0) + return false; + + foreach (var tag in tags) + { + if (Tags.Contains(tag)) + return true; + } + return false; + } + } +} diff --git a/Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildBundle.cs.meta b/Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildBundle.cs.meta new file mode 100644 index 00000000..cb0aaeb6 --- /dev/null +++ b/Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildBundle.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9109a09c01fd3d3448f0bfba85d8759f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildManifest.cs b/Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildManifest.cs new file mode 100644 index 00000000..95cc4392 --- /dev/null +++ b/Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildManifest.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace YooAsset +{ + /// + /// 构建期专用的清单模型 + /// + internal class BuildManifest + { + private bool _referrerGraphBuilt; + + // 全局标签表 + private bool _tagTableBuilt; + private string[] _tagTable; + private Dictionary _tagToIndex; + + // 全局目录表 + private bool _directoryTableBuilt; + private string[] _directoryTable; + private Dictionary _directoryToIndex; + + /// + /// 文件版本 + /// + public int FileVersion; + + /// + /// 启用可寻址资源定位 + /// + public bool EnableAddressable; + + /// + /// 支持无后缀名的资源定位地址 + /// + public bool SupportExtensionless; + + /// + /// 资源定位地址大小写不敏感 + /// + public bool LocationToLower; + + /// + /// 包含资源GUID数据 + /// + public bool IncludeAssetGuid; + + /// + /// 使用可寻址地址代替资源路径 + /// + public bool ReplaceAssetPathWithAddress; + + /// + /// 文件名称样式 + /// + public int OutputNameStyle; + + /// + /// 构建资源包类型 + /// + public int BuildBundleType; + + /// + /// 构建管线名称 + /// + public string BuildPipeline; + + /// + /// 资源包裹名称 + /// + public string PackageName; + + /// + /// 资源包裹的版本信息 + /// + public string PackageVersion; + + /// + /// 资源包裹的备注信息 + /// + public string PackageNote; + + /// + /// 资源列表 + /// + public List AssetList = new List(); + + /// + /// 资源包列表 + /// + public List BundleList = new List(); + + /// + /// 全局标签表(去重后的标签数组) + /// + public string[] TagTable + { + get { return _tagTable; } + } + + /// + /// 全局目录表(去重后的目录数组) + /// + public string[] DirectoryTable + { + get { return _directoryTable; } + } + + + /// + /// 构建资源包的引用关系图 + /// 说明:根据每个资源包的依赖列表,反向填充被依赖方的引用者集合。 + /// + public void BuildReferrerGraph() + { + if (_referrerGraphBuilt) + throw new YooInternalException("BuildReferrerGraph has already been called."); + _referrerGraphBuilt = true; + + for (int index = 0; index < BundleList.Count; index++) + { + var sourceBundle = BundleList[index]; + if (sourceBundle.DependentBundleIDs == null) + continue; + + foreach (int dependIndex in sourceBundle.DependentBundleIDs) + { + if (dependIndex >= 0 && dependIndex < BundleList.Count) + { + var dependBundle = BundleList[dependIndex]; + if (dependBundle.ReferrerBundleIDs.Contains(index)) + throw new YooInternalException($"Duplicate referrer bundle ID detected: referrer {index} -> bundle {dependIndex}."); + dependBundle.ReferrerBundleIDs.Add(index); + } + else + { + throw new System.ArgumentOutOfRangeException($"Invalid dependent bundle index: {dependIndex}. Valid range is 0 to {BundleList.Count - 1}."); + } + } + } + } + + /// + /// 构建去重后的全局标签表,并填充标签到索引的映射 + /// + public void BuildTagTable() + { + if (_tagTableBuilt) + throw new YooInternalException("BuildTagTable has already been called."); + _tagTableBuilt = true; + + HashSet tagSet = new HashSet(); + foreach (var buildAsset in AssetList) + { + if (buildAsset.Tags == null) + continue; + foreach (var tag in buildAsset.Tags) + { + tagSet.Add(tag); + } + } + foreach (var buildBundle in BundleList) + { + if (buildBundle.Tags == null) + continue; + foreach (var tag in buildBundle.Tags) + { + tagSet.Add(tag); + } + } + + var tagTable = tagSet.ToList(); + tagTable.Sort(StringComparer.Ordinal); //注意:排序增加序列化稳定性 + + if (tagTable.Count > ushort.MaxValue) + throw new YooInternalException($"Tag count exceeds the maximum value of {ushort.MaxValue}."); + + _tagTable = tagTable.ToArray(); + _tagToIndex = new Dictionary(_tagTable.Length); + for (int index = 0; index < _tagTable.Length; index++) + { + _tagToIndex.Add(_tagTable[index], index); + } + } + + /// + /// 将标签字符串数组转换为全局标签表的索引数组 + /// + public ushort[] ConvertTagsToIndices(string[] tags) + { + if (tags == null || tags.Length == 0) + return Array.Empty(); + + ushort[] indices = new ushort[tags.Length]; + for (int i = 0; i < tags.Length; i++) + { + indices[i] = (ushort)_tagToIndex[tags[i]]; + } + return indices; + } + + /// + /// 构建去重后的全局目录表,并填充目录到索引的映射 + /// + public void BuildDirectoryTable() + { + if (_directoryTableBuilt) + throw new YooInternalException("BuildDirectoryTable has already been called."); + _directoryTableBuilt = true; + + HashSet dirSet = new HashSet(); + foreach (var buildAsset in AssetList) + { + SplitAssetPath(buildAsset.AssetPath, out string directory, out string fileName); + dirSet.Add(directory); + } + + var dirTable = dirSet.ToList(); + dirTable.Sort(StringComparer.Ordinal); //注意:排序增加序列化稳定性 + + if (dirTable.Count > ushort.MaxValue) + throw new YooInternalException($"Directory count exceeds the maximum value of {ushort.MaxValue}."); + + _directoryTable = dirTable.ToArray(); + _directoryToIndex = new Dictionary(_directoryTable.Length); + for (int index = 0; index < _directoryTable.Length; index++) + { + _directoryToIndex.Add(_directoryTable[index], index); + } + } + + /// + /// 获取资源父目录在全局目录表中的索引 + /// + public ushort ConvertDirectoryToIndex(string directory) + { + return (ushort)_directoryToIndex[directory]; + } + + /// + /// 拆分资源路径为「父目录」与「文件名」 + /// + public static void SplitAssetPath(string assetPath, out string directory, out string fileName) + { + int slash = assetPath.LastIndexOf('/'); + if (slash < 0) + { + directory = string.Empty; + fileName = assetPath; + } + else + { + directory = assetPath.Substring(0, slash); + fileName = assetPath.Substring(slash + 1); + } + } + } +} diff --git a/Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildManifest.cs.meta b/Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildManifest.cs.meta new file mode 100644 index 00000000..f2eced19 --- /dev/null +++ b/Assets/YooAsset/Runtime/ResourcePackage/BuildModel/BuildManifest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f77081497483ffd44b94a78d4e95203c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Runtime/ResourcePackage/BundleFileNaming.cs b/Assets/YooAsset/Runtime/ResourcePackage/BundleFileNaming.cs new file mode 100644 index 00000000..b80c4dda --- /dev/null +++ b/Assets/YooAsset/Runtime/ResourcePackage/BundleFileNaming.cs @@ -0,0 +1,50 @@ +using System.IO; + +namespace YooAsset +{ + /// + /// 资源包文件命名工具 + /// + internal static class BundleFileNaming + { + /// + /// 获取资源包的文件名 + /// + /// 文件名称样式 + /// 资源包名称 + /// 文件哈希值 + /// 返回根据命名样式生成的远端文件名 + public static string GetBundleFileName(int nameStyle, string bundleName, string fileHash) + { + EFileNameStyle style = (EFileNameStyle)nameStyle; + switch (style) + { + case EFileNameStyle.HashName: + { + string fileExtension = Path.GetExtension(bundleName); + return StringUtility.Format("{0}{1}", fileHash, fileExtension); + } + + case EFileNameStyle.BundleName: + return bundleName; + + case EFileNameStyle.BundleName_HashName: + { + string fileExtension = Path.GetExtension(bundleName); + if (string.IsNullOrEmpty(fileExtension)) + { + return StringUtility.Format("{0}_{1}", bundleName, fileHash); + } + else + { + string fileName = bundleName.Remove(bundleName.LastIndexOf('.')); + return StringUtility.Format("{0}_{1}{2}", fileName, fileHash, fileExtension); + } + } + + default: + throw new System.NotImplementedException($"Invalid name style: {nameStyle}."); + } + } + } +} diff --git a/Assets/YooAsset/Runtime/ResourcePackage/BundleFileNaming.cs.meta b/Assets/YooAsset/Runtime/ResourcePackage/BundleFileNaming.cs.meta new file mode 100644 index 00000000..34cc8c16 --- /dev/null +++ b/Assets/YooAsset/Runtime/ResourcePackage/BundleFileNaming.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8eb74dcfa1fb85948ad1da5f188a6677 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Runtime/ResourcePackage/FileSystemHost.cs b/Assets/YooAsset/Runtime/ResourcePackage/FileSystemHost.cs index 8424f30e..3a5f711c 100644 --- a/Assets/YooAsset/Runtime/ResourcePackage/FileSystemHost.cs +++ b/Assets/YooAsset/Runtime/ResourcePackage/FileSystemHost.cs @@ -421,14 +421,14 @@ namespace YooAsset if (predicate(fileSystem, packageBundle)) { // 注意:未标记的资源包视为公共依赖,始终包含在下载列表中 - if (packageBundle.IsTagged() == false) + if (packageBundle.Tags.IsTagged == false) { var bundleInfo = new BundleInfo(fileSystem, packageBundle); result.Add(bundleInfo); } else { - if (packageBundle.HasAnyTag(tags)) + if (packageBundle.Tags.HasAnyTag(tags)) { var bundleInfo = new BundleInfo(fileSystem, packageBundle); result.Add(bundleInfo); diff --git a/Assets/YooAsset/Runtime/ResourcePackage/Operations/Internal/DeserializeManifestOperation.cs b/Assets/YooAsset/Runtime/ResourcePackage/Operations/Internal/DeserializeManifestOperation.cs index dc3617bd..df453a0c 100644 --- a/Assets/YooAsset/Runtime/ResourcePackage/Operations/Internal/DeserializeManifestOperation.cs +++ b/Assets/YooAsset/Runtime/ResourcePackage/Operations/Internal/DeserializeManifestOperation.cs @@ -13,7 +13,7 @@ namespace YooAsset private enum ESteps { None, - RestoreFileData, + DecryptManifest, DeserializeFileHeader, PrepareAssetList, DeserializeAssetList, @@ -26,6 +26,8 @@ namespace YooAsset private readonly IManifestDecryptor _decryptor; private byte[] _sourceData; private BufferReader _buffer; + private string[] _tagTable; + private string[] _directoryTable; private int _packageAssetCount; private int _packageBundleCount; private int _progressTotalValue; @@ -48,14 +50,14 @@ namespace YooAsset } protected override void InternalStart() { - _steps = ESteps.RestoreFileData; + _steps = ESteps.DecryptManifest; } protected override void InternalUpdate() { if (_steps == ESteps.None || _steps == ESteps.Done) return; - if (_steps == ESteps.RestoreFileData) + if (_steps == ESteps.DecryptManifest) { if (_decryptor != null) { @@ -110,6 +112,12 @@ namespace YooAsset Manifest.PackageVersion = _buffer.ReadString(); Manifest.PackageNote = _buffer.ReadString(); + // 读取全局标签表 + _tagTable = _buffer.ReadStringArray(); + + // 读取全局目录表 + _directoryTable = _buffer.ReadStringArray(); + // 检测配置 if (Manifest.EnableAddressable && Manifest.LocationToLower) throw new YooManifestInvalidException("Addressable mode does not support converting locations to lowercase."); @@ -138,18 +146,35 @@ namespace YooAsset while (_packageAssetCount > 0) { var packageAsset = new PackageAsset(); - packageAsset.Address = _buffer.ReadString(); + + // Address + if (Manifest.EnableAddressable) + packageAsset.Address = _buffer.ReadString(); + else + packageAsset.Address = string.Empty; + + // AssetPath + ushort dirIndex = _buffer.ReadUInt16(); if (replaceAssetPath) { packageAsset.AssetPath = packageAsset.Address; - _buffer.SkipString(); //跳过解析AssetPath + _buffer.SkipString(); //跳过解析文件名 } else { - packageAsset.AssetPath = _buffer.ReadString(); + packageAsset.AssetPath = ResolvePath(dirIndex); } - packageAsset.AssetGuid = _buffer.ReadString(); - packageAsset.AssetTags = _buffer.ReadStringArray(); + + // AssetGuid + if (Manifest.IncludeAssetGuid) + packageAsset.AssetGuid = _buffer.ReadHash16(); + else + packageAsset.AssetGuid = string.Empty; + + // Tags + var tagIndices = _buffer.ReadUInt16Array(); + packageAsset.Tags = ResolveTags(tagIndices); + packageAsset.BundleID = _buffer.ReadInt32(); packageAsset.DependentBundleIDs = _buffer.ReadInt32Array(); FillAssetCollection(Manifest, packageAsset, replaceAssetPath); @@ -180,12 +205,13 @@ namespace YooAsset var packageBundle = new PackageBundle(); packageBundle.BundleName = _buffer.ReadString(); packageBundle.UnityCrc = _buffer.ReadUInt32(); - packageBundle.FileHash = _buffer.ReadString(); + packageBundle.FileHash = _buffer.ReadHash16(); packageBundle.FileCrc = _buffer.ReadUInt32(); packageBundle.FileSize = _buffer.ReadInt64(); packageBundle.IsEncrypted = _buffer.ReadBoolean(); - packageBundle.Tags = _buffer.ReadStringArray(); + packageBundle.Tags = ResolveTags(_buffer.ReadUInt16Array()); packageBundle.DependentBundleIDs = _buffer.ReadInt32Array(); + packageBundle.Initialize(Manifest); FillBundleCollection(Manifest, packageBundle); _packageBundleCount--; @@ -202,7 +228,9 @@ namespace YooAsset if (_steps == ESteps.InitManifest) { - Manifest.Initialize(); + FillBundleMainAssets(Manifest); + FillBundleReferrerBundleIDs(Manifest); + _steps = ESteps.Done; SetResult(); } @@ -212,51 +240,77 @@ namespace YooAsset ExecuteBatch(); } + private PackageTags ResolveTags(ushort[] tagIndices) + { + if (tagIndices.Length == 0) + return new PackageTags(Array.Empty()); + + string[] tags = new string[tagIndices.Length]; + for (int i = 0; i < tagIndices.Length; i++) + { + ushort tagIndex = tagIndices[i]; + +#if UNITY_EDITOR || DEBUG + if (tagIndex >= _tagTable.Length) + throw new YooManifestInvalidException($"Invalid tag index: {tagIndex}. Valid range is 0 to {_tagTable.Length - 1}."); +#endif + + tags[i] = _tagTable[tagIndex]; + } + + return new PackageTags(tags); + } + private string ResolvePath(ushort dirIndex) + { +#if UNITY_EDITOR || DEBUG + if (dirIndex >= _directoryTable.Length) + throw new YooManifestInvalidException($"Invalid directory index: {dirIndex}. Valid range is 0 to {_directoryTable.Length - 1}."); +#endif + + string fileName = _buffer.ReadString(); + string directory = _directoryTable[dirIndex]; + if (string.IsNullOrEmpty(directory)) + return fileName; + else + return string.Concat(directory, "/", fileName); + } + private void CreateAssetCollection(PackageManifest manifest, int assetCount) { manifest.AssetList = new List(assetCount); - manifest.AssetDictionary = new Dictionary(assetCount); if (manifest.EnableAddressable) { - manifest.AssetPathsByLocation = new Dictionary(assetCount * 3); + manifest.AssetsByLocation = new Dictionary(assetCount * 3); } else { if (manifest.LocationToLower) - manifest.AssetPathsByLocation = new Dictionary(assetCount * 2, StringComparer.OrdinalIgnoreCase); + manifest.AssetsByLocation = new Dictionary(assetCount * 2, StringComparer.OrdinalIgnoreCase); else - manifest.AssetPathsByLocation = new Dictionary(assetCount * 2); + manifest.AssetsByLocation = new Dictionary(assetCount * 2); } if (manifest.IncludeAssetGuid) - manifest.AssetPathsByGuid = new Dictionary(assetCount); + manifest.AssetsByGuid = new Dictionary(assetCount); else - manifest.AssetPathsByGuid = new Dictionary(); + manifest.AssetsByGuid = new Dictionary(); } private void FillAssetCollection(PackageManifest manifest, PackageAsset packageAsset, bool replaceAssetPath) { // 添加到列表集合 manifest.AssetList.Add(packageAsset); - // 注意:我们不允许原始路径存在重名 - string assetPath = packageAsset.AssetPath; -#if UNITY_EDITOR || DEBUG - if (manifest.AssetDictionary.ContainsKey(assetPath)) - throw new YooManifestInvalidException($"Asset path already exists: '{assetPath}'."); -#endif - manifest.AssetDictionary.Add(assetPath, packageAsset); - - // 填充AssetPathMapping1 + // 填充AssetsByLocation { string location = packageAsset.AssetPath; - // 添加原生路径的映射 + // 添加原生路径的映射(注意:我们不允许原始路径存在重名) #if UNITY_EDITOR || DEBUG - if (manifest.AssetPathsByLocation.ContainsKey(location)) - throw new YooManifestInvalidException($"Location already exists: '{location}'."); + if (manifest.AssetsByLocation.ContainsKey(location)) + throw new YooManifestInvalidException($"Asset path already exists: '{location}'."); #endif - manifest.AssetPathsByLocation.Add(location, packageAsset.AssetPath); + manifest.AssetsByLocation.Add(location, packageAsset); // 添加无后缀名路径的映射 if (manifest.SupportExtensionless) @@ -264,22 +318,22 @@ namespace YooAsset string locationWithoutExtension = Path.ChangeExtension(location, null); if (ReferenceEquals(location, locationWithoutExtension) == false) { - if (manifest.AssetPathsByLocation.ContainsKey(locationWithoutExtension)) + if (manifest.AssetsByLocation.ContainsKey(locationWithoutExtension)) YooLogger.LogWarning($"Location already exists: '{locationWithoutExtension}'."); else - manifest.AssetPathsByLocation.Add(locationWithoutExtension, packageAsset.AssetPath); + manifest.AssetsByLocation.Add(locationWithoutExtension, packageAsset); } } } - // 填充AssetPathMapping2 + // 填充AssetsByGuid if (manifest.IncludeAssetGuid) { #if UNITY_EDITOR || DEBUG - if (manifest.AssetPathsByGuid.ContainsKey(packageAsset.AssetGuid)) + if (manifest.AssetsByGuid.ContainsKey(packageAsset.AssetGuid)) throw new YooManifestInvalidException($"Asset GUID already exists: '{packageAsset.AssetGuid}'."); #endif - manifest.AssetPathsByGuid.Add(packageAsset.AssetGuid, packageAsset.AssetPath); + manifest.AssetsByGuid.Add(packageAsset.AssetGuid, packageAsset); } // 添加可寻址地址 @@ -289,10 +343,10 @@ namespace YooAsset if (string.IsNullOrEmpty(location) == false) { #if UNITY_EDITOR || DEBUG - if (manifest.AssetPathsByLocation.ContainsKey(location)) + if (manifest.AssetsByLocation.ContainsKey(location)) throw new YooManifestInvalidException($"Location already exists: '{location}'."); #endif - manifest.AssetPathsByLocation.Add(location, packageAsset.AssetPath); + manifest.AssetsByLocation.Add(location, packageAsset); } } } @@ -306,9 +360,6 @@ namespace YooAsset } private void FillBundleCollection(PackageManifest manifest, PackageBundle packageBundle) { - // 初始化资源包 - packageBundle.Initialize(manifest); - // 添加到列表集合 manifest.BundleList.Add(packageBundle); @@ -316,5 +367,72 @@ namespace YooAsset manifest.BundlesByFileName.Add(packageBundle.GetFileName(), packageBundle); manifest.BundlesByGuid.Add(packageBundle.BundleGuid, packageBundle); } + + private void FillBundleMainAssets(PackageManifest manifest) + { + int bundleCount = manifest.BundleList.Count; + + // 1. 统计每个资源包的主资源数量 + int[] mainAssetCounts = new int[bundleCount]; + foreach (var packageAsset in manifest.AssetList) + { + int bundleID = packageAsset.BundleID; + if (bundleID < 0 || bundleID >= bundleCount) + throw new ArgumentOutOfRangeException($"Invalid bundle ID: {bundleID}. Valid range is 0 to {bundleCount - 1}."); + + mainAssetCounts[bundleID]++; + } + + // 2. 创建列表 + for (int index = 0; index < bundleCount; index++) + { + int capacity = mainAssetCounts[index]; + manifest.BundleList[index].MainAssets = new List(capacity); + } + + // 3. 填充数据 + foreach (var packageAsset in manifest.AssetList) + { + manifest.BundleList[packageAsset.BundleID].MainAssets.Add(packageAsset); + } + } + private void FillBundleReferrerBundleIDs(PackageManifest manifest) + { + int bundleCount = manifest.BundleList.Count; + + // 1. 统计每个资源包被引用的次数 + int[] referrerCounts = new int[bundleCount]; + for (int index = 0; index < bundleCount; index++) + { + foreach (int dependIndex in manifest.BundleList[index].DependentBundleIDs) + { + if (dependIndex < 0 || dependIndex >= bundleCount) + throw new ArgumentOutOfRangeException($"Invalid dependent bundle index: {dependIndex}. Valid range is 0 to {bundleCount - 1}."); + + referrerCounts[dependIndex]++; + } + } + + // 2. 创建列表 + for (int index = 0; index < bundleCount; index++) + { + int capacity = referrerCounts[index]; + manifest.BundleList[index].ReferrerBundleIDs = new List(capacity); + } + + // 3. 填充数据 + for (int index = 0; index < bundleCount; index++) + { + foreach (int dependIndex in manifest.BundleList[index].DependentBundleIDs) + { + var dependBundle = manifest.BundleList[dependIndex]; +#if UNITY_EDITOR || DEBUG + if (dependBundle.ReferrerBundleIDs.Contains(index)) + throw new YooManifestInvalidException($"Duplicate referrer bundle ID detected: referrer {index} -> bundle {dependIndex}."); +#endif + dependBundle.ReferrerBundleIDs.Add(index); + } + } + } } } \ No newline at end of file diff --git a/Assets/YooAsset/Runtime/ResourcePackage/Operations/Internal/SerializeManifestOperation.cs b/Assets/YooAsset/Runtime/ResourcePackage/Operations/Internal/SerializeManifestOperation.cs new file mode 100644 index 00000000..2d276ec7 --- /dev/null +++ b/Assets/YooAsset/Runtime/ResourcePackage/Operations/Internal/SerializeManifestOperation.cs @@ -0,0 +1,153 @@ +namespace YooAsset +{ + /// + /// 序列化清单文件操作 + /// + internal class SerializeManifestOperation : AsyncOperationBase + { + private enum ESteps + { + None, + SerializeFileHeader, + SerializeAssetList, + SerializeBundleList, + EncryptManifest, + Done, + } + + private readonly BuildManifest _manifest; + private readonly IManifestEncryptor _encryptor; + private BufferWriter _buffer; + private ESteps _steps = ESteps.None; + + /// + /// 序列化后的二进制数据 + /// + public byte[] FileData { get; private set; } + + /// + /// 创建序列化清单文件操作实例 + /// + /// 清单对象 + /// 清单加密器,为null时不加密 + public SerializeManifestOperation(BuildManifest manifest, IManifestEncryptor encryptor) + { + _manifest = manifest; + _encryptor = encryptor; + } + protected override void InternalStart() + { + _steps = ESteps.SerializeFileHeader; + } + protected override void InternalUpdate() + { + if (_steps == ESteps.None || _steps == ESteps.Done) + return; + + if (_steps == ESteps.SerializeFileHeader) + { + // 创建缓存器 + _buffer = new BufferWriter(PackageManifestConsts.MaxFileSize); + + // 写入文件标记 + _buffer.WriteUInt32(PackageManifestConsts.FileMagic); + + // 写入文件版本 + _buffer.WriteInt32(_manifest.FileVersion); + + // 写入文件头信息 + _buffer.WriteBoolean(_manifest.EnableAddressable); + _buffer.WriteBoolean(_manifest.SupportExtensionless); + _buffer.WriteBoolean(_manifest.LocationToLower); + _buffer.WriteBoolean(_manifest.IncludeAssetGuid); + _buffer.WriteBoolean(_manifest.ReplaceAssetPathWithAddress); + _buffer.WriteInt32(_manifest.OutputNameStyle); + _buffer.WriteInt32(_manifest.BuildBundleType); + _buffer.WriteString(_manifest.BuildPipeline); + _buffer.WriteString(_manifest.PackageName); + _buffer.WriteString(_manifest.PackageVersion); + _buffer.WriteString(_manifest.PackageNote); + + // 写入全局标签表 + _buffer.WriteStringArray(_manifest.TagTable); + + // 写入全局目录表 + _buffer.WriteStringArray(_manifest.DirectoryTable); + + _steps = ESteps.SerializeAssetList; + } + + if (_steps == ESteps.SerializeAssetList) + { + _buffer.WriteInt32(_manifest.AssetList.Count); + for (int i = 0; i < _manifest.AssetList.Count; i++) + { + var buildAsset = _manifest.AssetList[i]; + + // Address + if (_manifest.EnableAddressable) + _buffer.WriteString(buildAsset.Address); + + // AssetPath + BuildManifest.SplitAssetPath(buildAsset.AssetPath, out string directory, out string fileName); + ushort directoryIndex = _manifest.ConvertDirectoryToIndex(directory); + _buffer.WriteUInt16(directoryIndex); + _buffer.WriteString(fileName); + + // AssetGuid + if (_manifest.IncludeAssetGuid) + _buffer.WriteHash16(buildAsset.AssetGuid); + + // Tags + ushort[] tagIndices = _manifest.ConvertTagsToIndices(buildAsset.Tags); + _buffer.WriteUInt16Array(tagIndices); + + _buffer.WriteInt32(buildAsset.BundleID); + _buffer.WriteInt32Array(buildAsset.DependentBundleIDs); + } + + _steps = ESteps.SerializeBundleList; + } + + if (_steps == ESteps.SerializeBundleList) + { + _buffer.WriteInt32(_manifest.BundleList.Count); + for (int i = 0; i < _manifest.BundleList.Count; i++) + { + var buildBundle = _manifest.BundleList[i]; + ushort[] tagIndices = _manifest.ConvertTagsToIndices(buildBundle.Tags); + _buffer.WriteString(buildBundle.BundleName); + _buffer.WriteUInt32(buildBundle.UnityCrc); + _buffer.WriteHash16(buildBundle.FileHash); + _buffer.WriteUInt32(buildBundle.FileCrc); + _buffer.WriteInt64(buildBundle.FileSize); + _buffer.WriteBoolean(buildBundle.IsEncrypted); + _buffer.WriteUInt16Array(tagIndices); + _buffer.WriteInt32Array(buildBundle.DependentBundleIDs); + } + + _steps = ESteps.EncryptManifest; + } + + if (_steps == ESteps.EncryptManifest) + { + if (_encryptor != null) + { + var tempBytes = _buffer.ToArray(); + FileData = _encryptor.Encrypt(tempBytes); + } + else + { + FileData = _buffer.ToArray(); + } + + _steps = ESteps.Done; + SetResult(); + } + } + protected override void InternalWaitForCompletion() + { + ExecuteBatch(); + } + } +} diff --git a/Assets/YooAsset/Runtime/ResourcePackage/Operations/Internal/SerializeManifestOperation.cs.meta b/Assets/YooAsset/Runtime/ResourcePackage/Operations/Internal/SerializeManifestOperation.cs.meta new file mode 100644 index 00000000..38f050e8 --- /dev/null +++ b/Assets/YooAsset/Runtime/ResourcePackage/Operations/Internal/SerializeManifestOperation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dd920db2febc59c48a34b2899bd5c973 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Runtime/ResourcePackage/PackageAsset.cs b/Assets/YooAsset/Runtime/ResourcePackage/PackageAsset.cs index 8c068543..892fa492 100644 --- a/Assets/YooAsset/Runtime/ResourcePackage/PackageAsset.cs +++ b/Assets/YooAsset/Runtime/ResourcePackage/PackageAsset.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; namespace YooAsset { @@ -24,11 +23,6 @@ namespace YooAsset /// public string AssetGuid; - /// - /// 资源的分类标签 - /// - public string[] AssetTags; - /// /// 所属资源包ID /// @@ -41,29 +35,9 @@ namespace YooAsset public int[] DependentBundleIDs; /// - /// 临时数据对象(仅编辑器有效) + /// 资源的分类标签 /// [NonSerialized] - public object EditorUserData; - - /// - /// 是否包含指定的标签 - /// - /// 要检查的标签数组 - /// 如果包含任意一个标签返回true,否则返回false。 - public bool HasAnyTag(string[] tags) - { - if (tags == null || tags.Length == 0) - return false; - if (AssetTags == null || AssetTags.Length == 0) - return false; - - foreach (var tag in tags) - { - if (AssetTags.Contains(tag)) - return true; - } - return false; - } + public PackageTags Tags; } } \ No newline at end of file diff --git a/Assets/YooAsset/Runtime/ResourcePackage/PackageBundle.cs b/Assets/YooAsset/Runtime/ResourcePackage/PackageBundle.cs index 5aa9b613..e307543a 100644 --- a/Assets/YooAsset/Runtime/ResourcePackage/PackageBundle.cs +++ b/Assets/YooAsset/Runtime/ResourcePackage/PackageBundle.cs @@ -39,11 +39,6 @@ namespace YooAsset /// public bool IsEncrypted; - /// - /// 资源包的分类标签 - /// - public string[] Tags; - /// /// 依赖的资源包ID集合 /// 注意:引擎层构建查询结果 @@ -59,28 +54,28 @@ namespace YooAsset get { return FileHash; } } + /// + /// 资源包的分类标签 + /// + [NonSerialized] + public PackageTags Tags; + /// /// 包含的主资源集合 /// [NonSerialized] - public readonly List MainAssets = new List(10); + public List MainAssets; /// - /// 引用该资源包的资源包列表 + /// 引用该资源包的资源包ID列表 /// 说明:谁引用了该资源包 /// [NonSerialized] - public readonly List ReferrerBundleIDs = new List(10); - [NonSerialized] - private readonly HashSet _referrerBundleIDs = new HashSet(); + public List ReferrerBundleIDs; [NonSerialized] private PackageManifest _manifest; [NonSerialized] - private bool _isInitialized; - [NonSerialized] - private int _bundleType; - [NonSerialized] private string _fileName; @@ -94,14 +89,10 @@ namespace YooAsset /// /// 初始化资源包 /// - /// 所属的资源清单 public void Initialize(PackageManifest manifest) { - _isInitialized = true; _manifest = manifest; - _bundleType = manifest.BuildBundleType; - string fileExtension = PackageManifestHelper.GetRemoteBundleFileExtension(BundleName); - _fileName = PackageManifestHelper.GetRemoteBundleFileName(manifest.OutputNameStyle, BundleName, fileExtension, FileHash); + _fileName = BundleFileNaming.GetBundleFileName(manifest.OutputNameStyle, BundleName, FileHash); } /// @@ -110,9 +101,9 @@ namespace YooAsset /// 返回资源包类型 public int GetBundleType() { - if (_isInitialized == false) + if (_manifest == null) throw new YooInternalException("PackageBundle is not initialized."); - return _bundleType; + return _manifest.BuildBundleType; } /// @@ -121,68 +112,11 @@ namespace YooAsset /// 返回资源包文件名称 public string GetFileName() { - if (_isInitialized == false) + if (_manifest == null) throw new YooInternalException("PackageBundle is not initialized."); return _fileName; } - /// - /// 添加引用该资源包的资源包ID - /// - /// 引用该资源包的资源包ID - /// 记录谁引用了该资源包 - public void AddReferrerBundleID(int bundleID) - { - if (_referrerBundleIDs.Contains(bundleID) == false) - { - _referrerBundleIDs.Add(bundleID); - ReferrerBundleIDs.Add(bundleID); - } - } - - /// - /// 是否包含指定的单个标签 - /// - public bool HasTag(string tag) - { - if (Tags == null || Tags.Length == 0) - return false; - - for (int i = 0; i < Tags.Length; i++) - { - if (Tags[i] == tag) - return true; - } - return false; - } - - /// - /// 是否包含指定标签数组中的任意一个 - /// - public bool HasAnyTag(string[] tags) - { - if (tags == null || tags.Length == 0) - return false; - if (Tags == null || Tags.Length == 0) - return false; - - for (int i = 0; i < tags.Length; i++) - { - if (HasTag(tags[i])) - return true; - } - return false; - } - - /// - /// 是否包含分类标签 - /// - /// 如果包含至少一个标签返回true,否则返回false。 - public bool IsTagged() - { - return Tags != null && Tags.Length > 0; - } - #region 调试信息 [NonSerialized] private List _debugReferrerBundleNames; diff --git a/Assets/YooAsset/Runtime/ResourcePackage/PackageDetails.cs b/Assets/YooAsset/Runtime/ResourcePackage/PackageDetails.cs index 98ef8071..fee498ea 100644 --- a/Assets/YooAsset/Runtime/ResourcePackage/PackageDetails.cs +++ b/Assets/YooAsset/Runtime/ResourcePackage/PackageDetails.cs @@ -2,78 +2,150 @@ namespace YooAsset { /// - /// 资源包裹的详细信息,用于外部查询包裹配置。 + /// 资源包裹的详细信息 /// public class PackageDetails { + private readonly PackageManifest _manifest; + /// /// 文件版本 /// - public int FileVersion { get; internal set; } + public int FileVersion + { + get { return _manifest.FileVersion; } + } /// /// 启用可寻址资源定位 /// - public bool EnableAddressable { get; internal set; } + public bool EnableAddressable + { + get { return _manifest.EnableAddressable; } + } /// /// 支持无后缀名的资源定位地址 /// - public bool SupportExtensionless { get; internal set; } + public bool SupportExtensionless + { + get { return _manifest.SupportExtensionless; } + } /// /// 资源定位地址大小写不敏感 /// - public bool LocationToLower { get; internal set; } + public bool LocationToLower + { + get { return _manifest.LocationToLower; } + } /// /// 包含资源GUID数据 /// - public bool IncludeAssetGuid { get; internal set; } + public bool IncludeAssetGuid + { + get { return _manifest.IncludeAssetGuid; } + } /// /// 使用可寻址地址代替资源路径 /// - public bool ReplaceAssetPathWithAddress { get; internal set; } + public bool ReplaceAssetPathWithAddress + { + get { return _manifest.ReplaceAssetPathWithAddress; } + } /// /// 文件名称样式 /// - public int OutputNameStyle { get; internal set; } + public int OutputNameStyle + { + get { return _manifest.OutputNameStyle; } + } /// /// 构建资源包类型 /// - public int BuildBundleType { get; internal set; } + public int BuildBundleType + { + get { return _manifest.BuildBundleType; } + } /// /// 构建管线名称 /// - public string BuildPipeline { get; internal set; } + public string BuildPipeline + { + get { return _manifest.BuildPipeline; } + } /// /// 资源包裹名称 /// - public string PackageName { get; internal set; } + public string PackageName + { + get { return _manifest.PackageName; } + } /// /// 资源包裹的版本信息 /// - public string PackageVersion { get; internal set; } + public string PackageVersion + { + get { return _manifest.PackageVersion; } + } /// /// 资源包裹的备注信息 /// - public string PackageNote { get; internal set; } + public string PackageNote + { + get { return _manifest.PackageNote; } + } /// /// 主资源文件总数 /// - public int AssetTotalCount { get; internal set; } + public int AssetTotalCount + { + get { return _manifest.AssetList.Count; } + } /// /// 资源包文件总数 /// - public int BundleTotalCount { get; internal set; } + public int BundleTotalCount + { + get { return _manifest.BundleList.Count; } + } + + internal PackageDetails(PackageManifest manifest) + { + _manifest = manifest; + } + + /// + /// 返回包裹详细信息的字符串描述 + /// + public override string ToString() + { + var sb = new System.Text.StringBuilder(); + sb.AppendLine($"FileVersion : {FileVersion}"); + sb.AppendLine($"PackageName : {PackageName}"); + sb.AppendLine($"PackageVersion : {PackageVersion}"); + sb.AppendLine($"PackageNote : {PackageNote}"); + sb.AppendLine($"BuildPipeline : {BuildPipeline}"); + sb.AppendLine($"BuildBundleType : {BuildBundleType}"); + sb.AppendLine($"OutputNameStyle : {OutputNameStyle}"); + sb.AppendLine($"EnableAddressable : {EnableAddressable}"); + sb.AppendLine($"SupportExtensionless : {SupportExtensionless}"); + sb.AppendLine($"LocationToLower : {LocationToLower}"); + sb.AppendLine($"IncludeAssetGuid : {IncludeAssetGuid}"); + sb.AppendLine($"ReplaceAssetPathWithAddress : {ReplaceAssetPathWithAddress}"); + sb.AppendLine($"AssetTotalCount : {AssetTotalCount}"); + sb.AppendLine($"BundleTotalCount : {BundleTotalCount}"); + return sb.ToString(); + } } -} \ No newline at end of file +} diff --git a/Assets/YooAsset/Runtime/ResourcePackage/PackageManifest.cs b/Assets/YooAsset/Runtime/ResourcePackage/PackageManifest.cs index 1cfa774c..dbb993e2 100644 --- a/Assets/YooAsset/Runtime/ResourcePackage/PackageManifest.cs +++ b/Assets/YooAsset/Runtime/ResourcePackage/PackageManifest.cs @@ -82,22 +82,16 @@ namespace YooAsset public List BundleList = new List(); /// - /// 资源映射集合(提供AssetPath获取PackageAsset) + /// 资源映射集合(提供Location获取PackageAsset) /// [NonSerialized] - public Dictionary AssetDictionary; + public Dictionary AssetsByLocation; /// - /// 资源路径映射集合(提供Location获取AssetPath) + /// 资源映射集合(提供AssetGUID获取PackageAsset) /// [NonSerialized] - public Dictionary AssetPathsByLocation; - - /// - /// 资源路径映射集合(提供AssetGUID获取AssetPath) - /// - [NonSerialized] - public Dictionary AssetPathsByGuid; + public Dictionary AssetsByGuid; /// /// 资源包集合(提供BundleName获取PackageBundle) @@ -118,67 +112,13 @@ namespace YooAsset public Dictionary BundlesByGuid; - /// - /// 初始化资源清单 - /// - public void Initialize() - { - // 填充资源包内包含的主资源列表 - foreach (var packageAsset in AssetList) - { - int bundleID = packageAsset.BundleID; - if (bundleID >= 0 && bundleID < BundleList.Count) - { - var packageBundle = BundleList[bundleID]; - packageBundle.MainAssets.Add(packageAsset); - } - else - { - throw new System.ArgumentOutOfRangeException($"Invalid bundle ID: {bundleID}. Valid range is 0 to {BundleList.Count - 1}."); - } - } - - // 填充资源包引用关系 - for (int index = 0; index < BundleList.Count; index++) - { - var sourceBundle = BundleList[index]; - foreach (int dependIndex in sourceBundle.DependentBundleIDs) - { - if (dependIndex >= 0 && dependIndex < BundleList.Count) - { - var dependBundle = BundleList[dependIndex]; - dependBundle.AddReferrerBundleID(index); - } - else - { - throw new System.ArgumentOutOfRangeException($"Invalid dependent bundle index: {dependIndex}. Valid range is 0 to {BundleList.Count - 1}."); - } - } - } - } - /// /// 获取包裹的详细信息 /// /// 返回包含包裹配置信息的详细信息对象 public PackageDetails GetPackageDetails() { - PackageDetails details = new PackageDetails(); - details.FileVersion = FileVersion; - details.EnableAddressable = EnableAddressable; - details.SupportExtensionless = SupportExtensionless; - details.LocationToLower = LocationToLower; - details.IncludeAssetGuid = IncludeAssetGuid; - details.ReplaceAssetPathWithAddress = ReplaceAssetPathWithAddress; - details.OutputNameStyle = OutputNameStyle; - details.BuildBundleType = BuildBundleType; - details.BuildPipeline = BuildPipeline; - details.PackageName = PackageName; - details.PackageVersion = PackageVersion; - details.PackageNote = PackageNote; - details.AssetTotalCount = AssetList.Count; - details.BundleTotalCount = BundleList.Count; - return details; + return new PackageDetails(this); } /// @@ -191,8 +131,8 @@ namespace YooAsset if (string.IsNullOrEmpty(location)) return string.Empty; - if (AssetPathsByLocation.TryGetValue(location, out string assetPath)) - return assetPath; + if (AssetsByLocation.TryGetValue(location, out PackageAsset packageAsset)) + return packageAsset.AssetPath; else return string.Empty; } @@ -271,7 +211,7 @@ namespace YooAsset /// 如果找到返回true,否则返回false。 public bool TryGetPackageAsset(string assetPath, out PackageAsset result) { - return AssetDictionary.TryGetValue(assetPath, out result); + return AssetsByLocation.TryGetValue(assetPath, out result); } /// @@ -343,7 +283,7 @@ namespace YooAsset List result = new List(AssetList.Count); foreach (var packageAsset in AssetList) { - if (packageAsset.HasAnyTag(tags)) + if (packageAsset.Tags.HasAnyTag(tags)) { AssetInfo assetInfo = new AssetInfo(PackageName, packageAsset, null); result.Add(assetInfo); @@ -362,39 +302,20 @@ namespace YooAsset { DebugCheckLocation(location); - string assetPath = ResolveLocationToAssetPath(location); - if (TryGetPackageAsset(assetPath, out PackageAsset packageAsset)) - { - AssetInfo assetInfo = new AssetInfo(PackageName, packageAsset, assetType); - return assetInfo; - } - else - { - string error; - if (string.IsNullOrEmpty(location)) - error = "Location is null or empty."; - else - error = $"Location is invalid: '{location}'."; - AssetInfo assetInfo = new AssetInfo(PackageName, error); - return assetInfo; - } - } - private string ResolveLocationToAssetPath(string location) - { if (string.IsNullOrEmpty(location)) { YooLogger.LogError("Failed to map location to asset path. Location is null or empty."); - return string.Empty; + return new AssetInfo(PackageName, "Location is null or empty."); } - if (AssetPathsByLocation.TryGetValue(location, out string assetPath)) + if (AssetsByLocation.TryGetValue(location, out PackageAsset packageAsset)) { - return assetPath; + return new AssetInfo(PackageName, packageAsset, assetType); } else { YooLogger.LogWarning($"Failed to map location to asset path: '{location}'."); - return string.Empty; + return new AssetInfo(PackageName, $"Location is invalid: '{location}'."); } } @@ -409,43 +330,23 @@ namespace YooAsset if (IncludeAssetGuid == false) { YooLogger.LogWarning("Package manifest does not include asset GUID. Please check asset bundle collector settings."); - AssetInfo assetInfo = new AssetInfo(PackageName, "AssetGuid data is empty."); - return assetInfo; + return new AssetInfo(PackageName, "AssetGuid data is empty."); } - string assetPath = ResolveGuidToAssetPath(assetGuid); - if (TryGetPackageAsset(assetPath, out PackageAsset packageAsset)) - { - AssetInfo assetInfo = new AssetInfo(PackageName, packageAsset, assetType); - return assetInfo; - } - else - { - string error; - if (string.IsNullOrEmpty(assetGuid)) - error = "Asset GUID is null or empty."; - else - error = $"Asset GUID is invalid: '{assetGuid}'."; - AssetInfo assetInfo = new AssetInfo(PackageName, error); - return assetInfo; - } - } - private string ResolveGuidToAssetPath(string assetGuid) - { if (string.IsNullOrEmpty(assetGuid)) { YooLogger.LogError("Failed to map asset GUID to asset path. Asset GUID is null or empty."); - return string.Empty; + return new AssetInfo(PackageName, "Asset GUID is null or empty."); } - if (AssetPathsByGuid.TryGetValue(assetGuid, out string assetPath)) + if (AssetsByGuid.TryGetValue(assetGuid, out PackageAsset packageAsset)) { - return assetPath; + return new AssetInfo(PackageName, packageAsset, assetType); } else { YooLogger.LogWarning($"Failed to map asset GUID to asset path: '{assetGuid}'."); - return string.Empty; + return new AssetInfo(PackageName, $"Asset GUID is invalid: '{assetGuid}'."); } } diff --git a/Assets/YooAsset/Runtime/ResourcePackage/PackageManifestConsts.cs b/Assets/YooAsset/Runtime/ResourcePackage/PackageManifestConsts.cs index 348d6804..568df6a7 100644 --- a/Assets/YooAsset/Runtime/ResourcePackage/PackageManifestConsts.cs +++ b/Assets/YooAsset/Runtime/ResourcePackage/PackageManifestConsts.cs @@ -19,11 +19,11 @@ namespace YooAsset /// /// 文件版本号 /// - public const int FileVersion = 1; + public const int FileVersion = 2; /// - /// 文件最小合法大小(37字节) + /// 文件最小合法大小(39字节) /// - public const int MinFileSize = 37; + public const int MinFileSize = 39; } } \ No newline at end of file diff --git a/Assets/YooAsset/Runtime/ResourcePackage/PackageManifestHelper.cs b/Assets/YooAsset/Runtime/ResourcePackage/PackageManifestHelper.cs index 93554ce4..41297704 100644 --- a/Assets/YooAsset/Runtime/ResourcePackage/PackageManifestHelper.cs +++ b/Assets/YooAsset/Runtime/ResourcePackage/PackageManifestHelper.cs @@ -39,113 +39,18 @@ namespace YooAsset return fileHash == hashValue; } - /// - /// 将清单文件序列化为JSON格式 - /// - /// 保存路径 - /// 清单对象 - public static void SerializeManifestToJson(string savePath, PackageManifest manifest) - { - string json = JsonUtility.ToJson(manifest, true); - FileUtility.WriteAllText(savePath, json); - } - /// /// 将清单文件序列化为二进制格式 /// /// 保存路径 /// 清单对象 /// 清单加密(可为null) - public static void SerializeManifestToBinary(string savePath, PackageManifest manifest, IManifestEncryptor encryptor) + public static void SerializeManifestToBinary(string savePath, BuildManifest manifest, IManifestEncryptor encryptor) { - using (FileStream fs = new FileStream(savePath, FileMode.Create)) - { - // 创建缓存器 - BufferWriter buffer = new BufferWriter(PackageManifestConsts.MaxFileSize); - - // 写入文件标记 - buffer.WriteUInt32(PackageManifestConsts.FileMagic); - - // 写入文件版本 - buffer.WriteInt32(manifest.FileVersion); - - // 写入文件头信息 - buffer.WriteBoolean(manifest.EnableAddressable); - buffer.WriteBoolean(manifest.SupportExtensionless); - buffer.WriteBoolean(manifest.LocationToLower); - buffer.WriteBoolean(manifest.IncludeAssetGuid); - buffer.WriteBoolean(manifest.ReplaceAssetPathWithAddress); - buffer.WriteInt32(manifest.OutputNameStyle); - buffer.WriteInt32(manifest.BuildBundleType); - buffer.WriteString(manifest.BuildPipeline); - buffer.WriteString(manifest.PackageName); - buffer.WriteString(manifest.PackageVersion); - buffer.WriteString(manifest.PackageNote); - - // 写入资源列表 - buffer.WriteInt32(manifest.AssetList.Count); - for (int i = 0; i < manifest.AssetList.Count; i++) - { - var packageAsset = manifest.AssetList[i]; - buffer.WriteString(packageAsset.Address); - buffer.WriteString(packageAsset.AssetPath); - buffer.WriteString(packageAsset.AssetGuid); - buffer.WriteStringArray(packageAsset.AssetTags); - buffer.WriteInt32(packageAsset.BundleID); - buffer.WriteInt32Array(packageAsset.DependentBundleIDs); - } - - // 写入资源包列表 - buffer.WriteInt32(manifest.BundleList.Count); - for (int i = 0; i < manifest.BundleList.Count; i++) - { - var packageBundle = manifest.BundleList[i]; - buffer.WriteString(packageBundle.BundleName); - buffer.WriteUInt32(packageBundle.UnityCrc); - buffer.WriteString(packageBundle.FileHash); - buffer.WriteUInt32(packageBundle.FileCrc); - buffer.WriteInt64(packageBundle.FileSize); - buffer.WriteBoolean(packageBundle.IsEncrypted); - buffer.WriteStringArray(packageBundle.Tags); - buffer.WriteInt32Array(packageBundle.DependentBundleIDs); - } - - // 清单处理操作 - if (encryptor != null) - { - var tempBytes = buffer.ToArray(); - var resultBytes = encryptor.Encrypt(tempBytes); - fs.Write(resultBytes, 0, resultBytes.Length); - fs.Flush(); - } - else - { - // 写入文件流 - buffer.WriteToStream(fs); - fs.Flush(); - } - } - } - - /// - /// 从JSON字符串反序列化清单文件 - /// - /// JSON内容字符串 - /// 返回反序列化后的清单对象 - public static PackageManifest DeserializeManifestFromJson(string jsonContent) - { - var manifest = JsonUtility.FromJson(jsonContent); - - // 初始化资源包 - for (int i = 0; i < manifest.BundleList.Count; i++) - { - var packageBundle = manifest.BundleList[i]; - packageBundle.Initialize(manifest); - } - - // 初始化资源清单 - manifest.Initialize(); - return manifest; + SerializeManifestOperation operation = new SerializeManifestOperation(manifest, encryptor); + operation.StartOperation(); + operation.WaitForCompletion(); + FileUtility.WriteAllBytes(savePath, operation.FileData); } /// @@ -161,51 +66,5 @@ namespace YooAsset operation.WaitForCompletion(); return operation.Manifest; } - - /// - /// 获取资源文件的后缀名 - /// - /// 资源包名称 - /// 返回文件后缀名(包含点号) - public static string GetRemoteBundleFileExtension(string bundleName) - { - string fileExtension = Path.GetExtension(bundleName); - return fileExtension; - } - - /// - /// 获取远端的资源文件名 - /// - /// 文件名称样式 - /// 资源包名称 - /// 文件后缀名 - /// 文件哈希值 - /// 返回根据命名样式生成的远端文件名 - public static string GetRemoteBundleFileName(int nameStyle, string bundleName, string fileExtension, string fileHash) - { - EFileNameStyle style = (EFileNameStyle)nameStyle; - switch (style) - { - case EFileNameStyle.HashName: - return StringUtility.Format("{0}{1}", fileHash, fileExtension); - - case EFileNameStyle.BundleName: - return bundleName; - - case EFileNameStyle.BundleName_HashName: - if (string.IsNullOrEmpty(fileExtension)) - { - return StringUtility.Format("{0}_{1}", bundleName, fileHash); - } - else - { - string fileName = bundleName.Remove(bundleName.LastIndexOf('.')); - return StringUtility.Format("{0}_{1}{2}", fileName, fileHash, fileExtension); - } - - default: - throw new NotImplementedException($"Invalid name style: {nameStyle}."); - } - } } } \ No newline at end of file diff --git a/Assets/YooAsset/Runtime/ResourcePackage/PackageTags.cs b/Assets/YooAsset/Runtime/ResourcePackage/PackageTags.cs new file mode 100644 index 00000000..7c8390cf --- /dev/null +++ b/Assets/YooAsset/Runtime/ResourcePackage/PackageTags.cs @@ -0,0 +1,86 @@ +using System; + +namespace YooAsset +{ + /// + /// 标签集合 + /// + public class PackageTags + { + private readonly string[] _tags; + + /// + /// 解析后的完整标签字符串数组 + /// + internal string[] RawTags + { + get { return _tags; } + } + + /// + /// 标签数量 + /// + public int TagCount + { + get { return _tags.Length; } + } + + /// + /// 是否包含分类标签 + /// + public bool IsTagged + { + get { return _tags.Length > 0; } + } + + + /// + /// 创建标签集合实例 + /// + /// 解析后的标签字符串数组 + public PackageTags(string[] tags) + { + if (tags == null) + _tags = Array.Empty(); + else + _tags = tags; + } + + /// + /// 获取指定索引的标签 + /// + public string GetTag(int index) + { + return _tags[index]; + } + + /// + /// 是否包含指定的单个标签 + /// + public bool HasTag(string tag) + { + for (int i = 0; i < _tags.Length; i++) + { + if (_tags[i] == tag) + return true; + } + return false; + } + + /// + /// 是否包含指定标签数组中的任意一个 + /// + public bool HasAnyTag(string[] tags) + { + if (tags == null || tags.Length == 0 || _tags.Length == 0) + return false; + + for (int i = 0; i < tags.Length; i++) + { + if (HasTag(tags[i])) + return true; + } + return false; + } + } +} diff --git a/Assets/YooAsset/Runtime/ResourcePackage/PackageTags.cs.meta b/Assets/YooAsset/Runtime/ResourcePackage/PackageTags.cs.meta new file mode 100644 index 00000000..3c21fcdf --- /dev/null +++ b/Assets/YooAsset/Runtime/ResourcePackage/PackageTags.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8d3c7e71f91758244916b4f9d00c24a3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Runtime/Utility/BufferReader.cs b/Assets/YooAsset/Runtime/Utility/BufferReader.cs index 3ee587aa..17ceac2d 100644 --- a/Assets/YooAsset/Runtime/Utility/BufferReader.cs +++ b/Assets/YooAsset/Runtime/Utility/BufferReader.cs @@ -11,6 +11,7 @@ namespace YooAsset /// internal class BufferReader { + private readonly char[] _hashChars = new char[32]; private readonly byte[] _buffer; private int _position = 0; @@ -163,6 +164,38 @@ namespace YooAsset return value; } + /// + /// 读取哈希值(16字节数据转换为32位哈希值) + /// + public string ReadHash16() + { + EnsureCapacity(16); + for (int i = 0; i < 16; i++) + { + byte b = _buffer[_position++]; + _hashChars[i * 2] = GetHexChar(b >> 4); + _hashChars[i * 2 + 1] = GetHexChar(b & 0xF); + } + return new string(_hashChars); + } + + /// + /// 读取16位无符号整数数组 + /// + public ushort[] ReadUInt16Array() + { + ushort count = ReadUInt16(); + if (count == 0) + return Array.Empty(); + + ushort[] values = new ushort[count]; + for (int i = 0; i < count; i++) + { + values[i] = ReadUInt16(); + } + return values; + } + /// /// 读取32位有符号整数数组 /// @@ -222,5 +255,11 @@ namespace YooAsset throw new InvalidOperationException("Insufficient buffer capacity."); } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static char GetHexChar(int value) + { + return (char)(value < 10 ? '0' + value : 'a' + value - 10); + } } } \ No newline at end of file diff --git a/Assets/YooAsset/Runtime/Utility/BufferWriter.cs b/Assets/YooAsset/Runtime/Utility/BufferWriter.cs index b70e7509..eadc139b 100644 --- a/Assets/YooAsset/Runtime/Utility/BufferWriter.cs +++ b/Assets/YooAsset/Runtime/Utility/BufferWriter.cs @@ -178,6 +178,47 @@ namespace YooAsset } } + /// + /// 写入哈希值(32位哈希值转换为16字节数据) + /// + public void WriteHash16(string hashString) + { + if (hashString == null) + throw new ArgumentNullException(nameof(hashString)); + +#if UNITY_EDITOR || DEBUG + if (hashString.Length != 32) + throw new InvalidOperationException($"Hash string length must be 32, but got {hashString.Length}: '{hashString}'."); +#endif + + EnsureCapacity(16); + for (int i = 0; i < 16; i++) + { + int high = GetHexValue(hashString[i * 2]); + int low = GetHexValue(hashString[i * 2 + 1]); + _buffer[_position++] = (byte)((high << 4) | low); + } + } + + /// + /// 写入16位无符号整数数组 + /// + public void WriteUInt16Array(ushort[] values) + { + if (values == null) + throw new ArgumentNullException(nameof(values)); + + int count = values.Length; + if (count > ushort.MaxValue) + throw new OverflowException($"Array length exceeds the maximum value of {ushort.MaxValue}."); + + WriteUInt16(Convert.ToUInt16(count)); + for (int i = 0; i < count; i++) + { + WriteUInt16(values[i]); + } + } + /// /// 写入32位有符号整数数组 /// @@ -243,5 +284,16 @@ namespace YooAsset throw new InvalidOperationException("Insufficient buffer capacity."); } } + + private static int GetHexValue(char c) + { + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + throw new FormatException($"Invalid hex character: '{c}'."); + } } } \ No newline at end of file