mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
Merge pull request #88 from GeWuYou/refactor/controllers-to-context-aware-architecture-access
refactor(docs): 修正文档中关于IController的描述
This commit is contained in:
commit
23f186d395
@ -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>
|
||||
/// 架构访问 :控制器通常需要访问架构上下文。使用 [ContextAware] 特性
|
||||
/// 自动生成上下文访问能力:
|
||||
/// </para>
|
||||
/// <code>
|
||||
/// using GFramework.SourceGenerators.Abstractions.rule;
|
||||
///
|
||||
/// [ContextAware]
|
||||
/// public partial class PlayerController : IController
|
||||
/// {
|
||||
/// public void Initialize()
|
||||
/// {
|
||||
/// // [ContextAware] 实现 IContextAware 接口,可使用扩展方法
|
||||
/// var playerModel = this.GetModel<PlayerModel>();
|
||||
/// var gameSystem = this.GetSystem<GameSystem>();
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// <para>
|
||||
/// 注意:
|
||||
/// </para>
|
||||
/// <list type="bullet">
|
||||
/// <item>必须添加 partial 关键字</item>
|
||||
/// <item>[ContextAware] 特性会自动实现 IContextAware 接口</item>
|
||||
/// <item>可使用 this.GetModel()、this.GetSystem() 等扩展方法访问架构</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public interface IController;
|
||||
File diff suppressed because it is too large
Load Diff
@ -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 = this.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 = this.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 = this.GetModel<PlayerModel>();
|
||||
model.Health.Value -= 1;
|
||||
}
|
||||
|
||||
// ✅ 高效:缓存引用
|
||||
private PlayerModel _playerModel;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_playerModel = this.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());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
遵循这些最佳实践将帮助你构建可维护、高效、可扩展的应用程序。
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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()
|
||||
{
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
// 使用命令
|
||||
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" };
|
||||
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) - 数据模型,命令操作的数据
|
||||
|
||||
---
|
||||
|
||||
**许可证**:Apache 2.0
|
||||
File diff suppressed because it is too large
Load Diff
@ -232,13 +232,14 @@ 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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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 = 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
|
||||
[ContextAware]
|
||||
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#
|
||||
using GFramework.Core.Abstractions.controller;
|
||||
using GFramework.SourceGenerators.Abstractions.rule;
|
||||
|
||||
[ContextAware]
|
||||
public partial 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
|
||||
|
||||
@ -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;
|
||||
|
||||
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
@ -937,7 +937,7 @@ public partial class GameManager : Node, IController
|
||||
// 加载初始场景
|
||||
LoadInitialScene();
|
||||
|
||||
Context.SendEvent(new NewGameStartedEvent { PlayerName = playerName });
|
||||
this.SendEvent(new NewGameStartedEvent { PlayerName = playerName });
|
||||
}
|
||||
|
||||
public void LoadGame(int slotId)
|
||||
@ -952,19 +952,19 @@ public partial class GameManager : Node, IController
|
||||
// 恢复游戏状态
|
||||
RestoreGameState(saveData);
|
||||
|
||||
Context.SendEvent(new GameLoadedEvent { SlotId = slotId });
|
||||
this.SendEvent(new GameLoadedEvent { SlotId = slotId });
|
||||
Logger.Info("Game loaded successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning($"No save data found in slot {slotId}");
|
||||
Context.SendEvent(new GameLoadFailedEvent { SlotId = slotId });
|
||||
this.SendEvent(new GameLoadFailedEvent { SlotId = slotId });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error($"Failed to load game: {ex.Message}");
|
||||
Context.SendEvent(new GameLoadFailedEvent { SlotId = slotId, Error = ex.Message });
|
||||
this.SendEvent(new GameLoadFailedEvent { SlotId = slotId, Error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
@ -977,13 +977,13 @@ public partial class GameManager : Node, IController
|
||||
var saveData = CreateSaveData();
|
||||
_dataManager.SaveGame(slotId, saveData);
|
||||
|
||||
Context.SendEvent(new GameSavedEvent { SlotId = slotId });
|
||||
this.SendEvent(new GameSavedEvent { SlotId = slotId });
|
||||
Logger.Info("Game saved successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error($"Failed to save game: {ex.Message}");
|
||||
Context.SendEvent(new GameSaveFailedEvent { SlotId = slotId, Error = ex.Message });
|
||||
this.SendEvent(new GameSaveFailedEvent { SlotId = slotId, Error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
@ -1014,9 +1014,9 @@ public partial class GameManager : Node, IController
|
||||
gameWorld.AddChild(player);
|
||||
|
||||
// 恢复其他游戏状态
|
||||
Context.GetModel<PlayerModel>().Health.Value = saveData.PlayerHealth;
|
||||
Context.GetModel<GameModel>().CurrentLevel.Value = saveData.CurrentLevel;
|
||||
Context.GetModel<InventoryModel>().LoadFromData(saveData.Inventory);
|
||||
this.GetModel<PlayerModel>().Health.Value = saveData.PlayerHealth;
|
||||
this.GetModel<GameModel>().CurrentLevel.Value = saveData.CurrentLevel;
|
||||
this.GetModel<InventoryModel>().LoadFromData(saveData.Inventory);
|
||||
}
|
||||
|
||||
private SaveData CreateSaveData()
|
||||
@ -1026,9 +1026,9 @@ public partial class GameManager : Node, IController
|
||||
return new SaveData
|
||||
{
|
||||
PlayerPosition = player?.Position ?? Vector2.Zero,
|
||||
PlayerHealth = Context.GetModel<PlayerModel>().Health.Value,
|
||||
CurrentLevel = Context.GetModel<GameModel>().CurrentLevel.Value,
|
||||
Inventory = Context.GetModel<InventoryModel>().GetData(),
|
||||
PlayerHealth = this.GetModel<PlayerModel>().Health.Value,
|
||||
CurrentLevel = this.GetModel<GameModel>().CurrentLevel.Value,
|
||||
Inventory = this.GetModel<InventoryModel>().GetData(),
|
||||
Timestamp = DateTime.UtcNow,
|
||||
Version = 1
|
||||
};
|
||||
@ -1094,7 +1094,7 @@ public class AutoSaveSystem : AbstractSystem
|
||||
var saveData = CreateAutoSaveData();
|
||||
|
||||
// 保存到自动存档槽
|
||||
var storage = Context.GetUtility<IStorage>();
|
||||
var storage = this.GetUtility<IStorage>();
|
||||
storage.Write("autosave", saveData);
|
||||
storage.Write("autosave/timestamp", DateTime.UtcNow);
|
||||
|
||||
@ -1330,7 +1330,7 @@ public class PlayerModule : AbstractModule
|
||||
private void OnPlayerDeath(PlayerDeathEvent e)
|
||||
{
|
||||
// 触发保存模块的事件
|
||||
Context.SendEvent(new RequestAutoSaveEvent { Reason = "Player Death" });
|
||||
this.SendEvent(new RequestAutoSaveEvent { Reason = "Player Death" });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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
@ -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 = this.GetModel<PlayerModel>();
|
||||
_gameStateModel = this.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;
|
||||
this.SendEvent(new GameStartEvent());
|
||||
Console.WriteLine("Game started!");
|
||||
}
|
||||
|
||||
public void MovePlayer(Vector2 direction)
|
||||
{
|
||||
this.SendCommand(new MovePlayerCommand { Direction = direction });
|
||||
}
|
||||
|
||||
public void PlayerAttack(Vector2 target)
|
||||
{
|
||||
this.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
@ -75,7 +75,7 @@ private System.Collections.IEnumerator WaitUntilCondition()
|
||||
GD.Print("等待生命值恢复");
|
||||
|
||||
// 等待生命值大于 50
|
||||
var playerModel = Context.GetModel<PlayerModel>();
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
yield return new WaitUntil(() => playerModel.Health.Value > 50);
|
||||
|
||||
GD.Print("生命值已恢复!");
|
||||
@ -140,7 +140,7 @@ private System.Collections.IEnumerator EndOfFrameExample()
|
||||
```csharp
|
||||
private System.Collections.IEnumerator WaitUntilExample()
|
||||
{
|
||||
var health = Context.GetModel<PlayerModel>().Health;
|
||||
var health = this.GetModel<PlayerModel>().Health;
|
||||
|
||||
// 持续等待直到条件满足
|
||||
yield return new WaitUntil(() => health.Value > 0);
|
||||
@ -156,7 +156,7 @@ private System.Collections.IEnumerator WaitUntilExample()
|
||||
```csharp
|
||||
private System.Collections.IEnumerator WaitWhileExample()
|
||||
{
|
||||
var gameState = Context.GetModel<GameModel>();
|
||||
var gameState = this.GetModel<GameModel>();
|
||||
|
||||
// 等待游戏不再暂停
|
||||
yield return new WaitWhile(() => gameState.IsPaused.Value);
|
||||
@ -174,7 +174,7 @@ private System.Collections.IEnumerator WaitWhileExample()
|
||||
```csharp
|
||||
private System.Collections.IEnumerator CombinedWait()
|
||||
{
|
||||
var health = Context.GetModel<PlayerModel>().Health;
|
||||
var health = this.GetModel<PlayerModel>().Health;
|
||||
var button = GetNode<Button>("Button");
|
||||
|
||||
// 等待生命值恢复或按钮点击(任一条件满足即可)
|
||||
|
||||
@ -162,7 +162,7 @@ public partial class PlayerController : Node, IController
|
||||
public override void _Ready()
|
||||
{
|
||||
// 获取模型引用
|
||||
_playerModel = Context.GetModel<PlayerModel>();
|
||||
_playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
// 注册事件监听,自动与节点生命周期绑定
|
||||
this.RegisterEvent<PlayerInputEvent>(OnPlayerInput)
|
||||
@ -185,7 +185,7 @@ public partial class PlayerController : Node, IController
|
||||
MovePlayer(1, 0);
|
||||
break;
|
||||
case "attack":
|
||||
Context.SendCommand(new AttackCommand());
|
||||
this.SendCommand(new AttackCommand());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -312,7 +312,7 @@ public partial class UIController : Node, IController
|
||||
// Godot 信号 -> 框架事件
|
||||
this.CreateSignalBuilder(Button.SignalName.Pressed)
|
||||
.Connect(() => {
|
||||
Context.SendEvent(new UIButtonClickEvent { ButtonId = "start_game" });
|
||||
this.SendEvent(new UIButtonClickEvent { ButtonId = "start_game" });
|
||||
})
|
||||
.UnRegisterWhenNodeExitTree(this);
|
||||
|
||||
@ -392,7 +392,7 @@ public partial class WeaponController : Node, IController
|
||||
|
||||
protected override void OnInit()
|
||||
{
|
||||
_bulletPool = Context.GetSystem<BulletPoolSystem>();
|
||||
_bulletPool = this.GetSystem<BulletPoolSystem>();
|
||||
}
|
||||
|
||||
public void Shoot(Vector3 direction)
|
||||
@ -528,7 +528,7 @@ public partial class GameController : Node, IController
|
||||
Logger.Debug("Starting game");
|
||||
|
||||
// 发送游戏开始事件
|
||||
Context.SendEvent(new GameStartEvent());
|
||||
this.SendEvent(new GameStartEvent());
|
||||
|
||||
Logger.Info("Game started");
|
||||
}
|
||||
@ -536,7 +536,7 @@ public partial class GameController : Node, IController
|
||||
public void PauseGame()
|
||||
{
|
||||
Logger.Info("Game paused");
|
||||
Context.SendEvent(new GamePauseEvent());
|
||||
this.SendEvent(new GamePauseEvent());
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -610,7 +610,7 @@ public partial class PlayerController : CharacterBody2D, IController
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_playerModel = Context.GetModel<PlayerModel>();
|
||||
_playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
// 输入处理
|
||||
SetProcessInput(true);
|
||||
@ -639,7 +639,7 @@ public partial class PlayerController : CharacterBody2D, IController
|
||||
{
|
||||
if (CanShoot())
|
||||
{
|
||||
var bulletPool = Context.GetSystem<BulletPoolSystem>();
|
||||
var bulletPool = this.GetSystem<BulletPoolSystem>();
|
||||
var bullet = bulletPool.Spawn("player");
|
||||
|
||||
if (bullet != null)
|
||||
@ -648,7 +648,7 @@ public partial class PlayerController : CharacterBody2D, IController
|
||||
bullet.Initialize(GlobalPosition, direction.Normalized());
|
||||
GetTree().Root.AddChild(bullet);
|
||||
|
||||
Context.SendEvent(new BulletFiredEvent());
|
||||
this.SendEvent(new BulletFiredEvent());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -666,7 +666,7 @@ public partial class PlayerController : CharacterBody2D, IController
|
||||
private void Die()
|
||||
{
|
||||
Logger.Info("Player died");
|
||||
Context.SendEvent(new PlayerDeathEvent());
|
||||
this.SendEvent(new PlayerDeathEvent());
|
||||
QueueFreeX();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
{
|
||||
// 使用扩展方法访问架构([ContextAware] 实现 IContextAware 接口)
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var combatSystem = this.GetSystem<CombatSystem>();
|
||||
|
||||
this.SendEvent(new PlayerInitializedEvent());
|
||||
}
|
||||
|
||||
public void Attack(Enemy target)
|
||||
{
|
||||
var damage = this.GetUtility<DamageCalculator>().Calculate(this, target);
|
||||
this.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 = this.GetSystem<SaveSystem>();
|
||||
var uiSystem = this.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()
|
||||
{
|
||||
// 使用扩展方法访问架构([ContextAware] 实现 IContextAware 接口)
|
||||
var playerModel = this.GetModel<PlayerModel>();
|
||||
var combatSystem = this.GetSystem<CombatSystem>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**说明**:
|
||||
|
||||
- `IController` 是标记接口,标识这是一个控制器
|
||||
- `[ContextAware]` 提供架构访问能力
|
||||
- 两者配合使用是推荐的模式
|
||||
|
||||
### 何时继承 ContextAwareBase
|
||||
|
||||
如果类需要更多框架功能(如生命周期管理),应继承 `ContextAwareBase`:
|
||||
|
||||
```csharp
|
||||
// 推荐:需要生命周期管理时继承基类
|
||||
public class PlayerModel : AbstractModel
|
||||
{
|
||||
// AbstractModel 已经继承了 ContextAwareBase
|
||||
protected override void OnInit()
|
||||
{
|
||||
var config = this.GetUtility<ConfigLoader>().Load<PlayerConfig>();
|
||||
}
|
||||
}
|
||||
|
||||
// 推荐:简单组件使用属性
|
||||
[ContextAware]
|
||||
public partial class SimpleHelper
|
||||
{
|
||||
public void DoSomething()
|
||||
{
|
||||
this.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 = this.GetModel<SomeModel>(); // 可能为 null
|
||||
}
|
||||
|
||||
// ✅ 正确:在初始化方法中访问
|
||||
public void Initialize()
|
||||
{
|
||||
var model = this.GetModel<SomeModel>(); // 安全
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 优先使用 Context 属性而非接口方法
|
||||
|
||||
```csharp
|
||||
[ContextAware]
|
||||
public partial class MyController
|
||||
{
|
||||
public void DoSomething()
|
||||
{
|
||||
// ✅ 推荐:使用扩展方法
|
||||
var model = this.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
@ -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)
|
||||
|
||||
@ -168,7 +168,7 @@ public partial class PlayerService : IController
|
||||
|
||||
try
|
||||
{
|
||||
Context.SendCommand(command);
|
||||
this.SendCommand(command);
|
||||
Logger.Info($"Player {name} created successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -181,7 +181,7 @@ public partial class PlayerService : IController
|
||||
public PlayerData GetPlayer(string name)
|
||||
{
|
||||
var query = new GetPlayerQuery { PlayerName = name };
|
||||
return Context.SendQuery(query);
|
||||
return this.SendQuery(query);
|
||||
}
|
||||
|
||||
public List<PlayerData> GetAllPlayers(PlayerClass? classFilter = null, int? minLevel = null)
|
||||
@ -191,13 +191,13 @@ public partial class PlayerService : IController
|
||||
FilterByClass = classFilter,
|
||||
MinLevel = minLevel
|
||||
};
|
||||
return Context.SendQuery(query);
|
||||
return this.SendQuery(query);
|
||||
}
|
||||
|
||||
public PlayerStatistics GetPlayerStatistics(string playerName)
|
||||
{
|
||||
var query = new GetPlayerStatisticsQuery { PlayerName = playerName };
|
||||
return Context.SendQuery(query);
|
||||
return this.SendQuery(query);
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -1046,13 +1046,13 @@ public class PluginManager : IPluginContext
|
||||
}
|
||||
|
||||
// 尝试从架构中获取
|
||||
return _architecture.Context.GetUtility<T>();
|
||||
return _architecture.this.GetUtility<T>();
|
||||
}
|
||||
|
||||
public void RegisterEventHandler<T>(IEventHandler<T> handler) where T : IEvent
|
||||
{
|
||||
_eventHandlers.Add(handler);
|
||||
_architecture.Context.RegisterEvent<T>(handler.Handle);
|
||||
_architecture.this.RegisterEvent<T>(handler.Handle);
|
||||
_logger.Debug($"Event handler for {typeof(T).Name} registered by plugin system");
|
||||
}
|
||||
|
||||
@ -1167,7 +1167,7 @@ public class ChatService : IChatService
|
||||
chatChannel.AddMessage(chatMessage);
|
||||
|
||||
// 发送事件
|
||||
_architecture.Context.SendEvent(new ChatMessageReceivedEvent(chatMessage));
|
||||
_architecture.this.SendEvent(new ChatMessageReceivedEvent(chatMessage));
|
||||
}
|
||||
|
||||
public void SendSystemMessage(string message)
|
||||
@ -1181,7 +1181,7 @@ public class ChatService : IChatService
|
||||
};
|
||||
|
||||
_channels["global"].AddMessage(systemMessage);
|
||||
_architecture.Context.SendEvent(new ChatMessageReceivedEvent(systemMessage));
|
||||
_architecture.this.SendEvent(new ChatMessageReceivedEvent(systemMessage));
|
||||
}
|
||||
|
||||
private string FilterMessage(string message)
|
||||
@ -1444,7 +1444,7 @@ public class NetworkManager : Node, INetworkManager
|
||||
private void HandlePlayerPosition(PlayerPositionMessage message)
|
||||
{
|
||||
// 更新其他玩家位置
|
||||
Context.SendEvent(new NetworkPlayerPositionEvent
|
||||
this.SendEvent(new NetworkPlayerPositionEvent
|
||||
{
|
||||
PlayerId = message.PlayerId,
|
||||
Position = new Vector2(message.X, message.Y)
|
||||
@ -1454,7 +1454,7 @@ public class NetworkManager : Node, INetworkManager
|
||||
private void HandleChatMessage(ChatMessageMessage message)
|
||||
{
|
||||
// 显示聊天消息
|
||||
Context.SendEvent(new NetworkChatEvent
|
||||
this.SendEvent(new NetworkChatEvent
|
||||
{
|
||||
PlayerName = message.PlayerName,
|
||||
Message = message.Content,
|
||||
@ -1465,7 +1465,7 @@ public class NetworkManager : Node, INetworkManager
|
||||
private void HandlePlayerAction(PlayerActionMessage message)
|
||||
{
|
||||
// 处理玩家动作
|
||||
Context.SendEvent(new NetworkPlayerActionEvent
|
||||
this.SendEvent(new NetworkPlayerActionEvent
|
||||
{
|
||||
PlayerId = message.PlayerId,
|
||||
Action = message.Action,
|
||||
@ -1476,7 +1476,7 @@ public class NetworkManager : Node, INetworkManager
|
||||
private void HandleGameState(GameStateMessage message)
|
||||
{
|
||||
// 更新游戏状态
|
||||
Context.SendEvent(new NetworkGameStateEvent
|
||||
this.SendEvent(new NetworkGameStateEvent
|
||||
{
|
||||
State = message.State,
|
||||
Data = message.Data
|
||||
@ -1574,7 +1574,7 @@ public partial class NetworkController : Node, IController
|
||||
{
|
||||
var message = new PlayerPositionMessage
|
||||
{
|
||||
PlayerId = Context.GetModel<PlayerModel>().PlayerId,
|
||||
PlayerId = this.GetModel<PlayerModel>().PlayerId,
|
||||
X = position.X,
|
||||
Y = position.Y,
|
||||
Rotation = 0f // 根据实际需要设置
|
||||
@ -1587,7 +1587,7 @@ public partial class NetworkController : Node, IController
|
||||
{
|
||||
var chatMessage = new ChatMessageMessage
|
||||
{
|
||||
PlayerName = Context.GetModel<PlayerModel>().Name,
|
||||
PlayerName = this.GetModel<PlayerModel>().Name,
|
||||
Content = message,
|
||||
Channel = channel
|
||||
};
|
||||
@ -1598,13 +1598,13 @@ public partial class NetworkController : Node, IController
|
||||
private void OnNetworkConnected()
|
||||
{
|
||||
Logger.Info("Connected to network server");
|
||||
Context.SendEvent(new NetworkConnectedEvent());
|
||||
this.SendEvent(new NetworkConnectedEvent());
|
||||
}
|
||||
|
||||
private void OnNetworkDisconnected(string reason)
|
||||
{
|
||||
Logger.Info($"Disconnected from network server: {reason}");
|
||||
Context.SendEvent(new NetworkDisconnectedEvent { Reason = reason });
|
||||
this.SendEvent(new NetworkDisconnectedEvent { Reason = reason });
|
||||
}
|
||||
|
||||
private void OnNetworkMessageReceived(NetworkMessage message)
|
||||
@ -1615,7 +1615,7 @@ public partial class NetworkController : Node, IController
|
||||
private void OnConnectionFailed(string error)
|
||||
{
|
||||
Logger.Error($"Network connection failed: {error}");
|
||||
Context.SendEvent(new NetworkConnectionFailedEvent { Error = error });
|
||||
this.SendEvent(new NetworkConnectionFailedEvent { Error = error });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -186,7 +186,7 @@ public partial class PlayerController : CharacterBody2D, IController
|
||||
public override void _Ready()
|
||||
{
|
||||
// 设置上下文
|
||||
_playerModel = Context.GetModel<PlayerModel>();
|
||||
_playerModel = this.GetModel<PlayerModel>();
|
||||
|
||||
// 注册事件监听,自动与节点生命周期绑定
|
||||
this.RegisterEvent<PlayerInputEvent>(OnPlayerInput)
|
||||
@ -396,7 +396,7 @@ public partial class SignalController : Node, IController
|
||||
_timer.Start();
|
||||
|
||||
// 发送框架事件
|
||||
Context.SendEvent(new ButtonClickEvent { ButtonId = "main_button" });
|
||||
this.SendEvent(new ButtonClickEvent { ButtonId = "main_button" });
|
||||
}
|
||||
|
||||
private void OnTimerTimeout()
|
||||
@ -407,7 +407,7 @@ public partial class SignalController : Node, IController
|
||||
_progressBar.Value += 10;
|
||||
|
||||
// 发送框架事件
|
||||
Context.SendEvent(new TimerTimeoutEvent());
|
||||
this.SendEvent(new TimerTimeoutEvent());
|
||||
}
|
||||
|
||||
private void OnProgressChanged(double value)
|
||||
@ -415,7 +415,7 @@ public partial class SignalController : Node, IController
|
||||
Logger.Debug($"Progress changed: {value}");
|
||||
|
||||
// 发送框架事件
|
||||
Context.SendEvent(new ProgressChangeEvent { Value = value });
|
||||
this.SendEvent(new ProgressChangeEvent { Value = value });
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -450,7 +450,7 @@ public partial class SignalEventBridge : Node, IController
|
||||
// Godot 信号 -> 框架事件
|
||||
this.CreateSignalBuilder(_uiButton.SignalName.Pressed)
|
||||
.Connect(() => {
|
||||
Context.SendEvent(new UIActionEvent {
|
||||
this.SendEvent(new UIActionEvent {
|
||||
Action = "button_click",
|
||||
Source = "main_button"
|
||||
});
|
||||
@ -459,7 +459,7 @@ public partial class SignalEventBridge : Node, IController
|
||||
|
||||
this.CreateSignalBuilder(_healthBar.SignalName.HealthDepleted)
|
||||
.Connect(() => {
|
||||
Context.SendEvent(new PlayerDeathEvent { Source = "health_system" });
|
||||
this.SendEvent(new PlayerDeathEvent { Source = "health_system" });
|
||||
})
|
||||
.UnRegisterWhenNodeExitTree(this);
|
||||
}
|
||||
@ -599,7 +599,7 @@ public partial class SmartResourceLoader : Node, IController
|
||||
Logger.Info($"Resource loaded: {request.Path}");
|
||||
|
||||
// 发送资源加载完成事件
|
||||
Context.SendEvent(new ResourceLoadedEvent {
|
||||
this.SendEvent(new ResourceLoadedEvent {
|
||||
Path = request.Path,
|
||||
Resource = resource
|
||||
});
|
||||
@ -608,7 +608,7 @@ public partial class SmartResourceLoader : Node, IController
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error($"Failed to load resource {request.Path}: {ex.Message}");
|
||||
Context.SendEvent(new ResourceLoadFailedEvent {
|
||||
this.SendEvent(new ResourceLoadFailedEvent {
|
||||
Path = request.Path,
|
||||
Error = ex.Message
|
||||
});
|
||||
@ -751,7 +751,7 @@ public partial class SceneResourcePreloader : Node, IController
|
||||
|
||||
foreach (var assetPath in resourceSet.RequiredAssets)
|
||||
{
|
||||
Context.SendEvent(new ResourceLoadRequestEvent {
|
||||
this.SendEvent(new ResourceLoadRequestEvent {
|
||||
Path = assetPath,
|
||||
Priority = ResourcePriority.High
|
||||
});
|
||||
@ -977,7 +977,7 @@ public partial class MemoryOptimizedController : Node, IController
|
||||
Logger.Warning($"High memory usage detected: {memoryUsage / 1024 / 1024} MB");
|
||||
|
||||
// 触发内存清理
|
||||
Context.SendEvent(new HighMemoryUsageEvent {
|
||||
this.SendEvent(new HighMemoryUsageEvent {
|
||||
CurrentUsage = memoryUsage,
|
||||
Threshold = threshold
|
||||
});
|
||||
@ -1163,7 +1163,7 @@ public class PlayingState : IGameState
|
||||
public void Enter(StateMachineController controller)
|
||||
{
|
||||
controller.GetTree().Paused = false;
|
||||
controller.Context.SendEvent(new GameStartEvent());
|
||||
controller.this.SendEvent(new GameStartEvent());
|
||||
}
|
||||
|
||||
public void Update(StateMachineController controller, double delta) { }
|
||||
@ -1178,7 +1178,7 @@ public class PlayingState : IGameState
|
||||
|
||||
public void Exit(StateMachineController controller)
|
||||
{
|
||||
controller.Context.SendEvent(new GamePauseEvent());
|
||||
controller.this.SendEvent(new GamePauseEvent());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -832,7 +832,7 @@ public class ArchitectureIntegrationTests
|
||||
await _architecture.DestroyAsync();
|
||||
|
||||
// Assert
|
||||
var system = _architecture.Context.GetSystem<TestSystem>();
|
||||
var system = _architecture.this.GetSystem<TestSystem>();
|
||||
Assert.That(system!.DestroyCalled, Is.True);
|
||||
Assert.That(_architecture.CurrentPhase, Is.EqualTo(ArchitecturePhase.Destroyed));
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user