feat(architecture): 添加架构上下文管理和绑定功能

- 在Architecture类中添加GameContext.Bind调用以绑定架构上下文
- 创建GameContext类用于管理架构上下文实例的注册和获取
- 实现架构上下文的绑定、获取、查找和移除功能
- 更新ContextAwareGenerator生成器以使用懒加载方式获取上下文
- 在测试架构中添加就绪事件注册功能
- 添加架构上下文按类型注册的测试用例
This commit is contained in:
GwWuYou 2025-12-29 23:11:50 +08:00
parent 56ff201f94
commit 0be919d8b1
7 changed files with 160 additions and 95 deletions

View File

@ -1,4 +1,5 @@
using GFramework.Core.architecture;
using GFramework.Core.events;
using GFramework.Core.Tests.model;
using GFramework.Core.Tests.system;
@ -6,6 +7,7 @@ namespace GFramework.Core.Tests.architecture;
public sealed class TestArchitecture : Architecture
{
public bool ReadyEventFired { get; private set; }
public bool InitCalled { get; private set; }
protected override void Init()
@ -14,5 +16,6 @@ public sealed class TestArchitecture : Architecture
RegisterModel(new TestModel());
RegisterSystem(new TestSystem());
Context.RegisterEvent<ArchitectureEvents.ArchitectureLifecycleReadyEvent>(_ => { ReadyEventFired = true; });
}
}

View File

@ -54,4 +54,14 @@ public class ArchitectureInitializationTests
Assert.That(system, Is.Not.Null);
Assert.That(system!.Inited, Is.True);
}
[Test]
public void Architecture_Should_Register_Context_By_Type()
{
// Act
_architecture!.Initialize();
var ctx = GameContext.GetByType(_architecture!.GetType());
Assert.That(ctx, Is.Not.Null);
}
}

View File

