refactor : 代码重构

This commit is contained in:
何冠峰
2026-03-09 17:58:49 +08:00
committed by 何冠峰
parent ba6db2ff4d
commit 8b35472991
363 changed files with 7210 additions and 4286 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b819eb7cc257867439445ce1a660ea0a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,226 @@
# .NET 命名准则
> 参考来源:[Microsoft .NET 框架设计准则 - 命名准则](https://learn.microsoft.com/zh-cn/dotnet/standard/design-guidelines/naming-guidelines)
>
> 核心原则:**可读性优先、语义清晰、风格一致、避免歧义**
---
## 一、大小写约定
### 1.1 两种大小写风格
| 风格 | 规则 | 示例 |
|------|------|------|
| **PascalCasing** | 每个单词首字母大写(含首个单词) | `PropertyDescriptor``HtmlTag` |
| **camelCasing** | 除第一个单词外,每个单词首字母大写 | `propertyDescriptor``htmlTag` |
### 1.2 各标识符的大小写规则
| 标识符 | 大小写 | 示例 |
|--------|--------|------|
| 命名空间 | PascalCasing | `System.Security` |
| 类型(类/结构) | PascalCasing | `StreamReader` |
| 接口 | PascalCasing | `IEnumerable` |
| 方法 | PascalCasing | `ToString()` |
| 属性 | PascalCasing | `Length` |
| 事件 | PascalCasing | `Exited` |
| 字段(公共/静态) | PascalCasing | `InfiniteTimeout` |
| 枚举值 | PascalCasing | `FileMode.Append` |
| 参数 | camelCasing | `string value` |
### 1.3 首字母缩写词规则
- **两个字母**的缩写词:全部大写,如 `IO``DB`
- **三个字母及以上**的缩写词:仅首字母大写,如 `Html``Xml`
- camelCasing 中,开头的两字母缩写词全部小写:`ioStream`
### 1.4 常见复合词的正确写法
| 正确 (Pascal) | 正确 (camel) | 错误 |
|---------|---------|------|
| `Callback` | `callback` | ~~CallBack~~ |
| `Endpoint` | `endpoint` | ~~EndPoint~~ |
| `FileName` | `fileName` | ~~Filename~~ |
| `Hashtable` | `hashtable` | ~~HashTable~~ |
| `Id` | `id` | ~~ID~~ |
| `Metadata` | `metadata` | ~~MetaData~~ |
| `Namespace` | `namespace` | ~~NameSpace~~ |
| `Ok` | `ok` | ~~OK~~ |
| `UserName` | `userName` | ~~Username~~ |
| `WhiteSpace` | `whiteSpace` | ~~Whitespace~~ |
| `SignIn` | `signIn` | ~~SignOn~~ |
| `LogOff` | `logOff` | ~~LogOut~~ |
| `Email` | `email` | ~~EMail~~ |
### 1.5 区分大小写
- 不要假定所有语言都区分大小写
- 公开 API 中不应仅靠大小写来区分两个名称
---
## 二、常规命名约定
### 2.1 用词选择
- **可读性优先于简洁性**`CanScrollHorizontally` 优于 `ScrollableX`
- **语义清晰**`HorizontalAlignment` 优于 `AlignmentHorizontal`
- **禁止**使用下划线 `_`、连字符 `-` 或其他非字母字符
- **禁止**使用匈牙利命名法(如 `strName``iCount`
- **避免**与编程语言关键字冲突
### 2.2 缩写和首字母缩写词
- **禁止**使用缩写:用 `GetWindow` 而非 `GetWin`
- **禁止**使用未被广泛接受的首字母缩略词
### 2.3 避免语言特定名称
- 使用语义化名称:`GetLength` 优于 `GetInt`
- 需要表示类型时,使用 CLR 类型名称而非语言关键字:`ToInt64` 而非 `ToLong`
### 2.4 现有 API 的新版本命名
- 使用类似旧 API 的名称
- **优先添加后缀**而非前缀(便于 IntelliSense 排序)
- 用**数字后缀**标识版本(如 `64` 表示 64 位版本)
- **禁止**使用 `Ex` 后缀
---
## 三、程序集和 DLL 的名称
- 选择能体现**大范围功能**的名称
- 推荐格式:`<Company>.<Component>.dll`,如 `Litware.Controls.dll`
- 基于程序集所含命名空间的**公共前缀**来命名
---
## 四、命名空间的名称
- 推荐格式:`<Company>.(<Product>|<Technology>)[.<Feature>][.<Subnamespace>]`
- 使用 **PascalCasing**,用句点分隔:`Microsoft.Office.PowerPoint`
- 使用**与版本无关的稳定产品名称**
- 适当使用**复数**`System.Collections` 而非 `System.Collection`
- **禁止**命名空间与其内部类型同名
- **禁止**使用泛型名称如 `Element``Node``Log``Message`,应加限定:`FormElement``XmlNode``EventLog`
---
## 五、类、结构和接口的名称
### 5.1 类和结构
- 使用 PascalCasing**名词或名词短语**
- **禁止**添加 `C` 前缀
- 考虑以**基类名称结尾**`ArgumentOutOfRangeException`(派生自 `Exception`
### 5.2 接口
- 使用**形容词短语**,偶尔使用名词
- **必须**以 `I` 前缀开头:`IComponent``ICustomAttributeProvider``IPersistable`
### 5.3 泛型类型参数
- 单字母参数时用 `T``IComparer<T>`
- 描述性名称以 `T` 开头:`ISessionChannel<TSession>`
- 考虑在名称中体现约束:`TSession`(约束为 `ISession`
### 5.4 常见类型的后缀规则
| 基类/接口 | 后缀要求 |
|-----------|---------|
| `System.Attribute` | 添加 `Attribute` 后缀 |
| `System.Delegate`(事件用) | 添加 `EventHandler` 后缀 |
| `System.Delegate`(回调用) | 添加 `Callback` 后缀 |
| `System.EventArgs` | 添加 `EventArgs` 后缀 |
| `System.Exception` | 添加 `Exception` 后缀 |
| `IDictionary` / `IDictionary<TKey,TValue>` | 添加 `Dictionary` 后缀 |
| `IEnumerable` / `ICollection` / `IList` | 添加 `Collection` 后缀 |
| `System.IO.Stream` | 添加 `Stream` 后缀 |
### 5.5 枚举命名
- 普通枚举用**单数**名称:`FileMode`
- 位标志枚举用**复数**名称:`FileAttributes`
- **禁止**添加 `Enum``Flag``Flags` 后缀
- **禁止**在枚举值上使用前缀
- ✔️ 允许对枚举类型名称添加 `E` 前缀以提高可辨识度,项目内应保持风格一致
---
## 六、类型成员的名称
### 6.1 方法
- 使用**谓词或谓词短语**`CompareTo``Split``Trim`
### 6.2 属性
- 使用**名词、名词短语或形容词**
- 布尔属性使用肯定短语:`CanSeek` 而非 `CantSeek`
- 布尔属性可加 `Is``Can``Has` 前缀(仅在有意义时)
- 可以与类型同名:`public Color Color { get; set; }`
- 集合属性用**复数**`Items` 而非 `ItemList`
### 6.3 事件
- 使用**谓词或谓词短语**
- 用时态区分前后:`Closing`(关闭前)、`Closed`(关闭后)
- **禁止**用 `Before` / `After` 前后缀
- 事件处理程序委托添加 `EventHandler` 后缀
- 事件参数类添加 `EventArgs` 后缀
- 事件处理程序参数命名为 `sender``e`
### 6.4 字段
- 使用 PascalCasing
- 使用**名词、名词短语或形容词**
- **禁止**使用前缀(如 `g_``s_``m_`
---
## 七、参数命名
- 使用 **camelCasing**
- 使用**描述性名称**
- 根据**含义**而非类型命名
- 二元运算符重载:无特殊含义时用 `left` / `right`
- 一元运算符重载:无特殊含义时用 `value`
- **禁止**对运算符参数使用缩写或数字索引
---
## 八、资源命名
- 使用 **PascalCasing**
- 使用**描述性标识符**
- 仅使用字母数字字符和下划线
- 异常消息资源格式:`异常类型名 + 短标识符`,如 `ArgumentExceptionIllegalCharacters`
---
## 九、内部字段命名约定
以上命名准则主要适用于公开 API。对于内部实现代码以下前缀约定对代码审查者非常有价值
| 字段类型 | 前缀 | 示例 |
|---------|------|------|
| 私有/内部**实例**字段 | `_` | `_count``_name` |
| 私有/内部**静态**字段 | `s_` | `s_instance``s_defaultValue` |
| 私有/内部**线程静态**字段 | `t_` | `t_cachedBuffer` |
```csharp
public class ConnectionPool
{
private static readonly int s_maxPoolSize = 100;
[ThreadStatic]
private static Queue<Connection> t_localPool;
private readonly string _connectionString;
private int _activeCount;
}
```
> 注意:此约定仅适用于**私有和内部**字段,不适用于公共 API。公共字段如 `public static readonly` 字段)仍然使用 PascalCasing 且不带前缀,这与前文"六、类型成员的名称 - 6.4 字段"中"禁止使用前缀"的准则不矛盾。

View File

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

View File

@@ -0,0 +1,279 @@
# .NET 类型设计准则
> 参考来源:[Microsoft .NET 框架设计准则 - 类型设计准则](https://learn.microsoft.com/zh-cn/dotnet/standard/design-guidelines/type)
>
> 核心原则:**每种类型都应是一组定义完善的相关成员,而非不相关功能的随机集合。**
---
## 一、类型概述
从 CLR 的角度看只有两类类型(引用类型和值类型),但在框架设计中可细分为以下逻辑组:
| 类型 | 说明 |
|------|------|
| **类Class** | 引用类型的一般情况,支持丰富的面向对象功能,构成框架中的大部分类型 |
| **接口Interface** | 可由引用类型和值类型实现,用作多态层次结构的根,模拟多重继承 |
| **结构Struct** | 值类型的一般情况,应保留为小型简单类型,类似语言基元 |
| **枚举Enum** | 特殊值类型,用于定义一组简短的命名常量 |
| **静态类Static Class** | 仅包含静态成员的容器,用于提供操作快捷方式 |
| **委托/异常/属性/数组/集合** | 用于特定用途的引用类型特殊情况 |
---
## 二、在类和结构之间进行选择
### 2.1 引用类型 vs 值类型的核心区别
| 特征 | 引用类型(类) | 值类型(结构) |
|------|--------------|--------------|
| **分配位置** | 堆上分配,由 GC 回收 | 栈上分配或内联在包含类型中 |
| **数组分配** | 元素是引用,实例在堆上 | 元素是实际实例,内联分配 |
| **赋值行为** | 复制引用 | 复制整个值 |
| **传递方式** | 按引用传递 | 按值传递(产生副本) |
| **装箱** | 无装箱开销 | 转换为接口或引用类型时会装箱 |
### 2.2 选择准则
✔️ 如果类型的实例**较小**且通常**生存期较短**或通常**嵌入在其他对象中**,请考虑定义结构而不是类。
❌ **避免**定义结构,除非类型**同时满足**以下所有特征:
- 逻辑上表示单个值,类似基元类型(`int``double`
- 实例大小**低于 24 字节**
- 是**不可变**的
- 不必**频繁装箱**
> 在所有其他情况下,应将类型定义为类。
---
## 三、抽象类设计
❌ **禁止**在抽象类型中定义 `public``protected internal` 构造函数。
> 抽象类型无法被实例化,公共构造函数会对用户产生误导。
✔️ **必须**在抽象类中定义 `protected``internal` 构造函数。
- `protected` 构造函数更常用,允许基类在创建子类型时执行初始化
- `internal` 构造函数可将具体实现限制在定义类的程序集内
✔️ **必须**提供至少一种从你交付的每个抽象类继承的具体类型。
> 例如 `FileStream` 是抽象类 `Stream` 的具体实现,这有助于验证抽象类的设计。
---
## 四、静态类设计
静态类仅包含静态成员(除继承自 `Object` 的实例成员和私有构造函数外)。在 C# 中,声明为 `static` 的类是密封的、抽象的,且无法重写或声明实例成员。
### 适用场景
- 提供其他操作的快捷方式(如 `System.IO.File`
- 扩展方法的持有者
- 无法确保完整面向对象包装器的功能(如 `System.Environment`
### 设计准则
✔️ **谨慎**使用静态类,仅作为框架面向对象核心的支持类。
❌ **禁止**将静态类视为杂项存储桶。
❌ **禁止**在静态类中声明或重写实例成员。
✔️ 如果语言没有对静态类的内置支持,则将其声明为 `sealed abstract` 并添加私有实例构造函数。
---
## 五、接口设计
### 5.1 适用场景
- 需要一组**包含值类型**的类型支持通用 API
- 需要在已从其他类型继承的类型上支持功能(模拟多重继承)
### 5.2 设计准则
✔️ 如果需要包含值类型的多种类型支持通用 API请定义接口。
✔️ 如果需要在已有基类继承的类型上支持功能,请考虑定义接口。
✔️ **必须**至少提供一种类型作为接口的实现。
> 例如 `List<T>` 是 `IList<T>` 的实现。
✔️ **必须**至少提供一个使用该接口的 API以接口为参数的方法或类型为接口的属性
> 例如 `List<T>.Sort` 使用 `IComparer<T>` 接口。
❌ **避免**使用标记接口没有成员的接口应改用自定义属性Attribute
❌ **禁止**向已发布的接口添加新成员。
> 这样做会破坏现有实现,应创建新接口以避免版本控制问题。
✔️ 优先定义**类**而非接口。基于类的 API 可以更容易地演进——向类添加成员不会破坏现有代码,而向接口添加成员则会。
✔️ 使用**抽象类**而非接口来将协定与实现解耦。
❌ **禁止**在公共 API 中使用 `ICloneable`
> `ICloneable` 的问题在于:有些实现是浅拷贝,有些是深拷贝,调用者永远不知道会得到什么。这使得该接口实际上毫无用处。如果需要克隆机制,请定义类型特定的 `Clone` 方法。
> **一般规则**:在设计可重用库时,通常应选择类而非接口。
---
## 六、结构设计
❌ **禁止**为结构提供无参数构造函数。
> 遵循此准则可以创建结构数组,而无需在每个元素上运行构造函数。
❌ **禁止**定义可变值类型。
> 可变值类型问题:属性 getter 返回副本,开发者可能不知道正在修改副本而非原始值。
✔️ **必须**确保所有实例数据设置为零/false/null 的状态有效。
> 防止创建结构数组时意外产生无效实例。
✔️ 如果值类型会参与相等比较(如用作字典键、放入集合、使用 `==` 运算符),**必须**实现 `IEquatable<T>`,并同时重写 `Equals(object)``GetHashCode()`
> `Object.Equals` 在值类型上会导致装箱且效率低下(使用反射),`IEquatable<T>.Equals` 性能更好且无装箱。
❌ 对于仅作为数据载体、不参与相等比较的值类型,**不必**实现 `IEquatable<T>`
❌ **禁止**显式扩展 `ValueType`
✔️ 声明不可变值类型时使用 **`readonly struct`** 修饰符。
> 编译器理解 `readonly` 修饰符,在对 `readonly` 字段调用方法等操作时可避免产生额外的防御性复制。
```csharp
public readonly struct ZipCode
{
public ZipCode(string value) => Value = value;
public string Value { get; }
public override string ToString() => Value;
}
```
✔️ 对可变值类型的非可变方法标记 **`readonly`** 修饰符。
> 允许编译器在调用时跳过值复制。
```csharp
public struct Point
{
public float X;
public float Y;
public readonly override string ToString() => $"({X}, {Y})";
}
```
❌ **避免**定义 `ref struct`ref-like 值类型),除非用于低级性能关键场景。
> `ref struct` 只能存在于栈上,不能被装箱到堆中。因此不能用作其他类型的字段(除了其他 `ref struct`),也不能在 `async` 方法中使用。
> **总结**:结构适用于小型、单个、不可变且不频繁装箱的值。
---
## 七、枚举设计
### 7.1 两种枚举类型
| 类型 | 说明 | 命名 | 示例 |
|------|------|------|------|
| **简单枚举** | 表示小型封闭式选项集 | 单数名词 | `FileMode` |
| **标志枚举** | 支持对枚举值执行位运算 | 复数名词 | `FileAttributes` |
### 7.2 通用准则
✔️ 使用枚举对表示值集的参数、属性和返回值进行**强类型化**。
✔️ **优先**使用枚举而不是静态常量。
✔️ 为简单枚举提供一个**零值**(通常命名为 `None`)。
✔️ 考虑使用 `Int32` 作为枚举的基础类型(除非有特殊需求)。
❌ **禁止**对开放集合使用枚举(如操作系统版本、好友姓名等)。
❌ **禁止**提供用于将来使用的保留枚举值。
❌ **避免**公开只有一个值的枚举。
❌ **禁止**在枚举中包含哨兵值。
❌ **禁止**直接扩展 `System.Enum`
### 7.3 标志枚举设计
✔️ **必须**将 `[Flags]` 特性应用于标志枚举,不要应用于简单枚举。
✔️ **必须**对标志枚举值使用 **2 的幂**,以便用按位 OR 自由组合。
✔️ 考虑为常用标志组合提供特殊枚举值(如 `ReadWrite = Read | Write`)。
✔️ **必须**将标志枚举的零值命名为 `None`,表示"所有标志均已清除"。
❌ **避免**在某些值组合无效的情况下创建标志枚举。
❌ **避免**使用标志枚举值零,除非它表示"所有标志已清除"且命名为 `None`
### 7.4 向枚举添加值
✔️ 考虑向已发布的枚举添加值(兼容性风险通常较小)。
> 如果有不兼容风险,可添加新 API 返回新旧值,并弃用旧 API。
---
## 八、嵌套类型
嵌套类型在另一个类型的作用域内定义,可访问封闭类型的所有成员(包括私有字段)。
### 8.1 适用场景
- 对封闭类型的**实现细节**进行建模
- 例如:集合的枚举器可以是该集合的嵌套类型
### 8.2 设计准则
✔️ 当嵌套类型与外部类型之间的关系使得**成员可访问性语义**可取时,使用嵌套类型。
❌ **禁止**将公共嵌套类型用作逻辑分组构造(应使用命名空间)。
❌ **避免**公开的嵌套类型(极少数子类化或高级自定义方案除外)。
❌ 如果某个类型可能在其包含类型**之外被引用**,不要使用嵌套类型。
> 例如,传递给类方法的枚举不应定义为该类的嵌套类型。
❌ **禁止**使用需要通过客户端代码实例化的嵌套类型。
> 如果类型有公共构造函数,通常不应被嵌套。
❌ **禁止**将嵌套类型定义为接口的成员。
> 多种语言不支持此类构造。
---
## 九、速查表
| 场景 | 推荐类型 |
|------|---------|
| 大部分通用情况 | 类Class |
| 小型、不可变、类似基元的值 | 结构Struct |
| 需要值类型参与多态 | 接口Interface |
| 需要模拟多重继承 | 接口Interface |
| 封闭的命名常量集 | 枚举Enum |
| 可组合的位标志 | 标志枚举([Flags] Enum |
| 纯静态工具方法容器 | 静态类Static Class |
| 提供可扩展的基础实现 | 抽象类Abstract Class |
| 封闭类型的内部实现细节 | 嵌套类型Nested Type |

View File

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

View File

@@ -0,0 +1,339 @@
# .NET 成员设计准则
> 参考来源:[Microsoft .NET 框架设计准则 - 成员设计准则](https://learn.microsoft.com/zh-cn/dotnet/standard/design-guidelines/member)
>
> 方法、属性、事件、构造函数和字段统称为成员,是将框架功能展现给最终用户的手段。
---
## 一、成员重载
成员重载是指在同一类型上创建两个或多个仅参数数量或类型不同、但名称相同的成员。只有**方法、构造函数和索引属性**可以重载。
### 设计准则
✔️ 使用**描述性参数名称**来指示较短重载使用的默认值。
✔️ 仅使**最长的重载虚拟化**(如果需要扩展性),较短的重载应直接调用较长的重载。
✔️ 允许 `null` 作为可选参数传递。
✔️ 优先使用**成员重载**,而不是带默认参数的成员(默认参数不符合 CLS
❌ **避免**在重载中任意改变参数名称。相同输入的参数在所有重载中应保持相同名称。
❌ **避免**重载中参数顺序不一致。相同名称的参数应在所有重载中处于相同位置。
❌ **禁止**使用 `ref``out` 修饰符来区分重载。
❌ **禁止**使用位置相同、类型相似但语义不同的重载。
---
## 二、属性设计
属性在技术上与方法相似,但使用场景不同。它们应被视为"智能字段",兼具字段的调用语法和方法的灵活性。
### 2.1 通用准则
✔️ 如果调用方不应更改属性的值,请创建**只读属性**get-only
✔️ 为所有属性提供**合理的默认值**,确保不会导致安全漏洞或低效代码。
✔️ 允许按**任意顺序**设置属性,即使这会导致对象临时处于无效状态。
✔️ 如果属性 setter 引发异常,请**保留以前的值**。
❌ **禁止**提供仅有 setter 的属性,或 setter 比 getter 可访问性更广的属性。
> 如果无法提供 getter请改用以 `Set` 开头的方法,如 `SetCachePath`。
❌ **避免**在属性 getter 中引发异常。
> getter 应是简单操作,无前置条件。如果可能引发异常,应重新设计为方法。
### 2.2 索引器设计
✔️ 考虑使用索引器提供对内部数组中数据的访问。
✔️ 考虑为表示项集合的类型提供索引器。
✔️ 索引器默认使用 `Item` 作为名称(除非有明显更好的名称)。
❌ **避免**使用多个参数的索引属性(如需多参数,请改用 `Get`/`Set` 方法)。
❌ **避免**使用 `Int32``Int64``String``Object` 或枚举以外类型作为索引器参数。
❌ **禁止**同时提供索引器和语义等效的方法。
❌ **禁止**在同一类型中提供多个重载索引器系列。
### 2.3 属性更改通知事件
✔️ 考虑在修改高级 API通常是设计器组件中的属性值时引发**更改通知事件**。
✔️ 考虑在属性值因外部因素更改时引发更改通知事件。
---
## 三、构造函数设计
### 3.1 实例构造函数
✔️ 考虑提供**简单的**(理想情况下为默认)构造函数,参数少且都是基元或枚举类型。
✔️ 如果操作语义不直接映射到新实例的构造,考虑使用**静态工厂方法**代替构造函数。
✔️ 使用构造函数参数作为设置主属性的**快捷方式**。
✔️ 如果构造函数参数仅用于设置属性,则对参数和属性使用**相同的名称**(仅大小写不同)。
✔️ 在构造函数中执行**最少的工作**,其他处理应延迟到需要时。
✔️ 在适当时从实例构造函数**抛出异常**。
✔️ 如果需要无参数构造函数,请**显式声明**(避免添加参数化构造函数后隐式丢失)。
❌ **避免**在结构上显式定义无参数构造函数(使数组创建更快)。
**避免**在构造函数内对对象调用**虚拟成员**
> 调用虚拟成员会导致调用派生度最高的重写,即使该派生类的构造函数尚未完全执行。
### 3.2 类型构造函数(静态构造函数)
✔️ 将静态构造函数设为**私有**。
✔️ 考虑**内联初始化**静态字段,而非使用显式静态构造函数(运行时可优化性能)。
❌ **禁止**从静态构造函数引发异常。
> 如果类型构造函数抛出异常,该类型在当前应用程序域中将不可用。
---
## 四、事件设计
事件是最常见的回调形式,允许框架调用用户代码。
### 4.1 两种事件时机
| 类型 | 说明 | 示例 |
|------|------|------|
| **预事件** | 状态变更**之前**引发 | `Form.Closing` |
| **后事件** | 状态变更**之后**引发 | `Form.Closed` |
### 4.2 设计准则
✔️ 对事件使用"**raise**"(引发)一词,而非 "fire" 或 "trigger"。
✔️ 使用 `EventHandler<TEventArgs>` 而非手动创建新委托。
✔️ 在 Unity 项目中,可以使用 `Action<T>` 代替 `EventHandler<TEventArgs>` 作为事件委托类型,以保持与 Unity 生态惯例一致。
✔️ 考虑使用 `EventArgs` 的子类作为事件参数(即使最初为空,也便于将来扩展)。
✔️ 使用**受保护的虚拟方法**引发每个事件(仅适用于未密封类的非静态事件)。
> 方法命名约定:以 `On` 开头 + 事件名称,如 `OnClosing`。
✔️ 该方法接受一个参数,命名为 `e`,类型为事件参数类。
✔️ 考虑引发最终用户可**取消**的预事件(使用 `CancelEventArgs` 或其子类)。
❌ 引发非静态事件时,**禁止**将 `null` 作为 sender 传递。
✔️ 引发静态事件时,将 `null` 作为 sender 传递。
❌ 引发事件时,**禁止**将 `null` 作为事件数据参数传递(应使用 `EventArgs.Empty`)。
### 4.3 自定义事件处理程序
✔️ 事件处理程序返回类型为 `void`
✔️ 第一个参数类型为 `object`,命名为 `sender`
✔️ 第二个参数类型为 `EventArgs` 或其子类,命名为 `e`
❌ 事件处理程序**不超过两个参数**。
---
## 五、字段设计
封装原则要求存储在对象中的数据只能通过该对象访问。
### 设计准则
❌ **禁止**提供 `public``protected` 的实例字段(应使用属性代替)。
✔️ 在 Unity 项目中,需要通过 `JsonUtility` 序列化的 `[Serializable]` 类型,应使用公共字段而非属性(`JsonUtility` 不支持属性序列化)。
✔️ 对永远不会更改的常量使用 `const` 字段。
> 编译器将 const 值直接嵌入调用代码,更改 const 值会破坏兼容性。
✔️ 对预定义的对象实例使用 `public static readonly` 字段。
❌ **禁止**将可变类型的实例赋给 `readonly` 字段。
> `readonly` 仅阻止引用被替换,不阻止通过成员修改实例数据。例如数组、集合等可变类型的实例数据仍可被修改。
---
## 六、扩展方法
扩展方法允许使用实例方法调用语法调用静态方法,定义在静态"发起方"类中。
### 适用场景
✔️ 为接口的每个实现提供辅助功能(基于核心接口编写),如 LINQ 运算符。
✔️ 当实例方法会引入对某类型的依赖,且该依赖会破坏依赖管理规则时。
### 设计准则
❌ **避免**轻率地定义扩展方法,尤其是在你不拥有的类型上。
> 如果拥有类型源代码,考虑使用常规实例方法。
❌ **避免**在 `System.Object` 上定义扩展方法VB 用户无法使用扩展方法语法调用)。
❌ **禁止**将扩展方法放在与扩展类型相同的命名空间中(除非用于接口或依赖项管理)。
❌ **避免**使用相同签名定义多个扩展方法,即使在不同命名空间中。
❌ **避免**使用泛型命名空间名(如 "Extensions"),应使用描述性名称(如 "Routing")。
✔️ 如果类型是接口且扩展方法用于大多数场景,考虑放在与扩展类型相同的命名空间中。
---
## 七、运算符重载
运算符重载允许框架类型表现得像内置语言基元。
### 7.1 通用准则
✔️ 考虑在感觉像**基元类型**的类型中定义运算符重载。
✔️ 在表示**数字**的结构中定义运算符重载(如 `Decimal`)。
✔️ **对称**重载运算符:重载 `==` 则必须重载 `!=`,重载 `<` 则必须重载 `>`
✔️ 考虑为每个重载运算符提供具有**友好名称**的等效方法(因为某些语言不支持运算符重载)。
❌ **避免**定义运算符重载,除非类型感觉像基元类型。
❌ 定义运算符重载时**不要花哨**,操作结果应显而易见。
❌ 除非至少一个操作数属于定义重载的类型,否则**禁止**提供运算符重载。
### 7.2 常用运算符与友好方法名对照
| 运算符 | 友好方法名 |
|--------|----------|
| `+` (二元) | `Add` |
| `-` (二元) | `Subtract` |
| `*` (二元) | `Multiply` |
| `/` | `Divide` |
| `%` | `Mod` / `Remainder` |
| `==` | `Equals` |
| `!=` | `Equals` |
| `<` / `>` | `CompareTo` |
| `<=` / `>=` | `CompareTo` |
| `++` | `Increment` |
| `--` | `Decrement` |
| `- (一元)` | `Negate` |
### 7.3 转换运算符
❌ 如果最终用户**未明确预期**该转换,禁止提供转换运算符。
❌ **禁止**在类型域之外定义转换运算符(如不应将 `Double` 转换为 `DateTime`)。
❌ 如果转换**可能丢失数据**,禁止提供隐式转换运算符(可提供显式转换)。
❌ **禁止**从隐式强制转换引发异常。
✔️ 如果显式强制转换导致有损转换,且运算符契约不允许有损,请抛出 `InvalidCastException`
---
## 八、参数设计
### 8.1 通用准则
✔️ 使用提供成员所需功能的**最低派生参数类型**。
> 例如用 `IEnumerable` 而非 `ArrayList` 或 `IList` 作为枚举方法的参数。
✔️ 将所有 `out` 参数置于所有按值和 `ref` 参数**之后**。
✔️ 在重写成员或实现接口时,参数命名保持**一致**。
❌ **禁止**使用保留参数(将来需要更多输入时,添加新重载即可)。
❌ **禁止**公开采用指针、指针数组或多维数组作为参数的方法。
### 8.2 枚举 vs 布尔参数
✔️ 如果不使用枚举则成员会有两个或更多布尔参数,请改用**枚举**。
✔️ 考虑为构造函数参数使用布尔值(真正的双状态值,仅用于初始化布尔属性时)。
❌ **禁止**使用布尔值,除非绝对确定永远不需要两个以上的值。
```csharp
// 错误:布尔参数含义不明确
Stream stream = File.Open("foo.txt", true, false);
// 正确:使用枚举,语义清晰
Stream stream = File.Open("foo.txt", CasingOptions.CaseSensitive, FileMode.Open);
```
### 8.3 参数验证
✔️ 验证传递到 `public``protected` 或显式实现成员的参数,验证失败则抛出 `ArgumentException` 或其子类。
✔️ 如果传入 `null` 且成员不支持,抛出 `ArgumentNullException`
✔️ 验证枚举参数CLR 允许将任何整数值转换为枚举值)。
✔️ 注意可变参数在验证后可能已更改(安全敏感成员应先创建副本)。
❌ **禁止**使用 `Enum.IsDefined` 进行枚举范围检查。
### 8.4 参数传递方式
| 方式 | 说明 |
|------|------|
| **按值** (默认) | 成员收到参数副本 |
| **ref** | 成员收到参数引用,可修改调用方传递的参数 |
| **out** | 类似 ref但初始未赋值必须在返回前赋值 |
❌ **避免**使用 `out``ref` 参数(需要指针经验,且两者区别未被广泛理解)。
❌ **禁止**按引用传递引用类型(有限例外:如交换引用的方法)。
❌ **禁止**对值类型使用只读引用传递(`in`)。
> 结构本身应设计为小型,按值传递的开销很低。使用 `in` 反而增加了 API 的复杂性,收益甚微。
### 8.5 可变参数数params
✔️ 如果希望最终用户传递少量元素的数组,考虑添加 `params` 关键字。
✔️ 考虑在简单重载中使用 `params`,即使更复杂的重载不使用。
✔️ 尝试对参数排序以便使用 `params`
✔️ 考虑在性能敏感的 API 中为少量参数提供特殊重载(避免创建临时数组)。
✔️ 注意 `null` 可作为 params 数组传递,处理前应验证非 null。
❌ 如果调用方几乎总是已有数组作为输入,不要使用 `params`
❌ 如果成员会修改 params 数组,不要使用 `params`(临时数组的修改会丢失)。
❌ **禁止**使用 `varargs`(省略号)方法(不符合 CLS 标准)。

View File

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

View File

@@ -0,0 +1,268 @@
# .NET 扩展设计准则
> 参考来源:[Microsoft .NET 框架设计准则 - 针对扩展性进行设计](https://learn.microsoft.com/zh-cn/dotnet/standard/design-guidelines/designing-for-extensibility)
>
> 核心原则:**选择满足需求的最低成本扩展机制。可以在将来增加扩展性,但永远无法在不引入重大变更的情况下去除它。**
---
## 一、扩展性机制概述
框架中可通过多种方式实现扩展性,从低成本到高成本依次为:
| 机制 | 成本 | 能力 | 说明 |
|------|------|------|------|
| **非密封类** | 低 | 低 | 允许继承添加便捷成员 |
| **受保护成员** | 低 | 低-中 | 为子类提供高级自定义选项 |
| **事件** | 中 | 中 | 便捷一致的回调语法,与 IDE 集成 |
| **回调(委托)** | 中 | 中 | 允许用户提供自定义执行代码 |
| **虚拟成员** | 中-高 | 中-高 | 允许子类替代行为,适合特化场景 |
| **抽象(抽象类/接口)** | 高 | 高 | 最强大的扩展性是插件、IoC、管道等模式的核心 |
---
## 二、非密封类
默认情况下,类在大多数编程语言中是非密封的,这也是框架中大多数类的推荐默认值。
### 设计准则
✔️ 考虑使用没有额外虚拟成员或受保护成员的**非密封类**,作为一种为框架提供既经济又受欢迎的扩展性方法。
> 开发人员通常希望继承自非密封类,以便添加自定义构造函数、新方法或方法重载。例如 `System.Messaging.MessageQueue` 未密封,允许用户创建默认为特定队列路径的自定义队列。
> 非密封类型的测试成本相对较低,框架用户非常赞赏其提供的扩展性。
---
## 三、受保护成员
受保护成员本身不提供扩展性,但可以通过子类化来增强扩展性,用于公开高级自定义选项而无需复杂化主公共接口。
### 设计准则
✔️ 考虑使用受保护成员进行**高级自定义**。
✔️ 出于安全、文档和兼容性分析的目的,应将未密封类中的受保护成员**视为公共成员**。
> 任何人都可以继承未密封类并访问其受保护成员,因此公共成员所用的所有防御性编码做法同样适用于受保护成员。
---
## 四、事件和回调
回调是允许框架通过委托回调到用户代码的扩展点,通常通过方法参数传递委托。事件是回调的特例,提供便捷一致的语法。
### 设计准则
✔️ 考虑使用**回调**来允许用户提供框架要执行的自定义代码。
✔️ 考虑使用**事件**来允许用户自定义框架行为,无需了解面向对象设计。
✔️ **优先使用事件**而非普通回调,因为事件对更广泛的开发人员更熟悉,且与 Visual Studio 语句完成集成。
✔️ 定义带回调的 API 时,使用 `Func<...>``Action<...>``Expression<...>` 类型,而非自定义委托。
✔️ 衡量并了解使用 `Expression<...>` 对性能的影响。
> `Expression<...>` 在逻辑上等效于 `Func<...>` 和 `Action<...>` 委托,主要区别在于表达式适用于远程进程或计算机中求值的场景。
✔️ 理解调用委托即在执行**任意代码**,这可能导致安全性、正确性和兼容性问题。
❌ **避免**在性能敏感的 API 中使用回调。
---
## 五、虚拟成员
虚拟成员可以被替代以更改子类行为,在扩展性方面类似回调,但执行性能和内存消耗更优。
### 优缺点
| 方面 | 说明 |
|------|------|
| **优势** | 性能优于回调和事件;在特化场景中感觉更自然 |
| **劣势** | 行为只能在编译时修改(回调可在运行时修改);设计、测试和维护成本高;任何调用都可能被不可预测地替代 |
### 设计准则
❌ **禁止**无充分理由地使成员成为虚拟成员,需充分了解设计、测试和维护虚拟成员的所有成本。
> 虚拟成员对可进行的更改不太宽容,且比非虚拟成员慢(无法内联)。
✔️ 将扩展性限制在**绝对必要的范围内**。
✔️ 对虚拟成员优先选择 **`protected`** 访问级别而非 `public`
> 公共成员应通过调用受保护的虚拟成员来提供扩展性。这样将虚拟扩展点的范围限定在可使用的位置。
```csharp
// 推荐模式:公共方法调用受保护的虚拟方法
public class Control
{
public void Draw()
{
// 公共 API 逻辑...
OnDraw();
}
protected virtual void OnDraw()
{
// 子类可替代此方法来自定义绘制行为
}
}
```
---
## 六、抽象(抽象类型和接口)
抽象是描述协定但不提供完整实现的类型通常作为抽象类或接口实现。它们提供最强大的扩展性是插件、IoC、管道等架构模式的核心。
### 设计注意事项
- 有意义且经得起时间考验的抽象**很难设计**
- 成员过多 → 难以实现;成员过少 → 许多场景中无用
- 框架中抽象过多会**负面影响可用性**
- 良好的抽象对框架的**可测试性**非常重要(便于单元测试中替代依赖)
### 设计准则
**禁止**提供抽象,除非已通过开发**多个具体实现和 API** 对其进行测试验证。
✔️ 设计抽象时,在**抽象类和接口**之间仔细选择。
✔️ 考虑为抽象的具体实现提供**参考测试**,允许用户验证其实现是否正确实现了协定。
---
## 七、实现抽象的基类
基类主要设计为提供通用抽象或其他类的默认实现,位于继承层次结构的中间——根部的抽象和底部的自定义实现之间。
### 典型场景
```
IList<T> ← 抽象(接口)
Collection<T> ← 基类(提供默认实现)
MyCustomCollection ← 具体实现
```
> 例如 `Collection<T>` 和 `KeyedCollection<TKey,TItem>` 是 `IList<T>` 的实现辅助基类,降低了实现自定义集合的难度。
### 设计准则
✔️ 考虑使基类为 **`abstract`**,即使不包含抽象成员。
> 清楚地向用户传达该类专为继承设计。
✔️ 考虑将基类放置在**独立于主线场景类型的命名空间**中。
> 基类适用于高级扩展性场景,对大多数用户不感兴趣。
❌ 如果类用于公共 API**避免**使用 `Base` 后缀命名基类。
> 仅当基类为框架用户提供重要价值时才应使用。如果只对框架实现者有价值,应考虑**委托内部实现**而非继承。
---
## 八、密封
密封是限制扩展性的强大机制,可以密封整个类或单个成员。
### 8.1 密封类
❌ **禁止**无充分理由地密封类。
> "想不出扩展性场景"不是充分理由。框架用户喜欢出于各种原因继承类(如添加便捷成员)。
**密封类的充分理由**
| 理由 | 说明 |
|------|------|
| 静态类 | 静态类应声明为密封 |
| 安全敏感的受保护成员 | 类在继承的受保护成员中存储安全敏感信息 |
| 大量虚拟成员 | 逐个密封的成本超过保持非密封的好处 |
| 需要快速运行时查找的属性类 | 密封属性的性能略高于非密封的 |
### 8.2 密封成员
✔️ 考虑**密封你替代override的成员**。
> 引入虚拟成员可能导致的问题同样适用于替代(尽管程度稍低),密封替代可从继承层次结构的该点开始避免这些问题。
### 8.3 密封类型的约束
❌ **禁止**在密封类型上声明 `protected` 成员或 `virtual` 成员。
> 密封类型不可被继承,受保护成员无法被调用,虚拟方法无法被替代。
---
## 九、模板方法模式
模板方法模式允许子类在保留算法整体结构的同时重新定义其中的特定步骤。其核心目标是**控制扩展性**——将扩展点集中到单个受保护的虚拟方法中。
### 设计准则
❌ **避免**将 `public` 成员设为 `virtual`
✔️ 考虑使用**模板方法模式**提供更可控的扩展性。
✔️ 为非虚公共成员提供扩展点的受保护虚拟方法,命名时使用 **`Core` 后缀**或 **`Internal` 前缀**。
✔️ 在非虚方法中完成**参数验证和状态检查**,然后再调用虚拟方法。
```csharp
// 示例1Core 后缀风格
public class Control
{
// 公共非虚方法:执行验证,调用虚拟扩展点
public void SetBounds(int x, int y, int width, int height)
{
if (width < 0) throw new ArgumentOutOfRangeException(nameof(width));
if (height < 0) throw new ArgumentOutOfRangeException(nameof(height));
SetBoundsCore(x, y, width, height);
}
// 受保护虚拟方法:子类可替代的扩展点
protected virtual void SetBoundsCore(int x, int y, int width, int height)
{
// 默认实现...
}
}
// 示例2Internal 前缀风格
public abstract class AsyncOperationBase
{
internal void StartOperation()
{
// 状态检查、异常隔离...
InternalStart();
}
protected abstract void InternalStart();
}
```
> 非虚公共方法可确保特定代码在虚拟成员调用前后执行,并确保虚拟成员按固定顺序执行。常见错误是将多个重载都设为虚拟方法——应将扩展性集中到单个方法。
---
## 十、扩展机制选择速查表
| 需求 | 推荐机制 |
|------|---------|
| 允许用户添加便捷方法 | 非密封类 |
| 提供高级自定义选项 | 受保护成员 |
| 允许用户自定义行为(不需要 OOP 知识) | 事件 |
| 允许用户提供自定义执行逻辑 | 回调Func/Action |
| 允许子类特化行为(编译时) | 虚拟成员 |
| 定义可由多种类型实现的协定 | 抽象类 / 接口 |
| 降低实现抽象的难度 | 基类 |
| 限制扩展性 | 密封sealed |
> **原则**:优先选择低成本机制。在不确定时保持非密封,因为增加扩展性容易,但移除扩展性会引入破坏性变更。

View File

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

View File

@@ -0,0 +1,255 @@
# .NET 异常设计准则
> 参考来源:[Microsoft .NET 框架设计准则 - 异常设计准则](https://learn.microsoft.com/zh-cn/dotnet/standard/design-guidelines/exceptions)
>
> 核心原则:**异常处理比基于返回值的错误报告具有更多优势。框架应使用异常报告所有错误条件,包括执行错误。**
---
## 一、异常引发
### 1.1 基本原则
✔️ **必须**通过引发异常来报告执行失败。
> 当成员无法完成其设计用途(成员名称所暗示的内容)时,就是执行失败。例如 `OpenFile` 无法返回打开的文件句柄。
✔️ 如果代码遇到进一步执行不安全的情况,考虑调用 `System.Environment.FailFast` 终止进程,而不是引发异常。
✔️ 考虑引发异常对性能的影响。
> **每秒抛出速率超过 100 次**可能会显著影响大多数应用程序的性能。
✔️ **必须**记录所有由公开成员因违反成员协定而引发的异常,并将其视为协定的一部分。
> 协定中的异常不应在版本间更改(异常类型不变、不添加新异常)。
❌ **禁止**返回错误代码。异常是框架中报告错误的主要方法。
❌ **禁止**对正常控制流使用异常(系统故障和争用条件除外)。
❌ **禁止**让公共成员根据某些选项决定是否引发异常。
❌ **禁止**让公共成员将异常作为返回值或 `out` 参数返回。
### 1.2 异常引发的代码组织
✔️ 考虑使用**异常生成器方法**Exception Builder
> 从不同位置引发相同异常是常见的。使用辅助方法创建并初始化异常,可避免代码膨胀,并允许引发异常的成员被内联。
```csharp
// 异常生成器方法示例
private static void ThrowArgumentNullException(string paramName)
{
throw new ArgumentNullException(paramName);
}
public void DoSomething(string value)
{
if (value == null)
ThrowArgumentNullException(nameof(value));
// ... 方法逻辑(可被内联)
}
```
❌ **禁止**从异常筛选器块exception filter block中引发异常。
> 筛选器中引发的异常会被 CLR 捕获,导致筛选器返回 false难以调试。
❌ **避免**从 `finally` 块显式引发异常(由方法调用隐式引发的可以接受)。
---
## 二、使用标准异常类型
### 2.1 Exception 和 SystemException
❌ **禁止**引发 `System.Exception``System.SystemException`
❌ **禁止**在框架代码中捕获 `System.Exception``System.SystemException`,除非打算重新引发。
❌ **避免**捕获 `System.Exception``System.SystemException`(顶级异常处理程序除外)。
### 2.2 ApplicationException
❌ **禁止**引发或派生自 `ApplicationException`
### 2.3 InvalidOperationException
✔️ 如果对象处于**不适当的状态**,引发 `InvalidOperationException`
```csharp
// 示例:对象状态不正确
public void Start()
{
if (_isRunning)
throw new InvalidOperationException("服务已在运行中。");
// ...
}
```
### 2.4 ArgumentException 系列
✔️ 如果传递给成员的参数错误,引发 `ArgumentException` 或其子类型之一,优先使用**派生程度最高的异常类型**。
✔️ 引发 `ArgumentException` 子类时,**必须**设置 `ParamName` 属性。
✔️ 使用 `value` 作为属性 setter 隐式值参数的名称。
| 异常类型 | 使用场景 |
|---------|---------|
| `ArgumentException` | 参数值不合法 |
| `ArgumentNullException` | 参数为 null 但不支持 null |
| `ArgumentOutOfRangeException` | 参数值超出有效范围 |
```csharp
// 推荐用法
public void SetAge(int age)
{
if (age < 0 || age > 150)
throw new ArgumentOutOfRangeException(nameof(age), age, "年龄必须在 0-150 之间。");
}
public void SetName(string name)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
if (name.Length == 0)
throw new ArgumentException("名称不能为空。", nameof(name));
}
```
### 2.5 由 CLR 保留的异常(禁止显式引发)
以下异常仅由 CLR 基础设施引发,**禁止在用户代码中显式引发或捕获**
| 异常类型 | 说明 |
|---------|------|
| `NullReferenceException` | 空引用访问(应通过参数检查避免) |
| `IndexOutOfRangeException` | 数组越界(应通过参数检查避免) |
| `AccessViolationException` | 非法内存访问 |
| `StackOverflowException` | 栈溢出(禁止引发,禁止捕获) |
| `OutOfMemoryException` | 内存不足 |
| `COMException` | COM 互操作异常 |
| `SEHException` | 结构化异常处理 |
| `ExecutionEngineException` | 执行引擎异常 |
> 这些异常指示代码中的 bug 或 CLR 级别的故障,显式引发它们会暴露方法的实现细节。应通过参数检查来预防。
### 2.6 框架内部异常YooInternalException
`YooInternalException` 用于框架内部不变量断言,表示"理论上不可能到达的代码路径"。它与 `ArgumentException` 系列的区别在于**语义和受众**不同:
| 异常类型 | 语义 | 适用场景 |
|---------|------|---------|
| `ArgumentException` 系列 | 调用方传入了错误参数 | 公共 API 的参数校验 |
| `InvalidOperationException` | 对象处于不适当的状态 | 公共 API 的状态校验 |
| `YooInternalException` | 框架内部逻辑错误,理论不可达 | 内部方法的不变量断言 |
✔️ 在 `internal` 方法或框架内部流程中,对理论上不可能发生的状态使用 `YooInternalException`
✔️ 优先使用 `YooInternalException` 而非 `Debug.Assert`,确保在所有构建模式下均能捕获内部逻辑错误。
❌ **禁止**在公共 API 的参数校验中使用 `YooInternalException`,应使用 `ArgumentException` 系列。
---
## 三、异常和性能
### 3.1 性能问题
异常在引发时性能可能慢几个数量级。在严格遵循准则的同时,可通过两种设计模式实现良好性能。
❌ **禁止**因担心异常对性能的影响而使用错误代码。
### 3.2 Tester-Doer 模式(测试者-执行者)
将可能引发异常的成员拆分为两部分:先测试条件,再执行操作。
```csharp
// Tester-Doer 模式
ICollection<int> numbers = ...;
// Tester先检查前置条件
if (!numbers.IsReadOnly)
{
// Doer再执行操作
numbers.Add(1);
}
```
| 角色 | 说明 | 示例 |
|------|------|------|
| **Tester测试者** | 检查前置条件是否满足 | `IsReadOnly``CanSeek``ContainsKey` |
| **Doer执行者** | 执行可能引发异常的操作 | `Add``Seek``GetValue` |
✔️ 考虑对在**常见场景中可能引发异常**的成员使用 Tester-Doer 模式。
### 3.3 Try-Parse 模式(尝试-分析)
对于**极其性能敏感**的 API使用比 Tester-Doer 更快的 Try-Parse 模式。
```csharp
// Try-Parse 模式
public struct DateTime
{
// 失败时引发异常
public static DateTime Parse(string dateTime) { ... }
// 失败时返回 false成功通过 out 参数返回结果
public static bool TryParse(string dateTime, out DateTime result) { ... }
}
// 使用方式
if (DateTime.TryParse(input, out var date))
{
// 解析成功
}
else
{
// 解析失败,无异常开销
}
```
✔️ 对在常见场景中可能引发异常的成员使用 Try-Parse 模式。
✔️ 方法命名使用 **`Try` 前缀**和 **`bool` 返回类型**。
✔️ 为每个 Try-Parse 成员提供一个对应的**异常引发版本**。
> 例如 `Parse` / `TryParse`、`GetValue` / `TryGetValue`。
---
## 四、速查表
### 4.1 何时引发异常
| 场景 | 行为 |
|------|------|
| 成员无法完成其设计用途 | 引发异常 |
| 参数为 null不支持时 | 引发 `ArgumentNullException` |
| 参数超出有效范围 | 引发 `ArgumentOutOfRangeException` |
| 参数值不合法 | 引发 `ArgumentException` |
| 对象状态不正确 | 引发 `InvalidOperationException` |
| 操作不被支持 | 引发 `NotSupportedException` |
| 进一步执行不安全 | 调用 `Environment.FailFast` |
### 4.2 禁止做的事情
| 禁止 | 原因 |
|------|------|
| 返回错误代码 | 异常是唯一的错误报告方式 |
| 用异常做控制流 | 异常仅用于异常情况 |
| 引发 `Exception` / `SystemException` | 过于笼统 |
| 引发 CLR 保留异常 | 由运行时专用 |
| 在 finally 中显式引发 | 可能掩盖原始异常 |
| 在异常筛选器中引发 | 难以调试 |
### 4.3 性能优化模式选择
| 模式 | 适用场景 | 示例 |
|------|---------|------|
| **Tester-Doer** | 常见场景中的条件检查 | `if (!IsReadOnly) Add(item)` |
| **Try-Parse** | 极其性能敏感的 API | `TryParse(input, out result)` |

View File

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

View File

@@ -0,0 +1,335 @@
# .NET 设计模式准则
> 参考来源:《框架设计准则:可重用 .NET 库的约定、惯例和模式第3版》第9章
>
> 核心原则:**框架设计者应提供完整的端到端解决方案,而非仅提供 API。高级 API 应当"开箱即用",用户无需了解底层复杂性。**
---
## 一、聚合组件设计
聚合组件将多个底层分解类型Factored Types绑定到一个更高级别的组件中为常见场景提供简化入口。它是开发者探索其命名空间的**起点**。
### 1.1 Create-Set-Call 使用模式
所有聚合组件都应支持此模式:
```csharp
// 1. Create使用默认或简单构造函数创建
var queue = new MessageQueue();
// 2. Set设置属性
queue.Path = @".\private$\orders";
// 3. Call调用方法
queue.Send("Hello World");
```
### 1.2 聚合组件 vs 分解类型
| 特征 | 聚合组件 | 分解类型 |
|------|---------|---------|
| **目的** | 简化常见场景 | 提供精细控制 |
| **状态** | 可以有模式和临时无效状态 | 不应有模式,生命周期清晰 |
| **构造** | 默认/简单构造函数 | 可能需要复杂初始化 |
| **可用性优先级** | 最高 | 较低 |
| **示例** | `SerialPort``MessageQueue` | `Stream``GZipStream` |
```csharp
// 聚合组件暴露内部分解类型以支持高级场景
var port = new SerialPort("COM1");
port.Open();
GZipStream compressed = new GZipStream(port.BaseStream, CompressionMode.Compress);
compressed.Write(data, 0, data.Length);
port.Close();
```
### 1.3 设计准则
✔️ 考虑为常用功能区域提供聚合组件。
✔️ 设计聚合组件以便**极简初始化后即可使用**。如果需要初始化,未初始化时引发的异常应清楚说明需要做什么。
✔️ **必须**支持 Create-Set-Call 使用模式。
✔️ 为所有构造函数参数提供对应的可读写属性,确保始终可以用默认构造函数 + 属性设置代替参数化构造函数。
✔️ 在聚合组件中使用**事件**代替委托 API 和需要重写的虚拟成员。
✔️ 选择最具"吸引力"的名称给聚合组件(如 `File`),将较不直观的名称留给分解类型(如 `StreamReader`)。
❌ **禁止**在常见场景中要求用户继承、重写方法或实现接口。
❌ **禁止**在常见场景中要求用户做编写代码以外的事(如配置文件、生成资源文件等)。
❌ **禁止**将分解类型设计为有模式的,它们应有明确的生命周期。
✔️ 当用户在无效状态下调用方法时,抛出 `InvalidOperationException`,异常消息应清楚说明需要更改哪些属性。
---
## 二、异步编程准则
### 2.1 异步方法返回类型
✔️ 考虑在方法**经常同步完成**时返回 `ValueTask<T>`
> 例如 `Stream.ReadAsync` 由于缓冲区中常有可用数据,经常同步完成。使用 `ValueTask<T>` 避免了每次调用都分配 `Task<T>` 对象的开销。
```csharp
// ValueTask<T> 适用于经常同步完成的场景
public virtual ValueTask<int> ReadAsync(
Memory<byte> buffer,
CancellationToken cancellationToken = default);
```
> 注意:当方法确实异步完成时,`ValueTask<T>` 仍然需要创建 `Task` 实例。对于始终异步完成的操作,`ValueTask<T>` 会带来轻微的性能损失和可用性降低,此时应使用 `Task<T>`。
### 2.2 ConfigureAwait
✔️ 在 async 方法中等待异步操作时,使用 `await task.ConfigureAwait(false)`,除非在依赖同步上下文的应用模型中。
> 默认情况下,.NET 使用 `SynchronizationContext.Current` 决定如何继续处理 Task。控制台应用中Task 完成回调在后台线程上执行。WinForms/WPF 中,默认将所有回调调度到 UI 线程。`ConfigureAwait(false)` 避免了不必要的线程切换。
### 2.3 避免死锁
❌ **禁止**在 async 方法中调用 `Task.Wait()` 或读取 `Task.Result` 属性,应使用 `await`
> `await` 会让出当前 Task允许其他 Task 继续。`Task.Wait()` 和 `Task.Result` 执行阻塞式同步等待,效率低下且在某些场景下会导致死锁。
✔️ 在 async 方法的实现中,调用**异步方法变体**而非同步变体。
### 2.4 CancellationToken 取消准则
✔️ 因 `CancellationToken` 取消工作时,抛出 `OperationCanceledException`
```csharp
// 两种等效写法
if (cancellationToken.IsCancellationRequested)
throw new OperationCanceledException(cancellationToken);
// 或者
cancellationToken.ThrowIfCancellationRequested();
```
### 2.5 异步方法中的异常
✔️ 将用法错误(如参数验证)的异常直接从非 async 包装方法中抛出,以提高可调试性。
```csharp
// 正确:异常直接抛出,不包装在 Task 中
public Task SaveAsync(string filename)
{
if (filename == null)
throw new ArgumentNullException(nameof(filename));
return SaveAsyncCore(filename);
}
private async Task SaveAsyncCore(string filename)
{
// 实际异步工作...
}
```
```csharp
// 错误:异常在 Task 内部抛出,调用栈不清晰
public async Task SaveAsync(string filename)
{
if (filename == null)
throw new ArgumentNullException(nameof(filename));
// ...
}
```
### 2.6 IAsyncEnumerable\<T\>
✔️ 在使用 `yield return``IAsyncEnumerable` 方法中,为 `CancellationToken` 参数添加 `[EnumeratorCancellation]` 特性。
> 不添加此特性时,`GetAsyncEnumerator` 传入的 `CancellationToken` 值会被编译器生成的实现忽略。
```csharp
public static async IAsyncEnumerable<int> GenerateValues(
int start, int count,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
int end = start + count;
for (int i = start; i < end; i++)
{
await Task.Delay(i, cancellationToken).ConfigureAwait(false);
yield return i;
}
}
```
✔️ 对 `IAsyncEnumerable` 参数使用 `await foreach` 时,使用 `WithCancellation` 传递 `CancellationToken`
```csharp
await foreach (int value in source.WithCancellation(cancellationToken).ConfigureAwait(false))
{
// ...
}
```
---
## 三、Dispose 模式与 IAsyncDisposable
### 3.1 基本 Dispose 模式
托管内存只是系统资源的一种。文件句柄、网络连接、数据库连接等非托管资源仍需显式释放。
终结器Finalizer的缺点
- GC 在**不确定的时间**才调用终结器,延迟可能不可接受
- 需要终结的对象的内存回收会被**推迟到下一轮 GC**
✔️ 考虑在本身不持有非托管资源的类上实现基本 Dispose 模式,如果其**子类型可能持有**。
> 例如 `System.IO.Stream` 是一个不持有资源的抽象基类,但其大多数子类持有资源。
✔️ 在对象被释放后,从任何不能使用的成员中抛出 `ObjectDisposedException`
✔️ 考虑提供 `Close()` 方法作为 `Dispose()` 的补充(如果"close"是该领域的标准术语)。
### 3.2 作用域操作
✔️ 考虑返回一个 disposable 值,代替让调用者手动配对 "begin" 和 "end" 方法。
```csharp
// 使用 disposable 作用域代替 begin/end 配对
using (var scope = logger.BeginScope("Processing"))
{
// 作用域内的操作...
} // 自动结束作用域
```
### 3.3 IAsyncDisposable
`IAsyncDisposable` 允许在 `Dispose()` 涉及 I/O 或其他阻塞操作时使用异步方法进行资源清理。
```csharp
public partial class SomeType : IDisposable, IAsyncDisposable
{
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) { /* ... */ }
public async ValueTask DisposeAsync()
{
await DisposeAsyncCore();
Dispose(false);
GC.SuppressFinalize(this);
}
protected virtual ValueTask DisposeAsyncCore() { /* ... */ }
}
```
> 同时实现 `IDisposable` 和 `IAsyncDisposable` 时,`DisposeAsync` 调用 `Dispose(false)` 以避免同步释放路径重复执行。
---
## 四、Span / Memory 缓冲区操作
### 4.1 Span\<T\> 和 ReadOnlySpan\<T\>
`Span<T>` 表示来自托管数组、stackalloc 数组、指针或字符串的连续内存,支持 O(1) 切片操作。
✔️ 考虑使用 `Span<T>` 作为缓冲区的表示。
✔️ 尽可能使用 `ReadOnlySpan<T>` 代替 `Span<T>`
❌ **避免**返回 Span 或 ReadOnlySpan除非返回值的生命周期非常明确。
✔️ 对返回的、非来自调用方的 Span 提供**非常清晰的所有权规则文档**。
✔️ 考虑从 get-only 属性或无参方法返回 `ReadOnlySpan<T>` 来表示固定数据(当 T 是不可变类型时)。
✔️ 考虑返回 `System.Range`(表示 Span 参数的边界)而非参数的切片。
> `Span<T>` 可以转换为 `ReadOnlySpan<T>`,但反过来不行。如果方法接受 `ReadOnlySpan` 并返回切片,调用方无法轻松确定其可写 Span 的等效切片。
### 4.2 Memory\<T\> 和 ReadOnlyMemory\<T\>
`Span<T>``ref struct`,不能在异步方法中使用。需要存储缓冲区时应使用 `Memory<T>`
✔️ 在异步方法中使用 `ReadOnlyMemory<T>` 代替 `ReadOnlySpan<T>`
✔️ 在异步方法中使用 `Memory<T>` 代替 `Span<T>`
✔️ 当构造函数或方法的目的是**保存缓冲区引用**时,使用 `Memory<T>` / `ReadOnlyMemory<T>` 作为参数。
### 4.3 命名和重载
✔️ 对接受输出缓冲区和单个输入缓冲区的方法,输入缓冲区参数命名为 **`source`**,输出缓冲区参数命名为 **`destination`**。
❌ **避免**在 `Span<T>``ReadOnlySpan<T>` 之间,或 `Memory<T>``ReadOnlyMemory<T>` 之间重载同一方法。
> 如果一个方法以只读方式操作缓冲区,另一个以读写方式操作,它们应有不同的方法名称。
---
## 五、工厂模式
工厂是抽象对象创建过程的操作或操作集合,允许专用语义和更细粒度的实例化控制。
### 设计准则
✔️ **优先使用构造函数**而非工厂,因为构造函数通常更可用、一致和方便。
✔️ 当工厂方法声明在独立的工厂类型上时,考虑使用 **`Create` + 类型名称** 命名。
> 例如创建按钮的工厂方法命名为 `CreateButton`。在某些情况下可使用领域特定名称,如 `File.Open`。
✔️ 考虑使用 **类型名称 + `Factory`** 命名工厂类型。
> 例如创建 `Control` 对象的工厂类型命名为 `ControlFactory`。
---
## 六、超时处理
✔️ **优先使用方法参数**作为用户指定超时时间的机制。
```csharp
server.PerformOperation(timeout);
```
> 备选方案是使用属性:
```csharp
server.Timeout = timeout;
server.PerformOperation();
```
✔️ 优先使用 `TimeSpan` 表示超时时间。
✔️ 在 Unity 项目中,可以使用 `long` 毫秒数代替 `TimeSpan` 表示超时或时间切片,以避免值类型转换开销。
✔️ 超时到期时抛出 `System.TimeoutException`
❌ **禁止**使用错误代码指示超时到期。
---
## 七、LINQ 支持
✔️ 实现 `IEnumerable<T>` 以启用基本 LINQ 支持。
```csharp
public class RangeOfInt32s : IEnumerable<int>
{
public IEnumerator<int> GetEnumerator() { /* ... */ }
IEnumerator IEnumerable.GetEnumerator() { /* ... */ }
}
// 实现 IEnumerable<T> 后即可使用 LINQ
var result = new RangeOfInt32s().Where(x => x > 10);
```
✔️ 考虑实现 `ICollection<T>` 以提升查询运算符的性能。
✔️ 将查询扩展方法放在主命名空间的 **`Linq` 子命名空间**中。
> 例如 `System.Data` 功能的扩展方法应放在 `System.Data.Linq` 命名空间中。

View File

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

View File

@@ -0,0 +1,390 @@
# .NET 使用指南
> 参考来源:[Microsoft .NET 框架设计准则 - 使用指南](https://learn.microsoft.com/zh-cn/dotnet/standard/design-guidelines/usage-guidelines)
>
> 核心原则:**在公开 API 中正确使用内置框架类型,优先选择抽象和接口而非具体实现类型。**
---
## 一、数组
✔️ 在公共 API 中优先使用**集合**而不是数组。
✔️ 考虑使用**交错数组**`int[][]`)而不是多维数组(`int[,]`)。
> 交错数组的元素大小可以不同,浪费空间更少。此外 CLR 优化了交错数组的索引操作,在某些场景中性能更好。
❌ **禁止**使用只读数组字段。
> 字段本身是只读的不能被替换,但数组中的元素仍可被修改。
```csharp
// 错误:数组元素仍可被外部修改
public static readonly int[] InvalidValues = { 1, 2, 3 };
// 正确:使用 ReadOnlyCollection 或 ImmutableArray
public static readonly ReadOnlyCollection<int> InvalidValues =
new ReadOnlyCollection<int>(new[] { 1, 2, 3 });
```
---
## 二、特性Attribute
特性是可添加到程序集、类型、成员和参数的元数据注释,通过反射 API 在运行时访问。
### 设计准则
✔️ 使用 `Attribute` 后缀命名自定义特性类。
✔️ **必须**对自定义特性应用 `[AttributeUsage]`
✔️ 为**必需参数**提供只读属性和对应的构造函数参数。
✔️ 为**可选参数**提供可读写属性(通过 setter 设置)。
✔️ 如果可能,请**密封**自定义特性类(使查找速度更快)。
❌ **避免**重载自定义特性的构造函数(单一构造函数更清晰)。
❌ **避免**提供构造函数参数来初始化可选属性(不要同时支持构造函数和 setter 两种方式设置同一属性)。
```csharp
// 推荐设计
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public sealed class MyCustomAttribute : Attribute
{
// 必需参数:通过构造函数 + 只读属性
public MyCustomAttribute(string name)
{
Name = name;
}
public string Name { get; }
// 可选参数:通过可读写属性
public int Priority { get; set; }
}
// 使用方式
[MyCustom("example", Priority = 5)]
public class MyClass { }
```
---
## 三、集合
### 3.1 公共 API 中的集合选择
❌ **禁止**在公共 API 中使用弱类型集合。
> 所有表示集合项的返回值和参数都应使用确切的项类型。
❌ **禁止**在公共 API 中使用 `ArrayList``List<T>`
> 这些是为内部实现设计的数据结构。`List<T>` 牺牲了 API 简洁性和灵活性来优化性能。
❌ **禁止**在公共 API 中使用 `Hashtable``Dictionary<TKey,TValue>`
> 应使用 `IDictionary`、`IDictionary<TKey,TValue>` 或实现这些接口的自定义类型。
❌ **禁止**在同一类型上同时实现 `IEnumerator<T>``IEnumerable<T>`
❌ **禁止**从非 `GetEnumerator` 方法返回枚举器类型。
### 3.2 集合参数
✔️ 尽可能使用**最不专用的类型**作为参数类型。
> 大多数以集合为参数的成员应使用 `IEnumerable<T>` 接口。
❌ **避免**仅为访问 `Count` 属性而使用 `ICollection<T>` 作为参数。
> 应使用 `IEnumerable<T>`,并动态检查是否实现了 `ICollection<T>`。
### 3.3 集合属性和返回值
❌ **禁止**提供可设置的集合属性。
> 用户可以通过先清除再添加来替换内容。如需替换整个集合,提供 `AddRange` 方法。
✔️ 对读写集合的属性或返回值使用 `Collection<T>` 或其子类。
✔️ 对只读集合使用 `ReadOnlyCollection<T>` 或其子类。
❌ **禁止**从集合属性或返回集合的方法中返回 `null`
> **应返回空集合或空数组**。一般规则null 和空集合应被视为相同。
### 3.4 快照集合 vs 实时集合
| 类型 | 说明 | 示例 |
|------|------|------|
| **快照集合** | 表示某个时间点的状态 | 数据库查询结果 |
| **实时集合** | 始终表示当前状态 | ComboBox 的 Items |
❌ **禁止**从属性返回快照集合getter 应轻量,快照需要 O(n) 复制)。
✔️ 使用快照集合或实时 `IEnumerable<T>` 表示可变集合(无需显式修改即可变化的集合)。
### 3.5 数组 vs 集合选择
✔️ 在大多数情况下**优先选择集合**——更好的内容控制、更易使用。
✔️ 在**低级 API** 中考虑使用数组以最小化内存消耗和最大化性能。
✔️ 使用**字节数组**`byte[]`)而非字节集合。
❌ 如果每次调用属性 getter 都需要返回新数组(如内部数组的副本),**禁止**对属性使用数组。
### 3.6 自定义集合设计
✔️ 设计新集合时考虑继承自 `Collection<T>``ReadOnlyCollection<T>``KeyedCollection<TKey,TItem>`
✔️ **必须**实现 `IEnumerable<T>`,按需考虑实现 `ICollection<T>``IList<T>`
✔️ 如果集合项具有唯一键,考虑使用**键控集合**(继承自 `KeyedCollection<TKey,TItem>`)。
❌ **禁止**继承自非泛型基集合(如 `CollectionBase`)。
❌ **避免**在与集合概念无关的复杂 API 类型上实现集合接口。
### 3.7 自定义集合命名
✔️ 实现 `IDictionary` / `IDictionary<TKey,TValue>` 的类型使用 **`Dictionary`** 后缀。
✔️ 实现 `IEnumerable` 且表示项列表的类型使用 **`Collection`** 后缀。
✔️ 考虑用项类型名称作为集合名称前缀:`AddressCollection`
✔️ 只读集合考虑使用 **`ReadOnly`** 前缀:`ReadOnlyStringCollection`
❌ **避免**使用表示具体实现的后缀(如 `LinkedList``Hashtable`)。
---
## 四、序列化
序列化是将对象转换为可持久保存或传输格式的过程。
### 4.1 三种序列化技术
| 技术 | 主要类型 | 适用场景 |
|------|---------|---------|
| **数据协定序列化** | `[DataContract]` / `[DataMember]` / `DataContractSerializer` | 一般持久性、Web 服务、JSON |
| **XML 序列化** | `XmlSerializer` | 需要完全控制 XML 形状 |
| **运行时序列化** | `[Serializable]` / `ISerializable` / `BinaryFormatter` | .NET 远程处理 |
### 4.2 选择准则
✔️ 设计新类型时,考虑序列化需求。
✔️ 如果类型需要持久保存或用于 Web 服务,考虑支持**数据协定序列化**。
✔️ 如果需要完全控制 XML 形状,考虑支持 **XML 序列化**
✔️ 如果类型需要跨 .NET 远程处理边界传输,考虑支持**运行时序列化**。
❌ **避免**仅出于一般持久性原因支持运行时序列化或 XML 序列化,应优先使用数据协定序列化。
### 4.3 数据协定序列化准则
✔️ 如果类型可在部分信任中使用,考虑将数据成员标记为 `public`
✔️ 具有 `[DataMember]` 的属性**必须**同时具有 getter 和 setter。
✔️ 考虑使用序列化回调初始化反序列化后的实例(反序列化时不调用构造函数)。
> 使用 `[OnDeserialized]`、`[OnDeserializing]`、`[OnSerializing]`、`[OnSerialized]` 特性。
✔️ 考虑使用 `[KnownType]` 指示反序列化复杂对象图时应使用的具体类型。
✔️ 创建或更改可序列化类型时,考虑**向后和向前兼容性**。
✔️ 考虑实现 `IExtensibleDataObject` 以允许不同版本间的往返序列化。
### 4.4 XML 序列化准则
❌ **避免**专门为 XML 序列化设计类型(已被数据协定序列化取代)。
✔️ 如果需要精细控制 XML 形状,考虑实现 `IXmlSerializable` 接口。
### 4.5 运行时序列化准则
✔️ 如果需要完全控制序列化过程,实现 `ISerializable` 并提供序列化构造函数。
✔️ 序列化构造函数应为 `protected`
✔️ **必须**显式实现 `ISerializable` 成员。
```csharp
[Serializable]
public class Person : ISerializable
{
public string Name { get; set; }
public Person() { }
// 序列化构造函数protected
protected Person(SerializationInfo info, StreamingContext context)
{
Name = info.GetString(nameof(Name));
}
// 显式实现
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue(nameof(Name), Name);
}
}
```
---
## 五、System.Xml 用法
❌ **禁止**使用 `XmlNode``XmlDocument` 表示 XML 数据。
✔️ 使用 `XmlReader``XmlWriter``IXPathNavigable``XNode` 子类型作为接受或返回 XML 的成员的输入输出。
> 使用抽象类型可以将方法与内存中 XML 文档的具体实现分离,允许与虚拟 XML 数据源一起工作。
❌ 如果要创建表示基础对象模型的 XML 视图,**禁止**继承 `XmlDocument`
---
## 六、相等运算符
### 6.1 通用准则
❌ **禁止**只重载 `==``!=` 中的一个而忽略另一个(必须成对重载)。
✔️ 确保 `Object.Equals` 和相等运算符具有**相同的语义和相似的性能**。
> 重载相等运算符时,通常需要同时重写 `Object.Equals`。
❌ **避免**从相等运算符引发异常(参数为 null 时应返回 false
### 6.2 值类型的相等运算符
✔️ 如果相等有意义,**应该**对值类型重载相等运算符。
> 大多数语言没有为值类型提供默认的 `operator==` 实现。
### 6.3 引用类型的相等运算符
❌ **避免**在**可变引用类型**上重载相等运算符。
> 内置运算符实现引用相等性,改为值相等性会使许多开发人员困惑。不可变引用类型则问题较小。
❌ **避免**对引用类型重载相等运算符(如果实现速度远低于引用相等性)。
---
## 七、IEquatable / IComparable 准则
`IComparable<T>` 定义排序语义(小于、等于、大于),主要用于排序。`IEquatable<T>` 定义相等语义,主要用于查找。
✔️ **必须**在值类型上实现 `IEquatable<T>`
> `Object.Equals` 在值类型上会导致装箱,且默认实现使用反射,效率低下。
✔️ 实现 `IEquatable<T>` 时**必须**同时重写 `Object.Equals`
✔️ 实现 `IEquatable<T>` 时考虑重载 `operator==``operator!=`
✔️ 实现 `IComparable<T>` 时**必须**同时实现 `IEquatable<T>`
✔️ 重写 `Object.Equals` 时**必须**同时重写 `GetHashCode`
---
## 八、Object 准则
### 8.1 Object.Equals
- **值类型**默认实现:当所有字段相等时返回 true"值相等"),但实现**使用反射**,通常效率低下,**应当重写**
- **引用类型**默认实现:当两个引用指向同一对象时返回 true"引用相等"
❌ **禁止**在**可变引用类型**上实现值相等。
> 实现值相等的引用类型(如 `System.String`)应当是不可变的。
### 8.2 Object.GetHashCode
✔️ 重写 `Equals` 时**必须**重写 `GetHashCode`,确保被视为相等的两个对象具有相同的哈希码。
✔️ 尽力确保 `GetHashCode` 为类型的所有对象生成**均匀分布**的数字,以最小化哈希表冲突。
### 8.3 Object.ToString
`Object.ToString` 用于通用显示和调试目的,默认实现仅返回类型名称。
✔️ 建议**始终重写** `ToString`,提供有意义的字符串表示。
---
## 九、Nullable\<T\> 准则
❌ **禁止**使用 `Nullable<T>` 表示可选参数。
> 应使用方法重载来表示可选参数。
```csharp
// 正确:使用重载
public class Foo
{
public Foo(string name, int id) { }
public Foo(string name) { }
}
```
❌ **避免**使用 `Nullable<bool>` 表示通用三态值。
> `Nullable<bool>` 仅应用于真正可选的布尔值true、false、不可用。如果需要表示三种状态如 yes、no、cancel应使用枚举。
---
## 十、URI 准则
✔️ 使用 `System.Uri` 表示 URI 和 URL 数据,而非普通字符串。
> `System.Uri` 提供了更安全、更丰富的 URI 表示方式。使用普通字符串大量操作 URI 相关数据已被证明会导致许多安全和正确性问题。
---
## 十一、ICloneable 准则
❌ **禁止**在公共 API 中使用 `ICloneable`
> `ICloneable` 的问题在于协定未指定克隆是浅拷贝还是深拷贝——有些实现返回浅拷贝,有些返回深拷贝。调用者永远无法确定会得到什么,这使得该接口毫无用处。
✔️ 如果需要克隆机制,考虑在类型上定义自己的 `Clone` 方法,并明确文档说明是浅拷贝还是深拷贝。
---
## 十二、速查表
### 公共 API 中的类型选择
| 场景 | 推荐 | 避免 |
|------|------|------|
| 集合参数 | `IEnumerable<T>` | `List<T>``ArrayList` |
| 读写集合返回值 | `Collection<T>` 或子类 | `List<T>` |
| 只读集合返回值 | `ReadOnlyCollection<T>` | 数组(需复制) |
| 字典参数/返回值 | `IDictionary<TKey,TValue>` | `Dictionary<TKey,TValue>` |
| 表示 XML 数据 | `XmlReader``XNode` | `XmlNode``XmlDocument` |
| 序列化 | 数据协定序列化 | 运行时序列化(除非远程处理) |
| 低级高性能 API | 数组 | 集合 |
| 字节数据 | `byte[]` | `Collection<byte>` |
### 禁止事项
| 禁止 | 原因 |
|------|------|
| 公共 API 返回 `null` 集合 | 应返回空集合或空数组 |
| 可设置的集合属性 | 用户应通过 Clear+Add 替换内容 |
| 只读数组字段 | 元素仍可被修改 |
| 属性返回快照集合 | getter 应轻量,不应有 O(n) 开销 |
| 继承 `CollectionBase` | 应使用泛型基类 |

View File

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

View File

@@ -0,0 +1,214 @@
# .NET 日志规范
> 参考来源:[Microsoft .NET 框架设计准则 - 异常消息](https://learn.microsoft.com/zh-cn/dotnet/standard/design-guidelines/exceptions-and-performance) · [.NET Runtime 源码日志风格](https://github.com/dotnet/runtime)
>
> 核心原则:**措辞简洁准确、格式统一规范、使用地道英语、避免泄露敏感信息**
---
## 一、变量引用规则
### 1.1 字符串变量值加单引号
文件路径、名称、GUID、标识符等字符串变量值用单引号包裹便于在日志中区分变量边界。
```csharp
// ✔️
$"File cache entry not found: '{bundleGUID}'."
$"Invalid clear method: '{options.ClearMethod}'."
$"Could not find file '{filePath}'."
// ❌
$"File cache entry not found: {bundleGUID}."
```
### 1.2 数值变量不加引号
数值类型的变量(整数、浮点、枚举值)不加引号。
```csharp
// ✔️
$"MaxTimeSlice must be at least {MinTimeSlice} ms."
$"Does not support bundle type: {options.Bundle.BundleType}."
// ❌
$"Does not support bundle type: '{options.Bundle.BundleType}'."
```
### 1.3 类型名作句首主语不加引号
类型名(通常通过 `nameof()``GetType().Name` 获取)做句子主语时,不加引号。
```csharp
// ✔️
$"{nameof(SandboxBundleCache)} does not support synchronous loading."
$"AsyncOperationSystem is not initialized."
// ❌
$"'{nameof(SandboxBundleCache)}' does not support synchronous loading."
```
### 1.4 异常对象不加引号
异常对象(`{ex}``{ex.Message}`)直接拼接,不加引号。
```csharp
// ✔️
YooLogger.Error($"File verification exception: {ex.Message}.");
YooLogger.Warning($"Failed to delete cache bundle folder: {ex}.");
// ❌
YooLogger.Error($"File verification exception: '{ex.Message}'.");
```
---
## 二、消息格式规则
### 2.1 首字母大写,末尾加句号
所有日志和异常消息使用 Sentence case首字母大写末尾加句号。
```csharp
// ✔️
SetError("Unity engine load failed.");
throw new Exception("Catalog file data is null or empty.");
// ❌
SetError("unity engine load failed");
throw new Exception("catalog file data is null or empty");
```
### 2.2 使用完整句子
使用语法完整的句子,不省略主语或谓语。
> **来源**.NET 官方规范 — *"Do use complete sentences. 'Binding is too long.' rather than 'Binding too long.'"*
```csharp
// ✔️
"Decryptor is null."
"Loaded bundle handle is null."
// ❌
"Decryptor null."
"Bundle handle null."
```
### 2.3 不要以冠词或变量开头
消息以关键实体词开头,不要以 `The` / `A` / `An` 或变量开头,便于日志搜索和排序。
> **来源**.NET 官方规范 — *"Do not start with an article or variable. 'Log file {0} is full.' is preferable to '{0} log file is full.'"*
```csharp
// ✔️
$"Cache entry already exists: '{bundleGUID}'."
$"Log file '{name}' is full."
// ❌
$"The cache entry already exists: '{bundleGUID}'."
$"'{name}' log file is full."
```
### 2.4 不要使用感叹号和问号
消息末尾只使用句号,禁止感叹号和问号。
> **来源**.NET 官方规范 — *"Do not use question marks or exclamation points."*
```csharp
// ✔️
"Command is unrecognizable."
// ❌
"Command is unrecognizable!!"
"Did the operation complete?"
```
---
## 三、语气和措辞规则
### 3.1 使用中性语气
使用中性客观的措辞,不归咎用户,不拟人化程序。
> **来源**.NET 官方规范 — *"Do use a neutral tone."* / *"Do not personify."*
```csharp
// ✔️
"Command is unrecognizable."
"Node parameter cannot use this protocol."
// ❌
"Bad input."
"Parameter node does not speak any of our protocols."
```
### 3.2 省略 `this.` 前缀
`this.GetType().Name` 中的 `this.` 可省略。
```csharp
// ✔️
$"Exception in {GetType().Name}.InternalStart: {ex}."
// ❌
$"Exception in {this.GetType().Name}.InternalStart: {ex}."
```
### 3.3 不要泄露敏感信息
异常和日志消息中避免包含密钥、密码、完整用户路径等敏感信息。
> **来源**.NET 官方规范 — *"Do not disclose sensitive information in exception messages."*
---
## 四、一致性规则
### 4.1 保持一致性
同一项目中相同语义使用相同句式,避免同一错误用不同表达方式。
```csharp
// ✔️ 统一格式
$"File cache entry not found: '{bundleGUID}'." // 所有缓存未命中统一使用此句式
$"Exception in {GetType().Name}.{MethodName}: {ex}." // 所有异常日志统一使用此句式
// ❌ 同一语义不同表达
"Cached bundle not found." // ← 与上面的 "File cache entry not found" 不一致
```
### 4.2 避免中式英语和语法错误
使用地道的英语表达,避免中文直译造成的不自然句式。
| 中式英语 | 地道表达 | 说明 |
|----------|----------|------|
| `"cannot match the file hash"` | `"File hash does not match."` | 主语应是名词而非动词短语 |
| `"Failed fallback load bundle"` | `"Failed to load bundle with fallback."` | 缺少介词和完整句法 |
| `"Not found xxx in yyy"` | `"Could not find xxx in yyy."` | `Not found` 不是完整句子 |
| `"The bundle file is lose"` | `"The bundle file is missing."` | 词性错误lose → missing |
| `"Begin to download"` | `"Starting download."` | `begin to` 是典型中式英语 |
---
## 五、速查表
| # | 规则 | 正确示例 | 错误示例 |
|---|------|---------|---------|
| 1 | 字符串变量值加单引号 | `'{bundleGUID}'` | `{bundleGUID}` |
| 2 | 数值变量不加引号 | `{count}` | `'{count}'` |
| 3 | 类型名作主语不加引号 | `TypeName is not ...` | `'TypeName' is not ...` |
| 4 | 异常对象不加引号 | `{ex.Message}` | `'{ex.Message}'` |
| 5 | 首字母大写,末尾加句号 | `"Load failed."` | `"load failed"` |
| 6 | 使用完整句子 | `"Decryptor is null."` | `"Decryptor null."` |
| 7 | 不以冠词或变量开头 | `"Cache entry ..."` | `"The cache entry ..."` |
| 8 | 只用句号 | `"Failed."` | `"Failed!!"` |
| 9 | 省略 `this.` | `GetType().Name` | `this.GetType().Name` |
| 10 | 使用中性语气 | `"Command is unrecognizable."` | `"Bad input."` |
| 11 | 不泄露敏感信息 | — | 密钥、密码、用户路径 |
| 12 | 保持一致性 | 同语义同句式 | 同错误不同表达 |
| 13 | 避免中式英语 | `"File hash does not match."` | `"cannot match the file hash"` |

View File

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

View File

@@ -0,0 +1,353 @@
# .NET 注释规范
> 参考来源:[Microsoft C# XML 文档注释](https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/xmldoc/) · [推荐的文档注释标签](https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/xmldoc/recommended-tags) · [框架设计准则](https://learn.microsoft.com/zh-cn/dotnet/standard/design-guidelines/)
>
> 核心原则:**公共 API 必须注释、说明意图而非复述代码、保持与代码同步**
---
## 一、概述
C# 使用 XML 文档注释为代码提供结构化文档。编译器从 `///`(三斜杠)注释中生成 XML 文件,供 IDE 智能提示和文档生成工具使用。
---
## 二、XML 注释标签
### 2.1 核心标签
| 标签 | 用途 | 适用场景 |
|------|------|----------|
| `<summary>` | 简要描述类型或成员的功能 | 所有公共类型和成员 |
| `<param>` | 描述方法参数 | 方法、构造函数 |
| `<returns>` | 描述方法返回值 | 有返回值的方法 |
| `<exception>` | 说明可能抛出的异常 | 可能抛出异常的方法 |
| `<remarks>` | 补充说明,提供额外细节 | 需要详细解释的成员 |
| `<value>` | 描述属性所代表的值 | 属性 |
### 2.2 引用标签
| 标签 | 用途 | 示例 |
|------|------|------|
| `<see cref=""/>` | 行内引用其他类型或成员 | `<see cref="MyClass"/>` |
| `<seealso cref=""/>` | 在"另请参阅"区域添加引用 | `<seealso cref="OtherMethod"/>` |
| `<paramref name=""/>` | 引用参数名 | `<paramref name="count"/>` |
| `<typeparamref name=""/>` | 引用泛型类型参数名 | `<typeparamref name="T"/>` |
### 2.3 格式标签
| 标签 | 用途 |
|------|------|
| `<para>` | 在 `<remarks>` 等标签内分段 |
| `<list>` | 创建列表或表格 |
| `<code>` | 多行代码示例 |
| `<c>` | 行内代码片段 |
| `<example>` | 包含使用示例 |
### 2.4 泛型与继承标签
| 标签 | 用途 |
|------|------|
| `<typeparam>` | 描述泛型类型参数 |
| `<inheritdoc/>` | 继承基类或接口的文档注释 |
---
## 三、注释范围要求
### 3.1 必须添加注释的成员
| 访问级别 | 要求 |
|----------|------|
| `public` | **必须**添加完整 XML 注释 |
| `protected` | **必须**添加完整 XML 注释 |
| `internal` | **建议**添加 XML 注释 |
| `private` | 仅在逻辑复杂时添加 |
### 3.2 各成员类型的注释要求
| 成员类型 | 必需标签 | 可选标签 |
|----------|---------|---------|
| 类 / 结构 | `<summary>` | `<remarks>``<typeparam>` |
| 接口 | `<summary>` | `<remarks>` |
| 方法 | `<summary>``<param>``<returns>` | `<exception>``<remarks>``<example>` |
| 构造函数 | `<summary>``<param>` | `<exception>` |
| 属性 | `<summary>` | `<value>``<exception>` |
| 事件 | `<summary>` | `<remarks>` |
| 枚举类型 | `<summary>` | — |
| 枚举值 | `<summary>` | — |
| 委托 | `<summary>``<param>``<returns>` | — |
---
## 四、书写原则
### 4.1 `<summary>` 书写规范
✔️ **一句话**说明"做什么",而非"怎么做"。
✔️ 以**第三人称动词**开头。
✔️ 完整语句末尾**建议**使用句号,短语式描述可省略。
❌ **禁止**重复方法名或参数类型中已表达的信息。
❌ **禁止**以"这个方法"、"该类"等冗余前缀开头。
### 4.2 常见动词约定
| 成员类型 | 推荐开头动词 |
|----------|-------------|
| 返回 bool 的方法 / 属性 | "检查是否"、"判断是否" |
| 获取类方法 | "获取"、"查询" |
| 设置类方法 | "设置"、"更新" |
| 创建类方法 | "创建"、"构建"、"生成" |
| 事件 | "当……时触发" |
| 构造函数 | "创建 XXX 实例"XXX 为类型的中文描述) |
### 4.3 `<param>` 书写规范
✔️ 描述参数的**含义和用途**。
✔️ 说明有效范围和边界值(如为 `null` 时的行为、取值范围)。
✔️ 完整语句末尾**建议**使用句号,短语式描述可省略。
❌ **禁止**仅重复参数名或类型名,如"url 参数"。
### 4.4 `<returns>` 书写规范
✔️ 说明返回值的**含义**。
✔️ 说明**特殊返回值**的含义(如返回 `null` 表示未找到)。
### 4.5 `<exception>` 书写规范
✔️ 指定异常的**具体类型**`cref` 属性)。
✔️ 说明异常的**触发条件**。
✔️ 使用 `<paramref>` 引用相关参数。
---
## 五、注释示例
### 5.1 类注释
```csharp
/// <summary>
/// 资源包下载请求,负责管理单个资源包的下载生命周期。
/// </summary>
/// <remarks>
/// <para>支持断点续传和失败重试。</para>
/// <para>通过 <see cref="DownloadRetryController"/> 控制重试策略。</para>
/// </remarks>
public class BundleDownloadRequest
{
}
```
### 5.2 方法注释
```csharp
/// <summary>
/// 从指定 URL 异步下载资源包并保存到本地磁盘。
/// </summary>
/// <param name="url">资源包的完整下载地址。</param>
/// <param name="savePath">本地保存的目标路径。</param>
/// <param name="timeout">超时时间0 表示不限制。</param>
/// <returns>下载结果,包含状态码和错误信息。</returns>
/// <exception cref="ArgumentNullException">
/// 当 <paramref name="url"/> 或 <paramref name="savePath"/> 为 <c>null</c> 时抛出。
/// </exception>
/// <exception cref="TimeoutException">当下载超过指定时间时抛出。</exception>
public async Task<DownloadResult> DownloadAsync(string url, string savePath, int timeout = 30)
{
}
```
### 5.3 属性注释
```csharp
/// <summary>
/// 获取当前下载进度。
/// </summary>
/// <value>取值范围 0.0 ~ 1.0,其中 1.0 表示下载完成。</value>
public float Progress { get; private set; }
```
### 5.4 枚举注释
```csharp
/// <summary>
/// 下载任务的运行状态。
/// </summary>
public enum DownloadStatus
{
/// <summary>
/// 等待中,尚未开始下载。
/// </summary>
Pending,
/// <summary>
/// 正在下载中。
/// </summary>
Downloading,
/// <summary>
/// 下载已完成。
/// </summary>
Completed,
/// <summary>
/// 下载失败。
/// </summary>
Failed
}
```
### 5.5 泛型类注释
```csharp
/// <summary>
/// 通用对象池,提供对象的复用管理。
/// </summary>
/// <typeparam name="T">池化对象的类型,必须实现 <see cref="IDisposable"/>。</typeparam>
public class ObjectPool<T> where T : IDisposable
{
}
```
### 5.6 接口注释
```csharp
/// <summary>
/// 定义下载请求的标准行为。
/// </summary>
public interface IDownloadRequest
{
/// <summary>
/// 获取请求的远程 URL。
/// </summary>
string URL { get; }
/// <summary>
/// 取消当前下载请求。
/// </summary>
void Abort();
}
```
### 5.7 使用 `<inheritdoc/>`
```csharp
public class MyDownloadRequest : IDownloadRequest
{
/// <inheritdoc/>
public string URL { get; }
/// <inheritdoc/>
public void Abort()
{
}
}
```
### 5.8 使用 `<example>` 和 `<code>`
```csharp
/// <summary>
/// 根据资源路径异步加载资源对象。
/// </summary>
/// <param name="assetPath">资源路径。</param>
/// <returns>资源操作句柄,加载失败时资源对象为 <c>null</c>。</returns>
/// <example>
/// 加载一个预制体:
/// <code>
/// var handle = package.LoadAssetAsync&lt;GameObject&gt;("Assets/Prefabs/Player.prefab");
/// await handle.Task;
/// var prefab = handle.AssetObject as GameObject;
/// </code>
/// </example>
public AssetHandle LoadAssetAsync<T>(string assetPath)
{
}
```
---
## 六、反面示例
### 6.1 冗余注释
```csharp
// ❌ 差:重复方法签名中已有的信息
/// <summary>
/// 下载方法,参数是 url 和 savePath。
/// </summary>
public void Download(string url, string savePath) { }
// ✔️ 好:说明行为和目的
/// <summary>
/// 从远程服务器下载资源包并保存到本地磁盘。
/// </summary>
/// <param name="url">资源包的完整下载地址。</param>
/// <param name="savePath">本地保存的目标路径。</param>
public void Download(string url, string savePath) { }
```
### 6.2 废话参数注释
```csharp
// ❌ 差:仅重复参数名
/// <param name="name">名称。</param>
// ✔️ 好:说明含义和约束
/// <param name="name">资源包的唯一标识名称,不可为 <c>null</c> 或空字符串。</param>
```
### 6.3 缺少边界说明
```csharp
// ❌ 差:未说明返回 null 的情况
/// <returns>资源对象。</returns>
// ✔️ 好:说明特殊返回值
/// <returns>加载到的资源对象,如果资源不存在则返回 <c>null</c>。</returns>
```
---
## 七、行内注释规范
对于非 XML 文档注释(`//` 普通注释),遵循以下原则:
### 7.1 基本准则
✔️ 解释"**为什么**",而非"是什么"——代码本身应说明"做什么",注释解释不明显的意图或约束。
✔️ 标记待办事项使用 `// TODO:` 前缀。
✔️ 标记临时方案使用 `// HACK:` 前缀。
✔️ 修改代码时**同步更新**相关注释,过时的注释比没有注释更有害。
❌ **禁止**注释显而易见的代码,如 `// 递增计数器``// 返回结果`
❌ **禁止**用注释替代清晰的命名——如果需要注释来解释变量含义,应优先改善变量名。
### 7.2 示例
```csharp
// ❌ 差:复述代码
// 如果计数大于最大值,重置为零
if (count > maxCount)
count = 0;
// ✔️ 好:解释约束
// 环形缓冲区写满后从头覆盖,避免无限增长
if (count > maxCount)
count = 0;
```

View File

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

View File

@@ -1,4 +1,4 @@
using System.Collections;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
@@ -22,7 +22,7 @@ namespace YooAsset.Editor
/// </summary>
public static string GetStreamingAssetsRoot()
{
return YooAssetSettingsData.GetYooDefaultBuiltinRoot();
return YooAssetConfiguration.GetDefaultBuiltinRoot();
}
}
}

View File

@@ -8,7 +8,7 @@ namespace YooAsset.Editor
/// <summary>
/// 模拟构建
/// </summary>
public static PackageInvokeBuildResult SimulateBuild(PackageInvokeBuildParam buildParam)
public static PackageBuildResult SimulateBuild(PackageBuildParameters buildParam)
{
string packageName = buildParam.PackageName;
string buildPipelineName = buildParam.BuildPipelineName;
@@ -32,7 +32,7 @@ namespace YooAsset.Editor
BuildResult buildResult = pipeline.Run(buildParameters, false);
if (buildResult.Success)
{
var reulst = new PackageInvokeBuildResult();
var reulst = new PackageBuildResult();
reulst.PackageRootDirectory = buildResult.OutputPackageDirectory;
return reulst;
}

View File

@@ -200,9 +200,9 @@ namespace YooAsset.Editor
{
PackageBundle packageBundle = new PackageBundle();
packageBundle.BundleName = BundleName;
packageBundle.UnityCRC = PackageUnityCRC;
packageBundle.UnityCrc = PackageUnityCRC;
packageBundle.FileHash = PackageFileHash;
packageBundle.FileCRC = PackageFileCRC;
packageBundle.FileCrc = PackageFileCRC;
packageBundle.FileSize = PackageFileSize;
packageBundle.IsEncrypted = Encrypted;
return packageBundle;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
@@ -27,7 +27,7 @@ namespace YooAsset.Editor
// 拷贝补丁清单文件
{
string fileName = YooAssetSettingsData.GetManifestBinaryFileName(buildPackageName, buildPackageVersion);
string fileName = YooAssetConfiguration.GetManifestBinaryFileName(buildPackageName, buildPackageVersion);
string sourcePath = $"{packageOutputDirectory}/{fileName}";
string destPath = $"{buildinRootDirectory}/{fileName}";
EditorTools.CopyFile(sourcePath, destPath, true);
@@ -35,7 +35,7 @@ namespace YooAsset.Editor
// 拷贝补丁清单哈希文件
{
string fileName = YooAssetSettingsData.GetPackageHashFileName(buildPackageName, buildPackageVersion);
string fileName = YooAssetConfiguration.GetPackageHashFileName(buildPackageName, buildPackageVersion);
string sourcePath = $"{packageOutputDirectory}/{fileName}";
string destPath = $"{buildinRootDirectory}/{fileName}";
EditorTools.CopyFile(sourcePath, destPath, true);
@@ -43,7 +43,7 @@ namespace YooAsset.Editor
// 拷贝补丁清单版本文件
{
string fileName = YooAssetSettingsData.GetPackageVersionFileName(buildPackageName);
string fileName = YooAssetConfiguration.GetPackageVersionFileName(buildPackageName);
string sourcePath = $"{packageOutputDirectory}/{fileName}";
string destPath = $"{buildinRootDirectory}/{fileName}";
EditorTools.CopyFile(sourcePath, destPath, true);
@@ -66,7 +66,7 @@ namespace YooAsset.Editor
string[] tags = buildParametersContext.Parameters.BuildinFileCopyParams.Split(';');
foreach (var packageBundle in manifest.BundleList)
{
if (packageBundle.HasTag(tags) == false)
if (packageBundle.HasAnyTag(tags) == false)
continue;
string sourcePath = $"{packageOutputDirectory}/{packageBundle.FileName}";
string destPath = $"{buildinRootDirectory}/{packageBundle.FileName}";

View File

@@ -15,7 +15,7 @@ namespace YooAsset.Editor
string buildinRootDirectory = buildParametersContext.GetBuildinRootDirectory();
string buildPackageName = buildParametersContext.Parameters.PackageName;
var manifestServices = buildParametersContext.Parameters.ManifestDecryptor;
BuiltinCatalogTools.CreateFile(manifestServices, buildPackageName, buildinRootDirectory);
BuiltinCatalogHelper.CreateFile(manifestServices, buildPackageName, buildinRootDirectory);
// 刷新目录
AssetDatabase.Refresh();

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
@@ -69,9 +69,9 @@ namespace YooAsset.Editor
// 创建资源清单文本文件
{
string fileName = YooAssetSettingsData.GetManifestJsonFileName(buildParameters.PackageName, buildParameters.PackageVersion);
string fileName = YooAssetConfiguration.GetManifestJsonFileName(buildParameters.PackageName, buildParameters.PackageVersion);
string filePath = $"{packageOutputDirectory}/{fileName}";
PackageManifestTools.SerializeManifestToJson(filePath, manifest);
PackageManifestHelper.SerializeManifestToJson(filePath, manifest);
BuildLogger.Log($"Create package manifest file: {filePath}");
}
@@ -79,16 +79,16 @@ namespace YooAsset.Editor
string packageHash;
string packagePath;
{
string fileName = YooAssetSettingsData.GetManifestBinaryFileName(buildParameters.PackageName, buildParameters.PackageVersion);
string fileName = YooAssetConfiguration.GetManifestBinaryFileName(buildParameters.PackageName, buildParameters.PackageVersion);
packagePath = $"{packageOutputDirectory}/{fileName}";
PackageManifestTools.SerializeManifestToBinary(packagePath, manifest, buildParameters.ManifestEncryptor);
packageHash = HashUtility.ComputeFileCRC32(packagePath);
PackageManifestHelper.SerializeManifestToBinary(packagePath, manifest, buildParameters.ManifestEncryptor);
packageHash = HashUtility.ComputeFileCrc32(packagePath);
BuildLogger.Log($"Create package manifest file: {packagePath}");
}
// 创建资源清单哈希文件
{
string fileName = YooAssetSettingsData.GetPackageHashFileName(buildParameters.PackageName, buildParameters.PackageVersion);
string fileName = YooAssetConfiguration.GetPackageHashFileName(buildParameters.PackageName, buildParameters.PackageVersion);
string filePath = $"{packageOutputDirectory}/{fileName}";
FileUtility.WriteAllText(filePath, packageHash);
BuildLogger.Log($"Create package manifest hash file: {filePath}");
@@ -96,7 +96,7 @@ namespace YooAsset.Editor
// 创建资源清单版本文件
{
string fileName = YooAssetSettingsData.GetPackageVersionFileName(buildParameters.PackageName);
string fileName = YooAssetConfiguration.GetPackageVersionFileName(buildParameters.PackageName);
string filePath = $"{packageOutputDirectory}/{fileName}";
FileUtility.WriteAllText(filePath, buildParameters.PackageVersion);
BuildLogger.Log($"Create package manifest version file: {filePath}");
@@ -106,7 +106,7 @@ namespace YooAsset.Editor
{
ManifestContext manifestContext = new ManifestContext();
byte[] bytesData = FileUtility.ReadAllBytes(packagePath);
manifestContext.Manifest = PackageManifestTools.DeserializeManifestFromBinary(bytesData, buildParameters.ManifestDecryptor);
manifestContext.Manifest = PackageManifestHelper.DeserializeManifestFromBinary(bytesData, buildParameters.ManifestDecryptor);
context.SetContextObject(manifestContext);
}
}
@@ -154,7 +154,7 @@ namespace YooAsset.Editor
packageAsset.AssetPath = assetInfo.AssetInfo.AssetPath;
packageAsset.AssetGUID = buildMapContext.Command.IncludeAssetGUID ? assetInfo.AssetInfo.AssetGUID : string.Empty;
packageAsset.AssetTags = assetInfo.AssetTags.ToArray();
packageAsset.TempDataInEditor = assetInfo;
packageAsset.EditorUserData = assetInfo;
result.Add(packageAsset);
}
}
@@ -196,7 +196,7 @@ namespace YooAsset.Editor
// 记录资源对象所属的资源包ID
foreach (var packageAsset in manifest.AssetList)
{
var assetInfo = packageAsset.TempDataInEditor as BuildAssetInfo;
var assetInfo = packageAsset.EditorUserData as BuildAssetInfo;
packageAsset.BundleID = GetCachedBundleIndexID(assetInfo.BundleName);
}
@@ -204,7 +204,7 @@ namespace YooAsset.Editor
// 注意:依赖关系非引擎构建结果里查询!
foreach (var packageAsset in manifest.AssetList)
{
var mainAssetInfo = packageAsset.TempDataInEditor as BuildAssetInfo;
var mainAssetInfo = packageAsset.EditorUserData as BuildAssetInfo;
packageAsset.DependentBundleIDs = GetAssetDependBundleIDs(mainAssetInfo);
}
}
@@ -353,7 +353,7 @@ namespace YooAsset.Editor
var builtinPackageBundle = manifest.BundleList[builtinBundleID];
// 更新依赖资源包ID集合
HashSet<int> cacheBundleIDs = new HashSet<int>(builtinPackageBundle.ReferenceBundleIDs);
HashSet<int> cacheBundleIDs = new HashSet<int>(builtinPackageBundle.ReferrerBundleIDs);
HashSet<string> tempTags = new HashSet<string>();
foreach (var packageAsset in manifest.AssetList)
{

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -104,7 +104,7 @@ namespace YooAsset.Editor
reportBundleInfo.BundleName = packageBundle.BundleName;
reportBundleInfo.FileName = packageBundle.FileName;
reportBundleInfo.FileHash = packageBundle.FileHash;
reportBundleInfo.FileCRC = packageBundle.FileCRC;
reportBundleInfo.FileCRC = packageBundle.FileCrc;
reportBundleInfo.FileSize = packageBundle.FileSize;
reportBundleInfo.Encrypted = packageBundle.IsEncrypted;
reportBundleInfo.Tags = packageBundle.Tags;
@@ -118,7 +118,7 @@ namespace YooAsset.Editor
buildReport.IndependAssets = new List<ReportIndependAsset>(buildMapContext.IndependAssets);
// 序列化文件
string fileName = YooAssetSettingsData.GetBuildReportFileName(buildParameters.PackageName, buildParameters.PackageVersion);
string fileName = YooAssetConfiguration.GetBuildReportFileName(buildParameters.PackageName, buildParameters.PackageVersion);
string filePath = $"{packageOutputDirectory}/{fileName}";
BuildReport.Serialize(filePath, buildReport);
BuildLogger.Log($"Create build report file: {filePath}");
@@ -175,8 +175,8 @@ namespace YooAsset.Editor
/// </summary>
private List<string> GetBundleReferenceBundles(PackageManifest manifest, PackageBundle packageBundle)
{
List<string> referenceBundles = new List<string>(packageBundle.ReferenceBundleIDs.Count);
foreach (int index in packageBundle.ReferenceBundleIDs)
List<string> referenceBundles = new List<string>(packageBundle.ReferrerBundleIDs.Count);
foreach (int index in packageBundle.ReferrerBundleIDs)
{
string dependBundleName = manifest.BundleList[index].BundleName;
referenceBundles.Add(dependBundleName);

View File

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

View File

@@ -48,7 +48,7 @@ namespace YooAsset.Editor
protected override uint GetBundleFileCRC(BuildBundleInfo bundleInfo, BuildContext context)
{
string filePath = bundleInfo.PackageSourceFilePath;
return HashUtility.ComputeFileCRC32AsUInt(filePath);
return HashUtility.ComputeFileCrc32AsUInt(filePath);
}
protected override long GetBundleFileSize(BuildBundleInfo bundleInfo, BuildContext context)
{

View File

@@ -1,4 +1,4 @@

using System.Text;
using System;
@@ -36,7 +36,7 @@ namespace YooAsset.Editor
private string GetFilePathTempHash(string filePath)
{
byte[] bytes = Encoding.UTF8.GetBytes(filePath);
return HashUtility.ComputeBytesMD5(bytes);
return HashUtility.ComputeMD5(bytes);
// 注意:在文件路径的哈希值冲突的情况下,可以使用下面的方法
//return $"{HashUtility.BytesMD5(bytes)}-{Guid.NewGuid():N}";

View File

@@ -50,7 +50,7 @@ namespace YooAsset.Editor
protected override uint GetBundleFileCRC(BuildBundleInfo bundleInfo, BuildContext context)
{
string filePath = bundleInfo.PackageSourceFilePath;
return HashUtility.ComputeFileCRC32AsUInt(filePath);
return HashUtility.ComputeFileCrc32AsUInt(filePath);
}
protected override long GetBundleFileSize(BuildBundleInfo bundleInfo, BuildContext context)
{

View File

@@ -48,7 +48,7 @@ namespace YooAsset.Editor
protected override uint GetBundleFileCRC(BuildBundleInfo bundleInfo, BuildContext context)
{
string filePath = bundleInfo.PackageSourceFilePath;
return HashUtility.ComputeFileCRC32AsUInt(filePath);
return HashUtility.ComputeFileCrc32AsUInt(filePath);
}
protected override long GetBundleFileSize(BuildBundleInfo bundleInfo, BuildContext context)
{

View File

@@ -48,7 +48,7 @@ namespace YooAsset.Editor
if (File.Exists(_logFilePath))
File.Delete(_logFilePath);
FileUtility.EnsureFileDirectory(_logFilePath);
FileUtility.EnsureParentDirectoryExists(_logFilePath);
File.WriteAllText(_logFilePath, _logBuilder.ToString(), Encoding.UTF8);
_logBuilder.Clear();
}

View File

@@ -104,13 +104,13 @@ namespace YooAsset.Editor
_providerTableView.AddColumn(column);
}
// SpawnScene
// SceneName
{
var columnStyle = new ColumnStyle(150);
columnStyle.Stretchable = false;
columnStyle.Searchable = false;
columnStyle.Sortable = true;
var column = new TableColumn("SpawnScene", "Spawn Scene", columnStyle);
var column = new TableColumn("SceneName", "Scene Name", columnStyle);
column.MakeCell = () =>
{
var label = new Label();
@@ -313,7 +313,7 @@ namespace YooAsset.Editor
rowData.ProviderInfo = providerInfo;
rowData.AddAssetPathCell("PackageName", packageData.PackageName);
rowData.AddStringValueCell("AssetPath", providerInfo.AssetPath);
rowData.AddStringValueCell("SpawnScene", providerInfo.SpawnScene);
rowData.AddStringValueCell("SceneName", providerInfo.SceneName);
rowData.AddStringValueCell("StartTime", providerInfo.StartTime);
rowData.AddLongValueCell("LoadingTime", providerInfo.ElapsedMilliseconds);
rowData.AddLongValueCell("RefCount", providerInfo.ReferenceCount);

View File

@@ -187,13 +187,13 @@ namespace YooAsset.Editor
_usingTableView.AddColumn(column);
}
// SpawnScene
// SceneName
{
var columnStyle = new ColumnStyle(150);
columnStyle.Stretchable = false;
columnStyle.Searchable = false;
columnStyle.Sortable = true;
var column = new TableColumn("SpawnScene", "Spawn Scene", columnStyle);
var column = new TableColumn("SceneName", "Scene Name", columnStyle);
column.MakeCell = () =>
{
var label = new Label();
@@ -450,7 +450,7 @@ namespace YooAsset.Editor
var rowData = new UsingTableData();
rowData.ProviderInfo = providerInfo;
rowData.AddStringValueCell("UsingAssets", providerInfo.AssetPath);
rowData.AddStringValueCell("SpawnScene", providerInfo.SpawnScene);
rowData.AddStringValueCell("SceneName", providerInfo.SceneName);
rowData.AddStringValueCell("StartTime", providerInfo.StartTime);
rowData.AddLongValueCell("RefCount", providerInfo.ReferenceCount);
rowData.AddStringValueCell("Status", providerInfo.Status);

View File

@@ -322,7 +322,7 @@ namespace YooAsset.Editor
rowData.AddStringValueCell("StartTime", operationInfo.StartTime);
rowData.AddLongValueCell("ElapsedMS", operationInfo.ElapsedMilliseconds);
rowData.AddStringValueCell("Status", operationInfo.Status.ToString());
rowData.AddStringValueCell("Desc", operationInfo.OperationDesc);
rowData.AddStringValueCell("Desc", operationInfo.OperationDescription);
_sourceDatas.Add(rowData);
}
}
@@ -492,7 +492,7 @@ namespace YooAsset.Editor
// Desc
{
var label = container.Q<Label>("Desc");
label.text = operationInfo.OperationDesc;
label.text = operationInfo.OperationDescription;
}
}
private void FillTreeData(DiagnosticOperationInfo parentOperation, TreeNode rootNode)

View File

@@ -2,25 +2,21 @@ using System;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace YooAsset
{
/// <summary>
/// 异步操作基类,所有异步操作的抽象基类
/// 支持协程IEnumerator、Taskasync/await、回调等多种异步编程模式
/// 异步操作基类
/// </summary>
public abstract class AsyncOperationBase : IEnumerator, IComparable<AsyncOperationBase>
{
private List<AsyncOperationBase> _children;
private Action<AsyncOperationBase> _onCompleted;
private Action<AsyncOperationBase> _completedCallback;
private List<Action<AsyncOperationBase>> _completedCallbackList;
private EOperationStatus _status = EOperationStatus.None;
private string _error;
private uint _priority;
/// <summary>
/// 是否正处于同步等待状态
/// </summary>
internal bool IsWaitForCompletion { get; private set; }
/// <summary>
/// 标记脏(用于调度器检测并重排)
/// </summary>
@@ -29,12 +25,20 @@ namespace YooAsset
/// <summary>
/// 任务是否已结束已触发回调和Task完成
/// </summary>
internal bool IsFinished { get; private set; }
internal bool IsCompleted { get; private set; }
/// <summary>
/// 当前帧时间切片是否已用完同步等待时始终返回false
/// 是否正处于同步等待状态
/// </summary>
internal bool IsBusy
internal bool IsWaitForCompletion { get; private set; }
/// <summary>
/// 判断当前帧时间切片是否已用完
/// </summary>
/// <remarks>
/// 同步等待时始终返回 false以确保操作能持续执行直到完成
/// </remarks>
protected bool IsBusy
{
get
{
@@ -44,11 +48,16 @@ namespace YooAsset
}
}
/// <summary>
/// 任务优先级(值越大越优先执行)
/// </summary>
public uint Priority
{
get
{
return _priority;
}
set
{
if (_priority == value)
@@ -56,29 +65,15 @@ namespace YooAsset
_priority = value;
IsDirty = true;
}
get
{
return _priority;
}
}
/// <summary>
/// 任务状态
/// </summary>
public EOperationStatus Status { get; protected set; } = EOperationStatus.None;
/// <summary>
/// 错误信息
/// </summary>
public string Error { get; protected set; }
/// <summary>
/// 处理进度
/// 异步操作的处理进度0f - 1f
/// </summary>
public float Progress { get; protected set; }
/// <summary>
/// 任务逻辑是否完成Status为Succeeded、Failed
/// 判断异步操作是否已结束
/// </summary>
public bool IsDone
{
@@ -89,148 +84,106 @@ namespace YooAsset
}
/// <summary>
/// 完成事件
/// 操作失败时的错误描述
/// </summary>
public string Error
{
get { return _error; }
}
/// <summary>
/// 异步操作的当前状态
/// </summary>
public EOperationStatus Status
{
get { return _status; }
}
/// <summary>
/// 异步操作的完成事件
/// </summary>
/// <remarks>
/// 若注册时操作已完成,回调将立即执行
/// </remarks>
public event Action<AsyncOperationBase> Completed
{
add
{
if (value == null)
return;
if (IsDone)
{
try
{
// 注意:任务已完成,立即调用回调
value.Invoke(this);
if (value != null)
value.Invoke(this);
}
catch (Exception ex)
{
YooLogger.Error($"Exception in completion callback: {ex}");
YooLogger.Error($"Exception in completion callback: {ex}.");
}
}
else
{
_onCompleted += value;
if (_completedCallback == null)
{
_completedCallback = value;
}
else
{
if (_completedCallbackList == null)
_completedCallbackList = new List<Action<AsyncOperationBase>>(4);
_completedCallbackList.Add(value);
}
}
}
remove
{
_onCompleted -= value;
}
}
if (value == null)
return;
/// <summary>
/// 用于 async/await 的 Task 对象
/// </summary>
public Task Task
{
get
{
if (_taskCompletionSource == null)
if (_completedCallback == value)
{
_taskCompletionSource = new TaskCompletionSource<object>();
if (IsDone)
_taskCompletionSource.SetResult(null);
_completedCallback = null;
}
else if (_completedCallbackList != null)
{
_completedCallbackList.Remove(value);
}
return _taskCompletionSource.Task;
}
}
/// <summary>
/// 内部启动方法(子类必须实现)
/// 同步等待异步执行完毕
/// </summary>
internal abstract void InternalStart();
/// <summary>
/// 内部更新方法(子类必须实现)
/// </summary>
internal abstract void InternalUpdate();
/// <summary>
/// 内部中止方法(子类可选实现)
/// </summary>
internal virtual void InternalAbort()
/// <remarks>
/// 会在当前帧循环执行直到操作完成
/// </remarks>
public void WaitForCompletion()
{
}
// 注意:防止异步操作被挂起陷入无限死循环
if (_status == EOperationStatus.None)
{
StartOperation();
}
/// <summary>
/// 内部释放方法(子类可选实现)
/// </summary>
internal virtual void InternalDispose()
{
}
if (IsWaitForCompletion == false)
{
IsWaitForCompletion = true;
/// <summary>
/// 获取操作的描述信息(子类可选实现)
/// </summary>
internal virtual string InternalGetDescription()
{
return string.Empty;
}
if (IsDone == false)
InternalWaitForCompletion();
/// <summary>
/// 内部同步等待方法(子类可选实现)
/// 默认抛出异常,如果异步操作需要支持,子类应重写以支持同步等待
/// </summary>
internal virtual void InternalWaitForCompletion()
{
throw new YooInternalException($"InternalWaitForCompletion() is not implemented: {this.GetType().Name}");
}
if (IsDone == false)
{
_error = $"Operation '{GetType().Name}' did not complete during synchronous wait.";
_status = EOperationStatus.Failed;
YooLogger.Error(_error);
}
/// <summary>
/// 添加子任务
/// </summary>
internal void AddChildOperation(AsyncOperationBase child)
{
if (_children == null)
_children = new List<AsyncOperationBase>(10);
#if UNITY_EDITOR || DEBUG
if (child == null)
throw new YooInternalException("Child operation is null.");
if (ReferenceEquals(child, this))
throw new YooInternalException("Cannot add operation as its own child.");
if (_children.Contains(child))
throw new YooInternalException($"Child operation {child.GetType().Name} already exists.");
// 禁止形成环依赖
if (WouldCreateCycle(child))
throw new YooInternalException($"Adding {child.GetType().Name} would create a circular dependency with {this.GetType().Name}.");
#endif
_children.Add(child);
}
/// <summary>
/// 移除子任务
/// </summary>
internal void RemoveChildOperation(AsyncOperationBase child)
{
if (_children == null)
return;
#if UNITY_EDITOR || DEBUG
if (child == null)
throw new YooInternalException("Child operation is null.");
if (_children.Contains(child) == false)
throw new YooInternalException($"Child operation {child.GetType().Name} does not exist.");
#endif
_children.Remove(child);
}
/// <summary>
/// 获取异步操作说明
/// </summary>
internal string GetOperationDescription()
{
return InternalGetDescription();
// 注意强制收尾确保Task能完成
CompleteOperation();
}
}
/// <summary>
@@ -238,9 +191,9 @@ namespace YooAsset
/// </summary>
internal void StartOperation()
{
if (Status == EOperationStatus.None)
if (_status == EOperationStatus.None)
{
Status = EOperationStatus.Processing;
_status = EOperationStatus.Processing;
// 开始记录
DebugBeginRecording();
@@ -252,9 +205,17 @@ namespace YooAsset
}
catch (Exception ex)
{
Status = EOperationStatus.Failed;
Error = ex.ToString();
YooLogger.Error($"Exception in {this.GetType().Name}.InternalStart: {ex}");
// 注意:无论子类是否已调用 SetResult/SetError
// 内部逻辑抛出异常一律视为该异步任务失败。
_error = ex.ToString();
_status = EOperationStatus.Failed;
YooLogger.Error($"Exception in {GetType().Name}.InternalStart: {ex}.");
}
// 注意:同步完成的操作立即收尾
if (IsDone)
{
CompleteOperation();
}
}
}
@@ -278,15 +239,17 @@ namespace YooAsset
}
catch (Exception ex)
{
Status = EOperationStatus.Failed;
Error = ex.ToString();
YooLogger.Error($"Exception in {this.GetType().Name}.InternalUpdate: {ex}");
// 注意:无论子类是否已调用 SetResult/SetError
// 内部逻辑抛出异常一律视为该异步任务失败。
_error = ex.ToString();
_status = EOperationStatus.Failed;
YooLogger.Error($"Exception in {GetType().Name}.InternalUpdate: {ex}.");
}
}
if (IsDone && IsFinished == false)
if (IsDone && IsCompleted == false)
{
FinishOperation();
CompleteOperation();
}
}
@@ -297,70 +260,141 @@ namespace YooAsset
{
if (_children != null)
{
foreach (var child in _children)
for (int i = _children.Count - 1; i >= 0; i--)
{
child.AbortOperation();
_children[i].AbortOperation();
}
}
if (IsDone == false)
{
InternalAbort();
Status = EOperationStatus.Failed;
Error = "Aborted by user";
YooLogger.Warning($"Async operation {this.GetType().Name} has been aborted.");
_error = "Operation was aborted.";
_status = EOperationStatus.Failed;
YooLogger.Warning($"Async operation '{GetType().Name}' has been aborted.");
}
// 注意强制收尾确保Task能完成
FinishOperation();
CompleteOperation();
}
/// <summary>
/// 内部启动方法(子类必须实现)
/// </summary>
protected abstract void InternalStart();
/// <summary>
/// 内部更新方法(子类必须实现)
/// </summary>
protected abstract void InternalUpdate();
/// <summary>
/// 内部中止方法(子类可选实现)
/// </summary>
protected virtual void InternalAbort()
{
}
/// <summary>
/// 完成异步任务触发回调和Task完成
/// 内部释放方法(子类可选实现
/// </summary>
private void FinishOperation()
protected virtual void InternalDispose()
{
if (IsFinished == false)
{
IsFinished = true;
Progress = 1f;
// 结束记录
DebugEndRecording();
try
{
InternalDispose();
}
catch (Exception ex)
{
YooLogger.Error($"Exception in {this.GetType().Name}.InternalDispose: {ex}");
}
if (_onCompleted != null)
{
var invocations = _onCompleted.GetInvocationList();
InvokeSafely(invocations);
_onCompleted = null;
}
if (_taskCompletionSource != null)
_taskCompletionSource.TrySetResult(null);
}
}
private void InvokeSafely(Delegate[] invocations)
/// <summary>
/// 获取操作的描述信息(子类可选实现)
/// </summary>
/// <returns>操作的描述字符串,默认返回空字符串。</returns>
protected virtual string InternalGetDescription()
{
foreach (var handler in invocations)
{
try
{
((Action<AsyncOperationBase>)handler).Invoke(this);
}
catch (Exception ex)
{
YooLogger.Error($"Exception in invoke callback: {ex}");
}
}
return string.Empty;
}
/// <summary>
/// 内部同步等待方法(子类可选实现)
/// </summary>
/// <remarks>
/// 默认抛出异常,子类应重写以支持同步等待
/// </remarks>
protected virtual void InternalWaitForCompletion()
{
throw new YooInternalException($"{GetType().Name} does not override InternalWaitForCompletion.");
}
/// <summary>
/// 将操作标记为成功完成
/// </summary>
/// <exception cref="InvalidOperationException">操作已处于终结状态时抛出</exception>
protected void SetResult()
{
if (IsDone)
throw new InvalidOperationException(
$"Operation '{GetType().Name}' has already completed and cannot transition to another final state.");
_status = EOperationStatus.Succeeded;
}
/// <summary>
/// 将操作标记为失败
/// </summary>
/// <param name="error">错误描述</param>
/// <exception cref="InvalidOperationException">操作已处于终结状态时抛出</exception>
protected void SetError(string error)
{
if (IsDone)
throw new InvalidOperationException(
$"Operation '{GetType().Name}' has already completed and cannot transition to another final state.");
_error = error;
_status = EOperationStatus.Failed;
}
/// <summary>
/// 添加子任务
/// </summary>
/// <param name="child">要添加的子任务</param>
protected void AddChildOperation(AsyncOperationBase child)
{
if (_children == null)
_children = new List<AsyncOperationBase>(10);
#if UNITY_EDITOR || DEBUG
if (child == null)
throw new YooInternalException("Child operation is null.");
if (ReferenceEquals(child, this))
throw new YooInternalException("Cannot add operation as its own child.");
if (_children.Contains(child))
throw new YooInternalException($"Child operation '{child.GetType().Name}' already exists.");
// 禁止形成环依赖
if (WouldCreateCycle(child))
throw new YooInternalException($"Adding '{child.GetType().Name}' would create a circular dependency with '{GetType().Name}'.");
#endif
_children.Add(child);
}
/// <summary>
/// 移除子任务
/// </summary>
/// <param name="child">要移除的子任务</param>
protected void RemoveChildOperation(AsyncOperationBase child)
{
if (_children == null)
return;
#if UNITY_EDITOR || DEBUG
if (child == null)
throw new YooInternalException("Child operation is null.");
if (_children.Contains(child) == false)
throw new YooInternalException($"Child operation '{child.GetType().Name}' was not found.");
#endif
_children.Remove(child);
}
/// <summary>
@@ -379,7 +413,7 @@ namespace YooAsset
/// </summary>
/// <param name="count">最大执行次数默认1000次</param>
/// <remarks>
/// 用于需要快速完成但又不想完全阻塞主线程的场景
/// 用于需要快速完成但又不想完全阻塞主线程的场景
/// </remarks>
protected void ExecuteBatch(int count = 1000)
{
@@ -389,12 +423,10 @@ namespace YooAsset
int runCount = count;
while (true)
{
// 执行更新逻辑
UpdateOperation();
if (IsDone)
break;
// 当执行次数用完时
runCount--;
if (runCount <= 0)
break;
@@ -402,11 +434,13 @@ namespace YooAsset
}
/// <summary>
/// 无限次数的执行更新逻辑直到任务完成
/// 注意:该方法会阻塞主线程
/// 循环执行更新逻辑直到操作完成
/// </summary>
/// <param name="sleepMS">休眠时长</param>
protected void ExecuteUntilComplete(int sleepMS = 1)
/// <param name="sleepMilliseconds">每次循环后的休眠时长(毫秒)</param>
/// <remarks>
/// 该方法会阻塞调用线程,每次更新之间会短暂休眠以避免占满 CPU
/// </remarks>
protected void ExecuteUntilComplete(int sleepMilliseconds = 1)
{
if (IsDone)
return;
@@ -418,50 +452,81 @@ namespace YooAsset
break;
// 注意短暂休眠避免完全占用CPU资源
System.Threading.Thread.Sleep(sleepMS);
System.Threading.Thread.Sleep(sleepMilliseconds);
}
}
/// <summary>
/// 同步等待异步执行完毕(会阻塞当前线程
/// 完成异步任务触发回调和Task完成
/// </summary>
public void WaitForCompletion()
private void CompleteOperation()
{
// 注意:防止异步操作被挂起陷入无限死循环
if (Status == EOperationStatus.None)
if (IsCompleted == false)
{
StartOperation();
}
IsCompleted = true;
Progress = 1f;
if (IsWaitForCompletion == false)
{
IsWaitForCompletion = true;
// 结束记录
DebugEndRecording();
if (IsDone == false)
InternalWaitForCompletion();
if (IsDone == false)
try
{
Status = EOperationStatus.Failed;
Error = $"Operation {this.GetType().Name} failed to wait for completion.";
YooLogger.Error(Error);
InternalDispose();
}
catch (Exception ex)
{
YooLogger.Error($"Exception in {GetType().Name}.InternalDispose: {ex}.");
}
// 注意强制收尾确保Task能完成
FinishOperation();
InvokeCompletedCallbacks();
}
}
/// <summary>
/// 触发所有已注册的完成回调并清空
/// </summary>
private void InvokeCompletedCallbacks()
{
if (_completedCallback != null)
{
try
{
_completedCallback.Invoke(this);
}
catch (Exception ex)
{
YooLogger.Error($"Exception in completion callback: {ex}.");
}
_completedCallback = null;
}
if (_completedCallbackList != null)
{
for (int i = 0; i < _completedCallbackList.Count; i++)
{
try
{
_completedCallbackList[i].Invoke(this);
}
catch (Exception ex)
{
YooLogger.Error($"Exception in completion callback: {ex}.");
}
}
_completedCallbackList = null;
}
}
#region
/// <summary>
/// 任务开始时间格式HH:MM:SS仅DEBUG模式有效
/// 异步操作的开始时间格式HH:MM:SS
/// </summary>
public string StartTime { get; protected set; }
internal string StartTime { get; private set; }
/// <summary>
/// 处理耗时(单位:毫秒)
/// </summary>
public long ElapsedMilliseconds { get; protected set; }
internal long ElapsedMilliseconds { get; private set; }
/// <summary>
/// 任务耗时计时器
@@ -511,8 +576,10 @@ namespace YooAsset
/// <summary>
/// 检测添加子任务是否会形成循环依赖
/// 使用深度优先搜索DFS遍历子任务图
/// </summary>
/// <remarks>
/// 使用深度优先搜索遍历子任务图
/// </remarks>
private bool WouldCreateCycle(AsyncOperationBase child)
{
const int MaxCycleCheckDepth = 4096; // 循环检测最大深度
@@ -553,13 +620,15 @@ namespace YooAsset
/// <summary>
/// 获取调试信息
/// 注意:递归构建子树存在深度风险
/// </summary>
/// <remarks>
/// 递归构建子树存在深度风险
/// </remarks>
internal DiagnosticOperationInfo GetDiagnosticInfo()
{
var operationInfo = new DiagnosticOperationInfo();
operationInfo.OperationName = this.GetType().Name;
operationInfo.OperationDesc = GetOperationDescription();
operationInfo.OperationDescription = InternalGetDescription();
operationInfo.Priority = Priority;
operationInfo.Progress = Progress;
operationInfo.StartTime = StartTime;
@@ -585,6 +654,7 @@ namespace YooAsset
#endregion
#region
/// <inheritdoc />
public int CompareTo(AsyncOperationBase other)
{
return other.Priority.CompareTo(this.Priority);
@@ -593,17 +663,24 @@ namespace YooAsset
#region
/// <summary>
/// 用于支持 async/await 的任务完成源
/// 获取用于 async/await 的等待器
/// </summary>
private TaskCompletionSource<object> _taskCompletionSource;
/// <returns>当前操作的等待器</returns>
public OperationAwaiter GetAwaiter()
{
return new OperationAwaiter(this);
}
/// <inheritdoc />
bool IEnumerator.MoveNext()
{
return !IsDone;
}
/// <inheritdoc />
void IEnumerator.Reset()
{
}
/// <inheritdoc />
object IEnumerator.Current => null;
#endregion
}

View File

@@ -44,6 +44,11 @@ namespace YooAsset
public int CreationOrder { get; private set; }
/// <summary>
/// 创建异步操作调度器实例
/// </summary>
/// <param name="packageName">所属包裹的名称。</param>
/// <param name="creationOrder">创建顺序,用于同优先级时的稳定排序。</param>
public AsyncOperationScheduler(string packageName, int creationOrder)
{
PackageName = packageName;
@@ -53,12 +58,15 @@ namespace YooAsset
/// <summary>
/// 开始处理异步操作
/// </summary>
/// <param name="operation">要启动的异步操作</param>
/// <remarks>
/// 操作立即启动,但会先添加到待处理队列
/// 在下一次Update时才会合并到执行队列并参与调度更新
/// 操作立即启动,下一次 Update 时合并到执行队列
/// </remarks>
public void StartOperation(AsyncOperationBase operation)
{
if (operation == null)
throw new System.ArgumentNullException(nameof(operation));
_pendingOperations.Add(operation);
operation.StartOperation();
}
@@ -72,7 +80,7 @@ namespace YooAsset
for (int i = _runningOperations.Count - 1; i >= 0; i--)
{
var operation = _runningOperations[i];
if (operation.IsFinished)
if (operation.IsCompleted)
{
_runningOperations.RemoveAt(i);
}
@@ -108,7 +116,7 @@ namespace YooAsset
break;
var operation = _runningOperations[i];
if (operation.IsFinished)
if (operation.IsCompleted)
continue;
operation.UpdateOperation();
@@ -138,6 +146,7 @@ namespace YooAsset
/// <summary>
/// 获取调试信息
/// </summary>
/// <returns>包含所有运行中和待处理操作的诊断信息列表。</returns>
public List<DiagnosticOperationInfo> GetDiagnosticInfos()
{
int totalCount = _runningOperations.Count + _pendingOperations.Count;
@@ -161,6 +170,7 @@ namespace YooAsset
}
#region
/// <inheritdoc />
public int CompareTo(AsyncOperationScheduler other)
{
// 优先级高的排前面

View File

@@ -6,8 +6,7 @@ using System.Diagnostics;
namespace YooAsset
{
/// <summary>
/// 异步操作系统(静态调度器
/// 负责管理所有包裹的调度器,提供时间切片执行机制
/// 异步操作系统,负责管理所有包裹的调度器
/// </summary>
internal static class AsyncOperationSystem
{
@@ -15,11 +14,15 @@ namespace YooAsset
[UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)]
private static void OnRuntimeInitialize()
{
DestroyAll();
Shutdown();
}
#endif
public const string GlobalSchedulerName = "YOOASSET_GLOBAL_SCHEDULER"; // 全局调度器名称
/// <summary>
/// 全局调度器名称
/// </summary>
public const string GlobalSchedulerName = "YOOASSET_GLOBAL_SCHEDULER";
private const long MinTimeSlice = 10; // 最小时间片(毫秒)
private static readonly Dictionary<string, AsyncOperationScheduler> _schedulerDict = new Dictionary<string, AsyncOperationScheduler>(100);
@@ -33,8 +36,12 @@ namespace YooAsset
private static long _maxTimeSlice = long.MaxValue;
/// <summary>
/// 异步操作系统的每帧最大执行预算(毫秒)
/// 每帧最大执行预算(毫秒)
/// </summary>
/// <value>最小值为 <see cref="MinTimeSlice"/> 毫秒,低于此值将被自动钳制。</value>
/// <remarks>
/// 设置过小会导致每帧可执行的操作极少,影响整体加载速度
/// </remarks>
public static long MaxTimeSlice
{
get
@@ -46,7 +53,7 @@ namespace YooAsset
if (value < MinTimeSlice)
{
_maxTimeSlice = MinTimeSlice;
YooLogger.Warning($"MaxTimeSlice minimum value is {MinTimeSlice} milliseconds.");
YooLogger.Warning($"MaxTimeSlice must be at least {MinTimeSlice} ms, clamped to {MinTimeSlice}.");
}
else
{
@@ -56,7 +63,7 @@ namespace YooAsset
}
/// <summary>
/// 异步操作系统是否繁忙
/// 判断当前帧的时间切片预算是否已用完
/// </summary>
public static bool IsBusy
{
@@ -129,9 +136,9 @@ namespace YooAsset
}
/// <summary>
/// 销毁异步操作系统
/// 关闭异步操作系统
/// </summary>
public static void DestroyAll()
public static void Shutdown()
{
_isInitialized = false;
@@ -150,15 +157,18 @@ namespace YooAsset
}
/// <summary>
/// 创建包裹调度器
/// 创建指定包裹调度器
/// </summary>
/// <param name="packageName">包裹名称</param>
/// <param name="priority">初始优先级</param>
/// <returns>新创建的调度器实例</returns>
public static AsyncOperationScheduler CreatePackageScheduler(string packageName, uint priority)
{
DebugCheckInitialized(packageName);
if (_schedulerDict.ContainsKey(packageName))
{
throw new YooInternalException($"Package scheduler already exists: {packageName}");
throw new YooInternalException($"Package scheduler already exists: '{packageName}'.");
}
var scheduler = new AsyncOperationScheduler(packageName, _nextCreationOrder++);
@@ -169,8 +179,9 @@ namespace YooAsset
}
/// <summary>
/// 销毁包裹调度器
/// 销毁指定包裹调度器
/// </summary>
/// <param name="packageName">包裹名称</param>
public static void DestroyPackageScheduler(string packageName)
{
DebugCheckInitialized(packageName);
@@ -190,8 +201,9 @@ namespace YooAsset
}
/// <summary>
/// 清空并中止包裹所有任务
/// 中止并清空指定包裹所有正在执行的操作
/// </summary>
/// <param name="packageName">包裹名称</param>
public static void ClearPackageOperations(string packageName)
{
DebugCheckInitialized(packageName);
@@ -201,19 +213,26 @@ namespace YooAsset
}
/// <summary>
/// 开始处理异步操作类
/// 将异步操作提交到指定包裹的调度器中执行
/// </summary>
/// <param name="packageName">包裹名称</param>
/// <param name="operation">要执行的异步操作</param>
public static void StartOperation(string packageName, AsyncOperationBase operation)
{
DebugCheckInitialized(packageName);
if (operation == null)
throw new System.ArgumentNullException(nameof(operation));
var scheduler = GetScheduler(packageName);
scheduler.StartOperation(operation);
}
/// <summary>
/// 设置调度器优先级
/// 设置指定包裹调度器优先级
/// </summary>
/// <param name="packageName">包裹名称</param>
/// <param name="priority">优先级,值越大越优</param>
public static void SetSchedulerPriority(string packageName, uint priority)
{
DebugCheckInitialized(packageName);
@@ -223,8 +242,10 @@ namespace YooAsset
}
/// <summary>
/// 获取调度器优先级
/// 获取指定包裹调度器优先级
/// </summary>
/// <param name="packageName">包裹名称</param>
/// <returns>调度器的当前优先级。</returns>
public static uint GetSchedulerPriority(string packageName)
{
DebugCheckInitialized(packageName);
@@ -244,10 +265,15 @@ namespace YooAsset
}
// 严格模式:非默认包裹必须先创建调度器
throw new YooInternalException($"Operation scheduler not found: {packageName}.");
throw new YooInternalException($"Operation scheduler not found: '{packageName}'.");
}
#region
/// <summary>
/// 获取指定包裹中所有操作的诊断信息
/// </summary>
/// <param name="packageName">包裹名称。</param>
/// <returns>该包裹下所有操作的诊断信息列表。</returns>
internal static List<DiagnosticOperationInfo> GetDiagnosticInfos(string packageName)
{
DebugCheckInitialized(packageName);

View File

@@ -0,0 +1,48 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace YooAsset
{
/// <summary>
/// 支持异步编程的自定义 Awaiter
/// </summary>
public readonly struct OperationAwaiter : ICriticalNotifyCompletion
{
private readonly AsyncOperationBase _operation;
/// <summary>
/// 创建操作等待器实例
/// </summary>
/// <param name="operation">要等待的异步操作</param>
public OperationAwaiter(AsyncOperationBase operation)
{
_operation = operation;
}
/// <inheritdoc />
public bool IsCompleted => _operation.IsDone;
/// <summary>
/// 获取操作结果
/// </summary>
/// <remarks>
/// 业务失败不视为异常,此处不抛出异常
/// </remarks>
public void GetResult()
{
}
/// <inheritdoc />
public void OnCompleted(Action continuation)
{
UnsafeOnCompleted(continuation);
}
/// <inheritdoc />
public void UnsafeOnCompleted(Action continuation)
{
_operation.Completed += (op) => continuation();
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 2930435fc2ba91c4ba511260b9d119d3
guid: 9b47580c887846a49a5e3442b4ca622b
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -25,21 +25,35 @@ namespace YooAsset
get { return Error == null; }
}
/// <summary>
/// 创建失败结果
/// </summary>
public EvictionResult(string error)
{
Error = error;
BundleGUIDs = null;
}
/// <summary>
/// 创建成功结果
/// </summary>
public EvictionResult(List<string> bundleGUIDs)
{
Error = null;
BundleGUIDs = bundleGUIDs;
}
/// <summary>
/// 创建成功结果
/// </summary>
public static EvictionResult Success(List<string> bundleGUIDs)
{
return new EvictionResult(bundleGUIDs);
}
/// <summary>
/// 创建失败结果
/// </summary>
public static EvictionResult Failure(string error)
{
return new EvictionResult(error);

View File

@@ -23,19 +23,18 @@ namespace YooAsset
{
_error = error;
}
internal override void InternalStart()
protected override void InternalStart()
{
if (string.IsNullOrEmpty(_error))
{
Status = EOperationStatus.Succeeded;
SetResult();
}
else
{
Status = EOperationStatus.Failed;
Error = _error;
SetError(_error);
}
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
}
}

View File

@@ -7,14 +7,14 @@ namespace YooAsset
internal struct BCClearCacheOptions
{
/// <summary>
/// 清理
/// 清理
/// </summary>
public string ClearMode { get; set; }
public string ClearMethod { get; set; }
/// <summary>
/// 附加参数
/// </summary>
public object ClearParam { get; set; }
public object ClearParameter { get; set; }
/// <summary>
/// 资源清单

View File

@@ -53,12 +53,11 @@ namespace YooAsset
{
_error = error;
}
internal override void InternalStart()
protected override void InternalStart()
{
Status = EOperationStatus.Failed;
Error = _error;
SetError(_error);
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
}
}

View File

@@ -23,19 +23,18 @@ namespace YooAsset
{
_error = error;
}
internal override void InternalStart()
protected override void InternalStart()
{
if (string.IsNullOrEmpty(_error))
{
Status = EOperationStatus.Succeeded;
SetResult();
}
else
{
Status = EOperationStatus.Failed;
Error = _error;
SetError(_error);
}
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
}
}

View File

@@ -23,19 +23,18 @@ namespace YooAsset
{
_error = error;
}
internal override void InternalStart()
protected override void InternalStart()
{
if (string.IsNullOrEmpty(_error))
{
Status = EOperationStatus.Succeeded;
SetResult();
}
else
{
Status = EOperationStatus.Failed;
Error = _error;
SetError(_error);
}
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
}
}

View File

@@ -17,7 +17,7 @@ namespace YooAsset
public string FilePath { get; set; }
/// <summary>
/// 要缓存的文件数据
/// 要缓存的文件数据(可选)
/// </summary>
public byte[] FileData { get; set; }
}

View File

@@ -32,11 +32,11 @@ namespace YooAsset
{
_options = options;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.TryLoadFileData;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -53,8 +53,7 @@ namespace YooAsset
catch (Exception ex)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Failed to read file: {ex.Message}";
SetError($"Failed to read file: {ex.Message}.");
}
}
else
@@ -69,7 +68,7 @@ namespace YooAsset
// 注意从Web服务器下载数据
if (_downloadBytesRequest == null)
{
string url = DownloadSystemTools.ToLocalFileUrl(_options.FilePath);
string url = DownloadUrlHelper.ToLocalFileUrl(_options.FilePath);
var args = new DownloadDataRequestArgs(url, 60, 0);
_downloadBytesRequest = _options.DownloadBackend.CreateBytesRequest(args);
_downloadBytesRequest.SendRequest();
@@ -86,8 +85,7 @@ namespace YooAsset
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _downloadBytesRequest.Error;
SetError(_downloadBytesRequest.Error);
}
}
@@ -95,14 +93,13 @@ namespace YooAsset
{
try
{
Catalog = BuiltinCatalogTools.DeserializeFromBinary(_fileData);
Catalog = BuiltinCatalogHelper.DeserializeFromBinary(_fileData);
_steps = ESteps.CheckResult;
}
catch (Exception ex)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Failed to load catalog: {ex.Message}";
SetError($"Failed to load catalog: {ex.Message}.");
}
}
@@ -111,17 +108,16 @@ namespace YooAsset
if (Catalog.PackageName == _options.PackageName)
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Catalog package name {Catalog.PackageName} cannot match the file cache package name {_options.PackageName}";
SetError($"Catalog package name '{Catalog.PackageName}' does not match file cache package name '{_options.PackageName}'.");
}
}
}
internal override void InternalDispose()
protected override void InternalDispose()
{
if (_downloadBytesRequest != null)
{

View File

@@ -31,11 +31,11 @@ namespace YooAsset
{
_options = options;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.LoadBundle;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -52,8 +52,7 @@ namespace YooAsset
if (decryptor == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"{_options.CacheName} decryptor is null.";
SetError($"{_options.CacheName} decryptor is null.");
return;
}
@@ -73,16 +72,14 @@ namespace YooAsset
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"{_options.CacheName} does not support {decryptor.GetType().Name}";
SetError($"{_options.CacheName} does not support '{decryptor.GetType().Name}'.");
return;
}
if (result.Succeeded == false)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = result.Error;
SetError(result.Error);
return;
}
}
@@ -112,20 +109,19 @@ namespace YooAsset
if (_assetBundle == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = "Unity engine load failed.";
SetError("Unity engine load failed.");
UnityEngineLoadFailed = true;
CleanupStream();
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
BundleHandle = new AssetBundleHandle(_options.FilePath, _options.Bundle, _assetBundle, _loadStream);
}
}
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
ExecuteBatch();
}
@@ -142,7 +138,7 @@ namespace YooAsset
var args = new BundleDecryptArgs();
args.Bundle = _options.Bundle;
args.FilePath = _options.FilePath;
uint offset = decryptor.GetFileOffset(args);
ulong offset = (ulong)decryptor.GetFileOffset(args);
if (IsWaitForCompletion)
_assetBundle = AssetBundle.LoadFromFile(_options.FilePath, 0, offset);
@@ -156,7 +152,7 @@ namespace YooAsset
var args = new BundleDecryptArgs();
args.Bundle = _options.Bundle;
args.FilePath = _options.FilePath;
var binaryData = decryptor.GetDecryptData(args);
var binaryData = decryptor.GetDecryptedData(args);
if (binaryData == null)
return LoadResult.Failure($"{_options.CacheName} decryptor returned null data.");
@@ -172,8 +168,8 @@ namespace YooAsset
var args = new BundleDecryptArgs();
args.Bundle = _options.Bundle;
args.FilePath = _options.FilePath;
uint bufferSize = decryptor.GetReadBufferSize(args);
_loadStream = decryptor.GetDecryptStream(args);
uint bufferSize = (uint)decryptor.GetBufferSize(args);
_loadStream = decryptor.CreateDecryptionStream(args);
if (_loadStream == null)
return LoadResult.Failure($"{_options.CacheName} decryptor returned null stream.");

View File

@@ -24,11 +24,11 @@ namespace YooAsset
{
_options = options;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.LoadBundle;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -37,11 +37,10 @@ namespace YooAsset
{
if (_options.Bundle.IsEncrypted == false)
{
if (FileUtility.SupportsFileIO(_options.FilePath) == false)
if (FileUtility.IsFileIOSupported(_options.FilePath) == false)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"FileIO is not supported for builtin path: {_options.FilePath}";
SetError($"FileIO is not supported for builtin path: '{_options.FilePath}'.");
return;
}
@@ -53,8 +52,7 @@ namespace YooAsset
if (decryptor == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"{_options.CacheName} decryptor is null.";
SetError($"{_options.CacheName} decryptor is null.");
return;
}
@@ -66,16 +64,14 @@ namespace YooAsset
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"{_options.CacheName} does not support {decryptor.GetType().Name}";
SetError($"{_options.CacheName} does not support '{decryptor.GetType().Name}'.");
return;
}
if (result.Succeeded == false)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = result.Error;
SetError(result.Error);
return;
}
}
@@ -88,18 +84,17 @@ namespace YooAsset
if (_rawBundle == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Loaded raw bundle is null.";
SetError($"Loaded raw bundle is null.");
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
BundleHandle = new RawBundleHandle(_options.FilePath, _options.Bundle, _rawBundle);
}
}
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
ExecuteBatch();
}
@@ -114,7 +109,7 @@ namespace YooAsset
var args = new BundleDecryptArgs();
args.Bundle = _options.Bundle;
args.FilePath = _options.FilePath;
var binaryData = decryptor.GetDecryptData(args);
var binaryData = decryptor.GetDecryptedData(args);
if (binaryData == null)
return LoadResult.Failure($"{_options.CacheName} decryptor returned null data.");

View File

@@ -33,21 +33,21 @@ namespace YooAsset
_options = options;
// 注意:网络原因失败后,重新尝试直到成功
_downloadRetryController = new DownloadRetryController(int.MaxValue, options.RetryPolicy);
_downloadRetryController = new DownloadRetryController(int.MaxValue, options.DownloadRetryPolicy);
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.BundleRequest;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.BundleRequest)
{
string url = GetRequestURL();
var args = new DownloadAssetBundleRequestArgs(url, 0, _options.WatchdogTimeout, _options.DisableUnityWebCache, _options.Bundle.FileHash, _options.Bundle.UnityCRC);
string url = GetRequestUrl();
var args = new DownloadAssetBundleRequestArgs(url, 0, _options.WatchdogTimeout, _options.DisableUnityWebCache, _options.Bundle.FileHash, _options.Bundle.UnityCrc);
_downloadAssetBundleRequest = _options.DownloadBackend.CreateAssetBundleRequest(args);
_downloadAssetBundleRequest.SendRequest();
_steps = ESteps.CheckRequest;
@@ -61,18 +61,17 @@ namespace YooAsset
if (_downloadAssetBundleRequest.Status == EDownloadRequestStatus.Succeeded)
{
_options.URLPolicy.OnRequestSucceeded(_downloadAssetBundleRequest.Url);
_options.DownloadUrlPolicy.OnRequestSucceeded(_downloadAssetBundleRequest.Url);
var assetBundle = _downloadAssetBundleRequest.Result;
if (assetBundle == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Fatal error: downloaded asset bundle is null.";
SetError($"Fatal error: downloaded asset bundle is null.");
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
BundleHandle = new AssetBundleHandle(_downloadAssetBundleRequest.Url, _options.Bundle, assetBundle, null);
}
}
@@ -81,7 +80,7 @@ namespace YooAsset
string url = _downloadAssetBundleRequest.Url;
long httpCode = _downloadAssetBundleRequest.HttpCode;
string httpError = _downloadAssetBundleRequest.HttpError;
_options.URLPolicy.OnRequestFailed(url, httpCode, httpError);
_options.DownloadUrlPolicy.OnRequestFailed(url, httpCode, httpError);
if (IsWaitForCompletion == false && _downloadRetryController.CanRetryRequest(url, httpCode, httpError))
{
_downloadRetryController.StartRetryDelay();
@@ -90,8 +89,7 @@ namespace YooAsset
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _downloadAssetBundleRequest.Error;
SetError(_downloadAssetBundleRequest.Error);
}
}
}
@@ -105,14 +103,14 @@ namespace YooAsset
_downloadAssetBundleRequest = null;
}
if (_downloadRetryController.UpdateRetryDelay())
if (_downloadRetryController.TickRetryDelay())
{
Progress = 0f;
_steps = ESteps.BundleRequest;
}
}
}
internal override void InternalDispose()
protected override void InternalDispose()
{
if (_downloadAssetBundleRequest != null)
{
@@ -121,9 +119,9 @@ namespace YooAsset
}
}
private string GetRequestURL()
private string GetRequestUrl()
{
return _options.URLPolicy.SelectUrl(_options.CandidateURLs);
return _options.DownloadUrlPolicy.SelectUrl(_options.CandidateUrls);
}
}
@@ -156,13 +154,13 @@ namespace YooAsset
_options = options;
// 注意:网络原因失败后,重新尝试直到成功
_downloadRetryController = new DownloadRetryController(int.MaxValue, options.RetryPolicy);
_downloadRetryController = new DownloadRetryController(int.MaxValue, options.DownloadRetryPolicy);
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.DataRequest;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -173,15 +171,14 @@ namespace YooAsset
if (decryptor == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"{_options.CacheName} decryptor is null.";
SetError($"{_options.CacheName} decryptor is null.");
return;
}
if (decryptor is IBundleMemoryDecryptor)
{
_decryptor = decryptor as IBundleMemoryDecryptor;
string url = GetRequestURL();
string url = GetRequestUrl();
var args = new DownloadDataRequestArgs(url, 0, _options.WatchdogTimeout);
_downloadBytesRequest = _options.DownloadBackend.CreateBytesRequest(args);
_downloadBytesRequest.SendRequest();
@@ -190,8 +187,7 @@ namespace YooAsset
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"{_options.CacheName} does not support {decryptor.GetType().Name}";
SetError($"{_options.CacheName} does not support '{decryptor.GetType().Name}'.");
return;
}
}
@@ -204,7 +200,7 @@ namespace YooAsset
if (_downloadBytesRequest.Status == EDownloadRequestStatus.Succeeded)
{
_options.URLPolicy.OnRequestSucceeded(_downloadBytesRequest.Url);
_options.DownloadUrlPolicy.OnRequestSucceeded(_downloadBytesRequest.Url);
_steps = ESteps.VerifyData;
}
else
@@ -212,7 +208,7 @@ namespace YooAsset
string url = _downloadBytesRequest.Url;
long httpCode = _downloadBytesRequest.HttpCode;
string httpError = _downloadBytesRequest.HttpError;
_options.URLPolicy.OnRequestFailed(url, httpCode, httpError);
_options.DownloadUrlPolicy.OnRequestFailed(url, httpCode, httpError);
if (IsWaitForCompletion == false && _downloadRetryController.CanRetryRequest(url, httpCode, httpError))
{
_downloadRetryController.StartRetryDelay();
@@ -221,8 +217,7 @@ namespace YooAsset
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _downloadBytesRequest.Error;
SetError(_downloadBytesRequest.Error);
}
}
}
@@ -232,9 +227,9 @@ namespace YooAsset
// 注意:网络/代理/服务器异常导致内容不完整但请求仍成功
EFileVerifyResult verifyResult;
if (_options.DownloadVerifyLevel == EFileVerifyLevel.Low || _options.DownloadVerifyLevel == EFileVerifyLevel.Middle)
verifyResult = FileVerifyTools.FileVerify(_downloadBytesRequest.Result, _options.Bundle.FileSize, 0);
verifyResult = FileVerifyHelper.VerifyFile(_downloadBytesRequest.Result, _options.Bundle.FileSize, 0);
else if (_options.DownloadVerifyLevel == EFileVerifyLevel.High)
verifyResult = FileVerifyTools.FileVerify(_downloadBytesRequest.Result, _options.Bundle.FileSize, _options.Bundle.FileCRC);
verifyResult = FileVerifyHelper.VerifyFile(_downloadBytesRequest.Result, _options.Bundle.FileSize, _options.Bundle.FileCrc);
else
throw new System.NotImplementedException(_options.DownloadVerifyLevel.ToString());
@@ -244,10 +239,10 @@ namespace YooAsset
}
else
{
string error = $"[WebBundleVerify] Verify failed. Url:{_downloadBytesRequest.Url} Level: {_options.DownloadVerifyLevel} Result: {verifyResult}";
string error = $"[WebBundleVerify] Verify failed. Url: '{_downloadBytesRequest.Url}' Level: {_options.DownloadVerifyLevel} Result: {verifyResult}.";
YooLogger.Warning(error);
if (IsWaitForCompletion == false && _downloadRetryController.HasRetryQuota())
if (IsWaitForCompletion == false && _downloadRetryController.HasRetriesRemaining())
{
_downloadRetryController.StartRetryDelay();
_steps = ESteps.TryAgain;
@@ -255,8 +250,7 @@ namespace YooAsset
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = error;
SetError(error);
}
}
}
@@ -267,8 +261,7 @@ namespace YooAsset
if (result.Succeeded == false)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = result.Error;
SetError(result.Error);
return;
}
@@ -284,13 +277,12 @@ namespace YooAsset
if (assetBundle == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = "Unity engine load failed.";
SetError("Unity engine load failed.");
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
BundleHandle = new AssetBundleHandle(_downloadBytesRequest.Url, _options.Bundle, assetBundle, null);
}
}
@@ -304,14 +296,14 @@ namespace YooAsset
_downloadBytesRequest = null;
}
if (_downloadRetryController.UpdateRetryDelay())
if (_downloadRetryController.TickRetryDelay())
{
Progress = 0f;
_steps = ESteps.DataRequest;
}
}
}
internal override void InternalDispose()
protected override void InternalDispose()
{
if (_downloadBytesRequest != null)
{
@@ -325,16 +317,16 @@ namespace YooAsset
var args = new BundleDecryptArgs();
args.Bundle = _options.Bundle;
args.FileData = fileData;
var binaryData = decryptor.GetDecryptData(args);
var binaryData = decryptor.GetDecryptedData(args);
if (binaryData == null)
return LoadResult.Failure($"{_options.CacheName} decryptor returned null data.");
_createRequest = AssetBundle.LoadFromMemoryAsync(binaryData);
return LoadResult.Default();
}
private string GetRequestURL()
private string GetRequestUrl()
{
return _options.URLPolicy.SelectUrl(_options.CandidateURLs);
return _options.DownloadUrlPolicy.SelectUrl(_options.CandidateUrls);
}
}
}

