diff --git a/GFramework.Core.Tests/architecture/TestArchitecture.cs b/GFramework.Core.Tests/architecture/TestArchitecture.cs index a0a56f3..56a6546 100644 --- a/GFramework.Core.Tests/architecture/TestArchitecture.cs +++ b/GFramework.Core.Tests/architecture/TestArchitecture.cs @@ -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(_ => { ReadyEventFired = true; }); } } \ No newline at end of file diff --git a/GFramework.Core.Tests/tests/ArchitectureInitializationTests.cs b/GFramework.Core.Tests/tests/ArchitectureInitializationTests.cs index bd5f590..aaec305 100644 --- a/GFramework.Core.Tests/tests/ArchitectureInitializationTests.cs +++ b/GFramework.Core.Tests/tests/ArchitectureInitializationTests.cs @@ -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); + } } \ No newline at end of file diff --git a/GFramework.Core/architecture/Architecture.cs b/GFramework.Core/architecture/Architecture.cs index f9bc982..efa0ea8 100644 --- a/GFramework.Core/architecture/Architecture.cs +++ b/GFramework.Core/architecture/Architecture.cs @@ -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; diff --git a/GFramework.Core/architecture/GameContext.cs b/GFramework.Core/architecture/GameContext.cs new file mode 100644 index 0000000..354f342 --- /dev/null +++ b/GFramework.Core/architecture/GameContext.cs @@ -0,0 +1,113 @@ +using System.Collections.Concurrent; +using GFramework.Core.Abstractions.architecture; + +namespace GFramework.Core.architecture; + +/// +/// 游戏上下文管理类,用于管理当前的架构上下文实例 +/// +public static class GameContext +{ + private static readonly ConcurrentDictionary ArchitectureDictionary + = new(); + + + /// + /// 获取所有已注册的架构上下文的只读字典 + /// + public static IReadOnlyDictionary ArchitectureReadOnlyDictionary => + ArchitectureDictionary; + + /// + /// 绑定指定类型的架构上下文到管理器中 + /// + /// 架构类型 + /// 架构上下文实例 + /// 当指定类型的架构上下文已存在时抛出 + public static void Bind(Type architectureType, IArchitectureContext context) + { + if (!ArchitectureDictionary.TryAdd(architectureType, context)) + { + throw new InvalidOperationException( + $"Architecture context for '{architectureType.Name}' already exists"); + } + } + + /// + /// 获取字典中的第一个架构上下文 + /// + /// 返回字典中的第一个架构上下文实例 + /// 当字典为空时抛出 + public static IArchitectureContext GetFirstArchitecture() + { + return ArchitectureDictionary.Values.First(); + } + + /// + /// 根据类型获取对应的架构上下文 + /// + /// 要查找的架构类型 + /// 返回指定类型的架构上下文实例 + /// 当指定类型的架构上下文不存在时抛出 + 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"); + } + + + /// + /// 获取指定类型的架构上下文实例 + /// + /// 架构上下文类型,必须实现IArchitectureContext接口 + /// 指定类型的架构上下文实例 + /// 当指定类型的架构上下文不存在时抛出 + public static T Get() 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"); + } + + /// + /// 尝试获取指定类型的架构上下文实例 + /// + /// 架构上下文类型,必须实现IArchitectureContext接口 + /// 输出参数,如果找到则返回对应的架构上下文实例,否则返回null + /// 如果找到指定类型的架构上下文则返回true,否则返回false + public static bool TryGet(out T? context) + where T : class, IArchitectureContext + { + if (ArchitectureDictionary.TryGetValue(typeof(T), out var ctx)) + { + context = (T)ctx; + return true; + } + + context = null; + return false; + } + + /// + /// 移除指定类型的架构上下文绑定 + /// + /// 要移除的架构类型 + public static void Unbind(Type architectureType) + { + ArchitectureDictionary.TryRemove(architectureType, out _); + } + + // 测试专用 + /// + /// 为测试重置所有架构上下文(仅内部使用) + /// + internal static void ResetForTests() + { + ArchitectureDictionary.Clear(); + } +} \ No newline at end of file diff --git a/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorSnapshotTests.cs b/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorSnapshotTests.cs index f4fd820..90828aa 100644 --- a/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorSnapshotTests.cs +++ b/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorSnapshotTests.cs @@ -55,6 +55,21 @@ public class ContextAwareGeneratorSnapshotTests { } } + namespace GFramework.Core.architecture + { + using GFramework.Core.Abstractions.architecture; + public class GameContext{ + /// + /// 获取字典中的第一个架构上下文 + /// + /// 返回字典中的第一个架构上下文实例 + /// 当字典为空时抛出 + public static IArchitectureContext GetFirstArchitecture() + { + return null; + } + } + } """; // 执行生成器快照测试,将生成的代码与预期快照进行比较 diff --git a/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorTests.cs b/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorTests.cs deleted file mode 100644 index a714823..0000000 --- a/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorTests.cs +++ /dev/null @@ -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 = """ - // - #nullable enable - - namespace TestApp; - - partial class MyRule - { - /// - /// 自动注入的架构上下文 - /// - 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.RunAsync( - source + "\n" + frameworkStub, - ("MyRule.ContextAware.g.cs", expected) - ); - } -} \ No newline at end of file diff --git a/GFramework.SourceGenerators/rule/ContextAwareGenerator.cs b/GFramework.SourceGenerators/rule/ContextAwareGenerator.cs index 199b7f4..99e80a6 100644 --- a/GFramework.SourceGenerators/rule/ContextAwareGenerator.cs +++ b/GFramework.SourceGenerators/rule/ContextAwareGenerator.cs @@ -117,14 +117,28 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase /// 字符串构建器 private static void GenerateContextProperty(StringBuilder sb) { + sb.AppendLine(" private global::GFramework.Core.Abstractions.architecture.IArchitectureContext? _context;"); + sb.AppendLine(); sb.AppendLine(" /// "); - sb.AppendLine(" /// 自动注入的架构上下文"); + sb.AppendLine(" /// 自动获取的架构上下文(懒加载,默认使用第一个架构)"); sb.AppendLine(" /// "); + 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":