GFramework/docs/zh-CN/source-generators/context-aware-generator.md
GeWuYou 25f7779b4e refactor(docs): 将 Context 属性访问替换为扩展方法访问
- 将 Context.GetModel<T>() 调用替换为 this.GetModel<T>()
- 将 Context.GetSystem<T>() 调用替换为 this.GetSystem<T>()
- 将 Context.GetUtility<T>() 调用替换为 this.GetUtility<T>()
- 将 Context.SendCommand() 调用替换为 this.SendCommand()
- 将 Context.SendQuery() 调用替换为 this.SendQuery()
- 将 Context.SendEvent() 调用替换为 this.SendEvent()
- 将 Context.RegisterEvent<T>() 调用替换为 this.RegisterEvent<T>()
2026-03-08 11:37:31 +08:00

11 KiB
Raw Blame History

ContextAware 生成器

自动实现 IContextAware 接口,提供架构上下文访问能力

概述

ContextAware 生成器为标记了 [ContextAware] 属性的类自动生成 IContextAware 接口实现,使类能够便捷地访问架构上下文( IArchitectureContext)。这是 GFramework 中最常用的源码生成器之一,几乎所有需要与架构交互的组件都会使用它。

核心功能

  • 自动接口实现:无需手动实现 IContextAware 接口的 SetContext()GetContext() 方法
  • 懒加载上下文Context 属性在首次访问时自动初始化
  • 默认提供者:使用 GameContextProvider 作为默认上下文提供者
  • 测试友好:支持通过 SetContextProvider() 配置自定义上下文提供者

基础使用

标记类

使用 [ContextAware] 属性标记需要访问架构上下文的类:

using GFramework.SourceGenerators.Abstractions.rule;
using GFramework.Core.Abstractions.controller;

[ContextAware]
public partial class PlayerController : IController
{
    public void Initialize()
    {
        // 使用扩展方法访问架构([ContextAware] 实现 IContextAware 接口)
        var playerModel = this.GetModel<PlayerModel>();
        var combatSystem = this.GetSystem<CombatSystem>();

        this.SendEvent(new PlayerInitializedEvent());
    }

    public void Attack(Enemy target)
    {
        var damage = this.GetUtility<DamageCalculator>().Calculate(this, target);
        this.SendCommand(new DealDamageCommand(target, damage));
    }
}

必要条件

标记的类必须满足以下条件:

  1. 必须是 partial:生成器需要生成部分类代码
  2. 必须是 class 类型:不能是 structinterface
// ✅ 正确
[ContextAware]
public partial class MyController { }

// ❌ 错误:缺少 partial 关键字
[ContextAware]
public class MyController { }

// ❌ 错误:不能用于 struct
[ContextAware]
public partial struct MyStruct { }

生成的代码

编译器会为标记的类自动生成以下代码:

// <auto-generated/>
#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;

    /// <summary>
    /// 自动获取的架构上下文(懒加载,默认使用 GameContextProvider
    /// </summary>
    protected global::GFramework.Core.Abstractions.architecture.IArchitectureContext Context
    {
        get
        {
            if (_context == null)
            {
                _contextProvider ??= new global::GFramework.Core.architecture.GameContextProvider();
                _context = _contextProvider.GetContext();
            }

            return _context;
        }
    }

    /// <summary>
    /// 配置上下文提供者(用于测试或多架构场景)
    /// </summary>
    /// <param name="provider">上下文提供者实例</param>
    public static void SetContextProvider(global::GFramework.Core.Abstractions.architecture.IArchitectureContextProvider provider)
    {
        _contextProvider = provider;
    }

    /// <summary>
    /// 重置上下文提供者为默认值(用于测试清理)
    /// </summary>
    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;
    }
}

代码解析

生成的代码包含以下关键部分:

  1. 私有字段

    • _context:缓存的上下文实例
    • _contextProvider:静态上下文提供者(所有实例共享)
  2. Context 属性

    • protected 访问级别,子类可访问
    • 懒加载:首次访问时自动初始化
    • 使用 GameContextProvider 作为默认提供者
  3. 配置方法

    • SetContextProvider():设置自定义上下文提供者
    • ResetContextProvider():重置为默认提供者
  4. 显式接口实现

    • IContextAware.SetContext():允许外部设置上下文
    • IContextAware.GetContext():返回当前上下文

配置上下文提供者

测试场景

在单元测试中,通常需要使用自定义的上下文提供者:

[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();
    }
}

多架构场景

在某些高级场景中,可能需要同时运行多个架构实例:

public class MultiArchitectureManager
{
    private readonly Dictionary<string, IArchitecture> _architectures = new();

