diff --git a/docs/zh-CN/source-generators/context-aware-generator.md b/docs/zh-CN/source-generators/context-aware-generator.md new file mode 100644 index 0000000..42f8294 --- /dev/null +++ b/docs/zh-CN/source-generators/context-aware-generator.md @@ -0,0 +1,378 @@ +# ContextAware 生成器 + +> 自动实现 IContextAware 接口,提供架构上下文访问能力 + +## 概述 + +ContextAware 生成器为标记了 `[ContextAware]` 属性的类自动生成 `IContextAware` 接口实现,使类能够便捷地访问架构上下文( +`IArchitectureContext`)。这是 GFramework 中最常用的源码生成器之一,几乎所有需要与架构交互的组件都会使用它。 + +### 核心功能 + +- **自动接口实现**:无需手动实现 `IContextAware` 接口的 `SetContext()` 和 `GetContext()` 方法 +- **懒加载上下文**:`Context` 属性在首次访问时自动初始化 +- **默认提供者**:使用 `GameContextProvider` 作为默认上下文提供者 +- **测试友好**:支持通过 `SetContextProvider()` 配置自定义上下文提供者 + +## 基础使用 + +### 标记类 + +使用 `[ContextAware]` 属性标记需要访问架构上下文的类: + +```csharp +using GFramework.SourceGenerators.Abstractions.rule; +using GFramework.Core.Abstractions.controller; + +[ContextAware] +public partial class PlayerController : IController +{ + public void Initialize() + { + // Context 属性自动生成,提供架构上下文访问 + var playerModel = Context.GetModel(); + var combatSystem = Context.GetSystem(); + + Context.SendEvent(new PlayerInitializedEvent()); + } + + public void Attack(Enemy target) + { + var damage = Context.GetUtility().Calculate(this, target); + Context.SendCommand(new DealDamageCommand(target, damage)); + } +} +``` + +### 必要条件 + +标记的类必须满足以下条件: + +1. **必须是 `partial` 类**:生成器需要生成部分类代码 +2. **必须是 `class` 类型**:不能是 `struct` 或 `interface` + +```csharp +// ✅ 正确 +[ContextAware] +public partial class MyController { } + +// ❌ 错误:缺少 partial 关键字 +[ContextAware] +public class MyController { } + +// ❌ 错误:不能用于 struct +[ContextAware] +public partial struct MyStruct { } +``` + +## 生成的代码 + +编译器会为标记的类自动生成以下代码: + +```csharp +// +#nullable enable + +namespace YourNamespace; + +partial class PlayerController : global::GFramework.Core.Abstractions.rule.IContextAware +{ + private global::GFramework.Core.Abstractions.architecture.IArchitectureContext? _context; + private static global::GFramework.Core.Abstractions.architecture.IArchitectureContextProvider? _contextProvider; + + /// + /// 自动获取的架构上下文(懒加载,默认使用 GameContextProvider) + /// + protected global::GFramework.Core.Abstractions.architecture.IArchitectureContext Context + { + get + { + if (_context == null) + { + _contextProvider ??= new global::GFramework.Core.architecture.GameContextProvider(); + _context = _contextProvider.GetContext(); + } + + return _context; + } + } + + /// + /// 配置上下文提供者(用于测试或多架构场景) + /// + /// 上下文提供者实例 + public static void SetContextProvider(global::GFramework.Core.Abstractions.architecture.IArchitectureContextProvider provider) + { + _contextProvider = provider; + } + + /// + /// 重置上下文提供者为默认值(用于测试清理) + /// + public static void ResetContextProvider() + { + _contextProvider = null; + } + + void global::GFramework.Core.Abstractions.rule.IContextAware.SetContext(global::GFramework.Core.Abstractions.architecture.IArchitectureContext context) + { + _context = context; + } + + global::GFramework.Core.Abstractions.architecture.IArchitectureContext global::GFramework.Core.Abstractions.rule.IContextAware.GetContext() + { + return Context; + } +} +``` + +### 代码解析 + +生成的代码包含以下关键部分: + +1. **私有字段**: + - `_context`:缓存的上下文实例 + - `_contextProvider`:静态上下文提供者(所有实例共享) + +2. **Context 属性**: + - `protected` 访问级别,子类可访问 + - 懒加载:首次访问时自动初始化 + - 使用 `GameContextProvider` 作为默认提供者 + +3. **配置方法**: + - `SetContextProvider()`:设置自定义上下文提供者 + - `ResetContextProvider()`:重置为默认提供者 + +4. **显式接口实现**: + - `IContextAware.SetContext()`:允许外部设置上下文 + - `IContextAware.GetContext()`:返回当前上下文 + +## 配置上下文提供者 + +### 测试场景 + +在单元测试中,通常需要使用自定义的上下文提供者: + +```csharp +[Test] +public void TestPlayerController() +{ + // 创建测试架构 + var testArchitecture = new TestArchitecture(); + await testArchitecture.InitAsync(); + + // 配置自定义上下文提供者 + PlayerController.SetContextProvider(new TestContextProvider(testArchitecture)); + + try + { + // 测试代码 + var controller = new PlayerController(); + controller.Initialize(); + + // 验证... + } + finally + { + // 清理:重置上下文提供者 + PlayerController.ResetContextProvider(); + } +} +``` + +### 多架构场景 + +在某些高级场景中,可能需要同时运行多个架构实例: + +```csharp +public class MultiArchitectureManager +{ + private readonly Dictionary _architectures = new(); + + public void SwitchToArchitecture(string name) + { + var architecture = _architectures[name]; + var provider = new ScopedContextProvider(architecture); + + // 为所有使用 [ContextAware] 的类切换上下文 + PlayerController.SetContextProvider(provider); + EnemyController.SetContextProvider(provider); + // ... + } +} +``` + +## 使用场景 + +### 何时使用 [ContextAware] + +推荐在以下场景使用 `[ContextAware]` 属性: + +1. **Controller 层**:需要协调多个 Model/System 的控制器 +2. **Command/Query 实现**:需要访问架构服务的命令或查询 +3. **自定义组件**:不继承框架基类但需要上下文访问的组件 + +```csharp +[ContextAware] +public partial class GameFlowController : IController +{ + public async Task StartGame() + { + var saveSystem = Context.GetSystem(); + var uiSystem = Context.GetSystem(); + + await saveSystem.LoadAsync(); + await uiSystem.ShowMainMenuAsync(); + } +} +``` + +### 何时继承 ContextAwareBase + +如果类需要更多框架功能(如生命周期管理),应继承 `ContextAwareBase`: + +```csharp +// 推荐:需要生命���期管理时继承基类 +public class PlayerModel : AbstractModel +{ + // AbstractModel 已经继承了 ContextAwareBase + protected override void OnInit() + { + var config = Context.GetUtility().Load(); + } +} + +// 推荐:简单组件使用属性 +[ContextAware] +public partial class SimpleHelper +{ + public void DoSomething() + { + Context.SendEvent(new SomethingHappenedEvent()); + } +} +``` + +## 与 IContextAware 接口的关系 + +生成的代码实现了 `IContextAware` 接口: + +```csharp +namespace GFramework.Core.Abstractions.rule; + +public interface IContextAware +{ + void SetContext(IArchitectureContext context); + IArchitectureContext GetContext(); +} +``` + +这意味着标记了 `[ContextAware]` 的类可以: + +1. **被架构自动注入上下文**:实现 `IContextAware` 的类在注册到架构时会自动调用 `SetContext()` +2. **参与依赖注入**:可以作为 `IContextAware` 类型注入到其他组件 +3. **支持上下文传递**:可以通过 `GetContext()` 将上下文传递给其他组件 + +## 最佳实践 + +### 1. 始终使用 partial 关键字 + +```csharp +// ✅ 正确 +[ContextAware] +public partial class MyController { } + +// ❌ 错误:编译器会报错 +[ContextAware] +public class MyController { } +``` + +### 2. 在测试中清理上下文提供者 + +```csharp +[TearDown] +public void TearDown() +{ + // 避免测试之间的状态污染 + PlayerController.ResetContextProvider(); + EnemyController.ResetContextProvider(); +} +``` + +### 3. 避免在构造函数中访问 Context + +```csharp +[ContextAware] +public partial class MyController +{ + // ❌ 错误:构造函数执行时上下文可能未初始化 + public MyController() + { + var model = Context.GetModel(); // 可能为 null + } + + // ✅ 正确:在初始化方法中访问 + public void Initialize() + { + var model = Context.GetModel(); // 安全 + } +} +``` + +### 4. 优先使用 Context 属性而非接口方法 + +```csharp +[ContextAware] +public partial class MyController +{ + public void DoSomething() + { + // ✅ 推荐:使用生成的 Context 属性 + var model = Context.GetModel(); + + // ❌ 不推荐:显式调用接口方法 + var context = ((IContextAware)this).GetContext(); + var model2 = context.GetModel(); + } +} +``` + +## 诊断信息 + +生成器会在以下情况报告编译错误: + +### GFSG001: 类必须是 partial + +```csharp +[ContextAware] +public class MyController { } // 错误:缺少 partial 关键字 +``` + +**解决方案**:添加 `partial` 关键字 + +```csharp +[ContextAware] +public partial class MyController { } // ✅ 正确 +``` + +### GFSG002: ContextAware 只能用于类 + +```csharp +[ContextAware] +public partial struct MyStruct { } // 错误:不能用于 struct +``` + +**解决方案**:将 `struct` 改为 `class` + +```csharp +[ContextAware] +public partial class MyClass { } // ✅ 正确 +``` + +## 相关文档 + +- [Source Generators 概述](./index) +- [架构上下文](../core/context) +- [IContextAware 接口](../core/rule) +- [日志生成器](./logging-generator) +- [规则验证生成器](./rule-generator) diff --git a/docs/zh-CN/source-generators/enum-generator.md b/docs/zh-CN/source-generators/enum-generator.md index a9a98f0..e5aa9c7 100644 --- a/docs/zh-CN/source-generators/enum-generator.md +++ b/docs/zh-CN/source-generators/enum-generator.md @@ -170,4 +170,4 @@ public enum Difficulty - [Source Generators 概述](./index) - [日志生成器](./logging-generator) -- [规则生成器](./rule-generator) +- [ContextAware 生成器](./context-aware-generator) diff --git a/docs/zh-CN/source-generators/index.md b/docs/zh-CN/source-generators/index.md index 1f8ebf7..36df3c1 100644 --- a/docs/zh-CN/source-generators/index.md +++ b/docs/zh-CN/source-generators/index.md @@ -184,8 +184,8 @@ public partial class WarningOnlyLogger ### 基础使用 ```csharp -using GFramework.SourceGenerators.Attributes; -using GFramework.Core.Abstractions; +using GFramework.SourceGenerators.Abstractions.rule; +using GFramework.Core.Abstractions.controller; [ContextAware] public partial class PlayerController : IController @@ -195,7 +195,7 @@ public partial class PlayerController : IController // Context 属性自动生成,提供架构上下文访问 var playerModel = Context.GetModel(); var combatSystem = Context.GetSystem(); - + Context.SendEvent(new PlayerInitializedEvent()); } } @@ -207,57 +207,84 @@ public partial class PlayerController : IController ```csharp // -using GFramework.Core.Abstractions; +#nullable enable -namespace YourNamespace +namespace YourNamespace; + +partial class PlayerController : global::GFramework.Core.Abstractions.rule.IContextAware { - public partial class PlayerController : IContextAware + private global::GFramework.Core.Abstractions.architecture.IArchitectureContext? _context; + private static global::GFramework.Core.Abstractions.architecture.IArchitectureContextProvider? _contextProvider; + + /// + /// 自动获取的架构上下文(懒加载,默认使用 GameContextProvider) + /// + protected global::GFramework.Core.Abstractions.architecture.IArchitectureContext Context { - private IContextAware.Context _context; - - public IContextAware.Context Context => _context ??= new LazyContext(this); - - public void SetContext(IContextAware.Context context) - { - _context = context; - } - - public IContextAware.Context GetContext() + get { + if (_context == null) + { + _contextProvider ??= new global::GFramework.Core.architecture.GameContextProvider(); + _context = _contextProvider.GetContext(); + } + return _context; } } -} -``` -### 延迟初始化 - -```csharp -[ContextAware(useLazy = true)] -public partial class LazyContextExample -{ - public void AccessContext() + /// + /// 配置上下文提供者(用于测试或多架构场景) + /// + public static void SetContextProvider(global::GFramework.Core.Abstractions.architecture.IArchitectureContextProvider provider) { - // Context 会延迟初始化,直到第一次访问 - var model = Context.GetModel(); + _contextProvider = provider; + } + + /// + /// 重置上下文提供者为默认值(用于测试清理) + /// + public static void ResetContextProvider() + { + _contextProvider = null; + } + + void global::GFramework.Core.Abstractions.rule.IContextAware.SetContext(global::GFramework.Core.Abstractions.architecture.IArchitectureContext context) + { + _context = context; + } + + global::GFramework.Core.Abstractions.architecture.IArchitectureContext global::GFramework.Core.Abstractions.rule.IContextAware.GetContext() + { + return Context; } } ``` -### 上下文验证 +### 测试场景配置 + +在单元测试中,可以配置自定义的上下文提供者: ```csharp -[ContextAware(validateContext = true)] -public partial class ValidatedContextExample +[Test] +public async Task TestPlayerController() { - public void AccessContext() + var testArchitecture = new TestArchitecture(); + await testArchitecture.InitAsync(); + + // 配置测试上下文提供者 + PlayerController.SetContextProvider(new TestContextProvider(testArchitecture)); + + try { - // 每次访问都会验证上下文的有效性 - var model = Context.GetModel(); - if (Context.IsInvalid) - { - throw new InvalidOperationException("Context is invalid"); - } + var controller = new PlayerController(); + controller.Initialize(); + // 测试逻辑... + } + finally + { + // 清理:重置上下文提供者 + PlayerController.ResetContextProvider(); } } ``` diff --git a/docs/zh-CN/source-generators/logging-generator.md b/docs/zh-CN/source-generators/logging-generator.md index 0caf9d7..c72c9cf 100644 --- a/docs/zh-CN/source-generators/logging-generator.md +++ b/docs/zh-CN/source-generators/logging-generator.md @@ -157,4 +157,4 @@ public partial class MyClass - [Source Generators 概述](./index) - [枚举扩展生成器](./enum-generator) -- [规则生成器](./rule-generator) +- [ContextAware 生成器](./context-aware-generator) diff --git a/docs/zh-CN/source-generators/rule-generator.md b/docs/zh-CN/source-generators/rule-generator.md deleted file mode 100644 index 9776632..0000000 --- a/docs/zh-CN/source-generators/rule-generator.md +++ /dev/null @@ -1,163 +0,0 @@ -# 规则生成器 - -> GFramework.SourceGenerators 自动生成规则验证代码 - -## 概述 - -规则生成器为实现了规则接口的类型自动生成验证方法。这使得规则定义更加简洁,并确保规则的一致性。 - -## 基本用法 - -### 定义规则 - -```csharp -using GFramework.SourceGenerators.Attributes; - -[RuleFor(typeof(Player))] -public class PlayerRule : IRule -{ - public RuleResult Validate(Player player) - { - if (player.Health <= 0) - { - return RuleResult.Invalid("玩家生命值不能为零或负数"); - } - - if (string.IsNullOrEmpty(player.Name)) - { - return RuleResult.Invalid("玩家名称不能为空"); - } - - return RuleResult.Valid(); - } -} -``` - -### 使用生成的验证器 - -```csharp -public class PlayerValidator -{ - // 自动生成 Validate 方法 - public void ValidatePlayer(Player player) - { - var result = PlayerRuleValidator.Validate(player); - - if (!result.IsValid) - { - Console.WriteLine($"验证失败: {result.ErrorMessage}"); - } - } -} -``` - -## 组合规则 - -### 多规则组合 - -```csharp -[RuleFor(typeof(Player), RuleCombinationType.And)] -public class PlayerHealthRule : IRule -{ - public RuleResult Validate(Player player) - { - if (player.Health <= 0) - return RuleResult.Invalid("生命值必须大于0"); - return RuleResult.Valid(); - } -} - -[RuleFor(typeof(Player), RuleCombinationType.And)] -public class PlayerNameRule : IRule -{ - public RuleResult Validate(Player player) - { - if (string.IsNullOrEmpty(player.Name)) - return RuleResult.Invalid("名称不能为空"); - return RuleResult.Valid(); - } -} -``` - -### 验证所有规则 - -```csharp -public void ValidateAll(Player player) -{ - var results = PlayerRuleValidator.ValidateAll(player); - - foreach (var result in results) - { - if (!result.IsValid) - { - Console.WriteLine(result.ErrorMessage); - } - } -} -``` - -## 异步规则 - -```csharp -[RuleFor(typeof(Player), IsAsync = true)] -public class AsyncPlayerRule : IAsyncRule -{ - public async Task ValidateAsync(Player player) - { - // 异步验证,如检查服务器 - var isBanned = await CheckBanStatus(player.Id); - - if (isBanned) - { - return RuleResult.Invalid("玩家已被封禁"); - } - - return RuleResult.Valid(); - } -} -``` - -## 最佳实践 - -### 1. 单一职责 - -```csharp -// 推荐:每个规则只验证一个方面 -[RuleFor(typeof(Player))] -public class PlayerHealthRule : IRule { } - -[RuleFor(typeof(Player))] -public class PlayerNameRule : IRule { } - -// 避免:在一个规则中验证多个方面 -[RuleFor(typeof(Player))] -public class PlayerMegaRule : IRule -{ - public RuleResult Validate(Player player) - { - // 验证健康、名称、等级... - // 不要这样设计 - } -} -``` - -### 2. 清晰的错误信息 - -```csharp -public RuleResult Validate(Player player) -{ - // 推荐:具体的错误信息 - return RuleResult.Invalid($"玩家 {player.Name} 的生命值 {player.Health} 不能小于 1"); - - // 避免:模糊的错误信息 - return RuleResult.Invalid("无效的玩家"); -} -``` - ---- - -**相关文档**: - -- [Source Generators 概述](./index) -- [日志生成器](./logging-generator) -- [枚举扩展生成器](./enum-generator)