Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,15 @@ jobs:
--no-build \
--logger "trx;LogFileName=sg-$RANDOM.trx" \
--results-directory TestResults

- name: Test - GFramework.Ecs.Arch.Tests
run: |
dotnet test GFramework.Ecs.Arch.Tests \
-c Release \
--no-build \
--logger "trx;LogFileName=ecs-arch-$RANDOM.trx" \
--results-directory TestResults

- name: Generate CTRF report
run: |
mkdir -p ctrf
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Collections.Concurrent;

namespace GFramework.Core.Abstractions.architecture;

/// <summary>
/// 架构模块注册表 - 用于外部模块的自动注册
/// </summary>
public static class ArchitectureModuleRegistry
{
private static readonly ConcurrentDictionary<string, Func<IServiceModule>> _factories = new();

Check warning on line 10 in GFramework.Core.Abstractions/architecture/ArchitectureModuleRegistry.cs

View workflow job for this annotation

GitHub Actions / Build and Test

Use an overload that has a IEqualityComparer<string> or IComparer<string> parameter (https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0002.md)

/// <summary>
/// 注册模块工厂(幂等操作,相同模块名只会注册一次)
/// </summary>
/// <param name="factory">模块工厂函数</param>
public static void Register(Func<IServiceModule> factory)
{
// 创建临时实例以获取模块名(用于幂等性检查)
var tempModule = factory();
var moduleName = tempModule.ModuleName;

// 幂等注册:相同模块名只注册一次
_factories.TryAdd(moduleName, factory);
}

/// <summary>
/// 创建所有已注册的模块实例
/// </summary>
/// <returns>模块实例集合</returns>
public static IEnumerable<IServiceModule> CreateModules()
{
return _factories.Values.Select(f => f());
}

/// <summary>
/// 清空注册表(主要用于测试)
/// </summary>
public static void Clear()
{
_factories.Clear();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using GFramework.Core.Abstractions.ioc;
using GFramework.Core.Abstractions.properties;

namespace GFramework.Core.Abstractions.architecture;

Expand All @@ -18,8 +17,7 @@ public interface IServiceModuleManager
/// 注册内置的服务模块。
/// </summary>
/// <param name="container">IoC容器实例,用于解析依赖。</param>
/// <param name="properties">架构属性配置,用于模块初始化。</param>
void RegisterBuiltInModules(IIocContainer container, ArchitectureProperties properties);
void RegisterBuiltInModules(IIocContainer container);

/// <summary>
/// 获取所有已注册的服务模块。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,4 @@ public sealed class ArchitectureProperties
/// 默认值为 false,表示不启用严格验证。
/// </summary>
public bool StrictPhaseValidation { get; set; }

/// <summary>
/// 启用 ECS(Entity Component System)功能的开关。
/// 当设置为 true 时,架构将启用 ECS 相关功能。
/// </summary>
public bool EnableEcs { get; set; }
}
4 changes: 3 additions & 1 deletion GFramework.Core.Tests/GFramework.Core.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<TargetFrameworks>net10.0;net8.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Mediator.Abstractions" Version="3.0.1"/>
Expand Down
44 changes: 7 additions & 37 deletions GFramework.Core.Tests/architecture/ArchitectureServicesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using GFramework.Core.Abstractions.events;
using GFramework.Core.Abstractions.ioc;
using GFramework.Core.Abstractions.model;
using GFramework.Core.Abstractions.properties;
using GFramework.Core.Abstractions.query;
using GFramework.Core.Abstractions.system;
using GFramework.Core.Abstractions.utility;
Expand All @@ -15,7 +14,6 @@
using GFramework.Core.ioc;
using GFramework.Core.query;
using Mediator;
using NUnit.Framework;
using ICommand = GFramework.Core.Abstractions.command.ICommand;

namespace GFramework.Core.Tests.architecture;
Expand Down Expand Up @@ -48,8 +46,7 @@ public void SetUp()

private void RegisterBuiltInServices()
{
var properties = new ArchitectureProperties();
_services!.ModuleManager.RegisterBuiltInModules(_services.Container, properties);
_services!.ModuleManager.RegisterBuiltInModules(_services.Container);
}

/// <summary>
Expand Down Expand Up @@ -216,13 +213,11 @@ public void Multiple_Instances_Should_Have_Independent_Container()
[Test]
public void Multiple_Instances_Should_Have_Independent_EventBus()
{
var properties = new ArchitectureProperties();

var services1 = new ArchitectureServices();
services1.ModuleManager.RegisterBuiltInModules(services1.Container, properties);
services1.ModuleManager.RegisterBuiltInModules(services1.Container);

var services2 = new ArchitectureServices();
services2.ModuleManager.RegisterBuiltInModules(services2.Container, properties);
services2.ModuleManager.RegisterBuiltInModules(services2.Container);

Assert.That(services1.EventBus, Is.Not.SameAs(services2.EventBus));
}
Expand All @@ -233,13 +228,11 @@ public void Multiple_Instances_Should_Have_Independent_EventBus()
[Test]
public void Multiple_Instances_Should_Have_Independent_CommandBus()
{
var properties = new ArchitectureProperties();

var services1 = new ArchitectureServices();
services1.ModuleManager.RegisterBuiltInModules(services1.Container, properties);
services1.ModuleManager.RegisterBuiltInModules(services1.Container);

var services2 = new ArchitectureServices();
services2.ModuleManager.RegisterBuiltInModules(services2.Container, properties);
services2.ModuleManager.RegisterBuiltInModules(services2.Container);

Assert.That(services1.CommandExecutor, Is.Not.SameAs(services2.CommandExecutor));
}
Expand All @@ -250,13 +243,11 @@ public void Multiple_Instances_Should_Have_Independent_CommandBus()
[Test]
public void Multiple_Instances_Should_Have_Independent_QueryBus()
{
var properties = new ArchitectureProperties();

var services1 = new ArchitectureServices();
services1.ModuleManager.RegisterBuiltInModules(services1.Container, properties);
services1.ModuleManager.RegisterBuiltInModules(services1.Container);

var services2 = new ArchitectureServices();
services2.ModuleManager.RegisterBuiltInModules(services2.Container, properties);
services2.ModuleManager.RegisterBuiltInModules(services2.Container);

Assert.That(services1.QueryExecutor, Is.Not.SameAs(services2.QueryExecutor));
}
Expand All @@ -269,27 +260,6 @@ public void ModuleManager_Should_Not_Be_Null()
{
Assert.That(_services!.ModuleManager, Is.Not.Null);
}

/// <summary>
/// 测试EnableEcs配置开关
/// </summary>
[Test]
public void EnableEcs_Should_Control_Ecs_Module_Registration()
{
var propertiesWithEcs = new ArchitectureProperties { EnableEcs = true };
var propertiesWithoutEcs = new ArchitectureProperties { EnableEcs = false };

var servicesWithEcs = new ArchitectureServices();
servicesWithEcs.ModuleManager.RegisterBuiltInModules(servicesWithEcs.Container, propertiesWithEcs);

var servicesWithoutEcs = new ArchitectureServices();
servicesWithoutEcs.ModuleManager.RegisterBuiltInModules(servicesWithoutEcs.Container, propertiesWithoutEcs);

var modulesWithEcs = servicesWithEcs.ModuleManager.GetModules();
var modulesWithoutEcs = servicesWithoutEcs.ModuleManager.GetModules();

Assert.That(modulesWithEcs.Count, Is.GreaterThan(modulesWithoutEcs.Count));
}
}