    public void SwitchToArchitecture(string name)
    {
        var architecture = _architectures[name];
        var provider = new ScopedContextProvider(architecture);

        // 为所有使用 [ContextAware] 的类切换上下文
        PlayerController.SetContextProvider(provider);
        EnemyController.SetContextProvider(provider);
        // ...
    }
}

使用场景

何时使用 [ContextAware]

推荐在以下场景使用 [ContextAware] 属性:

  1. Controller 层:需要协调多个 Model/System 的控制器
  2. Command/Query 实现:需要访问架构服务的命令或查询
  3. 自定义组件:不继承框架基类但需要上下文访问的组件
[ContextAware]
public partial class GameFlowController : IController
{
    public async Task StartGame()
    {
        var saveSystem = this.GetSystem<SaveSystem>();
        var uiSystem = this.GetSystem<UISystem>();

        await saveSystem.LoadAsync();
        await uiSystem.ShowMainMenuAsync();
    }
}

与 IController 配合使用

在 Godot 项目中,控制器通常同时实现 IController 和使用 [ContextAware]

using GFramework.Core.Abstractions.controller;
using GFramework.SourceGenerators.Abstractions.rule;

[ContextAware]
public partial class PlayerController : Node, IController
{
    public override void _Ready()
    {
        // 使用扩展方法访问架构([ContextAware] 实现 IContextAware 接口)
        var playerModel = this.GetModel<PlayerModel>();
        var combatSystem = this.GetSystem<CombatSystem>();
    }
}

说明

  • IController 是标记接口,标识这是一个控制器
  • [ContextAware] 提供架构访问能力
  • 两者配合使用是推荐的模式

何时继承 ContextAwareBase

如果类需要更多框架功能(如生命周期管理),应继承 ContextAwareBase

// 推荐:需要生命周期管理时继承基类
public class PlayerModel : AbstractModel
{
    // AbstractModel 已经继承了 ContextAwareBase
    protected override void OnInit()
    {
        var config = this.GetUtility<ConfigLoader>().Load<PlayerConfig>();
    }
}

// 推荐:简单组件使用属性
[ContextAware]
public partial class SimpleHelper
{
    public void DoSomething()
    {
        this.SendEvent(new SomethingHappenedEvent());
    }
}

与 IContextAware 接口的关系

生成的代码实现了 IContextAware 接口:

namespace GFramework.Core.Abstractions.rule;

public interface IContextAware
{
    void SetContext(IArchitectureContext context);
    IArchitectureContext GetContext();
}

这意味着标记了 [ContextAware] 的类可以:

  1. 被架构自动注入上下文:实现 IContextAware 的类在注册到架构时会自动调用 SetContext()
  2. 参与依赖注入:可以作为 IContextAware 类型注入到其他组件
  3. 支持上下文传递:可以通过 GetContext() 将上下文传递给其他组件

最佳实践

1. 始终使用 partial 关键字

// ✅ 正确
[ContextAware]
public partial class MyController { }

// ❌ 错误:编译器会报错
[ContextAware]
public class MyController { }

2. 在测试中清理上下文提供者

[TearDown]
public void TearDown()
{
    // 避免测试之间的状态污染
    PlayerController.ResetContextProvider();
    EnemyController.ResetContextProvider();
}

3. 避免在构造函数中访问 Context

[ContextAware]
public partial class MyController
{
    // ❌ 错误:构造函数执行时上下文可能未初始化
    public MyController()
    {
        var model = this.GetModel<SomeModel>(); // 可能为 null
    }

    // ✅ 正确:在初始化方法中访问
    public void Initialize()
    {
        var model = this.GetModel<SomeModel>(); // 安全
    }
}

4. 优先使用 Context 属性而非接口方法

[ContextAware]
public partial class MyController
{
    public void DoSomething()
    {
        // ✅ 推荐:使用扩展方法
        var model = this.GetModel<SomeModel>();

        // ❌ 不推荐:显式调用接口方法
        var context = ((IContextAware)this).GetContext();
        var model2 = context.GetModel<SomeModel>();
    }
}

诊断信息

生成器会在以下情况报告编译错误:

GFSG001: 类必须是 partial

[ContextAware]
public class MyController { } // 错误:缺少 partial 关键字

解决方案:添加 partial 关键字

[ContextAware]
public partial class MyController { } // ✅ 正确

GFSG002: ContextAware 只能用于类

[ContextAware]
public partial struct MyStruct { } // 错误:不能用于 struct

解决方案:将 struct 改为 class

[ContextAware]
public partial class MyClass { } // ✅ 正确

相关文档