diff --git a/.gitignore b/.gitignore index d4f02de3..6eb7ea37 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,9 @@ /Bundles/ /ProjectSettings/ /App/ -/yoo/ +/yoo/ +/Assets/Docs +/Assets/Docs.meta /Assets/StreamingAssets /Assets/StreamingAssets.meta /Assets/Samples diff --git a/Assets/YooAsset/CHANGELOG.md b/Assets/YooAsset/CHANGELOG.md index e10db8fb..90dd9a42 100644 --- a/Assets/YooAsset/CHANGELOG.md +++ b/Assets/YooAsset/CHANGELOG.md @@ -2,1359 +2,6 @@ All notable changes to this package will be documented in this file. -## [2.3.18] - 2025-12-04 - -### Fixed - -- (#676) 修复了UniTask扩展包的编译报错。 -- (#684) 修复了资源配置窗口Group列表数量过多的时候,添加和删除按钮会变小的问题。 -- (#700) [**严重**] 修复了小游戏扩展库的下载器再失败后重试逻辑不起效的问题。 - -### Added - -- (#683) 新增了内置文件系统类初始化参数:UNPACK_FILE_SYSTEM_ROOT - - ```csharp - class FileSystemParametersDefine - { - // 指定解压文件的根目录 - public const string UNPACK_FILE_SYSTEM_ROOT = "UNPACK_FILE_SYSTEM_ROOT"; - } - ``` - -- (#682) 原生文件构建管线新增构建参数:IncludePathInHash - - ```csharp - class RawFileBuildParameters : BuildParameters - { - /// - /// 文件哈希值计算包含路径信息 - /// - public bool IncludePathInHash = false; - } - ``` - -- (#671) 新增扩展工具,可以生成空的包裹内置资源目录文件。 - - ```csharp - public class CreateEmptyCatalogWindow : EditorWindow - ``` - -- (#694) 新增资源清理方式:ClearBundleFilesByLocations - - ```csharp - public enum EFileClearMode - { - /// - /// 清理指定地址的文件 - /// 说明:需要指定参数,可选:string, string[], List - /// - ClearBundleFilesByLocations, - } - ``` - -## [2.3.17] - 2025-10-30 - -**非常重要**:修复了#627优化导致的资源清单CRC值为空的问题。 - -该问题会导致下载的损坏文件验证通过。 - -影响范围:v2.3.15版本,v2.3.16版本。 - -**非常重要**:(#661) 修复了Package销毁过程中,遇到正在加载的AssetBundle会导致无法卸载的问题。 - -该问题是偶现,引擎会提示AssetBundle已经加载,无法加载新的文件,导致资源对象加载失败! - -影响范围:所有版本! - -### Fixed - -- (#645) 修复了着色器变种收集工具,在极端情况下变种收集不完整的问题。 -- (#646) 修复了EditorSimulateMode模式下开启模拟下载tag不生效的问题。 -- (#667) 修复了所有编辑器窗口针对中文IME的输入问题。 -- (#670) 修复了Catalog文件生成过程中白名单未考虑自定义清单前缀名。 - -### Improvements - -- 重构并统一了资源清单的反序列化逻辑。 -- (#650) 解决互相依赖的资源包无法卸载的问题。需要开启宏定义:YOOASSET_EXPERIMENTAL -- (#655) 优化了初始化的时候,缓存文件搜索效率。安卓平台性能提升1倍,IOS平台性能提升3倍。 - -### Added - -- (#643) 新增构建参数,可以节省资源清单运行时内存 - - ```csharp - class ScriptableBuildParameters - { - /// - /// 使用可寻址地址代替资源路径 - /// 说明:开启此项可以节省运行时清单占用的内存! - /// - public bool ReplaceAssetPathWithAddress = false; - } - ``` - -- (#648) 新增初始化参数,可以自动释放引用计数为零的资源包 - - ```csharp - class InitializeParameters - { - /// - /// 当资源引用计数为零的时候自动释放资源包 - /// - public bool AutoUnloadBundleWhenUnused = false; - } - ``` - -### Changed - -- 程序集宏定义代码转移到扩展工程。参考MacroSupport文件夹。 - -## [2.3.16] - 2025-09-17 - -### Improvements - -- (#638) 优化了Provider加载机制,引用计数为零时自动挂起! - -### Fixed - -- (#644) [**严重**] 修复了2.3.15版本,资产量巨大的情况下,编辑器下模拟模式初始化耗时很久的问题。 - -### Added - -- (#639) 新增了文件系统参数:VIRTUAL_DOWNLOAD_MODE 和 VIRTUAL_DOWNLOAD_SPEED - - 编辑器下不需要构建AB,也可以模拟远端资源下载,等同真机运行环境。 - - ```csharp - class DefaultEditorFIleSystem - { - /// - /// 模拟虚拟下载模式 - /// - public bool VirtualDownloadMode { private set; get; } = false; - - /// - /// 模拟虚拟下载的网速(单位:字节) - /// - public int VirtualDownloadSpeed { private set; get; } = 1024; - } - ``` - -- (#640) 新增了文件系统参数:VIRTUAL_WEBGL_MODE - - 编辑器下不需要构建AB,也可以模拟小游戏开发环境,等同真机运行环境。 - - ```csharp - class DefaultEditorFIleSystem - { - /// - /// 模拟WebGL平台模式 - /// - public bool VirtualWebGLMode { private set; get; } = false; - } - ``` - -- (#642) 新增了文件系统参数:DOWNLOAD_WATCH_DOG_TIME - - 监控时间范围内,如果没有接收到任何下载数据,那么直接终止任务! - - ```csharp - class DefaultCacheFIleSystem - { - /// - /// 自定义参数:下载任务的看门狗机制监控时间 - /// - public int DownloadWatchDogTime { private set; get; } = int.MaxValue; - } - ``` - -### Changed - -- 下载器参数timeout移除。 - - 可以使用文件系统的看门狗机制代替。 - -- (#632) IFilterRule接口变动。 - - 收集器可以指定搜寻的资源类型,在收集目录资产量巨大的情况下,可以极大加快打包速度! - - ```csharp - public interface IFilterRule - { - /// - /// 搜寻的资源类型 - /// 说明:使用引擎方法搜索获取所有资源列表 - /// - string FindAssetType { get; } - } - ``` - - - -## [2.3.15] - 2025-09-09 - -**重要**:升级了资源清单版本,不兼容老版本。建议重新提审安装包。 - -### Improvements - -- 重构了UniTask扩展库的目录结构和说明文档。 -- 重构了内置文件系统类的加载和拷贝逻辑,解决在一些特殊机型上遇到的偶发性拷贝失败问题。 -- 增加了生成内置清单文件的窗口工具,详情见扩展工程里CreateBuildinCatalog目录。 -- 优化了异步操作系统的繁忙检测机制。 -- (#621) 资源配置页面可以展示DependCollector和StaticCollector包含的文件列表内容。 -- (#627) 优化了资源清单部分字段类型,CRC字段从字符串类型调整为整形,可以降低清单尺寸。 - -### Fixed - -- 修复了构建页面扩展类缺少指定属性报错的问题。 -- (#611) 修复了资源扫描器配置页面,修改备注信息后会丢失焦点的问题。 -- (#622) 修复了纯鸿蒙系统读取内置加密文件失败的问题。 -- (#620) 修复了LINUX系统URL地址转换失败的问题。 -- (#631) 修复了NET 4.x程序集库Math.Clamp导致的编译错误。 - -### Added - -- 新增了支持支付宝小游戏的文件系统扩展类。 - -- 新增了支持Taptap小游戏的文件系统扩展类。 - -- 新增了资源系统初始化参数:UseWeakReferenceHandle - - 目前处于预览版,可以在引擎设置页面开启宏:YOOASSET_EXPERIMENTAL - - ```csharp - /// - /// 启用弱引用资源句柄 - /// - public bool UseWeakReferenceHandle = false; - ``` - -- 内置文件系统和缓存文件系统新增初始化参数:FILE_VERIFY_MAX_CONCURRENCY - - ```csharp - /// - /// 自定义参数:初始化的时候缓存文件校验最大并发数 - /// - public int FileVerifyMaxConcurrency { private set; get; } - ``` - -- (#623) 内置构建管线新增构建参数:StripUnityVersion - - ```csharp - /// - /// 从文件头里剥离Unity版本信息 - /// - public bool StripUnityVersion = false; - ``` - -- 可编程构建管线新增构建参数:TrackSpriteAtlasDependencies - - ```csharp - /// - /// 自动建立资源对象对图集的依赖关系 - /// - public bool TrackSpriteAtlasDependencies = false; - ``` - -- (#617) 新增资源收集配置参数:SupportExtensionless - - 在不需要模糊加载模式的前提下,关闭此选项,可以降低运行时内存大小。 - - 该选项默认开启! - - ```csharp - public class CollectCommand - { - /// - /// 支持无后缀名的资源定位地址 - /// - public bool SupportExtensionless { set; get; } - } - ``` - -- (#625) 异步操作系统类新增监听方法。 - - ```csharp - class OperationSystem - { - /// - /// 监听任务开始 - /// - public static void RegisterStartCallback(Action callback); - - /// - /// 监听任务结束 - /// - public static void RegisterFinishCallback(Action callback); - } - ``` - - - -## [2.3.14] - 2025-07-23 - -**重要**:**所有下载相关的超时参数(timeout)已更新判定逻辑** - -超时不再以‘指定时间内未接收到任何数据’为判定条件,而是以‘指定时间内未完成整个下载任务’为判定条件。 - -### Improvements - -- 重构了核心代码的下载逻辑,解决了同步加载触发的下载任务没有完成的问题。 -- 扩展工程里新增了PreprocessBuildCatalog类,用于处理在构建应用程序前自动生成内置资源目录文件。 -- (#592) 优化了资源清单逻辑里不必要产生的GC逻辑。 - -### Fixed - -- (#590) 修复了TryUnloadUnusedAsset方法,在依赖嵌套层数过深导致没有卸载的问题。 - -### Added - -- 新增了支持Google Play的文件系统扩展示例。 - -- 新增了支持DefaultCacheFileSystem的单元测试用例。 - -- 新增了文件系统配置参数:DISABLE_ONDEMAND_DOWNLOAD - - ```csharp - public class FileSystemParametersDefine - { - // 禁用边玩边下机制 - public const string DISABLE_ONDEMAND_DOWNLOAD = "DISABLE_ONDEMAND_DOWNLO"; - } - ``` - -### Changed - -- IManifestServices接口拆分为了IManifestProcessServices和IManifestRestoreServices - - ```csharp - public interface IManifestProcessServices - { - /// - /// 处理资源清单(压缩或加密) - /// - byte[] ProcessManifest(byte[] fileData); - } - - public interface IManifestRestoreServices - { - /// - /// 还原资源清单(解压或解密) - /// - byte[] RestoreManifest(byte[] fileData); - } - ``` - -## [2.3.12] - 2025-07-01 - -### Improvements - -- 优化了同步接口导致的资源拷贝和资源验证性能开销高的现象。 -- 微信小游戏和抖音小游戏支持资源清单加密。 - -### Fixed - -- (#579) 修复了2.3.10版本资源包构建页面里CopyBuildinFileParam无法编辑问题。 -- (#572) 修复了资源收集页面指定收集的预制体名称变动的问题。 -- (#582) 修复了非递归收集依赖时,依赖列表中才包含主资源的问题。 - -### Added - -- 新增初始化参数:WebGLForceSyncLoadAsset - - ```csharp - public abstract class InitializeParameters - { - /// - /// WebGL平台强制同步加载资源对象 - /// Add commentMore actions - public bool WebGLForceSyncLoadAsset = false; - } - ``` - -- (#576) 新增了资源清单服务类:IManifestServices - - ```csharp - /// - /// 资源清单文件处理服务接口 - /// - public interface IManifestServices - { - /// - /// 处理资源清单(压缩和加密) - /// - byte[] ProcessManifest(byte[] fileData); - - /// - /// 还原资源清单(解压和解密) - /// - byte[] RestoreManifest(byte[] fileData); - } - ``` - -- (#585) 新增了本地文件拷贝服务类:ICopyLocalFileServices - - ```csharp - /// - /// 本地文件拷贝服务类 - /// - public interface ICopyLocalFileServices - { - void CopyFile(LocalFileInfo sourceFileInfo, string destFilePath); - } - ``` - -## [2.3.10] - 2025-06-17 - -### Improvements - -- 小游戏扩展库已经独立,可以单独导入到项目工程。 -- 编辑器里的TableView视图新增了AssetObjectCell类。 -- (#552) 微信小游戏文件系统类,增加了URL合法性的初始化检测机制。 -- (#566) 重构了资源构建页面,方便扩展自定义界面。 -- (#573) 完善了AssetDependencyDB的输出日志,可以正确输出丢失的引用资产信息。 - -### Fixed - -- 修复太空战机DEMO在退出运行模式时的报错。 -- (#551) 修复了Unity2019, Unity2020的代码兼容性报错。 -- (#569) 修复了TVOS平台的兼容问题。 -- (#564) 修复了TiktokFileSystem文件系统里appendTimeTicks无效的问题。 - -### Added - -- (#562) 新增了解密方法。 - - ```csharp - public interface IDecryptionServices - { - /// - /// 后备方式获取解密的资源包对象 - /// 注意:当正常解密方法失败后,会触发后备加载! - /// 说明:建议通过LoadFromMemory()方法加载资源对象作为保底机制。 - /// issues : https://github.com/tuyoogame/YooAsset/issues/562 - /// - DecryptResult LoadAssetBundleFallback(DecryptFileInfo fileInfo); - } - ``` - - - -## [2.3.9] - 2025-05-13 - -### Improvements - -- 增加了YOO_ASSET_EXPERIMENT宏,用于控制实验性代码的开关。 -- 构建管线目前会输出构建日志到输出目录下,方便查看引擎在构建时主动清空的控制台日志。 -- 优化了收集器tag传染扩散逻辑,避免Group里配置了Tag导致的无意义的警告信息。 -- 扩展工程内PanelMonitor代码默认关闭状态。 - -### Fixed - -- (#528) 修复了AssetDependencyDatabase在查询引擎资源对象是否存在的时效问题。 - -### Added - -- (#542) 新增了资源管理系统销毁方法。 - - 该方法会销毁所有的资源包裹和异步操作任务,以及卸载所有AssetBundle对象! - - ```csharp - public class YooAssets - { - /// - /// 销毁资源系统 - /// - public static void Destroy(); - } - ``` - -- 新增了SBP构建管线的构建参数 - - ```csharp - /// - /// 从AssetBundle文件头里剥离Unity版本信息 - /// - public bool StripUnityVersion = false; - ``` - -- 新增了构建错误码:BuiltinShadersBundleNameIsNull - -## [2.3.8] - 2025-04-17 - -### Improvements - -- 扩展工程里增加了“图集丢失变白块的解决方案”的相关代码。 - -### Fixed - -- (#528) 修复了微信小游戏平台WXFSClearUnusedBundleFiles无法清理的问题。 -- (#531) 修复了微信小游戏平台WXFSClearUnusedBundleFiles没有适配BundleName_HashName命名方式。 -- (#533) 修复了Editor程序集下无法访问YooAsset.Editor程序集里的internal字段的问题。 -- (#534) 修复了资源报告窗口AssetView视图里,依赖资源包列表显示不准确的问题。 - -## [2.3.7] - 2025-04-01 - -### Improvements - -- (#526) 运行时资源清单的哈希值验证兼容了MD5和CRC32两种方式。 -- (#515) 优化了资源路径大小写不敏感的逻辑代码,减少字符串操作产生的GC。 -- (#523) UnloadUnusedAssetsOperation方法支持了分帧处理。 - -### Fixed - -- (#520) 修复了UWP平台获取WWW加载路径未适配的问题。 - -### Added - -- 新增了文件系统初始化参数:INSTALL_CLEAR_MODE - - ```csharp - /// - /// 覆盖安装清理模式 - /// - public enum EOverwriteInstallClearMode - { - /// - /// 不做任何处理 - /// - None = 0, - - /// - /// 清理所有缓存文件(包含资源文件和清单文件) - /// - ClearAllCacheFiles = 1, - - /// - /// 清理所有缓存的资源文件 - /// - ClearAllBundleFiles = 2, - - /// - /// 清理所有缓存的清单文件 - /// - ClearAllManifestFiles = 3, - } - ``` - -- 新增了初始化参数:BundleLoadingMaxConcurrency - - ```csharp - public abstract class InitializeParameters - { - /// - /// 同时加载Bundle文件的最大并发数 - /// - public int BundleLoadingMaxConcurrency = int.MaxValue; - } - ``` - -## [2.3.6] - 2025-03-25 - -### Improvements - -- 构建管线新增了TaskCreateCatalog任务节点。 -- 内置文件系统的catalog文件现在存储在streammingAssets目录下。 - -### Fixed - -- (#486) 修复了微信小游戏文件系统调用ClearUnusedBundleFiles时候的异常。 - -## [2.3.5-preview] - 2025-03-14 - -### Fixed - -- (#502) 修复了原生缓存文件由于文件格式变动导致的加载本地缓存文件失败的问题。 -- (#504) 修复了MacOS平台Offline Play Mode模式请求本地资源清单失败的问题。 -- (#506) 修复了v2.3x版本LoadAllAssets方法计算依赖Bundle不完整的问题。 -- (#506) 修复了微信小游戏文件系统,在启用加密算法后卸载bundle报错的问题。 - -## [2.3.4-preview] - 2025-03-08 - -### Improvements - -- YooAsset支持了版本宏定义。 - - ```csharp - YOO_ASSET_2 - YOO_ASSET_2_3 - YOO_ASSET_2_3_OR_NEWER - ``` - -### Fixed - -- (#389) 修复了禁用域重载(Reload Domain)的情况下,再次启动游戏报错的问题。 -- (#496) 修复了文件系统参数RESUME_DOWNLOAD_MINMUM_SIZE传入int值会导致异常的错误。 -- (#498) 修复了v2.3版本尝试加载安卓包内的原生资源包失败的问题。 - -### Added - -- 新增了YooAssets.GetAllPackages()方法 - - ```csharp - /// - /// 获取所有资源包裹 - /// - public static List GetAllPackages() - ``` - -## [2.3.3-preview] - 2025-03-06 - -### Improvements - -- 新增了异步操作任务调试器,AssetBundleDebugger窗口-->OperationView视图模式 -- 编辑器下模拟构建默认启用依赖关系数据库,可以大幅降低编辑器下启动游戏的时间。 -- 单元测试用例增加加密解密测试用例。 - -### Fixed - -- (#492) 修复了发布的MAC平台应用,在启动的时候提示权限无法获取的问题。 - -## [2.3.2-preview] - 2025-02-27 - -### Fixed - -- (2.3.1) 修复小游戏平台下载器不生效的问题。 -- (#480) 修复了Unity工程打包导出时的报错。 - -### Added - -- 下载器新增参数:recursiveDownload - - ```csharp - /// - /// 创建资源下载器,用于下载指定的资源依赖的资源包文件 - /// - /// 下载资源对象所属资源包内所有资源对象依赖的资源包 - public ResourceDownloaderOperation CreateBundleDownloader() - ``` - -- 新增CustomPlayMode模式 - - ```csharp - /// - /// 自定义运行模式的初始化参数 - /// - public class CustomPlayModeParameters : InitializeParameters - { - /// - /// 文件系统初始化参数列表 - /// 注意:列表最后一个元素作为主文件系统! - /// - public List FileSystemParameterList; - } - ``` - -## [2.3.1-preview] - 2025-02-25 - -**资源加载依赖计算方式还原为了1.5x版本的模式,只加载资源对象实际依赖的资源包,不再以资源对象所在资源包的依赖关系为加载标准**。 - -### Improvements - -- 优化OperationSystem的更新机制,异步加载的耗时降低了50%。 -- 优化了Debugger窗口的显示页面,BundleView页面增加资源包的引用列表。 -- 优化了Reporter窗口的显示页面。 - -### Fixed - -- 修复了怀旧依赖模式下,TAG传染不正确的问题。 - -## [2.3.0-preview] - 2025-02-19 - -### Improvements - -资源收集窗口列表元素支持手动上下拖拽排序! - -资源扫描窗口列表元素支持手动上下拖拽排序! - -### Added - -- 新增了UIElements扩展类ReorderableListView - -- 新增初始化方法 - - ```csharp - public class YooAssets - { - /// - /// 设置异步系统参数,快速启动模式的开关 - /// 注意:该模式默认开启 - /// - public static void SetOperationSystemQuickStartMode(bool state) - } - ``` - -- 新增打包构建参数 - - ```csharp - public class BuildParameters - { - /// - /// 旧版依赖模式 - /// 说明:兼容YooAssets1.5.x版本 - /// - public bool LegacyDependency = false; - } - ``` - -### Fixed - -- (#472) 修复了Unity6平台,TableView视图无法显示问题。 -- 修复了微信小游戏和抖音小游戏未正确使用插件的卸载方法。 - -## [2.2.12] - 2025-02-14 - -### Improvements - -- WebGL网页平台支持文件加密。 -- 微信小游戏平台支持文件加密。 -- 抖音小游戏平台支持文件加密。 - -### Fixed - -- (#466) 修复了微信小游戏文件系统查询机制不生效! -- (#341) 修复了微信小游戏的下载进度异常问题。 -- (#471) 修复了Unity2019,Unity2020平台上,TableView视图无法显示的问题。 - -### Added - -- 新增了ResourcePackage.UnloadAllAssetsAsync(UnloadAllAssetsOptions options)方法 - - ```csharp - public sealed class UnloadAllAssetsOptions - { - /// - /// 释放所有资源句柄,防止卸载过程中触发完成回调! - /// - public bool ReleaseAllHandles = true; - - /// - /// 卸载过程中锁定加载操作,防止新的任务请求! - /// - public bool LockLoadOperation = true; - } - ``` - -## [2.2.11] - 2025-02-10 - -### Improvements - -- AssetArtScanner配置和生成报告的容错性检测。 - -### Fixed - -- (#465) 修复了特殊情况下,没有配置资源包文件后缀名构建失败的问题。 -- (#468) 修复了安卓平台二次启动加载原生文件或加密文件失败的问题。 - -## [2.2.10] - 2025-02-08 - -### Improvements - -- 新增了可扩展的AssetArtScanner资源扫描工具,详细请见官方说明文档。 -- 优化了AssetBundleReporter页面。 -- 优化了AssetBundleDebugger页面。 -- 优化了微信小游戏文件系统的缓存查询机制。 -- 优化了抖音小游戏文件系统的缓存查询机制。 - -### Fixed - -- (#447) 修复了Unity2019平台代码编译错误问题。 -- (#456) 修复了在Package未激活有效清单之前,无法销毁的问题。 -- (#452) 修复了内置文件系统类NeedPack方法总是返回TRUE的问题。 -- (#424) 适配了Unity6000版本替换了过时方法。 - -### Added - -- 新增了SBP构建管线构建参数:BuiltinShadersBundleName - -- 新增了SBP构建管线构建参数:MonoScriptsBundleName - -- 新增了全局构建管线构建参数:SingleReferencedPackAlone - - ```csharp - /// - /// 对单独引用的共享资源进行独立打包 - /// 说明:关闭该选项单独引用的共享资源将会构建到引用它的资源包内! - /// - public bool SingleReferencedPackAlone = true; - ``` - -- 新增了内置文件系统初始化参数:COPY_BUILDIN_PACKAGE_MANIFEST - - ```csharp - // 内置文件系统初始化的时候,自动拷贝内置清单到沙盒目录。 - var systemParameters = FileSystemParameters.CreateDefaultBuildinFileSystemParameters(); - systemParameters.AddParameter(FileSystemParametersDefine.COPY_BUILDIN_PACKAGE_MANIFEST, true); - ``` - -## [2.2.9] - 2025-01-14 - -### Fixed - -- (#438) 修复了纯血鸿蒙加载本地文件失败的问题。 -- (#445) 修复了小游戏扩展文件系统脚本编译错误。 - -### Changed - -- EditorSimulateModeHelper.SimulateBuild()方法变更 - - ```csharp - public static PackageInvokeBuildResult SimulateBuild(string packageName); - ``` - -## [2.2.8-preview] - 2025-01-03 - -新增了单元测试用例。 - -### Improvements - -- EditorSimulateModeHelper.SimulateBuild()方法提供指定自定义构建类 - - ```csharp - public class EditorSimulateBuildParam - { - /// - /// 模拟构建类所属程序集名称 - /// - public string InvokeAssmeblyName = "YooAsset.Editor"; - - /// - /// 模拟构建执行的类名全称 - /// 注意:类名必须包含命名空间! - /// - public string InvokeClassFullName = "YooAsset.Editor.AssetBundleSimulateBuilder"; - - /// - /// 模拟构建执行的方法名称 - /// 注意:执行方法必须满足 BindingFlags.Public | BindingFlags.Static - /// - public string InvokeMethodName = "SimulateBuild"; - } - ``` - -- 文件清理方式新增清理缓存清单。 - - ```csharp - /// - /// 文件清理方式 - /// - public enum EFileClearMode - { - /// - /// 清理所有清单 - /// - ClearAllManifestFiles, - - /// - /// 清理未在使用的清单 - /// - ClearUnusedManifestFiles, - } - ``` - -### Fixed - -- (#426) 修复了鸿蒙next平台加载内置文件路径报错的问题。 -- (#428) 修复了鸿蒙next平台加载内置文件路径报错的问题。 -- (#434) 修复了2.2版本 catalog文件对Json格式原生文件不记录的问题。 -- (#435) 修复了WebGL平台调用MD5算法触发异常的问题。 - -### Added - -- 新增了视频打包规则。 - - ```csharp - /// - /// 打包视频文件 - /// - [DisplayName("打包视频文件")] - public class PackVideoFile : IPackRule - ``` - -### Changed - -- 重命名FileSystemParameters.RootDirectory字段为PackageRoot -- 重命名ResourcePackage.ClearCacheBundleFilesAsync()方法为ClearCacheFilesAsync() - -## [2.2.7-preview] - 2024-12-30 - -### Improvements - -- 重构了下载器的委托方法。 - -- YooAssetSettings配置文件新增Package Manifest Prefix参数。 - - ```csharp - /// - /// 资源清单前缀名称(默认为空) - /// - public string PackageManifestPrefix = string.Empty; - ``` - -### Fixed - -- (#422) 修复了同步加载场景的NotImplementedException异常报错。 -- (#418) 修复了web远程文件系统初始化不正确的问题 -- (#392) 修复了引擎版本代码兼容相关的警告。 -- (#332) 修复了当用户的设备中有特殊字符时,URL路径无法被正确识别的问题。 - -### Added - -- 新增代码字段:AsyncOperationBase.PackageName - -### Changed - -- 重命名DownloaderOperation.OnDownloadOver()方法为DownloaderFinish() -- 重命名DownloaderOperation.OnDownloadProgress()方法为DownloadUpdate() -- 重命名DownloaderOperation.OnDownloadError()方法为DownloadError() -- 重命名DownloaderOperation.OnStartDownloadFile()方法为DownloadFileBegin() - -## [2.2.6-preview] - 2024-12-27 - -### Improvements - -- 增强了对Steam平台DLC拓展包的支持。 - - ```csharp - // 新增参数关闭Catalog目录查询内置文件的功能 - var fileSystemParams = CreateDefaultBuildinFileSystemParameters(); - fileSystemParams .AddParameter(FileSystemParametersDefine.DISABLE_CATALOG_FILE, true); - ``` - -- 资源句柄基类提供了统一的Release方法。 - - ```csharp - public abstract class HandleBase : IEnumerator, IDisposable - { - /// - /// 释放资源句柄 - /// - public void Release(); - - /// - /// 释放资源句柄 - /// - public void Dispose(); - } - ``` - -- 优化了场景卸载逻辑。 - - ```csharp - //框架内不在区分主场景和附加场景。 - //场景卸载后自动释放资源句柄。 - ``` - -### Fixed - -- 修复了Unity2020版本提示的脚本编译错误。 -- (#417) 修复了DefaultWebServerFileSystem文件系统内Catalog未起效的问题。 - -### Added - -- 新增示例文件 GetCacheBundleSizeOperation.cs - - 可以获取指定Package的缓存资源总大小。 - -### Removed - -- 移除了SceneHandle.IsMainScene()方法。 - -## [2.2.5-preview] - 2024-12-25 - -依赖的ScriptableBuildPipeline (SBP) 插件库版本切换为1.21.25版本! - -重构了ResourceManager相关的核心代码,方便借助文件系统扩展和支持更复杂的需求! - -### Editor - -- 新增了编辑器模拟构建管线 EditorSimulateBuildPipeline -- 移除了EBuildMode枚举类型,构建界面有变动。 -- IActiveRule分组激活接口新增GroupData类。 - -### Improvements - -- 增加抖音小游戏文件系统,见扩展示例代码。 - -- 微信小游戏文件系统支持删除无用缓存文件和全部缓存文件。 - -- 资源构建管线现在默认剔除了Gizmos和编辑器资源。 - -- 优化了资源构建管线里资源收集速度。 - - 资源收集速度提升100倍! - - ```csharp - class BuildParameters - { - /// - /// 使用资源依赖缓存数据库 - /// 说明:开启此项可以极大提高资源收集速度 - /// - public bool UseAssetDependencyDB = false; - } - ``` - -- WebPlayMode支持跨域加载。 - - ```csharp - // 创建默认的WebServer文件系统参数 - public static FileSystemParameters CreateDefaultWebServerFileSystemParameters(bool disableUnityWebCache = false) - - // 创建默认的WebRemote文件系统参数(支持跨域加载) - public static FileSystemParameters CreateDefaultWebRemoteFileSystemParameters(IRemoteServices remoteServices, bool disableUnityWebCache = false) - ``` - -- 编辑器模拟文件系统新增初始化参数:支持异步模拟加载帧数。 - - ```csharp - /// - /// 异步模拟加载最小帧数 - /// - FileSystemParametersDefine.ASYNC_SIMULATE_MIN_FRAME - - /// - /// 异步模拟加载最大帧数 - /// - FileSystemParametersDefine.ASYNC_SIMULATE_MAX_FRAME - ``` - -- 缓存文件系统新增初始化参数:支持设置下载器最大并发连接数和单帧最大请求数 - - ```csharp - var fileSystremParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters(); - fileSystremParams .AddParameter(FileSystemParametersDefine.DOWNLOAD_MAX_CONCURRENCY, 99); - fileSystremParams .AddParameter(FileSystemParametersDefine.DOWNLOAD_MAX_REQUEST_PER_FRAME, 10); - ``` - -### Fixed - -- (#349) 修复了在加载清单的时候,即使本地存在缓存文件还会去远端下载。 -- (#361) 修复了协程里等待的asset handle被release,会无限等待并输出警告信息。 -- (#359) 修复了SubAssetsHandle.GetSubAssetObject会获取到同名的主资源。 -- (#387) 修复了加密后文件哈希冲突的时候没有抛出异常错误。 -- (#404) 修复了Unity2022.3.8版本提示编译错误:Cannot resolve symbol 'AsyncInstantiateOperation' - -### Added - -- 新增示例文件 CopyBuildinManifestOperation.cs - -- 新增示例文件 LoadGameObjectOperation.cs - -- 新增了获取配置清单详情的方法 - - ```csharp - class ResourcePackage - { - public PackageDetails GetPackageDetails() - } - ``` - -- 新增了获取所有资源信息的方法 - - ```csharp - class ResourcePackage - { - public AssetInfo[] GetAllAssetInfos() - } - ``` - -- 新增了清理缓存文件的通用方法 - - ```csharp - /// - /// 文件清理方式 - /// - public enum EFileClearMode - { - /// - /// 清理所有文件 - /// - ClearAllBundleFiles = 1, - /// - /// 清理未在使用的文件 - /// - ClearUnusedBundleFiles = 2, - /// - /// 清理指定标签的文件 - /// 说明:需要指定参数,可选:string, string[], List - /// - ClearBundleFilesByTags = 3, - } - class ResourcePackage - { - /// - /// 清理缓存文件 - /// - /// 清理方式 - /// 执行参数 - public ClearCacheBundleFilesOperation ClearCacheBundleFilesAsync(EFileClearMode clearMode, object clearParam = null) - } - ``` - -### Changed - -- 修改了EditorSimulateModeHelper.SimulateBuild()方法 - -- 重命名ResourcePackage.GetAssetsInfoByTags()方法为GetAssetInfosByTags() - -- 实例化对象方法增加激活参数。 - - ```csharp - public InstantiateOperation InstantiateAsync(bool actived = true) - ``` - -- 清单文件的版本提升到2.2.5版本 - - ```csharp - /// - /// 资源包裹的备注信息 - /// - public string PackageNote; - ``` - - -### Removed - -- 移除了HostPlayModeParameters.DeliveryFileSystemParameters字段 -- 移除了ResourcePackage.ClearAllBundleFilesAsync()方法 -- 移除了ResourcePackage.ClearUnusedBundleFilesAsync()方法 -- 移除了FileSystemParameters.CreateDefaultBuildinRawFileSystemParameters()方法 -- 移除了FileSystemParameters.CreateDefaultCacheRawFileSystemParameters()方法 -- 移除了枚举类型:EDefaultBuildPipeline -- 移除了配置参数:YooAssetSettings.ManifestFileName - -## [2.2.4-preview] - 2024-08-15 - -### Fixed - -- 修复了HostPlayMode初始化卡死的问题。 - -## [2.2.3-preview] - 2024-08-13 - -### Fixed - -- (#311) 修复了断点续传下载器极小概率报错 : “416 Range Not Satisfiable” - -### Improvements - -- 原生文件构建管线支持原生文件加密。 - -- HostPlayMode模式下内置文件系统初始化参数可以为空。 - -- 场景加载增加了LocalPhysicsMode参数来控制物理运行模式。 - -- 默认的内置文件系统和缓存文件系统增加解密方法。 - - ```csharp - /// - /// 创建默认的内置文件系统参数 - /// - /// 加密文件解密服务类 - /// 缓存文件的校验等级 - /// 内置文件的根路径 - public static FileSystemParameters CreateDefaultBuildinFileSystemParameters(IDecryptionServices decryptionServices, EFileVerifyLevel verifyLevel, string rootDirectory); - - /// - /// 创建默认的缓存文件系统参数 - /// - /// 远端资源地址查询服务类 - /// 加密文件解密服务类 - /// 缓存文件的校验等级 - /// 文件系统的根目录 - public static FileSystemParameters CreateDefaultCacheFileSystemParameters(IRemoteServices remoteServices, IDecryptionServices decryptionServices, EFileVerifyLevel verifyLevel, string rootDirectory); - ``` - -## [2.2.2-preview] - 2024-07-31 - -### Fixed - -- (#321) 修复了在Unity2022里编辑器下离线模式运行失败的问题。 -- (#325) 修复了在Unity2019里编译报错问题。 - -## [2.2.1-preview] - 2024-07-10 - -统一了所有PlayMode的初始化逻辑,EditorSimulateMode和OfflinePlayMode初始化不再主动加载资源清单! - -### Added - -- 新增了IFileSystem.ReadFileData方法,支持原生文件自定义获取文本和二进制数据。 - -### Improvements - -- 优化了DefaultWebFileSystem和DefaultBuildFileSystem文件系统的内部初始化逻辑。 - -## [2.2.0-preview] - 2024-07-07 - -重构了运行时代码,新增了文件系统接口(IFileSystem)方便开发者扩展特殊需求。 - -新增微信小游戏文件系统示例代码,详细见Extension Sample/Runtime/WechatFileSystem - -### Added - -- 新增了ResourcePackage.DestroyAsync方法 - -- 新增了FileSystemParameters类帮助初始化文件系统 - - 内置了编辑器文件系统参数,内置文件系统参数,缓存文件系统参数,Web文件系统参数。 - - ```csharp - public class FileSystemParameters - { - /// - /// 文件系统类 - /// - public string FileSystemClass { private set; get; } - - /// - /// 文件系统的根目录 - /// - public string RootDirectory { private set; get; } - - /// - /// 添加自定义参数 - /// - public void AddParameter(string name, object value) - } - ``` - -### Changed - -- 重构了InitializeParameters初始化参数 -- 重命名YooAssets.DestroyPackage方法为RemovePackage -- 重命名ResourcePackage.UpdatePackageVersionAsync方法为RequestPackageVersionAsync -- 重命名ResourcePackage.UnloadUnusedAssets方法为UnloadUnusedAssetsAsync -- 重命名ResourcePackage.ForceUnloadAllAssets方法为UnloadAllAssetsAsync -- 重命名ResourcePackage.ClearUnusedCacheFilesAsync方法为ClearUnusedBundleFilesAsync -- 重命名ResourcePackage.ClearAllCacheFilesAsync方法为ClearAllBundleFilesAsync - -### Removed - -- 移除了YooAssets.Destroy方法 -- 移除了YooAssets.SetDownloadSystemClearFileResponseCode方法 -- 移除了YooAssets.SetCacheSystemDisableCacheOnWebGL方法 -- 移除了ResourcePackage.GetPackageBuildinRootDirectory方法 -- 移除了ResourcePackage.GetPackageSandboxRootDirectory方法 -- 移除了ResourcePackage.ClearPackageSandbox方法 -- 移除了IBuildinQueryServices接口 -- 移除了IDeliveryLoadServices接口 -- 移除了IDeliveryQueryServices接口 - - -## [2.1.2] - 2024-05-16 - -SBP库依赖版本升级至2.1.3 - -### Fixed - -- (#236) 修复了资源配置界面AutoCollectShader复选框没有刷新的问题。 -- (#244) 修复了导入器在安卓平台导入本地下载的资源失败的问题。 -- (#268) 修复了挂起场景未解除状态前无法卸载的问题。 -- (#269) 优化场景挂起流程,支持中途取消挂起操作。 -- (#276) 修复了HostPlayMode模式下,如果内置清单是最新版本,每次运行都会触发拷贝行为。 -- (#289) 修复了Unity2019版本脚本IWebRequester编译报错。 -- (#295) 解决了在安卓移动平台,华为和三星真机上有极小概率加载资源包失败 : Unable to open archive file - -### Added - -- 新增GetAllCacheFileInfosOperation()获取缓存文件信息的方法。 - -- 新增LoadSceneSync()同步加载场景的方法。 - -- 新增IIgnoreRule接口,资源收集流程可以自定义。 - -- 新增IWechatQueryServices接口,用于微信平台本地文件查询。 - - 后续将会通过虚拟文件系统来支持! - -### Changed - -- 调整了UnloadSceneOperation代码里场景的卸载顺序。 - -### Improvements - -- 优化了资源清单的解析过程。 -- 移除资源包名里的空格字符。 -- 支持华为鸿蒙系统。 - -## [2.1.1] - 2024-01-17 - -### Fixed - -- (#224) 修复了编辑器模式打包时 SimulateBuild 报错的问题。 -- (#223) 修复了资源构建界面读取配置导致的报错问题。 - -### Added - -- 支持共享资源打包规则,可以定制化独立的构建规则。 - - ```c# - public class BuildParameters - { - /// - /// 是否启用共享资源打包 - /// - public bool EnableSharePackRule = false; - } - ``` - -- 微信小游戏平台,资源下载器支持底层缓存查询。 - -## [2.1.0] - 2023-12-27 - -升级了 Scriptable build pipeline (SBP) 的版本,来解决图集引用的精灵图片冗余问题。 - -### Fixed - -- (#195) 修复了在EditorPlayMode模式下,AssetHandle.GetDownloadStatus()发生异常的问题。 -- (#201) 修复了断点续传失效的问题。 -- (#202) 修复了打包参数FileNameStyle设置为BundleName后,IQueryServices会一直返回true的问题。 -- (#205) 修复了HybridCLR插件里创建资源下载器触发的异常。 -- (#210) 修复了DownloaderOperation在未开始下载前,内部的PackageName为空的问题。 -- (#220) 修复了资源收集界面关闭后,撤回操作还会生效的问题。 -- 修复了下载器合并后重新计算下载字节数不正确的问题。 - -### Improvements - -- (#198) 资源收集界面禁用的分组不再检测合法性。 -- (#203) 资源构建类容许自定义打包的输出目录。 -- 资源构建报告增加未依赖的资源信息列表。 - -### Changed - -- IBuildinQueryServices和IDeliveryQueryServices查询方法变更。 - - ```c# - public interface IBuildinQueryServices - { - /// - /// 查询是否为应用程序内置的资源文件 - /// - /// 包裹名称 - /// 文件名称(包含文件的后缀格式) - /// 文件哈希值 - /// 返回查询结果 - bool Query(string packageName, string fileName, string fileCRC); - } - - public interface IDeliveryQueryServices - { - /// - /// 查询是否为开发者分发的资源文件 - /// - /// 包裹名称 - /// 文件名称(包含文件的后缀格式) - /// 文件哈希值 - /// 返回查询结果 - bool Query(string packageName, string fileName, string fileCRC); - } - ``` - - - -### Removed - -- (#212) 移除了构建报告里的资源冗余信息列表。 +## [3.0.0] - 2026-05-01 +beta released. diff --git a/Assets/YooAsset/Docs/1.命名准则.md b/Assets/YooAsset/Docs/1.命名准则.md deleted file mode 100644 index fb6817c0..00000000 --- a/Assets/YooAsset/Docs/1.命名准则.md +++ /dev/null @@ -1,228 +0,0 @@ -# .NET 命名准则 - -> 参考来源:[Microsoft .NET 框架设计准则 - 命名准则](https://learn.microsoft.com/zh-cn/dotnet/standard/design-guidelines/naming-guidelines) -> -> 核心原则:**可读性优先、语义清晰、风格一致、避免歧义** - ---- - -## 一、大小写约定 - -### 1.1 两种大小写风格 - -| 风格 | 规则 | 示例 | -|------|------|------| -| **PascalCasing** | 每个单词首字母大写(含首个单词) | `PropertyDescriptor`、`HtmlTag` | -| **camelCasing** | 除第一个单词外,每个单词首字母大写 | `propertyDescriptor`、`htmlTag` | - -### 1.2 各标识符的大小写规则 - -| 标识符 | 大小写 | 示例 | -|--------|--------|------| -| 命名空间 | PascalCasing | `System.Security` | -| 类型(类/结构) | PascalCasing | `StreamReader` | -| 接口 | PascalCasing | `IEnumerable` | -| 方法 | PascalCasing | `ToString()` | -| 属性 | PascalCasing | `Length` | -| 事件 | PascalCasing | `Exited` | -| 字段(公共/静态) | PascalCasing | `InfiniteTimeout` | -| 枚举值 | PascalCasing | `FileMode.Append` | -| 参数 | camelCasing | `string value` | - -### 1.3 首字母缩写词规则 - -- **两个字母**的缩写词:全部大写,如 `IO`、`DB` -- **三个字母及以上**的缩写词:仅首字母大写,如 `Html`、`Xml` -- camelCasing 中,开头的两字母缩写词全部小写:`ioStream` - -### 1.4 常见复合词的正确写法 - -| 正确 (Pascal) | 正确 (camel) | 错误 | -|---------|---------|------| -| `Callback` | `callback` | ~~CallBack~~ | -| `Endpoint` | `endpoint` | ~~EndPoint~~ | -| `FileName` | `fileName` | ~~Filename~~ | -| `Hashtable` | `hashtable` | ~~HashTable~~ | -| `Id` | `id` | ~~ID~~ | -| `Metadata` | `metadata` | ~~MetaData~~ | -| `Namespace` | `namespace` | ~~NameSpace~~ | -| `Ok` | `ok` | ~~OK~~ | -| `UserName` | `userName` | ~~Username~~ | -| `WhiteSpace` | `whiteSpace` | ~~Whitespace~~ | -| `SignIn` | `signIn` | ~~SignOn~~ | -| `LogOff` | `logOff` | ~~LogOut~~ | -| `Email` | `email` | ~~EMail~~ | - -> ✔️ 在 Unity 项目中,`ID` 保持全大写以与 Unity API 风格一致(如 `GetInstanceID()`、`PropertyToID()`),项目内应保持风格统一。 - -### 1.5 区分大小写 - -- 不要假定所有语言都区分大小写 -- 公开 API 中不应仅靠大小写来区分两个名称 - ---- - -## 二、常规命名约定 - -### 2.1 用词选择 - -- **可读性优先于简洁性**:`CanScrollHorizontally` 优于 `ScrollableX` -- **语义清晰**:`HorizontalAlignment` 优于 `AlignmentHorizontal` -- **禁止**使用下划线 `_`、连字符 `-` 或其他非字母字符 -- **禁止**使用匈牙利命名法(如 `strName`、`iCount`) -- **避免**与编程语言关键字冲突 - -### 2.2 缩写和首字母缩写词 - -- **禁止**使用缩写:用 `GetWindow` 而非 `GetWin` -- **禁止**使用未被广泛接受的首字母缩略词 - -### 2.3 避免语言特定名称 - -- 使用语义化名称:`GetLength` 优于 `GetInt` -- 需要表示类型时,使用 CLR 类型名称而非语言关键字:`ToInt64` 而非 `ToLong` - -### 2.4 现有 API 的新版本命名 - -- 使用类似旧 API 的名称 -- **优先添加后缀**而非前缀(便于 IntelliSense 排序) -- 用**数字后缀**标识版本(如 `64` 表示 64 位版本) -- **禁止**使用 `Ex` 后缀 - ---- - -## 三、程序集和 DLL 的名称 - -- 选择能体现**大范围功能**的名称 -- 推荐格式:`..dll`,如 `Litware.Controls.dll` -- 基于程序集所含命名空间的**公共前缀**来命名 - ---- - -## 四、命名空间的名称 - -- 推荐格式:`.(|)[.][.]` -- 使用 **PascalCasing**,用句点分隔:`Microsoft.Office.PowerPoint` -- 使用**与版本无关的稳定产品名称** -- 适当使用**复数**:`System.Collections` 而非 `System.Collection` -- **禁止**命名空间与其内部类型同名 -- **禁止**使用泛型名称如 `Element`、`Node`、`Log`、`Message`,应加限定:`FormElement`、`XmlNode`、`EventLog` - ---- - -## 五、类、结构和接口的名称 - -### 5.1 类和结构 - -- 使用 PascalCasing,**名词或名词短语** -- **禁止**添加 `C` 前缀 -- 考虑以**基类名称结尾**:`ArgumentOutOfRangeException`(派生自 `Exception`) - -### 5.2 接口 - -- 使用**形容词短语**,偶尔使用名词 -- **必须**以 `I` 前缀开头:`IComponent`、`ICustomAttributeProvider`、`IPersistable` - -### 5.3 泛型类型参数 - -- 单字母参数时用 `T`:`IComparer` -- 描述性名称以 `T` 开头:`ISessionChannel` -- 考虑在名称中体现约束:`TSession`(约束为 `ISession`) - -### 5.4 常见类型的后缀规则 - -| 基类/接口 | 后缀要求 | -|-----------|---------| -| `System.Attribute` | 添加 `Attribute` 后缀 | -| `System.Delegate`(事件用) | 添加 `EventHandler` 后缀 | -| `System.Delegate`(回调用) | 添加 `Callback` 后缀 | -| `System.EventArgs` | 添加 `EventArgs` 后缀 | -| `System.Exception` | 添加 `Exception` 后缀 | -| `IDictionary` / `IDictionary` | 添加 `Dictionary` 后缀 | -| `IEnumerable` / `ICollection` / `IList` | 添加 `Collection` 后缀 | -| `System.IO.Stream` | 添加 `Stream` 后缀 | - -### 5.5 枚举命名 - -- 普通枚举用**单数**名称:`FileMode` -- 位标志枚举用**复数**名称:`FileAttributes` -- **禁止**添加 `Enum`、`Flag`、`Flags` 后缀 -- **禁止**在枚举值上使用前缀 -- ✔️ 允许对枚举类型名称添加 `E` 前缀以提高可辨识度,项目内应保持风格一致 - ---- - -## 六、类型成员的名称 - -### 6.1 方法 - -- 使用**谓词或谓词短语**:`CompareTo`、`Split`、`Trim` - -### 6.2 属性 - -- 使用**名词、名词短语或形容词** -- 布尔属性使用肯定短语:`CanSeek` 而非 `CantSeek` -- 布尔属性可加 `Is`、`Can`、`Has` 前缀(仅在有意义时) -- 可以与类型同名:`public Color Color { get; set; }` -- 集合属性用**复数**:`Items` 而非 `ItemList` - -### 6.3 事件 - -- 使用**谓词或谓词短语** -- 用时态区分前后:`Closing`(关闭前)、`Closed`(关闭后) -- **禁止**用 `Before` / `After` 前后缀 -- 事件处理程序委托添加 `EventHandler` 后缀 -- 事件参数类添加 `EventArgs` 后缀 -- 事件处理程序参数命名为 `sender` 和 `e` - -### 6.4 字段 - -- 使用 PascalCasing -- 使用**名词、名词短语或形容词** -- **禁止**使用前缀(如 `g_`、`s_`、`m_`) - ---- - -## 七、参数命名 - -- 使用 **camelCasing** -- 使用**描述性名称** -- 根据**含义**而非类型命名 -- 二元运算符重载:无特殊含义时用 `left` / `right` -- 一元运算符重载:无特殊含义时用 `value` -- **禁止**对运算符参数使用缩写或数字索引 - ---- - -## 八、资源命名 - -- 使用 **PascalCasing** -- 使用**描述性标识符** -- 仅使用字母数字字符和下划线 -- 异常消息资源格式:`异常类型名 + 短标识符`,如 `ArgumentExceptionIllegalCharacters` - ---- - -## 九、内部字段命名约定 - -以上命名准则主要适用于公开 API。对于内部实现代码,以下前缀约定对代码审查者非常有价值: - -| 字段类型 | 前缀 | 示例 | -|---------|------|------| -| 私有/内部**实例**字段 | `_` | `_count`、`_name` | -| 私有/内部**静态**字段 | `s_` | `s_instance`、`s_defaultValue` | -| 私有/内部**线程静态**字段 | `t_` | `t_cachedBuffer` | - -```csharp -public class ConnectionPool -{ - private static readonly int s_maxPoolSize = 100; - [ThreadStatic] - private static Queue t_localPool; - - private readonly string _connectionString; - private int _activeCount; -} -``` - -> 注意:此约定仅适用于**私有和内部**字段,不适用于公共 API。公共字段(如 `public static readonly` 字段)仍然使用 PascalCasing 且不带前缀,这与前文"六、类型成员的名称 - 6.4 字段"中"禁止使用前缀"的准则不矛盾。 diff --git a/Assets/YooAsset/Docs/1.命名准则.md.meta b/Assets/YooAsset/Docs/1.命名准则.md.meta deleted file mode 100644 index 037f5264..00000000 --- a/Assets/YooAsset/Docs/1.命名准则.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: b6b6bc56d8bfe6c4fa3776f98ab3f822 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/YooAsset/Docs/2.类型设计准则.md b/Assets/YooAsset/Docs/2.类型设计准则.md deleted file mode 100644 index 98d6ce07..00000000 --- a/Assets/YooAsset/Docs/2.类型设计准则.md +++ /dev/null @@ -1,279 +0,0 @@ -# .NET 类型设计准则 - -> 参考来源:[Microsoft .NET 框架设计准则 - 类型设计准则](https://learn.microsoft.com/zh-cn/dotnet/standard/design-guidelines/type) -> -> 核心原则:**每种类型都应是一组定义完善的相关成员,而非不相关功能的随机集合。** - ---- - -## 一、类型概述 - -从 CLR 的角度看只有两类类型(引用类型和值类型),但在框架设计中可细分为以下逻辑组: - -| 类型 | 说明 | -|------|------| -| **类(Class)** | 引用类型的一般情况,支持丰富的面向对象功能,构成框架中的大部分类型 | -| **接口(Interface)** | 可由引用类型和值类型实现,用作多态层次结构的根,模拟多重继承 | -| **结构(Struct)** | 值类型的一般情况,应保留为小型简单类型,类似语言基元 | -| **枚举(Enum)** | 特殊值类型,用于定义一组简短的命名常量 | -| **静态类(Static Class)** | 仅包含静态成员的容器,用于提供操作快捷方式 | -| **委托/异常/属性/数组/集合** | 用于特定用途的引用类型特殊情况 | - ---- - -## 二、在类和结构之间进行选择 - -### 2.1 引用类型 vs 值类型的核心区别 - -| 特征 | 引用类型(类) | 值类型(结构) | -|------|--------------|--------------| -| **分配位置** | 堆上分配,由 GC 回收 | 栈上分配或内联在包含类型中 | -| **数组分配** | 元素是引用,实例在堆上 | 元素是实际实例,内联分配 | -| **赋值行为** | 复制引用 | 复制整个值 | -| **传递方式** | 按引用传递 | 按值传递(产生副本) | -| **装箱** | 无装箱开销 | 转换为接口或引用类型时会装箱 | - -### 2.2 选择准则 - -✔️ 如果类型的实例**较小**且通常**生存期较短**或通常**嵌入在其他对象中**,请考虑定义结构而不是类。 - -❌ **避免**定义结构,除非类型**同时满足**以下所有特征: - -- 逻辑上表示单个值,类似基元类型(`int`、`double`) -- 实例大小**低于 24 字节** -- 是**不可变**的 -- 不必**频繁装箱** - -> 在所有其他情况下,应将类型定义为类。 - ---- - -## 三、抽象类设计 - -❌ **禁止**在抽象类型中定义 `public` 或 `protected internal` 构造函数。 - -> 抽象类型无法被实例化,公共构造函数会对用户产生误导。 - -✔️ **必须**在抽象类中定义 `protected` 或 `internal` 构造函数。 - -- `protected` 构造函数更常用,允许基类在创建子类型时执行初始化 -- `internal` 构造函数可将具体实现限制在定义类的程序集内 - -✔️ **必须**提供至少一种从你交付的每个抽象类继承的具体类型。 - -> 例如 `FileStream` 是抽象类 `Stream` 的具体实现,这有助于验证抽象类的设计。 - ---- - -## 四、静态类设计 - -静态类仅包含静态成员(除继承自 `Object` 的实例成员和私有构造函数外)。在 C# 中,声明为 `static` 的类是密封的、抽象的,且无法重写或声明实例成员。 - -### 适用场景 - -- 提供其他操作的快捷方式(如 `System.IO.File`) -- 扩展方法的持有者 -- 无法确保完整面向对象包装器的功能(如 `System.Environment`) - -### 设计准则 - -✔️ **谨慎**使用静态类,仅作为框架面向对象核心的支持类。 - -❌ **禁止**将静态类视为杂项存储桶。 - -❌ **禁止**在静态类中声明或重写实例成员。 - -✔️ 如果语言没有对静态类的内置支持,则将其声明为 `sealed abstract` 并添加私有实例构造函数。 - ---- - -## 五、接口设计 - -### 5.1 适用场景 - -- 需要一组**包含值类型**的类型支持通用 API -- 需要在已从其他类型继承的类型上支持功能(模拟多重继承) - -### 5.2 设计准则 - -✔️ 如果需要包含值类型的多种类型支持通用 API,请定义接口。 - -✔️ 如果需要在已有基类继承的类型上支持功能,请考虑定义接口。 - -✔️ **必须**至少提供一种类型作为接口的实现。 - -> 例如 `List` 是 `IList` 的实现。 - -✔️ **必须**至少提供一个使用该接口的 API(以接口为参数的方法或类型为接口的属性)。 - -> 例如 `List.Sort` 使用 `IComparer` 接口。 - -❌ **避免**使用标记接口(没有成员的接口),应改用自定义属性(Attribute)。 - -❌ **禁止**向已发布的接口添加新成员。 - -> 这样做会破坏现有实现,应创建新接口以避免版本控制问题。 - -✔️ 优先定义**类**而非接口。基于类的 API 可以更容易地演进——向类添加成员不会破坏现有代码,而向接口添加成员则会。 - -✔️ 使用**抽象类**而非接口来将协定与实现解耦。 - -❌ **禁止**在公共 API 中使用 `ICloneable`。 - -> `ICloneable` 的问题在于:有些实现是浅拷贝,有些是深拷贝,调用者永远不知道会得到什么。这使得该接口实际上毫无用处。如果需要克隆机制,请定义类型特定的 `Clone` 方法。 - -> **一般规则**:在设计可重用库时,通常应选择类而非接口。 - ---- - -## 六、结构设计 - -❌ **禁止**为结构提供无参数构造函数。 - -> 遵循此准则可以创建结构数组,而无需在每个元素上运行构造函数。 - -❌ **禁止**定义可变值类型。 - -> 可变值类型问题:属性 getter 返回副本,开发者可能不知道正在修改副本而非原始值。 - -✔️ **必须**确保所有实例数据设置为零/false/null 的状态有效。 - -> 防止创建结构数组时意外产生无效实例。 - -✔️ 如果值类型会参与相等比较(如用作字典键、放入集合、使用 `==` 运算符),**必须**实现 `IEquatable`,并同时重写 `Equals(object)` 和 `GetHashCode()`。 - -> `Object.Equals` 在值类型上会导致装箱且效率低下(使用反射),`IEquatable.Equals` 性能更好且无装箱。 - -❌ 对于仅作为数据载体、不参与相等比较的值类型,**不必**实现 `IEquatable`。 - -❌ **禁止**显式扩展 `ValueType`。 - -✔️ 声明不可变值类型时使用 **`readonly struct`** 修饰符。 - -> 编译器理解 `readonly` 修饰符,在对 `readonly` 字段调用方法等操作时可避免产生额外的防御性复制。 - -```csharp -public readonly struct ZipCode -{ - public ZipCode(string value) => Value = value; - public string Value { get; } - public override string ToString() => Value; -} -``` - -✔️ 对可变值类型的非可变方法标记 **`readonly`** 修饰符。 - -> 允许编译器在调用时跳过值复制。 - -```csharp -public struct Point -{ - public float X; - public float Y; - public readonly override string ToString() => $"({X}, {Y})"; -} -``` - -❌ **避免**定义 `ref struct`(ref-like 值类型),除非用于低级性能关键场景。 - -> `ref struct` 只能存在于栈上,不能被装箱到堆中。因此不能用作其他类型的字段(除了其他 `ref struct`),也不能在 `async` 方法中使用。 - -> **总结**:结构适用于小型、单个、不可变且不频繁装箱的值。 - ---- - -## 七、枚举设计 - -### 7.1 两种枚举类型 - -| 类型 | 说明 | 命名 | 示例 | -|------|------|------|------| -| **简单枚举** | 表示小型封闭式选项集 | 单数名词 | `FileMode` | -| **标志枚举** | 支持对枚举值执行位运算 | 复数名词 | `FileAttributes` | - -### 7.2 通用准则 - -✔️ 使用枚举对表示值集的参数、属性和返回值进行**强类型化**。 - -✔️ **优先**使用枚举而不是静态常量。 - -✔️ 为简单枚举提供一个**零值**(通常命名为 `None`)。 - -✔️ 考虑使用 `Int32` 作为枚举的基础类型(除非有特殊需求)。 - -❌ **禁止**对开放集合使用枚举(如操作系统版本、好友姓名等)。 - -❌ **禁止**提供用于将来使用的保留枚举值。 - -❌ **避免**公开只有一个值的枚举。 - -❌ **禁止**在枚举中包含哨兵值。 - -❌ **禁止**直接扩展 `System.Enum`。 - -### 7.3 标志枚举设计 - -✔️ **必须**将 `[Flags]` 特性应用于标志枚举,不要应用于简单枚举。 - -✔️ **必须**对标志枚举值使用 **2 的幂**,以便用按位 OR 自由组合。 - -✔️ 考虑为常用标志组合提供特殊枚举值(如 `ReadWrite = Read | Write`)。 - -✔️ **必须**将标志枚举的零值命名为 `None`,表示"所有标志均已清除"。 - -❌ **避免**在某些值组合无效的情况下创建标志枚举。 - -❌ **避免**使用标志枚举值零,除非它表示"所有标志已清除"且命名为 `None`。 - -### 7.4 向枚举添加值 - -✔️ 考虑向已发布的枚举添加值(兼容性风险通常较小)。 - -> 如果有不兼容风险,可添加新 API 返回新旧值,并弃用旧 API。 - ---- - -## 八、嵌套类型 - -嵌套类型在另一个类型的作用域内定义,可访问封闭类型的所有成员(包括私有字段)。 - -### 8.1 适用场景 - -- 对封闭类型的**实现细节**进行建模 -- 例如:集合的枚举器可以是该集合的嵌套类型 - -### 8.2 设计准则 - -✔️ 当嵌套类型与外部类型之间的关系使得**成员可访问性语义**可取时,使用嵌套类型。 - -❌ **禁止**将公共嵌套类型用作逻辑分组构造(应使用命名空间)。 - -❌ **避免**公开的嵌套类型(极少数子类化或高级自定义方案除外)。 - -❌ 如果某个类型可能在其包含类型**之外被引用**,不要使用嵌套类型。 - -> 例如,传递给类方法的枚举不应定义为该类的嵌套类型。 - -❌ **禁止**使用需要通过客户端代码实例化的嵌套类型。 - -> 如果类型有公共构造函数,通常不应被嵌套。 - -❌ **禁止**将嵌套类型定义为接口的成员。 - -> 多种语言不支持此类构造。 - ---- - -## 九、速查表 - -| 场景 | 推荐类型 | -|------|---------| -| 大部分通用情况 | 类(Class) | -| 小型、不可变、类似基元的值 | 结构(Struct) | -| 需要值类型参与多态 | 接口(Interface) | -| 需要模拟多重继承 | 接口(Interface) | -| 封闭的命名常量集 | 枚举(Enum) | -| 可组合的位标志 | 标志枚举([Flags] Enum) | -| 纯静态工具方法容器 | 静态类(Static Class) | -| 提供可扩展的基础实现 | 抽象类(Abstract Class) | -| 封闭类型的内部实现细节 | 嵌套类型(Nested Type) | diff --git a/Assets/YooAsset/Docs/2.类型设计准则.md.meta b/Assets/YooAsset/Docs/2.类型设计准则.md.meta deleted file mode 100644 index fb0bbc2c..00000000 --- a/Assets/YooAsset/Docs/2.类型设计准则.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 982ed5c869a8a6c4d8adda239881d163 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/YooAsset/Docs/3.成员设计准则.md b/Assets/YooAsset/Docs/3.成员设计准则.md deleted file mode 100644 index b994893c..00000000 --- a/Assets/YooAsset/Docs/3.成员设计准则.md +++ /dev/null @@ -1,339 +0,0 @@ -# .NET 成员设计准则 - -> 参考来源:[Microsoft .NET 框架设计准则 - 成员设计准则](https://learn.microsoft.com/zh-cn/dotnet/standard/design-guidelines/member) -> -> 方法、属性、事件、构造函数和字段统称为成员,是将框架功能展现给最终用户的手段。 - ---- - -## 一、成员重载 - -成员重载是指在同一类型上创建两个或多个仅参数数量或类型不同、但名称相同的成员。只有**方法、构造函数和索引属性**可以重载。 - -### 设计准则 - -✔️ 使用**描述性参数名称**来指示较短重载使用的默认值。 - -✔️ 仅使**最长的重载虚拟化**(如果需要扩展性),较短的重载应直接调用较长的重载。 - -✔️ 允许 `null` 作为可选参数传递。 - -✔️ 优先使用**成员重载**,而不是带默认参数的成员(默认参数不符合 CLS)。 - -❌ **避免**在重载中任意改变参数名称。相同输入的参数在所有重载中应保持相同名称。 - -❌ **避免**重载中参数顺序不一致。相同名称的参数应在所有重载中处于相同位置。 - -❌ **禁止**使用 `ref` 或 `out` 修饰符来区分重载。 - -❌ **禁止**使用位置相同、类型相似但语义不同的重载。 - ---- - -## 二、属性设计 - -属性在技术上与方法相似,但使用场景不同。它们应被视为"智能字段",兼具字段的调用语法和方法的灵活性。 - -### 2.1 通用准则 - -✔️ 如果调用方不应更改属性的值,请创建**只读属性**(get-only)。 - -✔️ 为所有属性提供**合理的默认值**,确保不会导致安全漏洞或低效代码。 - -✔️ 允许按**任意顺序**设置属性,即使这会导致对象临时处于无效状态。 - -✔️ 如果属性 setter 引发异常,请**保留以前的值**。 - -❌ **禁止**提供仅有 setter 的属性,或 setter 比 getter 可访问性更广的属性。 - -> 如果无法提供 getter,请改用以 `Set` 开头的方法,如 `SetCachePath`。 - -❌ **避免**在属性 getter 中引发异常。 - -> getter 应是简单操作,无前置条件。如果可能引发异常,应重新设计为方法。 - -### 2.2 索引器设计 - -✔️ 考虑使用索引器提供对内部数组中数据的访问。 - -✔️ 考虑为表示项集合的类型提供索引器。 - -✔️ 索引器默认使用 `Item` 作为名称(除非有明显更好的名称)。 - -❌ **避免**使用多个参数的索引属性(如需多参数,请改用 `Get`/`Set` 方法)。 - -❌ **避免**使用 `Int32`、`Int64`、`String`、`Object` 或枚举以外类型作为索引器参数。 - -❌ **禁止**同时提供索引器和语义等效的方法。 - -❌ **禁止**在同一类型中提供多个重载索引器系列。 - -### 2.3 属性更改通知事件 - -✔️ 考虑在修改高级 API(通常是设计器组件)中的属性值时引发**更改通知事件**。 - -✔️ 考虑在属性值因外部因素更改时引发更改通知事件。 - ---- - -## 三、构造函数设计 - -### 3.1 实例构造函数 - -✔️ 考虑提供**简单的**(理想情况下为默认)构造函数,参数少且都是基元或枚举类型。 - -✔️ 如果操作语义不直接映射到新实例的构造,考虑使用**静态工厂方法**代替构造函数。 - -✔️ 使用构造函数参数作为设置主属性的**快捷方式**。 - -✔️ 如果构造函数参数仅用于设置属性,则对参数和属性使用**相同的名称**(仅大小写不同)。 - -✔️ 在构造函数中执行**最少的工作**,其他处理应延迟到需要时。 - -✔️ 在适当时从实例构造函数**抛出异常**。 - -✔️ 如果需要无参数构造函数,请**显式声明**(避免添加参数化构造函数后隐式丢失)。 - -❌ **避免**在结构上显式定义无参数构造函数(使数组创建更快)。 - -❌ **避免**在构造函数内对对象调用**虚拟成员**。 - -> 调用虚拟成员会导致调用派生度最高的重写,即使该派生类的构造函数尚未完全执行。 - -### 3.2 类型构造函数(静态构造函数) - -✔️ 将静态构造函数设为**私有**。 - -✔️ 考虑**内联初始化**静态字段,而非使用显式静态构造函数(运行时可优化性能)。 - -❌ **禁止**从静态构造函数引发异常。 - -> 如果类型构造函数抛出异常,该类型在当前应用程序域中将不可用。 - ---- - -## 四、事件设计 - -事件是最常见的回调形式,允许框架调用用户代码。 - -### 4.1 两种事件时机 - -| 类型 | 说明 | 示例 | -|------|------|------| -| **预事件** | 状态变更**之前**引发 | `Form.Closing` | -| **后事件** | 状态变更**之后**引发 | `Form.Closed` | - -### 4.2 设计准则 - -✔️ 对事件使用"**raise**"(引发)一词,而非 "fire" 或 "trigger"。 - -✔️ 使用 `EventHandler` 而非手动创建新委托。 - -✔️ 在 Unity 项目中,可以使用 `Action` 代替 `EventHandler` 作为事件委托类型,以保持与 Unity 生态惯例一致。 - -✔️ 考虑使用 `EventArgs` 的子类作为事件参数(即使最初为空,也便于将来扩展)。 - -✔️ 使用**受保护的虚拟方法**引发每个事件(仅适用于未密封类的非静态事件)。 - -> 方法命名约定:以 `On` 开头 + 事件名称,如 `OnClosing`。 - -✔️ 该方法接受一个参数,命名为 `e`,类型为事件参数类。 - -✔️ 考虑引发最终用户可**取消**的预事件(使用 `CancelEventArgs` 或其子类)。 - -❌ 引发非静态事件时,**禁止**将 `null` 作为 sender 传递。 - -✔️ 引发静态事件时,将 `null` 作为 sender 传递。 - -❌ 引发事件时,**禁止**将 `null` 作为事件数据参数传递(应使用 `EventArgs.Empty`)。 - -### 4.3 自定义事件处理程序 - -✔️ 事件处理程序返回类型为 `void`。 - -✔️ 第一个参数类型为 `object`,命名为 `sender`。 - -✔️ 第二个参数类型为 `EventArgs` 或其子类,命名为 `e`。 - -❌ 事件处理程序**不超过两个参数**。 - ---- - -## 五、字段设计 - -封装原则要求存储在对象中的数据只能通过该对象访问。 - -### 设计准则 - -❌ **禁止**提供 `public` 或 `protected` 的实例字段(应使用属性代替)。 - -✔️ 在 Unity 项目中,需要通过 `JsonUtility` 序列化的 `[Serializable]` 类型,应使用公共字段而非属性(`JsonUtility` 不支持属性序列化)。 - -✔️ 对永远不会更改的常量使用 `const` 字段。 - -> 编译器将 const 值直接嵌入调用代码,更改 const 值会破坏兼容性。 - -✔️ 对预定义的对象实例使用 `public static readonly` 字段。 - -❌ **禁止**将可变类型的实例赋给 `readonly` 字段。 - -> `readonly` 仅阻止引用被替换,不阻止通过成员修改实例数据。例如数组、集合等可变类型的实例数据仍可被修改。 - ---- - -## 六、扩展方法 - -扩展方法允许使用实例方法调用语法调用静态方法,定义在静态"发起方"类中。 - -### 适用场景 - -✔️ 为接口的每个实现提供辅助功能(基于核心接口编写),如 LINQ 运算符。 - -✔️ 当实例方法会引入对某类型的依赖,且该依赖会破坏依赖管理规则时。 - -### 设计准则 - -❌ **避免**轻率地定义扩展方法,尤其是在你不拥有的类型上。 - -> 如果拥有类型源代码,考虑使用常规实例方法。 - -❌ **避免**在 `System.Object` 上定义扩展方法(VB 用户无法使用扩展方法语法调用)。 - -❌ **禁止**将扩展方法放在与扩展类型相同的命名空间中(除非用于接口或依赖项管理)。 - -❌ **避免**使用相同签名定义多个扩展方法,即使在不同命名空间中。 - -❌ **避免**使用泛型命名空间名(如 "Extensions"),应使用描述性名称(如 "Routing")。 - -✔️ 如果类型是接口且扩展方法用于大多数场景,考虑放在与扩展类型相同的命名空间中。 - ---- - -## 七、运算符重载 - -运算符重载允许框架类型表现得像内置语言基元。 - -### 7.1 通用准则 - -✔️ 考虑在感觉像**基元类型**的类型中定义运算符重载。 - -✔️ 在表示**数字**的结构中定义运算符重载(如 `Decimal`)。 - -✔️ **对称**重载运算符:重载 `==` 则必须重载 `!=`,重载 `<` 则必须重载 `>`。 - -✔️ 考虑为每个重载运算符提供具有**友好名称**的等效方法(因为某些语言不支持运算符重载)。 - -❌ **避免**定义运算符重载,除非类型感觉像基元类型。 - -❌ 定义运算符重载时**不要花哨**,操作结果应显而易见。 - -❌ 除非至少一个操作数属于定义重载的类型,否则**禁止**提供运算符重载。 - -### 7.2 常用运算符与友好方法名对照 - -| 运算符 | 友好方法名 | -|--------|----------| -| `+` (二元) | `Add` | -| `-` (二元) | `Subtract` | -| `*` (二元) | `Multiply` | -| `/` | `Divide` | -| `%` | `Mod` / `Remainder` | -| `==` | `Equals` | -| `!=` | `Equals` | -| `<` / `>` | `CompareTo` | -| `<=` / `>=` | `CompareTo` | -| `++` | `Increment` | -| `--` | `Decrement` | -| `- (一元)` | `Negate` | - -### 7.3 转换运算符 - -❌ 如果最终用户**未明确预期**该转换,禁止提供转换运算符。 - -❌ **禁止**在类型域之外定义转换运算符(如不应将 `Double` 转换为 `DateTime`)。 - -❌ 如果转换**可能丢失数据**,禁止提供隐式转换运算符(可提供显式转换)。 - -❌ **禁止**从隐式强制转换引发异常。 - -✔️ 如果显式强制转换导致有损转换,且运算符契约不允许有损,请抛出 `InvalidCastException`。 - ---- - -## 八、参数设计 - -### 8.1 通用准则 - -✔️ 使用提供成员所需功能的**最低派生参数类型**。 - -> 例如用 `IEnumerable` 而非 `ArrayList` 或 `IList` 作为枚举方法的参数。 - -✔️ 将所有 `out` 参数置于所有按值和 `ref` 参数**之后**。 - -✔️ 在重写成员或实现接口时,参数命名保持**一致**。 - -❌ **禁止**使用保留参数(将来需要更多输入时,添加新重载即可)。 - -❌ **禁止**公开采用指针、指针数组或多维数组作为参数的方法。 - -### 8.2 枚举 vs 布尔参数 - -✔️ 如果不使用枚举则成员会有两个或更多布尔参数,请改用**枚举**。 - -✔️ 考虑为构造函数参数使用布尔值(真正的双状态值,仅用于初始化布尔属性时)。 - -❌ **禁止**使用布尔值,除非绝对确定永远不需要两个以上的值。 - -```csharp -// 错误:布尔参数含义不明确 -Stream stream = File.Open("foo.txt", true, false); - -// 正确:使用枚举,语义清晰 -Stream stream = File.Open("foo.txt", CasingOptions.CaseSensitive, FileMode.Open); -``` - -### 8.3 参数验证 - -✔️ 验证传递到 `public`、`protected` 或显式实现成员的参数,验证失败则抛出 `ArgumentException` 或其子类。 - -✔️ 如果传入 `null` 且成员不支持,抛出 `ArgumentNullException`。 - -✔️ 验证枚举参数(CLR 允许将任何整数值转换为枚举值)。 - -✔️ 注意可变参数在验证后可能已更改(安全敏感成员应先创建副本)。 - -❌ **禁止**使用 `Enum.IsDefined` 进行枚举范围检查。 - -### 8.4 参数传递方式 - -| 方式 | 说明 | -|------|------| -| **按值** (默认) | 成员收到参数副本 | -| **ref** | 成员收到参数引用,可修改调用方传递的参数 | -| **out** | 类似 ref,但初始未赋值,必须在返回前赋值 | - -❌ **避免**使用 `out` 或 `ref` 参数(需要指针经验,且两者区别未被广泛理解)。 - -❌ **禁止**按引用传递引用类型(有限例外:如交换引用的方法)。 - -❌ **禁止**对值类型使用只读引用传递(`in`)。 - -> 结构本身应设计为小型,按值传递的开销很低。使用 `in` 反而增加了 API 的复杂性,收益甚微。 - -### 8.5 可变参数数(params) - -✔️ 如果希望最终用户传递少量元素的数组,考虑添加 `params` 关键字。 - -✔️ 考虑在简单重载中使用 `params`,即使更复杂的重载不使用。 - -✔️ 尝试对参数排序以便使用 `params`。 - -✔️ 考虑在性能敏感的 API 中为少量参数提供特殊重载(避免创建临时数组)。 - -✔️ 注意 `null` 可作为 params 数组传递,处理前应验证非 null。 - -❌ 如果调用方几乎总是已有数组作为输入,不要使用 `params`。 - -❌ 如果成员会修改 params 数组,不要使用 `params`(临时数组的修改会丢失)。 - -❌ **禁止**使用 `varargs`(省略号)方法(不符合 CLS 标准)。 diff --git a/Assets/YooAsset/Docs/3.成员设计准则.md.meta b/Assets/YooAsset/Docs/3.成员设计准则.md.meta deleted file mode 100644 index fe237bbd..00000000 --- a/Assets/YooAsset/Docs/3.成员设计准则.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: f6b12280cb6fd3c42bfc80626a6eaf54 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/YooAsset/Docs/4.扩展设计准则.md b/Assets/YooAsset/Docs/4.扩展设计准则.md deleted file mode 100644 index f44e5214..00000000 --- a/Assets/YooAsset/Docs/4.扩展设计准则.md +++ /dev/null @@ -1,268 +0,0 @@ -# .NET 扩展设计准则 - -> 参考来源:[Microsoft .NET 框架设计准则 - 针对扩展性进行设计](https://learn.microsoft.com/zh-cn/dotnet/standard/design-guidelines/designing-for-extensibility) -> -> 核心原则:**选择满足需求的最低成本扩展机制。可以在将来增加扩展性,但永远无法在不引入重大变更的情况下去除它。** - ---- - -## 一、扩展性机制概述 - -框架中可通过多种方式实现扩展性,从低成本到高成本依次为: - -| 机制 | 成本 | 能力 | 说明 | -|------|------|------|------| -| **非密封类** | 低 | 低 | 允许继承添加便捷成员 | -| **受保护成员** | 低 | 低-中 | 为子类提供高级自定义选项 | -| **事件** | 中 | 中 | 便捷一致的回调语法,与 IDE 集成 | -| **回调(委托)** | 中 | 中 | 允许用户提供自定义执行代码 | -| **虚拟成员** | 中-高 | 中-高 | 允许子类替代行为,适合特化场景 | -| **抽象(抽象类/接口)** | 高 | 高 | 最强大的扩展性,是插件、IoC、管道等模式的核心 | - ---- - -## 二、非密封类 - -默认情况下,类在大多数编程语言中是非密封的,这也是框架中大多数类的推荐默认值。 - -### 设计准则 - -✔️ 考虑使用没有额外虚拟成员或受保护成员的**非密封类**,作为一种为框架提供既经济又受欢迎的扩展性方法。 - -> 开发人员通常希望继承自非密封类,以便添加自定义构造函数、新方法或方法重载。例如 `System.Messaging.MessageQueue` 未密封,允许用户创建默认为特定队列路径的自定义队列。 - -> 非密封类型的测试成本相对较低,框架用户非常赞赏其提供的扩展性。 - ---- - -## 三、受保护成员 - -受保护成员本身不提供扩展性,但可以通过子类化来增强扩展性,用于公开高级自定义选项而无需复杂化主公共接口。 - -### 设计准则 - -✔️ 考虑使用受保护成员进行**高级自定义**。 - -✔️ 出于安全、文档和兼容性分析的目的,应将未密封类中的受保护成员**视为公共成员**。 - -> 任何人都可以继承未密封类并访问其受保护成员,因此公共成员所用的所有防御性编码做法同样适用于受保护成员。 - ---- - -## 四、事件和回调 - -回调是允许框架通过委托回调到用户代码的扩展点,通常通过方法参数传递委托。事件是回调的特例,提供便捷一致的语法。 - -### 设计准则 - -✔️ 考虑使用**回调**来允许用户提供框架要执行的自定义代码。 - -✔️ 考虑使用**事件**来允许用户自定义框架行为,无需了解面向对象设计。 - -✔️ **优先使用事件**而非普通回调,因为事件对更广泛的开发人员更熟悉,且与 Visual Studio 语句完成集成。 - -✔️ 定义带回调的 API 时,使用 `Func<...>`、`Action<...>` 或 `Expression<...>` 类型,而非自定义委托。 - -✔️ 衡量并了解使用 `Expression<...>` 对性能的影响。 - -> `Expression<...>` 在逻辑上等效于 `Func<...>` 和 `Action<...>` 委托,主要区别在于表达式适用于远程进程或计算机中求值的场景。 - -✔️ 理解调用委托即在执行**任意代码**,这可能导致安全性、正确性和兼容性问题。 - -❌ **避免**在性能敏感的 API 中使用回调。 - ---- - -## 五、虚拟成员 - -虚拟成员可以被替代以更改子类行为,在扩展性方面类似回调,但执行性能和内存消耗更优。 - -### 优缺点 - -| 方面 | 说明 | -|------|------| -| **优势** | 性能优于回调和事件;在特化场景中感觉更自然 | -| **劣势** | 行为只能在编译时修改(回调可在运行时修改);设计、测试和维护成本高;任何调用都可能被不可预测地替代 | - -### 设计准则 - -❌ **禁止**无充分理由地使成员成为虚拟成员,需充分了解设计、测试和维护虚拟成员的所有成本。 - -> 虚拟成员对可进行的更改不太宽容,且比非虚拟成员慢(无法内联)。 - -✔️ 将扩展性限制在**绝对必要的范围内**。 - -✔️ 对虚拟成员优先选择 **`protected`** 访问级别而非 `public`。 - -> 公共成员应通过调用受保护的虚拟成员来提供扩展性。这样将虚拟扩展点的范围限定在可使用的位置。 - -```csharp -// 推荐模式:公共方法调用受保护的虚拟方法 -public class Control -{ - public void Draw() - { - // 公共 API 逻辑... - OnDraw(); - } - - protected virtual void OnDraw() - { - // 子类可替代此方法来自定义绘制行为 - } -} -``` - ---- - -## 六、抽象(抽象类型和接口) - -抽象是描述协定但不提供完整实现的类型,通常作为抽象类或接口实现。它们提供最强大的扩展性,是插件、IoC、管道等架构模式的核心。 - -### 设计注意事项 - -- 有意义且经得起时间考验的抽象**很难设计** -- 成员过多 → 难以实现;成员过少 → 许多场景中无用 -- 框架中抽象过多会**负面影响可用性** -- 良好的抽象对框架的**可测试性**非常重要(便于单元测试中替代依赖) - -### 设计准则 - -❌ **禁止**提供抽象,除非已通过开发**多个具体实现和 API** 对其进行测试验证。 - -✔️ 设计抽象时,在**抽象类和接口**之间仔细选择。 - -✔️ 考虑为抽象的具体实现提供**参考测试**,允许用户验证其实现是否正确实现了协定。 - ---- - -## 七、实现抽象的基类 - -基类主要设计为提供通用抽象或其他类的默认实现,位于继承层次结构的中间——根部的抽象和底部的自定义实现之间。 - -### 典型场景 - -``` -IList ← 抽象(接口) - ↑ -Collection ← 基类(提供默认实现) - ↑ -MyCustomCollection ← 具体实现 -``` - -> 例如 `Collection` 和 `KeyedCollection` 是 `IList` 的实现辅助基类,降低了实现自定义集合的难度。 - -### 设计准则 - -✔️ 考虑使基类为 **`abstract`**,即使不包含抽象成员。 - -> 清楚地向用户传达该类专为继承设计。 - -✔️ 考虑将基类放置在**独立于主线场景类型的命名空间**中。 - -> 基类适用于高级扩展性场景,对大多数用户不感兴趣。 - -❌ 如果类用于公共 API,**避免**使用 `Base` 后缀命名基类。 - -> 仅当基类为框架用户提供重要价值时才应使用。如果只对框架实现者有价值,应考虑**委托内部实现**而非继承。 - ---- - -## 八、密封 - -密封是限制扩展性的强大机制,可以密封整个类或单个成员。 - -### 8.1 密封类 - -❌ **禁止**无充分理由地密封类。 - -> "想不出扩展性场景"不是充分理由。框架用户喜欢出于各种原因继承类(如添加便捷成员)。 - -**密封类的充分理由**: - -| 理由 | 说明 | -|------|------| -| 静态类 | 静态类应声明为密封 | -| 安全敏感的受保护成员 | 类在继承的受保护成员中存储安全敏感信息 | -| 大量虚拟成员 | 逐个密封的成本超过保持非密封的好处 | -| 需要快速运行时查找的属性类 | 密封属性的性能略高于非密封的 | - -### 8.2 密封成员 - -✔️ 考虑**密封你替代(override)的成员**。 - -> 引入虚拟成员可能导致的问题同样适用于替代(尽管程度稍低),密封替代可从继承层次结构的该点开始避免这些问题。 - -### 8.3 密封类型的约束 - -❌ **禁止**在密封类型上声明 `protected` 成员或 `virtual` 成员。 - -> 密封类型不可被继承,受保护成员无法被调用,虚拟方法无法被替代。 - ---- - -## 九、模板方法模式 - -模板方法模式允许子类在保留算法整体结构的同时重新定义其中的特定步骤。其核心目标是**控制扩展性**——将扩展点集中到单个受保护的虚拟方法中。 - -### 设计准则 - -❌ **避免**将 `public` 成员设为 `virtual`。 - -✔️ 考虑使用**模板方法模式**提供更可控的扩展性。 - -✔️ 为非虚公共成员提供扩展点的受保护虚拟方法,命名时使用 **`Core` 后缀**或 **`Internal` 前缀**。 - -✔️ 在非虚方法中完成**参数验证和状态检查**,然后再调用虚拟方法。 - -```csharp -// 示例1:Core 后缀风格 -public class Control -{ - // 公共非虚方法:执行验证,调用虚拟扩展点 - public void SetBounds(int x, int y, int width, int height) - { - if (width < 0) throw new ArgumentOutOfRangeException(nameof(width)); - if (height < 0) throw new ArgumentOutOfRangeException(nameof(height)); - - SetBoundsCore(x, y, width, height); - } - - // 受保护虚拟方法:子类可替代的扩展点 - protected virtual void SetBoundsCore(int x, int y, int width, int height) - { - // 默认实现... - } -} - -// 示例2:Internal 前缀风格 -public abstract class AsyncOperationBase -{ - internal void StartOperation() - { - // 状态检查、异常隔离... - InternalStart(); - } - - protected abstract void InternalStart(); -} -``` - -> 非虚公共方法可确保特定代码在虚拟成员调用前后执行,并确保虚拟成员按固定顺序执行。常见错误是将多个重载都设为虚拟方法——应将扩展性集中到单个方法。 - ---- - -## 十、扩展机制选择速查表 - -| 需求 | 推荐机制 | -|------|---------| -| 允许用户添加便捷方法 | 非密封类 | -| 提供高级自定义选项 | 受保护成员 | -| 允许用户自定义行为(不需要 OOP 知识) | 事件 | -| 允许用户提供自定义执行逻辑 | 回调(Func/Action) | -| 允许子类特化行为(编译时) | 虚拟成员 | -| 定义可由多种类型实现的协定 | 抽象类 / 接口 | -| 降低实现抽象的难度 | 基类 | -| 限制扩展性 | 密封(sealed) | - -> **原则**:优先选择低成本机制。在不确定时保持非密封,因为增加扩展性容易,但移除扩展性会引入破坏性变更。 diff --git a/Assets/YooAsset/Docs/5.异常设计准则.md b/Assets/YooAsset/Docs/5.异常设计准则.md deleted file mode 100644 index 1820e7fe..00000000 --- a/Assets/YooAsset/Docs/5.异常设计准则.md +++ /dev/null @@ -1,255 +0,0 @@ -# .NET 异常设计准则 - -> 参考来源:[Microsoft .NET 框架设计准则 - 异常设计准则](https://learn.microsoft.com/zh-cn/dotnet/standard/design-guidelines/exceptions) -> -> 核心原则:**异常处理比基于返回值的错误报告具有更多优势。框架应使用异常报告所有错误条件,包括执行错误。** - ---- - -## 一、异常引发 - -### 1.1 基本原则 - -✔️ **必须**通过引发异常来报告执行失败。 - -> 当成员无法完成其设计用途(成员名称所暗示的内容)时,就是执行失败。例如 `OpenFile` 无法返回打开的文件句柄。 - -✔️ 如果代码遇到进一步执行不安全的情况,考虑调用 `System.Environment.FailFast` 终止进程,而不是引发异常。 - -✔️ 考虑引发异常对性能的影响。 - -> **每秒抛出速率超过 100 次**可能会显著影响大多数应用程序的性能。 - -✔️ **必须**记录所有由公开成员因违反成员协定而引发的异常,并将其视为协定的一部分。 - -> 协定中的异常不应在版本间更改(异常类型不变、不添加新异常)。 - -❌ **禁止**返回错误代码。异常是框架中报告错误的主要方法。 - -❌ **禁止**对正常控制流使用异常(系统故障和争用条件除外)。 - -❌ **禁止**让公共成员根据某些选项决定是否引发异常。 - -❌ **禁止**让公共成员将异常作为返回值或 `out` 参数返回。 - -### 1.2 异常引发的代码组织 - -✔️ 考虑使用**异常生成器方法**(Exception Builder)。 - -> 从不同位置引发相同异常是常见的。使用辅助方法创建并初始化异常,可避免代码膨胀,并允许引发异常的成员被内联。 - -```csharp -// 异常生成器方法示例 -private static void ThrowArgumentNullException(string paramName) -{ - throw new ArgumentNullException(paramName); -} - -public void DoSomething(string value) -{ - if (value == null) - ThrowArgumentNullException(nameof(value)); - // ... 方法逻辑(可被内联) -} -``` - -❌ **禁止**从异常筛选器块(exception filter block)中引发异常。 - -> 筛选器中引发的异常会被 CLR 捕获,导致筛选器返回 false,难以调试。 - -❌ **避免**从 `finally` 块显式引发异常(由方法调用隐式引发的可以接受)。 - ---- - -## 二、使用标准异常类型 - -### 2.1 Exception 和 SystemException - -❌ **禁止**引发 `System.Exception` 或 `System.SystemException`。 - -❌ **禁止**在框架代码中捕获 `System.Exception` 或 `System.SystemException`,除非打算重新引发。 - -❌ **避免**捕获 `System.Exception` 或 `System.SystemException`(顶级异常处理程序除外)。 - -### 2.2 ApplicationException - -❌ **禁止**引发或派生自 `ApplicationException`。 - -### 2.3 InvalidOperationException - -✔️ 如果对象处于**不适当的状态**,引发 `InvalidOperationException`。 - -```csharp -// 示例:对象状态不正确 -public void Start() -{ - if (_isRunning) - throw new InvalidOperationException("服务已在运行中。"); - // ... -} -``` - -### 2.4 ArgumentException 系列 - -✔️ 如果传递给成员的参数错误,引发 `ArgumentException` 或其子类型之一,优先使用**派生程度最高的异常类型**。 - -✔️ 引发 `ArgumentException` 子类时,**必须**设置 `ParamName` 属性。 - -✔️ 使用 `value` 作为属性 setter 隐式值参数的名称。 - -| 异常类型 | 使用场景 | -|---------|---------| -| `ArgumentException` | 参数值不合法 | -| `ArgumentNullException` | 参数为 null 但不支持 null | -| `ArgumentOutOfRangeException` | 参数值超出有效范围 | - -```csharp -// 推荐用法 -public void SetAge(int age) -{ - if (age < 0 || age > 150) - throw new ArgumentOutOfRangeException(nameof(age), age, "年龄必须在 0-150 之间。"); -} - -public void SetName(string name) -{ - if (name == null) - throw new ArgumentNullException(nameof(name)); - if (name.Length == 0) - throw new ArgumentException("名称不能为空。", nameof(name)); -} -``` - -### 2.5 由 CLR 保留的异常(禁止显式引发) - -以下异常仅由 CLR 基础设施引发,**禁止在用户代码中显式引发或捕获**: - -| 异常类型 | 说明 | -|---------|------| -| `NullReferenceException` | 空引用访问(应通过参数检查避免) | -| `IndexOutOfRangeException` | 数组越界(应通过参数检查避免) | -| `AccessViolationException` | 非法内存访问 | -| `StackOverflowException` | 栈溢出(禁止引发,禁止捕获) | -| `OutOfMemoryException` | 内存不足 | -| `COMException` | COM 互操作异常 | -| `SEHException` | 结构化异常处理 | -| `ExecutionEngineException` | 执行引擎异常 | - -> 这些异常指示代码中的 bug 或 CLR 级别的故障,显式引发它们会暴露方法的实现细节。应通过参数检查来预防。 - -### 2.6 框架内部异常(YooInternalException) - -`YooInternalException` 用于框架内部不变量断言,表示"理论上不可能到达的代码路径"。它与 `ArgumentException` 系列的区别在于**语义和受众**不同: - -| 异常类型 | 语义 | 适用场景 | -|---------|------|---------| -| `ArgumentException` 系列 | 调用方传入了错误参数 | 公共 API 的参数校验 | -| `InvalidOperationException` | 对象处于不适当的状态 | 公共 API 的状态校验 | -| `YooInternalException` | 框架内部逻辑错误,理论不可达 | 内部方法的不变量断言 | - -✔️ 在 `internal` 方法或框架内部流程中,对理论上不可能发生的状态使用 `YooInternalException`。 - -✔️ 优先使用 `YooInternalException` 而非 `Debug.Assert`,确保在所有构建模式下均能捕获内部逻辑错误。 - -❌ **禁止**在公共 API 的参数校验中使用 `YooInternalException`,应使用 `ArgumentException` 系列。 - ---- - -## 三、异常和性能 - -### 3.1 性能问题 - -异常在引发时性能可能慢几个数量级。在严格遵循准则的同时,可通过两种设计模式实现良好性能。 - -❌ **禁止**因担心异常对性能的影响而使用错误代码。 - -### 3.2 Tester-Doer 模式(测试者-执行者) - -将可能引发异常的成员拆分为两部分:先测试条件,再执行操作。 - -```csharp -// Tester-Doer 模式 -ICollection numbers = ...; - -// Tester:先检查前置条件 -if (!numbers.IsReadOnly) -{ - // Doer:再执行操作 - numbers.Add(1); -} -``` - -| 角色 | 说明 | 示例 | -|------|------|------| -| **Tester(测试者)** | 检查前置条件是否满足 | `IsReadOnly`、`CanSeek`、`ContainsKey` | -| **Doer(执行者)** | 执行可能引发异常的操作 | `Add`、`Seek`、`GetValue` | - -✔️ 考虑对在**常见场景中可能引发异常**的成员使用 Tester-Doer 模式。 - -### 3.3 Try-Parse 模式(尝试-分析) - -对于**极其性能敏感**的 API,使用比 Tester-Doer 更快的 Try-Parse 模式。 - -```csharp -// Try-Parse 模式 -public struct DateTime -{ - // 失败时引发异常 - public static DateTime Parse(string dateTime) { ... } - - // 失败时返回 false,成功通过 out 参数返回结果 - public static bool TryParse(string dateTime, out DateTime result) { ... } -} - -// 使用方式 -if (DateTime.TryParse(input, out var date)) -{ - // 解析成功 -} -else -{ - // 解析失败,无异常开销 -} -``` - -✔️ 对在常见场景中可能引发异常的成员使用 Try-Parse 模式。 - -✔️ 方法命名使用 **`Try` 前缀**和 **`bool` 返回类型**。 - -✔️ 为每个 Try-Parse 成员提供一个对应的**异常引发版本**。 - -> 例如 `Parse` / `TryParse`、`GetValue` / `TryGetValue`。 - ---- - -## 四、速查表 - -### 4.1 何时引发异常 - -| 场景 | 行为 | -|------|------| -| 成员无法完成其设计用途 | 引发异常 | -| 参数为 null(不支持时) | 引发 `ArgumentNullException` | -| 参数超出有效范围 | 引发 `ArgumentOutOfRangeException` | -| 参数值不合法 | 引发 `ArgumentException` | -| 对象状态不正确 | 引发 `InvalidOperationException` | -| 操作不被支持 | 引发 `NotSupportedException` | -| 进一步执行不安全 | 调用 `Environment.FailFast` | - -### 4.2 禁止做的事情 - -| 禁止 | 原因 | -|------|------| -| 返回错误代码 | 异常是唯一的错误报告方式 | -| 用异常做控制流 | 异常仅用于异常情况 | -| 引发 `Exception` / `SystemException` | 过于笼统 | -| 引发 CLR 保留异常 | 由运行时专用 | -| 在 finally 中显式引发 | 可能掩盖原始异常 | -| 在异常筛选器中引发 | 难以调试 | - -### 4.3 性能优化模式选择 - -| 模式 | 适用场景 | 示例 | -|------|---------|------| -| **Tester-Doer** | 常见场景中的条件检查 | `if (!IsReadOnly) Add(item)` | -| **Try-Parse** | 极其性能敏感的 API | `TryParse(input, out result)` | diff --git a/Assets/YooAsset/Docs/5.异常设计准则.md.meta b/Assets/YooAsset/Docs/5.异常设计准则.md.meta deleted file mode 100644 index 379adbd5..00000000 --- a/Assets/YooAsset/Docs/5.异常设计准则.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: d10f50d328fa47f44ad92a7b89f3d757 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/YooAsset/Docs/6.设计模式准则.md b/Assets/YooAsset/Docs/6.设计模式准则.md deleted file mode 100644 index adb691ac..00000000 --- a/Assets/YooAsset/Docs/6.设计模式准则.md +++ /dev/null @@ -1,335 +0,0 @@ -# .NET 设计模式准则 - -> 参考来源:《框架设计准则:可重用 .NET 库的约定、惯例和模式(第3版)》第9章 -> -> 核心原则:**框架设计者应提供完整的端到端解决方案,而非仅提供 API。高级 API 应当"开箱即用",用户无需了解底层复杂性。** - ---- - -## 一、聚合组件设计 - -聚合组件将多个底层分解类型(Factored Types)绑定到一个更高级别的组件中,为常见场景提供简化入口。它是开发者探索其命名空间的**起点**。 - -### 1.1 Create-Set-Call 使用模式 - -所有聚合组件都应支持此模式: - -```csharp -// 1. Create:使用默认或简单构造函数创建 -var queue = new MessageQueue(); -// 2. Set:设置属性 -queue.Path = @".\private$\orders"; -// 3. Call:调用方法 -queue.Send("Hello World"); -``` - -### 1.2 聚合组件 vs 分解类型 - -| 特征 | 聚合组件 | 分解类型 | -|------|---------|---------| -| **目的** | 简化常见场景 | 提供精细控制 | -| **状态** | 可以有模式和临时无效状态 | 不应有模式,生命周期清晰 | -| **构造** | 默认/简单构造函数 | 可能需要复杂初始化 | -| **可用性优先级** | 最高 | 较低 | -| **示例** | `SerialPort`、`MessageQueue` | `Stream`、`GZipStream` | - -```csharp -// 聚合组件暴露内部分解类型以支持高级场景 -var port = new SerialPort("COM1"); -port.Open(); -GZipStream compressed = new GZipStream(port.BaseStream, CompressionMode.Compress); -compressed.Write(data, 0, data.Length); -port.Close(); -``` - -### 1.3 设计准则 - -✔️ 考虑为常用功能区域提供聚合组件。 - -✔️ 设计聚合组件以便**极简初始化后即可使用**。如果需要初始化,未初始化时引发的异常应清楚说明需要做什么。 - -✔️ **必须**支持 Create-Set-Call 使用模式。 - -✔️ 为所有构造函数参数提供对应的可读写属性,确保始终可以用默认构造函数 + 属性设置代替参数化构造函数。 - -✔️ 在聚合组件中使用**事件**代替委托 API 和需要重写的虚拟成员。 - -✔️ 选择最具"吸引力"的名称给聚合组件(如 `File`),将较不直观的名称留给分解类型(如 `StreamReader`)。 - -❌ **禁止**在常见场景中要求用户继承、重写方法或实现接口。 - -❌ **禁止**在常见场景中要求用户做编写代码以外的事(如配置文件、生成资源文件等)。 - -❌ **禁止**将分解类型设计为有模式的,它们应有明确的生命周期。 - -✔️ 当用户在无效状态下调用方法时,抛出 `InvalidOperationException`,异常消息应清楚说明需要更改哪些属性。 - ---- - -## 二、异步编程准则 - -### 2.1 异步方法返回类型 - -✔️ 考虑在方法**经常同步完成**时返回 `ValueTask`。 - -> 例如 `Stream.ReadAsync` 由于缓冲区中常有可用数据,经常同步完成。使用 `ValueTask` 避免了每次调用都分配 `Task` 对象的开销。 - -```csharp -// ValueTask 适用于经常同步完成的场景 -public virtual ValueTask ReadAsync( - Memory buffer, - CancellationToken cancellationToken = default); -``` - -> 注意:当方法确实异步完成时,`ValueTask` 仍然需要创建 `Task` 实例。对于始终异步完成的操作,`ValueTask` 会带来轻微的性能损失和可用性降低,此时应使用 `Task`。 - -### 2.2 ConfigureAwait - -✔️ 在 async 方法中等待异步操作时,使用 `await task.ConfigureAwait(false)`,除非在依赖同步上下文的应用模型中。 - -> 默认情况下,.NET 使用 `SynchronizationContext.Current` 决定如何继续处理 Task。控制台应用中,Task 完成回调在后台线程上执行。WinForms/WPF 中,默认将所有回调调度到 UI 线程。`ConfigureAwait(false)` 避免了不必要的线程切换。 - -### 2.3 避免死锁 - -❌ **禁止**在 async 方法中调用 `Task.Wait()` 或读取 `Task.Result` 属性,应使用 `await`。 - -> `await` 会让出当前 Task,允许其他 Task 继续。`Task.Wait()` 和 `Task.Result` 执行阻塞式同步等待,效率低下且在某些场景下会导致死锁。 - -✔️ 在 async 方法的实现中,调用**异步方法变体**而非同步变体。 - -### 2.4 CancellationToken 取消准则 - -✔️ 因 `CancellationToken` 取消工作时,抛出 `OperationCanceledException`。 - -```csharp -// 两种等效写法 -if (cancellationToken.IsCancellationRequested) - throw new OperationCanceledException(cancellationToken); - -// 或者 -cancellationToken.ThrowIfCancellationRequested(); -``` - -### 2.5 异步方法中的异常 - -✔️ 将用法错误(如参数验证)的异常直接从非 async 包装方法中抛出,以提高可调试性。 - -```csharp -// 正确:异常直接抛出,不包装在 Task 中 -public Task SaveAsync(string filename) -{ - if (filename == null) - throw new ArgumentNullException(nameof(filename)); - return SaveAsyncCore(filename); -} - -private async Task SaveAsyncCore(string filename) -{ - // 实际异步工作... -} -``` - -```csharp -// 错误:异常在 Task 内部抛出,调用栈不清晰 -public async Task SaveAsync(string filename) -{ - if (filename == null) - throw new ArgumentNullException(nameof(filename)); - // ... -} -``` - -### 2.6 IAsyncEnumerable\ - -✔️ 在使用 `yield return` 的 `IAsyncEnumerable` 方法中,为 `CancellationToken` 参数添加 `[EnumeratorCancellation]` 特性。 - -> 不添加此特性时,`GetAsyncEnumerator` 传入的 `CancellationToken` 值会被编译器生成的实现忽略。 - -```csharp -public static async IAsyncEnumerable GenerateValues( - int start, int count, - [EnumeratorCancellation] CancellationToken cancellationToken = default) -{ - int end = start + count; - for (int i = start; i < end; i++) - { - await Task.Delay(i, cancellationToken).ConfigureAwait(false); - yield return i; - } -} -``` - -✔️ 对 `IAsyncEnumerable` 参数使用 `await foreach` 时,使用 `WithCancellation` 传递 `CancellationToken`。 - -```csharp -await foreach (int value in source.WithCancellation(cancellationToken).ConfigureAwait(false)) -{ - // ... -} -``` - ---- - -## 三、Dispose 模式与 IAsyncDisposable - -### 3.1 基本 Dispose 模式 - -托管内存只是系统资源的一种。文件句柄、网络连接、数据库连接等非托管资源仍需显式释放。 - -终结器(Finalizer)的缺点: -- GC 在**不确定的时间**才调用终结器,延迟可能不可接受 -- 需要终结的对象的内存回收会被**推迟到下一轮 GC** - -✔️ 考虑在本身不持有非托管资源的类上实现基本 Dispose 模式,如果其**子类型可能持有**。 - -> 例如 `System.IO.Stream` 是一个不持有资源的抽象基类,但其大多数子类持有资源。 - -✔️ 在对象被释放后,从任何不能使用的成员中抛出 `ObjectDisposedException`。 - -✔️ 考虑提供 `Close()` 方法作为 `Dispose()` 的补充(如果"close"是该领域的标准术语)。 - -### 3.2 作用域操作 - -✔️ 考虑返回一个 disposable 值,代替让调用者手动配对 "begin" 和 "end" 方法。 - -```csharp -// 使用 disposable 作用域代替 begin/end 配对 -using (var scope = logger.BeginScope("Processing")) -{ - // 作用域内的操作... -} // 自动结束作用域 -``` - -### 3.3 IAsyncDisposable - -`IAsyncDisposable` 允许在 `Dispose()` 涉及 I/O 或其他阻塞操作时使用异步方法进行资源清理。 - -```csharp -public partial class SomeType : IDisposable, IAsyncDisposable -{ - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) { /* ... */ } - - public async ValueTask DisposeAsync() - { - await DisposeAsyncCore(); - Dispose(false); - GC.SuppressFinalize(this); - } - - protected virtual ValueTask DisposeAsyncCore() { /* ... */ } -} -``` - -> 同时实现 `IDisposable` 和 `IAsyncDisposable` 时,`DisposeAsync` 调用 `Dispose(false)` 以避免同步释放路径重复执行。 - ---- - -## 四、Span / Memory 缓冲区操作 - -### 4.1 Span\ 和 ReadOnlySpan\ - -`Span` 表示来自托管数组、stackalloc 数组、指针或字符串的连续内存,支持 O(1) 切片操作。 - -✔️ 考虑使用 `Span` 作为缓冲区的表示。 - -✔️ 尽可能使用 `ReadOnlySpan` 代替 `Span`。 - -❌ **避免**返回 Span 或 ReadOnlySpan,除非返回值的生命周期非常明确。 - -✔️ 对返回的、非来自调用方的 Span 提供**非常清晰的所有权规则文档**。 - -✔️ 考虑从 get-only 属性或无参方法返回 `ReadOnlySpan` 来表示固定数据(当 T 是不可变类型时)。 - -✔️ 考虑返回 `System.Range`(表示 Span 参数的边界)而非参数的切片。 - -> `Span` 可以转换为 `ReadOnlySpan`,但反过来不行。如果方法接受 `ReadOnlySpan` 并返回切片,调用方无法轻松确定其可写 Span 的等效切片。 - -### 4.2 Memory\ 和 ReadOnlyMemory\ - -`Span` 是 `ref struct`,不能在异步方法中使用。需要存储缓冲区时应使用 `Memory`。 - -✔️ 在异步方法中使用 `ReadOnlyMemory` 代替 `ReadOnlySpan`。 - -✔️ 在异步方法中使用 `Memory` 代替 `Span`。 - -✔️ 当构造函数或方法的目的是**保存缓冲区引用**时,使用 `Memory` / `ReadOnlyMemory` 作为参数。 - -### 4.3 命名和重载 - -✔️ 对接受输出缓冲区和单个输入缓冲区的方法,输入缓冲区参数命名为 **`source`**,输出缓冲区参数命名为 **`destination`**。 - -❌ **避免**在 `Span` 和 `ReadOnlySpan` 之间,或 `Memory` 和 `ReadOnlyMemory` 之间重载同一方法。 - -> 如果一个方法以只读方式操作缓冲区,另一个以读写方式操作,它们应有不同的方法名称。 - ---- - -## 五、工厂模式 - -工厂是抽象对象创建过程的操作或操作集合,允许专用语义和更细粒度的实例化控制。 - -### 设计准则 - -✔️ **优先使用构造函数**而非工厂,因为构造函数通常更可用、一致和方便。 - -✔️ 当工厂方法声明在独立的工厂类型上时,考虑使用 **`Create` + 类型名称** 命名。 - -> 例如创建按钮的工厂方法命名为 `CreateButton`。在某些情况下可使用领域特定名称,如 `File.Open`。 - -✔️ 考虑使用 **类型名称 + `Factory`** 命名工厂类型。 - -> 例如创建 `Control` 对象的工厂类型命名为 `ControlFactory`。 - ---- - -## 六、超时处理 - -✔️ **优先使用方法参数**作为用户指定超时时间的机制。 - -```csharp -server.PerformOperation(timeout); -``` - -> 备选方案是使用属性: - -```csharp -server.Timeout = timeout; -server.PerformOperation(); -``` - -✔️ 优先使用 `TimeSpan` 表示超时时间。 - -✔️ 在 Unity 项目中,可以使用 `long` 毫秒数代替 `TimeSpan` 表示超时或时间切片,以避免值类型转换开销。 - -✔️ 超时到期时抛出 `System.TimeoutException`。 - -❌ **禁止**使用错误代码指示超时到期。 - ---- - -## 七、LINQ 支持 - -✔️ 实现 `IEnumerable` 以启用基本 LINQ 支持。 - -```csharp -public class RangeOfInt32s : IEnumerable -{ - public IEnumerator GetEnumerator() { /* ... */ } - IEnumerator IEnumerable.GetEnumerator() { /* ... */ } -} - -// 实现 IEnumerable 后即可使用 LINQ -var result = new RangeOfInt32s().Where(x => x > 10); -``` - -✔️ 考虑实现 `ICollection` 以提升查询运算符的性能。 - -✔️ 将查询扩展方法放在主命名空间的 **`Linq` 子命名空间**中。 - -> 例如 `System.Data` 功能的扩展方法应放在 `System.Data.Linq` 命名空间中。 diff --git a/Assets/YooAsset/Docs/6.设计模式准则.md.meta b/Assets/YooAsset/Docs/6.设计模式准则.md.meta deleted file mode 100644 index 96b1349f..00000000 --- a/Assets/YooAsset/Docs/6.设计模式准则.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 1ddd0c64da4a16244bf65c56c9d403b2 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/YooAsset/Docs/7.使用指南.md b/Assets/YooAsset/Docs/7.使用指南.md deleted file mode 100644 index 7685d936..00000000 --- a/Assets/YooAsset/Docs/7.使用指南.md +++ /dev/null @@ -1,390 +0,0 @@ -# .NET 使用指南 - -> 参考来源:[Microsoft .NET 框架设计准则 - 使用指南](https://learn.microsoft.com/zh-cn/dotnet/standard/design-guidelines/usage-guidelines) -> -> 核心原则:**在公开 API 中正确使用内置框架类型,优先选择抽象和接口而非具体实现类型。** - ---- - -## 一、数组 - -✔️ 在公共 API 中优先使用**集合**而不是数组。 - -✔️ 考虑使用**交错数组**(`int[][]`)而不是多维数组(`int[,]`)。 - -> 交错数组的元素大小可以不同,浪费空间更少。此外 CLR 优化了交错数组的索引操作,在某些场景中性能更好。 - -❌ **禁止**使用只读数组字段。 - -> 字段本身是只读的不能被替换,但数组中的元素仍可被修改。 - -```csharp -// 错误:数组元素仍可被外部修改 -public static readonly int[] InvalidValues = { 1, 2, 3 }; - -// 正确:使用 ReadOnlyCollection 或 ImmutableArray -public static readonly ReadOnlyCollection InvalidValues = - new ReadOnlyCollection(new[] { 1, 2, 3 }); -``` - ---- - -## 二、特性(Attribute) - -特性是可添加到程序集、类型、成员和参数的元数据注释,通过反射 API 在运行时访问。 - -### 设计准则 - -✔️ 使用 `Attribute` 后缀命名自定义特性类。 - -✔️ **必须**对自定义特性应用 `[AttributeUsage]`。 - -✔️ 为**必需参数**提供只读属性和对应的构造函数参数。 - -✔️ 为**可选参数**提供可读写属性(通过 setter 设置)。 - -✔️ 如果可能,请**密封**自定义特性类(使查找速度更快)。 - -❌ **避免**重载自定义特性的构造函数(单一构造函数更清晰)。 - -❌ **避免**提供构造函数参数来初始化可选属性(不要同时支持构造函数和 setter 两种方式设置同一属性)。 - -```csharp -// 推荐设计 -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] -public sealed class MyCustomAttribute : Attribute -{ - // 必需参数:通过构造函数 + 只读属性 - public MyCustomAttribute(string name) - { - Name = name; - } - - public string Name { get; } - - // 可选参数:通过可读写属性 - public int Priority { get; set; } -} - -// 使用方式 -[MyCustom("example", Priority = 5)] -public class MyClass { } -``` - ---- - -## 三、集合 - -### 3.1 公共 API 中的集合选择 - -❌ **禁止**在公共 API 中使用弱类型集合。 - -> 所有表示集合项的返回值和参数都应使用确切的项类型。 - -❌ **禁止**在公共 API 中使用 `ArrayList` 或 `List`。 - -> 这些是为内部实现设计的数据结构。`List` 牺牲了 API 简洁性和灵活性来优化性能。 - -❌ **禁止**在公共 API 中使用 `Hashtable` 或 `Dictionary`。 - -> 应使用 `IDictionary`、`IDictionary` 或实现这些接口的自定义类型。 - -❌ **禁止**在同一类型上同时实现 `IEnumerator` 和 `IEnumerable`。 - -❌ **禁止**从非 `GetEnumerator` 方法返回枚举器类型。 - -### 3.2 集合参数 - -✔️ 尽可能使用**最不专用的类型**作为参数类型。 - -> 大多数以集合为参数的成员应使用 `IEnumerable` 接口。 - -❌ **避免**仅为访问 `Count` 属性而使用 `ICollection` 作为参数。 - -> 应使用 `IEnumerable`,并动态检查是否实现了 `ICollection`。 - -### 3.3 集合属性和返回值 - -❌ **禁止**提供可设置的集合属性。 - -> 用户可以通过先清除再添加来替换内容。如需替换整个集合,提供 `AddRange` 方法。 - -✔️ 对读写集合的属性或返回值使用 `Collection` 或其子类。 - -✔️ 对只读集合使用 `ReadOnlyCollection` 或其子类。 - -❌ **禁止**从集合属性或返回集合的方法中返回 `null`。 - -> **应返回空集合或空数组**。一般规则:null 和空集合应被视为相同。 - -### 3.4 快照集合 vs 实时集合 - -| 类型 | 说明 | 示例 | -|------|------|------| -| **快照集合** | 表示某个时间点的状态 | 数据库查询结果 | -| **实时集合** | 始终表示当前状态 | ComboBox 的 Items | - -❌ **禁止**从属性返回快照集合(getter 应轻量,快照需要 O(n) 复制)。 - -✔️ 使用快照集合或实时 `IEnumerable` 表示可变集合(无需显式修改即可变化的集合)。 - -### 3.5 数组 vs 集合选择 - -✔️ 在大多数情况下**优先选择集合**——更好的内容控制、更易使用。 - -✔️ 在**低级 API** 中考虑使用数组以最小化内存消耗和最大化性能。 - -✔️ 使用**字节数组**(`byte[]`)而非字节集合。 - -❌ 如果每次调用属性 getter 都需要返回新数组(如内部数组的副本),**禁止**对属性使用数组。 - -### 3.6 自定义集合设计 - -✔️ 设计新集合时考虑继承自 `Collection`、`ReadOnlyCollection` 或 `KeyedCollection`。 - -✔️ **必须**实现 `IEnumerable`,按需考虑实现 `ICollection` 或 `IList`。 - -✔️ 如果集合项具有唯一键,考虑使用**键控集合**(继承自 `KeyedCollection`)。 - -❌ **禁止**继承自非泛型基集合(如 `CollectionBase`)。 - -❌ **避免**在与集合概念无关的复杂 API 类型上实现集合接口。 - -### 3.7 自定义集合命名 - -✔️ 实现 `IDictionary` / `IDictionary` 的类型使用 **`Dictionary`** 后缀。 - -✔️ 实现 `IEnumerable` 且表示项列表的类型使用 **`Collection`** 后缀。 - -✔️ 考虑用项类型名称作为集合名称前缀:`AddressCollection`。 - -✔️ 只读集合考虑使用 **`ReadOnly`** 前缀:`ReadOnlyStringCollection`。 - -❌ **避免**使用表示具体实现的后缀(如 `LinkedList`、`Hashtable`)。 - ---- - -## 四、序列化 - -序列化是将对象转换为可持久保存或传输格式的过程。 - -### 4.1 三种序列化技术 - -| 技术 | 主要类型 | 适用场景 | -|------|---------|---------| -| **数据协定序列化** | `[DataContract]` / `[DataMember]` / `DataContractSerializer` | 一般持久性、Web 服务、JSON | -| **XML 序列化** | `XmlSerializer` | 需要完全控制 XML 形状 | -| **运行时序列化** | `[Serializable]` / `ISerializable` / `BinaryFormatter` | .NET 远程处理 | - -### 4.2 选择准则 - -✔️ 设计新类型时,考虑序列化需求。 - -✔️ 如果类型需要持久保存或用于 Web 服务,考虑支持**数据协定序列化**。 - -✔️ 如果需要完全控制 XML 形状,考虑支持 **XML 序列化**。 - -✔️ 如果类型需要跨 .NET 远程处理边界传输,考虑支持**运行时序列化**。 - -❌ **避免**仅出于一般持久性原因支持运行时序列化或 XML 序列化,应优先使用数据协定序列化。 - -### 4.3 数据协定序列化准则 - -✔️ 如果类型可在部分信任中使用,考虑将数据成员标记为 `public`。 - -✔️ 具有 `[DataMember]` 的属性**必须**同时具有 getter 和 setter。 - -✔️ 考虑使用序列化回调初始化反序列化后的实例(反序列化时不调用构造函数)。 - -> 使用 `[OnDeserialized]`、`[OnDeserializing]`、`[OnSerializing]`、`[OnSerialized]` 特性。 - -✔️ 考虑使用 `[KnownType]` 指示反序列化复杂对象图时应使用的具体类型。 - -✔️ 创建或更改可序列化类型时,考虑**向后和向前兼容性**。 - -✔️ 考虑实现 `IExtensibleDataObject` 以允许不同版本间的往返序列化。 - -### 4.4 XML 序列化准则 - -❌ **避免**专门为 XML 序列化设计类型(已被数据协定序列化取代)。 - -✔️ 如果需要精细控制 XML 形状,考虑实现 `IXmlSerializable` 接口。 - -### 4.5 运行时序列化准则 - -✔️ 如果需要完全控制序列化过程,实现 `ISerializable` 并提供序列化构造函数。 - -✔️ 序列化构造函数应为 `protected`。 - -✔️ **必须**显式实现 `ISerializable` 成员。 - -```csharp -[Serializable] -public class Person : ISerializable -{ - public string Name { get; set; } - - public Person() { } - - // 序列化构造函数:protected - protected Person(SerializationInfo info, StreamingContext context) - { - Name = info.GetString(nameof(Name)); - } - - // 显式实现 - void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) - { - info.AddValue(nameof(Name), Name); - } -} -``` - ---- - -## 五、System.Xml 用法 - -❌ **禁止**使用 `XmlNode` 或 `XmlDocument` 表示 XML 数据。 - -✔️ 使用 `XmlReader`、`XmlWriter`、`IXPathNavigable` 或 `XNode` 子类型作为接受或返回 XML 的成员的输入输出。 - -> 使用抽象类型可以将方法与内存中 XML 文档的具体实现分离,允许与虚拟 XML 数据源一起工作。 - -❌ 如果要创建表示基础对象模型的 XML 视图,**禁止**继承 `XmlDocument`。 - ---- - -## 六、相等运算符 - -### 6.1 通用准则 - -❌ **禁止**只重载 `==` 和 `!=` 中的一个而忽略另一个(必须成对重载)。 - -✔️ 确保 `Object.Equals` 和相等运算符具有**相同的语义和相似的性能**。 - -> 重载相等运算符时,通常需要同时重写 `Object.Equals`。 - -❌ **避免**从相等运算符引发异常(参数为 null 时应返回 false)。 - -### 6.2 值类型的相等运算符 - -✔️ 如果相等有意义,**应该**对值类型重载相等运算符。 - -> 大多数语言没有为值类型提供默认的 `operator==` 实现。 - -### 6.3 引用类型的相等运算符 - -❌ **避免**在**可变引用类型**上重载相等运算符。 - -> 内置运算符实现引用相等性,改为值相等性会使许多开发人员困惑。不可变引用类型则问题较小。 - -❌ **避免**对引用类型重载相等运算符(如果实现速度远低于引用相等性)。 - ---- - -## 七、IEquatable / IComparable 准则 - -`IComparable` 定义排序语义(小于、等于、大于),主要用于排序。`IEquatable` 定义相等语义,主要用于查找。 - -✔️ **必须**在值类型上实现 `IEquatable`。 - -> `Object.Equals` 在值类型上会导致装箱,且默认实现使用反射,效率低下。 - -✔️ 实现 `IEquatable` 时**必须**同时重写 `Object.Equals`。 - -✔️ 实现 `IEquatable` 时考虑重载 `operator==` 和 `operator!=`。 - -✔️ 实现 `IComparable` 时**必须**同时实现 `IEquatable`。 - -✔️ 重写 `Object.Equals` 时**必须**同时重写 `GetHashCode`。 - ---- - -## 八、Object 准则 - -### 8.1 Object.Equals - -- **值类型**默认实现:当所有字段相等时返回 true("值相等"),但实现**使用反射**,通常效率低下,**应当重写** -- **引用类型**默认实现:当两个引用指向同一对象时返回 true("引用相等") - -❌ **禁止**在**可变引用类型**上实现值相等。 - -> 实现值相等的引用类型(如 `System.String`)应当是不可变的。 - -### 8.2 Object.GetHashCode - -✔️ 重写 `Equals` 时**必须**重写 `GetHashCode`,确保被视为相等的两个对象具有相同的哈希码。 - -✔️ 尽力确保 `GetHashCode` 为类型的所有对象生成**均匀分布**的数字,以最小化哈希表冲突。 - -### 8.3 Object.ToString - -`Object.ToString` 用于通用显示和调试目的,默认实现仅返回类型名称。 - -✔️ 建议**始终重写** `ToString`,提供有意义的字符串表示。 - ---- - -## 九、Nullable\ 准则 - -❌ **禁止**使用 `Nullable` 表示可选参数。 - -> 应使用方法重载来表示可选参数。 - -```csharp -// 正确:使用重载 -public class Foo -{ - public Foo(string name, int id) { } - public Foo(string name) { } -} -``` - -❌ **避免**使用 `Nullable` 表示通用三态值。 - -> `Nullable` 仅应用于真正可选的布尔值(true、false、不可用)。如果需要表示三种状态(如 yes、no、cancel),应使用枚举。 - ---- - -## 十、URI 准则 - -✔️ 使用 `System.Uri` 表示 URI 和 URL 数据,而非普通字符串。 - -> `System.Uri` 提供了更安全、更丰富的 URI 表示方式。使用普通字符串大量操作 URI 相关数据已被证明会导致许多安全和正确性问题。 - ---- - -## 十一、ICloneable 准则 - -❌ **禁止**在公共 API 中使用 `ICloneable`。 - -> `ICloneable` 的问题在于协定未指定克隆是浅拷贝还是深拷贝——有些实现返回浅拷贝,有些返回深拷贝。调用者永远无法确定会得到什么,这使得该接口毫无用处。 - -✔️ 如果需要克隆机制,考虑在类型上定义自己的 `Clone` 方法,并明确文档说明是浅拷贝还是深拷贝。 - ---- - -## 十二、速查表 - -### 公共 API 中的类型选择 - -| 场景 | 推荐 | 避免 | -|------|------|------| -| 集合参数 | `IEnumerable` | `List`、`ArrayList` | -| 读写集合返回值 | `Collection` 或子类 | `List` | -| 只读集合返回值 | `ReadOnlyCollection` | 数组(需复制) | -| 字典参数/返回值 | `IDictionary` | `Dictionary` | -| 表示 XML 数据 | `XmlReader`、`XNode` | `XmlNode`、`XmlDocument` | -| 序列化 | 数据协定序列化 | 运行时序列化(除非远程处理) | -| 低级高性能 API | 数组 | 集合 | -| 字节数据 | `byte[]` | `Collection` | - -### 禁止事项 - -| 禁止 | 原因 | -|------|------| -| 公共 API 返回 `null` 集合 | 应返回空集合或空数组 | -| 可设置的集合属性 | 用户应通过 Clear+Add 替换内容 | -| 只读数组字段 | 元素仍可被修改 | -| 属性返回快照集合 | getter 应轻量,不应有 O(n) 开销 | -| 继承 `CollectionBase` | 应使用泛型基类 | diff --git a/Assets/YooAsset/Docs/7.使用指南.md.meta b/Assets/YooAsset/Docs/7.使用指南.md.meta deleted file mode 100644 index bca66670..00000000 --- a/Assets/YooAsset/Docs/7.使用指南.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 95ca2d569cd5c124892e8f6e0a01efea -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/YooAsset/Docs/日志规范.md b/Assets/YooAsset/Docs/日志规范.md deleted file mode 100644 index 12c9fc19..00000000 --- a/Assets/YooAsset/Docs/日志规范.md +++ /dev/null @@ -1,225 +0,0 @@ -# .NET 日志规范 - -> 参考来源:[Microsoft .NET 框架设计准则 - 异常消息](https://learn.microsoft.com/zh-cn/dotnet/standard/design-guidelines/exceptions-and-performance) · [.NET Runtime 源码日志风格](https://github.com/dotnet/runtime) -> -> 核心原则:**措辞简洁准确、格式统一规范、使用地道英语、避免泄露敏感信息** - ---- - -## 一、变量引用规则 - -### 1.1 字符串变量值加单引号 - -文件路径、名称、GUID、标识符等字符串变量值用单引号包裹,便于在日志中区分变量边界。 - -```csharp -// ✔️ -$"File cache entry not found: '{bundleGuid}'." -$"Invalid clear method: '{options.ClearMethod}'." -$"Could not find file '{filePath}'." - -// ❌ -$"File cache entry not found: {bundleGuid}." -``` - -### 1.2 数值变量不加引号 - -数值类型的变量(整数、浮点、枚举值)不加引号。 - -```csharp -// ✔️ -$"MaxTimeSlice must be at least {MinTimeSlice} ms." -$"Does not support bundle type: {options.Bundle.BundleType}." - -// ❌ -$"Does not support bundle type: '{options.Bundle.BundleType}'." -``` - -### 1.3 类型名作句首主语不加引号 - -类型名(通常通过 `nameof()` 或 `GetType().Name` 获取)做句子主语时,不加引号。 - -```csharp -// ✔️ -$"{nameof(SandboxBundleCache)} does not support synchronous loading." -$"AsyncOperationSystem is not initialized." - -// ❌ -$"'{nameof(SandboxBundleCache)}' does not support synchronous loading." -``` - -### 1.4 异常对象不加引号 - -异常对象(`{ex}` 或 `{ex.Message}`)直接拼接,不加引号。 - -```csharp -// ✔️ -YooLogger.Error($"File verification exception: {ex.Message}."); -YooLogger.Warning($"Failed to delete cache bundle folder: {ex}."); - -// ❌ -YooLogger.Error($"File verification exception: '{ex.Message}'."); -``` - ---- - -## 二、消息格式规则 - -### 2.1 首字母大写,末尾加句号 - -所有日志和异常消息使用 Sentence case(首字母大写),末尾加句号。 - -```csharp -// ✔️ -SetError("Unity engine load failed."); -throw new Exception("Catalog file data is null or empty."); - -// ❌ -SetError("unity engine load failed"); -throw new Exception("catalog file data is null or empty"); -``` - -### 2.2 使用完整句子 - -使用语法完整的句子,不省略主语或谓语。 - -> **来源**:.NET 官方规范 — *"Do use complete sentences. 'Binding is too long.' rather than 'Binding too long.'"* - -```csharp -// ✔️ -"Decryptor is null." -"Loaded bundle handle is null." - -// ❌ -"Decryptor null." -"Bundle handle null." -``` - -> **例外**:`not found` 等 .NET 生态中广泛使用的惯用短语,允许省略 `was`,以保持简洁和一致性。 - -```csharp -// ✔️ 惯用短语,省略 was -"File cache entry not found: '{bundleGuid}'." -"Asset not found: '{assetPath}'." - -// ❌ 冗余 -"File cache entry was not found: '{bundleGuid}'." -``` - -### 2.3 不要以冠词或变量开头 - -消息以关键实体词开头,不要以 `The` / `A` / `An` 或变量开头,便于日志搜索和排序。 - -> **来源**:.NET 官方规范 — *"Do not start with an article or variable. 'Log file {0} is full.' is preferable to '{0} log file is full.'"* - -```csharp -// ✔️ -$"Cache entry already exists: '{bundleGuid}'." -$"Log file '{name}' is full." - -// ❌ -$"The cache entry already exists: '{bundleGuid}'." -$"'{name}' log file is full." -``` - -### 2.4 不要使用感叹号和问号 - -消息末尾只使用句号,禁止感叹号和问号。 - -> **来源**:.NET 官方规范 — *"Do not use question marks or exclamation points."* - -```csharp -// ✔️ -"Command is unrecognizable." - -// ❌ -"Command is unrecognizable!!" -"Did the operation complete?" -``` - ---- - -## 三、语气和措辞规则 - -### 3.1 使用中性语气 - -使用中性客观的措辞,不归咎用户,不拟人化程序。 - -> **来源**:.NET 官方规范 — *"Do use a neutral tone."* / *"Do not personify."* - -```csharp -// ✔️ -"Command is unrecognizable." -"Node parameter cannot use this protocol." - -// ❌ -"Bad input." -"Parameter node does not speak any of our protocols." -``` - -### 3.2 省略 `this.` 前缀 - -`this.GetType().Name` 中的 `this.` 可省略。 - -```csharp -// ✔️ -$"Exception in {GetType().Name}.InternalStart: {ex}." - -// ❌ -$"Exception in {this.GetType().Name}.InternalStart: {ex}." -``` - -### 3.3 不要泄露敏感信息 - -异常和日志消息中避免包含密钥、密码、完整用户路径等敏感信息。 - -> **来源**:.NET 官方规范 — *"Do not disclose sensitive information in exception messages."* - ---- - -## 四、一致性规则 - -### 4.1 保持一致性 - -同一项目中相同语义使用相同句式,避免同一错误用不同表达方式。 - -```csharp -// ✔️ 统一格式 -$"File cache entry not found: '{bundleGuid}'." // 所有缓存未命中统一使用此句式 -$"Exception in {GetType().Name}.{MethodName}: {ex}." // 所有异常日志统一使用此句式 - -// ❌ 同一语义不同表达 -"Cached bundle not found." // ← 与上面的 "File cache entry not found" 不一致 -``` - -### 4.2 避免中式英语和语法错误 - -使用地道的英语表达,避免中文直译造成的不自然句式。 - -| 中式英语 | 地道表达 | 说明 | -|----------|----------|------| -| `"cannot match the file hash"` | `"File hash does not match."` | 主语应是名词而非动词短语 | -| `"Failed fallback load bundle"` | `"Failed to load bundle with fallback."` | 缺少介词和完整句法 | -| `"Not found xxx in yyy"` | `"Could not find xxx in yyy."` | `Not found` 不是完整句子 | -| `"The bundle file is lose"` | `"The bundle file is missing."` | 词性错误(lose → missing) | -| `"Begin to download"` | `"Starting download."` | `begin to` 是典型中式英语 | - ---- - -## 五、速查表 - -| # | 规则 | 正确示例 | 错误示例 | -|---|------|---------|---------| -| 1 | 字符串变量值加单引号 | `'{bundleGuid}'` | `{bundleGuid}` | -| 2 | 数值变量不加引号 | `{count}` | `'{count}'` | -| 3 | 类型名作主语不加引号 | `TypeName is not ...` | `'TypeName' is not ...` | -| 4 | 异常对象不加引号 | `{ex.Message}` | `'{ex.Message}'` | -| 5 | 首字母大写,末尾加句号 | `"Load failed."` | `"load failed"` | -| 6 | 使用完整句子 | `"Decryptor is null."` | `"Decryptor null."` | -| 7 | 不以冠词或变量开头 | `"Cache entry ..."` | `"The cache entry ..."` | -| 8 | 只用句号 | `"Failed."` | `"Failed!!"` | -| 9 | 省略 `this.` | `GetType().Name` | `this.GetType().Name` | -| 10 | 使用中性语气 | `"Command is unrecognizable."` | `"Bad input."` | -| 11 | 不泄露敏感信息 | — | 密钥、密码、用户路径 | -| 12 | 保持一致性 | 同语义同句式 | 同错误不同表达 | -| 13 | 避免中式英语 | `"File hash does not match."` | `"cannot match the file hash"` | diff --git a/Assets/YooAsset/Docs/日志规范.md.meta b/Assets/YooAsset/Docs/日志规范.md.meta deleted file mode 100644 index a5831867..00000000 --- a/Assets/YooAsset/Docs/日志规范.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: a8f2f1e57843e104f95aa6ad5ad3e1a9 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/YooAsset/Docs/注释规范.md b/Assets/YooAsset/Docs/注释规范.md deleted file mode 100644 index 28bbdd0a..00000000 --- a/Assets/YooAsset/Docs/注释规范.md +++ /dev/null @@ -1,531 +0,0 @@ -# .NET 注释规范 - -> 参考来源:[Microsoft C# XML 文档注释](https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/xmldoc/) · [推荐的文档注释标签](https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/xmldoc/recommended-tags) · [框架设计准则](https://learn.microsoft.com/zh-cn/dotnet/standard/design-guidelines/) -> -> 核心原则:**公共 API 必须注释、说明意图而非复述代码、保持与代码同步** - ---- - -## 一、概述 - -C# 使用 XML 文档注释为代码提供结构化文档。编译器从 `///`(三斜杠)注释中生成 XML 文件,供 IDE 智能提示和文档生成工具使用。 - ---- - -## 二、XML 注释标签 - -### 2.1 核心标签 - -| 标签 | 用途 | 适用场景 | -|------|------|----------| -| `` | 简要描述类型或成员的功能 | 所有公共类型和成员 | -| `` | 描述方法参数 | 方法、构造函数 | -| `` | 描述方法返回值 | 有返回值的方法 | -| `` | 补充说明,提供额外细节 | 需要详细解释的成员 | -| `` | 描述属性所代表的值 | 属性 | - -### 2.2 引用标签 - -| 标签 | 用途 | 示例 | -|------|------|------| -| `` | 行内引用其他类型或成员 | `` | -| `` | 在"另请参阅"区域添加引用 | `` | -| `` | 引用参数名 | `` | -| `` | 引用泛型类型参数名 | `` | - -### 2.3 格式标签 - -| 标签 | 用途 | -|------|------| -| `` | 在 `` 等标签内分段 | -| `` | 创建列表或表格 | -| `` | 多行代码示例 | -| `` | 包含使用示例 | - -### 2.4 泛型与继承标签 - -| 标签 | 用途 | -|------|------| -| `` | 描述泛型类型参数 | -| `` | 继承基类或接口的文档注释 | - ---- - -## 三、注释范围要求 - -### 3.1 必须添加注释的成员 - -| 访问级别 | 要求 | -|----------|------| -| `public` | **必须**添加完整 XML 注释 | -| `protected` | **必须**添加完整 XML 注释 | -| `internal` | **建议**添加 XML 注释 | -| `private` | 仅在逻辑复杂时添加 | - - -### 3.2 各成员类型的注释要求 - -| 成员类型 | 必需标签 | 可选标签 | -|----------|---------|---------| -| 类 / 结构 | `` | ``、`` | -| 接口 | `` | `` | -| 方法 | ``、``、`` | ``、`` | -| 构造函数 | ``、`` | — | -| 属性 | `` | `` | -| 事件 | `` | `` | -| 枚举类型 | `` | — | -| 枚举值 | `` | — | -| 委托 | ``、``、`` | — | -| 接口实现成员 | `` | — | -| override 方法 | **不添加**注释 | — | - ---- - -## 四、书写原则 - -### 4.1 `` 书写规范 - -✔️ **一句话**说明"做什么",而非"怎么做"。 - -✔️ 以**第三人称动词**开头。 - -✔️ 句号规则:注释内容含逗号、分号等分句标点时,末尾**加**句号;纯短语**不加**句号。 - -```csharp -// ✔️ 含逗号 → 加句号 -/// 超时时间(秒),0 表示不限制。 -/// 下载结果,包含状态码和错误信息。 - -// ✔️ 纯短语 → 不加句号 -/// 资源包的完整下载地址 -/// 初始化操作句柄 -``` - -❌ **禁止**重复方法名或参数类型中已表达的信息。 - -❌ **禁止**在注释中重复方法命名后缀已表达的语义。常见后缀包括: - -| 命名后缀 | 已表达的语义 | 注释中禁止使用 | -|----------|------------|--------------| -| `Async` | 异步执行 | "异步" | -| `Internal` | 内部实现 | "内部" | - -❌ **禁止**以"这个方法"、"该类"等冗余前缀开头。 - -### 4.2 常见开头约定 - -#### 属性 —— 描述值本身,不加动词前缀 - -属性的 `{ get; }` / `{ get; set; }` 访问器已表达读写语义,summary 应直接描述**值的含义**,避免用"获取"、"获取或设置"等动词前缀重复已知信息。 - -| 属性类型 | 推荐写法 | 反例 | -|----------|---------|------| -| 只读属性 | 名词短语,如 "错误信息" | ~~"获取错误信息"~~ | -| 可读写属性 | 名词短语,如 "当前下载进度" | ~~"获取或设置当前下载进度"~~ | -| 返回 bool 的属性 | "是否……",如 "是否为只读缓存" | ~~"判断是否为只读缓存"~~ | - -#### 方法 —— 以动词开头 - -| 成员类型 | 推荐开头动词 | -|----------|-------------| -| 返回 bool 的方法 | "检查是否"、"判断是否" | -| 获取类方法 | "获取"、"查询" | -| 设置类方法 | "设置"、"更新" | -| 创建类方法 | "创建"、"构建"、"生成" | -| 事件 | "当……时触发" | -| 构造函数 | "创建 XXX 实例"(XXX 为类型的中文描述) | - -### 4.3 `` 书写规范 - -✔️ 描述参数的**含义和用途**。 - -✔️ 说明有效范围和边界值(如为 `null` 时的行为、取值范围)。 - -❌ **禁止**仅重复参数名或类型名,如"url 参数"。 - -### 4.4 `` 书写规范 - -✔️ 说明返回值的**含义**。 - -✔️ 说明**特殊返回值**的含义(如返回 `null` 表示未找到)。 - -### 4.5 `` 书写规范 - -✔️ 多行内容**必须**用 `` 包裹每段,否则 IDE 会将多行合并显示为一行。 - -```csharp -// ✔️ 好:每段用 包裹 -/// -/// 下载并加载 Unity AssetBundle 资源包 -/// 支持 Unity 内置缓存机制和 CRC 校验 -/// - -// ❌ 差:多行纯文本,IDE 显示时会合并为一行 -/// -/// 下载并加载 Unity AssetBundle 资源包 -/// 支持 Unity 内置缓存机制和 CRC 校验 -/// -``` - -✔️ 单行内容可直接书写,无需 ``。 - ---- - -## 五、注释示例 - -### 5.1 类注释 - -```csharp -/// -/// 资源包下载请求,负责管理单个资源包的下载生命周期。 -/// -/// -/// 支持断点续传和失败重试 -/// 通过 控制重试策略 -/// -public class BundleDownloadRequest -{ -} -``` - -### 5.2 方法注释 - -```csharp -/// -/// 从指定 URL 下载资源包并保存到本地磁盘 -/// -/// 资源包的完整下载地址 -/// 本地保存的目标路径 -/// 超时时间(秒),0 表示不限制。 -/// 下载结果,包含状态码和错误信息。 -public async Task DownloadAsync(string url, string savePath, int timeout = 30) -{ -} -``` - -### 5.3 属性注释 - -```csharp -/// -/// 当前下载进度 -/// -/// 取值范围 0.0 ~ 1.0,其中 1.0 表示下载完成。 -public float Progress { get; private set; } -``` - -### 5.4 枚举注释 - -```csharp -/// -/// 下载任务的运行状态 -/// -public enum DownloadStatus -{ - /// - /// 等待中,尚未开始下载。 - /// - Pending, - - /// - /// 正在下载中 - /// - Downloading, - - /// - /// 下载已完成 - /// - Completed, - - /// - /// 下载失败 - /// - Failed -} -``` - -### 5.5 泛型类注释 - -```csharp -/// -/// 通用对象池,提供对象的复用管理。 -/// -/// 池化对象的类型,必须实现 -public class ObjectPool where T : IDisposable -{ -} -``` - -### 5.6 接口注释 - -```csharp -/// -/// 定义下载请求的标准行为 -/// -public interface IDownloadRequest -{ - /// - /// 请求的远程 URL - /// - string URL { get; } - - /// - /// 取消当前下载请求 - /// - void Abort(); -} -``` - -### 5.7 使用 `` - -#### 接口实现 —— 使用 `` - -实现接口成员时,使用 `` 继承接口中定义的文档: - -```csharp -public class MyDownloadRequest : IDownloadRequest -{ - /// - public string URL { get; } - - /// - public void Abort() - { - } -} -``` - -#### 基类继承(override) —— 不添加任何注释 - -重写基类方法时,**不需要**添加任何 XML 文档注释(包括 ``)。方法签名中的 `override` 关键字已明确表达继承关系,IDE 会自动展示基类文档。 - -```csharp -// ✔️ 好:override 方法不添加注释 -protected override void InternalStart() -{ -} - -protected override void InternalUpdate() -{ -} - -// ❌ 差:多余的 inheritdoc -/// -protected override void InternalStart() -{ -} -``` - -### 5.8 使用 `` 和 `` - -```csharp -/// -/// 根据资源路径加载资源对象 -/// -/// 资源路径 -/// 资源操作句柄,加载失败时资源对象为 null。 -/// -/// 加载一个预制体: -/// -/// var handle = package.LoadAssetAsync<GameObject>("Assets/Prefabs/Player.prefab"); -/// await handle.Task; -/// var prefab = handle.AssetObject as GameObject; -/// -/// -public AssetHandle LoadAssetAsync(string assetPath) -{ -} -``` - ---- - -## 六、反面示例 - -### 6.1 冗余注释 - -```csharp -// ❌ 差:重复方法签名中已有的信息 -/// -/// 下载方法,参数是 url 和 savePath。 -/// -public void Download(string url, string savePath) { } - -// ✔️ 好:说明行为和目的 -/// -/// 从远程服务器下载资源包并保存到本地磁盘 -/// -/// 资源包的完整下载地址 -/// 本地保存的目标路径 -public void Download(string url, string savePath) { } -``` - -### 6.2 废话参数注释 - -```csharp -// ❌ 差:仅重复参数名 -/// 名称 - -// ✔️ 好:说明含义和约束 -/// 资源包的唯一标识名称 -``` - -### 6.3 重复命名后缀 - -```csharp -// ❌ 差:方法名 Async 后缀已表达异步语义 -/// -/// 异步加载资源对象 -/// -public AssetHandle LoadAssetAsync(string location) { } - -// ✔️ 好:描述功能意图,不重复后缀语义 -/// -/// 加载资源对象 -/// -public AssetHandle LoadAssetAsync(string location) { } -``` - -### 6.4 缺少边界说明 - -```csharp -// ❌ 差:未说明返回 null 的情况 -/// 资源对象 - -// ✔️ 好:说明特殊返回值 -/// 加载到的资源对象 -``` - ---- - -## 七、行内注释规范 - -对于非 XML 文档注释(`//` 普通注释),遵循以下原则: - -### 7.1 基本准则 - -✔️ 解释"**为什么**",而非"是什么"——代码本身应说明"做什么",注释解释不明显的意图或约束。 - -✔️ 标记待办事项使用 `// TODO:` 前缀。 - -✔️ 标记临时方案使用 `// HACK:` 前缀。 - -✔️ 修改代码时**同步更新**相关注释,过时的注释比没有注释更有害。 - -❌ **禁止**注释显而易见的代码,如 `// 递增计数器`、`// 返回结果`。 - -❌ **禁止**用注释替代清晰的命名——如果需要注释来解释变量含义,应优先改善变量名。 - -### 7.2 分段注释(例外) - -在较长的方法体中,允许使用**简短的分段注释**充当逻辑区域标题,帮助快速定位代码段落。这类注释虽然在形式上可能"复述"了下方代码的行为,但其核心价值是**导航与分区**,不属于上述"禁止复述"的范畴。 - -```csharp -// ✔️ 好:在长方法中用分段注释划分逻辑区域 -public void InitViewer() -{ - // 加载布局文件 - _visualAsset = UxmlLoader.LoadWindowUxml(); - ... - - // 资源包列表 - _bundleTableView = _root.Q("BundleTableView"); - ... - - // 使用列表 - _usingTableView = _root.Q("UsingTableView"); - ... - - // 面板分屏 - var topGroup = _root.Q("TopGroup"); - ... -} -``` - -判断标准:如果移除该注释后,读者需要逐行阅读才能理解方法内的结构,则应保留。 - -### 7.3 示例 - -```csharp -// ❌ 差:复述代码 -// 如果计数大于最大值,重置为零 -if (count > maxCount) - count = 0; - -// ✔️ 好:解释约束 -// 环形缓冲区写满后从头覆盖,避免无限增长 -if (count > maxCount) - count = 0; -``` - ---- - -## 八、术语翻译映射表 - -> 为保持注释用语统一,以下列出项目中常用英文类型/概念与其中文翻译的对应关系。 -> 撰写或审核注释时,请以此表为准。 - -### 8.1 核心领域类型 - -| 英文类型 / 概念 | 中文翻译 | 备注 | -|-----------------|---------|------| -| `PackageBundle` | 资源包描述 | 清单中的静态元数据(`Package*` = 描述) | -| `PackageAsset` | 资源描述 | 清单中的静态元数据(`Package*` = 描述) | -| `AssetInfo` | 资源信息 | 运行时上下文(`*Info` = 信息) | -| `BundleInfo` | 资源包信息 | 运行时上下文(`*Info` = 信息) | -| `ResourcePackage` | 资源包裹 | 顶层包裹容器 | -| `PackageManifest` | 包裹清单 / 资源清单 | — | -| `AssetBundle` | AssetBundle | Unity 引擎类型,保留英文 | -| `RawBundle` | 原生资源包 | 非 AssetBundle 的原始文件 | - -### 8.2 文件系统与缓存 - -| 英文类型 / 概念 | 中文翻译 | 备注 | -|-----------------|---------|------| -| `IFileSystem` | 文件系统 | — | -| `IBundleCache` | 缓存系统 | — | -| `ICacheEntry` | 缓存条目 | — | -| `BundleGuid` | 资源包 GUID / Bundle 唯一标识 | — | -| `ICacheEvictionPolicy` | 淘汰策略 | — | -| `EvictionResult` | 淘汰结果 | — | - -### 8.3 下载与网络 - -| 英文类型 / 概念 | 中文翻译 | 备注 | -|-----------------|---------|------| -| `IDownloadRequest` | 下载请求 | — | -| `IDownloadBackend` | 下载后台 | — | -| `IRemoteService` | 远端资源服务 | — | -| `IDownloadRetryPolicy` | 下载重试策略 | — | -| `IDownloadUrlPolicy` | URL 选择策略 | — | - -### 8.4 加密与解密 - -| 英文类型 / 概念 | 中文翻译 | 备注 | -|-----------------|---------|------| -| `IBundleEncryptor` | 资源包加密器 | — | -| `IBundleDecryptor` | 资源包解密器 | 基接口,需实现派生接口 | -| `IManifestEncryptor` | 资源清单加密器 | — | -| `IManifestDecryptor` | 资源清单解密器 | — | -| `BundleEncryptArgs` | 加密操作的输入参数 | — | -| `BundleDecryptArgs` | 解密操作的输入参数 | — | - -### 8.5 资源加载与句柄 - -| 英文类型 / 概念 | 中文翻译 | 备注 | -|-----------------|---------|------| -| `IBundleHandle` | 资源包句柄 | — | -| `AssetHandle` | 资源句柄 | — | -| `RawFileHandle` | 原生文件句柄 | — | -| `SubAssetsHandle` | 子资源句柄 | — | - -### 8.6 通用术语 - -| 英文术语 | 中文翻译 | 备注 | -|---------|---------|------| -| Package | 包裹 | 项目级容器 | -| Bundle | 资源包 | 构建产物单元 | -| Asset | 资源 | 单个资源文件 | -| Manifest | 清单 | — | -| Handle | 句柄 | — | -| Operation | 操作 / 异步操作 | — | -| Scheduler | 调度器 | — | -| Provider | 提供者 | — | \ No newline at end of file diff --git a/Assets/YooAsset/Docs/注释规范.md.meta b/Assets/YooAsset/Docs/注释规范.md.meta deleted file mode 100644 index 832121d4..00000000 --- a/Assets/YooAsset/Docs/注释规范.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 5673a09d2666eb847a78066794039d23 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/YooAsset/Docs/逻辑检测.md b/Assets/YooAsset/Docs/逻辑检测.md deleted file mode 100644 index 21d82e30..00000000 --- a/Assets/YooAsset/Docs/逻辑检测.md +++ /dev/null @@ -1,200 +0,0 @@ -# 逻辑检测 - -> **总则**:检查运行时代码的关键逻辑路径,确保宏分支、异步操作生命周期、参数完整性和资源释放均无遗漏。 - -## 1. 宏包裹代码逻辑 - -检查所有 `#if` / `#elif` / `#else` / `#endif` 包裹的代码: - -- 每个分支的变量赋值路径是否完整(不存在未赋值分支) -- `#else` 兜底分支是否覆盖所有未列出的平台 -- 各分支之间的语义是否一致(同一变量在不同分支中的类型和用途是否相同) -- 分支中引用的 API 是否与对应宏版本匹配(如 `UNITY_2020_3_OR_NEWER` 对应的 API) - -## 2. 子异步操作检查 - -**定义**:通过 `AddChildOperation()` 添加的操作为子异步操作。 - -### 2.1 同步等待传播 - -如果类实现了 `InternalWaitForCompletion()` 方法,所有子异步操作必须在该方法中主动调用 `WaitForCompletion()`。 - -``` -❌ 父操作实现了 InternalWaitForCompletion,但未对子操作调用 WaitForCompletion -✅ 父操作在 InternalWaitForCompletion 中对每个子操作调用 WaitForCompletion() -``` - -### 2.2 更新驱动 - -所有子异步操作必须在父操作的 `InternalUpdate()` 中主动调用 `UpdateOperation()`,确保子操作能被正常驱动。 - -``` -❌ 子操作被 AddChildOperation 添加后,未在 InternalUpdate 中调用 UpdateOperation -✅ 每个子操作在 InternalUpdate 中被调用 UpdateOperation() -``` - -## 3. 异步操作参数完整性 - -检查所有 Operation 的 Options 在 `new` 的位置是否完整填充了所有成员字段。 - -- 找到 Options 结构体的所有字段定义 -- 找到所有 `new XxxOptions(...)` 的调用点 -- 逐一比对构造参数是否覆盖了所有必填字段 -- 可选参数(有默认值)可以跳过 - -``` -❌ new DownloadFileRequestOptions(url, savePath, timeout) // 遗漏了 watchdogTimeout -✅ new DownloadFileRequestOptions(url, savePath, timeout, watchdogTimeout) -``` - -## 4. 整数溢出与取模安全 - -检查所有对 `int` 计数器执行 `%`(取模)运算的位置,确保不会因溢出产生负索引。 - -- 只增不减的 `int` 计数器(如失败计数、轮转索引),溢出 `int.MaxValue` 后变为负数 -- 负数 `%` 正数在 C# 中结果为负数,用作数组/列表索引会触发 `ArgumentOutOfRangeException` -- 使用 `& 0x7FFFFFFF` 清除符号位,保证结果始终为非负整数(.NET 中处理哈希取模的惯用手法) - -``` -❌ int index = _counter % list.Count; // _counter 溢出后 index 为负 -✅ int index = (_counter & 0x7FFFFFFF) % list.Count; // 始终非负 -``` - -## 5. 下载请求资源释放 - -### 5.1 IDownloadRequest 释放 - -检查所有持有 `IDownloadRequest`(及其子接口)实例的 Operation,是否在 `InternalDispose()` 中调用了 `Dispose()` 释放资源。 - -``` -❌ Operation 持有 IDownloadFileRequest 但未在 InternalDispose 中释放 -✅ protected override void InternalDispose() { _request?.Dispose(); } -``` - -### 5.2 下载数据完整性验证 - -检查文件数据下载完成后,是否有容错或数据完整性验证: - -- 是否校验文件大小(下载字节数与预期是否一致) -- 是否校验文件哈希(CRC / MD5 / SHA 等) -- 如果无校验,是否有其他容错机制(如重试、回退) -- 无任何验证且无容错机制的,标记为需要关注 - -## 6. 计时方式一致性 - -检查所有需要"测量经过时间"的逻辑,确保计时方式不会因帧率波动或 App 后台恢复而失真。 - -- 使用 `Time.unscaledDeltaTime` 累加计时的方式,在 App 从后台恢复时会产生巨大 spike(一帧跨越几十秒),导致等待逻辑在单帧内被直接跳过 -- 同一系统内应保持计时方式统一,避免一处用绝对时间戳、另一处用帧增量 -- 推荐使用 `TimeUtility.RealtimeSinceStartup` 记录起止时间戳做差值判断 - -``` -❌ _elapsed += Time.unscaledDeltaTime; return _elapsed >= delay; // 后台恢复时 spike 跳过等待 -✅ return TimeUtility.RealtimeSinceStartup - _startTime >= delay; // 绝对时间戳,不受 spike 影响 -``` - -## 7. 子类回调不得覆写基类已确定的状态 - -检查所有基类定义的"成功/失败回调"(如 `OnRequestSucceeded`、`OnRequestFailed`),子类在重写时不应直接修改基类已确定的 `Status`。 - -- 基类在调用回调前已设置 `Status = Succeeded`,若子类在回调中将其改为 `Failed`,后续基类逻辑可能与实际状态不一致 -- 回调应仅负责提取结果或设置错误信息,状态转换应由基类统一控制 -- 推荐将回调签名改为返回 `bool`(或通过 out 参数传递错误),基类根据返回值决定最终状态 - -``` -❌ protected override void OnRequestSucceeded() - { - if (result == null) Status = Failed; // 子类直接覆写基类已确定的 Succeeded 状态 - } -✅ protected override bool OnRequestSucceeded() - { - if (result == null) { Error = "..."; return false; } // 返回 false,由基类设置 Failed - return true; - } -``` - -## 8. 结构体不可变性检测 - -> 依据:[类型设计准则](2.类型设计准则.md) — "❌ **禁止**定义可变值类型" / "✔️ 声明不可变值类型时使用 `readonly struct` 修饰符" - -检查所有 `struct` 定义,**必须**声明为 `readonly struct`。存在可变 struct 即为违规。 - -检测要点: - -- `struct` 声明缺少 `readonly` 修饰符 -- 属性使用 `{ get; set; }` 而非 `{ get; }` -- 公共字段未声明为 `readonly` -- 缺少构造函数(依赖默认值逐字段赋值的模式) - -``` -❌ struct MutableConfig - { - public int Timeout { get; set; } // 可变属性 - public string Name { get; set; } - } - -✅ readonly struct ImmutableConfig - { - public int Timeout { get; } // 只读属性 - public string Name { get; } - public ImmutableConfig(int timeout, string name) - { - Timeout = timeout; - Name = name; - } - } -``` - -**禁止可变 struct 的原因**: - -- 值类型按值复制,可变成员修改作用于副本而非原值,极易产生静默 bug -- `readonly` 字段持有可变 struct 时,编译器每次访问都会产生防御性复制,造成不必要的性能开销 -- `readonly struct` 从根源上杜绝以上两类问题 - -## 9. 文件系统禁止重写 GetHashCode - -检查所有 `IFileSystem` 实现类,确认未重写 `GetHashCode()` 或 `Equals()`。 - -`BundleInfo.GetCombineKey()` 依赖 `RuntimeHelpers.GetHashCode(_fileSystem)` 获取基于引用标识的哈希值,用于下载器合并时的去重判断。如果文件系统实现类重写了 `GetHashCode()`,虽然 `RuntimeHelpers.GetHashCode` 本身不受影响,但会暗示该类型有自定义相等性语义,可能导致其他使用 `GetHashCode()` 的位置产生非预期的碰撞。 - -检测要点: - -- `IFileSystem` 实现类中存在 `override int GetHashCode()` 声明 -- `IFileSystem` 实现类中存在 `override bool Equals(object)` 声明 - -``` -❌ class SandboxFileSystem : IFileSystem - { - public override int GetHashCode() => PackageName.GetHashCode(); // 破坏引用标识语义 - } - -✅ class SandboxFileSystem : IFileSystem - { - // 不重写 GetHashCode,保持 object 默认的引用标识行为 - } -``` - -## 10. 不可变结构体命名参数检测 - -检测所有 `readonly struct` 构造函数调用中**位置参数 > 3 个**的位置,确认是否使用命名参数提高可读性。 - -`readonly struct` 只能通过构造函数一次性传入所有字段,参数数量通常较多,且参数顺序是唯一区分手段。同类型参数相邻时,顺序错误不会产生编译错误,容易引入静默 bug。 - -重点检查以下场景: - -- 构造函数参数 > 3 个 -- 多个同类型参数相邻(如连续多个 `bool`、`int`、接口类型) -- 参数名与属性名高度对应,使用命名参数可消除歧义 - -``` -❌ new Configuration(60, true, level, decryptor, backend, retryPolicy, urlPolicy); - -✅ new Configuration( - watchdogTimeout: 60, - disableUnityWebCache: true, - downloadVerifyLevel: level, - assetBundleDecryptor: decryptor, - downloadBackend: backend, - downloadRetryPolicy: retryPolicy, - downloadUrlPolicy: urlPolicy); -``` diff --git a/Assets/YooAsset/Docs/逻辑检测.md.meta b/Assets/YooAsset/Docs/逻辑检测.md.meta deleted file mode 100644 index 6562ca12..00000000 --- a/Assets/YooAsset/Docs/逻辑检测.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 200087470392cf24ebdd0efac8e44210 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/YooAsset/Editor/Assembly/AssemblyInfo.cs b/Assets/YooAsset/Editor/Assembly/AssemblyInfo.cs index 16b85520..a9ca6dd2 100644 --- a/Assets/YooAsset/Editor/Assembly/AssemblyInfo.cs +++ b/Assets/YooAsset/Editor/Assembly/AssemblyInfo.cs @@ -1,5 +1,5 @@ using System.Runtime.CompilerServices; // 外部友元 -[assembly: InternalsVisibleTo("YooAsset.EditorExtension")] +[assembly: InternalsVisibleTo("YooAsset.Extension.Editor")] [assembly: InternalsVisibleTo("Assembly-CSharp-Editor")] \ No newline at end of file diff --git a/Assets/YooAsset/Editor/BundleBuilder/BuildPipeline/BaseTasks/TaskCopyBundledFiles.cs b/Assets/YooAsset/Editor/BundleBuilder/BuildPipeline/BaseTasks/TaskCopyBundledFiles.cs index c97495e6..c93e1e55 100644 --- a/Assets/YooAsset/Editor/BundleBuilder/BuildPipeline/BaseTasks/TaskCopyBundledFiles.cs +++ b/Assets/YooAsset/Editor/BundleBuilder/BuildPipeline/BaseTasks/TaskCopyBundledFiles.cs @@ -36,7 +36,8 @@ namespace YooAsset.Editor // 清空首包资源目录 if (copyOption == EBundledCopyOption.ClearAndCopyAll || copyOption == EBundledCopyOption.ClearAndCopyByTags) { - EditorFileUtility.ClearFolder(bundledRootDirectory); + EditorFileUtility.DeleteDirectory(bundledRootDirectory); + EditorFileUtility.CreateDirectory(bundledRootDirectory); } // 拷贝补丁清单文件 diff --git a/Assets/YooAsset/Editor/Common/EditorAssetInfo.cs b/Assets/YooAsset/Editor/Common/EditorAssetInfo.cs index ecd0637a..eb413074 100644 --- a/Assets/YooAsset/Editor/Common/EditorAssetInfo.cs +++ b/Assets/YooAsset/Editor/Common/EditorAssetInfo.cs @@ -7,6 +7,7 @@ namespace YooAsset.Editor [Serializable] public class EditorAssetInfo : IComparable { + [NonSerialized] private string _fileExtension = null; /// diff --git a/Assets/YooAsset/Editor/Utilities/EditorFileUtility.cs b/Assets/YooAsset/Editor/Utilities/EditorFileUtility.cs index 8b297780..db777aa6 100644 --- a/Assets/YooAsset/Editor/Utilities/EditorFileUtility.cs +++ b/Assets/YooAsset/Editor/Utilities/EditorFileUtility.cs @@ -118,28 +118,6 @@ namespace YooAsset.Editor File.Copy(sourcePath, destPath, overwrite); } - /// - /// 清空文件夹 - /// - /// 要清理的文件夹路径 - public static void ClearFolder(string directoryPath) - { - if (Directory.Exists(directoryPath) == false) - return; - - string[] allFiles = Directory.GetFiles(directoryPath); - for (int i = 0; i < allFiles.Length; i++) - { - File.Delete(allFiles[i]); - } - - string[] allFolders = Directory.GetDirectories(directoryPath); - for (int i = 0; i < allFolders.Length; i++) - { - Directory.Delete(allFolders[i], true); - } - } - /// /// 获取文件字节大小 /// diff --git a/Assets/YooAsset/LICENSE.md b/Assets/YooAsset/LICENSE.md index 075661d2..5db28162 100644 --- a/Assets/YooAsset/LICENSE.md +++ b/Assets/YooAsset/LICENSE.md @@ -187,7 +187,7 @@ identification within third-party archives. Copyright 2018-2021 何冠峰 - Copyright 2021-2025 TuYoo Games + Copyright 2021-2026 TuYoo Games Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/Assets/YooAsset/Runtime/Assembly/AssemblyInfo.cs b/Assets/YooAsset/Runtime/Assembly/AssemblyInfo.cs index eec2c904..9cc6b5a3 100644 --- a/Assets/YooAsset/Runtime/Assembly/AssemblyInfo.cs +++ b/Assets/YooAsset/Runtime/Assembly/AssemblyInfo.cs @@ -1,12 +1,12 @@ -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; // 内部友元 [assembly: InternalsVisibleTo("YooAsset.Editor")] -[assembly: InternalsVisibleTo("YooAsset.Test")] -[assembly: InternalsVisibleTo("YooAsset.Test.Editor")] +[assembly: InternalsVisibleTo("YooAsset.Tests")] +[assembly: InternalsVisibleTo("YooAsset.Tests.Editor")] // 外部友元 [assembly: InternalsVisibleTo("YooAsset.MiniGame")] -[assembly: InternalsVisibleTo("YooAsset.RuntimeExtension")] -[assembly: InternalsVisibleTo("YooAsset.EditorExtension")] +[assembly: InternalsVisibleTo("YooAsset.Extension")] +[assembly: InternalsVisibleTo("YooAsset.Extension.Editor")] [assembly: InternalsVisibleTo("Assembly-CSharp-Editor")] \ No newline at end of file diff --git a/Assets/YooAsset/Runtime/AsyncOperation/AsyncOperationBase.cs b/Assets/YooAsset/Runtime/AsyncOperation/AsyncOperationBase.cs index 22e1fe1c..14fad5b6 100644 --- a/Assets/YooAsset/Runtime/AsyncOperation/AsyncOperationBase.cs +++ b/Assets/YooAsset/Runtime/AsyncOperation/AsyncOperationBase.cs @@ -8,7 +8,7 @@ namespace YooAsset /// /// 异步操作基类 /// - public abstract class AsyncOperationBase : IEnumerator, IComparable + public abstract partial class AsyncOperationBase : IEnumerator, IComparable { private List _children; private Action _completedCallback; diff --git a/Assets/YooAsset/Runtime/AsyncOperation/EOperationStatus.cs b/Assets/YooAsset/Runtime/AsyncOperation/EOperationStatus.cs index 103f4a81..ef9ef0d9 100644 --- a/Assets/YooAsset/Runtime/AsyncOperation/EOperationStatus.cs +++ b/Assets/YooAsset/Runtime/AsyncOperation/EOperationStatus.cs @@ -25,5 +25,13 @@ namespace YooAsset /// 已失败 /// Failed, + +#if YOOASSET_LEGACY_API + /// + /// v2.3 兼容别名 + /// + [System.Obsolete("Use Succeeded instead.")] + Succeed = Succeeded, +#endif } } \ No newline at end of file diff --git a/Assets/YooAsset/Runtime/BundleCache/Operations/Common/LoadWebAssetBundleOptions.cs b/Assets/YooAsset/Runtime/BundleCache/Operations/Common/LoadWebAssetBundleOptions.cs index 0333edbe..59690191 100644 --- a/Assets/YooAsset/Runtime/BundleCache/Operations/Common/LoadWebAssetBundleOptions.cs +++ b/Assets/YooAsset/Runtime/BundleCache/Operations/Common/LoadWebAssetBundleOptions.cs @@ -43,7 +43,7 @@ namespace YooAsset public int WatchdogTimeout { get; } /// - /// 禁用Unity的网络缓存 + /// 禁用 Unity 内置网络缓存 /// public bool DisableUnityWebCache { get; } diff --git a/Assets/YooAsset/Runtime/BundleCache/Policies/EvictionByLocationsPolicy.cs b/Assets/YooAsset/Runtime/BundleCache/Policies/EvictionByLocationsPolicy.cs index 109b8036..c6fd4a58 100644 --- a/Assets/YooAsset/Runtime/BundleCache/Policies/EvictionByLocationsPolicy.cs +++ b/Assets/YooAsset/Runtime/BundleCache/Policies/EvictionByLocationsPolicy.cs @@ -6,7 +6,7 @@ namespace YooAsset /// 按资源地址清理缓存文件 /// /// - /// ClearParameter 类型:string / string[] / List + /// ClearParameter 类型:string / array / list /// internal class EvictionByLocationsPolicy : ICacheEvictionPolicy { diff --git a/Assets/YooAsset/Runtime/BundleCache/Policies/EvictionByTagsPolicy.cs b/Assets/YooAsset/Runtime/BundleCache/Policies/EvictionByTagsPolicy.cs index 6a2b6ead..3001fbde 100644 --- a/Assets/YooAsset/Runtime/BundleCache/Policies/EvictionByTagsPolicy.cs +++ b/Assets/YooAsset/Runtime/BundleCache/Policies/EvictionByTagsPolicy.cs @@ -6,7 +6,7 @@ namespace YooAsset /// 按标签清理缓存文件 /// /// - /// ClearParameter 类型:string / string[] / List + /// ClearParameter 类型:string / array / list /// internal class EvictionByTagsPolicy : ICacheEvictionPolicy { diff --git a/Assets/YooAsset/Runtime/BundleCache/Services/WebGameBundleCache/WebGameBundleCache.cs b/Assets/YooAsset/Runtime/BundleCache/Services/WebGameBundleCache/WebGameBundleCache.cs index 70a4b6e2..81a4fa38 100644 --- a/Assets/YooAsset/Runtime/BundleCache/Services/WebGameBundleCache/WebGameBundleCache.cs +++ b/Assets/YooAsset/Runtime/BundleCache/Services/WebGameBundleCache/WebGameBundleCache.cs @@ -20,7 +20,7 @@ namespace YooAsset public int WatchdogTimeout { get; } /// - /// 禁用Unity的网络缓存 + /// 禁用 Unity 内置网络缓存 /// public bool DisableUnityWebCache { get; } diff --git a/Assets/YooAsset/Runtime/BundleCache/Services/WebRemoteBundleCache/WebRemoteBundleCache.cs b/Assets/YooAsset/Runtime/BundleCache/Services/WebRemoteBundleCache/WebRemoteBundleCache.cs index 659b163c..cf1f9c2d 100644 --- a/Assets/YooAsset/Runtime/BundleCache/Services/WebRemoteBundleCache/WebRemoteBundleCache.cs +++ b/Assets/YooAsset/Runtime/BundleCache/Services/WebRemoteBundleCache/WebRemoteBundleCache.cs @@ -16,7 +16,7 @@ namespace YooAsset public int WatchdogTimeout { get; } /// - /// 禁用Unity的网络缓存 + /// 禁用 Unity 内置网络缓存 /// public bool DisableUnityWebCache { get; } diff --git a/Assets/YooAsset/Runtime/BundleCache/Services/WebServerBundleCache/WebServerBundleCache.cs b/Assets/YooAsset/Runtime/BundleCache/Services/WebServerBundleCache/WebServerBundleCache.cs index 9af6bdb2..fe3de2eb 100644 --- a/Assets/YooAsset/Runtime/BundleCache/Services/WebServerBundleCache/WebServerBundleCache.cs +++ b/Assets/YooAsset/Runtime/BundleCache/Services/WebServerBundleCache/WebServerBundleCache.cs @@ -16,7 +16,7 @@ namespace YooAsset public int WatchdogTimeout { get; } /// - /// 禁用Unity的网络缓存 + /// 禁用 Unity 内置网络缓存 /// public bool DisableUnityWebCache { get; } diff --git a/Assets/YooAsset/Docs.meta b/Assets/YooAsset/Runtime/Compatibility.meta similarity index 77% rename from Assets/YooAsset/Docs.meta rename to Assets/YooAsset/Runtime/Compatibility.meta index cfee7de1..6629621e 100644 --- a/Assets/YooAsset/Docs.meta +++ b/Assets/YooAsset/Runtime/Compatibility.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: b819eb7cc257867439445ce1a660ea0a +guid: da061aaa46527ed44a052b269aeeaeb0 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/YooAsset/Runtime/Compatibility/CompatibleDownloader.cs b/Assets/YooAsset/Runtime/Compatibility/CompatibleDownloader.cs new file mode 100644 index 00000000..7321b557 --- /dev/null +++ b/Assets/YooAsset/Runtime/Compatibility/CompatibleDownloader.cs @@ -0,0 +1,191 @@ +#if YOOASSET_LEGACY_API +// YooAsset v2.3 兼容层 - DownloaderOperation 兼容 +// 通过 partial class 为 DownloaderOperation 补充 v2.3 的旧式回调属性和 BeginDownload 方法。 + +using System; + +namespace YooAsset +{ + #region v2.3 委托和数据类型 + /// + /// v2.3 下载器结束回调委托 + /// + [Obsolete("Use DownloadCompleted event instead.")] + public delegate void DownloaderFinish(DownloaderFinishData data); + + /// + /// v2.3 下载进度更新回调委托 + /// + [Obsolete("Use DownloadProgressChanged event instead.")] + public delegate void DownloadUpdateDelegate(DownloadUpdateData data); + + /// + /// v2.3 下载错误回调委托(重命名以避免与 v3 event DownloadError 冲突) + /// + [Obsolete("Use DownloadError event instead.")] + public delegate void DownloadErrorDelegate(DownloadErrorData data); + + /// + /// v2.3 开始下载文件回调委托 + /// + [Obsolete("Use DownloadFileStarted event instead.")] + public delegate void DownloadFileBeginDelegate(DownloadFileData data); + + /// + /// v2.3 下载器结束数据 + /// + [Obsolete("Use DownloadCompletedEventArgs instead.")] + public struct DownloaderFinishData + { + public string PackageName; + public bool Succeed; + } + + /// + /// v2.3 下载进度更新数据 + /// + [Obsolete("Use DownloadProgressChangedEventArgs instead.")] + public struct DownloadUpdateData + { + public string PackageName; + public float Progress; + public int TotalDownloadCount; + public int CurrentDownloadCount; + public long TotalDownloadBytes; + public long CurrentDownloadBytes; + } + + /// + /// v2.3 下载错误数据 + /// + [Obsolete("Use DownloadErrorEventArgs instead.")] + public struct DownloadErrorData + { + public string PackageName; + public string FileName; + public string ErrorInfo; + } + + /// + /// v2.3 下载文件数据 + /// + [Obsolete("Use DownloadFileStartedEventArgs instead.")] + public struct DownloadFileData + { + public string PackageName; + public string FileName; + public long FileSize; + } + #endregion + + public abstract partial class DownloaderOperation + { + private DownloaderFinish _downloadFinishCallback; + private DownloadUpdateDelegate _downloadUpdateCallback; + private DownloadErrorDelegate _downloadErrorCallback; + private DownloadFileBeginDelegate _downloadFileBeginCallback; + + private bool _finishBridged; + private bool _updateBridged; + private bool _errorBridged; + private bool _fileBeginBridged; + + [Obsolete("Use DownloadCompleted event instead.")] + public DownloaderFinish DownloadFinishCallback + { + get => _downloadFinishCallback; + set + { + _downloadFinishCallback = value; + if (!_finishBridged && value != null) + { + _finishBridged = true; + DownloadCompleted += args => + { + _downloadFinishCallback?.Invoke(new DownloaderFinishData + { + PackageName = args.PackageName, + Succeed = args.Succeeded + }); + }; + } + } + } + + [Obsolete("Use DownloadProgressChanged event instead.")] + public DownloadUpdateDelegate DownloadUpdateCallback + { + get => _downloadUpdateCallback; + set + { + _downloadUpdateCallback = value; + if (!_updateBridged && value != null) + { + _updateBridged = true; + DownloadProgressChanged += args => + { + _downloadUpdateCallback?.Invoke(new DownloadUpdateData + { + PackageName = args.PackageName, + Progress = args.Progress, + TotalDownloadCount = args.TotalDownloadCount, + CurrentDownloadCount = args.CurrentDownloadCount, + TotalDownloadBytes = args.TotalDownloadBytes, + CurrentDownloadBytes = args.CurrentDownloadBytes + }); + }; + } + } + } + + [Obsolete("Use DownloadError event instead.")] + public DownloadErrorDelegate DownloadErrorCallback + { + get => _downloadErrorCallback; + set + { + _downloadErrorCallback = value; + if (!_errorBridged && value != null) + { + _errorBridged = true; + DownloadError += args => + { + _downloadErrorCallback?.Invoke(new DownloadErrorData + { + PackageName = args.PackageName, + FileName = args.FileName, + ErrorInfo = args.ErrorInfo + }); + }; + } + } + } + + [Obsolete("Use DownloadFileStarted event instead.")] + public DownloadFileBeginDelegate DownloadFileBeginCallback + { + get => _downloadFileBeginCallback; + set + { + _downloadFileBeginCallback = value; + if (!_fileBeginBridged && value != null) + { + _fileBeginBridged = true; + DownloadFileStarted += args => + { + _downloadFileBeginCallback?.Invoke(new DownloadFileData + { + PackageName = args.PackageName, + FileName = args.FileName, + FileSize = args.FileSize + }); + }; + } + } + } + + [Obsolete("Use StartDownload() instead.")] + public void BeginDownload() => StartDownload(); + } +} +#endif diff --git a/Assets/YooAsset/Samples~/Mini Game/Runtime/Test/TiktokFileSystemTest.cs.meta b/Assets/YooAsset/Runtime/Compatibility/CompatibleDownloader.cs.meta similarity index 83% rename from Assets/YooAsset/Samples~/Mini Game/Runtime/Test/TiktokFileSystemTest.cs.meta rename to Assets/YooAsset/Runtime/Compatibility/CompatibleDownloader.cs.meta index f20dba27..fc93ff38 100644 --- a/Assets/YooAsset/Samples~/Mini Game/Runtime/Test/TiktokFileSystemTest.cs.meta +++ b/Assets/YooAsset/Runtime/Compatibility/CompatibleDownloader.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: ec9d17e765ccd4642aa3aa9ca0580799 +guid: 5c410769c5f99174897f030039b64116 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/YooAsset/Runtime/Compatibility/CompatibleFileSystem.cs b/Assets/YooAsset/Runtime/Compatibility/CompatibleFileSystem.cs new file mode 100644 index 00000000..47a400d6 --- /dev/null +++ b/Assets/YooAsset/Runtime/Compatibility/CompatibleFileSystem.cs @@ -0,0 +1,293 @@ +#if YOOASSET_LEGACY_API +// YooAsset v2.3 兼容层 - 文件系统参数兼容 +// 通过 partial class 为 FileSystemParameters 补充 v2.3 旧工厂方法, +// 并恢复 FileSystemParametersDefine、IRemoteServices、IManifestRestoreServices 等旧类型。 + +using System; +using System.Collections.Generic; +using System.IO; +using UnityEngine; + +namespace YooAsset +{ + #region FileSystemParameters partial + + public partial class FileSystemParameters + { + /// + /// v2.3 属性名,转发到 v3 的 FileSystemTypeName + /// + [Obsolete("Use FileSystemTypeName instead.")] + public string FileSystemClass + { + get { return FileSystemTypeName; } + } + + /// + /// 兼容 v2.3 REMOTE_SERVICES 参数对象 + /// + [Obsolete("Use AddParameter(EFileSystemParameter.RemoteService, IRemoteService) instead.")] + public void AddParameter(string paramName, IRemoteServices value) + { + if (paramName == FileSystemParametersDefine.REMOTE_SERVICES && value != null) + AddParameter(paramName, new RemoteServicesAdapter(value)); + else + AddParameter(paramName, (object)value); + } + + /// + /// 兼容 v2.3 MANIFEST_SERVICES 参数对象 + /// + [Obsolete("Use AddParameter(EFileSystemParameter.ManifestDecryptor, IManifestDecryptor) instead.")] + public void AddParameter(string paramName, IManifestRestoreServices value) + { + if (paramName == FileSystemParametersDefine.MANIFEST_SERVICES && value != null) + AddParameter(paramName, new ManifestRestoreServicesAdapter(value)); + else + AddParameter(paramName, (object)value); + } + + /// + /// 兼容 v2.3 INSTALL_CLEAR_MODE 枚举值 + /// + [Obsolete("Use AddParameter(EFileSystemParameter.InstallCleanupMode, EInstallCleanupMode) instead.")] + public void AddParameter(string paramName, EOverwriteInstallClearMode value) + { + if (paramName == FileSystemParametersDefine.INSTALL_CLEAR_MODE) + AddParameter(paramName, (EInstallCleanupMode)(int)value); + else + AddParameter(paramName, (object)value); + } + + /// + /// v2.3 旧拼写入口,转发到 CreateDefaultBuiltinFileSystemParameters + /// + [Obsolete("Use CreateDefaultBuiltinFileSystemParameters instead. IDecryptionServices is no longer supported; pass null or migrate to IBundleDecryptor.")] + public static FileSystemParameters CreateDefaultBuildinFileSystemParameters(IDecryptionServices decryptionServices = null, string packageRoot = null) + { + return CreateDefaultBuiltinFileSystemParameters(packageRoot); + } + + /// + /// v2.3 缓存文件系统入口,转发到 CreateDefaultSandboxFileSystemParameters + /// + [Obsolete("Use CreateDefaultSandboxFileSystemParameters instead. IDecryptionServices is no longer supported; pass null or migrate to IBundleDecryptor.")] + public static FileSystemParameters CreateDefaultCacheFileSystemParameters(IRemoteServices remoteServices, IDecryptionServices decryptionServices = null, string packageRoot = null) + { + var adapter = remoteServices != null ? new RemoteServicesAdapter(remoteServices) : null; + return CreateDefaultSandboxFileSystemParameters(adapter, packageRoot); + } + + /// + /// v2.3 WebServer 文件系统入口 + /// + [Obsolete("Use CreateDefaultWebServerFileSystemParameters(bool) instead. IWebDecryptionServices is no longer supported.")] + public static FileSystemParameters CreateDefaultWebServerFileSystemParameters(IWebDecryptionServices decryptionServices, bool disableUnityWebCache = false) + { + return CreateDefaultWebServerFileSystemParameters(disableUnityWebCache); + } + + /// + /// v2.3 WebRemote 文件系统入口 + /// + [Obsolete("Use CreateDefaultWebRemoteFileSystemParameters(IRemoteService, bool) instead. IWebDecryptionServices is no longer supported.")] + public static FileSystemParameters CreateDefaultWebRemoteFileSystemParameters(IRemoteServices remoteServices, IWebDecryptionServices decryptionServices = null, bool disableUnityWebCache = false) + { + var adapter = remoteServices != null ? new RemoteServicesAdapter(remoteServices) : null; + return CreateDefaultWebRemoteFileSystemParameters(adapter, disableUnityWebCache); + } + } + + #endregion + + #region FileSystemParametersDefine + + /// + /// v2.3 文件系统参数常量定义 + /// + [Obsolete("Use EFileSystemParameter enum instead.")] + public static class FileSystemParametersDefine + { + public const string FILE_VERIFY_LEVEL = "FileVerifyLevel"; + public const string FILE_VERIFY_MAX_CONCURRENCY = "FileVerifyMaxConcurrency"; + public const string INSTALL_CLEAR_MODE = "InstallCleanupMode"; + public const string REMOTE_SERVICES = "RemoteService"; + public const string DECRYPTION_SERVICES = "DECRYPTION_SERVICES"; + public const string MANIFEST_SERVICES = "ManifestDecryptor"; + public const string APPEND_FILE_EXTENSION = "APPEND_FILE_EXTENSION"; + public const string DISABLE_CATALOG_FILE = "DISABLE_CATALOG_FILE"; + public const string DISABLE_UNITY_WEB_CACHE = "DisableUnityWebCache"; + public const string DISABLE_ONDEMAND_DOWNLOAD = "DownloadDisableOndemand"; + public const string DOWNLOAD_MAX_CONCURRENCY = "DownloadMaxConcurrency"; + public const string DOWNLOAD_MAX_REQUEST_PER_FRAME = "DownloadMaxRequestPerFrame"; + public const string DOWNLOAD_WATCH_DOG_TIME = "DownloadWatchdogTimeout"; + public const string RESUME_DOWNLOAD_MINMUM_SIZE = "DownloadResumeMinimumSize"; + public const string RESUME_DOWNLOAD_RESPONSE_CODES = "RESUME_DOWNLOAD_RESPONSE_CODES"; + public const string VIRTUAL_WEBGL_MODE = "VirtualWebglMode"; + public const string VIRTUAL_DOWNLOAD_MODE = "VirtualDownloadMode"; + public const string VIRTUAL_DOWNLOAD_SPEED = "VirtualDownloadSpeed"; + public const string ASYNC_SIMULATE_MIN_FRAME = "AsyncSimulateMinFrame"; + public const string ASYNC_SIMULATE_MAX_FRAME = "AsyncSimulateMaxFrame"; + public const string COPY_BUILDIN_PACKAGE_MANIFEST = "CopyBuiltinPackageManifest"; + public const string COPY_BUILDIN_PACKAGE_MANIFEST_DEST_ROOT = "CopyBuiltinPackageManifestDestRoot"; + public const string COPY_LOCAL_FILE_SERVICES = "COPY_LOCAL_FILE_SERVICES"; + public const string UNPACK_FILE_SYSTEM_ROOT = "UnpackFileSystemRoot"; + } + + #endregion + + #region EOverwriteInstallClearMode + + /// + /// v2.3 覆盖安装清理模式(v3 改为 EInstallCleanupMode) + /// + [Obsolete("Use EInstallCleanupMode instead.")] + public enum EOverwriteInstallClearMode + { + None = 0, + ClearAllCacheFiles = 1, + ClearAllBundleFiles = 2, + ClearAllManifestFiles = 3, + } + + #endregion + + #region IRemoteServices + + /// + /// v2.3 远端资源地址查询服务接口 + /// + [Obsolete("Implement IRemoteService instead.")] + public interface IRemoteServices + { + string GetRemoteMainURL(string fileName); + string GetRemoteFallbackURL(string fileName); + } + + /// + /// 将 v2.3 IRemoteServices 适配为 v3 IRemoteService + /// + internal sealed class RemoteServicesAdapter : IRemoteService + { + private readonly IRemoteServices _legacy; + + public RemoteServicesAdapter(IRemoteServices legacy) + { + _legacy = legacy; + } + + public IReadOnlyList GetRemoteUrls(string fileName) + { + var urls = new List(2); + string main = _legacy.GetRemoteMainURL(fileName); + if (!string.IsNullOrEmpty(main)) + urls.Add(main); + string fallback = _legacy.GetRemoteFallbackURL(fileName); + if (!string.IsNullOrEmpty(fallback)) + urls.Add(fallback); + return urls; + } + } + + #endregion + + #region IManifestRestoreServices + + /// + /// v2.3 资源清单文件处理服务接口 + /// + [Obsolete("Implement IManifestDecryptor instead.")] + public interface IManifestRestoreServices + { + byte[] RestoreManifest(byte[] fileData); + } + + /// + /// 将 v2.3 IManifestRestoreServices 适配为 v3 IManifestDecryptor + /// + internal sealed class ManifestRestoreServicesAdapter : IManifestDecryptor + { + private readonly IManifestRestoreServices _legacy; + + public ManifestRestoreServicesAdapter(IManifestRestoreServices legacy) + { + _legacy = legacy; + } + + public byte[] Decrypt(byte[] fileData) + { + return _legacy.RestoreManifest(fileData); + } + } + + #endregion + + #region IDecryptionServices / IWebDecryptionServices + + /// + /// v2.3 解密文件信息(仅用于旧接口编译兼容) + /// + [Obsolete("v3 has split decryption into dedicated decryptor interfaces. Manual migration required.")] + public struct DecryptFileInfo + { + public string BundleName; + public string FileLoadPath; + public uint FileLoadCRC; + } + + /// + /// v2.3 解密结果(仅用于旧接口编译兼容) + /// + [Obsolete("v3 has split decryption into dedicated decryptor interfaces. Manual migration required.")] + public struct DecryptResult + { + public AssetBundle Result; + public AssetBundleCreateRequest CreateRequest; + public Stream ManagedStream; + } + + /// + /// v2.3 加密文件解密服务接口(仅用于编译兼容,无法自动转换为 v3 解密器) + /// + [Obsolete("v3 has split decryption into IBundleOffsetDecryptor, IBundleMemoryDecryptor, IBundleStreamDecryptor. Manual migration required.")] + public interface IDecryptionServices + { + DecryptResult LoadAssetBundle(DecryptFileInfo fileInfo); + DecryptResult LoadAssetBundleAsync(DecryptFileInfo fileInfo); + DecryptResult LoadAssetBundleFallback(DecryptFileInfo fileInfo); + byte[] ReadFileData(DecryptFileInfo fileInfo); + string ReadFileText(DecryptFileInfo fileInfo); + } + + /// + /// v2.3 Web 解密文件信息(仅用于旧接口编译兼容) + /// + [Obsolete("v3 no longer supports IWebDecryptionServices. Manual migration required.")] + public struct WebDecryptFileInfo + { + public string BundleName; + public uint FileLoadCRC; + public byte[] FileData; + } + + /// + /// v2.3 Web 解密结果(仅用于旧接口编译兼容) + /// + [Obsolete("v3 no longer supports IWebDecryptionServices. Manual migration required.")] + public struct WebDecryptResult + { + public AssetBundle Result; + } + + /// + /// v2.3 Web 解密服务接口(仅用于编译兼容,无法自动转换为 v3 解密器) + /// + [Obsolete("v3 no longer supports IWebDecryptionServices. Manual migration required.")] + public interface IWebDecryptionServices + { + WebDecryptResult LoadAssetBundle(WebDecryptFileInfo fileInfo); + } + + #endregion +} +#endif diff --git a/Assets/YooAsset/Samples~/Extension Sample/Runtime/CompatibleOldVersion.cs.meta b/Assets/YooAsset/Runtime/Compatibility/CompatibleFileSystem.cs.meta similarity index 83% rename from Assets/YooAsset/Samples~/Extension Sample/Runtime/CompatibleOldVersion.cs.meta rename to Assets/YooAsset/Runtime/Compatibility/CompatibleFileSystem.cs.meta index 466e4d35..e678a970 100644 --- a/Assets/YooAsset/Samples~/Extension Sample/Runtime/CompatibleOldVersion.cs.meta +++ b/Assets/YooAsset/Runtime/Compatibility/CompatibleFileSystem.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: b974d3d744622f3499d026f99074cd72 +guid: a0283a72bdff8b54b93d9807ba038dbf MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/YooAsset/Runtime/Compatibility/CompatibleHandles.cs b/Assets/YooAsset/Runtime/Compatibility/CompatibleHandles.cs new file mode 100644 index 00000000..8ad9d3a5 --- /dev/null +++ b/Assets/YooAsset/Runtime/Compatibility/CompatibleHandles.cs @@ -0,0 +1,207 @@ +#if YOOASSET_LEGACY_API +// YooAsset v2.3 兼容层 - Handle 类兼容 +// 通过 partial class 为各 Handle 类补充 v2.3 的旧属性和方法。 + +using System; +using System.Threading.Tasks; +using UnityEngine; + +namespace YooAsset +{ + /// + /// v2.3 下载状态结构体(v3 已移除) + /// + [Obsolete("DownloadStatus has been removed in v3.")] + public struct DownloadStatus + { + public bool IsDone; + public float Progress; + public long TotalBytes; + public long DownloadedBytes; + + public static DownloadStatus CreateDefaultStatus() + { + return new DownloadStatus(); + } + } + + #region HandleBase -- LastError / Task / GetDownloadStatus + public abstract partial class HandleBase + { + private TaskCompletionSource _taskCompletionSource; + + /// + /// v2.3: handle.LastError + /// + [Obsolete("Use Error instead.")] + public string LastError => Error; + + /// + /// v2.3: handle.Task (System.Threading.Tasks.Task) + /// + [Obsolete("Use 'await handle' directly instead.")] + public Task Task + { + get + { + if (CheckValidWithWarning() == false) + return null; + + if (_taskCompletionSource == null) + { + _taskCompletionSource = new TaskCompletionSource(); + if (Provider.IsDone) + { + _taskCompletionSource.TrySetResult(null); + } + else + { + Provider.Completed += delegate (AsyncOperationBase op) + { + _taskCompletionSource.TrySetResult(null); + }; + } + } + return _taskCompletionSource.Task; + } + } + + /// + /// v2.3: handle.GetDownloadStatus() + /// + [Obsolete("GetDownloadStatus is no longer supported in v3.")] + public DownloadStatus GetDownloadStatus() + { + return DownloadStatus.CreateDefaultStatus(); + } + } + #endregion + + #region SceneHandle -- UnSuspend / UnloadAsync + public sealed partial class SceneHandle + { + /// + /// v2.3: sceneHandle.UnSuspend() -> v3: AllowSceneActivation() + /// + [Obsolete("Use AllowSceneActivation() instead.")] + public bool UnSuspend() => AllowSceneActivation(); + + /// + /// v2.3: sceneHandle.UnloadAsync() -> v3: UnloadSceneAsync() + /// + [Obsolete("Use UnloadSceneAsync() instead.")] + public UnloadSceneOperation UnloadAsync() => UnloadSceneAsync(); + } + #endregion + + #region RawFileHandle -- GetRawFileData / GetRawFileText + public sealed partial class RawFileHandle + { + /// + /// v2.3: rawFileHandle.GetRawFileData() + /// + [Obsolete("Read file manually via GetRawFilePath().")] + public byte[] GetRawFileData() + { + if (CheckValidWithWarning() == false) return null; + return System.IO.File.ReadAllBytes(GetRawFilePath()); + } + + /// + /// v2.3: rawFileHandle.GetRawFileText() + /// + [Obsolete("Read file manually via GetRawFilePath().")] + public string GetRawFileText() + { + if (CheckValidWithWarning() == false) return null; + return System.IO.File.ReadAllText(GetRawFilePath()); + } + } + #endregion + + #region AssetHandle -- Instantiate 多重载 + public sealed partial class AssetHandle + { + /// + /// v2.3: handle.InstantiateSync(parent) + /// + [Obsolete("Use InstantiateSync(InstantiateOptions) instead.")] + public GameObject InstantiateSync(Transform parent) + { + var options = new InstantiateOptions(true, parent, false); + return InstantiateSync(options); + } + + /// + /// v2.3: handle.InstantiateSync(parent, worldPositionStays) + /// + [Obsolete("Use InstantiateSync(InstantiateOptions) instead.")] + public GameObject InstantiateSync(Transform parent, bool worldPositionStays) + { + var options = new InstantiateOptions(true, parent, worldPositionStays); + return InstantiateSync(options); + } + + /// + /// v2.3: handle.InstantiateSync(position, rotation) + /// + [Obsolete("Use InstantiateSync(InstantiateOptions) instead.")] + public GameObject InstantiateSync(Vector3 position, Quaternion rotation) + { + var options = new InstantiateOptions(true, position, rotation); + return InstantiateSync(options); + } + + /// + /// v2.3: handle.InstantiateSync(position, rotation, parent) + /// + [Obsolete("Use InstantiateSync(InstantiateOptions) instead.")] + public GameObject InstantiateSync(Vector3 position, Quaternion rotation, Transform parent) + { + var options = new InstantiateOptions(true, parent, position, rotation); + return InstantiateSync(options); + } + + /// + /// v2.3: handle.InstantiateAsync(parent, actived) + /// + [Obsolete("Use InstantiateAsync(InstantiateOptions) instead.")] + public InstantiateOperation InstantiateAsync(Transform parent, bool actived = true) + { + var options = new InstantiateOptions(actived, parent, false); + return InstantiateAsync(options); + } + + /// + /// v2.3: handle.InstantiateAsync(parent, worldPositionStays, actived) + /// + [Obsolete("Use InstantiateAsync(InstantiateOptions) instead.")] + public InstantiateOperation InstantiateAsync(Transform parent, bool worldPositionStays, bool actived = true) + { + var options = new InstantiateOptions(actived, parent, worldPositionStays); + return InstantiateAsync(options); + } + + /// + /// v2.3: handle.InstantiateAsync(position, rotation, actived) + /// + [Obsolete("Use InstantiateAsync(InstantiateOptions) instead.")] + public InstantiateOperation InstantiateAsync(Vector3 position, Quaternion rotation, bool actived = true) + { + var options = new InstantiateOptions(actived, position, rotation); + return InstantiateAsync(options); + } + + /// + /// v2.3: handle.InstantiateAsync(position, rotation, parent, actived) + /// + [Obsolete("Use InstantiateAsync(InstantiateOptions) instead.")] + public InstantiateOperation InstantiateAsync(Vector3 position, Quaternion rotation, Transform parent, bool actived = true) + { + var options = new InstantiateOptions(actived, parent, position, rotation); + return InstantiateAsync(options); + } + } + #endregion +} +#endif diff --git a/Assets/YooAsset/Samples~/Extension Sample/Runtime/ExtensionOperation/CopyBuildinManifestOperation.cs.meta b/Assets/YooAsset/Runtime/Compatibility/CompatibleHandles.cs.meta similarity index 83% rename from Assets/YooAsset/Samples~/Extension Sample/Runtime/ExtensionOperation/CopyBuildinManifestOperation.cs.meta rename to Assets/YooAsset/Runtime/Compatibility/CompatibleHandles.cs.meta index 745ac22b..bc8f12dc 100644 --- a/Assets/YooAsset/Samples~/Extension Sample/Runtime/ExtensionOperation/CopyBuildinManifestOperation.cs.meta +++ b/Assets/YooAsset/Runtime/Compatibility/CompatibleHandles.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 41ab464f315df234fb40b0c24e97ee23 +guid: 2c3bc780b23878f4f952d53fe28bf325 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/YooAsset/Runtime/Compatibility/CompatibleOperation.cs b/Assets/YooAsset/Runtime/Compatibility/CompatibleOperation.cs new file mode 100644 index 00000000..c75bcde3 --- /dev/null +++ b/Assets/YooAsset/Runtime/Compatibility/CompatibleOperation.cs @@ -0,0 +1,219 @@ +#if YOOASSET_LEGACY_API +// YooAsset v2.3 兼容层 - Operation 类型 +// 提供 v2.3 的 InitializeParameters 类族、Operation 包装类、ImportFileInfo 和 GameAsyncOperation。 + +using System; + +namespace YooAsset +{ + #region InitializeParameters + /// + /// v2.3 初始化参数基类 + /// + [Obsolete("Use specific Options classes (e.g. EditorSimulateModeOptions) instead.")] + public abstract class InitializeParameters + { + public int BundleLoadingMaxConcurrency = int.MaxValue; + public bool AutoUnloadBundleWhenUnused = false; + public bool WebGLForceSyncLoadAsset = false; + } + + /// + /// v2.3 编辑器模拟模式初始化参数 + /// + [Obsolete("Use EditorSimulateModeOptions instead.")] + public class EditorSimulateModeParameters : InitializeParameters + { + public FileSystemParameters EditorFileSystemParameters; + } + + /// + /// v2.3 离线运行模式初始化参数 + /// + [Obsolete("Use OfflinePlayModeOptions instead.")] + public class OfflinePlayModeParameters : InitializeParameters + { + public FileSystemParameters BuildinFileSystemParameters; + } + + /// + /// v2.3 联机运行模式初始化参数 + /// + [Obsolete("Use HostPlayModeOptions instead.")] + public class HostPlayModeParameters : InitializeParameters + { + public FileSystemParameters BuildinFileSystemParameters; + public FileSystemParameters CacheFileSystemParameters; + } + + /// + /// v2.3 WebGL 运行模式初始化参数 + /// + [Obsolete("Use WebPlayModeOptions instead.")] + public class WebPlayModeParameters : InitializeParameters + { + public FileSystemParameters WebServerFileSystemParameters; + public FileSystemParameters WebRemoteFileSystemParameters; + } + + /// + /// v2.3 自定义运行模式初始化参数 + /// + [Obsolete("Use CustomPlayModeOptions instead.")] + public class CustomPlayModeParameters : InitializeParameters + { + public readonly System.Collections.Generic.List FileSystemParameterList = new System.Collections.Generic.List(); + } + #endregion + + #region LegacyOperationWrapper + /// + /// v2.3 Operation 包装基类,将 v3 的 AsyncOperationBase 转发为旧类型。 + /// + [Obsolete("Use v3 operation types directly.")] + public class LegacyOperationWrapper : AsyncOperationBase + { + private bool _isDone = false; + protected readonly AsyncOperationBase _operation; + + internal LegacyOperationWrapper(AsyncOperationBase op) + { + _operation = op; + } + protected override void InternalStart() + { + } + protected override void InternalUpdate() + { + if (_isDone) + return; + + if (IsWaitForCompletion) + _operation.WaitForCompletion(); + + _operation.UpdateOperation(); + Progress = _operation.Progress; + if (_operation.IsDone == false) + return; + + _isDone = true; + if (_operation.Status == EOperationStatus.Succeeded) + SetResult(); + else + SetError(_operation.Error); + } + protected override void InternalWaitForCompletion() + { + ExecuteBatch(); + } + } + #endregion + + #region Legacy Operations + [Obsolete("Use InitializePackageOperation instead.")] + public class InitializationOperation : LegacyOperationWrapper + { + internal InitializationOperation(InitializePackageOperation op) : base(op) { } + } + + [Obsolete("Use DestroyPackageOperation instead.")] + public class DestroyOperation : LegacyOperationWrapper + { + internal DestroyOperation(DestroyPackageOperation op) : base(op) { } + } + + [Obsolete("Use LoadPackageManifestOperation instead.")] + public class UpdatePackageManifestOperation : LegacyOperationWrapper + { + internal UpdatePackageManifestOperation(LoadPackageManifestOperation op) : base(op) { } + } + + [Obsolete("Use PrefetchManifestOperation instead.")] + public class PreDownloadContentOperation : LegacyOperationWrapper + { + private readonly PrefetchManifestOperation _prefetchOp; + + internal PreDownloadContentOperation(PrefetchManifestOperation op) : base(op) + { + _prefetchOp = op; + } + + [Obsolete("Use PrefetchManifestOperation.CreateResourceDownloader(ResourceDownloaderOptions) instead.")] + public ResourceDownloaderOperation CreateResourceDownloader(int downloadingMaxNumber, int failedTryAgain) + { + var options = new ResourceDownloaderOptions(downloadingMaxNumber, failedTryAgain); + return _prefetchOp.CreateResourceDownloader(options); + } + + [Obsolete("Use PrefetchManifestOperation.CreateResourceDownloader(ResourceDownloaderOptions) instead.")] + public ResourceDownloaderOperation CreateResourceDownloader(string tag, int downloadingMaxNumber, int failedTryAgain) + { + string[] tags = new string[] { tag }; + var options = new ResourceDownloaderOptions(tags, downloadingMaxNumber, failedTryAgain); + return _prefetchOp.CreateResourceDownloader(options); + } + + [Obsolete("Use PrefetchManifestOperation.CreateResourceDownloader(ResourceDownloaderOptions) instead.")] + public ResourceDownloaderOperation CreateResourceDownloader(string[] tags, int downloadingMaxNumber, int failedTryAgain) + { + var options = new ResourceDownloaderOptions(tags, downloadingMaxNumber, failedTryAgain); + return _prefetchOp.CreateResourceDownloader(options); + } + } + #endregion + + #region ImportFileInfo + /// + /// v2.3 导入文件信息 + /// + [Obsolete("Use ImportBundleInfo instead.")] + public struct ImportFileInfo + { + public string FilePath; + public string BundleName; + public string BundleGUID; + } + #endregion + + #region GameAsyncOperation + /// + /// v2.3 游戏异步操作基类(v3 已移除) + /// + [Obsolete("GameAsyncOperation has been removed in v3. Use AsyncOperationBase directly.")] + public abstract class GameAsyncOperation : AsyncOperationBase + { + protected override void InternalStart() + { + OnStart(); + } + protected override void InternalUpdate() + { + OnUpdate(); + } + protected override void InternalAbort() + { + OnAbort(); + } + protected override void InternalWaitForCompletion() + { + OnWaitForAsyncComplete(); + } + + protected abstract void OnStart(); + protected abstract void OnUpdate(); + protected abstract void OnAbort(); + protected virtual void OnWaitForAsyncComplete() { } + + protected new bool IsBusy + { + get { return AsyncOperationSystem.IsBusy; } + } + + protected void Abort() + { + AbortOperation(); + } + } + #endregion +} +#endif diff --git a/Assets/YooAsset/Samples~/Mini Game/Runtime/Test/FileSystemTester.cs.meta b/Assets/YooAsset/Runtime/Compatibility/CompatibleOperation.cs.meta similarity index 83% rename from Assets/YooAsset/Samples~/Mini Game/Runtime/Test/FileSystemTester.cs.meta rename to Assets/YooAsset/Runtime/Compatibility/CompatibleOperation.cs.meta index e4df1b9b..471e5b7d 100644 --- a/Assets/YooAsset/Samples~/Mini Game/Runtime/Test/FileSystemTester.cs.meta +++ b/Assets/YooAsset/Runtime/Compatibility/CompatibleOperation.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 19ed2b81fece5b84fa771a5eeb108572 +guid: f16ea24f57c29f74babb8169fb1fa7a6 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/YooAsset/Runtime/Compatibility/CompatibleResourcePackage.cs b/Assets/YooAsset/Runtime/Compatibility/CompatibleResourcePackage.cs new file mode 100644 index 00000000..c4b3776c --- /dev/null +++ b/Assets/YooAsset/Runtime/Compatibility/CompatibleResourcePackage.cs @@ -0,0 +1,334 @@ +#if YOOASSET_LEGACY_API +// YooAsset v2.3 兼容层 - ResourcePackage 兼容方法 +// 通过 partial class 为 ResourcePackage 补充 v2.3 的旧接口。 + +using System; +using System.Collections.Generic; + +namespace YooAsset +{ + /// + /// v2.3 缓存清理模式(v3 改用字符串参数) + /// + [Obsolete("Use string-based ClearCacheOptions instead.")] + public enum EFileClearMode + { + ClearAllBundleFiles, + ClearUnusedBundleFiles, + ClearBundleFilesByLocations, + ClearBundleFilesByTags, + ClearAllManifestFiles, + ClearUnusedManifestFiles, + } + + public partial class ResourcePackage + { + #region 初始化 / 销毁 + [Obsolete("Use InitializePackageAsync(options) instead.")] + public InitializationOperation InitializeAsync(InitializeParameters parameters) + { + if (parameters is EditorSimulateModeParameters esp) + { + var options = new EditorSimulateModeOptions(); + options.BundleLoadingMaxConcurrency = esp.BundleLoadingMaxConcurrency; + options.AutoUnloadBundleWhenUnused = esp.AutoUnloadBundleWhenUnused; + options.WebGLForceSyncLoadAsset = esp.WebGLForceSyncLoadAsset; + options.EditorFileSystemParameters = esp.EditorFileSystemParameters; + var operation = InitializePackageAsync(options); + var wrapper = new InitializationOperation(operation); + AsyncOperationSystem.StartOperation(PackageName, wrapper); + return wrapper; + } + else if (parameters is OfflinePlayModeParameters opp) + { + var options = new OfflinePlayModeOptions(); + options.BundleLoadingMaxConcurrency = opp.BundleLoadingMaxConcurrency; + options.AutoUnloadBundleWhenUnused = opp.AutoUnloadBundleWhenUnused; + options.WebGLForceSyncLoadAsset = opp.WebGLForceSyncLoadAsset; + options.BuiltinFileSystemParameters = opp.BuildinFileSystemParameters; + var operation = InitializePackageAsync(options); + var wrapper = new InitializationOperation(operation); + AsyncOperationSystem.StartOperation(PackageName, wrapper); + return wrapper; + } + else if (parameters is HostPlayModeParameters hpp) + { + var options = new HostPlayModeOptions(); + options.BundleLoadingMaxConcurrency = hpp.BundleLoadingMaxConcurrency; + options.AutoUnloadBundleWhenUnused = hpp.AutoUnloadBundleWhenUnused; + options.WebGLForceSyncLoadAsset = hpp.WebGLForceSyncLoadAsset; + options.BuiltinFileSystemParameters = hpp.BuildinFileSystemParameters; + options.CacheFileSystemParameters = hpp.CacheFileSystemParameters; + var operation = InitializePackageAsync(options); + var wrapper = new InitializationOperation(operation); + AsyncOperationSystem.StartOperation(PackageName, wrapper); + return wrapper; + } + else if (parameters is WebPlayModeParameters wpp) + { + var options = new WebPlayModeOptions(); + options.BundleLoadingMaxConcurrency = wpp.BundleLoadingMaxConcurrency; + options.AutoUnloadBundleWhenUnused = wpp.AutoUnloadBundleWhenUnused; + options.WebGLForceSyncLoadAsset = wpp.WebGLForceSyncLoadAsset; + options.WebServerFileSystemParameters = wpp.WebServerFileSystemParameters; + options.WebRemoteFileSystemParameters = wpp.WebRemoteFileSystemParameters; + var operation = InitializePackageAsync(options); + var wrapper = new InitializationOperation(operation); + AsyncOperationSystem.StartOperation(PackageName, wrapper); + return wrapper; + } + else if (parameters is CustomPlayModeParameters cpp) + { + var options = new CustomPlayModeOptions(); + options.BundleLoadingMaxConcurrency = cpp.BundleLoadingMaxConcurrency; + options.AutoUnloadBundleWhenUnused = cpp.AutoUnloadBundleWhenUnused; + options.WebGLForceSyncLoadAsset = cpp.WebGLForceSyncLoadAsset; + foreach (var fsp in cpp.FileSystemParameterList) + options.FileSystemParameterList.Add(fsp); + var operation = InitializePackageAsync(options); + var wrapper = new InitializationOperation(operation); + AsyncOperationSystem.StartOperation(PackageName, wrapper); + return wrapper; + } + else + { + throw new NotImplementedException($"Unsupported InitializeParameters type: {parameters.GetType().Name}"); + } + } + + [Obsolete("Use DestroyPackageAsync() instead.")] + public DestroyOperation DestroyAsync() + { + var operation = DestroyPackageAsync(); + var wrapper = new DestroyOperation(operation); + AsyncOperationSystem.StartOperation(AsyncOperationSystem.GlobalSchedulerName, wrapper); + return wrapper; + } + #endregion + + #region 版本 / 清单 + [Obsolete("Use RequestPackageVersionAsync(RequestPackageVersionOptions) instead.")] + public RequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks, int timeout) + { + var options = new RequestPackageVersionOptions(appendTimeTicks, timeout); + return RequestPackageVersionAsync(options); + } + + [Obsolete("Use LoadPackageManifestAsync(LoadPackageManifestOptions) instead.")] + public UpdatePackageManifestOperation UpdatePackageManifestAsync(string packageVersion, int timeout = 60) + { + var options = new LoadPackageManifestOptions(packageVersion, timeout); + var operation = LoadPackageManifestAsync(options); + var wrapper = new UpdatePackageManifestOperation(operation); + AsyncOperationSystem.StartOperation(PackageName, wrapper); + return wrapper; + } + + [Obsolete("Use PrefetchManifestAsync(PrefetchManifestOptions) instead.")] + public PreDownloadContentOperation PreDownloadContentAsync(string packageVersion, int timeout = 60) + { + var options = new PrefetchManifestOptions(packageVersion, timeout); + var op = PrefetchManifestAsync(options); + var wrapper = new PreDownloadContentOperation(op); + AsyncOperationSystem.StartOperation(PackageName, wrapper); + return wrapper; + } + #endregion + + #region 缓存清理 + [Obsolete("Use ClearCacheAsync(ClearCacheOptions) instead.")] + public ClearCacheOperation ClearCacheFilesAsync(string fileClearMode, object clearParam = null) + { + var options = new ClearCacheOptions(fileClearMode, clearParam); + return ClearCacheAsync(options); + } + + [Obsolete("Use ClearCacheAsync(ClearCacheOptions) instead.")] + public ClearCacheOperation ClearCacheFilesAsync(EFileClearMode clearMode, object clearParam = null) + { + var options = new ClearCacheOptions(clearMode.ToString(), clearParam); + return ClearCacheAsync(options); + } + #endregion + + #region 资源卸载 + [Obsolete("Use UnloadUnusedAssetsAsync(UnloadUnusedAssetsOptions) instead.")] + public UnloadUnusedAssetsOperation UnloadUnusedAssetsAsync(int loopCount) + { + var options = new UnloadUnusedAssetsOptions(loopCount); + return UnloadUnusedAssetsAsync(options); + } + #endregion + + #region 资源查询 + [Obsolete("Use GetDownloadSize() instead.")] + public bool IsNeedDownloadFromRemote(string location) + { + return GetDownloadSize(location) > 0; + } + + [Obsolete("Use GetDownloadSize() instead.")] + public bool IsNeedDownloadFromRemote(AssetInfo assetInfo) + { + return GetDownloadSize(assetInfo) > 0; + } + + [Obsolete("Use IsLocationValid() instead.")] + public bool CheckLocationValid(string location) + { + return IsLocationValid(location); + } + + [Obsolete("Use GetAssetInfoByGuid() instead.")] + public AssetInfo GetAssetInfoByGUID(string assetGUID) + { + return GetAssetInfoByGuid(assetGUID); + } + + [Obsolete("Use GetAssetInfoByGuid() instead.")] + public AssetInfo GetAssetInfoByGUID(string assetGUID, Type type) + { + return GetAssetInfoByGuid(assetGUID, type); + } + #endregion + + #region 资源下载 + [Obsolete("Use CreateResourceDownloader(ResourceDownloaderOptions) instead.")] + public ResourceDownloaderOperation CreateResourceDownloader(int downloadingMaxNumber, int failedTryAgain) + { + var options = new ResourceDownloaderOptions(downloadingMaxNumber, failedTryAgain); + return CreateResourceDownloader(options); + } + + [Obsolete("Use CreateResourceDownloader(ResourceDownloaderOptions) instead.")] + public ResourceDownloaderOperation CreateResourceDownloader(string tag, int downloadingMaxNumber, int failedTryAgain) + { + string[] tags = new string[] { tag }; + var options = new ResourceDownloaderOptions(tags, downloadingMaxNumber, failedTryAgain); + return CreateResourceDownloader(options); + } + + [Obsolete("Use CreateResourceDownloader(ResourceDownloaderOptions) instead.")] + public ResourceDownloaderOperation CreateResourceDownloader(string[] tags, int downloadingMaxNumber, int failedTryAgain) + { + var options = new ResourceDownloaderOptions(tags, downloadingMaxNumber, failedTryAgain); + return CreateResourceDownloader(options); + } + + [Obsolete("Use CreateResourceDownloader(BundleDownloaderOptions) instead.")] + public ResourceDownloaderOperation CreateBundleDownloader(string location, bool recursiveDownload, int downloadingMaxNumber, int failedTryAgain) + { + var assetInfo = ConvertLocationToAssetInfo(location, null); + var options = new BundleDownloaderOptions(assetInfo, recursiveDownload, downloadingMaxNumber, failedTryAgain); + return CreateResourceDownloader(options); + } + + [Obsolete("Use CreateResourceDownloader(BundleDownloaderOptions) instead.")] + public ResourceDownloaderOperation CreateBundleDownloader(string location, int downloadingMaxNumber, int failedTryAgain) + { + return CreateBundleDownloader(location, false, downloadingMaxNumber, failedTryAgain); + } + + [Obsolete("Use CreateResourceDownloader(BundleDownloaderOptions) instead.")] + public ResourceDownloaderOperation CreateBundleDownloader(string[] locations, bool recursiveDownload, int downloadingMaxNumber, int failedTryAgain) + { + List assetInfos = new List(locations.Length); + foreach (var location in locations) + { + var assetInfo = ConvertLocationToAssetInfo(location, null); + assetInfos.Add(assetInfo); + } + var options = new BundleDownloaderOptions(assetInfos.ToArray(), recursiveDownload, downloadingMaxNumber, failedTryAgain); + return CreateResourceDownloader(options); + } + + [Obsolete("Use CreateResourceDownloader(BundleDownloaderOptions) instead.")] + public ResourceDownloaderOperation CreateBundleDownloader(string[] locations, int downloadingMaxNumber, int failedTryAgain) + { + return CreateBundleDownloader(locations, false, downloadingMaxNumber, failedTryAgain); + } + + [Obsolete("Use CreateResourceDownloader(BundleDownloaderOptions) instead.")] + public ResourceDownloaderOperation CreateBundleDownloader(AssetInfo assetInfo, bool recursiveDownload, int downloadingMaxNumber, int failedTryAgain) + { + AssetInfo[] assetInfos = new AssetInfo[] { assetInfo }; + var options = new BundleDownloaderOptions(assetInfos, recursiveDownload, downloadingMaxNumber, failedTryAgain); + return CreateResourceDownloader(options); + } + + [Obsolete("Use CreateResourceDownloader(BundleDownloaderOptions) instead.")] + public ResourceDownloaderOperation CreateBundleDownloader(AssetInfo assetInfo, int downloadingMaxNumber, int failedTryAgain) + { + return CreateBundleDownloader(assetInfo, false, downloadingMaxNumber, failedTryAgain); + } + + [Obsolete("Use CreateResourceDownloader(BundleDownloaderOptions) instead.")] + public ResourceDownloaderOperation CreateBundleDownloader(AssetInfo[] assetInfos, bool recursiveDownload, int downloadingMaxNumber, int failedTryAgain) + { + var options = new BundleDownloaderOptions(assetInfos, recursiveDownload, downloadingMaxNumber, failedTryAgain); + return CreateResourceDownloader(options); + } + + [Obsolete("Use CreateResourceDownloader(BundleDownloaderOptions) instead.")] + public ResourceDownloaderOperation CreateBundleDownloader(AssetInfo[] assetInfos, int downloadingMaxNumber, int failedTryAgain) + { + return CreateBundleDownloader(assetInfos, false, downloadingMaxNumber, failedTryAgain); + } + #endregion + + #region 资源解压 + [Obsolete("Use CreateResourceUnpacker(ResourceUnpackerOptions) instead.")] + public ResourceUnpackerOperation CreateResourceUnpacker(int unpackingMaxNumber, int failedTryAgain) + { + var options = new ResourceUnpackerOptions(unpackingMaxNumber, failedTryAgain); + return CreateResourceUnpacker(options); + } + + [Obsolete("Use CreateResourceUnpacker(ResourceUnpackerOptions) instead.")] + public ResourceUnpackerOperation CreateResourceUnpacker(string tag, int unpackingMaxNumber, int failedTryAgain) + { + string[] tags = new string[] { tag }; + var options = new ResourceUnpackerOptions(tags, unpackingMaxNumber, failedTryAgain); + return CreateResourceUnpacker(options); + } + + [Obsolete("Use CreateResourceUnpacker(ResourceUnpackerOptions) instead.")] + public ResourceUnpackerOperation CreateResourceUnpacker(string[] tags, int unpackingMaxNumber, int failedTryAgain) + { + var options = new ResourceUnpackerOptions(tags, unpackingMaxNumber, failedTryAgain); + return CreateResourceUnpacker(options); + } + #endregion + + #region 资源导入 + [Obsolete("Use CreateResourceImporter(BundleImporterOptions) instead.")] + public ResourceImporterOperation CreateResourceImporter(string[] filePaths, int importerMaxNumber, int failedTryAgain) + { + ImportFileInfo[] fileInfos = new ImportFileInfo[filePaths.Length]; + for (int i = 0; i < filePaths.Length; i++) + { + ImportFileInfo fileInfo = new ImportFileInfo(); + fileInfo.FilePath = filePaths[i]; + fileInfos[i] = fileInfo; + } + return CreateResourceImporter(fileInfos, importerMaxNumber, failedTryAgain); + } + + [Obsolete("Use CreateResourceImporter(BundleImporterOptions) instead.")] + public ResourceImporterOperation CreateResourceImporter(ImportFileInfo[] fileInfos, int importerMaxNumber, int failedTryAgain) + { + ImportBundleInfo[] bundleInfos = new ImportBundleInfo[fileInfos.Length]; + for (int i = 0; i < fileInfos.Length; i++) + { + bundleInfos[i] = new ImportBundleInfo( + filePath: fileInfos[i].FilePath, + bundleName: fileInfos[i].BundleName, + bundleGuid: fileInfos[i].BundleGUID); + } + var options = new BundleImporterOptions(bundleInfos, importerMaxNumber, failedTryAgain); + return CreateResourceImporter(options); + } + #endregion + } +} +#endif diff --git a/Assets/YooAsset/Runtime/Compatibility/CompatibleResourcePackage.cs.meta b/Assets/YooAsset/Runtime/Compatibility/CompatibleResourcePackage.cs.meta new file mode 100644 index 00000000..263015c3 --- /dev/null +++ b/Assets/YooAsset/Runtime/Compatibility/CompatibleResourcePackage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fcb10923f246d8844b4980dcb84bec86 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Runtime/Compatibility/CompatibleYooAssets.cs b/Assets/YooAsset/Runtime/Compatibility/CompatibleYooAssets.cs new file mode 100644 index 00000000..95d80e0f --- /dev/null +++ b/Assets/YooAsset/Runtime/Compatibility/CompatibleYooAssets.cs @@ -0,0 +1,83 @@ +#if YOOASSET_LEGACY_API +// YooAsset v2.3 兼容层 - YooAssets 静态类兼容 +// 通过 partial class 为 YooAssets 补充 v2.3 的旧静态属性和方法。 + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace YooAsset +{ + public static partial class YooAssets + { + /// + /// v2.3: YooAssets.Initialized + /// + [Obsolete("Use IsInitialized instead.")] + public static bool Initialized => IsInitialized; + + /// + /// v2.3: YooAssets.GetAllPackages() + /// + [Obsolete("Use GetPackages() instead.")] + public static List GetAllPackages() + { + return GetPackages().ToList(); + } + + /// + /// v2.3: YooAssets.TryGetPackage(string) + /// + [Obsolete("Use TryGetPackage(string, out ResourcePackage) instead.")] + public static ResourcePackage TryGetPackage(string packageName) + { + TryGetPackage(packageName, out var package); + return package; + } + + /// + /// v2.3: YooAssets.RemovePackage(ResourcePackage) + /// + [Obsolete("Use RemovePackage(string) instead.")] + public static bool RemovePackage(ResourcePackage package) + { + try + { + RemovePackage(package.PackageName); + return true; + } + catch + { + return false; + } + } + + /// + /// v2.3: YooAssets.SetOperationSystemMaxTimeSlice(long) + /// + [Obsolete("Use SetAsyncOperationMaxTimeSlice() instead.")] + public static void SetOperationSystemMaxTimeSlice(long milliseconds) + { + SetAsyncOperationMaxTimeSlice(milliseconds); + } + + /// + /// v2.3: YooAssets.SetDownloadSystemUnityWebRequest(delegate) + /// + [Obsolete("This API has been removed in v3.")] + public static void SetDownloadSystemUnityWebRequest(object createDelegate) + { + throw new System.NotImplementedException(); + } + + /// + /// v2.3: YooAssets.StartOperation(GameAsyncOperation) + /// + [Obsolete("GameAsyncOperation has been removed in v3.")] + public static void StartOperation(GameAsyncOperation operation) + { + AsyncOperationSystem.StartOperation(AsyncOperationSystem.GlobalSchedulerName, operation); + } + } +} +#endif diff --git a/Assets/YooAsset/Runtime/Compatibility/CompatibleYooAssets.cs.meta b/Assets/YooAsset/Runtime/Compatibility/CompatibleYooAssets.cs.meta new file mode 100644 index 00000000..579e0fbc --- /dev/null +++ b/Assets/YooAsset/Runtime/Compatibility/CompatibleYooAssets.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 93e9bc10b05be8b42a704832abed581d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Runtime/DiagnosticSystem/DiagnosticReport/DiagnosticPackageData.cs b/Assets/YooAsset/Runtime/DiagnosticSystem/DiagnosticReport/DiagnosticPackageData.cs index 27530d51..e507917b 100644 --- a/Assets/YooAsset/Runtime/DiagnosticSystem/DiagnosticReport/DiagnosticPackageData.cs +++ b/Assets/YooAsset/Runtime/DiagnosticSystem/DiagnosticReport/DiagnosticPackageData.cs @@ -9,7 +9,9 @@ namespace YooAsset [Serializable] internal class DiagnosticPackageData { + [NonSerialized] private readonly Dictionary _bundleInfoDict = new Dictionary(); + [NonSerialized] private bool _isParsed = false; /// diff --git a/Assets/YooAsset/Runtime/DownloadSystem/DownloadUrlHelper.cs b/Assets/YooAsset/Runtime/DownloadSystem/DownloadUrlHelper.cs index 90b07c93..bd38c90f 100644 --- a/Assets/YooAsset/Runtime/DownloadSystem/DownloadUrlHelper.cs +++ b/Assets/YooAsset/Runtime/DownloadSystem/DownloadUrlHelper.cs @@ -19,24 +19,11 @@ namespace YooAsset if (string.IsNullOrEmpty(filePath)) throw new YooInternalException("File path is null or empty."); - if (IsLocalFileUrl(filePath)) - return EscapeSpecialCharacters(filePath); - string url; - -#if UNITY_WEBGL - url = filePath; -#elif UNITY_ANDROID - url = new System.Uri(filePath).ToString(); -#elif UNITY_OPENHARMONY - // 注意:由于鸿蒙系统的特殊性,需要判断双形态 - if (UnityEngine.Application.streamingAssetsPath.StartsWith("jar:file://")) - url = StringUtility.Format("jar:file://{0}", filePath); + if (IsLocalFileUrl(filePath)) + url = filePath; else - url = new System.Uri(filePath).ToString(); -#else - url = new System.Uri(filePath).ToString(); -#endif + url = CreateLocalFileUrl(filePath); return EscapeSpecialCharacters(url); } @@ -60,6 +47,20 @@ namespace YooAsset return false; } + private static string CreateLocalFileUrl(string filePath) + { +#if UNITY_WEBGL + return filePath; +#elif UNITY_OPENHARMONY + // 注意:由于鸿蒙系统的特殊性,需要判断双形态 + if (UnityEngine.Application.streamingAssetsPath.StartsWith("jar:file://")) + return $"jar:file://{filePath}"; + else + return new System.Uri(filePath).ToString(); +#else + return new System.Uri(filePath).ToString(); +#endif + } private static string EscapeSpecialCharacters(string url) { // 处理特殊字符:用户设备路径可能包含特殊字符导致 URL 无法正确识别 diff --git a/Assets/YooAsset/Runtime/FileSystem/EFileSystemParameter.cs b/Assets/YooAsset/Runtime/FileSystem/EFileSystemParameter.cs index 31413e64..72271ab6 100644 --- a/Assets/YooAsset/Runtime/FileSystem/EFileSystemParameter.cs +++ b/Assets/YooAsset/Runtime/FileSystem/EFileSystemParameter.cs @@ -21,16 +21,6 @@ namespace YooAsset /// InstallCleanupMode, - /// - /// 远端资源地址查询服务类 - /// - RemoteService, - - /// - /// AssetBundle 解密器 - /// - AssetbundleDecryptor, - /// /// 禁用Unity的网络缓存 /// @@ -111,6 +101,16 @@ namespace YooAsset /// UnpackFileSystemRoot, + /// + /// 远端资源地址查询服务类 + /// + RemoteService, + + /// + /// AssetBundle 解密器 + /// + AssetbundleDecryptor, + /// /// RawBundle 解密器 /// @@ -136,4 +136,4 @@ namespace YooAsset /// DownloadUrlPolicy, } -} +} \ No newline at end of file diff --git a/Assets/YooAsset/Runtime/FileSystem/FileSystemHelper.cs b/Assets/YooAsset/Runtime/FileSystem/FileSystemHelper.cs index badcbec4..90a38267 100644 --- a/Assets/YooAsset/Runtime/FileSystem/FileSystemHelper.cs +++ b/Assets/YooAsset/Runtime/FileSystem/FileSystemHelper.cs @@ -22,4 +22,4 @@ namespace YooAsset paramName); } } -} +} \ No newline at end of file diff --git a/Assets/YooAsset/Runtime/FileSystem/FileSystemParameters.cs b/Assets/YooAsset/Runtime/FileSystem/FileSystemParameters.cs index 83a350be..0e744a59 100644 --- a/Assets/YooAsset/Runtime/FileSystem/FileSystemParameters.cs +++ b/Assets/YooAsset/Runtime/FileSystem/FileSystemParameters.cs @@ -6,7 +6,7 @@ namespace YooAsset /// /// 提供文件系统的创建参数与工厂方法 /// - public class FileSystemParameters + public partial class FileSystemParameters { internal readonly Dictionary _createParameters = new Dictionary(100); diff --git a/Assets/YooAsset/Runtime/FileSystem/Services/SandboxFileSystem/Operations/SFSDownloadBundleOperation.cs b/Assets/YooAsset/Runtime/FileSystem/Services/SandboxFileSystem/Operations/SFSDownloadBundleOperation.cs index ce946910..05ea258e 100644 --- a/Assets/YooAsset/Runtime/FileSystem/Services/SandboxFileSystem/Operations/SFSDownloadBundleOperation.cs +++ b/Assets/YooAsset/Runtime/FileSystem/Services/SandboxFileSystem/Operations/SFSDownloadBundleOperation.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using UnityEngine; namespace YooAsset @@ -20,6 +21,7 @@ namespace YooAsset private readonly SandboxFileSystem _fileSystem; private readonly FSDownloadBundleOptions _options; private readonly DownloadRetryController _downloadRetryController; + private IReadOnlyList _candidateUrls; private DownloadFileBaseOperation _downloadFileOp; private ESteps _steps = ESteps.None; @@ -172,8 +174,10 @@ namespace YooAsset /// private string GetRequestUrl(string fileName) { - var urls = _fileSystem.RemoteService.GetRemoteUrls(fileName); - return _fileSystem.DownloadUrlPolicy.SelectUrl(urls); + if (_candidateUrls == null) + _candidateUrls = _fileSystem.RemoteService.GetRemoteUrls(fileName); + + return _fileSystem.DownloadUrlPolicy.SelectUrl(_candidateUrls); } } } \ No newline at end of file diff --git a/Assets/YooAsset/Runtime/FileSystem/Services/WebGameFileSystem/WebGameFileSystem.cs b/Assets/YooAsset/Runtime/FileSystem/Services/WebGameFileSystem/WebGameFileSystem.cs index fc6fc30d..0002f891 100644 --- a/Assets/YooAsset/Runtime/FileSystem/Services/WebGameFileSystem/WebGameFileSystem.cs +++ b/Assets/YooAsset/Runtime/FileSystem/Services/WebGameFileSystem/WebGameFileSystem.cs @@ -31,9 +31,9 @@ namespace YooAsset #region 自定义参数 /// - /// 自定义参数:是否禁用 Unity 内置网络缓存 + /// 自定义参数:禁用 Unity 内置网络缓存 /// - public bool DisableUnityWebCache { get; private set; } = false; + public bool DisableUnityWebCache { get; private set; } = true; /// /// 自定义参数:下载看门狗超时时间(秒) @@ -165,7 +165,7 @@ namespace YooAsset // 创建默认的下载后台接口 if (DownloadBackend == null) - DownloadBackend = new UnityWebRequestBackend(null); + DownloadBackend = new UnityWebRequestBackend(); // 创建默认的下载重试策略 if (DownloadRetryPolicy == null) diff --git a/Assets/YooAsset/Runtime/FileSystem/Services/WebRemoteFileSystem/WebRemoteFileSystem.cs b/Assets/YooAsset/Runtime/FileSystem/Services/WebRemoteFileSystem/WebRemoteFileSystem.cs index 42f7cbd9..706fe6ab 100644 --- a/Assets/YooAsset/Runtime/FileSystem/Services/WebRemoteFileSystem/WebRemoteFileSystem.cs +++ b/Assets/YooAsset/Runtime/FileSystem/Services/WebRemoteFileSystem/WebRemoteFileSystem.cs @@ -31,7 +31,7 @@ namespace YooAsset public UnityWebRequestCreator WebRequestCreator { get; private set; } /// - /// 自定义参数:禁用Unity的网络缓存 + /// 自定义参数:禁用 Unity 内置网络缓存 /// public bool DisableUnityWebCache { get; private set; } = false; diff --git a/Assets/YooAsset/Runtime/FileSystem/Services/WebServerFileSystem/WebServerFileSystem.cs b/Assets/YooAsset/Runtime/FileSystem/Services/WebServerFileSystem/WebServerFileSystem.cs index 9d09820a..88237fb7 100644 --- a/Assets/YooAsset/Runtime/FileSystem/Services/WebServerFileSystem/WebServerFileSystem.cs +++ b/Assets/YooAsset/Runtime/FileSystem/Services/WebServerFileSystem/WebServerFileSystem.cs @@ -42,7 +42,7 @@ namespace YooAsset public UnityWebRequestCreator WebRequestCreator { get; private set; } /// - /// 自定义参数:禁用Unity的网络缓存 + /// 自定义参数:禁用 Unity 内置网络缓存 /// public bool DisableUnityWebCache { get; private set; } = false; diff --git a/Assets/YooAsset/Runtime/ResourceManager/Handles/AllAssetsHandle.cs b/Assets/YooAsset/Runtime/ResourceManager/Handles/AllAssetsHandle.cs index b18817dc..533ae2ac 100644 --- a/Assets/YooAsset/Runtime/ResourceManager/Handles/AllAssetsHandle.cs +++ b/Assets/YooAsset/Runtime/ResourceManager/Handles/AllAssetsHandle.cs @@ -6,7 +6,7 @@ namespace YooAsset /// /// 全资源句柄,用于加载资源包内所有资源对象。 /// - public sealed class AllAssetsHandle : HandleBase + public sealed partial class AllAssetsHandle : HandleBase { private System.Action _callback; diff --git a/Assets/YooAsset/Runtime/ResourceManager/Handles/AssetHandle.cs b/Assets/YooAsset/Runtime/ResourceManager/Handles/AssetHandle.cs index faca862a..71675eeb 100644 --- a/Assets/YooAsset/Runtime/ResourceManager/Handles/AssetHandle.cs +++ b/Assets/YooAsset/Runtime/ResourceManager/Handles/AssetHandle.cs @@ -5,7 +5,7 @@ namespace YooAsset /// /// 资源句柄,用于管理单个资源对象的加载和访问。 /// - public sealed class AssetHandle : HandleBase + public sealed partial class AssetHandle : HandleBase { private System.Action _callback; diff --git a/Assets/YooAsset/Runtime/ResourceManager/Handles/HandleBase.cs b/Assets/YooAsset/Runtime/ResourceManager/Handles/HandleBase.cs index ddf33581..7fbd3148 100644 --- a/Assets/YooAsset/Runtime/ResourceManager/Handles/HandleBase.cs +++ b/Assets/YooAsset/Runtime/ResourceManager/Handles/HandleBase.cs @@ -6,7 +6,7 @@ namespace YooAsset /// /// 资源句柄基类,提供资源加载状态查询和释放功能。 /// - public abstract class HandleBase : IEnumerator, IDisposable + public abstract partial class HandleBase : IEnumerator, IDisposable { private readonly AssetInfo _assetInfo; diff --git a/Assets/YooAsset/Runtime/ResourceManager/Handles/RawFileHandle.cs b/Assets/YooAsset/Runtime/ResourceManager/Handles/RawFileHandle.cs index b172ea80..6bc57dfc 100644 --- a/Assets/YooAsset/Runtime/ResourceManager/Handles/RawFileHandle.cs +++ b/Assets/YooAsset/Runtime/ResourceManager/Handles/RawFileHandle.cs @@ -4,7 +4,7 @@ namespace YooAsset /// /// 原生文件句柄,用于访问未经 Unity 处理的原始文件。 /// - public sealed class RawFileHandle : HandleBase + public sealed partial class RawFileHandle : HandleBase { private System.Action _callback; diff --git a/Assets/YooAsset/Runtime/ResourceManager/Handles/SceneHandle.cs b/Assets/YooAsset/Runtime/ResourceManager/Handles/SceneHandle.cs index be6046d0..a466f606 100644 --- a/Assets/YooAsset/Runtime/ResourceManager/Handles/SceneHandle.cs +++ b/Assets/YooAsset/Runtime/ResourceManager/Handles/SceneHandle.cs @@ -5,7 +5,7 @@ namespace YooAsset /// /// 场景句柄,用于管理场景的加载、激活和卸载。 /// - public sealed class SceneHandle : HandleBase + public sealed partial class SceneHandle : HandleBase { private System.Action _callback; diff --git a/Assets/YooAsset/Runtime/ResourceManager/Handles/SubAssetsHandle.cs b/Assets/YooAsset/Runtime/ResourceManager/Handles/SubAssetsHandle.cs index 0f3a38a9..9e1deb0b 100644 --- a/Assets/YooAsset/Runtime/ResourceManager/Handles/SubAssetsHandle.cs +++ b/Assets/YooAsset/Runtime/ResourceManager/Handles/SubAssetsHandle.cs @@ -6,7 +6,7 @@ namespace YooAsset /// /// 子资源句柄,用于管理资源包内子资源对象的加载和访问。 /// - public sealed class SubAssetsHandle : HandleBase + public sealed partial class SubAssetsHandle : HandleBase { private System.Action _callback; diff --git a/Assets/YooAsset/Runtime/ResourceManager/Operations/InstantiateOperation.cs b/Assets/YooAsset/Runtime/ResourceManager/Operations/InstantiateOperation.cs index 443f5202..b9c20da9 100644 --- a/Assets/YooAsset/Runtime/ResourceManager/Operations/InstantiateOperation.cs +++ b/Assets/YooAsset/Runtime/ResourceManager/Operations/InstantiateOperation.cs @@ -35,10 +35,12 @@ namespace YooAsset _handle = handle; _options = options; } + /// protected override void InternalStart() { _steps = ESteps.LoadObject; } + /// protected override void InternalUpdate() { if (_steps == ESteps.None || _steps == ESteps.Done) @@ -140,10 +142,12 @@ namespace YooAsset } #endif } + /// protected override void InternalWaitForCompletion() { ExecuteBatch(); } + /// protected override string InternalGetDescription() { var assetInfo = _handle.GetAssetInfo(); diff --git a/Assets/YooAsset/Runtime/ResourceManager/Operations/Internal/LoadBundleOperation.cs b/Assets/YooAsset/Runtime/ResourceManager/Operations/Internal/LoadBundleOperation.cs index b55a5e47..cdbdc0e2 100644 --- a/Assets/YooAsset/Runtime/ResourceManager/Operations/Internal/LoadBundleOperation.cs +++ b/Assets/YooAsset/Runtime/ResourceManager/Operations/Internal/LoadBundleOperation.cs @@ -33,6 +33,11 @@ namespace YooAsset /// public bool IsDestroyed { private set; get; } = false; + /// + /// 是否已收到强制销毁请求 + /// + public bool ForceDestroyRequested { private set; get; } = false; + /// /// 引用计数 /// @@ -57,6 +62,25 @@ namespace YooAsset if (_steps == ESteps.None || _steps == ESteps.Done) return; + if (ForceDestroyRequested) + { + if (_steps == ESteps.CheckConcurrency) + { + _steps = ESteps.Done; + SetError("Bundle loader force destroyed during package destruction."); + return; + } + + if (_steps == ESteps.LoadBundleFile) + { + // 注意:终止下载器 + if (_loadPackageBundleOp != null) + _loadPackageBundleOp.ShouldAbortDownload = true; + } + + // 注意:其它条件的情况下,继续往下走,等底层操作自然退出。 + } + if (_steps == ESteps.CheckConcurrency) { if (IsWaitForCompletion) @@ -141,8 +165,14 @@ namespace YooAsset /// /// 销毁资源包加载器并释放资源包 /// + /// + /// 该方法是幂等的,重复调用不会重复释放资源。 + /// public void DestroyLoader() { + if (IsDestroyed) + return; + IsDestroyed = true; // 注意:正在加载中的任务不可以销毁 @@ -163,10 +193,16 @@ namespace YooAsset } /// - /// 强制销毁资源包加载器(仅用于全局 Destroy 场景) + /// 强制销毁资源包加载器 /// + /// + /// 该方法是幂等的,仅用于全局 Destroy 场景,重复调用不会重复释放资源。 + /// public void ForceDestroyLoader() { + if (IsDestroyed) + return; + IsDestroyed = true; if (_steps == ESteps.LoadBundleFile) @@ -186,6 +222,14 @@ namespace YooAsset } } + /// + /// 请求强制销毁 + /// + public void RequestForceDestroy() + { + ForceDestroyRequested = true; + } + /// /// 是否可以销毁 /// @@ -271,26 +315,5 @@ namespace YooAsset } } - /// - /// 尝试终止加载器 - /// - public void TryAbortLoader() - { - if (IsDone == false) - { - if (_steps == ESteps.CheckConcurrency) - { - _steps = ESteps.Done; - SetError("Bundle loader aborted."); - } - - if (_steps == ESteps.LoadBundleFile) - { - // 注意:终止下载器 - if (_loadPackageBundleOp != null) - _loadPackageBundleOp.ShouldAbortDownload = true; - } - } - } } } \ No newline at end of file diff --git a/Assets/YooAsset/Runtime/ResourceManager/Operations/UnloadAllAssetsOperation.cs b/Assets/YooAsset/Runtime/ResourceManager/Operations/UnloadAllAssetsOperation.cs index 27bc696b..0e8208d5 100644 --- a/Assets/YooAsset/Runtime/ResourceManager/Operations/UnloadAllAssetsOperation.cs +++ b/Assets/YooAsset/Runtime/ResourceManager/Operations/UnloadAllAssetsOperation.cs @@ -13,7 +13,7 @@ namespace YooAsset None, CheckOptions, ReleaseAll, - TryAbortLoader, + RequestForceDestroy, CheckLoading, DestroyAll, Done, @@ -28,10 +28,12 @@ namespace YooAsset _resourceManager = resourceManager; _options = options; } + /// protected override void InternalStart() { _steps = ESteps.CheckOptions; } + /// protected override void InternalUpdate() { if (_steps == ESteps.None || _steps == ESteps.Done) @@ -55,14 +57,15 @@ namespace YooAsset if (_options.ShouldReleaseHandles) _resourceManager.ReleaseAllHandles(); - _steps = ESteps.TryAbortLoader; + _steps = ESteps.RequestForceDestroy; } - if (_steps == ESteps.TryAbortLoader) + if (_steps == ESteps.RequestForceDestroy) { - // 尝试终止所有加载任务 - // 注意:正在加载AssetBundle的任务无法终止 - _resourceManager.TryAbortAllBundleLoaders(); + // 向所有 Provider 和 BundleLoader 下发强制销毁请求 + _resourceManager.RequestForceDestroyAllProviders(); + _resourceManager.RequestForceDestroyAllBundleLoaders(); + _steps = ESteps.CheckLoading; } diff --git a/Assets/YooAsset/Runtime/ResourceManager/Operations/UnloadSceneOperation.cs b/Assets/YooAsset/Runtime/ResourceManager/Operations/UnloadSceneOperation.cs index 34dbac79..c21c96ec 100644 --- a/Assets/YooAsset/Runtime/ResourceManager/Operations/UnloadSceneOperation.cs +++ b/Assets/YooAsset/Runtime/ResourceManager/Operations/UnloadSceneOperation.cs @@ -42,10 +42,12 @@ namespace YooAsset throw new YooInternalException($"Unexpected provider type: '{provider.GetType().Name}'."); } } + /// protected override void InternalStart() { _steps = ESteps.CheckError; } + /// protected override void InternalUpdate() { if (_steps == ESteps.None || _steps == ESteps.Done) @@ -106,6 +108,7 @@ namespace YooAsset SetResult(); } } + /// protected override string InternalGetDescription() { return $"SceneName: {_provider.LoadedSceneName}"; diff --git a/Assets/YooAsset/Runtime/ResourceManager/Operations/UnloadUnusedAssetsOperation.cs b/Assets/YooAsset/Runtime/ResourceManager/Operations/UnloadUnusedAssetsOperation.cs index d31f4ce5..241bd067 100644 --- a/Assets/YooAsset/Runtime/ResourceManager/Operations/UnloadUnusedAssetsOperation.cs +++ b/Assets/YooAsset/Runtime/ResourceManager/Operations/UnloadUnusedAssetsOperation.cs @@ -22,11 +22,13 @@ namespace YooAsset _resourceManager = resourceManager; _options = options; } + /// protected override void InternalStart() { _steps = ESteps.UnloadUnused; _loopCounter = _options.MaxLoopCount; } + /// protected override void InternalUpdate() { if (_steps == ESteps.None || _steps == ESteps.Done) @@ -51,10 +53,12 @@ namespace YooAsset } } } + /// protected override void InternalWaitForCompletion() { ExecuteBatch(); } + /// protected override string InternalGetDescription() { return $"MaxLoopCount: {_options.MaxLoopCount}"; diff --git a/Assets/YooAsset/Runtime/ResourceManager/Providers/ProviderBase.cs b/Assets/YooAsset/Runtime/ResourceManager/Providers/ProviderBase.cs index d33a1169..7aaa32ec 100644 --- a/Assets/YooAsset/Runtime/ResourceManager/Providers/ProviderBase.cs +++ b/Assets/YooAsset/Runtime/ResourceManager/Providers/ProviderBase.cs @@ -70,6 +70,11 @@ namespace YooAsset /// public bool IsDestroyed { private set; get; } = false; + /// + /// 是否已收到强制销毁请求 + /// + public bool ForceDestroyRequested { private set; get; } = false; + /// /// 加载任务是否进行中 /// @@ -127,7 +132,18 @@ namespace YooAsset if (_steps == ESteps.None || _steps == ESteps.Done) return; - // 注意:未在加载中的任务可以挂起! + if (ForceDestroyRequested) + { + if (IsLoading == false) + { + SetFail("Provider force destroyed during package destruction."); + return; + } + + // 注意:已进入加载阶段则继续等待自然完成 + } + + // 注意:未在加载中的任务可以挂起 if (IsLoading == false) { if (RefCount <= 0) @@ -206,8 +222,14 @@ namespace YooAsset /// /// 销毁资源提供者 /// + /// + /// 该方法是幂等的,重复调用不会重复释放资源。 + /// public void DestroyProvider() { + if (IsDestroyed) + return; + IsDestroyed = true; // 检测是否为正常销毁 @@ -224,6 +246,14 @@ namespace YooAsset } } + /// + /// 请求强制销毁 + /// + public void RequestForceDestroy() + { + ForceDestroyRequested = true; + } + /// /// 是否可以销毁 /// diff --git a/Assets/YooAsset/Runtime/ResourceManager/ResourceManager.cs b/Assets/YooAsset/Runtime/ResourceManager/ResourceManager.cs index e4f9cd75..78bbd57c 100644 --- a/Assets/YooAsset/Runtime/ResourceManager/ResourceManager.cs +++ b/Assets/YooAsset/Runtime/ResourceManager/ResourceManager.cs @@ -449,13 +449,24 @@ namespace YooAsset } /// - /// 尝试终止所有 BundleLoader + /// 向所有 Provider 下发强制销毁请求 /// - internal void TryAbortAllBundleLoaders() + internal void RequestForceDestroyAllProviders() + { + foreach (var provider in _providerDict.Values) + { + provider.RequestForceDestroy(); + } + } + + /// + /// 向所有 BundleLoader 下发强制销毁请求 + /// + internal void RequestForceDestroyAllBundleLoaders() { foreach (var loader in _bundleLoaderDict.Values) { - loader.TryAbortLoader(); + loader.RequestForceDestroy(); } } diff --git a/Assets/YooAsset/Runtime/ResourcePackage/Operations/ClearCacheOperation.cs b/Assets/YooAsset/Runtime/ResourcePackage/Operations/ClearCacheOperation.cs index eb84031b..283f4bf2 100644 --- a/Assets/YooAsset/Runtime/ResourcePackage/Operations/ClearCacheOperation.cs +++ b/Assets/YooAsset/Runtime/ResourcePackage/Operations/ClearCacheOperation.cs @@ -28,10 +28,12 @@ namespace YooAsset _host = host; _options = options; } + /// protected override void InternalStart() { _steps = ESteps.Prepare; } + /// protected override void InternalUpdate() { if (_steps == ESteps.None || _steps == ESteps.Done) @@ -98,6 +100,7 @@ namespace YooAsset } } } + /// protected override string InternalGetDescription() { return $"ClearMethod: {_options.ClearMethod}"; diff --git a/Assets/YooAsset/Runtime/ResourcePackage/Operations/DestroyPackageOperation.cs b/Assets/YooAsset/Runtime/ResourcePackage/Operations/DestroyPackageOperation.cs index c0f88e92..80ec8d7c 100644 --- a/Assets/YooAsset/Runtime/ResourcePackage/Operations/DestroyPackageOperation.cs +++ b/Assets/YooAsset/Runtime/ResourcePackage/Operations/DestroyPackageOperation.cs @@ -16,21 +16,25 @@ namespace YooAsset } private readonly ResourcePackage _resourcePackage; + private readonly ResourceManager _resourceManager; private readonly UnloadAllAssetsOptions _options; private UnloadAllAssetsOperation _unloadAllAssetsOp; + private string _packageVersion = string.Empty; private ESteps _steps = ESteps.None; - internal DestroyPackageOperation(ResourcePackage resourcePackage, UnloadAllAssetsOptions options) + internal DestroyPackageOperation(ResourcePackage resourcePackage, ResourceManager resourceManager, UnloadAllAssetsOptions options) { _resourcePackage = resourcePackage; + _resourceManager = resourceManager; _options = options; } - + /// protected override void InternalStart() { _steps = ESteps.CheckInitStatus; } + /// protected override void InternalUpdate() { if (_steps == ESteps.None || _steps == ESteps.Done) @@ -52,9 +56,14 @@ namespace YooAsset case EOperationStatus.Succeeded: if (_resourcePackage.PackageValid) + { + _packageVersion = _resourcePackage.GetPackageVersion(); _steps = ESteps.UnloadAllAssets; + } else + { _steps = ESteps.DestroyPackage; + } break; default: @@ -66,7 +75,7 @@ namespace YooAsset { if (_unloadAllAssetsOp == null) { - _unloadAllAssetsOp = _resourcePackage.UnloadAllAssetsAsync(_options); + _unloadAllAssetsOp = new UnloadAllAssetsOperation(_resourceManager, _options); _unloadAllAssetsOp.StartOperation(); AddChildOperation(_unloadAllAssetsOp); } @@ -88,7 +97,6 @@ namespace YooAsset if (_steps == ESteps.DestroyPackage) { - // 销毁包裹 _resourcePackage.InternalDestroy(); // 最后清理该包裹的异步任务 @@ -99,9 +107,10 @@ namespace YooAsset SetResult(); } } + /// protected override string InternalGetDescription() { - return $"PackageVersion: {_resourcePackage.GetPackageVersion()}"; + return $"PackageVersion: {_packageVersion}"; } } } \ No newline at end of file diff --git a/Assets/YooAsset/Runtime/ResourcePackage/Operations/DownloaderOperation.cs b/Assets/YooAsset/Runtime/ResourcePackage/Operations/DownloaderOperation.cs index 9d94a1d4..1841f162 100644 --- a/Assets/YooAsset/Runtime/ResourcePackage/Operations/DownloaderOperation.cs +++ b/Assets/YooAsset/Runtime/ResourcePackage/Operations/DownloaderOperation.cs @@ -6,7 +6,7 @@ namespace YooAsset /// /// 下载操作基类,提供资源下载、暂停、恢复和取消功能。 /// - public abstract class DownloaderOperation : AsyncOperationBase + public abstract partial class DownloaderOperation : AsyncOperationBase { private enum ESteps { @@ -99,11 +99,13 @@ namespace YooAsset // 统计下载信息 CalculateStatistics(); } + /// protected override void InternalStart() { YooLogger.Log($"Beginning download of {TotalDownloadCount} files ({TotalDownloadBytes} bytes)."); _steps = ESteps.Check; } + /// protected override void InternalUpdate() { if (_steps == ESteps.None || _steps == ESteps.Done) diff --git a/Assets/YooAsset/Runtime/ResourcePackage/Operations/InitializePackageOperation.cs b/Assets/YooAsset/Runtime/ResourcePackage/Operations/InitializePackageOperation.cs index c3b7123a..4db81d1e 100644 --- a/Assets/YooAsset/Runtime/ResourcePackage/Operations/InitializePackageOperation.cs +++ b/Assets/YooAsset/Runtime/ResourcePackage/Operations/InitializePackageOperation.cs @@ -29,10 +29,12 @@ namespace YooAsset _package = package; _options = options; } + /// protected override void InternalStart() { _steps = ESteps.SetPlayMode; } + /// protected override void InternalUpdate() { if (_steps == ESteps.None || _steps == ESteps.Done) @@ -137,6 +139,7 @@ namespace YooAsset } } } + /// protected override string InternalGetDescription() { return $"PlayMode: {_playMode}"; diff --git a/Assets/YooAsset/Runtime/ResourcePackage/Operations/InitializePackageOptions.cs b/Assets/YooAsset/Runtime/ResourcePackage/Operations/InitializePackageOptions.cs index fe744b09..c70597bc 100644 --- a/Assets/YooAsset/Runtime/ResourcePackage/Operations/InitializePackageOptions.cs +++ b/Assets/YooAsset/Runtime/ResourcePackage/Operations/InitializePackageOptions.cs @@ -7,8 +7,6 @@ namespace YooAsset /// public abstract class InitializePackageOptions { - protected InitializePackageOptions() { } - /// /// 同时加载Bundle文件的最大并发数 /// diff --git a/Assets/YooAsset/Runtime/ResourcePackage/Operations/Internal/InitializeFileSystemOperation.cs b/Assets/YooAsset/Runtime/ResourcePackage/Operations/Internal/InitializeFileSystemOperation.cs index 142e1e49..58aad6ac 100644 --- a/Assets/YooAsset/Runtime/ResourcePackage/Operations/Internal/InitializeFileSystemOperation.cs +++ b/Assets/YooAsset/Runtime/ResourcePackage/Operations/Internal/InitializeFileSystemOperation.cs @@ -28,10 +28,12 @@ namespace YooAsset _host = host; _parametersList = parametersList; } + /// protected override void InternalStart() { _steps = ESteps.Prepare; } + /// protected override void InternalUpdate() { if (_steps == ESteps.None || _steps == ESteps.Done) diff --git a/Assets/YooAsset/Runtime/ResourcePackage/Operations/LoadPackageManifestOperation.cs b/Assets/YooAsset/Runtime/ResourcePackage/Operations/LoadPackageManifestOperation.cs index 49b9e5b8..99087763 100644 --- a/Assets/YooAsset/Runtime/ResourcePackage/Operations/LoadPackageManifestOperation.cs +++ b/Assets/YooAsset/Runtime/ResourcePackage/Operations/LoadPackageManifestOperation.cs @@ -25,10 +25,12 @@ namespace YooAsset _host = host; _options = options; } + /// protected override void InternalStart() { _steps = ESteps.CheckParams; } + /// protected override void InternalUpdate() { if (_steps == ESteps.None || _steps == ESteps.Done) @@ -95,6 +97,7 @@ namespace YooAsset } } } + /// protected override string InternalGetDescription() { return $"PackageVersion: {_options.PackageVersion}"; diff --git a/Assets/YooAsset/Runtime/ResourcePackage/Operations/PrefetchManifestOperation.cs b/Assets/YooAsset/Runtime/ResourcePackage/Operations/PrefetchManifestOperation.cs index ee5639e3..be5159f9 100644 --- a/Assets/YooAsset/Runtime/ResourcePackage/Operations/PrefetchManifestOperation.cs +++ b/Assets/YooAsset/Runtime/ResourcePackage/Operations/PrefetchManifestOperation.cs @@ -30,10 +30,12 @@ namespace YooAsset _host = host; _options = options; } + /// protected override void InternalStart() { _steps = ESteps.CheckParams; } + /// protected override void InternalUpdate() { if (_steps == ESteps.None || _steps == ESteps.Done) diff --git a/Assets/YooAsset/Runtime/ResourcePackage/Operations/RequestPackageVersionOperation.cs b/Assets/YooAsset/Runtime/ResourcePackage/Operations/RequestPackageVersionOperation.cs index 7b0291c0..77511d8f 100644 --- a/Assets/YooAsset/Runtime/ResourcePackage/Operations/RequestPackageVersionOperation.cs +++ b/Assets/YooAsset/Runtime/ResourcePackage/Operations/RequestPackageVersionOperation.cs @@ -29,10 +29,12 @@ namespace YooAsset _host = host; _options = options; } + /// protected override void InternalStart() { _steps = ESteps.RequestPackageVersion; } + /// protected override void InternalUpdate() { if (_steps == ESteps.None || _steps == ESteps.Done) diff --git a/Assets/YooAsset/Runtime/ResourcePackage/PackageBundle.cs b/Assets/YooAsset/Runtime/ResourcePackage/PackageBundle.cs index 1c076685..90590c4c 100644 --- a/Assets/YooAsset/Runtime/ResourcePackage/PackageBundle.cs +++ b/Assets/YooAsset/Runtime/ResourcePackage/PackageBundle.cs @@ -72,11 +72,16 @@ namespace YooAsset /// [NonSerialized] public readonly List ReferrerBundleIDs = new List(10); + [NonSerialized] private readonly HashSet _referrerBundleIDs = new HashSet(); + [NonSerialized] private PackageManifest _manifest; + [NonSerialized] private bool _isInitialized; + [NonSerialized] private int _bundleType; + [NonSerialized] private string _fileName; @@ -166,6 +171,7 @@ namespace YooAsset } #region 调试信息 + [NonSerialized] private List _debugReferrerBundleNames; /// diff --git a/Assets/YooAsset/Runtime/ResourcePackage/PackageManifest.cs b/Assets/YooAsset/Runtime/ResourcePackage/PackageManifest.cs index e428189e..1cfa774c 100644 --- a/Assets/YooAsset/Runtime/ResourcePackage/PackageManifest.cs +++ b/Assets/YooAsset/Runtime/ResourcePackage/PackageManifest.cs @@ -401,7 +401,7 @@ namespace YooAsset /// /// 将资源GUID转换为资源信息 /// - /// 资源GUID + /// 资源GUID /// 资源类型 /// 返回资源信息对象,如果转换失败会返回一个无效的资源信息。 public AssetInfo ConvertAssetGuidToAssetInfo(string assetGuid, System.Type assetType) diff --git a/Assets/YooAsset/Runtime/ResourcePackage/ResourcePackage.cs b/Assets/YooAsset/Runtime/ResourcePackage/ResourcePackage.cs index 05e26f55..265d3a0c 100644 --- a/Assets/YooAsset/Runtime/ResourcePackage/ResourcePackage.cs +++ b/Assets/YooAsset/Runtime/ResourcePackage/ResourcePackage.cs @@ -9,7 +9,7 @@ namespace YooAsset /// /// 资源包裹类 /// - public class ResourcePackage + public partial class ResourcePackage { private InitializePackageOperation _initializeOp; private ResourceManager _resourceManager; @@ -120,7 +120,7 @@ namespace YooAsset public DestroyPackageOperation DestroyPackageAsync() { var options = new UnloadAllAssetsOptions(true, true); - var operation = new DestroyPackageOperation(this, options); + var operation = new DestroyPackageOperation(this, _resourceManager, options); AsyncOperationSystem.StartOperation(AsyncOperationSystem.GlobalSchedulerName, operation); return operation; } diff --git a/Assets/YooAsset/Runtime/YooAsset.asmdef b/Assets/YooAsset/Runtime/YooAsset.asmdef index 8f1c3e77..94a93f3a 100644 --- a/Assets/YooAsset/Runtime/YooAsset.asmdef +++ b/Assets/YooAsset/Runtime/YooAsset.asmdef @@ -13,17 +13,17 @@ { "name": "com.tuyoogame.yooasset", "expression": "", - "define": "YOO_ASSET_2" + "define": "YOO_ASSET_3" }, { "name": "com.tuyoogame.yooasset", - "expression": "2.3", - "define": "YOO_ASSET_2_3" + "expression": "3.0.0-beta", + "define": "YOO_ASSET_3_0" }, { "name": "com.tuyoogame.yooasset", - "expression": "2.3", - "define": "YOO_ASSET_2_3_OR_NEWER" + "expression": "3.0.0-beta", + "define": "YOO_ASSET_3_0_OR_NEWER" } ], "noEngineReferences": false diff --git a/Assets/YooAsset/Samples~/Mini Game/Runtime/Test.meta b/Assets/YooAsset/Samples~/Extension Sample/Editor/AssetReference.meta similarity index 77% rename from Assets/YooAsset/Samples~/Mini Game/Runtime/Test.meta rename to Assets/YooAsset/Samples~/Extension Sample/Editor/AssetReference.meta index 941ef797..be8d6749 100644 --- a/Assets/YooAsset/Samples~/Mini Game/Runtime/Test.meta +++ b/Assets/YooAsset/Samples~/Extension Sample/Editor/AssetReference.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: cda191ef75dc59f408545d8f7d3644b0 +guid: 682ac5046f4686946b0003a449638651 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/AssetReference/GameObjectReferenceDrawer.cs b/Assets/YooAsset/Samples~/Extension Sample/Editor/AssetReference/GameObjectReferenceDrawer.cs new file mode 100644 index 00000000..a7bd5581 --- /dev/null +++ b/Assets/YooAsset/Samples~/Extension Sample/Editor/AssetReference/GameObjectReferenceDrawer.cs @@ -0,0 +1,61 @@ +using UnityEngine; +using UnityEditor; + +[CustomPropertyDrawer(typeof(GameObjectReference))] +public class GameObjectReferenceDrawer : PropertyDrawer +{ + private const float LineSpacing = 2f; + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + SerializedProperty packageNameProp = property.FindPropertyRelative("_packageName"); + SerializedProperty assetGUIDProp = property.FindPropertyRelative("_assetGUID"); + + EditorGUI.BeginProperty(position, label, property); + { + float lineHeight = EditorGUIUtility.singleLineHeight; + Rect line = new Rect(position.x, position.y, position.width, lineHeight); + + // 绘制 PackageName + packageNameProp.stringValue = EditorGUI.TextField(line, "Package Name", packageNameProp.stringValue); + + // 加载 GameObject + string assetGUID = assetGUIDProp.stringValue; + GameObject current = null; + if (string.IsNullOrEmpty(assetGUID) == false) + { + string assetPath = AssetDatabase.GUIDToAssetPath(assetGUID); + if (string.IsNullOrEmpty(assetPath) == false) + current = AssetDatabase.LoadAssetAtPath(assetPath); + } + + // 绘制 GameObject + line.y += lineHeight + LineSpacing; + GameObject newAsset = (GameObject)EditorGUI.ObjectField(line, "Game Object", current, typeof(GameObject), false); + if (newAsset != current) + { + if (newAsset == null) + { + assetGUIDProp.stringValue = ""; + } + else + { + string newPath = AssetDatabase.GetAssetPath(newAsset); + if (string.IsNullOrEmpty(newPath) == false) + assetGUIDProp.stringValue = AssetDatabase.AssetPathToGUID(newPath); + } + } + + // 绘制 AssetGUID + line.y += lineHeight + LineSpacing; + EditorGUI.BeginDisabledGroup(true); + EditorGUI.TextField(line, "Asset GUID", assetGUIDProp.stringValue); + EditorGUI.EndDisabledGroup(); + } + EditorGUI.EndProperty(); + } + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) + { + return EditorGUIUtility.singleLineHeight * 3 + LineSpacing * 2; + } +} diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/AssetReference/GameObjectReferenceDrawer.cs.meta b/Assets/YooAsset/Samples~/Extension Sample/Editor/AssetReference/GameObjectReferenceDrawer.cs.meta new file mode 100644 index 00000000..e47ae1b3 --- /dev/null +++ b/Assets/YooAsset/Samples~/Extension Sample/Editor/AssetReference/GameObjectReferenceDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 53eb285fc3a7b614089a000f8c9c738c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/ClearBuildCache/ClearBuildCache.cs b/Assets/YooAsset/Samples~/Extension Sample/Editor/ClearBuildCache/ClearBuildCache.cs index d4fffd95..54eda403 100644 --- a/Assets/YooAsset/Samples~/Extension Sample/Editor/ClearBuildCache/ClearBuildCache.cs +++ b/Assets/YooAsset/Samples~/Extension Sample/Editor/ClearBuildCache/ClearBuildCache.cs @@ -1,17 +1,31 @@ -using System; +using System; using System.IO; using UnityEditor; using UnityEngine; namespace YooAsset.Editor { + /// + /// 提供构建缓存清理菜单入口 + /// internal class ClearBuildCacheWindow { + private const string SBPEditorAssemblyName = "Unity.ScriptableBuildPipeline.Editor"; + private const string SBPBuildCacheTypeName = "UnityEditor.Build.Pipeline.Utilities.BuildCache"; + [MenuItem("Tools/Clear Build Cache", false, 2)] public static void OpenWindow() { // 清空SBP构建缓存 - UnityEditor.Build.Pipeline.Utilities.BuildCache.PurgeCache(false); + var buildCacheType = Type.GetType($"{SBPBuildCacheTypeName}, {SBPEditorAssemblyName}"); + if (buildCacheType != null) + { + EditorAssemblyUtility.InvokePublicStaticMethod(buildCacheType, "PurgeCache", false); + } + else + { + Debug.LogWarning($"Failed to find type: {SBPBuildCacheTypeName}"); + } // 删除AssetDependDB文件 string projectPath = YooAsset.Editor.EditorPathUtility.GetProjectPath(); @@ -20,6 +34,8 @@ namespace YooAsset.Editor { File.Delete(databaseFilePath); } + + Debug.Log("Clear build cache succeeded !"); } } } \ No newline at end of file diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuildinCatalog.meta b/Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuiltinCatalog.meta similarity index 100% rename from Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuildinCatalog.meta rename to Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuiltinCatalog.meta diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuildinCatalog/CreateBuildinCatalogWindow.cs b/Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuiltinCatalog/CreateBuiltinCatalogWindow.cs similarity index 69% rename from Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuildinCatalog/CreateBuildinCatalogWindow.cs rename to Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuiltinCatalog/CreateBuiltinCatalogWindow.cs index d4ae39fd..4195c32e 100644 --- a/Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuildinCatalog/CreateBuildinCatalogWindow.cs +++ b/Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuiltinCatalog/CreateBuiltinCatalogWindow.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using System.Collections.Generic; using System.Linq; using UnityEngine; @@ -6,16 +6,19 @@ using UnityEditor; namespace YooAsset.Editor { - public class CreateBuildinCatalogWindow : EditorWindow + /// + /// 提供内置资源清单生成工具窗口 + /// + public class CreateBuiltinCatalogWindow : EditorWindow { - static CreateBuildinCatalogWindow _thisInstance; + static CreateBuiltinCatalogWindow _thisInstance; - [MenuItem("Tools/内置清单生成工具(Catalog)", false, 101)] + [MenuItem("Tools/Builtin Catalog Generator", false, 101)] static void ShowWindow() { if (_thisInstance == null) { - _thisInstance = EditorWindow.GetWindow(typeof(CreateBuildinCatalogWindow), false, "内置清单生成工具", true) as CreateBuildinCatalogWindow; + _thisInstance = EditorWindow.GetWindow(typeof(CreateBuiltinCatalogWindow), false, "Builtin Catalog Generator", true) as CreateBuiltinCatalogWindow; _thisInstance.minSize = new Vector2(800, 600); } _thisInstance.Show(); @@ -27,7 +30,7 @@ namespace YooAsset.Editor { GUILayout.Space(10); EditorGUILayout.BeginHorizontal(); - if (GUILayout.Button("选择内置资源目录", GUILayout.MaxWidth(150))) + if (GUILayout.Button("Select Builtin Resource Directory", GUILayout.MaxWidth(250))) { string resultPath = EditorUtility.OpenFolderPanel("Find", "Assets/", "StreamingAssets"); if (!string.IsNullOrEmpty(resultPath)) @@ -38,7 +41,7 @@ namespace YooAsset.Editor if (string.IsNullOrEmpty(_directoryRoot) == false) { - if (GUILayout.Button("生成Catalog文件", GUILayout.MaxWidth(150))) + if (GUILayout.Button("Generate Catalog File", GUILayout.MaxWidth(150))) { CreateCatalogFile(_directoryRoot); } @@ -47,6 +50,13 @@ namespace YooAsset.Editor private void CreateCatalogFile(string directoryRoot) { + // 检查目录是否存在 + if (Directory.Exists(directoryRoot) == false) + { + Debug.LogError("Selected directory does not exist."); + return; + } + // 搜索所有Package目录 List packageRoots = GetPackageRoots(directoryRoot); foreach (var packageRoot in packageRoots) @@ -55,26 +65,20 @@ namespace YooAsset.Editor string packageName = directoryInfo.Name; try { - bool result = BuiltinCatalogHelper.CreateFile(null, packageName, packageRoot); //TODO 自行处理解密 + bool result = BuiltinCatalogHelper.CreateFile(null, packageName, packageRoot); // TODO: 根据业务需求处理清单解密。 if (result == false) { - Debug.LogError($"Create package {packageName} catalog file failed ! See the detail error in console !"); + Debug.LogError($"Failed to create a catalog file for package '{packageName}'. See Console for details."); } } catch (System.Exception ex) { - Debug.LogError($"Create package {packageName} catalog file failed ! {ex.Message}"); + Debug.LogError($"Failed to create a catalog file for package '{packageName}': {ex.Message}."); } } } private List GetPackageRoots(string rootPath) { - // 检查目录是否存在 - if (Directory.Exists(rootPath) == false) - { - throw new DirectoryNotFoundException($"目录不存在: {rootPath}"); - } - // 搜索所有 .version 文件(包含子目录) string[] versionFiles = Directory.GetFiles( rootPath, diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuildinCatalog/CreateBuildinCatalogWindow.cs.meta b/Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuiltinCatalog/CreateBuiltinCatalogWindow.cs.meta similarity index 100% rename from Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuildinCatalog/CreateBuildinCatalogWindow.cs.meta rename to Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuiltinCatalog/CreateBuiltinCatalogWindow.cs.meta diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuildinCatalog/CreateEmptyCatalogWindow.cs b/Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuiltinCatalog/CreateEmptyCatalogWindow.cs similarity index 71% rename from Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuildinCatalog/CreateEmptyCatalogWindow.cs rename to Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuiltinCatalog/CreateEmptyCatalogWindow.cs index c135a177..a6315b0f 100644 --- a/Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuildinCatalog/CreateEmptyCatalogWindow.cs +++ b/Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuiltinCatalog/CreateEmptyCatalogWindow.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using System.Collections.Generic; using System.Linq; using UnityEngine; @@ -6,16 +6,19 @@ using UnityEditor; namespace YooAsset.Editor { + /// + /// 提供空资源清单生成工具窗口 + /// public class CreateEmptyCatalogWindow : EditorWindow { static CreateEmptyCatalogWindow _thisInstance; - [MenuItem("Tools/空清单生成工具(Catalog)", false, 102)] + [MenuItem("Tools/Empty Catalog Generator", false, 102)] static void ShowWindow() { if (_thisInstance == null) { - _thisInstance = EditorWindow.GetWindow(typeof(CreateEmptyCatalogWindow), false, "空清单生成工具", true) as CreateEmptyCatalogWindow; + _thisInstance = EditorWindow.GetWindow(typeof(CreateEmptyCatalogWindow), false, "Empty Catalog Generator", true) as CreateEmptyCatalogWindow; _thisInstance.minSize = new Vector2(800, 600); } _thisInstance.Show(); @@ -32,9 +35,9 @@ namespace YooAsset.Editor if (string.IsNullOrEmpty(_packageName) == false) { - if (GUILayout.Button("生成空的Catalog文件", GUILayout.MaxWidth(150))) + if (GUILayout.Button("Generate Empty Catalog File", GUILayout.MaxWidth(150))) { - string outputPath = EditorDialogUtility.OpenFolderPanel("输出目录", "Assets/"); + string outputPath = EditorDialogUtility.OpenFolderPanel("Output Directory", "Assets/"); if (string.IsNullOrEmpty(outputPath) == false) { CreateEmptyCatalogFile(outputPath); @@ -50,12 +53,12 @@ namespace YooAsset.Editor bool result = BuiltinCatalogHelper.CreateEmptyFile(_packageName, string.Empty, outputPath); if (result == false) { - Debug.LogError($"Create package {_packageName} catalog file failed ! See the detail error in console !"); + Debug.LogError($"Failed to create a catalog file for package '{_packageName}'. See Console for details."); } } catch (System.Exception ex) { - Debug.LogError($"Create package {_packageName} catalog file failed ! {ex.Message}"); + Debug.LogError($"Failed to create a catalog file for package '{_packageName}': {ex.Message}."); } } } diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuildinCatalog/CreateEmptyCatalogWindow.cs.meta b/Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuiltinCatalog/CreateEmptyCatalogWindow.cs.meta similarity index 100% rename from Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuildinCatalog/CreateEmptyCatalogWindow.cs.meta rename to Assets/YooAsset/Samples~/Extension Sample/Editor/CreateBuiltinCatalog/CreateEmptyCatalogWindow.cs.meta diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/CustomBuildPipeline/CustomBuildPipelineViewer.cs b/Assets/YooAsset/Samples~/Extension Sample/Editor/CustomBuildPipeline/CustomBuildPipelineViewer.cs index 291f837d..bfed374c 100644 --- a/Assets/YooAsset/Samples~/Extension Sample/Editor/CustomBuildPipeline/CustomBuildPipelineViewer.cs +++ b/Assets/YooAsset/Samples~/Extension Sample/Editor/CustomBuildPipeline/CustomBuildPipelineViewer.cs @@ -8,6 +8,9 @@ using UnityEditor.UIElements; using UnityEngine.UIElements; using YooAsset.Editor; +/// +/// 提供自定义构建管线的编辑器视图 +/// [BuildPipelineAttribute("CustomBuildPipeline")] internal class CustomBuildPipelineViewer : LegacyBuildPipelineViewer { diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/CustomCollectRules/CustomAddressRule.cs b/Assets/YooAsset/Samples~/Extension Sample/Editor/CustomCollectRules/CustomAddressRule.cs new file mode 100644 index 00000000..11ac10ca --- /dev/null +++ b/Assets/YooAsset/Samples~/Extension Sample/Editor/CustomCollectRules/CustomAddressRule.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using UnityEngine; +using YooAsset.Editor; + +/// +/// 按文件名生成资源定位地址 +/// +[DisplayName("定位地址: 文件名.智能尾缀")] +public class AddressByFileNameAndExt : IAddressRule +{ + /// + public string GetAssetAddress(AddressRuleData data) + { + var extension = Path.GetExtension(data.AssetPath); + if (extension == ".asset") + { + var asset = UnityEditor.AssetDatabase.LoadAssetAtPath(data.AssetPath); + if (asset == null) + throw new InvalidOperationException($"Asset not found: '{data.AssetPath}'."); + + var assetType = asset.GetType(); + var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(data.AssetPath); + return fileNameWithoutExtension + $".{assetType.Name.ToLowerInvariant()}"; + } + + return Path.GetFileName(data.AssetPath); + } +} \ No newline at end of file diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/CustomCollectRules/CustomAdressRule.cs.meta b/Assets/YooAsset/Samples~/Extension Sample/Editor/CustomCollectRules/CustomAddressRule.cs.meta similarity index 100% rename from Assets/YooAsset/Samples~/Extension Sample/Editor/CustomCollectRules/CustomAdressRule.cs.meta rename to Assets/YooAsset/Samples~/Extension Sample/Editor/CustomCollectRules/CustomAddressRule.cs.meta diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/CustomCollectRules/CustomAdressRule.cs b/Assets/YooAsset/Samples~/Extension Sample/Editor/CustomCollectRules/CustomAdressRule.cs deleted file mode 100644 index f848951d..00000000 --- a/Assets/YooAsset/Samples~/Extension Sample/Editor/CustomCollectRules/CustomAdressRule.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using UnityEngine; -using YooAsset.Editor; - -[DisplayName("定位地址: 文件名.智能尾缀")] -public class AddressByFileNameAndExt : IAddressRule -{ - public string GetAssetAddress(AddressRuleData data) - { - var ext = Path.GetExtension(data.AssetPath); - if (ext == ".asset") - { - var a = UnityEditor.AssetDatabase.LoadAssetAtPath(data.AssetPath); - if (a == null) return ".errortype"; - var type = a.GetType(); - var dt = Path.GetFileNameWithoutExtension(data.AssetPath); - return dt + $".{type.Name.ToLowerInvariant()}"; - } - - return Path.GetFileName(data.AssetPath); - } -} \ No newline at end of file diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/CustomCollectRules/CustomPackRule.cs b/Assets/YooAsset/Samples~/Extension Sample/Editor/CustomCollectRules/CustomPackRule.cs index 84efeed7..52c60d2b 100644 --- a/Assets/YooAsset/Samples~/Extension Sample/Editor/CustomCollectRules/CustomPackRule.cs +++ b/Assets/YooAsset/Samples~/Extension Sample/Editor/CustomCollectRules/CustomPackRule.cs @@ -1,20 +1,24 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; using YooAsset.Editor; +/// +/// 将特效纹理按首字符分组到资源包 +/// [DisplayName("打包特效纹理(自定义)")] public class PackEffectTexture : IBundlePackRule { private const string PackDirectory = "Assets/Effect/Textures/"; + /// BundlePackRuleResult IBundlePackRule.GetPackRuleResult(BundlePackRuleData data) { string assetPath = data.AssetPath; if (assetPath.StartsWith(PackDirectory) == false) - throw new Exception($"Only support folder : {PackDirectory}"); + throw new ArgumentException($"Only support folder: {PackDirectory}", nameof(data)); string assetName = Path.GetFileName(assetPath).ToLower(); string firstChar = assetName.Substring(0, 1); @@ -24,9 +28,13 @@ public class PackEffectTexture : IBundlePackRule } } +/// +/// 按视频资源路径生成原始文件资源包 +/// [DisplayName("打包视频(自定义)")] public class PackVideo : IBundlePackRule { + /// public BundlePackRuleResult GetPackRuleResult(BundlePackRuleData data) { string bundleName = RemoveExtension(data.AssetPath); diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/MacroSupport/MacroDefine.cs b/Assets/YooAsset/Samples~/Extension Sample/Editor/MacroSupport/MacroDefine.cs index d3bd139c..01ff0ddf 100644 --- a/Assets/YooAsset/Samples~/Extension Sample/Editor/MacroSupport/MacroDefine.cs +++ b/Assets/YooAsset/Samples~/Extension Sample/Editor/MacroSupport/MacroDefine.cs @@ -1,18 +1,21 @@ -using System.Collections.Generic; +using System.Collections.Generic; #if YOO_MACRO_SUPPORT namespace YooAsset.Editor { - public class MacroDefine + /// + /// 提供 YooAsset 版本相关的脚本宏定义 + /// + public static class MacroDefine { /// - /// YooAsset版本宏定义 + /// YooAsset 版本宏定义集合 /// - public static readonly List Macros = new List() + public static IReadOnlyList Macros { get; } = new List() { - "YOO_ASSET_2", - "YOO_ASSET_2_3", - "YOO_ASSET_2_3_OR_NEWER", + "YOO_ASSET_3", + "YOO_ASSET_3_0", + "YOO_ASSET_3_0_OR_NEWER", }; } } diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/MacroSupport/MacroProcessor.cs b/Assets/YooAsset/Samples~/Extension Sample/Editor/MacroSupport/MacroProcessor.cs index 8337801f..2eff15cf 100644 --- a/Assets/YooAsset/Samples~/Extension Sample/Editor/MacroSupport/MacroProcessor.cs +++ b/Assets/YooAsset/Samples~/Extension Sample/Editor/MacroSupport/MacroProcessor.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -8,6 +8,9 @@ using UnityEditor; #if YOO_MACRO_SUPPORT namespace YooAsset.Editor { + /// + /// 在生成 C# 工程文件时注入 YooAsset 版本宏定义 + /// [InitializeOnLoad] public class MacroProcessor : AssetPostprocessor { @@ -41,7 +44,7 @@ namespace YooAsset.Editor } /// - /// 处理宏定义 + /// 处理工程文件中的宏定义 /// private static bool ProcessDefineConstants(XmlElement element) { @@ -76,7 +79,7 @@ namespace YooAsset.Editor } /// - /// 检测工程是否引用了YooAsset + /// 检查工程是否引用了 YooAsset /// private static bool IsCSProjectReferenced(XmlElement element) { @@ -93,7 +96,11 @@ namespace YooAsset.Editor if (childNode.Name != "Reference" && childNode.Name != "ProjectReference") continue; - string include = childNode.Attributes["Include"].Value; + XmlAttribute includeAttribute = childNode.Attributes["Include"]; + if (includeAttribute == null) + continue; + + string include = includeAttribute.Value; if (include.Contains("YooAsset")) return true; } diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/MacroSupport/RspGenerator.cs b/Assets/YooAsset/Samples~/Extension Sample/Editor/MacroSupport/RspGenerator.cs index 42c66b51..0d4e1872 100644 --- a/Assets/YooAsset/Samples~/Extension Sample/Editor/MacroSupport/RspGenerator.cs +++ b/Assets/YooAsset/Samples~/Extension Sample/Editor/MacroSupport/RspGenerator.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -9,10 +9,13 @@ using UnityEngine; #if YOO_MACRO_SUPPORT namespace YooAsset.Editor.Experiment { + /// + /// 通过 csc.rsp 文件注入 YooAsset 版本宏定义 + /// [InitializeOnLoad] public class RspGenerator { - // csc.rsp文件路径 + // Unity 编译器响应文件路径 private static string RspFilePath => Path.Combine(Application.dataPath, "csc.rsp"); static RspGenerator() @@ -21,9 +24,9 @@ namespace YooAsset.Editor.Experiment } /// - /// 更新csc.rsp文件 + /// 更新 csc.rsp 文件 /// - private static void UpdateRspFile(List addMacros, List removeMacros) + private static void UpdateRspFile(IReadOnlyList addMacros, IReadOnlyList removeMacros) { var existingDefines = new HashSet(); var otherLines = new List(); @@ -34,24 +37,26 @@ namespace YooAsset.Editor.Experiment // 2. 添加新宏 if (addMacros != null && addMacros.Count > 0) { - addMacros.ForEach(x => + foreach (var x in addMacros) { if (existingDefines.Contains(x) == false) existingDefines.Add(x); - }); + } } // 3. 移除指定宏 if (removeMacros != null && removeMacros.Count > 0) { - removeMacros.ForEach(x => + foreach (var x in removeMacros) { existingDefines.Remove(x); - }); + } } // 4. 重新生成内容 - WriteRspFile(existingDefines, otherLines); + bool changed = WriteRspFile(existingDefines, otherLines); + if (changed == false) + return; // 5. 刷新AssetDatabase AssetDatabase.Refresh(); @@ -59,7 +64,7 @@ namespace YooAsset.Editor.Experiment } /// - /// 读取csc.rsp文件,返回宏定义和其他行 + /// 读取 csc.rsp 文件中的宏定义和其他行 /// private static void ReadRspFile(HashSet defines, List others) { @@ -90,9 +95,10 @@ namespace YooAsset.Editor.Experiment } /// - /// 重新写入csc.rsp文件 + /// 重新写入 csc.rsp 文件 /// - private static void WriteRspFile(HashSet defines, List others) + /// 文件内容发生变化时返回 true + private static bool WriteRspFile(HashSet defines, List others) { StringBuilder sb = new StringBuilder(); if (others != null && others.Count > 0) @@ -108,7 +114,16 @@ namespace YooAsset.Editor.Experiment } } - File.WriteAllText(RspFilePath, sb.ToString()); + string newContent = sb.ToString(); + if (File.Exists(RspFilePath)) + { + string oldContent = File.ReadAllText(RspFilePath); + if (oldContent == newContent) + return false; + } + + File.WriteAllText(RspFilePath, newContent); + return true; } } } diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/PackageComparator/PackageComparatorWindow.cs b/Assets/YooAsset/Samples~/Extension Sample/Editor/PackageComparator/PackageComparatorWindow.cs index 2da5a7e9..e1fe18d9 100644 --- a/Assets/YooAsset/Samples~/Extension Sample/Editor/PackageComparator/PackageComparatorWindow.cs +++ b/Assets/YooAsset/Samples~/Extension Sample/Editor/PackageComparator/PackageComparatorWindow.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using System.Collections.Generic; using System.Linq; using UnityEngine; @@ -6,16 +6,19 @@ using UnityEditor; namespace YooAsset.Editor { + /// + /// 提供补丁包差异比对工具窗口 + /// public class PackageComparatorWindow : EditorWindow { static PackageComparatorWindow _thisInstance; - [MenuItem("Tools/补丁包比对工具", false, 103)] + [MenuItem("Tools/Patch Package Comparator", false, 103)] static void ShowWindow() { if (_thisInstance == null) { - _thisInstance = EditorWindow.GetWindow(typeof(PackageComparatorWindow), false, "补丁包比对工具", true) as PackageComparatorWindow; + _thisInstance = EditorWindow.GetWindow(typeof(PackageComparatorWindow), false, "Patch Package Comparator", true) as PackageComparatorWindow; _thisInstance.minSize = new Vector2(800, 600); } _thisInstance.Show(); @@ -32,7 +35,7 @@ namespace YooAsset.Editor { GUILayout.Space(10); EditorGUILayout.BeginHorizontal(); - if (GUILayout.Button("选择补丁包1", GUILayout.MaxWidth(150))) + if (GUILayout.Button("Select Patch Package 1", GUILayout.MaxWidth(150))) { string resultPath = EditorUtility.OpenFilePanel("Find", "Assets/", "bytes"); if (string.IsNullOrEmpty(resultPath)) @@ -44,7 +47,7 @@ namespace YooAsset.Editor GUILayout.Space(10); EditorGUILayout.BeginHorizontal(); - if (GUILayout.Button("选择补丁包2", GUILayout.MaxWidth(150))) + if (GUILayout.Button("Select Patch Package 2", GUILayout.MaxWidth(150))) { string resultPath = EditorUtility.OpenFilePanel("Find", "Assets/", "bytes"); if (string.IsNullOrEmpty(resultPath)) @@ -56,9 +59,18 @@ namespace YooAsset.Editor if (string.IsNullOrEmpty(_manifestPath1) == false && string.IsNullOrEmpty(_manifestPath2) == false) { - if (GUILayout.Button("比对差异", GUILayout.MaxWidth(150))) + if (GUILayout.Button("Compare Differences", GUILayout.MaxWidth(150))) { - ComparePackage(_changeList, _newList); + try + { + ComparePackage(_changeList, _newList); + } + catch (System.Exception ex) + { + _changeList.Clear(); + _newList.Clear(); + Debug.LogError($"Failed to compare patch packages: {ex.Message}."); + } } } @@ -66,7 +78,7 @@ namespace YooAsset.Editor using (new EditorGUI.DisabledScope(false)) { int totalCount = _changeList.Count; - EditorGUILayout.Foldout(true, $"差异列表 ( {totalCount} )"); + EditorGUILayout.Foldout(true, $"Changed Bundles ( {totalCount} )"); EditorGUI.indentLevel = 1; _scrollPos1 = EditorGUILayout.BeginScrollView(_scrollPos1); @@ -84,7 +96,7 @@ namespace YooAsset.Editor using (new EditorGUI.DisabledScope(false)) { int totalCount = _newList.Count; - EditorGUILayout.Foldout(true, $"新增列表 ( {totalCount} )"); + EditorGUILayout.Foldout(true, $"New Bundles ( {totalCount} )"); EditorGUI.indentLevel = 1; _scrollPos2 = EditorGUILayout.BeginScrollView(_scrollPos2); @@ -104,13 +116,13 @@ namespace YooAsset.Editor changeList.Clear(); newList.Clear(); - // 加载补丁清单1 + // 加载基准补丁清单 byte[] bytesData1 = FileUtility.ReadAllBytes(_manifestPath1); - PackageManifest manifest1 = PackageManifestHelper.DeserializeManifestFromBinary(bytesData1, null); //TODO 自行处理解密 + PackageManifest manifest1 = PackageManifestHelper.DeserializeManifestFromBinary(bytesData1, null); // TODO: 根据业务需求处理清单解密。 - // 加载补丁清单1 + // 加载待比对补丁清单 byte[] bytesData2 = FileUtility.ReadAllBytes(_manifestPath2); - PackageManifest manifest2 = PackageManifestHelper.DeserializeManifestFromBinary(bytesData2, null); //TODO 自行处理解密 + PackageManifest manifest2 = PackageManifestHelper.DeserializeManifestFromBinary(bytesData2, null); // TODO: 根据业务需求处理清单解密。 // 拷贝文件列表 foreach (var bundle2 in manifest2.BundleList) @@ -132,7 +144,7 @@ namespace YooAsset.Editor changeList.Sort((x, y) => string.Compare(x.BundleName, y.BundleName)); newList.Sort((x, y) => string.Compare(x.BundleName, y.BundleName)); - Debug.Log("资源包差异比对完成!"); + Debug.Log("Package comparison completed."); } } } diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/PackageImporter/PackageImporterWindow.cs b/Assets/YooAsset/Samples~/Extension Sample/Editor/PackageImporter/PackageImporterWindow.cs index b45a086e..16ae8f67 100644 --- a/Assets/YooAsset/Samples~/Extension Sample/Editor/PackageImporter/PackageImporterWindow.cs +++ b/Assets/YooAsset/Samples~/Extension Sample/Editor/PackageImporter/PackageImporterWindow.cs @@ -4,16 +4,19 @@ using UnityEditor; namespace YooAsset.Editor { + /// + /// 提供补丁包导入工具窗口 + /// public class PackageImporterWindow : EditorWindow { static PackageImporterWindow _thisInstance; - [MenuItem("Tools/补丁包导入工具", false, 104)] + [MenuItem("Tools/Patch Package Importer", false, 104)] static void ShowWindow() { if (_thisInstance == null) { - _thisInstance = EditorWindow.GetWindow(typeof(PackageImporterWindow), false, "补丁包导入工具", true) as PackageImporterWindow; + _thisInstance = EditorWindow.GetWindow(typeof(PackageImporterWindow), false, "Patch Package Importer", true) as PackageImporterWindow; _thisInstance.minSize = new Vector2(800, 600); } _thisInstance.Show(); @@ -26,7 +29,12 @@ namespace YooAsset.Editor { GUILayout.Space(10); EditorGUILayout.BeginHorizontal(); - if (GUILayout.Button("选择补丁包", GUILayout.MaxWidth(150))) + _packageName = EditorGUILayout.TextField("Package Name", _packageName); + EditorGUILayout.EndHorizontal(); + + GUILayout.Space(10); + EditorGUILayout.BeginHorizontal(); + if (GUILayout.Button("Select Patch Package", GUILayout.MaxWidth(150))) { string resultPath = EditorUtility.OpenFilePanel("Find", "Assets/", "bytes"); if (!string.IsNullOrEmpty(resultPath)) @@ -37,54 +45,84 @@ namespace YooAsset.Editor if (string.IsNullOrEmpty(_manifestPath) == false) { - if (GUILayout.Button("导入补丁包(全部文件)", GUILayout.MaxWidth(150))) + if (GUILayout.Button("Import Patch Package (All Files)", GUILayout.MaxWidth(150))) { - string streamingAssetsRoot = BundleBuilderHelper.GetStreamingAssetsRoot(); - EditorFileUtility.ClearFolder(streamingAssetsRoot); - CopyPackageFiles(_manifestPath); + if (string.IsNullOrEmpty(_packageName)) + { + Debug.LogError("Package name is empty."); + return; + } + + try + { + CopyPackageFiles(_manifestPath); + } + catch (System.Exception ex) + { + Debug.LogError($"Failed to import patch package '{_packageName}': {ex.Message}."); + } + finally + { + AssetDatabase.Refresh(); + } } } } private void CopyPackageFiles(string manifestFilePath) { - string manifestFileName = Path.GetFileNameWithoutExtension(manifestFilePath); - string outputDirectory = Path.GetDirectoryName(manifestFilePath); + string sourceRoot = Path.GetDirectoryName(manifestFilePath); + if (string.IsNullOrEmpty(sourceRoot)) + throw new DirectoryNotFoundException("Patch package directory does not exist."); + + string versionFileName = YooAssetConfiguration.GetPackageVersionFileName(_packageName); + string versionSourcePath = Path.Combine(sourceRoot, versionFileName); + string packageVersion = File.ReadAllText(versionSourcePath).Trim(); + string manifestFileName = YooAssetConfiguration.GetManifestBinaryFileName(_packageName, packageVersion); + string hashFileName = YooAssetConfiguration.GetPackageHashFileName(_packageName, packageVersion); + string selectedFileName = Path.GetFileName(manifestFilePath); + if (selectedFileName != manifestFileName) + throw new InvalidDataException($"Selected manifest file '{selectedFileName}' does not match expected manifest file '{manifestFileName}'."); + + string destRoot = Path.Combine(BundleBuilderHelper.GetStreamingAssetsRoot(), _packageName); + + // 清空旧目录 + EditorFileUtility.DeleteDirectory(destRoot); + EditorFileUtility.CreateDirectory(destRoot); // 拷贝核心文件 { - string sourcePath = $"{outputDirectory}/{manifestFileName}.bytes"; - string destPath = $"{BundleBuilderHelper.GetStreamingAssetsRoot()}/{_packageName}/{manifestFileName}.bytes"; + string sourcePath = Path.Combine(sourceRoot, manifestFileName); + string destPath = Path.Combine(destRoot, manifestFileName); EditorFileUtility.CopyFile(sourcePath, destPath, true); } { - string sourcePath = $"{outputDirectory}/{manifestFileName}.hash"; - string destPath = $"{BundleBuilderHelper.GetStreamingAssetsRoot()}/{_packageName}/{manifestFileName}.hash"; + string sourcePath = Path.Combine(sourceRoot, hashFileName); + string destPath = Path.Combine(destRoot, hashFileName); EditorFileUtility.CopyFile(sourcePath, destPath, true); } { - string fileName = YooAssetConfiguration.GetPackageVersionFileName(_packageName); - string sourcePath = $"{outputDirectory}/{fileName}"; - string destPath = $"{BundleBuilderHelper.GetStreamingAssetsRoot()}/{_packageName}/{fileName}"; + string sourcePath = versionSourcePath; + string destPath = Path.Combine(destRoot, versionFileName); EditorFileUtility.CopyFile(sourcePath, destPath, true); } // 加载补丁清单 byte[] bytesData = FileUtility.ReadAllBytes(manifestFilePath); - PackageManifest manifest = PackageManifestHelper.DeserializeManifestFromBinary(bytesData, null); //TODO 自行处理解密 + PackageManifest manifest = PackageManifestHelper.DeserializeManifestFromBinary(bytesData, null); // TODO: 根据业务需求处理清单解密。 // 拷贝文件列表 int fileCount = 0; foreach (var packageBundle in manifest.BundleList) { fileCount++; - string sourcePath = $"{outputDirectory}/{packageBundle.GetFileName()}"; - string destPath = $"{BundleBuilderHelper.GetStreamingAssetsRoot()}/{_packageName}/{packageBundle.GetFileName()}"; + string fileName = packageBundle.GetFileName(); + string sourcePath = Path.Combine(sourceRoot, fileName); + string destPath = Path.Combine(destRoot, fileName); EditorFileUtility.CopyFile(sourcePath, destPath, true); } - Debug.Log($"补丁包拷贝完成,一共拷贝了{fileCount}个资源文件"); - AssetDatabase.Refresh(); + Debug.Log($"Patch package copy completed. Copied {fileCount} bundle files."); } } } \ No newline at end of file diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/PreprocessBuild/PreprocessBuildCatalog.cs b/Assets/YooAsset/Samples~/Extension Sample/Editor/PreprocessBuild/PreprocessBuildCatalog.cs index bbf2a6d8..238459bb 100644 --- a/Assets/YooAsset/Samples~/Extension Sample/Editor/PreprocessBuild/PreprocessBuildCatalog.cs +++ b/Assets/YooAsset/Samples~/Extension Sample/Editor/PreprocessBuild/PreprocessBuildCatalog.cs @@ -5,23 +5,26 @@ using YooAsset.Editor; namespace YooAsset { + /// + /// 在应用构建前生成内置资源清单 + /// public class PreprocessBuildCatalog : UnityEditor.Build.IPreprocessBuildWithReport { + /// + /// 构建预处理回调顺序 + /// public int callbackOrder { get { return 0; } } - /// - /// 在构建应用程序前自动生成内置资源目录文件。 - /// 原理:搜索StreamingAssets目录下的所有资源文件,将这些文件信息写入文件,然后在运行时做查询用途。 - /// + /// public void OnPreprocessBuild(UnityEditor.Build.Reporting.BuildReport report) { - YooLogger.Log("Begin to create catalog file !"); + YooLogger.Log("Starting catalog file generation."); string rootPath = BundleBuilderHelper.GetStreamingAssetsRoot(); DirectoryInfo rootDirectory = new DirectoryInfo(rootPath); if (rootDirectory.Exists == false) { - Debug.LogWarning($"Can not found StreamingAssets root directory : {rootPath}"); + Debug.LogWarning("StreamingAssets root directory does not exist."); return; } @@ -30,18 +33,18 @@ namespace YooAsset foreach (var subDirectory in subDirectories) { string packageName = subDirectory.Name; - string pacakgeDirectory = subDirectory.FullName; + string packageDirectory = subDirectory.FullName; try { - bool result = BuiltinCatalogHelper.CreateFile(null, packageName, pacakgeDirectory); //TODO 自行处理解密 + bool result = BuiltinCatalogHelper.CreateFile(null, packageName, packageDirectory); // TODO: 根据业务需求处理清单解密。 if (result == false) { - Debug.LogError($"Create package {packageName} catalog file failed ! See the detail error in console !"); + Debug.LogError($"Failed to create a catalog file for package '{packageName}'. See Console for details."); } } catch (System.Exception ex) { - Debug.LogError($"Create package {packageName} catalog file failed ! {ex.Message}"); + Debug.LogError($"Failed to create a catalog file for package '{packageName}': {ex.Message}."); } } } diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/ShaderVariantCollector/ShaderVariantCollectionHelper.cs b/Assets/YooAsset/Samples~/Extension Sample/Editor/ShaderVariantCollector/ShaderVariantCollectionHelper.cs index 6650922c..98644572 100644 --- a/Assets/YooAsset/Samples~/Extension Sample/Editor/ShaderVariantCollector/ShaderVariantCollectionHelper.cs +++ b/Assets/YooAsset/Samples~/Extension Sample/Editor/ShaderVariantCollector/ShaderVariantCollectionHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; @@ -7,28 +7,51 @@ using UnityEngine.Rendering; using UnityEditor; using YooAsset.Editor; +/// +/// 封装 Unity 编辑器中的 ShaderVariantCollection 反射调用 +/// public static class ShaderVariantCollectionHelper { + /// + /// 清空当前编辑器记录的着色器变种集合 + /// public static void ClearCurrentShaderVariantCollection() { EditorAssemblyUtility.InvokeNonPublicStaticMethod(typeof(ShaderUtil), "ClearCurrentShaderVariantCollection"); } + + /// + /// 保存当前编辑器记录的着色器变种集合 + /// + /// 保存目标路径 public static void SaveCurrentShaderVariantCollection(string savePath) { EditorAssemblyUtility.InvokeNonPublicStaticMethod(typeof(ShaderUtil), "SaveCurrentShaderVariantCollection", savePath); } + + /// + /// 当前着色器变种集合中的着色器数量 + /// + /// 着色器数量 public static int GetCurrentShaderVariantCollectionShaderCount() { return (int)EditorAssemblyUtility.InvokeNonPublicStaticMethod(typeof(ShaderUtil), "GetCurrentShaderVariantCollectionShaderCount"); } + + /// + /// 当前着色器变种集合中的变种数量 + /// + /// 变种数量 public static int GetCurrentShaderVariantCollectionVariantCount() { return (int)EditorAssemblyUtility.InvokeNonPublicStaticMethod(typeof(ShaderUtil), "GetCurrentShaderVariantCollectionVariantCount"); } /// - /// 获取着色器的变种总数量 + /// 获取指定着色器资源的变种总数量 /// + /// 着色器资源路径 + /// 变种总数量的字符串表示 public static string GetShaderVariantCount(string assetPath) { Shader shader = AssetDatabase.LoadAssetAtPath(assetPath); diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/ShaderVariantCollector/ShaderVariantCollectionManifest.cs b/Assets/YooAsset/Samples~/Extension Sample/Editor/ShaderVariantCollector/ShaderVariantCollectionManifest.cs index 7175ce2a..71bd5775 100644 --- a/Assets/YooAsset/Samples~/Extension Sample/Editor/ShaderVariantCollector/ShaderVariantCollectionManifest.cs +++ b/Assets/YooAsset/Samples~/Extension Sample/Editor/ShaderVariantCollector/ShaderVariantCollectionManifest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; @@ -7,50 +7,70 @@ using UnityEngine; using UnityEngine.Rendering; using UnityEditor; +/// +/// 着色器变种集合的可序列化清单 +/// [Serializable] public class ShaderVariantCollectionManifest { + /// + /// 单个着色器变种的序列化信息 + /// [Serializable] public class ShaderVariantElement : IComparable { + /// + /// 用于稳定排序的组合键 + /// public string SortValue { private set; get; } /// - /// Pass type to use in this variant. + /// 变种使用的渲染通道类型 /// public PassType PassType; /// - /// Array of shader keywords to use in this variant. + /// 变种使用的着色器关键字数组 /// public string[] Keywords; + /// + /// 生成排序键 + /// public void MakeSortValue() { string combineKeyword = string.Empty; for (int i = 0; i < Keywords.Length; i++) { if (i == 0) - combineKeyword = Keywords[0]; + combineKeyword = Keywords[i]; else - combineKeyword = $"{combineKeyword}+{Keywords[0]}"; + combineKeyword = $"{combineKeyword}+{Keywords[i]}"; } SortValue = $"{PassType}+{combineKeyword}"; } + + /// public int CompareTo(ShaderVariantElement other) { return SortValue.CompareTo(other.SortValue); } } + /// + /// 单个着色器及其变种列表的序列化信息 + /// [Serializable] public class ShaderVariantInfo : IComparable { + /// + /// 用于稳定排序的组合键 + /// public string SortValue { private set; get; } /// - /// 着色器资源路径. + /// 着色器资源路径 /// public string AssetPath; @@ -69,10 +89,15 @@ public class ShaderVariantCollectionManifest /// public List ShaderVariantElements = new List(1000); + /// + /// 生成排序键 + /// public void MakeSortValue() { SortValue = AssetPath + "+" + ShaderName; } + + /// public int CompareTo(ShaderVariantInfo other) { return SortValue.CompareTo(other.SortValue); @@ -81,33 +106,36 @@ public class ShaderVariantCollectionManifest /// - /// Number of shaders in this collection + /// 清单中的着色器总数 /// public int ShaderTotalCount; /// - /// Number of total varians in this collection + /// 清单中的变种总数 /// public int VariantTotalCount; /// - /// Shader variants info list. + /// 着色器变种信息列表 /// public List ShaderVariantInfos = new List(1000); /// /// 添加着色器变种信息 /// + /// 着色器资源路径 + /// 着色器名称 + /// 渲染通道类型 + /// 着色器关键字数组 public void AddShaderVariant(string assetPath, string shaderName, PassType passType, string[] keywords) { - // 排序Keyword列表 - List temper = new List(keywords); - temper.Sort(); + List sortedKeywords = new List(keywords); + sortedKeywords.Sort(); var info = GetOrCreateShaderVariantInfo(assetPath, shaderName); ShaderVariantElement element = new ShaderVariantElement(); element.PassType = passType; - element.Keywords = temper.ToArray(); + element.Keywords = sortedKeywords.ToArray(); element.MakeSortValue(); info.ShaderVariantElements.Add(element); info.ShaderVariantCount++; @@ -126,20 +154,22 @@ public class ShaderVariantCollectionManifest } if (selectList.Count != 1) - throw new Exception("Should never get here !"); + throw new InvalidOperationException($"Unexpected duplicate ShaderVariantInfo entries (count={selectList.Count})."); return selectList[0]; } - /// - /// 解析SVC文件并将数据写入到清单 + /// 从 ShaderVariantCollection 提取清单数据 /// + /// 待解析的着色器变种集合 + /// 提取后的着色器变种清单 public static ShaderVariantCollectionManifest Extract(ShaderVariantCollection svc) { + if (svc == null) + throw new ArgumentNullException(nameof(svc)); + var manifest = new ShaderVariantCollectionManifest(); - manifest.ShaderTotalCount = ShaderVariantCollectionHelper.GetCurrentShaderVariantCollectionShaderCount(); - manifest.VariantTotalCount = ShaderVariantCollectionHelper.GetCurrentShaderVariantCollectionVariantCount(); using (var so = new SerializedObject(svc)) { @@ -149,30 +179,23 @@ public class ShaderVariantCollectionManifest for (int i = 0; i < shaderArray.arraySize; ++i) { var shaderRef = shaderArray.FindPropertyRelative($"data[{i}].first"); + var shader = shaderRef.objectReferenceValue as Shader; + if (shader == null) + throw new InvalidOperationException("Invalid shader in ShaderVariantCollection file."); + + string shaderAssetPath = AssetDatabase.GetAssetPath(shader); + string shaderName = shader.name; + + // 添加变种信息 var shaderVariantsArray = shaderArray.FindPropertyRelative($"data[{i}].second.variants"); - if (shaderRef != null && shaderRef.propertyType == SerializedPropertyType.ObjectReference && shaderVariantsArray != null && shaderVariantsArray.isArray) + for (int j = 0; j < shaderVariantsArray.arraySize; ++j) { - var shader = shaderRef.objectReferenceValue as Shader; - if (shader == null) - { - throw new Exception("Invalid shader in ShaderVariantCollection file."); - } + var propKeywords = shaderVariantsArray.FindPropertyRelative($"Array.data[{j}].keywords"); + var propPassType = shaderVariantsArray.FindPropertyRelative($"Array.data[{j}].passType"); - string shaderAssetPath = AssetDatabase.GetAssetPath(shader); - string shaderName = shader.name; - - // 添加变种信息 - for (int j = 0; j < shaderVariantsArray.arraySize; ++j) - { - var propKeywords = shaderVariantsArray.FindPropertyRelative($"Array.data[{j}].keywords"); - var propPassType = shaderVariantsArray.FindPropertyRelative($"Array.data[{j}].passType"); - if (propKeywords != null && propPassType != null && propKeywords.propertyType == SerializedPropertyType.String) - { - string[] keywords = propKeywords.stringValue.Split(' '); - PassType pathType = (PassType)propPassType.intValue; - manifest.AddShaderVariant(shaderAssetPath, shaderName, pathType, keywords); - } - } + string[] keywords = propKeywords.stringValue.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + PassType passType = (PassType)propPassType.intValue; + manifest.AddShaderVariant(shaderAssetPath, shaderName, passType, keywords); } } } @@ -185,6 +208,13 @@ public class ShaderVariantCollectionManifest shaderVariantInfo.ShaderVariantElements.Sort(); } + // 统计数量 + foreach (var shaderVariantInfo in manifest.ShaderVariantInfos) + { + manifest.VariantTotalCount += shaderVariantInfo.ShaderVariantElements.Count; + } + manifest.ShaderTotalCount = manifest.ShaderVariantInfos.Count; + return manifest; } } \ No newline at end of file diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/ShaderVariantCollector/ShaderVariantCollector.cs b/Assets/YooAsset/Samples~/Extension Sample/Editor/ShaderVariantCollector/ShaderVariantCollector.cs index 127dfe5d..e2b22cb2 100644 --- a/Assets/YooAsset/Samples~/Extension Sample/Editor/ShaderVariantCollector/ShaderVariantCollector.cs +++ b/Assets/YooAsset/Samples~/Extension Sample/Editor/ShaderVariantCollector/ShaderVariantCollector.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -8,6 +8,9 @@ using UnityEditor; using UnityEditor.SceneManagement; using YooAsset.Editor; +/// +/// 收集资源包裹中材质产生的着色器变种 +/// public static class ShaderVariantCollector { private enum ESteps @@ -34,21 +37,33 @@ public static class ShaderVariantCollector /// - /// 开始收集 + /// 启动着色器变种收集流程 /// + /// 收集结果保存路径,扩展名必须为 .shadervariants。 + /// 参与收集的资源包裹名称 + /// 每批处理的材质数量 + /// 收集完成后的回调 public static void Run(string savePath, string packageName, int processMaxNum, Action completedCallback) { if (_steps != ESteps.None) return; + if (EditorSceneUtility.HasDirtyScenes()) + { + UnityEngine.Debug.LogError("Unsaved scenes detected. Save all scenes before collecting shader variants."); + return; + } + if (Path.HasExtension(savePath) == false) savePath = $"{savePath}.shadervariants"; if (Path.GetExtension(savePath) != ".shadervariants") - throw new System.Exception("Shader variant file extension is invalid."); + throw new System.ArgumentException("Shader variant file extension is invalid.", nameof(savePath)); if (string.IsNullOrEmpty(packageName)) - throw new System.Exception("Package name is null or empty !"); + throw new System.ArgumentNullException(nameof(packageName)); + if (processMaxNum <= 0) + throw new System.ArgumentOutOfRangeException(nameof(processMaxNum), "Process capacity must be greater than zero."); - // 注意:先删除再保存,否则ShaderVariantCollection内容将无法及时刷新 + // Unity 对同名 ShaderVariantCollection 的刷新存在延迟,先删除旧资源再写入新结果。 AssetDatabase.DeleteAsset(savePath); EditorFileUtility.CreateFileDirectory(savePath); _savePath = savePath; @@ -67,6 +82,17 @@ public static class ShaderVariantCollector } private static void EditorUpdate() + { + try + { + InternalUpdate(); + } + catch (Exception ex) + { + Finish(false, ex); + } + } + private static void InternalUpdate() { if (_steps == ESteps.None) return; @@ -75,14 +101,14 @@ public static class ShaderVariantCollector { ShaderVariantCollectionHelper.ClearCurrentShaderVariantCollection(); _steps = ESteps.CollectAllMaterial; - return; //等待一帧 + return; // 等待一帧,让编辑器完成清理。 } if (_steps == ESteps.CollectAllMaterial) { _allMaterials = GetAllMaterials(); _steps = ESteps.CollectVariants; - return; //等待一帧 + return; // 等待一帧,让材质列表收集结果稳定。 } if (_steps == ESteps.CollectVariants) @@ -119,7 +145,7 @@ public static class ShaderVariantCollector if (_steps == ESteps.WaitingDone) { - // 注意:一定要延迟保存才会起效 + // Unity 需要等待若干帧后才能把当前变种集合写入资源文件。 if (_elapsedTime.ElapsedMilliseconds > WaitMilliseconds) { _elapsedTime.Stop(); @@ -129,9 +155,7 @@ public static class ShaderVariantCollector ShaderVariantCollectionHelper.SaveCurrentShaderVariantCollection(_savePath); CreateManifest(); - UnityEngine.Debug.Log($"搜集SVC完毕!"); - EditorApplication.update -= EditorUpdate; - _completedCallback?.Invoke(); + Finish(true); } } } @@ -164,7 +188,7 @@ public static class ShaderVariantCollector result.Add(assetPath); } } - EditorDialogUtility.DisplayProgressBar("搜集所有材质球", ++progressValue, collectResult.CollectAssets.Count); + EditorDialogUtility.DisplayProgressBar("Collect All Materials", ++progressValue, collectResult.CollectAssets.Count); } EditorDialogUtility.ClearProgressBar(); @@ -175,7 +199,7 @@ public static class ShaderVariantCollector { Camera camera = Camera.main; if (camera == null) - throw new System.Exception("Not found main camera."); + throw new System.InvalidOperationException("Main camera is missing."); // 设置主相机 float aspect = camera.aspect; @@ -208,13 +232,19 @@ public static class ShaderVariantCollector { x++; } - EditorDialogUtility.DisplayProgressBar("照射所有材质球", ++progressValue, materials.Count); + EditorDialogUtility.DisplayProgressBar("Render All Materials", ++progressValue, materials.Count); } EditorDialogUtility.ClearProgressBar(); } private static GameObject CreateSphere(string assetPath, Vector3 position, int index) { var material = AssetDatabase.LoadAssetAtPath(assetPath); + if (material == null) + { + UnityEngine.Debug.LogWarning($"Material not found: '{assetPath}'."); + return null; + } + var shader = material.shader; if (shader == null) return null; @@ -229,11 +259,12 @@ public static class ShaderVariantCollector { foreach (var go in _allSpheres) { - GameObject.DestroyImmediate(go); + if (go != null) + GameObject.DestroyImmediate(go); } _allSpheres.Clear(); - // 尝试释放编辑器加载的资源 + // 材质扫描会加载编辑器资源,收集结束后主动释放以降低内存占用。 EditorUtility.UnloadUnusedAssetsImmediate(true); } private static void CreateManifest() @@ -251,4 +282,39 @@ public static class ShaderVariantCollector AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate); } + private static void Finish(bool success, Exception exception = null) + { + try + { + if (success) + UnityEngine.Debug.Log("Shader variant collection completed."); + else + UnityEngine.Debug.LogError($"Shader variant collection failed: {exception}."); + + _completedCallback?.Invoke(); + } + finally + { + Cleanup(); + } + } + private static void Cleanup() + { + EditorApplication.update -= EditorUpdate; + EditorDialogUtility.ClearProgressBar(); + + if (_elapsedTime != null) + { + _elapsedTime.Stop(); + _elapsedTime = null; + } + + DestroyAllSpheres(); + _savePath = null; + _packageName = null; + _processMaxNum = 0; + _completedCallback = null; + _allMaterials = null; + _steps = ESteps.None; + } } \ No newline at end of file diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/ShaderVariantCollector/ShaderVariantCollectorSetting.cs b/Assets/YooAsset/Samples~/Extension Sample/Editor/ShaderVariantCollector/ShaderVariantCollectorSetting.cs index 62fecc01..b81b6beb 100644 --- a/Assets/YooAsset/Samples~/Extension Sample/Editor/ShaderVariantCollector/ShaderVariantCollectorSetting.cs +++ b/Assets/YooAsset/Samples~/Extension Sample/Editor/ShaderVariantCollector/ShaderVariantCollectorSetting.cs @@ -1,29 +1,54 @@ -using UnityEngine; +using UnityEngine; using UnityEditor; +/// +/// 保存着色器变种收集工具的编辑器偏好设置 +/// public class ShaderVariantCollectorSetting : ScriptableObject { private const string DefaultSavePath = "Assets/MyShaderVariants.shadervariants"; - public static string GeFileSavePath(string packageName) + /// + /// 查询收集结果保存路径 + /// + /// 资源包裹名称 + /// 收集结果保存路径 + public static string GetFileSavePath(string packageName) { - string key = $"{Application.productName}_{packageName}_GeFileSavePath"; + string key = $"{Application.productName}_{packageName}_GetFileSavePath"; return EditorPrefs.GetString(key, DefaultSavePath); } + + /// + /// 设置收集结果保存路径 + /// + /// 资源包裹名称 + /// 收集结果保存路径 public static void SetFileSavePath(string packageName, string savePath) { - string key = $"{Application.productName}_{packageName}_GeFileSavePath"; + string key = $"{Application.productName}_{packageName}_GetFileSavePath"; EditorPrefs.SetString(key, savePath); } - public static int GeProcessCapacity(string packageName) + /// + /// 查询单批处理容量 + /// + /// 资源包裹名称 + /// 单批处理容量 + public static int GetProcessCapacity(string packageName) { - string key = $"{Application.productName}_{packageName}_GeProcessCapacity"; + string key = $"{Application.productName}_{packageName}_GetProcessCapacity"; return EditorPrefs.GetInt(key, 1000); } + + /// + /// 设置单批处理容量 + /// + /// 资源包裹名称 + /// 单批处理容量 public static void SetProcessCapacity(string packageName, int capacity) { - string key = $"{Application.productName}_{packageName}_GeProcessCapacity"; + string key = $"{Application.productName}_{packageName}_GetProcessCapacity"; EditorPrefs.SetInt(key, capacity); } } \ No newline at end of file diff --git a/Assets/YooAsset/Samples~/Extension Sample/Editor/ShaderVariantCollector/ShaderVariantCollectorWindow.cs b/Assets/YooAsset/Samples~/Extension Sample/Editor/ShaderVariantCollector/ShaderVariantCollectorWindow.cs index 1285e7c8..49417572 100644 --- a/Assets/YooAsset/Samples~/Extension Sample/Editor/ShaderVariantCollector/ShaderVariantCollectorWindow.cs +++ b/Assets/YooAsset/Samples~/Extension Sample/Editor/ShaderVariantCollector/ShaderVariantCollectorWindow.cs @@ -1,140 +1,115 @@ -using System; -using System.Linq; using System.Collections.Generic; using UnityEditor; using UnityEngine; -using UnityEditor.UIElements; -using UnityEngine.UIElements; using YooAsset.Editor; +/// +/// 提供着色器变种收集工具窗口 +/// public class ShaderVariantCollectorWindow : EditorWindow { - [MenuItem("Tools/着色器变种收集器", false, 100)] + [MenuItem("Tools/Shader Variant Collector", false, 100)] public static void OpenWindow() { - ShaderVariantCollectorWindow window = GetWindow("着色器变种收集工具", true); + ShaderVariantCollectorWindow window = GetWindow("Shader Variant Collector", true); window.minSize = new Vector2(800, 600); } - private Button _collectButton; - private TextField _collectOutputField; - private Label _currentShaderCountField; - private Label _currentVariantCountField; - private SliderInt _processCapacitySlider; - private PopupField _packageField; - private List _packageNames; + private int _packageIndex; private string _currentPackageName; + private string _collectOutputPath; + private int _processCapacity; - public void CreateGUI() + private void OnEnable() { - try + _packageNames = GetBuildPackageNames(); + if (_packageNames.Count > 0) { - VisualElement root = this.rootVisualElement; - - // 加载布局文件 - var visualAsset = UxmlLoader.LoadWindowUxml(); - if (visualAsset == null) - return; - - visualAsset.CloneTree(root); - - // 包裹名称列表 - _packageNames = GetBuildPackageNames(); - _currentPackageName = _packageNames[0]; - - // 文件输出目录 - _collectOutputField = root.Q("CollectOutput"); - _collectOutputField.SetValueWithoutNotify(ShaderVariantCollectorSetting.GeFileSavePath(_currentPackageName)); - _collectOutputField.RegisterValueChangedCallback(evt => - { - ShaderVariantCollectorSetting.SetFileSavePath(_currentPackageName, _collectOutputField.value); - }); - - // 收集的包裹 - var packageContainer = root.Q("PackageContainer"); - if (_packageNames.Count > 0) - { - int defaultIndex = GetDefaultPackageIndex(_currentPackageName); - _packageField = new PopupField(_packageNames, defaultIndex); - _packageField.label = "Package"; - _packageField.style.width = 350; - _packageField.RegisterValueChangedCallback(evt => - { - _currentPackageName = _packageField.value; - }); - packageContainer.Add(_packageField); - } - else - { - _packageField = new PopupField(); - _packageField.label = "Package"; - _packageField.style.width = 350; - packageContainer.Add(_packageField); - } - - // 容器值 - _processCapacitySlider = root.Q("ProcessCapacity"); - _processCapacitySlider.SetValueWithoutNotify(ShaderVariantCollectorSetting.GeProcessCapacity(_currentPackageName)); -#if !UNITY_2020_3_OR_NEWER - _processCapacitySlider.label = $"Capacity ({_processCapacitySlider.value})"; - _processCapacitySlider.RegisterValueChangedCallback(evt => - { - ShaderVariantCollectorSetting.SetProcessCapacity(_currentPackageName, _processCapacitySlider.value); - _processCapacitySlider.label = $"Capacity ({_processCapacitySlider.value})"; - }); -#else - _processCapacitySlider.RegisterValueChangedCallback(evt => - { - ShaderVariantCollectorSetting.SetProcessCapacity(_currentPackageName, _processCapacitySlider.value); - }); -#endif - - _currentShaderCountField = root.Q