diff --git a/ai-plan/public/documentation-governance-and-refresh/todos/documentation-governance-and-refresh-tracking.md b/ai-plan/public/documentation-governance-and-refresh/todos/documentation-governance-and-refresh-tracking.md index fd3a2bdd..0447eb82 100644 --- a/ai-plan/public/documentation-governance-and-refresh/todos/documentation-governance-and-refresh-tracking.md +++ b/ai-plan/public/documentation-governance-and-refresh/todos/documentation-governance-and-refresh-tracking.md @@ -7,25 +7,28 @@ ## 当前恢复点 -- 恢复点编号:`DOCUMENTATION-GOVERNANCE-REFRESH-RP-001` -- 当前阶段:`Phase 1` +- 恢复点编号:`DOCUMENTATION-GOVERNANCE-REFRESH-RP-002` +- 当前阶段:`Phase 2` - 当前焦点: - - 已将当前工作树根目录的 legacy `local-plan/` 迁入 `ai-plan/public/documentation-governance-and-refresh/` - - 第一轮治理已完成 `AGENTS.md`、根 `README.md`、`getting-started` 与第一批高优先级模块 `README.md` - - 下一轮需要继续按栏目核对并重写 `docs/zh-CN/core/*`、`docs/zh-CN/game/*` 与 - `docs/zh-CN/source-generators/*` + - 已完成 `docs/zh-CN/core/index.md`、`docs/zh-CN/game/index.md` 与 + `docs/zh-CN/source-generators/index.md` 的 landing page 重写 + - 栏目入口已改为以模块定位、包关系、最小接入路径和继续阅读为主,不再沿用旧版失真教程结构 + - 下一轮需要继续核对并重写 `docs/zh-CN/core/*`、`docs/zh-CN/game/*` 与 + `docs/zh-CN/source-generators/*` 的专题页内容 ## 当前状态摘要 - 文档治理规则已收口到仓库规范,README、站点入口与采用链路不再依赖旧文档自证 -- 高优先级模块入口已补齐,首轮文档站构建校验已经通过 -- 当前主题仍是 active topic,因为核心栏目专题页仍可能包含与实现漂移的旧内容 +- 高优先级模块入口已补齐,栏目 landing page 已回到可作为默认导航入口的状态 +- 当前主题仍是 active topic,因为核心栏目下的专题页仍可能包含与实现漂移的旧内容 ## 当前活跃事实 - 旧 `local-plan/` 的详细 todo 与 trace 已迁入主题内 `archive/` - 当前分支 `docs/sdk-update-documentation` 已在 `ai-plan/public/README.md` 建立 topic 映射 - active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史 +- `core`、`game` 与 `source-generators` 三个栏目入口页现在都以模块 README 与当前包拆分为准 +- `docs` 站点构建已验证通过,修正了 VitePress 对 `docs/` 目录外相对链接的 dead-link 检查问题 ## 当前风险 @@ -46,9 +49,10 @@ - 旧 `local-plan/` 的详细实施历史与文档站构建结果已迁入主题内归档 - active 跟踪文件已按 `ai-plan` 治理规则精简为当前恢复入口 +- `cd docs && bun run build` ## 下一步 -1. 继续按栏目核对 `docs/zh-CN/core/*`,列出仍失真的页面与示例 -2. 再推进 `docs/zh-CN/game/*` 与 `docs/zh-CN/source-generators/*` 的专题页重写 -3. 若下一轮重写完成且验证通过,将栏目级详细过程迁入本 topic 的 `archive/` +1. 先从 `docs/zh-CN/core/*` 开始,逐页核对架构、上下文、生命周期、命令、查询与 CQRS 的示例和术语 +2. 再推进 `docs/zh-CN/game/*` 与 `docs/zh-CN/source-generators/*` 的专题页重写,优先处理仍引用旧安装方式或旧 API 的页面 +3. 若专题页批量重写完成且验证通过,将本轮 landing page 收口和下一轮专题页修订过程迁入本 topic 的 `archive/` diff --git a/ai-plan/public/documentation-governance-and-refresh/traces/documentation-governance-and-refresh-trace.md b/ai-plan/public/documentation-governance-and-refresh/traces/documentation-governance-and-refresh-trace.md index a324a41d..6b9bb94e 100644 --- a/ai-plan/public/documentation-governance-and-refresh/traces/documentation-governance-and-refresh-trace.md +++ b/ai-plan/public/documentation-governance-and-refresh/traces/documentation-governance-and-refresh-trace.md @@ -29,3 +29,26 @@ 1. 后续继续该主题时,只从 `ai-plan/public/documentation-governance-and-refresh/` 进入,不再恢复 `local-plan/` 2. 若 active 入口再次积累多轮已完成且已验证阶段,继续按同一模式迁入该主题自己的 `archive/` + +## 2026-04-21 + +### 阶段:栏目 landing page 收口(RP-002) + +- 依据 `ai-plan/public/README.md` 的 worktree 映射恢复 `documentation-governance-and-refresh` 主题,并确认该分支下一步应优先处理 `docs/zh-CN/core/*`、`game/*` 与 `source-generators/*` +- 复核 `docs/zh-CN/core/index.md`、`docs/zh-CN/game/index.md`、`docs/zh-CN/source-generators/index.md` 后确认:这三页仍保留旧版“大而全教程”结构,与当前模块 README、包拆分关系和推荐接入路径明显漂移 +- 对照 `GFramework.Core/README.md`、`GFramework.Game/README.md`、`GFramework.Core.SourceGenerators/README.md`、 + `GFramework.Game.SourceGenerators/README.md`、`GFramework.Cqrs.SourceGenerators/README.md` 与 + `GFramework.Godot.SourceGenerators/README.md`,重写三个栏目 landing page,使其回到“模块定位、包关系、最小接入路径、继续阅读”的可信入口形态 +- 首次执行 `cd docs && bun run build` 时发现 VitePress 会把跳到 `docs/` 目录外的相对链接判定为 dead link,因此将 landing page 末尾的模块 README 入口改为纯文本路径提示而非站内链接 +- 第二次执行 `cd docs && bun run build` 通过,说明当前 landing page 重写没有破坏站点构建 + +### 当前结论 + +- 当前默认导航入口已显著收敛,但专题页仍需逐页按源码与测试继续核对 +- 后续优先级应从 `core` 专题页开始,再向 `game` 与 `source-generators` 扩展 + +### 下一步 + +1. 审核 `docs/zh-CN/core/architecture.md`、`context.md`、`lifecycle.md`、`command.md`、`query.md`、`cqrs.md` +2. 记录每页的失真点、真实 API 名称与应保留的最小示例 +3. 完成一轮专题页重写后再次执行 `cd docs && bun run build` diff --git a/docs/zh-CN/core/index.md b/docs/zh-CN/core/index.md index 5c4837bf..306dabf7 100644 --- a/docs/zh-CN/core/index.md +++ b/docs/zh-CN/core/index.md @@ -1,674 +1,107 @@ -# GFramework.Core 核心框架 +# Core -> 一个基于 CQRS、MVC 和事件驱动的轻量级游戏开发架构框架 +`Core` 栏目对应 `GFramework` 的基础运行时层,主要覆盖 `GFramework.Core` 与 `GFramework.Core.Abstractions`,以及与之直接相邻的旧版 +`Command` / `Query` 执行器和新版 `CQRS` 迁移入口。 -## 目录 +如果你第一次接入框架,建议先把这里当作“运行时底座说明”,再按需进入 `Game`、`Godot` 或 Source Generators 栏目。 -- [框架概述](#框架概述) -- [核心概念](#核心概念) -- [架构图](#架构图) -- [快速开始](#快速开始) -- [包说明](#包说明) -- [组件联动](#组件联动) -- [最佳实践](#最佳实践) -- [设计理念](#设计理念) +## 先理解包关系 -## 框架概述 +- `GeWuYou.GFramework.Core` + - 基础运行时实现,包含 `Architecture`、上下文、生命周期、事件、属性、状态、资源、日志、协程、IoC 等能力。 +- `GeWuYou.GFramework.Core.Abstractions` + - 对应的契约层,适合只依赖接口、做模块拆分或测试替身。 +- `GeWuYou.GFramework.Cqrs` + - 推荐给新功能使用的新请求模型运行时。 +- `GeWuYou.GFramework.Game` + - 在 `Core` 之上叠加游戏层配置、数据、设置、场景与 UI。 +- `GeWuYou.GFramework.Core.SourceGenerators` + - 在编译期补齐日志、上下文注入、模块自动注册等样板代码。 -本框架是一个与平台无关的轻量级架构,它结合了多种经典设计模式: +如果你只想先把架构跑起来,最小安装组合仍是: -- **MVC 架构模式** - 清晰的层次划分 -- **CQRS 模式** - 命令查询职责分离 -- **IoC/DI** - 依赖注入和控制反转 -- **事件驱动** - 松耦合的组件通信 -- **响应式编程** - 可绑定属性和数据流 -- **阶段式生命周期管理** - 精细化的架构状态控制 - -**重要说明**:GFramework.Core 是与平台无关的核心模块,不包含任何 Godot 特定代码。Godot 集成功能在 GFramework.Godot 包中实现。 - -### 核心特性 - -- **清晰的分层架构** - Model、View、Controller、System、Utility 各司其职 -- **类型安全** - 基于泛型的组件获取和事件系统 -- **松耦合** - 通过事件和接口实现组件解耦 -- **易于测试** - 依赖注入和纯函数设计 -- **可扩展** - 基于接口的规则体系 -- **生命周期管理** - 自动的注册和注销机制 -- **模块化** - 支持架构模块安装 -- **平台无关** - Core 模块可以在任何 .NET 环境中使用 - -## 核心概念 - -### 五层架构 - -``` -┌─────────────────────────────────────────┐ -│ View / UI │ UI 层:用户界面 -├─────────────────────────────────────────┤ -│ Controller │ 控制层:连接 UI 和业务逻辑 -├─────────────────────────────────────────┤ -│ System │ 逻辑层:业务逻辑 -├─────────────────────────────────────────┤ -│ Model │ 数据层:游戏状态 -├─────────────────────────────────────────┤ -│ Utility │ 工具层:无状态工具 -└─────────────────────────────────────────┘ +```bash +dotnet add package GeWuYou.GFramework.Core +dotnet add package GeWuYou.GFramework.Core.Abstractions ``` -### 横切关注点 +## 这个栏目应该回答什么 -``` -Command ──┐ -Query ──┼──→ 跨层操作(修改/查询数据) -Event ──┘ -``` +`Core` 栏目不是旧版“完整框架教程”的镜像,而是当前实现的入口导航。这里的页面按能力域组织: -### 架构阶段 +- 架构与上下文 + - [architecture](./architecture.md) + - [context](./context.md) + - [lifecycle](./lifecycle.md) +- 旧版命令 / 查询执行器与迁移入口 + - [command](./command.md) + - [query](./query.md) + - [cqrs](./cqrs.md) +- 核心横切能力 + - [events](./events.md) + - [property](./property.md) + - [logging](./logging.md) + - [resource](./resource.md) + - [coroutine](./coroutine.md) + - [ioc](./ioc.md) +- 状态与扩展能力 + - [state-machine](./state-machine.md) + - [state-management](./state-management.md) + - [pause](./pause.md) + - [localization](./localization.md) + - [functional](./functional.md) + - [extensions](./extensions.md) -框架提供了精细化的生命周期管理,包含 11 个阶段: +## 最小接入路径 -``` -初始化流程: -None → BeforeUtilityInit → AfterUtilityInit → BeforeModelInit → AfterModelInit → BeforeSystemInit → AfterSystemInit → Ready +当前版本的最小运行时入口只有三个关键动作: -销毁流程: -Ready → Destroying → Destroyed +1. 继承 `Architecture` +2. 在 `OnInitialize()` 中注册模型、系统、工具或模块 +3. 通过 `architecture.Context` 或 `ContextAwareBase` 的扩展方法访问上下文 -异常流程: -Any → FailedInitialization -``` - -每个阶段都会触发 `PhaseChanged` 事件,允许组件监听架构状态变化。 - -## 架构图 - -### 整体架构 - -从 v1.1.0 开始,Architecture 类采用模块化设计,将职责分离到专门的管理器中: - -``` - ┌──────────────────┐ - │ Architecture │ ← 核心协调器 - └────────┬─────────┘ - │ - ┌────────────────────┼────────────────────┐ - │ │ │ - ┌────▼────────┐ ┌──────▼──────┐ ┌────────▼────────┐ - │ Lifecycle │ │ Component │ │ Modules │ - │ Manager │ │ Registry │ │ Manager │ - └─────────────┘ └─────────────┘ └─────────────────┘ - │ │ │ - │ │ │ - 生命周期管理 组件注册管理 模块管理 - - 阶段转换 - System 注册 - 模块安装 - - 钩子管理 - Model 注册 - 行为注册 - - 初始化/销毁 - Utility 注册 -``` - -这种设计遵循单一职责原则,使代码更易维护和测试。 - -``` - ┌──────────────────┐ - │ Architecture │ ← 管理所有组件 - └────────┬─────────┘ - │ - ┌────────────────────┼────────────────────┐ - │ │ │ - ┌───▼────┐ ┌───▼────┐ ┌───▼─────┐ - │ Model │ │ System │ │ Utility │ - │ 层 │ │ 层 │ │ 层 │ - └───┬────┘ └───┬────┘ └────────┘ - │ │ - │ ┌─────────────┤ - │ │ │ - ┌───▼────▼───┐ ┌───▼──────┐ - │ Controller │ │ Command/ │ - │ 层 │ │ Query │ - └─────┬──────┘ └──────────┘ - │ - ┌─────▼─────┐ - │ View │ - │ UI │ - └───────────┘ -``` - -### 数据流向 - -``` -用户输入 → Controller → Command → System → Model → Event → Controller → View 更新 - -查询流程:Controller → Query → Model → 返回数据 -``` - -## 快速开始 - -本框架采用"约定优于配置"的设计理念,只需 4 步即可搭建完整的架构。 - -### 为什么需要这个框架? - -在传统开发中,我们经常遇到这些问题: - -- 代码耦合严重:UI 直接访问游戏逻辑,逻辑直接操作 UI -- 难以维护:修改一个功能需要改动多个文件 -- 难以测试:业务逻辑和 UI 混在一起无法独立测试 -- 难以复用:代码紧密耦合,无法在其他项目中复用 - -本框架通过清晰的分层解决这些问题。 - -### 1. 定义架构(Architecture) - -**作用**:Architecture 是整个应用的"中央调度器",负责管理所有组件的生命周期。 +最小示例: ```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() { - // 注册 Model - 游戏数据 - RegisterModel(new PlayerModel()); - - // 注册 System - 业务逻辑 - RegisterSystem(new CombatSystem()); - - // 注册 Utility - 工具类 - RegisterUtility(new StorageUtility()); + RegisterModel(new CounterModel()); + RegisterSystem(new CounterSystem()); } } ``` -**优势**: +对应的完整起步示例见: -- **依赖注入**:组件通过上下文获取架构引用 -- **集中管理**:所有组件注册在一处,一目了然 -- **生命周期管理**:自动初始化和销毁 -- **平台无关**:可以在任何 .NET 环境中使用 +- [快速开始](../getting-started/quick-start.md) -### 2. 定义 Model(数据层) +## 新项目如何选择能力 -**作用**:Model 是应用的"数据库",只负责存储和管理状态。 +- 只需要基础架构、事件、日志、资源、协程: + - 先停留在 `Core` +- 要写新的请求/通知处理流: + - 优先阅读 [cqrs](./cqrs.md) +- 要接入游戏内容配置、设置、数据仓库、Scene 或 UI: + - 转到 [Game](../game/index.md) +- 要接入 Godot 节点、场景和项目元数据生成: + - 转到 `Godot` 与 Source Generators 栏目 -```csharp -public class PlayerModel : AbstractModel -{ - // 使用 BindableProperty 实现响应式数据 - public BindableProperty Health { get; } = new(100); - public BindableProperty Gold { get; } = new(0); - - protected override void OnInit() - { - // Model 中可以监听自己的数据变化 - Health.Register(hp => - { - if (hp <= 0) this.SendEvent(new PlayerDiedEvent()); - }); - } -} +## 推荐阅读顺序 -// 也可以不使用 BindableProperty -public class PlayerModel : AbstractModel -{ - public int Health { get; private set; } - public int Gold { get; private set; } - - protected override void OnInit() - { - Health = 100; - Gold = 0; - } -} -``` +1. [快速开始](../getting-started/quick-start.md) +2. [architecture](./architecture.md) +3. [context](./context.md) +4. [lifecycle](./lifecycle.md) +5. [cqrs](./cqrs.md) -**优势**: +之后再按实际需要进入具体专题页,而不是把 `Core` 当成一次性读完的大杂烩。 -- **数据响应式**:BindableProperty 让数据变化自动通知监听者 -- **职责单一**:只存储数据,不包含复杂业务逻辑 -- **易于测试**:可以独立测试数据逻辑 +## 对应模块入口 -### 3. 定义 System(业务逻辑层) - -**作用**:System 是应用的"大脑",处理所有业务逻辑。 - -```csharp -public class CombatSystem : AbstractSystem -{ - protected override void OnInit() - { - // System 通过事件驱动,响应游戏中的各种事件 - this.RegisterEvent(OnEnemyAttack); - } - - private void OnEnemyAttack(EnemyAttackEvent e) - { - var playerModel = this.GetModel(); - - // 处理业务逻辑:计算伤害、更新数据 - playerModel.Health.Value -= e.Damage; - - // 发送事件通知其他组件 - this.SendEvent(new PlayerTookDamageEvent { Damage = e.Damage }); - } -} -``` - -**优势**: - -- **事件驱动**:通过事件解耦,不同 System 之间松耦合 -- **可组合**:多个 System 协同工作,每个专注自己的领域 -- **易于扩展**:新增功能只需添加新的 System 和事件监听 - -### 4. 定义 Controller(控制层) - -**作用**:Controller 是"桥梁",连接 UI 和业务逻辑。 - -```csharp -public class PlayerController : IController -{ - // 通过依赖注入获取架构 - private readonly IArchitecture _architecture; - - public PlayerController(IArchitecture architecture) - { - _architecture = architecture; - } - - // 监听模型变化 - public void Initialize() - { - var playerModel = _architecture.GetModel(); - - // 数据绑定:Model 数据变化自动更新 UI - playerModel.Health.RegisterWithInitValue(OnHealthChanged); - } - - private void OnHealthChanged(int hp) - { - // 更新 UI 显示 - UpdateHealthDisplay(hp); - } - - private void UpdateHealthDisplay(int hp) { /* UI 更新逻辑 */ } -} -``` - -**优势**: - -- **自动更新 UI**:通过 BindableProperty,数据变化自动反映到界面 -- **分离关注点**:UI 逻辑和业务逻辑完全分离 -- **易于测试**:可以通过依赖注入模拟架构进行测试 - -### 完成!现在你有了一个完整的架构 - -这 4 步完成后,你就拥有了: - -- **清晰的数据层**(Model) -- **独立的业务逻辑**(System) -- **灵活的控制层**(Controller) -- **统一的生命周期管理**(Architecture) - -### 下一步该做什么? - -1. **添加 Command**:封装用户操作(如购买物品、使用技能) -2. **添加 Query**:封装数据查询(如查询背包物品数量) -3. **添加更多 System**:如任务系统、背包系统、商店系统 -4. **使用 Utility**:添加工具类(如存档工具、数学工具) -5. **使用模块**:通过 IArchitectureModule 扩展架构功能 - -## 包说明 - -### Architecture 内部结构 (v1.1.0+) - -从 v1.1.0 开始,Architecture 类采用模块化设计,将原本 708 行的单一类拆分为多个职责清晰的协作者: - -#### 1. Architecture (核心协调器) - -**职责**: 提供统一的公共 API,协调各个管理器 - -**主要方法**: - -- `RegisterSystem()` - 注册系统 -- `RegisterModel()` - 注册模型 -- `RegisterUtility()` - 注册工具 -- `InstallModule()` - 安装模块 -- `InitializeAsync()` / `Initialize()` - 初始化架构 -- `DestroyAsync()` / `Destroy()` - 销毁架构 - -**事件**: - -- `PhaseChanged` - 阶段变更事件 - -#### 2. ArchitectureBootstrapper (初始化基础设施编排器) - -**职责**: 在用户 `OnInitialize()` 执行前准备环境、服务和上下文,并在组件初始化完成后执行初始化收尾 - -**核心功能**: - -- 初始化环境对象 -- 注册内置服务模块 -- 绑定架构上下文到 `GameContext` -- 执行服务钩子 -- 在 `InitializeAllComponentsAsync()` 完成后通过 `CompleteInitialization()` 冻结 IoC 容器 - -#### 3. ArchitectureLifecycle (生命周期管理器) - -**职责**: 管理架构的生命周期和阶段转换 - -**核心功能**: - -- 11 个架构阶段的管理和转换 -- 生命周期钩子 (IArchitectureLifecycleHook) 管理 -- 组件初始化 (按 Utility → Model → System 顺序) -- 组件销毁 (逆序销毁) -- 就绪状态管理 - -**关键方法**: - -- `EnterPhase()` - 进入指定阶段 -- `RegisterLifecycleHook()` - 注册生命周期钩子 -- `InitializeAllComponentsAsync()` - 初始化所有组件 -- `DestroyAsync()` - 异步销毁 - -#### 4. ArchitectureComponentRegistry (组件注册管理器) - -**职责**: 管理 System、Model、Utility 的注册 - -**核心功能**: - -- 组件注册和验证 -- 自动设置组件上下文 (IContextAware) -- 自动注册组件生命周期 (IInitializable、IDestroyable) -- 支持实例注册和类型注册 - -**关键方法**: - -- `RegisterSystem()` - 注册系统 -- `RegisterModel()` - 注册模型 -- `RegisterUtility()` - 注册工具 - -> 命名提醒: 公开的 `ArchitectureServices` 负责容器和基础服务,并不承担组件注册职责。 -> `ArchitectureComponentRegistry` 才是内部的 System / Model / Utility 注册器。 - -#### 5. ArchitectureModules (模块管理器) - -**职责**: 管理架构模块和 CQRS 管道行为 - -**核心功能**: - -- 模块安装 (IArchitectureModule) -- CQRS 管道行为注册(推荐 API 为 `RegisterCqrsPipelineBehavior`) - -**关键方法**: - -- `InstallModule()` - 安装模块 -- `RegisterCqrsPipelineBehavior()` - 注册 CQRS 管道行为 - -#### 设计优势 - -这种模块化设计带来以下优势: - -1. **单一职责**: 每个类只负责一个明确的功能 -2. **易于测试**: 可以独立测试每个管理器 -3. **易于维护**: 修改某个功能不影响其他功能 -4. **易于扩展**: 添加新功能更容易 -5. **代码安全**: 消除了 `null!` 断言,所有字段在构造后立即可用 - -详细的设计决策已在架构实现重构中落地。 - ---- - -## 包说明 - -| 包名 | 职责 | 文档 | -|----------------------|-----------------|--------------------------| -| **architecture** | 架构核心,管理所有组件生命周期 | [查看](./architecture) | -| **constants** | 框架常量定义 | 本文档 | -| **model** | 数据模型层,存储状态 | [查看](./model) | -| **system** | 业务逻辑层,处理业务规则 | [查看](./system) | -| **controller** | 控制器层,连接视图和逻辑 | (在 Abstractions 中) | -| **utility** | 工具类层,提供无状态工具 | [查看](./utility) | -| **command** | 命令模式,封装写操作 | [查看](./command) | -| **query** | 查询模式,封装读操作 | [查看](./query) | -| **events** | 事件系统,组件间通信 | [查看](./events) | -| **property** | 可绑定属性,响应式编程 | [查看](./property) | -| **state-management** | 集中式状态容器与选择器 | [查看](./state-management) | -| **ioc** | IoC 容器,依赖注入 | [查看](./ioc) | -| **rule** | 规则接口,定义组件约束 | [查看](./rule) | -| **extensions** | 扩展方法,简化 API 调用 | [查看](./extensions) | -| **logging** | 日志系统,记录运行日志 | [查看](./logging) | -| **environment** | 环境接口,提供运行环境信息 | [查看](./environment) | -| **localization** | 本地化系统,多语言支持 | [查看](./localization) | - -## 组件联动 - -### 1. 初始化流程 - -``` -创建 Architecture 实例 - └─> 构造函数 - ├─> 初始化 Logger - ├─> 创建 ArchitectureBootstrapper - ├─> 创建 ArchitectureLifecycle - ├─> 创建 ArchitectureComponentRegistry - └─> 创建 ArchitectureModules - └─> InitializeAsync() - ├─> Bootstrapper 准备环境/服务/上下文 - ├─> OnInitialize() (用户注册组件) - │ ├─> RegisterModel → Model.SetContext() - │ ├─> RegisterSystem → System.SetContext() - │ └─> RegisterUtility → 注册到容器 - ├─> InitializeAllComponentsAsync() - │ ├─> BeforeUtilityInit → Utility.Initialize() - │ ├─> BeforeModelInit → Model.Initialize() - │ └─> BeforeSystemInit → System.Initialize() - ├─> CompleteInitialization() → 冻结 IoC 容器 - └─> 进入 Ready -``` - -**重要变更 (v1.1.0)**: 管理器现在在构造函数中初始化,而不是在 InitializeAsync 中。这消除了 `null!` 断言,提高了代码安全性。 - -### 2. Command 执行流程 - -``` -Controller.SendCommand(command) - └─> command.Execute() - └─> command.OnDo() // 子类实现 - ├─> GetModel() // 获取数据 - ├─> 修改 Model 数据 - └─> SendEvent() // 发送事件 -``` - -### 3. Event 传播流程 - -``` -组件.SendEvent(event) - └─> TypeEventSystem.Send(event) - └─> 通知所有订阅者 - ├─> Controller 响应 → 更新 UI - ├─> System 响应 → 执行逻辑 - └─> Model 响应 → 更新状态 -``` - -### 4. BindableProperty 数据绑定 - -``` -Model: BindableProperty Health = new(100); -Controller: Health.RegisterWithInitValue(hp => UpdateUI(hp)) -修改值: Health.Value = 50 → 触发所有回调 → 更新 UI -``` - -## 最佳实践 - -### 1. 分层职责原则 - -每一层都有明确的职责边界,遵循这些原则能让代码更清晰、更易维护。 - -**Model 层**: - -```csharp -// 好:只存储数据 -public class PlayerModel : AbstractModel -{ - public BindableProperty Health { get; } = new(100); - protected override void OnInit() { } -} - -// 坏:包含业务逻辑 -public class PlayerModel : AbstractModel -{ - public void TakeDamage(int damage) // 业务逻辑应在 System - { - Health.Value -= damage; - if (Health.Value <= 0) Die(); - } -} -``` - -**System 层**: - -```csharp -// 好:处理业务逻辑 -public class CombatSystem : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnAttack); - } - - private void OnAttack(AttackEvent e) - { - var target = this.GetModel(); - int finalDamage = CalculateDamage(e.BaseDamage, target); - target.Health.Value -= finalDamage; - } -} -``` - -### 2. 通信方式选择指南 - -| 通信方式 | 使用场景 | 优势 | -|----------------------|-----------|----------| -| **Command** | 用户操作、修改状态 | 可撤销、可记录 | -| **Query** | 查询数据、检查条件 | 明确只读意图 | -| **Event** | 通知其他组件 | 松耦合、可扩展 | -| **BindableProperty** | 数据变化通知 | 自动化、不会遗漏 | - -### 3. 生命周期管理 - -**为什么需要注销?** - -忘记注销监听器会导致: - -- **内存泄漏**:对象无法被 GC 回收 -- **逻辑错误**:已销毁的对象仍在响应事件 - -```csharp -// 使用 UnRegisterList 统一管理 -private IUnRegisterList _unregisterList = new UnRegisterList(); - -public void Initialize() -{ - this.RegisterEvent(OnEvent1) - .AddToUnregisterList(_unregisterList); - - model.Property.Register(OnPropertyChanged) - .AddToUnregisterList(_unregisterList); -} - -public void Cleanup() -{ - _unregisterList.UnRegisterAll(); -} -``` - -### 4. 性能优化技巧 - -```csharp -// 低效:每帧都查询 -var model = _architecture.GetModel(); // 频繁调用 - -// 高效:缓存引用 -private PlayerModel _playerModel; - -public void Initialize() -{ - _playerModel = _architecture.GetModel(); // 只查询一次 -} -``` - -## 设计理念 - -框架的设计遵循 SOLID 原则和经典设计模式。 - -### 1. 单一职责原则(SRP) - -- **Model**:只负责存储数据 -- **System**:只负责处理业务逻辑 -- **Controller**:只负责协调和输入处理 -- **Utility**:只负责提供工具方法 - -### 2. 开闭原则(OCP) - -- 通过**事件系统**添加新功能,无需修改现有代码 -- 新的 System 可以监听现有事件,插入自己的逻辑 - -### 3. 依赖倒置原则(DIP) - -- 所有组件通过接口交互 -- 通过 IoC 容器注入依赖 -- 易于替换实现和编写测试 - -### 4. 接口隔离原则(ISP) - -```csharp -// 小而专注的接口 -public interface ICanGetModel : IBelongToArchitecture { } -public interface ICanSendCommand : IBelongToArchitecture { } -public interface ICanRegisterEvent : IBelongToArchitecture { } - -// 组合需要的能力 -public interface IController : - ICanGetModel, - ICanSendCommand, - ICanRegisterEvent { } -``` - -### 5. 组合优于继承 - -通过接口组合获得能力,而不是通过继承。 - -### 框架核心设计模式 - -| 设计模式 | 应用位置 | 解决的问题 | 带来的好处 | -|-----------|------------|----------|--------| -| **工厂模式** | IoC 容器 | 组件的创建和管理 | 解耦创建逻辑 | -| **观察者模式** | Event 系统 | 组件间的通信 | 松耦合通信 | -| **命令模式** | Command | 封装操作请求 | 支持撤销重做 | -| **策略模式** | System | 不同的业务逻辑 | 易于切换策略 | -| **依赖注入** | 整体架构 | 组件间的依赖 | 自动管理依赖 | -| **模板方法** | Abstract 类 | 定义算法骨架 | 统一流程规范 | - -### 平台无关性 - -- **GFramework.Core**:纯 .NET 库,无任何平台特定代码 -- **GFramework.Godot**:Godot 特定实现,包含 Node 扩展、GodotLogger 等 -- 可以轻松将 Core 框架移植到其他平台(Unity、.NET MAUI 等) - ---- - -**版本**: 1.1.0 -**更新日期**: 2026-03-17 -**许可证**: Apache 2.0 - -## 更新日志 - -### v1.1.0 (2026-03-17) - -**重大重构**: - -- 拆分 Architecture 类为 4 个职责清晰的类 -- 消除 3 处 `null!` 强制断言,提高代码安全性 -- 在构造函数中初始化管理器,符合"构造即完整"原则 -- 添加 `PhaseChanged` 事件,支持阶段监听 - -**向后兼容**: 所有公共 API 保持不变,现有代码无需修改。 +- `GFramework.Core/README.md` +- `GFramework.Core.Abstractions/README.md` +- 仓库根 `README.md` diff --git a/docs/zh-CN/game/index.md b/docs/zh-CN/game/index.md index 37d8949a..7a0738dd 100644 --- a/docs/zh-CN/game/index.md +++ b/docs/zh-CN/game/index.md @@ -1,1423 +1,117 @@ -# GFramework.Game +# Game -> 游戏特定功能抽象 - 为游戏开发提供专门的工具和系统 +`Game` 栏目对应 `GFramework.Game` 与 `GFramework.Game.Abstractions` 这层游戏运行时能力。 -GFramework.Game 是 GFramework 框架的游戏特定功能模块,提供了游戏开发中常用的抽象和工具,包括资产管理、存储系统、序列化等核心功能。 +它建立在 `Core` 之上,负责把“能运行的基础架构”继续扩展成“可落地的游戏项目基础设施”,重点覆盖静态内容配置、数据与存档、设置、场景与 UI 路由、序列化和文件存储。 -## 📋 目录 +## 先理解包关系 -- [概述](#概述) -- [核心特性](#核心特性) -- [内容配置系统](#内容配置系统) -- [架构模块系统](#架构模块系统) -- [资产管理](#资产管理) -- [存储系统](#存储系统) -- [序列化系统](#序列化系统) -- [使用示例](#使用示例) -- [最佳实践](#最佳实践) -- [性能特性](#性能特性) +- `GeWuYou.GFramework.Game` + - 游戏层默认运行时实现。 +- `GeWuYou.GFramework.Game.Abstractions` + - 对应的契约层。 +- `GeWuYou.GFramework.Game.SourceGenerators` + - 面向 `schemas/**/*.schema.json` 的配置类型与表包装生成器。 +- `GeWuYou.GFramework.Core` + - 本层直接依赖的底座,提供架构、生命周期、事件、日志等通用能力。 -## 概述 +如果你的项目只想依赖契约,不想带入默认实现,请选择 `Game.Abstractions`。如果你需要可运行的默认基类与实现,请使用 +`Game`。 -GFramework.Game 为游戏开发提供了专门的功能模块,与 GFramework.Core 的平台无关特性完美结合,为游戏项目提供了一整套完整的解决方案。 +## 这个栏目应该回答什么 -### 核心设计理念 +`Game` 栏目聚焦“游戏项目该怎么接这些运行时能力”,不再保留与当前实现脱节的泛化模块示例。 -- **游戏导向**:专门针对游戏开发场景设计 -- **模块化架构**:可插拔的模块系统,按需组合 -- **数据持久化**:完善的存档和数据管理方案 -- **资源管理**:高效的资源加载和管理机制 +当前栏目主要入口: -## 核心特性 +- 配置与内容系统 + - [config-system](./config-system.md) +- 数据、设置、序列化与存储 + - [data](./data.md) + - [setting](./setting.md) + - [serialization](./serialization.md) + - [storage](./storage.md) +- 导航与界面 + - [scene](./scene.md) + - [ui](./ui.md) -### 🏗️ 模块化架构 +## 最小接入路径 -- **AbstractModule**:可重用的架构模块基类 -- **生命周期管理**:与框架生命周期深度集成 -- **依赖注入**:模块间的依赖自动管理 -- **配置驱动**:灵活的模块配置系统 +接入 `Game` 层前,先确保你已经完成 `Core` 的基础架构初始化。 -### 📦 资产管理 +最常见的四种接入目标如下: -- **统一资源目录**:集中化的资源注册和查询 -- **类型安全**:编译时类型检查和泛型支持 -- **重复检测**:自动检测资源重复注册 -- **映射支持**:灵活的资源映射和别名系统 - -### 💾 存储系统 - -- **分层存储**:命名空间支持的存储隔离 -- **多格式支持**:JSON、二进制等多种存储格式 -- **异步操作**:完整的异步存储 API -- **版本兼容**:存档版本管理和迁移支持 - -### 🔄 序列化系统 - -- **JSON 集成**:基于 Newtonsoft.Json 的序列化 -- **自定义序列化**:支持自定义序列化逻辑 -- **性能优化**:序列化缓存和优化策略 -- **类型安全**:强类型的序列化和反序列化 - -## 内容配置系统 - -`GFramework.Game` 当前包含面向静态游戏内容的 AI-First 配表能力,用于怪物、物品、技能、任务等只读内容数据。 - -这一能力的核心定位是: - -- 使用 `YAML` 作为配置源文件 -- 使用 `JSON Schema` 描述结构和约束 -- 在运行时以只读配置表形式暴露 -- 通过 Source Generator 生成配置类型和表包装 -- 配套 VS Code 工具提供浏览、校验和轻量编辑入口 - -如果你准备在游戏项目中接入这套系统,请先阅读: - -- [游戏内容配置系统](/zh-CN/game/config-system) - -该页面包含目录约定、运行时注册方式、schema 绑定方式、生成器接入约定和当前限制说明。 - -## 架构模块系统 - -### AbstractModule 基础使用 - -```csharp -using GFramework.Game.Architecture; - -public class AudioModule : AbstractModule -{ - public override void Install(IArchitecture architecture) - { - // 注册音频相关系统 - architecture.RegisterSystem(new AudioSystem()); - architecture.RegisterSystem(new MusicSystem()); - - // 注册音频工具 - architecture.RegisterUtility(new AudioUtility()); - architecture.RegisterUtility(new MusicUtility()); - } - - public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture) - { - switch (phase) - { - case ArchitecturePhase.BeforeModelInit: - // 在模型初始化前准备音频资源 - PreloadAudioResources(); - break; - - case ArchitecturePhase.Ready: - // 架构准备就绪,开始播放背景音乐 - StartBackgroundMusic(); - break; - - case ArchitecturePhase.Destroying: - // 清理音频资源 - CleanupAudioResources(); - break; - } - } - - private void PreloadAudioResources() - { - // 预加载音频资源 - var audioUtility = architecture.GetUtility(); - audioUtility.PreloadAudio("background_music"); - audioUtility.PreloadAudio("shoot_sound"); - audioUtility.PreloadAudio("explosion_sound"); - } - - private void StartBackgroundMusic() - { - var musicSystem = architecture.GetSystem(); - musicSystem.PlayBackgroundMusic("background_music"); - } - - private void CleanupAudioResources() - { - var audioUtility = architecture.GetUtility(); - audioUtility.UnloadAllAudio(); - } -} -``` - -### 复杂模块示例 - -```csharp -public class SaveModule : AbstractModule -{ - private ISaveSystem _saveSystem; - private IDataMigrationManager _migrationManager; - - public override void Install(IArchitecture architecture) - { - // 注册存储相关组件 - _saveSystem = new SaveSystem(); - architecture.RegisterUtility(_saveSystem); - - _migrationManager = new DataMigrationManager(); - architecture.RegisterUtility(_migrationManager); - - // 注册数据版本管理 - architecture.RegisterSystem(new SaveDataVersionSystem()); - } - - public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture) - { - switch (phase) - { - case ArchitecturePhase.AfterModelInit: - // 在模型初始化后设置数据迁移 - SetupDataMigrations(); - break; - - case ArchitecturePhase.Ready: - // 尝试自动加载存档 - TryAutoLoadSave(); - break; - } - } - - private void SetupDataMigrations() - { - // 设置数据版本迁移 - _migrationManager.RegisterMigration(1, 2, MigratePlayerDataV1ToV2); - _migrationManager.RegisterMigration(2, 3, MigratePlayerDataV2ToV3); - } - - private void TryAutoLoadSave() - { - if (_saveSystem.HasAutoSave()) - { - _saveSystem.LoadAutoSave(); - } - } - - private PlayerData MigratePlayerDataV1ToV2(PlayerData v1Data) - { - return new PlayerData - { - // 迁移逻辑 - Name = v1Data.Name, - Health = v1Data.Health, - // 新增字段 - MaxHealth = 100, - Level = 1 - }; - } - - private PlayerData MigratePlayerDataV2ToV3(PlayerData v2Data) - { - return new PlayerData - { - // 迁移逻辑 - Name = v2Data.Name, - Health = v2Data.Health, - MaxHealth = v2Data.MaxHealth, - Level = v2Data.Level, - // 新增字段 - Experience = 0, - Skills = new List() - }; - } -} -``` - -### 模块配置 - -```csharp -public class ModuleConfig -{ - public string SaveDirectory { get; set; } = "saves"; - public int AutoSaveInterval { get; set; } = 300; // 5分钟 - public bool EnableDataCompression { get; set; } = true; - public int MaxSaveSlots { get; set; } = 10; -} - -public class ConfigurableSaveModule : AbstractModule -{ - private ModuleConfig _config; - - public ConfigurableSaveModule(ModuleConfig config) - { - _config = config; - } - - public override void Install(IArchitecture architecture) - { - var saveSystem = new SaveSystem(_config); - architecture.RegisterUtility(saveSystem); - - // 配置自动保存 - if (_config.AutoSaveInterval > 0) - { - architecture.RegisterSystem(new AutoSaveSystem(_config.AutoSaveInterval)); - } - } -} -``` - -## 资产管理 - -### AbstractAssetCatalogUtility 基础使用 - -```csharp -using GFramework.Game.assets; - -public class GameAssetCatalog : AbstractAssetCatalogUtility -{ - public override void Initialize() - { - base.Initialize(); - - // 注册场景资产 - RegisterSceneUnit("Player", "res://scenes/Player.tscn"); - RegisterSceneUnit("Enemy", "res://scenes/Enemy.tscn"); - RegisterSceneUnit("Bullet", "res://scenes/Bullet.tscn"); - - // 注册场景页面 - RegisterScenePage("MainMenu", "res://ui/MainMenu.tscn"); - RegisterScenePage("GameUI", "res://ui/GameUI.tscn"); - RegisterScenePage("PauseMenu", "res://ui/PauseMenu.tscn"); - - // 注册通用资产 - RegisterAsset("PlayerTexture", "res://textures/player.png"); - RegisterAsset("EnemyTexture", "res://textures/enemy.png"); - RegisterAsset("ShootSound", "res://audio/shoot.wav"); - RegisterAsset("ExplosionSound", "res://audio/explosion.wav"); - } - - // 自定义资产验证 - protected override bool ValidateAsset(string key, string path) - { - if (!FileAccess.FileExists(path)) - { - GD.PrintErr($"Asset file not found: {path}"); - return false; - } - - return true; - } - - // 资产加载完成回调 - protected override void OnAssetLoaded(string key, object asset) - { - GD.Print($"Asset loaded: {key}"); - - // 对特定资产进行额外处理 - if (key == "PlayerTexture") - { - var texture = (Texture2D)asset; - // 预处理纹理... - } - } -} -``` - -### 资产映射系统 - -```csharp -public class AssetMapping -{ - public string Key { get; set; } - public string Path { get; set; } - public Type Type { get; set; } - public Dictionary Metadata { get; set; } = new(); -} - -public class AdvancedAssetCatalog : AbstractAssetCatalogUtility -{ - public override void Initialize() - { - base.Initialize(); - - // 使用映射对象注册资产 - RegisterAsset(new AssetMapping - { - Key = "Player", - Path = "res://scenes/Player.tscn", - Type = typeof(PackedScene), - Metadata = new Dictionary - { - ["category"] = "character", - ["tags"] = new[] { "player", "hero", "controlled" }, - ["health"] = 100, - ["speed"] = 5.0f - } - }); - - // 批量注册 - RegisterAssetsFromDirectory("res://textures/", "*.png", "texture"); - RegisterAssetsFromDirectory("res://audio/", "*.wav", "sound"); - } - - private void RegisterAssetsFromDirectory(string directory, string pattern, string prefix) - { - var dir = DirAccess.Open(directory); - if (dir == null) return; - - dir.ListDirBegin(); - var fileName = dir.GetNext(); - - while (!string.IsNullOrEmpty(fileName)) - { - if (fileName.EndsWith(pattern.Substring(1))) - { - var key = $"{prefix}{Path.GetFileNameWithoutExtension(fileName)}"; - var path = Path.Combine(directory, fileName); - - RegisterAsset(key, path); - } - - fileName = dir.GetNext(); - } - - dir.ListDirEnd(); - } -} -``` - -### 资产工厂模式 - -```csharp -public interface IAssetFactory -{ - T Create(string key); - bool CanCreate(string key); -} - -public class PlayerFactory : IAssetFactory -{ - private readonly AbstractAssetCatalogUtility _catalog; - - public PlayerFactory(AbstractAssetCatalogUtility catalog) - { - _catalog = catalog; - } - - public Player Create(string key) - { - var scene = _catalog.GetScene(key); - var player = scene.Instantiate(); - - // 配置玩家 - player.Health = GetPlayerHealth(key); - player.Speed = GetPlayerSpeed(key); - - return player; - } - - public bool CanCreate(string key) - { - return _catalog.HasScene(key) && key.StartsWith("Player"); - } - - private int GetPlayerHealth(string key) - { - var metadata = _catalog.GetAssetMetadata(key); - return metadata?.GetValueOrDefault("health", 100) ?? 100; - } - - private float GetPlayerSpeed(string key) - { - var metadata = _catalog.GetAssetMetadata(key); - return metadata?.GetValueOrDefault("speed", 5.0f) ?? 5.0f; - } -} -``` - -## 存储系统 - -### ScopedStorage 分层存储 +### 1. 只想先拿到文件存储 ```csharp +using GFramework.Core.Abstractions.Storage; +using GFramework.Game.Serializer; using GFramework.Game.Storage; -public class GameDataManager -{ - private readonly IStorage _rootStorage; - private readonly IStorage _playerStorage; - private readonly IStorage _saveStorage; - - public GameDataManager(IStorage rootStorage) - { - _rootStorage = rootStorage; - - // 创建分层存储 - _playerStorage = new ScopedStorage(rootStorage, "player"); - _saveStorage = new ScopedStorage(rootStorage, "saves"); - } - - public void SavePlayerData(string playerId, PlayerData data) - { - _playerStorage.Write($"{playerId}/profile", data); - _playerStorage.Write($"{playerId}/inventory", data.Inventory); - _playerStorage.Write($"{playerId}/stats", data.Stats); - } - - public PlayerData LoadPlayerData(string playerId) - { - var profile = _playerStorage.Read($"{playerId}/profile"); - var inventory = _playerStorage.Read($"{playerId}/inventory", new Inventory()); - var stats = _playerStorage.Read($"{playerId}/stats", new PlayerStats()); - - return new PlayerData - { - Profile = profile, - Inventory = inventory, - Stats = stats - }; - } - - public void SaveGame(int slotId, SaveData data) - { - _saveStorage.Write($"slot_{slotId}", data); - _saveStorage.Write($"slot_{slotId}/timestamp", DateTime.UtcNow); - _saveStorage.Write($"slot_{slotId}/version", data.Version); - } - - public SaveData LoadGame(int slotId) - { - return _saveStorage.Read($"slot_{slotId}"); - } - - public List GetSaveSlotInfos() - { - var infos = new List(); - - for (int i = 1; i <= 10; i++) - { - var timestamp = _saveStorage.Read($"slot_{i}/timestamp"); - var version = _saveStorage.Read($"slot_{i}/version"); - - if (timestamp != default) - { - infos.Add(new SaveSlotInfo - { - SlotId = i, - Timestamp = timestamp, - Version = version - }); - } - } - - return infos; - } -} +var serializer = new JsonSerializer(); +IStorage storage = new FileStorage("GameData", serializer); ``` -### 自定义存储实现 +### 2. 接入设置与存档 -```csharp -public class EncryptedStorage : IStorage -{ - private readonly IStorage _innerStorage; - private readonly IEncryptor _encryptor; - - public EncryptedStorage(IStorage innerStorage, IEncryptor encryptor) - { - _innerStorage = innerStorage; - _encryptor = encryptor; - } - - public void Write(string key, T data) - { - var json = JsonConvert.SerializeObject(data); - var encrypted = _encryptor.Encrypt(json); - _innerStorage.Write(key, encrypted); - } - - public T Read(string key, T defaultValue = default) - { - var encrypted = _innerStorage.Read(key); - if (string.IsNullOrEmpty(encrypted)) - return defaultValue; - - var json = _encryptor.Decrypt(encrypted); - return JsonConvert.DeserializeObject(json); - } - - public async Task WriteAsync(string key, T data) - { - var json = JsonConvert.SerializeObject(data); - var encrypted = _encryptor.Encrypt(json); - await _innerStorage.WriteAsync(key, encrypted); - } - - public async Task ReadAsync(string key, T defaultValue = default) - { - var encrypted = await _innerStorage.ReadAsync(key); - if (string.IsNullOrEmpty(encrypted)) - return defaultValue; - - var json = _encryptor.Decrypt(encrypted); - return JsonConvert.DeserializeObject(json); - } - - public bool Has(string key) - { - return _innerStorage.Has(key); - } - - public void Delete(string key) - { - _innerStorage.Delete(key); - } - - public void Clear() - { - _innerStorage.Clear(); - } -} -``` +典型顺序是: -### 存储缓存层 +1. 注册 `JsonSerializer` +2. 注册 `IStorage` +3. 注册 `ISettingsDataRepository` +4. 注册 `SettingsModel` +5. 注册 `SettingsSystem` +6. 注册 `ISaveRepository` -```csharp -public class CachedStorage : IStorage -{ - private readonly IStorage _innerStorage; - private readonly Dictionary _cache; - private readonly Dictionary _cacheTimestamps; - private readonly TimeSpan _cacheExpiry; - - public CachedStorage(IStorage innerStorage, TimeSpan cacheExpiry = default) - { - _innerStorage = innerStorage; - _cacheExpiry = cacheExpiry == default ? TimeSpan.FromMinutes(5) : cacheExpiry; - _cache = new Dictionary(); - _cacheTimestamps = new Dictionary(); - } - - public T Read(string key, T defaultValue = default) - { - if (_cache.TryGetValue(key, out var cachedValue) && - !IsCacheExpired(key)) - { - return (T)cachedValue; - } - - var value = _innerStorage.Read(key, defaultValue); - UpdateCache(key, value); - - return value; - } - - public void Write(string key, T data) - { - _innerStorage.Write(key, data); - UpdateCache(key, data); - } - - public async Task ReadAsync(string key, T defaultValue = default) - { - if (_cache.TryGetValue(key, out var cachedValue) && - !IsCacheExpired(key)) - { - return (T)cachedValue; - } - - var value = await _innerStorage.ReadAsync(key, defaultValue); - UpdateCache(key, value); - - return value; - } - - public async Task WriteAsync(string key, T data) - { - await _innerStorage.WriteAsync(key, data); - UpdateCache(key, data); - } - - public bool Has(string key) - { - return _cache.ContainsKey(key) || _innerStorage.Has(key); - } - - public void Delete(string key) - { - _cache.Remove(key); - _cacheTimestamps.Remove(key); - _innerStorage.Delete(key); - } - - public void Clear() - { - _cache.Clear(); - _cacheTimestamps.Clear(); - _innerStorage.Clear(); - } - - public void ClearCache() - { - _cache.Clear(); - _cacheTimestamps.Clear(); - } - - private bool IsCacheExpired(string key) - { - if (!_cacheTimestamps.TryGetValue(key, out var timestamp)) - return true; - - return DateTime.UtcNow - timestamp > _cacheExpiry; - } - - private void UpdateCache(string key, T value) - { - _cache[key] = value; - _cacheTimestamps[key] = DateTime.UtcNow; - } -} -``` +具体组合方式见: -## 序列化系统 +- [setting](./setting.md) +- [data](./data.md) +- [storage](./storage.md) -### JsonSerializer 使用 +### 3. 接入静态 YAML 配置 -```csharp -using GFramework.Game.Serializer; -using Newtonsoft.Json; +当你要维护怪物、物品、技能、任务等只读内容数据时,优先使用: -public class GameDataSerializer -{ - private readonly JsonSerializer _serializer; - - public GameDataSerializer() - { - // 在构造阶段完成全部 JsonSerializerSettings / Converter 配置, - // 后续把 _serializer 视为共享只读实例。 - _serializer = new JsonSerializer(new JsonSerializerSettings - { - Formatting = Formatting.Indented, - NullValueHandling = NullValueHandling.Ignore, - DefaultValueHandling = DefaultValueHandling.Populate, - TypeNameHandling = TypeNameHandling.None - }); - - // 自定义转换器 - _serializer.Converters.Add(new Vector2JsonConverter()); - _serializer.Converters.Add(new ColorJsonConverter()); - _serializer.Converters.Add(new GodotResourceJsonConverter()); - } - - public string Serialize(T data) - { - return _serializer.Serialize(data); - } - - public T Deserialize(string json) - { - return _serializer.Deserialize(json); - } - - public void SerializeToFile(string path, T data) - { - var json = Serialize(data); - FileAccess.Open(path, FileAccess.ModeFlags.Write).StoreString(json); - } - - public T DeserializeFromFile(string path) - { - if (!FileAccess.FileExists(path)) - return default(T); - - var file = FileAccess.Open(path, FileAccess.ModeFlags.Read); - var json = file.GetAsText(); - file.Close(); - - return Deserialize(json); - } -} -``` +- `GFramework.Game` 运行时 +- `GFramework.Game.SourceGenerators` +- `schemas/**/*.schema.json` + `config/**/*.yaml` -对于玩家可直接编辑的存档文件,默认应保持 `TypeNameHandling.None`。只有确实需要多态反序列化时,才应配合 -白名单 `SerializationBinder` 显式限制允许的类型集合。 +完整约定见: -### 自定义 JSON 转换器 +- [config-system](./config-system.md) -```csharp -public class Vector2JsonConverter : JsonConverter -{ - public override void WriteJson(JsonWriter writer, Vector2 value, JsonSerializer serializer) - { - writer.WriteStartObject(); - writer.WritePropertyName("x"); - writer.WriteValue(value.X); - writer.WritePropertyName("y"); - writer.WriteValue(value.Y); - writer.WriteEndObject(); - } - - public override Vector2 ReadJson(JsonReader reader, Type objectType, Vector2 existingValue, bool hasExistingValue, JsonSerializer serializer) - { - if (reader.TokenType == JsonToken.Null) - return Vector2.Zero; - - var obj = serializer.Deserialize>(reader); - return new Vector2(obj["x"], obj["y"]); - } -} +### 4. 接入 Scene / UI 路由 -public class ColorJsonConverter : JsonConverter -{ - public override void WriteJson(JsonWriter writer, Color value, JsonSerializer serializer) - { - writer.WriteValue(value.ToHtml()); - } - - public override Color ReadJson(JsonReader reader, Type objectType, Color existingValue, bool hasExistingValue, JsonSerializer serializer) - { - if (reader.TokenType == JsonToken.Null) - return Colors.White; - - var html = reader.Value.ToString(); - return Color.FromHtml(html); - } -} +`SceneRouterBase` 与 `UiRouterBase` 提供的是可复用基类,不直接绑定具体引擎。工厂、root、注册表通常由引擎适配层或项目自身提供。 -public class GodotResourceJsonConverter : JsonConverter -{ - public override void WriteJson(JsonWriter writer, Resource value, JsonSerializer serializer) - { - if (value == null) - { - writer.WriteNull(); - return; - } - - writer.WriteStartObject(); - writer.WritePropertyName("$type"); - writer.WriteValue(value.GetType().Name); - writer.WritePropertyName("path"); - writer.WriteValue(value.ResourcePath); - writer.WriteEndObject(); - } - - public override Resource ReadJson(JsonReader reader, Type objectType, Resource existingValue, bool hasExistingValue, JsonSerializer serializer) - { - if (reader.TokenType == JsonToken.Null) - return null; - - var obj = serializer.Deserialize>(reader); - var path = obj["path"]; - - return GD.Load(path); - } -} -``` +入口见: -### 版本化序列化 +- [scene](./scene.md) +- [ui](./ui.md) -```csharp -public class VersionedData -{ - public int Version { get; set; } - public Dictionary Data { get; set; } = new(); -} +## 推荐阅读顺序 -public class VersionedSerializer -{ - private readonly Dictionary _typeVersions = new(); - private readonly Dictionary _migrations = new(); - - public void RegisterVersion(int version) - { - _typeVersions[typeof(T)] = version; - } - - public void RegisterMigration(int fromVersion, int toVersion, IDataMigration migration) - { - var key = GetMigrationKey(typeof(T), fromVersion, toVersion); - _migrations[key] = migration; - } - - public string Serialize(T data) - { - var versioned = new VersionedData - { - Version = _typeVersions.GetValueOrDefault(typeof(T), 1), - Data = new Dictionary - { - ["type"] = typeof(T).Name, - ["data"] = JsonConvert.SerializeObject(data) - } - }; - - return JsonConvert.SerializeObject(versioned); - } - - public T Deserialize(string json) - { - var versioned = JsonConvert.DeserializeObject(json); - var currentVersion = _typeVersions.GetValueOrDefault(typeof(T), 1); - - // 迁移数据到当前版本 - var dataJson = MigrateData(versioned.Data["data"] as string, versioned.Version, currentVersion); - - return JsonConvert.DeserializeObject(dataJson); - } - - private string MigrateData(string dataJson, int fromVersion, int toVersion) - { - var currentData = dataJson; - var currentVersion = fromVersion; - - while (currentVersion < toVersion) - { - var migrationKey = GetMigrationKey(typeof(T), currentVersion, currentVersion + 1); - - if (_migrations.TryGetValue(migrationKey, out var migration)) - { - currentData = migration.Migrate(currentData); - currentVersion++; - } - else - { - throw new InvalidOperationException($"No migration found from version {currentVersion} to {currentVersion + 1}"); - } - } - - return currentData; - } - - private int GetMigrationKey(Type type, int fromVersion, int toVersion) - { - return HashCode.Combine(type.Name, fromVersion, toVersion); - } -} +1. [Core 入口](../core/index.md) +2. [config-system](./config-system.md) +3. [data](./data.md) +4. [setting](./setting.md) +5. [scene](./scene.md) 或 [ui](./ui.md) -public interface IDataMigration -{ - string Migrate(string data); -} +## 与真实接法的关系 -public interface IDataMigration : IDataMigration -{ - T Migrate(T data); -} -``` +这个栏目以源码、`*.csproj`、模块 `README.md` 与 `CoreGrid` 中已验证的接法为准。 -## 使用示例 +例如当前仓库与 `CoreGrid` 的共同事实包括: -### 完整的游戏数据管理系统 +- 配置系统采用 `YAML + JSON Schema + Source Generator` +- 设置持久化通常通过 `UnifiedSettingsDataRepository` +- 场景与 UI 路由依赖项目自己的 factory / root,而不是框架替你绑定引擎对象 -```csharp -// 1. 定义数据模型 -public class GameProfile -{ - public string PlayerName { get; set; } - public DateTime LastPlayed { get; set; } - public int TotalPlayTime { get; set; } - public List UnlockedAchievements { get; set; } = new(); -} +如果某个旧页面与这些事实冲突,应以源码和模块 README 为准,并在同一轮里修正文档。 -public class GameSettings -{ - public float MasterVolume { get; set; } = 1.0f; - public float MusicVolume { get; set; } = 0.8f; - public float SFXVolume { get; set; } = 0.9f; - public GraphicsSettings Graphics { get; set; } = new(); - public InputSettings Input { get; set; } = new(); -} +## 对应模块入口 -// 2. 创建游戏管理器 -[ContextAware] -[Log] -public partial class GameManager : Node, IController -{ - private GameAssetCatalog _assetCatalog; - private GameDataManager _dataManager; - private GameDataSerializer _serializer; - - protected override void OnInit() - { - // 初始化资产目录 - _assetCatalog = new GameAssetCatalog(); - _assetCatalog.Initialize(); - - // 初始化存储系统 - var rootStorage = new FileStorage("user://data/"); - var cachedStorage = new CachedStorage(rootStorage); - _dataManager = new GameDataManager(cachedStorage); - - // 初始化序列化器 - _serializer = new GameDataSerializer(); - - Logger.Info("Game manager initialized"); - } - - public void StartNewGame(string playerName) - { - Logger.Info($"Starting new game for player: {playerName}"); - - // 创建新的游戏档案 - var profile = new GameProfile - { - PlayerName = playerName, - LastPlayed = DateTime.UtcNow, - TotalPlayTime = 0 - }; - - _dataManager.SavePlayerData("current", profile); - - // 加载初始场景 - LoadInitialScene(); - - this.SendEvent(new NewGameStartedEvent { PlayerName = playerName }); - } - - public void LoadGame(int slotId) - { - Logger.Info($"Loading game from slot {slotId}"); - - try - { - var saveData = _dataManager.LoadGame(slotId); - if (saveData != null) - { - // 恢复游戏状态 - RestoreGameState(saveData); - - this.SendEvent(new GameLoadedEvent { SlotId = slotId }); - Logger.Info("Game loaded successfully"); - } - else - { - Logger.Warning($"No save data found in slot {slotId}"); - this.SendEvent(new GameLoadFailedEvent { SlotId = slotId }); - } - } - catch (Exception ex) - { - Logger.Error($"Failed to load game: {ex.Message}"); - this.SendEvent(new GameLoadFailedEvent { SlotId = slotId, Error = ex.Message }); - } - } - - public void SaveGame(int slotId) - { - Logger.Info($"Saving game to slot {slotId}"); - - try - { - var saveData = CreateSaveData(); - _dataManager.SaveGame(slotId, saveData); - - this.SendEvent(new GameSavedEvent { SlotId = slotId }); - Logger.Info("Game saved successfully"); - } - catch (Exception ex) - { - Logger.Error($"Failed to save game: {ex.Message}"); - this.SendEvent(new GameSaveFailedEvent { SlotId = slotId, Error = ex.Message }); - } - } - - private void LoadInitialScene() - { - var playerScene = _assetCatalog.GetScene("Player"); - var player = playerScene.Instantiate(); - - var gameWorldScene = _assetCatalog.GetScene("GameWorld"); - var gameWorld = gameWorldScene.Instantiate(); - - AddChild(gameWorld); - gameWorld.AddChild(player); - } - - private void RestoreGameState(SaveData saveData) - { - // 恢复玩家位置 - var playerScene = _assetCatalog.GetScene("Player"); - var player = playerScene.Instantiate(); - player.Position = saveData.PlayerPosition; - - // 恢复游戏世界 - var gameWorldScene = _assetCatalog.GetScene("GameWorld"); - var gameWorld = gameWorldScene.Instantiate(); - - AddChild(gameWorld); - gameWorld.AddChild(player); - - // 恢复其他游戏状态 - this.GetModel().Health.Value = saveData.PlayerHealth; - this.GetModel().CurrentLevel.Value = saveData.CurrentLevel; - this.GetModel().LoadFromData(saveData.Inventory); - } - - private SaveData CreateSaveData() - { - var player = GetTree().CurrentScene.FindChild("Player"); - - return new SaveData - { - PlayerPosition = player?.Position ?? Vector2.Zero, - PlayerHealth = this.GetModel().Health.Value, - CurrentLevel = this.GetModel().CurrentLevel.Value, - Inventory = this.GetModel().GetData(), - Timestamp = DateTime.UtcNow, - Version = 1 - }; - } -} -``` - -### 自动保存系统 - -```csharp -public class AutoSaveSystem : AbstractSystem -{ - private Timer _autoSaveTimer; - private int _currentSaveSlot; - private float _autoSaveInterval; - - public AutoSaveSystem(float intervalMinutes = 5.0f) - { - _autoSaveInterval = intervalMinutes * 60.0f; - } - - protected override void OnInit() - { - _autoSaveTimer = new Timer(); - _autoSaveTimer.WaitTime = _autoSaveInterval; - _autoSaveTimer.Timeout += OnAutoSave; - _autoSaveTimer.Autostart = true; - - AddChild(_autoSaveTimer); - - // 监听重要事件,立即自动保存 - this.RegisterEvent(OnImportantEvent); - this.RegisterEvent(OnImportantEvent); - this.RegisterEvent(OnImportantEvent); - - Logger.Info("Auto-save system initialized"); - } - - protected override void OnDestroy() - { - _autoSaveTimer?.Stop(); - - // 最后一次自动保存 - PerformAutoSave(); - } - - private void OnAutoSave() - { - PerformAutoSave(); - Logger.Debug("Periodic auto-save completed"); - } - - private void OnImportantEvent(IEvent e) - { - Logger.Info($"Important event {e.GetType().Name}, triggering auto-save"); - PerformAutoSave(); - } - - private void PerformAutoSave() - { - try - { - var saveData = CreateAutoSaveData(); - - // 保存到自动存档槽 - var storage = this.GetUtility(); - storage.Write("autosave", saveData); - storage.Write("autosave/timestamp", DateTime.UtcNow); - - Logger.Debug("Auto-save completed successfully"); - } - catch (Exception ex) - { - Logger.Error($"Auto-save failed: {ex.Message}"); - } - } - - private SaveData CreateAutoSaveData() - { - return new SaveData - { - // ... 创建保存数据 - }; - } -} -``` - -## 最佳实践 - -### 🏗️ 数据模型设计 - -#### 1. 版本化数据结构 - -```csharp -// 好的做法:版本化数据模型 -[Serializable] -public class PlayerDataV2 -{ - public int Version { get; set; } = 2; - public string Name { get; set; } - public int Health { get; set; } - public int MaxHealth { get; set; } = 100; // V2 新增 - public List Skills { get; set; } = new(); // V2 新增 -} - -// 避免:没有版本控制 -[Serializable] -public class PlayerData -{ - public string Name { get; set; } - public int Health { get; set; } - // 未来新增字段会导致兼容性问题 -} -``` - -#### 2. 数据验证 - -```csharp -public class PlayerData -{ - private string _name; - - public string Name - { - get => _name; - set - { - if (string.IsNullOrWhiteSpace(value)) - throw new ArgumentException("Player name cannot be empty"); - _name = value; - } - } - - public int Health - { - get => _health; - set => _health = Math.Max(0, value); // 确保不为负数 - } - - public void Validate() - { - if (string.IsNullOrWhiteSpace(Name)) - throw new ValidationException("Player name is required"); - - if (Health < 0) - throw new ValidationException("Health cannot be negative"); - } -} -``` - -### 💾 存储策略 - -#### 1. 分层存储命名 - -```csharp -// 好的做法:有意义的分层结构 -var playerStorage = new ScopedStorage(rootStorage, "players"); -var saveStorage = new ScopedStorage(rootStorage, "saves"); -var settingsStorage = new ScopedStorage(rootStorage, "settings"); -var tempStorage = new ScopedStorage(rootStorage, "temp"); - -// 避免的混乱命名 -var storage1 = new ScopedStorage(rootStorage, "data1"); -var storage2 = new ScopedStorage(rootStorage, "data2"); -``` - -#### 2. 存储性能优化 - -```csharp -// 好的做法:批量操作和缓存 -public class OptimizedDataManager -{ - private readonly IStorage _storage; - private readonly Dictionary _writeBuffer = new(); - - public void QueueWrite(string key, T data) - { - _writeBuffer[key] = data; - } - - public async Task FlushWritesAsync() - { - var tasks = _writeBuffer.Select(kvp => _storage.WriteAsync(kvp.Key, kvp.Value)); - await Task.WhenAll(tasks); - _writeBuffer.Clear(); - } -} - -// 避免:频繁的小写入 -public class InefficientDataManager -{ - public void UpdatePlayerStat(string stat, int value) - { - _storage.Write($"player/stats/{stat}", value); // 每次都写入磁盘 - } -} -``` - -### 🔄 序列化优化 - -#### 1. 选择合适的序列化格式 - -```csharp -// 好的做法:根据需求选择格式 -public class GameSerializer -{ - // JSON:可读性好,调试方便 - public string SerializeForDebug(object data) => JsonConvert.SerializeObject(data, Formatting.Indented); - - // 二进制:体积小,性能好 - public byte[] SerializeForStorage(object data) => MessagePackSerializer.Serialize(data); - - // 压缩:减少存储空间 - public byte[] SerializeWithCompression(object data) - { - var json = JsonConvert.SerializeObject(data); - return Compress(Encoding.UTF8.GetBytes(json)); - } -} -``` - -#### 2. 自定义序列化逻辑 - -```csharp -public class PlayerInventory -{ - public Dictionary Items { get; set; } = new(); - - [JsonIgnore] // 排除不需要序列化的属性 - public int TotalWeight => Items.Sum(kvp => GetItemWeight(kvp.Key) * kvp.Value); - - [JsonProperty("total_items")] // 自定义序列化名称 - public int TotalItems => Items.Values.Sum(); - - public bool ShouldSerializeItems() // 条件序列化 - { - return Items.Count > 0; - } - - [OnDeserialized] // 反序列化后处理 - private void OnDeserialized(StreamingContext context) - { - // 初始化默认值或执行验证 - Items ??= new Dictionary(); - } -} -``` - -### 🏭 模块设计模式 - -#### 1. 单一职责模块 - -```csharp -// 好的做法:模块职责单一 -public class AudioModule : AbstractModule -{ - // 只负责音频相关功能 -} - -public class SaveModule : AbstractModule -{ - // 只负责存档相关功能 -} - -// 避免:功能过于庞大 -public class GameModule : AbstractModule -{ - // 音频、存档、UI、输入全部混在一起 -} -``` - -#### 2. 模块间通信 - -```csharp -public class SaveModule : AbstractModule -{ - public override void Install(IArchitecture architecture) - { - architecture.RegisterUtility(new SaveUtility()); - } -} - -public class PlayerModule : AbstractModule -{ - public override void Install(IArchitecture architecture) - { - architecture.RegisterSystem(new PlayerSystem()); - } - - public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture) - { - if (phase == ArchitecturePhase.Ready) - { - // 通过事件进行模块间通信 - this.RegisterEvent(OnPlayerDeath); - } - } - - private void OnPlayerDeath(PlayerDeathEvent e) - { - // 触发保存模块的事件 - this.SendEvent(new RequestAutoSaveEvent { Reason = "Player Death" }); - } -} -``` - -## 性能特性 - -### 📊 内存管理 - -- **缓存策略**:多层缓存减少磁盘 I/O -- **延迟加载**:按需加载资源,减少内存占用 -- **对象池化**:重用对象,减少 GC 压力 -- **内存映射**:大文件使用内存映射技术 - -### ⚡ 存储性能 - -```csharp -// 性能对比示例 -public class StoragePerformanceTest -{ - // 同步操作:简单直接 - public void SyncWrite(IStorage storage, string key, object data) - { - storage.Write(key, data); // ~1-5ms - } - - // 异步操作:非阻塞 - public async Task AsyncWrite(IStorage storage, string key, object data) - { - await storage.WriteAsync(key, data); // ~0.1-1ms (不阻塞主线程) - } - - // 批量操作:高吞吐量 - public async Task BatchWrite(IStorage storage, Dictionary data) - { - var tasks = data.Select(kvp => storage.WriteAsync(kvp.Key, kvp.Value)); - await Task.WhenAll(tasks); // ~10-50ms for 100 items - } -} -``` - -### 🔄 序列化优化 - -- **格式选择**:JSON(可读性)vs 二进制(性能) -- **压缩技术**:减少存储空间和网络传输 -- **增量序列化**:只序列化变化的部分 -- **版本控制**:向后兼容的数据迁移 - ---- - -## 依赖关系 - -```mermaid -graph TD - A[GFramework.Game] --> B[GFramework.Core] - A --> C[GFramework.Core.Abstractions] - A --> D[Newtonsoft.Json] - A --> E[System.Text.Json] -``` - -## 版本兼容性 - -- **.NET**: 6.0+ -- **Newtonsoft.Json**: 13.0.3+ -- **GFramework.Core**: 与 Core 模块版本保持同步 ---- +- `GFramework.Game/README.md` +- `GFramework.Game.Abstractions/README.md` +- 仓库根 `README.md` diff --git a/docs/zh-CN/source-generators/index.md b/docs/zh-CN/source-generators/index.md index 7d11b400..cb07dba2 100644 --- a/docs/zh-CN/source-generators/index.md +++ b/docs/zh-CN/source-generators/index.md @@ -1,1320 +1,100 @@ # Source Generators -> 编译时代码生成 - 零运行时开销的代码增强工具 +`Source Generators` 栏目对应 `GFramework` 当前按模块拆分发布的编译期工具链。 -GFramework 当前按模块提供一组 Source Generators,通过编译时分析自动生成样板代码,显著提升开发效率并减少运行时开销。 +这里的重点不是“存在一个统一的大生成器包”,而是帮助你确认应该安装哪个生成器包、它服务哪个运行时模块,以及继续去看哪一类专题页。 -## 📋 目录 +## 当前包拆分 -- [概述](#概述) -- [核心特性](#核心特性) -- [安装配置](#安装配置) -- [Log 属性生成器](#log-属性生成器) -- [Config Schema 生成器](#config-schema-生成器) -- [CQRS Handler Registry 生成器](#cqrs-handler-registry-生成器) -- [ContextAware 属性生成器](#contextaware-属性生成器) -- [GenerateEnumExtensions 属性生成器](#generateenumextensions-属性生成器) -- [Priority 属性生成器](#priority-属性生成器) -- [Context Get 注入生成器](#context-get-注入生成器) -- [AutoRegisterModule 生成器](#autoregistermodule-生成器) -- [Godot 项目元数据生成](#godot-项目元数据生成) -- [GetNode 生成器 (Godot)](#getnode-生成器) -- [BindNodeSignal 生成器 (Godot)](#bindnodesignal-生成器) -- [AutoUiPage 生成器 (Godot)](#autouipage-生成器) -- [AutoScene 生成器 (Godot)](#autoscene-生成器) -- [AutoRegisterExportedCollections 生成器 (Godot)](#autoregisterexportedcollections-生成器) -- [诊断信息](#诊断信息) -- [性能优势](#性能优势) -- [使用示例](#使用示例) -- [最佳实践](#最佳实践) -- [常见问题](#常见问题) - -## 概述 - -GFramework 的 Source Generators 利用 Roslyn 源代码生成器技术,在编译时分析你的代码并自动生成常用的样板代码,让开发者专注于业务逻辑而不是重复的模板代码。 - -当前 NuGet 发布按模块拆分为: +GFramework 当前发布的生成器包是: - `GeWuYou.GFramework.Core.SourceGenerators` - `GeWuYou.GFramework.Game.SourceGenerators` -- `GeWuYou.GFramework.Godot.SourceGenerators` - `GeWuYou.GFramework.Cqrs.SourceGenerators` +- `GeWuYou.GFramework.Godot.SourceGenerators` -不存在 `GeWuYou.GFramework.SourceGenerators` 或 `GeWuYou.GFramework.SourceGenerators.Attributes` 这类聚合包。 +不存在 `GeWuYou.GFramework.SourceGenerators` 或 `*.SourceGenerators.Attributes` 这类聚合包。 -### 核心设计理念 +## 先按场景选包 -- **零运行时开销**:代码在编译时生成,无反射或动态调用 -- **类型安全**:编译时类型检查,避免运行时错误 -- **开发效率**:自动生成样板代码,减少重复工作 -- **可配置性**:支持多种配置选项满足不同需求 +- 想减少日志、上下文注入、模块自动注册等 Core 侧样板代码: + - 选择 `GeWuYou.GFramework.Core.SourceGenerators` +- 想把 `schemas/**/*.schema.json` 生成成配置类型和表包装: + - 选择 `GeWuYou.GFramework.Game.SourceGenerators` +- 想让 CQRS handler registry 在编译期生成,缩小运行时反射扫描范围: + - 选择 `GeWuYou.GFramework.Cqrs.SourceGenerators` +- 想在 Godot 项目里生成 AutoLoad / Input Action 入口,或减少节点与信号样板代码: + - 选择 `GeWuYou.GFramework.Godot.SourceGenerators` -## 核心特性 +## 与运行时的关系 -### 🎯 主要生成器 +这些包都是编译期工具链,不是运行时库。 -- **[Log] 属性**:自动生成 ILogger 字段和日志方法 -- **Config Schema 生成器**:根据 `*.schema.json` 生成配置类型和表包装 -- **CQRS Handler Registry 生成器**:为 CQRS handlers 生成程序集级注册表并缩小运行时反射范围 -- **[ContextAware] 属性**:自动实现 IContextAware 接口 -- **[GenerateEnumExtensions] 属性**:自动生成枚举扩展方法 -- **[Priority] 属性**:自动实现 IPrioritized 接口,为类添加优先级标记 -- **Context Get 注入特性**:自动注入架构组件(GetModel/GetSystem/GetUtility/GetService/GetAll) -- **[AutoRegisterModule] 属性**:为模块类自动生成固定顺序的 `Install(IArchitecture)` 注册代码 +对应关系如下: + +| 生成器包 | 主要服务的运行时 | +| --- | --- | +| `GFramework.Core.SourceGenerators` | `GFramework.Core` | +| `GFramework.Game.SourceGenerators` | `GFramework.Game` | +| `GFramework.Cqrs.SourceGenerators` | `GFramework.Cqrs` | +| `GFramework.Godot.SourceGenerators` | `GFramework.Godot` | + +安装时通常保持生成器包与对应运行时包版本一致,并将生成器声明为: + +```xml + +``` + +其他生成器包的安装模式相同。 + +## 这个栏目怎么读 + +### Core 侧通用生成器 + +- [logging-generator](./logging-generator.md) +- [context-aware-generator](./context-aware-generator.md) +- [context-get-generator](./context-get-generator.md) +- [enum-generator](./enum-generator.md) +- [priority-generator](./priority-generator.md) +- [auto-register-module-generator](./auto-register-module-generator.md) + +### Game / CQRS 相关生成器 + +- 配置 schema 生成与运行时接法: + - [../game/config-system.md](../game/config-system.md) +- CQRS registry 生成入口: + - [../core/cqrs.md](../core/cqrs.md) ### Godot 专用生成器 -- **Godot 项目元数据生成 (Godot)**:从 `project.godot` 生成 AutoLoad 与 Input Action 的强类型访问入口 -- **[GetNode] 属性 (Godot)**:自动获取 Godot 节点引用,支持多种查找模式 -- **[BindNodeSignal] 属性 (Godot)**:自动生成 Godot 节点信号绑定与解绑逻辑 -- **[AutoUiPage] 属性 (Godot)**:自动生成 UI 页面行为包装与页面 Key -- **[AutoScene] 属性 (Godot)**:自动生成场景行为包装与场景 Key -- **[AutoRegisterExportedCollections] 属性 (Godot)**:自动生成导出集合的批量注册方法 - -### 🔧 高级特性 - -- **智能诊断**:生成器包含详细的错误诊断信息 -- **增量编译**:只生成修改过的代码,提高编译速度 -- **命名空间控制**:灵活控制生成代码的命名空间 -- **可访问性控制**:支持不同的访问修饰符 -- **智能推断注入**:[GetAll] 自动推断字段类型并注入架构组件 -- **优先级排序建议**:分析器自动建议使用 GetAllByPriority 确保正确排序 - -## 安装配置 - -### NuGet 包安装 - -```xml - - - net6.0 - - - - - - - -``` - -如果你只使用 Godot 生成器或 CQRS 处理器注册生成器,请把上面的包替换为对应的 -`GeWuYou.GFramework.Godot.SourceGenerators` 或 `GeWuYou.GFramework.Cqrs.SourceGenerators`。 -这些拆分包会同时带上各自需要的 abstractions 程序集,不需要再额外安装单独的 `*.Attributes` 包。 -实际接入时请替换为当前发布版本,或与项目中其余 `GeWuYou.GFramework.*` 包保持同一版本。 - -### Config Schema 文件约定 - -当项目引用 `GeWuYou.GFramework.Game.SourceGenerators` 的打包产物时,生成器会默认从 `schemas/**/*.schema.json` 收集配置 -schema -文件并作为 `AdditionalFiles` 输入。 - -这意味着消费者项目通常只需要维护如下结构: - -```text -GameProject/ -├─ config/ -│ └─ monster/ -│ └─ slime.yaml -└─ schemas/ - └─ monster.schema.json -``` - -如果你需要完整接入运行时加载、schema 校验和 VS Code 工具链,请继续阅读: - -- [游戏内容配置系统](/zh-CN/game/config-system) - -## Config Schema 生成器 - -Config Schema 生成器会扫描 `*.schema.json` 文件,并生成: - -- 配置数据类型 -- 与 `IConfigTable` 对齐的表包装类型 - -这一生成器适合与 `GFramework.Game.Config.YamlConfigLoader` 配合使用,让 schema、运行时和工具链共享同一份结构约定。 - -当前支持的 schema 子集以内容配置系统文档中的说明为准,重点覆盖: - -- `object` 根节点 -- `required` -- `integer` -- `number` -- `boolean` -- `string` -- `array` - -### 项目文件配置 - -```xml - - - - net6.0 - true - Generated - - - - - - -``` - -## CQRS Handler Registry 生成器 - -`GeWuYou.GFramework.Cqrs.SourceGenerators` 会在编译期分析当前业务程序集中的 CQRS handlers,并生成: - -- `ICqrsHandlerRegistry` 实现,用于在启动时直接注册可安全引用的 handlers -- 程序集级 `CqrsHandlerRegistryAttribute` 元数据,供运行时优先走生成注册路径 -- 必要时的 `CqrsReflectionFallbackAttribute`,让运行时只补扫生成代码无法合法引用的 handlers - -### 接入包 - -如果你的项目已经使用 GFramework 架构层,请在现有 Core 依赖基础上补齐 CQRS runtime 与 generator: - -```xml - - - - - -``` - -如果当前项目还没有接入架构运行时,请同时保持 `GeWuYou.GFramework.Core` / -`GeWuYou.GFramework.Core.Abstractions` 与 CQRS 包版本一致。 - -### 最小示例 - -下面的最小示例展示了“安装 runtime + source generator 后,正常注册程序集”的接入方式。运行时会优先使用生成的 -handler registry;如果某个 handler 无法被生成代码直接引用,则自动补走定向反射回退。 - -```csharp -using GFramework.Core.Architectures; -using GFramework.Cqrs.Abstractions.Cqrs.Command; -using GFramework.Cqrs.Cqrs.Command; - -public sealed record CreatePlayerCommand(string Name) : ICommand; - -public sealed class CreatePlayerCommandHandler : AbstractCommandHandler -{ - public override ValueTask Handle(CreatePlayerCommand command, CancellationToken cancellationToken) - { - return ValueTask.FromResult(command.Name.Length); - } -} - -public sealed class GameArchitecture : Architecture -{ - protected override void OnInitialize() - { - RegisterCqrsHandlersFromAssembly(typeof(GameArchitecture).Assembly); - } -} -``` - -### 兼容性与迁移说明 - -- 不安装 `GeWuYou.GFramework.Cqrs.SourceGenerators` 也可以正常运行;此时 CQRS runtime 会继续使用反射扫描注册 - handlers。 -- 安装生成器后,不需要额外改写 `RegisterCqrsHandlersFromAssembly(...)` / - `RegisterCqrsHandlersFromAssemblies(...)` 调用点;运行时会自动优先使用生成注册表。 -- CQRS 消息基类位于 `GFramework.Cqrs.Command` / `Query` / `Notification`,而处理器基类位于 - `GFramework.Cqrs.Cqrs.*` 命名空间。文档示例需要分别引用两组命名空间。 - -## Log 属性生成器 - -[Log] 属性自动为标记的类生成日志记录功能,包括 ILogger 字段和便捷的日志方法。 - -### 基础使用 - -```csharp -using GFramework.Core.SourceGenerators.Abstractions.Logging; - -[Log] -public partial class PlayerController -{ - public void DoSomething() - { - Logger.Info("Doing something"); // 自动生成的 Logger 字段 - Logger.Debug("Debug information"); - Logger.Warning("Warning message"); - Logger.Error("Error occurred"); - } -} -``` - -### 生成的代码 - -编译器会自动生成如下代码: - -```csharp -// -public partial class PlayerController -{ - private static readonly ILogger Logger = - LoggerFactoryResolver.Provider.CreateLogger("YourNamespace.PlayerController"); -} -``` - -**注意**:生成器只生成 ILogger 字段,不生成日志方法。日志方法(Info、Debug、Error 等)来自 ILogger 接口本身。 - -### 高级配置 - -```csharp -[Log( - Name = "Custom.PlayerLogger", // 自定义日志分类名称 - FieldName = "CustomLogger", // 自定义字段名 - IsStatic = false, // 是否为静态字段 - AccessModifier = "public" // 访问修饰符 -)] -public partial class CustomLoggerExample -{ - public void LogSomething() - { - CustomLogger.Info("Custom logger message"); - } -} -``` - -### 配置选项说明 - -| 参数 | 类型 | 默认值 | 说明 | -|----------------|---------|-----------|---------------------------------| -| Name | string? | null | 日志分类名称(默认使用类名) | -| FieldName | string | "Logger" | 生成的字段名称 | -| IsStatic | bool | true | 是否生成静态字段 | -| AccessModifier | string | "private" | 访问修饰符(private/protected/public) | - -### 静态类支持 - -```csharp -[Log] -public static partial class MathHelper -{ - public static int Add(int a, int b) - { - Logger.Debug($"Adding {a} and {b}"); - return a + b; - } -} -``` - -## ContextAware 属性生成器 - -[ContextAware] 属性自动实现 IContextAware 接口,提供便捷的架构上下文访问能力。 - -### 基础使用 - -```csharp -using GFramework.Core.Abstractions.Controller; -using GFramework.Core.SourceGenerators.Abstractions.Rule; - -[ContextAware] -public partial class PlayerController : IController -{ - public void Initialize() - { - // 使用扩展方法访问架构([ContextAware] 实现 IContextAware 接口) - var playerModel = this.GetModel(); - var combatSystem = this.GetSystem(); - - this.SendEvent(new PlayerInitializedEvent()); - } -} -``` - -### 生成的代码 - -编译器会自动生成如下代码: - -```csharp -// -#nullable enable - -namespace YourNamespace; - -partial class PlayerController : global::GFramework.Core.Abstractions.Rule.IContextAware -{ - private global::GFramework.Core.Abstractions.Architecture.IArchitectureContext? _context; - private static global::GFramework.Core.Abstractions.Architecture.IArchitectureContextProvider? _contextProvider; - - /// - /// 自动获取的架构上下文(懒加载,默认使用 GameContextProvider) - /// - protected global::GFramework.Core.Abstractions.Architecture.IArchitectureContext Context - { - get - { - if (_context == null) - { - _contextProvider ??= new global::GFramework.Core.Architecture.GameContextProvider(); - _context = _contextProvider.GetContext(); - } - - return _context; - } - } - - /// - /// 配置上下文提供者(用于测试或多架构场景) - /// - public static void SetContextProvider(global::GFramework.Core.Abstractions.Architecture.IArchitectureContextProvider provider) - { - _contextProvider = provider; - } - - /// - /// 重置上下文提供者为默认值(用于测试清理) - /// - public static void ResetContextProvider() - { - _contextProvider = null; - } - - void global::GFramework.Core.Abstractions.Rule.IContextAware.SetContext(global::GFramework.Core.Abstractions.Architecture.IArchitectureContext context) - { - _context = context; - } - - global::GFramework.Core.Abstractions.Architecture.IArchitectureContext global::GFramework.Core.Abstractions.Rule.IContextAware.GetContext() - { - return Context; - } -} -``` - -### 测试场景配置 - -在单元测试中,可以配置自定义的上下文提供者: - -```csharp -[Test] -public async Task TestPlayerController() -{ - var testArchitecture = new TestArchitecture(); - await testArchitecture.InitAsync(); - - // 配置测试上下文提供者 - PlayerController.SetContextProvider(new TestContextProvider(testArchitecture)); - - try - { - var controller = new PlayerController(); - controller.Initialize(); - // 测试逻辑... - } - finally - { - // 清理:重置上下文提供者 - PlayerController.ResetContextProvider(); - } -} -``` - -### 与其他属性组合 - -```csharp -using GFramework.Core.Abstractions.Controller; -using GFramework.Core.SourceGenerators.Abstractions.Logging; -using GFramework.Core.SourceGenerators.Abstractions.Rule; - -[Log] -[ContextAware] -public partial class AdvancedController : IController -{ - public void ProcessRequest() - { - Logger.Info("Processing request"); - - var model = this.GetModel(); - Logger.Info($"Player health: {model.Health}"); - - this.SendCommand(new ProcessCommand()); - Logger.Debug("Command sent"); - } -} -``` - -## GenerateEnumExtensions 属性生成器 - -[GenerateEnumExtensions] 属性为枚举类型生成便捷的扩展方法,提高代码可读性和类型安全性。 - -### 基础使用 - -```csharp -using GFramework.Core.SourceGenerators.Abstractions.Enums; - -[GenerateEnumExtensions] -public enum GameState -{ - Playing, - Paused, - GameOver, - Menu -} - -// 自动生成的扩展方法: -public static class GameStateExtensions -{ - public static bool IsPlaying(this GameState state) => state == GameState.Playing; - public static bool IsPaused(this GameState state) => state == GameState.Paused; - public static bool IsGameOver(this GameState state) => state == GameState.GameOver; - public static bool IsMenu(this GameState state) => state == GameState.Menu; - - public static bool IsIn(this GameState state, params GameState[] values) - { - if (values == null) return false; - foreach (var v in values) if (state == v) return true; - return false; - } -} - -// 使用示例 -public class GameManager -{ - private GameState _currentState = GameState.Menu; - - public bool CanProcessInput() - { - return _currentState.IsPlaying() || _currentState.IsMenu(); - } - - public bool IsGameOver() - { - return _currentState.IsGameOver(); - } - - public bool IsActiveState() - { - return _currentState.IsIn(GameState.Playing, GameState.Menu); - } -} -``` - -### 配置选项 - -```csharp -[GenerateEnumExtensions( - GenerateIsMethods = true, // 是否生成 IsX 方法(默认 true) - GenerateIsInMethod = true // 是否生成 IsIn 方法(默认 true) -)] -public enum PlayerState -{ - Idle, - Walking, - Running, - Jumping, - Attacking -} -``` - -### 配置选项说明 - -| 参数 | 类型 | 默认值 | 说明 | -|--------------------|------|------|-------------------| -| GenerateIsMethods | bool | true | 是否为每个枚举值生成 IsX 方法 | -| GenerateIsInMethod | bool | true | 是否生成 IsIn 方法 | - -## Godot 项目元数据生成 - -Godot 项目元数据生成器会读取 `project.godot`,把项目级配置转换为稳定的编译期 API。 - -当前生成两个统一入口: - -- `GFramework.Godot.Generated.AutoLoads` -- `GFramework.Godot.Generated.InputActions` - -默认情况下,NuGet 包引用会自动把项目根目录下的 `project.godot` 加入 `AdditionalFiles`。如果你是在仓库内通过 analyzer -形式直接引用生成器,则需要手动加入: - -```xml - - - -``` - -### AutoLoad 映射 - -当某个 AutoLoad 不能仅靠类名唯一推断到 C# 节点类型时,可以使用 `[AutoLoad]` 指定映射: - -```csharp -using GFramework.Godot.SourceGenerators.Abstractions; -using Godot; - -[AutoLoad("GameServices")] -public partial class GameServices : Node -{ -} -``` - -生成后可以直接使用: - -```csharp -using GFramework.Godot.Generated; - -var services = AutoLoads.GameServices; - -if (AutoLoads.TryGetAudioBus(out var audioBus)) -{ -} -``` - -### Input Action 常量 - -`[input]` 段会被转换为强类型常量: - -```csharp -using GFramework.Godot.Generated; - -if (Input.IsActionPressed(InputActions.MoveUp)) -{ -} -``` - -完整说明请见:[Godot 项目元数据生成器](./godot-project-generator) - -## GetNode 生成器 - -GetNode 生成器为标记了 `[GetNode]` 特性的字段自动生成 Godot 节点获取代码,无需手动调用 `GetNode()` 方法。 - -### 主要功能 - -- **自动节点获取**:根据路径或字段名自动获取 Godot 节点 -- **多种查找模式**:支持唯一名(`%Name`)、相对路径、绝对路径查找 -- **可选节点支持**:可以标记节点为可选,获取失败时返回 null -- **_Ready 钩子**:自动生成 `_Ready()` 方法注入节点获取逻辑 - -### 基础示例 - -```csharp -using GFramework.Godot.SourceGenerators.Abstractions; -using Godot; - -public partial class PlayerHud : Control -{ - [GetNode] - private Label _healthLabel = null!; - - [GetNode("HUD/ScoreValue")] - private Label _scoreLabel = null!; - - public override void _Ready() - { - __InjectGetNodes_Generated(); - _healthLabel.Text = "100"; - } -} -``` - -**完整文档**:[GetNode 生成器](./get-node-generator) - -## AutoRegisterModule 生成器 - -AutoRegisterModule 生成器面向 GFramework 模块安装场景,为类上的注册声明自动生成 `Install(IArchitecture)`。 - -### 主要功能 - -- **声明式模块注册**:用 Attribute 声明 `Model` / `System` / `Utility` -- **稳定顺序输出**:按 Attribute 书写顺序生成注册代码 -- **零反射开销**:编译期生成 `new T()` 与 `architecture.RegisterXxx(...)` - -### 基础示例 - -```csharp -using GFramework.Core.SourceGenerators.Abstractions.Architectures; - -[AutoRegisterModule] -[RegisterModel(typeof(RunStateModel))] -[RegisterSystem(typeof(BuildSystem))] -[RegisterUtility(typeof(AudioUtility))] -public partial class GameplayModule -{ -} -``` - -**完整文档**:[AutoRegisterModule 生成器](./auto-register-module-generator) - -## BindNodeSignal 生成器 - -BindNodeSignal 生成器为标记了 `[BindNodeSignal]` 特性的方法自动生成节点事件绑定和解绑代码。 - -### 主要功能 - -- **自动事件绑定**:在 `_Ready()` 中自动订阅节点事件 -- **自动事件解绑**:在 `_ExitTree()` 中自动取消订阅 -- **多事件绑定**:一个方法可以绑定到多个节点事件 -- **类型安全检查**:编译时验证方法签名与事件委托的兼容性 - -### 基础示例 - -```csharp -using GFramework.Godot.SourceGenerators.Abstractions; -using Godot; - -public partial class MainMenu : Control -{ - private Button _startButton = null!; - - [BindNodeSignal(nameof(_startButton), nameof(Button.Pressed))] - private void OnStartButtonPressed() - { - StartGame(); - } - - public override void _Ready() - { - __BindNodeSignals_Generated(); - } - - public override void _ExitTree() - { - __UnbindNodeSignals_Generated(); - } -} -``` - -**完整文档**:[BindNodeSignal 生成器](./bind-node-signal-generator) - -## AutoUiPage 生成器 - -AutoUiPage 生成器为 Godot 页面节点自动生成 `UiKeyStr`、缓存的 `IUiPageBehavior` 和 `GetPage()`。 - -### 主要功能 - -- **页面键统一声明**:用一个 Attribute 同时声明页面 Key 与 `UiLayer` -- **行为缓存**:延迟创建并缓存 `IUiPageBehavior` -- **减少页面样板**:不再手写重复的页面工厂包装 - -### 基础示例 - -```csharp -using GFramework.Godot.SourceGenerators.Abstractions.UI; -using GFramework.Game.Abstractions.Enums; -using Godot; - -[AutoUiPage(nameof(UiKey.MainMenu), nameof(UiLayer.Page))] -public partial class MainMenu : Control -{ -} -``` - -**完整文档**:[AutoUiPage 生成器](./auto-ui-page-generator) - -## AutoScene 生成器 - -AutoScene 生成器为场景根节点自动生成 `SceneKeyStr`、缓存的 `ISceneBehavior` 和 `GetScene()`。 - -### 主要功能 - -- **场景键统一声明**:避免散落的场景 key 字符串 -- **行为缓存**:延迟创建并复用场景行为对象 -- **路由接入更直接**:场景节点可直接暴露标准场景行为入口 - -### 基础示例 - -```csharp -using GFramework.Godot.SourceGenerators.Abstractions.UI; -using GFramework.Game.Abstractions.Enums; -using Godot; - -[AutoScene(nameof(SceneKey.Gameplay))] -public partial class GameplayRoot : Node2D -{ -} -``` - -**完整文档**:[AutoScene 生成器](./auto-scene-generator) - -## AutoRegisterExportedCollections 生成器 - -AutoRegisterExportedCollections 生成器为 Godot 导出集合自动生成批量注册方法,适合启动入口和资源映射注册。 - -### 主要功能 - -- **批量注册样板收敛**:统一 `foreach + registry.Register(item)` 模式 -- **编译期校验**:验证集合、注册器成员和方法签名 -- **支持接口注册器**:兼容从基类和继承接口解析注册方法 - -### 基础示例 - -```csharp -using GFramework.Godot.SourceGenerators.Abstractions.UI; -using Godot; - -[AutoRegisterExportedCollections] -public partial class GameEntryPoint : Node -{ - [RegisterExportedCollection(nameof(_textureRegistry), "Registry")] - private Godot.Collections.Array? _textureConfigs; -} -``` - -**完整文档**:[AutoRegisterExportedCollections 生成器](./auto-register-exported-collections-generator) - -## 诊断信息 - -这一组 Source Generators 提供详细的编译时诊断信息,帮助开发者快速定位和解决问题。 - -### GF_Logging_001 - 日志字段名冲突 - -```csharp -[Log(fieldName = "Logger")] -public partial class ClassWithLogger -{ - private readonly ILogger Logger; // ❌ 冲突! -} -``` - -**错误信息**: `GF_Logging_001: Logger field name 'Logger' conflicts with existing field` - -**解决方案**: 更改字段名或移除冲突字段 - -```csharp -[Log(fieldName = "CustomLogger")] -public partial class ClassWithLogger -{ - private readonly ILogger Logger; // ✅ 不冲突 - private static readonly ILogger CustomLogger; // ✅ 生成器使用 CustomLogger -} -``` - -### GF_Rule_001 - ContextAware 接口冲突 - -```csharp -[ContextAware] -public partial class AlreadyContextAware : IContextAware // ❌ 冲突! -{ - // 已实现 IContextAware -} -``` - -**错误信息**: `GF_Rule_001: Type already implements IContextAware interface` - -**解决方案**: 移除 [ContextAware] 属性或移除手动实现 - -```csharp -// 方案1:移除属性 -public partial class AlreadyContextAware : IContextAware -{ - // 手动实现 -} - -// 方案2:移除手动实现,使用生成器 -[ContextAware] -public partial class AlreadyContextAware -{ - // 生成器自动实现 -} -``` - -### GF_Enum_001 - 枚举成员命名冲突 - -```csharp -[GenerateEnumExtensions] -public enum ConflictEnum -{ - IsPlaying, // ❌ 冲突!会生成 IsIsPlaying() - HasJump // ❌ 冲突!会生成 HasHasJump() -} -``` - -**错误信息**: `GF_Enum_001: Enum member name conflicts with generated method` - -**解决方案**: 重命名枚举成员或自定义前缀 - -```csharp -[GenerateEnumExtensions(customPrefix = "IsState")] -public enum ConflictEnum -{ - Playing, // ✅ 生成 IsStatePlaying() - Jump // ✅ 生成 IsStateJump() -} -``` - -### Priority 相关诊断 - -`Priority` 相关规则以专用文档为权威来源: - -- 完整生成器诊断见 [Priority 生成器](./priority-generator.md#诊断信息) -- 排序分析器规则见 [与 PriorityUsageAnalyzer 集成](./priority-generator.md#与-priorityusageanalyzer-集成) - -| 诊断 ID | 含义 | 常见修复方向 | -|-------------------------|-------------------------------|------------------------------| -| `GF_Priority_001` | `[Priority]` 只能应用于类 | 仅在 `partial class` 上使用特性 | -| `GF_Priority_002` | 类型已手动实现 `IPrioritized` | 删除特性或删除手写接口实现 | -| `GF_Priority_003` | 标记类型未声明为 `partial` | 为类型添加 `partial` | -| `GF_Priority_004` | 优先级参数无效 | 提供有效的整数优先级值 | -| `GF_Priority_005` | 嵌套类不支持生成 | 将目标类型提取为顶层类 | -| `GF_Priority_Usage_001` | 应优先使用 `GetAllByPriority()` | 对实现 `IPrioritized` 的类型改用排序获取 | - -### Context Get 相关诊断 - -`Context Get` 相关规则以专用文档为权威来源: - -- 完整诊断见 [Context Get 注入生成器](./context-get-generator.md#诊断信息) -- 调用时机建议见 [推荐调用时机与模式](./context-get-generator.md#推荐调用时机与模式) - -| 诊断 ID | 含义 | 常见修复方向 | -|---------------------|--------------------|---------------------------------------------------------------| -| `GF_ContextGet_001` | 嵌套类不支持生成注入 | 将目标类型提取为顶层类 | -| `GF_ContextGet_002` | 注入字段不能为 `static` | 改为实例字段 | -| `GF_ContextGet_003` | 注入字段不能为 `readonly` | 移除 `readonly` | -| `GF_ContextGet_004` | 字段类型与注入特性不匹配 | 使用符合特性约束的字段类型 | -| `GF_ContextGet_005` | 目标类型必须具备上下文访问能力 | 添加 `[ContextAware]`、实现 `IContextAware` 或继承 `ContextAwareBase` | -| `GF_ContextGet_006` | 同一字段不能声明多个注入特性 | 每个字段只保留一个注入特性 | -| `GF_ContextRegistration_001` | `Model` 使用点没有静态可见注册 | 在所属架构的初始化链路中显式注册对应 `Model` | -| `GF_ContextRegistration_002` | `System` 使用点没有静态可见注册 | 在所属架构的初始化链路中显式注册对应 `System` | -| `GF_ContextRegistration_003` | `Utility` 使用点没有静态可见注册 | 在所属架构的初始化链路中显式注册对应 `Utility` | - -## 性能优势 - -### 编译时 vs 运行时对比 - -| 特性 | 手动实现 | 反射实现 | 源码生成器 | -|-----------|------|------|-------| -| **运行时性能** | 最优 | 最差 | 最优 | -| **内存开销** | 最小 | 最大 | 最小 | -| **类型安全** | 编译时 | 运行时 | 编译时 | -| **开发效率** | 低 | 中 | 高 | -| **调试友好** | 好 | 差 | 好 | - -### 基准测试结果 - -```csharp -// 日志性能对比 (100,000 次调用) -// 手动实现: 0.23ms -// 反射实现: 45.67ms -// 源码生成器: 0.24ms (几乎无差异) - -// 上下文访问性能对比 (1,000,000 次访问) -// 手动实现: 0.12ms -// 反射实现: 23.45ms -// 源码生成器: 0.13ms (几乎无差异) -``` - -### 内存分配分析 - -```csharp -// 使用 source generators 的内存分配 -[Log] -[ContextAware] -public partial class EfficientController : IController -{ - public void Process() - { - Logger.Info("Processing"); // 0 分配 - var model = this.GetModel(); // 0 分配 - } -} - -// 对比反射实现的内存分配 -public class InefficientController : IController -{ - public void Process() - { - var logger = GetLoggerViaReflection(); // 每次分配 - var model = GetModelViaReflection(); // 每次分配 - } -} -``` - -## 使用示例 - -### 完整的游戏控制器示例 - -```csharp -using GFramework.Core.Abstractions.Controller; -using GFramework.Core.SourceGenerators.Abstractions.Logging; -using GFramework.Core.SourceGenerators.Abstractions.Rule; - -[Log] -[ContextAware] -public partial class GameController : Node, IController -{ - private PlayerModel _playerModel; - private CombatSystem _combatSystem; - - public override void _Ready() - { - // 初始化模型和系统引用 - _playerModel = this.GetModel(); - _combatSystem = this.GetSystem(); - - // 监听事件 - this.RegisterEvent(OnPlayerInput) - .UnRegisterWhenNodeExitTree(this); - - Logger.Info("Game controller initialized"); - } - - private void OnPlayerInput(PlayerInputEvent e) - { - Logger.Debug($"Processing player input: {e.Action}"); - - switch (e.Action) - { - case "attack": - HandleAttack(); - break; - case "defend": - HandleDefend(); - break; - } - } - - private void HandleAttack() - { - if (_playerModel.CanAttack()) - { - Logger.Info("Player attacks"); - _combatSystem.ProcessAttack(); - this.SendEvent(new AttackEvent()); - } - else - { - Logger.Warning("Player cannot attack - cooldown"); - } - } - - private void HandleDefend() - { - if (_playerModel.CanDefend()) - { - Logger.Info("Player defends"); - _playerModel.IsDefending.Value = true; - this.SendEvent(new DefendEvent()); - } - else - { - Logger.Warning("Player cannot defend"); - } - } -} -``` - -### 枚举状态管理示例 - -```csharp -[GenerateEnumExtensions( - generateIsMethods = true, - generateHasMethod = true, - generateInMethod = true, - includeToString = true -)] -public enum CharacterState -{ - Idle, - Walking, - Running, - Jumping, - Falling, - Attacking, - Hurt, - Dead -} - -using GFramework.Core.Abstractions.Controller; -using GFramework.Core.SourceGenerators.Abstractions.Logging; -using GFramework.Core.SourceGenerators.Abstractions.Rule; - -[Log] -[ContextAware] -public partial class CharacterController : Node, IController -{ - private CharacterModel _characterModel; - - public override void _Ready() - { - _characterModel = this.GetModel(); - - // 监听状态变化 - _characterModel.State.Register(OnStateChanged); - } - - private void OnStateChanged(CharacterState newState) - { - Logger.Info($"Character state changed to: {newState.ToDisplayString()}"); - - // 使用生成的扩展方法 - if (newState.IsDead()) - { - HandleDeath(); - } - else if (newState.IsHurt()) - { - HandleHurt(); - } - else if (newState.In(CharacterState.Walking, CharacterState.Running)) - { - StartMovementEffects(); - } - - // 检查是否可以接受输入 - if (newState.In(CharacterState.Idle, CharacterState.Walking, CharacterState.Running)) - { - EnableInput(); - } - else - { - DisableInput(); - } - } - - private bool CanAttack() - { - var state = _characterModel.State.Value; - return state.In(CharacterState.Idle, CharacterState.Walking, CharacterState.Running); - } - - private void HandleDeath() - { - Logger.Info("Character died"); - DisableInput(); - PlayDeathAnimation(); - this.SendEvent(new CharacterDeathEvent()); - } -} -``` - -## 最佳实践 - -### 🎯 属性使用策略 - -#### 1. 合理的属性组合 - -```csharp -// 好的做法:相关功能组合使用 -[Log] -[ContextAware] -public partial class BusinessLogicComponent : IComponent -{ - // 既有日志记录又有上下文访问 -} - -// 避免:不必要的属性 -[Log] // ❌ 静态工具类通常不需要日志 -public static class MathHelper -{ - public static int Add(int a, int b) => a + b; -} -``` - -#### 2. 命名约定 - -```csharp -// 好的做法:一致的命名 -[Log(fieldName = "Logger")] -public partial class PlayerController { } - -[Log(fieldName = "Logger")] -public partial class EnemyController { } - -// 避免:不一致的命名 -[Log(fieldName = "Logger")] -public partial class PlayerController { } - -[Log(fieldName = "CustomLogger")] -public partial class EnemyController { } -``` - -### 🏗️ 项目组织 - -#### 1. 分离生成器和业务逻辑 - -```csharp -// 好的做法:部分类分离 -// PlayerController.Logic.cs - 业务逻辑 -public partial class PlayerController : IController -{ - public void Move(Vector2 direction) - { - if (CanMove()) - { - UpdatePosition(direction); - Logger.Debug($"Player moved to {direction}"); - } - } -} - -// PlayerController.Generated.cs - 生成代码所在 -// 不需要手动维护,由生成器处理 -``` - -#### 2. 枚举设计 - -```csharp -// 好的做法:有意义的枚举设计 -[GenerateEnumExtensions] -public enum GameState -{ - MainMenu, // 主菜单 - Playing, // 游戏中 - Paused, // 暂停 - GameOver, // 游戏结束 - Victory // 胜利 -} - -// 避免:含义不明确的枚举值 -[GenerateEnumExtensions] -public enum State -{ - State1, - State2, - State3 -} -``` - -### 🔧 性能优化 - -#### 1. 避免过度日志 - -```csharp -// 好的做法:合理的日志级别 -[Log] -public partial class PerformanceCriticalComponent -{ - public void Update() - { - // 只在必要时记录日志 - if (_performanceCounter % 1000 == 0) - { - Logger.Debug($"Performance: {_performanceCounter} ticks"); - } - } -} - -// 避免:过度日志记录 -[Log] -public partial class NoisyComponent -{ - public void Update() - { - Logger.Debug($"Frame: {Engine.GetProcessFrames()}"); // 太频繁 - } -} -``` - -### 🛡️ 错误处理 - -```csharp -[Log] -[ContextAware] -public partial class RobustComponent : IComponent -{ - public void RiskyOperation() - { - try - { - var model = this.GetModel(); - model.PerformRiskyOperation(); - Logger.Info("Operation completed successfully"); - } - catch (Exception ex) - { - Logger.Error($"Operation failed: {ex.Message}"); - this.SendEvent(new OperationFailedEvent { Error = ex.Message }); - } - } -} -``` - -## 常见问题 - -### Q: 为什么需要标记类为 `partial`? - -**A**: 源代码生成器需要向现有类添加代码,`partial` 关键字允许一个类的定义分散在多个文件中。生成器会在编译时创建另一个部分类文件,包含生成的代码。 - -```csharp -[Log] -public partial class MyClass { } // ✅ 需要 partial - -[Log] -public class MyClass { } // ❌ 编译错误,无法添加生成代码 -``` - -### Q: 生成的代码在哪里? - -**A**: 生成的代码在编译过程中创建,默认位置在 `obj/Debug/net6.0/generated/` 目录下。可以在项目文件中配置输出位置: - -```xml - - - Generated - -``` - -### Q: 如何调试生成器问题? - -**A**: 生成器提供了详细的诊断信息: - -1. **查看错误列表**:编译错误会显示在 IDE 中 -2. **查看生成文件**:检查生成的代码文件 -3. **启用详细日志**:在项目文件中添加: - -```xml - - - true - -``` - -### Q: 可以自定义生成器吗? - -**A**: 当前版本的生成器支持有限的配置。如需完全自定义,可以创建自己的源代码生成器项目。 - -### Q: 性能影响如何? - -**A**: 源代码生成器对运行时性能的影响几乎为零: - -- **编译时**:可能会增加编译时间(通常几秒) -- **运行时**:与手写代码性能相同 -- **内存使用**:与手写代码内存使用相同 - -### Q: 与依赖注入框架兼容吗? - -**A**: 完全兼容。生成器创建的是标准代码,可以与任何依赖注入框架配合使用: - -```csharp -[Log] -[ContextAware] -public partial class ServiceComponent : IService -{ - // 可以通过构造函数注入依赖 - private readonly IDependency _dependency; - - public ServiceComponent(IDependency dependency) - { - _dependency = dependency; - Logger.Info("Service initialized with dependency injection"); - } -} -``` - ---- - -## 依赖关系 - -```mermaid -graph TD - A[GeWuYou.GFramework.Core.SourceGenerators] --> B[GFramework.Core.SourceGenerators.Abstractions] - A --> C[GFramework.SourceGenerators.Common] - A --> D[GFramework.Core.Abstractions] - A --> E[Microsoft.CodeAnalysis.CSharp] - A --> F[Microsoft.CodeAnalysis.Analyzers] - G[GeWuYou.GFramework.Game.SourceGenerators] --> C - H[GeWuYou.GFramework.Godot.SourceGenerators] --> C - I[GeWuYou.GFramework.Cqrs.SourceGenerators] --> C -``` - -## 版本兼容性 - -- **.NET**: 6.0+ -- **Visual Studio**: 2022 17.0+ -- **Rider**: 2022.3+ -- **Roslyn**: 4.0+ +- [godot-project-generator](./godot-project-generator.md) +- [get-node-generator](./get-node-generator.md) +- [bind-node-signal-generator](./bind-node-signal-generator.md) +- [auto-ui-page-generator](./auto-ui-page-generator.md) +- [auto-scene-generator](./auto-scene-generator.md) +- [auto-register-exported-collections-generator](./auto-register-exported-collections-generator.md) + +## 推荐接入顺序 + +1. 先确认你已经选定运行时层,而不是先装生成器 +2. 再按运行时模块补充对应的生成器包 +3. 只在确实需要的项目里安装生成器,避免为了“可能以后会用”而把所有包一起引入 + +例如: + +- 新项目只需要 Core 上下文注入和日志辅助: + - 安装 `Core` + `Core.SourceGenerators` +- 需要静态 YAML 配置: + - 安装 `Game` + `Game.SourceGenerators` +- 需要 CQRS 生成注册表: + - 安装 `Cqrs` + `Cqrs.SourceGenerators` + +## 对应模块入口 + +- `GFramework.Core.SourceGenerators/README.md` +- `GFramework.Game.SourceGenerators/README.md` +- `GFramework.Cqrs.SourceGenerators/README.md` +- `GFramework.Godot.SourceGenerators/README.md`