refactor(docs): 将控制器实现转换为上下文感知模式

- 将 IController 实现改为使用 [ContextAware] 特性和 partial 类
- 移除手动实现的 GetArchitecture 方法
- 将架构交互方法从 this 调用改为 Context 调用
- 添加必要的 using 语句以支持新的架构访问方式
- 更新所有涉及模型、系统、工具和事件的架构访问代码
- 统一架构访问模式以提高代码一致性和可维护性
This commit is contained in:
GeWuYou 2026-03-08 11:03:02 +08:00
parent 5190b7a463
commit 1b9e81bbdb
26 changed files with 18344 additions and 18165 deletions

View File

@ -1,10 +1,38 @@
namespace GFramework.Core.Abstractions.controller;
/// <summary>
/// 控制器接口,定义了控制器的基本契约和行为规范
/// </summary>
/// <remarks>
/// 该接口为框架中的控制器组件提供统一的抽象定义,
/// 用于实现控制器的标准功能和生命周期管理
/// </remarks>
namespace GFramework.Core.Abstractions.controller;
/// <summary>
/// 控制器标记接口,用于标识控制器组件
/// </summary>
/// <remarks>
/// <para>
/// IController 是一个标记接口Marker Interface不包含任何方法或属性。
/// 它的作用是标识一个类是控制器,用于协调 Model、System 和 UI 之间的交互。
/// </para>
/// <para>
/// <strong>架构访问</strong>:控制器通常需要访问架构上下文。使用 [ContextAware] 特性
/// 自动生成上下文访问能力:
/// </para>
/// <code>
/// using GFramework.SourceGenerators.Abstractions.rule;
///
/// [ContextAware]
/// public partial class PlayerController : IController
/// {
/// public void Initialize()
/// {
/// // Context 属性由 [ContextAware] 自动生成
/// var playerModel = Context.GetModel&lt;PlayerModel&gt;();
/// var gameSystem = Context.GetSystem&lt;GameSystem&gt;();
/// }
/// }
/// </code>
/// <para>
/// <strong>注意</strong>
/// </para>
/// <list type="bullet">
/// <item>必须添加 partial 关键字</item>
/// <item>[ContextAware] 特性会自动实现 IContextAware 接口</item>
/// <item>Context 属性提供架构上下文访问</item>
/// </list>
/// </remarks>
public interface IController;

File diff suppressed because it is too large Load Diff

View File