View File

@@ -20,7 +20,7 @@ namespace YooAsset
/// <summary>
/// 候选下载地址列表
/// </summary>
public IReadOnlyList<string> CandidateURLs { get; set; }
public IReadOnlyList<string> CandidateUrls { get; set; }
/// <summary>
/// AssetBundle 解密器
@@ -50,11 +50,11 @@ namespace YooAsset
/// <summary>
/// 下载重试判定策略
/// </summary>
public IDownloadRetryPolicy RetryPolicy { get; set; }
public IDownloadRetryPolicy DownloadRetryPolicy { get; set; }
/// <summary>
/// URL 选择策略
/// </summary>
public IDownloadUrlPolicy URLPolicy { get; set; }
public IDownloadUrlPolicy DownloadUrlPolicy { get; set; }
}
}

View File

@@ -7,6 +7,7 @@ namespace YooAsset
/// </summary>
internal class EvictionAllPolicy : ICacheEvictionPolicy
{
/// <inheritdoc />
public EvictionResult SelectEvictionTargets(IReadOnlyCollection<ICacheEntry> cacheEntries, BCClearCacheOptions options)
{
var bundleGUIDs = new List<string>(cacheEntries.Count);

View File

@@ -4,26 +4,29 @@ namespace YooAsset
{
/// <summary>
/// 按资源地址清理缓存文件
/// ClearParam 类型string / string[] / List<string>
/// </summary>
/// <remarks>
/// ClearParameter 类型string / string[] / List<string>
/// </remarks>
internal class EvictionByLocationsPolicy : ICacheEvictionPolicy
{
/// <inheritdoc />
public EvictionResult SelectEvictionTargets(IReadOnlyCollection<ICacheEntry> cacheEntries, BCClearCacheOptions options)
{
if (options.Manifest == null)
return EvictionResult.Failure("Active package manifest not found.");
if (options.ClearParam == null)
if (options.ClearParameter == null)
return EvictionResult.Failure("Clear param is null.");
string[] locations;
if (options.ClearParam is string str)
if (options.ClearParameter is string str)
locations = new string[] { str };
else if (options.ClearParam is List<string> list)
else if (options.ClearParameter is List<string> list)
locations = list.ToArray();
else if (options.ClearParam is string[] array)
else if (options.ClearParameter is string[] array)
locations = array;
else
return EvictionResult.Failure($"Invalid clear param: {options.ClearParam.GetType().FullName}");
return EvictionResult.Failure($"Invalid clear param: {options.ClearParameter.GetType().FullName}");
var bundleGUIDs = new List<string>(locations.Length);
foreach (var location in locations)
@@ -32,7 +35,7 @@ namespace YooAsset
if (options.Manifest.TryGetPackageAsset(assetPath, out PackageAsset packageAsset))
{
PackageBundle bundle = options.Manifest.GetMainPackageBundle(packageAsset.BundleID);
bundleGUIDs.Add(bundle.BundleGUID);
bundleGUIDs.Add(bundle.BundleGuid);
}
}
return EvictionResult.Success(bundleGUIDs);

View File

@@ -4,34 +4,37 @@ namespace YooAsset
{
/// <summary>
/// 按标签清理缓存文件
/// ClearParam 类型string / string[] / List<string>
/// </summary>
/// <remarks>
/// ClearParameter 类型string / string[] / List<string>
/// </remarks>
internal class EvictionByTagsPolicy : ICacheEvictionPolicy
{
/// <inheritdoc />
public EvictionResult SelectEvictionTargets(IReadOnlyCollection<ICacheEntry> cacheEntries, BCClearCacheOptions options)
{
if (options.Manifest == null)
return EvictionResult.Failure("Active package manifest not found.");
if (options.ClearParam == null)
if (options.ClearParameter == null)
return EvictionResult.Failure("Clear param is null.");
string[] tags;
if (options.ClearParam is string str)
if (options.ClearParameter is string str)
tags = new string[] { str };
else if (options.ClearParam is List<string> list)
else if (options.ClearParameter is List<string> list)
tags = list.ToArray();
else if (options.ClearParam is string[] array)
else if (options.ClearParameter is string[] array)
tags = array;
else
return EvictionResult.Failure($"Invalid clear param: {options.ClearParam.GetType().FullName}");
return EvictionResult.Failure($"Invalid clear param: {options.ClearParameter.GetType().FullName}");
var bundleGUIDs = new List<string>(cacheEntries.Count);
foreach (var entry in cacheEntries)
{
if (options.Manifest.TryGetPackageBundleByBundleGUID(entry.BundleGUID, out PackageBundle bundle))
{
if (bundle.HasTag(tags))
bundleGUIDs.Add(bundle.BundleGUID);
if (bundle.HasAnyTag(tags))
bundleGUIDs.Add(bundle.BundleGuid);
}
}
return EvictionResult.Success(bundleGUIDs);

View File

@@ -7,6 +7,7 @@ namespace YooAsset
/// </summary>
internal class EvictionUnusedPolicy : ICacheEvictionPolicy
{
/// <inheritdoc />
public EvictionResult SelectEvictionTargets(IReadOnlyCollection<ICacheEntry> cacheEntries, BCClearCacheOptions options)
{
if (options.Manifest == null)
@@ -15,7 +16,7 @@ namespace YooAsset
var bundleGUIDs = new List<string>(cacheEntries.Count);
foreach (var entry in cacheEntries)
{
if (options.Manifest.IsIncludeBundleFile(entry.BundleGUID) == false)
if (options.Manifest.ContainsBundle(entry.BundleGUID) == false)
{
bundleGUIDs.Add(entry.BundleGUID);
}

View File

@@ -11,7 +11,7 @@ namespace YooAsset
/// <summary>
/// 内置文件缓存配置
/// </summary>
internal struct CacheConfig
internal struct Configuration
{
/// <summary>
/// AssetBundle 解密器
@@ -34,7 +34,7 @@ namespace YooAsset
/// <summary>
/// 缓存配置
/// </summary>
internal readonly CacheConfig Config;
internal readonly Configuration Config;
#region
/// <summary>
@@ -65,8 +65,8 @@ namespace YooAsset
/// <summary>
/// 已占用空间
/// 说明:按缓存索引累计
/// </summary>
/// <remarks>按缓存索引累计</remarks>
public long SpaceOccupied { get; private set; }
#endregion
@@ -76,55 +76,62 @@ namespace YooAsset
/// <param name="packageName">包裹名称</param>
/// <param name="rootPath">缓存根目录</param>
/// <param name="config">缓存配置</param>
public BuiltinBundleCache(string packageName, string rootPath, CacheConfig config)
public BuiltinBundleCache(string packageName, string rootPath, Configuration config)
{
PackageName = packageName;
RootPath = rootPath;
Config = config;
IsReadOnly = true;
}
/// <inheritdoc />
public void Dispose()
{
}
/// <inheritdoc />
public virtual BCInitializeOperation InitializeAsync()
{
var operation = new BFCInitializeOperation(this);
var operation = new BBCInitializeOperation(this);
return operation;
}
/// <inheritdoc />
public virtual BCWriteCacheOperation WriteCacheAsync(BCWriteCacheOptions options)
{
var operation = new BCWriteCacheCompleteOperation($"{nameof(BuiltinBundleCache)} is readonly.");
return operation;
}
/// <inheritdoc />
public virtual BCClearCacheOperation ClearCacheAsync(BCClearCacheOptions options)
{
var operation = new BCClearCacheCompleteOperation();
return operation;
}
/// <inheritdoc />
public virtual BCVerifyCacheOperation VerifyCacheAsync(BCVerifyCacheOptions options)
{
var operation = new BCVerifyCacheCompleteOperation();
return operation;
}
/// <inheritdoc />
public virtual BCLoadBundleOperation LoadBundleAsync(BCLoadBundleOptions options)
{
if (options.Bundle.BundleType == (int)EBundleType.AssetBundle)
{
var operation = new BFCLoadAssetBundleOperation(this, options.Bundle);
var operation = new BBCLoadAssetBundleOperation(this, options.Bundle);
return operation;
}
else if (options.Bundle.BundleType == (int)EBundleType.RawBundle)
{
var operation = new BFCLoadRawBundleOperation(this, options.Bundle);
var operation = new BBCLoadRawBundleOperation(this, options.Bundle);
return operation;
}
else
{
string error = $"{nameof(BuiltinBundleCache)} does not support bundle type: {options.Bundle.BundleType}";
string error = $"{nameof(BuiltinBundleCache)} does not support bundle type: {options.Bundle.BundleType}.";
var operation = new BCLoadBundleErrorOperation(error);
return operation;
}
}
/// <inheritdoc />
public virtual bool IsCached(string bundleGUID)
{
return _cacheEntries.ContainsKey(bundleGUID);
@@ -148,7 +155,7 @@ namespace YooAsset
internal void AddEntry(string bundleGUID, BuiltinBundleCacheEntry cacheEntry)
{
if (_cacheEntries.ContainsKey(bundleGUID))
throw new YooInternalException($"Cache entry already exists: {bundleGUID}");
throw new YooInternalException($"Cache entry already exists: '{bundleGUID}'.");
_cacheEntries.Add(bundleGUID, cacheEntry);
}

View File

@@ -11,10 +11,10 @@ namespace YooAsset
internal class BuiltinCatalog
{
/// <summary>
/// 内置资源文件条目
/// 内置资源目录条目
/// </summary>
[Serializable]
public class FileEntry
public class CatalogEntry
{
/// <summary>
/// 资源包唯一标识
@@ -30,7 +30,7 @@ namespace YooAsset
/// <summary>
/// 文件版本
/// </summary>
public string FileVersion;
public int FileVersion;
/// <summary>
/// 包裹名称
@@ -43,8 +43,8 @@ namespace YooAsset
public string PackageVersion;
/// <summary>
/// 文件条目列表
/// 目录条目列表
/// </summary>
public List<FileEntry> FileEntries = new List<FileEntry>();
public List<CatalogEntry> Entries = new List<CatalogEntry>();
}
}

View File

@@ -12,14 +12,14 @@ namespace YooAsset
public const int MaxFileSize = 104857600;
/// <summary>
/// 文件头标
/// 文件头标
/// </summary>
public const uint FileHeader = 0x133C5EE;
public const uint FileMagic = 0x133C5EE;
/// <summary>
/// 文件格式版本
/// 文件版本
/// </summary>
public const string FileVersion = "1.0.0";
public const int FileVersion = 1;
/// <summary>

View File

@@ -8,7 +8,7 @@ namespace YooAsset
/// <summary>
/// 内置资源目录工具类
/// </summary>
internal static class BuiltinCatalogTools
internal static class BuiltinCatalogHelper
{
#if UNITY_EDITOR
/// <summary>
@@ -20,11 +20,11 @@ namespace YooAsset
// 获取资源清单版本
string packageVersion;
{
string versionFileName = YooAssetSettingsData.GetPackageVersionFileName(packageName);
string versionFileName = YooAssetConfiguration.GetPackageVersionFileName(packageName);
string versionFilePath = $"{packageDirectory}/{versionFileName}";
if (File.Exists(versionFilePath) == false)
{
Debug.LogError($"Package version file not found: {versionFilePath}");
Debug.LogError($"Package version file not found: '{versionFilePath}'.");
return false;
}
@@ -34,16 +34,16 @@ namespace YooAsset
// 加载资源清单文件
PackageManifest packageManifest;
{
string manifestFileName = YooAssetSettingsData.GetManifestBinaryFileName(packageName, packageVersion);
string manifestFileName = YooAssetConfiguration.GetManifestBinaryFileName(packageName, packageVersion);
string manifestFilePath = $"{packageDirectory}/{manifestFileName}";
if (File.Exists(manifestFilePath) == false)
{
Debug.LogError($"Package manifest file not found: {manifestFilePath}");
Debug.LogError($"Package manifest file not found: '{manifestFilePath}'.");
return false;
}
var binaryData = FileUtility.ReadAllBytes(manifestFilePath);
packageManifest = PackageManifestTools.DeserializeManifestFromBinary(binaryData, decryptor);
packageManifest = PackageManifestHelper.DeserializeManifestFromBinary(binaryData, decryptor);
}
// 获取文件名映射关系
@@ -51,7 +51,7 @@ namespace YooAsset
{
foreach (var packageBundle in packageManifest.BundleList)
{
fileMapping.Add(packageBundle.FileName, packageBundle.BundleGUID);
fileMapping.Add(packageBundle.FileName, packageBundle.BundleGuid);
}
}
@@ -69,11 +69,11 @@ namespace YooAsset
BuiltinCatalogConsts.JsonFileName,
BuiltinCatalogConsts.BinaryFileName
};
string packageVersionFileName = YooAssetSettingsData.GetPackageVersionFileName(packageName);
string packageHashFileName = YooAssetSettingsData.GetPackageHashFileName(packageName, packageVersion);
string manifestBinaryFileName = YooAssetSettingsData.GetManifestBinaryFileName(packageName, packageVersion);
string manifestJsonFileName = YooAssetSettingsData.GetManifestJsonFileName(packageName, packageVersion);
string reportFileName = YooAssetSettingsData.GetBuildReportFileName(packageName, packageVersion);
string packageVersionFileName = YooAssetConfiguration.GetPackageVersionFileName(packageName);
string packageHashFileName = YooAssetConfiguration.GetPackageHashFileName(packageName, packageVersion);
string manifestBinaryFileName = YooAssetConfiguration.GetManifestBinaryFileName(packageName, packageVersion);
string manifestJsonFileName = YooAssetConfiguration.GetManifestJsonFileName(packageName, packageVersion);
string reportFileName = YooAssetConfiguration.GetBuildReportFileName(packageName, packageVersion);
whiteFileNameList.Add(packageVersionFileName);
whiteFileNameList.Add(packageHashFileName);
whiteFileNameList.Add(manifestBinaryFileName);
@@ -94,14 +94,14 @@ namespace YooAsset
string fileName = fileInfo.Name;
if (fileMapping.TryGetValue(fileName, out string bundleGUID))
{
var fileEntry = new BuiltinCatalog.FileEntry();
var fileEntry = new BuiltinCatalog.CatalogEntry();
fileEntry.BundleGUID = bundleGUID;
fileEntry.FileName = fileName;
buildinCatalog.FileEntries.Add(fileEntry);
buildinCatalog.Entries.Add(fileEntry);
}
else
{
Debug.LogWarning($"Failed to map file: {fileName}");
Debug.LogWarning($"Failed to map file: '{fileName}'.");
}
}
@@ -151,7 +151,7 @@ namespace YooAsset
}
/// <summary>
/// 序列化JSON文件
/// 序列化JSON 文件
/// </summary>
public static void SerializeToJson(string savePath, BuiltinCatalog catalog)
{
@@ -160,7 +160,7 @@ namespace YooAsset
}
/// <summary>
/// 序列化二进制文件
/// 序列化二进制文件
/// </summary>
public static void SerializeToBinary(string savePath, BuiltinCatalog catalog)
{
@@ -170,22 +170,22 @@ namespace YooAsset
BufferWriter buffer = new BufferWriter(BuiltinCatalogConsts.MaxFileSize);
// 写入文件标记
buffer.WriteUInt32(BuiltinCatalogConsts.FileHeader);
buffer.WriteUInt32(BuiltinCatalogConsts.FileMagic);
// 写入文件版本
buffer.WriteUTF8(BuiltinCatalogConsts.FileVersion);
buffer.WriteInt32(BuiltinCatalogConsts.FileVersion);
// 写入文件头信息
buffer.WriteUTF8(catalog.PackageName);
buffer.WriteUTF8(catalog.PackageVersion);
buffer.WriteString(catalog.PackageName);
buffer.WriteString(catalog.PackageVersion);
// 写入资源包列表
buffer.WriteInt32(catalog.FileEntries.Count);
for (int i = 0; i < catalog.FileEntries.Count; i++)
buffer.WriteInt32(catalog.Entries.Count);
for (int i = 0; i < catalog.Entries.Count; i++)
{
var fileWrapper = catalog.FileEntries[i];
buffer.WriteUTF8(fileWrapper.BundleGUID);
buffer.WriteUTF8(fileWrapper.FileName);
var fileWrapper = catalog.Entries[i];
buffer.WriteString(fileWrapper.BundleGUID);
buffer.WriteString(fileWrapper.FileName);
}
// 写入文件流
@@ -196,7 +196,7 @@ namespace YooAsset
#endif
/// <summary>
/// 反序列化JSON文件
/// 从 JSON 反序列化
/// </summary>
public static BuiltinCatalog DeserializeFromJson(string jsonContent)
{
@@ -204,7 +204,7 @@ namespace YooAsset
}
/// <summary>
/// 反序列化(二进制文件)
/// 从二进制数据反序列化
/// </summary>
public static BuiltinCatalog DeserializeFromBinary(byte[] binaryData)
{
@@ -215,31 +215,31 @@ namespace YooAsset
BufferReader buffer = new BufferReader(binaryData);
// 读取文件标记
uint fileHeader = buffer.ReadUInt32();
if (fileHeader != BuiltinCatalogConsts.FileHeader)
uint fileMagic = buffer.ReadUInt32();
if (fileMagic != BuiltinCatalogConsts.FileMagic)
throw new Exception("Invalid catalog file.");
// 读取文件版本
string fileVersion = buffer.ReadUTF8();
int fileVersion = buffer.ReadInt32();
if (fileVersion != BuiltinCatalogConsts.FileVersion)
throw new Exception($"The catalog file version is not compatible: {fileVersion} != {BuiltinCatalogConsts.FileVersion}");
throw new Exception($"The catalog file version is not compatible: {fileVersion} != {BuiltinCatalogConsts.FileVersion}.");
BuiltinCatalog catalog = new BuiltinCatalog();
{
// 读取文件头信息
catalog.FileVersion = fileVersion;
catalog.PackageName = buffer.ReadUTF8();
catalog.PackageVersion = buffer.ReadUTF8();
catalog.PackageName = buffer.ReadString();
catalog.PackageVersion = buffer.ReadString();
// 读取文件条目列表
int fileCount = buffer.ReadInt32();
catalog.FileEntries = new List<BuiltinCatalog.FileEntry>(fileCount);
catalog.Entries = new List<BuiltinCatalog.CatalogEntry>(fileCount);
for (int i = 0; i < fileCount; i++)
{
var fileEntry = new BuiltinCatalog.FileEntry();
fileEntry.BundleGUID = buffer.ReadUTF8();
fileEntry.FileName = buffer.ReadUTF8();
catalog.FileEntries.Add(fileEntry);
var fileEntry = new BuiltinCatalog.CatalogEntry();
fileEntry.BundleGUID = buffer.ReadString();
fileEntry.FileName = buffer.ReadString();
catalog.Entries.Add(fileEntry);
}
}

View File

@@ -4,7 +4,7 @@ namespace YooAsset
/// <summary>
/// 内置文件缓存初始化操作
/// </summary>
internal class BFCInitializeOperation : BCInitializeOperation
internal class BBCInitializeOperation : BCInitializeOperation
{
private enum ESteps
{
@@ -18,15 +18,15 @@ namespace YooAsset
private LoadBuiltinCatalogOperation _loadBuiltinCatalogOp;
private ESteps _steps = ESteps.None;
public BFCInitializeOperation(BuiltinBundleCache fileCache)
public BBCInitializeOperation(BuiltinBundleCache fileCache)
{
_fileCache = fileCache;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.LoadCatalog;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -55,15 +55,14 @@ namespace YooAsset
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _loadBuiltinCatalogOp.Error;
SetError(_loadBuiltinCatalogOp.Error);
}
}
if (_steps == ESteps.RecordEntry)
{
var catalog = _loadBuiltinCatalogOp.Catalog;
foreach (var fileEntry in catalog.FileEntries)
foreach (var fileEntry in catalog.Entries)
{
string filePath = PathUtility.Combine(_fileCache.RootPath, fileEntry.FileName);
var cacheEntry = new BuiltinBundleCacheEntry(fileEntry.BundleGUID, filePath);
@@ -71,7 +70,7 @@ namespace YooAsset
}
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
}
}
}

View File

@@ -4,7 +4,7 @@ namespace YooAsset
/// <summary>
/// 内置文件缓存加载 AssetBundle 操作
/// </summary>
internal class BFCLoadAssetBundleOperation : BCLoadBundleOperation
internal class BBCLoadAssetBundleOperation : BCLoadBundleOperation
{
private enum ESteps
{
@@ -20,28 +20,27 @@ namespace YooAsset
private BuiltinBundleCacheEntry _cacheEntry;
private ESteps _steps = ESteps.None;
public BFCLoadAssetBundleOperation(BuiltinBundleCache fileCache, PackageBundle bundle)
public BBCLoadAssetBundleOperation(BuiltinBundleCache fileCache, PackageBundle bundle)
{
_fileCache = fileCache;
_bundle = bundle;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.GetEntry;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.GetEntry)
{
_cacheEntry = _fileCache.GetEntry(_bundle.BundleGUID);
_cacheEntry = _fileCache.GetEntry(_bundle.BundleGuid);
if (_cacheEntry == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"File cache entry not found: {_bundle.BundleGUID}";
SetError($"File cache entry not found: '{_bundle.BundleGuid}'.");
}
else
{
@@ -76,18 +75,17 @@ namespace YooAsset
throw new YooInternalException("Loaded bundle handle is null.");
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
BundleHandle = _loadLocalAssetBundleOp.BundleHandle;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _loadLocalAssetBundleOp.Error;
SetError(_loadLocalAssetBundleOp.Error);
}
}
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
ExecuteBatch();
}
@@ -96,7 +94,7 @@ namespace YooAsset
/// <summary>
/// 内置文件缓存加载 RawBundle 操作
/// </summary>
internal class BFCLoadRawBundleOperation : BCLoadBundleOperation
internal class BBCLoadRawBundleOperation : BCLoadBundleOperation
{
private enum ESteps
{
@@ -112,28 +110,27 @@ namespace YooAsset
private BuiltinBundleCacheEntry _cacheEntry;
private ESteps _steps = ESteps.None;
public BFCLoadRawBundleOperation(BuiltinBundleCache fileCache, PackageBundle bundle)
public BBCLoadRawBundleOperation(BuiltinBundleCache fileCache, PackageBundle bundle)
{
_fileCache = fileCache;
_bundle = bundle;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.GetEntry;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.GetEntry)
{
_cacheEntry = _fileCache.GetEntry(_bundle.BundleGUID);
_cacheEntry = _fileCache.GetEntry(_bundle.BundleGuid);
if (_cacheEntry == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"File cache entry not found: {_bundle.BundleGUID}";
SetError($"File cache entry not found: '{_bundle.BundleGuid}'.");
}
else
{
@@ -168,18 +165,17 @@ namespace YooAsset
throw new YooInternalException("Loaded bundle handle is null.");
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
BundleHandle = _loadLocalRawBundleOp.BundleHandle;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _loadLocalRawBundleOp.Error;
SetError(_loadLocalRawBundleOp.Error);
}
}
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
ExecuteBatch();
}

View File

@@ -11,7 +11,7 @@ namespace YooAsset
/// <summary>
/// 编辑器文件缓存配置
/// </summary>
internal struct CacheConfig
internal struct Configuration
{
/// <summary>
/// 虚拟下载模式,模拟资源下载流程
@@ -39,7 +39,7 @@ namespace YooAsset
/// <summary>
/// 缓存配置
/// </summary>
internal readonly CacheConfig Config;
internal readonly Configuration Config;
#region
/// <summary>
@@ -70,8 +70,8 @@ namespace YooAsset
/// <summary>
/// 已占用空间
/// 说明:按缓存索引累计
/// </summary>
/// <remarks>按缓存索引累计</remarks>
public long SpaceOccupied { get; private set; }
#endregion
@@ -81,59 +81,65 @@ namespace YooAsset
/// <param name="packageName">包裹名称</param>
/// <param name="rootPath">缓存根目录</param>
/// <param name="config">缓存配置</param>
public EditorBundleCache(string packageName, string rootPath, CacheConfig config)
public EditorBundleCache(string packageName, string rootPath, Configuration config)
{
PackageName = packageName;
RootPath = rootPath;
Config = config;
IsReadOnly = true;
}
/// <inheritdoc />
public void Dispose()
{
}
/// <inheritdoc />
public virtual BCInitializeOperation InitializeAsync()
{
var operation = new EBCInitializeOperation(this);
return operation;
}
/// <inheritdoc />
public virtual BCWriteCacheOperation WriteCacheAsync(BCWriteCacheOptions options)
{
var operation = new EBCWriteCacheOperation(this, options);
return operation;
}
/// <inheritdoc />
public virtual BCClearCacheOperation ClearCacheAsync(BCClearCacheOptions options)
{
ICacheEvictionPolicy policy = CreateEvictionPolicy(options);
if (policy == null)
return new BCClearCacheCompleteOperation($"Invalid clear mode: {options.ClearMode}");
return new BCClearCacheCompleteOperation($"Invalid clear method: '{options.ClearMethod}'.");
return new EBCClearCacheOperation(this, options, policy);
}
/// <summary>
/// 根据 ClearMode 创建对应的淘汰策略实例
/// 根据 ClearMethod 创建对应的淘汰策略实例
/// </summary>
protected virtual ICacheEvictionPolicy CreateEvictionPolicy(BCClearCacheOptions options)
{
if (options.ClearMode == EFileClearMode.ClearAllBundleFiles.ToString())
if (options.ClearMethod == ClearCacheMethods.ClearAllBundleFiles)
return new EvictionAllPolicy();
if (options.ClearMode == EFileClearMode.ClearUnusedBundleFiles.ToString())
if (options.ClearMethod == ClearCacheMethods.ClearUnusedBundleFiles)
return new EvictionUnusedPolicy();
if (options.ClearMode == EFileClearMode.ClearBundleFilesByLocations.ToString())
if (options.ClearMethod == ClearCacheMethods.ClearBundleFilesByLocations)
return new EvictionByLocationsPolicy();
if (options.ClearMode == EFileClearMode.ClearBundleFilesByTags.ToString())
if (options.ClearMethod == ClearCacheMethods.ClearBundleFilesByTags)
return new EvictionByTagsPolicy();
if (options.ClearParam is ICacheEvictionPolicy customPolicy)
if (options.ClearParameter is ICacheEvictionPolicy customPolicy)
return customPolicy;
return null;
}
/// <inheritdoc />
public virtual BCVerifyCacheOperation VerifyCacheAsync(BCVerifyCacheOptions options)
{
var operation = new BCVerifyCacheCompleteOperation();
return operation;
}
/// <inheritdoc />
public virtual BCLoadBundleOperation LoadBundleAsync(BCLoadBundleOptions options)
{
if (options.Bundle.BundleType == (int)EBundleType.VirtualBundle)
@@ -143,11 +149,12 @@ namespace YooAsset
}
else
{
string error = $"{nameof(EditorBundleCache)} does not support bundle type: {options.Bundle.BundleType}";
string error = $"{nameof(EditorBundleCache)} does not support bundle type: {options.Bundle.BundleType}.";
var operation = new BCLoadBundleErrorOperation(error);
return operation;
}
}
/// <inheritdoc />
public virtual bool IsCached(string bundleGUID)
{
if (Config.VirtualDownloadMode)
@@ -171,7 +178,7 @@ namespace YooAsset
internal void AddEntry(string bundleGUID, EditorBundleCacheEntry cacheEntry)
{
if (_cacheEntries.ContainsKey(bundleGUID))
throw new YooInternalException($"Cache entry already exists: {bundleGUID}");
throw new YooInternalException($"Cache entry already exists: '{bundleGUID}'.");
_cacheEntries.Add(bundleGUID, cacheEntry);
}

View File

@@ -27,11 +27,11 @@ namespace YooAsset
_options = options;
_policy = policy;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.GetResult;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -44,8 +44,7 @@ namespace YooAsset
if (clearResult.Succeeded == false)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = clearResult.Error;
SetError(clearResult.Error);
return;
}
@@ -61,10 +60,10 @@ namespace YooAsset
}
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
}
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
ExecuteBatch();
}

View File

@@ -12,11 +12,11 @@ namespace YooAsset
{
_fileCache = cache;
}
internal override void InternalStart()
protected override void InternalStart()
{
Status = EOperationStatus.Succeeded;
SetResult();
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
}
}

View File

@@ -26,23 +26,22 @@ namespace YooAsset
_fileCache = fileCache;
_bundle = bundle;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.CheckCache;
_asyncSimulateFrame = GetAsyncSimulateFrame();
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.CheckCache)
{
if (_fileCache.IsCached(_bundle.BundleGUID) == false)
if (_fileCache.IsCached(_bundle.BundleGuid) == false)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"File cache entry not found: {_bundle.BundleGUID}";
SetError($"File cache entry not found: '{_bundle.BundleGuid}'.");
return;
}
@@ -51,12 +50,11 @@ namespace YooAsset
if (_steps == ESteps.CheckFilePath)
{
_editorFilePath = EditorFileSystemTools.GetEditorFilePath(_bundle);
_editorFilePath = EditorFileSystemHelper.GetEditorFilePath(_bundle);
if (string.IsNullOrEmpty(_editorFilePath))
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Editor file path is null. Bundle: {_bundle.BundleName}";
SetError($"Editor file path is null. Bundle: '{_bundle.BundleName}'.");
}
else
{
@@ -71,13 +69,12 @@ namespace YooAsset
if (_fileCache.Config.VirtualWebGLMode)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = "WebGL mode only supports async load method.";
SetError("WebGL mode only supports async load method.");
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
BundleHandle = new VirtualBundleHandle(_editorFilePath, _bundle);
}
}
@@ -87,13 +84,13 @@ namespace YooAsset
if (_asyncSimulateFrame <= 0)
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
BundleHandle = new VirtualBundleHandle(_editorFilePath, _bundle);
}
}
}
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
ExecuteBatch();
}

View File

@@ -23,22 +23,21 @@ namespace YooAsset
_fileCache = cache;
_options = options;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.CheckCache;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.CheckCache)
{
if (_fileCache.IsCached(_options.Bundle.BundleGUID))
if (_fileCache.IsCached(_options.Bundle.BundleGuid))
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = "The bundle is already cached.";
SetError("The bundle is already cached.");
}
else
{
@@ -48,13 +47,13 @@ namespace YooAsset
if (_steps == ESteps.CacheFile)
{
var cacheEntry = new EditorBundleCacheEntry(_options.Bundle.BundleGUID, _options.FilePath);
_fileCache.AddEntry(_options.Bundle.BundleGUID, cacheEntry);
var cacheEntry = new EditorBundleCacheEntry(_options.Bundle.BundleGuid, _options.FilePath);
_fileCache.AddEntry(_options.Bundle.BundleGuid, cacheEntry);
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
}
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
ExecuteBatch();
}

View File

@@ -1,31 +0,0 @@

namespace YooAsset
{
/// <summary>
/// 文件清理方式
/// </summary>
public enum EFileClearMode
{
/// <summary>
/// 清理所有文件
/// </summary>
ClearAllBundleFiles,
/// <summary>
/// 清理未在使用的文件
/// </summary>
ClearUnusedBundleFiles,
/// <summary>
/// 清理指定地址的文件
/// 说明需要指定参数可选string, string[], List<string>
/// </summary>
ClearBundleFilesByLocations,
/// <summary>
/// 清理指定标签的文件
/// 说明需要指定参数可选string, string[], List<string>
/// </summary>
ClearBundleFilesByTags,
}
}

View File

@@ -6,7 +6,7 @@ namespace YooAsset
/// <summary>
/// 文件校验工具类
/// </summary>
internal class FileVerifyTools
internal class FileVerifyHelper
{
/// <summary>
/// 校验文件完整性
@@ -15,7 +15,7 @@ namespace YooAsset
/// <param name="fileSize">期望的文件大小</param>
/// <param name="fileCRC">期望的文件CRC值</param>
/// <returns>校验结果</returns>
public static EFileVerifyResult FileVerify(string filePath, long fileSize, uint fileCRC)
public static EFileVerifyResult VerifyFile(string filePath, long fileSize, uint fileCRC)
{
try
{
@@ -35,7 +35,7 @@ namespace YooAsset
// 可选条件验证文件CRC
if (fileCRC > 0)
{
uint crc = HashUtility.ComputeFileCRC32AsUInt(filePath);
uint crc = HashUtility.ComputeFileCrc32AsUInt(filePath);
if (crc == fileCRC)
return EFileVerifyResult.Succeed;
else
@@ -48,7 +48,7 @@ namespace YooAsset
}
catch (Exception ex)
{
YooLogger.Error($"File verification exception: {ex.Message}");
YooLogger.Error($"File verification exception: {ex.Message}.");
return EFileVerifyResult.Exception;
}
}
@@ -60,7 +60,7 @@ namespace YooAsset
/// <param name="fileSize">期望的文件大小</param>
/// <param name="fileCRC">期望的文件CRC值</param>
/// <returns>校验结果</returns>
public static EFileVerifyResult FileVerify(byte[] fileData, long fileSize, uint fileCRC)
public static EFileVerifyResult VerifyFile(byte[] fileData, long fileSize, uint fileCRC)
{
try
{
@@ -80,7 +80,7 @@ namespace YooAsset
// 可选条件验证文件CRC
if (fileCRC > 0)
{
uint crc = HashUtility.ComputeBytesCRC32AsUInt(fileData);
uint crc = HashUtility.ComputeCrc32AsUInt(fileData);
if (crc == fileCRC)
return EFileVerifyResult.Succeed;
else
@@ -93,7 +93,7 @@ namespace YooAsset
}
catch (Exception ex)
{
YooLogger.Error($"File verification exception: {ex.Message}");
YooLogger.Error($"File verification exception: {ex.Message}.");
return EFileVerifyResult.Exception;
}
}

View File

@@ -25,11 +25,11 @@ namespace YooAsset
_fileCache = fileCache;
_bundleGUIDs = bundleGUIDs;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.CheckParam;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -39,7 +39,7 @@ namespace YooAsset
if (_bundleGUIDs == null || _bundleGUIDs.Count == 0)
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
return;
}
@@ -66,11 +66,11 @@ namespace YooAsset
if (_bundleGUIDs.Count == 0)
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
}
}
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
ExecuteBatch();
}

View File

@@ -33,11 +33,11 @@ namespace YooAsset
{
_fileCache = fileCache;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.Prepare;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -54,7 +54,7 @@ namespace YooAsset
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
}
}
@@ -67,7 +67,7 @@ namespace YooAsset
_filesEnumerator = null;
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
double costTime = TimeUtility.RealtimeSinceStartup - _verifyStartTime;
YooLogger.Log($"Search cache files elapsed time {costTime:f1} seconds");
}