@ -246,7 +246,7 @@ public abstract class Architecture(
{
_logger = Configuration.LoggerFactory.GetLogger(GetType().Name);
_context ??= new ArchitectureContext(Container, TypeEventSystem, Configuration.LoggerFactory);
GameContext.Bind(GetType(), _context);
// 创建架构运行时实例
Runtime = new ArchitectureRuntime(_context);
((ArchitectureContext)_context).Runtime = Runtime;
@ -299,7 +299,7 @@ public abstract class Architecture(
{
_logger = Configuration.LoggerFactory.GetLogger(GetType().Name);
_context ??= new ArchitectureContext(Container, TypeEventSystem, Configuration.LoggerFactory);
GameContext.Bind(GetType(), _context);
// 创建架构运行时实例
Runtime = new ArchitectureRuntime(_context);
((ArchitectureContext)_context).Runtime = Runtime;

View File

@ -0,0 +1,113 @@
using System.Collections.Concurrent;
using GFramework.Core.Abstractions.architecture;
namespace GFramework.Core.architecture;
/// <summary>
/// 游戏上下文管理类,用于管理当前的架构上下文实例
/// </summary>
public static class GameContext
{
private static readonly ConcurrentDictionary<Type, IArchitectureContext> ArchitectureDictionary
= new();
/// <summary>
/// 获取所有已注册的架构上下文的只读字典
/// </summary>
public static IReadOnlyDictionary<Type, IArchitectureContext> ArchitectureReadOnlyDictionary =>
ArchitectureDictionary;
/// <summary>
/// 绑定指定类型的架构上下文到管理器中
/// </summary>
/// <param name="architectureType">架构类型</param>
/// <param name="context">架构上下文实例</param>
/// <exception cref="InvalidOperationException">当指定类型的架构上下文已存在时抛出</exception>
public static void Bind(Type architectureType, IArchitectureContext context)
{
if (!ArchitectureDictionary.TryAdd(architectureType, context))
{
throw new InvalidOperationException(
$"Architecture context for '{architectureType.Name}' already exists");
}
}
/// <summary>
/// 获取字典中的第一个架构上下文
/// </summary>
/// <returns>返回字典中的第一个架构上下文实例</returns>
/// <exception cref="InvalidOperationException">当字典为空时抛出</exception>
public static IArchitectureContext GetFirstArchitecture()
{
return ArchitectureDictionary.Values.First();
}
/// <summary>
/// 根据类型获取对应的架构上下文
/// </summary>
/// <param name="type">要查找的架构类型</param>
/// <returns>返回指定类型的架构上下文实例</returns>
/// <exception cref="InvalidOperationException">当指定类型的架构上下文不存在时抛出</exception>
public static IArchitectureContext GetByType(Type type)
{
if (ArchitectureDictionary.TryGetValue(type, out var context))
return context;
throw new InvalidOperationException(
$"Architecture context for '{type.Name}' not found");
}
/// <summary>
/// 获取指定类型的架构上下文实例
/// </summary>
/// <typeparam name="T">架构上下文类型必须实现IArchitectureContext接口</typeparam>
/// <returns>指定类型的架构上下文实例</returns>
/// <exception cref="InvalidOperationException">当指定类型的架构上下文不存在时抛出</exception>
public static T Get<T>() where T : class, IArchitectureContext
{
if (ArchitectureDictionary.TryGetValue(typeof(T), out var ctx))
return (T)ctx;
throw new InvalidOperationException(
$"Architecture context '{typeof(T).Name}' not found");
}
/// <summary>
/// 尝试获取指定类型的架构上下文实例
/// </summary>
/// <typeparam name="T">架构上下文类型必须实现IArchitectureContext接口</typeparam>
/// <param name="context">输出参数如果找到则返回对应的架构上下文实例否则返回null</param>
/// <returns>如果找到指定类型的架构上下文则返回true否则返回false</returns>
public static bool TryGet<T>(out T? context)
where T : class, IArchitectureContext
{
if (ArchitectureDictionary.TryGetValue(typeof(T), out var ctx))
{
context = (T)ctx;
return true;
}
context = null;
return false;
}
/// <summary>
/// 移除指定类型的架构上下文绑定
/// </summary>
/// <param name="architectureType">要移除的架构类型</param>
public static void Unbind(Type architectureType)
{
ArchitectureDictionary.TryRemove(architectureType, out _);
}
// 测试专用
/// <summary>
/// 为测试重置所有架构上下文(仅内部使用)
/// </summary>
internal static void ResetForTests()
{
ArchitectureDictionary.Clear();
}
}

View File

@ -55,6 +55,21 @@ public class ContextAwareGeneratorSnapshotTests
{
}
}
namespace GFramework.Core.architecture
{
using GFramework.Core.Abstractions.architecture;
public class GameContext{
/// <summary>
/// 获取字典中的第一个架构上下文
/// </summary>
/// <returns>返回字典中的第一个架构上下文实例</returns>
/// <exception cref="InvalidOperationException">当字典为空时抛出</exception>
public static IArchitectureContext GetFirstArchitecture()
{
return null;
}
}
}
""";
// 执行生成器快照测试,将生成的代码与预期快照进行比较

View File

@ -1,90 +0,0 @@
using GFramework.SourceGenerators.rule;
using GFramework.SourceGenerators.Tests.core;
using NUnit.Framework;
namespace GFramework.SourceGenerators.Tests.rule;
[TestFixture]
public class ContextAwareGeneratorTests
{
[Test]
public async Task Generates_ContextAware_Code_When_Interface_Inherits_IContextAware()
{
const string source = """
using System;
namespace GFramework.SourceGenerators.Abstractions.rule
{
[AttributeUsage(AttributeTargets.Class)]
public sealed class ContextAwareAttribute : Attribute
{
}
}
namespace TestApp
{
using GFramework.SourceGenerators.Abstractions.rule;
using GFramework.Core.Abstractions.rule;
public interface IMyRuleContextAware : IContextAware
{
}
[ContextAware]
public partial class MyRule : IMyRuleContextAware
{
}
}
""";
const string frameworkStub = """
namespace GFramework.Core.Abstractions.rule
{
public interface IContextAware
{
void SetContext(
GFramework.Core.Abstractions.architecture.IArchitectureContext context);
GFramework.Core.Abstractions.architecture.IArchitectureContext GetContext();
}
}
namespace GFramework.Core.Abstractions.architecture
{
public interface IArchitectureContext {}
}
""";
const string expected = """
// <auto-generated/>
#nullable enable
namespace TestApp;
partial class MyRule
{
/// <summary>
/// 自动注入的架构上下文
/// </summary>
protected GFramework.Core.Abstractions.architecture.IArchitectureContext Context { get; private set; } = 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;
}
}
""";
await GeneratorTest<ContextAwareGenerator>.RunAsync(
source + "\n" + frameworkStub,
("MyRule.ContextAware.g.cs", expected)
);
}
}

View File

@ -117,14 +117,28 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
/// <param name="sb">字符串构建器</param>
private static void GenerateContextProperty(StringBuilder sb)
{
sb.AppendLine(" private global::GFramework.Core.Abstractions.architecture.IArchitectureContext? _context;");
sb.AppendLine();
sb.AppendLine(" /// <summary>");
sb.AppendLine(" /// 自动注入的架构上下文");
sb.AppendLine(" /// 自动获取的架构上下文(懒加载,默认使用第一个架构)");
sb.AppendLine(" /// </summary>");
sb.AppendLine(" protected global::GFramework.Core.Abstractions.architecture.IArchitectureContext Context");
sb.AppendLine(" {");
sb.AppendLine(" get");
sb.AppendLine(" {");
sb.AppendLine(" if (_context == null)");
sb.AppendLine(" {");
sb.AppendLine(
$" protected {PathContests.CoreAbstractionsNamespace}.architecture.IArchitectureContext Context {{ get; private set; }} = null!;");
" _context = global::GFramework.Core.architecture.GameContext.GetFirstArchitecture();");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" return _context;");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine();
}
// =========================
// 显式接口实现(使用 global::
// =========================
@ -189,7 +203,7 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
switch (method.Name)
{
case "SetContext":
sb.AppendLine(" Context = context;");
sb.AppendLine(" _context = context;");
break;
case "GetContext":