@ -1,483 +1,491 @@
# 最佳实践
本文档总结了使用 GFramework 的最佳实践和设计模式。
## 架构设计
### 1. 清晰的职责分离
**原则**:每一层都有明确的职责,不要混淆。
```csharp
// ✅ 正确的职责分离
public class PlayerModel : AbstractModel
{
// Model只存储数据
public BindableProperty<int> Health { get; } = new(100);
public BindableProperty<int> Score { get; } = new(0);
}
public class CombatSystem : AbstractSystem
{
// System处理业务逻辑
protected override void OnInit()
{
this.RegisterEvent<AttackEvent>(OnAttack);
}
private void OnAttack(AttackEvent e)
{
var player = this.GetModel<PlayerModel>();
player.Health.Value -= e.Damage;
}
}
public class PlayerController : IController
{
// Controller连接 UI 和逻辑
public void Initialize()
{
var player = _architecture.GetModel<PlayerModel>();
player.Health.RegisterWithInitValue(OnHealthChanged);
}
private void OnHealthChanged(int health)
{
UpdateHealthDisplay(health);
}
}
```
### 2. 事件驱动设计
**原则**:使用事件解耦组件,避免直接调用。
```csharp
// ❌ 紧耦合
public class SystemA : AbstractSystem
{
private void OnEvent(EventA e)
{
var systemB = this.GetSystem<SystemB>();
systemB.DoSomething(); // 直接调用
}
}
// ✅ 松耦合
public class SystemA : AbstractSystem
{
private void OnEvent(EventA e)
{
this.SendEvent(new EventB()); // 发送事件
}
}
public class SystemB : AbstractSystem
{
protected override void OnInit()
{
this.RegisterEvent<EventB>(OnEventB);
}
}
```
### 3. 命令查询分离
**原则**明确区分修改状态Command和查询状态Query
```csharp
// ✅ 正确的 CQRS
public class MovePlayerCommand : AbstractCommand
{
public Vector2 Direction { get; set; }
protected override void OnDo()
{
// 修改状态
this.SendEvent(new PlayerMovedEvent { Direction = Direction });
}
}
public class GetPlayerPositionQuery : AbstractQuery<Vector2>
{
protected override Vector2 OnDo()
{
// 只查询,不修改
return this.GetModel<PlayerModel>().Position.Value;
}
}
```
## 代码组织
### 1. 项目结构
```
GameProject/
├── Models/
│ ├── PlayerModel.cs
│ ├── GameStateModel.cs
│ └── InventoryModel.cs
├── Systems/
│ ├── CombatSystem.cs
│ ├── InventorySystem.cs
│ └── GameLogicSystem.cs
├── Commands/
│ ├── AttackCommand.cs
│ ├── MoveCommand.cs
│ └── UseItemCommand.cs
├── Queries/
│ ├── GetPlayerHealthQuery.cs
│ └── GetInventoryItemsQuery.cs
├── Events/
│ ├── PlayerDiedEvent.cs
│ ├── ItemUsedEvent.cs
│ └── EnemyDamagedEvent.cs
├── Controllers/
│ ├── PlayerController.cs
│ └── UIController.cs
├── Utilities/
│ ├── StorageUtility.cs
│ └── MathUtility.cs
└── GameArchitecture.cs
```
### 2. 命名规范
```csharp
// Models使用 Model 后缀
public class PlayerModel : AbstractModel { }
public class GameStateModel : AbstractModel { }
// Systems使用 System 后缀
public class CombatSystem : AbstractSystem { }
public class InventorySystem : AbstractSystem { }
// Commands使用 Command 后缀
public class AttackCommand : AbstractCommand { }
public class MoveCommand : AbstractCommand { }
// Queries使用 Query 后缀
public class GetPlayerHealthQuery : AbstractQuery<int> { }
public class GetInventoryItemsQuery : AbstractQuery<List<Item>> { }
// Events使用 Event 后缀
public class PlayerDiedEvent : IEvent { }
public class ItemUsedEvent : IEvent { }
// Controllers使用 Controller 后缀
public class PlayerController : IController { }
// Utilities使用 Utility 后缀
public class StorageUtility : IUtility { }
```
## 内存管理
### 1. 正确的注销管理
```csharp
public class MyController : IController
{
private IUnRegisterList _unregisterList = new UnRegisterList();
public void Initialize()
{
var model = _architecture.GetModel<PlayerModel>();
// 注册事件并添加到注销列表
this.RegisterEvent<PlayerDiedEvent>(OnPlayerDied)
.AddToUnregisterList(_unregisterList);
// 注册属性监听并添加到注销列表
model.Health.Register(OnHealthChanged)
.AddToUnregisterList(_unregisterList);
}
public void Cleanup()
{
// 统一注销所有监听器
_unregisterList.UnRegisterAll();
}
private void OnPlayerDied(PlayerDiedEvent e) { }
private void OnHealthChanged(int health) { }
}
```
### 2. 生命周期管理
```csharp
public class GameManager
{
private GameArchitecture _architecture;
public void StartGame()
{
_architecture = new GameArchitecture();
_architecture.Initialize();
}
public void EndGame()
{
// 销毁架构,自动清理所有组件
_architecture.Destroy();
_architecture = null;
}
}
```
## 性能优化
### 1. 缓存组件引用
```csharp
// ❌ 低效:每次都查询
public void Update()
{
var model = _architecture.GetModel<PlayerModel>();
model.Health.Value -= 1;
}
// ✅ 高效:缓存引用
private PlayerModel _playerModel;
public void Initialize()
{
_playerModel = _architecture.GetModel<PlayerModel>();
}
public void Update()
{
_playerModel.Health.Value -= 1;
}
```
### 2. 避免频繁的事件创建
```csharp
// ❌ 低效:每帧创建新事件
public void Update()
{
this.SendEvent(new UpdateEvent()); // 频繁分配内存
}
// ✅ 高效:复用事件或使用对象池
private UpdateEvent _updateEvent = new UpdateEvent();
public void Update()
{
this.SendEvent(_updateEvent);
}
```
### 3. 异步处理重操作
```csharp
public class LoadDataCommand : AbstractCommand
{
protected override async void OnDo()
{
// 异步加载数据,不阻塞主线程
var data = await LoadDataAsync();
this.SendEvent(new DataLoadedEvent { Data = data });
}
private async Task<Data> LoadDataAsync()
{
return await Task.Run(() =>
{
// 耗时操作
return new Data();
});
}
}
```
## 测试
### 1. 单元测试
```csharp
[TestFixture]
public class CombatSystemTests
{
private GameArchitecture _architecture;
private PlayerModel _playerModel;
[SetUp]
public void Setup()
{
_architecture = new TestArchitecture();
_architecture.Initialize();
_playerModel = _architecture.GetModel<PlayerModel>();
}
[TearDown]
public void Teardown()
{
_architecture.Destroy();
}
[Test]
public void PlayerTakeDamage_ReducesHealth()
{
_playerModel.Health.Value = 100;
_architecture.SendEvent(new DamageEvent { Amount = 10 });
Assert.AreEqual(90, _playerModel.Health.Value);
}
[Test]
public void PlayerDies_WhenHealthReachesZero()
{
_playerModel.Health.Value = 10;
_architecture.SendEvent(new DamageEvent { Amount = 10 });
Assert.AreEqual(0, _playerModel.Health.Value);
}
}
```
### 2. 集成测试
```csharp
[TestFixture]
public class GameFlowTests
{
private GameArchitecture _architecture;
[SetUp]
public void Setup()
{
_architecture = new GameArchitecture();
_architecture.Initialize();
}
[Test]
public void CompleteGameFlow()
{
// 初始化
var player = _architecture.GetModel<PlayerModel>();
Assert.AreEqual(100, player.Health.Value);
// 执行操作
_architecture.SendCommand(new AttackCommand { Damage = 20 });
// 验证结果
Assert.AreEqual(80, player.Health.Value);
}
}
```
## 文档
### 1. 代码注释
```csharp
/// <summary>
/// 玩家模型,存储玩家的所有状态数据
/// </summary>
public class PlayerModel : AbstractModel
{
/// <summary>
/// 玩家的生命值,使用 BindableProperty 实现响应式更新
/// </summary>
public BindableProperty<int> Health { get; } = new(100);
protected override void OnInit()
{
// 监听生命值变化,当生命值为 0 时发送死亡事件
Health.Register(hp =>
{
if (hp <= 0)
this.SendEvent(new PlayerDiedEvent());
});
}
}
```
### 2. 架构文档
为你的项目编写架构文档,说明:
- 主要的 Model、System、Command、Query
- 关键事件流
- 组件间的通信方式
- 扩展点和插件机制
## 常见陷阱
### 1. 在 Model 中包含业务逻辑
```csharp
// ❌ 错误
public class PlayerModel : AbstractModel
{
public void TakeDamage(int damage)
{
Health.Value -= damage;
if (Health.Value <= 0)
Die();
}
}
// ✅ 正确
public class CombatSystem : AbstractSystem
{
private void OnDamage(DamageEvent e)
{
var player = this.GetModel<PlayerModel>();
player.Health.Value -= e.Amount;
}
}
```
### 2. 忘记注销监听器
```csharp
// ❌ 错误:可能导致内存泄漏
public void Initialize()
{
this.RegisterEvent<Event1>(OnEvent1); // 未注销
}
// ✅ 正确
private IUnRegisterList _unregisterList = new UnRegisterList();
public void Initialize()
{
this.RegisterEvent<Event1>(OnEvent1)
.AddToUnregisterList(_unregisterList);
}
public void Cleanup()
{
_unregisterList.UnRegisterAll();
}
```
### 3. 直接调用其他系统
```csharp
// ❌ 错误:紧耦合
public class SystemA : AbstractSystem
{
private void OnEvent(EventA e)
{
var systemB = this.GetSystem<SystemB>();
systemB.DoSomething();
}
}
// ✅ 正确:使用事件解耦
public class SystemA : AbstractSystem
{
private void OnEvent(EventA e)
{
this.SendEvent(new EventB());
}
}
```
---
遵循这些最佳实践将帮助你构建可维护、高效、可扩展的应用程序。
# 最佳实践
本文档总结了使用 GFramework 的最佳实践和设计模式。
## 架构设计
### 1. 清晰的职责分离
**原则**:每一层都有明确的职责,不要混淆。
```csharp
// ✅ 正确的职责分离
public class PlayerModel : AbstractModel
{
// Model只存储数据
public BindableProperty<int> Health { get; } = new(100);
public BindableProperty<int> Score { get; } = new(0);
}
public class CombatSystem : AbstractSystem
{
// System处理业务逻辑
protected override void OnInit()
{
this.RegisterEvent<AttackEvent>(OnAttack);
}
private void OnAttack(AttackEvent e)
{
var player = this.GetModel<PlayerModel>();
player.Health.Value -= e.Damage;
}
}
using GFramework.Core.Abstractions.controller;
using GFramework.SourceGenerators.Abstractions.rule;
[ContextAware]
public partial class PlayerController : IController
{
// Controller连接 UI 和逻辑
public void Initialize()
{
var player = Context.GetModel<PlayerModel>();
player.Health.RegisterWithInitValue(OnHealthChanged);
}
private void OnHealthChanged(int health)
{
UpdateHealthDisplay(health);
}
}
```
### 2. 事件驱动设计
**原则**:使用事件解耦组件,避免直接调用。
```csharp
// ❌ 紧耦合
public class SystemA : AbstractSystem
{
private void OnEvent(EventA e)
{
var systemB = this.GetSystem<SystemB>();
systemB.DoSomething(); // 直接调用
}
}
// ✅ 松耦合
public class SystemA : AbstractSystem
{
private void OnEvent(EventA e)
{
this.SendEvent(new EventB()); // 发送事件
}
}
public class SystemB : AbstractSystem
{
protected override void OnInit()
{
this.RegisterEvent<EventB>(OnEventB);
}
}
```
### 3. 命令查询分离
**原则**明确区分修改状态Command和查询状态Query
```csharp
// ✅ 正确的 CQRS
public class MovePlayerCommand : AbstractCommand
{
public Vector2 Direction { get; set; }
protected override void OnDo()
{
// 修改状态
this.SendEvent(new PlayerMovedEvent { Direction = Direction });
}
}
public class GetPlayerPositionQuery : AbstractQuery<Vector2>
{
protected override Vector2 OnDo()
{
// 只查询,不修改
return this.GetModel<PlayerModel>().Position.Value;
}
}
```
## 代码组织
### 1. 项目结构
```
GameProject/
├── Models/
│ ├── PlayerModel.cs
│ ├── GameStateModel.cs
│ └── InventoryModel.cs
├── Systems/
│ ├── CombatSystem.cs
│ ├── InventorySystem.cs
│ └── GameLogicSystem.cs
├── Commands/
│ ├── AttackCommand.cs
│ ├── MoveCommand.cs
│ └── UseItemCommand.cs
├── Queries/
│ ├── GetPlayerHealthQuery.cs
│ └── GetInventoryItemsQuery.cs
├── Events/
│ ├── PlayerDiedEvent.cs
│ ├── ItemUsedEvent.cs
│ └── EnemyDamagedEvent.cs
├── Controllers/
│ ├── PlayerController.cs
│ └── UIController.cs
├── Utilities/
│ ├── StorageUtility.cs
│ └── MathUtility.cs
└── GameArchitecture.cs
```
### 2. 命名规范
```csharp
// Models使用 Model 后缀
public class PlayerModel : AbstractModel { }
public class GameStateModel : AbstractModel { }
// Systems使用 System 后缀
public class CombatSystem : AbstractSystem { }
public class InventorySystem : AbstractSystem { }
// Commands使用 Command 后缀
public class AttackCommand : AbstractCommand { }
public class MoveCommand : AbstractCommand { }
// Queries使用 Query 后缀
public class GetPlayerHealthQuery : AbstractQuery<int> { }
public class GetInventoryItemsQuery : AbstractQuery<List<Item>> { }
// Events使用 Event 后缀
public class PlayerDiedEvent : IEvent { }
public class ItemUsedEvent : IEvent { }
// Controllers使用 Controller 后缀
public class PlayerController : IController { }
// Utilities使用 Utility 后缀
public class StorageUtility : IUtility { }
```
## 内存管理
### 1. 正确的注销管理
```csharp
using GFramework.Core.Abstractions.controller;
using GFramework.SourceGenerators.Abstractions.rule;
[ContextAware]
public partial class MyController : IController
{
private IUnRegisterList _unregisterList = new UnRegisterList();
public void Initialize()
{
var model = Context.GetModel<PlayerModel>();
// 注册事件并添加到注销列表
Context.RegisterEvent<PlayerDiedEvent>(OnPlayerDied)
.AddToUnregisterList(_unregisterList);
// 注册属性监听并添加到注销列表
model.Health.Register(OnHealthChanged)
.AddToUnregisterList(_unregisterList);
}
public void Cleanup()
{
// 统一注销所有监听器
_unregisterList.UnRegisterAll();
}
private void OnPlayerDied(PlayerDiedEvent e) { }
private void OnHealthChanged(int health) { }
}
```
### 2. 生命周期管理
```csharp
public class GameManager
{
private GameArchitecture _architecture;
public void StartGame()
{
_architecture = new GameArchitecture();
_architecture.Initialize();
}
public void EndGame()
{
// 销毁架构,自动清理所有组件
_architecture.Destroy();
_architecture = null;
}
}
```
## 性能优化
### 1. 缓存组件引用
```csharp
// ❌ 低效:每次都查询
public void Update()
{
var model = Context.GetModel<PlayerModel>();
model.Health.Value -= 1;
}
// ✅ 高效:缓存引用
private PlayerModel _playerModel;
public void Initialize()
{
_playerModel = Context.GetModel<PlayerModel>();
}
public void Update()
{
_playerModel.Health.Value -= 1;
}
```
### 2. 避免频繁的事件创建
```csharp
// ❌ 低效:每帧创建新事件
public void Update()
{
Context.SendEvent(new UpdateEvent()); // 频繁分配内存
}
// ✅ 高效:复用事件或使用对象池
private UpdateEvent _updateEvent = new UpdateEvent();
public void Update()
{
Context.SendEvent(_updateEvent);
}
```
### 3. 异步处理重操作
```csharp
public class LoadDataCommand : AbstractCommand
{
protected override async void OnDo()
{
// 异步加载数据,不阻塞主线程
var data = await LoadDataAsync();
this.SendEvent(new DataLoadedEvent { Data = data });
}
private async Task<Data> LoadDataAsync()
{
return await Task.Run(() =>
{
// 耗时操作
return new Data();
});
}
}
```
## 测试
### 1. 单元测试
```csharp
[TestFixture]
public class CombatSystemTests
{
private GameArchitecture _architecture;
private PlayerModel _playerModel;
[SetUp]
public void Setup()
{
_architecture = new TestArchitecture();
_architecture.Initialize();
_playerModel = _architecture.GetModel<PlayerModel>();
}
[TearDown]
public void Teardown()
{
_architecture.Destroy();
}
[Test]
public void PlayerTakeDamage_ReducesHealth()
{
_playerModel.Health.Value = 100;
_architecture.SendEvent(new DamageEvent { Amount = 10 });
Assert.AreEqual(90, _playerModel.Health.Value);
}
[Test]
public void PlayerDies_WhenHealthReachesZero()
{
_playerModel.Health.Value = 10;
_architecture.SendEvent(new DamageEvent { Amount = 10 });
Assert.AreEqual(0, _playerModel.Health.Value);
}
}
```
### 2. 集成测试
```csharp
[TestFixture]
public class GameFlowTests
{
private GameArchitecture _architecture;
[SetUp]
public void Setup()
{
_architecture = new GameArchitecture();
_architecture.Initialize();
}
[Test]
public void CompleteGameFlow()
{
// 初始化
var player = _architecture.GetModel<PlayerModel>();
Assert.AreEqual(100, player.Health.Value);
// 执行操作
_architecture.SendCommand(new AttackCommand { Damage = 20 });
// 验证结果
Assert.AreEqual(80, player.Health.Value);
}
}
```
## 文档
### 1. 代码注释
```csharp
/// <summary>
/// 玩家模型,存储玩家的所有状态数据
/// </summary>
public class PlayerModel : AbstractModel
{
/// <summary>
/// 玩家的生命值,使用 BindableProperty 实现响应式更新
/// </summary>
public BindableProperty<int> Health { get; } = new(100);
protected override void OnInit()
{
// 监听生命值变化,当生命值为 0 时发送死亡事件
Health.Register(hp =>
{
if (hp <= 0)
this.SendEvent(new PlayerDiedEvent());
});
}
}
```
### 2. 架构文档
为你的项目编写架构文档,说明:
- 主要的 Model、System、Command、Query
- 关键事件流
- 组件间的通信方式
- 扩展点和插件机制
## 常见陷阱
### 1. 在 Model 中包含业务逻辑
```csharp
// ❌ 错误
public class PlayerModel : AbstractModel
{
public void TakeDamage(int damage)
{
Health.Value -= damage;
if (Health.Value <= 0)
Die();
}
}
// ✅ 正确
public class CombatSystem : AbstractSystem
{
private void OnDamage(DamageEvent e)
{
var player = this.GetModel<PlayerModel>();
player.Health.Value -= e.Amount;
}
}
```
### 2. 忘记注销监听器
```csharp
// ❌ 错误:可能导致内存泄漏
public void Initialize()
{
Context.RegisterEvent<Event1>(OnEvent1); // 未注销
}
// ✅ 正确
private IUnRegisterList _unregisterList = new UnRegisterList();
public void Initialize()
{
Context.RegisterEvent<Event1>(OnEvent1)
.AddToUnregisterList(_unregisterList);
}
public void Cleanup()
{
_unregisterList.UnRegisterAll();
}
```
### 3. 直接调用其他系统
```csharp
// ❌ 错误:紧耦合
public class SystemA : AbstractSystem
{
private void OnEvent(EventA e)
{
var systemB = this.GetSystem<SystemB>();
systemB.DoSomething();
}
}
// ✅ 正确:使用事件解耦
public class SystemA : AbstractSystem
{
private void OnEvent(EventA e)
{
this.SendEvent(new EventB());
}
}
```
---
遵循这些最佳实践将帮助你构建可维护、高效、可扩展的应用程序。

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,447 +1,451 @@
# Command 包使用说明
## 概述
Command 包实现了命令模式Command Pattern用于封装用户操作和业务逻辑。通过命令模式可以将请求封装为对象实现操作的参数化、队列化、日志记录、撤销等功能。
命令系统是 GFramework CQRS 架构的重要组成部分,与事件系统和查询系统协同工作,实现完整的业务逻辑处理流程。
## 核心接口
### ICommand
无返回值命令接口,定义了命令的基本契约。
**核心方法:**
```csharp
void Execute(); // 执行命令
```
### ICommand`<TResult>`
带返回值的命令接口,用于需要返回执行结果的命令。
**核心方法:**
```csharp
TResult Execute(); // 执行命令并返回结果
```
## 核心类
### AbstractCommand
无返回值命令的抽象基类,提供了命令的基础实现。它继承自 ContextAwareBase具有上下文感知能力。
**核心方法:**
```csharp
void ICommand.Execute(); // 实现 ICommand 接口
protected abstract void OnExecute(); // 抽象执行方法,由子类实现
```
**使用示例:**
```csharp
// 定义一个无返回值的基础命令
public class SimpleCommand : AbstractCommand
{
protected override void OnExecute()
{
var playerModel = this.GetModel<PlayerModel>();
playerModel.Health.Value = playerModel.MaxHealth.Value;
this.SendEvent(new PlayerHealthRestoredEvent());
}
}
// 使用命令
public class GameController : IController
{
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public void OnRestoreHealthButtonClicked()
{
this.SendCommand(new SimpleCommand());
}
}
```
### AbstractCommand`<TResult>`
无输入参数但带返回值的命令基类。
**核心方法:**
```csharp
TResult ICommand<TResult>.Execute(); // 实现 ICommand<TResult> 接口
protected abstract TResult OnExecute(); // 抽象执行方法,由子类实现
```
**使用示例:**
```csharp
// 定义一个无输入但有返回值的命令
public class GetPlayerHealthQuery : AbstractCommand<int>
{
protected override int OnExecute()
{
var playerModel = this.GetModel<PlayerModel>();
return playerModel.Health.Value;
}
}
// 使用命令
public class UISystem : AbstractSystem
{
protected override void OnInit()
{
this.RegisterEvent<UpdateUIEvent>(OnUpdateUI);
}
private void OnUpdateUI(UpdateUIEvent e)
{
var health = this.SendCommand(new GetPlayerHealthQuery());
Console.WriteLine($"Player health: {health}");
}
}
```
## 命令的生命周期
1. **创建命令**:实例化命令对象,传入必要的参数
2. **执行命令**:调用 `Execute()` 方法,内部委托给 `OnExecute()`
3. **返回结果**:对于带返回值的命令,返回执行结果
4. **命令销毁**:命令执行完毕后可以被垃圾回收
**注意事项:**
- 命令应该是无状态的,执行完即可丢弃
- 避免在命令中保存长期引用
- 命令执行应该是原子操作
## CommandBus - 命令总线
### 功能说明
`CommandBus` 是命令执行的核心组件,负责发送和执行命令。
**主要方法:**
```csharp
void Send(ICommand command); // 发送无返回值命令
TResult Send<TResult>(ICommand<TResult> command); // 发送带返回值命令
```
**特点:**
- 统一的命令执行入口
- 支持同步命令执行
- 与架构上下文集成
### 使用示例
```csharp
// 通过架构获取命令总线
var commandBus = architecture.Context.CommandBus;
// 发送无返回值命令
commandBus.Send(new StartGameCommand(1, "Player1"));
// 发送带返回值命令
var damage = commandBus.Send(new CalculateDamageCommand(100, 50));
```
## 命令基类变体
框架提供了多种命令基类以满足不同需求:
### AbstractCommand`<TInput>`
带输入参数的无返回值命令类。通过 `ICommandInput` 接口传递参数。
**核心方法:**
```csharp
void ICommand.Execute(); // 实现 ICommand 接口
protected abstract void OnExecute(TInput input); // 抽象执行方法,接收输入参数
```
**使用示例:**
```csharp
// 定义输入对象
public class StartGameInput : ICommandInput
{
public int LevelId { get; set; }
public string PlayerName { get; set; }
}
// 定义命令
public class StartGameCommand : AbstractCommand<StartGameInput>
{
protected override void OnExecute(StartGameInput input)
{
var playerModel = this.GetModel<PlayerModel>();
var gameModel = this.GetModel<GameModel>();
playerModel.PlayerName.Value = input.PlayerName;
gameModel.CurrentLevel.Value = input.LevelId;
gameModel.GameState.Value = GameState.Playing;
this.SendEvent(new GameStartedEvent());
}
}
// 使用命令
public class GameController : IController
{
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public void OnStartButtonClicked()
{
var input = new StartGameInput { LevelId = 1, PlayerName = "Player1" };
this.SendCommand(new StartGameCommand { Input = input });
}
}
```
### AbstractCommand`<TInput, TResult>`
既带输入参数又带返回值的命令类。
**核心方法:**
```csharp
TResult ICommand<TResult>.Execute(); // 实现 ICommand<TResult> 接口
protected abstract TResult OnExecute(TInput input); // 抽象执行方法,接收输入参数
```
**使用示例:**
```csharp
// 定义输入对象
public class CalculateDamageInput : ICommandInput
{
public int AttackerAttackPower { get; set; }
public int DefenderDefense { get; set; }
}
// 定义命令
public class CalculateDamageCommand : AbstractCommand<CalculateDamageInput, int>
{
protected override int OnExecute(CalculateDamageInput input)
{
var config = this.GetModel<GameConfigModel>();
var baseDamage = input.AttackerAttackPower - input.DefenderDefense;
var finalDamage = Math.Max(1, baseDamage * config.DamageMultiplier);
return (int)finalDamage;
}
}
// 使用命令
public class CombatSystem : AbstractSystem
{
protected override void OnInit() { }
public void Attack(Character attacker, Character defender)
{
var input = new CalculateDamageInput
{
AttackerAttackPower = attacker.AttackPower,
DefenderDefense = defender.Defense
};
var damage = this.SendCommand(new CalculateDamageCommand { Input = input });
defender.Health -= damage;
this.SendEvent(new DamageDealtEvent(attacker, defender, damage));
}
}
```
### AbstractAsyncCommand`<TInput>`
支持异步执行的带输入参数的无返回值命令基类。
**核心方法:**
```csharp
Task IAsyncCommand.ExecuteAsync(); // 实现异步命令接口
protected abstract Task OnExecuteAsync(TInput input); // 抽象异步执行方法
```
### AbstractAsyncCommand`<TInput, TResult>`
支持异步执行的既带输入参数又带返回值的命令基类。
**核心方法:**
```csharp
Task<TResult> IAsyncCommand<TResult>.ExecuteAsync(); // 实现异步命令接口
protected abstract Task<TResult> OnExecuteAsync(TInput input); // 抽象异步执行方法
```
**使用示例:**
```csharp
// 定义输入对象
public class LoadSaveDataInput : ICommandInput
{
public string SaveSlot { get; set; }
}
// 定义异步命令
public class LoadSaveDataCommand : AbstractAsyncCommand<LoadSaveDataInput, SaveData>
{
protected override async Task<SaveData> OnExecuteAsync(LoadSaveDataInput input)
{
var storage = this.GetUtility<IStorageUtility>();
return await storage.LoadSaveDataAsync(input.SaveSlot);
}
}
// 使用异步命令
public class SaveSystem : AbstractSystem
{
protected override void OnInit()
{
this.RegisterEvent<LoadGameRequestEvent>(OnLoadGameRequest);
}
private async void OnLoadGameRequest(LoadGameRequestEvent e)
{
var input = new LoadSaveDataInput { SaveSlot = e.SaveSlot };
var saveData = await this.SendCommandAsync(new LoadSaveDataCommand { Input = input });
if (saveData != null)
{
this.SendEvent(new GameLoadedEvent { SaveData = saveData });
}
}
}
```
## 命令处理器执行
所有发送给命令总线的命令最终都会通过 `CommandExecutor` 来执行:
```csharp
public class CommandExecutor
{
public static void Execute(ICommand command)
{
command.Execute();
}
public static TResult Execute<TResult>(ICommand<TResult> command)
{
return command.Execute();
}
}
```
**特点:**
- 提供统一的命令执行机制
- 支持同步和异步命令执行
- 可以扩展添加中间件逻辑
## 使用场景
### 1. 用户交互操作
```csharp
public class SaveGameCommand : AbstractCommand
{
private readonly string _saveSlot;
public SaveGameCommand(string saveSlot)
{
_saveSlot = saveSlot;
}
protected override void OnExecute()
{
var saveSystem = this.GetSystem<SaveSystem>();
var playerModel = this.GetModel<PlayerModel>();
saveSystem.SavePlayerData(playerModel, _saveSlot);
this.SendEvent(new GameSavedEvent(_saveSlot));
}
}
```
### 2. 业务流程控制
```csharp
public class LoadLevelCommand : AbstractCommand
{
private readonly int _levelId;
public LoadLevelCommand(int levelId)
{
_levelId = levelId;
}
protected override void OnExecute()
{
var levelSystem = this.GetSystem<LevelSystem>();
var uiSystem = this.GetSystem<UISystem>();
// 显示加载界面
uiSystem.ShowLoadingScreen();
// 加载关卡
levelSystem.LoadLevel(_levelId);
// 发送事件
this.SendEvent(new LevelLoadedEvent(_levelId));
}
}
```
## 最佳实践
1. **保持命令原子性**:一个命令应该完成一个完整的业务操作
2. **命令无状态**:命令不应该保存长期状态,执行完即可丢弃
3. **参数通过构造函数传递**:命令需要的参数应在创建时传入
4. **避免命令嵌套**:命令内部尽量不要发送其他命令,使用事件通信
5. **合理使用返回值**:只在确实需要返回结果时使用带返回值的命令
6. **命令命名规范**:使用动词+名词形式,如 `StartGameCommand``SavePlayerCommand`
7. **单一职责原则**:每个命令只负责一个特定的业务操作
8. **使用异步命令**:对于需要长时间执行的操作,使用异步命令避免阻塞
9. **命令验证**:在命令执行前验证输入参数的有效性
10. **错误处理**:在命令中适当处理异常情况
## 命令模式优势
### 1. 可扩展性
- 命令可以被序列化和存储
- 支持命令队列和批处理
- 便于实现撤销/重做功能
### 2. 可测试性
- 命令逻辑独立,易于单元测试
- 可以模拟命令执行结果
- 支持行为驱动开发
### 3. 可维护性
- 业务逻辑集中管理
- 降低组件间耦合度
- 便于重构和扩展
## 相关包
- [`architecture`](./architecture.md) - 架构核心,负责命令的分发和执行
- [`extensions`](./extensions.md) - 提供 `SendCommand()` 扩展方法
- [`query`](./query.md) - 查询模式,用于数据查询
- [`events`](./events.md) - 事件系统,命令执行后的通知机制
- [`system`](./system.md) - 业务系统,命令的主要执行者
- [`model`](./model.md) - 数据模型,命令操作的数据
---
# Command 包使用说明
## 概述
Command 包实现了命令模式Command Pattern用于封装用户操作和业务逻辑。通过命令模式可以将请求封装为对象实现操作的参数化、队列化、日志记录、撤销等功能。
命令系统是 GFramework CQRS 架构的重要组成部分,与事件系统和查询系统协同工作,实现完整的业务逻辑处理流程。
## 核心接口
### ICommand
无返回值命令接口,定义了命令的基本契约。
**核心方法:**
```csharp
void Execute(); // 执行命令
```
### ICommand`<TResult>`
带返回值的命令接口,用于需要返回执行结果的命令。
**核心方法:**
```csharp
TResult Execute(); // 执行命令并返回结果
```
## 核心类
### AbstractCommand
无返回值命令的抽象基类,提供了命令的基础实现。它继承自 ContextAwareBase具有上下文感知能力。
**核心方法:**
```csharp
void ICommand.Execute(); // 实现 ICommand 接口
protected abstract void OnExecute(); // 抽象执行方法,由子类实现
```
**使用示例:**
```csharp
// 定义一个无返回值的基础命令
public class SimpleCommand : AbstractCommand
{
protected override void OnExecute()
{
var playerModel = this.GetModel<PlayerModel>();
playerModel.Health.Value = playerModel.MaxHealth.Value;
this.SendEvent(new PlayerHealthRestoredEvent());
}
}
// 使用命令
using GFramework.Core.Abstractions.controller;
using GFramework.SourceGenerators.Abstractions.rule;
[ContextAware]
public partial class GameController : IController
{
public void OnRestoreHealthButtonClicked()
{
Context.SendCommand(new SimpleCommand());
}
}
```
### AbstractCommand`<TResult>`
无输入参数但带返回值的命令基类。
**核心方法:**
```csharp
TResult ICommand<TResult>.Execute(); // 实现 ICommand<TResult> 接口
protected abstract TResult OnExecute(); // 抽象执行方法,由子类实现
```
**使用示例:**
```csharp
// 定义一个无输入但有返回值的命令
public class GetPlayerHealthQuery : AbstractCommand<int>
{
protected override int OnExecute()
{
var playerModel = this.GetModel<PlayerModel>();
return playerModel.Health.Value;
}
}
// 使用命令
public class UISystem : AbstractSystem
{
protected override void OnInit()
{
this.RegisterEvent<UpdateUIEvent>(OnUpdateUI);
}
private void OnUpdateUI(UpdateUIEvent e)
{
var health = this.SendCommand(new GetPlayerHealthQuery());
Console.WriteLine($"Player health: {health}");
}
}
```
## 命令的生命周期
1. **创建命令**:实例化命令对象,传入必要的参数
2. **执行命令**:调用 `Execute()` 方法,内部委托给 `OnExecute()`
3. **返回结果**:对于带返回值的命令,返回执行结果
4. **命令销毁**:命令执行完毕后可以被垃圾回收
**注意事项:**
- 命令应该是无状态的,执行完即可丢弃
- 避免在命令中保存长期引用
- 命令执行应该是原子操作
## CommandBus - 命令总线
### 功能说明
`CommandBus` 是命令执行的核心组件,负责发送和执行命令。
**主要方法:**
```csharp
void Send(ICommand command); // 发送无返回值命令
TResult Send<TResult>(ICommand<TResult> command); // 发送带返回值命令
```
**特点:**
- 统一的命令执行入口
- 支持同步命令执行
- 与架构上下文集成
### 使用示例
```csharp
// 通过架构获取命令总线
var commandBus = architecture.Context.CommandBus;
// 发送无返回值命令
commandBus.Send(new StartGameCommand(1, "Player1"));
// 发送带返回值命令
var damage = commandBus.Send(new CalculateDamageCommand(100, 50));
```
## 命令基类变体
框架提供了多种命令基类以满足不同需求:
### AbstractCommand`<TInput>`
带输入参数的无返回值命令类。通过 `ICommandInput` 接口传递参数。
**核心方法:**
```csharp
void ICommand.Execute(); // 实现 ICommand 接口
protected abstract void OnExecute(TInput input); // 抽象执行方法,接收输入参数
```
**使用示例:**
```csharp
// 定义输入对象
public class StartGameInput : ICommandInput
{
public int LevelId { get; set; }
public string PlayerName { get; set; }
}
// 定义命令
public class StartGameCommand : AbstractCommand<StartGameInput>
{
protected override void OnExecute(StartGameInput input)
{
var playerModel = this.GetModel<PlayerModel>();
var gameModel = this.GetModel<GameModel>();
playerModel.PlayerName.Value = input.PlayerName;
gameModel.CurrentLevel.Value = input.LevelId;
gameModel.GameState.Value = GameState.Playing;
this.SendEvent(new GameStartedEvent());
}
}
// 使用命令
using GFramework.Core.Abstractions.controller;
using GFramework.SourceGenerators.Abstractions.rule;
[ContextAware]
public partial class GameController : IController
{
public void OnStartButtonClicked()
{
var input = new StartGameInput { LevelId = 1, PlayerName = "Player1" };
Context.SendCommand(new StartGameCommand { Input = input });
}
}
```
### AbstractCommand`<TInput, TResult>`
既带输入参数又带返回值的命令类。
**核心方法:**
```csharp
TResult ICommand<TResult>.Execute(); // 实现 ICommand<TResult> 接口
protected abstract TResult OnExecute(TInput input); // 抽象执行方法,接收输入参数
```
**使用示例:**
```csharp
// 定义输入对象
public class CalculateDamageInput : ICommandInput
{
public int AttackerAttackPower { get; set; }
public int DefenderDefense { get; set; }
}
// 定义命令
public class CalculateDamageCommand : AbstractCommand<CalculateDamageInput, int>
{
protected override int OnExecute(CalculateDamageInput input)
{
var config = this.GetModel<GameConfigModel>();
var baseDamage = input.AttackerAttackPower - input.DefenderDefense;
var finalDamage = Math.Max(1, baseDamage * config.DamageMultiplier);
return (int)finalDamage;
}
}
// 使用命令
public class CombatSystem : AbstractSystem
{
protected override void OnInit() { }
public void Attack(Character attacker, Character defender)
{
var input = new CalculateDamageInput
{
AttackerAttackPower = attacker.AttackPower,
DefenderDefense = defender.Defense
};
var damage = this.SendCommand(new CalculateDamageCommand { Input = input });
defender.Health -= damage;
this.SendEvent(new DamageDealtEvent(attacker, defender, damage));
}
}
```
### AbstractAsyncCommand`<TInput>`
支持异步执行的带输入参数的无返回值命令基类。
**核心方法:**
```csharp
Task IAsyncCommand.ExecuteAsync(); // 实现异步命令接口
protected abstract Task OnExecuteAsync(TInput input); // 抽象异步执行方法
```
### AbstractAsyncCommand`<TInput, TResult>`
支持异步执行的既带输入参数又带返回值的命令基类。
**核心方法:**
```csharp
Task<TResult> IAsyncCommand<TResult>.ExecuteAsync(); // 实现异步命令接口
protected abstract Task<TResult> OnExecuteAsync(TInput input); // 抽象异步执行方法
```
**使用示例:**
```csharp
// 定义输入对象
public class LoadSaveDataInput : ICommandInput
{
public string SaveSlot { get; set; }
}
// 定义异步命令
public class LoadSaveDataCommand : AbstractAsyncCommand<LoadSaveDataInput, SaveData>
{
protected override async Task<SaveData> OnExecuteAsync(LoadSaveDataInput input)
{
var storage = this.GetUtility<IStorageUtility>();
return await storage.LoadSaveDataAsync(input.SaveSlot);
}
}
// 使用异步命令
public class SaveSystem : AbstractSystem
{
protected override void OnInit()
{
this.RegisterEvent<LoadGameRequestEvent>(OnLoadGameRequest);
}
private async void OnLoadGameRequest(LoadGameRequestEvent e)
{
var input = new LoadSaveDataInput { SaveSlot = e.SaveSlot };
var saveData = await this.SendCommandAsync(new LoadSaveDataCommand { Input = input });
if (saveData != null)
{
this.SendEvent(new GameLoadedEvent { SaveData = saveData });
}
}
}
```
## 命令处理器执行
所有发送给命令总线的命令最终都会通过 `CommandExecutor` 来执行:
```csharp
public class CommandExecutor
{
public static void Execute(ICommand command)
{
command.Execute();
}
public static TResult Execute<TResult>(ICommand<TResult> command)
{
return command.Execute();
}
}
```
**特点:**
- 提供统一的命令执行机制
- 支持同步和异步命令执行
- 可以扩展添加中间件逻辑
## 使用场景
### 1. 用户交互操作
```csharp
public class SaveGameCommand : AbstractCommand
{
private readonly string _saveSlot;
public SaveGameCommand(string saveSlot)
{
_saveSlot = saveSlot;
}
protected override void OnExecute()
{
var saveSystem = this.GetSystem<SaveSystem>();
var playerModel = this.GetModel<PlayerModel>();
saveSystem.SavePlayerData(playerModel, _saveSlot);
this.SendEvent(new GameSavedEvent(_saveSlot));
}
}
```
### 2. 业务流程控制
```csharp
public class LoadLevelCommand : AbstractCommand
{
private readonly int _levelId;
public LoadLevelCommand(int levelId)
{
_levelId = levelId;
}
protected override void OnExecute()
{
var levelSystem = this.GetSystem<LevelSystem>();
var uiSystem = this.GetSystem<UISystem>();
// 显示加载界面
uiSystem.ShowLoadingScreen();
// 加载关卡
levelSystem.LoadLevel(_levelId);
// 发送事件
this.SendEvent(new LevelLoadedEvent(_levelId));
}
}
```
## 最佳实践
1. **保持命令原子性**:一个命令应该完成一个完整的业务操作
2. **命令无状态**:命令不应该保存长期状态,执行完即可丢弃
3. **参数通过构造函数传递**:命令需要的参数应在创建时传入
4. **避免命令嵌套**:命令内部尽量不要发送其他命令,使用事件通信
5. **合理使用返回值**:只在确实需要返回结果时使用带返回值的命令
6. **命令命名规范**:使用动词+名词形式,如 `StartGameCommand``SavePlayerCommand`
7. **单一职责原则**:每个命令只负责一个特定的业务操作
8. **使用异步命令**:对于需要长时间执行的操作,使用异步命令避免阻塞
9. **命令验证**:在命令执行前验证输入参数的有效性
10. **错误处理**:在命令中适当处理异常情况
## 命令模式优势
### 1. 可扩展性
- 命令可以被序列化和存储
- 支持命令队列和批处理
- 便于实现撤销/重做功能
### 2. 可测试性
- 命令逻辑独立,易于单元测试
- 可以模拟命令执行结果
- 支持行为驱动开发
### 3. 可维护性
- 业务逻辑集中管理
- 降低组件间耦合度
- 便于重构和扩展
## 相关包
- [`architecture`](./architecture.md) - 架构核心,负责命令的分发和执行
- [`extensions`](./extensions.md) - 提供 `SendCommand()` 扩展方法
- [`query`](./query.md) - 查询模式,用于数据查询
- [`events`](./events.md) - 事件系统,命令执行后的通知机制
- [`system`](./system.md) - 业务系统,命令的主要执行者
- [`model`](./model.md) - 数据模型,命令操作的数据
---
**许可证**Apache 2.0