View File

@@ -39,11 +39,11 @@ namespace YooAsset
_fileVerifyMaxConcurrency = fileVerifyMaxConcurrency;
_pendingVerifyList = elements;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.InitVerify;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -82,7 +82,7 @@ namespace YooAsset
else
{
_failedCount++;
YooLogger.Warning($"File verification failed (code: {verifyElement.VerifyResultCode}). Deleting files: {verifyElement.FolderPath}");
YooLogger.Warning($"File verification failed (code: {verifyElement.VerifyResultCode}). Deleting files: '{verifyElement.FolderPath}'.");
verifyElement.DeleteCacheFolder();
}
}
@@ -92,7 +92,7 @@ namespace YooAsset
if (_pendingVerifyList.Count == 0 && _activeVerifyList.Count == 0)
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
double costTime = TimeUtility.RealtimeSinceStartup - _verifyStartTime;
YooLogger.Log($"Verify cache files elapsed time {costTime:f1} seconds");
}
@@ -143,12 +143,12 @@ namespace YooAsset
return EFileVerifyResult.InfoFileInvalid;
var reader = new BufferReader(binaryData);
uint magic = reader.ReadUInt32();
if (magic != SandboxBundleCacheConsts.InfoFileMagic)
uint fileMagic = reader.ReadUInt32();
if (fileMagic != SandboxBundleCacheConsts.InfoFileMagic)
return EFileVerifyResult.InfoFileMagicError;
int version = reader.ReadInt32();
if (version != SandboxBundleCacheConsts.InfoFileVersion)
int fileVersion = reader.ReadInt32();
if (fileVersion != SandboxBundleCacheConsts.InfoFileVersion)
return EFileVerifyResult.InfoFileVersionError;
uint dataFileCRC = reader.ReadUInt32();
@@ -159,15 +159,15 @@ namespace YooAsset
if (verifyLevel == EFileVerifyLevel.Low)
return EFileVerifyResult.Succeed;
else if (verifyLevel == EFileVerifyLevel.Middle)
return FileVerifyTools.FileVerify(element.DataFilePath, dataFileSize, 0);
return FileVerifyHelper.VerifyFile(element.DataFilePath, dataFileSize, 0);
else if (verifyLevel == EFileVerifyLevel.High)
return FileVerifyTools.FileVerify(element.DataFilePath, dataFileSize, dataFileCRC);
return FileVerifyHelper.VerifyFile(element.DataFilePath, dataFileSize, dataFileCRC);
else
throw new System.NotImplementedException(verifyLevel.ToString());
}
catch (Exception ex)
{
YooLogger.Error($"File verification exception: {ex.Message}");
YooLogger.Error($"File verification exception: {ex.Message}.");
return EFileVerifyResult.Exception;
}
}