#region Test Classes
Expand Down
2 changes: 0 additions & 2 deletions GFramework.Core/GFramework.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,5 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.3"/>
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0"/>
<PackageReference Include="Arch" Version="2.1.0"/>
<PackageReference Include="Arch.System" Version="1.1.0"/>
</ItemGroup>
</Project>
4 changes: 2 additions & 2 deletions GFramework.Core/architecture/Architecture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -641,8 +641,8 @@ private async Task InitializeInternalAsync(bool asyncMode)
_logger = LoggerFactoryResolver.Provider.CreateLogger(GetType().Name);
Environment.Initialize();

// 注册内置服务模块(根据配置)
Services.ModuleManager.RegisterBuiltInModules(Container, Configuration.ArchitectureProperties);
// 注册内置服务模块
Services.ModuleManager.RegisterBuiltInModules(Container);

// 将 Environment 注册到容器(如果尚未注册)
if (!Container.Contains<IEnvironment>())
Expand Down
21 changes: 11 additions & 10 deletions GFramework.Core/services/ServiceModuleManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
using GFramework.Core.Abstractions.ioc;
using GFramework.Core.Abstractions.lifecycle;
using GFramework.Core.Abstractions.logging;
using GFramework.Core.Abstractions.properties;
using GFramework.Core.ecs;
using GFramework.Core.logging;
using GFramework.Core.services.modules;

