From ebdc231c073e763fdd4988784820f5ede3ea0eb2 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Sat, 18 Apr 2026 10:08:05 +0800 Subject: [PATCH] =?UTF-8?q?docs(sdk):=20=E6=9B=B4=E6=96=B0=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E8=A7=84=E8=8C=83=E5=B9=B6=E6=B7=BB=E5=8A=A0VitePress?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 调整文档真实性原则,以源码和测试为首要证据源 - 新增模块README要求,规定所有用户包必须有说明文档 - 更新仓库文档规范,要求根README与文档站点分类一致 - 添加VitePress配置文件,支持中文搜索和泛型转义 - 创建入门指南文档,说明GFramework模块组成和接入路径 - 添加快速开始教程,演示Core模块最小使用示例 - 为Core模块添加详细README文档 - 为Core.Abstractions添加契约层说明文档 - 为Core.SourceGenerators添加源码生成器文档 - 为Game模块添加运行时层详细说明文档 --- AGENTS.md | 67 ++- GFramework.Core.Abstractions/README.md | 62 ++- GFramework.Core.SourceGenerators/README.md | 112 +++-- GFramework.Core/README.md | 100 +++-- GFramework.Cqrs.Abstractions/README.md | 101 +++++ GFramework.Cqrs.SourceGenerators/README.md | 67 +++ GFramework.Cqrs/README.md | 154 +++++++ GFramework.Game.Abstractions/README.md | 241 ++++++++++- GFramework.Game.SourceGenerators/README.md | 70 ++++ GFramework.Game/README.md | 344 ++++++++++++++- README.md | 140 +++---- docs/.vitepress/config.mts | 1 + docs/zh-CN/abstractions/index.md | 20 + docs/zh-CN/getting-started/index.md | 466 +++++---------------- docs/zh-CN/getting-started/quick-start.md | 326 +++----------- 15 files changed, 1452 insertions(+), 819 deletions(-) create mode 100644 GFramework.Cqrs.Abstractions/README.md create mode 100644 GFramework.Cqrs.SourceGenerators/README.md create mode 100644 GFramework.Cqrs/README.md create mode 100644 GFramework.Game.SourceGenerators/README.md create mode 100644 docs/zh-CN/abstractions/index.md diff --git a/AGENTS.md b/AGENTS.md index 720b53af..4fd3b7b8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -237,23 +237,32 @@ bash scripts/validate-csharp-naming.sh - If a framework abstraction changes meaning or intended usage, update the explanatory comments in code as part of the same change. -### Task Tracking +### Documentation Source Of Truth -- When working from a tracked implementation plan, contributors MUST update the corresponding tracking document under - `local-plan/todos/` in the same change. -- Tracking updates MUST reflect completed work, newly discovered issues, validation results, and the next recommended - recovery point. -- Completing code changes without updating the active tracking document is considered incomplete work. -- For any multi-step refactor, migration, or cross-module task, contributors MUST create or adopt a dedicated recovery - document under `local-plan/todos/` before making substantive code changes. -- Recovery documents MUST record the current phase, the active recovery point identifier, known risks, and the next - recommended resume step so another contributor or subagent can continue the work safely. -- Contributors MUST maintain a matching execution trace under `local-plan/traces/` for complex work. The trace should - record the current date, key decisions, validation milestones, and the immediate next step. -- When a task spans multiple commits or is likely to exceed a single agent context window, update both the recovery - document and the trace at each meaningful milestone before pausing or handing work off. -- If subagents are used on a complex task, the main agent MUST capture the delegated scope and any accepted findings in - the active recovery document or trace before continuing implementation. +- Treat source code, `*.csproj`, tests, generated snapshots, and packaging metadata as the primary evidence for + documentation updates. +- Treat `CoreGrid` as a secondary evidence source for real project adoption patterns, directory layouts, and end-to-end + usage examples. +- Treat existing `README.md` files and `docs/zh-CN/` pages as editable outputs, not authoritative truth. +- If existing documentation conflicts with code or tests, update the documentation to match the implementation instead + of preserving outdated wording. +- Do not publish example code, setup steps, or package guidance that cannot be traced back to code, tests, or a + verified consumer project. + +### Module README Requirements + +- Every user-facing package or module directory that contains a `*.csproj` intended for direct consumption MUST have a + sibling `README.md`. +- Use the canonical filename `README.md`. Do not introduce new `ReadMe.md` or other filename variants. +- A module README MUST describe: + - the module's purpose + - the relationship to adjacent runtime, abstractions, or generator packages + - the major subdirectories or subsystems the reader is expected to use + - the minimum adoption path + - the corresponding `docs/zh-CN/` entry points +- Adding a new top-level module directory without a `README.md` is considered incomplete work. +- If a module's responsibilities, setup, public API surface, generator inputs, or adoption path change, update that + module's `README.md` in the same change. ### Repository Documentation @@ -273,6 +282,32 @@ bash scripts/validate-csharp-naming.sh documentation is considered incomplete work. - Do not rely on “the code is self-explanatory” for framework features that consumers need to adopt; write the adoption path down so future users do not need to rediscover it from source. +- The repository root `README.md` MUST mirror the current top-level documentation taxonomy used by the docs site. + Do not maintain a second, differently named navigation system in the root README. +- Prefer linking the root `README.md` to section landing pages such as `index.md` instead of deep-linking to a single + article when the target is intended to be a documentation category. +- If a docs category appears in VitePress navigation or sidebar, it MUST have a real landing page or be removed from + navigation in the same change. +- When examples are rewritten, preserve only the parts that remain true. Delete or replace speculative examples instead + of lightly editing them into another inaccurate form. + +### Task Tracking + +- When working from a tracked implementation plan, contributors MUST update the corresponding tracking document under + `local-plan/todos/` in the same change. +- Tracking updates MUST reflect completed work, newly discovered issues, validation results, and the next recommended + recovery point. +- Completing code changes without updating the active tracking document is considered incomplete work. +- For any multi-step refactor, migration, or cross-module task, contributors MUST create or adopt a dedicated recovery + document under `local-plan/todos/` before making substantive code changes. +- Recovery documents MUST record the current phase, the active recovery point identifier, known risks, and the next + recommended resume step so another contributor or subagent can continue the work safely. +- Contributors MUST maintain a matching execution trace under `local-plan/traces/` for complex work. The trace should + record the current date, key decisions, validation milestones, and the immediate next step. +- When a task spans multiple commits or is likely to exceed a single agent context window, update both the recovery + document and the trace at each meaningful milestone before pausing or handing work off. +- If subagents are used on a complex task, the main agent MUST capture the delegated scope and any accepted findings in + the active recovery document or trace before continuing implementation. ### Documentation Preview diff --git a/GFramework.Core.Abstractions/README.md b/GFramework.Core.Abstractions/README.md index 632868f4..4be033ab 100644 --- a/GFramework.Core.Abstractions/README.md +++ b/GFramework.Core.Abstractions/README.md @@ -1,27 +1,51 @@ # GFramework.Core.Abstractions -GFramework 框架的抽象层定义模块,包含所有核心组件的接口定义。 +`GFramework.Core.Abstractions` 承载 `Core` 运行时对应的接口、枚举和值对象,用来定义跨模块协作边界。 -## 主要内容 +## 什么时候单独依赖它 -- 架构核心接口 (IArchitecture, IArchitectureContext等) -- 数据模型接口 (IModel) -- 业务系统接口 (ISystem) -- 控制器接口 (IController) -- 命令与查询接口 (ICommand, IQuery) -- 事件系统接口 (IEvent, IEventBus) -- 依赖注入容器接口 (IIocContainer) -- 可绑定属性接口 (IBindableProperty) -- 状态管理接口 (IStore, IReducer, IStateSelector, IStoreBuilder) -- 日志系统接口 (ILogger) +- 你在做插件、适配层或扩展包,只想依赖契约,不想把完整运行时拉进来 +- 你需要为测试、编辑器工具或生成器提供替身实现 +- 你在做多模块拆分,希望上层只面向接口编程 -## 设计原则 +如果你只是直接使用框架功能,优先安装 `GFramework.Core`。 -- 接口隔离,每个接口职责单一 -- 依赖倒置,上层依赖抽象接口 -- 类型安全,充分利用泛型系统 -- 广泛兼容,基于 netstandard2.0 +## 包关系 -## 详细文档 +- 契约层:`GFramework.Core.Abstractions` +- 实现层:`GFramework.Core` +- 相关扩展: + - `GFramework.Cqrs.Abstractions` + - `GFramework.Game.Abstractions` -参见 [docs/zh-CN/abstractions/](../docs/zh-CN/abstractions/) 目录下的详细文档。 +## 契约地图 + +| 目录 | 作用 | +| --- | --- | +| `Architectures/` | `IArchitecture`、模块、阶段监听与服务管理契约 | +| `Command/` / `Query/` | 旧版命令与查询执行器接口 | +| `Controller/` | `IController` | +| `Events/` | 事件契约、解绑接口与传播上下文 | +| `Model/` / `Systems/` / `Utility/` | 核心组件接口 | +| `State/` / `StateManagement/` | 状态机、Store、reducer、selector 契约 | +| `Property/` | `IBindableProperty` 与只读属性接口 | +| `Resource/` | 资源管理与释放策略契约 | +| `Localization/` | 本地化表、格式化与异常类型 | +| `Logging/` | logger、log entry、factory 相关契约 | +| `Ioc/` | `IIocContainer` | +| `Lifecycle/` | 初始化 / 销毁生命周期契约 | +| `Coroutine/` | 时间源、yield 指令与协程状态枚举 | +| `Pause/` | 暂停栈、token 与状态事件 | +| `Storage/` / `Serializer/` / `Versioning/` | 通用存储、序列化与版本化契约 | + +## 采用建议 + +- 框架消费者通常同时安装 `GFramework.Core` 与 `GFramework.Core.Abstractions` +- 若你只需要对接口编程,可以仅引用本包,再在应用层自行提供实现 +- 若你在写上层模块,优先把公共契约放在 `*.Abstractions`,实现放在对应 runtime 包 + +## 对应文档 + +- 抽象接口栏目:[`../docs/zh-CN/abstractions/index.md`](../docs/zh-CN/abstractions/index.md) +- Core 抽象页:[`../docs/zh-CN/abstractions/core-abstractions.md`](../docs/zh-CN/abstractions/core-abstractions.md) +- Core 运行时入口:[`../GFramework.Core/README.md`](../GFramework.Core/README.md) diff --git a/GFramework.Core.SourceGenerators/README.md b/GFramework.Core.SourceGenerators/README.md index 0d60714c..e644afc5 100644 --- a/GFramework.Core.SourceGenerators/README.md +++ b/GFramework.Core.SourceGenerators/README.md @@ -1,63 +1,95 @@ -# GFramework.SourceGenerators +# GFramework.Core.SourceGenerators -Core 侧通用源码生成器模块。 +`GFramework.Core.SourceGenerators` 承载 Core 侧的通用源码生成器与分析器,用来减少样板代码并把部分约束前移到编译期。 -## Context Get 注入 +## 模块定位 -当类本身是上下文感知类型时,可以通过字段特性生成一个手动调用的注入方法: +这个包属于编译期工具链,不是运行时库。 -- `[GetService]` -- `[GetServices]` -- `[GetSystem]` -- `[GetSystems]` +当前仓库中的主要目录: + +- `Architectures/` +- `Analyzers/` +- `Bases/` +- `Enums/` +- `Logging/` +- `Rule/` +- `Diagnostics/` + +对应的生成器家族主要包括: + +- 日志相关生成器 +- `ContextAware` 及上下文注入辅助 +- 枚举扩展生成器 +- 优先级相关生成器 +- 模块自动注册 +- 注册可见性分析器 + +## 包关系 + +- 运行时:`GFramework.Core` +- 契约与特性:`GFramework.Core.SourceGenerators.Abstractions` +- 公共生成器支撑:`GFramework.SourceGenerators.Common` + +如果你还需要游戏配置 schema 生成或 Godot 专用生成器,应分别安装: + +- `GFramework.Game.SourceGenerators` +- `GFramework.Godot.SourceGenerators` + +## 主要能力 + +### 上下文注入与 `ContextAware` + +该包支持围绕上下文感知类型生成辅助代码,例如: + +- `[ContextAware]` - `[GetModel]` - `[GetModels]` +- `[GetSystem]` +- `[GetSystems]` - `[GetUtility]` - `[GetUtilities]` +- `[GetService]` +- `[GetServices]` - `[GetAll]` -上下文感知类满足以下任一条件即可: +这类生成器适合用于 View、Controller、Godot 节点包装或其他需要频繁访问架构上下文的类型。 -- 类上带有 `[ContextAware]` -- 继承 `ContextAwareBase` -- 实现 `IContextAware` +### 日志辅助 -生成器会生成 `__InjectContextBindings_Generated()`,需要在合适的生命周期中手动调用。在 Godot 中通常放在 `_Ready()`: +支持通过生成器减少 `ILogger` 相关样板代码。 -```csharp -using GFramework.SourceGenerators.Abstractions.Rule; +### 注册分析器 -[ContextAware] -public partial class InventoryPanel -{ - [GetModel] - private IInventoryModel _inventory = null!; +包内还包含分析器,用来检查 `Model`、`System`、`Utility` 的使用点是否能在所属架构中找到静态可见注册,帮助尽早发现“代码可以编译、运行时却缺注册”的问题。 - [GetServices] - private IReadOnlyList _strategies = null!; +## 最小接入路径 - public override void _Ready() - { - __InjectContextBindings_Generated(); - } -} +```xml + + + ``` -`[GetAll]` 作用于类本身,会自动扫描字段并推断 `Model`、`System`、`Utility` 相关的 `GetX` 调用;已显式标记字段的优先级更高。 +如果你想查看生成代码,可在消费者项目里启用: -`Service` 和 `Services` 绑定不会在 `[GetAll]` 下自动推断。对于普通引用类型字段,请显式使用 `[GetService]` 或 -`[GetServices]`,避免将非上下文服务字段误判为服务依赖。 +```xml + + true + Generated + +``` -`[GetAll]` 会跳过 `const`、`static` 和 `readonly` 字段。若某个字段本来会被 `[GetAll]` 推断为 -`Model`、`System` 或 `Utility` 绑定,但因为是不可赋值的 `static` 或 `readonly` 字段而被跳过,生成器会发出警告提示该字段不会参与生成。 +## 适用场景 -## 注册分析器 +- 你希望减少上下文绑定与日志相关样板代码 +- 你需要在编译期发现部分注册可见性问题 +- 你在做模块化架构,希望固定某些重复注册模式 -包现在同时包含一个注册可见性分析器,用于检查 `Model`、`System`、`Utility` 的使用点是否能在所属架构中找到静态可见注册。 +## 对应文档 -- 覆盖字段特性注入:`[GetModel]`、`[GetModels]`、`[GetSystem]`、`[GetSystems]`、`[GetUtility]`、`[GetUtilities]` -- 覆盖手写调用:`GetModel()`、`GetModels()`、`GetSystem()`、`GetSystems()`、`GetUtility()`、`GetUtilities()` -- 默认报告 `Warning` -- 当前只分析静态可见的注册路径,例如 `OnInitialize()`、`InstallModules()`、`InstallModule(new Module())` - -对于反射、运行时条件分支、外部程序集动态注册等路径,分析器不会强行推断;当无法唯一确定组件所属架构时,也会选择不报,优先降低误报。 +- 源码生成器总览:[`../docs/zh-CN/source-generators/index.md`](../docs/zh-CN/source-generators/index.md) +- Core 栏目:[`../docs/zh-CN/core/index.md`](../docs/zh-CN/core/index.md) diff --git a/GFramework.Core/README.md b/GFramework.Core/README.md index 47fc0aa7..8287bd24 100644 --- a/GFramework.Core/README.md +++ b/GFramework.Core/README.md @@ -1,32 +1,84 @@ # GFramework.Core -GFramework 框架的核心模块,提供MVC架构的基础设施。 +`GFramework.Core` 是框架的基础运行时,负责架构生命周期、组件注册、上下文访问,以及不依赖具体引擎的通用能力。 -## 主要功能 +如果你只想先把框架跑起来,应先从这个模块开始。 -- **Architecture** - 应用程序架构管理,支持依赖注入、生命周期管理和模块化扩展 -- **Model** - 数据模型层,管理应用状态和数据 -- **System** - 业务逻辑层,处理核心业务逻辑和事件响应 -- **Controller** - 控制器层,处理用户输入和UI协调 -- **Command** - 命令模式实现,封装用户操作 -- **Query** - 查询模式实现,支持CQRS架构 -- **Events** - 事件系统,实现组件间松耦合通信 -- **IoC** - 轻量级依赖注入容器 -- **Property** - 可绑定属性,支持数据绑定和响应式编程 -- **StateManagement** - 集中式状态容器,支持状态归约、选择器和诊断 -- **Utility** - 无状态工具类 -- **Pool** - 对象池系统,减少GC压力 -- **Extensions** - 框架扩展方法 -- **Logging** - 日志系统 -- **Environment** - 环境配置管理 +## 模块定位 -## 设计原则 +这一层提供: -- 与平台解耦,不依赖特定游戏引擎 -- 接口隔离,职责单一 -- 依赖倒置,面向接口编程 -- 组合优于继承 +- `Architecture` 与 `ArchitectureContext` +- `Model` / `System` / `Utility` 运行时 +- 旧版 `Command` / `Query` 执行器 +- 事件、属性、状态机、状态管理 +- 资源、日志、协程、并发、环境与本地化 -## 详细文档 +它不负责: -参见 [docs/zh-CN/core/](../docs/zh-CN/core/) 目录下的详细文档。 +- 游戏内容配置、Scene / UI / Storage 等游戏层能力 +- Godot 节点与场景集成 +- 新版 CQRS 请求模型的消息契约定义 + +## 包关系 + +- 直接依赖: + - `GFramework.Cqrs` + - `GFramework.Cqrs.Abstractions` + - `GFramework.Core.Abstractions` +- 常见上层模块: + - `GFramework.Game` + - `GFramework.Godot` + +如果你只需要契约,不需要实现层,改为依赖 [`../GFramework.Core.Abstractions/README.md`](../GFramework.Core.Abstractions/README.md)。 + +## 子系统地图 + +| 目录 | 作用 | +| --- | --- | +| `Architectures/` | 架构入口、上下文、生命周期、模块安装与组件注册 | +| `Command/` | 旧版命令执行器与同步 / 异步命令基类 | +| `Query/` | 旧版查询执行器与同步 / 异步查询基类 | +| `Events/` | 事件总线、事件作用域、统计与过滤 | +| `Property/` | `BindableProperty` 与相关解绑对象 | +| `State/` | 状态机与状态切换事件 | +| `StateManagement/` | Store、selector、middleware 与状态诊断 | +| `Coroutine/` | 协程调度、快照、统计与优先级 | +| `Resource/` | 资源缓存、句柄和释放策略 | +| `Logging/` | logger、factory、配置与组合日志器 | +| `Ioc/` | 基于 `Microsoft.Extensions.DependencyInjection` 的容器适配 | +| `Concurrency/` | 键控异步锁与统计 | +| `Pause/` | 暂停栈和暂停范围 | +| `Localization/` | 本地化表与格式化入口 | +| `Functional/` | `Option`、`Result` 等轻量函数式工具 | +| `Extensions/` | 上下文与集合等扩展方法 | + +## 最小接入路径 + +```bash +dotnet add package GeWuYou.GFramework.Core +dotnet add package GeWuYou.GFramework.Core.Abstractions +``` + +最小入口: + +1. 继承 `Architecture` +2. 在 `OnInitialize()` 中注册模型、系统、工具或模块 +3. 通过 `architecture.Context` 或 `ContextAwareBase` 的扩展方法访问上下文 + +最小示例见: + +- [`../docs/zh-CN/getting-started/quick-start.md`](../docs/zh-CN/getting-started/quick-start.md) + +## 什么时候继续接别的包 + +- 需要推荐的新请求模型:加 `GFramework.Cqrs` +- 需要游戏层路由、设置、配置和存储:加 `GFramework.Game` +- 需要 Godot 节点与场景适配:加 `GFramework.Godot` +- 需要编译期生成日志、上下文注入或模块注册:加 `GFramework.Core.SourceGenerators` + +## 对应文档 + +- Core 栏目:[`../docs/zh-CN/core/index.md`](../docs/zh-CN/core/index.md) +- CQRS:[`../docs/zh-CN/core/cqrs.md`](../docs/zh-CN/core/cqrs.md) +- 入门指南:[`../docs/zh-CN/getting-started/index.md`](../docs/zh-CN/getting-started/index.md) diff --git a/GFramework.Cqrs.Abstractions/README.md b/GFramework.Cqrs.Abstractions/README.md new file mode 100644 index 00000000..38166b8b --- /dev/null +++ b/GFramework.Cqrs.Abstractions/README.md @@ -0,0 +1,101 @@ +# GFramework.Cqrs.Abstractions + +`GFramework.Cqrs.Abstractions` 提供 GFramework CQRS 的最小契约层。它只包含消息接口、处理器接口、运行时 seam 和管道契约,不包含默认 dispatcher、处理器扫描或任何 `GFramework.Core` 运行时实现。适合以下场景: + +- 你的业务程序集只需要声明 Command、Query、Notification、Stream Request 或处理器接口。 +- 你希望把消息契约放在更稳定的基础层,避免直接依赖默认 runtime 实现。 +- 你要为其他运行时、测试环境或自定义容器实现兼容的 CQRS 接口。 + +## 模块定位 + +- 这是 CQRS 的“协议层”。 +- 目标框架为 `netstandard2.1`,用于被更上层模块稳定引用。 +- 当前包不负责处理器自动注册,也不负责请求分发。 + +如果你需要默认消息基类、处理器基类、上下文扩展方法和运行时实现,请使用 `GeWuYou.GFramework.Cqrs`。 + +## 包关系 + +推荐按职责引用: + +- `GeWuYou.GFramework.Cqrs.Abstractions` + - 提供 `IRequest`、`INotification`、`IStreamRequest`、`IRequestHandler<,>`、`INotificationHandler<>`、`IPipelineBehavior<,>`、`ICqrsRuntime`、`ICqrsContext`、`Unit` 等基础契约。 +- `GeWuYou.GFramework.Cqrs` + - 引用本包,并提供默认 runtime、处理器注册、消息基类、处理器基类、上下文扩展方法。 +- `GeWuYou.GFramework.Cqrs.SourceGenerators` + - 可选。面向消费端程序集生成 `ICqrsHandlerRegistry` 注册表,减少冷启动反射扫描;未生成或不适用时,运行时仍会回退到反射注册。 + +## 子系统地图 + +本包当前可以分为几类契约: + +- 消息契约 + - `Cqrs/IRequest.cs` + - `Cqrs/INotification.cs` + - `Cqrs/IStreamRequest.cs` + - `Cqrs/Command/ICommand.cs` + - `Cqrs/Query/IQuery.cs` + - `Cqrs/Request/IRequestInput.cs` + - `Cqrs/Command/ICommandInput.cs` + - `Cqrs/Query/IQueryInput.cs` + - `Cqrs/Notification/INotificationInput.cs` +- 处理器契约 + - `Cqrs/IRequestHandler.cs` + - `Cqrs/INotificationHandler.cs` + - `Cqrs/IStreamRequestHandler.cs` +- 运行时 seam + - `Cqrs/ICqrsRuntime.cs` + - `Cqrs/ICqrsContext.cs` + - `Cqrs/ICqrsHandlerRegistrar.cs` +- 管道与辅助类型 + - `Cqrs/IPipelineBehavior.cs` + - `Cqrs/MessageHandlerDelegate.cs` + - `Cqrs/Unit.cs` + +## 最小接入路径 + +如果你只想在基础层定义一个可被上层 runtime 消费的 Query,可以只依赖本包: + +```bash +dotnet add package GeWuYou.GFramework.Cqrs.Abstractions +``` + +```csharp +using GFramework.Cqrs.Abstractions.Cqrs.Query; + +public sealed record GetPlayerProfileInput(int PlayerId) : IQueryInput; + +public sealed class GetPlayerProfileQuery : IQuery +{ + public GetPlayerProfileQuery(GetPlayerProfileInput input) + { + Input = input; + } + + public GetPlayerProfileInput Input { get; } +} + +public sealed class GetPlayerProfileHandler + : IRequestHandler +{ + public ValueTask Handle( + GetPlayerProfileQuery request, + CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } +} +``` + +这条路径适合“只声明契约”的场景。真正执行分发时,仍需要上层提供 `ICqrsRuntime` 和处理器注册流程,通常由 `GeWuYou.GFramework.Cqrs` 与 `GFramework.Core` 完成。 + +## 使用边界 + +- 只引用本包时,没有 `CommandBase`、`QueryBase`、`NotificationBase` 等消息基类。 +- 只引用本包时,没有 `AbstractCommandHandler`、`AbstractQueryHandler`、`AbstractNotificationHandler` 等处理器基类。 +- `ICqrsContext` 当前是轻量 marker seam;默认 runtime 在需要向 `IContextAware` 处理器注入上下文时,仍要求传入的上下文同时实现 `IArchitectureContext`。 + +## 文档入口 + +- 运行时与整体接入说明:`docs/zh-CN/core/cqrs.md` +- 如果你需要默认实现而不是契约层,请看 `GFramework.Cqrs/README.md` diff --git a/GFramework.Cqrs.SourceGenerators/README.md b/GFramework.Cqrs.SourceGenerators/README.md new file mode 100644 index 00000000..18b9f4f5 --- /dev/null +++ b/GFramework.Cqrs.SourceGenerators/README.md @@ -0,0 +1,67 @@ +# GFramework.Cqrs.SourceGenerators + +`GFramework.Cqrs.SourceGenerators` 用于在编译期为当前业务程序集生成 CQRS handler registry,减少运行时程序集扫描与反射注册成本。 + +## 模块定位 + +这个包是编译期生成器,不是运行时消息或处理器库。 + +生成器会分析当前业务程序集中的: + +- `IRequestHandler<,>` +- `INotificationHandler<>` +- `IStreamRequestHandler<,>` + +并生成: + +- `ICqrsHandlerRegistry` 实现 +- 程序集级 `CqrsHandlerRegistryAttribute` +- 必要时的 `CqrsReflectionFallbackAttribute` 元数据 + +## 包关系 + +- 运行时:`GFramework.Cqrs` +- 契约层:`GFramework.Cqrs.Abstractions` +- 生成器:`GFramework.Cqrs.SourceGenerators` + +不安装这个包也可以正常使用 CQRS;区别只在于运行时会更多依赖反射扫描注册 handlers。 + +## 当前代码入口 + +仓库内该包的主要实现位于: + +- `Cqrs/CqrsHandlerRegistryGenerator.cs` + +它会在可以安全生成静态注册器时前移注册工作;对无法由生成代码直接引用的 handler,则通过 reflection fallback 元数据让运行时做定向补扫,而不是整程序集盲扫。 + +## 最小接入路径 + +```xml + + + + + +``` + +运行时侧仍按正常方式注册程序集: + +```csharp +RegisterCqrsHandlersFromAssembly(typeof(GameArchitecture).Assembly); +``` + +安装生成器后,运行时会优先走生成的 registry;无法静态表达的部分再走定向回退。 + +## 什么时候值得安装 + +- 你的业务程序集里 handler 数量较多 +- 你希望缩小冷启动时的反射扫描范围 +- 你需要把 handler 注册路径收束到编译期并保持可诊断 + +## 对应文档 + +- CQRS 栏目:[`../docs/zh-CN/core/cqrs.md`](../docs/zh-CN/core/cqrs.md) +- 源码生成器总览:[`../docs/zh-CN/source-generators/index.md`](../docs/zh-CN/source-generators/index.md) diff --git a/GFramework.Cqrs/README.md b/GFramework.Cqrs/README.md new file mode 100644 index 00000000..656a90d3 --- /dev/null +++ b/GFramework.Cqrs/README.md @@ -0,0 +1,154 @@ +# GFramework.Cqrs + +`GFramework.Cqrs` 是 GFramework 的默认 CQRS runtime 包。它在 `GFramework.Cqrs.Abstractions` 之上提供请求分发、通知发布、流式请求处理、处理器注册、上下文扩展方法,以及消息/处理器基类,面向直接使用 GFramework CQRS 的业务模块。 + +## 模块定位 + +- 这是 CQRS 的“默认实现层”。 +- 包内同时承载运行时基础设施和面向业务代码的便捷基类。 +- 它依赖 `GFramework.Cqrs.Abstractions` 与 `GFramework.Core.Abstractions`。 +- 在标准 GFramework 架构启动路径中,`GFramework.Core` 的 `CqrsRuntimeModule` 会把默认 runtime、处理器注册器与注册服务自动接入容器。 + +如果你只需要声明跨模块共享的消息契约,而不想依赖默认 runtime,请改为引用 `GeWuYou.GFramework.Cqrs.Abstractions`。 + +## 包关系 + +推荐的依赖关系如下: + +- `GeWuYou.GFramework.Cqrs.Abstractions` + - 最小 CQRS 契约层。 +- `GeWuYou.GFramework.Cqrs` + - 默认 runtime 与业务侧常用基类。 +- `GeWuYou.GFramework.Cqrs.SourceGenerators` + - 可选。为消费端程序集生成 `ICqrsHandlerRegistry`,运行时优先走生成注册表;缺失或不适用时,回退到反射扫描。 +- `GFramework.Core` + - 架构上下文中实际调用 `ICqrsRuntime`,并在模块初始化时注册 CQRS 基础设施。 + +## 子系统地图 + +本包当前主要由以下子系统组成: + +- 消息基类 + - `Command/CommandBase.cs` + - `Query/QueryBase.cs` + - `Request/RequestBase.cs` + - `Notification/NotificationBase.cs` +- 处理器基类 + - `Cqrs/CqrsContextAwareHandlerBase.cs` + - `Cqrs/Command/AbstractCommandHandler.cs` + - `Cqrs/Query/AbstractQueryHandler.cs` + - `Cqrs/Notification/AbstractNotificationHandler.cs` + - `Cqrs/Request/AbstractRequestHandler.cs` + - `Cqrs/Request/AbstractStreamRequestHandler.cs` + - `Cqrs/Command/AbstractStreamCommandHandler.cs` + - `Cqrs/Query/AbstractStreamQueryHandler.cs` +- 请求管道 + - `Cqrs/Behaviors/LoggingBehavior.cs` + - `Cqrs/Behaviors/PerformanceBehavior.cs` + - 管道契约定义在 `GFramework.Cqrs.Abstractions` 的 `IPipelineBehavior<,>` +- 默认 runtime 与注册入口 + - `CqrsRuntimeFactory.cs` + - `Internal/CqrsDispatcher.cs` + - `Internal/CqrsHandlerRegistrar.cs` + - `Internal/DefaultCqrsHandlerRegistrar.cs` + - `Internal/DefaultCqrsRegistrationService.cs` +- 生成注册表协作接口 + - `ICqrsHandlerRegistry.cs` + - `CqrsHandlerRegistryAttribute.cs` + - `CqrsReflectionFallbackAttribute.cs` +- 业务侧扩展入口 + - `Extensions/ContextAwareCqrsExtensions.cs` + - `Extensions/ContextAwareCqrsCommandExtensions.cs` + - `Extensions/ContextAwareCqrsQueryExtensions.cs` + +## 最小接入路径 + +在标准 GFramework 架构中,最小接入通常是“安装包 + 定义消息 + 定义处理器 + 通过上下文发送”: + +```bash +dotnet add package GeWuYou.GFramework.Cqrs +dotnet add package GeWuYou.GFramework.Cqrs.Abstractions +``` + +如果你希望减少处理器注册时的反射扫描,再额外安装: + +```bash +dotnet add package GeWuYou.GFramework.Cqrs.SourceGenerators +``` + +示例: + +```csharp +using GFramework.Cqrs.Command; +using GFramework.Cqrs.Cqrs.Command; +using GFramework.Cqrs.Abstractions.Cqrs.Command; + +public sealed record CreatePlayerInput(string Name) : ICommandInput; + +public sealed class CreatePlayerCommand : CommandBase +{ + public CreatePlayerCommand(CreatePlayerInput input) : base(input) + { + } +} + +public sealed class CreatePlayerCommandHandler + : AbstractCommandHandler +{ + public override ValueTask Handle( + CreatePlayerCommand command, + CancellationToken cancellationToken) + { + var playerModel = Context.GetModel(); + var playerId = playerModel.Create(command.Input.Name); + return ValueTask.FromResult(playerId); + } +} +``` + +在 `IContextAware` 对象内发送命令: + +```csharp +using GFramework.Cqrs.Extensions; + +var playerId = await this.SendAsync(new CreatePlayerCommand(new CreatePlayerInput("Alice"))); +``` + +在 `ArchitectureContext` 上也可以直接使用统一 CQRS 入口,例如 `SendRequestAsync`、`SendQueryAsync`、`PublishAsync` 和 `CreateStream`。 + +## 运行时行为 + +- 请求分发 + - `CqrsDispatcher` 按请求实际类型解析 `IRequestHandler<,>`,未找到处理器会抛出异常。 +- 通知分发 + - 通知会分发给所有已注册 `INotificationHandler<>`;零处理器时默认静默完成。 +- 流式请求 + - 通过 `IStreamRequest` 和 `IStreamRequestHandler<,>` 返回 `IAsyncEnumerable`。 +- 上下文注入 + - 处理器基类继承 `CqrsContextAwareHandlerBase`,runtime 会在分发前注入当前 `IArchitectureContext`。 + - 如果处理器或行为需要上下文注入,而当前 `ICqrsContext` 不是 `IArchitectureContext`,默认实现会抛出异常。 +- 管道行为 + - 所有已注册 `IPipelineBehavior` 会包裹请求处理器执行。 + - 当前包内提供了 `LoggingBehavior` 和 `PerformanceBehavior` 两个可复用行为。 + +## 处理器注册与程序集接入 + +默认注册流程由 `ICqrsRegistrationService.RegisterHandlers(IEnumerable)` 协调,语义是: + +- 同一程序集按稳定键去重,避免重复注册。 +- 优先尝试消费端程序集上的 `ICqrsHandlerRegistry` 生成注册器。 +- 生成注册器不可用,或声明了 `CqrsReflectionFallbackAttribute` 时,回退到反射扫描。 +- 处理器以 transient 方式注册,避免上下文感知处理器在并发请求间共享可变上下文。 + +如果你走标准 `GFramework.Core` 架构初始化路径,这些步骤通常由框架自动完成;裸容器或测试环境则需要显式补齐 runtime 与注册入口。 + +## 适用边界 + +- 这个包是默认实现,不是“纯契约包”。 +- 处理器基类依赖 runtime 在分发前注入上下文,不适合脱离 dispatcher 直接手动实例化后调用。 +- README 中的消息基类和 handler 基类位于 `GFramework.Cqrs`,接口契约位于 `GFramework.Cqrs.Abstractions`;最小示例通常需要同时引入这两个命名空间层级。 + +## 文档入口 + +- 总体文档:`docs/zh-CN/core/cqrs.md` +- 契约层说明:`GFramework.Cqrs.Abstractions/README.md` diff --git a/GFramework.Game.Abstractions/README.md b/GFramework.Game.Abstractions/README.md index fe14fe80..eb2031c7 100644 --- a/GFramework.Game.Abstractions/README.md +++ b/GFramework.Game.Abstractions/README.md @@ -1,13 +1,240 @@ # GFramework.Game.Abstractions -GFramework.Game 模块的抽象层,提供游戏业务相关的接口定义。 +`GFramework.Game.Abstractions` 是 `GFramework` 游戏层的契约包。 -## 主要内容 +它建立在 `GFramework.Core.Abstractions` 之上,只定义接口、枚举、路由上下文、事件契约和少量可直接复用的数据类型,不提供完整运行时实现。它的主要用途是把游戏业务层、引擎适配层、公共 feature 包与具体实现解耦。 -- 游戏业务常用抽象接口 -- 与 GFramework.Core.Abstractions 配合使用的契约 +如果你需要可直接运行的默认实现,请改为依赖 `GFramework.Game`。 -## 使用建议 +## 包定位 -- 若需直接使用完整游戏扩展能力,优先使用 GFramework.Game -- 若在做模块拆分或需要解耦,可单独依赖本包 +- 为游戏相关子系统提供稳定契约,而不是默认实现。 +- 适合被多个程序集共同引用,避免公共业务层直接依赖具体运行时。 +- 典型使用场景: + - 定义 `IScene`、`IUiPage`、`ISettingsData`、`IData` 等业务对象 + - 让 feature 包只感知 `IConfigRegistry`、`ISaveRepository`、`ISettingsModel`、`IUiRouter`、`ISceneRouter` + - 在引擎适配层之外共享设置、场景参数、UI 参数、存档数据类型 + +## 与相邻包的关系 + +- `GFramework.Core.Abstractions` + - 本包直接依赖它。 + - 提供 `ISystem`、`IModel`、`IUtility`、上下文 utility 等底层抽象。 +- `GFramework.Game` + - 本包的主要实现层。 + - `FileStorage`、`ScopedStorage`、`JsonSerializer`、`SettingsModel`、`SaveRepository`、`SceneRouterBase`、`UiRouterBase`、`YamlConfigLoader` 等都在实现这里的契约。 +- 引擎适配包或项目代码 + - `IUiFactory`、`ISceneFactory`、`IUiRoot`、`ISceneRoot`、资源注册表等通常由引擎适配层或游戏项目自己实现。 + - CoreGrid 的真实结构也是这样:页面/场景 factory、root、registry 在项目层,运行时基类和契约来自 `GFramework.Game` 与本包。 + +## 子系统地图 + +### `Config/` + +静态内容配置的读取侧契约。 + +- `IConfigLoader` +- `IConfigRegistry` +- `IConfigTable` +- `ConfigLoadException` +- `ConfigLoadDiagnostic` +- `ConfigLoadFailureKind` + +这一层只描述“如何注册与访问配置表”,不关心底层来自 YAML、二进制还是远程源。 + +### `Data/` + +可写数据与存档契约。 + +- `IData` +- `IVersionedData` +- `IDataLocation` +- `IDataLocationProvider` +- `IDataRepository` +- `ISettingsDataRepository` +- `ISaveRepository` +- `ISaveMigration` +- `DataRepositoryOptions` +- `Data/Events/*` + +这一层让业务代码不需要知道数据最终按“单文件一项”还是“统一文件多 section”持久化。 + +### `Setting/` + +设置系统契约。 + +- `ISettingsData` +- `IResetApplyAbleSettings` +- `ISettingsModel` +- `ISettingsSystem` +- `ISettingsMigration` +- `ISettingsChangedEvent` +- `Setting/Data/*` + - 内置了 `AudioSettings`、`GraphicsSettings`、`LocalizationSettings` 三类常见设置数据 + +这里定义的是“设置生命周期和应用语义”,不限定具体引擎。 + +### `Scene/` + +场景导航契约。 + +- `IScene` +- `ISceneBehavior` +- `ISceneFactory` +- `ISceneRoot` +- `ISceneRouter` +- `ISceneTransitionHandler` +- `ISceneAroundTransitionHandler` +- `ISceneRouteGuard` +- `IGameSceneRegistry` + +如果你想把场景定义放在公共业务层,通常依赖本包就够了。 + +### `UI/` + +UI 页面与路由契约。 + +- `IUiPage` +- `IUiPageBehavior` +- `IUiFactory` +- `IUiRoot` +- `IUiRouter` +- `IUiTransitionHandler` +- `IUiAroundTransitionHandler` +- `IUiRouteGuard` +- `UiHandle` +- `UiTransitionHandlerOptions` +- `UiInteractionProfile` + +`IUiRouter` 不只覆盖页面栈,还覆盖 Overlay / Modal / Toast / Topmost 等层级 UI 语义。 + +### `Routing/` + +- `IRoute` +- `IRouteContext` +- `IRouteGuard` + +Scene 与 UI 路由共享这套基础约定。 + +### `Storage/` + +- `IFileStorage` +- `IScopedStorage` + +它们继承自核心层的 `IStorage`,用于表达“文件存储实现”和“带作用域前缀的存储实现”这两个角色。 + +### `Asset/` 与 `Enums/` + +- `Asset/` + - 资源注册表契约,如 `IAssetRegistry` +- `Enums/` + - UI/Scene 转场、UI 层级、输入动作、存储类型等公共枚举 + +## 最小接入路径 + +### 1. 只想在公共业务层声明游戏对象 + +直接依赖本包,定义业务数据和交互参数: + +```csharp +using GFramework.Game.Abstractions.Data; +using GFramework.Game.Abstractions.Scene; +using GFramework.Game.Abstractions.UI; + +public sealed class GameSaveData : IVersionedData +{ + public int Version { get; set; } = 1; + public DateTime LastModified { get; set; } = DateTime.UtcNow; +} + +public sealed class GameplayEnterParam : ISceneEnterParam +{ + public required string Seed { get; init; } +} + +public sealed class PauseMenuParam : IUiPageEnterParam +{ + public bool AllowRestart { get; init; } +} +``` + +这个阶段不需要把 `GFramework.Game` 一起带进来。 + +### 2. 只想让 feature 包依赖抽象,不绑定具体实现 + +直接面向接口编程: + +```csharp +public sealed class ContinueGameCommandHandler +{ + private readonly ISaveRepository _saveRepository; + private readonly ISceneRouter _sceneRouter; + + public ContinueGameCommandHandler( + ISaveRepository saveRepository, + ISceneRouter sceneRouter) + { + _saveRepository = saveRepository; + _sceneRouter = sceneRouter; + } +} +``` + +这样 feature 包不必知道底层到底是 `SaveRepository`、Godot 适配层,还是你自己的实现。 + +### 3. 什么时候要升级到 `GFramework.Game` + +一旦你需要下面任一项,就不该只停留在本包: + +- 默认的 JSON 序列化实现 +- 文件系统存储实现 +- 设置模型与系统实现 +- 槽位存档仓库实现 +- YAML 配置加载器 +- Scene / UI 路由基类 + +也就是说,本包回答的是“项目各层如何约定”,`GFramework.Game` 回答的是“这些约定默认怎么跑起来”。 + +## CoreGrid 里的真实用法线索 + +CoreGrid 对本包的使用方式,能比较清楚地说明它的职责边界: + +- 公共脚本广泛引用: + - `IUiRouter` + - `ISceneRouter` + - `ISettingsModel` + - `ISettingsSystem` + - `ISaveRepository` + - `IConfigRegistry` +- 业务数据和参数实现引用: + - `IData` / `IVersionedData` + - `ISceneEnterParam` + - `IUiPageEnterParam` +- 真正的实现和装配则放在: + - `GFramework.Game` + - `GFramework.Godot.*` + - CoreGrid 自己的模块、factory、root、registry + +这正是本包的设计目标:让业务层依赖稳定契约,而不是依赖具体运行时细节。 + +## 对应文档入口 + +虽然大部分详细文档写在 `GFramework.Game` 侧,但阅读顺序仍然适用于本包: + +- 游戏模块总览:[docs/zh-CN/game/index.md](../docs/zh-CN/game/index.md) +- 内容配置系统:[docs/zh-CN/game/config-system.md](../docs/zh-CN/game/config-system.md) +- 数据与存档:[docs/zh-CN/game/data.md](../docs/zh-CN/game/data.md) +- 设置系统:[docs/zh-CN/game/setting.md](../docs/zh-CN/game/setting.md) +- 存储系统:[docs/zh-CN/game/storage.md](../docs/zh-CN/game/storage.md) +- 序列化系统:[docs/zh-CN/game/serialization.md](../docs/zh-CN/game/serialization.md) +- 场景系统:[docs/zh-CN/game/scene.md](../docs/zh-CN/game/scene.md) +- UI 系统:[docs/zh-CN/game/ui.md](../docs/zh-CN/game/ui.md) + +## 选择建议 + +- 选 `GFramework.Game.Abstractions` + - 你在写共享业务层、公共 feature 包、纯契约层 +- 选 `GFramework.Game` + - 你需要默认实现、基础设施拼装、运行时启动入口 +- 两者一起用 + - 最常见。公共层依赖 abstractions,应用层或引擎层依赖 runtime diff --git a/GFramework.Game.SourceGenerators/README.md b/GFramework.Game.SourceGenerators/README.md new file mode 100644 index 00000000..050c4d16 --- /dev/null +++ b/GFramework.Game.SourceGenerators/README.md @@ -0,0 +1,70 @@ +# GFramework.Game.SourceGenerators + +`GFramework.Game.SourceGenerators` 负责把 `schemas/**/*.schema.json` 转成游戏内容配置类型和表包装代码。 + +它服务的核心场景是:让 `YAML` 配置、`JSON Schema`、运行时加载器和工具链共享一套结构定义。 + +## 模块定位 + +这个包是编译期生成器,不是运行时库。 + +它会在编译期读取 schema,并生成: + +- 配置数据类型 +- 对应的表包装类型 +- 与 `GFramework.Game.Config` 运行时协作的访问辅助代码 + +## 包关系 + +- 运行时:`GFramework.Game` +- 生成器:`GFramework.Game.SourceGenerators` +- 公共生成器支撑:`GFramework.SourceGenerators.Common` + +如果你的项目还会使用 `[Log]`、`[ContextAware]` 或 Core 侧上下文注入特性,还需要同时安装 +`GFramework.Core.SourceGenerators`。 + +## 目录与输入约定 + +当前项目结构显示该生成器主要围绕以下代码组织: + +- `Config/SchemaConfigGenerator.cs` +- `Diagnostics/ConfigSchemaDiagnostics.cs` +- `GeWuYou.GFramework.Game.SourceGenerators.targets` + +消费者项目的推荐目录约定: + +```text +GameProject/ +├─ config/ +│ └─ monster/ +│ └─ slime.yaml +└─ schemas/ + └─ monster.schema.json +``` + +默认情况下,打包产物会通过 `targets` 把 `schemas/**/*.schema.json` 纳入 `AdditionalFiles`。 + +## 最小接入路径 + +```xml + + + + +``` + +如果你在仓库内用 `ProjectReference` 调试,仍需要把对应 `targets` 接进消费者项目。 + +## 什么时候使用它 + +- 你想把静态游戏内容维护成 `YAML` +- 你希望在编译期拿到强类型配置访问入口 +- 你希望运行时加载、schema 校验和编辑工具链共用同一份结构定义 + +## 对应文档 + +- 配置系统:[`../docs/zh-CN/game/config-system.md`](../docs/zh-CN/game/config-system.md) +- 源码生成器总览:[`../docs/zh-CN/source-generators/index.md`](../docs/zh-CN/source-generators/index.md) diff --git a/GFramework.Game/README.md b/GFramework.Game/README.md index 0efaa231..9bc7f8ff 100644 --- a/GFramework.Game/README.md +++ b/GFramework.Game/README.md @@ -1,16 +1,344 @@ # GFramework.Game -GFramework 框架的游戏通用模块,提供游戏开发常用的功能。 +`GFramework.Game` 是 `GFramework` 面向游戏项目的运行时层。 -## 主要功能 +它建立在 `GFramework.Core` 与 `GFramework.Game.Abstractions` 之上,提供可直接落地的实现与基类,覆盖静态内容配置、数据与存档、设置、场景路由、UI 路由、序列化、文件存储、状态机扩展等常见游戏运行时需求。 -- **Settings** - 游戏设置系统,支持设置分类和配置应用 +如果你的项目只需要“契约”而不想带入实现,请依赖 `GFramework.Game.Abstractions`。如果你的项目需要真正可运行的默认实现或基类,请依赖本包。 -## 依赖关系 +## 包定位 -- 依赖 GFramework.Core -- 依赖 GFramework.Core.Abstractions +- 面向使用者的默认运行时实现层,不是纯接口包。 +- 适合作为游戏项目启动层、基础设施层、引擎适配层之上的通用运行时底座。 +- 当前最成熟、最明确的能力集中在: + - 静态内容配置:`Config/` + - 数据与存档:`Data/` + - 设置系统:`Setting/` + - 场景与 UI 路由基类:`Scene/`、`UI/` + - 序列化与文件存储:`Serializer/`、`Storage/` -## 详细文档 +## 与相邻包的关系 -参见 [docs/zh-CN/game/](../docs/zh-CN/game/) 目录下的详细文档。 +- `GFramework.Core` + - 本包直接依赖它。 + - 提供架构、上下文注入、事件、日志、系统/模型/utility 生命周期等底层能力。 +- `GFramework.Game.Abstractions` + - 本包的上游契约层。 + - 本包中的 `FileStorage`、`SettingsModel`、`SaveRepository`、`YamlConfigLoader`、`SceneRouterBase`、`UiRouterBase` 等都在实现这里定义的接口。 +- `GFramework.Game.SourceGenerators` + - 主要与配置系统配合使用。 + - 当你需要 schema 驱动的 YAML 配表时,运行时包是 `GFramework.Game`,生成时代码由 source generators 补齐。 +- 引擎适配包或项目内适配层 + - 本包提供的是“引擎无关”的核心逻辑和基类。 + - 真正和 Godot、Unity、MonoGame 等引擎对象打交道的工厂、根节点、资源注册表,通常在相邻引擎包或游戏项目内实现。 + - CoreGrid 的真实接法就是这样:配置文件 IO 由 `GFramework.Godot.Config` 适配,UI/Scene factory 与 root 由项目自己提供。 + +## 子系统地图 + +### `Config/` + +面向静态游戏内容的只读配置系统。 + +- `YamlConfigLoader` + - 从文件系统根目录加载 YAML 配置表,并注册到 `IConfigRegistry` +- `ConfigRegistry` + - 运行时配置表注册表 +- `GameConfigBootstrap` + - 非 `Architecture` 场景下的官方配置启动入口 +- `GameConfigModule` + - `Architecture` 场景下的官方配置接入模块 +- `InMemoryConfigTable` + - 配置表默认只读承载 +- `YamlConfigHotReloadOptions` + - 开发期热重载控制 + +这个子系统通常与 `GFramework.Game.SourceGenerators` 一起使用,而不是手写大量注册代码。 + +对应文档: + +- [docs/zh-CN/game/config-system.md](../docs/zh-CN/game/config-system.md) +- [docs/zh-CN/game/index.md](../docs/zh-CN/game/index.md) + +### `Data/` + +面向可写业务数据、设置持久化与存档槽位的仓库实现。 + +- `DataRepository` + - 一条 `IDataLocation` 对应一份持久化对象 +- `UnifiedSettingsDataRepository` + - 把多个设置 section 聚合到单一文件中 +- `SaveRepository` + - 面向槽位存档,支持版本迁移链 +- `SaveConfiguration` + - 槽位目录、文件名、前缀等约定 + +CoreGrid 的真实用法: + +- 设置持久化使用 `UnifiedSettingsDataRepository` +- 存档使用 `SaveRepository` +- 两者共用同一个底层存储 utility + +对应文档: + +- [docs/zh-CN/game/data.md](../docs/zh-CN/game/data.md) +- [docs/zh-CN/game/setting.md](../docs/zh-CN/game/setting.md) + +### `Setting/` + +设置生命周期编排层。 + +- `SettingsModel` + - 管理 `ISettingsData` 实例、迁移、加载、保存、重置 + - 编排 applicator 的 `Apply` +- `SettingsSystem` + - 面向业务代码暴露更直接的系统级入口 +- `Setting/Events/*` + - 设置初始化、应用、保存、重置相关事件 + +CoreGrid 的真实用法: + +- 在模型模块中创建 `SettingsModel` +- 注册多个 applicator +- 启动时先 `InitializeAsync()`,再 `ApplyAll()` +- 退出时统一 `SaveAll()` + +对应文档: + +- [docs/zh-CN/game/setting.md](../docs/zh-CN/game/setting.md) + +### `Storage/` + +面向本地文件系统的基础存储。 + +- `FileStorage` + - 基于目录与文件的 `IStorage` 实现 + - 负责路径清洗、细粒度锁、原子写入、层级 key 到目录结构的映射 +- `ScopedStorage` + - 为底层存储增加前缀作用域 + +这部分能力经常被 `DataRepository`、`SaveRepository`、`UnifiedSettingsDataRepository` 复用。 + +对应文档: + +- [docs/zh-CN/game/storage.md](../docs/zh-CN/game/storage.md) +- [GFramework.Game/Storage/ReadMe.md](./Storage/ReadMe.md) + +### `Serializer/` + +- `JsonSerializer` + - 当前默认序列化实现 + - 同时可作为 `ISerializer` 与 `IRuntimeTypeSerializer` + +它通常先于存储和数据仓库被注册。 + +对应文档: + +- [docs/zh-CN/game/serialization.md](../docs/zh-CN/game/serialization.md) + +### `Scene/` 与 `UI/` + +面向游戏导航的可复用基类,不直接绑定具体引擎。 + +- `SceneRouterBase` + - 依赖 `ISceneFactory`、`ISceneRoot` + - 提供栈式场景路由与转换处理管道 +- `UiRouterBase` + - 依赖 `IUiFactory`、`IUiRoot` + - 提供页面栈、Overlay/Modal/Toast 等层级 UI、输入动作分发、暂停联动 +- `Scene/Handler/*`、`UI/Handler/*` + - 默认转换处理器基类与日志处理器 + +CoreGrid 的真实用法: + +- 项目自定义 `SceneRouter : SceneRouterBase` +- 项目自定义 `UiRouter : UiRouterBase` +- 工厂、注册表、root 都由项目或引擎适配层提供 + +对应文档: + +- [docs/zh-CN/game/scene.md](../docs/zh-CN/game/scene.md) +- [docs/zh-CN/game/ui.md](../docs/zh-CN/game/ui.md) + +### `Routing/` 与 `State/` + +- `Routing/RouterBase` + - Scene/UI 路由共享基类 +- `State/GameStateMachineSystem` + - 对核心状态机系统的游戏向封装 + +这两部分一般被上层子系统消费,不是多数项目的第一接入点。 + +## 最小接入路径 + +下面按最常见的四种接入目标给出最短路径。 + +### 1. 只想先拿到文件存储 + +```csharp +using GFramework.Core.Abstractions.Serializer; +using GFramework.Core.Abstractions.Storage; +using GFramework.Game.Serializer; +using GFramework.Game.Storage; + +ISerializer serializer = new JsonSerializer(); +IStorage storage = new FileStorage("GameData", serializer); + +await storage.WriteAsync("player/profile", new { Name = "Alice", Level = 3 }); +``` + +如果你需要逻辑隔离,再包一层 `ScopedStorage`: + +```csharp +var settingsStorage = new ScopedStorage(storage, "settings"); +``` + +### 2. 接入设置和存档 + +运行时最小拼装顺序通常是: + +1. 注册 `JsonSerializer` +2. 注册一个 `IStorage` 实现 +3. 注册 `ISettingsDataRepository` +4. 创建并注册 `SettingsModel` +5. 注册 applicator +6. 注册 `SettingsSystem` +7. 注册 `ISaveRepository` + +示意代码: + +```csharp +var serializer = new JsonSerializer(); +var storage = new FileStorage("GameData", serializer); + +architecture.RegisterUtility(serializer); +architecture.RegisterUtility(storage); + +architecture.RegisterUtility( + new UnifiedSettingsDataRepository( + storage, + serializer, + new DataRepositoryOptions { BasePath = "settings", AutoBackup = true })); + +architecture.RegisterModel( + new SettingsModel( + new MySettingsLocationProvider(), + architecture.Context.GetUtility()) + .RegisterApplicator(new MyAudioSettingsApplicator())); + +architecture.RegisterSystem(new SettingsSystem()); + +architecture.RegisterUtility>( + new SaveRepository( + storage, + new SaveConfiguration + { + SaveRoot = "saves", + SaveSlotPrefix = "slot_", + SaveFileName = "save.json" + })); +``` + +启动时: + +```csharp +await settingsModel.InitializeAsync(); +await settingsSystem.ApplyAll(); +``` + +退出前: + +```csharp +await settingsSystem.SaveAll(); +``` + +CoreGrid 目前就是按这个思路接入,只是底层存储换成了 Godot 适配实现。 + +### 3. 接入静态 YAML 配置 + +如果你不走 `Architecture` 生命周期,直接使用 `GameConfigBootstrap`: + +```csharp +var bootstrap = new GameConfigBootstrap( + new GameConfigBootstrapOptions + { + RootPath = contentRootPath, + ConfigureLoader = static loader => + { + loader.RegisterAllGeneratedConfigTables(); + } + }); + +await bootstrap.InitializeAsync(); + +var registry = bootstrap.Registry; +``` + +如果你走 `Architecture`,优先使用 `GameConfigModule`,并在较早阶段安装。 + +这一能力几乎总是与 source generators 绑定使用。目录、schema、生成器与热重载约定请直接看: + +- [docs/zh-CN/game/config-system.md](../docs/zh-CN/game/config-system.md) + +### 4. 接入 Scene / UI 路由 + +这里的最小前提不是“直接 new 一个 router”,而是先补齐运行时依赖: + +- `ISceneFactory` / `IUiFactory` +- `ISceneRoot` / `IUiRoot` +- 具体页面或场景行为实现 + +然后让项目自己的 router 继承基类: + +```csharp +public sealed class MySceneRouter : SceneRouterBase +{ + protected override void RegisterHandlers() + { + RegisterHandler(new LoggingTransitionHandler()); + } +} + +public sealed class MyUiRouter : UiRouterBase +{ + protected override void RegisterHandlers() + { + RegisterHandler(new GFramework.Game.UI.Handler.LoggingTransitionHandler()); + } +} +``` + +这类 router 适合作为你的项目层或引擎适配层代码,而不是直接修改本包。 + +## CoreGrid 里的真实用法线索 + +当前仓库内,CoreGrid 对本包的使用大致分成三层: + +- 配置 + - `CoreGridConfigHost` 使用生成表元数据与 YAML loader 完成配置注册 +- 设置与存档 + - `UtilityModule` 注册序列化器、底层存储、`UnifiedSettingsDataRepository`、`SaveRepository` + - `ModelModule` 创建 `SettingsModel` 并注册 applicator +- 路由 + - `SceneRouter` 继承 `SceneRouterBase` + - `UiRouter` 继承 `UiRouterBase` + +这说明本包更适合做“游戏基础设施层”,而不是把所有引擎对象耦死在包内部。 + +## 文档入口 + +- 游戏模块总览:[docs/zh-CN/game/index.md](../docs/zh-CN/game/index.md) +- 内容配置系统:[docs/zh-CN/game/config-system.md](../docs/zh-CN/game/config-system.md) +- 数据与存档:[docs/zh-CN/game/data.md](../docs/zh-CN/game/data.md) +- 设置系统:[docs/zh-CN/game/setting.md](../docs/zh-CN/game/setting.md) +- 存储系统:[docs/zh-CN/game/storage.md](../docs/zh-CN/game/storage.md) +- 序列化系统:[docs/zh-CN/game/serialization.md](../docs/zh-CN/game/serialization.md) +- 场景系统:[docs/zh-CN/game/scene.md](../docs/zh-CN/game/scene.md) +- UI 系统:[docs/zh-CN/game/ui.md](../docs/zh-CN/game/ui.md) + +## 什么时候不该直接依赖本包 + +以下场景优先考虑只依赖 `GFramework.Game.Abstractions`: + +- 你在做纯领域层、协议层或可复用 feature 包,只想引用接口和数据契约 +- 你已经有自己的配置、存储、路由实现,只想复用统一契约 +- 你不希望业务程序集带入 `Newtonsoft.Json`、`YamlDotNet` 等运行时依赖 diff --git a/README.md b/README.md index ba5efd3d..5e7ba73e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # GFramework -> 面向游戏开发场景的模块化 C# 框架,核心能力与具体引擎解耦,可按需组合 Core / Game / Godot / Source Generators。 +> 面向游戏开发场景的模块化 C# 框架,按运行时、抽象层、引擎集成和源码生成器拆分能力。 [![NuGet Core](https://img.shields.io/badge/NuGet-GeWuYou.GFramework.Core-2C7BE5)](https://www.nuget.org/packages/GeWuYou.GFramework.Core) [![NuGet Meta](https://img.shields.io/badge/NuGet-GeWuYou.GFramework-1F9D55)](https://www.nuget.org/packages/GeWuYou.GFramework) @@ -8,84 +8,87 @@ [![.NET](https://img.shields.io/badge/.NET-8.0+-purple)](https://dotnet.microsoft.com/) [![License](https://img.shields.io/badge/License-Apache%202.0-blue)](LICENSE) [![zread](https://img.shields.io/badge/Ask_Zread-_.svg?style=flat-square&color=00b0aa&labelColor=000000&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTQuOTYxNTYgMS42MDAxSDIuMjQxNTZDMS44ODgxIDEuNjAwMSAxLjYwMTU2IDEuODg2NjQgMS42MDE1NiAyLjI0MDFWNC45NjAxQzEuNjAxNTYgNS4zMTM1NiAxLjg4ODEgNS42MDAxIDIuMjQxNTYgNS42MDAxSDQuOTYxNTZDNS5zMTUwMiA1LjYwMDEgNS42MDE1NiA1LjMxMzU2IDUuNjAxNTYgNC45NjAxVjIuMjQwMUM1LjYwMTU2IDEuODg2NjQgNS4zMTUwMiAxLjYwMDEgNC45NjE1NiAxLjYwMDFaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00Ljk2MTU2IDEwLjM5OTlIMi4yNDE1NkMxLjg4ODEgMTAuMzk5OSAxLjYwMTU2IDEwLjY4NjQgMS42MDE1NiAxMS4wMzk5VjEzLjc1OTlDMS42MDE1NiAxNC4xMTM0IDEuODg4MSAxNC4zOTk5IDIuMjQxNTYgMTQuMzk5OUg0Ljk2MTU2QzUuMzE1MDIgMTQuMzk5OSA1LjYwMTU2IDE0LjExMzQgNS42MDE1NiAxMy43NTk5VjExLjAzOTlDNS42MDE1NiAxMC42ODY0IDUuMzE1MDIgMTAuMzk5OSA0Ljk2MTU2IDEwLjM5OTlaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik0xMy43NTg0IDEuNjAwMUgxMS4wMzg0QzEwLjY4NSAxLjYwMDEgMTAuMzk4NCAxLjg4NjY0IDEwLjM5ODQgMi4yNDAxVjQuOTYwMUMxMC4zOTg0IDUuMzEzNTYgMTAuNjg1IDUuNjAwMSAxMS4wMzg0IDUuNjAwMUgxMy43NTg0QzE0LjExMTkgNS42MDAxIDE0LjM5ODQgNS4zMTM1NiAxNC4zOTg0IDQuOTYwMVYyLjI0MDFDMTQuMzk4NCAxLjg4NjY0IDE0LjExMTkgMS42MDAxIDEzLjc1ODQgMS42MDAxWiIgZmlsbD0iI2ZmZiIvPgo8cGF0aCBkPSJNNCAxMkwxMiA0TDQgMTJaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00IDEyTDEyIDQiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L3N2Zz4K&logoColor=ffffff)](https://zread.ai/GeWuYou/GFramework) ---- -## 项目简介 +## 从哪里开始 -GFramework 采用清晰分层与模块化设计,强调: +- 第一次接触框架:[`docs/zh-CN/getting-started/index.md`](docs/zh-CN/getting-started/index.md) +- 想先跑一个最小例子:[`docs/zh-CN/getting-started/quick-start.md`](docs/zh-CN/getting-started/quick-start.md) +- 已经知道要用哪个模块:直接进入对应模块目录下的 `README.md` -- **架构分层(Architecture / Model / System / Utility)** -- **命令与查询分离(CQRS)** -- **类型安全事件机制** -- **可绑定属性与响应式数据流** -- **可扩展的 IOC/生命周期管理** -- **基于 Roslyn 的源码生成能力** +## 模块地图 -项目灵感参考自 [QFramework](https://github.com/liangxiegame/QFramework),并在模块边界、工程组织和可扩展性方面进行了持续重构。 - -## 功能模块 - -| 模块 | 说明 | 文档 | -|-------------------------------------------------------------------------|-----------------------------------------|---------------------------------------------------| -| `GFramework.Core` | 平台无关的核心架构能力(架构、命令、查询、事件、属性、IOC、日志等) | [查看](GFramework.Core/README.md) | -| `GFramework.Core.Abstractions` | Core 对应的抽象接口定义 | [查看](GFramework.Core.Abstractions/README.md) | -| `GFramework.Game` | 游戏业务侧扩展(状态、配置、存储、UI 等) | [查看](GFramework.Game/README.md) | -| `GFramework.Game.Abstractions` | Game 模块抽象接口定义 | [查看](GFramework.Game.Abstractions/README.md) | -| `GFramework.Godot` | Godot 集成层(节点扩展、场景/设置/存储适配等) | [查看](GFramework.Godot/README.md) | -| `GFramework.Cqrs` / `GFramework.Cqrs.Abstractions` | CQRS runtime、契约与 handler 注册基础设施 | [查看](docs/zh-CN/core/cqrs.md) | -| `GFramework.Core.SourceGenerators` | Core 侧源码生成器(日志、枚举扩展、规则、模块注册等) | [查看](GFramework.Core.SourceGenerators/README.md) | -| `GFramework.Game.SourceGenerators` / `GFramework.Cqrs.SourceGenerators` | 游戏配置 schema 与 CQRS handler registry 生成器 | [查看](docs/zh-CN/source-generators/index.md) | -| `GFramework.Godot.SourceGenerators` | Godot 场景下的源码生成器扩展 | [查看](GFramework.Godot.SourceGenerators/README.md) | +| 模块 | 作用 | 入口 | +| --- | --- | --- | +| `GFramework.Core` | 架构、命令、查询、事件、状态、日志、资源、协程等基础运行时 | [README](GFramework.Core/README.md) | +| `GFramework.Core.Abstractions` | `Core` 对应的契约层,适合面向接口开发或做模块拆分 | [README](GFramework.Core.Abstractions/README.md) | +| `GFramework.Cqrs` | 新版 CQRS runtime,提供 request dispatcher、notification publish 与 handler 注册 | [README](GFramework.Cqrs/README.md) | +| `GFramework.Cqrs.Abstractions` | CQRS 消息、处理器、pipeline 行为等契约 | [README](GFramework.Cqrs.Abstractions/README.md) | +| `GFramework.Game` | 面向游戏项目的配置、数据、路由、场景、UI、设置和存储运行时 | [README](GFramework.Game/README.md) | +| `GFramework.Game.Abstractions` | `Game` 对应的契约层 | [README](GFramework.Game.Abstractions/README.md) | +| `GFramework.Godot` | Godot 集成层,负责把框架能力接入节点、场景、UI、设置与存储 | [README](GFramework.Godot/README.md) | +| `GFramework.Ecs.Arch` | Arch ECS 集成 | [README](GFramework.Ecs.Arch/README.md) | +| `GFramework.Core.SourceGenerators` | Core 侧通用源码生成器与分析器 | [README](GFramework.Core.SourceGenerators/README.md) | +| `GFramework.Game.SourceGenerators` | 游戏内容配置 schema 生成器 | [README](GFramework.Game.SourceGenerators/README.md) | +| `GFramework.Cqrs.SourceGenerators` | CQRS handler registry 生成器 | [README](GFramework.Cqrs.SourceGenerators/README.md) | +| `GFramework.Godot.SourceGenerators` | Godot 场景专用源码生成器 | [README](GFramework.Godot.SourceGenerators/README.md) | ## 文档导航 -- 入门教程:[`docs/zh-CN/tutorials/getting-started.md`](docs/zh-CN/tutorials/getting-started.md) -- Godot 集成:[`docs/zh-CN/godot/index.md`](docs/zh-CN/godot/index.md) -- 进阶模式:[`docs/zh-CN/core/index.md`](docs/zh-CN/core/index.md) -- 最佳实践:[`docs/zh-CN/best-practices/architecture-patterns.md`](docs/zh-CN/best-practices/architecture-patterns.md) -- API 参考:[`docs/zh-CN/api-reference/`](docs/zh-CN/api-reference/) +仓库根 README 与文档站点保持同一套栏目命名: -> 如果你更偏好按模块阅读,建议从各子项目 `README.md` 开始,再回到 `docs/` 查阅专题文档。 +- 入门指南:[`docs/zh-CN/getting-started/index.md`](docs/zh-CN/getting-started/index.md) +- Core:[`docs/zh-CN/core/index.md`](docs/zh-CN/core/index.md) +- Game:[`docs/zh-CN/game/index.md`](docs/zh-CN/game/index.md) +- Godot:[`docs/zh-CN/godot/index.md`](docs/zh-CN/godot/index.md) +- 教程:[`docs/zh-CN/tutorials/index.md`](docs/zh-CN/tutorials/index.md) +- 源码生成器:[`docs/zh-CN/source-generators/index.md`](docs/zh-CN/source-generators/index.md) +- ECS:[`docs/zh-CN/ecs/index.md`](docs/zh-CN/ecs/index.md) +- 抽象接口:[`docs/zh-CN/abstractions/index.md`](docs/zh-CN/abstractions/index.md) +- 最佳实践:[`docs/zh-CN/best-practices/index.md`](docs/zh-CN/best-practices/index.md) +- API 参考:[`docs/zh-CN/api-reference/index.md`](docs/zh-CN/api-reference/index.md) +- FAQ:[`docs/zh-CN/faq.md`](docs/zh-CN/faq.md) +- 故障排查:[`docs/zh-CN/troubleshooting.md`](docs/zh-CN/troubleshooting.md) +- 贡献:[`docs/zh-CN/contributing.md`](docs/zh-CN/contributing.md) -## 包选择说明(避免混淆) +## 包选择 -- **`GeWuYou.GFramework`**:聚合元包(Meta Package),用于一键引入常用能力集合,适合快速试用或原型阶段。 -- **`GeWuYou.GFramework.Core`**:核心起步包,适合希望按模块精细控制依赖的项目(推荐生产项目从此起步)。 +- `GeWuYou.GFramework` + 当前是聚合元包,只聚合 `GFramework.Core` 与 `GFramework.Game` 这两层运行时,适合快速试用。 +- `GeWuYou.GFramework.Core` + 推荐的最小起步包。先从核心运行时开始,再按需叠加 `Cqrs`、`Game`、`Godot` 和 Source Generators。 +- `GeWuYou.GFramework.*.Abstractions` + 适合需要单独依赖契约层、插件化、测试替身或多模块解耦的场景。 +- `GeWuYou.GFramework.*.SourceGenerators` + 只在需要编译期生成代码时安装,版本应与运行时包保持一致。 -如果你已明确技术栈,建议优先按模块安装(Core / Cqrs / Game / Godot / Source Generators),避免不必要依赖。 - -## 快速安装 - -按实际需求选择依赖: +## 最小安装组合 ```bash -# 核心能力(推荐最小起步) +# 最小起步 dotnet add package GeWuYou.GFramework.Core dotnet add package GeWuYou.GFramework.Core.Abstractions -# CQRS +# 新版 CQRS dotnet add package GeWuYou.GFramework.Cqrs dotnet add package GeWuYou.GFramework.Cqrs.Abstractions -# 游戏扩展 +# 游戏层运行时 dotnet add package GeWuYou.GFramework.Game dotnet add package GeWuYou.GFramework.Game.Abstractions -# Godot 集成(仅 Godot 项目需要) +# Godot 集成 dotnet add package GeWuYou.GFramework.Godot -# 按场景选择源码生成器(可选,但推荐) +# 按需安装源码生成器 dotnet add package GeWuYou.GFramework.Core.SourceGenerators dotnet add package GeWuYou.GFramework.Game.SourceGenerators -dotnet add package GeWuYou.GFramework.Godot.SourceGenerators dotnet add package GeWuYou.GFramework.Cqrs.SourceGenerators +dotnet add package GeWuYou.GFramework.Godot.SourceGenerators ``` -## 可选模块导入 +## 可选全局 using -发布后的运行时包支持可选的模块级自动导入,但默认关闭,避免在普通项目里无意污染命名空间。 - -在 NuGet 消费项目中显式开启: +NuGet 消费项目可显式开启模块级自动导入: ```xml @@ -93,9 +96,7 @@ dotnet add package GeWuYou.GFramework.Cqrs.SourceGenerators ``` -启用后,项目已引用的 GFramework 运行时模块会通过 `buildTransitive` 自动注入其推荐命名空间。 - -如果某几个命名空间不想导入,可以局部排除: +如果只想排除部分命名空间: ```xml @@ -104,45 +105,36 @@ dotnet add package GeWuYou.GFramework.Cqrs.SourceGenerators ``` -> 该能力面向 NuGet 包消费场景。若你在本地解决方案中直接使用 `ProjectReference`,仍建议保留自己的 `GlobalUsings.cs` 或手写 -`using`。 +> 该能力主要面向 NuGet 消费场景。仓库内 `ProjectReference` 方式仍建议显式维护自己的 `GlobalUsings.cs`。 ## 仓库结构 ```text GFramework.sln -├─ GFramework.Cqrs/ -├─ GFramework.Cqrs.Abstractions/ ├─ GFramework.Core/ ├─ GFramework.Core.Abstractions/ -├─ GFramework.Core.SourceGenerators/ -├─ GFramework.Core.SourceGenerators.Abstractions/ +├─ GFramework.Cqrs/ +├─ GFramework.Cqrs.Abstractions/ ├─ GFramework.Game/ ├─ GFramework.Game.Abstractions/ -├─ GFramework.Game.SourceGenerators/ ├─ GFramework.Godot/ -├─ GFramework.Godot.SourceGenerators/ +├─ GFramework.Ecs.Arch/ +├─ GFramework.Core.SourceGenerators/ +├─ GFramework.Game.SourceGenerators/ ├─ GFramework.Cqrs.SourceGenerators/ +├─ GFramework.Godot.SourceGenerators/ ├─ GFramework.SourceGenerators.Common/ -├─ docs/ -└─ docfx/ +└─ docs/ ``` -## 兼容性 - -- **运行时/工具链**:基于 .NET 生态,具体以各项目 `*.csproj` 的 `TargetFramework` 为准。 -- **引擎集成**:当前提供 Godot 集成模块,Core 层可迁移至其他 .NET 场景。 - ## 贡献 -欢迎提交 Issue 与 Pull Request: +提交功能或行为变更时,请把代码、测试和文档一起更新: -1. 提交 Issue 时请优先选择对应模板:`Bug Report / 缺陷报告`、`Feature Request / 功能建议`、`Documentation / 文档改进`、`Question / 使用咨询` -2. 提交前先搜索现有 Issues,并阅读相关 README、文档或排障页面 -3. Fork 本仓库并创建特性分支 -4. 补充必要的测试或文档更新 -5. 提交 PR,描述变更背景、方案与验证结果 +1. 先阅读对应模块目录下的 `README.md` +2. 如果改动影响采用路径、安装方式、公共 API 或目录结构,同时更新 `docs/zh-CN/` +3. 对跨模块或多阶段任务,维护 `local-plan/todos/` 与 `local-plan/traces/` ## 许可证 -本项目采用 [Apache License 2.0](LICENSE)。 +本仓库当前采用 [Apache License 2.0](LICENSE)。 diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index bcac727e..431f22f1 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -116,6 +116,7 @@ export default defineConfig({ text: '更多', items: [ { text: 'ECS', link: '/zh-CN/ecs/' }, + { text: '抽象接口', link: '/zh-CN/abstractions/' }, { text: '源码生成器', link: '/zh-CN/source-generators' }, { text: '最佳实践', link: '/zh-CN/best-practices/' }, { text: 'API 参考', link: '/zh-CN/api-reference' }, diff --git a/docs/zh-CN/abstractions/index.md b/docs/zh-CN/abstractions/index.md new file mode 100644 index 00000000..ffa159c5 --- /dev/null +++ b/docs/zh-CN/abstractions/index.md @@ -0,0 +1,20 @@ +# 抽象接口 + +`GFramework.*.Abstractions` 用来承载跨模块协作所需的契约,而不是运行时实现。 + +适合阅读这部分内容的场景: + +- 你要做模块拆分,只想依赖接口,不想直接引用完整运行时 +- 你要为测试、编辑器工具或插件提供替身实现 +- 你在维护生成器、适配层或二次封装,需要先理解契约边界 + +## 阅读顺序 + +- Core 抽象层:[`core-abstractions.md`](./core-abstractions.md) +- Game 抽象层:[`game-abstractions.md`](./game-abstractions.md) + +## 使用建议 + +- 如果你只是想直接使用框架功能,优先从对应运行时模块的 `README.md` 和栏目页开始。 +- 只有在明确需要“契约层而非实现层”时,才单独依赖 `*.Abstractions` 包。 +- 抽象层页面会解释接口分组与职责;真正的安装和采用路径,仍应以运行时模块 README 与 `getting-started` 为主。 diff --git a/docs/zh-CN/getting-started/index.md b/docs/zh-CN/getting-started/index.md index f9732230..9f3dca77 100644 --- a/docs/zh-CN/getting-started/index.md +++ b/docs/zh-CN/getting-started/index.md @@ -1,359 +1,107 @@ -# 架构概览 - -GFramework 采用经典的五层架构模式,结合 CQRS 和事件驱动设计,为游戏开发提供清晰、可维护的架构基础。 - -## 核心架构模式 - -### 五层架构 - -``` -┌─────────────────────────────────────────┐ -│ View / UI │ ← 用户界面层 -├─────────────────────────────────────────┤ -│ Controller │ ← 控制层 -├─────────────────────────────────────────┤ -│ System │ ← 业务逻辑层 -├─────────────────────────────────────────┤ -│ Model │ ← 数据层 -├─────────────────────────────────────────┤ -│ Utility │ ← 工具层 -└─────────────────────────────────────────┘ -``` - -### 跨层操作机制 - -``` -Command ──┐ -Query ──┼──→ 跨层操作(修改/查询数据) -Event ──┘ -``` - -### 生命周期阶段 - -``` -初始化:Init → BeforeUtilityInit → AfterUtilityInit → BeforeModelInit → AfterModelInit → BeforeSystemInit → AfterSystemInit → Ready -销毁:Destroy → Destroying → Destroyed -``` - -## 核心组件详解 - -### 1. Architecture(架构) - -应用的中央调度器,负责管理所有组件的生命周期。 - -```csharp -public class GameArchitecture : Architecture -{ - protected override void Init() - { - // 注册所有组件 - RegisterModel(new PlayerModel()); - RegisterSystem(new CombatSystem()); - RegisterUtility(new StorageUtility()); - } -} -``` - -**主要职责:** - -- 组件注册和管理 -- 生命周期协调 -- 依赖注入 -- 跨组件通信协调 - -### 2. Model(数据模型) - -应用的状态存储层,只负责数据的存储和管理。 - -```csharp -public class PlayerModel : AbstractModel -{ - public BindableProperty Health { get; } = new(100); - public BindableProperty Name { get; } = new("Player"); - - protected override void OnInit() - { - // 监听自身数据变化 - Health.Register(OnHealthChanged); - } - - private void OnHealthChanged(int newHealth) - { - if (newHealth <= 0) - this.SendEvent(new PlayerDiedEvent()); - } -} -``` - -**设计原则:** - -- 只存储数据,不包含业务逻辑 -- 使用 BindableProperty 实现响应式数据 -- 通过事件通知数据变化 - -### 3. System(业务系统) - -应用的业务逻辑处理层。 - -```csharp -public class CombatSystem : AbstractSystem -{ - protected override void OnInit() - { -// 订阅相关事件 -this.GetEvent().Register(OnAttack); -} - - private void OnAttack(AttackEvent e) - { - var attacker = e.Attacker; - var target = e.Target; - - // 计算伤害 - var damage = CalculateDamage(attacker, target); - - // 更新目标生命值 - target.Health.Value -= damage; - - // 发送伤害事件 - this.SendEvent(new DamageEvent(target, damage)); - } - - private int CalculateDamage(Entity attacker, Entity target) - { - return Mathf.Max(1, attacker.Attack.Value - target.Defense.Value); - } -} -``` - -**设计原则:** - -- 处理业务逻辑,不直接存储数据 -- 通过事件与其他组件通信 -- 从 Model 获取数据,向 Model 发送更新 - -### 4. Controller(控制器) - -连接 UI 和业务逻辑的桥梁。 - -```csharp -public class PlayerController : IController -{ - private IArchitecture _architecture; - private PlayerModel _playerModel; - - public PlayerController(IArchitecture architecture) - { - _architecture = architecture; - _playerModel = architecture.GetModel(); - - // 监听模型变化并更新 UI - _playerModel.Health.RegisterWithInitValue(UpdateHealthDisplay); - } - - public void OnPlayerInput(Vector2 direction) - { - // 将用户输入转换为命令 - _architecture.SendCommand(new MovePlayerCommand { Direction = direction }); - } - - private void UpdateHealthDisplay(int health) - { - // 更新 UI 显示 - Console.WriteLine($"Player Health: {health}"); - } -} -``` - -**核心功能:** - -- 接收用户输入 -- 发送命令到系统 -- 监听模型变化更新 UI -- 协调 UI 和业务逻辑 - -### 5. Utility(工具类) - -提供无状态的辅助功能。 - -```csharp -public class StorageUtility : IUtility -{ - public void SaveData(string key, T data) - { - // 实现数据保存逻辑 - } - - public T LoadData(string key, T defaultValue = default) - { - // 实现数据加载逻辑 - return defaultValue; - } -} -``` - -**使用场景:** - -- 数据存储和读取 -- 数学计算工具 -- 字符串处理 -- 网络通信辅助 - -## 通信机制 - -### 1. Command(命令) - -用于修改应用状态的操作: - -```csharp -public class MovePlayerCommand : AbstractCommand -{ - public Vector2 Direction { get; set; } - - protected override void OnDo() - { - // 执行移动逻辑 - this.SendEvent(new PlayerMovedEvent { Position = CalculateNewPosition() }); - } -} -``` - -### 2. Query(查询) - -用于查询应用状态:` - -```csharp -public class GetPlayerHealthQuery : AbstractQuery -{ - protected override int OnDo() - { - var playerModel = this.GetModel(); - return playerModel.Health.Value; - } -} -``` - -### 3. Event(事件) - -组件间通信的主要机制:` - -``` -// 发送事件 -this.SendEvent(new PlayerDiedEvent()); - -// 监听事件 -this.RegisterEvent(OnPlayerDied); -``` - -## 响应式编程 - -### BindableProperty - -```csharp -public class PlayerModel : AbstractModel -{ - public BindableProperty Health { get; } = new(100); - public BindableProperty Name { get; } = new("Player"); -} - -// 使用方式 -playerModel.Health.Value = 50; // 自动触发所有监听器 -playerModel.Health.Register(newValue => { - Console.WriteLine($"Health changed to: {newValue}"); -}); -``` - -### 数据绑定优势 - -- **自动更新**:数据变化自动通知监听者 -- **内存安全**:自动管理监听器生命周期 -- **类型安全**:编译时类型检查 -- **性能优化**:只在值真正改变时触发 - -## 最佳实践 - -### 1. 分层职责明确 - -```csharp -// ✅ 正确:Model 只存储数据 -public class PlayerModel : AbstractModel -{ - public BindableProperty Health { get; } = new(100); -} - -// ❌ 错误:Model 包含业务逻辑 -public class PlayerModel : AbstractModel -{ - public void TakeDamage(int damage) // 业务逻辑应该在 System 中 - { - Health.Value -= damage; - } -} -``` - -### 2. 事件驱动设计 - -```csharp -// ✅ 正确:使用事件解耦 -public class CombatSystem : AbstractSystem -{ - private void OnPlayerAttack(PlayerAttackEvent e) - { - // 处理攻击逻辑 - this.SendEvent(new EnemyDamagedEvent { Damage = CalculateDamage() }); - } -} - -// ❌ 错误:直接调用其他组件 -public class CombatSystem : AbstractSystem -{ - private void OnPlayerAttack(PlayerAttackEvent e) - { - var enemySystem = this.GetSystem(); // 紧耦合 - enemySystem.TakeDamage(CalculateDamage()); - } -} -``` - -### 3. 命令查询分离 - -```csharp -// ✅ 正确:明确区分命令和查询 -public class MovePlayerCommand : AbstractCommand { } // 修改状态 -public class GetPlayerPositionQuery : AbstractQuery { } // 查询状态 - -// ❌ 错误:混合读写操作 -public class PlayerManager -{ - public void MoveAndGetPosition(Vector2 direction, out Vector2 position) // 职责不清 - { - // ... - } -} -``` - -## 架构优势 - -### 1. 可维护性 - -- 清晰的职责分离 -- 松耦合的组件设计 -- 易于定位和修复问题 - -### 2. 可测试性 - -- 组件可独立测试 -- 依赖可轻松模拟 -- 支持单元测试和集成测试 - -### 3. 可扩展性 - -- 新功能通过添加组件实现 -- 现有组件无需修改 -- 支持插件化架构 - -### 4. 团队协作 - -- 统一的架构规范 -- 易于新人上手 -- 减少代码冲突 \ No newline at end of file +# 入门指南 + +这一部分只回答三个问题: + +1. `GFramework` 由哪些模块组成 +2. 第一次接入应该从哪个包开始 +3. 最小可运行路径是什么 + +如果你还没决定具体用法,先阅读本栏目;如果你已经明确要用某个模块,直接进入对应模块目录下的 `README.md` 会更快。 + +## 推荐起步路径 + +### 只想先把架构跑起来 + +从 `Core` 开始: + +- `GeWuYou.GFramework.Core` +- `GeWuYou.GFramework.Core.Abstractions` + +这组包提供: + +- `Architecture` +- `Model` / `System` / `Utility` +- 旧版 `Command` / `Query` 执行器 +- 事件、属性、状态机、状态管理、资源、日志、协程等基础设施 + +对应文档: + +- [`../core/index.md`](../core/index.md) +- [`quick-start.md`](./quick-start.md) + +### 想用新版 CQRS + +在 `Core` 基础上补: + +- `GeWuYou.GFramework.Cqrs` +- `GeWuYou.GFramework.Cqrs.Abstractions` + +这组包提供: + +- 统一 request dispatcher +- notification publish +- pipeline behaviors +- handler 注册与反射回退机制 + +对应文档: + +- [`../core/cqrs.md`](../core/cqrs.md) +- 仓库内模块入口:`GFramework.Cqrs/README.md` + +### 想做游戏运行时 + +在 `Core` 基础上按需补: + +- `GeWuYou.GFramework.Game` +- `GeWuYou.GFramework.Game.Abstractions` + +这组包提供: + +- 内容配置系统 +- 数据存取与设置 +- Scene / UI / Routing 抽象与运行时 +- 文件存储和序列化 + +对应文档: + +- [`../game/index.md`](../game/index.md) +- 仓库内模块入口:`GFramework.Game/README.md` + +### 想接入 Godot + +继续叠加: + +- `GeWuYou.GFramework.Godot` + +对应文档: + +- [`../godot/index.md`](../godot/index.md) +- 仓库内模块入口:`GFramework.Godot/README.md` + +## Source Generators 什么时候装 + +只在需要编译期生成代码时再装: + +- `GeWuYou.GFramework.Core.SourceGenerators` +- `GeWuYou.GFramework.Game.SourceGenerators` +- `GeWuYou.GFramework.Cqrs.SourceGenerators` +- `GeWuYou.GFramework.Godot.SourceGenerators` + +典型场景: + +- 自动生成日志、上下文绑定、模块注册代码 +- 从 `schema` 生成游戏配置类型 +- 为 CQRS handlers 生成注册表 +- 生成 Godot 节点、场景和 UI 包装代码 + +## 建议阅读顺序 + +1. [`quick-start.md`](./quick-start.md) +2. 你准备使用的模块 README +3. 对应栏目页,例如 `core/`、`game/`、`godot/` +4. 需要更完整示例时,再进入 `tutorials/` + +## 注意 + +- 旧文档里有一些早期示例已经和当前 API 漂移。本栏目以后只保留经过代码或测试核对的最小路径。 +- 若根 README、模块 README 与某篇专题页冲突,以模块 README 和当前代码为准。 diff --git a/docs/zh-CN/getting-started/quick-start.md b/docs/zh-CN/getting-started/quick-start.md index ce3c2988..6667e799 100644 --- a/docs/zh-CN/getting-started/quick-start.md +++ b/docs/zh-CN/getting-started/quick-start.md @@ -1,323 +1,105 @@ # 快速开始 -本指南将帮助您快速构建第一个基于 GFramework 的应用程序。 +本页给出一个只依赖 `Core` 的最小路径,用来确认你已经成功接入 `Architecture`、`Model`、`System` 与旧版命令执行器。 -## 1. 创建项目架构 +> 说明:当前仓库同时存在旧版 `Command` / `Query` 执行器与新版 `CQRS` runtime。本页故意先用最短路径说明基础架构如何跑起来;如果你要写新功能,随后应继续阅读 [`../core/cqrs.md`](../core/cqrs.md)。 -首先定义您的应用架构: +## 1. 安装最小依赖 + +```bash +dotnet add package GeWuYou.GFramework.Core +dotnet add package GeWuYou.GFramework.Core.Abstractions +``` + +## 2. 定义一个最小架构 ```csharp -using GFramework.Core.Architecture; +using GFramework.Core.Architectures; -public class GameArchitecture : Architecture +public sealed class CounterArchitecture : Architecture { - protected override void Init() + protected override void OnInitialize() { - // 注册模型 - 存储应用状态 - RegisterModel(new PlayerModel()); - RegisterModel(new GameStateModel()); - - // 注册系统 - 处理业务逻辑 - RegisterSystem(new PlayerSystem()); - RegisterSystem(new GameLogicSystem()); - - // 注册工具类 - 提供辅助功能 - RegisterUtility(new StorageUtility()); + RegisterModel(new CounterModel()); + RegisterSystem(new CounterSystem()); } } ``` -## 2. 定义数据模型 +这里要点只有两个: -创建您的数据模型: +- 架构入口是 `Architecture` +- 当前版本使用 `protected override void OnInitialize()` 注册模型、系统和工具 + +## 3. 定义一个模型 ```csharp -public class PlayerModel : AbstractModel +using GFramework.Core.Model; +using GFramework.Core.Property; + +public sealed class CounterModel : AbstractModel { - // 使用可绑定属性实现响应式数据 - public BindableProperty Name { get; } = new("Player"); - public BindableProperty Health { get; } = new(100); - public BindableProperty Score { get; } = new(0); - + public BindableProperty Count { get; } = new(0); + protected override void OnInit() { - // 监听健康值变化 - Health.Register(OnHealthChanged); } - - private void OnHealthChanged(int newHealth) - { - if (newHealth <= 0) - { - this.SendEvent(new PlayerDiedEvent()); - } - } -} - -public class GameStateModel : AbstractModel -{ - public BindableProperty IsGameRunning { get; } = new(false); - public BindableProperty CurrentLevel { get; } = new(1); } ``` -## 3. 实现业务逻辑 +`BindableProperty` 适合承载可观察状态;如果你只需要一个最小例子,保持 `OnInit()` 为空即可。 -创建处理业务逻辑的系统: +## 4. 定义一个系统 ```csharp -public class PlayerSystem : AbstractSystem +using GFramework.Core.Systems; + +public sealed class CounterSystem : AbstractSystem { protected override void OnInit() { - // 监听玩家输入事件 - this.RegisterEvent(OnPlayerMove); - this.RegisterEvent(OnPlayerAttack); - } - - private void OnPlayerMove(PlayerMoveEvent e) - { - var playerModel = this.GetModel(); - // 处理移动逻辑 - Console.WriteLine($"Player moved to {e.Direction}"); - } - - private void OnPlayerAttack(PlayerAttackEvent e) - { - var playerModel = this.GetModel(); - // 处理攻击逻辑 - playerModel.Score.Value += 10; - this.SendEvent(new EnemyDamagedEvent { Damage = 25 }); - } -} - -public class GameLogicSystem : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnEnemyDamaged); - this.RegisterEvent(OnPlayerDied); - } - - private void OnEnemyDamaged(EnemyDamagedEvent e) - { - Console.WriteLine($"Enemy took {e.Damage} damage"); - // 检查是否需要升级关卡 - CheckLevelProgress(); - } - - private void OnPlayerDied(PlayerDiedEvent e) - { - var gameState = this.GetModel(); - gameState.IsGameRunning.Value = false; - Console.WriteLine("Game Over!"); - } - - private void CheckLevelProgress() - { - // 实现关卡进度检查逻辑 } } ``` -## 4. 定义事件 - -创建应用中使用的事件: +## 5. 定义一个命令 ```csharp -public class PlayerMoveEvent : IEvent -{ - public Vector2 Direction { get; set; } -} +using GFramework.Core.Command; -public class PlayerAttackEvent : IEvent +public sealed class IncreaseCountCommand : AbstractCommand { - public Vector2 TargetPosition { get; set; } -} - -public class PlayerDiedEvent : IEvent -{ - // 玩家死亡事件 -} - -public class EnemyDamagedEvent : IEvent -{ - public int Damage { get; set; } + protected override void OnExecute() + { + var model = GetModel(); + model.Count.Value += 1; + } } ``` -## 5. 创建控制器 +`AbstractCommand` 继承自 `ContextAwareBase`,所以命令内部可以直接访问 `GetModel()`、`GetSystem()` 等上下文方法。 -实现控制器来连接 UI 和业务逻辑: +## 6. 初始化并执行 ```csharp -using GFramework.Core.Abstractions.Controller; -using GFramework.SourceGenerators.Abstractions.Rule; +var architecture = new CounterArchitecture(); +architecture.Initialize(); -[ContextAware] -public partial class GameController : IController -{ - private PlayerModel _playerModel; - private GameStateModel _gameStateModel; +architecture.Context.SendCommand(new IncreaseCountCommand()); - public void Initialize() - { - _playerModel = this.GetModel(); - _gameStateModel = this.GetModel(); - - // 初始化事件监听 - InitializeEventListeners(); - } - - private void InitializeEventListeners() - { - // 监听模型变化并更新 UI - _playerModel.Health.RegisterWithInitValue(OnHealthChanged); - _playerModel.Score.RegisterWithInitValue(OnScoreChanged); - _gameStateModel.IsGameRunning.Register(OnGameStateChanged); - } - - public void StartGame() - { - _gameStateModel.IsGameRunning.Value = true; - this.SendEvent(new GameStartEvent()); - Console.WriteLine("Game started!"); - } - - public void MovePlayer(Vector2 direction) - { - this.SendCommand(new MovePlayerCommand { Direction = direction }); - } - - public void PlayerAttack(Vector2 target) - { - this.SendCommand(new AttackCommand { TargetPosition = target }); - } - - // UI 更新回调 - private void OnHealthChanged(int health) - { - UpdateHealthDisplay(health); - } - - private void OnScoreChanged(int score) - { - UpdateScoreDisplay(score); - } - - private void OnGameStateChanged(bool isRunning) - { - UpdateGameStatusDisplay(isRunning); - } - - private void UpdateHealthDisplay(int health) - { - // 更新血条 UI - Console.WriteLine($"Health: {health}"); - } - - private void UpdateScoreDisplay(int score) - { - // 更新分数显示 - Console.WriteLine($"Score: {score}"); - } - - private void UpdateGameStatusDisplay(bool isRunning) - { - // 更新游戏状态显示 - Console.WriteLine($"Game running: {isRunning}"); - } -} +var count = architecture.Context.GetModel().Count.Value; +Console.WriteLine(count); // 1 ``` -## 6. 定义命令 +如果你能走通这一步,说明以下链路已经成立: -创建命令来封装用户操作: - -```csharp -public class MovePlayerCommand : AbstractCommand -{ - public Vector2 Direction { get; set; } - - protected override void OnDo() - { - // 发送移动事件 - this.SendEvent(new PlayerMoveEvent { Direction = Direction }); - } -} - -public class AttackCommand : AbstractCommand -{ - public Vector2 TargetPosition { get; set; } - - protected override void OnDo() - { - // 发送攻击事件 - this.SendEvent(new PlayerAttackEvent { TargetPosition = TargetPosition }); - } -} -``` - -## 7. 运行应用 - -现在让我们运行这个简单的应用: - -```csharp -class Program -{ - static void Main(string[] args) - { - // 创建并初始化架构 - var architecture = new GameArchitecture(); - architecture.Initialize(); - - // 创建控制器 - var gameController = new GameController(); - gameController.Initialize(); - - // 开始游戏 - gameController.StartGame(); - - // 模拟玩家操作 - gameController.MovePlayer(new Vector2(1, 0)); - gameController.PlayerAttack(new Vector2(5, 5)); - - // 模拟玩家受伤 - var playerModel = architecture.GetModel(); - playerModel.Health.Value = 50; - - // 模拟玩家死亡 - playerModel.Health.Value = 0; - - Console.WriteLine("Press any key to exit..."); - Console.ReadKey(); - } -} -``` - -## 8. 运行结果 - -执行程序后,您应该看到类似以下输出: - -``` -Game started! -Game running: True -Player moved to (1, 0) -Player took 25 damage -Score: 10 -Health: 50 -Health: 0 -Player died -Game Over! -Game running: False -Press any key to exit... -``` +- 架构初始化 +- 模型 / 系统注册 +- 上下文访问 +- 旧版命令执行 ## 下一步 -这个简单的示例展示了 GFramework 的核心概念: - -1. **架构模式** - 清晰的分层结构 -2. **响应式数据** - BindableProperty 自动更新 -3. **事件驱动** - 松耦合的组件通信 -4. **命令模式** - 封装用户操作 +- 想切到推荐的新请求模型:看 [`../core/cqrs.md`](../core/cqrs.md) +- 想进入游戏层能力:看 [`../game/index.md`](../game/index.md) +- 想看模块入口而不是栏目页:回到对应模块目录下的 `README.md`