View File

@@ -29,11 +29,11 @@ namespace YooAsset
{
_element = element;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.VerifyFile;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -57,17 +57,16 @@ namespace YooAsset
if (VerifyResult == EFileVerifyResult.Succeed)
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Failed to verify file: {_element.FilePath} ErrorCode: {VerifyResult}";
SetError($"Failed to verify file '{_element.FilePath}'. ErrorCode: {VerifyResult}.");
}
}
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
//注意: 等待子线程验证文件完毕,该操作会挂起主线程!
ExecuteUntilComplete();
@@ -76,7 +75,7 @@ namespace YooAsset
private void VerifyFileInThread(object obj)
{
TempFileInfo element = (TempFileInfo)obj;
int resultCode = (int)FileVerifyTools.FileVerify(element.FilePath, element.FileSize, element.FileCRC);
int resultCode = (int)FileVerifyHelper.VerifyFile(element.FilePath, element.FileSize, element.FileCRC);
element.VerifyResultCode = resultCode; //注意: 一次命令赋值
}
}

View File

@@ -27,11 +27,11 @@ namespace YooAsset
_policy = policy;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.GetResult;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -44,8 +44,7 @@ namespace YooAsset
if (clearResult.Succeeded == false)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = clearResult.Error;
SetError(clearResult.Error);
return;
}
@@ -68,17 +67,16 @@ namespace YooAsset
if (_clearCacheFilesOp.Status == EOperationStatus.Succeeded)
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _clearCacheFilesOp.Error;
SetError(_clearCacheFilesOp.Error);
}
}
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
ExecuteBatch();
}