Expand Down Expand Up @@ -44,42 +42,45 @@ public void RegisterModule(IServiceModule? module)

/// <summary>
/// 注册内置服务模块,并根据优先级排序后完成服务注册。
/// 内置模块包括事件总线、命令执行器、查询执行器等核心模块
/// 并根据配置决定是否启用ECS模块
/// 内置模块包括事件总线、命令执行器、查询执行器等核心模块
/// 同时注册通过 ArchitectureModuleRegistry 自动注册的外部模块
/// </summary>
/// <param name="container">IoC容器实例,用于模块服务注册。</param>
/// <param name="properties">架构属性配置,用于判断是否启用ECS模块。</param>
public void RegisterBuiltInModules(IIocContainer container, ArchitectureProperties properties)
public void RegisterBuiltInModules(IIocContainer container)
{
if (_builtInModulesRegistered)
{
_logger.Warn("Built-in modules already registered, skipping duplicate registration");
return;
}

// 注册内置模块
RegisterModule(new EventBusModule());
RegisterModule(new CommandExecutorModule());
RegisterModule(new QueryExecutorModule());
RegisterModule(new AsyncQueryExecutorModule());

if (properties.EnableEcs)
// 注册外部模块(通过 ArchitectureModuleRegistry 自动注册)
foreach (var module in ArchitectureModuleRegistry.CreateModules())
{
RegisterModule(new ArchEcsModule(enabled: true));
_logger.Info("ECS module enabled via configuration");
RegisterModule(module);
_logger.Info($"External module registered: {module.ModuleName}");
}

// 按优先级排序
var sortedModules = _modules.OrderBy(m => m.Priority).ToList();
_modules.Clear();
_modules.AddRange(sortedModules);

// 注册服务
foreach (var module in _modules.Where(module => module.IsEnabled))
{
_logger.Debug($"Registering services for module: {module.ModuleName}");
module.Register(container);
}

_builtInModulesRegistered = true;
_logger.Info($"Registered {_modules.Count} built-in service modules");
_logger.Info($"Registered {_modules.Count} service modules");
}

/// <summary>
Expand Down
22 changes: 22 additions & 0 deletions GFramework.Ecs.Arch.Abstractions/ArchOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace GFramework.Ecs.Arch.Abstractions;

/// <summary>
/// Arch ECS 配置选项
/// </summary>
public sealed class ArchOptions
{
/// <summary>
/// World 初始容量
/// </summary>
public int WorldCapacity { get; set; } = 1000;

/// <summary>
/// 是否启用统计信息
/// </summary>
public bool EnableStatistics { get; set; }

/// <summary>
/// 模块优先级
/// </summary>
public int Priority { get; set; } = 50;
}
18 changes: 18 additions & 0 deletions GFramework.Ecs.Arch.Abstractions/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project>
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.264">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Meziantou.Polyfill" Version="1.0.71">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<PackageId>GeWuYou.$(AssemblyName)</PackageId>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\GFramework.Core.Abstractions\GFramework.Core.Abstractions.csproj" PrivateAssets="all"/>
</ItemGroup>

</Project>
3 changes: 3 additions & 0 deletions GFramework.Ecs.Arch.Abstractions/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
global using System;
global using System.Collections.Generic;
global using System.Threading.Tasks;
15 changes: 15 additions & 0 deletions GFramework.Ecs.Arch.Abstractions/IArchEcsModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using GFramework.Core.Abstractions.architecture;

namespace GFramework.Ecs.Arch.Abstractions;

/// <summary>
/// Arch ECS 模块接口 - 定义 ECS 模块的核心契约
/// </summary>
public interface IArchEcsModule : IServiceModule
{
/// <summary>
/// 更新所有 ECS 系统
/// </summary>
/// <param name="deltaTime">帧间隔时间</param>
void Update(float deltaTime);
}
16 changes: 16 additions & 0 deletions GFramework.Ecs.Arch.Abstractions/IArchSystemAdapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using GFramework.Core.Abstractions.system;

namespace GFramework.Ecs.Arch.Abstractions;

/// <summary>
/// Arch 系统适配器接口 - 桥接 Arch.System.ISystem&lt;T&gt; 到框架上下文
/// </summary>
/// <typeparam name="T">系统数据类型(通常是 float 表示 deltaTime)</typeparam>
public interface IArchSystemAdapter<T> : ISystem
{
/// <summary>
/// 更新系统
/// </summary>
/// <param name="t">系统数据参数(通常是 deltaTime)</param>
void Update(in T t);
}
Loading
Loading