mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
- 更新 Core、Game、Godot 与 source-generators 多个页面的 reader-facing 契约说明 - 将旧文档和旧入口对比句式改成直接陈述当前默认入口、约束与推荐做法 - 补充 documentation full coverage active topic 的 RP-047 跟踪与验证记录
199 lines
6.6 KiB
Markdown
199 lines
6.6 KiB
Markdown
---
|
||
title: ContextAware 生成器
|
||
description: 说明 [ContextAware] 当前会生成什么、何时使用、与 ContextAwareBase 的边界以及测试场景。
|
||
---
|
||
|
||
# ContextAware 生成器
|
||
|
||
`[ContextAware]` 是 `GFramework.Core.SourceGenerators` 中最常用的一类生成器。它的职责很明确:
|
||
|
||
- 为当前类型自动补齐 `IContextAware`
|
||
- 提供可复用的上下文懒加载入口
|
||
- 让类型可以直接使用 `this.GetSystem<T>()`、`this.GetModel<T>()`、`this.GetUtility<T>()` 等扩展方法
|
||
|
||
它不负责注册服务,也不会替你决定应该取哪个 `System` / `Model`。它解决的是“当前类型如何拿到架构上下文”。
|
||
|
||
## 当前包关系
|
||
|
||
- 特性来源:`GFramework.Core.SourceGenerators.Abstractions`
|
||
- 生成器实现:`GFramework.Core.SourceGenerators`
|
||
- 运行时接口:`GFramework.Core.Abstractions.Rule.IContextAware`
|
||
- 常用扩展方法:`GFramework.Core.Extensions`
|
||
|
||
如果只安装运行时 `GFramework.Core` 而没有安装 `Core.SourceGenerators`,`[ContextAware]` 本身不会生效。
|
||
|
||
## 最小用法
|
||
|
||
```csharp
|
||
using GFramework.Core.Abstractions.Controller;
|
||
using GFramework.Core.Extensions;
|
||
using GFramework.Core.SourceGenerators.Abstractions.Rule;
|
||
|
||
[ContextAware]
|
||
public partial class PlayerController : IController
|
||
{
|
||
public void Initialize()
|
||
{
|
||
var playerModel = this.GetModel<IPlayerModel>();
|
||
var combatSystem = this.GetSystem<ICombatSystem>();
|
||
|
||
combatSystem.Bind(playerModel);
|
||
}
|
||
}
|
||
```
|
||
|
||
当前最重要的前置条件只有两个:
|
||
|
||
- 必须是 `class`
|
||
- 必须声明为 `partial`
|
||
|
||
如果缺少这两个条件,生成器不会补代码。
|
||
|
||
## 当前会生成什么
|
||
|
||
按当前源码,`[ContextAware]` 会为目标类型生成:
|
||
|
||
- `IContextAware` 的显式接口实现
|
||
- 受保护的 `Context` 属性
|
||
- 类型级静态 `SetContextProvider(...)`
|
||
- 类型级静态 `ResetContextProvider()`
|
||
- 一个实例级 `_context` 缓存字段
|
||
- 一个类型级共享的 `_contextProvider`
|
||
- 一个类型级锁 `_contextSync`
|
||
|
||
这意味着它不是“每次访问都现查当前架构”。当前行为更接近:
|
||
|
||
1. 先看当前实例是否已经缓存了上下文
|
||
2. 如果没有,就在同步锁内取共享 provider
|
||
3. 若 provider 为空,则回退到 `GameContextProvider`
|
||
4. 把拿到的上下文缓存到当前实例
|
||
|
||
## 当前语义里最容易误解的点
|
||
|
||
### provider 是按类型共享,不是按实例共享
|
||
|
||
`SetContextProvider(...)` 影响的是“这个生成类型的后续实例或尚未初始化上下文的实例”,不是全仓库所有 `[ContextAware]`
|
||
类型共享同一个 provider。
|
||
|
||
### provider 切换不会自动刷新已缓存实例
|
||
|
||
一旦某个实例已经把上下文缓存进 `_context`,后续再调用:
|
||
|
||
- `SetContextProvider(...)`
|
||
- `ResetContextProvider()`
|
||
|
||
都不会自动改写这个实例的已缓存上下文。
|
||
|
||
如果你确实要覆盖某个现有实例的上下文,应显式调用:
|
||
|
||
```csharp
|
||
((IContextAware)controller).SetContext(context);
|
||
```
|
||
|
||
### 生成路径和 `ContextAwareBase` 不是一回事
|
||
|
||
当前源码里两者的默认回退策略不同:
|
||
|
||
- `[ContextAware]` 生成实现
|
||
- 通过共享 provider 回退,默认 provider 是 `GameContextProvider`
|
||
- 带同步锁,支持 `SetContextProvider(...)` / `ResetContextProvider()`
|
||
- `ContextAwareBase`
|
||
- 只维护简单的实例级缓存
|
||
- 不维护共享 provider
|
||
- 默认直接回退到 `GameContext.GetFirstArchitectureContext()`
|
||
|
||
因此,这两条路径不是“只是写法不同”,而是共享 provider 策略和实例缓存边界都不同。
|
||
|
||
## 何时使用 `[ContextAware]`
|
||
|
||
优先用于这些场景:
|
||
|
||
- 你的类型不是 `AbstractSystem`、`AbstractModel`、`AbstractCommand` 这类已经继承 `ContextAwareBase` 的框架基类
|
||
- 你希望在测试中显式切换 provider
|
||
- 你需要在同一生成类型上统一切换上下文来源
|
||
- 你在 Godot 节点、Controller、ViewModel、包装器类型上只想获得上下文访问能力
|
||
|
||
典型例子:
|
||
|
||
- `IController` 实现
|
||
- Godot `Node` / `Control` 的项目侧包装器
|
||
- 不继承框架基类但要访问架构的辅助类型
|
||
|
||
## 何时改用 `ContextAwareBase`
|
||
|
||
以下场景优先考虑 `ContextAwareBase` 或已经继承它的框架基类:
|
||
|
||
- 你本来就继承 `AbstractSystem`、`AbstractModel`、`AbstractCommand`、`AbstractQuery`
|
||
- 你不需要类型级共享 provider
|
||
- 你只需要简单的实例级上下文缓存
|
||
- 调用线程模型已经天然串行,不需要生成实现那套 provider 切换与同步语义
|
||
|
||
如果一个类型已经通过继承链拿到了 `ContextAwareBase`,通常没必要再额外标 `[ContextAware]`。
|
||
|
||
## 与 Context Get 注入的关系
|
||
|
||
`[GetModel]`、`[GetSystem]`、`[GetUtility]`、`[GetService]` 这类字段注入生成器,并不是独立工作的。
|
||
|
||
按当前 `ContextGetGenerator` 的判定规则,目标类型必须满足以下三者之一:
|
||
|
||
- 标记了 `[ContextAware]`
|
||
- 实现了 `IContextAware`
|
||
- 继承了 `ContextAwareBase`
|
||
|
||
所以更准确的理解是:
|
||
|
||
- `[ContextAware]` 负责“让类型成为 context-aware 类型”
|
||
- Context Get 系列特性负责“在这个前提下继续减少字段取值样板”
|
||
|
||
## 测试场景
|
||
|
||
如果测试里不想依赖默认全局上下文,推荐显式配置 provider:
|
||
|
||
```csharp
|
||
PlayerController.SetContextProvider(new TestContextProvider(testArchitecture.Context));
|
||
|
||
try
|
||
{
|
||
var controller = new PlayerController();
|
||
controller.Initialize();
|
||
}
|
||
finally
|
||
{
|
||
PlayerController.ResetContextProvider();
|
||
}
|
||
```
|
||
|
||
需要注意两点:
|
||
|
||
- `ResetContextProvider()` 只会重置共享 provider,不会清除已创建实例上的 `_context`
|
||
- 如果测试要复用同一实例并切换上下文,应该显式调用 `((IContextAware)instance).SetContext(...)`
|
||
|
||
## 诊断与约束
|
||
|
||
当前文档里最值得记住的约束只有这些:
|
||
|
||
- 非 `class` 会触发 `GF_Rule_001`
|
||
- 非 `partial` 不会生成实现,并会触发公共 partial 约束诊断
|
||
- 嵌套、字段注入等其他错误通常由对应的 Context Get 生成器和其诊断补充报告
|
||
|
||
## 与旧写法的边界
|
||
|
||
下面这些旧说法已经不够准确:
|
||
|
||
- “`[ContextAware]` 只是帮你补一个简单的 `GetContext()`”
|
||
- “切换 provider 后,已有实例会自动跟着切换”
|
||
- “`[ContextAware]` 和 `ContextAwareBase` 的默认行为完全一致”
|
||
|
||
当前更准确的理解是:
|
||
|
||
- 生成实现带有实例缓存、类型级共享 provider 和同步锁
|
||
- provider 切换只影响尚未缓存上下文的实例
|
||
- `ContextAwareBase` 是更轻量的实例级缓存路径
|
||
|
||
## 推荐阅读
|
||
|
||
1. [ContextGet 生成器](./context-get-generator.md)
|
||
2. [日志生成器](./logging-generator.md)
|
||
3. [Core 模块总览](../core/index.md)
|
||
4. [源码生成器总览](./index.md)
|