View File

@@ -23,11 +23,11 @@ namespace YooAsset
{
_fileCache = fileCache;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.SearchCacheFiles;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -53,8 +53,7 @@ namespace YooAsset
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _searchCacheFilesOp.Error;
SetError(_searchCacheFilesOp.Error);
}
}
@@ -75,13 +74,12 @@ namespace YooAsset
if (_verifyCacheFilesOp.Status == EOperationStatus.Succeeded)
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _verifyCacheFilesOp.Error;
SetError(_verifyCacheFilesOp.Error);
}
}
}

View File

@@ -31,23 +31,22 @@ namespace YooAsset
_fileCache = fileCache;
_bundle = bundle;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.GetEntry;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.GetEntry)
{
_cacheEntry = _fileCache.GetEntry(_bundle.BundleGUID);
_cacheEntry = _fileCache.GetEntry(_bundle.BundleGuid);
if (_cacheEntry == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"File cache entry not found: {_bundle.BundleGUID}";
SetError($"File cache entry not found: '{_bundle.BundleGuid}'.");
}
else
{
@@ -82,7 +81,7 @@ namespace YooAsset
throw new YooInternalException("Loaded asset bundle handle is null.");
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
BundleHandle = _loadLocalAssetBundleOp.BundleHandle;
}
else
@@ -95,8 +94,7 @@ namespace YooAsset
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _loadLocalAssetBundleOp.Error;
SetError(_loadLocalAssetBundleOp.Error);
}
}
}
@@ -129,8 +127,7 @@ namespace YooAsset
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _verifyCacheOp.Error;
SetError(_verifyCacheOp.Error);
}
}
@@ -145,8 +142,7 @@ namespace YooAsset
if (_fileCache.Config.AssetBundleFallbackDecryptor == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"{nameof(SandboxBundleCache)} fallback decryptor is null.";
SetError($"{nameof(SandboxBundleCache)} fallback decryptor is null.");
return;
}
@@ -154,8 +150,7 @@ namespace YooAsset
if (assetBundle == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Failed fallback load encrypted asset bundle: {_bundle.BundleName}";
SetError($"Failed to load encrypted asset bundle '{_bundle.BundleName}' with fallback.");
}
}
else
@@ -164,20 +159,19 @@ namespace YooAsset
if (assetBundle == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Failed fallback load asset bundle: {_bundle.BundleName}";
SetError($"Failed to load asset bundle '{_bundle.BundleName}' with fallback.");
}
}
if (assetBundle != null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
BundleHandle = new AssetBundleHandle(_cacheEntry.DataFilePath, _bundle, assetBundle, null);
}
}
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
ExecuteBatch();
}
@@ -192,7 +186,7 @@ namespace YooAsset
var args = new BundleDecryptArgs();
args.Bundle = _bundle;
args.FilePath = _cacheEntry.DataFilePath;
var fileData = decryptor.GetDecryptData(args);
var fileData = decryptor.GetDecryptedData(args);
return AssetBundle.LoadFromMemory(fileData);
}
}
@@ -222,23 +216,22 @@ namespace YooAsset
_fileCache = fileCache;
_bundle = bundle;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.GetEntry;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.GetEntry)
{
_cacheEntry = _fileCache.GetEntry(_bundle.BundleGUID);
_cacheEntry = _fileCache.GetEntry(_bundle.BundleGuid);
if (_cacheEntry == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"File cache entry not found: {_bundle.BundleGUID}";
SetError($"File cache entry not found: '{_bundle.BundleGuid}'.");
}
else
{
@@ -273,18 +266,17 @@ namespace YooAsset
throw new YooInternalException("Loaded raw bundle handle is null.");
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
BundleHandle = _loadLocalRawBundleOp.BundleHandle;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _loadLocalRawBundleOp.Error;
SetError(_loadLocalRawBundleOp.Error);
}
}
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
ExecuteBatch();
}
@@ -301,11 +293,11 @@ namespace YooAsset
Done,
}
internal override void InternalStart()
protected override void InternalStart()
{
throw new NotImplementedException();
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
throw new NotImplementedException();
}

