GFramework/docs/zh-CN/source-generators/context-aware-generator.md
gewuyou 8a117201d4 docs(documentation): 收口专题页README导航入口
- 更新专题页推荐阅读中的 README 入口为可点击 GitHub 链接
- 修复 source-generators 与 Game/ECS/CQRS 页面中的裸路径导航
- 更新 documentation-full-coverage-governance 跟踪与轨迹以记录第二批治理结果
2026-04-23 21:01:28 +08:00

6.7 KiB
Raw Blame History

title, description
title description
ContextAware 生成器 说明 [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] 本身不会生效。

最小用法

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()

都不会自动改写这个实例的已缓存上下文。

如果你确实要覆盖某个现有实例的上下文,应显式调用:

((IContextAware)controller).SetContext(context);

生成路径和 ContextAwareBase 不是一回事

当前源码里两者的默认回退策略不同:

  • [ContextAware] 生成实现
    • 通过共享 provider 回退,默认 provider 是 GameContextProvider
    • 带同步锁,支持 SetContextProvider(...) / ResetContextProvider()
  • ContextAwareBase
    • 只维护简单的实例级缓存
    • 不维护共享 provider
    • 默认直接回退到 GameContext.GetFirstArchitectureContext()

因此,旧文档里把两条路径混写成“只是写法不同”已经不准确。

何时使用 [ContextAware]

优先用于这些场景:

  • 你的类型不是 AbstractSystemAbstractModelAbstractCommand 这类已经继承 ContextAwareBase 的框架基类
  • 你希望在测试中显式切换 provider
  • 你需要在同一生成类型上统一切换上下文来源
  • 你在 Godot 节点、Controller、ViewModel、包装器类型上只想获得上下文访问能力

典型例子:

  • IController 实现
  • Godot Node / Control 的项目侧包装器
  • 不继承框架基类但要访问架构的辅助类型

何时改用 ContextAwareBase

以下场景优先考虑 ContextAwareBase 或已经继承它的框架基类:

  • 你本来就继承 AbstractSystemAbstractModelAbstractCommandAbstractQuery
  • 你不需要类型级共享 provider
  • 你只需要简单的实例级上下文缓存
  • 调用线程模型已经天然串行,不需要生成实现那套 provider 切换与同步语义

如果一个类型已经通过继承链拿到了 ContextAwareBase,通常没必要再额外标 [ContextAware]

与 Context Get 注入的关系

[GetModel][GetSystem][GetUtility][GetService] 这类字段注入生成器,并不是独立工作的。

按当前 ContextGetGenerator 的判定规则,目标类型必须满足以下三者之一:

  • 标记了 [ContextAware]
  • 实现了 IContextAware
  • 继承了 ContextAwareBase

所以更准确的理解是:

  • [ContextAware] 负责“让类型成为 context-aware 类型”
  • Context Get 系列特性负责“在这个前提下继续减少字段取值样板”

测试场景

如果测试里不想依赖默认全局上下文,推荐显式配置 provider

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. context-get-generator.md
  2. logging-generator.md
  3. ../core/index.md
  4. GFramework.Core.SourceGenerators/README.md