diff --git a/docs/zh-CN/source-generators/context-get-generator.md b/docs/zh-CN/source-generators/context-get-generator.md new file mode 100644 index 0000000..6f370d3 --- /dev/null +++ b/docs/zh-CN/source-generators/context-get-generator.md @@ -0,0 +1,714 @@ +# Context Get 注入生成器 + +> 自动注入架构组件,消除样板代码 + +Context Get 注入生成器提供自动注入架构组件的能力,通过源代码生成器在编译时自动生成依赖注入代码,无需手动调用 +GetModel/GetSystem 等方法。 + +## 概述 + +### 核心功能 + +- **自动注入**:自动注入 Model、System、Utility 和 Service 实例 +- **集合注入**:支持注入多个同类型组件的集合 +- **智能推断**:`[GetAll]` 特性可自动识别字段类型并注入 +- **零运行时开销**:编译时生成代码,无反射或动态调用 + +### 与 ContextAware 的关系 + +Context Get 注入依赖 `[ContextAware]` 特性提供的上下文访问能力。使用注入特性前,类必须: + +- 标记 `[ContextAware]` 特性,或 +- 实现 `IContextAware` 接口,或 +- 继承 `ContextAwareBase` 类 + +## 注入特性 + +### 支持的9个特性 + +| 特性 | 应用目标 | 功能描述 | 类型约束 | +|------------------|------|------------------|------------------------------------| +| `[GetModel]` | 字段 | 注入单个 Model 实例 | 必须实现 IModel | +| `[GetModels]` | 字段 | 注入 Model 集合 | IReadOnlyList\ | +| `[GetSystem]` | 字段 | 注入单个 System 实例 | 必须实现 ISystem | +| `[GetSystems]` | 字段 | 注入 System 集合 | IReadOnlyList\ | +| `[GetUtility]` | 字段 | 注入单个 Utility 实例 | 必须实现 IUtility | +| `[GetUtilities]` | 字段 | 注入 Utility 集合 | IReadOnlyList\ | +| `[GetService]` | 字段 | 注入单个服务实例 | 必须是引用类型 | +| `[GetServices]` | 字段 | 注入服务集合 | IReadOnlyList\ where T : class | +| `[GetAll]` | 类 | 自动推断并注入所有符合类型的字段 | 智能推断 | + +## 基础使用 + +### 单实例注入 + +使用 `[GetModel]`、`[GetSystem]`、`[GetUtility]` 和 `[GetService]` 注入单个实例: + +```csharp +using GFramework.SourceGenerators.Abstractions.Rule; +using GFramework.Core.Abstractions.Model; +using GFramework.Core.Abstractions.Systems; +using GFramework.Core.Abstractions.Utility; + +[ContextAware] +public partial class PlayerController +{ + [GetModel] + private IPlayerModel _playerModel = null!; + + [GetSystem] + private ICombatSystem _combatSystem = null!; + + [GetUtility] + private IPathFinder _pathFinder = null!; + + [GetService] + private IAudioService _audioService = null!; + + public void Initialize() + { + // 字段已自动注入,可直接使用 + _combatSystem.Attack(_playerModel); + } +} +``` + +### 集合注入 + +使用 `[GetModels]`、`[GetSystems]`、`[GetUtilities]` 和 `[GetServices]` 注入多个实例: + +```csharp +using GFramework.SourceGenerators.Abstractions.Rule; + +[ContextAware] +public partial class StrategyManager +{ + [GetModels] + private IReadOnlyList _playerModels = null!; + + [GetSystems] + private IReadOnlyList _gameSystems = null!; + + public void ProcessAll() + { + foreach (var system in _gameSystems) + { + system.Initialize(); + } + } +} +``` + +### 智能推断注入 `[GetAll]` + +`[GetAll]` 特性标记在类上,自动推断所有符合类型的字段并注入: + +```csharp +using GFramework.SourceGenerators.Abstractions.Rule; + +[ContextAware] +[GetAll] +public partial class GameManager +{ + // 自动推断为 Model 注入 + private IPlayerModel _playerModel = null!; + + // 自动推断为 System 注入 + private ICombatSystem _combatSystem = null!; + + // 自动推断为 Utility 注入 + private IPathFinder _pathFinder = null!; + + // 自动推断为集合注入 + private IReadOnlyList _strategies = null!; + + // Service 不会被自动推断(需要显式标记) + [GetService] + private IExternalService _externalService = null!; +} +``` + +## 生成代码解析 + +### 注入方法生成 + +生成器会为每个注入字段生成一个私有方法: + +```csharp +// +#nullable enable + +using GFramework.Core.Extensions; + +namespace YourNamespace; + +partial class PlayerController +{ + private void __InjectContextBindings_Generated() + { + _playerModel = this.GetModel(); + _combatSystem = this.GetSystem(); + _pathFinder = this.GetUtility(); + _audioService = this.GetService(); + } +} +``` + +### 调用时机 + +注入方法需要在适当的时机手动调用: + +```csharp +[ContextAware] +[GetAll] +public partial class GameController +{ + private IPlayerModel _player = null!; + private ICombatSystem _combat = null!; + + public GameController() + { + // 在构造函数后调用注入方法 + __InjectContextBindings_Generated(); + } + + public void Initialize() + { + // 此时字段已注入,可以安全使用 + _combat.Initialize(_player); + } +} +``` + +## 智能推断规则 + +### 自动推断的类型 + +`[GetAll]` 会自动识别并注入以下类型: + +| 字段类型 | 注入方式 | 生成代码示例 | +|---------------------------|-------|--------------------------| +| IModel 及其子类型 | 单实例注入 | `this.GetModel()` | +| ISystem 及其子类型 | 单实例注入 | `this.GetSystem()` | +| IUtility 及其子类型 | 单实例注入 | `this.GetUtility()` | +| IReadOnlyList\ | 集合注入 | `this.GetModels()` | +| IReadOnlyList\ | 集合注入 | `this.GetSystems()` | +| IReadOnlyList\ | 集合注入 | `this.GetUtilities()` | + +### 不自动推断的类型 + +以下类型不会自动推断,需要显式标记特性: + +- **Service 类型**:任意引用类型太宽泛,需要使用 `[GetService]` 或 `[GetServices]` 显式标记 +- **Godot.Node 及其子类**:避免与 Godot 引擎的节点注入冲突 + +```csharp +[ContextAware] +[GetAll] +public partial class GameNode : Node +{ + // ✅ 自动注入 + private IPlayerModel _model = null!; + + // ✅ 自动注入 + private IGameSystem _system = null!; + + // ❌ 不会自动注入(Service 需要显式标记) + [GetService] + private IAudioService _audio = null!; + + // ❌ 不会自动注入(Godot.Node 被排除) + private Node _otherNode = null!; +} +``` + +## 使用场景 + +### 控制器依赖注入 + +```csharp +using GFramework.Core.Abstractions.Controller; +using GFramework.SourceGenerators.Abstractions.Rule; + +[ContextAware] +[GetAll] +public partial class GameController : IController +{ + private IPlayerModel _player = null!; + private IEnemyModel _enemy = null!; + private ICombatSystem _combat = null!; + private IAudioSystem _audio = null!; + + public void Initialize() + { + _combat.Initialize(_player, _enemy); + } + + public void Attack() + { + _combat.Attack(_player, _enemy); + _audio.PlayAttackSound(); + } +} +``` + +### 策略模式实现 + +```csharp +[ContextAware] +public partial class StrategyManager +{ + [GetServices] + private IReadOnlyList _strategies = null!; + + public IStrategy SelectBestStrategy() + { + return _strategies.FirstOrDefault(s => s.CanExecute()) + ?? throw new InvalidOperationException("No strategy available"); + } +} +``` + +### Godot 节点集成 + +```csharp +using Godot; +using GFramework.SourceGenerators.Abstractions.Rule; + +[ContextAware] +[GetAll] +public partial class PlayerNode : Node +{ + private IPlayerModel _model = null!; + private IMovementSystem _movement = null!; + + public override void _Ready() + { + __InjectContextBindings_Generated(); + _movement.Initialize(_model); + } +} +``` + +## 诊断信息 + +### GF_ContextGet_001 - 不支持嵌套类 + +**错误信息**:`Class '{ClassName}' cannot use context Get injection inside a nested type` + +**场景**:在嵌套类中使用注入特性 + +```csharp +[ContextAware] +public partial class OuterClass +{ + [GetModel] // ❌ 错误 + private IModel _model = null!; + + public partial class InnerClass // 嵌套类 + { + [GetModel] + private IModel _innerModel = null!; + } +} +``` + +**解决方案**:避免在嵌套类中使用注入特性,将其提取为独立的类 + +### GF_ContextGet_002 - 不支持静态字段 + +**错误信息**:`Field '{FieldName}' cannot be static when using generated context Get injection` + +**场景**:标记静态字段进行注入 + +```csharp +[ContextAware] +public partial class GameController +{ + [GetModel] + private static IPlayerModel _playerModel = null!; // ❌ 错误 +} +``` + +**解决方案**:改为实例字段 + +```csharp +[ContextAware] +public partial class GameController +{ + [GetModel] + private IPlayerModel _playerModel = null!; // ✅ 正确 +} +``` + +### GF_ContextGet_003 - 不支持只读字段 + +**错误信息**:`Field '{FieldName}' cannot be readonly when using generated context Get injection` + +**场景**:标记只读字段进行注入 + +```csharp +[ContextAware] +public partial class GameController +{ + [GetModel] + private readonly IPlayerModel _playerModel = null!; // ❌ 错误 +} +``` + +**解决方案**:移除 `readonly` 关键字 + +```csharp +[ContextAware] +public partial class GameController +{ + [GetModel] + private IPlayerModel _playerModel = null!; // ✅ 正确 +} +``` + +### GF_ContextGet_004 - 字段类型不匹配 + +**错误信息**:`Field '{FieldName}' type '{FieldType}' is not valid for [{AttributeName}]` + +**场景**:字段类型与注入特性要求的类型不匹配 + +```csharp +[ContextAware] +public partial class GameController +{ + [GetModel] + private ISystem _system = null!; // ❌ 错误:ISystem 不实现 IModel +} +``` + +**解决方案**:确保字段类型符合特性要求 + +```csharp +[ContextAware] +public partial class GameController +{ + [GetModel] + private IPlayerModel _model = null!; // ✅ 正确 + + [GetSystem] + private ISystem _system = null!; // ✅ 正确 +} +``` + +### GF_ContextGet_005 - 必须是上下文感知类型 + +**错误信息**:`Class '{ClassName}' must be context-aware to use generated context Get injection` + +**场景**:类未标记 `[ContextAware]` 或未实现 `IContextAware` + +```csharp +public partial class GameController // ❌ 缺少 [ContextAware] +{ + [GetModel] + private IPlayerModel _model = null!; +} +``` + +**解决方案**:添加 `[ContextAware]` 特性或实现 `IContextAware` 接口 + +```csharp +[ContextAware] // ✅ 正确 +public partial class GameController +{ + [GetModel] + private IPlayerModel _model = null!; +} +``` + +### GF_ContextGet_006 - 不支持多个注入特性 + +**错误信息**:`Field '{FieldName}' cannot declare multiple generated context Get attributes` + +**场景**:同一字段标记了多个注入特性 + +```csharp +[ContextAware] +public partial class GameController +{ + [GetModel] + [GetSystem] // ❌ 错误:多个特性冲突 + private IPlayerModel _model = null!; +} +``` + +**解决方案**:每个字段只标记一个注入特性 + +```csharp +[ContextAware] +public partial class GameController +{ + [GetModel] + private IPlayerModel _model = null!; // ✅ 正确 + + [GetSystem] + private ISystem _system = null!; // ✅ 正确 +} +``` + +## 最佳实践 + +### 1. 优先使用 [GetAll] + +使用 `[GetAll]` 可以减少特性标记,保持代码简洁: + +```csharp +// ✅ 推荐:简洁 +[ContextAware] +[GetAll] +public partial class Controller +{ + private ICoreModel _core = null!; + private ICoreSystem _system = null!; + private ICoreUtility _utility = null!; +} + +// ❌ 不推荐:冗余 +[ContextAware] +public partial class Controller +{ + [GetModel] + private ICoreModel _core = null!; + + [GetSystem] + private ICoreSystem _system = null!; + + [GetUtility] + private ICoreUtility _utility = null!; +} +``` + +### 2. 为字段提供默认值 + +使用 `= null!` 或其他默认值避免编译警告: + +```csharp +[ContextAware] +[GetAll] +public partial class Controller +{ + // ✅ 推荐:明确提供默认值 + private IPlayerModel _player = null!; + + // ❌ 不推荐:会产生编译警告 + private IPlayerModel _player; +} +``` + +### 3. 避免过度注入 + +只注入真正需要的依赖,保持类的职责单一: + +```csharp +// ✅ 推荐:只注入必需的依赖 +[ContextAware] +[GetAll] +public partial class CombatController +{ + private ICombatModel _combat = null!; + private ICombatSystem _system = null!; +} + +// ❌ 不推荐:注入过多依赖 +[ContextAware] +[GetAll] +public partial class MegaController +{ + private IModel1 _m1 = null!; + private IModel2 _m2 = null!; + private IModel3 _m3 = null!; + // ... 10+ 个依赖,职责不清 +} +``` + +### 4. 显式标记 Service 注入 + +Service 类型不会自动推断,需要显式标记以保持意图清晰: + +```csharp +[ContextAware] +[GetAll] +public partial class ServiceManager +{ + // 自动推断 + private IPlayerModel _model = null!; + private IGameSystem _system = null!; + + // Service 必须显式标记 + [GetService] + private IExternalService _external = null!; +} +``` + +### 5. 在构造函数或初始化方法中调用注入 + +确保在使用字段前调用注入方法: + +```csharp +[ContextAware] +[GetAll] +public partial class GameController +{ + private IPlayerModel _player = null!; + + public GameController() + { + // ✅ 在构造函数中调用 + __InjectContextBindings_Generated(); + } + + public void Initialize() + { + // ✅ 此时字段已注入 + _player.Reset(); + } +} +``` + +## 高级场景 + +### 泛型类型支持 + +注入支持泛型类型字段: + +```csharp +[ContextAware] +public partial class GenericController where TModel : IModel +{ + [GetModel] + private TModel _model = null!; +} +``` + +### 接口与实现类型 + +字段类型应使用接口而非具体实现: + +```csharp +[ContextAware] +[GetAll] +public partial class Controller +{ + // ✅ 推荐:使用接口类型 + private IPlayerModel _player = null!; + + // ❌ 不推荐:使用具体实现类型 + private PlayerModel _player = null!; +} +``` + +### 多架构场景 + +在多架构场景中,可以通过 `SetContextProvider` 切换架构: + +```csharp +[ContextAware] +[GetAll] +public partial class GameController +{ + private IPlayerModel _player = null!; + + public static void SetArchitecture(IArchitecture architecture) + { + // 切换架构提供者 + SetContextProvider(new CustomContextProvider(architecture)); + } +} +``` + +## 常见问题 + +### Q: 为什么 Service 不会自动推断? + +**A**: Service 可以是任意引用类型,自动推断可能导致误注入。显式标记确保意图清晰,避免意外行为。 + +### Q: 可以在构造函数中使用注入的字段吗? + +**A**: 不可以。注入方法需要在构造函数中调用,字段在调用后才被注入。推荐在单独的初始化方法中使用。 + +```csharp +[ContextAware] +[GetAll] +public partial class Controller +{ + private IPlayerModel _player = null!; + + public Controller() + { + __InjectContextBindings_Generated(); + // ❌ 不推荐:在这里使用 _player + } + + public void Initialize() + { + // ✅ 推荐:在初始化方法中使用 + _player.Reset(); + } +} +``` + +### Q: GetAll 会注入所有字段吗? + +**A**: 不会。`[GetAll]` 只注入符合类型约束的字段: + +- 实现 IModel、ISystem、IUtility 接口的类型 +- 上述类型的 `IReadOnlyList` 集合 +- 排除 Godot.Node 类型 +- 排除 Service 类型 + +### Q: 集合注入可以是 List\ 吗? + +**A**: 不可以。集合注入只支持 `IReadOnlyList` 类型,这是为了保证注入集合的不可变性。 + +```csharp +[ContextAware] +public partial class Controller +{ + // ✅ 正确 + [GetModels] + private IReadOnlyList _players = null!; + + // ❌ 错误:不支持 + [GetModels] + private List _players = null!; +} +``` + +### Q: 如何在测试中模拟注入? + +**A**: 配合 `[ContextAware]` 的 `SetContextProvider` 方法,使用测试架构: + +```csharp +[Test] +public void TestController() +{ + var testArchitecture = new TestArchitecture(); + testArchitecture.RegisterModel(new MockPlayerModel()); + + GameController.SetContextProvider(new TestContextProvider(testArchitecture)); + + try + { + var controller = new GameController(); + controller.__InjectContextBindings_Generated(); + controller.Initialize(); + // 测试逻辑... + } + finally + { + GameController.ResetContextProvider(); + } +} +``` + +## 相关文档 + +- [Source Generators 概述](./index.md) +- [ContextAware 生成器](./context-aware-generator.md) +- [架构组件](../core/architecture.md) +- [依赖注入](../core/ioc.md) diff --git a/docs/zh-CN/source-generators/enum-generator.md b/docs/zh-CN/source-generators/enum-generator.md index 892aaf2..26c0f95 100644 --- a/docs/zh-CN/source-generators/enum-generator.md +++ b/docs/zh-CN/source-generators/enum-generator.md @@ -86,6 +86,42 @@ public enum GameState } ``` +### 配置选项说明 + +#### 实际支持的选项 + +当前版本只支持以下配置选项: + +| 参数 | 类型 | 默认值 | 说明 | +|--------------------|------|------|-------------------| +| GenerateIsMethods | bool | true | 是否为每个枚举值生成 IsX 方法 | +| GenerateIsInMethod | bool | true | 是否生成 IsIn 方法 | + +#### 未实现的选项 + +以下选项在属性定义中存在,但生成器当前版本**未实现**: + +- `GenerateHasMethod`:未实现 HasX 方法生成 +- `IncludeToString`:未实现 ToString 扩展方法 + +```csharp +// ❌ 以下选项不会生效 +[GenerateEnumExtensions( + GenerateIsMethods = true, + GenerateIsInMethod = true, + GenerateHasMethod = true, // 未实现,参数会被忽略 + IncludeToString = true // 未实现,参数会被忽略 +)] +public enum GameState +{ + Normal, + Paused, + GameOver +} +``` + +**注意**:这些选项计划在后续版本中实现,届时会更新文档说明。 + ### 只生成 IsX 方法 ```csharp diff --git a/docs/zh-CN/source-generators/index.md b/docs/zh-CN/source-generators/index.md index 1614441..9db2434 100644 --- a/docs/zh-CN/source-generators/index.md +++ b/docs/zh-CN/source-generators/index.md @@ -12,6 +12,8 @@ GFramework.SourceGenerators 是 GFramework 框架的源代码生成器包,通 - [Log 属性生成器](#log-属性生成器) - [ContextAware 属性生成器](#contextaware-属性生成器) - [GenerateEnumExtensions 属性生成器](#generateenumextensions-属性生成器) +- [Priority 属性生成器](#priority-属性生成器) +- [Context Get 注入生成器](#context-get-注入生成器) - [诊断信息](#诊断信息) - [性能优势](#性能优势) - [使用示例](#使用示例) @@ -36,6 +38,8 @@ GFramework.SourceGenerators 利用 Roslyn 源代码生成器技术,在编译 - **[Log] 属性**:自动生成 ILogger 字段和日志方法 - **[ContextAware] 属性**:自动实现 IContextAware 接口 - **[GenerateEnumExtensions] 属性**:自动生成枚举扩展方法 +- **[Priority] 属性**:自动实现 IPrioritized 接口,为类添加优先级标记 +- **Context Get 注入特性**:自动注入架构组件(GetModel/GetSystem/GetUtility/GetService/GetAll) ### 🔧 高级特性 @@ -43,6 +47,8 @@ GFramework.SourceGenerators 利用 Roslyn 源代码生成器技术,在编译 - **增量编译**:只生成修改过的代码,提高编译速度 - **命名空间控制**:灵活控制生成代码的命名空间 - **可访问性控制**:支持不同的访问修饰符 +- **智能推断注入**:[GetAll] 自动推断字段类型并注入架构组件 +- **优先级排序建议**:分析器自动建议使用 GetAllByPriority 确保正确排序 ## 安装配置 @@ -453,6 +459,313 @@ public enum ConflictEnum } ``` +### Priority 生成器诊断 + +#### GF_Priority_001 - Priority 只能应用于类 + +**错误信息**: `Priority attribute can only be applied to classes` + +**场景**:将 `[Priority]` 特性应用于非类类型 + +```csharp +[Priority(10)] +public interface IMyInterface // ❌ 错误 +{ +} + +[Priority(10)] +public struct MyStruct // ❌ 错误 +{ +} +``` + +**解决方案**:只在类上使用 `[Priority]` 特性 + +```csharp +[Priority(10)] +public partial class MyClass // ✅ 正确 +{ +} +``` + +#### GF_Priority_002 - 已实现 IPrioritized 接口 + +**错误信息**: `Type already implements IPrioritized interface` + +**场景**:类已手动实现 `IPrioritized` 接口,同时使用了 `[Priority]` 特性 + +```csharp +[Priority(10)] +public partial class MyClass : IPrioritized // ❌ 冲突 +{ + public int Priority => 10; +} +``` + +**解决方案**:移除 `[Priority]` 特性或移除手动实现 + +```csharp +// 方案1:移除特性 +public partial class MyClass : IPrioritized +{ + public int Priority => 10; +} + +// 方案2:移除手动实现 +[Priority(10)] +public partial class MyClass +{ + // 自动生成 IPrioritized 实现 +} +``` + +#### GF_Priority_003 - 必须声明为 partial + +**错误信息**: `Class must be declared as partial when using Priority attribute` + +**场景**:使用 `[Priority]` 特性的类未声明为 `partial` + +```csharp +[Priority(10)] +public class MyClass // ❌ 缺少 partial +{ +} +``` + +**解决方案**:添加 `partial` 关键字 + +```csharp +[Priority(10)] +public partial class MyClass // ✅ 正确 +{ +} +``` + +#### GF_Priority_004 - 优先级值无效 + +**错误信息**: `Priority value is invalid` + +**场景**:`[Priority]` 特性未提供有效的优先级值 + +```csharp +[Priority] // ❌ 缺少参数 +public partial class MyClass +{ +} +``` + +**解决方案**:提供有效的优先级值 + +```csharp +[Priority(10)] // ✅ 正确 +public partial class MyClass +{ +} +``` + +#### GF_Priority_005 - 不支持嵌套类 + +**错误信息**: `Nested class is not supported` + +**场景**:在嵌套类中使用 `[Priority]` 特性 + +```csharp +public partial class OuterClass +{ + [Priority(10)] + public partial class InnerClass // ❌ 错误 + { + } +} +``` + +**解决方案**:将嵌套类提取为独立的类 + +```csharp +[Priority(10)] +public partial class InnerClass // ✅ 正确 +{ +} +``` + +### Context Get 生成器诊断 + +#### GF_ContextGet_001 - 不支持嵌套类 + +**错误信息**: `Class '{ClassName}' cannot use context Get injection inside a nested type` + +**场景**:在嵌套类中使用注入特性 + +```csharp +[ContextAware] +public partial class OuterClass +{ + public partial class InnerClass + { + [GetModel] // ❌ 错误 + private IModel _model = null!; + } +} +``` + +**解决方案**:避免在嵌套类中使用注入特性 + +#### GF_ContextGet_002 - 不支持静态字段 + +**错误信息**: `Field '{FieldName}' cannot be static when using generated context Get injection` + +**场景**:标记静态字段进行注入 + +```csharp +[ContextAware] +public partial class MyClass +{ + [GetModel] + private static IModel _model = null!; // ❌ 错误 +} +``` + +**解决方案**:改为实例字段 + +```csharp +[ContextAware] +public partial class MyClass +{ + [GetModel] + private IModel _model = null!; // ✅ 正确 +} +``` + +#### GF_ContextGet_003 - 不支持只读字段 + +**错误信息**: `Field '{FieldName}' cannot be readonly when using generated context Get injection` + +**场景**:标记只读字段进行注入 + +```csharp +[ContextAware] +public partial class MyClass +{ + [GetModel] + private readonly IModel _model = null!; // ❌ 错误 +} +``` + +**解决方案**:移除 `readonly` 关键字 + +```csharp +[ContextAware] +public partial class MyClass +{ + [GetModel] + private IModel _model = null!; // ✅ 正确 +} +``` + +#### GF_ContextGet_004 - 字段类型不匹配 + +**错误信息**: `Field '{FieldName}' type '{FieldType}' is not valid for [{AttributeName}]` + +**场景**:字段类型与注入特性要求的类型不匹配 + +```csharp +[ContextAware] +public partial class MyClass +{ + [GetModel] + private ISystem _system = null!; // ❌ 错误:ISystem 不实现 IModel +} +``` + +**解决方案**:确保字段类型符合特性要求 + +```csharp +[ContextAware] +public partial class MyClass +{ + [GetModel] + private IModel _model = null!; // ✅ 正确 + + [GetSystem] + private ISystem _system = null!; // ✅ 正确 +} +``` + +#### GF_ContextGet_005 - 必须是上下文感知类型 + +**错误信息**: `Class '{ClassName}' must be context-aware to use generated context Get injection` + +**场景**:类未标记 `[ContextAware]` 或未实现 `IContextAware` + +```csharp +public partial class MyClass // ❌ 缺少 [ContextAware] +{ + [GetModel] + private IModel _model = null!; +} +``` + +**解决方案**:添加 `[ContextAware]` 特性 + +```csharp +[ContextAware] // ✅ 正确 +public partial class MyClass +{ + [GetModel] + private IModel _model = null!; +} +``` + +#### GF_ContextGet_006 - 不支持多个注入特性 + +**错误信息**: `Field '{FieldName}' cannot declare multiple generated context Get attributes` + +**场景**:同一字段标记了多个注入特性 + +```csharp +[ContextAware] +public partial class MyClass +{ + [GetModel] + [GetSystem] // ❌ 错误:多个特性冲突 + private IModel _model = null!; +} +``` + +**解决方案**:每个字段只标记一个注入特性 + +```csharp +[ContextAware] +public partial class MyClass +{ + [GetModel] + private IModel _model = null!; // ✅ 正确 + + [GetSystem] + private ISystem _system = null!; // ✅ 正确 +} +``` + +### Priority 使用分析器诊断 + +#### GF_Priority_Usage_001 - 建议使用 GetAllByPriority + +**错误信息**: `Consider using GetAllByPriority() instead of GetAll() for types implementing IPrioritized` + +**场景**:获取实现了 `IPrioritized` 的类型时使用 `GetAll()` 而非 `GetAllByPriority()` + +```csharp +// ❌ 不推荐:可能未按优先级排序 +var systems = context.GetAll(); +``` + +**解决方案**:使用 `GetAllByPriority()` 确保按优先级排序 + +```csharp +// ✅ 推荐:确保按优先级排序 +var systems = context.GetAllByPriority(); +``` + ## 性能优势 ### 编译时 vs 运行时对比 diff --git a/docs/zh-CN/source-generators/priority-generator.md b/docs/zh-CN/source-generators/priority-generator.md new file mode 100644 index 0000000..6a4980a --- /dev/null +++ b/docs/zh-CN/source-generators/priority-generator.md @@ -0,0 +1,747 @@ +# Priority 生成器 + +> 自动实现 IPrioritized 接口,为类添加优先级标记 + +Priority 生成器通过源代码生成器自动实现 `IPrioritized` 接口,简化优先级标记和排序逻辑的实现。 + +## 概述 + +### 核心功能 + +- **自动实现接口**:自动实现 `IPrioritized` 接口的 `Priority` 属性 +- **优先级标记**:通过特性参数指定优先级值 +- **编译时生成**:在编译时生成代码,零运行时开销 +- **类型安全**:编译时类型检查,避免运行时错误 + +### 适用场景 + +- 系统初始化顺序控制 +- 事件处理器优先级排序 +- 服务注册顺序管理 +- 需要按优先级排序的任何场景 + +## 基础使用 + +### 标记优先级 + +使用 `[Priority]` 特性为类标记优先级: + +```csharp +using GFramework.SourceGenerators.Abstractions.Bases; + +[Priority(10)] +public partial class MySystem +{ + // 自动生成 IPrioritized 接口实现 +} +``` + +### 生成代码 + +编译器会自动生成如下代码: + +```csharp +// +#nullable enable + +namespace YourNamespace; + +partial class MySystem : global::GFramework.Core.Abstractions.Bases.IPrioritized +{ + /// + /// 获取优先级值: 10 + /// + public int Priority => 10; +} +``` + +### 使用生成的优先级 + +生成的 `Priority` 属性可用于排序: + +```csharp +using GFramework.Core.Abstractions.Bases; + +// 获取所有实现了 IPrioritized 的系统 +var systems = new List { system1, system2, system3 }; + +// 按优先级排序(值越小,优先级越高) +var sorted = systems.OrderBy(s => s.Priority).ToList(); + +// 依次初始化 +foreach (var system in sorted) +{ + system.Initialize(); +} +``` + +## 优先级值语义 + +### 值的含义 + +| 优先级值范围 | 含义 | 使用场景 | +|--------|-------|------------------| +| 负数 | 高优先级 | 核心系统、关键事件处理器 | +| 0 | 默认优先级 | 普通系统、一般事件处理器 | +| 正数 | 低优先级 | 可延迟初始化的系统、非关键处理器 | + +### PriorityGroup 常量 + +推荐使用 `PriorityGroup` 预定义常量来标记优先级: + +```csharp +using GFramework.Core.Abstractions.Bases; +using GFramework.SourceGenerators.Abstractions.Bases; + +[Priority(PriorityGroup.Critical)] // -100 +public partial class InputSystem : AbstractSystem { } + +[Priority(PriorityGroup.High)] // -50 +public partial class PhysicsSystem : AbstractSystem { } + +[Priority(PriorityGroup.Normal)] // 0 +public partial class GameplaySystem : AbstractSystem { } + +[Priority(PriorityGroup.Low)] // 50 +public partial class AudioSystem : AbstractSystem { } + +[Priority(PriorityGroup.Deferred)] // 100 +public partial class CleanupSystem : AbstractSystem { } +``` + +**PriorityGroup 常量定义**: + +```csharp +namespace GFramework.Core.Abstractions.Bases; + +public static class PriorityGroup +{ + public const int Critical = -100; // 关键:输入、网络等 + public const int High = -50; // 高:物理、碰撞等 + public const int Normal = 0; // 正常:游戏逻辑等 + public const int Low = 50; // 低:音频、特效等 + public const int Deferred = 100; // 延迟:清理、统计等 +} +``` + +## 使用场景 + +### 系统初始化顺序 + +控制系统初始化的顺序,确保依赖关系正确: + +```csharp +using GFramework.Core.Abstractions.Systems; +using GFramework.SourceGenerators.Abstractions.Bases; +using GFramework.Core.Abstractions.Bases; + +// 输入系统最先初始化 +[Priority(PriorityGroup.Critical)] +public partial class InputSystem : AbstractSystem +{ + protected override void OnInit() + { + // 初始化输入处理 + } +} + +// 物理系统次之 +[Priority(PriorityGroup.High)] +public partial class PhysicsSystem : AbstractSystem +{ + protected override void OnInit() + { + // 初始化物理引擎 + } +} + +// 游戏逻辑系统在中间 +[Priority(PriorityGroup.Normal)] +public partial class GameplaySystem : AbstractSystem +{ + protected override void OnInit() + { + // 初始化游戏逻辑 + } +} + +// 音频系统可以稍后 +[Priority(PriorityGroup.Low)] +public partial class AudioSystem : AbstractSystem +{ + protected override void OnInit() + { + // 初始化音频引擎 + } +} +``` + +在架构中按优先级初始化: + +```csharp +public class GameArchitecture : Architecture +{ + protected override void InitSystems() + { + // 获取所有系统并按优先级排序 + var systems = this.GetAllByPriority(); + + foreach (var system in systems) + { + system.Init(); + } + } +} +``` + +### 事件处理器优先级 + +控制事件处理器的执行顺序: + +```csharp +using GFramework.Core.Abstractions.Events; + +// 关键事件处理器最先执行 +[Priority(PriorityGroup.Critical)] +public partial class CriticalEventHandler : IEventHandler +{ + public void Handle(CriticalEvent e) + { + // 处理关键事件 + } +} + +// 普通处理器在中间执行 +[Priority(PriorityGroup.Normal)] +public partial class NormalEventHandler : IEventHandler +{ + public void Handle(CriticalEvent e) + { + // 处理普通逻辑 + } +} + +// 日志记录器最后执行 +[Priority(PriorityGroup.Deferred)] +public partial class EventLogger : IEventHandler +{ + public void Handle(CriticalEvent e) + { + // 记录日志 + } +} +``` + +事件总线按优先级调用处理器: + +```csharp +public class EventBus : IEventBus +{ + public void Send(TEvent e) where TEvent : IEvent + { + // 获取所有处理器并按优先级排序 + var handlers = this.GetAllByPriority>(); + + foreach (var handler in handlers) + { + handler.Handle(e); + } + } +} +``` + +### 服务排序 + +控制多个服务实现的优先级: + +```csharp +// 高优先级服务 +[Priority(PriorityGroup.High)] +public partial class PremiumService : IService +{ + public void Execute() + { + // 优先执行 + } +} + +// 默认服务 +[Priority(PriorityGroup.Normal)] +public partial class DefaultService : IService +{ + public void Execute() + { + // 默认执行 + } +} + +// 后备服务 +[Priority(PriorityGroup.Low)] +public partial class FallbackService : IService +{ + public void Execute() + { + // 最后备选 + } +} +``` + +## 与 PriorityUsageAnalyzer 集成 + +### GF_Priority_Usage_001 诊断 + +`PriorityUsageAnalyzer` 分析器会检测应该使用 `GetAllByPriority()` 而非 `GetAll()` 的场景: + +**错误示例**: + +```csharp +// ❌ 不推荐:可能未按优先级排序 +var systems = context.GetAll(); +``` + +**正确示例**: + +```csharp +// ✅ 推荐:确保按优先级排序 +var systems = context.GetAllByPriority(); +``` + +### 分析器规则 + +当满足以下条件时,分析器会报告 `GF_Priority_Usage_001` 诊断: + +1. 类型实现了 `IPrioritized` 接口 +2. 使用了 `GetAll()` 方法 +3. 建议改用 `GetAllByPriority()` 方法 + +## 诊断信息 + +### GF_Priority_001 - 只能应用于类 + +**错误信息**:`Priority attribute can only be applied to classes` + +**场景**:将 `[Priority]` 特性应用于非类类型 + +```csharp +[Priority(10)] +public interface IMyInterface // ❌ 错误 +{ +} + +[Priority(10)] +public struct MyStruct // ❌ 错误 +{ +} +``` + +**解决方案**:只在类上使用 `[Priority]` 特性 + +```csharp +[Priority(10)] +public partial class MyClass // ✅ 正确 +{ +} +``` + +### GF_Priority_002 - 已实现 IPrioritized 接口 + +**错误信息**:`Type '{ClassName}' already implements IPrioritized interface` + +**场景**:类已手动实现 `IPrioritized` 接口 + +```csharp +[Priority(10)] +public partial class MySystem : IPrioritized // ❌ 冲突 +{ + public int Priority => 10; // 手动实现 +} +``` + +**解决方案**:移除 `[Priority]` 特性或移除手动实现 + +```csharp +// 方案1:移除特性,使用手动实现 +public partial class MySystem : IPrioritized +{ + public int Priority => 10; +} + +// 方案2:移除手动实现,使用生成器 +[Priority(10)] +public partial class MySystem +{ + // 生成器自动实现 +} +``` + +### GF_Priority_003 - 必须声明为 partial + +**错误信息**:`Class '{ClassName}' must be declared as partial` + +**场景**:类未声明为 `partial` + +```csharp +[Priority(10)] +public class MySystem // ❌ 缺少 partial +{ +} +``` + +**解决方案**:添加 `partial` 关键字 + +```csharp +[Priority(10)] +public partial class MySystem // ✅ 正确 +{ +} +``` + +### GF_Priority_004 - 优先级值无效 + +**错误信息**:`Priority value is invalid` + +**场景**:特性参数无效或未提供 + +```csharp +[Priority] // ❌ 缺少参数 +public partial class MySystem +{ +} +``` + +**解决方案**:提供有效的优先级值 + +```csharp +[Priority(10)] // ✅ 正确 +public partial class MySystem +{ +} +``` + +### GF_Priority_005 - 不支持嵌套类 + +**错误信息**:`Nested class '{ClassName}' is not supported` + +**场景**:在嵌套类中使用 `[Priority]` 特性 + +```csharp +public partial class OuterClass +{ + [Priority(10)] + public partial class InnerClass // ❌ 错误 + { + } +} +``` + +**解决方案**:将嵌套类提取为独立的类 + +```csharp +[Priority(10)] +public partial class InnerClass // ✅ 正确 +{ +} +``` + +## 最佳实践 + +### 1. 使用 PriorityGroup 常量 + +避免使用魔法数字,优先使用预定义常量: + +```csharp +// ✅ 推荐:使用常量 +[Priority(PriorityGroup.Critical)] +public partial class InputSystem { } + +// ❌ 不推荐:魔法数字 +[Priority(-100)] +public partial class InputSystem { } +``` + +### 2. 预留优先级间隔 + +为未来扩展预留间隔: + +```csharp +public static class SystemPriority +{ + public const int Input = -100; + public const int PrePhysics = -90; // 预留扩展 + public const int Physics = -80; + public const int PostPhysics = -70; // 预留扩展 + public const int Gameplay = 0; + public const int PostGameplay = 10; // 预留扩展 + public const int Audio = 50; + public const int Cleanup = 100; +} +``` + +### 3. 文档化优先级语义 + +为自定义优先级值添加注释: + +```csharp +/// +/// 输入系统,优先级 -100,需要最先初始化以接收输入事件 +/// +[Priority(PriorityGroup.Critical)] +public partial class InputSystem : AbstractSystem +{ +} +``` + +### 4. 避免优先级冲突 + +当多个类有相同优先级时,执行顺序不确定。应避免依赖特定顺序: + +```csharp +// ❌ 不推荐:相同优先级,顺序不确定 +[Priority(0)] +public partial class SystemA { } + +[Priority(0)] +public partial class SystemB { } + +// ✅ 推荐:明确区分优先级 +[Priority(-10)] +public partial class SystemA { } + +[Priority(0)] +public partial class SystemB { } +``` + +### 5. 只在真正需要排序的场景使用 + +不要滥用优先级特性,只在确实需要排序的场景使用: + +```csharp +// ✅ 推荐:需要排序的系统 +[Priority(PriorityGroup.Critical)] +public partial class InputSystem : AbstractSystem { } + +// ❌ 不推荐:不需要排序的工具类 +[Priority(10)] +public static partial class MathHelper // 静态工具类无需优先级 +{ + public static int Add(int a, int b) => a + b; +} +``` + +## 高级场景 + +### 泛型类支持 + +Priority 特性支持泛型类: + +```csharp +[Priority(20)] +public partial class GenericSystem : ISystem +{ + public void Init() + { + // 泛型系统的初始化 + } +} +``` + +### 与其他特性组合 + +Priority 可以与其他源代码生成器特性组合使用: + +```csharp +using GFramework.SourceGenerators.Abstractions.Bases; +using GFramework.SourceGenerators.Abstractions.Logging; +using GFramework.SourceGenerators.Abstractions.Rule; + +[Priority(PriorityGroup.High)] +[Log] +[ContextAware] +public partial class HighPrioritySystem : AbstractSystem +{ + protected override void OnInit() + { + Logger.Info("High priority system initialized"); + } +} +``` + +### 运行时优先级查询 + +可以在运行时查询优先级值: + +```csharp +public void ProcessSystems() +{ + var systems = this.GetAllByPriority(); + + foreach (var system in systems) + { + if (system is IPrioritized prioritized) + { + Console.WriteLine($"System: {system.GetType().Name}, Priority: {prioritized.Priority}"); + } + + system.Init(); + } +} +``` + +## 常见问题 + +### Q: 优先级值可以是任意整数吗? + +**A**: 是的,优先级值可以是任何 `int` 类型的值。推荐使用 `PriorityGroup` 预定义常量或在项目中定义自己的优先级常量。 + +### Q: 多个类有相同优先级会怎样? + +**A**: 当多个类有相同的优先级值时,它们的相对顺序是不确定的。建议为每个类设置不同的优先级值,或在文档中明确说明相同优先级类的执行顺序不保证。 + +### Q: 可以在运行时改变优先级吗? + +**A**: 不可以。`Priority` 属性是只读的,值在编译时确定。如果需要运行时改变优先级,应手动实现 `IPrioritized` 接口。 + +```csharp +public class DynamicPrioritySystem : IPrioritized +{ + private int _priority; + + public int Priority => _priority; + + public void SetPriority(int value) + { + _priority = value; + } +} +``` + +### Q: 优先级支持继承吗? + +**A**: `[Priority]` 特性标记为 `Inherited = false`,不会被子类继承。每个子类需要独立标记优先级。 + +```csharp +[Priority(10)] +public partial class BaseSystem { } + +public partial class DerivedSystem : BaseSystem // 不会继承 Priority = 10 +{ + // 需要重新标记 + // [Priority(20)] +} +``` + +### Q: 如何在不使用特性的情况下实现优先级? + +**A**: 可以手动实现 `IPrioritized` 接口: + +```csharp +public class ManualPrioritySystem : IPrioritized +{ + public int Priority => 10; + + // 手动实现提供更大的灵活性 + public int Priority => _config.Enabled ? 10 : 100; +} +``` + +### Q: 负数优先级和正数优先级的区别是什么? + +**A**: 优先级值用于排序,通常值越小优先级越高: + +- 负数(-100):高优先级,最先执行 +- 零(0):默认优先级 +- 正数(100):低优先级,最后执行 + +具体含义取决于使用场景,但推荐遵循 `PriorityGroup` 定义的语义。 + +## 实际应用示例 + +### 游戏系统架构 + +完整的游戏系统初始化示例: + +```csharp +using GFramework.Core.Abstractions.Systems; +using GFramework.SourceGenerators.Abstractions.Bases; +using GFramework.Core.Abstractions.Bases; + +// 输入系统(最先初始化) +[Priority(PriorityGroup.Critical)] +[Log] +public partial class InputSystem : AbstractSystem +{ + protected override void OnInit() + { + Logger.Info("Input system initialized"); + } +} + +// 物理系统 +[Priority(PriorityGroup.High)] +[Log] +public partial class PhysicsSystem : AbstractSystem +{ + protected override void OnInit() + { + Logger.Info("Physics system initialized"); + } +} + +// 游戏逻辑系统 +[Priority(PriorityGroup.Normal)] +[Log] +[ContextAware] +public partial class GameplaySystem : AbstractSystem +{ + protected override void OnInit() + { + Logger.Info("Gameplay system initialized"); + } +} + +// 音频系统 +[Priority(PriorityGroup.Low)] +[Log] +public partial class AudioSystem : AbstractSystem +{ + protected override void OnInit() + { + Logger.Info("Audio system initialized"); + } +} + +// 清理系统(最后执行) +[Priority(PriorityGroup.Deferred)] +[Log] +public partial class CleanupSystem : AbstractSystem +{ + protected override void OnInit() + { + Logger.Info("Cleanup system initialized"); + } +} +``` + +在架构中初始化: + +```csharp +public class GameArchitecture : Architecture +{ + protected override async Task InitAsync() + { + // 获取所有系统并按优先级排序 + var systems = this.GetAllByPriority(); + + foreach (var system in systems) + { + await system.InitAsync(); + } + } +} +``` + +## 相关文档 + +- [Source Generators 概述](./index.md) +- [ContextAware 生成器](./context-aware-generator.md) +- [系统初始化](../core/system.md)