File diff suppressed because it is too large Load Diff

View File

@ -232,17 +232,18 @@ public class GameArchitecture : Architecture
using Arch.Core;
using GFramework.Core.Abstractions.controller;
using MyGame.Components;
using GFramework.Core.Abstractions.controller;
using GFramework.SourceGenerators.Abstractions.rule;
public class GameController : IController
[ContextAware]
public partial class GameController : IController
{
private World _world;
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public void Start()
{
// 获取 World
_world = this.GetService<World>();
_world = Context.GetService<World>();
// 创建玩家实体
var player = _world.Create(

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,427 +1,434 @@
# Property 包使用说明
## 概述
Property 包提供了可绑定属性BindableProperty的实现支持属性值的监听和响应式编程。这是实现数据绑定和响应式编程的核心组件。
BindableProperty 是 GFramework 中 Model 层数据管理的基础,通过事件机制实现属性变化的通知。
## 核心接口
### IReadonlyBindableProperty`<T>`
只读可绑定属性接口,提供属性值的读取和变更监听功能。
**核心成员:**
```csharp
// 获取属性值
T Value { get; }
// 注册监听(不立即触发回调)
IUnRegister Register(Action<T> onValueChanged);
// 注册监听并立即触发回调传递当前值
IUnRegister RegisterWithInitValue(Action<T> action);
// 取消监听
void UnRegister(Action<T> onValueChanged);
```
### IBindableProperty`<T>`
可绑定属性接口,继承自只读接口,增加了修改能力。
**核心成员:**
```csharp
// 可读写的属性值
new T Value { get; set; }
// 设置值但不触发事件
void SetValueWithoutEvent(T newValue);
```
## 核心类
### BindableProperty`<T>`
可绑定属性的完整实现。
**核心方法:**
```csharp
// 构造函数
BindableProperty(T defaultValue = default!);
// 属性值
T Value { get; set; }
// 注册监听
IUnRegister Register(Action<T> onValueChanged);
IUnRegister RegisterWithInitValue(Action<T> action);
// 取消监听
void UnRegister(Action<T> onValueChanged);
// 设置值但不触发事件
void SetValueWithoutEvent(T newValue);
// 设置自定义比较器
BindableProperty<T> WithComparer(Func<T, T, bool> comparer);
```
**使用示例:**
```csharp
// 创建可绑定属性
var health = new BindableProperty<int>(100);
// 监听值变化(不会立即触发)
var unregister = health.Register(newValue =>
{
Console.WriteLine($"Health changed to: {newValue}");
});
// 设置值(会触发监听器)
health.Value = 50; // 输出: Health changed to: 50
// 取消监听
unregister.UnRegister();
// 设置值但不触发事件
health.SetValueWithoutEvent(75);
```
**高级功能:**
```csharp
// 1. 注册并立即获得当前值
health.RegisterWithInitValue(value =>
{
Console.WriteLine($"Current health: {value}"); // 立即输出当前值
// 后续值变化时也会调用
});
// 2. 自定义比较器(静态方法)
BindableProperty<int>.Comparer = (a, b) => Math.Abs(a - b) < 1;
// 3. 使用实例方法设置比较器
var position = new BindableProperty<Vector3>(Vector3.Zero)
.WithComparer((a, b) => a.DistanceTo(b) < 0.01f); // 距离小于0.01认为相等
// 4. 字符串比较器示例
var name = new BindableProperty<string>("Player")
.WithComparer((a, b) => string.Equals(a, b, StringComparison.OrdinalIgnoreCase));
```
### BindablePropertyUnRegister`<T>`
可绑定属性的注销器,负责清理监听。
**使用示例:**
```csharp
var unregister = health.Register(OnHealthChanged);
// 当需要取消监听时
unregister.UnRegister();
```
## BindableProperty 工作原理
BindableProperty 基于事件系统实现属性变化通知:
1. **值设置**:当设置 `Value` 属性时,首先进行值比较
2. **变化检测**:使用 `EqualityComparer<T>.Default` 或自定义比较器检测值变化
3. **事件触发**:如果值发生变化,调用所有注册的回调函数
4. **内存管理**:通过 `IUnRegister` 机制管理监听器的生命周期
## 在 Model 中使用
### 定义可绑定属性
```csharp
public class PlayerModel : AbstractModel
{
// 可读写属性
public BindableProperty<string> Name { get; } = new("Player");
public BindableProperty<int> Level { get; } = new(1);
public BindableProperty<int> Health { get; } = new(100);
public BindableProperty<int> MaxHealth { get; } = new(100);
public BindableProperty<Vector3> Position { get; } = new(Vector3.Zero);
// 只读属性(外部只能读取和监听)
public IReadonlyBindableProperty<int> ReadonlyHealth => Health;
protected override void OnInit()
{
// 内部监听属性变化
Health.Register(hp =>
{
if (hp <= 0)
{
this.SendEvent(new PlayerDiedEvent());
}
else if (hp < MaxHealth.Value * 0.3f)
{
this.SendEvent(new LowHealthWarningEvent());
}
});
// 监听等级变化
Level.Register(newLevel =>
{
this.SendEvent(new PlayerLevelUpEvent { NewLevel = newLevel });
});
}
// 业务方法
public void TakeDamage(int damage)
{
Health.Value = Math.Max(0, Health.Value - damage);
}
public void Heal(int amount)
{
Health.Value = Math.Min(MaxHealth.Value, Health.Value + amount);
}
public float GetHealthPercentage()
{
return (float)Health.Value / MaxHealth.Value;
}
}
```
## 在 Controller 中监听
### UI 数据绑定
```csharp
public partial class PlayerUI : Control, IController
{
[Export] private Label _healthLabel;
[Export] private Label _nameLabel;
[Export] private ProgressBar _healthBar;
private IUnRegisterList _unregisterList = new UnRegisterList();
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public override void _Ready()
{
var playerModel = this.GetModel<PlayerModel>();
// 绑定生命值到UI立即显示当前值
playerModel.Health
.RegisterWithInitValue(health =>
{
_healthLabel.Text = $"HP: {health}/{playerModel.MaxHealth.Value}";
_healthBar.Value = (float)health / playerModel.MaxHealth.Value * 100;
})
.AddToUnregisterList(_unregisterList);
// 绑定最大生命值
playerModel.MaxHealth
.RegisterWithInitValue(maxHealth =>
{
_healthBar.MaxValue = maxHealth;
})
.AddToUnregisterList(_unregisterList);
// 绑定名称
playerModel.Name
.RegisterWithInitValue(name =>
{
_nameLabel.Text = name;
})
.AddToUnregisterList(_unregisterList);
// 绑定位置(仅用于调试显示)
playerModel.Position
.RegisterWithInitValue(pos =>
{
// 仅在调试模式下显示
#if DEBUG
Console.WriteLine($"Player position: {pos}");
#endif
})
.AddToUnregisterList(_unregisterList);
}
public override void _ExitTree()
{
_unregisterList.UnRegisterAll();
}
}
```
## 常见使用模式
### 1. 双向绑定
```c#
// Model
public class SettingsModel : AbstractModel
{
public BindableProperty<float> MasterVolume { get; } = new(1.0f);
protected override void OnInit() { }
}
// UI Controller
public partial class VolumeSlider : HSlider, IController
{
private BindableProperty<float> _volumeProperty;
public override void _Ready()
{
_volumeProperty = this.GetModel<SettingsModel>().MasterVolume;
// Model -> UI
_volumeProperty.RegisterWithInitValue(vol => Value = vol)
.UnRegisterWhenNodeExitTree(this);
// UI -> Model
ValueChanged += newValue => _volumeProperty.Value = (float)newValue;
}
}
```
### 2. 计算属性
```c#
public class PlayerModel : AbstractModel
{
public BindableProperty<int> Health { get; } = new(100);
public BindableProperty<int> MaxHealth { get; } = new(100);
public BindableProperty<float> HealthPercent { get; } = new(1.0f);
protected override void OnInit()
{
// 自动计算百分比
Action updatePercent = () =>
{
HealthPercent.Value = (float)Health.Value / MaxHealth.Value;
};
Health.Register(_ => updatePercent());
MaxHealth.Register(_ => updatePercent());
updatePercent(); // 初始计算
}
}
```
### 3. 属性验证
```c#
public class PlayerModel : AbstractModel
{
private BindableProperty<int> _health = new(100);
public BindableProperty<int> Health
{
get => _health;
set
{
// 限制范围
var clampedValue = Math.Clamp(value.Value, 0, MaxHealth.Value);
_health.Value = clampedValue;
}
}
public BindableProperty<int> MaxHealth { get; } = new(100);
protected override void OnInit() { }
}
```
### 4. 条件监听
```c#
public class CombatController : Node, IController
{
public override void _Ready()
{
var playerModel = this.GetModel<PlayerModel>();
// 只在生命值低于30%时显示警告
playerModel.Health.Register(hp =>
{
if (hp < playerModel.MaxHealth.Value * 0.3f)
{
ShowLowHealthWarning();
}
else
{
HideLowHealthWarning();
}
}).UnRegisterWhenNodeExitTree(this);
}
}
```
## 性能优化
### 1. 避免频繁触发
```c#
// 使用 SetValueWithoutEvent 批量修改
public void LoadPlayerData(SaveData data)
{
// 临时关闭事件
Health.SetValueWithoutEvent(data.Health);
Mana.SetValueWithoutEvent(data.Mana);
Gold.SetValueWithoutEvent(data.Gold);
// 最后统一触发一次更新事件
this.SendEvent(new PlayerDataLoadedEvent());
}
```
### 2. 自定义比较器
```c#
// 避免浮点数精度问题导致的频繁触发
var position = new BindableProperty<Vector3>()
.WithComparer((a, b) => a.DistanceTo(b) < 0.001f);
```
## 实现原理
### 值变化检测
```c#
// 使用 EqualityComparer<T>.Default 进行比较
if (!EqualityComparer<T>.Default.Equals(value, MValue))
{
MValue = value;
_mOnValueChanged?.Invoke(value);
}
```
### 事件触发机制
```c#
// 当值变化时触发所有注册的回调
_mOnValueChanged?.Invoke(value);
```
## 最佳实践
1. **在 Model 中定义属性** - BindableProperty 主要用于 Model 层
2. **使用只读接口暴露** - 防止外部随意修改
3. **及时注销监听** - 使用 UnRegisterList 或 UnRegisterWhenNodeExitTree
4. **使用 RegisterWithInitValue** - UI 绑定时立即获取初始值
5. **避免循环依赖** - 属性监听器中修改其他属性要小心
6. **使用自定义比较器** - 对于浮点数等需要精度控制的属性
## 相关包
- [`model`](./model.md) - Model 中大量使用 BindableProperty
- [`events`](./events.md) - BindableProperty 基于事件系统实现
- [`extensions`](./extensions.md) - 提供便捷的注销扩展方法
---
**许可证**: Apache 2.0
# Property 包使用说明
## 概述
Property 包提供了可绑定属性BindableProperty的实现支持属性值的监听和响应式编程。这是实现数据绑定和响应式编程的核心组件。
BindableProperty 是 GFramework 中 Model 层数据管理的基础,通过事件机制实现属性变化的通知。
## 核心接口
### IReadonlyBindableProperty`<T>`
只读可绑定属性接口,提供属性值的读取和变更监听功能。
**核心成员:**
```csharp
// 获取属性值
T Value { get; }
// 注册监听(不立即触发回调)
IUnRegister Register(Action<T> onValueChanged);
// 注册监听并立即触发回调传递当前值
IUnRegister RegisterWithInitValue(Action<T> action);
// 取消监听
void UnRegister(Action<T> onValueChanged);
```
### IBindableProperty`<T>`
可绑定属性接口,继承自只读接口,增加了修改能力。
**核心成员:**
```csharp
// 可读写的属性值
new T Value { get; set; }
// 设置值但不触发事件
void SetValueWithoutEvent(T newValue);
```
## 核心类
### BindableProperty`<T>`
可绑定属性的完整实现。
**核心方法:**
```csharp
// 构造函数
BindableProperty(T defaultValue = default!);
// 属性值
T Value { get; set; }
// 注册监听
IUnRegister Register(Action<T> onValueChanged);
IUnRegister RegisterWithInitValue(Action<T> action);
// 取消监听
void UnRegister(Action<T> onValueChanged);
// 设置值但不触发事件
void SetValueWithoutEvent(T newValue);
// 设置自定义比较器
BindableProperty<T> WithComparer(Func<T, T, bool> comparer);
```
**使用示例:**
```csharp
// 创建可绑定属性
var health = new BindableProperty<int>(100);
// 监听值变化(不会立即触发)
var unregister = health.Register(newValue =>
{
Console.WriteLine($"Health changed to: {newValue}");
});
// 设置值(会触发监听器)
health.Value = 50; // 输出: Health changed to: 50
// 取消监听
unregister.UnRegister();
// 设置值但不触发事件
health.SetValueWithoutEvent(75);
```
**高级功能:**
```csharp
// 1. 注册并立即获得当前值
health.RegisterWithInitValue(value =>
{
Console.WriteLine($"Current health: {value}"); // 立即输出当前值
// 后续值变化时也会调用
});
// 2. 自定义比较器(静态方法)
BindableProperty<int>.Comparer = (a, b) => Math.Abs(a - b) < 1;
// 3. 使用实例方法设置比较器
var position = new BindableProperty<Vector3>(Vector3.Zero)
.WithComparer((a, b) => a.DistanceTo(b) < 0.01f); // 距离小于0.01认为相等
// 4. 字符串比较器示例
var name = new BindableProperty<string>("Player")
.WithComparer((a, b) => string.Equals(a, b, StringComparison.OrdinalIgnoreCase));
```
### BindablePropertyUnRegister`<T>`
可绑定属性的注销器,负责清理监听。
**使用示例:**
```csharp
var unregister = health.Register(OnHealthChanged);
// 当需要取消监听时
unregister.UnRegister();
```
## BindableProperty 工作原理
BindableProperty 基于事件系统实现属性变化通知:
1. **值设置**:当设置 `Value` 属性时,首先进行值比较
2. **变化检测**:使用 `EqualityComparer<T>.Default` 或自定义比较器检测值变化
3. **事件触发**:如果值发生变化,调用所有注册的回调函数
4. **内存管理**:通过 `IUnRegister` 机制管理监听器的生命周期
## 在 Model 中使用
### 定义可绑定属性
```csharp
public class PlayerModel : AbstractModel
{
// 可读写属性
public BindableProperty<string> Name { get; } = new("Player");
public BindableProperty<int> Level { get; } = new(1);
public BindableProperty<int> Health { get; } = new(100);
public BindableProperty<int> MaxHealth { get; } = new(100);
public BindableProperty<Vector3> Position { get; } = new(Vector3.Zero);
// 只读属性(外部只能读取和监听)
public IReadonlyBindableProperty<int> ReadonlyHealth => Health;
protected override void OnInit()
{
// 内部监听属性变化
Health.Register(hp =>
{
if (hp <= 0)
{
this.SendEvent(new PlayerDiedEvent());
}
else if (hp < MaxHealth.Value * 0.3f)
{
this.SendEvent(new LowHealthWarningEvent());
}
});
// 监听等级变化
Level.Register(newLevel =>
{
this.SendEvent(new PlayerLevelUpEvent { NewLevel = newLevel });
});
}
// 业务方法
public void TakeDamage(int damage)
{
Health.Value = Math.Max(0, Health.Value - damage);
}
public void Heal(int amount)
{
Health.Value = Math.Min(MaxHealth.Value, Health.Value + amount);
}
public float GetHealthPercentage()
{
return (float)Health.Value / MaxHealth.Value;
}
}
```
## 在 Controller 中监听
### UI 数据绑定
```csharp
using GFramework.Core.Abstractions.controller;
using GFramework.SourceGenerators.Abstractions.rule;
[ContextAware]
public partial class PlayerUI : Control, IController
{
[Export] private Label _healthLabel;
[Export] private Label _nameLabel;
[Export] private ProgressBar _healthBar;
private IUnRegisterList _unregisterList = new UnRegisterList();
public override void _Ready()
{
var playerModel = Context.GetModel<PlayerModel>();
// 绑定生命值到UI立即显示当前值
playerModel.Health
.RegisterWithInitValue(health =>
{
_healthLabel.Text = $"HP: {health}/{playerModel.MaxHealth.Value}";
_healthBar.Value = (float)health / playerModel.MaxHealth.Value * 100;
})
.AddToUnregisterList(_unregisterList);
// 绑定最大生命值
playerModel.MaxHealth
.RegisterWithInitValue(maxHealth =>
{
_healthBar.MaxValue = maxHealth;
})
.AddToUnregisterList(_unregisterList);
// 绑定名称
playerModel.Name
.RegisterWithInitValue(name =>
{
_nameLabel.Text = name;
})
.AddToUnregisterList(_unregisterList);
// 绑定位置(仅用于调试显示)
playerModel.Position
.RegisterWithInitValue(pos =>
{
// 仅在调试模式下显示
#if DEBUG
Console.WriteLine($"Player position: {pos}");
#endif
})
.AddToUnregisterList(_unregisterList);
}
public override void _ExitTree()
{
_unregisterList.UnRegisterAll();
}
}
```
## 常见使用模式
### 1. 双向绑定
```c#
// Model
public class SettingsModel : AbstractModel
{
public BindableProperty<float> MasterVolume { get; } = new(1.0f);
protected override void OnInit() { }
}
// UI Controller
[ContextAware]
public partial class VolumeSlider : HSlider, IController
{
private BindableProperty<float> _volumeProperty;
public override void _Ready()
{
_volumeProperty = Context.GetModel<SettingsModel>().MasterVolume;
// Model -> UI
_volumeProperty.RegisterWithInitValue(vol => Value = vol)
.UnRegisterWhenNodeExitTree(this);
// UI -> Model
ValueChanged += newValue => _volumeProperty.Value = (float)newValue;
}
}
```
### 2. 计算属性
```c#
public class PlayerModel : AbstractModel
{
public BindableProperty<int> Health { get; } = new(100);
public BindableProperty<int> MaxHealth { get; } = new(100);
public BindableProperty<float> HealthPercent { get; } = new(1.0f);
protected override void OnInit()
{
// 自动计算百分比
Action updatePercent = () =>
{
HealthPercent.Value = (float)Health.Value / MaxHealth.Value;
};
Health.Register(_ => updatePercent());
MaxHealth.Register(_ => updatePercent());
updatePercent(); // 初始计算
}
}
```
### 3. 属性验证
```c#
public class PlayerModel : AbstractModel
{
private BindableProperty<int> _health = new(100);
public BindableProperty<int> Health
{
get => _health;
set
{
// 限制范围
var clampedValue = Math.Clamp(value.Value, 0, MaxHealth.Value);
_health.Value = clampedValue;
}
}
public BindableProperty<int> MaxHealth { get; } = new(100);
protected override void OnInit() { }
}
```
### 4. 条件监听
```c#
using GFramework.Core.Abstractions.controller;
using GFramework.SourceGenerators.Abstractions.rule;
[ContextAware]
public partial class CombatController : Node, IController
{
public override void _Ready()
{
var playerModel = Context.GetModel<PlayerModel>();
// 只在生命值低于30%时显示警告
playerModel.Health.Register(hp =>
{
if (hp < playerModel.MaxHealth.Value * 0.3f)
{
ShowLowHealthWarning();
}
else
{
HideLowHealthWarning();
}
}).UnRegisterWhenNodeExitTree(this);
}
}
```
## 性能优化
### 1. 避免频繁触发
```c#
// 使用 SetValueWithoutEvent 批量修改
public void LoadPlayerData(SaveData data)
{
// 临时关闭事件
Health.SetValueWithoutEvent(data.Health);
Mana.SetValueWithoutEvent(data.Mana);
Gold.SetValueWithoutEvent(data.Gold);
// 最后统一触发一次更新事件
this.SendEvent(new PlayerDataLoadedEvent());
}
```
### 2. 自定义比较器
```c#
// 避免浮点数精度问题导致的频繁触发
var position = new BindableProperty<Vector3>()
.WithComparer((a, b) => a.DistanceTo(b) < 0.001f);
```
## 实现原理
### 值变化检测
```c#
// 使用 EqualityComparer<T>.Default 进行比较
if (!EqualityComparer<T>.Default.Equals(value, MValue))
{
MValue = value;
_mOnValueChanged?.Invoke(value);
}
```
### 事件触发机制
```c#
// 当值变化时触发所有注册的回调
_mOnValueChanged?.Invoke(value);
```
## 最佳实践
1. **在 Model 中定义属性** - BindableProperty 主要用于 Model 层
2. **使用只读接口暴露** - 防止外部随意修改
3. **及时注销监听** - 使用 UnRegisterList 或 UnRegisterWhenNodeExitTree
4. **使用 RegisterWithInitValue** - UI 绑定时立即获取初始值
5. **避免循环依赖** - 属性监听器中修改其他属性要小心
6. **使用自定义比较器** - 对于浮点数等需要精度控制的属性
## 相关包
- [`model`](./model.md) - Model 中大量使用 BindableProperty
- [`events`](./events.md) - BindableProperty 基于事件系统实现
- [`extensions`](./extensions.md) - 提供便捷的注销扩展方法
---
**许可证**: Apache 2.0

View File

@ -144,13 +144,15 @@ public class LoadPlayerDataQuery : AbstractAsyncQuery<LoadPlayerDataInput, Playe
### 2. 发送查询
```csharp
public class ShopUI : IController
using GFramework.Core.Abstractions.controller;
using GFramework.SourceGenerators.Abstractions.rule;
[ContextAware]
public partial class ShopUI : IController
{
[Export] private Button _buyButton;
[Export] private int _itemPrice = 100;
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public void OnReady()
{
_buyButton.Pressed += OnBuyButtonPressed;
@ -160,12 +162,12 @@ public class ShopUI : IController
{
// 查询玩家金币
var query = new GetPlayerGoldQuery { Input = new GetPlayerGoldInput() };
int playerGold = this.SendQuery(query);
int playerGold = Context.SendQuery(query);
if (playerGold >= _itemPrice)
{
// 发送购买命令
this.SendCommand(new BuyItemCommand { Input = new BuyItemInput { ItemId = "sword_01" } });
Context.SendCommand(new BuyItemCommand { Input = new BuyItemInput { ItemId = "sword_01" } });
}
else
{

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,320 +1,323 @@
# 快速开始
本指南将帮助您快速构建第一个基于 GFramework 的应用程序。
## 1. 创建项目架构
首先定义您的应用架构:
```csharp
using GFramework.Core.architecture;
public class GameArchitecture : Architecture
{
protected override void Init()
{
// 注册模型 - 存储应用状态
RegisterModel(new PlayerModel());
RegisterModel(new GameStateModel());
// 注册系统 - 处理业务逻辑
RegisterSystem(new PlayerSystem());
RegisterSystem(new GameLogicSystem());
// 注册工具类 - 提供辅助功能
RegisterUtility(new StorageUtility());
}
}
```
## 2. 定义数据模型
创建您的数据模型:
```csharp
public class PlayerModel : AbstractModel
{
// 使用可绑定属性实现响应式数据
public BindableProperty<string> Name { get; } = new("Player");
public BindableProperty<int> Health { get; } = new(100);
public BindableProperty<int> Score { get; } = new(0);
protected override void OnInit()
{
// 监听健康值变化
Health.Register(OnHealthChanged);
}
private void OnHealthChanged(int newHealth)
{
if (newHealth <= 0)
{
this.SendEvent(new PlayerDiedEvent());
}
}
}
public class GameStateModel : AbstractModel
{
public BindableProperty<bool> IsGameRunning { get; } = new(false);
public BindableProperty<int> CurrentLevel { get; } = new(1);
}
```
## 3. 实现业务逻辑
创建处理业务逻辑的系统:
```csharp
public class PlayerSystem : AbstractSystem
{
protected override void OnInit()
{
// 监听玩家输入事件
this.RegisterEvent<PlayerMoveEvent>(OnPlayerMove);
this.RegisterEvent<PlayerAttackEvent>(OnPlayerAttack);
}
private void OnPlayerMove(PlayerMoveEvent e)
{
var playerModel = this.GetModel<PlayerModel>();
// 处理移动逻辑
Console.WriteLine($"Player moved to {e.Direction}");
}
private void OnPlayerAttack(PlayerAttackEvent e)
{
var playerModel = this.GetModel<PlayerModel>();
// 处理攻击逻辑
playerModel.Score.Value += 10;
this.SendEvent(new EnemyDamagedEvent { Damage = 25 });
}
}
public class GameLogicSystem : AbstractSystem
{
protected override void OnInit()
{
this.RegisterEvent<EnemyDamagedEvent>(OnEnemyDamaged);
this.RegisterEvent<PlayerDiedEvent>(OnPlayerDied);
}
private void OnEnemyDamaged(EnemyDamagedEvent e)
{
Console.WriteLine($"Enemy took {e.Damage} damage");
// 检查是否需要升级关卡
CheckLevelProgress();
}
private void OnPlayerDied(PlayerDiedEvent e)
{
var gameState = this.GetModel<GameStateModel>();
gameState.IsGameRunning.Value = false;
Console.WriteLine("Game Over!");
}
private void CheckLevelProgress()
{
// 实现关卡进度检查逻辑
}
}
```
## 4. 定义事件
创建应用中使用的事件:
```csharp
public class PlayerMoveEvent : IEvent
{
public Vector2 Direction { get; set; }
}
public class PlayerAttackEvent : IEvent
{
public Vector2 TargetPosition { get; set; }
}
public class PlayerDiedEvent : IEvent
{
// 玩家死亡事件
}
public class EnemyDamagedEvent : IEvent
{
public int Damage { get; set; }
}
```
## 5. 创建控制器
实现控制器来连接 UI 和业务逻辑:
```csharp
public class GameController : IController
{
private IArchitecture _architecture;
private PlayerModel _playerModel;
private GameStateModel _gameStateModel;
public GameController(IArchitecture architecture)
{
_architecture = architecture;
_playerModel = architecture.GetModel<PlayerModel>();
_gameStateModel = architecture.GetModel<GameStateModel>();
// 初始化事件监听
InitializeEventListeners();
}
private void InitializeEventListeners()
{
// 监听模型变化并更新 UI
_playerModel.Health.RegisterWithInitValue(OnHealthChanged);
_playerModel.Score.RegisterWithInitValue(OnScoreChanged);
_gameStateModel.IsGameRunning.Register(OnGameStateChanged);
}
public void StartGame()
{
_gameStateModel.IsGameRunning.Value = true;
_architecture.SendEvent(new GameStartEvent());
Console.WriteLine("Game started!");
}
public void MovePlayer(Vector2 direction)
{
_architecture.SendCommand(new MovePlayerCommand { Direction = direction });
}
public void PlayerAttack(Vector2 target)
{
_architecture.SendCommand(new AttackCommand { TargetPosition = target });
}
// UI 更新回调
private void OnHealthChanged(int health)
{
UpdateHealthDisplay(health);
}
private void OnScoreChanged(int score)
{
UpdateScoreDisplay(score);
}
private void OnGameStateChanged(bool isRunning)
{
UpdateGameStatusDisplay(isRunning);
}
private void UpdateHealthDisplay(int health)
{
// 更新血条 UI
Console.WriteLine($"Health: {health}");
}
private void UpdateScoreDisplay(int score)
{
// 更新分数显示
Console.WriteLine($"Score: {score}");
}
private void UpdateGameStatusDisplay(bool isRunning)
{
// 更新游戏状态显示
Console.WriteLine($"Game running: {isRunning}");
}
}
```
## 6. 定义命令
创建命令来封装用户操作:
```csharp
public class MovePlayerCommand : AbstractCommand
{
public Vector2 Direction { get; set; }
protected override void OnDo()
{
// 发送移动事件
this.SendEvent(new PlayerMoveEvent { Direction = Direction });
}
}
public class AttackCommand : AbstractCommand
{
public Vector2 TargetPosition { get; set; }
protected override void OnDo()
{
// 发送攻击事件
this.SendEvent(new PlayerAttackEvent { TargetPosition = TargetPosition });
}
}
```
## 7. 运行应用
现在让我们运行这个简单的应用:
```csharp
class Program
{
static void Main(string[] args)
{
// 创建并初始化架构
var architecture = new GameArchitecture();
architecture.Initialize();
// 创建控制器
var gameController = new GameController(architecture);
// 开始游戏
gameController.StartGame();
// 模拟玩家操作
gameController.MovePlayer(new Vector2(1, 0));
gameController.PlayerAttack(new Vector2(5, 5));
// 模拟玩家受伤
var playerModel = architecture.GetModel<PlayerModel>();
playerModel.Health.Value = 50;
// 模拟玩家死亡
playerModel.Health.Value = 0;
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
```
## 8. 运行结果
执行程序后,您应该看到类似以下输出:
```
Game started!
Game running: True
Player moved to (1, 0)
Player took 25 damage
Score: 10
Health: 50
Health: 0
Player died
Game Over!
Game running: False
Press any key to exit...
```
## 下一步
这个简单的示例展示了 GFramework 的核心概念:
1. **架构模式** - 清晰的分层结构
2. **响应式数据** - BindableProperty 自动更新
3. **事件驱动** - 松耦合的组件通信
4. **命令模式** - 封装用户操作
# 快速开始
本指南将帮助您快速构建第一个基于 GFramework 的应用程序。
## 1. 创建项目架构
首先定义您的应用架构:
```csharp
using GFramework.Core.architecture;
public class GameArchitecture : Architecture
{
protected override void Init()
{
// 注册模型 - 存储应用状态
RegisterModel(new PlayerModel());
RegisterModel(new GameStateModel());
// 注册系统 - 处理业务逻辑
RegisterSystem(new PlayerSystem());
RegisterSystem(new GameLogicSystem());
// 注册工具类 - 提供辅助功能
RegisterUtility(new StorageUtility());
}
}
```
## 2. 定义数据模型
创建您的数据模型:
```csharp
public class PlayerModel : AbstractModel
{
// 使用可绑定属性实现响应式数据
public BindableProperty<string> Name { get; } = new("Player");
public BindableProperty<int> Health { get; } = new(100);
public BindableProperty<int> Score { get; } = new(0);
protected override void OnInit()
{
// 监听健康值变化
Health.Register(OnHealthChanged);
}
private void OnHealthChanged(int newHealth)
{
if (newHealth <= 0)
{
this.SendEvent(new PlayerDiedEvent());
}
}
}
public class GameStateModel : AbstractModel
{
public BindableProperty<bool> IsGameRunning { get; } = new(false);
public BindableProperty<int> CurrentLevel { get; } = new(1);
}
```
## 3. 实现业务逻辑
创建处理业务逻辑的系统:
```csharp
public class PlayerSystem : AbstractSystem
{
protected override void OnInit()
{
// 监听玩家输入事件
this.RegisterEvent<PlayerMoveEvent>(OnPlayerMove);
this.RegisterEvent<PlayerAttackEvent>(OnPlayerAttack);
}
private void OnPlayerMove(PlayerMoveEvent e)
{
var playerModel = this.GetModel<PlayerModel>();
// 处理移动逻辑
Console.WriteLine($"Player moved to {e.Direction}");
}
private void OnPlayerAttack(PlayerAttackEvent e)
{
var playerModel = this.GetModel<PlayerModel>();
// 处理攻击逻辑
playerModel.Score.Value += 10;
this.SendEvent(new EnemyDamagedEvent { Damage = 25 });
}
}
public class GameLogicSystem : AbstractSystem
{
protected override void OnInit()
{
this.RegisterEvent<EnemyDamagedEvent>(OnEnemyDamaged);
this.RegisterEvent<PlayerDiedEvent>(OnPlayerDied);
}
private void OnEnemyDamaged(EnemyDamagedEvent e)
{
Console.WriteLine($"Enemy took {e.Damage} damage");
// 检查是否需要升级关卡
CheckLevelProgress();
}
private void OnPlayerDied(PlayerDiedEvent e)
{
var gameState = this.GetModel<GameStateModel>();
gameState.IsGameRunning.Value = false;
Console.WriteLine("Game Over!");
}
private void CheckLevelProgress()
{
// 实现关卡进度检查逻辑
}
}
```
## 4. 定义事件
创建应用中使用的事件:
```csharp
public class PlayerMoveEvent : IEvent
{
public Vector2 Direction { get; set; }
}
public class PlayerAttackEvent : IEvent
{
public Vector2 TargetPosition { get; set; }
}
public class PlayerDiedEvent : IEvent
{
// 玩家死亡事件
}
public class EnemyDamagedEvent : IEvent
{
public int Damage { get; set; }
}
```
## 5. 创建控制器
实现控制器来连接 UI 和业务逻辑:
```csharp
using GFramework.Core.Abstractions.controller;
using GFramework.SourceGenerators.Abstractions.rule;
[ContextAware]
public partial class GameController : IController
{
private PlayerModel _playerModel;
private GameStateModel _gameStateModel;
public void Initialize()
{
_playerModel = Context.GetModel<PlayerModel>();
_gameStateModel = Context.GetModel<GameStateModel>();
// 初始化事件监听
InitializeEventListeners();
}
private void InitializeEventListeners()
{
// 监听模型变化并更新 UI
_playerModel.Health.RegisterWithInitValue(OnHealthChanged);
_playerModel.Score.RegisterWithInitValue(OnScoreChanged);
_gameStateModel.IsGameRunning.Register(OnGameStateChanged);
}
public void StartGame()
{
_gameStateModel.IsGameRunning.Value = true;
Context.SendEvent(new GameStartEvent());
Console.WriteLine("Game started!");
}
public void MovePlayer(Vector2 direction)
{
Context.SendCommand(new MovePlayerCommand { Direction = direction });
}
public void PlayerAttack(Vector2 target)
{
Context.SendCommand(new AttackCommand { TargetPosition = target });
}
// UI 更新回调
private void OnHealthChanged(int health)
{
UpdateHealthDisplay(health);
}
private void OnScoreChanged(int score)
{
UpdateScoreDisplay(score);
}
private void OnGameStateChanged(bool isRunning)
{
UpdateGameStatusDisplay(isRunning);
}
private void UpdateHealthDisplay(int health)
{
// 更新血条 UI
Console.WriteLine($"Health: {health}");
}
private void UpdateScoreDisplay(int score)
{
// 更新分数显示
Console.WriteLine($"Score: {score}");
}
private void UpdateGameStatusDisplay(bool isRunning)
{
// 更新游戏状态显示
Console.WriteLine($"Game running: {isRunning}");
}
}
```
## 6. 定义命令
创建命令来封装用户操作:
```csharp
public class MovePlayerCommand : AbstractCommand
{
public Vector2 Direction { get; set; }
protected override void OnDo()
{
// 发送移动事件
this.SendEvent(new PlayerMoveEvent { Direction = Direction });
}
}
public class AttackCommand : AbstractCommand
{
public Vector2 TargetPosition { get; set; }
protected override void OnDo()
{
// 发送攻击事件
this.SendEvent(new PlayerAttackEvent { TargetPosition = TargetPosition });
}
}
```
## 7. 运行应用
现在让我们运行这个简单的应用:
```csharp
class Program
{
static void Main(string[] args)
{
// 创建并初始化架构
var architecture = new GameArchitecture();
architecture.Initialize();
// 创建控制器
var gameController = new GameController();
gameController.Initialize();
// 开始游戏
gameController.StartGame();
// 模拟玩家操作
gameController.MovePlayer(new Vector2(1, 0));
gameController.PlayerAttack(new Vector2(5, 5));
// 模拟玩家受伤
var playerModel = architecture.GetModel<PlayerModel>();
playerModel.Health.Value = 50;
// 模拟玩家死亡
playerModel.Health.Value = 0;
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
```
## 8. 运行结果
执行程序后,您应该看到类似以下输出:
```
Game started!
Game running: True
Player moved to (1, 0)
Player took 25 damage
Score: 10
Health: 50
Health: 0
Player died
Game Over!
Game running: False
Press any key to exit...
```
## 下一步
这个简单的示例展示了 GFramework 的核心概念:
1. **架构模式** - 清晰的分层结构
2. **响应式数据** - BindableProperty 自动更新
3. **事件驱动** - 松耦合的组件通信
4. **命令模式** - 封装用户操作

File diff suppressed because it is too large Load Diff

View File

@ -1,377 +1,403 @@
# 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<PlayerModel>();
var combatSystem = Context.GetSystem<CombatSystem>();
Context.SendEvent(new PlayerInitializedEvent());
}
public void Attack(Enemy target)
{
var damage = Context.GetUtility<DamageCalculator>().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
// <auto-generated/>
#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;
/// <summary>
/// 自动获取的架构上下文(懒加载,默认使用 GameContextProvider
/// </summary>
protected global::GFramework.Core.Abstractions.architecture.IArchitectureContext Context
{
get
{
if (_context == null)
{
_contextProvider ??= new global::GFramework.Core.architecture.GameContextProvider();
_context = _contextProvider.GetContext();
}
return _context;
}
}
/// <summary>
/// 配置上下文提供者(用于测试或多架构场景)
/// </summary>
/// <param name="provider">上下文提供者实例</param>
public static void SetContextProvider(global::GFramework.Core.Abstractions.architecture.IArchitectureContextProvider provider)
{
_contextProvider = provider;
}
/// <summary>
/// 重置上下文提供者为默认值(用于测试清理)
/// </summary>
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<string, IArchitecture> _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<SaveSystem>();
var uiSystem = Context.GetSystem<UISystem>();
await saveSystem.LoadAsync();
await uiSystem.ShowMainMenuAsync();
}
}
```
### 何时继承 ContextAwareBase
如果类需要更多框架功能(如生命周期管理),应继承 `ContextAwareBase`
```csharp
// 推荐:需要生命周期管理时继承基类
public class PlayerModel : AbstractModel
{
// AbstractModel 已经继承了 ContextAwareBase
protected override void OnInit()
{
var config = Context.GetUtility<ConfigLoader>().Load<PlayerConfig>();
}
}
// 推荐:简单组件使用属性
[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<SomeModel>(); // 可能为 null
}
// ✅ 正确:在初始化方法中访问
public void Initialize()
{
var model = Context.GetModel<SomeModel>(); // 安全
}
}
```
### 4. 优先使用 Context 属性而非接口方法
```csharp
[ContextAware]
public partial class MyController
{
public void DoSomething()
{
// ✅ 推荐:使用生成的 Context 属性
var model = Context.GetModel<SomeModel>();
// ❌ 不推荐:显式调用接口方法
var context = ((IContextAware)this).GetContext();
var model2 = context.GetModel<SomeModel>();
}
}
```
## 诊断信息
生成器会在以下情况报告编译错误:
### 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)
# 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<PlayerModel>();
var combatSystem = Context.GetSystem<CombatSystem>();
Context.SendEvent(new PlayerInitializedEvent());
}
public void Attack(Enemy target)
{
var damage = Context.GetUtility<DamageCalculator>().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
// <auto-generated/>
#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;
/// <summary>
/// 自动获取的架构上下文(懒加载,默认使用 GameContextProvider
/// </summary>
protected global::GFramework.Core.Abstractions.architecture.IArchitectureContext Context
{
get
{
if (_context == null)
{
_contextProvider ??= new global::GFramework.Core.architecture.GameContextProvider();
_context = _contextProvider.GetContext();
}
return _context;
}
}
/// <summary>
/// 配置上下文提供者(用于测试或多架构场景)
/// </summary>
/// <param name="provider">上下文提供者实例</param>
public static void SetContextProvider(global::GFramework.Core.Abstractions.architecture.IArchitectureContextProvider provider)
{
_contextProvider = provider;
}
/// <summary>
/// 重置上下文提供者为默认值(用于测试清理)
/// </summary>
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<string, IArchitecture> _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<SaveSystem>();
var uiSystem = Context.GetSystem<UISystem>();
await saveSystem.LoadAsync();
await uiSystem.ShowMainMenuAsync();
}
}
```
### 与 IController 配合使用
在 Godot 项目中,控制器通常同时实现 `IController` 和使用 `[ContextAware]`
```csharp
using GFramework.Core.Abstractions.controller;
using GFramework.SourceGenerators.Abstractions.rule;
[ContextAware]
public partial class PlayerController : Node, IController
{
public override void _Ready()
{
// Context 属性由 [ContextAware] 自动生成
var playerModel = Context.GetModel<PlayerModel>();
var combatSystem = Context.GetSystem<CombatSystem>();
}
}
```
**说明**
- `IController` 是标记接口,标识这是一个控制器
- `[ContextAware]` 提供架构访问能力
- 两者配合使用是推荐的模式
### 何时继承 ContextAwareBase
如果类需要更多框架功能(如生命周期管理),应继承 `ContextAwareBase`
```csharp
// 推荐:需要生命周期管理时继承基类
public class PlayerModel : AbstractModel
{
// AbstractModel 已经继承了 ContextAwareBase
protected override void OnInit()
{
var config = Context.GetUtility<ConfigLoader>().Load<PlayerConfig>();
}
}
// 推荐:简单组件使用属性
[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<SomeModel>(); // 可能为 null
}
// ✅ 正确:在初始化方法中访问
public void Initialize()
{
var model = Context.GetModel<SomeModel>(); // 安全
}
}
```
### 4. 优先使用 Context 属性而非接口方法
```csharp
[ContextAware]
public partial class MyController
{
public void DoSomething()
{
// ✅ 推荐:使用生成的 Context 属性
var model = Context.GetModel<SomeModel>();
// ❌ 不推荐:显式调用接口方法
var context = ((IContextAware)this).GetContext();
var model2 = context.GetModel<SomeModel>();
}
}
```
## 诊断信息
生成器会在以下情况报告编译错误:
### 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)

File diff suppressed because it is too large Load Diff

View File

@ -1,337 +1,341 @@
# 日志生成器
> GFramework.SourceGenerators 自动生成日志代码,减少样板代码
## 概述
日志生成器是一个 Source Generator它会自动为标记了 `[Log]` 特性的类生成 ILogger 字段。这消除了手动编写日志字段的需要,让开发者专注于业务逻辑。
## 基本用法
### 标记类
```csharp
using GFramework.SourceGenerators.Abstractions.logging;
[Log]
public partial class MyService
{
public void DoSomething()
{
// 自动生成的 Logger 字段可直接使用
Logger.Info("执行操作");
}
}
```
### 生成代码
上面的代码会被编译时转换为:
```csharp
// <auto-generated/>
public partial class MyService
{
private static readonly ILogger Logger =
LoggerFactoryResolver.Provider.CreateLogger("YourNamespace.MyService");
}
```
**注意**:生成器只生成 ILogger 字段不生成日志方法。日志方法Info、Debug、Error 等)来自 ILogger 接口本身。
## 日志级别
生成的 Logger 字段支持 ILogger 接口的所有方法:
```csharp
[Log]
public partial class MyClass
{
public void Example()
{
// 调试信息
Logger.Debug($"调试信息: {value}");
// 普通信息
Logger.Info("操作成功");
// 警告
Logger.Warning($"警告: {message}");
// 错误
Logger.Error($"错误: {ex.Message}");
// 严重错误
Logger.Critical("系统故障");
}
}
```
## 自定义日志类别
```csharp
[Log("Gameplay")]
public partial class GameplaySystem
{
// 日志会标记为 Gameplay 类别
public void Update()
{
Logger.Info("游戏逻辑更新");
}
}
```
## 配置选项
### 自定义字段名称
```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 集成
```csharp
[Log]
[ContextAware]
public partial class GodotController : Node
{
public override void _Ready()
{
Logger.Info("控制器已准备就绪");
}
}
```
### 与架构集成
```csharp
[Log]
public partial class MySystem : AbstractSystem
{
protected override void OnInit()
{
Logger.Info("系统初始化");
}
}
```
## 实际应用示例
### 游戏控制器
```csharp
[Log]
[ContextAware]
public partial class PlayerController : IController
{
public void HandleInput(string action)
{
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("DataService")]
public partial class DataProcessor
{
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)
- [ContextAware 生成器](./context-aware-generator)
# 日志生成器
> GFramework.SourceGenerators 自动生成日志代码,减少样板代码
## 概述
日志生成器是一个 Source Generator它会自动为标记了 `[Log]` 特性的类生成 ILogger 字段。这消除了手动编写日志字段的需要,让开发者专注于业务逻辑。
## 基本用法
### 标记类
```csharp
using GFramework.SourceGenerators.Abstractions.logging;
[Log]
public partial class MyService
{
public void DoSomething()
{
// 自动生成的 Logger 字段可直接使用
Logger.Info("执行操作");
}
}
```
### 生成代码
上面的代码会被编译时转换为:
```csharp
// <auto-generated/>
public partial class MyService
{
private static readonly ILogger Logger =
LoggerFactoryResolver.Provider.CreateLogger("YourNamespace.MyService");
}
```
**注意**:生成器只生成 ILogger 字段不生成日志方法。日志方法Info、Debug、Error 等)来自 ILogger 接口本身。
## 日志级别
生成的 Logger 字段支持 ILogger 接口的所有方法:
```csharp
[Log]
public partial class MyClass
{
public void Example()
{
// 调试信息
Logger.Debug($"调试信息: {value}");
// 普通信息
Logger.Info("操作成功");
// 警告
Logger.Warning($"警告: {message}");
// 错误
Logger.Error($"错误: {ex.Message}");
// 严重错误
Logger.Critical("系统故障");
}
}
```
## 自定义日志类别
```csharp
[Log("Gameplay")]
public partial class GameplaySystem
{
// 日志会标记为 Gameplay 类别
public void Update()
{
Logger.Info("游戏逻辑更新");
}
}
```
## 配置选项
### 自定义字段名称
```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 集成
```csharp
[Log]
[ContextAware]
public partial class GodotController : Node
{
public override void _Ready()
{
Logger.Info("控制器已准备就绪");
}
}
```
### 与架构集成
```csharp
[Log]
public partial class MySystem : AbstractSystem
{
protected override void OnInit()
{
Logger.Info("系统初始化");
}
}
```
## 实际应用示例
### 游戏控制器
```csharp
using GFramework.Core.Abstractions.controller;
using GFramework.SourceGenerators.Abstractions.logging;
using GFramework.SourceGenerators.Abstractions.rule;
[Log]
[ContextAware]
public partial class PlayerController : IController
{
public void HandleInput(string action)
{
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("DataService")]
public partial class DataProcessor
{
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)
- [ContextAware 生成器](./context-aware-generator)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff