diff --git a/docs/core/architecture.md b/docs/core/architecture.md new file mode 100644 index 0000000..43638b4 --- /dev/null +++ b/docs/core/architecture.md @@ -0,0 +1,509 @@ +# Architecture 包使用说明 + +## 概述 + +Architecture 包是整个框架的核心,提供了基于 MVC 架构模式的应用程序架构基础。它实现了依赖注入(IoC)容器、组件生命周期管理,以及命令、查询、事件的统一调度机制。 + +**注意**:本框架的 Core 模块与 Godot 解耦,Godot 相关集成在 GFramework.Godot 包中实现。 + +## 核心接口 + +### IArchitecture + +架构接口,定义了框架的核心功能契约。 + +**主要职责:** + +- 组件注册:注册 System、Model、Utility +- 组件获取:从容器中获取已注册的组件 +- 命令处理:发送并执行命令 +- 查询处理:发送并执行查询 +- 事件管理:发送、注册、注销事件 + +**核心方法:** + +```csharp +// 组件注册 +void RegisterSystem(TSystem system) where TSystem : ISystem; +void RegisterModel(TModel model) where TModel : IModel; +void RegisterUtility(TUtility utility) where TUtility : IUtility; + +// 组件获取(通过容器) +T GetModel() where T : class, IModel; +T GetSystem() where T : class, ISystem; +T GetUtility() where T : class, IUtility; + +// 命令处理 +void SendCommand(ICommand command); +TResult SendCommand(ICommand command); + +// 查询处理 +TResult SendQuery(IQuery query); + +// 事件管理 +void SendEvent() where T : new(); +void SendEvent(T e); +IUnRegister RegisterEvent(Action onEvent); +void UnRegisterEvent(Action onEvent); +``` + +### IArchitecturePhaseAware + +架构阶段感知接口,允许组件监听架构阶段变化。 + +**核心方法:** + +```csharp +void OnArchitecturePhase(ArchitecturePhase phase); +``` + +### IArchitectureModule + +架构模块接口,支持模块化架构扩展。 + +**核心方法:** + +```csharp +void Install(IArchitecture architecture); +``` + +### IAsyncInitializable + +异步初始化接口,支持组件异步初始化。 + +**核心方法:** + +```csharp +Task InitializeAsync(); +``` + +## 核心类 + +### [`Architecture`](Architecture.cs) + +架构基类,实现了 `IArchitecture` 接口,提供完整的架构功能实现。 + +**构造函数参数:** + +```csharp +public abstract class Architecture( + IArchitectureConfiguration? configuration = null, + IEnvironment? environment = null, + IArchitectureServices? services = null, + IArchitectureContext? context = null +) +``` + +**特性:** + +- **阶段式生命周期管理** + :支持多个架构阶段(BeforeUtilityInit、AfterUtilityInit、BeforeModelInit、AfterModelInit、BeforeSystemInit、AfterSystemInit、Ready、Destroying、Destroyed) +- **模块安装系统**:支持通过 `InstallModule` 扩展架构功能 +- **异步初始化**:支持同步和异步两种初始化方式 +- **IoC 容器集成**:内置依赖注入容器 +- **事件系统集成**:集成类型化事件系统 +- **与平台无关**:Core 模块不依赖 Godot,可以在任何 .NET 环境中使用 + +**架构阶段:** + +```csharp +public enum ArchitecturePhase +{ + None = 0, // 初始阶段 + BeforeUtilityInit = 1, // 工具初始化前 + AfterUtilityInit = 2, // 工具初始化后 + BeforeModelInit = 3, // 模型初始化前 + AfterModelInit = 4, // 模型初始化后 + BeforeSystemInit = 5, // 系统初始化前 + AfterSystemInit = 6, // 系统初始化后 + Ready = 7, // 就绪状态 + FailedInitialization = 8, // 初始化失败 + Destroying = 9, // 正在销毁 + Destroyed = 10 // 已销毁 +} +``` + +**初始化流程:** + +1. 创建架构实例(传入配置或使用默认配置) +2. 调用用户自定义的 `Init()` 方法 +3. 初始化上下文工具(Context Utility) +4. 初始化所有注册的 Model +5. 初始化所有注册的 System +6. 冻结 IOC 容器 +7. 进入 Ready 阶段 + +**销毁流程:** + +1. 进入 Destroying 阶段 +2. 发送 `ArchitectureDestroyingEvent` 事件 +3. 销毁所有 System +4. 进入 Destroyed 阶段 +5. 发送 `ArchitectureDestroyedEvent` 事件 + +**使用示例:** + +``csharp +// 1. 定义你的架构(继承 Architecture 基类) +public class GameArchitecture : Architecture +{ +protected override void Init() +{ +// 注册 Model +RegisterModel(new PlayerModel()); +RegisterModel(new InventoryModel()); + + // 注册 System + RegisterSystem(new GameplaySystem()); + RegisterSystem(new SaveSystem()); + + // 注册 Utility + RegisterUtility(new StorageUtility()); + RegisterUtility(new TimeUtility()); + } + +} + +// 2. 创建并初始化架构 +var architecture = new GameArchitecture(); +architecture.Initialize(); + +// 或者异步初始化 +// var architecture = new GameArchitecture(); +// await architecture.InitializeAsync(); + +// 3. 通过依赖注入使用架构 +// 在 Controller 或其他组件中注入架构实例 +public class GameController : IController +{ +private readonly IArchitecture _architecture; + + // 通过构造函数注入架构 + public GameController(IArchitecture architecture) + { + _architecture = architecture; + } + + public void Start() + { + // 获取 Model + var playerModel = _architecture.GetModel(); + + // 发送命令 + _architecture.SendCommand(new StartGameCommand()); + + // 发送查询 + var score = _architecture.SendQuery(new GetScoreQuery()); + + // 注册事件 + _architecture.RegisterEvent(OnPlayerDied); + } + + private void OnPlayerDied(PlayerDiedEvent e) + { + // 处理玩家死亡事件 + } + +} + +``` + +**核心方法与属性:** + +### 初始化方法 + +#### `Initialize()` + +同步初始化方法,阻塞当前线程直到初始化完成。 + +```csharp +public void Initialize() +``` + +**使用示例:** + +```csharp +var architecture = new GameArchitecture(); +architecture.Initialize(); // 阻塞等待初始化完成 +``` + +**异常处理:** + +如果初始化过程中发生异常,架构会进入 `FailedInitialization` 阶段并发送 `ArchitectureFailedInitializationEvent` 事件。 + +#### `InitializeAsync()` + +异步初始化方法,返回 Task 以便调用者可以等待初始化完成。 + +```csharp +public async Task InitializeAsync() +``` + +**使用示例:** + +```csharp +var architecture = new GameArchitecture(); +await architecture.InitializeAsync(); // 异步等待初始化完成 +``` + +**优势:** + +- 支持异步初始化 Model 和 System +- 可以利用异步 I/O 操作(如异步加载数据) +- 提高初始化性能 + +### 模块管理 + +#### `InstallModule(IArchitectureModule module)` + +安装架构模块,用于扩展架构功能。 + +```csharp +public void InstallModule(IArchitectureModule module) +``` + +**参数:** + +- `module`:要安装的模块实例 + +**使用示例:** + +```csharp +// 定义模块 +public class NetworkModule : IArchitectureModule +{ + public void Install(IArchitecture architecture) + { + // 注册网络相关的 System 和 Utility + architecture.RegisterSystem(new NetworkSystem()); + architecture.RegisterUtility(new HttpClientUtility()); + } +} + +// 安装模块 +var architecture = new GameArchitecture(); +architecture.InstallModule(new NetworkModule()); +``` + +### 生命周期钩子 + +#### `RegisterLifecycleHook(IArchitectureLifecycle hook)` + +注册生命周期钩子,用于在架构阶段变化时执行自定义逻辑。 + +```csharp +public void RegisterLifecycleHook(IArchitectureLifecycle hook) +``` + +**参数:** + +- `hook`:生命周期钩子实例 + +**使用示例:** + +```csharp +// 定义生命周期钩子 +public class PerformanceMonitorHook : IArchitectureLifecycle +{ + public void OnPhase(ArchitecturePhase phase, IArchitecture architecture) + { + switch (phase) + { + case ArchitecturePhase.BeforeModelInit: + Console.WriteLine("开始监控 Model 初始化性能"); + break; + case ArchitecturePhase.AfterModelInit: + Console.WriteLine("Model 初始化完成,停止监控"); + break; + case ArchitecturePhase.Ready: + Console.WriteLine("架构就绪,开始性能统计"); + break; + } + } +} + +// 注册生命周期钩子 +var architecture = new GameArchitecture(); +architecture.RegisterLifecycleHook(new PerformanceMonitorHook()); +architecture.Initialize(); +``` + +**注意:** 生命周期钩子只能在架构进入 Ready 阶段之前注册。 + +### 属性 + +#### `CurrentPhase` + +获取当前架构的阶段。 + +```csharp +public ArchitecturePhase CurrentPhase { get; } +``` + +**使用示例:** + +```csharp +var architecture = new GameArchitecture(); + +// 检查架构是否已就绪 +if (architecture.CurrentPhase == ArchitecturePhase.Ready) +{ + Console.WriteLine("架构已就绪,可以开始游戏"); +} + +// 在异步操作中检查阶段变化 +await Task.Run(async () => +{ + while (architecture.CurrentPhase != ArchitecturePhase.Ready) + { + Console.WriteLine($"当前阶段: {architecture.CurrentPhase}"); + await Task.Delay(100); + } +}); +``` + +#### `Context` + +获取架构上下文,提供对架构服务的访问。 + +```csharp +public IArchitectureContext Context { get; } +``` + +**使用示例:** + +```csharp +// 通过 Context 访问服务 +var context = architecture.Context; +var eventBus = context.EventBus; +var commandBus = context.CommandBus; +var queryBus = context.QueryBus; +var environment = context.Environment; +``` + +**高级特性:** + +``csharp +// 1. 使用自定义配置 +var config = new ArchitectureConfiguration(); +var architecture = new GameArchitecture(configuration: config); + +// 2. 模块安装 +var module = new GameModule(); +architecture.InstallModule(module); + +// 3. 监听架构阶段变化 +public class GamePhaseListener : IArchitecturePhaseAware +{ +public void OnArchitecturePhase(ArchitecturePhase phase) +{ +switch (phase) +{ +case ArchitecturePhase.Ready: +GD.Print("架构已就绪,可以开始游戏了"); +break; +case ArchitecturePhase.Destroying: +GD.Print("架构正在销毁"); +break; +} +} +} + +// 4. 生命周期钩子 +public class LifecycleHook : IArchitectureLifecycle +{ +public void OnPhase(ArchitecturePhase phase, IArchitecture architecture) +{ +GD.Print($"架构阶段变化: {phase}"); +} +} + +``` + +### [`ArchitectureConfiguration`](ArchitectureConfiguration.cs) + +架构配置类,用于配置架构的行为。 + +**使用示例:** + +``csharp +var config = new ArchitectureConfiguration +{ + // 严格阶段验证 + StrictPhaseValidation = true, + // 允许延迟注册 + AllowLateRegistration = false +}; + +var architecture = new GameArchitecture(configuration: config); +``` + +### [`ArchitectureServices`](ArchitectureServices.cs) + +架构服务类,管理命令总线、查询总线、IOC容器和类型事件系统。 + +### [`ArchitectureContext`](ArchitectureContext.cs) + +架构上下文类,提供对架构服务的访问。 + +### [`GameContext`](GameContext.cs) + +游戏上下文类,管理架构上下文与类型的绑定关系。 + +## 设计模式 + +### 1. 依赖注入 + +通过构造函数注入或容器解析获取架构实例。 + +### 2. 依赖注入(IoC) + +使用内置 IoC 容器管理组件生命周期和依赖关系。 + +### 3. 命令模式 + +通过 `ICommand` 封装所有用户操作。 + +### 4. 查询模式(CQRS) + +通过 `IQuery` 分离查询和命令操作。 + +### 5. 观察者模式 + +通过事件系统实现组件间的松耦合通信。 + +### 6. 阶段式生命周期管理 + +通过 `ArchitecturePhase` 枚举和生命周期钩子管理架构状态。 + +### 7. 组合优于继承 + +通过接口组合获得不同能力,而不是深层继承链。 + +## 最佳实践 + +1. **保持架构类简洁**:只在 `Init()` 中注册组件,不要包含业务逻辑 +2. **合理划分职责**: + - Model:数据和状态 + - System:业务逻辑和规则 + - Utility:无状态的工具方法 +3. **使用依赖注入**:通过构造函数注入架构实例,便于测试 +4. **事件命名规范**:使用过去式命名事件类,如 `PlayerDiedEvent` +5. **避免循环依赖**:System 不应直接引用 System,应通过事件通信 +6. **使用模块扩展**:通过 `IArchitectureModule` 实现架构的可扩展性 +7. **Core 模块与平台解耦**:GFramework.Core 不包含 Godot 相关代码,Godot 集成在单独模块中 + +## 相关包 + +- [`command`](./command.md) - 命令模式实现 +- [`query`](./query.md) - 查询模式实现 +- [`events`](./events.md) - 事件系统 +- [`ioc`](./ioc.md) - IoC 容器 +- [`model`](./model.md) - 数据模型 +- [`system`](./system.md) - 业务系统 +- [`utility`](./utility.md) - 工具类 +- **GFramework.Godot** - Godot 特定集成(GodotNode 扩展、GodotLogger 等) + +--- + +**许可证**: Apache 2.0 diff --git a/docs/core/command.md b/docs/core/command.md new file mode 100644 index 0000000..701281d --- /dev/null +++ b/docs/core/command.md @@ -0,0 +1,309 @@ +# Command 包使用说明 + +## 概述 + +Command 包实现了命令模式(Command Pattern),用于封装用户操作和业务逻辑。通过命令模式,可以将请求封装为对象,实现操作的参数化、队列化、日志记录、撤销等功能。 + +## 核心接口 + +### 1. [`ICommand`](ICommand.cs) + +无返回值命令接口,定义了命令的基本契约。 + +**核心方法:** + +```csharp +void Execute(); // 执行命令 +``` + +### 2. [`ICommand`](ICommand.cs) + +带返回值的命令接口,用于需要返回执行结果的命令。 + +**核心方法:** + +```csharp +TResult Execute(); // 执行命令并返回结果 +``` + +## 核心类 + +### 1. [`AbstractCommand`](AbstractCommand.cs) + +无返回值命令的抽象基类,提供了命令的基础实现。它继承自 [ContextAwareBase](file:///d:/Project/Rider/GFramework/GFramework.Core.Abstractions/rule/IContextAware.cs#L4-L28) +,具有上下文感知能力。 + +**使用示例:** + +```csharp +// 定义一个命令输入参数 +public struct StartGameCommandInput : ICommandInput +{ + public int LevelId { get; set; } + public string PlayerName { get; set; } +} + +// 定义一个开始游戏的命令 +public class StartGameCommand : AbstractCommand +{ + public StartGameCommand(StartGameCommandInput input) : base(input) + { + } + + protected override void OnExecute(StartGameCommandInput input) + { + // 获取需要的模型 + var playerModel = this.GetModel(); + var gameModel = this.GetModel(); + + // 执行业务逻辑 + 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() + { + // 发送命令实例 + this.SendCommand(new StartGameCommand(new StartGameCommandInput + { + LevelId = 1, + PlayerName = "Player1" + })); + } +} +``` + +### 2. [`AbstractCommand`](AbstractCommand.cs) + +带返回值命令的抽象基类,同样继承自 [ContextAwareBase](file:///d:/Project/Rider/GFramework/GFramework.Core.Abstractions/rule/IContextAware.cs#L4-L28)。 + +**使用示例:** + +```csharp +// 定义一个计算伤害的命令输入 +public struct CalculateDamageCommandInput : ICommandInput +{ + public int AttackerAttackPower { get; set; } + public int DefenderDefense { get; set; } +} + +// 定义一个计算伤害的命令 +public class CalculateDamageCommand : AbstractCommand +{ + public CalculateDamageCommand(CalculateDamageCommandInput input) : base(input) + { + } + + protected override int OnExecute(CalculateDamageCommandInput input) + { + // 获取游戏配置 + var config = this.GetModel(); + + // 计算最终伤害 + 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 damage = this.SendCommand(new CalculateDamageCommand(new CalculateDamageCommandInput + { + AttackerAttackPower = attacker.AttackPower, + DefenderDefense = defender.Defense + })); + + // 应用伤害 + defender.Health -= damage; + + // 发送伤害事件 + this.SendEvent(new DamageDealtEvent(attacker, defender, damage)); + } +} +``` + +## 命令的生命周期 + +1. **创建命令**:实例化命令对象,传入必要的参数 +2. **执行命令**:调用 `Execute()` 方法,内部委托给 `OnExecute()` +3. **返回结果**:对于带返回值的命令,返回执行结果 +4. **命令销毁**:命令执行完毕后可以被垃圾回收 + +## CommandBus - 命令总线 + +### 功能说明 + +[CommandBus](file:///d:/Project/Rider/GFramework/GFramework.Core/command/CommandBus.cs#L8-L34) 是命令执行的核心组件,负责发送和执行命令。 + +**主要方法:** + +```csharp +void Send(ICommand command); // 发送无返回值命令 +TResult Send(ICommand command); // 发送带返回值命令 +``` + +### 使用示例 + +```csharp +var commandBus = new CommandBus(); + +// 发送无返回值命令 +commandBus.Send(new StartGameCommand(new StartGameCommandInput())); + +// 发送带返回值命令 +var result = commandBus.Send(new CalculateDamageCommand(new CalculateDamageCommandInput())); +``` + +## EmptyCommandInput - 空命令输入 + +当命令不需要输入参数时,可以使用 `EmptyCommandInput` 类: + +```csharp +public class SimpleActionCommand : AbstractCommand +{ + public SimpleActionCommand(EmptyCommandInput input) : base(input) + { + } + + protected override void OnExecute(EmptyCommandInput input) + { + // 执行简单操作,无需额外参数 + this.SendEvent(new SimpleActionEvent()); + } +} +``` + +## 使用场景 + +### 1. 用户交互操作 + +```csharp +public struct SaveGameCommandInput : ICommandInput +{ + public string SaveSlot { get; set; } +} + +public class SaveGameCommand : AbstractCommand +{ + public SaveGameCommand(SaveGameCommandInput input) : base(input) + { + } + + protected override void OnExecute(SaveGameCommandInput input) + { + var saveSystem = this.GetSystem(); + var playerModel = this.GetModel(); + + saveSystem.SavePlayerData(playerModel, input.SaveSlot); + this.SendEvent(new GameSavedEvent(input.SaveSlot)); + } +} +``` + +### 2. 业务流程控制 + +```csharp +public struct LoadLevelCommandInput : ICommandInput +{ + public int LevelId { get; set; } +} + +public class LoadLevelCommand : AbstractCommand +{ + public LoadLevelCommand(LoadLevelCommandInput input) : base(input) + { + } + + protected override void OnExecute(LoadLevelCommandInput input) + { + var levelSystem = this.GetSystem(); + var uiSystem = this.GetSystem(); + + // 显示加载界面 + uiSystem.ShowLoadingScreen(); + + // 加载关卡 + levelSystem.LoadLevel(input.LevelId); + + // 发送事件 + this.SendEvent(new LevelLoadedEvent(input.LevelId)); + } +} +``` + +## 最佳实践 + +1. **保持命令原子性**:一个命令应该完成一个完整的业务操作 +2. **命令无状态**:命令不应该保存长期状态,执行完即可丢弃 +3. **参数通过构造函数传递**:命令需要的参数应在创建时传入 +4. **避免命令嵌套**:命令内部尽量不要发送其他命令,使用事件通信 +5. **合理使用返回值**:只在确实需要返回结果时使用 `AbstractCommand` +6. **命令命名规范**:使用动词+名词形式,如 `StartGameCommand`、`SavePlayerCommand` +7. **输入参数结构化**:使用 `ICommandInput` 接口的实现类来组织命令参数 + +## 扩展功能 + +### 命令撤销/重做(可扩展) + +```csharp +public struct MoveCommandInput : ICommandInput +{ + public Vector3 NewPosition { get; set; } +} + +// 实现可撤销命令 +public class MoveCommand : AbstractCommand, IUndoableCommand +{ + private Vector3 _oldPosition; + private Vector3 _newPosition; + + public MoveCommand(MoveCommandInput input) : base(input) + { + _newPosition = input.NewPosition; + } + + protected override void OnExecute(MoveCommandInput input) + { + var player = this.GetModel(); + _oldPosition = player.Position; + player.Position = input.NewPosition; + } + + public void Undo() + { + var player = this.GetModel(); + player.Position = _oldPosition; + } +} +``` + +## 相关包 + +- [`architecture`](./architecture.md) - 架构核心,负责命令的分发和执行 +- [`extensions`](./extensions.md) - 提供 `SendCommand()` 扩展方法 +- [`query`](./query.md) - 查询模式,用于数据查询 +- [`events`](./events.md) - 事件系统,命令执行后的通知机制 +- [`system`](./system.md) - 业务系统,命令的主要执行者 +- [`model`](./model.md) - 数据模型,命令操作的数据 + +--- + +**许可证**: Apache 2.0 \ No newline at end of file diff --git a/docs/core/controller.md b/docs/core/controller.md new file mode 100644 index 0000000..a511a14 --- /dev/null +++ b/docs/core/controller.md @@ -0,0 +1,413 @@ +# Controller 包使用说明 + +## 概述 + +Controller 包定义了控制器(Controller)的接口规范。控制器是 MVC 架构中的 C 层,负责处理用户交互、协调视图和模型,是连接表现层和业务层的桥梁。 + +**注意**:本框架使用依赖注入模式,Controller 通过构造函数或属性注入获取架构实例,而非使用全局单例。 + +## 核心接口 + +### [`IController`](IController.cs) + +控制器接口,定义了控制器需要实现的所有功能契约。 + +**继承的能力接口:** + +- [`ICanSendCommand`](../command/ICanSendCommand.cs) - 可发送命令 +- [`ICanGetSystem`](../system/ICanGetSystem.cs) - 可获取系统 +- [`ICanGetModel`](../model/ICanGetModel.cs) - 可获取模型 +- [`ICanRegisterEvent`](../events/ICanRegisterEvent.cs) - 可注册事件 +- [`ICanSendQuery`](../query/ICanSendQuery.cs) - 可发送查询 +- [`ICanGetUtility`](../utility/ICanGetUtility.cs) - 可获取工具 + +**能力说明:** + +控制器拥有框架中最全面的能力集合,可以: + +1. 发送命令执行业务逻辑 +2. 获取系统调用服务 +3. 获取模型读写数据 +4. 注册事件监听变化 +5. 发送查询获取信息 +6. 获取工具使用辅助功能 + +## 使用示例 + +### 基础控制器实现(依赖注入模式) + +```csharp +using GFramework.Core.architecture; + +// 通过依赖注入获取架构 +public class PlayerController : IController +{ + private readonly IArchitecture _architecture; + private readonly IUnRegisterList _unregisterList = new UnRegisterList(); + + // 通过构造函数注入架构 + public PlayerController(IArchitecture architecture) + { + _architecture = architecture; + } + + public void Initialize() + { + // 获取模型 + var playerModel = _architecture.GetModel(); + + // 监听模型变化 + playerModel.Health.RegisterWithInitValue(OnHealthChanged) + .AddToUnregisterList(_unregisterList); + + // 注册事件 + _architecture.RegisterEvent(OnPlayerLevelUp) + .AddToUnregisterList(_unregisterList); + } + + // 处理用户输入 + public void ProcessInput(double delta) + { + if (Input.IsActionJustPressed("attack")) + { + // 发送命令 + _architecture.SendCommand(new AttackCommand()); + } + + if (Input.IsActionJustPressed("use_item")) + { + // 发送查询 + var inventory = _architecture.SendQuery(new GetInventoryQuery()); + if (inventory.HasItem("potion")) + { + _architecture.SendCommand(new UseItemCommand("potion")); + } + } + } + + private void OnHealthChanged(int newHealth) + { + // 更新 UI 显示 + UpdateHealthBar(newHealth); + } + + private void OnPlayerLevelUp(PlayerLevelUpEvent e) + { + // 显示升级特效 + ShowLevelUpEffect(); + } + + public void Cleanup() + { + // 清理事件注册 + _unregisterList.UnRegisterAll(); + } + + private void UpdateHealthBar(int health) { /* UI 更新逻辑 */ } + private void ShowLevelUpEffect() { /* 特效逻辑 */ } +} +``` + +### UI 控制器示例 + +```csharp +// UI 面板控制器 +public class MainMenuController : IController +{ + [Inject] private IArchitecture _architecture; + [Inject] private IUISystem _uiSystem; + + [Export] private Button _startButton; + [Export] private Button _settingsButton; + [Export] private Button _quitButton; + + public void Initialize() + { + // 绑定按钮事件 + _startButton.Pressed += OnStartButtonPressed; + _settingsButton.Pressed += OnSettingsButtonPressed; + _quitButton.Pressed += OnQuitButtonPressed; + + // 获取模型更新 UI + var gameModel = _architecture.GetModel(); + UpdateUI(gameModel); + } + + private void OnStartButtonPressed() + { + // 通过命令启动游戏 + _architecture.SendCommand(); + } + + private void OnSettingsButtonPressed() + { + // 查询当前设置 + var settings = _architecture.SendQuery(new GetSettingsQuery()); + + // 打开设置面板 + _uiSystem.OpenSettingsPanel(settings); + } + + private void OnQuitButtonPressed() + { + // 发送退出命令 + _architecture.SendCommand(); + } + + private void UpdateUI(GameModel model) { /* UI 更新逻辑 */ } +} +``` + +### 复杂交互控制器 + +```csharp +// 战斗控制器 +public class CombatController : IController +{ + [Inject] protected IArchitecture _architecture; + + private IUnRegisterList _unregisterList = new UnRegisterList(); + private PlayerModel _playerModel; + private CombatSystem _combatSystem; + + [PostConstruct] + public void Init() + { + // 缓存常用引用 + _playerModel = _architecture.GetModel(); + _combatSystem = _architecture.GetSystem(); + + // 注册多个事件 + _architecture.RegisterEvent(OnEnemySpawned) + .AddToUnregisterList(_unregisterList); + + _architecture.RegisterEvent(OnCombatEnded) + .AddToUnregisterList(_unregisterList); + + // 监听模型状态 + _playerModel.CombatState.Register(OnCombatStateChanged) + .AddToUnregisterList(_unregisterList); + } + + private void OnEnemySpawned(EnemySpawnedEvent e) + { + // 进入战斗状态 + _architecture.SendCommand(new EnterCombatCommand(e.Enemy)); + } + + private void OnCombatEnded(CombatEndedEvent e) + { + if (e.Victory) + { + // 查询奖励 + var rewards = _architecture.SendQuery(new CalculateRewardsQuery(e.Enemy)); + + // 发放奖励 + _architecture.SendCommand(new GiveRewardsCommand(rewards)); + } + else + { + // 处理失败 + _architecture.SendCommand(); + } + } + + private void OnCombatStateChanged(CombatState state) + { + // 根据战斗状态更新 UI + _architecture.GetSystem().UpdateCombatUI(state); + } + + public void Cleanup() + { + _unregisterList.UnRegisterAll(); + } +} +``` + +## 控制器职责 + +### ✅ 应该做的事 + +1. **处理用户输入** + - 键盘、鼠标、触摸输入 + - UI 按钮点击 + - 手势识别 + +2. **协调视图和模型** + - 监听模型变化更新视图 + - 将用户操作转换为命令 + +3. **管理界面逻辑** + - UI 元素的显示/隐藏 + - 动画播放控制 + - 视觉反馈 + +4. **事件监听** + - 注册关心的事件 + - 响应事件更新界面 + +### ❌ 不应该做的事 + +1. **不包含业务逻辑** + - 业务逻辑应该在 System 中 + - 控制器只负责调用和协调 + +2. **不直接修改模型** + - 应该通过 Command 修改模型 + - 避免直接访问 Model 的 setter + +3. **不处理复杂计算** + - 复杂计算应该在 Query 或 System 中 + - 控制器保持简洁 + +4. **不保存核心状态** + - 核心状态应该在 Model 中 + - 控制器可以保存临时 UI 状态 + +## 生命周期管理 + +### 事件注销 + +```csharp +public class MyController : IController +{ + [Inject] private IArchitecture _architecture; + + // 使用 UnRegisterList 统一管理 + private IUnRegisterList _unregisterList = new UnRegisterList(); + + public void Initialize() + { + // 所有事件注册都添加到列表 + _architecture.RegisterEvent(OnGameEvent) + .AddToUnregisterList(_unregisterList); + + _architecture.GetModel().Health.Register(OnHealthChanged) + .AddToUnregisterList(_unregisterList); + } + + public void Cleanup() + { + // 统一注销所有事件 + _unregisterList.UnRegisterAll(); + } +} +``` + +## 最佳实践 + +1. **使用依赖注入获取依赖** + - 通过构造函数注入 `IArchitecture` + - 使用 `[Inject]` 属性标记注入字段 + +2. **保持控制器轻量** + - 复杂逻辑放在 Command、Query、System 中 + - 控制器只做协调和转发 + +3. **合理使用缓存** + - 频繁使用的 Model、System 可以缓存引用 + - 平衡性能和内存占用 + +4. **统一管理事件注销** + - 使用 `IUnRegisterList` 统一管理 + - 在 `Cleanup()` 中统一注销 + +5. **命名规范** + - 控制器类名:`XxxController` + - 使用 `[Inject]` 或构造函数注入获取架构 + +## 常见模式 + +### 数据绑定模式 + +```csharp +public class ScoreController : IController +{ + [Inject] private IArchitecture _architecture; + + public void Initialize() + { + // 绑定模型数据到 UI + _architecture.GetModel() + .Score + .RegisterWithInitValue(score => UpdateDisplay(score)) + .AddToUnregisterList(_unregisterList); + } + + private void UpdateDisplay(int score) + { + // 更新分数显示 + } +} +``` + +### 状态机模式 + +```csharp +public class PlayerStateController : IController +{ + [Inject] private IArchitecture _architecture; + private Dictionary _stateHandlers; + + public void Initialize() + { + _stateHandlers = new Dictionary + { + { PlayerState.Idle, HandleIdleState }, + { PlayerState.Moving, HandleMovingState }, + { PlayerState.Attacking, HandleAttackingState } + }; + + _architecture.GetModel() + .State + .Register(OnStateChanged) + .AddToUnregisterList(_unregisterList); + } + + private void OnStateChanged(PlayerState state) + { + _stateHandlers[state]?.Invoke(); + } +} +``` + +## 与 Godot 集成 + +在 Godot 项目中,可以使用 GFramework.Godot 提供的扩展: + +```csharp +using GFramework.Godot; + +public partial class GodotPlayerController : Node, IController +{ + [Inject] private IArchitecture _architecture; + + public override void _Ready() + { + // 使用 Godot 特定的自动注销扩展 + _architecture.RegisterEvent(OnPlayerDied) + .UnRegisterWhenNodeExitTree(this); + + _architecture.GetModel() + .Health + .RegisterWithInitValue(OnHealthChanged) + .UnRegisterWhenNodeExitTree(this); + } +} +``` + +## 相关包 + +- [`architecture`](./architecture.md) - 提供架构访问能力 +- [`command`](./command.md) - 控制器发送命令执行业务逻辑 +- [`query`](./query.md) - 控制器发送查询获取数据 +- [`events`](./events.md) - 控制器注册事件监听变化 +- [`model`](./model.md) - 控制器读取模型数据 +- [`system`](./system.md) - 控制器调用系统服务 +- [`extensions`](./extensions.md) - 提供便捷的扩展方法 +- **GFramework.Godot** - Godot 特定的控制器扩展 + +--- + +**许可证**: Apache 2.0 diff --git a/docs/core/environment.md b/docs/core/environment.md new file mode 100644 index 0000000..338664c --- /dev/null +++ b/docs/core/environment.md @@ -0,0 +1,218 @@ +# Environment 包使用说明 + +## 概述 + +Environment 包定义了环境配置功能。Environment 提供了一个键值对存储系统,用于在运行时存储和获取各种环境特定的值,如配置选项、路径设置等。它允许应用程序在不同环境下灵活调整行为。 + +## 核心接口 + +### IEnvironment + +环境接口,定义了环境值的存储和获取功能。 + +**核心成员:** + +```csharp +string Name { get; } // 环境名称 +T? Get(string key) where T : class; // 根据键获取值 +bool TryGet(string key, out T value) where T : class; // 尝试获取值 +T GetRequired(string key) where T : class; // 获取必需值(不存在则抛异常) +void Register(string key, object value); // 注册键值对 +void Initialize(); // 初始化环境 +``` + +## 核心类 + +### [`EnvironmentBase`](EnvironmentBase.cs) + +环境基础抽象类,实现了 IEnvironment 接口,提供环境值的存储和获取功能。 + +**使用方式:** + +```csharp +public abstract class EnvironmentBase : ContextAwareBase, IEnvironment +{ + protected readonly Dictionary Values = new(); // 存储环境值 + + public abstract string Name { get; } // 环境名称 + + public virtual T? Get(string key) where T : class + { + return TryGet(key, out var value) ? value : null; + } + + public virtual bool TryGet(string key, out T value) where T : class + { + if (Values.TryGetValue(key, out var obj) && obj is T typed) + { + value = typed; + return true; + } + + value = null!; + return false; + } + + public virtual T GetRequired(string key) where T : class + { + if (TryGet(key, out var value)) + return value; + + throw new InvalidOperationException( + $"Environment [{Name}] missing required value: key='{key}', type='{typeof(T).Name}'"); + } + + void IEnvironment.Register(string key, object value); + protected void Register(string key, object value); // 注册键值对 + public abstract void Initialize(); // 初始化环境 +} +``` + +### [`DefaultEnvironment`](DefaultEnvironment.cs) + +默认环境实现类,继承自 EnvironmentBase。 + +**使用方式:** + +```csharp +public class DefaultEnvironment : EnvironmentBase +{ + public override string Name { get; } = "Default"; // 环境名称 + + public override void Initialize() + { + // 默认环境初始化逻辑 + } +} +``` + +## 基本使用 + +### 1. 定义自定义环境 + +```csharp +public class GameEnvironment : EnvironmentBase +{ + public override string Name { get; } = "Game"; + + public override void Initialize() + { + // 注册一些环境特定的值 + Register("GameMode", "Survival"); + Register("MaxPlayers", 4); + Register("ServerAddress", "localhost"); + } + + // 便捷方法 + public string GameMode => Get("GameMode") ?? "Default"; + public int MaxPlayers => Get("MaxPlayers") ?? 1; + public string ServerAddress => Get("ServerAddress") ?? "localhost"; +} +``` + +### 2. 在架构中使用环境 + +```csharp +public class GameArchitecture : Architecture +{ + protected override void Init() + { + // 环境已经在架构初始化过程中自动初始化 + // 但是我们可以在需要的时候获取环境值 + var gameMode = this.Context.Environment.Get("GameMode"); + + // 注册模型和系统 + RegisterModel(new PlayerModel()); + RegisterSystem(new GameplaySystem()); + } +} +``` + +### 3. 使用环境值 + +```csharp +public class NetworkSystem : AbstractSystem +{ + private string _serverAddress; + + protected override void OnInit() + { + // 从环境中获取服务器地址 + _serverAddress = this.GetContext().Environment.Get("ServerAddress") ?? "localhost"; + + // 注册事件 + this.RegisterEvent(OnConnectToServer); + } + + private void OnConnectToServer(ConnectToServerEvent e) + { + // 使用环境中的服务器地址 + Connect(_serverAddress, e.Port); + } + + private void Connect(string address, int port) + { + // 连接逻辑 + } +} +``` + +## Environment vs Configuration + +### Environment(环境) + +- **动态性**:运行时可以更改 +- **灵活性**:根据部署环境调整行为 +- **存储类型**:运行时值、连接信息等 +- **访问方式**:通过键值对访问 + +### Configuration(配置) + +- **静态性**:通常在启动时确定 +- **持久性**:保存在文件或外部源 +- **存储类型**:应用设置、参数等 +- **访问方式**:通过配置对象访问 + +## 最佳实践 + +1. **明确环境名称**:为每个环境提供有意义的名称 +2. **类型安全**:使用泛型方法确保类型安全 +3. **错误处理**:使用 GetRequired 方法获取必需值 +4. **初始化时机**:在架构初始化期间完成环境设置 +5. **避免过度使用**:仅存储环境相关的值 + +## 错误示例 + +```csharp +// ❌ 错误:获取必需值但不存在 +public class BadExampleSystem : AbstractSystem +{ + protected override void OnInit() + { + // 如果"RequiredValue"不存在,这会抛出异常 + var value = this.GetContext().Environment.GetRequired("RequiredValue"); + } +} + +// ✅ 正确:安全获取值 +public class GoodExampleSystem : AbstractSystem +{ + protected override void OnInit() + { + // 使用 TryGet 安全获取值 + if (this.GetContext().Environment.TryGet("OptionalValue", out var value)) + { + // 处理值 + } + + // 或者提供默认值 + var gameMode = this.GetContext().Environment.Get("GameMode") ?? "Default"; + } +} +``` + +## 相关包 + +- [`architecture`](./architecture.md) - 在架构中使用环境配置 +- [`rule`](./rule.md) - 环境基类继承自 ContextAwareBase +- [`ioc`](./ioc.md) - 环境值可通过IoC容器管理 \ No newline at end of file diff --git a/docs/core/events.md b/docs/core/events.md new file mode 100644 index 0000000..6e4684e --- /dev/null +++ b/docs/core/events.md @@ -0,0 +1,524 @@ +# Events 包使用说明 + +## 概述 + +Events 包提供了一套完整的事件系统,实现了观察者模式(Observer Pattern)。通过事件系统,可以实现组件间的松耦合通信,支持无参和带参事件、事件注册/注销、以及灵活的事件组合。 + +## 核心接口 + +### 1. [`IEvent`](file:///d:/Project/Rider/GFramework/GFramework.Core.Abstractions/events/IEvent.cs#L7-L11) + +基础事件接口,定义了事件注册的基本功能。 + +**核心方法:** + +```csharp +IUnRegister Register(Action onEvent); // 注册事件处理函数 +``` + +### 2. [`IUnRegister`](file:///d:/Project/Rider/GFramework/GFramework.Core.Abstractions/events/IUnRegister.cs#L6-L10) + +注销接口,用于取消事件注册。 + +**核心方法:** + +```csharp +void UnRegister(); // 执行注销操作 +``` + +### 3. [ + +`IUnRegisterList`](file:///d:/Project/Rider/GFramework/GFramework.Core.Abstractions/events/IUnRegisterList.cs#L6-L10) + +注销列表接口,用于批量管理注销对象。 + +**属性:** + +```csharp +IList UnregisterList { get; } // 获取注销列表 +``` + +### 4. [`IEventBus`](file:///d:/Project/Rider/GFramework/GFramework.Core.Abstractions/events/IEventBus.cs#L6-L22) + +事件总线接口,提供基于类型的事件发送和注册。 + +**核心方法:** + +```csharp +IUnRegister Register(Action onEvent); // 注册类型化事件 +void Send(T e); // 发送事件实例 +void Send() where T : new(); // 发送事件(自动创建实例) +``` + +## 核心类 + +### 1. [`EasyEvent`](EasyEvent.cs) + +无参事件类,支持注册、注销和触发无参事件。 + +**使用示例:** + +```csharp +// 创建事件 +var onClicked = new EasyEvent(); + +// 注册监听 +var unregister = onClicked.Register(() => +{ + GD.Print("Button clicked!"); +}); + +// 触发事件 +onClicked.Trigger(); + +// 取消注册 +unregister.UnRegister(); +``` + +### 2. [`Event`](EasyEventGeneric.cs) + +单参数泛型事件类,支持一个参数的事件。 + +**使用示例:** + +```csharp +// 创建带参数的事件 +var onScoreChanged = new Event(); + +// 注册监听 +onScoreChanged.Register(newScore => +{ + GD.Print($"Score changed to: {newScore}"); +}); + +// 触发事件并传递参数 +onScoreChanged.Trigger(100); +``` + +### 3. [`Event`](EasyEventGeneric.cs) + +双参数泛型事件类。 + +**使用示例:** + +```csharp +// 伤害事件:攻击者、伤害值 +var onDamageDealt = new Event(); + +onDamageDealt.Register((attacker, damage) => +{ + GD.Print($"{attacker} dealt {damage} damage!"); +}); + +onDamageDealt.Trigger("Player", 50); +``` + +### 4. [`EasyEvents`](EasyEvents.cs) + +全局事件管理器,提供类型安全的事件注册和获取。 + +**使用示例:** + +```csharp +// 注册全局事件类型 +EasyEvents.Register(); + +// 获取事件实例 +var gameStartEvent = EasyEvents.Get(); + +// 注册监听 +gameStartEvent.Register(() => +{ + GD.Print("Game started!"); +}); + +// 触发事件 +gameStartEvent.Trigger(); +``` + +### 5. [`EventBus`](EventBus.cs) + +类型化事件系统,支持基于类型的事件发送和注册。 + +**使用示例:** + +```csharp +// 使用全局事件系统 +var eventBus = new EventBus(); + +// 注册类型化事件 +eventBus.Register(e => +{ + GD.Print($"Player died at position: {e.Position}"); +}); + +// 发送事件(传递实例) +eventBus.Send(new PlayerDiedEvent +{ + Position = new Vector3(10, 0, 5) +}); + +// 发送事件(自动创建实例) +eventBus.Send(); +``` + +### 6. [`DefaultUnRegister`](DefaultUnRegister.cs) + +默认注销器实现,封装注销回调。 + +**使用示例:** + +```csharp +Action onUnregister = () => GD.Print("Unregistered"); +var unregister = new DefaultUnRegister(onUnregister); + +// 执行注销 +unregister.UnRegister(); +``` + +### 7. [`OrEvent`](OrEvent.cs) + +事件或运算组合器,当任意一个事件触发时触发。 + +**使用示例:** + +```csharp +var onAnyInput = new OrEvent() + .Or(onKeyPressed) + .Or(onMouseClicked) + .Or(onTouchDetected); + +// 当上述任意事件触发时,执行回调 +onAnyInput.Register(() => +{ + GD.Print("Input detected!"); +}); +``` + +### 8. [`UnRegisterList`](UnRegisterList.cs) + +批量管理注销对象的列表。 + +**使用示例:** + +```csharp +var unregisterList = new UnRegisterList(); + +// 添加到列表 +someEvent.Register(OnEvent).AddToUnregisterList(unregisterList); + +// 批量注销 +unregisterList.UnRegisterAll(); +``` + +### 9. [`ArchitectureEvents`](ArchitectureEvents.cs) + +定义了架构生命周期相关的事件。 + +**包含事件:** + +- `ArchitectureLifecycleReadyEvent` - 架构生命周期准备就绪 +- `ArchitectureDestroyingEvent` - 架构销毁中 +- `ArchitectureDestroyedEvent` - 架构已销毁 +- `ArchitectureFailedInitializationEvent` - 架构初始化失败 + +## 在架构中使用事件 + +### 定义事件类 + +```csharp +// 简单事件 +public struct GameStartedEvent { } + +// 带数据的事件 +public struct PlayerDiedEvent +{ + public Vector3 Position; + public string Cause; +} + +// 复杂事件 +public struct LevelCompletedEvent +{ + public int LevelId; + public float CompletionTime; + public int Score; + public List Achievements; +} +``` + +### Model 中发送事件 + +```csharp +public class PlayerModel : AbstractModel +{ + public BindableProperty Health { get; } = new(100); + + protected override void OnInit() + { + // 监听生命值变化 + Health.Register(newHealth => + { + if (newHealth <= 0) + { + // 发送玩家死亡事件 + this.SendEvent(new PlayerDiedEvent + { + Position = Position, + Cause = "Health depleted" + }); + } + }); + } +} +``` + +### System 中发送事件 + +```csharp +public class CombatSystem : AbstractSystem +{ + protected override void OnInit() { } + + public void DealDamage(Character attacker, Character target, int damage) + { + target.Health -= damage; + + // 发送伤害事件 + this.SendEvent(new DamageDealtEvent + { + Attacker = attacker.Name, + Target = target.Name, + Damage = damage + }); + } +} +``` + +### Controller 中注册事件 + +```csharp +public partial class GameController : Node, IController +{ + private IUnRegisterList _unregisterList = new UnRegisterList(); + + public IArchitecture GetArchitecture() => GameArchitecture.Interface; + + public override void _Ready() + { + // 注册多个事件 + this.RegisterEvent(OnGameStarted) + .AddToUnregisterList(_unregisterList); + + this.RegisterEvent(OnPlayerDied) + .AddToUnregisterList(_unregisterList); + + this.RegisterEvent(OnLevelCompleted) + .AddToUnregisterList(_unregisterList); + } + + private void OnGameStarted(GameStartedEvent e) + { + GD.Print("Game started!"); + } + + private void OnPlayerDied(PlayerDiedEvent e) + { + GD.Print($"Player died at {e.Position}: {e.Cause}"); + ShowGameOverScreen(); + } + + private void OnLevelCompleted(LevelCompletedEvent e) + { + GD.Print($"Level {e.LevelId} completed! Score: {e.Score}"); + ShowVictoryScreen(e); + } + + public override void _ExitTree() + { + _unregisterList.UnRegisterAll(); + } +} +``` + +## 高级用法 + +### 1. 事件链式组合 + +```csharp +// 使用 Or 组合多个事件 +var onAnyDamage = new OrEvent() + .Or(onPhysicalDamage) + .Or(onMagicDamage) + .Or(onPoisonDamage); + +onAnyDamage.Register(() => +{ + PlayDamageSound(); +}); +``` + +### 2. 事件过滤 + +```csharp +// 只处理高伤害事件 +this.RegisterEvent(e => +{ + if (e.Damage >= 50) + { + ShowCriticalHitEffect(); + } +}); +``` + +### 3. 事件转发 + +```csharp +public class EventBridge : AbstractSystem +{ + protected override void OnInit() + { + // 将内部事件转发为公共事件 + this.RegisterEvent(e => + { + this.SendEvent(new PublicPlayerDiedEvent + { + PlayerId = e.Id, + Timestamp = DateTime.Now + }); + }); + } +} +``` + +### 4. 临时事件监听 + +```csharp +public class TutorialController : Node, IController +{ + public override void _Ready() + { + // 只监听一次 + IUnRegister unregister = null; + unregister = this.RegisterEvent(e => + { + ShowTutorialComplete(); + unregister?.UnRegister(); // 立即注销 + }); + } +} +``` + +### 5. 条件事件 + +```csharp +public class AchievementSystem : AbstractSystem +{ + private int _killCount = 0; + + protected override void OnInit() + { + this.RegisterEvent(e => + { + _killCount++; + + // 条件满足时发送成就事件 + if (_killCount >= 100) + { + this.SendEvent(new AchievementUnlockedEvent + { + AchievementId = "kill_100_enemies" + }); + } + }); + } +} +``` + +## 生命周期管理 + +### 使用 UnRegisterList + +```csharp +public class MyController : Node, IController +{ + // 统一管理所有注销对象 + private IUnRegisterList _unregisterList = new UnRegisterList(); + + public override void _Ready() + { + // 所有注册都添加到列表 + this.RegisterEvent(OnEvent1) + .AddToUnregisterList(_unregisterList); + + this.RegisterEvent(OnEvent2) + .AddToUnregisterList(_unregisterList); + } + + public override void _ExitTree() + { + // 一次性注销所有 + _unregisterList.UnRegisterAll(); + } +} +``` + +### 使用 Godot 节点生命周期 + +```csharp +public override void _Ready() +{ + // 当节点退出场景树时自动注销 + this.RegisterEvent(OnGameEvent) + .UnRegisterWhenNodeExitTree(this); +} +``` + +## 最佳实践 + +1. **事件命名规范** + - 使用过去式:`PlayerDiedEvent`、`LevelCompletedEvent` + - 使用 `Event` 后缀:便于识别 + - 使用结构体:减少内存分配 + +2. **事件数据设计** + - 只包含必要信息 + - 使用值类型(struct)提高性能 + - 避免传递可变引用 + +3. **避免事件循环** + - 事件处理器中谨慎发送新事件 + - 使用命令打破循环依赖 + +4. **合理使用事件** + - 用于通知状态变化 + - 用于跨模块通信 + - 不用于返回数据(使用 Query) + +5. **注销管理** + - 始终注销事件监听 + - 使用 `IUnRegisterList` 批量管理 + - 利用 Godot 节点生命周期 + +6. **性能考虑** + - 避免频繁触发的事件(如每帧) + - 事件处理器保持轻量 + - 使用结构体事件减少 GC + +## 事件 vs 其他通信方式 + +| 方式 | 适用场景 | 优点 | 缺点 | +|----------------------|--------------|-----------|---------| +| **Event** | 状态变化通知、跨模块通信 | 松耦合、一对多 | 难以追踪调用链 | +| **Command** | 执行操作、修改状态 | 封装逻辑、可撤销 | 单向通信 | +| **Query** | 查询数据 | 职责清晰、有返回值 | 同步调用 | +| **BindableProperty** | UI 数据绑定 | 自动更新、响应式 | 仅限单一属性 | + +## 相关包 + +- [`architecture`](./architecture.md) - 提供全局事件系统 +- [`extensions`](./extensions.md) - 提供事件扩展方法 +- [`property`](./property.md) - 可绑定属性基于事件实现 +- [`controller`](./controller.md) - 控制器监听事件 +- [`model`](./model.md) - 模型发送事件 +- [`system`](./system.md) - 系统发送和监听事件 \ No newline at end of file diff --git a/docs/core/extensions.md b/docs/core/extensions.md new file mode 100644 index 0000000..5e3df53 --- /dev/null +++ b/docs/core/extensions.md @@ -0,0 +1,552 @@ +# Extensions 包使用说明 + +## 概述 + +Extensions 包提供了一系列扩展方法,简化了框架各个接口的使用。通过扩展方法,可以用更简洁的语法访问框架功能,提高代码可读性和开发效率。 + +## 扩展方法类别 + +### 1. ContextAware 扩展 ([`ContextAwareExtensions.cs`](ContextAwareExtensions.cs)) + +为 [`IContextAware`](../../GFramework.Core.Abstractions/rule/IContextAware.cs) +提供扩展方法,允许直接从实现了 [IContextAware](file:///d:/Project/Rider/GFramework/GFramework.Core.Abstractions/rule/IContextAware.cs) +的对象获取架构组件。 + +#### GetSystem 扩展方法 + +```csharp +public static TSystem? GetSystem(this IContextAware contextAware) + where TSystem : class, ISystem +``` + +**使用示例:** + +```csharp +// 在实现了 IContextAware 的类中使用 +public class PlayerController : IController +{ + public void UpdateUI() + { + // 直接通过 this 调用 + var playerSystem = this.GetSystem(); + var inventorySystem = this.GetSystem(); + } +} +``` + +#### GetModel 扩展方法 + +```csharp +public static TModel? GetModel(this IContextAware contextAware) + where TModel : class, IModel +``` + +**使用示例:** + +```csharp +public class PlayerController : IController +{ + public void UpdateStats() + { + // 获取模型 + var playerModel = this.GetModel(); + var inventoryModel = this.GetModel(); + + // 使用模型数据 + playerModel.Health += 10; + } +} +``` + +#### GetUtility 扩展方法 + +```csharp +public static TUtility? GetUtility(this IContextAware contextAware) + where TUtility : class, IUtility +``` + +**使用示例:** + +```csharp +public class GameModel : AbstractModel, IContextAware +{ + protected override void OnInit() + { + // 获取工具 + var timeUtility = this.GetUtility(); + var storageUtility = this.GetUtility(); + } +} +``` + +#### SendCommand 扩展方法 + +```csharp +// 发送无返回值的命令 +public static void SendCommand(this IContextAware contextAware, ICommand command) + +// 发送带返回值的命令 +public static TResult SendCommand(this IContextAware contextAware, ICommand command) +``` + +**使用示例:** + +```csharp +public class GameController : IController +{ + public void OnStartButtonClicked() + { + // 发送命令实例 + this.SendCommand(new StartGameCommand()); + + // 发送带返回值的命令 + var result = this.SendCommand(new CalculateScoreCommand()); + } +} +``` + +#### SendQuery 扩展方法 + +```csharp +public static TResult SendQuery(this IContextAware contextAware, IQuery query) +``` + +**使用示例:** + +```csharp +public class InventoryController : IController +{ + public void ShowInventory() + { + // 发送查询获取数据 + var items = this.SendQuery(new GetInventoryItemsQuery()); + var gold = this.SendQuery(new GetPlayerGoldQuery()); + + UpdateInventoryUI(items, gold); + } +} +``` + +#### SendEvent 扩展方法 + +```csharp +// 发送无参事件 +public static void SendEvent(this IContextAware contextAware) where T : new() + +// 发送事件实例 +public static void SendEvent(this IContextAware contextAware, T e) where T : class +``` + +**使用示例:** + +```csharp +public class PlayerModel : AbstractModel, IContextAware +{ + public void TakeDamage(int damage) + { + Health -= damage; + + if (Health <= 0) + { + // 方式1:发送无参事件 + this.SendEvent(); + + // 方式2:发送带数据的事件 + this.SendEvent(new PlayerDiedEvent + { + Position = Position, + Cause = "Damage" + }); + } + } +} +``` + +#### RegisterEvent 扩展方法 + +```csharp +public static IUnRegister RegisterEvent(this IContextAware contextAware, Action handler) +``` + +**使用示例:** + +```csharp +public class GameController : IController +{ + private IUnRegisterList _unregisterList = new UnRegisterList(); + + public void Initialize() + { + // 注册事件监听 + this.RegisterEvent(OnGameStarted) + .AddToUnregisterList(_unregisterList); + + this.RegisterEvent(OnPlayerLevelUp) + .AddToUnregisterList(_unregisterList); + } + + private void OnGameStarted(GameStartedEvent e) { } + private void OnPlayerLevelUp(PlayerLevelUpEvent e) { } +} +``` + +#### UnRegisterEvent 扩展方法 + +```csharp +public static void UnRegisterEvent(this IContextAware contextAware, Action onEvent) +``` + +### GetEnvironment 扩展方法 + +```csharp +public static T? GetEnvironment(this IContextAware contextAware) where T : class +public static IEnvironment GetEnvironment(this IContextAware contextAware) +``` + +### 2. Object 扩展 ([`ObjectExtensions.cs`](ObjectExtensions.cs)) + +提供基于运行时类型判断的对象扩展方法,用于简化类型分支、链式调用和架构分派逻辑。 + +#### IfType 扩展方法 + +```csharp +// 最简单的类型判断 +public static bool IfType(this object obj, Action action) + +// 带条件的类型判断 +public static bool IfType( + this object obj, + Func predicate, + Action action +) + +// 条件判断,带不匹配时的处理 +public static void IfType( + this object obj, + Action whenMatch, + Action? whenNotMatch = null +) +``` + +**使用示例:** + +```csharp +object obj = new MyRule(); + +// 简单类型判断 +bool executed = obj.IfType(rule => +{ + rule.Initialize(); +}); + +// 带条件的类型判断 +obj.IfType( + r => r.Enabled, // 条件 + r => r.Execute() // 执行动作 +); + +// 带不匹配处理的类型判断 +obj.IfType( + rule => rule.Execute(), + other => Logger.Warn($"Unsupported type: {other.GetType()}") +); +``` + +#### IfType 扩展方法 + +```csharp +public static TResult? IfType( + this object obj, + Func func +) +``` + +**使用示例:** + +```csharp +object obj = new MyRule { Name = "TestRule" }; + +string? name = obj.IfType(r => r.Name); +``` + +#### As 和 Do 扩展方法 + +```csharp +// 安全类型转换 +public static T? As(this object obj) where T : class + +// 流式调用 +public static T Do(this T obj, Action action) +``` + +**使用示例:** + +```csharp +// 安全类型转换 +obj.As() + ?.Execute(); + +// 流式调用 +obj.As() + ?.Do(r => r.Initialize()) + ?.Do(r => r.Execute()); + +// 组合使用 +obj.As() + ?.Do(rule => + { + if (rule.Enabled) + rule.Execute(); + }); +``` + +#### SwitchType 扩展方法 + +```csharp +public static void SwitchType( + this object obj, + params (Type type, Action action)[] handlers +) +``` + +**使用示例:** + +```csharp +obj.SwitchType( + (typeof(IRule), o => HandleRule((IRule)o)), + (typeof(ISystem), o => HandleSystem((ISystem)o)), + (typeof(IModel), o => HandleModel((IModel)o)) +); +``` + +### 3. OrEvent 扩展 ([`OrEventExtensions.cs`](OrEventExtensions.cs)) + +为 [`IEvent`](../../GFramework.Core.Abstractions/events/IEvent.cs) 提供事件组合功能。 + +#### OrEventExtensions + +```csharp +public static OrEvent Or(this IEvent self, IEvent e) +``` + +**使用示例:** + +```csharp +// 组合多个事件:当任意一个触发时执行 +var onAnyInput = onKeyPressed.Or(onMouseClicked).Or(onTouchDetected); + +onAnyInput.Register(() => +{ + GD.Print("Any input detected!"); +}); + +// 链式组合 +var onAnyDamage = onPhysicalDamage + .Or(onMagicDamage) + .Or(onPoisonDamage); +``` + +### 4. UnRegisterList 扩展 ([`UnRegisterListExtension.cs`](UnRegisterListExtension.cs)) + +为 [`IUnRegister`](../events/IUnRegister.cs) 和 [`IUnRegisterList`](../events/IUnRegisterList.cs) 提供批量管理功能。 + +#### UnRegisterListExtension + +```csharp +// 添加到注销列表 +public static void AddToUnregisterList(this IUnRegister self, + IUnRegisterList unRegisterList) + +// 批量注销 +public static void UnRegisterAll(this IUnRegisterList self) +``` + +**使用示例:** + +```csharp +public class ComplexController : IController +{ + private IUnRegisterList _unregisterList = new UnRegisterList(); + + public void Initialize() + { + // 所有注册都添加到列表中 + this.RegisterEvent(OnEvent1) + .AddToUnregisterList(_unregisterList); + + this.RegisterEvent(OnEvent2) + .AddToUnregisterList(_unregisterList); + + this.GetModel().Property1.Register(OnProperty1Changed) + .AddToUnregisterList(_unregisterList); + + this.GetModel().Property2.Register(OnProperty2Changed) + .AddToUnregisterList(_unregisterList); + } + + public void Cleanup() + { + // 一次性注销所有 + _unregisterList.UnRegisterAll(); + } +} +``` + +## 完整使用示例 + +### Controller 示例 + +```csharp +public partial class GameplayController : IController +{ + private IUnRegisterList _unregisterList = new UnRegisterList(); + + public void Initialize() + { + // 使用扩展方法获取 Model + var playerModel = this.GetModel(); + var gameModel = this.GetModel(); + + // 使用扩展方法注册事件 + this.RegisterEvent(OnGameStarted) + .AddToUnregisterList(_unregisterList); + + // 监听可绑定属性 + playerModel.Health.Register(OnHealthChanged) + .AddToUnregisterList(_unregisterList); + } + + public void Process(double delta) + { + // 发送命令 + this.SendCommand(new AttackCommand(targetId: 1)); + + // 发送查询 + var hasPotion = this.SendQuery(new HasItemQuery("health_potion")); + if (hasPotion) + { + this.SendCommand(); + } + } + + private void OnGameStarted(GameStartedEvent e) + { + Console.WriteLine("Game started!"); + } + + private void OnHealthChanged(int health) + { + UpdateHealthBar(health); + } + + public void Cleanup() + { + _unregisterList.UnRegisterAll(); + } +} +``` + +### Command 示例 + +```csharp +public class ComplexGameCommand : AbstractCommand +{ + protected override void OnExecute() + { + // 获取多个组件 + var playerModel = this.GetModel(); + var gameSystem = this.GetSystem(); + var timeUtility = this.GetUtility(); + + // 执行业务逻辑 + var currentTime = timeUtility.GetCurrentTime(); + gameSystem.ProcessGameLogic(playerModel, currentTime); + + // 发送事件通知 + this.SendEvent(new GameStateChangedEvent()); + + // 可以发送其他命令(谨慎使用) + this.SendCommand(); + } +} +``` + +### System 示例 + +```csharp +public class AchievementSystem : AbstractSystem +{ + protected override void OnInit() + { + // 注册事件监听 + this.RegisterEvent(OnEnemyKilled); + this.RegisterEvent(OnLevelCompleted); + } + + private void OnEnemyKilled(EnemyKilledEvent e) + { + // 获取模型 + var playerModel = this.GetModel(); + playerModel.EnemyKillCount++; + + // 检查成就 + if (playerModel.EnemyKillCount >= 100) + { + // 发送成就解锁事件 + this.SendEvent(new AchievementUnlockedEvent + { + AchievementId = "kill_100_enemies" + }); + } + } + + private void OnLevelCompleted(LevelCompletedEvent e) + { + // 发送查询 + var completionTime = this.SendQuery(new GetLevelTimeQuery(e.LevelId)); + + if (completionTime < 60) + { + this.SendEvent(new AchievementUnlockedEvent + { + AchievementId = "speed_runner" + }); + } + } +} +``` + +## 扩展方法的优势 + +1. **简洁的语法** + :不需要显式调用 [GetContext()](file:///d:/Project/Rider/GFramework/GFramework.Core.Abstractions/rule/IContextAware.cs#L13-L15) +2. **类型安全**:编译时检查类型 +3. **可读性高**:代码意图更清晰 +4. **智能提示**:IDE 可以提供完整的自动补全 +5. **链式调用**:支持流式编程风格 + +## 注意事项 + +1. **确保引用命名空间:** + ```csharp + using GFramework.Core.extensions; + ``` + +2. **理解扩展方法本质:** + - 扩展方法是静态方法的语法糖 + - 不会改变原始类型的结构 + - 仅在编译时解析 + +3. **性能考虑:** + - 扩展方法本身无性能开销 + - 实际调用的是底层方法 + +## 相关包 + +- [`architecture`](./architecture.md) - 扩展方法最终调用架构方法 +- [`command`](./command.md) - 命令发送扩展 +- [`query`](./query.md) - 查询发送扩展 +- [`events`](./events.md) - 事件注册和 Or 组合扩展 +- [`model`](./model.md) - 模型获取扩展 +- [`system`](./system.md) - 系统获取扩展 +- [`utility`](./utility.md) - 工具获取扩展 \ No newline at end of file diff --git a/docs/core/ioc.md b/docs/core/ioc.md new file mode 100644 index 0000000..9b15b7d --- /dev/null +++ b/docs/core/ioc.md @@ -0,0 +1,689 @@ +# IoC 包使用说明 + +## 概述 + +IoC(Inversion of Control,控制反转)包提供了一个轻量级的依赖注入容器,用于管理框架中各种组件的注册和获取。通过 IoC +容器,可以实现组件间的解耦,便于测试和维护。 + +## 核心类 + +### [`IocContainer`](IocContainer.cs) + +IoC 容器类,负责管理对象的注册和获取。 + +**主要功能:** + +- 注册实例到容器 +- 从容器中获取实例 +- 类型安全的依赖管理 +- 线程安全操作 +- 容器冻结保护 + +## 核心方法 + +### 1. Register 和 Register(Type, object) + +注册一个实例到容器中。 + +```csharp +public void Register(T instance) +public void Register(Type type, object instance) +``` + +**参数:** + +- `instance`: 要注册的实例对象 +- `type`: 要注册的类型(重载方法) + +**使用示例:** + +```csharp +var container = new IocContainer(); + +// 注册各种类型的实例 +container.Register(new PlayerModel()); +container.Register(new GameSystem()); +container.Register(new StorageUtility()); +``` + +### 2. RegisterSingleton + +注册单例实例到容器中。一个类型只允许一个实例。 + +```csharp +public void RegisterSingleton(T instance) +``` + +**参数:** + +- `instance`: 要注册的单例实例 + +**使用示例:** + +```csharp +var container = new IocContainer(); + +// 注册单例 +container.RegisterSingleton(new PlayerModel()); +``` + +### 3. RegisterPlurality + +注册多个实例,将实例注册到其实现的所有接口和具体类型上。 + +```csharp +public void RegisterPlurality(object instance) +``` + +**参数:** + +- `instance`: 要注册的实例 + +### 4. RegisterSystem + +注册系统实例,将其绑定到其所有实现的接口上。 + +```csharp +public void RegisterSystem(ISystem system) +``` + +**参数:** + +- `system`: 系统实例对象 + +### 5. Get 和 GetAll + +从容器中获取指定类型的实例。 + +```csharp +public T? Get() where T : class +public IReadOnlyList GetAll() where T : class +``` + +**返回值:** + +- `Get`: 返回指定类型的实例,如果未找到则返回 `null` +- `GetAll`: 返回指定类型的所有实例列表,如果未找到则返回空数组 + +**使用示例:** + +```csharp +// 获取已注册的实例 +var playerModel = container.Get(); +var gameSystems = container.GetAll(); + +// 如果类型未注册,Get 返回 null,GetAll 返回空数组 +var unknownService = container.Get(); // null +``` + +### 6. GetRequired + +获取指定类型的必需实例,如果没有注册或注册了多个实例会抛出异常。 + +```csharp +public T GetRequired() where T : class +``` + +**返回值:** + +- 返回找到的唯一实例 + +### 7. GetAllSorted + +获取并排序(系统调度专用)。 + +```csharp +public IReadOnlyList GetAllSorted(Comparison comparison) where T : class +``` + +**参数:** + +- `comparison`: 比较器委托,定义排序规则 + +**返回值:** + +- 按指定方式排序后的实例列表 + +## 在框架中的使用 + +### Architecture 中的应用 + +IoC 容器是 [`Architecture`](../architecture/Architecture.cs) 类的核心组件,用于管理所有的 System、Model 和 Utility。 + +```csharp +public abstract class Architecture : IArchitecture +{ + // 内置 IoC 容器 + private readonly IocContainer _mContainer = new(); + + // 注册系统 + public void RegisterSystem(TSystem system) where TSystem : ISystem + { + system.SetArchitecture(this); + _mContainer.Register(system); // 注册到容器 + // ... + } + + // 获取系统 + public TSystem GetSystem() where TSystem : class, ISystem + => _mContainer.Get(); // 从容器获取 + + // Model 和 Utility 同理 +} +``` + +### 注册组件到容器 + +```csharp +public class GameArchitecture : Architecture +{ + protected override void Init() + { + // 这些方法内部都使用 IoC 容器 + + // 注册 Model(存储游戏数据) + RegisterModel(new PlayerModel()); + RegisterModel(new InventoryModel()); + + // 注册 System(业务逻辑) + RegisterSystem(new GameplaySystem()); + RegisterSystem(new SaveSystem()); + + // 注册 Utility(工具类) + RegisterUtility(new TimeUtility()); + RegisterUtility(new StorageUtility()); + } +} +``` + +### 从容器获取组件 + +```csharp +// 通过扩展方法间接使用 IoC 容器 +public class PlayerController : IController +{ + public void Start() + { + // GetModel 内部调用 Architecture.GetModel + // Architecture.GetModel 内部调用 IocContainer.Get + var playerModel = this.GetModel(); + + var gameplaySystem = this.GetSystem(); + var timeUtility = this.GetUtility(); + } +} +``` + +## 工作原理 + +### 内部实现 + +```csharp +public class IocContainer +{ + // 使用字典存储类型到实例集合的映射 + private readonly Dictionary> _typeIndex = new(); + private readonly HashSet _objects = []; + private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.NoRecursion); + private volatile bool _frozen = false; + + public void Register(T instance) + { + // 获取写锁以确保线程安全 + _lock.EnterWriteLock(); + try + { + RegisterInternal(typeof(T), instance!); + } + finally + { + _lock.ExitWriteLock(); + } + } + + public T? Get() where T : class + { + _lock.EnterReadLock(); + try + { + if (_typeIndex.TryGetValue(typeof(T), out var set) && set.Count > 0) + { + var result = set.First() as T; + return result; + } + + return null; + } + finally + { + _lock.ExitReadLock(); + } + } + + private void RegisterInternal(Type type, object instance) + { + if (_frozen) throw new InvalidOperationException("IocContainer is frozen"); + + _objects.Add(instance); + + if (!_typeIndex.TryGetValue(type, out var set)) + { + set = []; + _typeIndex[type] = set; + } + + set.Add(instance); + } +} +``` + +### 线程安全机制 + +容器使用 [ReaderWriterLockSlim](xref:System.Threading.ReaderWriterLockSlim) 来确保线程安全操作,允许多个线程同时读取,但在写入时阻止其他线程访问。 + +### 注册流程 + +``` +用户代码 + ↓ +RegisterSystem(system) + ↓ +IocContainer.Register(system) + ↓ +加写锁 -> Dictionary[typeof(T)] 添加实例到HashSet +``` + +### 获取流程 + +``` +用户代码 + ↓ +this.GetSystem() + ↓ +Architecture.GetSystem() + ↓ +IocContainer.Get() + ↓ +加读锁 -> Dictionary.TryGetValue(typeof(T)) 获取HashSet + ↓ +返回HashSet中第一个实例或 null +``` + +## 使用示例 + +### 基础使用 + +```csharp +// 1. 创建容器 +var container = new IocContainer(); + +// 2. 注册服务 +var playerService = new PlayerService(); +container.Register(playerService); + +// 3. 获取服务 +var service = container.Get(); +service.DoSomething(); +``` + +### 接口和实现分离 + +```csharp +// 定义接口 +public interface IDataService +{ + void SaveData(string data); + string LoadData(); +} + +// 实现类 +public class LocalDataService : IDataService +{ + public void SaveData(string data) { /* 本地存储 */ } + public string LoadData() { /* 本地加载 */ return ""; } +} + +public class CloudDataService : IDataService +{ + public void SaveData(string data) { /* 云端存储 */ } + public string LoadData() { /* 云端加载 */ return ""; } +} + +// 注册(可以根据配置选择不同实现) +var container = new IocContainer(); + +#if CLOUD_SAVE +container.Register(new CloudDataService()); +#else +container.Register(new LocalDataService()); +#endif + +// 使用(不需要关心具体实现) +var dataService = container.Get(); +dataService.SaveData("game data"); +``` + +### 注册多个实现 + +```csharp +var container = new IocContainer(); + +// 注册多个相同接口的不同实现 +container.Register(new LocalDataService()); +container.Register(new CloudDataService()); + +// 获取单个实例(返回第一个) +var singleService = container.Get(); // 返回第一个注册的实例 + +// 获取所有实例 +var allServices = container.GetAll(); // 返回两个实例的列表 +``` + +## 其他实用方法 + +### `Contains()` + +检查容器中是否包含指定类型的实例。 + +```csharp +public bool Contains() where T : class +``` + +**参数:** + +- 无泛型参数 + +**返回值:** + +- 如果容器中包含指定类型的实例则返回 `true`,否则返回 `false` + +**使用示例:** + +```csharp +var container = new IocContainer(); + +// 检查服务是否已注册 +if (container.Contains()) +{ + Console.WriteLine("Player service is available"); +} + +// 根据检查结果决定是否注册 +if (!container.Contains()) +{ + container.Register(new SettingsService()); +} +``` + +**应用场景:** + +- 条件注册服务 +- 检查依赖是否可用 +- 动态功能开关 + +### `ContainsInstance(object instance)` + +判断容器中是否包含某个具体的实例对象。 + +```csharp +public bool ContainsInstance(object instance) +``` + +**参数:** + +- `instance`:待查询的实例对象 + +**返回值:** + +- 若容器中包含该实例则返回 `true`,否则返回 `false` + +**使用示例:** + +```csharp +var container = new IocContainer(); + +var service = new MyService(); +container.Register(service); + +// 检查特定实例是否在容器中 +if (container.ContainsInstance(service)) +{ + Console.WriteLine("This instance is registered in the container"); +} + +// 检查另一个实例 +var anotherService = new MyService(); +if (!container.ContainsInstance(anotherService)) +{ + Console.WriteLine("This instance is not in the container"); +} +``` + +**应用场景:** + +- 避免重复注册同一实例 +- 检查对象是否已被管理 +- 调试和日志记录 + +### `Clear()` + +清空容器中的所有实例。 + +```csharp +public void Clear() +``` + +**使用示例:** + +```csharp +var container = new IocContainer(); + +// 注册多个服务 +container.Register(new Service1()); +container.Register(new Service2()); +container.Register(new Service3()); + +// 清空容器 +container.Clear(); + +// 检查是否清空成功 +Console.WriteLine($"Contains IService1: {container.Contains()}"); // False +Console.WriteLine($"Contains IService2: {container.Contains()}"); // False +``` + +**应用场景:** + +- 重置容器状态 +- 内存清理 +- 测试环境准备 + +**注意事项:** + +- 容器冻结后也可以调用 `Clear()` 方法 +- 清空后,所有已注册的实例都将丢失 +- 不会自动清理已注册对象的其他引用 + +## 设计特点 + +### 1. 简单轻量 + +- 支持多种注册方式:普通注册、单例注册、多实例注册 +- 基于字典和哈希集实现,性能高效 +- 无复杂的依赖解析逻辑 + +### 2. 手动注册 + +- 需要显式注册每个组件 +- 不支持自动依赖注入 +- 完全可控的组件生命周期 + +### 3. 多实例支持 + +- 每个类型可以注册多个实例 +- 提供 [GetAll](file:///d:/Project/Rider/GFramework/GFramework.Core/ioc/IocContainer.cs#L244-L261) 方法获取所有实例 +- 提供 [Get](file:///d:/Project/Rider/GFramework/GFramework.Core/ioc/IocContainer.cs#L208-L225) 方法获取单个实例 + +### 4. 类型安全 + +- 基于泛型,编译时类型检查 +- 避免字符串键导致的错误 +- IDE 友好,支持自动补全 + +### 5. 线程安全 + +- 使用读写锁确保多线程环境下的安全操作 +- 读操作可以并发执行 +- 写操作独占锁,防止并发修改冲突 + +### 6. 容器冻结 + +- 提供 [Freeze](file:///d:/Project/Rider/GFramework/GFramework.Core/ioc/IocContainer.cs#L359-L372) 方法,防止进一步修改容器内容 +- 防止在初始化后意外修改注册内容 + +## 与其他 IoC 容器的区别 + +### 本框架的 IocContainer + +```csharp +// 简单直接 +var container = new IocContainer(); +container.Register(new MyService()); +var service = container.Get(); +``` + +**特点:** + +- ✅ 简单易用 +- ✅ 性能高 +- ✅ 线程安全 +- ✅ 支持多实例 +- ❌ 不支持构造函数注入 +- ❌ 不支持自动解析依赖 +- ❌ 不支持生命周期管理(Transient/Scoped/Singleton) + +### 完整的 IoC 框架(如 Autofac、Zenject) + +```csharp +// 复杂但功能强大 +var builder = new ContainerBuilder(); +builder.RegisterType().As().SingleInstance(); +builder.RegisterType().WithParameter("config", config); +var container = builder.Build(); + +// 自动解析依赖 +var controller = container.Resolve(); +``` + +**特点:** + +- ✅ 自动依赖注入 +- ✅ 生命周期管理 +- ✅ 复杂场景支持 +- ❌ 学习成本高 +- ❌ 性能开销大 + +## 最佳实践 + +### 1. 在架构初始化时注册 + +```csharp +public class GameArchitecture : Architecture +{ + protected override void Init() + { + // 按顺序注册组件 + // 1. 工具类(无依赖) + RegisterUtility(new TimeUtility()); + RegisterUtility(new StorageUtility()); + + // 2. 模型(可能依赖工具) + RegisterModel(new PlayerModel()); + RegisterModel(new GameModel()); + + // 3. 系统(可能依赖模型和工具) + RegisterSystem(new GameplaySystem()); + RegisterSystem(new SaveSystem()); + } +} +``` + +### 2. 使用接口类型注册 + +```csharp +// ❌ 不推荐:直接使用实现类 +RegisterSystem(new ConcreteSystem()); +var system = GetSystem(); + +// ✅ 推荐:使用接口 +RegisterSystem(new ConcreteSystem()); +var system = GetSystem(); +``` + +### 3. 避免运行时频繁注册 + +```csharp +// ❌ 不好:游戏运行时频繁注册 +void Update() +{ + RegisterService(new TempService()); // 每帧创建 +} + +// ✅ 好:在初始化时一次性注册 +protected override void Init() +{ + RegisterService(new PersistentService()); +} +``` + +### 4. 检查 null 返回值 + +```csharp +// 获取可能不存在的服务 +var service = container.Get(); +if (service != null) +{ + service.DoSomething(); +} +else +{ + GD.Print("Service not registered!"); +} +``` + +### 5. 合理使用容器冻结 + +```csharp +// 在架构初始化完成后冻结容器,防止意外修改 +protected override void OnInit() +{ + // 注册所有组件 + RegisterModel(new PlayerModel()); + RegisterSystem(new GameSystem()); + // ... + + // 冻结容器 + Container.Freeze(); // 此后无法再注册新组件 +} +``` + +## 注意事项 + +1. **线程安全操作**:容器内部使用读写锁确保线程安全,无需额外同步 + +2. **容器冻结**:一旦调用 [Freeze](file:///d:/Project/Rider/GFramework/GFramework.Core/ioc/IocContainer.cs#L359-L372) + 方法,将不能再注册新实例 + +3. **单例注册限制 + **:[RegisterSingleton](file:///d:/Project/Rider/GFramework/GFramework.Core/ioc/IocContainer.cs#L84-L106) + 方法确保一个类型只能有一个实例,重复注册会抛出异常 + +4. **内存管理**:容器持有的实例不会自动释放,需要注意内存泄漏问题 + +5. **注册顺序**:组件的依赖关系需要手动保证,先注册被依赖的组件 + +## 相关包 + +- [`architecture`](./architecture.md) - 使用 IoC 容器管理所有组件 +- [`model`](./model.md) - Model 通过 IoC 容器注册和获取 +- [`system`](./system.md) - System 通过 IoC 容器注册和获取 +- [`utility`](./utility.md) - Utility 通过 IoC 容器注册和获取 \ No newline at end of file diff --git a/docs/core/logging.md b/docs/core/logging.md new file mode 100644 index 0000000..4a6ecdf --- /dev/null +++ b/docs/core/logging.md @@ -0,0 +1,364 @@ +# Logging 包使用说明 + +## 概述 + +Logging 包提供了灵活的日志系统,支持多级别日志记录。默认日志级别为 `Info`,确保框架的关键操作都能被记录下来。 + +## 核心接口 + +### [ILogger](ILogger.cs) + +日志记录器接口,定义了日志记录的基本功能。 + +**核心方法:** + +```csharp +// 日志级别检查 +bool IsTraceEnabled(); +bool IsDebugEnabled(); +bool IsInfoEnabled(); +bool IsWarnEnabled(); +bool IsErrorEnabled(); +bool IsFatalEnabled(); + +// 记录日志 +void Trace(string msg); +void Trace(string format, object arg); +void Trace(string format, object arg1, object arg2); +void Trace(string format, params object[] arguments); +void Trace(string msg, Exception t); + +void Debug(string msg); +void Debug(string format, object arg); +void Debug(string format, object arg1, object arg2); +void Debug(string format, params object[] arguments); +void Debug(string msg, Exception t); + +void Info(string msg); +void Info(string format, object arg); +void Info(string format, object arg1, object arg2); +void Info(string format, params object[] arguments); +void Info(string msg, Exception t); + +void Warn(string msg); +void Warn(string format, object arg); +void Warn(string format, object arg1, object arg2); +void Warn(string format, params object[] arguments); +void Warn(string msg, Exception t); + +void Error(string msg); +void Error(string format, object arg); +void Error(string format, object arg1, object arg2); +void Error(string format, params object[] arguments); +void Error(string msg, Exception t); + +void Fatal(string msg); +void Fatal(string format, object arg); +void Fatal(string format, object arg1, object arg2); +void Fatal(string format, params object[] arguments); +void Fatal(string msg, Exception t); + +// 获取日志器名称 +string Name(); +``` + +### [ILoggerFactory](ILoggerFactory.cs) + +日志工厂接口,用于创建日志记录器实例。 + +**核心方法:** + +```csharp +ILogger GetLogger(string name, LogLevel minLevel = LogLevel.Info); +``` + +### [ILoggerFactoryProvider](ILoggerFactoryProvider.cs) + +日志工厂提供程序接口,用于获取日志工厂。 + +**核心方法:** + +```csharp +ILoggerFactory GetLoggerFactory(); +ILogger CreateLogger(string name); +``` + +### [LogLevel](LogLevel.cs) + +日志级别枚举。 + +```csharp +public enum LogLevel +{ + Trace = 0, // 最详细的跟踪信息 + Debug = 1, // 调试信息 + Info = 2, // 一般信息(默认级别) + Warning = 3, // 警告信息 + Error = 4, // 错误信息 + Fatal = 5 // 致命错误 +} +``` + +## 核心类 + +### [AbstractLogger](AbstractLogger.cs) + +抽象日志基类,封装了日志级别判断、格式化与异常处理逻辑。平台日志器只需实现 `Write` 方法即可。 + +**使用示例:** + +```csharp +public class CustomLogger : AbstractLogger +{ + public CustomLogger(string? name = null, LogLevel minLevel = LogLevel.Info) + : base(name, minLevel) + { + } + + protected override void Write(LogLevel level, string message, Exception? exception) + { + // 自定义日志输出逻辑 + var logMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{level}] {message}"; + if (exception != null) + logMessage += $"\n{exception}"; + + Console.WriteLine(logMessage); + } +} +``` + +### [ConsoleLogger](ConsoleLogger.cs) + +控制台日志记录器实现,支持彩色输出。 + +**使用示例:** + +```csharp +// 创建控制台日志记录器 +var logger = new ConsoleLogger("MyLogger", LogLevel.Debug); + +// 记录不同级别的日志 +logger.Info("应用程序启动"); +logger.Debug("调试信息"); +logger.Warn("警告信息"); +logger.Error("错误信息"); +logger.Fatal("致命错误"); +``` + +**输出格式:** + +``` +[2025-01-09 01:40:00.000] INFO [MyLogger] 应用程序启动 +[2025-01-09 01:40:01.000] DEBUG [MyLogger] 调试信息 +[2025-01-09 01:40:02.000] WARN [MyLogger] 警告信息 +``` + +**日志级别颜色:** + +- **Trace**: 深灰色 +- **Debug**: 青色 +- **Info**: 白色 +- **Warning**: 黄色 +- **Error**: 红色 +- **Fatal**: 洋红色 + +**构造函数参数:** + +- `name`:日志器名称,默认为 "ROOT" +- `minLevel`:最低日志级别,默认为 LogLevel.Info +- `writer`:TextWriter 输出流,默认为 Console.Out +- `useColors`:是否使用颜色,默认为 true(仅在输出到控制台时生效) + +### [ConsoleLoggerFactory](ConsoleLoggerFactory.cs) + +控制台日志工厂,用于创建控制台日志记录器实例。 + +**使用示例:** + +```csharp +var factory = new ConsoleLoggerFactory(); +var logger = factory.GetLogger("MyModule", LogLevel.Debug); +logger.Info("日志记录器创建成功"); +``` + +### [ConsoleLoggerFactoryProvider](ConsoleLoggerFactoryProvider.cs) + +控制台日志工厂提供程序实现。 + +**使用示例:** + +```csharp +var provider = new ConsoleLoggerFactoryProvider(); +provider.MinLevel = LogLevel.Debug; // 设置最低日志级别 +var logger = provider.CreateLogger("MyApp"); +logger.Info("应用程序启动"); +``` + +### [LoggerFactoryResolver](LoggerFactoryResolver.cs) + +日志工厂提供程序解析器,用于管理和提供日志工厂提供程序实例。 + +**使用示例:** + +```csharp +// 设置日志工厂提供程序 +LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider(); + +// 设置最小日志级别 +LoggerFactoryResolver.MinLevel = LogLevel.Debug; + +// 获取日志记录器 +var logger = LoggerFactoryResolver.Provider.CreateLogger("MyApp"); +logger.Info("应用程序启动"); +``` + +## 在架构中使用日志 + +### 1. 在 Architecture 中使用 + +```csharp +public class GameArchitecture : Architecture +{ + protected override void Init() + { + var logger = LoggerFactoryResolver.Provider.CreateLogger("GameArchitecture"); + logger.Info("游戏架构初始化开始"); + + RegisterModel(new PlayerModel()); + RegisterSystem(new GameSystem()); + + logger.Info("游戏架构初始化完成"); + } +} +``` + +### 2. 在 System 中使用 + +```csharp +public class CombatSystem : AbstractSystem +{ + protected override void OnInit() + { + var logger = LoggerFactoryResolver.Provider.CreateLogger("CombatSystem"); + logger.Info("战斗系统初始化完成"); + } + + protected override void OnDestroy() + { + var logger = LoggerFactoryResolver.Provider.CreateLogger("CombatSystem"); + logger.Info("战斗系统已销毁"); + } +} +``` + +### 3. 在 Model 中使用 + +```csharp +public class PlayerModel : AbstractModel +{ + protected override void OnInit() + { + var logger = LoggerFactoryResolver.Provider.CreateLogger("PlayerModel"); + logger.Info("玩家模型初始化完成"); + } +} +``` + +### 4. 自定义日志级别 + +```csharp +public class DebugLogger : AbstractLogger +{ + public DebugLogger() : base("Debug", LogLevel.Debug) + { + } + + protected override void Write(LogLevel level, string message, Exception? exception) + { + // 只输出调试及更高级别的日志 + if (level >= LogLevel.Debug) + { + Console.WriteLine($"[{level}] {message}"); + if (exception != null) + Console.WriteLine(exception); + } + } +} +``` + +## 日志级别说明 + +| 级别 | 说明 | 使用场景 | +|-------------|----------|-------------------| +| **Trace** | 最详细的跟踪信息 | 调试复杂的执行流程,记录函数调用等 | +| **Debug** | 调试信息 | 开发阶段,记录变量值、流程分支等 | +| **Info** | 一般信息 | 记录重要的业务流程和系统状态 | +| **Warning** | 警告信息 | 可能的问题但不中断程序执行 | +| **Error** | 错误信息 | 影响功能但不致命的问题 | +| **Fatal** | 致命错误 | 导致程序无法继续运行的严重错误 | + +## 最佳实践 + +1. **使用合适的日志级别**: + - 使用 `Info` 记录重要业务流程 + - 使用 `Debug` 记录调试信息 + - 使用 `Warning` 记录异常情况 + - 使用 `Error` 记录错误但不影响程序运行 + - 使用 `Fatal` 记录严重错误 + +2. **提供上下文信息**: + ```csharp + logger.Info($"用户登录成功: UserId={userId}, UserName={userName}"); + ``` + +3. **异常日志记录**: + ```csharp + try + { + // 业务逻辑 + } + catch (Exception ex) + { + logger.Error("数据库操作失败", ex); + } + ``` + +4. **分类使用日志**: + ```csharp + var dbLogger = LoggerFactoryResolver.Provider.CreateLogger("Database"); + var netLogger = LoggerFactoryResolver.Provider.CreateLogger("Network"); + + dbLogger.Info("查询用户数据"); + netLogger.Debug("发送HTTP请求"); + ``` + +5. **在框架组件中合理使用日志**: + ```csharp + // 在系统初始化时记录 + var logger = LoggerFactoryResolver.Provider.CreateLogger("System"); + logger.Info("系统初始化完成"); + ``` + +## 注意事项 + +1. **日志级别检查**: + - 每个日志方法都会自动检查日志级别 + - 如果当前级别低于最小级别,不会输出日志 + +2. **格式化参数**: + - 支持字符串格式化参数 + - 支持异常信息传递 + +3. **ConsoleLogger 的额外参数**: + - ConsoleLogger 现在支持自定义TextWriter输出流 + - 支持禁用颜色输出的功能(useColors参数) + +## 相关包 + +- [architecture](./architecture.md) - 架构核心,使用日志系统记录生命周期事件 +- [property](./property.md) - 可绑定属性基于事件系统实现 +- [extensions](./extensions.md) - 提供便捷的扩展方法 + +--- + +**许可证**: Apache 2.0 \ No newline at end of file diff --git a/docs/core/model.md b/docs/core/model.md new file mode 100644 index 0000000..ddf6f80 --- /dev/null +++ b/docs/core/model.md @@ -0,0 +1,184 @@ +# Model 包使用说明 + +## 概述 + +Model 包定义了数据模型层的接口和基类。Model 是 MVC 架构中的 M 层,负责管理应用程序的数据和状态。Model +层应该只包含数据和简单的数据逻辑,不包含复杂的业务逻辑。 + +## 核心接口 + +### [`IModel`](IModel.cs) + +模型接口,定义了模型的基本行为和功能。 + +**继承的能力接口:** + +- [`IContextAware`](../rule/IContextAware.cs) - 上下文感知接口 +- [`ILogAware`](../rule/ILogAware.cs) - 日志感知接口 + +**核心方法:** + +```csharp +void Init(); // 初始化模型 +void OnArchitecturePhase(ArchitecturePhase phase); // 处理架构阶段事件 +``` + +### [`ICanGetModel`](ICanGetModel.cs) + +标记接口,表示实现者可以获取模型。继承自 [`IBelongToArchitecture`](../rule/IBelongToArchitecture.cs)。 + +## 核心类 + +### [`AbstractModel`](AbstractModel.cs) + +抽象模型基类,实现IModel接口,提供模型的基础实现。该类继承自[ +`ContextAwareBase`](file:///d:/Project/Rider/GFramework/GFramework.Core/rule/ContextAwareBase.cs#L11-L37),提供了上下文感知能力。 + +**使用示例:** + +```csharp +public class PlayerModel : AbstractModel +{ + // 使用 BindableProperty 定义可监听的属性 + public BindableProperty Name { get; } = new("Player"); + public BindableProperty Level { get; } = new(1); + public BindableProperty Health { get; } = new(100); + public BindableProperty MaxHealth { get; } = new(100); + + protected override void OnInit() + { + // 监听生命值变化 + Health.Register(newHealth => + { + if (newHealth <= 0) + { + this.SendEvent(new PlayerDiedEvent()); + } + }); + } + + public override void OnArchitecturePhase(ArchitecturePhase phase) + { + switch (phase) + { + case ArchitecturePhase.Initializing: + // 架构初始化阶段的处理 + break; + case ArchitecturePhase.Ready: + // 架构就绪阶段的处理 + break; + // ... 其他阶段处理 + } + } +} +``` + +## Model 的职责 + +### ✅ 应该做的事 + +1. **存储数据和状态** +2. **提供数据访问接口** +3. **监听自身属性变化并做出响应** +4. **发送数据变化事件** +5. **处理架构生命周期事件** + +### ❌ 不应该做的事 + +1. **不包含复杂业务逻辑** - 业务逻辑应该在 System 中 +2. **不直接依赖其他 Model** - 通过 Command 协调 +3. **不包含 UI 逻辑** - UI 逻辑应该在 Controller 中 + +## 常见 Model 示例 + +### 玩家数据模型 + +```csharp +public class PlayerModel : AbstractModel +{ + public BindableProperty PlayerId { get; } = new(""); + public BindableProperty PlayerName { get; } = new("Player"); + public BindableProperty Level { get; } = new(1); + public BindableProperty Health { get; } = new(100); + public BindableProperty MaxHealth { get; } = new(100); + public BindableProperty Gold { get; } = new(0); + + public Vector3 Position { get; set; } + + protected override void OnInit() + { + Health.Register(hp => + { + if (hp <= 0) + { + this.SendEvent(new PlayerDiedEvent()); + } + }); + } + + public override void OnArchitecturePhase(ArchitecturePhase phase) + { + switch (phase) + { + case ArchitecturePhase.Ready: + // 模型准备好后的处理 + _log?.Log("PlayerModel is ready."); + break; + default: + break; + } + } +} +``` + +### 游戏状态模型 + +```csharp +public class GameModel : AbstractModel +{ + public BindableProperty State { get; } = new(GameState.Menu); + public BindableProperty Score { get; } = new(0); + public BindableProperty HighScore { get; } = new(0); + public BindableProperty CurrentLevel { get; } = new(1); + + protected override void OnInit() + { + Score.Register(newScore => + { + if (newScore > HighScore.Value) + { + HighScore.Value = newScore; + this.SendEvent(new NewHighScoreEvent { Score = newScore }); + } + }); + } + + public override void OnArchitecturePhase(ArchitecturePhase phase) + { + switch (phase) + { + case ArchitecturePhase.ShuttingDown: + // 游戏模型清理工作 + break; + default: + break; + } + } +} +``` + +## 最佳实践 + +1. **使用 BindableProperty 存储需要监听的数据** +2. **Model 之间不要直接调用,通过 Command/System 协调** +3. **复杂计算和业务逻辑放在 System 中** +4. **使用事件通知数据的重要变化** +5. **保持 Model 简单纯粹,只做数据管理** + +## 相关包 + +- [`architecture`](./architecture.md) - 提供 Model 的注册和获取 +- [`property`](./property.md) - BindableProperty 用于定义可监听属性 +- [`events`](./events.md) - Model 发送事件通知变化 +- [`utility`](./utility.md) - Model 可以使用工具类 +- [`extensions`](./extensions.md) - 提供 GetModel 等扩展方法 \ No newline at end of file diff --git a/docs/core/pool.md b/docs/core/pool.md new file mode 100644 index 0000000..f818dfa --- /dev/null +++ b/docs/core/pool.md @@ -0,0 +1,951 @@ +# 对象池系统 (Object Pool System) + +## 概述 + +GFramework 的对象池系统是一个高效的内存管理机制,旨在减少垃圾回收(GC)压力,通过复用对象实例来提高应用程序性能。该系统实现了对象的创建、获取、释放和销毁的完整生命周期管理。 + +**核心优势:** + +- **减少 GC 压力**:复用对象实例,避免频繁的内存分配和回收 +- **提高性能**:避免重复创建开销大的对象 +- **灵活管理**:支持多个独立的对象池,按需分类管理 +- **自动生命周期**:与 System 生命周期集成,自动管理对象销毁 +- **类型安全**:基于泛型实现,编译时类型检查 + +## 核心组件 + +### IPoolableObject 接口 + +定义了可池化对象的行为规范,所有需要池化的对象都必须实现此接口。 + +```csharp +public interface IPoolableObject +{ + /// + /// 当对象从池中获取时调用,用于初始化或重置对象状态 + /// + void OnAcquire(); + + /// + /// 当对象被放回池中时调用,用于清理对象状态 + /// + void OnRelease(); + + /// + /// 当对象池被销毁时调用,用于执行最终清理 + /// + void OnPoolDestroy(); +} +``` + +**生命周期:** + +``` +创建 → Acquire(从池取出)→ 使用 → Release(放回池)→ 可再次 Acquire + ↓ + Pool Destroy → OnPoolDestroy → 销毁 +``` + +### IObjectPoolSystem 接口 + +定义了对象池系统的基本操作接口。 + +```csharp +public interface IObjectPoolSystem + where TObject : IPoolableObject + where TKey : notnull +{ + /// + /// 从指定键的对象池中获取一个对象 + /// + /// 对象池的键值 + /// 获取到的对象实例 + TObject Acquire(TKey key); + + /// + /// 将对象释放回指定键的对象池中 + /// + /// 对象池的键值 + /// 需要释放的对象 + void Release(TKey key, TObject obj); + + /// + /// 清空所有对象池,销毁所有池中的对象 + /// + void Clear(); +} +``` + +### AbstractObjectPoolSystem 抽象类 + +实现了 `IObjectPoolSystem` 接口的具体逻辑,提供了对象池管理的完整实现。 + +**核心特性:** + +- 使用字典存储多个对象池,以键区分不同的对象池 +- 使用栈(Stack)存储池中的对象,实现 LIFO(后进先出)管理 +- 提供获取和释放对象的方法 +- 通过抽象方法 `Create` 让子类决定如何创建对象 +- 在系统销毁时自动清理所有对象池 + +**内部实现:** + +```csharp +public abstract class AbstractObjectPoolSystem + : AbstractSystem, IObjectPoolSystem + where TObject : IPoolableObject + where TKey : notnull +{ + // 存储对象池的字典,键为池标识,值为对应类型的对象栈 + protected readonly Dictionary> Pools = new(); + + // 获取对象 + public TObject Acquire(TKey key) + { + if (!Pools.TryGetValue(key, out var pool)) + { + pool = new Stack(); + Pools[key] = pool; + } + + var obj = pool.Count > 0 + ? pool.Pop() // 从池中取出 + : Create(key); // 创建新对象 + + obj.OnAcquire(); // 调用对象的获取钩子 + return obj; + } + + // 释放对象 + public void Release(TKey key, TObject obj) + { + obj.OnRelease(); // 调用对象的释放钩子 + + if (!Pools.TryGetValue(key, out var pool)) + { + pool = new Stack(); + Pools[key] = pool; + } + + pool.Push(obj); // 放回池中 + } + + // 清空所有对象池 + public void Clear() + { + foreach (var obj in Pools.Values.SelectMany(pool => pool)) + { + obj.OnPoolDestroy(); // 调用对象的销毁钩子 + } + + Pools.Clear(); + } + + // 子类实现:创建新对象 + protected abstract TObject Create(TKey key); + + // 系统销毁时自动清空对象池 + protected override void OnDestroy() + { + Clear(); + } +} +``` + +## 基本使用 + +### 1. 定义池化对象 + +首先,创建一个实现 `IPoolableObject` 接口的类。 + +```csharp +public class Bullet : IPoolableObject +{ + public int Damage { get; private set; } + public float Speed { get; private set; } + public Vector3 Position { get; private set; } + public Vector3 Direction { get; private set; } + public bool IsActive { get; private set; } + + public void OnAcquire() + { + // 从池中获取时调用,初始化对象状态 + IsActive = true; + } + + public void OnRelease() + { + // 放回池中时调用,清理对象状态 + IsActive = false; + Damage = 0; + Speed = 0; + Position = Vector3.Zero; + Direction = Vector3.Zero; + } + + public void OnPoolDestroy() + { + // 对象池销毁时调用,执行最终清理 + // 可以在这里释放非托管资源 + } + + // 设置子弹属性的方法 + public void Setup(int damage, float speed, Vector3 position, Vector3 direction) + { + Damage = damage; + Speed = speed; + Position = position; + Direction = direction; + } + + // 更新子弹逻辑 + public void Update(float deltaTime) + { + Position += Direction * Speed * deltaTime; + } +} +``` + +### 2. 实现对象池系统 + +继承 `AbstractObjectPoolSystem` 并实现 `Create` 方法。 + +```csharp +public class BulletPoolSystem : AbstractObjectPoolSystem +{ + protected override Bullet Create(string key) + { + // 根据键值创建不同类型的子弹 + return key switch + { + "standard" => new Bullet(), + "heavy" => new Bullet(), + "explosive" => new Bullet(), + _ => new Bullet() + }; + } + + protected override void OnInit() + { + // 可以预先创建一些对象放入池中 + for (int i = 0; i < 10; i++) + { + var bullet = Create("standard"); + bullet.OnAcquire(); + bullet.OnRelease(); + Release("standard", bullet); + } + } +} +``` + +### 3. 在架构中注册对象池系统 + +```csharp +public class GameArchitecture : Architecture +{ + protected override void Init() + { + // 注册其他组件 + RegisterModel(new PlayerModel()); + RegisterSystem(new CombatSystem()); + + // 注册对象池系统 + RegisterSystem(new BulletPoolSystem()); + } +} +``` + +### 4. 使用对象池 + +```csharp +public class ShootingSystem : AbstractSystem +{ + private BulletPoolSystem _bulletPool; + private List _activeBullets = new(); + + protected override void OnInit() + { + _bulletPool = this.GetSystem(); + this.RegisterEvent(OnShoot); + this.RegisterEvent(OnUpdate); + } + + private void OnShoot(ShootEvent e) + { + // 从对象池获取子弹 + var bullet = _bulletPool.Acquire("standard"); + + // 设置子弹属性 + bullet.Setup( + damage: e.Damage, + speed: 10.0f, + position: e.StartPosition, + direction: e.Direction + ); + + // 添加到活跃列表 + _activeBullets.Add(bullet); + } + + private void OnUpdate(GameUpdateEvent e) + { + // 更新所有活跃子弹 + for (int i = _activeBullets.Count - 1; i >= 0; i--) + { + var bullet = _activeBullets[i]; + bullet.Update(e.DeltaTime); + + // 检查子弹是否需要销毁(例如:超出范围或击中目标) + if (ShouldDestroyBullet(bullet)) + { + // 放回对象池 + _bulletPool.Release("standard", bullet); + _activeBullets.RemoveAt(i); + } + } + } + + private bool ShouldDestroyBullet(Bullet bullet) + { + // 简单示例:子弹超出一定范围则销毁 + return bullet.Position.Length() > 1000.0f; + } +} +``` + +## 高级用法 + +### 1. 多键对象池管理 + +```csharp +public class ParticlePoolSystem : AbstractObjectPoolSystem +{ + protected override Particle Create(string key) + { + // 根据键值创建不同类型的粒子效果 + return key switch + { + "explosion" => new Particle(explosionPrefab), + "smoke" => new Particle(smokePrefab), + "spark" => new Particle(sparkPrefab), + _ => throw new ArgumentException($"Unknown particle type: {key}") + }; + } + + // 提供便捷方法 + public Particle SpawnExplosion(Vector3 position) + { + var particle = Acquire("explosion"); + particle.Position = position; + particle.Play(); + return particle; + } +} + +// 使用 +public class EffectSystem : AbstractSystem +{ + private ParticlePoolSystem _particlePool; + + protected override void OnInit() + { + _particlePool = this.GetSystem(); + } + + public void PlayExplosion(Vector3 position) + { + _particlePool.SpawnExplosion(position); + } +} +``` + +### 2. 动态对象池管理 + +```csharp +public class EnemyPoolSystem : AbstractObjectPoolSystem +{ + protected override Enemy Create(string key) + { + // 根据敌人类型创建不同的敌人 + var enemyPrefab = LoadEnemyPrefab(key); + return new Enemy(enemyPrefab); + } + + // 动态注册新的敌人类型池 + public void RegisterEnemyType(string enemyType) + { + if (!Pools.ContainsKey(enemyType)) + { + Pools[enemyType] = new Stack(); + + // 预热:预先创建几个敌人放入池中 + for (int i = 0; i < 3; i++) + { + var enemy = Create(enemyType); + enemy.OnAcquire(); + enemy.OnRelease(); + Release(enemyType, enemy); + } + } + } +} + +// 使用 +public class EnemySpawnerSystem : AbstractSystem +{ + private EnemyPoolSystem _enemyPool; + + protected override void OnInit() + { + _enemyPool = this.GetSystem(); + + // 注册不同类型的敌人 + _enemyPool.RegisterEnemyType("goblin"); + _enemyPool.RegisterEnemyType("orc"); + _enemyPool.RegisterEnemyType("dragon"); + } + + public void SpawnEnemy(string enemyType, Vector3 position) + { + var enemy = _enemyPool.Acquire(enemyType); + enemy.Position = position; + enemy.Activate(); + } +} +``` + +### 3. 对象池大小限制 + +```csharp +public class LimitedBulletPoolSystem : AbstractObjectPoolSystem +{ + private const int MaxPoolSize = 50; + + protected override Bullet Create(string key) + { + return new Bullet(); + } + + public new void Release(string key, Bullet obj) + { + // 检查对象池大小 + if (Pools.TryGetValue(key, out var pool) && pool.Count >= MaxPoolSize) + { + // 池已满,不回收对象,让它被 GC 回收 + return; + } + + // 调用基类的 Release 方法 + base.Release(key, obj); + } +} +``` + +### 4. 对象池统计和调试 + +```csharp +public class DebuggablePoolSystem : AbstractObjectPoolSystem +{ + public Dictionary PoolSizes => Pools.ToDictionary( + kvp => kvp.Key, + kvp => kvp.Value.Count + ); + + public int TotalPooledObjects => Pools.Values.Sum(stack => stack.Count); + + public void LogPoolStatus() + { + foreach (var (key, stack) in Pools) + { + Console.WriteLine($"Pool [{key}]: {stack.Count} objects"); + } + + Console.WriteLine($"Total pooled objects: {TotalPooledObjects}"); + } + + protected override void OnDestroy() + { + LogPoolStatus(); + base.OnDestroy(); + } +} +``` + +## 使用场景 + +### 1. 游戏对象池 + +**适用对象:** + +- 子弹、箭矢、投射物 +- 敌人、NPC +- 爆炸效果、粒子系统 +- UI 元素(提示、对话框) + +**示例:子弹池** + +```csharp +// 定义子弹 +public class Bullet : IPoolableObject +{ + public Vector3 Position { get; private set; } + public Vector3 Velocity { get; private set; } + public float Lifetime { get; private set; } + + public void OnAcquire() + { + Lifetime = 5.0f; // 5秒后自动销毁 + } + + public void OnRelease() + { + Position = Vector3.Zero; + Velocity = Vector3.Zero; + } + + public void OnPoolDestroy() { } + + public void Update(float deltaTime) + { + Position += Velocity * deltaTime; + Lifetime -= deltaTime; + } +} + +// 子弹对象池 +public class BulletPoolSystem : AbstractObjectPoolSystem +{ + protected override Bullet Create(string key) => new Bullet(); +} + +// 射击系统 +public class ShootingSystem : AbstractSystem +{ + private BulletPoolSystem _bulletPool; + private List _activeBullets = new(); + + protected override void OnInit() + { + _bulletPool = this.GetSystem(); + this.RegisterEvent(OnShoot); + this.RegisterEvent(OnUpdate); + } + + private void OnShoot(ShootEvent e) + { + var bullet = _bulletPool.Acquire("normal"); + bullet.Position = e.StartPosition; + bullet.Velocity = e.Direction * e.Speed; + _activeBullets.Add(bullet); + } + + private void OnUpdate(GameUpdateEvent e) + { + for (int i = _activeBullets.Count - 1; i >= 0; i--) + { + var bullet = _activeBullets[i]; + bullet.Update(e.DeltaTime); + + if (bullet.Lifetime <= 0) + { + _bulletPool.Release("normal", bullet); + _activeBullets.RemoveAt(i); + } + } + } +} +``` + +### 2. UI 元素池 + +**适用对象:** + +- 对话框 +- 提示框 +- 菜单项 +- 列表项 + +**示例:提示框池** + +```csharp +public class Tooltip : IPoolableObject +{ + public string Text { get; set; } + public bool IsActive { get; private set; } + + public void OnAcquire() + { + IsActive = true; + } + + public void OnRelease() + { + IsActive = false; + Text = ""; + } + + public void OnPoolDestroy() { } + + public void Show(string text, Vector3 position) + { + Text = text; + // 更新 UI 位置和内容 + } +} + +public class TooltipPoolSystem : AbstractObjectPoolSystem +{ + protected override Tooltip Create(string key) => new Tooltip(); +} + +public class UISystem : AbstractSystem +{ + private TooltipPoolSystem _tooltipPool; + + protected override void OnInit() + { + _tooltipPool = this.GetSystem(); + } + + public void ShowTooltip(string text, Vector3 position) + { + var tooltip = _tooltipPool.Acquire("default"); + tooltip.Show(text, position); + } +} +``` + +### 3. 网络消息对象池 + +**适用对象:** + +- 网络包 +- 协议消息 +- 数据包 + +**示例:网络包池** + +```csharp +public class NetworkPacket : IPoolableObject +{ + public byte[] Data { get; private set; } + public int Length { get; private set; } + + public void OnAcquire() + { + Data = Array.Empty(); + Length = 0; + } + + public void OnRelease() + { + // 清理敏感数据 + if (Data != null) + { + Array.Clear(Data, 0, Data.Length); + } + Length = 0; + } + + public void OnPoolDestroy() { } + + public void SetData(byte[] data) + { + Data = data; + Length = data.Length; + } +} + +public class PacketPoolSystem : AbstractObjectPoolSystem +{ + protected override NetworkPacket Create(string key) => new NetworkPacket(); +} +``` + +## 最佳实践 + +### 1. 对象生命周期管理 + +```csharp +// ✅ 好的做法:确保所有对象都放回池中 +public class BulletSystem : AbstractSystem +{ + private List _activeBullets = new(); + + protected override void OnDestroy() + { + // 系统销毁时,确保所有活跃子弹都放回池中 + foreach (var bullet in _activeBullets) + { + _bulletPool.Release("standard", bullet); + } + + _activeBullets.Clear(); + base.OnDestroy(); + } +} + +// ❌ 不好的做法:忘记放回对象,导致泄漏 +public class BadBulletSystem : AbstractSystem +{ + private List _activeBullets = new(); + + private void OnUpdate(GameUpdateEvent e) + { + // 子弹销毁时忘记放回池中 + if (bullet.Lifetime <= 0) + { + _activeBullets.RemoveAt(i); + // 忘记调用 _bulletPool.Release(...) + } + } +} +``` + +### 2. 对象状态重置 + +```csharp +// ✅ 好的做法:在 OnRelease 中彻底重置对象状态 +public class Bullet : IPoolableObject +{ + public int Damage { get; set; } + public float Speed { get; set; } + public List Tags { get; set; } + public Dictionary Data { get; set; } + + public void OnRelease() + { + // 重置所有属性 + Damage = 0; + Speed = 0; + Tags?.Clear(); + Data?.Clear(); + + // 也可以设置为新实例(如果性能允许) + Tags = new List(); + Data = new Dictionary(); + } +} + +// ❌ 不好的做法:不完全重置状态 +public class BadBullet : IPoolableObject +{ + public List Tags = new List(); + + public void OnRelease() + { + // 只清空列表,但列表实例本身保留 + // 这可能导致问题:如果其他代码持有列表引用 + Tags.Clear(); + } +} +``` + +### 3. 对象池预热 + +```csharp +// ✅ 好的做法:预先创建一些对象放入池中 +public class BulletPoolSystem : AbstractObjectPoolSystem +{ + protected override Bullet Create(string key) => new Bullet(); + + protected override void OnInit() + { + // 为常用的子弹类型预热对象池 + var commonTypes = new[] { "standard", "heavy", "sniper" }; + + foreach (var type in commonTypes) + { + // 预先创建 5 个对象 + for (int i = 0; i < 5; i++) + { + var bullet = Create(type); + bullet.OnAcquire(); + bullet.OnRelease(); + Release(type, bullet); + } + } + } +} +``` + +### 4. 对象池大小管理 + +```csharp +// ✅ 好的做法:限制对象池大小,避免内存浪费 +public class BoundedPoolSystem : AbstractObjectPoolSystem +{ + private const int MaxPoolSize = 100; + + public new void Release(string key, PooledObject obj) + { + if (Pools.TryGetValue(key, out var pool) && pool.Count >= MaxPoolSize) + { + // 池已满,不回收对象 + return; + } + + base.Release(key, obj); + } +} + +// ✅ 好的做法:动态调整对象池大小 +public class AdaptivePoolSystem : AbstractObjectPoolSystem +{ + private Dictionary _peakUsage = new(); + + public new void Release(string key, PooledObject obj) + { + // 记录峰值使用量 + if (Pools.TryGetValue(key, out var pool)) + { + _peakUsage[key] = Math.Max(_peakUsage.GetValueOrDefault(key, 0), pool.Count); + } + + // 根据使用情况动态调整 + int maxAllowed = _peakUsage.GetValueOrDefault(key, 10) * 2; + if (pool.Count >= maxAllowed) + { + return; // 不回收 + } + + base.Release(key, obj); + } +} +``` + +### 5. 调试和监控 + +```csharp +// ✅ 好的做法:添加对象池统计功能 +public class MonitoredPoolSystem : AbstractObjectPoolSystem +{ + private Dictionary _acquireCount = new(); + private Dictionary _releaseCount = new(); + + public new PooledObject Acquire(string key) + { + _acquireCount[key] = _acquireCount.GetValueOrDefault(key, 0) + 1; + return base.Acquire(key); + } + + public new void Release(string key, PooledObject obj) + { + _releaseCount[key] = _releaseCount.GetValueOrDefault(key, 0) + 1; + base.Release(key, obj); + } + + public void PrintStatistics() + { + foreach (var key in Pools.Keys) + { + int acquired = _acquireCount.GetValueOrDefault(key, 0); + int released = _releaseCount.GetValueOrDefault(key, 0); + int leaked = acquired - released; + + Console.WriteLine($"Pool [{key}]:"); + Console.WriteLine($" Acquired: {acquired}"); + Console.WriteLine($" Released: {released}"); + Console.WriteLine($" Leaked: {leaked}"); + Console.WriteLine($" Pool size: {Pools[key].Count}"); + } + } +} +``` + +## 性能优化 + +### 1. 减少对象创建 + +```csharp +// ✅ 好的做法:对象池预热,避免运行时创建 +public class PreheatedPoolSystem : AbstractObjectPoolSystem +{ + protected override Bullet Create(string key) => new Bullet(); + + protected override void OnInit() + { + // 预热常用对象池 + PreheatPool("standard", 20); + PreheatPool("heavy", 10); + } + + private void PreheatPool(string key, int count) + { + for (int i = 0; i < count; i++) + { + var bullet = Create(key); + bullet.OnAcquire(); + bullet.OnRelease(); + Release(key, bullet); + } + } +} +``` + +### 2. 使用值类型作为键 + +```csharp +// ✅ 好的做法:使用枚举或整数作为键,避免字符串比较 +public enum BulletType +{ + Standard, + Heavy, + Explosive +} + +public class FastBulletPoolSystem : AbstractObjectPoolSystem +{ + protected override Bullet Create(BulletType key) => key switch + { + BulletType.Standard => new Bullet(), + BulletType.Heavy => new Bullet(), + BulletType.Explosive => new Bullet(), + _ => throw new ArgumentException() + }; +} +``` + +### 3. 批量操作 + +```csharp +// ✅ 好的做法:批量获取和释放对象 +public class BatchPoolSystem : AbstractObjectPoolSystem +{ + public List AcquireBatch(string key, int count) + { + var objects = new List(count); + for (int i = 0; i < count; i++) + { + objects.Add(Acquire(key)); + } + return objects; + } + + public void ReleaseBatch(string key, IEnumerable objects) + { + foreach (var obj in objects) + { + Release(key, obj); + } + } +} +``` + +## 注意事项 + +1. **对象状态管理**:确保 `OnRelease` 方法彻底重置对象状态,避免状态污染 +2. **对象引用**:不要在放回对象池后继续持有对象引用 +3. **线程安全**:对象池本身不是线程安全的,如需多线程访问,需要自行加锁 +4. **内存泄漏**:确保所有获取的对象最终都放回池中 +5. **对象池大小**:合理设置对象池大小,避免内存浪费或频繁创建 + +## 相关包 + +- [`system`](./system.md) - 对象池系统继承自 AbstractSystem +- [`architecture`](./architecture.md) - 在架构中注册对象池系统 + +--- + +**许可证**: Apache 2.0 diff --git a/docs/core/property.md b/docs/core/property.md new file mode 100644 index 0000000..464d4a6 --- /dev/null +++ b/docs/core/property.md @@ -0,0 +1,341 @@ +# Property 包使用说明 + +## 概述 + +Property 包提供了可绑定属性(BindableProperty)的实现,支持属性值的监听和响应式编程。这是实现数据绑定和响应式编程的核心组件。 + +## 核心接口 + +### [`IReadonlyBindableProperty`](IReadonlyBindableProperty.cs) + +只读可绑定属性接口,提供属性值的读取和变更监听功能。 + +**核心成员:** + +```csharp +// 获取属性值 +T Value { get; } + +// 注册监听(不立即触发回调) +IUnRegister Register(Action onValueChanged); + +// 注册监听并立即触发回调传递当前值 +IUnRegister RegisterWithInitValue(Action action); + +// 取消监听 +void UnRegister(Action onValueChanged); +``` + +### [`IBindableProperty`](IBindableProperty.cs) + +可绑定属性接口,继承自只读接口,增加了修改能力。 + +**核心成员:** + +```csharp +// 可读写的属性值 +new T Value { get; set; } + +// 设置值但不触发事件 +void SetValueWithoutEvent(T newValue); +``` + +## 核心类 + +### [`BindableProperty`](BindableProperty.cs) + +可绑定属性的完整实现。 + +**使用示例:** + +```csharp +// 创建可绑定属性 +var health = new BindableProperty(100); + +// 监听值变化(不会立即触发) +var unregister = health.Register(newValue => +{ + GD.Print($"Health changed to: {newValue}"); +}); + +// 设置值(会触发监听器) +health.Value = 50; // 输出: Health changed to: 50 + +// 取消监听 +unregister.UnRegister(); + +// 设置值但不触发事件 +health.SetValueWithoutEvent(75); +``` + +**高级功能:** + +```csharp +// 1. 注册并立即获得当前值 +health.RegisterWithInitValue(value => +{ + GD.Print($"Current health: {value}"); // 立即输出当前值 + // 后续值变化时也会调用 +}); + +// 2. 自定义比较器(静态方法) +BindableProperty.Comparer = (a, b) => Math.Abs(a - b) < 1; + +// 3. 使用实例方法设置比较器 +var position = new BindableProperty(Vector3.Zero) + .WithComparer((a, b) => a.DistanceTo(b) < 0.01f); // 距离小于0.01认为相等 +``` + +### [`BindablePropertyUnRegister`](BindablePropertyUnRegister.cs) + +可绑定属性的注销器,负责清理监听。 + +**使用示例:** + +```csharp +var unregister = health.Register(OnHealthChanged); +// 当需要取消监听时 +unregister.UnRegister(); +``` + +## 在 Model 中使用 + +### 定义可绑定属性 + +```csharp +public class PlayerModel : AbstractModel +{ + // 可读写属性 + public BindableProperty Name { get; } = new("Player"); + public BindableProperty Level { get; } = new(1); + public BindableProperty Health { get; } = new(100); + public BindableProperty MaxHealth { get; } = new(100); + + // 只读属性(外部只能读取和监听) + public IReadonlyBindableProperty ReadonlyHealth => Health; + + protected override void OnInit() + { + // 内部监听属性变化 + Health.Register(hp => + { + if (hp <= 0) + { + this.SendEvent(new PlayerDiedEvent()); + } + }); + } +} +``` + +## 在 Controller 中监听 + +### UI 数据绑定 + +```csharp +public partial class PlayerUI : Control, IController +{ + [Export] private Label _healthLabel; + [Export] private Label _nameLabel; + + private IUnRegisterList _unregisterList = new UnRegisterList(); + + public IArchitecture GetArchitecture() => GameArchitecture.Interface; + + public override void _Ready() + { + var playerModel = this.GetModel(); + + // 绑定生命值到UI(立即显示当前值) + playerModel.Health + .RegisterWithInitValue(health => + { + _healthLabel.Text = $"HP: {health}/{playerModel.MaxHealth.Value}"; + }) + .AddToUnregisterList(_unregisterList); + + // 绑定名称 + playerModel.Name + .RegisterWithInitValue(name => + { + _nameLabel.Text = name; + }) + .AddToUnregisterList(_unregisterList); + } + + public override void _ExitTree() + { + _unregisterList.UnRegisterAll(); + } +} +``` + +## 常见使用模式 + +### 1. 双向绑定 + +```csharp +// Model +public class SettingsModel : AbstractModel +{ + public BindableProperty MasterVolume { get; } = new(1.0f); + protected override void OnInit() { } +} + +// UI Controller +public partial class VolumeSlider : HSlider, IController +{ + private BindableProperty _volumeProperty; + + public override void _Ready() + { + _volumeProperty = this.GetModel().MasterVolume; + + // Model -> UI + _volumeProperty.RegisterWithInitValue(vol => Value = vol) + .UnRegisterWhenNodeExitTree(this); + + // UI -> Model + ValueChanged += newValue => _volumeProperty.Value = (float)newValue; + } +} +``` + +### 2. 计算属性 + +```csharp +public class PlayerModel : AbstractModel +{ + public BindableProperty Health { get; } = new(100); + public BindableProperty MaxHealth { get; } = new(100); + public BindableProperty 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. 属性验证 + +```csharp +public class PlayerModel : AbstractModel +{ + private BindableProperty _health = new(100); + + public BindableProperty Health + { + get => _health; + set + { + // 限制范围 + var clampedValue = Math.Clamp(value.Value, 0, MaxHealth.Value); + _health.Value = clampedValue; + } + } + + public BindableProperty MaxHealth { get; } = new(100); + + protected override void OnInit() { } +} +``` + +### 4. 条件监听 + +```csharp +public class CombatController : Node, IController +{ + public override void _Ready() + { + var playerModel = this.GetModel(); + + // 只在生命值低于30%时显示警告 + playerModel.Health.Register(hp => + { + if (hp < playerModel.MaxHealth.Value * 0.3f) + { + ShowLowHealthWarning(); + } + else + { + HideLowHealthWarning(); + } + }).UnRegisterWhenNodeExitTree(this); + } +} +``` + +## 性能优化 + +### 1. 避免频繁触发 + +```csharp +// 使用 SetValueWithoutEvent 批量修改 +public void LoadPlayerData(SaveData data) +{ + // 临时关闭事件 + Health.SetValueWithoutEvent(data.Health); + Mana.SetValueWithoutEvent(data.Mana); + Gold.SetValueWithoutEvent(data.Gold); + + // 最后统一触发一次更新事件 + this.SendEvent(new PlayerDataLoadedEvent()); +} +``` + +### 2. 自定义比较器 + +```csharp +// 避免浮点数精度问题导致的频繁触发 +var position = new BindableProperty() + .WithComparer((a, b) => a.DistanceTo(b) < 0.001f); +``` + +## 实现原理 + +### 值变化检测 + +```csharp +// 使用 EqualityComparer.Default 进行比较 +if (!EqualityComparer.Default.Equals(value, MValue)) +{ + MValue = value; + _mOnValueChanged?.Invoke(value); +} +``` + +### 事件触发机制 + +```csharp +// 当值变化时触发所有注册的回调 +_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 diff --git a/docs/core/query.md b/docs/core/query.md new file mode 100644 index 0000000..c0556fe --- /dev/null +++ b/docs/core/query.md @@ -0,0 +1,447 @@ +# Query 包使用说明 + +## 概述 + +Query 包实现了 CQRS(命令查询职责分离)模式中的查询部分。Query 用于封装数据查询逻辑,与 Command 不同的是,Query +有返回值且不应该修改系统状态。 + +## 核心接口 + +### IQuery + +查询接口,定义了查询的基本契约。 + +**核心成员:** + +```csharp +TResult Do(); // 执行查询并返回结果 +``` + +## 核心类 + +### [`AbstractQuery`](AbstractQuery.cs) + +抽象查询基类,提供了查询的基础实现。它接受一个泛型输入参数 TInput,该参数必须实现 IQueryInput 接口。 + +**使用方式:** + +```csharp +public abstract class AbstractQuery(TInput input) : ContextAwareBase, IQuery + where TInput : IQueryInput +{ + public TResult Do() => OnDo(input); // 执行查询,传入输入参数 + protected abstract TResult OnDo(TInput input); // 子类实现查询逻辑 +} +``` + +### [`EmptyQueryInput`](EmptyQueryInput.cs) + +空查询输入类,用于表示不需要任何输入参数的查询操作。 + +**使用方式:** + +```csharp +public sealed class EmptyQueryInput : IQueryInput +{ + // 作为占位符使用,适用于那些不需要额外输入参数的查询场景 +} +``` + +### [`QueryBus`](QueryBus.cs) + +查询总线实现,负责执行查询并返回结果。 + +**使用方式:** + +```csharp +public sealed class QueryBus : IQueryBus +{ + public TResult Send(IQuery query) + { + ArgumentNullException.ThrowIfNull(query); + return query.Do(); + } +} +``` + +## 基本使用 + +### 1. 定义查询 + +``csharp +// 定义查询输入参数 +public record GetPlayerGoldQueryInput : IQueryInput; + +// 查询玩家金币数量 +public class GetPlayerGoldQuery : AbstractQuery +{ +public GetPlayerGoldQuery() : base(new EmptyQueryInput()) +{ +} + + protected override int OnDo(GetPlayerGoldQueryInput input) + { + return this.GetModel().Gold.Value; + } + +} + +// 查询玩家是否死亡 +public record IsPlayerDeadQueryInput : IQueryInput; + +public class IsPlayerDeadQuery : AbstractQuery +{ +public IsPlayerDeadQuery() : base(new EmptyQueryInput()) +{ +} + + protected override bool OnDo(IsPlayerDeadQueryInput input) + { + return this.GetModel().Health.Value <= 0; + } + +} + +// 查询背包中指定物品的数量 +public record GetItemCountQueryInput(string ItemId) : IQueryInput; + +public class GetItemCountQuery : AbstractQuery +{ +public GetItemCountQuery(string itemId) : base(new GetItemCountQueryInput(itemId)) +{ +} + + protected override int OnDo(GetItemCountQueryInput input) + { + var inventory = this.GetModel(); + return inventory.GetItemCount(input.ItemId); + } + +} + +``` + +### 2. 发送查询(在 Controller 中) + +``csharp +public partial class ShopUI : Control, IController +{ + [Export] private Button _buyButton; + [Export] private int _itemPrice = 100; + + public IArchitecture GetArchitecture() => GameArchitecture.Interface; + + public override void _Ready() + { + _buyButton.Pressed += OnBuyButtonPressed; + } + + private void OnBuyButtonPressed() + { + // 查询玩家金币 + int playerGold = this.SendQuery(new GetPlayerGoldQuery()); + + if (playerGold >= _itemPrice) + { + // 发送购买命令 + this.SendCommand(new BuyItemCommand { ItemId = "sword_01" }); + } + else + { + GD.Print("金币不足!"); + } + } +} +``` + +### 3. 在 System 中使用 + +``csharp +public class CombatSystem : AbstractSystem +{ +protected override void OnInit() +{ +// 注册事件监听 +this.RegisterEvent(OnEnemyAttack); +} + + private void OnEnemyAttack(EnemyAttackEvent e) + { + // 查询玩家是否已经死亡 + bool isDead = this.SendQuery(new IsPlayerDeadQuery()); + + if (!isDead) + { + // 执行伤害逻辑 + this.SendCommand(new TakeDamageCommand { Damage = e.Damage }); + } + } + +} + +``` + +## 高级用法 + +### 1. 带参数的复杂查询 + +``csharp +// 查询指定范围内的敌人列表 +public class GetEnemiesInRangeQuery : AbstractQuery> +{ + public Vector3 Center { get; set; } + public float Radius { get; set; } + + protected override List OnDo() + { + var enemySystem = this.GetSystem(); + return enemySystem.GetEnemiesInRange(Center, Radius); + } +} + +// 使用 +var enemies = this.SendQuery(new GetEnemiesInRangeQuery +{ + Center = playerPosition, + Radius = 10.0f +}); +``` + +### 2. 组合查询 + +``csharp +// 查询玩家是否可以使用技能 +public class CanUseSkillQuery : AbstractQuery +{ +public string SkillId { get; set; } + + protected override bool OnDo() + { + var playerModel = this.GetModel(); + var skillModel = this.GetModel(); + + // 查询技能消耗 + var skillCost = this.SendQuery(new GetSkillCostQuery { SkillId = SkillId }); + + // 检查是否满足条件 + return playerModel.Mana.Value >= skillCost.ManaCost + && !this.SendQuery(new IsSkillOnCooldownQuery { SkillId = SkillId }); + } + +} + +public class GetSkillCostQuery : AbstractQuery +{ +public string SkillId { get; set; } + + protected override SkillCost OnDo() + { + return this.GetModel().GetSkillCost(SkillId); + } + +} + +public class IsSkillOnCooldownQuery : AbstractQuery +{ +public string SkillId { get; set; } + + protected override bool OnDo() + { + return this.GetModel().IsOnCooldown(SkillId); + } + +} + +``` + +### 3. 聚合数据查询 + +``csharp +// 查询玩家战斗力 +public class GetPlayerPowerQuery : AbstractQuery +{ + protected override int OnDo() + { + var playerModel = this.GetModel(); + var equipmentModel = this.GetModel(); + + int basePower = playerModel.Level.Value * 10; + int equipmentPower = equipmentModel.GetTotalPower(); + int buffPower = this.SendQuery(new GetBuffPowerQuery()); + + return basePower + equipmentPower + buffPower; + } +} + +// 查询玩家详细信息(用于UI显示) +public class GetPlayerInfoQuery : AbstractQuery +{ + protected override PlayerInfo OnDo() + { + var playerModel = this.GetModel(); + + return new PlayerInfo + { + Name = playerModel.Name.Value, + Level = playerModel.Level.Value, + Health = playerModel.Health.Value, + MaxHealth = playerModel.MaxHealth.Value, + Gold = this.SendQuery(new GetPlayerGoldQuery()), + Power = this.SendQuery(new GetPlayerPowerQuery()) + }; + } +} +``` + +### 4. 跨 System 查询 + +``csharp +// 在 AI System 中查询玩家状态 +public class EnemyAISystem : AbstractSystem +{ +protected override void OnInit() { } + + public void UpdateEnemyBehavior(Enemy enemy) + { + // 查询玩家位置 + var playerPos = this.SendQuery(new GetPlayerPositionQuery()); + + // 查询玩家是否在攻击范围内 + bool inRange = this.SendQuery(new IsPlayerInRangeQuery + { + Position = enemy.Position, + Range = enemy.AttackRange + }); + + if (inRange) + { + // 查询是否可以攻击 + bool canAttack = this.SendQuery(new CanEnemyAttackQuery + { + EnemyId = enemy.Id + }); + + if (canAttack) + { + this.SendCommand(new EnemyAttackCommand { EnemyId = enemy.Id }); + } + } + } + +} + +``` + +## Command vs Query + +### Command(命令) + +- **用途**:修改系统状态 +- **返回值**:无返回值(void) +- **示例**:购买物品、造成伤害、升级角色 + +### Query(查询) + +- **用途**:读取数据,不修改状态 +- **返回值**:有返回值 +- **示例**:获取金币数量、检查技能冷却、查询玩家位置 + +``csharp +// ❌ 错误:在 Query 中修改状态 +public class BadQuery : AbstractQuery +{ + protected override int OnDo() + { + var model = this.GetModel(); + model.Gold.Value += 100; // 不应该在 Query 中修改数据! + return model.Gold.Value; + } +} + +// ✅ 正确:Query 只读取数据 +public class GoodQuery : AbstractQuery +{ + protected override int OnDo() + { + return this.GetModel().Gold.Value; + } +} + +// ✅ 修改数据应该使用 Command +public class AddGoldCommand : AbstractCommand +{ + public int Amount { get; set; } + + protected override void OnExecute() + { + var model = this.GetModel(); + model.Gold.Value += Amount; + } +} +``` + +## 最佳实践 + +1. **查询只读取,不修改** - 保持 Query 的纯粹性 +2. **小而专注** - 每个 Query 只负责一个具体的查询任务 +3. **可组合** - 复杂查询可以通过组合简单查询实现 +4. **避免过度查询** - 如果需要频繁查询,考虑使用 BindableProperty +5. **命名清晰** - Query 名称应该清楚表达查询意图(Get、Is、Can、Has等前缀) + +## 性能优化 + +### 1. 缓存查询结果 + +``csharp +// 在 Model 中缓存复杂计算 +public class PlayerModel : AbstractModel +{ +private int? _cachedPower; + + public int GetPower() + { + if (_cachedPower == null) + { + _cachedPower = CalculatePower(); + } + return _cachedPower.Value; + } + + private int CalculatePower() + { + // 复杂计算... + return 100; + } + + public void InvalidatePowerCache() + { + _cachedPower = null; + } + +} + +``` + +### 2. 批量查询 + +``csharp +// 一次查询多个数据,而不是多次单独查询 +public class GetMultipleItemCountsQuery : AbstractQuery> +{ + public List ItemIds { get; set; } + + protected override Dictionary OnDo() + { + var inventory = this.GetModel(); + return ItemIds.ToDictionary(id => id, id => inventory.GetItemCount(id)); + } +} +``` + +## 相关包 + +- [`command`](./command.md) - CQRS 的命令部分 +- [`model`](./model.md) - Query 主要从 Model 获取数据 +- [`system`](./system.md) - System 中可以发送 Query +- [`controller`](./controller.md) - Controller 中可以发送 Query +- [`extensions`](./extensions.md) - 提供 SendQuery 扩展方法 \ No newline at end of file diff --git a/docs/core/rule.md b/docs/core/rule.md new file mode 100644 index 0000000..87eb445 --- /dev/null +++ b/docs/core/rule.md @@ -0,0 +1,316 @@ +# Rule 包使用说明 + +## 概述 + +Rule 包定义了框架的核心规则接口,这些接口规定了框架各个组件之间的关系和约束。所有框架组件都需要遵循这些规则接口。 + +## 核心接口 + +### IContextAware + +标记接口,表示该类型可以感知架构上下文。 + +**接口定义:** + +```csharp +public interface IContextAware +{ + void SetContext(IArchitectureContext context); + IArchitectureContext GetContext(); +} +``` + +**实现此接口的类型:** + +- System +- Query +- Model +- Command +- 以及其他需要感知架构上下文的组件 + +**作用:** +所有实现此接口的类型都能够获取其所属的架构上下文实例,从而访问架构提供的各种能力。 + +## 核心类 + +### [`ContextAwareBase`](ContextAwareBase.cs) + +上下文感知基类,实现了 IContextAware 接口,为需要感知架构上下文的类提供基础实现。 + +**使用方式:** + +```csharp +public abstract class ContextAwareBase : IContextAware +{ + protected IArchitectureContext? Context { get; set; } + + void IContextAware.SetContext(IArchitectureContext context) + { + Context = context; + OnContextReady(); // 上下文准备好后调用此方法 + } + + IArchitectureContext IContextAware.GetContext() + { + Context ??= GameContext.GetFirstArchitectureContext(); + return Context; + } + + protected virtual void OnContextReady() // 子类可以重写此方法进行初始化 + { + } +} +``` + +## 接口关系图 + +``` +IContextAware (上下文感知接口) + ↓ 被继承于 + ├── AbstractSystem (抽象系统基类) + ├── AbstractQuery (抽象查询基类) + ├── AbstractModel (抽象模型基类) + └── AbstractCommand (抽象命令基类) +``` + +## 使用场景 + +### 1. Component 继承 ContextAwareBase + +```csharp +// 组件通过继承 ContextAwareBase 获得架构上下文访问能力 +public partial class PlayerController : Node, IController +{ + // 不再需要手动实现 IContextAware,基类已处理 + // 可以直接使用扩展方法 + public override void _Ready() + { + var playerModel = this.GetModel(); + this.SendCommand(new InitPlayerCommand()); + this.RegisterEvent(OnPlayerDied); + } + + private void OnPlayerDied(PlayerDiedEvent e) + { + GD.Print("Player died!"); + } +} +``` + +### 2. Command 继承 AbstractCommand (IContextAware) + +```csharp +// Command 继承 AbstractCommand,自动成为 IContextAware +public class BuyItemCommand : AbstractCommand +{ + public string ItemId { get; set; } + + protected override void OnExecute() + { + // 框架或上下文系统会自动注入 IArchitectureContext + // 所以这里可以直接使用 this.GetModel + var playerModel = this.GetModel(); + var shopModel = this.GetModel(); + + int price = shopModel.GetItemPrice(ItemId); + if (playerModel.Gold.Value >= price) + { + playerModel.Gold.Value -= price; + this.SendCommand(new AddItemCommand { ItemId = ItemId }); + } + } +} +``` + +### 3. 自定义组件遵循规则 + +```csharp +// 自定义管理器遵循框架规则,继承 ContextAwareBase +public class SaveManager : ContextAwareBase +{ + // 不再需要手动构造函数传参,上下文由框架注入 + // protected override void OnContextReady() 可用于初始化 + + public void SaveGame() + { + // 因为继承了 ContextAwareBase,可以使用扩展方法 + var playerModel = this.GetModel(); + var saveData = new SaveData + { + PlayerName = playerModel.Name.Value, + Level = playerModel.Level.Value, + Gold = playerModel.Gold.Value + }; + + // 保存逻辑... + } +} +``` + +## 规则约束 + +### 1. 组件注册规则 + +```csharp +public class GameArchitecture : Architecture +{ + protected override void Init() + { + // Model/System/Utility 自动获得架构引用 + this.RegisterModel(new PlayerModel()); + this.RegisterSystem(new CombatSystem()); + this.RegisterUtility(new StorageUtility()); + } +} +``` + +### 2. Command/Query 自动注入规则 + +```csharp +// Controller 中发送 Command +public class ShopUI : Control, IController +{ + public IArchitecture GetArchitecture() => GameArchitecture.Interface; + + private void OnBuyButtonPressed() + { + var command = new BuyItemCommand { ItemId = "sword_01" }; + + // SendCommand 内部会自动调用 command.SetArchitecture(this.GetArchitecture()) + this.SendCommand(command); + } +} + +// Command 执行时已经有了架构引用 +public class BuyItemCommand : AbstractCommand +{ + public string ItemId { get; set; } + + protected override void OnExecute() + { + // 此时 GetArchitecture() 已经可用 + var model = this.GetModel(); + } +} +``` + +## 设计模式 + +### 1. 依赖注入模式 + +Rule 接口体现了依赖注入(DI)的思想: + +```csharp +// 接口定义了"需要什么" +public interface IContextAware +{ + void SetContext(IArchitectureContext context); + IArchitectureContext GetContext(); +} + +// 框架负责"提供什么" +public static class CanSendExtensions +{ + public static void SendCommand(this ICanSendCommand self, T command) + where T : ICommand + { + // 自动注入架构上下文依赖 + command.SetContext(self.GetContext()); + command.Execute(); + } +} +``` + +### 2. 接口隔离原则(ISP) + +Rule 接口遵循接口隔离原则,每个接口职责单一: + +```csharp +// ❌ 不好的设计:一个大接口包含所有能力 +public interface IBigInterface +{ + void SetContext(IArchitectureContext context); + IArchitectureContext GetContext(); + T GetModel() where T : class, IModel; + T GetSystem() where T : class, ISystem; + void SendCommand(T command) where T : ICommand; + // ... 更多方法 +} + +// ✅ 好的设计:小接口组合 +public interface IContextAware { ... } // 只负责上下文的设置与获取 +public interface ICanGetModel { ... } // 只负责获取 Model +public interface ICanSendCommand { ... } // 只负责发送 Command +``` + +### 3. 组合优于继承 + +通过接口组合实现不同能力: + +```csharp +// Controller 需要获取 Model 和发送 Command +public interface IController : ICanGetModel, ICanGetSystem, ICanSendCommand, + ICanSendQuery, ICanRegisterEvent +{ +} + +// Command 需要上下文感知和获取 Model/System +public interface ICommand : IContextAware, ICanGetModel, ICanGetSystem, + ICanSendEvent, ICanSendQuery +{ +} + +// System 只需要获取其他组件 +public interface ISystem : IContextAware, ICanGetModel, ICanGetUtility, ICanGetSystem, + ICanRegisterEvent, ICanSendEvent, ICanSendQuery +{ +} +``` + +## 扩展规则 + +### 自定义规则接口 + +```csharp +// 定义新的规则接口 +public interface ICanAccessDatabase : IBelongToArchitecture +{ +} + +// 提供扩展方法 +public static class CanAccessDatabaseExtensions +{ + public static DatabaseUtility GetDatabase(this ICanAccessDatabase self) + { + return self.GetArchitecture().GetUtility(); + } +} + +// 让特定组件实现这个接口 +public class DatabaseCommand : AbstractCommand, ICanAccessDatabase +{ + protected override void OnExecute() + { + var db = this.GetDatabase(); + // 使用数据库... + } +} +``` + +## 最佳实践 + +1. **遵循既有规则** - 优先使用框架提供的规则接口 +2. **不要绕过规则** - 不要直接存储架构引用,使用接口获取 +3. **接口组合** - 通过实现多个小接口获得所需能力 +4. **扩展规则** - 需要新能力时,定义新的规则接口 +5. **保持一致** - 自定义组件也应遵循相同的规则体系 + +## 相关包 + +- [`architecture`](./architecture.md) - 定义 IArchitectureContext 接口 +- [`command`](./command.md) - Command 继承 AbstractCommand (IContextAware) +- [`query`](./query.md) - Query 继承 AbstractQuery (IContextAware) +- [`controller`](./controller.md) - Controller 实现 ICanSendCommand 等接口 +- [`system`](./system.md) - System 继承 AbstractSystem (IContextAware) +- [`model`](./model.md) - Model 继承 AbstractModel (IContextAware) +- [`extensions`](./extensions.md) - 基于规则接口提供扩展方法 \ No newline at end of file diff --git a/docs/core/system.md b/docs/core/system.md new file mode 100644 index 0000000..cb8f421 --- /dev/null +++ b/docs/core/system.md @@ -0,0 +1,544 @@ +# System 包使用说明 + +## 概述 + +System 包定义了业务逻辑层(Business Logic Layer)。System 负责处理游戏的核心业务逻辑,协调 Model 之间的交互,响应事件并执行复杂的业务流程。 + +## 核心接口 + +### [`ICanGetSystem`](ICanGetSystem.cs) + +标记接口,表示该类型可以获取其他 System。 + +**继承关系:** + +```csharp +public interface ICanGetSystem : IBelongToArchitecture +``` + +### [`ISystem`](ISystem.cs) + +System 接口,定义了系统的基本行为。 + +**核心成员:** + +```csharp +void Init(); // 系统初始化方法 +void Destroy(); // 系统销毁方法 +void OnArchitecturePhase(ArchitecturePhase phase); // 处理架构阶段事件 +``` + +**继承的能力:** + +- `ICanSetArchitecture` - 可设置架构 +- `ICanGetModel` - 可获取 Model +- `ICanGetUtility` - 可获取 Utility +- `ICanGetSystem` - 可获取其他 System +- `ICanRegisterEvent` - 可注册事件 +- `ICanSendEvent` - 可发送事件 + +## 核心类 + +### [`AbstractSystem`](AbstractSystem.cs) + +抽象 System 基类,提供了 System 的基础实现。继承自 ContextAwareBase,具有上下文感知能力。 + +**使用方式:** + +```csharp +public abstract class AbstractSystem : ContextAwareBase, ISystem +{ + void ISystem.Init() => OnInit(); // 系统初始化,内部调用抽象方法 OnInit() + void ISystem.Destroy() => OnDestroy(); // 系统销毁,内部调用 OnDestroy() + protected abstract void OnInit(); // 子类实现初始化逻辑 + protected virtual void OnDestroy(); // 子类可选择重写销毁逻辑 + public virtual void OnArchitecturePhase(ArchitecturePhase phase); // 处理架构阶段事件 +} +``` + +## 基本使用 + +### 1. 定义 System + +```csharp +// 战斗系统 +public class CombatSystem : AbstractSystem +{ + protected override void OnInit() + { + // 注册事件监听 + this.RegisterEvent(OnEnemyAttack); + this.RegisterEvent(OnPlayerAttack); + } + + private void OnEnemyAttack(EnemyAttackEvent e) + { + var playerModel = this.GetModel(); + + // 计算伤害 + int damage = CalculateDamage(e.AttackPower, playerModel.Defense.Value); + + // 应用伤害 + playerModel.Health.Value -= damage; + + // 发送伤害事件 + this.SendEvent(new PlayerTookDamageEvent { Damage = damage }); + } + + private void OnPlayerAttack(PlayerAttackEvent e) + { + var playerModel = this.GetModel(); + var enemyModel = this.GetModel(); + + int damage = CalculateDamage(playerModel.AttackPower.Value, e.Enemy.Defense); + e.Enemy.Health -= damage; + + this.SendEvent(new EnemyTookDamageEvent + { + EnemyId = e.Enemy.Id, + Damage = damage + }); + } + + private int CalculateDamage(int attackPower, int defense) + { + return Math.Max(1, attackPower - defense / 2); + } + + protected override void OnDestroy() + { + // 清理资源 + base.OnDestroy(); + } +} +``` + +### 2. 注册 System + +```csharp +public class GameArchitecture : Architecture +{ + protected override void Init() + { + // 注册 Model + this.RegisterModel(new PlayerModel()); + this.RegisterModel(new EnemyModel()); + + // 注册 System(系统注册后会自动调用 Init) + this.RegisterSystem(new CombatSystem()); + this.RegisterSystem(new InventorySystem()); + this.RegisterSystem(new QuestSystem()); + } +} +``` + +### 3. 在其他组件中获取 System + +```csharp +// 在 Controller 中 +public partial class GameController : Node, IController +{ + public IArchitecture GetArchitecture() => GameArchitecture.Interface; + + public override void _Ready() + { + // 获取 System + var combatSystem = this.GetSystem(); + var questSystem = this.GetSystem(); + } +} + +// 在 Command 中 +public class StartBattleCommand : AbstractCommand +{ + protected override void OnExecute() + { + var combatSystem = this.GetSystem(); + // 使用 System... + } +} +``` + +## 常见使用模式 + +### 1. 事件驱动的 System + +```csharp +public class InventorySystem : AbstractSystem +{ + protected override void OnInit() + { + // 监听物品相关事件 + this.RegisterEvent(OnItemAdded); + this.RegisterEvent(OnItemRemoved); + this.RegisterEvent(OnItemUsed); + } + + private void OnItemAdded(ItemAddedEvent e) + { + var inventoryModel = this.GetModel(); + + // 添加物品 + inventoryModel.AddItem(e.ItemId, e.Count); + + // 检查成就 + CheckAchievements(e.ItemId); + + // 发送通知 + this.SendEvent(new ShowNotificationEvent + { + Message = $"获得物品: {e.ItemId} x{e.Count}" + }); + } + + private void OnItemUsed(ItemUsedEvent e) + { + var inventoryModel = this.GetModel(); + var playerModel = this.GetModel(); + + if (inventoryModel.HasItem(e.ItemId)) + { + // 应用物品效果 + ApplyItemEffect(e.ItemId, playerModel); + + // 移除物品 + inventoryModel.RemoveItem(e.ItemId, 1); + + this.SendEvent(new ItemEffectAppliedEvent { ItemId = e.ItemId }); + } + } + + private void ApplyItemEffect(string itemId, PlayerModel player) + { + // 物品效果逻辑... + if (itemId == "health_potion") + { + player.Health.Value = Math.Min( + player.Health.Value + 50, + player.MaxHealth.Value + ); + } + } + + private void CheckAchievements(string itemId) + { + // 成就检查逻辑... + } +} +``` + +### 2. 定时更新的 System + +```csharp +public class BuffSystem : AbstractSystem +{ + private List _activeBuffs = new(); + + protected override void OnInit() + { + this.RegisterEvent(OnBuffApplied); + this.RegisterEvent(OnUpdate); + } + + private void OnBuffApplied(BuffAppliedEvent e) + { + _activeBuffs.Add(new BuffData + { + BuffId = e.BuffId, + Duration = e.Duration, + RemainingTime = e.Duration + }); + + ApplyBuffEffect(e.BuffId, true); + } + + private void OnUpdate(GameUpdateEvent e) + { + // 更新所有 Buff + for (int i = _activeBuffs.Count - 1; i >= 0; i--) + { + var buff = _activeBuffs[i]; + buff.RemainingTime -= e.DeltaTime; + + if (buff.RemainingTime <= 0) + { + // Buff 过期 + ApplyBuffEffect(buff.BuffId, false); + _activeBuffs.RemoveAt(i); + + this.SendEvent(new BuffExpiredEvent { BuffId = buff.BuffId }); + } + } + } + + private void ApplyBuffEffect(string buffId, bool apply) + { + var playerModel = this.GetModel(); + // 应用或移除 Buff 效果... + } +} +``` + +### 3. 跨 System 协作 + +```csharp +public class QuestSystem : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnEnemyKilled); + this.RegisterEvent(OnItemCollected); + } + + private void OnEnemyKilled(EnemyKilledEvent e) + { + var questModel = this.GetModel(); + var activeQuests = questModel.GetActiveQuests(); + + foreach (var quest in activeQuests) + { + if (quest.Type == QuestType.KillEnemy && quest.TargetId == e.EnemyType) + { + quest.Progress++; + + if (quest.Progress >= quest.RequiredAmount) + { + // 任务完成 + CompleteQuest(quest.Id); + } + } + } + } + + private void CompleteQuest(string questId) + { + var questModel = this.GetModel(); + var quest = questModel.GetQuest(questId); + + // 标记任务完成 + questModel.CompleteQuest(questId); + + // 发放奖励(通过其他 System) + this.SendEvent(new QuestCompletedEvent + { + QuestId = questId, + Rewards = quest.Rewards + }); + } +} + +public class RewardSystem : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnQuestCompleted); + } + + private void OnQuestCompleted(QuestCompletedEvent e) + { + var playerModel = this.GetModel(); + + // 发放奖励 + foreach (var reward in e.Rewards) + { + switch (reward.Type) + { + case RewardType.Gold: + playerModel.Gold.Value += reward.Amount; + break; + case RewardType.Experience: + playerModel.Experience.Value += reward.Amount; + break; + case RewardType.Item: + this.SendEvent(new ItemAddedEvent + { + ItemId = reward.ItemId, + Count = reward.Amount + }); + break; + } + } + + this.SendEvent(new RewardsGrantedEvent { Rewards = e.Rewards }); + } +} +``` + +### 4. 管理复杂状态机 + +```csharp +public class GameStateSystem : AbstractSystem +{ + private GameState _currentState = GameState.MainMenu; + + protected override void OnInit() + { + this.RegisterEvent(OnStateChangeRequest); + } + + private void OnStateChangeRequest(GameStateChangeRequestEvent e) + { + if (CanTransition(_currentState, e.TargetState)) + { + ExitState(_currentState); + _currentState = e.TargetState; + EnterState(_currentState); + + this.SendEvent(new GameStateChangedEvent + { + PreviousState = _currentState, + NewState = e.TargetState + }); + } + } + + private bool CanTransition(GameState from, GameState to) + { + // 状态转换规则 + return (from, to) switch + { + (GameState.MainMenu, GameState.Playing) => true, + (GameState.Playing, GameState.Paused) => true, + (GameState.Paused, GameState.Playing) => true, + (GameState.Playing, GameState.GameOver) => true, + _ => false + }; + } + + private void EnterState(GameState state) + { + switch (state) + { + case GameState.Playing: + // 开始游戏 + this.SendCommand(new StartGameCommand()); + break; + case GameState.Paused: + // 暂停游戏 + this.SendEvent(new GamePausedEvent()); + break; + case GameState.GameOver: + // 游戏结束 + this.SendCommand(new GameOverCommand()); + break; + } + } + + private void ExitState(GameState state) + { + // 清理当前状态 + } +} +``` + +## System vs Model + +### Model(数据层) + +- **职责**:存储数据和状态 +- **特点**:被动,等待修改 +- **示例**:PlayerModel、InventoryModel + +### System(逻辑层) + +- **职责**:处理业务逻辑,协调 Model +- **特点**:主动,响应事件 +- **示例**:CombatSystem、QuestSystem + +```csharp +// ✅ 正确的职责划分 + +// Model: 存储数据 +public class PlayerModel : AbstractModel +{ + public BindableProperty Health { get; } = new(100); + public BindableProperty Mana { get; } = new(50); + + protected override void OnInit() { } +} + +// System: 处理逻辑 +public class CombatSystem : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnAttack); + } + + private void OnAttack(AttackEvent e) + { + var playerModel = this.GetModel(); + + // System 负责计算和决策 + int damage = CalculateDamage(e); + playerModel.Health.Value -= damage; + + if (playerModel.Health.Value <= 0) + { + this.SendEvent(new PlayerDiedEvent()); + } + } +} +``` + +## 最佳实践 + +1. **单一职责** - 每个 System 专注于一个业务领域 +2. **事件驱动** - 通过事件与其他组件通信 +3. **无状态或少状态** - 优先将状态存储在 Model 中 +4. **可组合** - System 之间通过事件松耦合协作 +5. **初始化注册** - 在 `OnInit` 中注册所有事件监听 + +## 性能优化 + +### 1. 避免频繁的 GetModel/GetSystem + +```csharp +// ❌ 不好:每次都获取 +private void OnUpdate(GameUpdateEvent e) +{ + var model = this.GetModel(); // 频繁调用 + // ... +} + +// ✅ 好:缓存引用 +private PlayerModel _playerModel; + +protected override void OnInit() +{ + _playerModel = this.GetModel(); // 只获取一次 +} + +private void OnUpdate(GameUpdateEvent e) +{ + // 直接使用缓存的引用 + _playerModel.Health.Value += 1; +} +``` + +### 2. 批量处理 + +```csharp +public class ParticleSystem : AbstractSystem +{ + private List _particles = new(); + + private void OnUpdate(GameUpdateEvent e) + { + // 批量更新,而不是每个粒子发一个事件 + for (int i = _particles.Count - 1; i >= 0; i--) + { + UpdateParticle(_particles[i], e.DeltaTime); + } + } +} +``` + +## 相关包 + +- [`model`](./model.md) - System 操作 Model 的数据 +- [`events`](./events.md) - System 通过事件通信 +- [`command`](./command.md) - System 中可以发送 Command +- [`query`](./query.md) - System 中可以发送 Query +- [`utility`](./utility.md) - System 可以使用 Utility +- [`architecture`](./architecture.md) - 在架构中注册 System \ No newline at end of file diff --git a/docs/core/utility.md b/docs/core/utility.md new file mode 100644 index 0000000..711484c --- /dev/null +++ b/docs/core/utility.md @@ -0,0 +1,613 @@ +# Utility 包使用说明 + +## 概述 + +Utility 包定义了工具类层。Utility 提供无状态的辅助功能,如数学计算、文件操作、序列化等通用工具方法。与 System 不同,Utility +不依赖架构状态,是纯粹的工具函数集合。 + +## 核心接口 + +### IUtility + +Utility 标记接口,所有工具类都应实现此接口。 + +**接口定义:** + +```csharp +public interface IUtility +{ + // 标记接口,无方法定义 +} +``` + +### IContextUtility + +上下文工具接口,扩展了IUtility接口,为需要感知架构上下文的工具类提供基础能力。 + +**接口定义:** + +```csharp +public interface IContextUtility : IUtility +{ + void Init(); // 初始化上下文工具 +} +``` + +## 核心类 + +### [`AbstractContextUtility`](AbstractContextUtility.cs) + +抽象上下文工具类,提供上下文相关的通用功能实现。继承自 ContextAwareBase 并实现 IContextUtility 接口。 + +**使用方式:** + +```csharp +public abstract class AbstractContextUtility : ContextAwareBase, IContextUtility +{ + protected ILogger Logger = null!; + + void IContextUtility.Init() + { + var name = GetType().Name; + Logger = LoggerFactoryResolver.Provider.CreateLogger(name); + Logger.Debug($"Initializing Context Utility: {name}"); + + OnInit(); // 子类实现初始化逻辑 + + Logger.Info($"Context Utility initialized: {name}"); + } + + protected abstract void OnInit(); // 子类实现具体的初始化逻辑 +} +``` + +## 基本使用 + +### 1. 定义 Utility + +```csharp +// 存储工具类,继承自AbstractContextUtility +public class StorageUtility : AbstractContextUtility +{ + private const string SavePath = "user://save_data.json"; + + protected override void OnInit() + { + Logger.Info("StorageUtility initialized"); + } + + public void Save(T data) + { + string json = JsonSerializer.Serialize(data); + // 实际保存逻辑 + File.WriteAllText(SavePath, json); + } + + public T Load() + { + if (!File.Exists(SavePath)) + return default(T); + + string json = File.ReadAllText(SavePath); + return JsonSerializer.Deserialize(json); + } + + public void Delete() + { + if (File.Exists(SavePath)) + { + File.Delete(SavePath); + } + } +} + +// 数学工具类,作为普通Utility +public class MathUtility : IUtility +{ + public float Lerp(float a, float b, float t) + { + return a + (b - a) * Math.Clamp(t, 0f, 1f); + } + + public bool IsInRange(float value, float min, float max) + { + return value >= min && value <= max; + } +} + +// 时间工具类 +public class TimeUtility : IUtility +{ + public string FormatTime(float seconds) + { + int minutes = (int)(seconds / 60); + int secs = (int)(seconds % 60); + return $"{minutes:D2}:{secs:D2}"; + } + + public long GetCurrentTimestamp() + { + return DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + } + + public bool IsExpired(long timestamp, int durationSeconds) + { + return GetCurrentTimestamp() > timestamp + durationSeconds; + } +} +``` + +### 2. 注册 Utility + +```csharp +public class GameArchitecture : Architecture +{ + protected override void Init() + { + // 注册 Utility(不需要初始化) + this.RegisterUtility(new StorageUtility()); + this.RegisterUtility(new MathUtility()); + this.RegisterUtility(new TimeUtility()); + } +} +``` + +### 3. 使用 Utility + +```csharp +// 在 System 中使用 +public class SaveSystem : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnSaveGame); + this.RegisterEvent(OnLoadGame); + } + + private void OnSaveGame(SaveGameEvent e) + { + var storage = this.GetUtility(); + var playerModel = this.GetModel(); + + var saveData = new SaveData + { + PlayerName = playerModel.Name.Value, + Level = playerModel.Level.Value, + Gold = playerModel.Gold.Value, + Timestamp = this.GetUtility().GetCurrentTimestamp() + }; + + storage.Save(saveData); + this.SendEvent(new GameSavedEvent()); + } + + private void OnLoadGame(LoadGameEvent e) + { + var storage = this.GetUtility(); + var saveData = storage.Load(); + + if (saveData != null) + { + var playerModel = this.GetModel(); + playerModel.Name.Value = saveData.PlayerName; + playerModel.Level.Value = saveData.Level; + playerModel.Gold.Value = saveData.Gold; + + this.SendEvent(new GameLoadedEvent()); + } + } +} + +// 在 Command 中使用 +public class MovePlayerCommand : AbstractCommand +{ + public Vector3 TargetPosition { get; set; } + public float Speed { get; set; } + + protected override void OnExecute() + { + var playerModel = this.GetModel(); + var mathUtil = this.GetUtility(); + + // 使用工具类计算 + Vector3 currentPos = playerModel.Position.Value; + Vector3 direction = (TargetPosition - currentPos).Normalized(); + Vector3 newPos = currentPos + direction * Speed; + + playerModel.Position.Value = newPos; + } +} +``` + +## 常见 Utility 类型 + +### 1. 序列化/反序列化工具 + +```csharp +public class JsonUtility : IUtility +{ + public string Serialize(T obj) + { + return Json.Stringify(obj); + } + + public T Deserialize(string json) where T : new() + { + return Json.Parse(json); + } + + public bool TryDeserialize(string json, out T result) where T : new() + { + try + { + result = Json.Parse(json); + return true; + } + catch + { + result = default; + return false; + } + } +} +``` + +### 2. 随机数工具 + +```csharp +public class RandomUtility : IUtility +{ + private Random _random = new Random(); + + public int Range(int min, int max) + { + return _random.Next(min, max + 1); + } + + public float Range(float min, float max) + { + return min + (float)_random.NextDouble() * (max - min); + } + + public T Choose(params T[] items) + { + return items[Range(0, items.Length - 1)]; + } + + public List Shuffle(List list) + { + var shuffled = new List(list); + for (int i = shuffled.Count - 1; i > 0; i--) + { + int j = Range(0, i); + (shuffled[i], shuffled[j]) = (shuffled[j], shuffled[i]); + } + return shuffled; + } + + public bool Probability(float chance) + { + return _random.NextDouble() < chance; + } +} +``` + +### 3. 字符串工具 + +```csharp +public class StringUtility : IUtility +{ + public string Truncate(string text, int maxLength, string suffix = "...") + { + if (text.Length <= maxLength) + return text; + return text.Substring(0, maxLength - suffix.Length) + suffix; + } + + public string FormatNumber(int number) + { + if (number >= 1000000) + return $"{number / 1000000.0:F1}M"; + if (number >= 1000) + return $"{number / 1000.0:F1}K"; + return number.ToString(); + } + + public string ToTitleCase(string text) + { + return System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(text.ToLower()); + } + + public bool IsValidEmail(string email) + { + return Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$"); + } +} +``` + +### 4. 加密工具 + +```csharp +public class EncryptionUtility : IUtility +{ + private const string EncryptionKey = "YourSecretKey123"; + + public string Encrypt(string plainText) + { + byte[] data = System.Text.Encoding.UTF8.GetBytes(plainText); + byte[] encrypted = EncryptBytes(data); + return Convert.ToBase64String(encrypted); + } + + public string Decrypt(string encryptedText) + { + byte[] data = Convert.FromBase64String(encryptedText); + byte[] decrypted = DecryptBytes(data); + return System.Text.Encoding.UTF8.GetString(decrypted); + } + + private byte[] EncryptBytes(byte[] data) + { + // 简单的 XOR 加密示例(实际项目应使用更安全的算法) + byte[] key = System.Text.Encoding.UTF8.GetBytes(EncryptionKey); + byte[] result = new byte[data.Length]; + for (int i = 0; i < data.Length; i++) + { + result[i] = (byte)(data[i] ^ key[i % key.Length]); + } + return result; + } + + private byte[] DecryptBytes(byte[] data) + { + return EncryptBytes(data); // XOR 解密与加密相同 + } +} +``` + +### 5. 对象池工具 + +```csharp +public class ObjectPoolUtility : IUtility +{ + private Dictionary> _pools = new(); + + public T Get() where T : new() + { + Type type = typeof(T); + if (_pools.ContainsKey(type) && _pools[type].Count > 0) + { + return (T)_pools[type].Dequeue(); + } + return new T(); + } + + public void Return(T obj) + { + Type type = typeof(T); + if (!_pools.ContainsKey(type)) + { + _pools[type] = new Queue(); + } + _pools[type].Enqueue(obj); + } + + public void Clear() + { + Type type = typeof(T); + if (_pools.ContainsKey(type)) + { + _pools[type].Clear(); + } + } + + public void ClearAll() + { + _pools.Clear(); + } +} +``` + +### 6. 日志工具 + +```csharp +public class LogUtility : IUtility +{ + public enum LogLevel + { + Debug, + Info, + Warning, + Error + } + + public void Log(string message, LogLevel level = LogLevel.Info) + { + string prefix = level switch + { + LogLevel.Debug => "[DEBUG]", + LogLevel.Info => "[INFO]", + LogLevel.Warning => "[WARN]", + LogLevel.Error => "[ERROR]", + _ => "" + }; + + string timestamp = DateTime.Now.ToString("HH:mm:ss"); + GD.Print($"{timestamp} {prefix} {message}"); + } + + public void Debug(string message) => Log(message, LogLevel.Debug); + public void Info(string message) => Log(message, LogLevel.Info); + public void Warning(string message) => Log(message, LogLevel.Warning); + public void Error(string message) => Log(message, LogLevel.Error); +} +``` + +## Utility vs System + +### Utility(工具层) + +- **无状态** - 不存储业务数据 +- **纯函数** - 相同输入产生相同输出 +- **独立性** - 不依赖架构状态 +- **可复用** - 可在多个项目中使用 + +### System(逻辑层) + +- **有状态** - 可能存储临时状态 +- **业务逻辑** - 处理特定业务流程 +- **架构依赖** - 需要访问 Model +- **项目特定** - 针对特定项目设计 + +```csharp +// ✅ Utility: 无状态的工具方法 +public class MathUtility : IUtility +{ + public float CalculateDamage(float attackPower, float defense) + { + return Math.Max(1, attackPower - defense * 0.5f); + } +} + +// ✅ System: 有状态的业务逻辑 +public class CombatSystem : AbstractSystem +{ + private List _activeCombats = new(); + + protected override void OnInit() + { + this.RegisterEvent(OnAttack); + } + + private void OnAttack(AttackEvent e) + { + var mathUtil = this.GetUtility(); + var playerModel = this.GetModel(); + + // 使用 Utility 计算,但在 System 中处理业务逻辑 + float damage = mathUtil.CalculateDamage(e.AttackPower, playerModel.Defense.Value); + playerModel.Health.Value -= (int)damage; + + _activeCombats.Add(new CombatInstance { Damage = damage }); + } +} +``` + +## 最佳实践 + +1. **保持无状态** - Utility 不应存储业务状态 +2. **纯函数优先** - 相同输入应产生相同输出 +3. **单一职责** - 每个 Utility 专注于一类功能 +4. **避免依赖** - 不依赖 Model、System 等架构组件 +5. **可测试** - 易于单元测试的纯函数 + +## 错误示例 + +```csharp +// ❌ 错误:Utility 中存储状态 +public class BadUtility : IUtility +{ + private int _counter = 0; // 不应该有状态 + + public int GetNextId() + { + return ++_counter; // 依赖内部状态 + } +} + +// ❌ 错误:Utility 中访问 Model +public class BadUtility2 : IUtility +{ + public void DoSomething() + { + // Utility 不应该访问架构组件 + var model = this.GetModel(); // 编译错误! + } +} + +// ✅ 正确:如果需要状态,应该使用 System +public class IdGeneratorSystem : AbstractSystem +{ + private int _counter = 0; + + protected override void OnInit() { } + + public int GetNextId() + { + return ++_counter; + } +} +``` + +## 性能优化 + +### 1. 缓存计算结果 + +```csharp +public class PathfindingUtility : IUtility +{ + private Dictionary<(Vector3, Vector3), List> _pathCache = new(); + + public List FindPath(Vector3 start, Vector3 end, bool useCache = true) + { + var key = (start, end); + + if (useCache && _pathCache.ContainsKey(key)) + { + return _pathCache[key]; + } + + var path = CalculatePath(start, end); + + if (useCache) + { + _pathCache[key] = path; + } + + return path; + } + + private List CalculatePath(Vector3 start, Vector3 end) + { + // A* 算法等复杂计算... + return new List(); + } + + public void ClearCache() + { + _pathCache.Clear(); + } +} +``` + +### 2. 对象复用 + +```csharp +public class CollectionUtility : IUtility +{ + private List _tempList = new(); + + public List GetPointsInRadius(Vector3 center, float radius, List points) + { + _tempList.Clear(); + + foreach (var point in points) + { + if (point.DistanceTo(center) <= radius) + { + _tempList.Add(point); + } + } + + return new List(_tempList); // 返回副本 + } +} +``` + +## 相关包 + +- [`system`](./system.md) - System 中使用 Utility +- [`command`](./command.md) - Command 中可以使用 Utility +- [`architecture`](./architecture.md) - 在架构中注册 Utility +- [`ioc`](./ioc.md) - Utility 通过 IoC 容器管理 +- [`extensions`](./extensions.md) - 提供 GetUtility 扩展方法 \ No newline at end of file diff --git a/docs/game/setting.md b/docs/game/setting.md new file mode 100644 index 0000000..12a3f1a --- /dev/null +++ b/docs/game/setting.md @@ -0,0 +1,204 @@ +# 设置系统 (Settings System) + +## 概述 + +设置系统是 GFramework.Game 的核心组件之一,负责管理游戏中各种设置配置。该系统采用了模型-系统分离的设计模式,支持设置部分(Section)的管理和设置应用器模式。 + +## 核心类 + +### SettingsModel + +设置模型类,继承自 `AbstractModel` 并实现 `ISettingsModel` 接口。 + +**主要功能:** + +- 管理不同类型的设置部分(Settings Section) +- 提供类型安全的设置访问 +- 支持可应用设置对象的注册 + +**关键方法:** + +- `Get()` - 获取或创建指定类型的设置部分 +- `TryGet(Type, out ISettingsSection)` - 尝试获取设置部分 +- `Register(IApplyAbleSettings)` - 注册可应用的设置对象 +- `All()` - 获取所有设置部分 + +### SettingsSystem + +设置系统类,继承自 `AbstractSystem` 并实现 `ISettingsSystem` 接口。 + +**主要功能:** + +- 应用设置配置到相应系统 +- 支持单个或批量设置应用 +- 自动识别可应用设置类型 + +**关键方法:** + +- `ApplyAll()` - 应用所有设置配置 +- `Apply()` - 应用指定类型的设置 +- `Apply(IEnumerable)` - 应用指定类型集合的设置 + +## 架构设计 + +```mermaid +graph TD + A[ISettingsModel] --> B[SettingsModel] + C[ISettingsSystem] --> D[SettingsSystem] + + B --> E[Dictionary] + D --> B + + F[ISettingsSection] --> G[IApplyAbleSettings] + H[AudioSettings] --> G + I[GraphicsSettings] --> G + + E --> H + E --> I + + J[Application] --> D + D --> G +``` + +## 使用示例 + +### 基本使用 + +```csharp +// 获取设置模型 +var settingsModel = this.GetModel(); + +// 获取或创建音频设置 +var audioSettings = settingsModel.Get(); +audioSettings.MasterVolume = 0.8f; +audioSettings.BgmVolume = 0.6f; +audioSettings.SfxVolume = 0.9f; + +// 注册设置到模型 +settingsModel.Register(audioSettings); +``` + +### 应用设置 + +```csharp +// 获取设置系统 +var settingsSystem = this.GetSystem(); + +// 应用所有设置 +await settingsSystem.ApplyAll(); + +// 应用特定类型设置 +await settingsSystem.Apply(); + +// 应用多个类型设置 +var types = new[] { typeof(GodotAudioSettings), typeof(GodotGraphicsSettings) }; +await settingsSystem.Apply(types); +``` + +### 创建自定义设置 + +```csharp +/// +/// 游戏设置类 +/// +public class GameSettings : ISettingsSection +{ + public float GameSpeed { get; set; } = 1.0f; + public int Difficulty { get; set; } = 1; + public bool AutoSave { get; set; } = true; +} + +// 使用自定义设置 +var gameSettings = settingsModel.Get(); +gameSettings.GameSpeed = 1.5f; +``` + +### 创建可应用设置 + +```csharp +/// +/// 游戏设置应用器 +/// +public class GameSettings : ISettingsSection, IApplyAbleSettings +{ + public float GameSpeed { get; set; } = 1.0f; + public int Difficulty { get; set; } = 1; + + public Task Apply() + { + // 应用游戏速度 + Time.timeScale = GameSpeed; + + // 应用难度设置 + GameDifficulty.Current = Difficulty; + + return Task.CompletedTask; + } +} +``` + +## 接口定义 + +### ISettingsSection + +```csharp +public interface ISettingsSection +{ + // 设置部分的标识接口 +} +``` + +### IApplyAbleSettings + +```csharp +public interface IApplyAbleSettings : ISettingsSection +{ + Task Apply(); +} +``` + +### ISettingsModel + +```csharp +public interface ISettingsModel +{ + T Get() where T : class, ISettingsSection, new(); + bool TryGet(Type type, out ISettingsSection section); + IEnumerable All(); + void Register(IApplyAbleSettings applyAble); +} +``` + +### ISettingsSystem + +```csharp +public interface ISettingsSystem +{ + Task ApplyAll(); + Task Apply() where T : class, ISettingsSection; + Task Apply(Type settingsType); + Task Apply(IEnumerable settingsTypes); +} +``` + +## 设计模式 + +该系统使用了以下设计模式: + +1. **Repository Pattern** - SettingsModel 作为设置数据的仓库 +2. **Command Pattern** - IApplyAbleSettings 的 Apply 方法作为命令 +3. **Factory Pattern** - Get() 方法创建设置实例 +4. **Template Method** - AbstractSystem 提供初始化模板 + +## 最佳实践 + +1. **设置分类** - 将相关设置组织到同一个设置类中 +2. **延迟应用** - 批量修改后再应用,而不是每次修改都应用 +3. **类型安全** - 使用泛型方法确保类型安全 +4. **可测试性** - 通过接口实现便于单元测试 + +## 相关链接 + +- [Godot 设置模块](../../GFramework.Godot/setting/README.md) +- [存储模块](../../GFramework.Godot/storage/README.md) +- [抽象接口定义](../../../GFramework.Core/Abstractions/) \ No newline at end of file diff --git a/docs/godot/extensions.md b/docs/godot/extensions.md new file mode 100644 index 0000000..737c3fa --- /dev/null +++ b/docs/godot/extensions.md @@ -0,0 +1,335 @@ +# Godot 扩展方法 (Godot Extensions) + +## 概述 + +Godot 扩展方法模块为 Godot 引擎提供了丰富的便捷扩展方法集合。这些扩展方法简化了常见的 Godot +开发任务,提高了代码的可读性和开发效率。该模块遵循流畅接口设计原则,支持链式调用。 + +## 模块结构 + +```mermaid +graph TD + A[Extensions] --> B[GodotPathExtensions] + A --> C[NodeExtensions] + A --> D[SignalFluentExtensions] + A --> E[UnRegisterExtension] + D --> F[SignalBuilder] + + B --> G[路径判断扩展] + C --> H[节点生命周期] + C --> I[节点查询] + C --> J[场景树操作] + C --> K[输入控制] + C --> L[调试工具] + D --> M[信号连接系统] + E --> N[事件管理] +``` + +## 扩展模块详解 + +### 1. 路径扩展 (GodotPathExtensions) + +提供 Godot 虚拟路径的判断和识别功能。 + +**主要方法:** + +- `IsUserPath()` - 判断是否为 `user://` 路径 +- `IsResPath()` - 判断是否为 `res://` 路径 +- `IsGodotPath()` - 判断是否为 Godot 虚拟路径 + +**使用示例:** + +```csharp +string savePath = "user://save.dat"; +string configPath = "res://config.json"; +string logPath = "C:/logs/debug.log"; + +if (savePath.IsUserPath()) Console.WriteLine("用户数据路径"); +if (configPath.IsResPath()) Console.WriteLine("资源路径"); +if (logPath.IsGodotPath()) Console.WriteLine("Godot 虚拟路径"); +else Console.WriteLine("文件系统路径"); +``` + +### 2. 节点扩展 (NodeExtensions) + +最丰富的扩展模块,提供全面的节点操作功能。 + +#### 节点生命周期管理 + +```csharp +// 安全释放节点 +node.QueueFreeX(); // 延迟释放 +node.FreeX(); // 立即释放 + +// 等待节点就绪 +await node.WaitUntilReady(); + +// 检查节点有效性 +if (node.IsValidNode()) Console.WriteLine("节点有效"); +if (node.IsInvalidNode()) Console.WriteLine("节点无效"); +``` + +#### 节点查询操作 + +```csharp +// 查找子节点 +var sprite = node.FindChildX("Sprite"); +var parent = node.GetParentX(); + +// 获取或创建节点 +var panel = parent.GetOrCreateNode("MainPanel"); + +// 遍历子节点 +node.ForEachChild(sprite => { + sprite.Modulate = Colors.White; +}); +``` + +#### 场景树操作 + +```csharp +// 获取根节点 +var root = node.GetRootNodeX(); + +// 异步添加子节点 +await parent.AddChildX(childNode); + +// 设置场景树暂停状态 +node.Paused(true); // 暂停 +node.Paused(false); // 恢复 +``` + +#### 输入控制 + +```csharp +// 标记输入事件已处理 +node.SetInputAsHandled(); + +// 禁用/启用输入 +node.DisableInput(); +node.EnableInput(); +``` + +#### 调试工具 + +```csharp +// 打印节点路径 +node.LogNodePath(); + +// 打印节点树 +node.PrintTreeX(); + +// 安全延迟调用 +node.SafeCallDeferred("UpdateUI"); +``` + +#### 类型转换 + +```csharp +// 安全的类型转换 +var button = node.OfType