refactor : 重构代码

This commit is contained in:
何冠峰
2026-01-20 19:20:50 +08:00
parent 8ef7eabec8
commit 65bfd3d892
49 changed files with 734 additions and 3090 deletions

View File

@@ -125,13 +125,13 @@ namespace YooAsset.Editor
_providerTableView.AddColumn(column);
}
// BeginTime
// StartTime
{
var columnStyle = new ColumnStyle(100);
columnStyle.Stretchable = false;
columnStyle.Searchable = false;
columnStyle.Sortable = true;
var column = new TableColumn("BeginTime", "Begin Time", columnStyle);
var column = new TableColumn("StartTime", "Start Time", columnStyle);
column.MakeCell = () =>
{
var label = new Label();
@@ -314,7 +314,7 @@ namespace YooAsset.Editor
rowData.AddAssetPathCell("PackageName", packageData.PackageName);
rowData.AddStringValueCell("AssetPath", providerInfo.AssetPath);
rowData.AddStringValueCell("SpawnScene", providerInfo.OriginScene);
rowData.AddStringValueCell("BeginTime", providerInfo.StartTime);
rowData.AddStringValueCell("StartTime", providerInfo.StartTime);
rowData.AddLongValueCell("LoadingTime", providerInfo.ElapsedMS);
rowData.AddLongValueCell("RefCount", providerInfo.ReferenceCount);
rowData.AddStringValueCell("Status", providerInfo.Status.ToString());

View File

@@ -208,13 +208,13 @@ namespace YooAsset.Editor
_usingTableView.AddColumn(column);
}
// BeginTime
// StartTime
{
var columnStyle = new ColumnStyle(100);
columnStyle.Stretchable = false;
columnStyle.Searchable = false;
columnStyle.Sortable = true;
var column = new TableColumn("BeginTime", "Begin Time", columnStyle);
var column = new TableColumn("StartTime", "Start Time", columnStyle);
column.MakeCell = () =>
{
var label = new Label();
@@ -451,7 +451,7 @@ namespace YooAsset.Editor
rowData.ProviderInfo = providerInfo;
rowData.AddStringValueCell("UsingAssets", providerInfo.AssetPath);
rowData.AddStringValueCell("SpawnScene", providerInfo.OriginScene);
rowData.AddStringValueCell("BeginTime", providerInfo.StartTime);
rowData.AddStringValueCell("StartTime", providerInfo.StartTime);
rowData.AddLongValueCell("RefCount", providerInfo.ReferenceCount);
rowData.AddStringValueCell("Status", providerInfo.Status);
sourceDatas.Add(rowData);

View File

@@ -147,13 +147,13 @@ namespace YooAsset.Editor
_operationTableView.AddColumn(column);
}
// BeginTime
// StartTime
{
var columnStyle = new ColumnStyle(100);
columnStyle.Stretchable = false;
columnStyle.Searchable = false;
columnStyle.Sortable = true;
var column = new TableColumn("BeginTime", "Begin Time", columnStyle);
var column = new TableColumn("StartTime", "Start Time", columnStyle);
column.MakeCell = () =>
{
var label = new Label();
@@ -168,14 +168,14 @@ namespace YooAsset.Editor
_operationTableView.AddColumn(column);
}
// ProcessTime
// ElapsedMS
{
var columnStyle = new ColumnStyle(130);
columnStyle.Stretchable = false;
columnStyle.Searchable = false;
columnStyle.Sortable = true;
columnStyle.Units = "ms";
var column = new TableColumn("ProcessTime", "Process Time", columnStyle);
var column = new TableColumn("ElapsedMS", "Elapsed MS", columnStyle);
column.MakeCell = () =>
{
var label = new Label();
@@ -259,19 +259,19 @@ namespace YooAsset.Editor
_bottomToolbar.Add(button);
}
// BeginTime
// StartTime
{
ToolbarButton button = new ToolbarButton();
button.text = "BeginTime";
button.text = "StartTime";
button.style.flexGrow = 0;
button.style.width = 100;
_bottomToolbar.Add(button);
}
// ProcessTime
// ElapsedMS
{
ToolbarButton button = new ToolbarButton();
button.text = "ProcessTime (ms)";
button.text = "ElapsedMS";
button.style.flexGrow = 0;
button.style.width = 130;
_bottomToolbar.Add(button);
@@ -319,8 +319,8 @@ namespace YooAsset.Editor
rowData.AddStringValueCell("OperationName", operationInfo.OperationName);
rowData.AddLongValueCell("Priority", operationInfo.Priority);
rowData.AddDoubleValueCell("Progress", operationInfo.Progress);
rowData.AddStringValueCell("BeginTime", operationInfo.StartTime);
rowData.AddLongValueCell("LoadingTime", operationInfo.ElapsedMS);
rowData.AddStringValueCell("StartTime", operationInfo.StartTime);
rowData.AddLongValueCell("ElapsedMS", operationInfo.ElapsedMS);
rowData.AddStringValueCell("Status", operationInfo.Status.ToString());
rowData.AddStringValueCell("Desc", operationInfo.OperationDesc);
_sourceDatas.Add(rowData);
@@ -408,20 +408,20 @@ namespace YooAsset.Editor
container.Add(label);
}
// BeginTime
// StartTime
{
var label = new Label();
label.name = "BeginTime";
label.name = "StartTime";
label.style.flexGrow = 0f;
label.style.width = 100;
label.style.unityTextAlign = TextAnchor.MiddleLeft;
container.Add(label);
}
// ProcessTime
// ElapsedMS
{
var label = new Label();
label.name = "ProcessTime";
label.name = "ElapsedMS";
label.style.flexGrow = 0f;
label.style.width = 130;
label.style.unityTextAlign = TextAnchor.MiddleLeft;
@@ -464,15 +464,15 @@ namespace YooAsset.Editor
label.text = operationInfo.Progress.ToString();
}
// BeginTime
// StartTime
{
var label = container.Q<Label>("BeginTime");
var label = container.Q<Label>("StartTime");
label.text = operationInfo.StartTime;
}
// ProcessTime
// ElapsedMS
{
var label = container.Q<Label>("ProcessTime");
var label = container.Q<Label>("ElapsedMS");
label.text = operationInfo.ElapsedMS.ToString();
}

View File

@@ -0,0 +1,478 @@
# DiagnosticSystem 诊断模块
## 模块概述
DiagnosticSystem 是 YooAsset 资源管理系统的**运行时诊断模块**,负责收集和传输资源加载的实时状态信息。该模块支持 Editor 与运行时 Player 之间的双向通信,为 AssetBundleDebugger 窗口提供数据支持。
### 可见性说明
DiagnosticSystem 属于 YooAsset Runtime 的内部基础模块,目录内大多数类型为 `internal`(仅供 YooAsset Runtime 内部程序集使用)。业务层建议通过 `YooAssets.GetDebugReport()` 等上层接口获取诊断数据,避免直接依赖本模块的内部类型。
### 核心职责
- 资源加载状态采集
- Provider/Bundle/Operation 诊断信息汇总
- Editor 与 Player 的远程通信
- 诊断数据的序列化与传输
---
## 边界与上层协作
DiagnosticSystem 的职责是提供"诊断数据采集 + 远程通信 + 序列化传输"的基础能力:
- **本模块不负责数据展示**:数据展示由 Editor 端的 AssetBundleDebugger 窗口实现。
- **本模块不负责数据持久化**:诊断数据为实时采集,不进行本地存储。
- **本模块不负责数据分析**:数据分析和统计由上层调试工具实现。
---
## 设计目标
| 目标 | 说明 |
|------|------|
| **实时性** | 支持单次采样和持续采样两种模式 |
| **低侵入** | 仅在需要时采集数据,不影响正常运行性能 |
| **跨平台** | 支持 Editor 模拟和真机远程调试 |
| **可扩展性** | 诊断数据结构支持版本控制 |
---
## 架构概念
### 分层架构
```
┌─────────────────────────────────────────────────────────┐
│ AssetBundleDebugger (Editor) │
│ (数据展示层) │
└─────────────────────────┬───────────────────────────────┘
┌─────────────────────────▼───────────────────────────────┐
│ MockEditorConnection (Editor) │
│ EditorConnection (Runtime) │
│ (通信层) │
└─────────────────────────┬───────────────────────────────┘
│ 双向消息
┌─────────────────────────▼───────────────────────────────┐
│ MockPlayerConnection (Editor) │
│ PlayerConnection (Runtime) │
│ (通信层) │
└─────────────────────────┬───────────────────────────────┘
┌─────────────────────────▼───────────────────────────────┐
│ RemoteDebugBehaviour │
│ (运行时组件) │
│ 接收命令、采集数据、发送报告 │
└─────────────────────────┬───────────────────────────────┘
┌─────────────────────────▼───────────────────────────────┐
│ DiagnosticReport │
│ (数据模型) │
│ DiagnosticPackageData / ProviderInfo / BundleInfo │
└─────────────────────────────────────────────────────────┘
```
### 核心组件
- **数据模型层**: 定义诊断数据的结构DiagnosticReport、DiagnosticPackageData 等)
- **通信层**: 处理 Editor 与 Player 之间的消息传递
- **行为组件层**: 运行时 MonoBehaviour负责采集和发送诊断数据
---
## 文件结构
```
DiagnosticSystem/
├── DiagnosticBundleInfo.cs # 资源包诊断信息
├── DiagnosticOperationInfo.cs # 异步操作诊断信息
├── DiagnosticPackageData.cs # 包裹诊断数据容器
├── DiagnosticProviderInfo.cs # 资源加载诊断信息
├── DiagnosticReport.cs # 诊断报告(顶层数据结构)
├── DiagnosticSystemDefine.cs # 诊断系统常量定义
├── EDebugCommandType.cs # 调试命令类型枚举
├── RemoteDebugCommand.cs # 远程调试命令
├── RemoteDebugBehaviour.cs # 运行时调试组件
├── MockEditorConnection.cs # 模拟 Editor 连接Editor 用)
└── MockPlayerConnection.cs # 模拟 Player 连接Editor 用)
```
---
## 数据模型
### DiagnosticReport诊断报告
顶层数据结构,包含所有包裹的诊断数据。
```csharp
[Serializable]
internal class DiagnosticReport
{
/// <summary>
/// 调试器版本
/// </summary>
public string DebuggerVersion;
/// <summary>
/// 报告发生的游戏帧
/// </summary>
public int FrameCount;
/// <summary>
/// 包裹数据列表
/// </summary>
public List<DiagnosticPackageData> PackageDataList;
// 序列化/反序列化方法
public static byte[] Serialize(DiagnosticReport report);
public static DiagnosticReport Deserialize(byte[] data);
}
```
### DiagnosticPackageData包裹诊断数据
单个包裹的诊断数据容器。
```csharp
[Serializable]
internal class DiagnosticPackageData
{
/// <summary>
/// 包裹名称
/// </summary>
public string PackageName;
/// <summary>
/// 资源加载的诊断信息列表
/// </summary>
public List<DiagnosticProviderInfo> ProviderInfos;
/// <summary>
/// 资源包的诊断信息列表
/// </summary>
public List<DiagnosticBundleInfo> BundleInfos;
/// <summary>
/// 异步操作的诊断信息列表
/// </summary>
public List<DiagnosticOperationInfo> OperationInfos;
// 快速查找方法
public DiagnosticBundleInfo GetBundleInfo(string bundleName);
}
```
### DiagnosticProviderInfo资源加载诊断信息
描述单个资源加载操作的状态。
```csharp
[Serializable]
internal struct DiagnosticProviderInfo
{
public string AssetPath; // 资源对象路径
public string OriginScene; // 资源加载时所在的场景
public string StartTime; // 资源加载开始时间
public long ElapsedMS; // 加载耗时(毫秒)
public int ReferenceCount; // 引用计数
public string Status; // 资源的加载状态
public List<string> DependentBundles; // 依赖的资源包列表
}
```
### DiagnosticBundleInfo资源包诊断信息
描述单个 AssetBundle 的状态。
```csharp
[Serializable]
internal struct DiagnosticBundleInfo
{
public string BundleName; // 资源包名称
public int ReferenceCount; // 引用计数
public string Status; // 资源包的加载状态
public List<string> ReferencedByBundles; // 该资源包被谁引用
}
```
### DiagnosticOperationInfo异步操作诊断信息
描述异步操作的执行状态。
```csharp
[Serializable]
internal struct DiagnosticOperationInfo
{
public string OperationName; // 异步操作的名称
public string OperationDesc; // 异步操作的说明
public uint Priority; // 异步操作的优先级
public string StartTime; // 开始的时间
public long ElapsedMS; // 处理耗时(毫秒)
public float Progress; // 异步操作的执行进度
public string Status; // 异步操作的执行状态
public List<DiagnosticOperationInfo> Children; // 子任务列表
}
```
---
## 通信协议
### 命令类型
```csharp
/// <summary>
/// 远程调试命令类型
/// </summary>
internal enum EDebugCommandType
{
/// <summary>
/// 采样一次
/// </summary>
SampleOnce = 0,
/// <summary>
/// 持续采样
/// </summary>
AutoSampling = 1,
}
```
### RemoteDebugCommand远程调试命令
Editor 向 Player 发送的调试指令。
```csharp
[Serializable]
internal class RemoteDebugCommand
{
/// <summary>
/// 命令类型
/// </summary>
public int CommandType;
/// <summary>
/// 命令附加参数
/// </summary>
public string Parameter;
// 序列化/反序列化方法
public static byte[] Serialize(RemoteDebugCommand command);
public static RemoteDebugCommand Deserialize(byte[] data);
}
```
### 消息标识符
```csharp
internal class DiagnosticSystemDefine
{
/// <summary>
/// 调试器版本号
/// </summary>
public const string DebuggerVersion = "1.0";
/// <summary>
/// Player 向 Editor 发送消息的标识符
/// </summary>
public static readonly Guid PlayerToEditorMessageId;
/// <summary>
/// Editor 向 Player 发送消息的标识符
/// </summary>
public static readonly Guid EditorToPlayerMessageId;
}
```
---
## 核心类说明
### RemoteDebugBehaviour
运行时调试组件,负责接收 Editor 命令并发送诊断数据。
**职责:**
- 监听 Editor 发送的调试命令
- 根据命令采集诊断数据
- 将诊断报告发送回 Editor
**采样模式:**
- `SampleOnce`: 单次采样,采集一帧数据后停止
- `AutoSampling`: 持续采样,每帧自动采集并发送数据
**生命周期:**
```
Awake() ──► 初始化连接
OnEnable() ──► 注册消息处理器
LateUpdate()──► 检查采样标志,采集并发送数据
OnDisable() ──► 注销消息处理器
```
### MockEditorConnection / MockPlayerConnection
Editor 模式下的模拟连接,用于本地调试。
**特性:**
- 在 Editor 中模拟 `EditorConnection``PlayerConnection` 的行为
- 实现消息的本地传递,无需真机连接
- 支持消息注册、注销和发送
```csharp
// 注册消息处理器
MockPlayerConnection.Instance.Register(messageId, callback);
// 发送消息
MockPlayerConnection.Instance.Send(messageId, data);
// 注销消息处理器
MockPlayerConnection.Instance.Unregister(messageId);
```
---
## 通信流程
### Editor 模式(本地调试)
```
┌───────────────────┐ ┌───────────────────┐
│ Debugger Window │ │ RemoteDebugBehaviour│
│ (Editor) │ │ (PlayMode) │
└────────┬──────────┘ └────────┬──────────┘
│ │
│ 1. 发送采样命令 │
│ ─────────────────────────────►
│ MockEditorConnection.Send() │
│ │
│ │ 2. 接收命令
│ │ HandleEditorMessage()
│ │
│ │ 3. 采集诊断数据
│ │ YooAssets.GetDebugReport()
│ │
│ 4. 返回诊断报告 │
│ ◄─────────────────────────────
│ MockPlayerConnection.Send() │
│ │
│ 5. 解析并展示数据 │
▼ ▼
```
### 真机模式(远程调试)
```
┌───────────────────┐ ┌───────────────────┐
│ Debugger Window │ │ RemoteDebugBehaviour│
│ (Editor) │ USB │ (Device) │
└────────┬──────────┘ ════ └────────┬──────────┘
│ │
│ 1. 发送采样命令 │
│ ─────────────────────────────►
│ EditorConnection.Send() │
│ │
│ │ 2. 接收命令
│ │ PlayerConnection.Register()
│ │
│ │ 3. 采集诊断数据
│ │
│ 4. 返回诊断报告 │
│ ◄─────────────────────────────
│ PlayerConnection.Send() │
│ │
│ 5. 解析并展示数据 │
▼ ▼
```
---
## 设计模式
### 单例模式
`MockEditorConnection``MockPlayerConnection` 使用单例模式:
```csharp
public static MockPlayerConnection Instance
{
get
{
if (_instance == null)
_instance = new MockPlayerConnection();
return _instance;
}
}
```
### 观察者模式
通过消息注册机制实现观察者模式:
```
Register(messageId, callback) ──► 订阅消息
Unregister(messageId) ──► 取消订阅
HandleXxxMessage(messageId, data) ──► 通知订阅者
```
### 命令模式
`RemoteDebugCommand` 封装调试指令:
```
Editor Player
│ │
│ RemoteDebugCommand │
│ ┌──────────────────┐ │
│ │ CommandType: 0 │ │
│ │ Parameter: "" │ ──────►│ 执行采样
│ └──────────────────┘ │
│ │
│ RemoteDebugCommand │
│ ┌──────────────────┐ │
│ │ CommandType: 1 │ │
│ │ Parameter: "open"│ ──────►│ 开启持续采样
│ └──────────────────┘ │
```
---
## 类关系图
```
DiagnosticReport (诊断报告)
└── List<DiagnosticPackageData> (包裹数据)
├── List<DiagnosticProviderInfo> (资源加载信息)
├── List<DiagnosticBundleInfo> (资源包信息)
└── List<DiagnosticOperationInfo> (异步操作信息)
└── List<DiagnosticOperationInfo> (子任务)
RemoteDebugBehaviour (运行时组件)
├── 接收 ──► RemoteDebugCommand
└── 发送 ──► DiagnosticReport
MockEditorConnection ◄────► MockPlayerConnection (Editor 模拟通信)
EditorConnection ◄────► PlayerConnection (真机通信)
```
---
## 注意事项
1. **采样性能**:持续采样模式会每帧采集数据,可能影响性能,建议仅在调试时使用
2. **数据序列化**:使用 Unity 的 `JsonUtility` 进行序列化,受其限制(如不支持 Dictionary
3. **版本兼容**`DebuggerVersion` 用于版本控制Editor 和 Player 版本不匹配时可能无法正常工作
4. **子任务深度**`DiagnosticOperationInfo.Children` 存在序列化深度限制10层
5. **Editor 模式**:在 Editor 模式下使用 Mock 连接进行本地通信,无需真机
6. **真机调试**:真机调试需要通过 USB 连接,使用 Unity 的 `PlayerConnection` API
7. **线程安全**:所有操作应在主线程进行
8. **资源释放**`MockEditorConnection``MockPlayerConnection` 在 Domain Reload 时会自动重置

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 0ee8a6b68676976428b1886c273eaeb6
guid: 5fa2b66c20800124c8dd5cb77e854ce3
TextScriptImporter:
externalObjects: {}
userData:

View File

@@ -144,7 +144,7 @@ internal interface IDownloadRequest : IDisposable
string URL { get; }
// 生命周期
bool IsDone { get; } // 每次访问自动轮询
bool IsDone { get; } // 访问自动调用 PollingRequest()
EDownloadRequestStatus Status { get; }
// 进度跟踪
@@ -561,20 +561,25 @@ SimulateRequestFile (独立实现) ──► IDownloadFileRequest
### DownloadSystemTools
提供跨平台的工具函数
提供跨平台的 URL 转换和判断功能
| 方法 | 说明 |
|------|------|
| `ToLocalURL()` | 转换本地路径为文件协议 URL |
| `IsLocalFileURL()` | 判断是否本地文件 URL |
| 方法 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| `ToLocalURL(path)` | `string path` 本地文件路径 | 可用于 UnityWebRequest 的文件协议 URL | 转换本地路径为文件协议 URL(自动处理特殊字符) |
| `IsLocalFileURL(url)` | `string url` 要判断的 URL | `bool` 是否本地文件 URL | 判断 URL 是否为 `file:``jar:file:` 协议 |
### DownloadFailureCounter
请求失败计数器,用于诊断统计
网络请求失败计数器(诊断用)
- 线程安全:内部使用 `Dictionary` 且未加锁,约定只在主线程调用;如需多线程统计请在外层加锁或改实现
- Key 规则`$"{packageName}_{eventName}"`
- 统计口径:**仅统计网络请求失败**`IDownloadRequest.Status != Succeed` 时记录),不统计内容为空、校验失败、解析失败等业务层失败
- **线程安全**:内部使用 `Dictionary` 且未加锁,约定只在 Unity 主线程调用;如需多线程/回调线程调用,请在外层加锁或改为并发容器实现
- **Key 格式**`$"{packageName}_{eventName}"`
- **统计口径****仅统计网络请求失败**`IDownloadRequest.Status != Succeed` 时记录),不统计内容为空、校验失败、解析失败等业务层失败
| 方法 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| `RecordFailure(packageName, eventName)` | `string packageName` 资源包名称, `string eventName` 事件名称 | `void` | 记录一次失败 |
| `GetFailureCount(packageName, eventName)` | `string packageName` 资源包名称, `string eventName` 事件名称 | `int` 失败次数(未记录过返回 0 | 获取失败次数 |
```csharp
// 记录失败

View File

@@ -49,7 +49,7 @@ namespace YooAsset
if (_steps == ESteps.LoadAsset)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
if (_assetInfo.AssetType == null)
Result = _assetBundle.LoadAllAssets();
@@ -71,7 +71,7 @@ namespace YooAsset
{
if (_request != null)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
// 强制挂起主线程(注意:该操作会很耗时)
YooLogger.Warning("Suspend the main thread to load unity asset.");

View File

@@ -49,7 +49,7 @@ namespace YooAsset
if (_steps == ESteps.LoadAsset)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
if (_assetInfo.AssetType == null)
Result = _assetBundle.LoadAsset(_assetInfo.AssetPath);
@@ -71,7 +71,7 @@ namespace YooAsset
{
if (_request != null)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
// 强制挂起主线程(注意:该操作会很耗时)
YooLogger.Warning("Suspend the main thread to load unity asset.");

View File

@@ -36,7 +36,7 @@ namespace YooAsset
if (_steps == ESteps.LoadScene)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
// 注意:场景同步加载方法不会立即加载场景,而是在下一帧加载。
Result = SceneManager.LoadScene(_assetInfo.AssetPath, _loadParams);
@@ -69,7 +69,7 @@ namespace YooAsset
{
if (_asyncOperation != null)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
//注意:场景加载无法强制异步转同步
YooLogger.Error("The scene is loading asyn.");

View File

@@ -49,7 +49,7 @@ namespace YooAsset
if (_steps == ESteps.LoadAsset)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
if (_assetInfo.AssetType == null)
Result = _assetBundle.LoadAssetWithSubAssets(_assetInfo.AssetPath);
@@ -71,7 +71,7 @@ namespace YooAsset
{
if (_request != null)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
// 强制挂起主线程(注意:该操作会很耗时)
YooLogger.Warning("Suspend the main thread to load unity asset.");

View File

@@ -43,7 +43,7 @@ namespace YooAsset
if (_steps == ESteps.LoadScene)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
Result = UnityEditor.SceneManagement.EditorSceneManager.LoadSceneInPlayMode(_assetInfo.AssetPath, _loadParams);
_steps = ESteps.CheckResult;
@@ -74,7 +74,7 @@ namespace YooAsset
{
if (_asyncOperation != null)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
// 注意:场景加载无法强制异步转同步
YooLogger.Error("The scene is loading asyn.");

View File

@@ -54,7 +54,7 @@ namespace YooAsset
}
}
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
if (_bundle.Encrypted)
{
@@ -90,7 +90,7 @@ namespace YooAsset
{
if (_createRequest != null)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
// 强制挂起主线程(注意:该操作会很耗时)
YooLogger.Warning("Suspend the main thread to load unity bundle.");
@@ -253,7 +253,7 @@ namespace YooAsset
}
}
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
if (_bundle.Encrypted)
{
@@ -289,7 +289,7 @@ namespace YooAsset
{
if (_createRequest != null)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
// 强制挂起主线程(注意:该操作会很耗时)
YooLogger.Warning("Suspend the main thread to load unity bundle.");

View File

@@ -126,7 +126,7 @@ namespace YooAsset
}
}
}
internal override string InternalGetDesc()
internal override string InternalGetDescription()
{
return $"PackageVersion : {_packageVersion} PackageHash : {_packageHash}";
}

View File

@@ -85,7 +85,7 @@ namespace YooAsset
AddChildOperation(_downloadFileOp);
}
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
_downloadFileOp.WaitForAsyncComplete();
_downloadFileOp.UpdateOperation();
@@ -110,7 +110,7 @@ namespace YooAsset
{
if (_downloadFileOp != null)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
_downloadFileOp.WaitForAsyncComplete();
_downloadFileOp.UpdateOperation();
@@ -137,7 +137,7 @@ namespace YooAsset
}
}
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
if (_bundle.Encrypted)
{
@@ -173,7 +173,7 @@ namespace YooAsset
{
if (_createRequest != null)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
// 强制挂起主线程(注意:该操作会很耗时)
YooLogger.Warning("Suspend the main thread to load unity bundle.");
@@ -354,7 +354,7 @@ namespace YooAsset
AddChildOperation(_downloadFileOp);
}
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
_downloadFileOp.WaitForAsyncComplete();
_downloadFileOp.UpdateOperation();
@@ -379,7 +379,7 @@ namespace YooAsset
{
if (_downloadFileOp != null)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
_downloadFileOp.WaitForAsyncComplete();
_downloadFileOp.UpdateOperation();

View File

@@ -75,7 +75,7 @@ namespace YooAsset
// 检测下载结果
if (_steps == ESteps.CheckRequest)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
_downloadFileOp.WaitForAsyncComplete();
_downloadFileOp.UpdateOperation();
@@ -92,7 +92,7 @@ namespace YooAsset
}
else
{
if (IsWaitForAsyncComplete == false && _failedTryAgain > 0)
if (IsWaitingForAsyncComplete == false && _failedTryAgain > 0)
{
_steps = ESteps.TryAgain;
YooLogger.Warning($"Failed download : {_downloadFileOp.URL} Try again.");

View File

@@ -99,7 +99,7 @@ namespace YooAsset
}
}
}
internal override string InternalGetDesc()
internal override string InternalGetDescription()
{
return $"PackageVersion : {_packageVersion} PackageHash : {_packageHash}";
}

View File

@@ -27,7 +27,7 @@ namespace YooAsset
{
URL = url;
}
internal override string InternalGetDesc()
internal override string InternalGetDescription()
{
return $"RefCount : {RefCount}";
}

View File

@@ -99,7 +99,7 @@ namespace YooAsset
if (_steps == ESteps.CheckRequest)
{
//TODO 更新下载后台,防止无限挂起
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
_fileSystem.DownloadBackend.Update();
DownloadProgress = _request.DownloadProgress;
@@ -135,7 +135,7 @@ namespace YooAsset
AddChildOperation(_verifyOperation);
}
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
_verifyOperation.WaitForAsyncComplete();
_verifyOperation.UpdateOperation();

View File

@@ -98,7 +98,7 @@ namespace YooAsset
AddChildOperation(_verifyOperation);
}
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
_verifyOperation.WaitForAsyncComplete();
_verifyOperation.UpdateOperation();

View File

@@ -113,7 +113,7 @@ namespace YooAsset
}
}
}
internal override string InternalGetDesc()
internal override string InternalGetDescription()
{
return $"{_fileSystem.GetType().FullName}";
}

View File

@@ -70,7 +70,7 @@ namespace YooAsset
AddChildOperation(_downloadFileOp);
}
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
_downloadFileOp.WaitForAsyncComplete();
_downloadFileOp.UpdateOperation();
@@ -95,7 +95,7 @@ namespace YooAsset
{
if (_downloadFileOp != null)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
_downloadFileOp.WaitForAsyncComplete();
_downloadFileOp.UpdateOperation();
@@ -110,7 +110,7 @@ namespace YooAsset
if (_steps == ESteps.LoadAssetBundle)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
if (_fileSystem.VirtualWebGLMode)
{

View File

@@ -91,7 +91,7 @@ namespace YooAsset
}
else
{
if (IsWaitForAsyncComplete == false && _failedTryAgain > 0)
if (IsWaitingForAsyncComplete == false && _failedTryAgain > 0)
{
_steps = ESteps.TryAgain;
YooLogger.Warning($"Failed download : {_downloadFileOp.URL} Try again.");

View File

@@ -99,7 +99,7 @@ namespace YooAsset
}
}
}
internal override string InternalGetDesc()
internal override string InternalGetDescription()
{
return $"PackageVersion : {_packageVersion} PackageHash : {_packageHash}";
}

View File

@@ -120,7 +120,7 @@ internal class LoadWebPackageManifestOperation : AsyncOperationBase
}
}
}
internal override string InternalGetDesc()
internal override string InternalGetDescription()
{
return $"PackageVersion : {_packageVersion} PackageHash : {_packageHash}";
}

View File

@@ -110,7 +110,7 @@ namespace YooAsset
}
}
}
internal override string InternalGetDesc()
internal override string InternalGetDescription()
{
return $"PackageVersion : {_packageVersion} PackageHash : {_packageHash}";
}

View File

@@ -6,16 +6,20 @@ using System.Threading.Tasks;
namespace YooAsset
{
/// <summary>
/// 异步操作基类,所有异步操作的抽象基类
/// 支持协程IEnumerator、Taskasync/await、回调等多种异步编程模式
/// </summary>
public abstract class AsyncOperationBase : IEnumerator, IComparable<AsyncOperationBase>
{
private List<AsyncOperationBase> _childs;
private Action<AsyncOperationBase> _callback;
private List<AsyncOperationBase> _children;
private Action<AsyncOperationBase> _completedCallbacks;
private uint _priority;
/// <summary>
/// 等待异步执行完成
/// 是否正处于同步等待状态
/// </summary>
internal bool IsWaitForAsyncComplete { get; private set; }
internal bool IsWaitingForAsyncComplete { get; private set; }
/// <summary>
/// 标记脏(用于调度器检测并重排)
@@ -23,25 +27,25 @@ namespace YooAsset
internal bool IsDirty { get; set; }
/// <summary>
/// 是否已完成
/// 任务是否已结束已触发回调和Task完成
/// </summary>
internal bool IsFinish { get; private set; }
internal bool IsFinished { get; private set; }
/// <summary>
/// 异步系统是否繁忙
/// 当前帧时间切片是否已用完同步等待时始终返回false
/// </summary>
internal bool IsBusy
{
get
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
return false;
return OperationSystem.IsBusy;
}
}
/// <summary>
/// 任务优先级
/// 任务优先级(值越大越优先执行)
/// </summary>
public uint Priority
{
@@ -74,7 +78,7 @@ namespace YooAsset
public float Progress { get; protected set; }
/// <summary>
/// 是否已经完成
/// 任务逻辑是否完成Status为Succeed或Failed
/// </summary>
public bool IsDone
{
@@ -108,17 +112,17 @@ namespace YooAsset
}
else
{
_callback += value;
_completedCallbacks += value;
}
}
remove
{
_callback -= value;
_completedCallbacks -= value;
}
}
/// <summary>
/// 异步操作任务
/// 用于 async/await 的 Task 对象
/// </summary>
public Task Task
{
@@ -134,16 +138,37 @@ namespace YooAsset
}
}
/// <summary>
/// 内部启动方法(子类必须实现)
/// </summary>
internal abstract void InternalStart();
/// <summary>
/// 内部更新方法(子类必须实现)
/// </summary>
internal abstract void InternalUpdate();
/// <summary>
/// 内部中止方法(子类可选实现)
/// </summary>
internal virtual void InternalAbort()
{
}
/// <summary>
/// 内部同步等待方法(子类可选实现)
/// 默认抛出异常,如果异步操作需要支持,子类应重写以支持同步等待
/// </summary>
internal virtual void InternalWaitForAsyncComplete()
{
throw new YooInternalException($"InternalWaitForAsyncComplete() not implemented : {this.GetType().Name}");
}
internal virtual string InternalGetDesc()
/// <summary>
/// 获取操作的描述信息(子类可选实现)
/// </summary>
internal virtual string InternalGetDescription()
{
return string.Empty;
}
@@ -153,8 +178,8 @@ namespace YooAsset
/// </summary>
internal void AddChildOperation(AsyncOperationBase child)
{
if (_childs == null)
_childs = new List<AsyncOperationBase>(10);
if (_children == null)
_children = new List<AsyncOperationBase>(10);
#if UNITY_EDITOR || DEBUG
if (child == null)
@@ -163,7 +188,7 @@ namespace YooAsset
if (ReferenceEquals(child, this))
throw new YooInternalException("The child node cannot be itself.");
if (_childs.Contains(child))
if (_children.Contains(child))
throw new YooInternalException($"The child node {child.GetType().Name} already exists.");
// 禁止形成环依赖
@@ -171,7 +196,7 @@ namespace YooAsset
throw new YooInternalException($"AddChildOperation would create a cycle : {this.GetType().Name} -> {child.GetType().Name}");
#endif
_childs.Add(child);
_children.Add(child);
}
/// <summary>
@@ -179,26 +204,26 @@ namespace YooAsset
/// </summary>
internal void RemoveChildOperation(AsyncOperationBase child)
{
if (_childs == null)
if (_children == null)
return;
#if UNITY_EDITOR || DEBUG
if (child == null)
throw new YooInternalException("The child node is null.");
if (_childs.Contains(child) == false)
if (_children.Contains(child) == false)
throw new YooInternalException($"The child node {child.GetType().Name} not exists.");
#endif
_childs.Remove(child);
_children.Remove(child);
}
/// <summary>
/// 获取异步操作说明
/// </summary>
internal string GetOperationDesc()
internal string GetOperationDescription()
{
return InternalGetDesc();
return InternalGetDescription();
}
/// <summary>
@@ -252,7 +277,7 @@ namespace YooAsset
}
}
if (IsDone && IsFinish == false)
if (IsDone && IsFinished == false)
{
FinishOperation();
}
@@ -263,9 +288,9 @@ namespace YooAsset
/// </summary>
internal void AbortOperation()
{
if (_childs != null)
if (_children != null)
{
foreach (var child in _childs)
foreach (var child in _children)
{
child.AbortOperation();
}
@@ -284,21 +309,21 @@ namespace YooAsset
}
/// <summary>
/// 强制结束异步任务
/// 完成异步任务触发回调和Task完成
/// </summary>
private void FinishOperation()
{
if (IsFinish == false)
if (IsFinished == false)
{
IsFinish = true;
IsFinished = true;
Progress = 1f;
// 结束记录
DebugEndRecording();
if (_callback != null)
if (_completedCallbacks != null)
{
var invocationList = _callback.GetInvocationList();
var invocationList = _completedCallbacks.GetInvocationList();
foreach (var handler in invocationList)
{
try
@@ -312,7 +337,7 @@ namespace YooAsset
}
}
_callback = null;
_completedCallbacks = null;
if (_taskCompletionSource != null)
_taskCompletionSource.TrySetResult(null);
}
@@ -388,9 +413,9 @@ namespace YooAsset
StartOperation();
}
if (IsWaitForAsyncComplete == false)
if (IsWaitingForAsyncComplete == false)
{
IsWaitForAsyncComplete = true;
IsWaitingForAsyncComplete = true;
if (IsDone == false)
InternalWaitForAsyncComplete();
@@ -409,52 +434,58 @@ namespace YooAsset
#region
/// <summary>
/// 开始的时间
/// 任务开始的时间格式HH:MM:SS仅DEBUG模式有效
/// </summary>
public string BeginTime { get; protected set; }
public string StartTime { get; protected set; }
/// <summary>
/// 处理耗时(单位:毫秒)
/// </summary>
public long ProcessTime { get; protected set; }
public long ElapsedMS { get; protected set; }
// 加载耗时统计
private Stopwatch _watch = null;
/// <summary>
/// 任务耗时计时器
/// </summary>
private Stopwatch _stopwatch = null;
[Conditional("DEBUG")]
private void DebugBeginRecording()
{
if (_watch == null)
if (_stopwatch == null)
{
BeginTime = SpawnTimeToString(TimeUtility.RealtimeSinceStartup);
_watch = Stopwatch.StartNew();
StartTime = FormatElapsedTime(TimeUtility.RealtimeSinceStartup);
_stopwatch = Stopwatch.StartNew();
}
}
[Conditional("DEBUG")]
private void DebugUpdateRecording()
{
if (_watch != null)
if (_stopwatch != null)
{
ProcessTime = _watch.ElapsedMilliseconds;
ElapsedMS = _stopwatch.ElapsedMilliseconds;
}
}
[Conditional("DEBUG")]
private void DebugEndRecording()
{
if (_watch != null)
if (_stopwatch != null)
{
ProcessTime = _watch.ElapsedMilliseconds;
_watch = null;
ElapsedMS = _stopwatch.ElapsedMilliseconds;
_stopwatch = null;
}
}
private string SpawnTimeToString(double spawnTime)
/// <summary>
/// 将游戏运行时间格式化为 HH:MM:SS 格式
/// </summary>
/// <param name="time">运行时间(秒)</param>
private string FormatElapsedTime(double time)
{
double h = System.Math.Floor(spawnTime / 3600);
double m = System.Math.Floor(spawnTime / 60 - h * 60);
double s = System.Math.Floor(spawnTime - m * 60 - h * 3600);
double h = System.Math.Floor(time / 3600);
double m = System.Math.Floor(time / 60 - h * 60);
double s = System.Math.Floor(time - m * 60 - h * 3600);
return h.ToString("00") + ":" + m.ToString("00") + ":" + s.ToString("00");
}
@@ -487,13 +518,13 @@ namespace YooAsset
if (ReferenceEquals(node, this))
return true;
if (node._childs == null)
if (node._children == null)
continue;
// 将子节点加入栈
for (int i = 0; i < node._childs.Count; i++)
for (int i = 0; i < node._children.Count; i++)
{
stack.Push(node._childs[i]);
stack.Push(node._children[i]);
}
}
@@ -508,21 +539,21 @@ namespace YooAsset
{
var operationInfo = new DiagnosticOperationInfo();
operationInfo.OperationName = this.GetType().Name;
operationInfo.OperationDesc = GetOperationDesc();
operationInfo.OperationDesc = GetOperationDescription();
operationInfo.Priority = Priority;
operationInfo.Progress = Progress;
operationInfo.StartTime = BeginTime;
operationInfo.ElapsedMS = ProcessTime;
operationInfo.StartTime = StartTime;
operationInfo.ElapsedMS = ElapsedMS;
operationInfo.Status = Status.ToString();
if (_childs == null)
if (_children == null)
{
operationInfo.Children = new List<DiagnosticOperationInfo>();
}
else
{
operationInfo.Children = new List<DiagnosticOperationInfo>(_childs.Count);
foreach (var child in _childs)
operationInfo.Children = new List<DiagnosticOperationInfo>(_children.Count);
foreach (var child in _children)
{
var childInfo = child.GetDebugOperationInfo();
operationInfo.Children.Add(childInfo);
@@ -541,6 +572,11 @@ namespace YooAsset
#endregion
#region
/// <summary>
/// 用于支持 async/await 的任务完成源
/// </summary>
private TaskCompletionSource<object> _taskCompletionSource;
bool IEnumerator.MoveNext()
{
return !IsDone;
@@ -549,8 +585,6 @@ namespace YooAsset
{
}
object IEnumerator.Current => null;
private TaskCompletionSource<object> _taskCompletionSource;
#endregion
}
}

View File

@@ -1,11 +1,34 @@

namespace YooAsset
{
/// <summary>
/// 异步操作状态枚举
/// </summary>
public enum EOperationStatus
{
/// <summary>
/// 未开始
/// </summary>
None,
/// <summary>
/// 处理中
/// </summary>
Processing,
/// <summary>
/// 已成功
/// </summary>
Succeed,
Failed
/// <summary>
/// 已失败
/// </summary>
Failed,
/// <summary>
/// 已中止
/// </summary>
Aborted,
}
}

View File

@@ -9,7 +9,7 @@ namespace YooAsset
internal class OperationScheduler : IComparable<OperationScheduler>
{
private readonly List<AsyncOperationBase> _operations = new List<AsyncOperationBase>(100);
private readonly List<AsyncOperationBase> _newList = new List<AsyncOperationBase>(100);
private readonly List<AsyncOperationBase> _pendingOperations = new List<AsyncOperationBase>(100);
private uint _priority;
/// <summary>
@@ -41,24 +41,25 @@ namespace YooAsset
/// <summary>
/// 创建顺序(用于同优先级稳定排序)
/// </summary>
public int CreateIndex { get; private set; }
public int CreationOrder { get; private set; }
public OperationScheduler(string packageName, int createIndex)
public OperationScheduler(string packageName, int creationOrder)
{
PackageName = packageName;
CreateIndex = createIndex;
CreationOrder = creationOrder;
}
/// <summary>
/// 开始处理异步操作
/// </summary>
/// <remarks>
/// 操作会先添加到临时队列在下一次Update时才会开始执行
/// 操作会立即启动,但会先添加到待处理队列。
/// 在下一次Update时才会合并到执行队列并参与调度更新
/// </remarks>
public void StartOperation(AsyncOperationBase operation)
{
_newList.Add(operation);
_pendingOperations.Add(operation);
operation.StartOperation();
}
@@ -71,17 +72,17 @@ namespace YooAsset
for (int i = _operations.Count - 1; i >= 0; i--)
{
var operation = _operations[i];
if (operation.IsFinish)
if (operation.IsFinished)
{
_operations.RemoveAt(i);
}
}
// 添加新增的异步操作
if (_newList.Count > 0)
if (_pendingOperations.Count > 0)
{
_operations.AddRange(_newList);
_newList.Clear();
_operations.AddRange(_pendingOperations);
_pendingOperations.Clear();
}
// 检测是否需要执行排序
@@ -107,7 +108,7 @@ namespace YooAsset
break;
var operation = _operations[i];
if (operation.IsFinish)
if (operation.IsFinished)
continue;
operation.UpdateOperation();
@@ -120,11 +121,11 @@ namespace YooAsset
public void ClearAll()
{
// 终止临时队列里的任务
foreach (var operation in _newList)
foreach (var operation in _pendingOperations)
{
operation.AbortOperation();
}
_newList.Clear();
_pendingOperations.Clear();
// 终止正在进行的任务
foreach (var operation in _operations)
@@ -139,7 +140,7 @@ namespace YooAsset
/// </summary>
public List<DiagnosticOperationInfo> GetDebugOperationInfos()
{
int totalCount = _operations.Count + _newList.Count;
int totalCount = _operations.Count + _pendingOperations.Count;
List<DiagnosticOperationInfo> result = new List<DiagnosticOperationInfo>(totalCount);
// 包含正在执行的任务
@@ -150,7 +151,7 @@ namespace YooAsset
}
// 包含待处理的新任务
foreach (var operation in _newList)
foreach (var operation in _pendingOperations)
{
var operationInfo = operation.GetDebugOperationInfo();
result.Add(operationInfo);
@@ -167,7 +168,7 @@ namespace YooAsset
if (result == 0)
{
// 优先级相同,按创建顺序
result = this.CreateIndex.CompareTo(other.CreateIndex);
result = this.CreationOrder.CompareTo(other.CreationOrder);
}
return result;
}

View File

@@ -5,6 +5,10 @@ using System.Diagnostics;
namespace YooAsset
{
/// <summary>
/// 异步操作系统(静态调度器)
/// 负责管理所有包裹的调度器,提供时间切片执行机制
/// </summary>
internal static class OperationSystem
{
#if UNITY_EDITOR
@@ -18,13 +22,13 @@ namespace YooAsset
public const string GlobalSchedulerName = "YOOASSET_GLOBAL_SCHEDULER"; // 全局调度器名称
private const long MinTimeSlice = 10; // 最小时间片(毫秒)
private static readonly Dictionary<string, OperationScheduler> _schedulerDic = new Dictionary<string, OperationScheduler>(100);
private static readonly Dictionary<string, OperationScheduler> _schedulerDict = new Dictionary<string, OperationScheduler>(100);
private static readonly List<OperationScheduler> _schedulerList = new List<OperationScheduler>(100);
private static bool _isInitialized;
private static int _createIndex;
private static int _nextCreationIndex;
// 计时器相关
private static Stopwatch _watch;
private static Stopwatch _systemStopwatch;
private static long _frameTime;
private static long _maxTimeSlice = long.MaxValue;
@@ -58,14 +62,14 @@ namespace YooAsset
{
get
{
if (_watch == null)
if (_systemStopwatch == null)
return false;
if (_maxTimeSlice == long.MaxValue)
return false;
// 注意 : 单次调用开销约1微秒
return _watch.ElapsedMilliseconds - _frameTime >= _maxTimeSlice;
return _systemStopwatch.ElapsedMilliseconds - _frameTime >= _maxTimeSlice;
}
}
@@ -82,7 +86,7 @@ namespace YooAsset
}
_isInitialized = true;
_watch = Stopwatch.StartNew();
_systemStopwatch = Stopwatch.StartNew();
// 创建全局调度器
CreatePackageScheduler(GlobalSchedulerName, uint.MaxValue);
@@ -112,7 +116,7 @@ namespace YooAsset
}
// 更新帧时间
_frameTime = _watch.ElapsedMilliseconds;
_frameTime = _systemStopwatch.ElapsedMilliseconds;
// 更新调度器
for (int i = 0; i < _schedulerList.Count; i++)
@@ -136,11 +140,11 @@ namespace YooAsset
{
scheduler.ClearAll();
}
_schedulerDic.Clear();
_schedulerDict.Clear();
_schedulerList.Clear();
_createIndex = 0;
_nextCreationIndex = 0;
_watch = null;
_systemStopwatch = null;
_frameTime = 0;
_maxTimeSlice = long.MaxValue;
}
@@ -152,13 +156,13 @@ namespace YooAsset
{
DebugEnsureInitialized(packageName);
if (_schedulerDic.ContainsKey(packageName))
if (_schedulerDict.ContainsKey(packageName))
{
throw new YooInternalException($"Package scheduler already exists: {packageName}");
}
var scheduler = new OperationScheduler(packageName, _createIndex++);
_schedulerDic.Add(packageName, scheduler);
var scheduler = new OperationScheduler(packageName, _nextCreationIndex++);
_schedulerDict.Add(packageName, scheduler);
_schedulerList.Add(scheduler);
scheduler.Priority = priority;
return scheduler;
@@ -177,10 +181,10 @@ namespace YooAsset
throw new YooInternalException("Cannot destroy the global package scheduler.");
}
if (_schedulerDic.TryGetValue(packageName, out var scheduler))
if (_schedulerDict.TryGetValue(packageName, out var scheduler))
{
scheduler.ClearAll();
_schedulerDic.Remove(packageName);
_schedulerDict.Remove(packageName);
_schedulerList.Remove(scheduler);
}
}
@@ -188,7 +192,7 @@ namespace YooAsset
/// <summary>
/// 销毁包裹的所有任务
/// </summary>
public static void ClearPackageOperation(string packageName)
public static void ClearPackageOperations(string packageName)
{
DebugEnsureInitialized(packageName);
@@ -234,7 +238,7 @@ namespace YooAsset
/// </summary>
private static OperationScheduler GetScheduler(string packageName)
{
if (_schedulerDic.TryGetValue(packageName, out var scheduler))
if (_schedulerDict.TryGetValue(packageName, out var scheduler))
{
return scheduler;
}

View File

@@ -1,555 +0,0 @@
# OperationSystem 异步操作系统
## 模块概述
OperationSystem 是 YooAsset 资源管理系统的**异步操作调度核心**负责管理所有异步操作的生命周期、调度执行和状态追踪。该模块提供了统一的异步操作抽象支持协程、async/await、回调等多种异步编程模式。
### 对外使用说明
- `AsyncOperationBase` / `EOperationStatus` 为对外公开类型,业务层可直接使用(协程/Task/Completed
- `OperationSystem` / `OperationScheduler` 为内部调度实现,由 `YooAssets.Update()` 驱动,业务层一般无需直接调用。
- 时间切片预算建议通过 `YooAssets.SetOperationSystemMaxTimeSlice(milliseconds)` 配置(内部会设置 `OperationSystem.MaxTimeSlice`)。
### 核心职责
- 异步操作的统一抽象和生命周期管理
- 基于优先级的操作调度
- 时间切片执行(防止主线程阻塞)
- 多种异步编程模式支持
- 操作状态追踪和调试信息收集
---
## 设计目标
| 目标 | 说明 |
|------|------|
| **统一抽象** | 所有异步操作继承同一基类,接口一致 |
| **灵活调度** | 支持优先级排序、时间切片、帧预算控制 |
| **多模式支持** | 协程IEnumerator、Taskasync/await、回调 |
| **可调试性** | 完整的状态追踪、耗时统计、层级关系 |
| **线程安全** | 所有调度逻辑在主线程执行 |
---
## 架构概念
### 系统架构
```
┌─────────────────────────────────────────────────────────┐
│ 上层调用者 │
│ (ResourceManager / FileSystem / 业务层) │
└─────────────────────────┬───────────────────────────────┘
│ StartOperation()
┌─────────────────────────▼───────────────────────────────┐
│ OperationSystem │
│ (调度器) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 优先级队列 │ │ 时间切片 │ │ 回调通知 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────┬───────────────────────────────┘
│ UpdateOperation()
┌─────────────────────────▼───────────────────────────────┐
│ AsyncOperationBase │
│ (操作基类) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 状态机 │ │ 子任务管理 │ │ 异步模式 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
```
### 核心组件
- **OperationSystem**: 静态调度器,管理所有操作的执行
- **OperationScheduler**: 包裹级调度器,维护操作队列并负责更新调度
- **AsyncOperationBase**: 异步操作基类,定义生命周期和状态
- **EOperationStatus**: 操作状态枚举
---
## 文件结构
```
OperationSystem/
├── EOperationStatus.cs # 操作状态枚举
├── AsyncOperationBase.cs # 异步操作基类
├── OperationScheduler.cs # 包裹级调度器
├── OperationSystem.cs # 异步操作调度器
```
---
## 枚举定义
### EOperationStatus操作状态
```csharp
public enum EOperationStatus
{
None, // 未开始
Processing, // 处理中
Succeed, // 已成功
Failed // 已失败
}
```
**状态转换:**
```
StartOperation() InternalUpdate()
None ─────────────────► Processing ─────────────────┬──► Succeed
└──► Failed
```
---
## 核心类说明
### AsyncOperationBase异步操作基类
所有异步操作的抽象基类,实现了 `IEnumerator``IComparable<AsyncOperationBase>` 接口。
#### 公共属性
| 属性 | 类型 | 说明 |
|------|------|------|
| `Priority` | `uint` | 任务优先级(值越大越优先) |
| `Status` | `EOperationStatus` | 当前状态 |
| `Error` | `string` | 错误信息(失败时) |
| `Progress` | `float` | 处理进度0-1 |
| `IsDone` | `bool` | 是否已完成Succeed 或 Failed |
| `Task` | `Task` | 用于 async/await |
| `BeginTime` | `string` | 开始时间(调试用) |
| `ProcessTime` | `long` | 处理耗时毫秒(调试用) |
> 说明:`AsyncOperationBase` 本身不保存包裹名称;包裹名称由 `OperationSystem.StartOperation(packageName, operation)` 传入,并由 `OperationScheduler` 维护。
#### 内部协作:时间切片(`IsBusy`
为配合异步系统的时间切片预算(可通过 `YooAssets.SetOperationSystemMaxTimeSlice` 配置),`AsyncOperationBase` 提供了内部属性 `IsBusy``internal`)用于任务在 `InternalUpdate()` 内主动让出本帧预算。
- 推荐用法:在 `InternalUpdate()` 内部(或内部子步骤)在执行重逻辑前判断 `IsBusy`,若繁忙则 `return`,把工作拆到下一帧继续执行。
- 同步等待特殊处理:当调用了 `WaitForAsyncComplete()` 进入同步等待阶段时,`IsBusy` 会强制返回 `false`,避免因时间切片判断导致同步等待无法推进。
- 注意:`WaitForAsyncComplete()` 会阻塞主线程,应谨慎使用;同步等待阶段不受时间切片保护,可能带来卡顿。
#### 公共事件
```csharp
/// <summary>
/// 完成事件(支持后注册立即触发)
/// </summary>
public event Action<AsyncOperationBase> Completed;
```
#### 公共方法
```csharp
/// <summary>
/// 同步等待异步操作完成
/// </summary>
public void WaitForAsyncComplete();
```
#### 内部抽象方法(子类实现)
| 方法 | 说明 |
|------|------|
| `InternalStart()` | 操作开始时调用 |
| `InternalUpdate()` | 每帧更新时调用 |
| `InternalAbort()` | 操作中止时调用(可选) |
| `InternalWaitForAsyncComplete()` | 同步等待时调用(可选) |
| `InternalGetDesc()` | 获取操作描述(可选) |
#### 子任务管理
```csharp
// 添加/移除子任务(内部使用)
internal void AddChildOperation(AsyncOperationBase child);
internal void RemoveChildOperation(AsyncOperationBase child);
```
**调用约束(重要):**
- 仅允许在 Unity 主线程调用(与 `OperationSystem.Update()` 的调度线程一致)。
- 不要在 `InternalUpdate()` 正在遍历/处理中途频繁增删子任务;推荐在任务启动阶段完成子任务挂接,或在确保无并发修改风险的安全点调整。
- `AbortOperation()` 会递归中止子任务,子任务的生命周期由父任务统一管理;避免在 `Completed` 回调里再去修改子任务关系,防止时序混乱。
---
### OperationSystem调度器
静态类,负责异步操作的调度和管理。
#### 配置属性
```csharp
/// <summary>
/// 每帧最大执行时间(毫秒)
/// 默认值long.MaxValue无限制
/// </summary>
public static long MaxTimeSlice { set; get; }
/// <summary>
/// 当前帧是否已超时
/// </summary>
public static bool IsBusy { get; }
```
#### 核心方法
```csharp
/// <summary>
/// 初始化异步操作系统
/// </summary>
public static void Initialize();
/// <summary>
/// 每帧更新(由 YooAssets 驱动)
/// </summary>
public static void Update();
/// <summary>
/// 销毁所有操作
/// </summary>
public static void DestroyAll();
/// <summary>
/// 清理指定包裹的所有操作
/// </summary>
public static void ClearPackageOperation(string packageName);
/// <summary>
/// 启动异步操作
/// </summary>
public static void StartOperation(string packageName, AsyncOperationBase operation);
/// <summary>
/// 设置调度器优先级
/// </summary>
public static void SetSchedulerPriority(string packageName, uint priority);
/// <summary>
/// 获取调度器优先级
/// </summary>
public static uint GetSchedulerPriority(string packageName);
```
#### 包裹调度说明
- `packageName` 不允许为空(`null` / `""`),否则会抛出异常。
- 若在 YooAsset 内部需要使用全局调度器,请传入 `OperationSystem.GlobalSchedulerName``Initialize()` 时自动创建)。
- `packageName` 为非全局调度器名称时,必须先通过 `YooAssets.CreatePackage(packageName)` 创建包裹(内部会注册对应 `OperationScheduler`),否则会抛出异常。
#### 回调监听
OperationSystem **当前未提供**全局任务开始/结束回调的注册接口。
如需监听任务结束(推荐),请直接订阅具体任务的 `Completed` 事件:
```csharp
var operation = package.LoadAssetAsync<GameObject>(location);
operation.Completed += op =>
{
// TODO : 根据 op.Status 判断成功/失败
};
```
---
## 异步编程模式
### 1. 协程模式IEnumerator
```csharp
IEnumerator LoadAsset()
{
var operation = package.LoadAssetAsync<GameObject>("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<GameObject>("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<GameObject>("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<GameObject>("Assets/Prefab.prefab");
operation.WaitForAsyncComplete(); // 阻塞等待完成
if (operation.Status == EOperationStatus.Succeed)
{
GameObject prefab = operation.AssetObject as GameObject;
}
}
```
---
## 调度机制
### 优先级调度
操作按 `Priority` 属性降序排列,优先级高的操作先执行。
#### 操作优先级
```csharp
var operation = package.LoadAssetAsync<GameObject>(location);
operation.Priority = 100; // 设置高优先级
```
#### 包裹优先级
通过 `ResourcePackage.PackagePriority` 可以设置包裹的调度器优先级,值越大越优先更新。
```csharp
// 创建包裹时指定优先级
var package = YooAssets.CreatePackage("MyPackage", 100);
// 运行时动态调整优先级
package.PackagePriority = 200;
// 获取当前优先级
uint priority = package.PackagePriority;
```
**使用场景:**
- 多包裹场景下,可根据游戏状态动态调整包裹优先级
- 例如:进入战斗时提高战斗资源包的优先级,退出战斗时恢复默认优先级
**排序规则:**
- 新操作添加时:若新增队列存在非零优先级,则触发排序
- 运行中修改 `Priority`:调度器会在每帧 `Update()` 的排序阶段检测 `IsDirty` 并触发重排;若在某个操作的 `InternalUpdate()` 内修改(本帧排序已完成),则新的优先级会延后一帧生效(可能与预期不符)
- 若期望本帧生效:请尽量在任务入队前或本帧调度器 `Update()` 开始前设置 `Priority`,避免在 `InternalUpdate()` 内临时调整
- 排序使用 `List.Sort()` 进行原地排序;频繁修改优先级会带来额外排序开销,建议按需使用
### 时间切片
通过 `MaxTimeSlice` 控制每帧最大执行时间,防止主线程阻塞。
```csharp
// 设置每帧最多执行 8 毫秒
YooAssets.SetOperationSystemMaxTimeSlice(16);
```
**操作侧协作建议:**
- 在操作的 `InternalUpdate()` 中使用 `IsBusy``AsyncOperationBase` 的内部属性)进行“自愿让出”,将重任务拆分到多帧执行。
- 在同步等待(`WaitForAsyncComplete()`)阶段,`IsBusy` 会强制返回 `false`,以保证同步等待推进;此时需要自行评估卡顿风险。
**执行流程:**
```
每帧 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
[Serializable]
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<DebugOperationInfo> Childs; // 子任务列表注意JsonUtility 序列化深度限制)
}
```
> 说明:该结构体真实定义位于 `Runtime/DiagnosticSystem/DebugOperationInfo.cs`,这里仅展示关键字段以便理解。
### 获取调试信息
```csharp
// 获取指定包裹的所有操作信息(内部调试接口)
// packageName 不允许为空;全局调度器请使用 OperationSystem.GlobalSchedulerName内部
// 非全局包裹需先 YooAssets.CreatePackage(packageName)
var infos = OperationSystem.GetDebugOperationInfos(OperationSystem.GlobalSchedulerName);
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");
```
---
## 设计模式
### 模板方法模式
`AsyncOperationBase` 定义算法骨架,子类实现具体步骤:
```
AsyncOperationBase
├── StartOperation() ──► InternalStart() [子类实现]
├── UpdateOperation() ──► InternalUpdate() [子类实现]
├── AbortOperation() ──► InternalAbort() [子类实现]
└── WaitForAsyncComplete() ──► InternalWaitForAsyncComplete() [子类实现]
```
### 状态机模式
操作状态由 `EOperationStatus` 管理:
```
┌──────┐ StartOperation() ┌────────────┐ UpdateOperation() ┌─────────┐
│ None │ ─────────────────► │ Processing │ ──────────────────► │ Succeed │
└──────┘ └────────────┘ └─────────┘
│ UpdateOperation() / AbortOperation()
┌──────────┐
│ Failed │
└──────────┘
```
### 组合模式
通过内部子任务列表支持父子操作关系:
```
ParentOperation
├── ChildOperation1
├── ChildOperation2
└── ChildOperation3
└── GrandChildOperation
```
---
## 类继承关系
```
IEnumerator + IComparable<AsyncOperationBase>
AsyncOperationBase (抽象基类)
└── [YooAsset 内部操作]
├── InitializationOperation
├── LoadAssetOperation
├── LoadSceneOperation
├── DownloadOperation
└── ...
```
---
## 注意事项
1. **主线程执行**:所有操作的调度和更新都在 Unity 主线程执行
2. **时间切片**:设置合理的 `MaxTimeSlice` 避免卡顿(建议 10-16ms小于 10ms 会被钳制到 10ms
3. **同步等待**`WaitForAsyncComplete()` 会阻塞主线程,谨慎使用
4. **子任务中止**:父操作中止时会自动中止所有子操作
5. **回调异常**`Completed` 回调中的异常会被捕获并记录,不会中断系统
6. **编辑器重置**:编辑器中使用 `RuntimeInitializeOnLoadMethod` 自动重置状态
7. **循环保护**:在 `InternalWaitForAsyncComplete()` 中建议使用 `RunBatchExecution()`(默认 1000 次)限制单次推进次数,避免陷入无限循环或长时间占用主线程

View File

@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 2afe3d5ffb611b241919112b40d9c7d0
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -51,7 +51,7 @@ namespace YooAsset
return;
}
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
_handle.WaitForAsyncComplete();
if (_handle.IsDone == false)
@@ -95,7 +95,7 @@ namespace YooAsset
_instantiateAsync = InstantiateAsyncInternal(_handle.AssetObject, _options);
}
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
_instantiateAsync.WaitForCompletion();
if (_instantiateAsync.isDone == false)
@@ -132,7 +132,7 @@ namespace YooAsset
{
RunBatchExecution();
}
internal override string InternalGetDesc()
internal override string InternalGetDescription()
{
var assetInfo = _handle.GetAssetInfo();
return $"AssetPath : {assetInfo.AssetPath}";

View File

@@ -67,7 +67,7 @@ namespace YooAsset
if (_steps == ESteps.CheckConcurrency)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
_steps = ESteps.LoadBundleFile;
}
@@ -90,7 +90,7 @@ namespace YooAsset
AddChildOperation(_loadBundleOp);
}
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
_loadBundleOp.WaitForAsyncComplete();
_loadBundleOp.UpdateOperation();
@@ -129,7 +129,7 @@ namespace YooAsset
{
RunBatchExecution();
}
internal override string InternalGetDesc()
internal override string InternalGetDescription()
{
return $"BundleName : {LoadBundleInfo.Bundle.BundleName}";
}

View File

@@ -110,7 +110,7 @@ namespace YooAsset
Status = EOperationStatus.Succeed;
}
}
internal override string InternalGetDesc()
internal override string InternalGetDescription()
{
return $"SceneName : {_provider.SceneName}";
}

View File

@@ -54,7 +54,7 @@ namespace YooAsset
{
RunBatchExecution();
}
internal override string InternalGetDesc()
internal override string InternalGetDescription()
{
return $"LoopCount : {_options.LoopCount}";
}

View File

@@ -22,7 +22,7 @@ namespace YooAsset
#endif
}
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
_loadAllAssetsOp.WaitForAsyncComplete();
_loadAllAssetsOp.UpdateOperation();

View File

@@ -22,7 +22,7 @@ namespace YooAsset
#endif
}
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
_loadAssetOp.WaitForAsyncComplete();
_loadAssetOp.UpdateOperation();

View File

@@ -137,7 +137,7 @@ namespace YooAsset
if (_steps == ESteps.WaitBundleLoader)
{
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
{
foreach (var bundleLoader in _bundleLoaders)
{
@@ -185,7 +185,7 @@ namespace YooAsset
{
RunBatchExecution();
}
internal override string InternalGetDesc()
internal override string InternalGetDescription()
{
return $"AssetPath : {MainAssetInfo.AssetPath}";
}

View File

@@ -27,7 +27,7 @@ namespace YooAsset
AddChildOperation(_loadSceneOp);
}
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
_loadSceneOp.WaitForAsyncComplete();
// 注意:场景加载中途可以取消挂起

View File

@@ -22,7 +22,7 @@ namespace YooAsset
#endif
}
if (IsWaitForAsyncComplete)
if (IsWaitingForAsyncComplete)
_loadSubAssetsOp.WaitForAsyncComplete();
_loadSubAssetsOp.UpdateOperation();

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 23897062e5e2c98498f4bd00a407dda8
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -426,8 +426,8 @@ namespace YooAsset
DiagnosticProviderInfo providerInfo = new DiagnosticProviderInfo();
providerInfo.AssetPath = provider.MainAssetInfo.AssetPath;
providerInfo.OriginScene = provider.OriginScene;
providerInfo.StartTime = provider.BeginTime;
providerInfo.ElapsedMS = provider.ProcessTime;
providerInfo.StartTime = provider.StartTime;
providerInfo.ElapsedMS = provider.ElapsedMS;
providerInfo.ReferenceCount = provider.RefCount;
providerInfo.Status = provider.Status.ToString();
providerInfo.DependentBundles = provider.GetDebugDependBundles();

View File

@@ -98,7 +98,7 @@ namespace YooAsset
}
}
}
internal override string InternalGetDesc()
internal override string InternalGetDescription()
{
return $"ClearMode : {_options.ClearMode}";
}

View File

@@ -92,13 +92,13 @@ namespace YooAsset
// 最后清理该包裹的异步任务
// 注意:对于有线程操作的异步任务,需要保证线程安全释放。
OperationSystem.ClearPackageOperation(_resourcePackage.PackageName);
OperationSystem.ClearPackageOperations(_resourcePackage.PackageName);
_steps = ESteps.Done;
Status = EOperationStatus.Succeed;
}
}
internal override string InternalGetDesc()
internal override string InternalGetDescription()
{
return $"PackageVersion : {_resourcePackage.GetPackageVersion()}";
}

View File

@@ -181,7 +181,7 @@ namespace YooAsset
}
}
}
internal override string InternalGetDesc()
internal override string InternalGetDescription()
{
return $"PlayMode : {_playMode}";
}

View File

@@ -87,7 +87,7 @@ namespace YooAsset
}
}
}
internal override string InternalGetDesc()
internal override string InternalGetDescription()
{
return $"PackageVersion : {_options.PackageVersion}";
}

File diff suppressed because it is too large Load Diff

View File

@@ -44,7 +44,7 @@ public class TestLoadAsset
{
Assert.AreEqual(loadFrame, Time.frameCount);
};
Assert.AreEqual(true, assetHandle.Provider.IsFinish);
Assert.AreEqual(true, assetHandle.Provider.IsFinished);
Assert.AreEqual(EOperationStatus.Succeed, assetHandle.Status);
var audioClip = assetHandle.AssetObject as AudioClip;