View File

@@ -24,22 +24,21 @@ namespace YooAsset
_fileCache = cache;
_options = options;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.Check;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.Check)
{
if (_fileCache.IsCached(_options.Bundle.BundleGUID) == false)
if (_fileCache.IsCached(_options.Bundle.BundleGuid) == false)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = "Cached bundle not found.";
SetError("Cached bundle not found.");
}
else
{
@@ -51,8 +50,8 @@ namespace YooAsset
{
if (_verifyTempFileOp == null)
{
var entry = _fileCache.GetEntry(_options.Bundle.BundleGUID);
var element = new TempFileInfo(entry.DataFilePath, _options.Bundle.FileCRC, _options.Bundle.FileSize);
var entry = _fileCache.GetEntry(_options.Bundle.BundleGuid);
var element = new TempFileInfo(entry.DataFilePath, _options.Bundle.FileCrc, _options.Bundle.FileSize);
_verifyTempFileOp = new VerifyTempFileOperation(element);
_verifyTempFileOp.StartOperation();
AddChildOperation(_verifyTempFileOp);
@@ -68,23 +67,22 @@ namespace YooAsset
if (_verifyTempFileOp.Status == EOperationStatus.Succeeded)
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _verifyTempFileOp.Error;
SetError(_verifyTempFileOp.Error);
if (_options.DeleteCacheEntryOnFailure)
{
YooLogger.Error($"Found corrupted bundle file. Removing cache entry: {_options.Bundle.BundleGUID}");
_fileCache.RemoveEntry(_options.Bundle.BundleGUID);
YooLogger.Error($"Found corrupted bundle file. Removing cache entry: '{_options.Bundle.BundleGuid}'.");
_fileCache.RemoveEntry(_options.Bundle.BundleGuid);
}
}
}
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
ExecuteBatch();
}

