diff --git a/Assets/YooAsset/Runtime/DiagnosticSystem/README.md b/Assets/YooAsset/Runtime/DiagnosticSystem/README.md new file mode 100644 index 00000000..36ad0ebd --- /dev/null +++ b/Assets/YooAsset/Runtime/DiagnosticSystem/README.md @@ -0,0 +1,945 @@ +# DiagnosticSystem 诊断系统 + +## 模块概述 + +DiagnosticSystem 是 YooAsset 的**远程调试诊断系统**,提供运行时资源管理状态的实时可视化和性能分析能力。该系统通过编辑器窗口与运行时游戏进行双向通信,实时采集和展示资源加载、Bundle 管理、异步操作等调试信息。 + +### 核心特性 + +- **实时远程调试**:在 Unity 编辑器中查看游戏运行时的资源管理状态 +- **完整状态快照**:采集所有资源、Bundle、异步操作的实时信息 +- **历史数据回溯**:缓存最近 500 帧数据,支持时间回溯分析 +- **双模式采样**:支持单次采样和自动连续采样 +- **低性能开销**:按需采样,无需连续监控 + +### 模块统计 + +| 组件 | 职责 | +|------|------| +| 核心通信 | RemoteDebuggerInRuntime + 双连接层 | +| 数据结构 | 5 个调试信息结构体 | +| 命令系统 | RemoteCommand 命令定义 | +| **总计** | 10 个核心文件,完整的远程诊断框架 | + +--- + +## 设计目标 + +| 目标 | 说明 | +|------|------| +| **实时可视化** | 在编辑器中实时查看运行时资源状态 | +| **性能监控** | 收集加载耗时、引用计数、下载进度等性能指标 | +| **内存诊断** | 追踪资源出生场景、引用计数,识别潜在内存泄漏 | +| **操作追踪** | 显示异步操作树,包括嵌套和依赖关系 | +| **低开销设计** | 按需采样而非连续监控,DEBUG 模式自动启用 | + +--- + +## 文件结构 + +``` +DiagnosticSystem/ +├── RemoteDebuggerDefine.cs # 全局定义和常量 +├── RemoteCommand.cs # 命令定义和序列化 +├── DebugReport.cs # 调试报告(顶层容器) +├── DebugPackageData.cs # 包级调试数据 +├── DebugProviderInfo.cs # 资源加载器调试信息 +├── DebugBundleInfo.cs # 资源包调试信息 +├── DebugOperationInfo.cs # 异步操作调试信息 +├── RemoteDebuggerInRuntime.cs # 运行时调试器主类 +├── RemotePlayerConnection.cs # 编辑器模拟连接层 +└── RemoteEditorConnection.cs # 运行时模拟连接层 +``` + +--- + +## 核心类说明 + +### RemoteDebuggerDefine + +全局定义类,包含调试器版本和通信协议的 GUID 标识符。 + +```csharp +internal class RemoteDebuggerDefine +{ + // 调试器版本(用于版本校验) + public const string DebuggerVersion = "2.3.3"; + + // 消息标识符(GUID) + public static readonly Guid kMsgPlayerSendToEditor = + new Guid("e34a5702dd353724aa315fb8011f08c3"); // 运行时→编辑器 + + public static readonly Guid kMsgEditorSendToPlayer = + new Guid("4d1926c9df5b052469a1c63448b7609a"); // 编辑器→运行时 +} +``` + +### RemoteCommand + +命令定义类,用于编辑器向运行时发送采样指令。 + +```csharp +internal enum ERemoteCommand +{ + SampleOnce = 0, // 单次采样 + SampleAuto = 1, // 自动采样(连续) +} + +[Serializable] +internal class RemoteCommand +{ + public int CommandType; // ERemoteCommand 枚举值 + public string CommandParam; // 命令参数 + + // 序列化/反序列化(JSON 格式) + public static byte[] Serialize(RemoteCommand command) + { + return Encoding.UTF8.GetBytes(JsonUtility.ToJson(command)); + } + + public static RemoteCommand Deserialize(byte[] data) + { + return JsonUtility.FromJson(Encoding.UTF8.GetString(data)); + } +} +``` + +**命令示例:** + +```json +// 单次采样 +{ + "CommandType": 0, + "CommandParam": "" +} + +// 开启自动采样 +{ + "CommandType": 1, + "CommandParam": "open" +} + +// 关闭自动采样 +{ + "CommandType": 1, + "CommandParam": "close" +} +``` + +### DebugReport + +调试报告容器,包含完整的系统状态快照。 + +```csharp +[Serializable] +internal class DebugReport +{ + // 调试器版本(用于版本校验) + public string DebuggerVersion = RemoteDebuggerDefine.DebuggerVersion; + + // 游戏帧数 + public int FrameCount; + + // 包级调试数据列表(一个游戏可能有多个资源包) + public List PackageDatas = new List(10); + + // 序列化/反序列化 + public static byte[] Serialize(DebugReport debugReport); + public static DebugReport Deserialize(byte[] data); +} +``` + +### DebugPackageData + +包级调试数据,包含单个资源包的所有诊断信息。 + +```csharp +[Serializable] +internal class DebugPackageData +{ + // 资源包名称 + public string PackageName; + + // 资源加载器列表 + public List ProviderInfos = new List(1000); + + // 资源包列表 + public List BundleInfos = new List(1000); + + // 异步操作列表 + public List OperationInfos = new List(1000); + + // 运行时查询字典(非序列化,按需构建) + [NonSerialized] + public Dictionary BundleInfoDic; + + // 延迟解析字典 + public DebugBundleInfo GetBundleInfo(string bundleName); +} +``` + +### DebugProviderInfo + +资源加载器(Provider)的调试信息。 + +```csharp +[Serializable] +internal struct DebugProviderInfo : IComparer, IComparable +{ + public string PackageName; // 所属包名 + public string AssetPath; // 资源路径(如 "Assets/Prefabs/Player.prefab") + public string SpawnScene; // 资源加载时的活跃场景名 + public string BeginTime; // 加载开始时间(格式:HH:mm:ss.fff) + public long LoadingTime; // 加载耗时(单位:毫秒) + public int RefCount; // 引用计数 + public string Status; // 加载状态(None/Processing/Succeed/Failed) + public List DependBundles; // 依赖的资源包名列表 + + // 按 AssetPath 字母排序 + public int CompareTo(DebugProviderInfo other); +} +``` + +**关键诊断价值:** +- `SpawnScene`:追踪资源在哪个场景被加载,帮助识别资源泄漏 +- `LoadingTime`:性能分析,识别加载慢的资源 +- `RefCount`:引用计数监控,RefCount > 0 表示资源仍在使用 +- `DependBundles`:依赖分析,理解资源加载的完整依赖链 + +### DebugBundleInfo + +资源包(Bundle)的调试信息。 + +```csharp +[Serializable] +internal struct DebugBundleInfo : IComparer, IComparable +{ + public string BundleName; // 资源包名称 + public int RefCount; // 引用计数(当前有多少个 Provider 在使用) + public string Status; // 加载状态 + public List ReferenceBundles; // 反向依赖(谁引用了我) + + // 按 BundleName 字母排序 + public int CompareTo(DebugBundleInfo other); +} +``` + +**关键诊断价值:** +- `RefCount`:Bundle 引用计数,为 0 时可以被卸载 +- `ReferenceBundles`:反向依赖分析,了解 Bundle 被哪些其他 Bundle 依赖 + +### DebugOperationInfo + +异步操作的调试信息,支持递归树结构。 + +```csharp +[Serializable] +internal struct DebugOperationInfo : IComparer, IComparable +{ + public string OperationName; // 操作类名(如 "LoadAssetOperation") + public string OperationDesc; // 操作说明(自定义描述) + public uint Priority; // 优先级(用于操作排序) + public float Progress; // 进度(0.0 - 1.0) + public string BeginTime; // 操作开始时间 + public long ProcessTime; // 处理耗时(单位:毫秒) + public string Status; // 操作状态(None/Processing/Succeed/Failed) + public List Childs; // 子操作列表(支持嵌套树结构) + + public int CompareTo(DebugOperationInfo other); +} +``` + +**递归树结构示例:** +``` +InitializationOperation +├─ LoadManifestOperation +│ ├─ LoadBundleFileOperation (manifest.bundle) +│ └─ DeserializeManifestOperation +└─ InitFileSystemOperation +``` + +**关键诊断价值:** +- `OperationName`:操作类型识别 +- `ProcessTime`:性能瓶颈分析 +- `Childs`:操作依赖关系可视化 + +--- + +## 通信系统 + +### RemoteDebuggerInRuntime + +运行时调试器主类,负责接收命令、采样数据、发送报告。 + +```csharp +internal class RemoteDebuggerInRuntime : MonoBehaviour +{ + // 采样控制标志 + private static bool _sampleOnce = false; // 单次采样 + private static bool _autoSample = false; // 连续采样 + + // 运行时初始化 + [RuntimeInitializeOnLoadMethod] + private static void RuntimeInitializeOnLoad() + { + _sampleOnce = false; + _autoSample = false; + } + + private void Awake() + { + RemotePlayerConnection.Instance.Initialize(); + } + + private void OnEnable() + { + // 注册命令接收回调 + RemotePlayerConnection.Instance.Register( + RemoteDebuggerDefine.kMsgEditorSendToPlayer, + OnHandleEditorMessage); + } + + private void LateUpdate() + { + // 采样逻辑(在一帧的最后执行) + if (_autoSample || _sampleOnce) + { + _sampleOnce = false; + var debugReport = YooAssets.GetDebugReport(); + var data = DebugReport.Serialize(debugReport); + + RemotePlayerConnection.Instance.Send( + RemoteDebuggerDefine.kMsgPlayerSendToEditor, + data); + } + } + + private static void OnHandleEditorMessage(MessageEventArgs args) + { + var command = RemoteCommand.Deserialize(args.data); + + if (command.CommandType == (int)ERemoteCommand.SampleOnce) + { + _sampleOnce = true; + } + else if (command.CommandType == (int)ERemoteCommand.SampleAuto) + { + _autoSample = (command.CommandParam == "open"); + } + } +} +``` + +**关键设计点:** +1. **LateUpdate 时机**:确保该帧所有资源加载完成后再采样 +2. **状态重置**:`[RuntimeInitializeOnLoadMethod]` 确保编辑器重新编译时重置状态 +3. **DEBUG 模式自动启用**:通过 `#if DEBUG` 条件编译自动添加组件 + +### 双连接层架构 + +YooAsset 支持两种通信模式: + +| 模式 | 使用场景 | 实现方式 | +|------|----------|----------| +| **编辑器模拟模式** | 开发调试 | `RemotePlayerConnection` + `RemoteEditorConnection`(虚拟连接) | +| **发布版本** | 运营期监控 | Unity 的 `PlayerConnection` API(真实网络) | + +#### RemotePlayerConnection(编辑器模拟模式) + +```csharp +internal class RemotePlayerConnection +{ + private static RemotePlayerConnection _instance; + private readonly Dictionary> _messageCallbacks; + + public static RemotePlayerConnection Instance + { + get + { + if (_instance == null) + _instance = new RemotePlayerConnection(); + return _instance; + } + } + + public void Register(Guid messageID, UnityAction callback) + { + _messageCallbacks.Add(messageID, callback); + } + + public void Send(Guid messageID, byte[] data) + { + // 在编辑器模拟模式下,发送给虚拟编辑器连接 + RemoteEditorConnection.Instance.HandlePlayerMessage(messageID, data); + } + + internal void HandleEditorMessage(Guid messageID, byte[] data) + { + if (_messageCallbacks.TryGetValue(messageID, out var callback)) + { + callback.Invoke(new MessageEventArgs { playerId = 0, data = data }); + } + } +} +``` + +#### RemoteEditorConnection(运行时模拟模式) + +```csharp +internal class RemoteEditorConnection +{ + private static RemoteEditorConnection _instance; + private readonly Dictionary> _messageCallbacks; + + public static RemoteEditorConnection Instance + { + get + { + if (_instance == null) + _instance = new RemoteEditorConnection(); + return _instance; + } + } + + public void Register(Guid messageID, UnityAction callback) + { + _messageCallbacks.Add(messageID, callback); + } + + public void Send(Guid messageID, byte[] data) + { + // 发送给虚拟运行时连接 + RemotePlayerConnection.Instance.HandleEditorMessage(messageID, data); + } + + internal void HandlePlayerMessage(Guid messageID, byte[] data) + { + if (_messageCallbacks.TryGetValue(messageID, out var callback)) + { + callback.Invoke(new MessageEventArgs { playerId = 0, data = data }); + } + } +} +``` + +--- + +## 通信协议 + +### 协议规范 + +**协议版本:** 2.3.3 + +**编码格式:** +``` +C# 对象 → JsonUtility.ToJson() → JSON 字符串 → Encoding.UTF8.GetBytes() → byte[] +``` + +**消息类型:** + +| 方向 | GUID | 数据类型 | 说明 | +|------|------|----------|------| +| 编辑器→运行时 | `4d1926c9df5b052469a1c63448b7609a` | `RemoteCommand` | 采样命令 | +| 运行时→编辑器 | `e34a5702dd353724aa315fb8011f08c3` | `DebugReport` | 调试报告 | + +### 双向通信流程 + +``` +[编辑器 UI] + │ + ├─ 用户点击 "Sample" 按钮 + │ └─ 发送 RemoteCommand (SampleOnce) + │ + └─ 用户开启 "Record" 开关 + └─ 发送 RemoteCommand (SampleAuto, "open") + + ↓ RemoteEditorConnection.Send() + ↓ + RemotePlayerConnection.HandleEditorMessage() + ↓ 触发回调 + +[运行时] + RemoteDebuggerInRuntime.OnHandleEditorMessage() + ↓ + 设置采样标志 (_sampleOnce 或 _autoSample) + ↓ + LateUpdate 中采样 + ↓ + YooAssets.GetDebugReport() + ├─ 收集所有 ResourcePackage 数据 + ├─ DebugProviderInfo[] (从 ProviderDic) + ├─ DebugBundleInfo[] (从 LoaderDic) + └─ DebugOperationInfo[] (从 _operations) + ↓ + DebugReport.Serialize() + ↓ + RemotePlayerConnection.Send() + ↓ + RemoteEditorConnection.HandlePlayerMessage() + ↓ + +[编辑器] + AssetBundleDebuggerWindow.OnHandlePlayerMessage() + ↓ + 版本校验 (DebuggerVersion) + ↓ + RemotePlayerSession.AddDebugReport() + ↓ + 缓存到历史记录 (最多 500 帧) + ↓ + UI 刷新显示 +``` + +### 版本校验机制 + +```csharp +// 编辑器端版本校验 +private void OnHandlePlayerMessage(MessageEventArgs args) +{ + var debugReport = DebugReport.Deserialize(args.data); + + // 版本校验 + if (debugReport.DebuggerVersion != RemoteDebuggerDefine.DebuggerVersion) + { + Debug.LogWarning( + $"Debugger versions are inconsistent : " + + $"{debugReport.DebuggerVersion} != {RemoteDebuggerDefine.DebuggerVersion}"); + return; // 丢弃不兼容的数据 + } + + // 处理数据... +} +``` + +**设计意图:** 防止编辑器和运行时的调试器版本不一致导致的数据格式错误。 + +--- + +## 数据收集流程 + +### 完整采样流程 + +``` +RemoteDebuggerInRuntime.LateUpdate() + ↓ +检查 _sampleOnce 或 _autoSample 标志 + ↓ YES +调用 YooAssets.GetDebugReport() + │ + ├─ 初始化 DebugReport + ├─ 设置 FrameCount = Time.frameCount + ├─ 遍历每个 ResourcePackage: + │ │ + │ └─ package.GetDebugPackageData() + │ │ + │ ├─ 创建 DebugPackageData + │ ├─ 设置 PackageName + │ │ + │ ├─ 收集 ProviderInfos: + │ │ ResourceManager.GetDebugProviderInfos() + │ │ 遍历 ProviderDic: + │ │ └─ 每个 ProviderOperation 提供: + │ │ - MainAssetInfo.AssetPath + │ │ - SpawnScene(场景名) + │ │ - BeginTime(开始时间) + │ │ - ProcessTime(耗时) + │ │ - RefCount(引用计数) + │ │ - Status(加载状态) + │ │ - GetDebugDependBundles()(依赖列表) + │ │ + │ ├─ 收集 BundleInfos: + │ │ ResourceManager.GetDebugBundleInfos() + │ │ 遍历 LoaderDic: + │ │ └─ 每个 LoadBundleFileOperation 提供: + │ │ - BundleName + │ │ - RefCount + │ │ - Status + │ │ - FilterReferenceBundles()(反向依赖) + │ │ + │ └─ 收集 OperationInfos: + │ OperationSystem.GetDebugOperationInfos(PackageName) + │ 遍历 _operations(按 PackageName 过滤): + │ └─ 递归构建操作树: + │ - GetType().Name(操作类名) + │ - GetOperationDesc()(自定义描述) + │ - Priority(优先级) + │ - Progress(进度) + │ - BeginTime(开始时间) + │ - ProcessTime(耗时) + │ - Status(状态) + │ - Childs(子操作列表) + │ + └─ 返回 DebugReport +``` + +### 性能指标收集 + +#### 资源加载耗时 + +```csharp +// AsyncOperationBase 中的自动计时 +private Stopwatch _watch = null; + +internal void InternalStart() +{ + if (_watch == null) + { + BeginTime = SpawnTimeToString(UnityEngine.Time.realtimeSinceStartup); + _watch = Stopwatch.StartNew(); + } +} + +internal void InternalUpdate() +{ + ProcessTime = _watch.ElapsedMilliseconds; + // ... 持续计时 +} +``` + +#### 场景信息追踪 + +```csharp +// ProviderOperation.cs +[Conditional("DEBUG")] // 仅在 DEBUG 模式下启用 +public void InitProviderDebugInfo() +{ + SpawnScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name; +} +``` + +#### 引用计数监控 + +```csharp +// ProviderOperation 和 LoadBundleFileOperation 都维护 RefCount +public int RefCount { get; } // 当前被引用的次数 +``` + +--- + +## 与其他模块的交互 + +``` +YooAssets (全局入口) + │ + ├─ 初始化阶段: + │ #if DEBUG + │ _driver.AddComponent(); + │ #endif + │ + └─ 数据收集入口: + GetDebugReport() + ├─ 遍历所有 ResourcePackage + └─ 构建 DebugReport + +ResourcePackage (资源包) + │ + └─ GetDebugPackageData() + ├─ 调用 ResourceManager.GetDebugProviderInfos() + ├─ 调用 ResourceManager.GetDebugBundleInfos() + └─ 调用 OperationSystem.GetDebugOperationInfos() + +ResourceManager (资源管理器) + │ + ├─ GetDebugProviderInfos() + │ └─ 遍历 ProviderDic (Dictionary) + │ + └─ GetDebugBundleInfos() + └─ 遍历 LoaderDic (Dictionary) + +OperationSystem (操作系统) + │ + └─ GetDebugOperationInfos(packageName) + └─ 遍历 _operations (List) + └─ 递归收集子操作 (Childs) + +AsyncOperationBase (异步操作基类) + │ + ├─ BeginTime:操作开始时间 + ├─ ProcessTime:累计处理耗时 + ├─ Status:操作状态 + ├─ Progress:进度 + └─ GetOperationDesc():自定义描述 + +ProviderOperation (资源提供者) + │ + ├─ SpawnScene:加载时的活跃场景 + └─ GetDebugDependBundles():依赖包列表 + +LoadBundleFileOperation (Bundle 加载器) + │ + ├─ RefCount:引用计数 + └─ LoadBundleInfo:Bundle 信息 +``` + +--- + +## 使用场景 + +### 场景 1:运行时资源泄漏诊断 + +**问题:** 游戏切换场景后内存持续增长,怀疑有资源未释放。 + +**诊断步骤:** +1. 打开 AssetBundle Debugger 窗口 +2. 开启 Record 模式(自动采样) +3. 切换场景前后观察 ProviderInfos 列表 +4. 检查 `RefCount > 0` 且 `SpawnScene` 为旧场景的资源 +5. 定位未释放的资源和对应的代码位置 + +**关键字段:** +- `SpawnScene`:资源在哪个场景被加载 +- `RefCount`:引用计数,应该为 0 +- `AssetPath`:资源路径,定位具体资源 + +### 场景 2:资源加载性能分析 + +**问题:** 首次加载场景卡顿严重。 + +**诊断步骤:** +1. 单次采样(Sample Once) +2. 切换到 Asset View +3. 按 `LoadingTime` 降序排序 +4. 识别加载耗时最长的资源 +5. 分析 `DependBundles` 了解依赖链 + +**关键字段:** +- `LoadingTime`:加载耗时(毫秒) +- `DependBundles`:依赖的 Bundle 列表 +- `Status`:加载状态 + +### 场景 3:Bundle 引用分析 + +**问题:** 某个 Bundle 无法被卸载。 + +**诊断步骤:** +1. 切换到 Bundle View +2. 搜索目标 Bundle +3. 检查 `RefCount` 和 `ReferenceBundles` +4. 追踪哪些资源正在使用该 Bundle +5. 定位未释放的资源引用 + +**关键字段:** +- `RefCount`:Bundle 引用计数 +- `ReferenceBundles`:反向依赖列表 +- `Status`:Bundle 加载状态 + +### 场景 4:异步操作监控 + +**问题:** 复杂的初始化流程卡住,不知道在哪个步骤。 + +**诊断步骤:** +1. 切换到 Operation View +2. 查看操作树结构 +3. 检查 `Status` 为 `Processing` 的操作 +4. 分析 `Progress` 了解进度 +5. 通过 `Childs` 了解操作依赖关系 + +**关键字段:** +- `OperationName`:操作类型 +- `OperationDesc`:操作描述 +- `Progress`:进度(0.0 - 1.0) +- `Childs`:子操作列表 + +--- + +## 数据导出 + +### JSON 导出功能 + +编辑器窗口支持导出当前帧的完整调试数据为 JSON 文件。 + +**导出示例:** + +```json +{ + "DebuggerVersion": "2.3.3", + "FrameCount": 1234, + "PackageDatas": [ + { + "PackageName": "DefaultPackage", + "ProviderInfos": [ + { + "PackageName": "DefaultPackage", + "AssetPath": "Assets/Prefabs/Player.prefab", + "SpawnScene": "GameScene", + "BeginTime": "12:34:56.789", + "LoadingTime": 45, + "RefCount": 1, + "Status": "Succeed", + "DependBundles": [ + "assets_prefabs.bundle" + ] + } + ], + "BundleInfos": [ + { + "BundleName": "assets_prefabs.bundle", + "RefCount": 1, + "Status": "Succeed", + "ReferenceBundles": [] + } + ], + "OperationInfos": [ + { + "OperationName": "LoadAssetOperation", + "OperationDesc": "Load assets_prefabs.bundle", + "Priority": 0, + "Progress": 1.0, + "BeginTime": "12:34:56.745", + "ProcessTime": 44, + "Status": "Succeed", + "Childs": [] + } + ] + } + ] +} +``` + +**用途:** +- 离线分析和归档 +- 性能数据对比 +- 问题复现和追踪 + +--- + +## 系统架构图 + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ Unity Editor Window │ +│ AssetBundleDebuggerWindow │ +│ ┌──────────────────────────────────────────────────────────────┐ │ +│ │ UI Controls: │ │ +│ │ - Sample Button (SampleOnce) │ │ +│ │ - Record Toggle (SampleAuto) │ │ +│ │ - View Mode Menu (Asset/Bundle/Operation View) │ │ +│ │ - Frame Slider (历史帧导航) │ │ +│ │ - Search Field (关键词搜索) │ │ +│ │ - Export Button (JSON 导出) │ │ +│ └──────────────────────────────────────────────────────────────┘ │ +└────────────┬──────────────────────────────────────────────────────────┘ + │ + ┌─────▼───────────────────────────────────┐ + │ RemoteEditorConnection (虚拟连接) │ + │ - Register callbacks │ + │ - Send/Receive commands & reports │ + └─────┬──────────────────────────────┬────┘ + │ │ + ┌────────▼─────────────┐ ┌──────────▼───────────────┐ + │ RemoteCommand │ │ DebugReport │ + │ (Serialize) │ │ (Deserialize) │ + │ ↓ JSON │ │ ← JSON │ + │ ↓ UTF-8 bytes │ │ ← UTF-8 bytes │ + └────────┬─────────────┘ └──────────┬───────────────┘ + │ │ + │ ═══════════════════════ │ + │ Internet / Emulation │ + │ ═══════════════════════ │ + │ │ + ┌────────▼─────────────┐ ┌──────────▼───────────────┐ + │ PlayerConnection │ │ RemotePlayerConnection │ + │ (真实连接) │ │ (虚拟连接) │ + │ 或模拟连接 │ │ │ + └────────┬─────────────┘ └──────────┬───────────────┘ + │ │ + └──────────────┬───────────────┘ + │ + ┌──────▼──────────────────────────────┐ + │ RemoteDebuggerInRuntime │ + │ (MonoBehaviour) │ + │ ┌──────────────────────────────┐ │ + │ │ _sampleOnce (bool) │ │ + │ │ _autoSample (bool) │ │ + │ └──────────────────────────────┘ │ + │ ┌──────────────────────────────┐ │ + │ │ Awake() - 初始化连接 │ │ + │ │ OnEnable() - 注册回调 │ │ + │ │ LateUpdate() - 采样触发 │ │ + │ │ OnHandleEditorMessage() - 收命令│ │ + │ └──────────────────────────────┘ │ + └──────────┬──────────────────────────┘ + │ + ┌──────▼────────────────────┐ + │ YooAssets.GetDebugReport() │ + │ (全系统数据收集入口) │ + └──────┬────────────────────┘ + │ + ┌───────────┼───────────┐ + │ │ │ + ┌──────────▼──┐ ┌─────▼────┐ ┌──▼─────────────┐ + │ResourcePkg 1│ │ResourcePkg2 │ResourcePackageN│ + └──────┬──────┘ └────┬─────┘ └──┬──────────┘ + │ │ │ + └───────────────┼──────────┘ + │ + ┌───────────▼──────────┐ + │ DebugPackageData │ + │ ┌─────────────────┐ │ + │ │ PackageName │ │ + │ │ ProviderInfos[] │ │ + │ │ BundleInfos[] │ │ + │ │ OperationInfos[]│ │ + │ └─────────────────┘ │ + └─────────────────────┘ +``` + +--- + +## 注意事项 + +1. **DEBUG 模式自动启用** + - 诊断系统仅在 `DEBUG` 模式下启用(通过 `#if DEBUG` 条件编译) + - Release 构建中不会包含诊断代码,无性能开销 + +2. **版本兼容性** + - 编辑器和运行时的调试器版本必须一致 + - 版本不一致的数据会被自动丢弃 + +3. **历史数据限制** + - 最多缓存 500 帧历史数据(可配置) + - 超过限制后,最早的数据会被移除 + +4. **JSON 序列化深度限制** + - Unity JsonUtility 序列化深度限制为 10 层 + - 操作树(Childs)嵌套过深可能导致序列化失败 + +5. **性能开销** + - 单次采样:低开销,仅在需要时采集 + - 自动采样:每帧采集,有一定性能开销,建议仅在需要时开启 + +6. **LateUpdate 时机** + - 采样在 LateUpdate 中执行,确保该帧所有操作已更新 + - 避免在采样过程中资源状态发生变化 + +7. **非序列化字典** + - `DebugPackageData.BundleInfoDic` 使用 `[NonSerialized]` 标记 + - 字典在首次查询时才构建,减少序列化开销 + +8. **场景追踪条件编译** + - `SpawnScene` 字段仅在 DEBUG 模式下赋值(`[Conditional("DEBUG")]`) + - Release 构建中该字段为空字符串 + +--- + +## 性能优化建议 + +1. **按需采样** + - 优先使用单次采样(Sample Once) + - 仅在需要连续监控时开启自动采样(Record) + +2. **及时关闭 Record** + - 分析完成后及时关闭自动采样 + - 避免不必要的性能开销 + +3. **合理设置历史缓存** + - 根据内存情况调整 `MaxReportCount` + - 默认 500 帧已足够大多数分析场景 + +4. **导出数据离线分析** + - 对于复杂的性能问题,导出 JSON 数据 + - 在编辑器外使用专业工具进行分析 + +5. **Release 构建移除诊断代码** + - 确保 Release 构建使用 Release 配置 + - 诊断代码通过 `#if DEBUG` 自动移除 diff --git a/Assets/YooAsset/Runtime/DiagnosticSystem/README.md.meta b/Assets/YooAsset/Runtime/DiagnosticSystem/README.md.meta new file mode 100644 index 00000000..bef758ec --- /dev/null +++ b/Assets/YooAsset/Runtime/DiagnosticSystem/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5fa2b66c20800124c8dd5cb77e854ce3 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Runtime/DownloadSystem/README.md b/Assets/YooAsset/Runtime/DownloadSystem/README.md new file mode 100644 index 00000000..28551ceb --- /dev/null +++ b/Assets/YooAsset/Runtime/DownloadSystem/README.md @@ -0,0 +1,548 @@ +# DownloadSystem 下载模块 + +## 模块概述 + +DownloadSystem 是 YooAsset 资源管理系统的**底层网络下载层**,负责处理所有 HTTP 网络请求。该模块提供了统一的下载接口抽象,支持文件下载、断点续传、并发控制、看门狗监控等功能。 + +### 核心职责 + +- HTTP/HTTPS 文件下载 +- 断点续传支持 +- 看门狗超时保护 +- 多种下载类型(文件/字节/文本/AssetBundle) +- 可插拔的网络库后端 + +--- + +## 设计目标 + +| 目标 | 说明 | +|------|------| +| **可扩展性** | 支持可插拔的网络库后端(UnityWebRequest/BestHTTP/自研) | +| **鲁棒性** | 看门狗超时保护、自动清理失败文件、完整错误信息 | +| **高性能** | 轮询模式无阻塞、支持多并发请求 | +| **易用性** | 流畅的参数构建 API、清晰的状态转换 | + +--- + +## 架构概念 + +### 分层架构 + +``` +┌─────────────────────────────────────────────────────────┐ +│ 上层调用者 │ +│ (FileSystem / ResourceManager) │ +└─────────────────────────┬───────────────────────────────┘ + │ +┌─────────────────────────▼───────────────────────────────┐ +│ IDownloadBackend │ +│ (后端接口) │ +│ 定义网络库合约,工厂模式创建请求 │ +└─────────────────────────┬───────────────────────────────┘ + │ +┌─────────────────────────▼───────────────────────────────┐ +│ IDownloadRequest │ +│ (请求接口) │ +│ 轮询式生命周期管理,状态机驱动 │ +└─────────────────────────┬───────────────────────────────┘ + │ +┌─────────────────────────▼───────────────────────────────┐ +│ UnityWebRequest / 其他网络库 │ +│ (底层实现) │ +└─────────────────────────────────────────────────────────┘ +``` + +### 核心组件 + +- **后端层 (IDownloadBackend)**: 定义网络库实现合约,通过工厂方法创建各类请求 +- **请求层 (IDownloadRequest)**: 统一的请求生命周期管理,支持轮询驱动 +- **参数层 (Args 结构体)**: 配置下载行为(超时、断点续传、看门狗等) + +--- + +## 文件结构 + +``` +DownloadSystem/ +├── Interface/ # 接口定义 +│ ├── IDownloadBackend.cs # 后端接口(工厂模式) +│ └── IDownloadRequest.cs # 请求接口层次结构 +│ +├── DefaultDownloadBackend/ # 默认后端实现 +│ └── UnityWebRequestBackend.cs # UnityWebRequest 后端 +│ +├── DefaultDownloadRequest/ # 默认请求实现 +│ ├── UnityWebRequestDownloaderBase.cs # 基础下载器(抽象类) +│ ├── UnityWebRequestFileDownloader.cs # 文件下载器 +│ ├── UnityWebRequestHeadDownloader.cs # HEAD 请求器 +│ ├── UnityWebRequestBytesDownloader.cs # 字节下载器 +│ ├── UnityWebRequestTextDownloader.cs # 文本下载器 +│ ├── UnityWebRequestAssetBundleDownloader.cs # AssetBundle 下载器 +│ └── VirtualFileDownloader.cs # 模拟下载器(编辑器用) +│ +├── DownloadSystemDefine.cs # 枚举、结构体定义 +├── DownloadSystemHelper.cs # 工具函数 +└── WebRequestCounter.cs # 请求失败计数器 +``` + +--- + +## 接口说明 + +### IDownloadBackend(后端接口) + +定义网络库实现的合约,通过工厂方法创建各类下载请求。 + +```csharp +public interface IDownloadBackend +{ + /// + /// 后端标识名称(用于日志/调试) + /// + string Name { get; } + + /// + /// 定期驱动更新(部分第三方库需要) + /// + void Update(); + + // 工厂方法 - 创建各类请求 + IDownloadHeadRequest CreateHeadRequest(DownloadDataRequestArgs args); + IDownloadFileRequest CreateFileRequest(DownloadFileRequestArgs args); + IDownloadBytesRequest CreateBytesRequest(DownloadDataRequestArgs args); + IDownloadTextRequest CreateTextRequest(DownloadDataRequestArgs args); + IDownloadAssetBundleRequest CreateAssetBundleRequest(DownloadAssetBundleRequestArgs args); + IDownloadFileRequest CreateSimulateRequest(DownloadSimulateRequestArgs args); +} +``` + +### IDownloadRequest(基础请求接口) + +所有下载请求的通用接口,定义生命周期和状态管理。 + +```csharp +public interface IDownloadRequest : IDisposable +{ + // 元信息 + string URL { get; } + + // 生命周期 + bool IsDone { get; } // 每次访问自动轮询 + EDownloadRequestStatus Status { get; } + + // 进度跟踪 + float DownloadProgress { get; } // 0f - 1f + long DownloadedBytes { get; } // 本次请求新增字节数 + + // 诊断信息 + long HttpCode { get; } + string Error { get; } + + // 生命周期方法 + void SendRequest(); // 发起请求 + void PollingRequest(); // 轮询状态 + void AbortRequest(); // 中止请求 +} +``` + +### 专化请求接口 + +| 接口 | 用途 | 特有属性 | +|------|------|----------| +| `IDownloadHeadRequest` | HEAD 请求,获取响应头 | `ETag`, `LastModified`, `ContentLength`, `ContentType` | +| `IDownloadFileRequest` | 文件下载到本地 | `SavePath` | +| `IDownloadBytesRequest` | 下载到内存(字节数组) | `byte[] Result` | +| `IDownloadTextRequest` | 下载文本内容 | `string Result` | +| `IDownloadAssetBundleRequest` | 下载并加载 AssetBundle | `AssetBundle Result` | + +--- + +## 结构体定义 + +### 请求状态枚举 + +```csharp +public enum EDownloadRequestStatus +{ + None, // 未开始 + Running, // 进行中 + Succeed, // 已成功 + Failed, // 已失败 + Aborted // 已中止(用户中止或看门狗超时) +} +``` + +### 请求参数结构体 + +#### DownloadFileRequestArgs(文件下载参数) + +```csharp +public struct DownloadFileRequestArgs +{ + public string URL; // 请求地址 + public int Timeout; // 响应超时(秒),0=无限制 + public int WatchdogTime; // 看门狗超时(秒) + + public string SavePath; // 文件保存路径 + public bool AppendToFile; // 追加写入(断点续传) + public bool RemoveFileOnAbort; // 中止时删除文件 + public long ResumeFromBytes; // 断点续传起始位置 + + public Dictionary Headers; // 自定义请求头 +} +``` + +#### DownloadDataRequestArgs(数据下载参数) + +```csharp +public struct DownloadDataRequestArgs +{ + public string URL; // 请求地址 + public int Timeout; // 响应超时(秒) + public int WatchdogTime; // 看门狗超时(秒) + public Dictionary Headers; // 自定义请求头 +} +``` + +#### DownloadAssetBundleRequestArgs(AssetBundle 下载参数) + +```csharp +public struct DownloadAssetBundleRequestArgs +{ + public string URL; // 请求地址 + public int Timeout; // 响应超时 + public int WatchdogTime; // 看门狗超时 + + public bool DisableUnityWebCache; // 禁用 Unity 缓存(推荐 true) + public string FileHash; // 文件哈希(缓存启用时需要) + public uint UnityCRC; // Unity CRC 校验值 + + public Dictionary Headers; +} +``` + +#### DownloadSimulateRequestArgs(模拟下载参数) + +```csharp +public struct DownloadSimulateRequestArgs +{ + public string URL; // 标识符 + public long FileSize; // 模拟文件大小 + public long DownloadSpeed; // 模拟速度(字节/秒),默认 1MB/s +} +``` + +### 回调数据结构体 + +| 结构体 | 用途 | 关键字段 | +|--------|------|----------| +| `DownloaderFinishData` | 下载完成回调 | `PackageName`, `Succeed` | +| `DownloadUpdateData` | 进度更新回调 | `Progress`, `TotalDownloadBytes`, `CurrentDownloadBytes` | +| `DownloadErrorData` | 下载错误回调 | `FileName`, `ErrorInfo` | +| `DownloadFileData` | 文件完成回调 | `FileName`, `FileSize` | +| `ImportFileInfo` | 导入文件元数据 | `FilePath`, `BundleName`, `BundleGUID` | + +--- + +## 核心类说明 + +### UnityWebRequestBackend + +默认的后端实现,基于 Unity 的 UnityWebRequest API。 + +**特性:** +- 支持自定义 UnityWebRequest 创建方式(证书验证、代理等) +- 无需手动调用 Update(),UnityWebRequest 自动驱动 + +```csharp +// 自定义 UnityWebRequest 创建 +DownloadSystemHelper.UnityWebRequestCreater = (url) => +{ + var request = new UnityWebRequest(url, UnityWebRequest.kHttpVerbGET); + // 自定义配置... + return request; +}; +``` + +### UnityWebRequestDownloaderBase + +抽象基类,封装所有下载器的通用逻辑。 + +**职责:** +- 管理请求生命周期和状态转换 +- 实现看门狗监控机制 +- 追踪下载进度和字节数 +- 处理超时和错误 + +**生命周期:** + +``` +None ──► SendRequest() ──► Running ──► PollingRequest() ──┬──► Succeed + ├──► Failed + └──► Aborted +``` + +### 具体下载器 + +| 下载器 | 实现接口 | 使用场景 | +|--------|----------|----------| +| `UnityWebRequestFileDownloader` | `IDownloadFileRequest` | 大文件下载到本地 | +| `UnityWebRequestHeadDownloader` | `IDownloadHeadRequest` | 检查资源信息 | +| `UnityWebRequestBytesDownloader` | `IDownloadBytesRequest` | 小文件内存加载 | +| `UnityWebRequestTextDownloader` | `IDownloadTextRequest` | 文本文件下载 | +| `UnityWebRequestAssetBundleDownloader` | `IDownloadAssetBundleRequest` | AB 包下载加载 | +| `VirtualFileDownloader` | `IDownloadFileRequest` | 编辑器模拟下载 | + +--- + +## 使用示例 + +### 基础文件下载 + +```csharp +// 1. 创建后端和请求 +IDownloadBackend backend = new UnityWebRequestBackend(); +var args = new DownloadFileRequestArgs +{ + URL = "https://example.com/file.zip", + SavePath = "/path/to/save/file.zip", + Timeout = 30 +}; +IDownloadFileRequest request = backend.CreateFileRequest(args); + +// 2. 发起并轮询 +request.SendRequest(); +while (!request.IsDone) +{ + await Task.Yield(); + // 可选:显示进度 + float progress = request.DownloadProgress; +} + +// 3. 检查结果 +if (request.Status == EDownloadRequestStatus.Succeed) +{ + Debug.Log("下载成功"); +} +else +{ + Debug.LogError($"下载失败: {request.Error}"); +} + +// 4. 清理资源 +request.Dispose(); +``` + +### 断点续传 + +```csharp +// 获取已下载的文件大小 +long existingFileSize = new FileInfo(savePath).Length; + +var args = new DownloadFileRequestArgs +{ + URL = url, + SavePath = savePath, + ResumeFromBytes = existingFileSize, // 断点位置 + AppendToFile = true, // 追加写入 + RemoveFileOnAbort = false // 中止时保留文件 +}; + +IDownloadFileRequest request = backend.CreateFileRequest(args); +request.SendRequest(); +// ... 轮询等待完成 +``` + +### 看门狗保护 + +```csharp +var args = new DownloadFileRequestArgs +{ + URL = url, + SavePath = path, + WatchdogTime = 30 // 30秒无数据自动中止 +}; + +IDownloadFileRequest request = backend.CreateFileRequest(args); +request.SendRequest(); + +while (!request.IsDone) +{ + await Task.Yield(); +} + +// 检查是否因看门狗超时而中止 +if (request.Status == EDownloadRequestStatus.Aborted) +{ + Debug.LogWarning("下载超时,已自动中止"); +} +``` + +### HEAD 请求获取文件信息 + +```csharp +var args = new DownloadDataRequestArgs +{ + URL = "https://example.com/file.zip" +}; + +IDownloadHeadRequest request = backend.CreateHeadRequest(args); +request.SendRequest(); + +while (!request.IsDone) +{ + await Task.Yield(); +} + +if (request.Status == EDownloadRequestStatus.Succeed) +{ + long fileSize = request.ContentLength; + string etag = request.ETag; + string lastModified = request.LastModified; + + Debug.Log($"文件大小: {fileSize}, ETag: {etag}"); +} +``` + +### 下载字节数据 + +```csharp +var args = new DownloadDataRequestArgs +{ + URL = "https://example.com/data.json" +}; + +IDownloadBytesRequest request = backend.CreateBytesRequest(args); +request.SendRequest(); + +while (!request.IsDone) +{ + await Task.Yield(); +} + +if (request.Status == EDownloadRequestStatus.Succeed) +{ + byte[] data = request.Result; + // 处理数据... +} +``` + +--- + +## 设计模式 + +### 工厂模式 + +`IDownloadBackend` 作为工厂接口,创建各类下载请求对象: + +``` +IDownloadBackend + ├── CreateHeadRequest() ──► IDownloadHeadRequest + ├── CreateFileRequest() ──► IDownloadFileRequest + ├── CreateBytesRequest() ──► IDownloadBytesRequest + ├── CreateTextRequest() ──► IDownloadTextRequest + ├── CreateAssetBundleRequest() ──► IDownloadAssetBundleRequest + └── CreateSimulateRequest() ──► IDownloadFileRequest +``` + +### 策略模式 + +通过实现 `IDownloadBackend` 接口,可以替换底层网络库: + +``` +IDownloadBackend (接口) + ├── UnityWebRequestBackend (默认实现) + ├── BestHTTPBackend (可扩展) + └── CustomBackend (自定义) +``` + +### 状态机模式 + +请求生命周期通过状态机管理: + +``` +┌──────┐ SendRequest() ┌─────────┐ +│ None │ ──────────────────► │ Running │ +└──────┘ └────┬────┘ + │ PollingRequest() + ┌─────────────┼─────────────┐ + ▼ ▼ ▼ + ┌─────────┐ ┌──────────┐ ┌─────────┐ + │ Succeed │ │ Failed │ │ Aborted │ + └─────────┘ └──────────┘ └─────────┘ +``` + +### 看门狗模式 + +监控数据接收,防止网络卡顿导致请求无限等待: + +``` +每帧轮询 PollingRequest() + │ + ├── 收到新数据 ──► 重置计时器 + │ + └── 未收到数据 ──► 计时器累加 + │ + └── 超过 WatchdogTime ──► AbortRequest() +``` + +--- + +## 类继承关系 + +``` +IDownloadRequest (基础接口) + │ + ├── IDownloadHeadRequest (HEAD 请求) + ├── IDownloadFileRequest (文件下载) + ├── IDownloadBytesRequest (字节下载) + ├── IDownloadTextRequest (文本下载) + └── IDownloadAssetBundleRequest (AssetBundle 下载) + +UnityWebRequestDownloaderBase (抽象基类) + │ + ├── UnityWebRequestFileDownloader ──► IDownloadFileRequest + ├── UnityWebRequestHeadDownloader ──► IDownloadHeadRequest + ├── UnityWebRequestBytesDownloader ──► IDownloadBytesRequest + ├── UnityWebRequestTextDownloader ──► IDownloadTextRequest + └── UnityWebRequestAssetBundleDownloader ──► IDownloadAssetBundleRequest + +VirtualFileDownloader (独立实现) ──► IDownloadFileRequest +``` + +--- + +## 工具类 + +### DownloadSystemHelper + +提供跨平台的工具函数: + +| 方法 | 说明 | +|------|------| +| `NewUnityWebRequestGet()` | 创建 GET 请求(支持自定义工厂) | +| `ConvertToWWWPath()` | 转换本地路径为 WWW 协议 URL | +| `IsRequestLocalFile()` | 判断是否本地文件请求 | + +### WebRequestCounter + +请求失败计数器,用于诊断统计: + +```csharp +// 记录失败 +WebRequestCounter.RecordRequestFailed(packageName, eventName); + +// 查询失败次数 +int count = WebRequestCounter.GetRequestFailedCount(packageName, eventName); +``` + +--- + +## 注意事项 + +1. **资源释放**:使用完毕后务必调用 `Dispose()` 释放资源 +2. **断点续传**:需要服务器支持 `Range` 请求头和 `206 Partial Content` 响应 +3. **看门狗超时**:设置为 0 表示禁用,建议根据网络环境设置合理值 +4. **内存下载**:`IDownloadBytesRequest` 会将整个响应体加载到内存,不适合大文件 +5. **线程安全**:所有下载请求的创建和轮询应在主线程进行 diff --git a/Assets/YooAsset/Runtime/DownloadSystem/README.md.meta b/Assets/YooAsset/Runtime/DownloadSystem/README.md.meta new file mode 100644 index 00000000..9223b345 --- /dev/null +++ b/Assets/YooAsset/Runtime/DownloadSystem/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2c65d3ba8da65004b8d36dbeecc6c6be +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Runtime/FileSystem/DefaultBuildinFileSystem/README.md b/Assets/YooAsset/Runtime/FileSystem/DefaultBuildinFileSystem/README.md new file mode 100644 index 00000000..e5b80558 --- /dev/null +++ b/Assets/YooAsset/Runtime/FileSystem/DefaultBuildinFileSystem/README.md @@ -0,0 +1,488 @@ +# DefaultBuildinFileSystem 内置文件系统 + +## 模块概述 + +DefaultBuildinFileSystem 是 YooAsset 的**内置资源文件系统**,用于管理打包到应用程序中的资源文件(StreamingAssets)。该文件系统支持 AssetBundle 和原生文件的加载,并内置解压文件系统以处理 Android/OpenHarmony 平台的特殊需求。 + +### 核心特性 + +- **内置资源管理**:管理 StreamingAssets 目录下的资源文件 +- **Catalog 目录系统**:使用目录文件快速查询内置资源 +- **自动解压机制**:Android/OpenHarmony 平台自动解压加密和原生文件 +- **清单拷贝功能**:支持将内置清单拷贝到沙盒目录 +- **加密资源支持**:通过解密服务接口支持加密资源加载 + +--- + +## 设计目标 + +| 目标 | 说明 | +|------|------| +| **跨平台支持** | 统一处理各平台 StreamingAssets 的访问差异 | +| **高效查询** | 通过 Catalog 文件快速判断资源是否内置 | +| **自动解压** | 自动处理 Android 平台无法直接访问的资源 | +| **灵活配置** | 支持多种参数配置适应不同需求 | + +--- + +## 文件结构 + +``` +DefaultBuildinFileSystem/ +├── DefaultBuildinFileSystem.cs # 文件系统主类 +├── DefaultBuildinFileSystemDefine.cs # 常量定义 +├── DefaultBuildinFileCatalog.cs # 内置资源目录结构 +├── CatalogDefine.cs # Catalog 文件格式定义 +├── CatalogTools.cs # Catalog 序列化工具 +└── Operation/ # 操作类 + ├── DBFSInitializeOperation.cs # 初始化操作 + ├── DBFSRequestPackageVersionOperation.cs # 请求版本操作 + ├── DBFSLoadPackageManifestOperation.cs # 加载清单操作 + ├── DBFSLoadBundleOperation.cs # 加载资源包操作 + └── internal/ # 内部操作类 + ├── CopyBuildinFileOperation.cs # 拷贝内置文件操作 + ├── LoadBuildinCatalogFileOperation.cs # 加载 Catalog 文件操作 + ├── LoadBuildinPackageManifestOperation.cs# 加载清单文件操作 + ├── RequestBuildinPackageHashOperation.cs # 请求哈希文件操作 + └── RequestBuildinPackageVersionOperation.cs # 请求版本文件操作 +``` + +--- + +## 核心类说明 + +### DefaultBuildinFileSystem + +内置文件系统的主类,实现 `IFileSystem` 接口。 + +#### 基本属性 + +| 属性 | 类型 | 说明 | +|------|------|------| +| `PackageName` | `string` | 包裹名称 | +| `FileRoot` | `string` | 文件根目录(StreamingAssets 下的包裹目录) | +| `FileCount` | `int` | 已记录的内置文件数量 | +| `DownloadBackend` | `IDownloadBackend` | 下载后台接口 | + +#### 自定义参数 + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `InstallClearMode` | `EOverwriteInstallClearMode` | `ClearAllManifestFiles` | 覆盖安装时的缓存清理模式 | +| `FileVerifyLevel` | `EFileVerifyLevel` | `Middle` | 文件校验级别 | +| `FileVerifyMaxConcurrency` | `int` | `32` | 文件校验最大并发数 | +| `AppendFileExtension` | `bool` | `false` | 是否追加文件扩展名 | +| `DisableCatalogFile` | `bool` | `false` | 禁用 Catalog 目录文件 | +| `CopyBuildinPackageManifest` | `bool` | `false` | 是否拷贝内置清单到沙盒 | +| `CopyBuildinPackageManifestDestRoot` | `string` | `null` | 清单拷贝目标目录 | +| `UnpackFileSystemRoot` | `string` | `null` | 解压文件系统根目录 | +| `DecryptionServices` | `IDecryptionServices` | `null` | 解密服务接口 | +| `ManifestServices` | `IManifestRestoreServices` | `null` | 清单恢复服务接口 | +| `CopyLocalFileServices` | `ICopyLocalFileServices` | `null` | 本地文件拷贝服务接口 | + +#### 核心方法 + +```csharp +// 生命周期 +void OnCreate(string packageName, string packageRoot); +void OnDestroy(); +void SetParameter(string name, object value); + +// 异步操作 +FSInitializeFileSystemOperation InitializeFileSystemAsync(); +FSRequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks, int timeout); +FSLoadPackageManifestOperation LoadPackageManifestAsync(string packageVersion, int timeout); +FSLoadBundleOperation LoadBundleFile(PackageBundle bundle); +FSDownloadFileOperation DownloadFileAsync(PackageBundle bundle, DownloadFileOptions options); +FSClearCacheFilesOperation ClearCacheFilesAsync(PackageManifest manifest, ClearCacheFilesOptions options); + +// 文件查询 +bool Belong(PackageBundle bundle); // 检查是否属于内置文件 +bool Exists(PackageBundle bundle); // 检查文件是否存在 +bool NeedDownload(PackageBundle bundle);// 始终返回 false +bool NeedUnpack(PackageBundle bundle); // 检查是否需要解压 +bool NeedImport(PackageBundle bundle); // 始终返回 false + +// 文件访问 +string GetBundleFilePath(PackageBundle bundle); +byte[] ReadBundleFileData(PackageBundle bundle); +string ReadBundleFileText(PackageBundle bundle); +``` + +--- + +## Catalog 目录系统 + +### DefaultBuildinFileCatalog + +内置资源目录结构,记录所有内置资源文件的信息。 + +```csharp +[Serializable] +internal class DefaultBuildinFileCatalog +{ + [Serializable] + public class FileWrapper + { + public string BundleGUID; // 资源包 GUID + public string FileName; // 文件名 + } + + public string FileVersion; // 文件版本 + public string PackageName; // 包裹名称 + public string PackageVersion; // 包裹版本 + public List Wrappers; // 文件列表 +} +``` + +### CatalogDefine + +Catalog 文件格式常量定义。 + +```csharp +internal class CatalogDefine +{ + public const int FileMaxSize = 104857600; // 文件极限大小(100MB) + public const uint FileSign = 0x133C5EE; // 文件头标记 + public const string FileVersion = "1.0.0"; // 文件格式版本 +} +``` + +### CatalogTools + +Catalog 文件的序列化和反序列化工具。 + +| 方法 | 说明 | +|------|------| +| `CreateCatalogFile()` | 生成包裹的内置资源目录文件(编辑器) | +| `CreateEmptyCatalogFile()` | 生成空的内置资源目录文件(编辑器) | +| `SerializeToJson()` | 序列化为 JSON 文件 | +| `DeserializeFromJson()` | 从 JSON 文件反序列化 | +| `SerializeToBinary()` | 序列化为二进制文件 | +| `DeserializeFromBinary()` | 从二进制文件反序列化 | + +--- + +## 操作类说明 + +### DBFSInitializeOperation + +初始化操作,执行以下步骤: + +``` +状态流程: +┌─────────────────────────────────────────────────────────────┐ +│ CopyBuildinPackageManifest = true ? │ +│ │ │ +│ ├── Yes ──► LoadBuildinPackageVersion │ +│ │ └── RequestBuildinPackageVersionOp │ +│ │ ↓ │ +│ │ CopyBuildinPackageHash │ +│ │ └── CopyBuildinFileOperation │ +│ │ ↓ │ +│ │ CopyBuildinPackageManifest │ +│ │ └── CopyBuildinFileOperation │ +│ │ ↓ │ +│ └── No ─────────────────┘ │ +│ ↓ │ +│ InitUnpackFileSystem │ +│ └── DefaultUnpackFileSystem.Init │ +│ ↓ │ +│ DisableCatalogFile = true ? │ +│ ├── Yes ──► Done (Succeed) │ +│ └── No ──► LoadCatalogFile │ +│ └── LoadBuildinCatalog │ +│ ↓ │ +│ RecordCatalogFile │ +│ ↓ │ +│ Done (Succeed) │ +└─────────────────────────────────────────────────────────────┘ +``` + +### DBFSLoadBundleOperation + +加载资源包操作,支持多种资源类型。 + +#### DBFSLoadAssetBundleOperation + +加载 AssetBundle 文件。 + +``` +状态流程: +LoadAssetBundle + ├── 加密资源 ──► DecryptionServices.LoadAssetBundle[Async] + └── 普通资源 ──► AssetBundle.LoadFromFile[Async] + ↓ +CheckResult + ├── 成功 ──► AssetBundleResult + └── 失败 ──► Error +``` + +#### DBFSLoadRawBundleOperation + +加载原生文件。 + +``` +状态流程: +LoadBuildinRawBundle + ├── Android 平台 ──► Error(不支持直接读取) + └── 其他平台 ──► RawBundleResult +``` + +#### DBFSLoadInstantBundleOperation + +加载团结引擎(Tuanjie)专用资源包(需要 `TUANJIE_1_7_OR_NEWER` 宏)。 + +--- + +## 内部操作类 + +### LoadBuildinCatalogFileOperation + +加载 Catalog 目录文件。 + +``` +状态流程: +TryLoadFileData + ├── 文件存在 ──► File.ReadAllBytes + └── 文件不存在 ──► RequestFileData (UnityWebRequest) + ↓ +LoadCatalog + └── CatalogTools.DeserializeFromBinary +``` + +### CopyBuildinFileOperation + +拷贝内置文件到目标路径。 + +``` +状态流程: +CheckFileExist + ├── 目标已存在 ──► Done (Succeed) + └── 目标不存在 ──► TryCopyFile + ↓ +TryCopyFile + ├── 源文件存在 ──► File.Copy + └── 源文件不存在 ──► UnpackFile (UnityWebRequest) +``` + +--- + +## 解压机制 + +### 自动解压条件 + +在 Android/OpenHarmony 平台上,以下情况需要解压到沙盒: + +```csharp +protected virtual bool IsUnpackBundleFile(PackageBundle bundle) +{ +#if UNITY_ANDROID || UNITY_OPENHARMONY + if (bundle.Encrypted) // 加密资源 + return true; + if (bundle.BundleType == RawBundle) // 原生文件 + return true; + return false; +#else + return false; +#endif +} +``` + +### 解压文件系统 + +内置文件系统在创建时会自动创建一个 `DefaultUnpackFileSystem` 实例: + +```csharp +public virtual void OnCreate(string packageName, string packageRoot) +{ + // 创建解压文件系统 + var remoteServices = new DefaultUnpackRemoteServices(_packageRoot); + _unpackFileSystem = new DefaultUnpackFileSystem(); + _unpackFileSystem.SetParameter(REMOTE_SERVICES, remoteServices); + _unpackFileSystem.SetParameter(FILE_VERIFY_LEVEL, FileVerifyLevel); + // ... 其他参数 + _unpackFileSystem.OnCreate(packageName, UnpackFileSystemRoot); +} +``` + +--- + +## 平台差异处理 + +### Android 平台限制 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Android 平台特殊处理 │ +├─────────────────────────────────────────────────────────────┤ +│ StreamingAssets 文件位于 APK 压缩包内,无法直接访问: │ +│ │ +│ ✓ AssetBundle.LoadFromFile 支持(Unity 内部处理) │ +│ ✗ File.ReadAllBytes 不支持 │ +│ ✗ File.Exists 不支持 │ +│ ✓ UnityWebRequest 支持(jar:file:// 协议) │ +│ │ +│ 解决方案: │ +│ 1. 加密资源 → 自动解压到沙盒 │ +│ 2. 原生文件 → 自动解压到沙盒 │ +│ 3. Catalog → 使用 UnityWebRequest 读取 │ +└─────────────────────────────────────────────────────────────┘ +``` + +### WebGL 平台 + +```csharp +#if UNITY_WEBGL + _steps = ESteps.Done; + Status = EOperationStatus.Failed; + Error = $"{nameof(DefaultBuildinFileSystem)} is not support WEBGL platform !"; +#endif +``` + +WebGL 平台不支持 DefaultBuildinFileSystem,应使用 `DefaultWebServerFileSystem`。 + +--- + +## 使用示例 + +### 基础配置 + +```csharp +// 创建内置文件系统参数 +var buildinParams = FileSystemParameters.CreateDefaultBuildinFileSystemParameters(); + +// 初始化包裹 +var initParams = new OfflinePlayModeParameters(); +initParams.BuildinFileSystemParameters = buildinParams; +var initOp = package.InitializeAsync(initParams); +``` + +### 配置解密服务 + +```csharp +var buildinParams = FileSystemParameters.CreateDefaultBuildinFileSystemParameters(); + +// 设置解密服务 +buildinParams.AddParameter( + FileSystemParametersDefine.DECRYPTION_SERVICES, + new MyDecryptionServices() +); +``` + +### 配置清单拷贝 + +```csharp +var buildinParams = FileSystemParameters.CreateDefaultBuildinFileSystemParameters(); + +// 启用清单拷贝(用于离线模式切换到联机模式) +buildinParams.AddParameter( + FileSystemParametersDefine.COPY_BUILDIN_PACKAGE_MANIFEST, + true +); + +// 可选:指定拷贝目标目录 +buildinParams.AddParameter( + FileSystemParametersDefine.COPY_BUILDIN_PACKAGE_MANIFEST_DEST_ROOT, + "/custom/path" +); +``` + +### 禁用 Catalog 文件 + +```csharp +var buildinParams = FileSystemParameters.CreateDefaultBuildinFileSystemParameters(); + +// 禁用 Catalog(所有资源视为内置) +buildinParams.AddParameter( + FileSystemParametersDefine.DISABLE_CATALOG_FILE, + true +); +``` + +### 配置解压文件系统根目录 + +```csharp +var buildinParams = FileSystemParameters.CreateDefaultBuildinFileSystemParameters(); + +// 设置解压文件系统的根目录 +buildinParams.AddParameter( + FileSystemParametersDefine.UNPACK_FILE_SYSTEM_ROOT, + "/custom/unpack/path" +); +``` + +--- + +## 参数常量 + +```csharp +// 安装清理 +FileSystemParametersDefine.INSTALL_CLEAR_MODE // EOverwriteInstallClearMode + +// 文件校验 +FileSystemParametersDefine.FILE_VERIFY_LEVEL // EFileVerifyLevel +FileSystemParametersDefine.FILE_VERIFY_MAX_CONCURRENCY // int + +// 文件配置 +FileSystemParametersDefine.APPEND_FILE_EXTENSION // bool +FileSystemParametersDefine.DISABLE_CATALOG_FILE // bool + +// 清单拷贝 +FileSystemParametersDefine.COPY_BUILDIN_PACKAGE_MANIFEST // bool +FileSystemParametersDefine.COPY_BUILDIN_PACKAGE_MANIFEST_DEST_ROOT // string + +// 解压配置 +FileSystemParametersDefine.UNPACK_FILE_SYSTEM_ROOT // string + +// 服务接口 +FileSystemParametersDefine.DECRYPTION_SERVICES // IDecryptionServices +FileSystemParametersDefine.MANIFEST_SERVICES // IManifestRestoreServices +FileSystemParametersDefine.COPY_LOCAL_FILE_SERVICES // ICopyLocalFileServices +``` + +--- + +## 类继承关系 + +``` +IFileSystem + └── DefaultBuildinFileSystem + └── (内部持有) DefaultUnpackFileSystem + +FSInitializeFileSystemOperation + └── DBFSInitializeOperation + +FSRequestPackageVersionOperation + └── DBFSRequestPackageVersionOperation + +FSLoadPackageManifestOperation + └── DBFSLoadPackageManifestOperation + +FSLoadBundleOperation + ├── DBFSLoadAssetBundleOperation + ├── DBFSLoadRawBundleOperation + └── DBFSLoadInstantBundleOperation (Tuanjie) + +AsyncOperationBase + ├── LoadBuildinCatalogFileOperation + ├── CopyBuildinFileOperation + ├── LoadBuildinPackageManifestOperation + ├── RequestBuildinPackageHashOperation + └── RequestBuildinPackageVersionOperation + +BundleResult + ├── AssetBundleResult ← AssetBundle 资源 + └── RawBundleResult ← 原生文件 +``` + +--- + +## 注意事项 + +1. **WebGL 不支持**:DefaultBuildinFileSystem 不支持 WebGL 平台 +2. **Android 限制**:Android 平台无法直接读取 StreamingAssets 中的原生文件 +3. **Catalog 文件**:构建时需要生成 Catalog 文件,否则需要禁用 Catalog 功能 +4. **解压目录**:解压的文件存储在 `UnpackFileSystemRoot` 指定的目录 +5. **加密资源**:加密资源在 Android/OpenHarmony 平台会自动解压到沙盒 +6. **清单拷贝**:启用清单拷贝可以支持从离线模式平滑切换到联机模式 diff --git a/Assets/YooAsset/Runtime/FileSystem/DefaultBuildinFileSystem/README.md.meta b/Assets/YooAsset/Runtime/FileSystem/DefaultBuildinFileSystem/README.md.meta new file mode 100644 index 00000000..9eb6cb8e --- /dev/null +++ b/Assets/YooAsset/Runtime/FileSystem/DefaultBuildinFileSystem/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7ec531875d1515b4496e0e9035e63661 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Runtime/FileSystem/DefaultCacheFileSystem/README.md b/Assets/YooAsset/Runtime/FileSystem/DefaultCacheFileSystem/README.md new file mode 100644 index 00000000..2e49b9d9 --- /dev/null +++ b/Assets/YooAsset/Runtime/FileSystem/DefaultCacheFileSystem/README.md @@ -0,0 +1,791 @@ +# DefaultCacheFileSystem 缓存文件系统 + +## 模块概述 + +DefaultCacheFileSystem 是 YooAsset 的**缓存文件系统**,负责管理从远程服务器下载并缓存到本地沙盒的资源文件。该文件系统是联机运行模式(HostPlayMode)的核心组件,提供完整的下载、验证、缓存和加载功能。 + +### 核心特性 + +- **智能缓存管理**:基于 GUID 的文件索引,支持增量更新 +- **断点续传**:大文件下载支持从断点继续 +- **多线程验证**:后台线程验证文件完整性,不阻塞主线程 +- **并发下载**:可配置的下载并发数和请求速率 +- **覆盖安装检测**:App 版本变更时自动清理过期缓存 +- **加密支持**:支持加密资源包的解密加载 + +--- + +## 设计目标 + +| 目标 | 说明 | +|------|------| +| **高性能** | 多线程验证、并发下载、路径缓存优化 | +| **高可靠** | CRC/Hash 验证、损坏文件自动清理、加载失败重试 | +| **可扩展** | 支持自定义解密服务、远程服务、本地拷贝服务 | +| **易配置** | 丰富的参数配置,适应不同网络环境 | + +--- + +## 文件结构 + +``` +DefaultCacheFileSystem/ +├── DefaultCacheFileSystem.cs # 文件系统主类 +├── DefaultCacheFileSystemDefine.cs # 常量定义 +├── EOverwriteInstallClearMode.cs # 覆盖安装清理模式枚举 +├── ApplicationFootPrint.cs # 应用版本足迹 +├── Elements/ # 元素类 +│ ├── RecordFileElement.cs # 缓存文件记录元素 +│ ├── TempFileElement.cs # 临时文件元素 +│ └── VerifyFileElement.cs # 验证文件元素 +└── Operation/ # 操作类 + ├── DCFSInitializeOperation.cs # 初始化操作 + ├── DCFSRequestPackageVersionOperation.cs # 请求版本操作 + ├── DCFSLoadPackageManifestOperation.cs # 加载清单操作 + ├── DCFSLoadBundleOperation.cs # 加载资源包操作 + └── internal/ # 内部操作类 + ├── SearchCacheFilesOperation.cs # 搜索缓存文件 + ├── VerifyCacheFilesOperation.cs # 验证缓存文件 + ├── VerifyTempFileOperation.cs # 验证临时文件 + ├── DownloadPackageHashOperation.cs # 下载哈希文件 + ├── DownloadPackageManifestOperation.cs # 下载清单文件 + ├── DownloadPackageBundleOperation.cs # 下载资源包 + ├── LoadCachePackageHashOperation.cs # 加载缓存哈希 + ├── LoadCachePackageManifestOperation.cs # 加载缓存清单 + ├── ClearAllCacheBundleFilesOperation.cs # 清理所有缓存 + ├── ClearUnusedCacheBundleFilesOperation.cs # 清理未使用缓存 + ├── ClearCacheBundleFilesByTagsOperaiton.cs # 按标签清理 + ├── ClearCacheBundleFilesByLocationsOperaiton.cs # 按位置清理 + ├── ClearAllCacheManifestFilesOperation.cs # 清理所有清单 + ├── ClearUnusedCacheManifestFilesOperation.cs # 清理未使用清单 + └── Scheduler/ # 下载调度器 + ├── DownloadSchedulerOperation.cs # 下载调度器 + ├── DownloadAndCacheFileOperation.cs # 下载并缓存基类 + ├── DownloadAndCacheRemoteFileOperation.cs # 远程文件下载 + └── DownloadAndCacheLocalFileOperation.cs # 本地文件拷贝 +``` + +--- + +## 核心类说明 + +### DefaultCacheFileSystem + +缓存文件系统的主类,实现 `IFileSystem` 接口。 + +#### 基本属性 + +| 属性 | 类型 | 说明 | +|------|------|------| +| `PackageName` | `string` | 包裹名称 | +| `FileRoot` | `string` | 缓存根目录 | +| `FileCount` | `int` | 已缓存文件数量 | +| `DownloadBackend` | `IDownloadBackend` | 下载后台接口 | +| `DownloadScheduler` | `DownloadSchedulerOperation` | 下载调度器 | + +#### 自定义参数 + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `RemoteServices` | `IRemoteServices` | - | 远程服务接口(必需) | +| `InstallClearMode` | `EOverwriteInstallClearMode` | `ClearAllManifestFiles` | 覆盖安装缓存清理模式 | +| `FileVerifyLevel` | `EFileVerifyLevel` | `Middle` | 初始化时文件校验级别 | +| `FileVerifyMaxConcurrency` | `int` | `32` | 文件校验最大并发数(1-256) | +| `AppendFileExtension` | `bool` | `false` | 数据文件追加文件扩展名 | +| `DisableOnDemandDownload` | `bool` | `false` | 禁用边玩边下机制 | +| `DownloadMaxConcurrency` | `int` | `10` | 最大并发下载数(1-64) | +| `DownloadMaxRequestPerFrame` | `int` | `5` | 每帧最大请求数(1-20) | +| `DownloadWatchDogTime` | `int` | `0` | 下载看门狗超时时间(秒) | +| `ResumeDownloadMinimumSize` | `long` | `long.MaxValue` | 启用断点续传的最小文件大小 | +| `ResumeDownloadResponseCodes` | `List` | `null` | 断点续传关注的HTTP错误码 | +| `DecryptionServices` | `IDecryptionServices` | `null` | 解密服务接口 | +| `ManifestServices` | `IManifestRestoreServices` | `null` | 清单服务接口 | +| `CopyLocalFileServices` | `ICopyLocalFileServices` | `null` | 本地文件拷贝服务 | + +#### 核心方法 + +```csharp +// 生命周期 +void OnCreate(string packageName, string packageRoot); +void OnDestroy(); +void SetParameter(string name, object value); + +// 异步操作 +FSInitializeFileSystemOperation InitializeFileSystemAsync(); +FSRequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks, int timeout); +FSLoadPackageManifestOperation LoadPackageManifestAsync(string packageVersion, int timeout); +FSLoadBundleOperation LoadBundleFile(PackageBundle bundle); +FSDownloadFileOperation DownloadFileAsync(PackageBundle bundle, DownloadFileOptions options); +FSClearCacheFilesOperation ClearCacheFilesAsync(PackageManifest manifest, ClearCacheFilesOptions options); + +// 文件查询 +bool Belong(PackageBundle bundle); // 始终返回 true(保底加载) +bool Exists(PackageBundle bundle); // 检查文件是否已缓存 +bool NeedDownload(PackageBundle bundle); // 检查是否需要下载 +bool NeedUnpack(PackageBundle bundle); // 始终返回 false +bool NeedImport(PackageBundle bundle); // 检查是否需要导入 + +// 文件访问 +string GetBundleFilePath(PackageBundle bundle); +byte[] ReadBundleFileData(PackageBundle bundle); +string ReadBundleFileText(PackageBundle bundle); +``` + +--- + +## 缓存目录结构 + +``` +{CacheRoot}/{PackageName}/ +├── BundleFiles/ # 资源包文件目录 +│ ├── {Hash[0:2]}/ # 哈希前两位分组(256个目录) +│ │ ├── {BundleGUID}/ # 资源包 GUID 目录 +│ │ │ ├── __data # 数据文件(或 __data.bundle) +│ │ │ └── __info # 信息文件(CRC + Size) +│ │ └── ... +│ └── ... +├── ManifestFiles/ # 清单文件目录 +│ ├── {PackageName}_{Version}.bytes # 清单二进制文件 +│ ├── {PackageName}_{Version}.hash # 清单哈希文件 +│ └── __app_footprint.txt # 应用版本足迹文件 +└── TempFiles/ # 临时文件目录 + ├── {BundleGUID} # 下载中的临时文件 + └── ... +``` + +### 信息文件格式(__info) + +``` +| 字段 | 类型 | 大小 | 说明 | +|------|------|------|------| +| DataFileCRC | uint32 | 4 bytes | 数据文件 CRC | +| DataFileSize | int64 | 8 bytes | 数据文件大小 | +``` + +--- + +## 操作类说明 + +### DCFSInitializeOperation + +初始化操作,执行完整的缓存系统初始化流程。 + +``` +状态流程: +CheckAppFootPrint + ├── 版本相同 → 继续 + └── 版本不同 → 根据 InstallClearMode 清理缓存 + ↓ +SearchCacheFiles + └── SearchCacheFilesOperation + └── 遍历 BundleFiles 目录 + └── 收集需要验证的文件 + ↓ +VerifyCacheFiles + └── VerifyCacheFilesOperation(多线程) + ├── 验证成功 → 记录到 _records + └── 验证失败 → 删除损坏文件 + ↓ +CreateDownloadScheduler + └── 创建 DownloadSchedulerOperation + ↓ +Done → Status = Succeed +``` + +#### 状态机枚举 + +```csharp +private enum ESteps +{ + None, + CheckAppFootPrint, // 检查应用版本足迹 + SearchCacheFiles, // 搜索缓存文件 + VerifyCacheFiles, // 验证缓存文件 + CreateDownloadScheduler,// 创建下载调度器 + Done // 完成 +} +``` + +### DCFSLoadAssetBundleOperation + +加载 AssetBundle 操作,支持按需下载和多重容错机制。 + +``` +状态流程: +CheckExist + ├── 已缓存 → LoadAssetBundle + └── 未缓存 → 检查 DisableOnDemandDownload + ├── 禁用 → Failed + └── 启用 → DownloadFile + ↓ +DownloadFile + └── DownloadFileAsync() + ├── 下载成功 → LoadAssetBundle + └── 下载失败 → Failed + ↓ +LoadAssetBundle + ├── 未加密 → AssetBundle.LoadFromFile[Async] + └── 已加密 → DecryptionServices.LoadAssetBundle[Async] + ↓ +CheckResult + ├── 加载成功 → AssetBundleResult → Succeed + └── 加载失败 → 验证文件完整性 + ├── 验证通过 → LoadFromMemory 重试 + └── 验证失败 → 删除损坏文件 → Failed +``` + +#### 移动平台容错机制 + +```csharp +// 注意:在安卓移动平台,华为和三星真机上有极小概率加载资源包失败。 +// 说明:大多数情况在首次安装下载资源到沙盒内,游戏过程中切换到后台再回到游戏内有很大概率触发! +string filePath = _fileSystem.GetCacheBundleFileLoadPath(_bundle); +byte[] fileData = FileUtility.ReadAllBytes(filePath); +if (fileData != null && fileData.Length > 0) +{ + _assetBundle = AssetBundle.LoadFromMemory(fileData); + // ... +} +``` + +### DCFSLoadRawBundleOperation + +加载原生资源包操作,处理文件格式变更场景。 + +```csharp +// 注意:缓存的原生文件的格式,可能会在业务端根据需求发生变动! +// 注意:这里需要校验文件格式,如果不一致对本地文件进行修正! +if (File.Exists(filePath) == false) +{ + var recordFileElement = _fileSystem.GetRecordFileElement(_bundle); + File.Move(recordFileElement.DataFilePath, filePath); +} +``` + +--- + +## 下载调度器 + +### DownloadSchedulerOperation + +管理所有活跃的下载任务,控制并发数量。 + +#### 核心属性 + +| 属性 | 类型 | 说明 | +|------|------|------| +| `Paused` | `bool` | 是否已暂停 | +| `ActiveDownloadCount` | `int` | 当前活跃的下载任务数 | +| `PendingDownloadCount` | `int` | 当前等待中的下载任务数 | + +#### 工作原理 + +``` +InternalUpdate() + │ + ├── 1. 驱动下载后台 _fileSystem.DownloadBackend.Update() + │ + ├── 2. 遍历下载器集合 + │ ├── 已完成 → 加入移除列表 + │ └── RefCount <= 0 → 中止并移除 + │ + ├── 3. 移除已完成/中止的下载器 + │ + └── 4. 启动新下载任务(如未暂停) + ├── 计算可启动数量 = min(maxConcurrency - active, maxRequestPerFrame) + └── 启动等待中的下载器 +``` + +#### 引用计数机制 + +```csharp +// 查询旧的下载器(复用) +if (_downloaders.TryGetValue(bundle.BundleGUID, out var oldDownloader)) +{ + oldDownloader.Reference(); // 引用计数 +1 + return oldDownloader; +} + +// 创建新的下载器 +DownloadAndCacheFileOperation newDownloader; +// ... +newDownloader.Reference(); // 引用计数 +1 +``` + +### DownloadAndCacheRemoteFileOperation + +远程文件下载操作,支持断点续传。 + +``` +状态流程: +CreateRequest + ├── 文件大小 >= ResumeDownloadMinimumSize + │ └── CreateResumeRequest(断点续传) + └── 文件大小 < ResumeDownloadMinimumSize + └── CreateNormalRequest(普通下载) + ↓ +CheckRequest + ├── 下载成功 → VerifyBundleFile + └── 下载失败 → ClearTempFileWhenError → Failed + ↓ +VerifyBundleFile + └── VerifyTempFileOperation(多线程验证) + ├── 验证成功 → CacheBundleFile + └── 验证失败 → 删除临时文件 → Failed + ↓ +CacheBundleFile + └── WriteCacheBundleFile() + ├── 成功 → 删除临时文件 → Succeed + └── 失败 → Failed +``` + +#### 断点续传实现 + +```csharp +private IDownloadRequest CreateResumeRequest() +{ + // 获取下载起始位置 + if (File.Exists(_tempFilePath)) + { + FileInfo fileInfo = new FileInfo(_tempFilePath); + if (fileInfo.Length >= _bundle.FileSize) + { + File.Delete(_tempFilePath); // 文件已完整,删除重下 + } + else + { + _fileOriginLength = fileInfo.Length; // 记录已下载大小 + } + } + + var args = new DownloadFileRequestArgs( + URL, _tempFilePath, timeout, watchdogTime, + appendToFile: true, // 追加写入 + removeFileOnAbort: false, // 中止时保留文件 + resumeFromBytes: _fileOriginLength // 断点位置 + ); + return _fileSystem.DownloadBackend.CreateFileRequest(args); +} +``` + +--- + +## 缓存验证系统 + +### VerifyCacheFilesOperation + +多线程缓存文件验证,在初始化时执行。 + +#### 验证流程 + +``` +InitVerify + ├── 获取系统线程池信息 + └── 计算实际并发数 = min(threads, FileVerifyMaxConcurrency) + ↓ +UpdateVerify(循环) + ├── 检测已完成的验证任务 + │ ├── 验证成功 → RecordBundleFile + │ └── 验证失败 → DeleteFiles + │ + └── 启动新的验证任务 + └── ThreadPool.QueueUserWorkItem(VerifyInThread) +``` + +#### 验证级别 + +```csharp +private EFileVerifyResult VerifyingCacheFile(VerifyFileElement element, EFileVerifyLevel verifyLevel) +{ + if (verifyLevel == EFileVerifyLevel.Low) + { + // Low:仅检查文件存在 + if (File.Exists(element.InfoFilePath) == false) + return EFileVerifyResult.InfoFileNotExisted; + if (File.Exists(element.DataFilePath) == false) + return EFileVerifyResult.DataFileNotExisted; + return EFileVerifyResult.Succeed; + } + else + { + // Middle/High:检查文件存在 + CRC/Size 验证 + _fileSystem.ReadBundleInfoFile(element.InfoFilePath, out element.DataFileCRC, out element.DataFileSize); + return FileVerifyHelper.FileVerify(element.DataFilePath, element.DataFileSize, element.DataFileCRC, verifyLevel); + } +} +``` + +### VerifyTempFileOperation + +下载文件验证,在线程池中执行。 + +```csharp +private void VerifyInThread(object obj) +{ + TempFileElement element = (TempFileElement)obj; + int result = (int)FileVerifyHelper.FileVerify( + element.TempFilePath, + element.TempFileSize, + element.TempFileCRC, + EFileVerifyLevel.High // 始终使用高级验证 + ); + element.Result = result; // 线程安全的结果设置 +} +``` + +--- + +## 覆盖安装检测 + +### ApplicationFootPrint + +应用版本足迹,用于检测 App 覆盖安装。 + +```csharp +public static bool IsDirty(DefaultCacheFileSystem fileSystem) +{ + string filePath = fileSystem.GetSandboxAppFootPrintFilePath(); + if (File.Exists(filePath)) + { + string footPrint = FileUtility.ReadAllText(filePath); + return IsValidVersion(footPrint) == false; // 版本不同 + } + return true; // 文件不存在 +} +``` + +### EOverwriteInstallClearMode + +覆盖安装时的缓存清理模式。 + +| 枚举值 | 说明 | +|--------|------| +| `NeverClear` | 不清理任何缓存 | +| `ClearAllManifestFiles` | 清理所有清单文件(默认) | +| `ClearAllBundleAndManifestFiles` | 清理所有资源包和清单文件 | + +--- + +## 缓存清理操作 + +### 清理模式对照表 + +| 清理模式 | 操作类 | 说明 | +|----------|--------|------| +| `ClearAllBundleFiles` | `ClearAllCacheBundleFilesOperation` | 清理所有缓存资源包 | +| `ClearUnusedBundleFiles` | `ClearUnusedCacheBundleFilesOperation` | 清理不在清单中的资源包 | +| `ClearBundleFilesByTags` | `ClearCacheBundleFilesByTagsOperaiton` | 按标签清理 | +| `ClearBundleFilesByLocations` | `ClearCacheBundleFilesByLocationsOperaiton` | 按资源路径清理 | +| `ClearAllManifestFiles` | `ClearAllCacheManifestFilesOperation` | 清理所有清单文件 | +| `ClearUnusedManifestFiles` | `ClearUnusedCacheManifestFilesOperation` | 清理未使用的清单文件 | + +### 时间切片清理 + +```csharp +for (int i = _allBundleGUIDs.Count - 1; i >= 0; i--) +{ + string bundleGUID = _allBundleGUIDs[i]; + _fileSystem.DeleteCacheBundleFile(bundleGUID); + _allBundleGUIDs.RemoveAt(i); + + // 检查操作系统是否繁忙,避免阻塞主线程 + if (OperationSystem.IsBusy) + break; +} +``` + +--- + +## 元素类说明 + +### RecordFileElement + +缓存文件记录元素,存储已验证的缓存文件信息。 + +```csharp +internal class RecordFileElement +{ + public readonly string InfoFilePath; // 信息文件路径 + public readonly string DataFilePath; // 数据文件路径 + public readonly uint DataFileCRC; // 数据文件 CRC + public readonly long DataFileSize; // 数据文件大小 + + public bool DeleteFolder(); // 删除整个文件夹 +} +``` + +### TempFileElement + +临时文件元素,用于下载文件验证。 + +```csharp +internal class TempFileElement +{ + public readonly string TempFilePath; // 临时文件路径 + public readonly uint TempFileCRC; // 预期 CRC + public readonly long TempFileSize; // 预期文件大小 + + private int _result = 0; + public int Result // 线程安全的验证结果 + { + get => Interlocked.CompareExchange(ref _result, 0, 0); + set => Interlocked.Exchange(ref _result, value); + } +} +``` + +### VerifyFileElement + +验证文件元素,用于缓存文件批量验证。 + +```csharp +internal class VerifyFileElement +{ + public readonly string PackageName; // 包裹名称 + public readonly string BundleGUID; // 资源包 GUID + public readonly string FileRootPath; // 文件根目录 + public readonly string DataFilePath; // 数据文件路径 + public readonly string InfoFilePath; // 信息文件路径 + + public uint DataFileCRC; // 数据文件 CRC + public long DataFileSize; // 数据文件大小 + + private int _result = 0; + public int Result // 线程安全的验证结果 + { + get => Interlocked.CompareExchange(ref _result, 0, 0); + set => Interlocked.Exchange(ref _result, value); + } + + public void DeleteFiles(); // 删除所有相关文件 +} +``` + +--- + +## 使用示例 + +### 基础配置 + +```csharp +// 创建远程服务接口 +class GameRemoteServices : IRemoteServices +{ + public string GetRemoteMainURL(string fileName) + { + return $"https://cdn.example.com/bundles/{fileName}"; + } + public string GetRemoteFallbackURL(string fileName) + { + return $"https://cdn-backup.example.com/bundles/{fileName}"; + } +} + +// 创建缓存文件系统参数 +var cacheParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters( + remoteServices: new GameRemoteServices() +); + +// 初始化包裹 +var initParams = new HostPlayModeParameters(); +initParams.BuildinFileSystemParameters = buildinParams; +initParams.CacheFileSystemParameters = cacheParams; +var initOp = package.InitializeAsync(initParams); +``` + +### 配置下载参数 + +```csharp +var cacheParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters( + remoteServices: new GameRemoteServices() +); + +// 设置下载并发数 +cacheParams.AddParameter(FileSystemParametersDefine.DOWNLOAD_MAX_CONCURRENCY, 8); + +// 设置每帧最大请求数 +cacheParams.AddParameter(FileSystemParametersDefine.DOWNLOAD_MAX_REQUEST_PER_FRAME, 3); + +// 设置下载看门狗时间(秒) +cacheParams.AddParameter(FileSystemParametersDefine.DOWNLOAD_WATCH_DOG_TIME, 30); +``` + +### 配置断点续传 + +```csharp +var cacheParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters( + remoteServices: new GameRemoteServices() +); + +// 启用断点续传的最小文件大小(1MB) +cacheParams.AddParameter(FileSystemParametersDefine.RESUME_DOWNLOAD_MINMUM_SIZE, 1024 * 1024); + +// 断点续传关注的HTTP错误码(这些错误码时删除临时文件重新下载) +var responseCodes = new List { 416 }; // Range Not Satisfiable +cacheParams.AddParameter(FileSystemParametersDefine.RESUME_DOWNLOAD_RESPONSE_CODES, responseCodes); +``` + +### 配置文件验证 + +```csharp +var cacheParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters( + remoteServices: new GameRemoteServices() +); + +// 设置验证级别 +cacheParams.AddParameter(FileSystemParametersDefine.FILE_VERIFY_LEVEL, EFileVerifyLevel.High); + +// 设置验证并发数 +cacheParams.AddParameter(FileSystemParametersDefine.FILE_VERIFY_MAX_CONCURRENCY, 64); +``` + +### 配置加密支持 + +```csharp +// 自定义解密服务 +class GameDecryptionServices : IDecryptionServices +{ + public DecryptResult LoadAssetBundle(DecryptFileInfo fileInfo) + { + // 实现解密逻辑 + byte[] data = DecryptFile(fileInfo.FileLoadPath); + AssetBundle bundle = AssetBundle.LoadFromMemory(data); + return new DecryptResult { Result = bundle }; + } + // ... +} + +var cacheParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters( + remoteServices: new GameRemoteServices() +); + +// 设置解密服务 +cacheParams.AddParameter(FileSystemParametersDefine.DECRYPTION_SERVICES, new GameDecryptionServices()); +``` + +### 配置覆盖安装行为 + +```csharp +var cacheParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters( + remoteServices: new GameRemoteServices() +); + +// 覆盖安装时清理所有资源包和清单 +cacheParams.AddParameter( + FileSystemParametersDefine.INSTALL_CLEAR_MODE, + EOverwriteInstallClearMode.ClearAllBundleAndManifestFiles +); +``` + +### 清理缓存 + +```csharp +// 清理所有缓存 +var clearOp = package.ClearCacheFilesAsync(EFileClearMode.ClearAllBundleFiles); +await clearOp.ToTask(); + +// 清理未使用的缓存 +var clearOp = package.ClearCacheFilesAsync(EFileClearMode.ClearUnusedBundleFiles); +await clearOp.ToTask(); + +// 按标签清理 +var clearOp = package.ClearCacheFilesAsync(EFileClearMode.ClearBundleFilesByTags, "dlc1"); +await clearOp.ToTask(); +``` + +--- + +## 参数常量 + +```csharp +// 远程服务 +FileSystemParametersDefine.REMOTE_SERVICES // IRemoteServices: 远程服务接口 + +// 覆盖安装 +FileSystemParametersDefine.INSTALL_CLEAR_MODE // EOverwriteInstallClearMode: 清理模式 + +// 文件验证 +FileSystemParametersDefine.FILE_VERIFY_LEVEL // EFileVerifyLevel: 验证级别 +FileSystemParametersDefine.FILE_VERIFY_MAX_CONCURRENCY // int: 验证并发数 + +// 文件格式 +FileSystemParametersDefine.APPEND_FILE_EXTENSION // bool: 追加文件扩展名 + +// 下载控制 +FileSystemParametersDefine.DISABLE_ONDEMAND_DOWNLOAD // bool: 禁用边玩边下 +FileSystemParametersDefine.DOWNLOAD_MAX_CONCURRENCY // int: 下载并发数 +FileSystemParametersDefine.DOWNLOAD_MAX_REQUEST_PER_FRAME // int: 每帧请求数 +FileSystemParametersDefine.DOWNLOAD_WATCH_DOG_TIME // int: 看门狗时间 + +// 断点续传 +FileSystemParametersDefine.RESUME_DOWNLOAD_MINMUM_SIZE // long: 最小文件大小 +FileSystemParametersDefine.RESUME_DOWNLOAD_RESPONSE_CODES // List: 关注错误码 + +// 服务接口 +FileSystemParametersDefine.DECRYPTION_SERVICES // IDecryptionServices: 解密服务 +FileSystemParametersDefine.MANIFEST_SERVICES // IManifestRestoreServices: 清单服务 +FileSystemParametersDefine.COPY_LOCAL_FILE_SERVICES // ICopyLocalFileServices: 本地拷贝服务 +``` + +--- + +## 类继承关系 + +``` +IFileSystem + └── DefaultCacheFileSystem + +FSInitializeFileSystemOperation + └── DCFSInitializeOperation + +FSRequestPackageVersionOperation + └── DCFSRequestPackageVersionOperation + +FSLoadPackageManifestOperation + └── DCFSLoadPackageManifestOperation + +FSLoadBundleOperation + ├── DCFSLoadAssetBundleOperation + └── DCFSLoadRawBundleOperation + +FSDownloadFileOperation + └── DownloadPackageBundleOperation + +FSClearCacheFilesOperation + ├── ClearAllCacheBundleFilesOperation + ├── ClearUnusedCacheBundleFilesOperation + ├── ClearCacheBundleFilesByTagsOperaiton + ├── ClearCacheBundleFilesByLocationsOperaiton + ├── ClearAllCacheManifestFilesOperation + └── ClearUnusedCacheManifestFilesOperation + +AsyncOperationBase + ├── DownloadSchedulerOperation + ├── DownloadAndCacheFileOperation (abstract) + │ ├── DownloadAndCacheRemoteFileOperation + │ └── DownloadAndCacheLocalFileOperation + ├── SearchCacheFilesOperation + ├── VerifyCacheFilesOperation + ├── VerifyTempFileOperation + ├── DownloadPackageHashOperation + ├── DownloadPackageManifestOperation + ├── LoadCachePackageHashOperation + ├── LoadCachePackageManifestOperation + └── RequestRemotePackageVersionOperation + +BundleResult + ├── AssetBundleResult ← AssetBundle 资源 + └── RawBundleResult ← 原生文件资源 +``` + +--- + +## 注意事项 + +1. **远程服务必需**:使用缓存文件系统必须配置 `IRemoteServices` 接口 +2. **保底加载**:缓存文件系统的 `Belong()` 始终返回 true,作为资源加载的保底方案 +3. **线程安全**:文件验证和下载使用后台线程,但核心逻辑仍在主线程执行 +4. **并发限制**:合理设置下载和验证的并发数,避免系统过载 +5. **移动平台**:Android 平台存在极小概率的 AssetBundle 加载失败,系统会自动尝试 LoadFromMemory 作为备选方案 +6. **断点续传**:启用断点续传时,需要合理设置 `ResumeDownloadMinimumSize` 和 `ResumeDownloadResponseCodes` +7. **覆盖安装**:App 版本更新时会自动检测并根据配置清理缓存,避免旧缓存导致问题 diff --git a/Assets/YooAsset/Runtime/FileSystem/DefaultCacheFileSystem/README.md.meta b/Assets/YooAsset/Runtime/FileSystem/DefaultCacheFileSystem/README.md.meta new file mode 100644 index 00000000..e822c168 --- /dev/null +++ b/Assets/YooAsset/Runtime/FileSystem/DefaultCacheFileSystem/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8292f707fbf60854e852e1a75824d892 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Runtime/FileSystem/DefaultEditorFileSystem/README.md b/Assets/YooAsset/Runtime/FileSystem/DefaultEditorFileSystem/README.md new file mode 100644 index 00000000..b8c65e5d --- /dev/null +++ b/Assets/YooAsset/Runtime/FileSystem/DefaultEditorFileSystem/README.md @@ -0,0 +1,424 @@ +# DefaultEditorFileSystem 编辑器模拟文件系统 + +## 模块概述 + +DefaultEditorFileSystem 是 YooAsset 的**编辑器模拟文件系统**,专为 Unity 编辑器开发环境设计。该文件系统无需构建实际的 AssetBundle 文件,直接使用 Unity 的 AssetDatabase API 加载资源,实现快速迭代开发。 + +### 核心特性 + +- **无需构建资源包**:直接使用 AssetDatabase 加载资源 +- **模拟下载流程**:支持模拟网络下载行为(用于 UI 调试) +- **模拟异步延迟**:可配置异步加载的模拟帧数 +- **WebGL 模式模拟**:支持模拟 WebGL 平台行为 + +--- + +## 设计目标 + +| 目标 | 说明 | +|------|------| +| **快速迭代** | 无需构建 AssetBundle,修改资源后立即生效 | +| **行为模拟** | 模拟真实环境的下载和加载行为 | +| **调试友好** | 支持 UI 进度条等功能的调试 | +| **零配置** | 开箱即用,最小化配置需求 | + +--- + +## 文件结构 + +``` +DefaultEditorFileSystem/ +├── DefaultEditorFileSystem.cs # 文件系统主类 +├── DefaultEditorFileSystemDefine.cs # 常量定义(预留) +└── Operation/ # 操作类 + ├── DEFSInitializeOperation.cs # 初始化操作 + ├── DEFSRequestPackageVersionOperation.cs # 请求版本操作 + ├── DEFSLoadPackageManifestOperation.cs # 加载清单操作 + ├── DEFSLoadBundleOperation.cs # 加载资源包操作 + └── internal/ # 内部操作类 + ├── DownloadVirutalBundleOperation.cs # 虚拟下载操作 + ├── LoadEditorPackageVersionOperation.cs # 加载版本文件操作 + ├── LoadEditorPackageHashOperation.cs # 加载哈希文件操作 + └── LoadEditorPackageManifestOperation.cs # 加载清单文件操作 +``` + +--- + +## 核心类说明 + +### DefaultEditorFileSystem + +编辑器模拟文件系统的主类,实现 `IFileSystem` 接口。 + +#### 基本属性 + +| 属性 | 类型 | 说明 | +|------|------|------| +| `PackageName` | `string` | 包裹名称 | +| `FileRoot` | `string` | 文件根目录(清单文件所在目录) | +| `FileCount` | `int` | 文件数量(始终返回 0) | +| `DownloadBackend` | `IDownloadBackend` | 下载后台接口 | + +#### 自定义参数 + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `VirtualWebGLMode` | `bool` | `false` | 模拟 WebGL 平台模式 | +| `VirtualDownloadMode` | `bool` | `false` | 模拟虚拟下载模式 | +| `VirtualDownloadSpeed` | `int` | `1024` | 模拟下载速度(字节/秒) | +| `AsyncSimulateMinFrame` | `int` | `1` | 异步加载最小模拟帧数 | +| `AsyncSimulateMaxFrame` | `int` | `1` | 异步加载最大模拟帧数 | + +#### 核心方法 + +```csharp +// 生命周期 +void OnCreate(string packageName, string packageRoot); +void OnDestroy(); +void SetParameter(string name, object value); + +// 异步操作 +FSInitializeFileSystemOperation InitializeFileSystemAsync(); +FSRequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks, int timeout); +FSLoadPackageManifestOperation LoadPackageManifestAsync(string packageVersion, int timeout); +FSLoadBundleOperation LoadBundleFile(PackageBundle bundle); +FSDownloadFileOperation DownloadFileAsync(PackageBundle bundle, DownloadFileOptions options); +FSClearCacheFilesOperation ClearCacheFilesAsync(PackageManifest manifest, ClearCacheFilesOptions options); + +// 文件查询 +bool Belong(PackageBundle bundle); // 始终返回 true +bool Exists(PackageBundle bundle); // VirtualDownloadMode 时检查记录 +bool NeedDownload(PackageBundle bundle);// VirtualDownloadMode 时返回未记录的文件 +bool NeedUnpack(PackageBundle bundle); // 始终返回 false +bool NeedImport(PackageBundle bundle); // 始终返回 false + +// 文件访问 +string GetBundleFilePath(PackageBundle bundle); // 返回资源路径 +byte[] ReadBundleFileData(PackageBundle bundle); // 读取文件二进制 +string ReadBundleFileText(PackageBundle bundle); // 读取文件文本 +``` + +--- + +## 操作类说明 + +### DEFSInitializeOperation + +初始化操作,立即完成(无需任何初始化工作)。 + +``` +状态流程:InternalStart() → Status = Succeed +``` + +### DEFSRequestPackageVersionOperation + +请求包裹版本操作,从本地版本文件读取版本号。 + +``` +状态流程: +LoadPackageVersion + └── LoadEditorPackageVersionOperation + └── 读取 {PackageName}_Version.txt + ├── 成功 → PackageVersion = 文件内容 + └── 失败 → Error +``` + +### DEFSLoadPackageManifestOperation + +加载资源清单操作,从本地清单文件加载并解析清单。 + +``` +状态流程: +LoadEditorPackageHash + └── LoadEditorPackageHashOperation + └── 读取 {PackageName}_{Version}.hash + ├── 成功 → PackageHash + └── 失败 → Error + ↓ +LoadEditorPackageManifest + └── LoadEditorPackageManifestOperation + ├── 读取 {PackageName}_{Version}.bytes + ├── 验证哈希值 + └── 反序列化清单 + ├── 成功 → Manifest + └── 失败 → Error +``` + +### DEFSLoadBundleOperation + +加载资源包操作,支持虚拟下载模式和异步模拟延迟。 + +``` +状态流程: +CheckExist + ├── 文件存在 → LoadAssetBundle + └── 文件不存在 → DownloadFile + ↓ +DownloadFile + └── DownloadVirtualBundleOperation + ├── 模拟下载进度 + └── 记录下载完成 + ↓ +LoadAssetBundle + └── 等待模拟帧数 + ↓ +CheckResult + └── 创建 VirtualBundleResult → Status = Succeed +``` + +#### 状态机枚举 + +```csharp +private enum ESteps +{ + None, + CheckExist, // 检查文件是否存在 + DownloadFile, // 下载文件(虚拟下载) + AbortDownload, // 中断下载 + LoadAssetBundle, // 加载资源包(模拟延迟) + CheckResult, // 检查结果 + Done // 完成 +} +``` + +### DownloadVirtualBundleOperation + +虚拟下载操作,模拟网络下载行为。 + +**特性:** +- 使用 `VirtualFileDownloader` 模拟下载进度 +- 支持失败重试机制 +- 下载完成后记录到 `_records` 字典 + +``` +状态流程: +CheckExists + ├── 文件已记录 → Status = Succeed + └── 文件未记录 → CreateRequest + ↓ +CreateRequest + └── DownloadSimulateRequestArgs + ├── URL = BundleName + ├── FileSize = Bundle.FileSize + └── DownloadSpeed = VirtualDownloadSpeed + ↓ +CheckRequest + ├── 下载成功 → RecordDownloadFile() → Status = Succeed + └── 下载失败 → TryAgain 或 Status = Failed +``` + +--- + +## 内部操作类 + +### LoadEditorPackageVersionOperation + +从本地文件加载包裹版本号。 + +| 属性 | 说明 | +|------|------| +| `PackageVersion` | 读取到的版本号字符串 | + +### LoadEditorPackageHashOperation + +从本地文件加载包裹哈希值。 + +| 属性 | 说明 | +|------|------| +| `PackageHash` | 读取到的哈希值字符串 | + +### LoadEditorPackageManifestOperation + +加载并反序列化资源清单。 + +| 属性 | 说明 | +|------|------| +| `Manifest` | 反序列化后的清单对象 | + +**处理流程:** +1. 读取清单二进制文件 +2. 使用哈希值验证文件完整性 +3. 反序列化为 `PackageManifest` 对象 + +--- + +## 工作原理 + +### 资源加载机制 + +``` +用户请求加载资源 + │ + ▼ +DefaultEditorFileSystem.LoadBundleFile() + │ + ▼ +DEFSLoadBundleOperation + │ + ▼ +创建 VirtualBundleResult + │ + ▼ +VirtualBundleResult.LoadAssetAsync() + │ + ▼ +VirtualBundleLoadAssetOperation + │ + ▼ +AssetDatabase.LoadAssetAtPath() ← Unity 编辑器 API + │ + ▼ +返回资源对象 +``` + +### 虚拟下载模式 + +当 `VirtualDownloadMode = true` 时: + +1. **首次加载**:资源被视为"未下载",需要执行虚拟下载 +2. **虚拟下载**:使用 `VirtualFileDownloader` 模拟下载进度 +3. **记录完成**:下载完成后将 BundleGUID 记录到 `_records` 字典 +4. **后续加载**:检查 `_records` 字典,已记录的资源直接加载 + +```csharp +// 记录下载完成的文件 +protected readonly Dictionary _records; + +// 检查文件是否存在 +public virtual bool Exists(PackageBundle bundle) +{ + if (VirtualDownloadMode) + return _records.ContainsKey(bundle.BundleGUID); + else + return true; +} +``` + +### 异步模拟延迟 + +通过 `AsyncSimulateMinFrame` 和 `AsyncSimulateMaxFrame` 参数模拟异步加载延迟: + +```csharp +// 获取随机模拟帧数 +public int GetAsyncSimulateFrame() +{ + return UnityEngine.Random.Range(AsyncSimulateMinFrame, AsyncSimulateMaxFrame + 1); +} + +// 在 DEFSLoadBundleOperation 中等待 +if (_steps == ESteps.LoadAssetBundle) +{ + if (_asyncSimulateFrame <= 0) + _steps = ESteps.CheckResult; + else + _asyncSimulateFrame--; +} +``` + +--- + +## 使用示例 + +### 基础配置 + +```csharp +// 创建编辑器文件系统参数 +var editorParams = FileSystemParameters.CreateDefaultEditorFileSystemParameters( + packageRoot: "Assets/GameRes/Bundles/DefaultPackage" +); + +// 初始化包裹 +var initParams = new EditorSimulateModeParameters(); +initParams.EditorFileSystemParameters = editorParams; +var initOp = package.InitializeAsync(initParams); +``` + +### 启用虚拟下载模式 + +```csharp +var editorParams = FileSystemParameters.CreateDefaultEditorFileSystemParameters( + packageRoot: "Assets/GameRes/Bundles/DefaultPackage" +); + +// 启用虚拟下载模式 +editorParams.AddParameter(FileSystemParametersDefine.VIRTUAL_DOWNLOAD_MODE, true); +editorParams.AddParameter(FileSystemParametersDefine.VIRTUAL_DOWNLOAD_SPEED, 1024 * 100); // 100KB/s +``` + +### 配置异步模拟延迟 + +```csharp +var editorParams = FileSystemParameters.CreateDefaultEditorFileSystemParameters( + packageRoot: "Assets/GameRes/Bundles/DefaultPackage" +); + +// 设置异步加载延迟 1-3 帧 +editorParams.AddParameter(FileSystemParametersDefine.ASYNC_SIMULATE_MIN_FRAME, 1); +editorParams.AddParameter(FileSystemParametersDefine.ASYNC_SIMULATE_MAX_FRAME, 3); +``` + +### 模拟 WebGL 模式 + +```csharp +var editorParams = FileSystemParameters.CreateDefaultEditorFileSystemParameters( + packageRoot: "Assets/GameRes/Bundles/DefaultPackage" +); + +// 启用 WebGL 模拟模式 +editorParams.AddParameter(FileSystemParametersDefine.VIRTUAL_WEBGL_MODE, true); +``` + +--- + +## 参数常量 + +```csharp +// 模拟模式参数 +FileSystemParametersDefine.VIRTUAL_WEBGL_MODE // bool: 模拟 WebGL 模式 +FileSystemParametersDefine.VIRTUAL_DOWNLOAD_MODE // bool: 模拟下载模式 +FileSystemParametersDefine.VIRTUAL_DOWNLOAD_SPEED // int: 模拟下载速度(字节/秒) +FileSystemParametersDefine.ASYNC_SIMULATE_MIN_FRAME // int: 异步模拟最小帧数 +FileSystemParametersDefine.ASYNC_SIMULATE_MAX_FRAME // int: 异步模拟最大帧数 +``` + +--- + +## 类继承关系 + +``` +IFileSystem + └── DefaultEditorFileSystem + +FSInitializeFileSystemOperation + └── DEFSInitializeOperation + +FSRequestPackageVersionOperation + └── DEFSRequestPackageVersionOperation + +FSLoadPackageManifestOperation + └── DEFSLoadPackageManifestOperation + +FSLoadBundleOperation + └── DEFSLoadBundleOperation + +FSDownloadFileOperation + └── DownloadVirtualBundleOperation + +AsyncOperationBase + ├── LoadEditorPackageVersionOperation + ├── LoadEditorPackageHashOperation + └── LoadEditorPackageManifestOperation + +BundleResult + └── VirtualBundleResult ← 编辑器模式专用 +``` + +--- + +## 注意事项 + +1. **仅限编辑器**:此文件系统仅在 Unity 编辑器中有效 +2. **需要构建清单**:虽然不需要构建 AssetBundle,但需要构建资源清单文件 +3. **VirtualBundle 类型**:只支持 `EBuildBundleType.VirtualBundle` 类型的资源包 +4. **WebGL 模式限制**:`VirtualWebGLMode` 下不支持同步加载(`WaitForAsyncComplete`) +5. **性能差异**:编辑器模式下的加载性能与真机不同,仅供开发调试使用 diff --git a/Assets/YooAsset/Runtime/FileSystem/DefaultEditorFileSystem/README.md.meta b/Assets/YooAsset/Runtime/FileSystem/DefaultEditorFileSystem/README.md.meta new file mode 100644 index 00000000..1d1a98f7 --- /dev/null +++ b/Assets/YooAsset/Runtime/FileSystem/DefaultEditorFileSystem/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 10ca037f4f07977458b8e94e4ea0f32c +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Runtime/FileSystem/DefaultUnpackFileSystem/README.md b/Assets/YooAsset/Runtime/FileSystem/DefaultUnpackFileSystem/README.md new file mode 100644 index 00000000..35eb1a09 --- /dev/null +++ b/Assets/YooAsset/Runtime/FileSystem/DefaultUnpackFileSystem/README.md @@ -0,0 +1,385 @@ +# DefaultUnpackFileSystem 解压文件系统 + +## 模块概述 + +DefaultUnpackFileSystem 是 YooAsset 的**解压文件系统**,专为处理 Android 和 OpenHarmony 平台的内置资源解压需求而设计。该文件系统继承自 `DefaultCacheFileSystem`,复用其完整的下载、验证、缓存功能,仅重定义存储目录结构。 + +### 核心特性 + +- **继承复用**:完全继承 DefaultCacheFileSystem 的所有功能 +- **独立存储**:使用独立的目录存储解压后的资源 +- **本地下载**:通过 WWW 路径从 StreamingAssets "下载"资源 +- **平台适配**:解决 Android APK 内文件无法直接访问的问题 + +--- + +## 设计目标 + +| 目标 | 说明 | +|------|------| +| **平台兼容** | 解决 Android/OpenHarmony 平台 APK 内文件访问限制 | +| **代码复用** | 继承 DefaultCacheFileSystem,避免重复实现 | +| **资源隔离** | 解压资源与下载缓存分开存储,便于管理 | +| **透明集成** | 作为 DefaultBuildinFileSystem 的内部组件工作 | + +--- + +## 文件结构 + +``` +DefaultUnpackFileSystem/ +├── DefaultUnpackFileSystem.cs # 解压文件系统主类 +├── DefaultUnpackFileSystemDefine.cs # 常量定义 +└── DefaultUnpackRemoteServices.cs # 本地资源服务接口 +``` + +--- + +## 核心类说明 + +### DefaultUnpackFileSystem + +解压文件系统主类,继承自 `DefaultCacheFileSystem`。 + +```csharp +internal class DefaultUnpackFileSystem : DefaultCacheFileSystem +{ + public override void OnCreate(string packageName, string rootDirectory) + { + base.OnCreate(packageName, rootDirectory); + + // 重写保存根目录和临时目录 + _cacheBundleFilesRoot = PathUtility.Combine(_packageRoot, "UnpackBundleFiles"); + _cacheManifestFilesRoot = PathUtility.Combine(_packageRoot, "UnpackManifestFiles"); + _tempFilesRoot = PathUtility.Combine(_packageRoot, "UnpackTempFiles"); + } +} +``` + +#### 继承的功能 + +由于继承自 `DefaultCacheFileSystem`,DefaultUnpackFileSystem 拥有以下完整功能: + +| 功能 | 说明 | +|------|------| +| 文件下载 | 通过 DownloadScheduler 下载资源 | +| 断点续传 | 支持大文件断点续传 | +| 文件验证 | 多线程 CRC/Hash 验证 | +| 缓存管理 | 记录已解压文件,避免重复解压 | +| 加密支持 | 支持 IDecryptionServices 解密 | +| 覆盖安装检测 | App 版本变更时清理解压缓存 | + +### DefaultUnpackFileSystemDefine + +常量定义类,定义解压目录名称。 + +```csharp +internal class DefaultUnpackFileSystemDefine +{ + /// + /// 保存的资源文件的文件夹名称 + /// + public const string SaveBundleFilesFolderName = "UnpackBundleFiles"; + + /// + /// 保存的清单文件的文件夹名称 + /// + public const string SaveManifestFilesFolderName = "UnpackManifestFiles"; + + /// + /// 下载的临时文件的文件夹名称 + /// + public const string TempFilesFolderName = "UnpackTempFiles"; +} +``` + +### DefaultUnpackRemoteServices + +本地资源服务接口,将 StreamingAssets 路径转换为 WWW 请求路径。 + +```csharp +internal class DefaultUnpackRemoteServices : IRemoteServices +{ + private readonly string _buildinPackageRoot; + protected readonly Dictionary _mapping = new Dictionary(10000); + + public DefaultUnpackRemoteServices(string buildinPackRoot) + { + _buildinPackageRoot = buildinPackRoot; + } + + // 主地址和备用地址相同(本地文件) + string IRemoteServices.GetRemoteMainURL(string fileName) + { + return GetFileLoadURL(fileName); + } + + string IRemoteServices.GetRemoteFallbackURL(string fileName) + { + return GetFileLoadURL(fileName); + } + + private string GetFileLoadURL(string fileName) + { + if (_mapping.TryGetValue(fileName, out string url) == false) + { + string filePath = PathUtility.Combine(_buildinPackageRoot, fileName); + url = DownloadSystemHelper.ConvertToWWWPath(filePath); + _mapping.Add(fileName, url); + } + return url; + } +} +``` + +#### 路径转换示例 + +``` +输入文件名: bundle_abc123.bundle + +StreamingAssets 路径: + {Application.streamingAssetsPath}/DefaultPackage/bundle_abc123.bundle + +WWW 请求路径 (Android): + jar:file:///data/app/com.example.game.apk!/assets/DefaultPackage/bundle_abc123.bundle +``` + +--- + +## 目录结构 + +### 与 DefaultCacheFileSystem 对比 + +| 目录类型 | DefaultCacheFileSystem | DefaultUnpackFileSystem | +|----------|----------------------|------------------------| +| 资源文件 | `BundleFiles/` | `UnpackBundleFiles/` | +| 清单文件 | `ManifestFiles/` | `UnpackManifestFiles/` | +| 临时文件 | `TempFiles/` | `UnpackTempFiles/` | + +### 实际目录结构 + +``` +{SandboxRoot}/{PackageName}/ +├── BundleFiles/ # DefaultCacheFileSystem 下载缓存 +│ └── ... +├── ManifestFiles/ # DefaultCacheFileSystem 清单缓存 +│ └── ... +├── UnpackBundleFiles/ # DefaultUnpackFileSystem 解压缓存 +│ ├── {Hash[0:2]}/ +│ │ └── {BundleGUID}/ +│ │ ├── __data +│ │ └── __info +│ └── ... +├── UnpackManifestFiles/ # DefaultUnpackFileSystem 清单 +│ └── ... +└── UnpackTempFiles/ # DefaultUnpackFileSystem 临时文件 + └── ... +``` + +--- + +## 工作原理 + +### 解压触发条件 + +在 `DefaultBuildinFileSystem` 中,以下情况会触发解压: + +```csharp +protected virtual bool IsUnpackBundleFile(PackageBundle bundle) +{ + if (Belong(bundle) == false) + return false; + +#if UNITY_ANDROID || UNITY_OPENHARMONY + // Android/OpenHarmony 平台 + if (bundle.Encrypted) + return true; // 加密资源需要解压 + + if (bundle.BundleType == (int)EBuildBundleType.RawBundle) + return true; // 原生文件需要解压 + + return false; // 普通 AssetBundle 不需要解压 +#else + return false; // 其他平台不需要解压 +#endif +} +``` + +### 解压流程 + +``` +DefaultBuildinFileSystem + │ + ├── NeedUnpack(bundle) 检查 + │ └── IsUnpackBundleFile(bundle) + │ ├── Android/OpenHarmony 平台 + │ │ ├── 加密资源 → true + │ │ └── RawBundle → true + │ └── 其他平台 → false + │ + ├── 需要解压时 + │ └── _unpackFileSystem.DownloadFileAsync(bundle, options) + │ └── DefaultUnpackRemoteServices.GetRemoteMainURL() + │ └── 返回 StreamingAssets 的 WWW 路径 + │ ↓ + │ └── DownloadAndCacheRemoteFileOperation + │ ├── 从 APK 内 "下载" 资源 + │ ├── 验证文件完整性 + │ └── 保存到 UnpackBundleFiles 目录 + │ + └── 加载资源时 + └── _unpackFileSystem.LoadBundleFile(bundle) + └── 从 UnpackBundleFiles 加载 +``` + +### 资源加载委托 + +```csharp +// DefaultBuildinFileSystem.LoadBundleFile() +public virtual FSLoadBundleOperation LoadBundleFile(PackageBundle bundle) +{ + // 需要解压的资源,委托给解压文件系统加载 + if (IsUnpackBundleFile(bundle)) + { + return _unpackFileSystem.LoadBundleFile(bundle); + } + + // 普通资源直接从 StreamingAssets 加载 + // ... +} +``` + +--- + +## 与 DefaultBuildinFileSystem 集成 + +### 初始化流程 + +```csharp +// DefaultBuildinFileSystem.OnCreate() +public virtual void OnCreate(string packageName, string packageRoot) +{ + // ... 基础初始化 ... + + // 创建解压文件系统 + var remoteServices = new DefaultUnpackRemoteServices(_packageRoot); + _unpackFileSystem = new DefaultUnpackFileSystem(); + + // 传递配置参数 + _unpackFileSystem.SetParameter(REMOTE_SERVICES, remoteServices); + _unpackFileSystem.SetParameter(INSTALL_CLEAR_MODE, InstallClearMode); + _unpackFileSystem.SetParameter(FILE_VERIFY_LEVEL, FileVerifyLevel); + _unpackFileSystem.SetParameter(FILE_VERIFY_MAX_CONCURRENCY, FileVerifyMaxConcurrency); + _unpackFileSystem.SetParameter(APPEND_FILE_EXTENSION, AppendFileExtension); + _unpackFileSystem.SetParameter(DECRYPTION_SERVICES, DecryptionServices); + _unpackFileSystem.SetParameter(COPY_LOCAL_FILE_SERVICES, CopyLocalFileServices); + + // 使用指定的解压根目录 + _unpackFileSystem.OnCreate(packageName, UnpackFileSystemRoot); +} +``` + +### 方法委托关系 + +| DefaultBuildinFileSystem 方法 | 解压资源时的委托目标 | +|------------------------------|-------------------| +| `LoadBundleFile()` | `_unpackFileSystem.LoadBundleFile()` | +| `GetBundleFilePath()` | `_unpackFileSystem.GetBundleFilePath()` | +| `ReadBundleFileData()` | `_unpackFileSystem.ReadBundleFileData()` | +| `ReadBundleFileText()` | `_unpackFileSystem.ReadBundleFileText()` | +| `DownloadFileAsync()` | `_unpackFileSystem.DownloadFileAsync()` | +| `ClearCacheFilesAsync()` | `_unpackFileSystem.ClearCacheFilesAsync()` | + +--- + +## 使用场景 + +### 场景 1:Android 加密资源 + +``` +问题:Android 平台无法直接从 APK 内读取文件进行解密 +解决:先解压到沙盒,再从沙盒读取并解密 + +流程: +1. 检测到加密资源 → IsUnpackBundleFile() = true +2. 首次加载 → NeedUnpack() = true +3. 执行解压 → DownloadFileAsync() 从 APK 复制到沙盒 +4. 后续加载 → NeedUnpack() = false,直接从沙盒加载 +``` + +### 场景 2:Android 原生文件 + +``` +问题:RawBundle 需要通过文件路径访问,APK 内路径不可直接访问 +解决:解压到沙盒,返回沙盒内的文件路径 + +流程: +1. 检测到 RawBundle → IsUnpackBundleFile() = true +2. 首次访问 → 解压到 UnpackBundleFiles +3. GetBundleFilePath() 返回沙盒路径 +4. 业务代码使用标准文件 API 访问 +``` + +### 场景 3:普通 AssetBundle + +``` +情况:Android 平台的普通(未加密)AssetBundle +处理:不需要解压,Unity 可以直接从 APK 内加载 + +流程: +1. IsUnpackBundleFile() = false +2. 直接使用 AssetBundle.LoadFromFile() 加载 +3. Unity 内部处理 APK 访问 +``` + +--- + +## 类继承关系 + +``` +IFileSystem + └── DefaultCacheFileSystem + └── DefaultUnpackFileSystem ← 仅重写目录名称 + +IRemoteServices + └── DefaultUnpackRemoteServices ← 本地 WWW 路径服务 +``` + +--- + +## 配置参数 + +DefaultUnpackFileSystem 继承 DefaultCacheFileSystem 的所有参数: + +| 参数 | 类型 | 说明 | +|------|------|------| +| `REMOTE_SERVICES` | `IRemoteServices` | 由 DefaultUnpackRemoteServices 提供 | +| `INSTALL_CLEAR_MODE` | `EOverwriteInstallClearMode` | 覆盖安装清理模式 | +| `FILE_VERIFY_LEVEL` | `EFileVerifyLevel` | 文件验证级别 | +| `FILE_VERIFY_MAX_CONCURRENCY` | `int` | 验证并发数 | +| `APPEND_FILE_EXTENSION` | `bool` | 追加文件扩展名 | +| `DECRYPTION_SERVICES` | `IDecryptionServices` | 解密服务 | +| `COPY_LOCAL_FILE_SERVICES` | `ICopyLocalFileServices` | 本地拷贝服务 | + +--- + +## 注意事项 + +1. **内部组件**:DefaultUnpackFileSystem 是 DefaultBuildinFileSystem 的内部组件,不建议单独使用 +2. **平台限定**:解压功能仅在 Android 和 OpenHarmony 平台生效 +3. **存储占用**:解压会额外占用设备存储空间(相当于资源的两份拷贝) +4. **首次加载**:需要解压的资源首次加载会有额外耗时 +5. **自动管理**:解压缓存由系统自动管理,包括覆盖安装时的清理 +6. **继承完整性**:继承了 DefaultCacheFileSystem 的所有功能,包括断点续传、多线程验证等 + +--- + +## 与其他文件系统对比 + +| 特性 | DefaultUnpackFileSystem | DefaultCacheFileSystem | +|------|------------------------|----------------------| +| 数据来源 | StreamingAssets (本地) | 远程服务器 | +| 主/备用地址 | 相同(本地路径) | 不同(CDN 地址) | +| 存储目录 | UnpackXxxFiles | XxxFiles | +| 使用方式 | 作为内部组件 | 独立使用 | +| 继承关系 | 子类 | 父类 | diff --git a/Assets/YooAsset/Runtime/FileSystem/DefaultUnpackFileSystem/README.md.meta b/Assets/YooAsset/Runtime/FileSystem/DefaultUnpackFileSystem/README.md.meta new file mode 100644 index 00000000..4ab2604c --- /dev/null +++ b/Assets/YooAsset/Runtime/FileSystem/DefaultUnpackFileSystem/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 402224e40b04b0d458f12f5925d229b7 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Runtime/FileSystem/DefaultWebRemoteFileSystem/README.md b/Assets/YooAsset/Runtime/FileSystem/DefaultWebRemoteFileSystem/README.md new file mode 100644 index 00000000..c1169028 --- /dev/null +++ b/Assets/YooAsset/Runtime/FileSystem/DefaultWebRemoteFileSystem/README.md @@ -0,0 +1,504 @@ +# DefaultWebRemoteFileSystem Web远程文件系统 + +## 模块概述 + +DefaultWebRemoteFileSystem 是 YooAsset 的 **Web 远程文件系统**,专为从远程服务器直接加载资源而设计。该文件系统不缓存文件到本地,每次都从远程 URL 加载资源,适用于 WebGL 平台的跨域资源加载或特殊的网络资源场景。 + +### 核心特性 + +- **无本地缓存**:直接从远程 URL 加载,不写入本地文件 +- **跨域支持**:通过 `IRemoteServices` 支持跨域资源下载 +- **Unity 缓存控制**:可选择禁用 Unity 的 Web 请求缓存 +- **加密支持**:支持 `IWebDecryptionServices` 解密 Web 资源 +- **失败重试**:内置下载失败自动重试机制 + +--- + +## 设计目标 + +| 目标 | 说明 | +|------|------| +| **轻量级** | 无缓存管理,结构简洁 | +| **即时加载** | 每次从远程获取最新资源 | +| **跨域兼容** | 支持 WebGL 平台的跨域限制处理 | +| **可配置** | 支持 Unity Web 缓存控制和自定义解密 | + +--- + +## 文件结构 + +``` +DefaultWebRemoteFileSystem/ +├── DefaultWebRemoteFileSystem.cs # 文件系统主类 +└── Operation/ # 操作类 + ├── DWRFSInitializeOperation.cs # 初始化操作 + ├── DWRFSRequestPackageVersionOperation.cs # 请求版本操作 + ├── DWRFSLoadPackageManifestOperation.cs # 加载清单操作 + └── DWRFSLoadBundleOperation.cs # 加载资源包操作 +``` + +### 依赖的共享模块 + +DefaultWebRemoteFileSystem 依赖 `WebGame` 目录下的共享操作类: + +``` +FileSystem/WebGame/Operation/ +├── LoadWebAssetBundleOperation.cs # Web 资源包加载基类 +├── LoadWebNormalAssetBundleOperation.cs # 普通资源包加载 +├── LoadWebEncryptAssetBundleOperation.cs # 加密资源包加载 +├── RequestWebPackageVersionOperation.cs # 请求版本文件 +├── RequestWebPackageHashOperation.cs # 请求哈希文件 +└── LoadWebPackageManifestOperation.cs # 加载清单文件 +``` + +--- + +## 核心类说明 + +### DefaultWebRemoteFileSystem + +Web 远程文件系统的主类,实现 `IFileSystem` 接口。 + +#### 基本属性 + +| 属性 | 类型 | 说明 | +|------|------|------| +| `PackageName` | `string` | 包裹名称 | +| `FileRoot` | `string` | 始终返回空字符串(无本地存储) | +| `FileCount` | `int` | 始终返回 0(无本地文件) | +| `DownloadBackend` | `IDownloadBackend` | 下载后台接口 | + +#### 自定义参数 + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `DisableUnityWebCache` | `bool` | `false` | 禁用 Unity 的网络缓存 | +| `RemoteServices` | `IRemoteServices` | - | 远程服务接口(必需) | +| `DecryptionServices` | `IWebDecryptionServices` | `null` | Web 解密服务接口 | +| `ManifestServices` | `IManifestRestoreServices` | `null` | 清单服务接口 | + +#### 核心方法 + +```csharp +// 生命周期 +void OnCreate(string packageName, string packageRoot); +void OnDestroy(); +void SetParameter(string name, object value); + +// 异步操作 +FSInitializeFileSystemOperation InitializeFileSystemAsync(); +FSRequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks, int timeout); +FSLoadPackageManifestOperation LoadPackageManifestAsync(string packageVersion, int timeout); +FSLoadBundleOperation LoadBundleFile(PackageBundle bundle); +FSClearCacheFilesOperation ClearCacheFilesAsync(...); // 直接返回完成 + +// 不支持的操作 +FSDownloadFileOperation DownloadFileAsync(...); // 抛出 NotImplementedException +string GetBundleFilePath(...); // 抛出 NotImplementedException +byte[] ReadBundleFileData(...); // 抛出 NotImplementedException +string ReadBundleFileText(...); // 抛出 NotImplementedException + +// 文件查询(固定返回值) +bool Belong(PackageBundle bundle); // 始终返回 true +bool Exists(PackageBundle bundle); // 始终返回 true +bool NeedDownload(PackageBundle bundle);// 始终返回 false +bool NeedUnpack(PackageBundle bundle); // 始终返回 false +bool NeedImport(PackageBundle bundle); // 始终返回 false +``` + +--- + +## 操作类说明 + +### DWRFSInitializeOperation + +初始化操作,立即完成(无需任何初始化工作)。 + +```csharp +internal override void InternalStart() +{ + Status = EOperationStatus.Succeed; // 直接成功 +} +``` + +### DWRFSRequestPackageVersionOperation + +请求包裹版本操作,从远程服务器获取版本文件。 + +``` +状态流程: +RequestPackageVersion + └── RequestWebPackageVersionOperation + └── 请求 {PackageName}_Version.txt + ├── 成功 → PackageVersion = 文件内容 → Succeed + └── 失败 → Failed +``` + +#### 请求地址轮换 + +```csharp +// 轮流使用主地址和备用地址 +if (_requestCount % 2 == 0) + url = _remoteServices.GetRemoteMainURL(fileName); +else + url = _remoteServices.GetRemoteFallbackURL(fileName); + +// 可选:添加时间戳防止缓存 +if (_appendTimeTicks) + return $"{url}?{System.DateTime.UtcNow.Ticks}"; +``` + +### DWRFSLoadPackageManifestOperation + +加载资源清单操作,从远程下载并解析清单。 + +``` +状态流程: +RequestWebPackageHash + └── RequestWebPackageHashOperation + └── 请求 {PackageName}_{Version}.hash + ├── 成功 → PackageHash + └── 失败 → Failed + ↓ +LoadWebPackageManifest + └── LoadWebPackageManifestOperation + └── 请求 {PackageName}_{Version}.bytes + ├── 验证哈希 + └── 反序列化清单 + ├── 成功 → Manifest → Succeed + └── 失败 → Failed +``` + +### DWRFSLoadAssetBundleOperation + +加载资源包操作,从远程 URL 直接加载 AssetBundle。 + +``` +状态流程: +LoadWebAssetBundle + ├── 未加密 → LoadWebNormalAssetBundleOperation + │ └── UnityWebRequestAssetBundle.GetAssetBundle() + │ ├── 成功 → AssetBundle → AssetBundleResult + │ └── 失败 → TryAgain 或 Failed + │ + └── 已加密 → LoadWebEncryptAssetBundleOperation + └── DownloadBytesRequest + └── 下载原始字节 + └── IWebDecryptionServices.LoadAssetBundle() + ├── 成功 → AssetBundle → AssetBundleResult + └── 失败 → TryAgain 或 Failed +``` + +#### 状态机枚举 + +```csharp +private enum ESteps +{ + None, + LoadWebAssetBundle, // 加载 Web 资源包 + Done // 完成 +} +``` + +#### 同步加载限制 + +```csharp +internal override void InternalWaitForAsyncComplete() +{ + if (_steps != ESteps.Done) + { + _steps = ESteps.Done; + Status = EOperationStatus.Failed; + Error = "WebGL platform not support sync load method !"; + UnityEngine.Debug.LogError(Error); + } +} +``` + +--- + +## 共享 Web 操作类 + +### LoadWebNormalAssetBundleOperation + +普通(未加密)AssetBundle 的 Web 加载操作。 + +``` +CreateRequest + └── DownloadAssetBundleRequest + ├── URL: 主地址或备用地址(轮换) + ├── DisableUnityWebCache: 是否禁用缓存 + ├── FileHash: 用于 Unity 缓存键 + └── UnityCRC: CRC 验证 + ↓ +CheckRequest + ├── 成功 → Result = AssetBundle + └── 失败 → TryAgain(重试)或 Failed +``` + +#### Unity Web 缓存机制 + +```csharp +// 使用 Unity 的内置缓存 +var args = new DownloadAssetBundleRequestArgs( + url, + timeout: 0, + watchdogTime: 0, + disableUnityWebCache: _disableUnityWebCache, + cacheHash: _bundle.FileHash, // 缓存键 + unityCRC: _bundle.UnityCRC // CRC 验证 +); +``` + +### LoadWebEncryptAssetBundleOperation + +加密 AssetBundle 的 Web 加载操作。 + +``` +CreateRequest + └── 检查 DecryptionServices + ├── null → Failed + └── 有效 → DownloadBytesRequest + ↓ +CheckRequest + ├── 下载成功 → LoadEncryptedAssetBundle() + │ └── IWebDecryptionServices.LoadAssetBundle(fileData) + │ ├── 解密成功 → Result = AssetBundle + │ └── 解密失败 → Failed + └── 下载失败 → TryAgain 或 Failed +``` + +#### 加密资源加载 + +```csharp +private AssetBundle LoadEncryptedAssetBundle(byte[] fileData) +{ + var fileInfo = new WebDecryptFileInfo(); + fileInfo.BundleName = _bundle.BundleName; + fileInfo.FileLoadCRC = _bundle.UnityCRC; + fileInfo.FileData = fileData; // 下载的原始字节 + var decryptResult = _decryptionServices.LoadAssetBundle(fileInfo); + return decryptResult.Result; +} +``` + +--- + +## 失败重试机制 + +Web 加载操作内置失败重试机制: + +```csharp +// 检测下载结果 +if (_unityAssetBundleRequestOp.Status == EDownloadRequestStatus.Succeed) +{ + _steps = ESteps.Done; + Status = EOperationStatus.Succeed; + Result = _unityAssetBundleRequestOp.Result; +} +else +{ + if (_failedTryAgain > 0) + { + _steps = ESteps.TryAgain; + YooLogger.Warning($"Failed download : {url} Try again !"); + } + else + { + _steps = ESteps.Done; + Status = EOperationStatus.Failed; + Error = _unityAssetBundleRequestOp.Error; + } +} + +// 重新尝试下载(1秒后) +if (_steps == ESteps.TryAgain) +{ + _tryAgainTimer += Time.unscaledDeltaTime; + if (_tryAgainTimer > 1f) + { + _tryAgainTimer = 0f; + _failedTryAgain--; + _steps = ESteps.CreateRequest; // 重新创建请求 + } +} +``` + +--- + +## 使用示例 + +### 基础配置 + +```csharp +// 创建远程服务接口 +class GameRemoteServices : IRemoteServices +{ + public string GetRemoteMainURL(string fileName) + { + return $"https://cdn.example.com/bundles/{fileName}"; + } + public string GetRemoteFallbackURL(string fileName) + { + return $"https://cdn-backup.example.com/bundles/{fileName}"; + } +} + +// 创建 Web 远程文件系统参数 +var webRemoteParams = FileSystemParameters.CreateDefaultWebRemoteFileSystemParameters( + remoteServices: new GameRemoteServices() +); + +// 初始化包裹(WebGL 模式) +var initParams = new WebPlayModeParameters(); +initParams.WebServerFileSystemParameters = webServerParams; +initParams.WebRemoteFileSystemParameters = webRemoteParams; +var initOp = package.InitializeAsync(initParams); +``` + +### 禁用 Unity Web 缓存 + +```csharp +var webRemoteParams = FileSystemParameters.CreateDefaultWebRemoteFileSystemParameters( + remoteServices: new GameRemoteServices() +); + +// 禁用 Unity 的 Web 请求缓存(始终获取最新资源) +webRemoteParams.AddParameter(FileSystemParametersDefine.DISABLE_UNITY_WEB_CACHE, true); +``` + +### 配置 Web 解密服务 + +```csharp +// 自定义 Web 解密服务 +class GameWebDecryptionServices : IWebDecryptionServices +{ + public WebDecryptResult LoadAssetBundle(WebDecryptFileInfo fileInfo) + { + // 解密下载的字节数据 + byte[] decryptedData = Decrypt(fileInfo.FileData); + AssetBundle bundle = AssetBundle.LoadFromMemory(decryptedData); + return new WebDecryptResult { Result = bundle }; + } +} + +var webRemoteParams = FileSystemParameters.CreateDefaultWebRemoteFileSystemParameters( + remoteServices: new GameRemoteServices() +); + +// 设置 Web 解密服务 +webRemoteParams.AddParameter( + FileSystemParametersDefine.DECRYPTION_SERVICES, + new GameWebDecryptionServices() +); +``` + +### 跨域资源加载 + +```csharp +// 跨域远程服务 +class CrossDomainRemoteServices : IRemoteServices +{ + private readonly string _mainDomain; + private readonly string _fallbackDomain; + + public CrossDomainRemoteServices(string mainDomain, string fallbackDomain) + { + _mainDomain = mainDomain; + _fallbackDomain = fallbackDomain; + } + + public string GetRemoteMainURL(string fileName) + { + // 主 CDN 域名 + return $"https://{_mainDomain}/assets/{fileName}"; + } + + public string GetRemoteFallbackURL(string fileName) + { + // 备用 CDN 域名 + return $"https://{_fallbackDomain}/assets/{fileName}"; + } +} + +// 使用跨域服务 +var remoteServices = new CrossDomainRemoteServices( + mainDomain: "cdn-us.example.com", + fallbackDomain: "cdn-eu.example.com" +); + +var webRemoteParams = FileSystemParameters.CreateDefaultWebRemoteFileSystemParameters( + remoteServices: remoteServices +); +``` + +--- + +## 参数常量 + +```csharp +// Unity 缓存控制 +FileSystemParametersDefine.DISABLE_UNITY_WEB_CACHE // bool: 禁用 Unity Web 缓存 + +// 服务接口 +FileSystemParametersDefine.REMOTE_SERVICES // IRemoteServices: 远程服务接口 +FileSystemParametersDefine.DECRYPTION_SERVICES // IWebDecryptionServices: Web 解密服务 +FileSystemParametersDefine.MANIFEST_SERVICES // IManifestRestoreServices: 清单服务 +``` + +--- + +## 类继承关系 + +``` +IFileSystem + └── DefaultWebRemoteFileSystem + +FSInitializeFileSystemOperation + └── DWRFSInitializeOperation + +FSRequestPackageVersionOperation + └── DWRFSRequestPackageVersionOperation + +FSLoadPackageManifestOperation + └── DWRFSLoadPackageManifestOperation + +FSLoadBundleOperation + └── DWRFSLoadAssetBundleOperation + +AsyncOperationBase + ├── LoadWebAssetBundleOperation (abstract) + │ ├── LoadWebNormalAssetBundleOperation + │ └── LoadWebEncryptAssetBundleOperation + ├── RequestWebPackageVersionOperation + ├── RequestWebPackageHashOperation + └── LoadWebPackageManifestOperation + +BundleResult + └── AssetBundleResult +``` + +--- + +## 与其他文件系统对比 + +| 特性 | DefaultWebRemoteFileSystem | DefaultCacheFileSystem | DefaultWebServerFileSystem | +|------|---------------------------|------------------------|---------------------------| +| 本地缓存 | ❌ 无 | ✅ 有 | ❌ 无 | +| 支持 RawBundle | ❌ | ✅ | ❌ | +| 同步加载 | ❌ | ✅ | ❌ | +| 断点续传 | ❌ | ✅ | ❌ | +| 跨域支持 | ✅ | ✅ | ✅ | +| 适用场景 | WebGL 跨域 | 常规游戏 | WebGL 同域 | + +--- + +## 注意事项 + +1. **仅支持 AssetBundle**:不支持 RawBundle 类型的资源加载 +2. **不支持同步加载**:WebGL 平台限制,`WaitForAsyncComplete()` 会直接返回失败 +3. **无本地缓存**:每次加载都从远程获取,注意网络流量 +4. **部分方法未实现**:`DownloadFileAsync`、`GetBundleFilePath`、`ReadBundleFileData`、`ReadBundleFileText` 会抛出异常 +5. **远程服务必需**:必须配置 `IRemoteServices` 接口 +6. **Unity 缓存**:默认使用 Unity 的 Web 请求缓存,可通过参数禁用 +7. **加密资源**:加密资源需要配置 `IWebDecryptionServices`(注意是 Web 专用接口,非 `IDecryptionServices`) diff --git a/Assets/YooAsset/Runtime/FileSystem/DefaultWebRemoteFileSystem/README.md.meta b/Assets/YooAsset/Runtime/FileSystem/DefaultWebRemoteFileSystem/README.md.meta new file mode 100644 index 00000000..b752b4fb --- /dev/null +++ b/Assets/YooAsset/Runtime/FileSystem/DefaultWebRemoteFileSystem/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9f6744d0bcd43f84ea4279925784afb2 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Runtime/FileSystem/DefaultWebServerFileSystem/README.md b/Assets/YooAsset/Runtime/FileSystem/DefaultWebServerFileSystem/README.md new file mode 100644 index 00000000..9cfffd8c --- /dev/null +++ b/Assets/YooAsset/Runtime/FileSystem/DefaultWebServerFileSystem/README.md @@ -0,0 +1,525 @@ +# DefaultWebServerFileSystem Web服务器文件系统 + +## 模块概述 + +DefaultWebServerFileSystem 是 YooAsset 的 **Web 服务器文件系统**,专为 WebGL 平台的**同域资源加载**而设计。该文件系统从与 WebGL 构建相同的服务器(StreamingAssets 目录)加载资源,通过 Catalog 目录文件管理内置资源清单。 + +### 核心特性 + +- **同域加载**:从 WebGL 构建所在服务器加载资源 +- **Catalog 管理**:通过目录文件追踪可用资源 +- **路径映射**:自动将本地路径转换为 WWW 路径 +- **Unity 缓存控制**:可选择禁用 Unity 的 Web 请求缓存 +- **加密支持**:支持 `IWebDecryptionServices` 解密 Web 资源 + +--- + +## 设计目标 + +| 目标 | 说明 | +|------|------| +| **WebGL 内置资源** | 加载与 WebGL 构建一起部署的资源 | +| **资源追踪** | 通过 Catalog 文件精确知道哪些资源可用 | +| **无跨域问题** | 从同一服务器加载,避免 CORS 问题 | +| **与 Buildin 对应** | WebGL 版本的 DefaultBuildinFileSystem | + +--- + +## 文件结构 + +``` +DefaultWebServerFileSystem/ +├── DefaultWebServerFileSystem.cs # 文件系统主类 +└── Operation/ # 操作类 + ├── DWSFSInitializeOperation.cs # 初始化操作 + ├── DWSFSRequestPackageVersionOperation.cs # 请求版本操作 + ├── DWSFSLoadPackageManifestOperation.cs # 加载清单操作 + ├── DWSFSLoadBundleOperation.cs # 加载资源包操作 + └── internal/ # 内部操作类 + ├── LoadWebServerCatalogFileOperation.cs # 加载目录文件 + ├── RequestWebServerPackageVersionOperation.cs # 请求版本文件 + ├── RequestWebServerPackageHashOperation.cs # 请求哈希文件 + └── LoadWebServerPackageManifestOperation.cs # 加载清单文件 +``` + +### 依赖的共享模块 + +DefaultWebServerFileSystem 依赖 `WebGame` 目录下的共享操作类: + +``` +FileSystem/WebGame/Operation/ +├── LoadWebAssetBundleOperation.cs # Web 资源包加载基类 +├── LoadWebNormalAssetBundleOperation.cs # 普通资源包加载 +└── LoadWebEncryptAssetBundleOperation.cs # 加密资源包加载 +``` + +--- + +## 核心类说明 + +### DefaultWebServerFileSystem + +Web 服务器文件系统的主类,实现 `IFileSystem` 接口。 + +#### 内部类 + +```csharp +public class FileWrapper +{ + public string FileName { private set; get; } + + public FileWrapper(string fileName) + { + FileName = fileName; + } +} +``` + +#### 基本属性 + +| 属性 | 类型 | 说明 | +|------|------|------| +| `PackageName` | `string` | 包裹名称 | +| `FileRoot` | `string` | Web 包裹根目录 | +| `FileCount` | `int` | 始终返回 0 | +| `DownloadBackend` | `IDownloadBackend` | 下载后台接口 | + +#### 自定义参数 + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `DisableUnityWebCache` | `bool` | `false` | 禁用 Unity 的网络缓存 | +| `DecryptionServices` | `IWebDecryptionServices` | `null` | Web 解密服务接口 | +| `ManifestServices` | `IManifestRestoreServices` | `null` | 清单服务接口 | + +#### 核心方法 + +```csharp +// 生命周期 +void OnCreate(string packageName, string packageRoot); +void OnDestroy(); +void SetParameter(string name, object value); + +// 异步操作 +FSInitializeFileSystemOperation InitializeFileSystemAsync(); +FSRequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks, int timeout); +FSLoadPackageManifestOperation LoadPackageManifestAsync(string packageVersion, int timeout); +FSLoadBundleOperation LoadBundleFile(PackageBundle bundle); +FSClearCacheFilesOperation ClearCacheFilesAsync(...); // 直接返回完成 + +// 不支持的操作 +FSDownloadFileOperation DownloadFileAsync(...); // 抛出 NotImplementedException +string GetBundleFilePath(...); // 抛出 NotImplementedException +byte[] ReadBundleFileData(...); // 抛出 NotImplementedException +string ReadBundleFileText(...); // 抛出 NotImplementedException + +// 文件查询(基于 Catalog) +bool Belong(PackageBundle bundle); // 检查是否在 _wrappers 字典中 +bool Exists(PackageBundle bundle); // 检查是否在 _wrappers 字典中 +bool NeedDownload(PackageBundle bundle);// 始终返回 false +bool NeedUnpack(PackageBundle bundle); // 始终返回 false +bool NeedImport(PackageBundle bundle); // 始终返回 false +``` + +#### 内部方法 + +```csharp +// 路径获取 +string GetDefaultWebPackageRoot(string packageName); // 默认包裹根目录 +string GetWebFileLoadPath(PackageBundle bundle); // 资源文件加载路径 +string GetWebPackageVersionFilePath(); // 版本文件路径 +string GetWebPackageHashFilePath(string packageVersion); // 哈希文件路径 +string GetWebPackageManifestFilePath(string packageVersion); // 清单文件路径 +string GetCatalogBinaryFileLoadPath(); // Catalog 文件路径 + +// Catalog 管理 +bool RecordCatalogFile(string bundleGUID, FileWrapper wrapper); +``` + +--- + +## Catalog 目录系统 + +DefaultWebServerFileSystem 使用 Catalog 文件追踪可用的内置资源,这与 DefaultBuildinFileSystem 的机制相同。 + +### Catalog 文件 + +| 文件 | 路径 | 说明 | +|------|------|------| +| Catalog 二进制文件 | `{PackageRoot}/{PackageName}_buildin.bytes` | 资源目录信息 | + +### Catalog 数据结构 + +```csharp +// _wrappers 字典:BundleGUID → FileWrapper +protected readonly Dictionary _wrappers; + +// FileWrapper 包含文件名信息 +public class FileWrapper +{ + public string FileName { private set; get; } +} +``` + +### Belong 与 Exists 判断 + +```csharp +public virtual bool Belong(PackageBundle bundle) +{ + // 检查 Catalog 中是否包含该资源 + return _wrappers.ContainsKey(bundle.BundleGUID); +} + +public virtual bool Exists(PackageBundle bundle) +{ + // 同样基于 Catalog 判断 + return _wrappers.ContainsKey(bundle.BundleGUID); +} +``` + +--- + +## 操作类说明 + +### DWSFSInitializeOperation + +初始化操作,加载 Catalog 目录文件。 + +``` +状态流程: +LoadCatalogFile + └── LoadWebServerCatalogFileOperation + └── 请求 {PackageName}_buildin.bytes + ├── 下载二进制数据 + └── 反序列化 Catalog + ├── 验证 PackageName + └── 遍历 Wrappers + └── RecordCatalogFile() + ├── 成功 → Succeed + └── 失败 → Failed +``` + +### LoadWebServerCatalogFileOperation + +加载 Web 服务器 Catalog 文件的内部操作。 + +```csharp +// 关键流程 +if (_steps == ESteps.LoadCatalog) +{ + var catalog = CatalogTools.DeserializeFromBinary(_webDataRequestOp.Result); + + // 验证包裹名称 + if (catalog.PackageName != _fileSystem.PackageName) + { + Error = $"Catalog file package name {catalog.PackageName} cannot match..."; + return; + } + + // 记录所有内置资源 + foreach (var wrapper in catalog.Wrappers) + { + var fileWrapper = new DefaultWebServerFileSystem.FileWrapper(wrapper.FileName); + _fileSystem.RecordCatalogFile(wrapper.BundleGUID, fileWrapper); + } +} +``` + +### DWSFSRequestPackageVersionOperation + +请求包裹版本操作,从 Web 服务器获取版本文件。 + +``` +状态流程: +RequestPackageVersion + └── RequestWebServerPackageVersionOperation + └── 请求 {FileRoot}/{PackageName}_Version.txt + ├── 转换为 WWW 路径 + └── 下载文本内容 + ├── 成功 → PackageVersion + └── 失败 → Failed +``` + +### DWSFSLoadPackageManifestOperation + +加载资源清单操作,从 Web 服务器加载并解析清单。 + +``` +状态流程: +RequestWebPackageHash + └── RequestWebServerPackageHashOperation + └── 请求 {PackageName}_{Version}.hash + ├── 成功 → PackageHash + └── 失败 → Failed + ↓ +LoadWebPackageManifest + └── LoadWebServerPackageManifestOperation + ├── 请求 {PackageName}_{Version}.bytes + ├── 验证哈希 + └── 反序列化清单 + ├── 成功 → Manifest + └── 失败 → Failed +``` + +### LoadWebServerPackageManifestOperation + +加载清单文件的内部操作,包含哈希验证。 + +``` +状态流程: +RequestFileData + └── DownloadBytesRequest + └── 下载清单二进制数据 + ↓ +VerifyFileData + └── ManifestTools.VerifyManifestData() + ├── 验证成功 → LoadManifest + └── 验证失败 → Failed + ↓ +LoadManifest + └── DeserializeManifestOperation + ├── 反序列化成功 → Manifest → Succeed + └── 反序列化失败 → Failed +``` + +### DWSFSLoadAssetBundleOperation + +加载资源包操作,从 Web 服务器加载 AssetBundle。 + +``` +状态流程: +LoadWebAssetBundle + ├── 获取文件路径 → 转换为 WWW 路径 + │ + ├── 未加密 → LoadWebNormalAssetBundleOperation + │ └── UnityWebRequestAssetBundle + │ ├── 成功 → AssetBundleResult + │ └── 失败 → Failed + │ + └── 已加密 → LoadWebEncryptAssetBundleOperation + └── DownloadBytesRequest + └── IWebDecryptionServices.LoadAssetBundle() + ├── 成功 → AssetBundleResult + └── 失败 → Failed +``` + +#### 路径转换 + +```csharp +// 获取本地文件路径 +string fileLoadPath = _fileSystem.GetWebFileLoadPath(_bundle); + +// 转换为 WWW 请求路径 +string mainURL = DownloadSystemHelper.ConvertToWWWPath(fileLoadPath); + +// 主 URL 和备用 URL 相同(同域加载) +DownloadFileOptions options = new DownloadFileOptions(int.MaxValue); +options.SetURL(mainURL, mainURL); +``` + +#### 同步加载限制 + +```csharp +internal override void InternalWaitForAsyncComplete() +{ + if (_steps != ESteps.Done) + { + _steps = ESteps.Done; + Status = EOperationStatus.Failed; + Error = "WebGL platform not support sync load method !"; + UnityEngine.Debug.LogError(Error); + } +} +``` + +--- + +## 路径映射机制 + +DefaultWebServerFileSystem 使用路径缓存优化性能: + +```csharp +// 文件路径缓存 +protected readonly Dictionary _webFilePathMapping = new Dictionary(10000); + +public string GetWebFileLoadPath(PackageBundle bundle) +{ + if (_webFilePathMapping.TryGetValue(bundle.BundleGUID, out string filePath) == false) + { + // 组合路径:{WebPackageRoot}/{FileName} + filePath = PathUtility.Combine(_webPackageRoot, bundle.FileName); + _webFilePathMapping.Add(bundle.BundleGUID, filePath); + } + return filePath; +} +``` + +### 默认路径 + +```csharp +protected string GetDefaultWebPackageRoot(string packageName) +{ + // 使用默认的内置资源根目录(StreamingAssets) + string rootDirectory = YooAssetSettingsData.GetYooDefaultBuildinRoot(); + return PathUtility.Combine(rootDirectory, packageName); +} +``` + +--- + +## 与 DefaultWebRemoteFileSystem 对比 + +| 特性 | DefaultWebServerFileSystem | DefaultWebRemoteFileSystem | +|------|---------------------------|---------------------------| +| 用途 | WebGL 同域内置资源 | WebGL 跨域远程资源 | +| Catalog 系统 | ✅ 有 | ❌ 无 | +| Belong/Exists | 基于 Catalog 判断 | 始终返回 true | +| 远程服务 | ❌ 不需要 | ✅ 需要 IRemoteServices | +| URL 生成 | 本地路径转 WWW | 远程服务接口生成 | +| 主/备用地址 | 相同(同域) | 不同(可配置) | + +--- + +## 使用示例 + +### 基础配置 + +```csharp +// 创建 Web 服务器文件系统参数 +var webServerParams = FileSystemParameters.CreateDefaultWebServerFileSystemParameters(); + +// 初始化包裹(WebGL 模式) +var initParams = new WebPlayModeParameters(); +initParams.WebServerFileSystemParameters = webServerParams; +initParams.WebRemoteFileSystemParameters = webRemoteParams; // 可选:跨域资源 +var initOp = package.InitializeAsync(initParams); +``` + +### 自定义包裹根目录 + +```csharp +// 创建参数时指定自定义路径 +var webServerParams = FileSystemParameters.CreateDefaultWebServerFileSystemParameters( + packageRoot: "Assets/StreamingAssets/MyCustomPath/DefaultPackage" +); +``` + +### 禁用 Unity Web 缓存 + +```csharp +var webServerParams = FileSystemParameters.CreateDefaultWebServerFileSystemParameters(); + +// 禁用 Unity 的 Web 请求缓存 +webServerParams.AddParameter(FileSystemParametersDefine.DISABLE_UNITY_WEB_CACHE, true); +``` + +### 配置 Web 解密服务 + +```csharp +// 自定义 Web 解密服务 +class GameWebDecryptionServices : IWebDecryptionServices +{ + public WebDecryptResult LoadAssetBundle(WebDecryptFileInfo fileInfo) + { + byte[] decryptedData = Decrypt(fileInfo.FileData); + AssetBundle bundle = AssetBundle.LoadFromMemory(decryptedData); + return new WebDecryptResult { Result = bundle }; + } +} + +var webServerParams = FileSystemParameters.CreateDefaultWebServerFileSystemParameters(); + +// 设置 Web 解密服务 +webServerParams.AddParameter( + FileSystemParametersDefine.DECRYPTION_SERVICES, + new GameWebDecryptionServices() +); +``` + +### WebGL 双文件系统配置 + +```csharp +// WebGL 典型配置:内置 + 远程 +var webServerParams = FileSystemParameters.CreateDefaultWebServerFileSystemParameters(); +var webRemoteParams = FileSystemParameters.CreateDefaultWebRemoteFileSystemParameters( + remoteServices: new GameRemoteServices() +); + +var initParams = new WebPlayModeParameters(); +initParams.WebServerFileSystemParameters = webServerParams; // 同域内置资源 +initParams.WebRemoteFileSystemParameters = webRemoteParams; // 跨域热更资源 + +var initOp = package.InitializeAsync(initParams); +``` + +--- + +## 参数常量 + +```csharp +// Unity 缓存控制 +FileSystemParametersDefine.DISABLE_UNITY_WEB_CACHE // bool: 禁用 Unity Web 缓存 + +// 服务接口 +FileSystemParametersDefine.DECRYPTION_SERVICES // IWebDecryptionServices: Web 解密服务 +FileSystemParametersDefine.MANIFEST_SERVICES // IManifestRestoreServices: 清单服务 +``` + +--- + +## 类继承关系 + +``` +IFileSystem + └── DefaultWebServerFileSystem + +FSInitializeFileSystemOperation + └── DWSFSInitializeOperation + +FSRequestPackageVersionOperation + └── DWSFSRequestPackageVersionOperation + +FSLoadPackageManifestOperation + └── DWSFSLoadPackageManifestOperation + +FSLoadBundleOperation + └── DWSFSLoadAssetBundleOperation + +AsyncOperationBase + ├── LoadWebServerCatalogFileOperation + ├── RequestWebServerPackageVersionOperation + ├── RequestWebServerPackageHashOperation + ├── LoadWebServerPackageManifestOperation + └── LoadWebAssetBundleOperation (共享) + ├── LoadWebNormalAssetBundleOperation + └── LoadWebEncryptAssetBundleOperation + +BundleResult + └── AssetBundleResult +``` + +--- + +## 与 DefaultBuildinFileSystem 对比 + +| 特性 | DefaultWebServerFileSystem | DefaultBuildinFileSystem | +|------|---------------------------|-------------------------| +| 平台 | WebGL | 非 WebGL(移动端、PC) | +| 加载方式 | UnityWebRequest | AssetBundle.LoadFromFile | +| Catalog 系统 | ✅ 相同机制 | ✅ 相同机制 | +| 同步加载 | ❌ 不支持 | ✅ 支持 | +| 解包机制 | ❌ 无 | ✅ 有(Android APK) | +| 文件访问 | WWW 路径 | 本地文件路径 | + +--- + +## 注意事项 + +1. **WebGL 专用**:此文件系统专为 WebGL 平台设计,非 WebGL 平台应使用 DefaultBuildinFileSystem +2. **仅支持 AssetBundle**:不支持 RawBundle 类型的资源加载 +3. **不支持同步加载**:WebGL 平台限制,`WaitForAsyncComplete()` 会直接返回失败 +4. **Catalog 必需**:初始化时必须成功加载 Catalog 文件,否则无法确定资源归属 +5. **同域加载**:资源文件必须与 WebGL 构建在同一服务器上 +6. **部分方法未实现**:`DownloadFileAsync`、`GetBundleFilePath`、`ReadBundleFileData`、`ReadBundleFileText` 会抛出异常 +7. **加密资源**:加密资源需要配置 `IWebDecryptionServices`(Web 专用接口) +8. **路径缓存**:内部使用字典缓存路径映射,提升重复访问性能 diff --git a/Assets/YooAsset/Runtime/FileSystem/DefaultWebServerFileSystem/README.md.meta b/Assets/YooAsset/Runtime/FileSystem/DefaultWebServerFileSystem/README.md.meta new file mode 100644 index 00000000..fdb00a4d --- /dev/null +++ b/Assets/YooAsset/Runtime/FileSystem/DefaultWebServerFileSystem/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ac4e3eb80a469df408bb2af7d57b3f8c +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Runtime/FileSystem/README.md b/Assets/YooAsset/Runtime/FileSystem/README.md new file mode 100644 index 00000000..e6df0e99 --- /dev/null +++ b/Assets/YooAsset/Runtime/FileSystem/README.md @@ -0,0 +1,464 @@ +# FileSystem 文件系统模块 + +## 模块概述 + +FileSystem 是 YooAsset 资源管理系统的**文件访问抽象层**,负责统一管理不同来源的资源文件访问。该模块采用策略模式设计,通过 `IFileSystem` 接口抽象不同的文件存储策略,支持编辑器模拟、内置资源、缓存资源、WebGL 等多种场景。 + +### 核心职责 + +- 统一的文件系统接口抽象 +- 资源包文件的加载和管理 +- 资源清单的请求和加载 +- 缓存文件的验证和清理 +- 资源下载的调度和管理 + +--- + +## 设计目标 + +| 目标 | 说明 | +|------|------| +| **统一抽象** | 通过 IFileSystem 接口统一不同来源的文件访问 | +| **策略模式** | 可插拔的文件系统实现,支持自定义扩展 | +| **多场景支持** | 编辑器、单机、联机、WebGL 等多种运行模式 | +| **灵活配置** | 通过参数系统支持丰富的自定义配置 | + +--- + +## 架构概念 + +### 系统架构 + +``` +┌─────────────────────────────────────────────────────────┐ +│ ResourcePackage │ +│ (资源包管理) │ +└─────────────────────────┬───────────────────────────────┘ + │ +┌─────────────────────────▼───────────────────────────────┐ +│ PlayModeImpl │ +│ (运行模式实现) │ +│ 管理多个 IFileSystem,按优先级查询 │ +└─────────────────────────┬───────────────────────────────┘ + │ +┌─────────────────────────▼───────────────────────────────┐ +│ IFileSystem │ +│ (文件系统接口) │ +├─────────────────────────────────────────────────────────┤ +│ DefaultEditorFileSystem │ 编辑器模拟文件系统 │ +│ DefaultBuildinFileSystem │ 内置资源文件系统 │ +│ DefaultCacheFileSystem │ 缓存资源文件系统 │ +│ DefaultUnpackFileSystem │ 解压资源文件系统 │ +│ DefaultWebServerFileSystem │ WebGL 服务器文件系统 │ +│ DefaultWebRemoteFileSystem │ WebGL 远程文件系统 │ +└─────────────────────────────────────────────────────────┘ + │ +┌─────────────────────────▼───────────────────────────────┐ +│ BundleResult │ +│ (资源包加载结果) │ +├─────────────────────────────────────────────────────────┤ +│ AssetBundleResult │ Unity AssetBundle 加载结果 │ +│ RawBundleResult │ 原生文件加载结果 │ +│ VirtualBundleResult│ 虚拟资源包结果(编辑器模拟) │ +└─────────────────────────────────────────────────────────┘ +``` + +### 核心组件 + +- **IFileSystem**: 文件系统核心接口,定义所有文件操作的契约 +- **FileSystemParameters**: 文件系统参数配置,支持自定义参数注入 +- **BundleResult**: 资源包加载结果抽象,封装不同类型的加载结果 +- **FSOperation**: 文件系统操作基类,定义异步操作的抽象 + +--- + +## 文件结构 + +``` +FileSystem/ +├── Interface/ # 接口定义 +│ └── IFileSystem.cs # 文件系统核心接口 +│ +├── Operation/ # 操作基类定义 +│ ├── FSInitializeFileSystemOperation.cs # 初始化操作 +│ ├── FSRequestPackageVersionOperation.cs # 请求版本操作 +│ ├── FSLoadPackageManifestOperation.cs # 加载清单操作 +│ ├── FSLoadBundleOperation.cs # 加载资源包操作 +│ ├── FSDownloadFileOperation.cs # 下载文件操作 +│ ├── FSClearCacheFilesOperation.cs # 清理缓存操作 +│ ├── FSLoadAssetOperation.cs # 加载资源操作 +│ ├── FSLoadAllAssetsOperation.cs # 加载所有资源操作 +│ ├── FSLoadSubAssetsOperation.cs # 加载子资源操作 +│ └── FSLoadSceneOperation.cs # 加载场景操作 +│ +├── BundleResult/ # 资源包加载结果 +│ ├── BundleResult.cs # 结果基类 +│ ├── AssetBundleResult/ # AssetBundle 结果实现 +│ ├── RawBundleResult/ # 原生文件结果实现 +│ └── VirtualBundleResult/ # 虚拟资源包结果实现 +│ +├── WebGame/ # WebGL 相关操作 +│ └── Operation/ # WebGL 专用操作类 +│ +├── FileSystemParameters.cs # 文件系统参数 +├── FileSystemParametersDefine.cs # 参数名称常量定义 +├── FileVerifyHelper.cs # 文件校验辅助类 +├── EFileVerifyLevel.cs # 文件校验等级枚举 +├── EFileVerifyResult.cs # 文件校验结果枚举 +└── EFileClearMode.cs # 文件清理模式枚举 +``` + +--- + +## 核心接口 + +### IFileSystem(文件系统接口) + +定义文件系统的核心契约,所有文件系统实现都必须实现此接口。 + +```csharp +internal interface IFileSystem +{ + // 基本属性 + string PackageName { get; } // 包裹名称 + string FileRoot { get; } // 文件根目录 + int FileCount { get; } // 文件数量 + + // 生命周期 + void OnCreate(string packageName, string packageRoot); + void OnDestroy(); + void SetParameter(string name, object value); + + // 异步操作 + FSInitializeFileSystemOperation InitializeFileSystemAsync(); + FSRequestPackageVersionOperation RequestPackageVersionAsync(bool appendTimeTicks, int timeout); + FSLoadPackageManifestOperation LoadPackageManifestAsync(string packageVersion, int timeout); + FSLoadBundleOperation LoadBundleFile(PackageBundle bundle); + FSDownloadFileOperation DownloadFileAsync(PackageBundle bundle, DownloadFileOptions options); + FSClearCacheFilesOperation ClearCacheFilesAsync(PackageManifest manifest, ClearCacheFilesOptions options); + + // 文件查询 + bool Belong(PackageBundle bundle); // 查询文件归属 + bool Exists(PackageBundle bundle); // 查询文件是否存在 + bool NeedDownload(PackageBundle bundle);// 是否需要下载 + bool NeedUnpack(PackageBundle bundle); // 是否需要解压 + bool NeedImport(PackageBundle bundle); // 是否需要导入 + + // 文件访问 + string GetBundleFilePath(PackageBundle bundle); + byte[] ReadBundleFileData(PackageBundle bundle); + string ReadBundleFileText(PackageBundle bundle); +} +``` + +--- + +## 操作类定义 + +### 文件系统操作基类 + +所有文件系统操作都继承自 `AsyncOperationBase`,提供异步执行能力。 + +| 操作类 | 说明 | 输出属性 | +|--------|------|----------| +| `FSInitializeFileSystemOperation` | 初始化文件系统 | - | +| `FSRequestPackageVersionOperation` | 请求包裹版本 | `PackageVersion` | +| `FSLoadPackageManifestOperation` | 加载资源清单 | `Manifest` | +| `FSLoadBundleOperation` | 加载资源包文件 | `Result`, `DownloadProgress`, `DownloadedBytes` | +| `FSDownloadFileOperation` | 下载文件 | `DownloadedBytes`, `DownloadProgress` | +| `FSClearCacheFilesOperation` | 清理缓存文件 | - | + +### 资源加载操作基类 + +| 操作类 | 说明 | 输出属性 | +|--------|------|----------| +| `FSLoadAssetOperation` | 加载单个资源 | `Result` (Object) | +| `FSLoadAllAssetsOperation` | 加载所有资源 | `Result` (Object[]) | +| `FSLoadSubAssetsOperation` | 加载子资源 | `Result` (Object[]) | +| `FSLoadSceneOperation` | 加载场景 | `Result` (Scene) | + +--- + +## BundleResult 资源包结果 + +### 基类定义 + +```csharp +internal abstract class BundleResult +{ + // 资源包管理 + public abstract void UnloadBundleFile(); + public abstract string GetBundleFilePath(); + public abstract byte[] ReadBundleFileData(); + public abstract string ReadBundleFileText(); + + // 资源加载 + public abstract FSLoadAssetOperation LoadAssetAsync(AssetInfo assetInfo); + public abstract FSLoadAllAssetsOperation LoadAllAssetsAsync(AssetInfo assetInfo); + public abstract FSLoadSubAssetsOperation LoadSubAssetsAsync(AssetInfo assetInfo); + public abstract FSLoadSceneOperation LoadSceneOperation(AssetInfo assetInfo, LoadSceneParameters loadParams, bool suspendLoad); +} +``` + +### 实现类型 + +| 类型 | 说明 | 使用场景 | +|------|------|----------| +| `AssetBundleResult` | Unity AssetBundle 结果 | 正常构建的 AssetBundle 文件 | +| `RawBundleResult` | 原生文件结果 | 原生文件(非 AssetBundle) | +| `VirtualBundleResult` | 虚拟资源包结果 | 编辑器模拟模式 | + +--- + +## 枚举定义 + +### EFileVerifyLevel(文件校验等级) + +```csharp +public enum EFileVerifyLevel +{ + Low = 1, // 仅验证文件存在 + Middle = 2, // 验证文件大小 + High = 3 // 验证文件大小和 CRC +} +``` + +### EFileVerifyResult(文件校验结果) + +```csharp +internal enum EFileVerifyResult +{ + Exception = -7, // 验证异常 + CacheNotFound = -6, // 未找到缓存信息 + InfoFileNotExisted = -5, // 信息文件不存在 + DataFileNotExisted = -4, // 数据文件不存在 + FileNotComplete = -3, // 文件内容不足 + FileOverflow = -2, // 文件内容溢出 + FileCrcError = -1, // 文件 CRC 不匹配 + None = 0, // 默认状态 + Succeed = 1 // 验证成功 +} +``` + +### EFileClearMode(文件清理模式) + +```csharp +public enum EFileClearMode +{ + ClearAllBundleFiles, // 清理所有资源文件 + ClearUnusedBundleFiles, // 清理未使用的资源文件 + ClearBundleFilesByLocations,// 按地址清理资源文件 + ClearBundleFilesByTags, // 按标签清理资源文件 + ClearAllManifestFiles, // 清理所有清单文件 + ClearUnusedManifestFiles // 清理未使用的清单文件 +} +``` + +--- + +## 参数系统 + +### FileSystemParameters(文件系统参数) + +用于创建和配置文件系统实例。 + +```csharp +public class FileSystemParameters +{ + public string FileSystemClass { get; } // 文件系统类名 + public string PackageRoot { get; } // 包裹根目录 + + public void AddParameter(string name, object value); +} +``` + +### 参数名称常量 + +```csharp +public class FileSystemParametersDefine +{ + // 文件校验 + public const string FILE_VERIFY_LEVEL = "FILE_VERIFY_LEVEL"; + public const string FILE_VERIFY_MAX_CONCURRENCY = "FILE_VERIFY_MAX_CONCURRENCY"; + + // 下载配置 + public const string DOWNLOAD_MAX_CONCURRENCY = "DOWNLOAD_MAX_CONCURRENCY"; + public const string DOWNLOAD_MAX_REQUEST_PER_FRAME = "DOWNLOAD_MAX_REQUEST_PER_FRAME"; + public const string DOWNLOAD_WATCH_DOG_TIME = "DOWNLOAD_WATCH_DOG_TIME"; + public const string RESUME_DOWNLOAD_MINMUM_SIZE = "RESUME_DOWNLOAD_MINMUM_SIZE"; + public const string RESUME_DOWNLOAD_RESPONSE_CODES = "RESUME_DOWNLOAD_RESPONSE_CODES"; + + // 服务接口 + public const string REMOTE_SERVICES = "REMOTE_SERVICES"; + public const string DECRYPTION_SERVICES = "DECRYPTION_SERVICES"; + public const string MANIFEST_SERVICES = "MANIFEST_SERVICES"; + public const string COPY_LOCAL_FILE_SERVICES = "COPY_LOCAL_FILE_SERVICES"; + + // 功能开关 + public const string DISABLE_CATALOG_FILE = "DISABLE_CATALOG_FILE"; + public const string DISABLE_UNITY_WEB_CACHE = "DISABLE_UNITY_WEB_CACHE"; + public const string DISABLE_ONDEMAND_DOWNLOAD = "DISABLE_ONDEMAND_DOWNLOAD"; + public const string APPEND_FILE_EXTENSION = "APPEND_FILE_EXTENSION"; + + // 模拟模式 + public const string VIRTUAL_WEBGL_MODE = "VIRTUAL_WEBGL_MODE"; + public const string VIRTUAL_DOWNLOAD_MODE = "VIRTUAL_DOWNLOAD_MODE"; + public const string VIRTUAL_DOWNLOAD_SPEED = "VIRTUAL_DOWNLOAD_SPEED"; + public const string ASYNC_SIMULATE_MIN_FRAME = "ASYNC_SIMULATE_MIN_FRAME"; + public const string ASYNC_SIMULATE_MAX_FRAME = "ASYNC_SIMULATE_MAX_FRAME"; + + // 其他 + public const string INSTALL_CLEAR_MODE = "INSTALL_CLEAR_MODE"; + public const string COPY_BUILDIN_PACKAGE_MANIFEST = "COPY_BUILDIN_PACKAGE_MANIFEST"; + public const string UNPACK_FILE_SYSTEM_ROOT = "UNPACK_FILE_SYSTEM_ROOT"; +} +``` + +--- + +## 使用示例 + +### 创建文件系统参数 + +```csharp +// 编辑器文件系统 +var editorParams = FileSystemParameters.CreateDefaultEditorFileSystemParameters( + packageRoot: "Assets/GameRes/Bundles" +); + +// 内置文件系统 +var buildinParams = FileSystemParameters.CreateDefaultBuildinFileSystemParameters( + decryptionServices: new MyDecryptionServices() +); + +// 缓存文件系统 +var cacheParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters( + remoteServices: new MyRemoteServices(), + decryptionServices: new MyDecryptionServices() +); + +// WebGL 服务器文件系统 +var webServerParams = FileSystemParameters.CreateDefaultWebServerFileSystemParameters( + disableUnityWebCache: true +); + +// WebGL 远程文件系统 +var webRemoteParams = FileSystemParameters.CreateDefaultWebRemoteFileSystemParameters( + remoteServices: new MyRemoteServices(), + disableUnityWebCache: true +); +``` + +### 添加自定义参数 + +```csharp +var cacheParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters(remoteServices); + +// 设置文件校验级别 +cacheParams.AddParameter(FileSystemParametersDefine.FILE_VERIFY_LEVEL, EFileVerifyLevel.High); + +// 设置下载并发数 +cacheParams.AddParameter(FileSystemParametersDefine.DOWNLOAD_MAX_CONCURRENCY, 16); + +// 设置看门狗时间 +cacheParams.AddParameter(FileSystemParametersDefine.DOWNLOAD_WATCH_DOG_TIME, 30); +``` + +--- + +## 文件系统实现 + +| 文件系统 | 说明 | 适用场景 | +|----------|------|----------| +| `DefaultEditorFileSystem` | 编辑器模拟文件系统 | 编辑器开发,快速迭代 | +| `DefaultBuildinFileSystem` | 内置资源文件系统 | StreamingAssets 内置资源 | +| `DefaultCacheFileSystem` | 缓存资源文件系统 | 下载并缓存的远程资源 | +| `DefaultUnpackFileSystem` | 解压资源文件系统 | 从内置解压到沙盒的资源 | +| `DefaultWebServerFileSystem` | WebGL 服务器文件系统 | WebGL 本地服务器资源 | +| `DefaultWebRemoteFileSystem` | WebGL 远程文件系统 | WebGL 远程 CDN 资源 | + +--- + +## 设计模式 + +### 策略模式 + +通过 `IFileSystem` 接口实现可插拔的文件系统: + +``` +IFileSystem (接口) + │ + ├── DefaultEditorFileSystem + ├── DefaultBuildinFileSystem + ├── DefaultCacheFileSystem + │ └── DefaultUnpackFileSystem (继承) + ├── DefaultWebServerFileSystem + └── DefaultWebRemoteFileSystem +``` + +### 工厂模式 + +`FileSystemParameters` 通过反射创建文件系统实例: + +```csharp +internal IFileSystem CreateFileSystem(string packageName) +{ + Type classType = Type.GetType(FileSystemClass); + var instance = (IFileSystem)Activator.CreateInstance(classType, true); + + foreach (var param in CreateParameters) + instance.SetParameter(param.Key, param.Value); + + instance.OnCreate(packageName, PackageRoot); + return instance; +} +``` + +### 模板方法模式 + +`BundleResult` 定义资源加载的算法骨架,子类实现具体加载逻辑: + +``` +BundleResult (抽象基类) + │ + ├── AssetBundleResult → AssetBundle.LoadAssetAsync + ├── RawBundleResult → 直接读取文件 + └── VirtualBundleResult → AssetDatabase.LoadAssetAtPath +``` + +--- + +## 类继承关系 + +``` +AsyncOperationBase + │ + ├── FSInitializeFileSystemOperation + ├── FSRequestPackageVersionOperation + ├── FSLoadPackageManifestOperation + ├── FSLoadBundleOperation + │ └── FSLoadBundleCompleteOperation + ├── FSDownloadFileOperation + ├── FSClearCacheFilesOperation + │ └── FSClearCacheFilesCompleteOperation + ├── FSLoadAssetOperation + ├── FSLoadAllAssetsOperation + ├── FSLoadSubAssetsOperation + └── FSLoadSceneOperation + +BundleResult + │ + ├── AssetBundleResult + ├── RawBundleResult + └── VirtualBundleResult +``` + +--- + +## 注意事项 + +1. **文件系统选择**:根据运行模式选择合适的文件系统组合 +2. **参数配置**:合理配置校验等级和并发数,平衡性能和安全性 +3. **自定义扩展**:可通过实现 `IFileSystem` 接口创建自定义文件系统 +4. **WebGL 限制**:WebGL 平台不支持持久化缓存,需使用专用文件系统 +5. **解密服务**:加密资源需要提供对应的解密服务实现 diff --git a/Assets/YooAsset/Runtime/FileSystem/README.md.meta b/Assets/YooAsset/Runtime/FileSystem/README.md.meta new file mode 100644 index 00000000..e8b64503 --- /dev/null +++ b/Assets/YooAsset/Runtime/FileSystem/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3775825eb24ba6c46b557a3c96a09397 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Runtime/OperationSystem/README.md b/Assets/YooAsset/Runtime/OperationSystem/README.md new file mode 100644 index 00000000..56ee82f5 --- /dev/null +++ b/Assets/YooAsset/Runtime/OperationSystem/README.md @@ -0,0 +1,635 @@ +# OperationSystem 异步操作系统 + +## 模块概述 + +OperationSystem 是 YooAsset 资源管理系统的**异步操作调度核心**,负责管理所有异步操作的生命周期、调度执行和状态追踪。该模块提供了统一的异步操作抽象,支持协程、async/await、回调等多种异步编程模式。 + +### 核心职责 + +- 异步操作的统一抽象和生命周期管理 +- 基于优先级的操作调度 +- 时间切片执行(防止主线程阻塞) +- 多种异步编程模式支持 +- 操作状态追踪和调试信息收集 + +--- + +## 设计目标 + +| 目标 | 说明 | +|------|------| +| **统一抽象** | 所有异步操作继承同一基类,接口一致 | +| **灵活调度** | 支持优先级排序、时间切片、帧预算控制 | +| **多模式支持** | 协程(IEnumerator)、Task(async/await)、回调 | +| **可调试性** | 完整的状态追踪、耗时统计、层级关系 | +| **线程安全** | 所有调度逻辑在主线程执行 | + +--- + +## 架构概念 + +### 系统架构 + +``` +┌─────────────────────────────────────────────────────────┐ +│ 上层调用者 │ +│ (ResourceManager / FileSystem / 业务层) │ +└─────────────────────────┬───────────────────────────────┘ + │ StartOperation() +┌─────────────────────────▼───────────────────────────────┐ +│ OperationSystem │ +│ (调度器) │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ 优先级队列 │ │ 时间切片 │ │ 回调通知 │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +└─────────────────────────┬───────────────────────────────┘ + │ UpdateOperation() +┌─────────────────────────▼───────────────────────────────┐ +│ AsyncOperationBase │ +│ (操作基类) │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ 状态机 │ │ 子任务管理 │ │ 异步模式 │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +### 核心组件 + +- **OperationSystem**: 静态调度器,管理所有操作的执行 +- **AsyncOperationBase**: 异步操作基类,定义生命周期和状态 +- **GameAsyncOperation**: 游戏层操作基类,提供更友好的 API +- **EOperationStatus**: 操作状态枚举 + +--- + +## 文件结构 + +``` +OperationSystem/ +├── EOperationStatus.cs # 操作状态枚举 +├── AsyncOperationBase.cs # 异步操作基类 +├── OperationSystem.cs # 异步操作调度器 +└── GameAsyncOperation.cs # 游戏层操作基类 +``` + +--- + +## 枚举定义 + +### EOperationStatus(操作状态) + +```csharp +public enum EOperationStatus +{ + None, // 未开始 + Processing, // 处理中 + Succeed, // 已成功 + Failed // 已失败 +} +``` + +**状态转换:** + +``` + StartOperation() InternalUpdate() +None ─────────────────► Processing ─────────────────┬──► Succeed + │ + └──► Failed +``` + +--- + +## 核心类说明 + +### AsyncOperationBase(异步操作基类) + +所有异步操作的抽象基类,实现了 `IEnumerator` 和 `IComparable` 接口。 + +#### 公共属性 + +| 属性 | 类型 | 说明 | +|------|------|------| +| `Priority` | `uint` | 任务优先级(值越大越优先) | +| `Status` | `EOperationStatus` | 当前状态 | +| `Error` | `string` | 错误信息(失败时) | +| `Progress` | `float` | 处理进度(0-1) | +| `PackageName` | `string` | 所属包裹名称 | +| `IsDone` | `bool` | 是否已完成(Succeed 或 Failed) | +| `Task` | `Task` | 用于 async/await | +| `BeginTime` | `string` | 开始时间(调试用) | +| `ProcessTime` | `long` | 处理耗时毫秒(调试用) | + +#### 公共事件 + +```csharp +/// +/// 完成事件(支持后注册立即触发) +/// +public event Action Completed; +``` + +#### 公共方法 + +```csharp +/// +/// 同步等待异步操作完成 +/// +public void WaitForAsyncComplete(); +``` + +#### 内部抽象方法(子类实现) + +| 方法 | 说明 | +|------|------| +| `InternalStart()` | 操作开始时调用 | +| `InternalUpdate()` | 每帧更新时调用 | +| `InternalAbort()` | 操作中止时调用(可选) | +| `InternalWaitForAsyncComplete()` | 同步等待时调用(可选) | +| `InternalGetDesc()` | 获取操作描述(可选) | + +#### 子任务管理 + +```csharp +// 子任务列表 +internal readonly List Childs; + +// 添加/移除子任务 +internal void AddChildOperation(AsyncOperationBase child); +internal void RemoveChildOperation(AsyncOperationBase child); +``` + +--- + +### OperationSystem(调度器) + +静态类,负责异步操作的调度和管理。 + +#### 配置属性 + +```csharp +/// +/// 每帧最大执行时间(毫秒) +/// 默认值:long.MaxValue(无限制) +/// +public static long MaxTimeSlice { set; get; } + +/// +/// 当前帧是否已超时 +/// +public static bool IsBusy { get; } +``` + +#### 核心方法 + +```csharp +/// +/// 初始化异步操作系统 +/// +public static void Initialize(); + +/// +/// 每帧更新(由 YooAssets 驱动) +/// +public static void Update(); + +/// +/// 销毁所有操作 +/// +public static void DestroyAll(); + +/// +/// 清理指定包裹的所有操作 +/// +public static void ClearPackageOperation(string packageName); + +/// +/// 启动异步操作 +/// +public static void StartOperation(string packageName, AsyncOperationBase operation); +``` + +#### 回调监听 + +```csharp +/// +/// 注册任务开始回调 +/// +public static void RegisterStartCallback(Action callback); + +/// +/// 注册任务结束回调 +/// +public static void RegisterFinishCallback(Action callback); +``` + +--- + +### GameAsyncOperation(游戏层基类) + +继承 `AsyncOperationBase`,为业务层提供更友好的 API。 + +```csharp +public abstract class GameAsyncOperation : AsyncOperationBase +{ + /// + /// 异步操作开始 + /// + protected abstract void OnStart(); + + /// + /// 异步操作更新 + /// + protected abstract void OnUpdate(); + + /// + /// 异步操作终止 + /// + protected abstract void OnAbort(); + + /// + /// 异步等待完成(可选重写) + /// + protected virtual void OnWaitForAsyncComplete(); + + /// + /// 异步操作系统是否繁忙 + /// + protected bool IsBusy(); + + /// + /// 终止异步操作 + /// + protected void Abort(); +} +``` + +--- + +## 异步编程模式 + +### 1. 协程模式(IEnumerator) + +```csharp +IEnumerator LoadAsset() +{ + var operation = package.LoadAssetAsync("Assets/Prefab.prefab"); + yield return operation; + + if (operation.Status == EOperationStatus.Succeed) + { + GameObject prefab = operation.AssetObject as GameObject; + } +} +``` + +### 2. Task 模式(async/await) + +```csharp +async Task LoadAssetAsync() +{ + var operation = package.LoadAssetAsync("Assets/Prefab.prefab"); + await operation.Task; + + if (operation.Status == EOperationStatus.Succeed) + { + GameObject prefab = operation.AssetObject as GameObject; + } +} +``` + +### 3. 回调模式(Completed 事件) + +```csharp +void LoadAsset() +{ + var operation = package.LoadAssetAsync("Assets/Prefab.prefab"); + operation.Completed += OnLoadCompleted; +} + +void OnLoadCompleted(AsyncOperationBase op) +{ + var operation = op as AssetHandle; + if (operation.Status == EOperationStatus.Succeed) + { + GameObject prefab = operation.AssetObject as GameObject; + } +} +``` + +### 4. 同步等待模式 + +```csharp +void LoadAssetSync() +{ + var operation = package.LoadAssetAsync("Assets/Prefab.prefab"); + operation.WaitForAsyncComplete(); // 阻塞等待完成 + + if (operation.Status == EOperationStatus.Succeed) + { + GameObject prefab = operation.AssetObject as GameObject; + } +} +``` + +--- + +## 调度机制 + +### 优先级调度 + +操作按 `Priority` 属性降序排列,优先级高的操作先执行。 + +```csharp +var operation = package.LoadAssetAsync(location); +operation.Priority = 100; // 设置高优先级 +``` + +**排序规则:** +- 新操作添加时检查是否需要排序 +- 仅当存在非零优先级时触发排序 +- 使用 `List.Sort()` 进行原地排序 + +### 时间切片 + +通过 `MaxTimeSlice` 控制每帧最大执行时间,防止主线程阻塞。 + +```csharp +// 设置每帧最多执行 8 毫秒 +OperationSystem.MaxTimeSlice = 8; +``` + +**执行流程:** + +``` +每帧 Update() + │ + ├── 记录帧开始时间 _frameTime + │ + └── 遍历操作队列 + │ + ├── 检查 IsBusy(是否超时) + │ │ + │ └── 超时则中断本帧 + │ + └── 执行 operation.UpdateOperation() +``` + +### 操作生命周期 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 操作生命周期 │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. 创建操作 │ +│ └── Status = None │ +│ │ +│ 2. StartOperation() │ +│ ├── Status = Processing │ +│ ├── DebugBeginRecording() │ +│ ├── InternalStart() │ +│ └── 添加到 _newList │ +│ │ +│ 3. Update() - 每帧调度 │ +│ ├── 移除已完成操作 │ +│ ├── 合并 _newList 到 _operations │ +│ ├── 按优先级排序(如需要) │ +│ └── 遍历执行 UpdateOperation() │ +│ │ +│ 4. UpdateOperation() │ +│ ├── DebugUpdateRecording() │ +│ ├── InternalUpdate() │ +│ └── 检查 IsDone │ +│ │ │ +│ └── 完成时: │ +│ ├── IsFinish = true │ +│ ├── Progress = 1f │ +│ ├── DebugEndRecording() │ +│ ├── 触发 Completed 回调 │ +│ └── 设置 TaskCompletionSource │ +│ │ +│ 5. 下一帧移除完成的操作 │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 调试支持 + +### 调试信息结构 + +```csharp +internal struct DebugOperationInfo +{ + public string OperationName; // 操作类型名 + public string OperationDesc; // 操作描述 + public uint Priority; // 优先级 + public float Progress; // 进度 + public string BeginTime; // 开始时间 + public long ProcessTime; // 处理耗时(毫秒) + public string Status; // 状态 + public List Childs; // 子操作 +} +``` + +### 获取调试信息 + +```csharp +// 获取指定包裹的所有操作信息 +var infos = OperationSystem.GetDebugOperationInfos("DefaultPackage"); + +foreach (var info in infos) +{ + Debug.Log($"{info.OperationName}: {info.Status}, {info.ProcessTime}ms"); +} +``` + +### 耗时统计 + +在 DEBUG 模式下自动统计操作耗时: + +```csharp +// 操作完成后可获取耗时 +Debug.Log($"开始时间: {operation.BeginTime}"); +Debug.Log($"处理耗时: {operation.ProcessTime}ms"); +``` + +--- + +## 使用示例 + +### 自定义异步操作 + +```csharp +public class MyCustomOperation : GameAsyncOperation +{ + private int _step = 0; + + protected override void OnStart() + { + // 初始化操作 + _step = 0; + } + + protected override void OnUpdate() + { + // 检查系统是否繁忙(时间切片) + if (IsBusy()) + return; + + // 执行步骤 + switch (_step) + { + case 0: + // 第一步 + Progress = 0.3f; + _step = 1; + break; + case 1: + // 第二步 + Progress = 0.6f; + _step = 2; + break; + case 2: + // 完成 + Status = EOperationStatus.Succeed; + break; + } + } + + protected override void OnAbort() + { + // 清理资源 + } +} +``` + +### 启动自定义操作 + +```csharp +var operation = new MyCustomOperation(); +OperationSystem.StartOperation("DefaultPackage", operation); + +// 使用回调 +operation.Completed += (op) => +{ + if (op.Status == EOperationStatus.Succeed) + Debug.Log("操作成功"); +}; + +// 或使用 await +await operation.Task; +``` + +### 带子任务的操作 + +```csharp +public class ParentOperation : GameAsyncOperation +{ + private ChildOperation _child; + + protected override void OnStart() + { + _child = new ChildOperation(); + AddChildOperation(_child); // 添加子任务 + OperationSystem.StartOperation(PackageName, _child); + } + + protected override void OnUpdate() + { + if (_child.IsDone) + { + if (_child.Status == EOperationStatus.Succeed) + Status = EOperationStatus.Succeed; + else + Status = EOperationStatus.Failed; + } + } + + protected override void OnAbort() + { + // 子任务会自动中止 + } +} +``` + +--- + +## 设计模式 + +### 模板方法模式 + +`AsyncOperationBase` 定义算法骨架,子类实现具体步骤: + +``` +AsyncOperationBase + │ + ├── StartOperation() ──► InternalStart() [子类实现] + ├── UpdateOperation() ──► InternalUpdate() [子类实现] + ├── AbortOperation() ──► InternalAbort() [子类实现] + └── WaitForAsyncComplete() ──► InternalWaitForAsyncComplete() [子类实现] +``` + +### 状态机模式 + +操作状态由 `EOperationStatus` 管理: + +``` +┌──────┐ StartOperation() ┌────────────┐ UpdateOperation() ┌─────────┐ +│ None │ ─────────────────► │ Processing │ ──────────────────► │ Succeed │ +└──────┘ └────────────┘ └─────────┘ + │ + │ UpdateOperation() / AbortOperation() + ▼ + ┌──────────┐ + │ Failed │ + └──────────┘ +``` + +### 组合模式 + +通过 `Childs` 列表支持父子操作关系: + +``` +ParentOperation + ├── ChildOperation1 + ├── ChildOperation2 + └── ChildOperation3 + └── GrandChildOperation +``` + +--- + +## 类继承关系 + +``` +IEnumerator + IComparable + │ + ▼ + AsyncOperationBase (抽象基类) + │ + ├── GameAsyncOperation (游戏层基类) + │ │ + │ └── [业务层自定义操作] + │ + └── [YooAsset 内部操作] + │ + ├── InitializationOperation + ├── LoadAssetOperation + ├── LoadSceneOperation + ├── DownloadOperation + └── ... +``` + +--- + +## 注意事项 + +1. **主线程执行**:所有操作的调度和更新都在 Unity 主线程执行 +2. **时间切片**:设置合理的 `MaxTimeSlice` 避免卡顿(建议 8-16ms) +3. **同步等待**:`WaitForAsyncComplete()` 会阻塞主线程,谨慎使用 +4. **子任务中止**:父操作中止时会自动中止所有子操作 +5. **回调异常**:`Completed` 回调中的异常会被捕获并记录,不会中断系统 +6. **编辑器重置**:编辑器中使用 `RuntimeInitializeOnLoadMethod` 自动重置状态 +7. **循环保护**:`WaitForAsyncComplete()` 有 1000 帧上限,防止无限循环 diff --git a/Assets/YooAsset/Runtime/OperationSystem/README.md.meta b/Assets/YooAsset/Runtime/OperationSystem/README.md.meta new file mode 100644 index 00000000..42a9054c --- /dev/null +++ b/Assets/YooAsset/Runtime/OperationSystem/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2afe3d5ffb611b241919112b40d9c7d0 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Runtime/ResourceManager/README.md b/Assets/YooAsset/Runtime/ResourceManager/README.md new file mode 100644 index 00000000..28fb9407 --- /dev/null +++ b/Assets/YooAsset/Runtime/ResourceManager/README.md @@ -0,0 +1,1269 @@ +# ResourceManager 资源管理器 + +## 模块概述 + +ResourceManager 是 YooAsset 资源管理系统的**核心执行层**,负责资源加载的实际执行和生命周期管理。该模块通过 Provider 模式实现资源加载抽象,通过 Handle 模式提供面向用户的类型安全访问接口。 + +### 核心特性 + +- **Provider 抽象**:统一的资源加载操作模式,支持复用机制 +- **Handle 封装**:类型安全的用户访问接口,支持协程和 async/await +- **引用计数**:精确的资源生命周期追踪,自动卸载未使用资源 +- **并发控制**:可配置的 Bundle 加载并发限制,避免系统过载 + +### 模块统计 + +| 子模块 | 职责 | +|--------|------| +| 核心 | ResourceManager + DownloadStatus | +| Handle 系统 | 面向用户的资源句柄 | +| Provider 系统 | 资源加载执行器 | +| Operation 系统 | 异步操作集合 | +| **总计** | 完整资源管理系统 | + +--- + +## 设计目标 + +| 目标 | 说明 | +|------|------| +| **统一加载接口** | 通过 Provider 模式抽象不同类型资源的加载逻辑 | +| **类型安全访问** | Handle 模式封装资源访问,提供编译时类型检查 | +| **资源生命周期** | 引用计数精确追踪,支持自动和手动卸载 | +| **并发流量控制** | 可配置的 Bundle 加载并发数,平衡性能与稳定性 | +| **Provider 复用** | 相同资源的多次加载共享同一个 Provider | + +--- + +## 文件结构 + +``` +ResourceManager/ +├── ResourceManager.cs # 主管理类 +├── DownloadStatus.cs # 下载状态结构体 +├── Handle/ # 句柄系统 +│ ├── HandleBase.cs # 句柄基类 +│ ├── HandleFactory.cs # 句柄工厂 +│ ├── AssetHandle.cs # 资源句柄 +│ ├── SceneHandle.cs # 场景句柄 +│ ├── SubAssetsHandle.cs # 子资源句柄 +│ ├── AllAssetsHandle.cs # 全部资源句柄 +│ └── RawFileHandle.cs # 原生文件句柄 +├── Provider/ # 提供者系统 +│ ├── ProviderOperation.cs # 提供者基类 +│ ├── AssetProvider.cs # 资源提供者 +│ ├── SceneProvider.cs # 场景提供者 +│ ├── SubAssetsProvider.cs # 子资源提供者 +│ ├── AllAssetsProvider.cs # 全部资源提供者 +│ ├── RawFileProvider.cs # 原生文件提供者 +│ └── CompletedProvider.cs # 完成提供者(错误快速返回) +└── Operation/ # 操作系统 + ├── Internal/ + │ └── LoadBundleFileOperation.cs # Bundle 加载操作 + ├── InstantiateOperation.cs # 实例化操作 + ├── UnloadSceneOperation.cs # 场景卸载操作 + ├── UnloadAllAssetsOperation.cs # 全部资源卸载操作 + └── UnloadUnusedAssetsOperation.cs # 未使用资源卸载操作 +``` + +--- + +## 核心类说明 + +### ResourceManager + +资源管理器主类,作为资源加载操作的中央枢纽。 + +```csharp +internal class ResourceManager +{ + // 核心数据结构 + internal readonly Dictionary ProviderDic; // 容量 5000 + internal readonly Dictionary LoaderDic; // 容量 5000 + internal readonly List SceneHandles; // 容量 100 + + // 配置属性 + public bool AutoUnloadBundleWhenUnused { get; } // 自动卸载未使用的 Bundle + public bool WebGLForceSyncLoadAsset { get; } // WebGL 强制同步加载 + public bool LockLoadOperation { get; set; } // 加载操作锁定 + public int BundleLoadingMaxConcurrency { get; } // Bundle 并发加载数量 (默认32,范围1-256) + public int BundleLoadingCounter { get; set; } // 正在加载的 Bundle 计数 +} +``` + +#### 初始化和销毁 + +```csharp +public void Initialize(InitializeParameters parameters, IBundleQuery bundleServices) +{ + _bundleLoadingMaxConcurrency = parameters.BundleLoadingMaxConcurrency; + AutoUnloadBundleWhenUnused = parameters.AutoUnloadBundleWhenUnused; + WebGLForceSyncLoadAsset = parameters.WebGLForceSyncLoadAsset; + _bundleQuery = bundleServices; + SceneManager.sceneUnloaded += OnSceneUnloaded; +} + +public void Destroy() +{ + SceneManager.sceneUnloaded -= OnSceneUnloaded; +} +``` + +#### 核心加载 API + +| 方法 | 返回类型 | 说明 | +|------|----------|------| +| `LoadAssetAsync()` | `AssetHandle` | 加载单个资源 | +| `LoadSceneAsync()` | `SceneHandle` | 加载场景 | +| `LoadSubAssetsAsync()` | `SubAssetsHandle` | 加载子资源 | +| `LoadAllAssetsAsync()` | `AllAssetsHandle` | 加载所有资源 | +| `LoadRawFileAsync()` | `RawFileHandle` | 加载原生文件 | + +#### Bundle 加载器管理 + +```csharp +// 创建主 Bundle 加载器 +internal LoadBundleFileOperation CreateMainBundleFileLoader(AssetInfo assetInfo) + +// 创建依赖 Bundle 加载器列表 +internal List CreateDependBundleFileLoaders(AssetInfo assetInfo) + +// 检查是否繁忙 +public bool BundleLoadingIsBusy() +{ + return BundleLoadingCounter >= _bundleLoadingMaxConcurrency; +} +``` + +### DownloadStatus + +下载状态结构体,用于追踪资源下载进度。 + +```csharp +public struct DownloadStatus +{ + public long DownloadedBytes; // 已下载字节数 + public long TotalBytes; // 总字节数 + public float Progress; // 下载进度 (0-1) + + public static DownloadStatus CreateDefaultStatus() + { + return new DownloadStatus + { + DownloadedBytes = 0, + TotalBytes = 0, + Progress = 1f + }; + } +} +``` + +--- + +## Provider 系统 + +Provider 系统负责资源加载的实际执行,每种资源类型都有对应的 Provider 实现。 + +### ProviderOperation(基类) + +所有资源提供者的抽象基类,继承自 `AsyncOperationBase`。 + +#### 状态机 + +``` +None → StartBundleLoader → WaitBundleLoader → ProcessBundleResult → Done +``` + +```csharp +protected enum ESteps +{ + None = 0, + StartBundleLoader, // 启动所有 Bundle 加载器 + WaitBundleLoader, // 等待 Bundle 加载完成 + ProcessBundleResult, // 处理 Bundle 结果(子类实现) + Done, // 完成 +} +``` + +#### 核心属性 + +```csharp +public abstract class ProviderOperation : AsyncOperationBase +{ + public string ProviderGUID { get; } // 唯一标识符 + public AssetInfo MainAssetInfo { get; } // 资源信息 + public UnityEngine.Object AssetObject { get; } // 单个资源对象 + public UnityEngine.Object[] AllAssetObjects { get; } // 全部资源对象数组 + public UnityEngine.Object[] SubAssetObjects { get; } // 子资源对象数组 + public Scene SceneObject { get; } // 场景对象 + public BundleResult BundleResultObject { get; } // Bundle 结果 + public int RefCount { get; } // 引用计数 + public bool IsDestroyed { get; } // 销毁标志 + public string SceneName { get; } // 场景名称 +} +``` + +#### 引用计数管理 + +```csharp +// 创建句柄(引用计数 +1) +public T CreateHandle() where T : HandleBase +{ + RefCount++; + HandleBase handle = HandleFactory.CreateHandle(this, typeof(T)); + _handles.Add(handle); + return handle as T; +} + +// 释放句柄(引用计数 -1) +public void ReleaseHandle(HandleBase handle) +{ + if (RefCount <= 0) + throw new YooInternalException(...); + + if (_handles.Remove(handle) == false) + throw new YooInternalException(...); + + RefCount--; +} + +// 检查是否可销毁 +public bool CanDestroyProvider() +{ + // 注意:正在加载中的任务不可以销毁 + if (IsLoading) return false; + return RefCount <= 0; +} +``` + +#### 核心执行流程 + +```csharp +internal override void InternalUpdate() +{ + if (_steps == ESteps.StartBundleLoader) + { + // 启动主 Bundle 加载器和所有依赖 Bundle 加载器 + foreach (var bundleLoader in _bundleLoaders) + { + bundleLoader.StartOperation(); + AddChildOperation(bundleLoader); + } + _steps = ESteps.WaitBundleLoader; + } + + if (_steps == ESteps.WaitBundleLoader) + { + // 等待所有 Bundle 加载完成并验证成功 + foreach (var bundleLoader in _bundleLoaders) + { + if (bundleLoader.IsDone == false) return; + if (bundleLoader.Status != EOperationStatus.Succeed) + { + InvokeCompletion(error, EOperationStatus.Failed); + return; + } + } + _steps = ESteps.ProcessBundleResult; + } + + if (_steps == ESteps.ProcessBundleResult) + { + // 子类实现的核心加载逻辑 + ProcessBundleResult(); + } +} +``` + +### Provider 子类 + +| 类型 | 职责 | 结果属性 | 说明 | +|------|------|----------|------| +| `AssetProvider` | 加载单个资源 | `AssetObject` | 最常用的加载方式 | +| `SceneProvider` | 加载场景 | `SceneObject` | 支持挂起加载 | +| `SubAssetsProvider` | 加载子资源 | `SubAssetObjects` | 用于图集、精灵等 | +| `AllAssetsProvider` | 加载所有资源 | `AllAssetObjects` | 加载 Bundle 内所有资源 | +| `RawFileProvider` | 加载原生文件 | `BundleResultObject` | 配置文件、二进制数据等 | +| `CompletedProvider` | 错误快速返回 | - | 用于无效请求的快速失败 | + +#### AssetProvider 示例 + +```csharp +internal class AssetProvider : ProviderOperation +{ + private FSLoadAssetOperation _loadAssetOp; + + protected override void ProcessBundleResult() + { + if (_loadAssetOp == null) + { + _loadAssetOp = BundleResultObject.LoadAssetAsync(MainAssetInfo); + _loadAssetOp.StartOperation(); + AddChildOperation(_loadAssetOp); + +#if UNITY_WEBGL + if (_resManager.WebGLForceSyncLoadAsset) + _loadAssetOp.WaitForAsyncComplete(); +#endif + } + + _loadAssetOp.UpdateOperation(); + Progress = _loadAssetOp.Progress; + + if (_loadAssetOp.IsDone == false) return; + + if (_loadAssetOp.Status != EOperationStatus.Succeed) + InvokeCompletion(_loadAssetOp.Error, EOperationStatus.Failed); + else + { + AssetObject = _loadAssetOp.Result; + InvokeCompletion(string.Empty, EOperationStatus.Succeed); + } + } +} +``` + +#### SceneProvider 示例 + +```csharp +internal class SceneProvider : ProviderOperation +{ + private LoadSceneParameters _loadParams; + private bool _suspendLoad; + + public SceneProvider(ResourceManager manager, string providerGUID, AssetInfo assetInfo, + LoadSceneParameters loadParams, bool suspendLoad) + : base(manager, providerGUID, assetInfo) + { + _loadParams = loadParams; + _suspendLoad = suspendLoad; + SceneName = Path.GetFileNameWithoutExtension(assetInfo.AssetPath); + } + + // 取消挂起加载 + public void UnSuspendLoad() + { + _suspendLoad = false; + } + + protected override void ProcessBundleResult() + { + if (_loadSceneOp == null) + { + _loadSceneOp = BundleResultObject.LoadSceneOperation( + MainAssetInfo, _loadParams, _suspendLoad); + _loadSceneOp.StartOperation(); + AddChildOperation(_loadSceneOp); + } + + // 支持中途取消挂起 + if (_suspendLoad == false) + _loadSceneOp.UnSuspendLoad(); + + // ... 进度更新和完成处理 + } +} +``` + +--- + +## Handle 系统 + +Handle 系统提供面向用户的资源访问接口,通过代理模式封装 Provider。 + +### HandleBase(基类) + +所有句柄的抽象基类,实现 `IEnumerator` 和 `IDisposable` 接口。 + +```csharp +public abstract class HandleBase : IEnumerator, IDisposable +{ + private readonly AssetInfo _assetInfo; + internal ProviderOperation Provider { get; private set; } + + // 有效性检查 + public bool IsValid + { + get { return Provider != null && Provider.IsDestroyed == false; } + } + + // 释放句柄 + public void Release() + { + if (IsValidWithWarning == false) return; + Provider.ReleaseHandle(this); + + // 主动卸载零引用的资源包 + if (Provider.RefCount == 0) + Provider.TryUnloadBundle(); + + Provider = null; + } + + // IDisposable 实现 + public void Dispose() + { + this.Release(); + } +} +``` + +#### 代理 Provider 属性 + +```csharp +public EOperationStatus Status +{ + get { return IsValidWithWarning ? Provider.Status : EOperationStatus.None; } +} + +public string LastError +{ + get { return IsValidWithWarning ? Provider.Error : string.Empty; } +} + +public float Progress +{ + get { return IsValidWithWarning ? Provider.Progress : 0; } +} + +public bool IsDone +{ + get { return IsValidWithWarning ? Provider.IsDone : true; } +} + +public Task Task +{ + get { return IsValidWithWarning ? Provider.Task : null; } +} +``` + +#### 协程支持 + +```csharp +bool IEnumerator.MoveNext() +{ + return !IsDone; // 返回 false 时循环结束 +} + +void IEnumerator.Reset() { } + +object IEnumerator.Current +{ + get { return Provider; } +} +``` + +### 具体 Handle 类型 + +#### AssetHandle + +资源句柄,用于访问加载的单个资源。 + +```csharp +public sealed class AssetHandle : HandleBase +{ + private System.Action _callback; + + // 完成事件 + public event System.Action Completed + { + add + { + if (IsValidWithWarning == false) + throw new YooHandleException(...); + if (Provider.IsDone) + value.Invoke(this); // 已完成则立即调用 + else + _callback += value; // 未完成则等待 + } + remove { _callback -= value; } + } + + // 资源对象访问 + public UnityEngine.Object AssetObject + { + get { return IsValidWithWarning ? Provider.AssetObject : null; } + } + + public TAsset GetAssetObject() where TAsset : UnityEngine.Object + { + return IsValidWithWarning ? Provider.AssetObject as TAsset : null; + } + + // 实例化支持 + public GameObject InstantiateSync(); + public GameObject InstantiateSync(Transform parent); + public GameObject InstantiateSync(Transform parent, bool worldPositionStays); + public GameObject InstantiateSync(Vector3 position, Quaternion rotation); + public GameObject InstantiateSync(Vector3 position, Quaternion rotation, Transform parent); + + public InstantiateOperation InstantiateAsync(); + public InstantiateOperation InstantiateAsync(Transform parent); + public InstantiateOperation InstantiateAsync(Transform parent, bool worldPositionStays); + public InstantiateOperation InstantiateAsync(Vector3 position, Quaternion rotation); + public InstantiateOperation InstantiateAsync(Vector3 position, Quaternion rotation, Transform parent); +} +``` + +#### SceneHandle + +场景句柄,用于管理加载的场景。 + +```csharp +public class SceneHandle : HandleBase +{ + internal string PackageName { get; set; } + + // 场景名称 + public string SceneName + { + get { return IsValidWithWarning ? Provider.SceneName : string.Empty; } + } + + // 场景对象 + public Scene SceneObject + { + get { return IsValidWithWarning ? Provider.SceneObject : new Scene(); } + } + + // 激活场景 + public bool ActivateScene() + { + if (IsValidWithWarning == false) return false; + if (SceneObject.IsValid() && SceneObject.isLoaded) + return SceneManager.SetActiveScene(SceneObject); + return false; + } + + // 取消挂起加载 + public bool UnSuspend() + { + if (IsValidWithWarning == false) return false; + if (Provider is SceneProvider provider) + provider.UnSuspendLoad(); + return true; + } + + // 卸载场景 + public UnloadSceneOperation UnloadAsync() + { + if (IsValidWithWarning == false) + { + var operation = new UnloadSceneOperation(error); + OperationSystem.StartOperation(packageName, operation); + return operation; + } + + var op = new UnloadSceneOperation(Provider); + OperationSystem.StartOperation(packageName, op); + return op; + } +} +``` + +#### SubAssetsHandle + +子资源句柄,用于访问 Bundle 内的子资源(如图集中的精灵)。 + +```csharp +public sealed class SubAssetsHandle : HandleBase +{ + // 所有子资源 + public IReadOnlyList SubAssetObjects + { + get { return IsValidWithWarning ? Provider.SubAssetObjects : null; } + } + + // 获取指定名称的子资源 + public TObject GetSubAssetObject(string assetName) + where TObject : UnityEngine.Object + { + if (IsValidWithWarning == false) return null; + + foreach (var assetObject in Provider.SubAssetObjects) + { + if (assetObject.name == assetName && assetObject is TObject) + return assetObject as TObject; + } + + YooLogger.Warning($"Not found sub asset object : {assetName}"); + return null; + } + + // 获取所有指定类型的子资源 + public TObject[] GetSubAssetObjects() + where TObject : UnityEngine.Object + { + if (IsValidWithWarning == false) return null; + + List result = new List(Provider.SubAssetObjects.Length); + foreach (var assetObject in Provider.SubAssetObjects) + { + var retObject = assetObject as TObject; + if (retObject != null) + result.Add(retObject); + } + return result.ToArray(); + } +} +``` + +#### AllAssetsHandle + +全部资源句柄,用于访问 Bundle 内的所有资源。 + +```csharp +public sealed class AllAssetsHandle : HandleBase +{ + public IReadOnlyList AllAssetObjects + { + get { return IsValidWithWarning ? Provider.AllAssetObjects : null; } + } +} +``` + +#### RawFileHandle + +原生文件句柄,用于访问未经 Unity 处理的原始文件。 + +```csharp +public class RawFileHandle : HandleBase +{ + // 读取二进制数据 + public byte[] GetRawFileData() + { + if (IsValidWithWarning == false) return null; + return Provider.BundleResultObject.ReadBundleFileData(); + } + + // 读取文本内容 + public string GetRawFileText() + { + if (IsValidWithWarning == false) return null; + return Provider.BundleResultObject.ReadBundleFileText(); + } + + // 获取文件路径 + public string GetRawFilePath() + { + if (IsValidWithWarning == false) return string.Empty; + return Provider.BundleResultObject.GetBundleFilePath(); + } +} +``` + +### HandleFactory + +句柄工厂,使用字典映射类型到构造函数。 + +```csharp +internal static class HandleFactory +{ + private static readonly Dictionary> + _handleFactory = new Dictionary>() + { + { typeof(AssetHandle), op => new AssetHandle(op) }, + { typeof(SceneHandle), op => new SceneHandle(op) }, + { typeof(SubAssetsHandle), op => new SubAssetsHandle(op) }, + { typeof(AllAssetsHandle), op => new AllAssetsHandle(op) }, + { typeof(RawFileHandle), op => new RawFileHandle(op) } + }; + + public static HandleBase CreateHandle(ProviderOperation operation, Type type) + { + if (_handleFactory.TryGetValue(type, out var factory) == false) + throw new NotImplementedException($"Handle type {type.FullName} is not supported."); + return factory(operation); + } +} +``` + +--- + +## Loader 系统 + +### LoadBundleFileOperation + +Bundle 文件加载操作,负责 Bundle 的实际加载和并发控制。 + +#### 状态机 + +``` +None → CheckConcurrency → LoadBundleFile → Done +``` + +```csharp +private enum ESteps +{ + None, + CheckConcurrency, // 检查并发限制 + LoadBundleFile, // 执行 Bundle 加载 + Done, +} +``` + +#### 核心属性 + +```csharp +internal class LoadBundleFileOperation : AsyncOperationBase +{ + public BundleInfo LoadBundleInfo { get; } // Bundle 信息 + public BundleResult Result { get; set; } // 加载结果 + public int RefCount { get; } // 引用计数 + public long DownloadedBytes { get; set; } // 已下载字节数 + public float DownloadProgress { get; set; } // 下载进度 + public bool IsDestroyed { get; } // 销毁标志 +} +``` + +#### 并发控制流程 + +```csharp +internal override void InternalUpdate() +{ + if (_steps == ESteps.CheckConcurrency) + { + // 检查是否超过最大并发加载数 + if (_resManager.BundleLoadingIsBusy()) + return; // 等待直到有空闲位置 + + _steps = ESteps.LoadBundleFile; + } + + if (_steps == ESteps.LoadBundleFile) + { + if (_loadBundleOp == null) + { + // 统计计数增加 + _resManager.BundleLoadingCounter++; + _loadBundleOp = LoadBundleInfo.LoadBundleFile(); + _loadBundleOp.StartOperation(); + AddChildOperation(_loadBundleOp); + } + + // ... 等待加载完成 ... + + // 统计计数减少 + _resManager.BundleLoadingCounter--; + } +} +``` + +#### 引用计数管理 + +```csharp +public void Reference() +{ + RefCount++; +} + +public void Release() +{ + RefCount--; +} + +public bool CanDestroyLoader() +{ + if (CanReleasableLoader() == false) + return false; + + // 检查引用链上的资源包是否已经全部销毁 + // 注意:互相引用的资源包无法卸载! + if (LoadBundleInfo.Bundle.ReferenceBundleIDs.Count > 0) + { + foreach (var bundleID in LoadBundleInfo.Bundle.ReferenceBundleIDs) + { +#if YOOASSET_EXPERIMENTAL + if (_resManager.CheckBundleReleasable(bundleID) == false) + return false; +#else + if (_resManager.CheckBundleDestroyed(bundleID) == false) + return false; +#endif + } + } + + return true; +} +``` + +#### Provider 管理 + +```csharp +private readonly List _providers = new List(100); + +public void AddProvider(ProviderOperation provider) +{ + if (_providers.Contains(provider) == false) + _providers.Add(provider); +} + +public void TryDestroyProviders() +{ + // 获取可销毁的 Provider 列表 + _removeList.Clear(); + foreach (var provider in _providers) + { + if (provider.CanDestroyProvider()) + _removeList.Add(provider); + } + + // 销毁 Provider + foreach (var provider in _removeList) + { + _providers.Remove(provider); + provider.DestroyProvider(); + } + + // 从 ResourceManager 中移除 + if (_removeList.Count > 0) + { + _resManager.RemoveBundleProviders(_removeList); + _removeList.Clear(); + } +} +``` + +--- + +## 操作类系统 + +### InstantiateOperation + +实例化操作,支持同步和异步实例化 GameObject。 + +```csharp +public class InstantiateOperation : AsyncOperationBase +{ + public GameObject Result { get; private set; } + + // 支持的实例化参数 + private Transform _parent; + private bool _worldPositionStays; + private Vector3 _position; + private Quaternion _rotation; + private bool _setPositionAndRotation; +} +``` + +### UnloadSceneOperation + +场景卸载操作。 + +```csharp +public class UnloadSceneOperation : AsyncOperationBase +{ + // 状态流程:UnloadScene → Done + private enum ESteps + { + None, + UnloadScene, + Done, + } +} +``` + +### UnloadAllAssetsOperation + +卸载包裹内所有资源操作。 + +```csharp +public class UnloadAllAssetsOperation : AsyncOperationBase +{ + // 状态流程:CheckInit → ClearProvider → ClearLoader → Done + private enum ESteps + { + None, + CheckInit, + ClearProvider, + ClearLoader, + Done, + } +} +``` + +### UnloadUnusedAssetsOperation + +卸载未使用资源操作,支持迭代清理。 + +```csharp +public class UnloadUnusedAssetsOperation : AsyncOperationBase +{ + private int _maxIterationCount; // 最大迭代次数 + + // 每次迭代清理一轮未使用的资源 + // 复杂的依赖链可能需要多次迭代 +} +``` + +--- + +## 关键工作流 + +### 资源加载流程 + +``` +用户请求 LoadAssetAsync(location) + ↓ +ResourcePackage 转换 → AssetInfo + ↓ +ResourceManager.LoadAssetAsync(assetInfo) + ├─ 构造 ProviderGUID (LoadAssetAsync + AssetInfo.GUID) + ├─ 查找已有 Provider (ProviderDic.TryGetValue) + ├─ 创建新 Provider (如不存在) + │ ├─ new AssetProvider(...) + │ ├─ 创建 LoadBundleFileOperation (主 Bundle) + │ └─ 创建 LoadBundleFileOperation[] (依赖 Bundle) + ├─ 注册到 ProviderDic + ├─ 启动 OperationSystem + └─ 返回 Handle (provider.CreateHandle()) + ↓ +ProviderOperation.InternalUpdate() + ├─ StartBundleLoader:启动所有 Bundle 加载器 + ├─ WaitBundleLoader:等待 Bundle 加载完成 + └─ ProcessBundleResult:调用 BundleResult.LoadAssetAsync() + ↓ +LoadBundleFileOperation.InternalUpdate() + ├─ CheckConcurrency:检查并发限制 + ├─ LoadBundleFile:调用 IFileSystem.LoadBundleFile() + └─ 返回 BundleResult + ↓ +用户获得 Handle + ├─ await handle.ToTask() + ├─ yield return handle + └─ handle.AssetObject +``` + +### 资源卸载流程 + +``` +用户释放 handle.Release() + ↓ +HandleBase.Release() + ├─ Provider.ReleaseHandle(this) + ├─ RefCount-- + └─ 如果 RefCount == 0:Provider.TryUnloadBundle() + ↓ +ProviderOperation.TryUnloadBundle() + └─ 如果 AutoUnloadBundleWhenUnused: + ResourceManager.TryUnloadUnusedAsset(assetInfo, loopCount) + ↓ +ResourceManager.TryUnloadUnusedAsset() + ├─ 循环处理依赖链 (loopCount 次) + ├─ 卸载主 Bundle + │ ├─ TryDestroyProviders() + │ ├─ CanDestroyLoader() 检查 + │ └─ DestroyLoader() + └─ 卸载依赖 Bundle + ├─ CanDestroyLoader() 检查 + └─ DestroyLoader() + ↓ +LoadBundleFileOperation.DestroyLoader() + └─ BundleResult.UnloadBundleFile() +``` + +### Provider 复用机制 + +``` +多次加载同一资源: + LoadAssetAsync("Player.prefab") → Provider A (新建) + LoadAssetAsync("Player.prefab") → Provider A (复用) + LoadAssetAsync("Player.prefab") → Provider A (复用) + +ProviderGUID 构造规则: + ProviderGUID = MethodName + AssetInfo.GUID + 例如:LoadAssetAsync + abc123def456 → "LoadAssetAsyncabc123def456" + +复用条件: + if (ProviderDic.TryGetValue(providerGUID, out provider)) + return provider.CreateHandle(); // 复用现有 Provider + +每次复用: + RefCount++ (通过 CreateHandle) +``` + +--- + +## 类继承关系图 + +``` +AsyncOperationBase +├── ProviderOperation (资源提供者基类) +│ ├── AssetProvider (加载单个资源) +│ ├── SceneProvider (加载场景) +│ ├── SubAssetsProvider (加载子资源) +│ ├── AllAssetsProvider (加载所有资源) +│ ├── RawFileProvider (加载原生文件) +│ └── CompletedProvider (错误快速返回) +├── LoadBundleFileOperation (Bundle 加载) +├── InstantiateOperation (实例化) +├── UnloadSceneOperation (场景卸载) +├── UnloadAllAssetsOperation (全部卸载) +└── UnloadUnusedAssetsOperation (未使用卸载) + +HandleBase (IEnumerator, IDisposable) +├── AssetHandle (资源句柄) +├── SceneHandle (场景句柄) +├── SubAssetsHandle (子资源句柄) +├── AllAssetsHandle (全部资源句柄) +└── RawFileHandle (原生文件句柄) +``` + +--- + +## 与其他模块的交互 + +``` +ResourcePackage + │ + ├─ 持有 → ResourceManager 实例 + │ ├─ LoadAssetAsync() + │ ├─ LoadSceneAsync() + │ ├─ LoadSubAssetsAsync() + │ ├─ LoadAllAssetsAsync() + │ └─ LoadRawFileAsync() + │ + └─ 实现 → IBundleQuery 接口 + ├─ GetMainBundleInfo(AssetInfo) → BundleInfo + ├─ GetDependBundleInfos(AssetInfo) → List + └─ GetMainBundleName(bundleID) → string + +ResourceManager + │ + ├─ 依赖 → IBundleQuery (Bundle 查询) + │ + ├─ 使用 → BundleInfo + │ └─ LoadBundleFile() → FSLoadBundleOperation + │ + └─ 间接使用 → IFileSystem + └─ 通过 BundleInfo 调用 + ├─ LoadBundleFile(Bundle) + └─ 返回 FSLoadBundleOperation +``` + +### 并发流量控制 + +``` +ResourceManager 维护的限制: +├─ BundleLoadingMaxConcurrency (默认 32,范围 1-256) +│ └─ 限制同时加载的 Bundle 文件数量 +├─ BundleLoadingCounter (当前计数) +│ └─ 由 LoadBundleFileOperation 增减 +└─ BundleLoadingIsBusy() 检查 + └─ 超过限制则阻塞新的 Bundle 加载 + +FileSystem (Cache) 维护的限制: +├─ FileVerifyMaxConcurrency (默认 32,范围 1-256) +│ └─ 限制同时验证的文件数量 +├─ DownloadMaxConcurrency (默认 10,范围 1-64) +│ └─ 限制同时下载的文件数量 +└─ DownloadMaxRequestPerFrame (默认 5,范围 1-20) + └─ 限制每帧发起的下载请求数 +``` + +--- + +## 使用示例 + +### 加载资源 + +```csharp +// 异步加载(async/await) +var handle = package.LoadAssetAsync("Assets/Prefabs/Player.prefab"); +await handle.ToTask(); +var prefab = handle.GetAssetObject(); + +// 使用资源 +var instance = GameObject.Instantiate(prefab); + +// 释放资源(必须!) +handle.Release(); +``` + +```csharp +// 异步加载(协程) +var handle = package.LoadAssetAsync("Assets/Prefabs/Player.prefab"); +yield return handle; +var prefab = handle.GetAssetObject(); + +// 使用资源 +var instance = GameObject.Instantiate(prefab); + +// 释放资源 +handle.Release(); +``` + +```csharp +// 异步加载(回调) +var handle = package.LoadAssetAsync("Assets/Prefabs/Player.prefab"); +handle.Completed += (h) => +{ + var prefab = h.GetAssetObject(); + var instance = GameObject.Instantiate(prefab); + + // 注意:回调中也需要释放 + h.Release(); +}; +``` + +### 加载场景 + +```csharp +// 加载场景(支持挂起) +var sceneHandle = package.LoadSceneAsync("Assets/Scenes/Main.unity", + LoadSceneMode.Additive, suspendLoad: true); + +// 在适当时机取消挂起 +sceneHandle.UnSuspend(); + +await sceneHandle.ToTask(); + +// 激活场景 +sceneHandle.ActivateScene(); + +// 卸载场景 +var unloadOp = sceneHandle.UnloadAsync(); +await unloadOp.ToTask(); +``` + +### 加载子资源 + +```csharp +// 加载图集 +var handle = package.LoadSubAssetsAsync("Assets/Atlas/UI.spriteatlas"); +await handle.ToTask(); + +// 获取特定精灵 +var homeIcon = handle.GetSubAssetObject("icon_home"); +var settingsIcon = handle.GetSubAssetObject("icon_settings"); + +// 获取所有精灵 +var allSprites = handle.GetSubAssetObjects(); + +// 释放资源 +handle.Release(); +``` + +### 加载原生文件 + +```csharp +// 加载 JSON 配置 +var handle = package.LoadRawFileAsync("Assets/Config/settings.json"); +await handle.ToTask(); + +// 读取文本 +string json = handle.GetRawFileText(); +var settings = JsonUtility.FromJson(json); + +// 释放资源 +handle.Release(); +``` + +```csharp +// 加载二进制文件 +var handle = package.LoadRawFileAsync("Assets/Data/binary.bytes"); +await handle.ToTask(); + +// 读取二进制数据 +byte[] data = handle.GetRawFileData(); + +// 或获取文件路径(用于第三方库) +string path = handle.GetRawFilePath(); + +handle.Release(); +``` + +### 实例化操作 + +```csharp +var handle = package.LoadAssetAsync("Assets/Prefabs/Player.prefab"); +await handle.ToTask(); + +// 同步实例化 +var go1 = handle.InstantiateSync(); +var go2 = handle.InstantiateSync(parentTransform); +var go3 = handle.InstantiateSync(parentTransform, worldPositionStays: false); +var go4 = handle.InstantiateSync(position, rotation); +var go5 = handle.InstantiateSync(position, rotation, parentTransform); + +// 异步实例化 +var instOp = handle.InstantiateAsync(parentTransform); +await instOp.ToTask(); +var go6 = instOp.Result; + +// 释放资源 +handle.Release(); +``` + +### 使用 using 语句自动释放 + +```csharp +// 利用 IDisposable 接口 +using (var handle = package.LoadAssetAsync("Assets/Prefabs/Player.prefab")) +{ + await handle.ToTask(); + var prefab = handle.GetAssetObject(); + var instance = GameObject.Instantiate(prefab); +} +// using 块结束时自动调用 handle.Dispose() → Release() +``` + +--- + +## 注意事项 + +1. **必须释放 Handle** + - 使用后必须调用 `handle.Release()` 避免内存泄漏 + - 可以使用 `using` 语句自动释放 + +2. **Provider 复用** + - 相同资源的多次加载会复用同一个 Provider + - 每次加载都会增加引用计数 + +3. **并发限制** + - Bundle 加载最大并发数为 256,默认 32 + - 超过限制的加载请求会排队等待 + +4. **引用计数** + - RefCount 为 0 且 `AutoUnloadBundleWhenUnused` 为 true 时自动卸载 + - 复杂依赖链可能需要多次迭代才能完全卸载 + +5. **WebGL 特殊处理** + - 可配置 `WebGLForceSyncLoadAsset` 强制同步加载 + - WebGL 平台不支持多线程 + +6. **循环依赖** + - 通过 `YOOASSET_EXPERIMENTAL` 宏处理循环 Bundle 依赖 + - 实验性功能,需谨慎使用 + +7. **线程安全** + - 所有资源管理逻辑在主线程执行 + - 多线程仅用于下载和文件验证 + +8. **场景加载** + - 场景不支持 Provider 复用 + - 每次加载都会创建新的 Provider(使用递增的 sceneCreateIndex) + +--- + +## 性能优化建议 + +1. **合理设置并发数** + - 根据设备性能调整 `BundleLoadingMaxConcurrency` + - 移动设备建议 16-32,PC 可设置更高 + +2. **预加载常用资源** + - 提前加载常用资源并持有 Handle + - 避免运行时频繁加载/卸载 + +3. **批量加载** + - 使用 `LoadAllAssetsAsync` 加载 Bundle 内所有资源 + - 减少多次加载请求的开销 + +4. **及时释放** + - 不再使用的资源及时调用 `Release()` + - 避免内存占用过高 + +5. **使用资源标签** + - 合理规划资源标签 + - 利用标签进行批量下载和加载 diff --git a/Assets/YooAsset/Runtime/ResourceManager/README.md.meta b/Assets/YooAsset/Runtime/ResourceManager/README.md.meta new file mode 100644 index 00000000..f5e04ab6 --- /dev/null +++ b/Assets/YooAsset/Runtime/ResourceManager/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 23897062e5e2c98498f4bd00a407dda8 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/YooAsset/Runtime/ResourcePackage/README.md b/Assets/YooAsset/Runtime/ResourcePackage/README.md new file mode 100644 index 00000000..3cc7b297 --- /dev/null +++ b/Assets/YooAsset/Runtime/ResourcePackage/README.md @@ -0,0 +1,1192 @@ +# ResourcePackage 资源包管理模块 + +## 模块概述 + +ResourcePackage 是 YooAsset 资源管理系统的**核心模块**,作为资源操作的统一入口,负责资源包的完整生命周期管理。该模块提供资源加载、清单管理、下载更新、缓存清理等核心功能。 + +### 核心特性 + +- **统一入口**:ResourcePackage 作为资源操作的门面,提供所有公开 API +- **多模式支持**:支持 Editor/Offline/Host/Web/Custom 五种运行模式 +- **高效查询**:字典缓存实现 O(1) 资源定位 +- **异步优先**:所有操作基于 AsyncOperationBase 异步框架 +- **灵活下载**:支持按标签、按路径、按资源的多种下载方式 + +--- + +## 设计目标 + +| 目标 | 说明 | +|------|------| +| **统一门面** | ResourcePackage 封装所有资源操作,简化使用复杂度 | +| **模式抽象** | 通过 IPlayMode 接口统一不同运行模式的行为 | +| **高效索引** | 清单初始化时构建多个字典,加速运行时查询 | +| **生命周期** | 完整管理包裹从创建到销毁的全过程 | + +--- + +## 文件结构 + +``` +ResourcePackage/ +├── ResourcePackage.cs # 包裹管理门面 +├── PackageManifest.cs # 资源清单 +├── PackageAsset.cs # 资源元数据 +├── PackageBundle.cs # 资源包元数据 +├── ManifestTools.cs # 清单序列化工具 +├── ManifestDefine.cs # 清单常量定义 +├── AssetInfo.cs # 资源信息(公开API) +├── BundleInfo.cs # 资源包信息(内部) +├── PackageDetail.cs # 包裹详情 +├── EBuildBundleType.cs # 资源包类型枚举 +├── EFileNameStyle.cs # 文件名风格枚举 +├── Interface/ # 接口定义 +│ ├── IPlayMode.cs # 播放模式接口 +│ └── IBundleQuery.cs # 资源包查询接口 +├── PlayMode/ # 播放模式实现 +│ ├── PlayModeImpl.cs # 播放模式核心实现 +│ └── EditorSimulateModeHelper.cs # 编辑器模拟辅助 +└── Operation/ # 操作类 + ├── InitializationOperation.cs # 初始化操作 + ├── DestroyOperation.cs # 销毁操作 + ├── RequestPackageVersionOperation.cs # 请求版本 + ├── UpdatePackageManifestOperation.cs # 更新清单 + ├── PreDownloadContentOperation.cs # 预下载 + ├── ClearCacheFilesOperation.cs # 清理缓存 + ├── DownloaderOperation.cs # 下载操作基类 + └── Internal/ + └── DeserializeManifestOperation.cs # 清单反序列化 +``` + +--- + +## 核心类说明 + +### ResourcePackage + +包裹管理门面类,提供所有资源操作的公开 API。 + +#### 核心属性 + +| 属性 | 类型 | 说明 | +|------|------|------| +| `PackageName` | `string` | 包裹唯一标识 | +| `InitializeStatus` | `EOperationStatus` | 初始化状态 | +| `PackageValid` | `bool` | 包裹是否有效(清单已加载) | + +#### 生命周期方法 + +```csharp +// 异步初始化包裹 +InitializationOperation InitializeAsync(InitializeParameters parameters); + +// 异步销毁包裹 +DestroyOperation DestroyAsync(); +``` + +#### 清单操作方法 + +```csharp +// 请求最新版本号 +RequestPackageVersionOperation RequestPackageVersionAsync( + bool appendTimeTicks = true, int timeout = 60); + +// 更新资源清单 +UpdatePackageManifestOperation UpdatePackageManifestAsync( + string packageVersion, int timeout = 60); + +// 预下载指定版本内容 +PreDownloadContentOperation PreDownloadContentAsync( + string packageVersion, int timeout = 60); + +// 获取包裹版本 +string GetPackageVersion(); + +// 获取包裹详细信息 +PackageDetails GetPackageDetails(); +``` + +#### 资源加载方法(5种模式) + +```csharp +// 1. 加载单个资源 +AssetHandle LoadAssetSync(string location); +AssetHandle LoadAssetAsync(string location); +AssetHandle LoadAssetAsync(AssetInfo assetInfo); + +// 2. 加载子资源 +SubAssetsHandle LoadSubAssetsSync(string location); +SubAssetsHandle LoadSubAssetsAsync(string location); + +// 3. 加载资源包内全部资源 +AllAssetsHandle LoadAllAssetsSync(string location); +AllAssetsHandle LoadAllAssetsAsync(string location); + +// 4. 加载场景 +SceneHandle LoadSceneSync(string location, LoadSceneMode sceneMode); +SceneHandle LoadSceneAsync(string location, LoadSceneMode sceneMode); + +// 5. 加载原生文件 +RawFileHandle LoadRawFileSync(string location); +RawFileHandle LoadRawFileAsync(string location); +``` + +#### 资源信息查询方法 + +```csharp +// 按定位地址获取资源信息 +AssetInfo GetAssetInfo(string location); + +// 按 GUID 获取资源信息 +AssetInfo GetAssetInfoByGUID(string assetGUID); + +// 按标签获取资源信息列表 +AssetInfo[] GetAssetInfos(string tag); +AssetInfo[] GetAssetInfos(string[] tags); + +// 获取全部资源信息 +AssetInfo[] GetAllAssetInfos(); + +// 验证定位地址有效性 +bool CheckLocationValid(string location); + +// 检查是否需要从远端下载 +bool IsNeedDownloadFromRemote(string location); +bool IsNeedDownloadFromRemote(AssetInfo assetInfo); +``` + +#### 下载器创建方法(4种模式) + +```csharp +// 下载全部需要下载的资源 +ResourceDownloaderOperation CreateResourceDownloader( + int downloadingMaxNumber, int failedTryAgain); + +// 按标签下载 +ResourceDownloaderOperation CreateResourceDownloader( + string tag, int downloadingMaxNumber, int failedTryAgain); +ResourceDownloaderOperation CreateResourceDownloader( + string[] tags, int downloadingMaxNumber, int failedTryAgain); + +// 按资源路径下载 +ResourceDownloaderOperation CreateBundleDownloader( + string location, int downloadingMaxNumber, int failedTryAgain); +ResourceDownloaderOperation CreateBundleDownloader( + string[] locations, int downloadingMaxNumber, int failedTryAgain); + +// 按 AssetInfo 下载 +ResourceDownloaderOperation CreateBundleDownloader( + AssetInfo[] assetInfos, int downloadingMaxNumber, int failedTryAgain); +``` + +#### 解压器创建方法 + +```csharp +// 解压全部需要解压的资源 +ResourceUnpackerOperation CreateResourceUnpacker( + int unpackingMaxNumber, int failedTryAgain); + +// 按标签解压 +ResourceUnpackerOperation CreateResourceUnpacker( + string tag, int unpackingMaxNumber, int failedTryAgain); +ResourceUnpackerOperation CreateResourceUnpacker( + string[] tags, int unpackingMaxNumber, int failedTryAgain); +``` + +#### 导入器创建方法 + +```csharp +// 按文件路径导入 +ResourceImporterOperation CreateResourceImporter( + string[] filePaths, int importerMaxNumber, int failedTryAgain); + +// 按导入信息导入 +ResourceImporterOperation CreateResourceImporter( + ImportFileInfo[] fileInfos, int importerMaxNumber, int failedTryAgain); +``` + +#### 缓存清理方法 + +```csharp +// 清理缓存文件 +ClearCacheFilesOperation ClearCacheFilesAsync(EFileClearMode clearMode); +ClearCacheFilesOperation ClearCacheFilesAsync(string clearMode, object clearParam); +``` + +#### 资源卸载方法 + +```csharp +// 强制卸载全部资源 +UnloadAllAssetsOperation UnloadAllAssetsAsync(); + +// 卸载未使用的资源 +UnloadUnusedAssetsOperation UnloadUnusedAssetsAsync(int loopCount = 100); + +// 尝试卸载指定资源 +bool TryUnloadUnusedAsset(string location); +bool TryUnloadUnusedAsset(AssetInfo assetInfo); +``` + +--- + +## 清单系统 + +### PackageManifest + +资源清单文件,存储资源和资源包的元数据,是资源查询的数据库。 + +#### 配置信息 + +| 属性 | 类型 | 说明 | +|------|------|------| +| `FileVersion` | `string` | 清单文件格式版本 | +| `EnableAddressable` | `bool` | 启用可寻址定位 | +| `SupportExtensionless` | `bool` | 支持无扩展名寻址 | +| `LocationToLower` | `bool` | 定位地址大小写不敏感 | +| `IncludeAssetGUID` | `bool` | 包含资源 GUID | +| `ReplaceAssetPathWithAddress` | `bool` | 用 Address 替换 AssetPath | +| `OutputNameStyle` | `int` | 文件名样式 | +| `BuildBundleType` | `int` | 构建资源包类型 | +| `BuildPipeline` | `string` | 构建管线名称 | + +#### 包裹信息 + +| 属性 | 类型 | 说明 | +|------|------|------| +| `PackageName` | `string` | 包裹名称 | +| `PackageVersion` | `string` | 包裹版本 | +| `PackageNote` | `string` | 包裹备注 | + +#### 核心数据 + +| 属性 | 类型 | 说明 | +|------|------|------| +| `AssetList` | `List` | 主资源列表 | +| `BundleList` | `List` | 资源包列表 | + +#### 运行时字典(初始化时构建) + +| 字典 | 键 | 值 | 说明 | +|------|-----|-----|------| +| `AssetDic` | AssetPath | PackageAsset | 资源路径查询 | +| `AssetPathMapping1` | Location | AssetPath | 定位地址→资源路径 | +| `AssetPathMapping2` | AssetGUID | AssetPath | GUID→资源路径 | +| `BundleDic1` | BundleName | PackageBundle | 按名称查询 | +| `BundleDic2` | FileName | PackageBundle | 按文件名查询 | +| `BundleDic3` | BundleGUID | PackageBundle | 按 GUID 查询 | + +#### 查询方法 + +```csharp +// 定位地址转换 +bool TryMappingToAssetPath(string location, out string assetPath); + +// 资源查询 +bool TryGetPackageAsset(string assetPath, out PackageAsset result); + +// 资源包查询(3种方式) +bool TryGetPackageBundleByBundleName(string bundleName, out PackageBundle result); +bool TryGetPackageBundleByFileName(string fileName, out PackageBundle result); +bool TryGetPackageBundleByBundleGUID(string bundleGUID, out PackageBundle result); + +// 获取资源主包 +PackageBundle GetMainPackageBundle(AssetInfo assetInfo); + +// 获取资源依赖包 +PackageBundle[] GetAssetAllDependencies(PackageAsset asset); + +// 获取资源包依赖 +PackageBundle[] GetBundleAllDependencies(PackageBundle bundle); + +// 定位地址转 AssetInfo +AssetInfo ConvertLocationToAssetInfo(string location, System.Type assetType); + +// GUID 转 AssetInfo +AssetInfo ConvertAssetGUIDToAssetInfo(string assetGUID, System.Type assetType); + +// 按标签获取资源 +AssetInfo[] GetAssetInfosByTags(string[] tags); + +// 检查资源包是否在清单中 +bool IsIncludeBundleFile(string bundleGUID); +``` + +### PackageAsset + +单个资源的元数据(可序列化)。 + +```csharp +public class PackageAsset +{ + public string Address; // 可寻址地址 + public string AssetPath; // 资源路径 (Assets/...) + public string AssetGUID; // 资源 GUID + public string[] AssetTags; // 分类标签 + public int BundleID; // 所属资源包 ID(索引) + public int[] DependBundleIDs; // 依赖资源包 ID 数组 + + // 检查是否包含指定标签 + public bool HasTag(string[] tags); +} +``` + +### PackageBundle + +资源包的元数据(可序列化)。 + +```csharp +public class PackageBundle +{ + // 基础信息 + public string BundleName; // 资源包名称 + public string BundleGUID { get; } // GUID(= FileHash) + public uint UnityCRC; // Unity 生成的 CRC + public string FileHash; // 文件哈希值 + public uint FileCRC; // 文件 CRC 校验码 + public long FileSize; // 文件大小(字节) + public bool Encrypted; // 是否加密 + + // 分类和依赖 + public string[] Tags; // 资源包分类标签 + public int[] DependBundleIDs; // 依赖资源包 ID(引擎层) + + // 运行时属性 + public int BundleType { get; } // 资源包类型 + public string FileName { get; } // 远端文件名 + public string FileExtension { get; } // 文件扩展名 + + // 运行时列表 + public List IncludeMainAssets; // 包含的主资源 + public List ReferenceBundleIDs; // 引用此包的 ID 列表 + + // 检查是否包含标签 + public bool HasTag(string[] tags); + public bool HasAnyTags(); + + // 对比相等性(基于 FileHash) + public bool Equals(PackageBundle otherBundle); +} +``` + +### ManifestTools + +清单序列化工具类。 + +```csharp +// 验证清单数据完整性 +static bool VerifyManifestData(byte[] fileData, string hashValue); + +// 序列化 +static string SerializeToJson(PackageManifest manifest); +static byte[] SerializeToBinary(PackageManifest manifest); + +// 反序列化 +static PackageManifest DeserializeFromJson(string jsonContent); +static PackageManifest DeserializeFromBinary(byte[] binaryData); + +// 文件命名 +static string GetRemoteBundleFileExtension(string bundleName); +static string GetRemoteBundleFileName(OutputNameStyle style, ...); +``` + +### ManifestDefine + +清单常量定义。 + +```csharp +public class ManifestDefine +{ + public const int FileMaxSize = 104857600; // 100MB + public const uint FileSign = 0x594F4F; // "YOO" 标记 + public const string FileVersion = "2025.9.30"; // 当前版本 + public const string VERSION_2025_8_28 = "2025.8.28"; + public const string VERSION_2025_9_30 = "2025.9.30"; + public const bool BackwardCompatible = true; // 向后兼容 +} +``` + +--- + +## 运行模式系统 + +### IPlayMode 接口 + +定义不同运行模式的统一接口。 + +```csharp +public interface IPlayMode +{ + // 当前活跃的清单 + PackageManifest ActiveManifest { set; get; } + + // 销毁文件系统 + void DestroyFileSystem(); + + // 版本和清单操作 + RequestPackageVersionOperation RequestPackageVersionAsync(...); + UpdatePackageManifestOperation UpdatePackageManifestAsync(...); + PreDownloadContentOperation PreDownloadContentAsync(...); + ClearCacheFilesOperation ClearCacheFilesAsync(...); + + // 下载器创建 + ResourceDownloaderOperation CreateResourceDownloaderByAll(...); + ResourceDownloaderOperation CreateResourceDownloaderByTags(...); + ResourceDownloaderOperation CreateResourceDownloaderByPaths(...); + + // 解压器创建 + ResourceUnpackerOperation CreateResourceUnpackerByAll(...); + ResourceUnpackerOperation CreateResourceUnpackerByTags(...); + + // 导入器创建 + ResourceImporterOperation CreateResourceImporterByFilePaths(...); + ResourceImporterOperation CreateResourceImporterByFileInfos(...); +} +``` + +### PlayModeImpl + +播放模式核心实现,同时实现 `IPlayMode` 和 `IBundleQuery` 接口。 + +#### 核心属性 + +```csharp +public class PlayModeImpl : IPlayMode, IBundleQuery +{ + public readonly string PackageName; + public readonly EPlayMode PlayMode; + public readonly List FileSystems = new List(10); + public PackageManifest ActiveManifest { set; get; } +} +``` + +#### 文件系统管理 + +```csharp +// 获取主文件系统(列表最后一个) +public IFileSystem GetMainFileSystem(); + +// 获取资源包所属的文件系统 +public IFileSystem GetBelongFileSystem(PackageBundle bundle); +``` + +#### IBundleQuery 实现 + +```csharp +// 获取主资源包信息 +BundleInfo GetMainBundleInfo(AssetInfo assetInfo); + +// 获取依赖资源包信息 +BundleInfo[] GetDependBundleInfos(AssetInfo assetInfo); + +// 获取资源包名称 +string GetMainBundleName(int bundleID); +``` + +#### 下载/解压/导入列表生成 + +```csharp +// 生成全部下载列表 +List GetDownloadListByAll(PackageManifest manifest); + +// 按标签筛选下载列表 +List GetDownloadListByTags(PackageManifest manifest, string[] tags); + +// 按资源路径筛选下载列表 +List GetDownloadListByPaths(PackageManifest manifest, string[] locations); + +// 解压列表 +List GetUnpackListByAll(PackageManifest manifest); +List GetUnpackListByTags(PackageManifest manifest, string[] tags); + +// 导入列表 +List GetImporterListByFilePaths(...); +List GetImporterListByFileInfos(...); +``` + +### 五种运行模式 + +| 模式 | 说明 | 文件系统 | +|------|------|----------| +| `EditorSimulateMode` | 编辑器模拟模式 | DefaultEditorFileSystem | +| `OfflinePlayMode` | 单机运行模式 | DefaultBuildinFileSystem | +| `HostPlayMode` | 联机运行模式 | Buildin + Cache | +| `WebPlayMode` | WebGL 运行模式 | WebServer + WebRemote | +| `CustomPlayMode` | 自定义模式 | 用户自定义 | + +#### 文件系统优先级 + +``` +FileSystems[0] → FileSystems[1] → ... → FileSystems[N] (主文件系统) + ↓ ↓ + 编辑器或内置资源 远端或缓存资源 +``` + +--- + +## 操作类系统 + +### InitializationOperation + +初始化操作,管理包裹的初始化流程。 + +``` +状态流程: +Prepare + ↓ +ClearOldFileSystem + └── 清理旧的文件系统(如果存在) + ↓ +InitFileSystem + └── 逐个初始化 FileSystems + ├── FileSystem[0].InitializeFileSystemAsync() + ├── FileSystem[1].InitializeFileSystemAsync() + └── ... + ↓ +CheckInitResult + └── 检查所有文件系统初始化结果 + ├── 任一失败 → Status = Failed + └── 全部成功 → Status = Succeed + ↓ +Done +``` + +### DestroyOperation + +销毁操作,管理包裹的销毁流程。 + +``` +状态流程: +CheckInitStatus + ├── 初始化中 → Status = Failed(不允许销毁) + └── 其他状态 → 继续 + ↓ +UnloadAllAssets + └── 如果初始化成功,卸载所有资源 + ↓ +DestroyPackage + ├── 销毁包裹 + └── OperationSystem.ClearPackageOperation() + ↓ +Done → Status = Succeed +``` + +### RequestPackageVersionOperation + +请求版本操作,获取远端最新版本号。 + +``` +状态流程: +RequestPackageVersion + └── 委托给主文件系统 + └── FileSystem.RequestPackageVersionAsync() + ├── 成功 → PackageVersion = 版本号 + └── 失败 → Error + ↓ +Done +``` + +### UpdatePackageManifestOperation + +更新清单操作,加载指定版本的清单。 + +``` +状态流程: +CheckParams + └── 验证参数有效性 + ↓ +CheckActiveManifest + └── 检查当前清单版本 + ├── 版本匹配 → 直接成功(复用) + └── 版本不匹配 → 继续加载 + ↓ +LoadPackageManifest + └── 主文件系统加载清单 + └── FileSystem.LoadPackageManifestAsync() + ├── 成功 → Manifest = 新清单 + └── 失败 → Error + ↓ +Done +``` + +### PreDownloadContentOperation + +预下载操作,预加载指定版本的清单并支持创建下载器。 + +``` +状态流程: +CheckParams + └── 验证参数有效性 + ↓ +CheckActiveManifest + └── 检查是否已有相同版本清单 + ├── 有 → 复用清单 + └── 无 → 继续加载 + ↓ +LoadPackageManifest + └── 加载指定版本清单 + ├── 成功 → 可创建下载器 + └── 失败 → Error + ↓ +Done +``` + +#### 预下载后创建下载器 + +```csharp +// 下载全部 +CreateResourceDownloader(int downloadingMaxNumber, int failedTryAgain); + +// 按标签下载 +CreateResourceDownloader(string tag, ...); +CreateResourceDownloader(string[] tags, ...); + +// 按资源路径下载 +CreateResourceDownloader(string[] locations, ...); +``` + +### ClearCacheFilesOperation + +缓存清理操作,清理指定的缓存文件。 + +``` +状态流程: +Prepare + └── 准备清理任务 + ↓ +ClearCacheFiles + └── 逐个文件系统清理 + └── FileSystem.ClearCacheFilesAsync(options) + ↓ +CheckClearResult + └── 检查清理结果 + ├── 任一失败 → Status = Failed + └── 全部成功 → Status = Succeed + ↓ +Done +``` + +### DownloaderOperation + +下载操作基类,管理并发下载任务。 + +#### 核心属性 + +```csharp +public abstract class DownloaderOperation : AsyncOperationBase +{ + // 下载统计 + public int TotalDownloadCount { get; } // 总下载数 + public long TotalDownloadBytes { get; } // 总大小(字节) + public int CurrentDownloadCount { get; } // 已完成数 + public long CurrentDownloadBytes { get; } // 已完成大小 + public float Progress { get; } // 进度 0.0~1.0 + + // 并发控制 + protected const int MAX_LOADER_COUNT = 64; // 最大同时下载数 +} +``` + +#### 回调委托 + +```csharp +// 下载完成回调 +public DownloadFinishDelegate DownloadFinishCallback; + +// 进度更新回调 +public DownloadUpdateDelegate DownloadUpdateCallback; + +// 下载错误回调 +public DownloadErrorDelegate DownloadErrorCallback; + +// 开始下载文件回调 +public DownloadFileBeginDelegate DownloadFileBeginCallback; +``` + +#### 状态流程 + +``` +Check + └── 检查下载列表是否为空 + ├── 为空 → 直接成功 + └── 不为空 → 开始下载 + ↓ +Loading + ├── 检测已完成的下载器 + ├── 移除完成的下载器 + ├── 更新进度回调 + ├── 创建新下载器(≤ MAX_LOADER_COUNT) + └── 处理失败重试 + ↓ +Done +``` + +#### 子类 + +| 类 | 说明 | +|-----|------| +| `ResourceDownloaderOperation` | 资源下载器 | +| `ResourceUnpackerOperation` | 资源解压器 | +| `ResourceImporterOperation` | 资源导入器 | + +### DeserializeManifestOperation + +清单反序列化操作,从二进制数据反序列化清单。 + +``` +状态流程: +RestoreFileData + └── 调用插件还原数据(如果有) + ↓ +DeserializeFileHeader + └── 读取文件头 + ├── 验证 FileSign (0x594F4F) + └── 检查 FileVersion 兼容性 + ↓ +PrepareAssetList + └── 读取资源数量 + ↓ +DeserializeAssetList(时间切片) + └── 逐个反序列化资源 + ↓ +PrepareBundleList + └── 读取资源包数量 + ↓ +DeserializeBundleList(时间切片) + └── 逐个反序列化资源包 + ↓ +InitManifest + └── 初始化清单(构建字典) + ↓ +Done → Manifest +``` + +--- + +## 辅助类说明 + +### AssetInfo + +资源信息包装类(公开 API)。 + +```csharp +public class AssetInfo +{ + // 公开属性 + public string PackageName { get; } // 所属包裹 + public System.Type AssetType { get; } // 资源类型 + public string Error { get; } // 错误信息 + public bool IsInvalid { get; } // 身份是否无效 + public string Address { get; } // 可寻址地址 + public string AssetPath { get; } // 资源路径 + + // 内部属性 + internal ELoadMethod LoadMethod; // 加载方法 + internal string GUID { get; } // 唯一标识符 + internal PackageAsset Asset { get; } // 内部对象 +} +``` + +### BundleInfo + +资源包信息(内部使用)。 + +```csharp +public class BundleInfo +{ + private readonly IFileSystem _fileSystem; + public readonly PackageBundle Bundle; + + // 加载资源包 + public FSLoadBundleOperation LoadBundleFile(); + + // 创建下载器 + public FSDownloadFileOperation CreateDownloader(int failedTryAgain); + + // 检查是否需要下载 + public bool IsNeedDownloadFromRemote(); + + // 生成唯一标识(用于去重) + public string GetDownloadCombineGUID(); +} +``` + +### PackageDetails + +包裹详细信息(公开 API)。 + +```csharp +public class PackageDetails +{ + public string FileVersion; + public bool EnableAddressable; + public bool SupportExtensionless; + public bool LocationToLower; + public bool IncludeAssetGUID; + public bool ReplaceAssetPathWithAddress; + public int OutputNameStyle; + public int BuildBundleType; + public string BuildPipeline; + public string PackageName; + public string PackageVersion; + public string PackageNote; + public int AssetTotalCount; // 主资源总数 + public int BundleTotalCount; // 资源包总数 +} +``` + +--- + +## 枚举定义 + +### EBuildBundleType + +资源包类型枚举。 + +```csharp +public enum EBuildBundleType +{ + Unknown = 0, // 未知(默认) + VirtualBundle = 1, // 虚拟包(编辑器模拟) + AssetBundle = 2, // Unity AssetBundle + RawBundle = 3, // 原生文件(未压缩) + InstantBundle = 4, // Unity China InstantBundle +} +``` + +### EFileNameStyle + +文件名命名风格枚举。 + +```csharp +public enum EFileNameStyle +{ + HashName = 0, // "abc123def456.bundle" + BundleName = 1, // "assets_ui.bundle" + BundleName_HashName = 2, // "assets_ui_abc123def456.bundle" +} +``` + +### ELoadMethod + +资源加载方法枚举(内部)。 + +```csharp +internal enum ELoadMethod +{ + None = 0, + LoadAsset, // 加载单个资源 + LoadSubAssets, // 加载子资源 + LoadAllAssets, // 加载资源包内全部 + LoadScene, // 加载场景 + LoadRawFile, // 加载原生文件 +} +``` + +--- + +## 关键工作流 + +### 初始化流程 + +``` +1. YooAssets.CreatePackage(packageName) + └── new ResourcePackage(packageName) + +2. package.InitializeAsync(parameters) + ├── CheckInitializeParameters() + ├── new ResourceManager() + ├── new PlayModeImpl() + ├── new InitializationOperation() + └── OperationSystem.StartOperation() + +3. InitializationOperation 逐个初始化 FileSystem + ├── DefaultEditorFileSystem(编辑器模拟) + ├── DefaultBuildinFileSystem(StreamingAssets) + ├── DefaultCacheFileSystem(已缓存资源) + └── DefaultWebServerFileSystem(WebGL) + +4. PlayModeImpl.ActiveManifest = fileSystem.LoadPackageManifest() + └── 清单反序列化并初始化 +``` + +### 资源加载流程 + +``` +package.LoadAssetAsync(location) + │ + ├── ConvertLocationToAssetInfo(location) + │ └── PlayModeImpl.ActiveManifest.ConvertLocationToAssetInfo() + │ └── AssetPathMapping1[location] → AssetPath + │ └── AssetDic[assetPath] → PackageAsset + │ + ├── ResourceManager.LoadAssetAsync(assetInfo) + │ ├── ProviderDic 查询或创建 Provider + │ ├── Provider.StartBundleLoader() + │ │ ├── GetMainBundleInfo() → BundleInfo + │ │ └── GetDependBundleInfos() → List + │ │ + │ └── 并发加载 AssetBundle + │ ├── PlayModeImpl.GetBelongFileSystem() + │ └── IFileSystem.LoadBundleFile() + │ + └── return AssetHandle +``` + +### 清单更新流程 + +``` +package.RequestPackageVersionAsync() + └── PlayModeImpl.GetMainFileSystem().RequestPackageVersionAsync() + │ + ▼ +package.UpdatePackageManifestAsync(version) + ├── 检查 ActiveManifest 版本是否已匹配 + └── PlayModeImpl.GetMainFileSystem().LoadPackageManifestAsync(version) + └── ManifestTools.DeserializeFromBinary() + └── DeserializeManifestOperation(逐个反序列化资源包) +``` + +### 下载流程 + +``` +downloader = package.CreateResourceDownloader(...) + └── PlayModeImpl.GetDownloadListByXXX() + ├── 遍历清单的 BundleList + ├── GetBelongFileSystem(bundle) + └── fileSystem.NeedDownload(bundle) + │ + ▼ +downloader.BeginDownload() + └── DownloaderOperation.InternalUpdate() + ├── 创建 ≤ MAX_LOADER_COUNT 个 FSDownloadFileOperation + ├── fileSystem.DownloadFileAsync(bundle, options) + ├── 更新进度和回调 + └── 失败文件进入重试列表 +``` + +--- + +## 类继承关系 + +``` +AsyncOperationBase +├── InitializationOperation +├── DestroyOperation +├── RequestPackageVersionOperation +│ └── RequestPackageVersionImplOperation +├── UpdatePackageManifestOperation +├── PreDownloadContentOperation +├── ClearCacheFilesOperation +├── DownloaderOperation (abstract) +│ ├── ResourceDownloaderOperation +│ ├── ResourceUnpackerOperation +│ └── ResourceImporterOperation +└── DeserializeManifestOperation + +IPlayMode +└── PlayModeImpl (also implements IBundleQuery) + +PackageManifest +├── List AssetList +└── List BundleList +``` + +--- + +## 使用示例 + +### 初始化包裹(联机模式) + +```csharp +// 创建包裹 +var package = YooAssets.CreatePackage("DefaultPackage"); + +// 创建内置文件系统参数 +var buildinParams = FileSystemParameters.CreateDefaultBuildinFileSystemParameters(); + +// 创建缓存文件系统参数 +var cacheParams = FileSystemParameters.CreateDefaultCacheFileSystemParameters( + remoteServices: new GameRemoteServices() +); + +// 创建联机模式初始化参数 +var initParams = new HostPlayModeParameters(); +initParams.BuildinFileSystemParameters = buildinParams; +initParams.CacheFileSystemParameters = cacheParams; + +// 异步初始化 +var initOp = package.InitializeAsync(initParams); +await initOp.ToTask(); + +if (initOp.Status == EOperationStatus.Succeed) +{ + Debug.Log("包裹初始化成功!"); +} +``` + +### 更新清单 + +```csharp +// 请求最新版本 +var versionOp = package.RequestPackageVersionAsync(); +await versionOp.ToTask(); + +if (versionOp.Status != EOperationStatus.Succeed) +{ + Debug.LogError($"请求版本失败:{versionOp.Error}"); + return; +} + +string packageVersion = versionOp.PackageVersion; +Debug.Log($"最新版本:{packageVersion}"); + +// 更新清单 +var updateOp = package.UpdatePackageManifestAsync(packageVersion); +await updateOp.ToTask(); + +if (updateOp.Status == EOperationStatus.Succeed) +{ + Debug.Log("清单更新成功!"); +} +``` + +### 下载资源 + +```csharp +// 创建下载器(最多10个并发,失败重试3次) +var downloader = package.CreateResourceDownloader(10, 3); + +if (downloader.TotalDownloadCount == 0) +{ + Debug.Log("没有需要下载的资源"); + return; +} + +Debug.Log($"需要下载 {downloader.TotalDownloadCount} 个文件," + + $"总大小 {downloader.TotalDownloadBytes / 1024 / 1024}MB"); + +// 注册回调 +downloader.DownloadUpdateCallback = (info) => +{ + Debug.Log($"下载进度:{info.Progress * 100:F1}%"); +}; + +downloader.DownloadErrorCallback = (info) => +{ + Debug.LogError($"下载错误:{info.FileName} - {info.Error}"); +}; + +// 开始下载 +downloader.BeginDownload(); +await downloader.ToTask(); + +if (downloader.Status == EOperationStatus.Succeed) +{ + Debug.Log("下载完成!"); +} +``` + +### 按标签下载 + +```csharp +// 按单个标签下载 +var downloader = package.CreateResourceDownloader("level1", 10, 3); + +// 按多个标签下载 +var downloader = package.CreateResourceDownloader( + new string[] { "level1", "level2" }, 10, 3); + +downloader.BeginDownload(); +await downloader.ToTask(); +``` + +### 加载资源 + +```csharp +// 异步加载预制体 +var handle = package.LoadAssetAsync("Assets/Prefabs/Player.prefab"); +await handle.ToTask(); + +if (handle.Status == EOperationStatus.Succeed) +{ + var prefab = handle.AssetObject as GameObject; + var player = GameObject.Instantiate(prefab); + + // 使用完毕后释放 + // handle.Release(); +} +else +{ + Debug.LogError($"加载失败:{handle.LastError}"); +} +``` + +### 加载场景 + +```csharp +// 异步加载场景(叠加模式) +var handle = package.LoadSceneAsync("Assets/Scenes/GameScene.unity", LoadSceneMode.Additive); +await handle.ToTask(); + +if (handle.Status == EOperationStatus.Succeed) +{ + Debug.Log("场景加载成功!"); +} +``` + +### 预下载指定版本 + +```csharp +// 预下载特定版本 +var predownloadOp = package.PreDownloadContentAsync("1.0.5"); +await predownloadOp.ToTask(); + +if (predownloadOp.Status == EOperationStatus.Succeed) +{ + // 创建下载器 + var downloader = predownloadOp.CreateResourceDownloader(10, 3); + downloader.BeginDownload(); + await downloader.ToTask(); +} +``` + +### 清理缓存 + +```csharp +// 清理所有缓存 +var clearOp = package.ClearCacheFilesAsync(EFileClearMode.ClearAllBundleFiles); +await clearOp.ToTask(); + +// 清理未使用的缓存 +var clearOp = package.ClearCacheFilesAsync(EFileClearMode.ClearUnusedBundleFiles); +await clearOp.ToTask(); + +// 按标签清理 +var clearOp = package.ClearCacheFilesAsync( + EFileClearMode.ClearBundleFilesByTags.ToString(), "dlc_old"); +await clearOp.ToTask(); +``` + +### 卸载资源 + +```csharp +// 卸载未使用的资源 +var unloadOp = package.UnloadUnusedAssetsAsync(); +await unloadOp.ToTask(); + +// 强制卸载全部资源 +var unloadOp = package.UnloadAllAssetsAsync(); +await unloadOp.ToTask(); +``` + +--- + +## 注意事项 + +1. **初始化顺序**:必须先初始化包裹才能进行其他操作 +2. **清单更新**:建议在更新清单前调用 `UnloadAllAssetsAsync()` 释放已加载资源 +3. **资源释放**:使用后必须调用 `Handle.Release()` 避免内存泄漏 +4. **并发限制**:下载器最大并发数为 64,建议根据网络环境合理设置 +5. **版本兼容**:清单文件版本需 >= 2025.8.28 +6. **异常处理**:操作失败时检查 `Status` 和 `Error` 属性 +7. **线程安全**:所有核心逻辑在 Unity 主线程执行,无需额外同步 diff --git a/Assets/YooAsset/Runtime/ResourcePackage/README.md.meta b/Assets/YooAsset/Runtime/ResourcePackage/README.md.meta new file mode 100644 index 00000000..6f584ed2 --- /dev/null +++ b/Assets/YooAsset/Runtime/ResourcePackage/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0ee8a6b68676976428b1886c273eaeb6 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: