diff --git a/.claude/settings.json b/.claude/settings.json deleted file mode 100644 index c6802dc..0000000 --- a/.claude/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "enabledPlugins": { - "oh-my-claudecode@omc": true - } -} diff --git a/.gitignore b/.gitignore index b27afa0..702e68b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ GFramework.sln.DotSettings.user # ai opencode.json .claude/settings.local.json +.claude/settings.json .omc/ docs/.omc/ docs/.vitepress/cache/ \ No newline at end of file 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..18032f7 --- /dev/null +++ b/docs/zh-CN/source-generators/context-aware-generator.md @@ -0,0 +1,377 @@ +# 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 async Task 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) diff --git a/docs/zh-CN/source-generators/enum-generator.md b/docs/zh-CN/source-generators/enum-generator.md index a9a98f0..2e3b455 100644 --- a/docs/zh-CN/source-generators/enum-generator.md +++ b/docs/zh-CN/source-generators/enum-generator.md @@ -1,173 +1,236 @@ # 枚举扩展生成器 -> GFramework.SourceGenerators 自动生成枚举扩展方法 +> 自动为枚举类型生成扩展方法 ## 概述 -枚举扩展生成器为枚举类型自动生成常用的扩展方法,如获取描述、转换为字符串、解析等。这大大简化了枚举的操作。 +枚举扩展生成器为标记了 `[GenerateEnumExtensions]` 属性的枚举自动生成两种扩展方法: -## 基本用法 +1. **IsX 方法**:为每个枚举值生成判断方法 +2. **IsIn 方法**:判断枚举值是否在指定集合中 -### 标记枚举 +## 基础使用 ```csharp -using GFramework.SourceGenerators.Attributes; +using GFramework.SourceGenerators.Abstractions.enums; -[EnumExtensions] +[GenerateEnumExtensions] +public enum GameState +{ + Normal, + Paused, + GameOver +} +``` + +## 生成的代码 + +编译器会自动生成如下扩展方法: + +```csharp +// +public static class GameStateExtensions +{ + // 为每个枚举值生成 IsX 方法 + public static bool IsNormal(this GameState value) + => value == GameState.Normal; + + public static bool IsPaused(this GameState value) + => value == GameState.Paused; + + public static bool IsGameOver(this GameState value) + => value == GameState.GameOver; + + // 生成 IsIn 方法 + public static bool IsIn(this GameState value, params GameState[] values) + { + if (values == null) return false; + foreach (var v in values) if (value == v) return true; + return false; + } +} +``` + +## 使用示例 + +```csharp +var state = GameState.Paused; + +// 使用 IsX 方法 +if (state.IsPaused()) +{ + Console.WriteLine("游戏已暂停"); +} + +// 使用 IsIn 方法 +if (state.IsIn(GameState.Paused, GameState.GameOver)) +{ + Console.WriteLine("游戏未在运行中"); +} +``` + +## 配置选项 + +可以通过属性参数控制生成行为: + +```csharp +[GenerateEnumExtensions( + GenerateIsMethods = true, // 是否生成 IsX 方法(默认 true) + GenerateIsInMethod = true // 是否生成 IsIn 方法(默认 true) +)] +public enum GameState +{ + Normal, + Paused, + GameOver +} +``` + +### 只生成 IsX 方法 + +```csharp +[GenerateEnumExtensions(GenerateIsInMethod = false)] +public enum GameState +{ + Normal, + Paused +} + +// 只生成 IsNormal() 和 IsPaused(),不生成 IsIn() +``` + +### 只生成 IsIn 方法 + +```csharp +[GenerateEnumExtensions(GenerateIsMethods = false)] +public enum GameState +{ + Normal, + Paused +} + +// 只生成 IsIn(),不生成 IsNormal() 和 IsPaused() +``` + +## 最佳实践 + +### 1. 命名约定 + +生成的方法名基于枚举值名称: + +- `Normal` → `IsNormal()` +- `GameOver` → `IsGameOver()` +- `HTTP_ERROR` → `IsHTTP_ERROR()` + +### 2. 性能考虑 + +生成的方法是内联的,性能与手写代码相同: +```csharp +// 生成的代码 +public static bool IsNormal(this GameState value) + => value == GameState.Normal; + +// 等价于手写 +if (state == GameState.Normal) { } +``` + +### 3. 可读性提升 + +```csharp +// 使用生成的方法(推荐) +if (state.IsPaused()) +{ + ResumeGame(); +} + +// 直接比较(不推荐) +if (state == GameState.Paused) +{ + ResumeGame(); +} +``` + +## 实际应用示例 + +### 游戏状态管理 + +```csharp +[GenerateEnumExtensions] +public enum GameState +{ + MainMenu, + Playing, + Paused, + GameOver, + Victory +} + +public class GameManager +{ + private GameState _currentState = GameState.MainMenu; + + public bool CanProcessInput() + { + // 使用 IsIn 方法简化多值判断 + return _currentState.IsIn(GameState.Playing, GameState.MainMenu); + } + + public void Update() + { + // 使用 IsX 方法提高可读性 + if (_currentState.IsPlaying()) + { + UpdateGameLogic(); + } + else if (_currentState.IsPaused()) + { + UpdatePauseMenu(); + } + } +} +``` + +### 角色状态控制 + +```csharp +[GenerateEnumExtensions] public enum PlayerState { Idle, + Walking, Running, Jumping, Attacking } -``` -### 生成的方法 - -上面的代码会被转换为: - -```csharp -public static class PlayerStateExtensions +public class PlayerController { - public static string GetDescription(this PlayerState value) + private PlayerState _state = PlayerState.Idle; + + public bool CanAttack() { - // 返回枚举的描述 + // 清晰表达可以攻击的状态 + return _state.IsIn(PlayerState.Idle, PlayerState.Walking, PlayerState.Running); } - public static bool HasFlag(this PlayerState value, PlayerState flag) + public void HandleInput(string action) { - // 检查是否包含标志 - } - - public static PlayerState FromString(string value) - { - // 从字符串解析枚举 + if (action == "jump" && !_state.IsJumping()) + { + _state = PlayerState.Jumping; + } } } ``` -## 常用方法 +## 限制 -### 获取描述 +1. **只支持枚举类型**:不能用于类或结构体 +2. **不支持 Flags 枚举的特殊处理**:对于 `[Flags]` 枚举,生成的方法仍然是简单的相等比较 +3. **不生成其他方法**:只生成 IsX 和 IsIn 方法 -```csharp -[EnumExtensions] -public enum ItemQuality -{ - [Description("普通")] - Common, - - [Description("稀有")] - Rare, - - [Description("史诗")] - Epic -} - -public void PrintQuality(ItemQuality quality) -{ - // 获取描述文本 - Console.WriteLine(quality.GetDescription()); - // 输出: "普通" / "稀有" / "史诗" -} -``` - -### 安全解析 - -```csharp -public void ParseState(string input) -{ - // 安全地解析字符串为枚举 - if (PlayerState.Running.TryParse(input, out var state)) - { - Console.WriteLine($"状态: {state}"); - } -} -``` - -### 获取所有值 - -```csharp -public void ListAllStates() -{ - // 获取所有枚举值 - foreach (var state in PlayerState.GetAllValues()) - { - Console.WriteLine(state); - } -} -``` - -## 标志枚举 - -对于使用 `[Flags]` 特性的枚举: - -```csharp -[EnumExtensions] -[Flags] -public enum PlayerPermissions -{ - None = 0, - Read = 1, - Write = 2, - Execute = 4, - All = Read | Write | Execute -} - -public void CheckPermissions() -{ - var permissions = PlayerPermissions.Read | PlayerPermissions.Write; - - // 检查是否包含特定权限 - if (permissions.HasFlag(PlayerPermissions.Write)) - { - Console.WriteLine("有写入权限"); - } - - // 获取所有设置的标志 - foreach (var flag in permissions.GetFlags()) - { - Console.WriteLine($"权限: {flag}"); - } -} -``` - -## 自定义行为 - -### 忽略某些值 - -```csharp -[EnumExtensions(IgnoreValues = new[] { ItemQuality.Undefined })] -public enum ItemQuality -{ - Undefined, - Common, - Rare, - Epic -} - -// GetAllValues() 不会返回 Undefined -``` - -### 自定义转换 - -```csharp -[EnumExtensions(CaseSensitive = false)] -public enum Difficulty -{ - Easy, - Medium, - Hard -} - -// FromString("EASY") 也能正确解析 -``` - ---- - -**相关文档**: +## 相关文档 - [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..c404cdb 100644 --- a/docs/zh-CN/source-generators/index.md +++ b/docs/zh-CN/source-generators/index.md @@ -107,46 +107,41 @@ public partial class PlayerController ```csharp // -using Microsoft.Extensions.Logging; - -namespace YourNamespace +public partial class PlayerController { - public partial class PlayerController - { - private static readonly ILogger Logger = - LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger(); - } + private static readonly ILogger Logger = + LoggerFactoryResolver.Provider.CreateLogger("YourNamespace.PlayerController"); } ``` +**注意**:生成器只生成 ILogger 字段,不生成日志方法。日志方法(Info、Debug、Error 等)来自 ILogger 接口本身。 + ### 高级配置 ```csharp [Log( - fieldName = "CustomLogger", // 自定义字段名 - accessModifier = AccessModifier.Public, // 访问修饰符 - isStatic = false, // 是否为静态字段 - loggerName = "Custom.PlayerLogger", // 自定义日志器名称 - includeLoggerInterface = true // 是否包含 ILogger 接口 + Name = "Custom.PlayerLogger", // 自定义日志分类名称 + FieldName = "CustomLogger", // 自定义字段名 + IsStatic = false, // 是否为静态字段 + AccessModifier = "public" // 访问修饰符 )] public partial class CustomLoggerExample { public void LogSomething() { - CustomLogger.LogInformation("Custom logger message"); + CustomLogger.Info("Custom logger message"); } } ``` ### 配置选项说明 -| 参数 | 类型 | 默认值 | 说明 | -|--------------------------|----------------|----------|---------------------| -| `fieldName` | string | "Logger" | 生成的日志字段名称 | -| `accessModifier` | AccessModifier | Private | 字段访问修饰符 | -| `isStatic` | bool | true | 是否生成静态字段 | -| `loggerName` | string | null | 自定义日志器名称,null 时使用类名 | -| `includeLoggerInterface` | bool | false | 是否包含 ILogger 接口实现 | +| 参数 | 类型 | 默认值 | 说明 | +|----------------|---------|-----------|---------------------------------| +| Name | string? | null | 日志分类名称(默认使用类名) | +| FieldName | string | "Logger" | 生成的字段名称 | +| IsStatic | bool | true | 是否生成静态字段 | +| AccessModifier | string | "private" | 访问修饰符(private/protected/public) | ### 静态类支持 @@ -162,21 +157,6 @@ public static partial class MathHelper } ``` -### 日志级别控制 - -```csharp -[Log(minLevel = LogLevel.Warning)] -public partial class WarningOnlyLogger -{ - public void ProcessData() - { - Logger.Debug("This won't be logged"); // 低于最小级别,被过滤 - Logger.Warning("This will be logged"); - Logger.Error("This will also be logged"); - } -} -``` - ## ContextAware 属性生成器 [ContextAware] 属性自动实现 IContextAware 接口,提供便捷的架构上下文访问能力。 @@ -184,8 +164,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 +175,7 @@ public partial class PlayerController : IController // Context 属性自动生成,提供架构上下文访问 var playerModel = Context.GetModel(); var combatSystem = Context.GetSystem(); - + Context.SendEvent(new PlayerInitializedEvent()); } } @@ -207,57 +187,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(); } } ``` @@ -289,7 +296,7 @@ public partial class AdvancedController : IController ### 基础使用 ```csharp -using GFramework.SourceGenerators.Attributes; +using GFramework.SourceGenerators.Abstractions.enums; [GenerateEnumExtensions] public enum GameState @@ -307,10 +314,12 @@ public static class GameStateExtensions public static bool IsPaused(this GameState state) => state == GameState.Paused; public static bool IsGameOver(this GameState state) => state == GameState.GameOver; public static bool IsMenu(this GameState state) => state == GameState.Menu; - + public static bool IsIn(this GameState state, params GameState[] values) { - return values.Contains(state); + if (values == null) return false; + foreach (var v in values) if (state == v) return true; + return false; } } @@ -318,17 +327,17 @@ public static class GameStateExtensions public class GameManager { private GameState _currentState = GameState.Menu; - + public bool CanProcessInput() { return _currentState.IsPlaying() || _currentState.IsMenu(); } - + public bool IsGameOver() { return _currentState.IsGameOver(); } - + public bool IsActiveState() { return _currentState.IsIn(GameState.Playing, GameState.Menu); @@ -336,15 +345,12 @@ public class GameManager } ``` -### 自定义扩展方法 +### 配置选项 ```csharp [GenerateEnumExtensions( - generateIsMethods = true, - generateHasMethod = true, - generateInMethod = true, - customPrefix = "Is", - includeToString = true + GenerateIsMethods = true, // 是否生成 IsX 方法(默认 true) + GenerateIsInMethod = true // 是否生成 IsIn 方法(默认 true) )] public enum PlayerState { @@ -354,84 +360,14 @@ public enum PlayerState Jumping, Attacking } - -// 生成更多扩展方法 -public static class PlayerStateExtensions -{ - public static bool IsIdle(this PlayerState state) => state == PlayerState.Idle; - public static bool IsWalking(this PlayerState state) => state == PlayerState.Walking; - public static bool IsRunning(this PlayerState state) => state == PlayerState.Running; - public static bool IsJumping(this PlayerState state) => state == PlayerState.Jumping; - public static bool IsAttacking(this PlayerState state) => state == PlayerState.Attacking; - - public static bool HasIdle(this PlayerState state) => state == PlayerState.Idle; - public static bool HasWalking(this PlayerState state) => state == PlayerState.Walking; - // ... 其他 Has 方法 - - public static bool In(this PlayerState state, params PlayerState[] values) - { - return values.Contains(state); - } - - public static string ToDisplayString(this PlayerState state) - { - return state switch - { - PlayerState.Idle => "Idle", - PlayerState.Walking => "Walking", - PlayerState.Running => "Running", - PlayerState.Jumping => "Jumping", - PlayerState.Attacking => "Attacking", - _ => state.ToString() - }; - } -} -``` - -### 位标志枚举支持 - -```csharp -[GenerateEnumExtensions] -[Flags] -public enum PlayerAbilities -{ - None = 0, - Jump = 1 << 0, - Run = 1 << 1, - Attack = 1 << 2, - Defend = 1 << 3, - Magic = 1 << 4 -} - -// 生成位标志扩展方法 -public static class PlayerAbilitiesExtensions -{ - public static bool HasJump(this PlayerAbilities abilities) => abilities.HasFlag(PlayerAbilities.Jump); - public static bool HasRun(this PlayerAbilities abilities) => abilities.HasFlag(PlayerAbilities.Run); - // ... 其他位标志方法 - - public static bool HasAny(this PlayerAbilities abilities, params PlayerAbilities[] flags) - { - return flags.Any(flag => abilities.HasFlag(flag)); - } - - public static bool HasAll(this PlayerAbilities abilities, params PlayerAbilities[] flags) - { - return flags.All(flag => abilities.HasFlag(flag)); - } -} ``` ### 配置选项说明 -| 参数 | 类型 | 默认值 | 说明 | -|---------------------|--------|-------|------------------------| -| `generateIsMethods` | bool | true | 是否生成 IsX() 方法 | -| `generateHasMethod` | bool | true | 是否生成 HasX() 方法 | -| `generateInMethod` | bool | true | 是否生成 In(params T[]) 方法 | -| `customPrefix` | string | "Is" | 方法名前缀 | -| `includeToString` | bool | false | 是否生成 ToString 扩展 | -| `namespace` | string | null | 生成扩展类的命名空间 | +| 参数 | 类型 | 默认值 | 说明 | +|--------------------|------|------|-------------------| +| GenerateIsMethods | bool | true | 是否为每个枚举值生成 IsX 方法 | +| GenerateIsInMethod | bool | true | 是否生成 IsIn 方法 | ## 诊断信息 @@ -812,7 +748,7 @@ public enum State ```csharp // 好的做法:合理的日志级别 -[Log(minLevel = LogLevel.Information)] +[Log] public partial class PerformanceCriticalComponent { public void Update() @@ -826,7 +762,7 @@ public partial class PerformanceCriticalComponent } // 避免:过度日志记录 -[Log(minLevel = LogLevel.Debug)] +[Log] public partial class NoisyComponent { public void Update() @@ -836,47 +772,8 @@ public partial class NoisyComponent } ``` -#### 2. 延迟上下文初始化 - -```csharp -// 好的做法:延迟初始化 -[ContextAware(useLazy = true)] -public partial class LazyContextComponent : IComponent -{ - // 只有在第一次访问 Context 时才会初始化 - public void Initialize() - { - // 如果这里不需要 Context,就不会初始化 - SomeOtherInitialization(); - } -} -``` - ### 🛡️ 错误处理 -#### 1. 上下文验证 - -```csharp -[ContextAware(validateContext = true)] -public partial class SafeContextComponent : IComponent -{ - public void ProcessData() - { - if (Context.IsInvalid) - { - Logger.Error("Context is invalid, cannot process data"); - return; - } - - // 安全地使用 Context - var model = Context.GetModel(); - // ... - } -} -``` - -#### 2. 异常处理配合 - ```csharp [Log] [ContextAware] diff --git a/docs/zh-CN/source-generators/logging-generator.md b/docs/zh-CN/source-generators/logging-generator.md index 0caf9d7..88bbc44 100644 --- a/docs/zh-CN/source-generators/logging-generator.md +++ b/docs/zh-CN/source-generators/logging-generator.md @@ -4,22 +4,21 @@ ## 概述 -日志生成器是一个 Source Generator,它会自动为标记了 `[Log]` 特性的类生成 Logger 字段和日志方法调用。这消除了手动编写日志代码的需要,让开发者专注于业务逻辑。 +日志生成器是一个 Source Generator,它会自动为标记了 `[Log]` 特性的类生成 ILogger 字段。这消除了手动编写日志字段的需要,让开发者专注于业务逻辑。 ## 基本用法 ### 标记类 ```csharp -using GFramework.SourceGenerators.Attributes; +using GFramework.SourceGenerators.Abstractions.logging; [Log] public partial class MyService { public void DoSomething() { - // 自动生成 Logger 字段 - // 自动生成日志调用 + // 自动生成的 Logger 字段可直接使用 Logger.Info("执行操作"); } } @@ -30,31 +29,19 @@ public partial class MyService 上面的代码会被编译时转换为: ```csharp +// public partial class MyService { - // 自动生成的字段 - [CompilerGenerated] - private ILogger _logger; - - // 自动生成的属性 - [CompilerGenerated] - public ILogger Logger - { - get - { - if (_logger == null) - { - _logger = LoggerFactory.CreateLogger(); - } - return _logger; - } - } + private static readonly ILogger Logger = + LoggerFactoryResolver.Provider.CreateLogger("YourNamespace.MyService"); } ``` +**注意**:生成器只生成 ILogger 字段,不生成日志方法。日志方法(Info、Debug、Error 等)来自 ILogger 接口本身。 + ## 日志级别 -生成的日志方法支持多种级别: +生成的 Logger 字段支持 ILogger 接口的所有方法: ```csharp [Log] @@ -83,7 +70,7 @@ public partial class MyClass ## 自定义日志类别 ```csharp -[Log(LogCategory.Gameplay)] +[Log("Gameplay")] public partial class GameplaySystem { // 日志会标记为 Gameplay 类别 @@ -94,6 +81,55 @@ public partial class GameplaySystem } ``` +## 配置选项 + +### 自定义字段名称 + +```csharp +[Log(FieldName = "_customLogger")] +public partial class MyClass +{ + public void DoSomething() + { + // 使用自定义字段名 + _customLogger.Info("使用自定义日志器"); + } +} +``` + +### 非静态字段 + +```csharp +[Log(IsStatic = false)] +public partial class InstanceLogger +{ + // 生成实例字段而非静态字段 + public void LogMessage() + { + Logger.Info("实例日志"); + } +} +``` + +### 访问修饰符 + +```csharp +[Log(AccessModifier = "protected")] +public partial class ProtectedLogger +{ + // 生成 protected 字段 +} +``` + +### 配置选项说明 + +| 参数 | 类型 | 默认值 | 说明 | +|----------------|---------|-----------|---------------------------------| +| Name | string? | null | 日志分类名称(默认使用类名) | +| FieldName | string | "Logger" | 生成的字段名称 | +| IsStatic | bool | true | 是否生成静态字段 | +| AccessModifier | string | "private" | 访问修饰符(private/protected/public) | + ## 与其他模块集成 ### 与 Godot 集成 @@ -123,38 +159,179 @@ public partial class MySystem : AbstractSystem } ``` -## 配置选项 +## 实际应用示例 -### 禁用自动生成 +### 游戏控制器 ```csharp -// 禁用自动日志调用生成 -[Log(AutoLog = false)] -public partial class MyClass +[Log] +[ContextAware] +public partial class PlayerController : IController { - // 仍会生成 Logger 字段,但不会自动生成日志调用 - public void DoSomething() + public void HandleInput(string action) { - // 需要手动调用 Logger - Logger.Info("手动日志"); + Logger.Debug($"处理输入: {action}"); + + switch (action) + { + case "jump": + Logger.Info("玩家跳跃"); + Jump(); + break; + case "attack": + Logger.Info("玩家攻击"); + Attack(); + break; + default: + Logger.Warning($"未知操作: {action}"); + break; + } + } + + private void Jump() + { + try + { + // 跳跃逻辑 + Logger.Debug("跳跃执行成功"); + } + catch (Exception ex) + { + Logger.Error($"跳跃失败: {ex.Message}"); + } } } ``` -### 自定义字段名称 +### 数据处理服务 ```csharp -[Log(FieldName = "_customLogger")] -public partial class MyClass +[Log("DataService")] +public partial class DataProcessor { - // Logger 字段名称为 _customLogger + public void ProcessData(string data) + { + Logger.Info($"开始处理数据,长度: {data.Length}"); + + if (string.IsNullOrEmpty(data)) + { + Logger.Warning("数据为空,跳过处理"); + return; + } + + try + { + // 处理逻辑 + Logger.Debug("数据处理中..."); + // ... + Logger.Info("数据处理完成"); + } + catch (Exception ex) + { + Logger.Error($"数据处理失败: {ex.Message}"); + throw; + } + } } ``` +## 最佳实践 + +### 1. 合理使用日志级别 + +```csharp +[Log] +public partial class BestPracticeExample +{ + public void ProcessRequest() + { + // Debug: 详细的调试信息 + Logger.Debug("开始处理请求"); + + // Info: 重要的业务流程信息 + Logger.Info("请求处理成功"); + + // Warning: 可恢复的异常情况 + Logger.Warning("缓存未命中,使用默认值"); + + // Error: 错误但不影响系统运行 + Logger.Error("处理失败,将重试"); + + // Critical: 严重错误,可能导致系统崩溃 + Logger.Critical("数据库连接失败"); + } +} +``` + +### 2. 避免过度日志 + +```csharp +[Log] +public partial class PerformanceExample +{ + private int _frameCount = 0; + + public void Update() + { + // 好的做法:定期记录 + if (_frameCount % 1000 == 0) + { + Logger.Debug($"已运行 {_frameCount} 帧"); + } + _frameCount++; + + // 避免:每帧都记录 + // Logger.Debug($"帧 {_frameCount}"); // ❌ 太频繁 + } +} +``` + +### 3. 结构化日志信息 + +```csharp +[Log] +public partial class StructuredLogging +{ + public void ProcessUser(int userId, string action) + { + // 好的做法:包含上下文信息 + Logger.Info($"用户操作 [UserId={userId}, Action={action}]"); + + // 避免:信息不完整 + // Logger.Info("用户操作"); // ❌ 缺少上下文 + } +} +``` + +## 常见问题 + +### Q: 为什么需要 partial 关键字? + +**A**: 源代码生成器需要向现有类添加代码,`partial` 关键字允许一个类的定义分散在多个文件中。 + +### Q: 可以在静态类中使用吗? + +**A**: 可以,生成器会自动生成静态字段: + +```csharp +[Log] +public static partial class StaticHelper +{ + public static void DoSomething() + { + Logger.Info("静态方法日志"); + } +} +``` + +### Q: 如何自定义日志工厂? + +**A**: 通过配置 `LoggerFactoryResolver.Provider` 来自定义日志工厂实现。 + --- **相关文档**: - [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)