View File

@@ -27,22 +27,21 @@ namespace YooAsset
_fileCache = fileCache;
_options = options;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.Check;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.Check)
{
if (_fileCache.IsCached(_options.Bundle.BundleGUID))
if (_fileCache.IsCached(_options.Bundle.BundleGuid))
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = "The bundle is already cached.";
SetError("The bundle is already cached.");
}
else
{
@@ -54,7 +53,7 @@ namespace YooAsset
{
if (_verifyTempFileOp == null)
{
var element = new TempFileInfo(_options.FilePath, _options.Bundle.FileCRC, _options.Bundle.FileSize);
var element = new TempFileInfo(_options.FilePath, _options.Bundle.FileCrc, _options.Bundle.FileSize);
_verifyTempFileOp = new VerifyTempFileOperation(element);
_verifyTempFileOp.StartOperation();
AddChildOperation(_verifyTempFileOp);
@@ -74,8 +73,7 @@ namespace YooAsset
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _verifyTempFileOp.Error;
SetError(_verifyTempFileOp.Error);
}
}
@@ -90,7 +88,7 @@ namespace YooAsset
try
{
// 阶段A准备目标目录清理可能存在的残留文件
FileUtility.EnsureFileDirectory(dataFilePath);
FileUtility.EnsureParentDirectoryExists(dataFilePath);
DeleteFileSafely(dataTempPath);
DeleteFileSafely(infoTempPath);
@@ -102,7 +100,7 @@ namespace YooAsset
var buffer = new BufferWriter(64);
buffer.WriteUInt32(SandboxBundleCacheConsts.InfoFileMagic);
buffer.WriteInt32(SandboxBundleCacheConsts.InfoFileVersion);
buffer.WriteUInt32(_options.Bundle.FileCRC);
buffer.WriteUInt32(_options.Bundle.FileCrc);
buffer.WriteInt64(_options.Bundle.FileSize);
buffer.WriteInt64(nowTicks); // CreatedAtTicks
buffer.WriteInt64(nowTicks); // LastAccessAtTicks
@@ -122,8 +120,7 @@ namespace YooAsset
catch (Exception ex)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Failed to write cache file. Error: {ex.Message}";
SetError($"Failed to write cache file. Error: {ex.Message}.");
YooLogger.Error(Error);
// 回滚:清理临时文件,正式文件不受影响
@@ -133,13 +130,13 @@ namespace YooAsset
}
// 阶段D注册内存缓存条目
var cacheEntry = new SandboxBundleCacheEntry(_options.Bundle.BundleGUID, infoFilePath, dataFilePath);
_fileCache.AddEntry(_options.Bundle.BundleGUID, cacheEntry);
var cacheEntry = new SandboxBundleCacheEntry(_options.Bundle.BundleGuid, infoFilePath, dataFilePath);
_fileCache.AddEntry(_options.Bundle.BundleGuid, cacheEntry);
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
}
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
ExecuteBatch();
}
@@ -153,7 +150,7 @@ namespace YooAsset
}
catch (Exception ex)
{
YooLogger.Warning($"Failed to delete file: {filePath} Error: {ex.Message}");
YooLogger.Warning($"Failed to delete file '{filePath}'. Error: {ex.Message}.");
}
}
}

View File

@@ -11,7 +11,7 @@ namespace YooAsset
/// <summary>
/// 沙盒文件缓存配置
/// </summary>
internal struct CacheConfig
internal struct Configuration
{
/// <summary>
/// 文件校验最大并发数
@@ -47,7 +47,7 @@ namespace YooAsset
/// <summary>
/// 缓存配置
/// </summary>
internal readonly CacheConfig Config;
internal readonly Configuration Config;
#region
/// <summary>
@@ -78,8 +78,8 @@ namespace YooAsset
/// <summary>
/// 已占用空间
/// 说明:按缓存索引累计
/// </summary>
/// <remarks>按缓存索引累计</remarks>
public long SpaceOccupied { get; private set; }
#endregion
@@ -89,59 +89,65 @@ namespace YooAsset
/// <param name="packageName">包裹名称</param>
/// <param name="rootPath">缓存根目录</param>
/// <param name="config">缓存配置</param>
public SandboxBundleCache(string packageName, string rootPath, CacheConfig config)
public SandboxBundleCache(string packageName, string rootPath, Configuration config)
{
PackageName = packageName;
RootPath = rootPath;
Config = config;
IsReadOnly = false;
}
/// <inheritdoc />
public void Dispose()
{
}
/// <inheritdoc />
public virtual BCInitializeOperation InitializeAsync()
{
var operation = new SBCInitializeOperation(this);
return operation;
}
/// <inheritdoc />
public virtual BCWriteCacheOperation WriteCacheAsync(BCWriteCacheOptions options)
{
var operation = new SBCWriteCacheOperation(this, options);
return operation;
}
/// <inheritdoc />
public virtual BCClearCacheOperation ClearCacheAsync(BCClearCacheOptions options)
{
ICacheEvictionPolicy policy = CreateEvictionPolicy(options);
if (policy == null)
return new BCClearCacheCompleteOperation($"Invalid clear mode: {options.ClearMode}");
return new BCClearCacheCompleteOperation($"Invalid clear method: '{options.ClearMethod}'.");
return new SBCClearCacheOperation(this, options, policy);
}
/// <summary>
/// 根据 ClearMode 创建对应的淘汰策略实例
/// 根据 ClearMethod 创建对应的淘汰策略实例
/// </summary>
protected virtual ICacheEvictionPolicy CreateEvictionPolicy(BCClearCacheOptions options)
{
if (options.ClearMode == EFileClearMode.ClearAllBundleFiles.ToString())
if (options.ClearMethod == ClearCacheMethods.ClearAllBundleFiles)
return new EvictionAllPolicy();
if (options.ClearMode == EFileClearMode.ClearUnusedBundleFiles.ToString())
if (options.ClearMethod == ClearCacheMethods.ClearUnusedBundleFiles)
return new EvictionUnusedPolicy();
if (options.ClearMode == EFileClearMode.ClearBundleFilesByLocations.ToString())
if (options.ClearMethod == ClearCacheMethods.ClearBundleFilesByLocations)
return new EvictionByLocationsPolicy();
if (options.ClearMode == EFileClearMode.ClearBundleFilesByTags.ToString())
if (options.ClearMethod == ClearCacheMethods.ClearBundleFilesByTags)
return new EvictionByTagsPolicy();
if (options.ClearParam is ICacheEvictionPolicy customPolicy)
if (options.ClearParameter is ICacheEvictionPolicy customPolicy)
return customPolicy;
return null;
}
/// <inheritdoc />
public virtual BCVerifyCacheOperation VerifyCacheAsync(BCVerifyCacheOptions options)
{
var operation = new SBCVerifyCacheOperation(this, options);
return operation;
}
/// <inheritdoc />
public virtual BCLoadBundleOperation LoadBundleAsync(BCLoadBundleOptions options)
{
if (options.Bundle.BundleType == (int)EBundleType.AssetBundle)
@@ -156,11 +162,12 @@ namespace YooAsset
}
else
{
string error = $"{nameof(SandboxBundleCache)} does not support bundle type: {options.Bundle.BundleType}";
string error = $"{nameof(SandboxBundleCache)} does not support bundle type: {options.Bundle.BundleType}.";
var operation = new BCLoadBundleErrorOperation(error);
return operation;
}
}
/// <inheritdoc />
public virtual bool IsCached(string bundleGUID)
{
return _cacheEntries.ContainsKey(bundleGUID);
@@ -172,11 +179,11 @@ namespace YooAsset
/// </summary>
internal string GetDataFilePath(PackageBundle bundle)
{
if (_dataFilePathMapping.TryGetValue(bundle.BundleGUID, out string filePath) == false)
if (_dataFilePathMapping.TryGetValue(bundle.BundleGuid, out string filePath) == false)
{
string folderName = GetHashFolderName(bundle.FileHash);
filePath = PathUtility.Combine(RootPath, folderName, bundle.BundleGUID, SandboxBundleCacheConsts.BundleDataFileName);
_dataFilePathMapping.Add(bundle.BundleGUID, filePath);
filePath = PathUtility.Combine(RootPath, folderName, bundle.BundleGuid, SandboxBundleCacheConsts.BundleDataFileName);
_dataFilePathMapping.Add(bundle.BundleGuid, filePath);
}
return filePath;
}
@@ -186,11 +193,11 @@ namespace YooAsset
/// </summary>
internal string GetInfoFilePath(PackageBundle bundle)
{
if (_infoFilePathMapping.TryGetValue(bundle.BundleGUID, out string filePath) == false)
if (_infoFilePathMapping.TryGetValue(bundle.BundleGuid, out string filePath) == false)
{
string folderName = GetHashFolderName(bundle.FileHash);
filePath = PathUtility.Combine(RootPath, folderName, bundle.BundleGUID, SandboxBundleCacheConsts.BundleInfoFileName);
_infoFilePathMapping.Add(bundle.BundleGUID, filePath);
filePath = PathUtility.Combine(RootPath, folderName, bundle.BundleGuid, SandboxBundleCacheConsts.BundleInfoFileName);
_infoFilePathMapping.Add(bundle.BundleGuid, filePath);
}
return filePath;
}
@@ -201,7 +208,7 @@ namespace YooAsset
internal string GetDataTempFilePath(PackageBundle bundle)
{
string folderName = GetHashFolderName(bundle.FileHash);
return PathUtility.Combine(RootPath, folderName, bundle.BundleGUID, SandboxBundleCacheConsts.BundleDataTempFileName);
return PathUtility.Combine(RootPath, folderName, bundle.BundleGuid, SandboxBundleCacheConsts.BundleDataTempFileName);
}
/// <summary>
@@ -210,7 +217,7 @@ namespace YooAsset
internal string GetInfoTempFilePath(PackageBundle bundle)
{
string folderName = GetHashFolderName(bundle.FileHash);
return PathUtility.Combine(RootPath, folderName, bundle.BundleGUID, SandboxBundleCacheConsts.BundleInfoTempFileName);
return PathUtility.Combine(RootPath, folderName, bundle.BundleGuid, SandboxBundleCacheConsts.BundleInfoTempFileName);
}
/// <summary>
@@ -238,7 +245,7 @@ namespace YooAsset
internal void AddEntry(string bundleGUID, SandboxBundleCacheEntry cacheEntry)
{
if (_cacheEntries.ContainsKey(bundleGUID))
throw new YooInternalException($"Cache entry already exists: {bundleGUID}");
throw new YooInternalException($"Cache entry already exists: '{bundleGUID}'.");
_cacheEntries.Add(bundleGUID, cacheEntry);
SpaceOccupied += cacheEntry.GetFileSize();
@@ -262,7 +269,7 @@ namespace YooAsset
private string GetHashFolderName(string fileHash)
{
if (string.IsNullOrEmpty(fileHash))
throw new YooInternalException();
throw new YooInternalException("File hash is null or empty.");
if (fileHash.Length <= HashFolderNameLength)
return fileHash;

View File

@@ -61,7 +61,7 @@ namespace YooAsset
}
catch (Exception ex)
{
YooLogger.Error($"Failed to delete sandbox file. Error: {ex.Message}");
YooLogger.Error($"Failed to delete sandbox file. Error: {ex.Message}.");
return false;
}
}

View File

@@ -58,7 +58,7 @@ namespace YooAsset
}
catch (System.Exception ex)
{
YooLogger.Warning($"Failed to delete cache bundle folder: {ex}");
YooLogger.Warning($"Failed to delete cache bundle folder: {ex}.");
}
}
}

View File

@@ -12,11 +12,11 @@ namespace YooAsset
{
_fileCache = cache;
}
internal override void InternalStart()
protected override void InternalStart()
{
Status = EOperationStatus.Succeeded;
SetResult();
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
}
}

View File

@@ -25,11 +25,11 @@ namespace YooAsset
_fileCache = fileCache;
_options = options;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.GetEntry;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -40,8 +40,7 @@ namespace YooAsset
if (_cacheEntry == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"File cache entry not found: {_options.Bundle.BundleGUID}";
SetError($"File cache entry not found: '{_options.Bundle.BundleGuid}'.");
}
else
{
@@ -56,14 +55,14 @@ namespace YooAsset
var options = new LoadWebAssetBundleOptions();
options.CacheName = _fileCache.GetType().Name;
options.Bundle = _options.Bundle;
options.CandidateURLs = _cacheEntry.URLs;
options.CandidateUrls = _cacheEntry.URLs;
options.AssetBundleDecryptor = _fileCache.Config.AssetBundleDecryptor;
options.DownloadBackend = _fileCache.Config.DownloadBackend;
options.DownloadVerifyLevel = _fileCache.Config.DownloadVerifyLevel;
options.WatchdogTimeout = _fileCache.Config.WatchdogTimeout;
options.DisableUnityWebCache = _fileCache.Config.DisableUnityWebCache;
options.RetryPolicy = _fileCache.Config.RetryPolicy;
options.URLPolicy = _fileCache.Config.URLPolicy;
options.DownloadRetryPolicy = _fileCache.Config.DownloadRetryPolicy;
options.DownloadUrlPolicy = _fileCache.Config.DownloadUrlPolicy;
if (_options.Bundle.IsEncrypted)
_loadWebAssetBundleOp = new LoadWebEncryptedAssetBundleOperation(options);
@@ -84,24 +83,22 @@ namespace YooAsset
throw new YooInternalException("Loaded bundle handle is null.");
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
BundleHandle = _loadWebAssetBundleOp.BundleHandle;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _loadWebAssetBundleOp.Error;
SetError(_loadWebAssetBundleOp.Error);
}
}
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
if (_steps != ESteps.Done)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"{nameof(WebRemoteBundleCache)} does not support synchronous asset bundle loading.";
SetError($"{nameof(WebRemoteBundleCache)} does not support synchronous asset bundle loading.");
YooLogger.Error(Error);
}
}

View File

@@ -11,7 +11,7 @@ namespace YooAsset
/// <summary>
/// Web远端文件缓存配置
/// </summary>
internal struct CacheConfig
internal struct Configuration
{
/// <summary>
/// 看门狗超时时间
@@ -36,7 +36,7 @@ namespace YooAsset
/// <summary>
/// 远程服务接口
/// </summary>
public IRemoteServices RemoteServices { get; set; }
public IRemoteService RemoteService { get; set; }
/// <summary>
/// 下载后台接口
@@ -46,12 +46,12 @@ namespace YooAsset
/// <summary>
/// 下载重试判定策略
/// </summary>
public IDownloadRetryPolicy RetryPolicy { get; set; }
public IDownloadRetryPolicy DownloadRetryPolicy { get; set; }
/// <summary>
/// URL 选择策略
/// </summary>
public IDownloadUrlPolicy URLPolicy { get; set; }
public IDownloadUrlPolicy DownloadUrlPolicy { get; set; }
}
private readonly Dictionary<string, WebRemoteBundleCacheEntry> _cacheEntries = new Dictionary<string, WebRemoteBundleCacheEntry>(10000);
@@ -59,7 +59,7 @@ namespace YooAsset
/// <summary>
/// 缓存配置
/// </summary>
internal readonly CacheConfig Config;
internal readonly Configuration Config;
#region
/// <summary>
@@ -90,8 +90,8 @@ namespace YooAsset
/// <summary>
/// 已占用空间
/// 说明:按缓存索引累计
/// </summary>
/// <remarks>按缓存索引累计</remarks>
public long SpaceOccupied { get; private set; }
#endregion
@@ -101,36 +101,42 @@ namespace YooAsset
/// <param name="packageName">包裹名称</param>
/// <param name="rootPath">缓存根目录</param>
/// <param name="config">缓存配置</param>
public WebRemoteBundleCache(string packageName, string rootPath, CacheConfig config)
public WebRemoteBundleCache(string packageName, string rootPath, Configuration config)
{
PackageName = packageName;
RootPath = rootPath;
Config = config;
IsReadOnly = true;
}
/// <inheritdoc />
public void Dispose()
{
}
/// <inheritdoc />
public virtual BCInitializeOperation InitializeAsync()
{
var operation = new WRBCInitializeOperation(this);
return operation;
}
/// <inheritdoc />
public virtual BCWriteCacheOperation WriteCacheAsync(BCWriteCacheOptions options)
{
var operation = new BCWriteCacheCompleteOperation($"{nameof(WebRemoteBundleCache)} is readonly.");
return operation;
}
/// <inheritdoc />
public virtual BCClearCacheOperation ClearCacheAsync(BCClearCacheOptions options)
{
var operation = new BCClearCacheCompleteOperation();
return operation;
}
/// <inheritdoc />
public virtual BCVerifyCacheOperation VerifyCacheAsync(BCVerifyCacheOptions options)
{
var operation = new BCVerifyCacheCompleteOperation();
return operation;
}
/// <inheritdoc />
public virtual BCLoadBundleOperation LoadBundleAsync(BCLoadBundleOptions options)
{
if (options.Bundle.BundleType == (int)EBundleType.AssetBundle)
@@ -140,11 +146,12 @@ namespace YooAsset
}
else
{
string error = $"{nameof(WebRemoteBundleCache)} does not support bundle type: {options.Bundle.BundleType}";
string error = $"{nameof(WebRemoteBundleCache)} does not support bundle type: {options.Bundle.BundleType}.";
var operation = new BCLoadBundleErrorOperation(error);
return operation;
}
}
/// <inheritdoc />
public virtual bool IsCached(string bundleGUID)
{
return true;
@@ -156,15 +163,15 @@ namespace YooAsset
/// </summary>
internal WebRemoteBundleCacheEntry GetEntry(PackageBundle bundle)
{
if (_cacheEntries.TryGetValue(bundle.BundleGUID, out WebRemoteBundleCacheEntry entry))
if (_cacheEntries.TryGetValue(bundle.BundleGuid, out WebRemoteBundleCacheEntry entry))
{
return entry;
}
else
{
var urls = Config.RemoteServices.GetRemoteURLs(bundle.FileName);
var newEntry = new WebRemoteBundleCacheEntry(bundle.BundleGUID, urls);
_cacheEntries.Add(bundle.BundleGUID, newEntry);
var urls = Config.RemoteService.GetRemoteUrls(bundle.FileName);
var newEntry = new WebRemoteBundleCacheEntry(bundle.BundleGuid, urls);
_cacheEntries.Add(bundle.BundleGuid, newEntry);
return newEntry;
}
}

View File

@@ -22,11 +22,11 @@ namespace YooAsset
{
_fileCache = cache;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.LoadCatalog;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -55,15 +55,14 @@ namespace YooAsset
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _loadBuiltinCatalogOp.Error;
SetError(_loadBuiltinCatalogOp.Error);
}
}
if (_steps == ESteps.RecordEntry)
{
var catalog = _loadBuiltinCatalogOp.Catalog;
foreach (var fileEntry in catalog.FileEntries)
foreach (var fileEntry in catalog.Entries)
{
string filePath = PathUtility.Combine(_fileCache.RootPath, fileEntry.FileName);
var cacheEntry = new WebServerBundleCacheEntry(fileEntry.BundleGUID, filePath);
@@ -71,7 +70,7 @@ namespace YooAsset
}
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
}
}
}

View File

@@ -25,23 +25,22 @@ namespace YooAsset
_fileCache = fileCache;
_options = options;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.GetEntry;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.GetEntry)
{
_cacheEntry = _fileCache.GetEntry(_options.Bundle.BundleGUID);
_cacheEntry = _fileCache.GetEntry(_options.Bundle.BundleGuid);
if (_cacheEntry == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"File cache entry not found: {_options.Bundle.BundleGUID}";
SetError($"File cache entry not found: '{_options.Bundle.BundleGuid}'.");
}
else
{
@@ -53,18 +52,18 @@ namespace YooAsset
{
if (_loadWebAssetBundleOp == null)
{
string url = DownloadSystemTools.ToLocalFileUrl(_cacheEntry.FilePath);
string url = DownloadUrlHelper.ToLocalFileUrl(_cacheEntry.FilePath);
var options = new LoadWebAssetBundleOptions();
options.CacheName = _fileCache.GetType().Name;
options.Bundle = _options.Bundle;
options.CandidateURLs = new[] { url };
options.CandidateUrls = new[] { url };
options.AssetBundleDecryptor = _fileCache.Config.AssetBundleDecryptor;
options.DownloadBackend = _fileCache.Config.DownloadBackend;
options.DownloadVerifyLevel = _fileCache.Config.DownloadVerifyLevel;
options.WatchdogTimeout = _fileCache.Config.WatchdogTimeout;
options.DisableUnityWebCache = _fileCache.Config.DisableUnityWebCache;
options.RetryPolicy = _fileCache.Config.RetryPolicy;
options.URLPolicy = _fileCache.Config.URLPolicy;
options.DownloadRetryPolicy = _fileCache.Config.DownloadRetryPolicy;
options.DownloadUrlPolicy = _fileCache.Config.DownloadUrlPolicy;
if (_options.Bundle.IsEncrypted)
_loadWebAssetBundleOp = new LoadWebEncryptedAssetBundleOperation(options);
@@ -85,24 +84,22 @@ namespace YooAsset
throw new YooInternalException("Loaded bundle handle is null.");
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
BundleHandle = _loadWebAssetBundleOp.BundleHandle;
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = _loadWebAssetBundleOp.Error;
SetError(_loadWebAssetBundleOp.Error);
}
}
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
if (_steps != ESteps.Done)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"{nameof(WebServerBundleCache)} does not support synchronous asset bundle loading.";
SetError($"{nameof(WebServerBundleCache)} does not support synchronous asset bundle loading.");
YooLogger.Error(Error);
}
}

View File

@@ -11,7 +11,7 @@ namespace YooAsset
/// <summary>
/// Web服务器文件缓存配置
/// </summary>
internal struct CacheConfig
internal struct Configuration
{
/// <summary>
/// 看门狗超时时间
@@ -41,12 +41,12 @@ namespace YooAsset
/// <summary>
/// 下载重试判定策略
/// </summary>
public IDownloadRetryPolicy RetryPolicy { get; set; }
public IDownloadRetryPolicy DownloadRetryPolicy { get; set; }
/// <summary>
/// URL 选择策略
/// </summary>
public IDownloadUrlPolicy URLPolicy { get; set; }
public IDownloadUrlPolicy DownloadUrlPolicy { get; set; }
}
private readonly Dictionary<string, WebServerBundleCacheEntry> _cacheEntries = new Dictionary<string, WebServerBundleCacheEntry>(10000);
@@ -54,7 +54,7 @@ namespace YooAsset
/// <summary>
/// 缓存配置
/// </summary>
internal readonly CacheConfig Config;
internal readonly Configuration Config;
#region
/// <summary>
@@ -85,8 +85,8 @@ namespace YooAsset
/// <summary>
/// 已占用空间
/// 说明:按缓存索引累计
/// </summary>
/// <remarks>按缓存索引累计</remarks>
public long SpaceOccupied { get; private set; }
#endregion
@@ -96,36 +96,42 @@ namespace YooAsset
/// <param name="packageName">包裹名称</param>
/// <param name="rootPath">缓存根目录</param>
/// <param name="config">缓存配置</param>
public WebServerBundleCache(string packageName, string rootPath, CacheConfig config)
public WebServerBundleCache(string packageName, string rootPath, Configuration config)
{
PackageName = packageName;
RootPath = rootPath;
Config = config;
IsReadOnly = true;
}
/// <inheritdoc />
public void Dispose()
{
}
/// <inheritdoc />
public virtual BCInitializeOperation InitializeAsync()
{
var operation = new WSBCInitializeOperation(this);
return operation;
}
/// <inheritdoc />
public virtual BCWriteCacheOperation WriteCacheAsync(BCWriteCacheOptions options)
{
var operation = new BCWriteCacheCompleteOperation($"{nameof(WebServerBundleCache)} is readonly.");
return operation;
}
/// <inheritdoc />
public virtual BCClearCacheOperation ClearCacheAsync(BCClearCacheOptions options)
{
var operation = new BCClearCacheCompleteOperation();
return operation;
}
/// <inheritdoc />
public virtual BCVerifyCacheOperation VerifyCacheAsync(BCVerifyCacheOptions options)
{
var operation = new BCVerifyCacheCompleteOperation();
return operation;
}
/// <inheritdoc />
public virtual BCLoadBundleOperation LoadBundleAsync(BCLoadBundleOptions options)
{
if (options.Bundle.BundleType == (int)EBundleType.AssetBundle)
@@ -135,11 +141,12 @@ namespace YooAsset
}
else
{
string error = $"{nameof(WebServerBundleCache)} does not support bundle type: {options.Bundle.BundleType}";
string error = $"{nameof(WebServerBundleCache)} does not support bundle type: {options.Bundle.BundleType}.";
var operation = new BCLoadBundleErrorOperation(error);
return operation;
}
}
/// <inheritdoc />
public virtual bool IsCached(string bundleGUID)
{
return _cacheEntries.ContainsKey(bundleGUID);
@@ -163,7 +170,7 @@ namespace YooAsset
internal void AddEntry(string bundleGUID, WebServerBundleCacheEntry cacheEntry)
{
if (_cacheEntries.ContainsKey(bundleGUID))
throw new YooInternalException($"Cache entry already exists: {bundleGUID}");
throw new YooInternalException($"Cache entry already exists: '{bundleGUID}'.");
_cacheEntries.Add(bundleGUID, cacheEntry);
}

View File

@@ -28,11 +28,11 @@ namespace YooAsset
_assetBundle = assetBundle;
_assetInfo = assetInfo;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.CheckBundle;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -42,8 +42,7 @@ namespace YooAsset
if (_assetBundle == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"The bundle {_packageBundle.BundleName} has been destroyed due to Unity engine bugs.";
SetError($"The bundle {_packageBundle.BundleName} has been destroyed due to Unity engine bugs.");
return;
}
@@ -99,18 +98,17 @@ namespace YooAsset
error = $"Failed to load all assets: {_assetInfo.AssetPath} AssetType: {_assetInfo.AssetType} AssetBundle: {_packageBundle.BundleName}";
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = error;
SetError(error);
YooLogger.Error(error);
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
}
}
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
ExecuteBatch();
}

View File

@@ -28,11 +28,11 @@ namespace YooAsset
_assetBundle = assetBundle;
_assetInfo = assetInfo;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.CheckBundle;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -42,8 +42,7 @@ namespace YooAsset
if (_assetBundle == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"The bundle {_packageBundle.BundleName} has been destroyed due to Unity engine bugs.";
SetError($"The bundle {_packageBundle.BundleName} has been destroyed due to Unity engine bugs.");
return;
}
@@ -99,18 +98,17 @@ namespace YooAsset
error = $"Failed to load asset: {_assetInfo.AssetPath} AssetType: {_assetInfo.AssetType} AssetBundle: {_packageBundle.BundleName}";
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = error;
SetError(error);
YooLogger.Error(Error);
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
}
}
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
ExecuteBatch();
}

View File

@@ -28,11 +28,11 @@ namespace YooAsset
_loadSceneParams = loadSceneParams;
_allowSceneActivation = allowSceneActivation;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.LoadScene;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -60,8 +60,7 @@ namespace YooAsset
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Failed to load scene: {_assetInfo.AssetPath}";
SetError($"Failed to load scene: {_assetInfo.AssetPath}");
YooLogger.Error(Error);
}
}
@@ -94,18 +93,17 @@ namespace YooAsset
if (Result.IsValid())
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"The loaded scene is invalid: {_assetInfo.AssetPath}";
SetError($"The loaded scene is invalid: {_assetInfo.AssetPath}");
YooLogger.Error(Error);
}
}
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
//注意:场景加载不支持异步转同步,为了支持同步加载方法需要实现该方法!
ExecuteOnce();

View File

@@ -28,11 +28,11 @@ namespace YooAsset
_assetBundle = assetBundle;
_assetInfo = assetInfo;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.CheckBundle;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -42,8 +42,7 @@ namespace YooAsset
if (_assetBundle == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"The bundle {_packageBundle.BundleName} has been destroyed due to Unity engine bugs.";
SetError($"The bundle {_packageBundle.BundleName} has been destroyed due to Unity engine bugs.");
return;
}
@@ -99,18 +98,17 @@ namespace YooAsset
error = $"Failed to load sub-assets: {_assetInfo.AssetPath} AssetType: {_assetInfo.AssetType} AssetBundle: {_packageBundle.BundleName}";
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = error;
SetError(error);
YooLogger.Error(error);
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
}
}
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
ExecuteBatch();
}

View File

@@ -6,12 +6,11 @@ namespace YooAsset
/// </summary>
internal class RBHLoadAllAssetsOperation : BHLoadAllAssetsOperation
{
internal override void InternalStart()
protected override void InternalStart()
{
Status = EOperationStatus.Failed;
Error = $"{nameof(RBHLoadAllAssetsOperation)} does not support loading all assets.";
SetError($"{nameof(RBHLoadAllAssetsOperation)} does not support loading all assets.");
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
}
}

View File

@@ -25,11 +25,11 @@ namespace YooAsset
_rawBundle = rawBundle;
_assetInfo = assetInfo;
}
internal override void InternalStart()
protected override void InternalStart()
{
_steps = ESteps.LoadObject;
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
@@ -45,14 +45,13 @@ namespace YooAsset
if (Result == null)
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Failed to load raw file object: {_assetInfo.AssetPath}";
SetError($"Failed to load raw file object: {_assetInfo.AssetPath}");
YooLogger.Error(Error);
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
}
}
}

View File

@@ -6,12 +6,11 @@ namespace YooAsset
/// </summary>
internal class RBHLoadSceneOperation : BHLoadSceneOperation
{
internal override void InternalStart()
protected override void InternalStart()
{
Status = EOperationStatus.Failed;
Error = $"{nameof(RBHLoadSceneOperation)} does not support scene loading.";
SetError($"{nameof(RBHLoadSceneOperation)} does not support scene loading.");
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
}
public override void AllowSceneActivation()

View File

@@ -6,12 +6,11 @@ namespace YooAsset
/// </summary>
internal class RBHLoadSubAssetsOperation : BHLoadSubAssetsOperation
{
internal override void InternalStart()
protected override void InternalStart()
{
Status = EOperationStatus.Failed;
Error = $"{nameof(RBHLoadSubAssetsOperation)} does not support loading sub-assets.";
SetError($"{nameof(RBHLoadSubAssetsOperation)} does not support loading sub-assets.");
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
}
}

View File

@@ -25,17 +25,16 @@ namespace YooAsset
_packageBundle = packageBundle;
_assetInfo = assetInfo;
}
internal override void InternalStart()
protected override void InternalStart()
{
#if UNITY_EDITOR
_steps = ESteps.CheckBundle;
#else
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"{nameof(VBHLoadAllAssetsOperation)} is only supported in the Unity Editor.";
SetError($"{nameof(VBHLoadAllAssetsOperation)} is only supported in the Unity Editor.");
#endif
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
#if UNITY_EDITOR
if (_steps == ESteps.None || _steps == ESteps.Done)
@@ -48,8 +47,7 @@ namespace YooAsset
if (string.IsNullOrEmpty(guid))
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Asset not found: {_assetInfo.AssetPath}";
SetError($"Asset not found: {_assetInfo.AssetPath}");
YooLogger.Error(Error);
return;
}
@@ -62,7 +60,7 @@ namespace YooAsset
if (_assetInfo.AssetType == null)
{
List<UnityEngine.Object> result = new List<UnityEngine.Object>();
foreach (var packageAsset in _packageBundle.IncludeMainAssets)
foreach (var packageAsset in _packageBundle.MainAssets)
{
string assetPath = packageAsset.AssetPath;
UnityEngine.Object mainAsset = UnityEditor.AssetDatabase.LoadMainAssetAtPath(assetPath);
@@ -74,7 +72,7 @@ namespace YooAsset
else
{
List<UnityEngine.Object> result = new List<UnityEngine.Object>();
foreach (var packageAsset in _packageBundle.IncludeMainAssets)
foreach (var packageAsset in _packageBundle.MainAssets)
{
string assetPath = packageAsset.AssetPath;
UnityEngine.Object mainAsset = UnityEditor.AssetDatabase.LoadAssetAtPath(assetPath, _assetInfo.AssetType);
@@ -97,19 +95,18 @@ namespace YooAsset
error = $"Failed to load all assets: {_assetInfo.AssetPath} AssetType: {_assetInfo.AssetType}";
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = error;
SetError(error);
YooLogger.Error(error);
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
}
}
#endif
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
ExecuteBatch();
}

View File

@@ -24,17 +24,16 @@ namespace YooAsset
_packageBundle = packageBundle;
_assetInfo = assetInfo;
}
internal override void InternalStart()
protected override void InternalStart()
{
#if UNITY_EDITOR
_steps = ESteps.CheckBundle;
#else
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"{nameof(VBHLoadAssetOperation)} is only supported in the Unity Editor.";
SetError($"{nameof(VBHLoadAssetOperation)} is only supported in the Unity Editor.");
#endif
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
#if UNITY_EDITOR
if (_steps == ESteps.None || _steps == ESteps.Done)
@@ -47,8 +46,7 @@ namespace YooAsset
if (string.IsNullOrEmpty(guid))
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Asset not found: {_assetInfo.AssetPath}";
SetError($"Asset not found: {_assetInfo.AssetPath}");
YooLogger.Error(Error);
return;
}
@@ -76,19 +74,18 @@ namespace YooAsset
error = $"Failed to load asset object: {_assetInfo.AssetPath} AssetType: {_assetInfo.AssetType}";
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = error;
SetError(error);
YooLogger.Error(error);
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
}
}
#endif
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
ExecuteBatch();
}

View File

@@ -28,17 +28,16 @@ namespace YooAsset
_loadParams = loadParams;
_allowSceneActivation = allowSceneActivation;
}
internal override void InternalStart()
protected override void InternalStart()
{
#if UNITY_EDITOR
_steps = ESteps.LoadScene;
#else
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"{nameof(VBHLoadSceneOperation)} is only supported in the Unity Editor.";
SetError($"{nameof(VBHLoadSceneOperation)} is only supported in the Unity Editor.");
#endif
}
internal override void InternalUpdate()
protected override void InternalUpdate()
{
#if UNITY_EDITOR
if (_steps == ESteps.None || _steps == ESteps.Done)
@@ -64,8 +63,7 @@ namespace YooAsset
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"Failed to load scene: {_assetInfo.AssetPath}";
SetError($"Failed to load scene: {_assetInfo.AssetPath}");
YooLogger.Error(Error);
return;
}
@@ -99,19 +97,18 @@ namespace YooAsset
if (Result.IsValid())
{
_steps = ESteps.Done;
Status = EOperationStatus.Succeeded;
SetResult();
}
else
{
_steps = ESteps.Done;
Status = EOperationStatus.Failed;
Error = $"The loaded scene is invalid: {_assetInfo.AssetPath}";
SetError($"The loaded scene is invalid: {_assetInfo.AssetPath}");
YooLogger.Error(Error);
}
}
#endif
}
internal override void InternalWaitForCompletion()
protected override void InternalWaitForCompletion()
{
//注意:场景加载不支持异步转同步,为了支持同步加载方法需要实现该方法!
ExecuteOnce();

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