From af76e0ab0ba7343ab73628f6de4769a5d1192599 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Sun, 8 Mar 2026 19:45:36 +0800 Subject: [PATCH 1/7] =?UTF-8?q?refactor(ecs):=20=E7=A7=BB=E9=99=A4Arch=20E?= =?UTF-8?q?CS=E6=A8=A1=E5=9D=97=E5=8F=8A=E7=9B=B8=E5=85=B3=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E5=92=8C=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除Position组件结构体定义 - 删除Velocity组件结构体定义 - 删除MovementSystem移动系统实现 - 移除ArchEcsModule ECS模块管理器 - 删除ArchSystemAdapter适配器基类 - 从ServiceModuleManager中移除ECS模块注册逻辑 - 从ArchitectureProperties中移除EnableEcs配置选项 - 删除ECS相关的单元测试文件 - 从项目文件中移除Arch和Arch.System包引用 - 从解决方案文件中移除ECS相关项目引用 - 更新项目配置文件中的目标框架和测试项目属性 --- .../ArchitectureModuleRegistry.cs | 35 ++++ .../properties/ArchitectureProperties.cs | 6 - .../GFramework.Core.Tests.csproj | 4 +- .../architecture/ArchitectureServicesTests.cs | 22 --- GFramework.Core/GFramework.Core.csproj | 2 - .../services/ServiceModuleManager.cs | 19 +- .../ArchOptions.cs | 22 +++ .../Directory.Build.props | 18 ++ .../GFramework.Ecs.Arch.Abstractions.csproj | 13 ++ .../GlobalUsings.cs | 3 + .../IArchEcsModule.cs | 15 ++ .../IArchSystemAdapter.cs | 16 ++ .../GFramework.Ecs.Arch.Tests.csproj | 25 +++ GFramework.Ecs.Arch.Tests/GlobalUsings.cs | 5 + .../ecs/EcsAdvancedTests.cs | 8 +- .../ecs/EcsBasicTests.cs | 8 +- .../ecs/EcsIntegrationTests.cs | 8 +- .../integration/AutoRegistrationTests.cs | 75 ++++++++ .../ArchEcsModule.cs | 5 +- GFramework.Ecs.Arch/ArchModuleInitializer.cs | 21 +++ .../ArchSystemAdapter.cs | 24 +-- .../GFramework.Ecs.Arch.csproj | 23 +++ GFramework.Ecs.Arch/GlobalUsings.cs | 5 + GFramework.Ecs.Arch/README.md | 170 ++++++++++++++++++ .../components/Position.cs | 2 +- .../components/Velocity.cs | 2 +- .../extensions/ArchExtensions.cs | 34 ++++ .../systems/MovementSystem.cs | 4 +- .../GFramework.SourceGenerators.Tests.csproj | 4 +- GFramework.csproj | 9 + GFramework.sln | 155 +++++++++++++++- 31 files changed, 687 insertions(+), 75 deletions(-) create mode 100644 GFramework.Core.Abstractions/architecture/ArchitectureModuleRegistry.cs create mode 100644 GFramework.Ecs.Arch.Abstractions/ArchOptions.cs create mode 100644 GFramework.Ecs.Arch.Abstractions/Directory.Build.props create mode 100644 GFramework.Ecs.Arch.Abstractions/GFramework.Ecs.Arch.Abstractions.csproj create mode 100644 GFramework.Ecs.Arch.Abstractions/GlobalUsings.cs create mode 100644 GFramework.Ecs.Arch.Abstractions/IArchEcsModule.cs create mode 100644 GFramework.Ecs.Arch.Abstractions/IArchSystemAdapter.cs create mode 100644 GFramework.Ecs.Arch.Tests/GFramework.Ecs.Arch.Tests.csproj create mode 100644 GFramework.Ecs.Arch.Tests/GlobalUsings.cs rename {GFramework.Core.Tests => GFramework.Ecs.Arch.Tests}/ecs/EcsAdvancedTests.cs (97%) rename {GFramework.Core.Tests => GFramework.Ecs.Arch.Tests}/ecs/EcsBasicTests.cs (96%) rename {GFramework.Core.Tests => GFramework.Ecs.Arch.Tests}/ecs/EcsIntegrationTests.cs (97%) create mode 100644 GFramework.Ecs.Arch.Tests/integration/AutoRegistrationTests.cs rename {GFramework.Core/ecs => GFramework.Ecs.Arch}/ArchEcsModule.cs (95%) create mode 100644 GFramework.Ecs.Arch/ArchModuleInitializer.cs rename {GFramework.Core/ecs => GFramework.Ecs.Arch}/ArchSystemAdapter.cs (96%) create mode 100644 GFramework.Ecs.Arch/GFramework.Ecs.Arch.csproj create mode 100644 GFramework.Ecs.Arch/GlobalUsings.cs create mode 100644 GFramework.Ecs.Arch/README.md rename {GFramework.Core/ecs => GFramework.Ecs.Arch}/components/Position.cs (92%) rename {GFramework.Core/ecs => GFramework.Ecs.Arch}/components/Velocity.cs (93%) create mode 100644 GFramework.Ecs.Arch/extensions/ArchExtensions.cs rename {GFramework.Core/ecs => GFramework.Ecs.Arch}/systems/MovementSystem.cs (92%) diff --git a/GFramework.Core.Abstractions/architecture/ArchitectureModuleRegistry.cs b/GFramework.Core.Abstractions/architecture/ArchitectureModuleRegistry.cs new file mode 100644 index 0000000..1259545 --- /dev/null +++ b/GFramework.Core.Abstractions/architecture/ArchitectureModuleRegistry.cs @@ -0,0 +1,35 @@ +namespace GFramework.Core.Abstractions.architecture; + +/// +/// 架构模块注册表 - 用于外部模块的自动注册 +/// +public static class ArchitectureModuleRegistry +{ + private static readonly List> _factories = []; + + /// + /// 注册模块工厂 + /// + /// 模块工厂函数 + public static void Register(Func factory) + { + _factories.Add(factory); + } + + /// + /// 创建所有已注册的模块实例 + /// + /// 模块实例集合 + public static IEnumerable CreateModules() + { + return _factories.Select(f => f()); + } + + /// + /// 清空注册表(主要用于测试) + /// + public static void Clear() + { + _factories.Clear(); + } +} \ No newline at end of file diff --git a/GFramework.Core.Abstractions/properties/ArchitectureProperties.cs b/GFramework.Core.Abstractions/properties/ArchitectureProperties.cs index 1915685..f1a5eca 100644 --- a/GFramework.Core.Abstractions/properties/ArchitectureProperties.cs +++ b/GFramework.Core.Abstractions/properties/ArchitectureProperties.cs @@ -17,10 +17,4 @@ public sealed class ArchitectureProperties /// 默认值为 false,表示不启用严格验证。 /// public bool StrictPhaseValidation { get; set; } - - /// - /// 启用 ECS(Entity Component System)功能的开关。 - /// 当设置为 true 时,架构将启用 ECS 相关功能。 - /// - public bool EnableEcs { get; set; } } \ No newline at end of file diff --git a/GFramework.Core.Tests/GFramework.Core.Tests.csproj b/GFramework.Core.Tests/GFramework.Core.Tests.csproj index eed0620..f394be1 100644 --- a/GFramework.Core.Tests/GFramework.Core.Tests.csproj +++ b/GFramework.Core.Tests/GFramework.Core.Tests.csproj @@ -1,9 +1,11 @@  + net8.0;net10.0 disable enable - net10.0;net8.0 + false + true diff --git a/GFramework.Core.Tests/architecture/ArchitectureServicesTests.cs b/GFramework.Core.Tests/architecture/ArchitectureServicesTests.cs index efe5aad..936204b 100644 --- a/GFramework.Core.Tests/architecture/ArchitectureServicesTests.cs +++ b/GFramework.Core.Tests/architecture/ArchitectureServicesTests.cs @@ -15,7 +15,6 @@ using GFramework.Core.events; 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; @@ -269,27 +268,6 @@ public class ArchitectureServicesTests { Assert.That(_services!.ModuleManager, Is.Not.Null); } - - /// - /// 测试EnableEcs配置开关 - /// - [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 diff --git a/GFramework.Core/GFramework.Core.csproj b/GFramework.Core/GFramework.Core.csproj index 05baa84..41e4b38 100644 --- a/GFramework.Core/GFramework.Core.csproj +++ b/GFramework.Core/GFramework.Core.csproj @@ -13,7 +13,5 @@ - - diff --git a/GFramework.Core/services/ServiceModuleManager.cs b/GFramework.Core/services/ServiceModuleManager.cs index 0f16073..68bd2c0 100644 --- a/GFramework.Core/services/ServiceModuleManager.cs +++ b/GFramework.Core/services/ServiceModuleManager.cs @@ -3,7 +3,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; @@ -44,11 +43,11 @@ public sealed class ServiceModuleManager : IServiceModuleManager /// /// 注册内置服务模块,并根据优先级排序后完成服务注册。 - /// 内置模块包括事件总线、命令执行器、查询执行器等核心模块, - /// 并根据配置决定是否启用ECS模块。 + /// 内置模块包括事件总线、命令执行器、查询执行器等核心模块。 + /// 同时注册通过 ModuleInitializer 自动注册的外部模块。 /// /// IoC容器实例,用于模块服务注册。 - /// 架构属性配置,用于判断是否启用ECS模块。 + /// 架构属性配置。 public void RegisterBuiltInModules(IIocContainer container, ArchitectureProperties properties) { if (_builtInModulesRegistered) @@ -57,21 +56,25 @@ public sealed class ServiceModuleManager : IServiceModuleManager return; } + // 注册内置模块 RegisterModule(new EventBusModule()); RegisterModule(new CommandExecutorModule()); RegisterModule(new QueryExecutorModule()); RegisterModule(new AsyncQueryExecutorModule()); - if (properties.EnableEcs) + // 注册外部模块(通过 ModuleInitializer 自动注册) + 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}"); @@ -79,7 +82,7 @@ public sealed class ServiceModuleManager : IServiceModuleManager } _builtInModulesRegistered = true; - _logger.Info($"Registered {_modules.Count} built-in service modules"); + _logger.Info($"Registered {_modules.Count} service modules"); } /// diff --git a/GFramework.Ecs.Arch.Abstractions/ArchOptions.cs b/GFramework.Ecs.Arch.Abstractions/ArchOptions.cs new file mode 100644 index 0000000..926f877 --- /dev/null +++ b/GFramework.Ecs.Arch.Abstractions/ArchOptions.cs @@ -0,0 +1,22 @@ +namespace GFramework.Ecs.Arch.Abstractions; + +/// +/// Arch ECS 配置选项 +/// +public sealed class ArchOptions +{ + /// + /// World 初始容量 + /// + public int WorldCapacity { get; set; } = 1000; + + /// + /// 是否启用统计信息 + /// + public bool EnableStatistics { get; set; } + + /// + /// 模块优先级 + /// + public int Priority { get; set; } = 50; +} \ No newline at end of file diff --git a/GFramework.Ecs.Arch.Abstractions/Directory.Build.props b/GFramework.Ecs.Arch.Abstractions/Directory.Build.props new file mode 100644 index 0000000..8febdc4 --- /dev/null +++ b/GFramework.Ecs.Arch.Abstractions/Directory.Build.props @@ -0,0 +1,18 @@ + + + netstandard2.1 + true + true + preview + + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers + + + diff --git a/GFramework.Ecs.Arch.Abstractions/GFramework.Ecs.Arch.Abstractions.csproj b/GFramework.Ecs.Arch.Abstractions/GFramework.Ecs.Arch.Abstractions.csproj new file mode 100644 index 0000000..b937843 --- /dev/null +++ b/GFramework.Ecs.Arch.Abstractions/GFramework.Ecs.Arch.Abstractions.csproj @@ -0,0 +1,13 @@ + + + + GeWuYou.$(AssemblyName) + true + enable + + + + + + + diff --git a/GFramework.Ecs.Arch.Abstractions/GlobalUsings.cs b/GFramework.Ecs.Arch.Abstractions/GlobalUsings.cs new file mode 100644 index 0000000..9d7f7e7 --- /dev/null +++ b/GFramework.Ecs.Arch.Abstractions/GlobalUsings.cs @@ -0,0 +1,3 @@ +global using System; +global using System.Collections.Generic; +global using System.Threading.Tasks; \ No newline at end of file diff --git a/GFramework.Ecs.Arch.Abstractions/IArchEcsModule.cs b/GFramework.Ecs.Arch.Abstractions/IArchEcsModule.cs new file mode 100644 index 0000000..6dcc631 --- /dev/null +++ b/GFramework.Ecs.Arch.Abstractions/IArchEcsModule.cs @@ -0,0 +1,15 @@ +using GFramework.Core.Abstractions.architecture; + +namespace GFramework.Ecs.Arch.Abstractions; + +/// +/// Arch ECS 模块接口 - 定义 ECS 模块的核心契约 +/// +public interface IArchEcsModule : IServiceModule +{ + /// + /// 更新所有 ECS 系统 + /// + /// 帧间隔时间 + void Update(float deltaTime); +} \ No newline at end of file diff --git a/GFramework.Ecs.Arch.Abstractions/IArchSystemAdapter.cs b/GFramework.Ecs.Arch.Abstractions/IArchSystemAdapter.cs new file mode 100644 index 0000000..59c42d6 --- /dev/null +++ b/GFramework.Ecs.Arch.Abstractions/IArchSystemAdapter.cs @@ -0,0 +1,16 @@ +using GFramework.Core.Abstractions.system; + +namespace GFramework.Ecs.Arch.Abstractions; + +/// +/// Arch 系统适配器接口 - 桥接 Arch.System.ISystem<T> 到框架上下文 +/// +/// 系统数据类型(通常是 float 表示 deltaTime) +public interface IArchSystemAdapter : ISystem +{ + /// + /// 更新系统 + /// + /// 系统数据参数(通常是 deltaTime) + void Update(in T t); +} \ No newline at end of file diff --git a/GFramework.Ecs.Arch.Tests/GFramework.Ecs.Arch.Tests.csproj b/GFramework.Ecs.Arch.Tests/GFramework.Ecs.Arch.Tests.csproj new file mode 100644 index 0000000..1d5346f --- /dev/null +++ b/GFramework.Ecs.Arch.Tests/GFramework.Ecs.Arch.Tests.csproj @@ -0,0 +1,25 @@ + + + + net8.0;net10.0 + disable + enable + false + true + false + + + + + + + + + + + + + + + + diff --git a/GFramework.Ecs.Arch.Tests/GlobalUsings.cs b/GFramework.Ecs.Arch.Tests/GlobalUsings.cs new file mode 100644 index 0000000..170ef5b --- /dev/null +++ b/GFramework.Ecs.Arch.Tests/GlobalUsings.cs @@ -0,0 +1,5 @@ +global using System; +global using System.Collections.Generic; +global using System.Threading.Tasks; +global using NUnit.Framework; +global using GFramework.Ecs.Arch; \ No newline at end of file diff --git a/GFramework.Core.Tests/ecs/EcsAdvancedTests.cs b/GFramework.Ecs.Arch.Tests/ecs/EcsAdvancedTests.cs similarity index 97% rename from GFramework.Core.Tests/ecs/EcsAdvancedTests.cs rename to GFramework.Ecs.Arch.Tests/ecs/EcsAdvancedTests.cs index 3831138..3ef8f07 100644 --- a/GFramework.Core.Tests/ecs/EcsAdvancedTests.cs +++ b/GFramework.Ecs.Arch.Tests/ecs/EcsAdvancedTests.cs @@ -2,13 +2,11 @@ using System.Diagnostics.CodeAnalysis; using Arch.Core; using GFramework.Core.Abstractions.rule; using GFramework.Core.architecture; -using GFramework.Core.ecs; -using GFramework.Core.ecs.components; -using GFramework.Core.ecs.systems; using GFramework.Core.ioc; -using NUnit.Framework; +using GFramework.Ecs.Arch.components; +using GFramework.Ecs.Arch.systems; -namespace GFramework.Core.Tests.ecs; +namespace GFramework.Ecs.Arch.Tests.ecs; /// /// ECS 高级功能测试类 - 使用 Arch 原生 API diff --git a/GFramework.Core.Tests/ecs/EcsBasicTests.cs b/GFramework.Ecs.Arch.Tests/ecs/EcsBasicTests.cs similarity index 96% rename from GFramework.Core.Tests/ecs/EcsBasicTests.cs rename to GFramework.Ecs.Arch.Tests/ecs/EcsBasicTests.cs index c1d9f52..896789a 100644 --- a/GFramework.Core.Tests/ecs/EcsBasicTests.cs +++ b/GFramework.Ecs.Arch.Tests/ecs/EcsBasicTests.cs @@ -2,13 +2,11 @@ using System.Diagnostics.CodeAnalysis; using Arch.Core; using GFramework.Core.Abstractions.rule; using GFramework.Core.architecture; -using GFramework.Core.ecs; -using GFramework.Core.ecs.components; -using GFramework.Core.ecs.systems; using GFramework.Core.ioc; -using NUnit.Framework; +using GFramework.Ecs.Arch.components; +using GFramework.Ecs.Arch.systems; -namespace GFramework.Core.Tests.ecs; +namespace GFramework.Ecs.Arch.Tests.ecs; /// /// ECS 基础功能测试类 - 使用 Arch 原生 API diff --git a/GFramework.Core.Tests/ecs/EcsIntegrationTests.cs b/GFramework.Ecs.Arch.Tests/ecs/EcsIntegrationTests.cs similarity index 97% rename from GFramework.Core.Tests/ecs/EcsIntegrationTests.cs rename to GFramework.Ecs.Arch.Tests/ecs/EcsIntegrationTests.cs index 55287a9..db9c290 100644 --- a/GFramework.Core.Tests/ecs/EcsIntegrationTests.cs +++ b/GFramework.Ecs.Arch.Tests/ecs/EcsIntegrationTests.cs @@ -2,13 +2,11 @@ using System.Diagnostics.CodeAnalysis; using Arch.Core; using GFramework.Core.Abstractions.rule; using GFramework.Core.architecture; -using GFramework.Core.ecs; -using GFramework.Core.ecs.components; -using GFramework.Core.ecs.systems; using GFramework.Core.ioc; -using NUnit.Framework; +using GFramework.Ecs.Arch.components; +using GFramework.Ecs.Arch.systems; -namespace GFramework.Core.Tests.ecs; +namespace GFramework.Ecs.Arch.Tests.ecs; /// /// ECS 集成测试类 - 使用 Arch 原生 API diff --git a/GFramework.Ecs.Arch.Tests/integration/AutoRegistrationTests.cs b/GFramework.Ecs.Arch.Tests/integration/AutoRegistrationTests.cs new file mode 100644 index 0000000..b0a6d5f --- /dev/null +++ b/GFramework.Ecs.Arch.Tests/integration/AutoRegistrationTests.cs @@ -0,0 +1,75 @@ +using System.Linq; +using Arch.Core; +using GFramework.Core.Abstractions.properties; +using GFramework.Core.architecture; +using GFramework.Core.ioc; +using GFramework.Ecs.Arch.Abstractions; + +namespace GFramework.Ecs.Arch.Tests.integration; + +/// +/// 自动注册集成测试 +/// +[TestFixture] +public class AutoRegistrationTests +{ + [SetUp] + public void Setup() + { + _container = new MicrosoftDiContainer(); + _context = new ArchitectureContext(_container); + } + + [TearDown] + public void TearDown() + { + _container?.Clear(); + _context = null; + } + + private MicrosoftDiContainer? _container; + private ArchitectureContext? _context; + + /// + /// 测试 Arch ECS 模块是否自动注册 + /// + [Test] + public void ArchEcsModule_Should_Be_Auto_Registered() + { + // Arrange - 手动触发模块初始化器(模拟自动注册) + ArchModuleInitializer.Initialize(); + + var services = new ArchitectureServices(); + var properties = new ArchitectureProperties(); + + // Act + services.ModuleManager.RegisterBuiltInModules(services.Container, properties); + var modules = services.ModuleManager.GetModules(); + + // Assert + var archModule = modules.FirstOrDefault(m => m.ModuleName == nameof(ArchEcsModule)); + Assert.That(archModule, Is.Not.Null, "ArchEcsModule should be auto-registered"); + Assert.That(archModule, Is.InstanceOf()); + } + + /// + /// 测试 World 是否正确注册到容器 + /// + [Test] + public void World_Should_Be_Registered_In_Container() + { + // Arrange - 手动触发模块初始化器 + ArchModuleInitializer.Initialize(); + + var services = new ArchitectureServices(); + var properties = new ArchitectureProperties(); + + // Act + services.ModuleManager.RegisterBuiltInModules(services.Container, properties); + services.ModuleManager.InitializeAllAsync(false).Wait(); + + // Assert + var world = services.Container.Get(); + Assert.That(world, Is.Not.Null, "World should be registered in container"); + } +} \ No newline at end of file diff --git a/GFramework.Core/ecs/ArchEcsModule.cs b/GFramework.Ecs.Arch/ArchEcsModule.cs similarity index 95% rename from GFramework.Core/ecs/ArchEcsModule.cs rename to GFramework.Ecs.Arch/ArchEcsModule.cs index 784b822..cad7205 100644 --- a/GFramework.Core/ecs/ArchEcsModule.cs +++ b/GFramework.Ecs.Arch/ArchEcsModule.cs @@ -1,13 +1,12 @@ using Arch.Core; -using GFramework.Core.Abstractions.architecture; using GFramework.Core.Abstractions.ioc; -namespace GFramework.Core.ecs; +namespace GFramework.Ecs.Arch; /// /// Arch ECS 模块 - 核心适配器,桥接 Arch 到框架生命周期 /// -public sealed class ArchEcsModule : IServiceModule +public sealed class ArchEcsModule : IArchEcsModule { private readonly List> _systems = []; private IIocContainer? _container; diff --git a/GFramework.Ecs.Arch/ArchModuleInitializer.cs b/GFramework.Ecs.Arch/ArchModuleInitializer.cs new file mode 100644 index 0000000..5e8a7f4 --- /dev/null +++ b/GFramework.Ecs.Arch/ArchModuleInitializer.cs @@ -0,0 +1,21 @@ +using System.Runtime.CompilerServices; +using GFramework.Core.Abstractions.architecture; + +namespace GFramework.Ecs.Arch; + +/// +/// Arch ECS 模块自动初始化器 +/// 使用 ModuleInitializer 特性在程序启动时自动注册模块 +/// +public static class ArchModuleInitializer +{ + /// + /// 模块初始化方法,在程序启动时自动调用 + /// + [ModuleInitializer] + public static void Initialize() + { + // 注册 Arch ECS 模块工厂 + ArchitectureModuleRegistry.Register(() => new ArchEcsModule(enabled: true)); + } +} \ No newline at end of file diff --git a/GFramework.Core/ecs/ArchSystemAdapter.cs b/GFramework.Ecs.Arch/ArchSystemAdapter.cs similarity index 96% rename from GFramework.Core/ecs/ArchSystemAdapter.cs rename to GFramework.Ecs.Arch/ArchSystemAdapter.cs index 8a86c98..6f7e99f 100644 --- a/GFramework.Core/ecs/ArchSystemAdapter.cs +++ b/GFramework.Ecs.Arch/ArchSystemAdapter.cs @@ -3,19 +3,29 @@ using GFramework.Core.extensions; using GFramework.Core.system; using ArchSys = Arch.System; -namespace GFramework.Core.ecs; +namespace GFramework.Ecs.Arch; /// /// Arch 系统适配器 - 桥接 Arch.System.ISystem<T> 到框架上下文 /// /// 系统数据类型(通常是 float 表示 deltaTime) -public abstract class ArchSystemAdapter : AbstractSystem, ArchSys.ISystem +public abstract class ArchSystemAdapter : AbstractSystem, IArchSystemAdapter, ArchSys.ISystem { /// /// 获取或设置 Arch ECS 世界的实例 /// public World World { get; private set; } = null!; + /// + /// 显式实现 Arch.System.ISystem<T> 的主更新方法 + /// 调用受保护的虚方法 OnUpdate 以强制子类实现核心更新逻辑 + /// + /// 系统数据参数(通常是 deltaTime) + public void Update(in T t) + { + OnUpdate(in t); + } + // ===== Arch 显式接口实现 ===== /// @@ -37,16 +47,6 @@ public abstract class ArchSystemAdapter : AbstractSystem, ArchSys.ISystem OnBeforeUpdate(in t); } - /// - /// 显式实现 Arch.System.ISystem<T> 的主更新方法 - /// 调用受保护的虚方法 OnUpdate 以强制子类实现核心更新逻辑 - /// - /// 系统数据参数(通常是 deltaTime) - public void Update(in T t) - { - OnUpdate(in t); - } - /// /// 显式实现 Arch.System.ISystem<T> 的更新后回调方法 /// 调用受保护的虚方法 OnAfterUpdate 以允许子类自定义后处理逻辑 diff --git a/GFramework.Ecs.Arch/GFramework.Ecs.Arch.csproj b/GFramework.Ecs.Arch/GFramework.Ecs.Arch.csproj new file mode 100644 index 0000000..2b58a0e --- /dev/null +++ b/GFramework.Ecs.Arch/GFramework.Ecs.Arch.csproj @@ -0,0 +1,23 @@ + + + + GeWuYou.$(AssemblyName) + net8.0;net9.0;net10.0 + disable + enable + true + true + + + + + + + + + + + + + diff --git a/GFramework.Ecs.Arch/GlobalUsings.cs b/GFramework.Ecs.Arch/GlobalUsings.cs new file mode 100644 index 0000000..50bd209 --- /dev/null +++ b/GFramework.Ecs.Arch/GlobalUsings.cs @@ -0,0 +1,5 @@ +global using System; +global using System.Collections.Generic; +global using System.Threading.Tasks; +global using GFramework.Core.Abstractions; +global using GFramework.Ecs.Arch.Abstractions; \ No newline at end of file diff --git a/GFramework.Ecs.Arch/README.md b/GFramework.Ecs.Arch/README.md new file mode 100644 index 0000000..1525346 --- /dev/null +++ b/GFramework.Ecs.Arch/README.md @@ -0,0 +1,170 @@ +# GFramework.Ecs.Arch + +GFramework 的 Arch ECS 集成包,提供开箱即用的 ECS(Entity Component System)支持。 + +## 特性 + +- 🚀 **自动集成** - 引入 NuGet 包即可自动启用,无需手动配置 +- 🔌 **零依赖** - 不使用时,Core 包无 Arch 依赖 +- 🎯 **类型安全** - 完整的类型系统和编译时检查 +- ⚡ **高性能** - 基于 Arch ECS 的高性能实现 +- 🔧 **易扩展** - 简单的系统适配器模式 + +## 快速开始 + +### 1. 安装包 + +```bash +dotnet add package GeWuYou.GFramework.Ecs.Arch +``` + +### 2. 创建组件 + +```csharp +using System.Runtime.InteropServices; + +[StructLayout(LayoutKind.Sequential)] +public struct Position(float x, float y) +{ + public float X { get; set; } = x; + public float Y { get; set; } = y; +} + +[StructLayout(LayoutKind.Sequential)] +public struct Velocity(float x, float y) +{ + public float X { get; set; } = x; + public float Y { get; set; } = y; +} +``` + +### 3. 创建系统 + +```csharp +using Arch.Core; +using GFramework.Ecs.Arch; + +public sealed class MovementSystem : ArchSystemAdapter +{ + private QueryDescription _query; + + protected override void OnArchInitialize() + { + _query = new QueryDescription() + .WithAll(); + } + + protected override void OnUpdate(in float deltaTime) + { + World.Query(in _query, (ref Position pos, ref Velocity vel) => + { + pos.X += vel.X * deltaTime; + pos.Y += vel.Y * deltaTime; + }); + } +} +``` + +### 4. 注册系统 + +```csharp +public class MyArchitecture : Architecture +{ + protected override void OnRegisterSystem(IIocContainer container) + { + container.Register(); + } +} +``` + +### 5. 创建实体 + +```csharp +var world = this.GetService(); +var entity = world.Create( + new Position(0, 0), + new Velocity(1, 1) +); +``` + +### 6. 更新系统 + +```csharp +var ecsModule = this.GetService(); +ecsModule.Update(deltaTime); +``` + +## 配置选项 + +可以通过配置文件或代码配置 Arch ECS: + +### 代码配置 + +```csharp +services.ConfigureArch(options => +{ + options.WorldCapacity = 2000; + options.EnableStatistics = true; + options.Priority = 50; +}); +``` + +### 配置文件 + +```json +{ + "GFramework": { + "Modules": { + "Arch": { + "Enabled": true, + "Priority": 50, + "WorldCapacity": 1000, + "EnableStatistics": false + } + } + } +} +``` + +## 架构说明 + +### 模块自动注册 + +本包使用 `ModuleInitializer` 特性实现自动注册,无需手动配置: + +```csharp +[ModuleInitializer] +public static void Initialize() +{ + ArchitectureModuleRegistry.Register(() => new ArchEcsModule(enabled: true)); +} +``` + +### 系统适配器 + +`ArchSystemAdapter` 桥接 Arch.System.ISystem 到 GFramework 架构: + +- 自动获取 World 实例 +- 集成到框架生命周期 +- 支持上下文感知(Context-Aware) + +### 生命周期 + +1. **注册阶段** - 模块自动注册到架构 +2. **初始化阶段** - 创建 World,初始化系统 +3. **运行阶段** - 每帧调用 Update +4. **销毁阶段** - 清理资源,销毁 World + +## 示例 + +完整示例请参考 `GFramework.Ecs.Arch.Tests` 项目。 + +## 依赖 + +- GFramework.Core >= 1.0.0 +- Arch >= 2.1.0 +- Arch.System >= 1.1.0 + +## 许可证 + +MIT License diff --git a/GFramework.Core/ecs/components/Position.cs b/GFramework.Ecs.Arch/components/Position.cs similarity index 92% rename from GFramework.Core/ecs/components/Position.cs rename to GFramework.Ecs.Arch/components/Position.cs index 204f66d..fe625d0 100644 --- a/GFramework.Core/ecs/components/Position.cs +++ b/GFramework.Ecs.Arch/components/Position.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace GFramework.Core.ecs.components; +namespace GFramework.Ecs.Arch.components; /// /// 位置组件,用于表示实体在二维空间中的坐标位置。 diff --git a/GFramework.Core/ecs/components/Velocity.cs b/GFramework.Ecs.Arch/components/Velocity.cs similarity index 93% rename from GFramework.Core/ecs/components/Velocity.cs rename to GFramework.Ecs.Arch/components/Velocity.cs index 26465b2..dab5eb6 100644 --- a/GFramework.Core/ecs/components/Velocity.cs +++ b/GFramework.Ecs.Arch/components/Velocity.cs @@ -1,6 +1,6 @@ using System.Runtime.InteropServices; -namespace GFramework.Core.ecs.components; +namespace GFramework.Ecs.Arch.components; /// /// 速度结构体,用于表示二维空间中实体的瞬时速度向量 diff --git a/GFramework.Ecs.Arch/extensions/ArchExtensions.cs b/GFramework.Ecs.Arch/extensions/ArchExtensions.cs new file mode 100644 index 0000000..7dcd08d --- /dev/null +++ b/GFramework.Ecs.Arch/extensions/ArchExtensions.cs @@ -0,0 +1,34 @@ +using GFramework.Core.Abstractions.architecture; +using Microsoft.Extensions.DependencyInjection; + +namespace GFramework.Ecs.Arch.extensions; + +/// +/// Arch ECS 扩展方法 +/// +public static class ArchExtensions +{ + /// + /// 配置 Arch ECS 选项 + /// + public static IServiceCollection ConfigureArch( + this IServiceCollection services, + Action configure) + { + var options = new ArchOptions(); + configure(options); + services.AddSingleton(options); + return services; + } + + /// + /// 显式启用 Arch ECS 模块(备选方案) + /// + public static TArchitecture UseArch(this TArchitecture architecture) + where TArchitecture : IArchitecture + { + // 此方法为显式注册提供支持 + // 实际注册由 ModuleInitializer 自动完成 + return architecture; + } +} \ No newline at end of file diff --git a/GFramework.Core/ecs/systems/MovementSystem.cs b/GFramework.Ecs.Arch/systems/MovementSystem.cs similarity index 92% rename from GFramework.Core/ecs/systems/MovementSystem.cs rename to GFramework.Ecs.Arch/systems/MovementSystem.cs index 897ca8b..3f3b923 100644 --- a/GFramework.Core/ecs/systems/MovementSystem.cs +++ b/GFramework.Ecs.Arch/systems/MovementSystem.cs @@ -1,7 +1,7 @@ using Arch.Core; -using GFramework.Core.ecs.components; +using GFramework.Ecs.Arch.components; -namespace GFramework.Core.ecs.systems; +namespace GFramework.Ecs.Arch.systems; /// /// 移动系统 - 继承 ArchSystemAdapter diff --git a/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj b/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj index 3a8c827..4941dcf 100644 --- a/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj +++ b/GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj @@ -1,9 +1,11 @@  + net8.0;net10.0 disable enable - net10.0;net8.0 + false + true diff --git a/GFramework.csproj b/GFramework.csproj index 09f1d65..ae91b88 100644 --- a/GFramework.csproj +++ b/GFramework.csproj @@ -47,6 +47,9 @@ + + + @@ -82,6 +85,9 @@ + + + @@ -103,6 +109,9 @@ + + + diff --git a/GFramework.sln b/GFramework.sln index 3eba55b..04b1d8f 100644 --- a/GFramework.sln +++ b/GFramework.sln @@ -1,6 +1,6 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework ", "GFramework.csproj", "{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework", "GFramework.csproj", "{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.SourceGenerators", "GFramework.SourceGenerators\GFramework.SourceGenerators.csproj", "{E9D51809-0351-4B83-B85B-B5F469AAB3B8}" EndProject @@ -26,63 +26,216 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Game.Abstraction EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Core.Tests", "GFramework.Core.Tests\GFramework.Core.Tests.csproj", "{759BCD95-A9D9-4D8F-9255-A9F1B661DF74}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Ecs.Arch.Abstractions", "GFramework.Ecs.Arch.Abstractions\GFramework.Ecs.Arch.Abstractions.csproj", "{5E1488B2-6554-408D-83FB-EB2FFFABF545}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Ecs.Arch", "GFramework.Ecs.Arch\GFramework.Ecs.Arch.csproj", "{E9B49EE9-25BC-47C1-83CE-92F636B9D491}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Ecs.Arch.Tests", "GFramework.Ecs.Arch.Tests\GFramework.Ecs.Arch.Tests.csproj", "{112CF413-4596-4AA3-B3FE-65532802FDD6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Debug|x64.ActiveCfg = Debug|Any CPU + {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Debug|x64.Build.0 = Debug|Any CPU + {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Debug|x86.ActiveCfg = Debug|Any CPU + {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Debug|x86.Build.0 = Debug|Any CPU {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|Any CPU.ActiveCfg = Release|Any CPU {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|Any CPU.Build.0 = Release|Any CPU + {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|x64.ActiveCfg = Release|Any CPU + {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|x64.Build.0 = Release|Any CPU + {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|x86.ActiveCfg = Release|Any CPU + {9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|x86.Build.0 = Release|Any CPU {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|x64.ActiveCfg = Debug|Any CPU + {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|x64.Build.0 = Debug|Any CPU + {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|x86.ActiveCfg = Debug|Any CPU + {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|x86.Build.0 = Debug|Any CPU {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|Any CPU.ActiveCfg = Release|Any CPU {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|Any CPU.Build.0 = Release|Any CPU + {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|x64.ActiveCfg = Release|Any CPU + {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|x64.Build.0 = Release|Any CPU + {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|x86.ActiveCfg = Release|Any CPU + {E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|x86.Build.0 = Release|Any CPU {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|x64.ActiveCfg = Debug|Any CPU + {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|x64.Build.0 = Debug|Any CPU + {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|x86.ActiveCfg = Debug|Any CPU + {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|x86.Build.0 = Debug|Any CPU {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|Any CPU.ActiveCfg = Release|Any CPU {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|Any CPU.Build.0 = Release|Any CPU + {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|x64.ActiveCfg = Release|Any CPU + {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|x64.Build.0 = Release|Any CPU + {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|x86.ActiveCfg = Release|Any CPU + {84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|x86.Build.0 = Release|Any CPU {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Debug|x64.ActiveCfg = Debug|Any CPU + {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Debug|x64.Build.0 = Debug|Any CPU + {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Debug|x86.ActiveCfg = Debug|Any CPU + {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Debug|x86.Build.0 = Debug|Any CPU {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Release|Any CPU.ActiveCfg = Release|Any CPU {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Release|Any CPU.Build.0 = Release|Any CPU + {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Release|x64.ActiveCfg = Release|Any CPU + {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Release|x64.Build.0 = Release|Any CPU + {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Release|x86.ActiveCfg = Release|Any CPU + {A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Release|x86.Build.0 = Release|Any CPU {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Debug|x64.ActiveCfg = Debug|Any CPU + {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Debug|x64.Build.0 = Debug|Any CPU + {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Debug|x86.ActiveCfg = Debug|Any CPU + {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Debug|x86.Build.0 = Debug|Any CPU {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Release|Any CPU.ActiveCfg = Release|Any CPU {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Release|Any CPU.Build.0 = Release|Any CPU + {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Release|x64.ActiveCfg = Release|Any CPU + {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Release|x64.Build.0 = Release|Any CPU + {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Release|x86.ActiveCfg = Release|Any CPU + {FC56D81A-3A3B-4B49-B318-363DFA0D8206}.Release|x86.Build.0 = Release|Any CPU {0B00816B-E8B2-4562-8C11-0C06CE761638}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0B00816B-E8B2-4562-8C11-0C06CE761638}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B00816B-E8B2-4562-8C11-0C06CE761638}.Debug|x64.ActiveCfg = Debug|Any CPU + {0B00816B-E8B2-4562-8C11-0C06CE761638}.Debug|x64.Build.0 = Debug|Any CPU + {0B00816B-E8B2-4562-8C11-0C06CE761638}.Debug|x86.ActiveCfg = Debug|Any CPU + {0B00816B-E8B2-4562-8C11-0C06CE761638}.Debug|x86.Build.0 = Debug|Any CPU {0B00816B-E8B2-4562-8C11-0C06CE761638}.Release|Any CPU.ActiveCfg = Release|Any CPU {0B00816B-E8B2-4562-8C11-0C06CE761638}.Release|Any CPU.Build.0 = Release|Any CPU + {0B00816B-E8B2-4562-8C11-0C06CE761638}.Release|x64.ActiveCfg = Release|Any CPU + {0B00816B-E8B2-4562-8C11-0C06CE761638}.Release|x64.Build.0 = Release|Any CPU + {0B00816B-E8B2-4562-8C11-0C06CE761638}.Release|x86.ActiveCfg = Release|Any CPU + {0B00816B-E8B2-4562-8C11-0C06CE761638}.Release|x86.Build.0 = Release|Any CPU {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Debug|x64.ActiveCfg = Debug|Any CPU + {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Debug|x64.Build.0 = Debug|Any CPU + {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Debug|x86.ActiveCfg = Debug|Any CPU + {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Debug|x86.Build.0 = Debug|Any CPU {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Release|Any CPU.Build.0 = Release|Any CPU + {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Release|x64.ActiveCfg = Release|Any CPU + {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Release|x64.Build.0 = Release|Any CPU + {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Release|x86.ActiveCfg = Release|Any CPU + {C56FD287-CBC6-4C44-B3DF-103FA3660CA0}.Release|x86.Build.0 = Release|Any CPU {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Debug|x64.ActiveCfg = Debug|Any CPU + {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Debug|x64.Build.0 = Debug|Any CPU + {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Debug|x86.ActiveCfg = Debug|Any CPU + {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Debug|x86.Build.0 = Debug|Any CPU {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Release|Any CPU.ActiveCfg = Release|Any CPU {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Release|Any CPU.Build.0 = Release|Any CPU + {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Release|x64.ActiveCfg = Release|Any CPU + {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Release|x64.Build.0 = Release|Any CPU + {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Release|x86.ActiveCfg = Release|Any CPU + {3A1132B7-EC3B-4BB6-A752-8ADC92BC08A0}.Release|x86.Build.0 = Release|Any CPU {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Debug|x64.ActiveCfg = Debug|Any CPU + {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Debug|x64.Build.0 = Debug|Any CPU + {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Debug|x86.ActiveCfg = Debug|Any CPU + {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Debug|x86.Build.0 = Debug|Any CPU {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Release|Any CPU.ActiveCfg = Release|Any CPU {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Release|Any CPU.Build.0 = Release|Any CPU + {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Release|x64.ActiveCfg = Release|Any CPU + {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Release|x64.Build.0 = Release|Any CPU + {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Release|x86.ActiveCfg = Release|Any CPU + {BB047F43-6AA0-4EA0-8AE9-E6B9784D9E8E}.Release|x86.Build.0 = Release|Any CPU {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Debug|x64.ActiveCfg = Debug|Any CPU + {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Debug|x64.Build.0 = Debug|Any CPU + {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Debug|x86.ActiveCfg = Debug|Any CPU + {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Debug|x86.Build.0 = Debug|Any CPU {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Release|Any CPU.ActiveCfg = Release|Any CPU {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Release|Any CPU.Build.0 = Release|Any CPU + {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Release|x64.ActiveCfg = Release|Any CPU + {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Release|x64.Build.0 = Release|Any CPU + {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Release|x86.ActiveCfg = Release|Any CPU + {B6511C9A-40E1-4E51-8D1F-18EAFB3C5BFC}.Release|x86.Build.0 = Release|Any CPU {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Debug|x64.ActiveCfg = Debug|Any CPU + {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Debug|x64.Build.0 = Debug|Any CPU + {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Debug|x86.ActiveCfg = Debug|Any CPU + {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Debug|x86.Build.0 = Debug|Any CPU {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Release|Any CPU.ActiveCfg = Release|Any CPU {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Release|Any CPU.Build.0 = Release|Any CPU + {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Release|x64.ActiveCfg = Release|Any CPU + {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Release|x64.Build.0 = Release|Any CPU + {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Release|x86.ActiveCfg = Release|Any CPU + {31BA9F62-153A-4943-A8A0-7571FC7D5FEE}.Release|x86.Build.0 = Release|Any CPU {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Debug|x64.ActiveCfg = Debug|Any CPU + {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Debug|x64.Build.0 = Debug|Any CPU + {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Debug|x86.ActiveCfg = Debug|Any CPU + {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Debug|x86.Build.0 = Debug|Any CPU {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Release|Any CPU.ActiveCfg = Release|Any CPU {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Release|Any CPU.Build.0 = Release|Any CPU + {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Release|x64.ActiveCfg = Release|Any CPU + {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Release|x64.Build.0 = Release|Any CPU + {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Release|x86.ActiveCfg = Release|Any CPU + {E20DBA4C-CEB9-4184-B614-5A99A9AE4472}.Release|x86.Build.0 = Release|Any CPU {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Debug|Any CPU.Build.0 = Debug|Any CPU + {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Debug|x64.ActiveCfg = Debug|Any CPU + {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Debug|x64.Build.0 = Debug|Any CPU + {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Debug|x86.ActiveCfg = Debug|Any CPU + {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Debug|x86.Build.0 = Debug|Any CPU {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Release|Any CPU.ActiveCfg = Release|Any CPU {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Release|Any CPU.Build.0 = Release|Any CPU + {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Release|x64.ActiveCfg = Release|Any CPU + {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Release|x64.Build.0 = Release|Any CPU + {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Release|x86.ActiveCfg = Release|Any CPU + {759BCD95-A9D9-4D8F-9255-A9F1B661DF74}.Release|x86.Build.0 = Release|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Debug|x64.ActiveCfg = Debug|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Debug|x64.Build.0 = Debug|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Debug|x86.ActiveCfg = Debug|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Debug|x86.Build.0 = Debug|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Release|Any CPU.Build.0 = Release|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Release|x64.ActiveCfg = Release|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Release|x64.Build.0 = Release|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Release|x86.ActiveCfg = Release|Any CPU + {5E1488B2-6554-408D-83FB-EB2FFFABF545}.Release|x86.Build.0 = Release|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Debug|x64.ActiveCfg = Debug|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Debug|x64.Build.0 = Debug|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Debug|x86.ActiveCfg = Debug|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Debug|x86.Build.0 = Debug|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Release|Any CPU.Build.0 = Release|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Release|x64.ActiveCfg = Release|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Release|x64.Build.0 = Release|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Release|x86.ActiveCfg = Release|Any CPU + {E9B49EE9-25BC-47C1-83CE-92F636B9D491}.Release|x86.Build.0 = Release|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Debug|x64.ActiveCfg = Debug|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Debug|x64.Build.0 = Debug|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Debug|x86.ActiveCfg = Debug|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Debug|x86.Build.0 = Debug|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Release|Any CPU.Build.0 = Release|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Release|x64.ActiveCfg = Release|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Release|x64.Build.0 = Release|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Release|x86.ActiveCfg = Release|Any CPU + {112CF413-4596-4AA3-B3FE-65532802FDD6}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection EndGlobal From bca92e52a3107e2eeca6b7b795ffeba7b9231638 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Sun, 8 Mar 2026 20:19:24 +0800 Subject: [PATCH 2/7] =?UTF-8?q?refactor(ecs):=20=E9=87=8D=E6=9E=84=20Arch?= =?UTF-8?q?=20ECS=20=E6=A8=A1=E5=9D=97=E6=B3=A8=E5=86=8C=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除自动初始化器,改用显式注册方式 - 修改 UseArch 扩展方法支持可选配置参数 - 更新文档说明新的集成方式和配置选项 - 删除自动注册相关的测试代码 - 调整 README 中的使用示例和架构说明 --- .../integration/AutoRegistrationTests.cs | 75 --------------- GFramework.Ecs.Arch/ArchModuleInitializer.cs | 21 ----- GFramework.Ecs.Arch/README.md | 94 ++++++++++++------- .../extensions/ArchExtensions.cs | 33 +++---- 4 files changed, 73 insertions(+), 150 deletions(-) delete mode 100644 GFramework.Ecs.Arch.Tests/integration/AutoRegistrationTests.cs delete mode 100644 GFramework.Ecs.Arch/ArchModuleInitializer.cs diff --git a/GFramework.Ecs.Arch.Tests/integration/AutoRegistrationTests.cs b/GFramework.Ecs.Arch.Tests/integration/AutoRegistrationTests.cs deleted file mode 100644 index b0a6d5f..0000000 --- a/GFramework.Ecs.Arch.Tests/integration/AutoRegistrationTests.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System.Linq; -using Arch.Core; -using GFramework.Core.Abstractions.properties; -using GFramework.Core.architecture; -using GFramework.Core.ioc; -using GFramework.Ecs.Arch.Abstractions; - -namespace GFramework.Ecs.Arch.Tests.integration; - -/// -/// 自动注册集成测试 -/// -[TestFixture] -public class AutoRegistrationTests -{ - [SetUp] - public void Setup() - { - _container = new MicrosoftDiContainer(); - _context = new ArchitectureContext(_container); - } - - [TearDown] - public void TearDown() - { - _container?.Clear(); - _context = null; - } - - private MicrosoftDiContainer? _container; - private ArchitectureContext? _context; - - /// - /// 测试 Arch ECS 模块是否自动注册 - /// - [Test] - public void ArchEcsModule_Should_Be_Auto_Registered() - { - // Arrange - 手动触发模块初始化器(模拟自动注册) - ArchModuleInitializer.Initialize(); - - var services = new ArchitectureServices(); - var properties = new ArchitectureProperties(); - - // Act - services.ModuleManager.RegisterBuiltInModules(services.Container, properties); - var modules = services.ModuleManager.GetModules(); - - // Assert - var archModule = modules.FirstOrDefault(m => m.ModuleName == nameof(ArchEcsModule)); - Assert.That(archModule, Is.Not.Null, "ArchEcsModule should be auto-registered"); - Assert.That(archModule, Is.InstanceOf()); - } - - /// - /// 测试 World 是否正确注册到容器 - /// - [Test] - public void World_Should_Be_Registered_In_Container() - { - // Arrange - 手动触发模块初始化器 - ArchModuleInitializer.Initialize(); - - var services = new ArchitectureServices(); - var properties = new ArchitectureProperties(); - - // Act - services.ModuleManager.RegisterBuiltInModules(services.Container, properties); - services.ModuleManager.InitializeAllAsync(false).Wait(); - - // Assert - var world = services.Container.Get(); - Assert.That(world, Is.Not.Null, "World should be registered in container"); - } -} \ No newline at end of file diff --git a/GFramework.Ecs.Arch/ArchModuleInitializer.cs b/GFramework.Ecs.Arch/ArchModuleInitializer.cs deleted file mode 100644 index 5e8a7f4..0000000 --- a/GFramework.Ecs.Arch/ArchModuleInitializer.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Runtime.CompilerServices; -using GFramework.Core.Abstractions.architecture; - -namespace GFramework.Ecs.Arch; - -/// -/// Arch ECS 模块自动初始化器 -/// 使用 ModuleInitializer 特性在程序启动时自动注册模块 -/// -public static class ArchModuleInitializer -{ - /// - /// 模块初始化方法,在程序启动时自动调用 - /// - [ModuleInitializer] - public static void Initialize() - { - // 注册 Arch ECS 模块工厂 - ArchitectureModuleRegistry.Register(() => new ArchEcsModule(enabled: true)); - } -} \ No newline at end of file diff --git a/GFramework.Ecs.Arch/README.md b/GFramework.Ecs.Arch/README.md index 1525346..96501af 100644 --- a/GFramework.Ecs.Arch/README.md +++ b/GFramework.Ecs.Arch/README.md @@ -4,7 +4,7 @@ GFramework 的 Arch ECS 集成包,提供开箱即用的 ECS(Entity Component ## 特性 -- 🚀 **自动集成** - 引入 NuGet 包即可自动启用,无需手动配置 +- 🎯 **显式集成** - 符合 .NET 生态习惯的显式注册方式 - 🔌 **零依赖** - 不使用时,Core 包无 Arch 依赖 - 🎯 **类型安全** - 完整的类型系统和编译时检查 - ⚡ **高性能** - 基于 Arch ECS 的高性能实现 @@ -18,7 +18,29 @@ GFramework 的 Arch ECS 集成包,提供开箱即用的 ECS(Entity Component dotnet add package GeWuYou.GFramework.Ecs.Arch ``` -### 2. 创建组件 +### 2. 注册 ECS 模块 + +```csharp +// 在架构初始化时添加 Arch ECS 支持 +var architecture = new GameArchitecture(config) + .UseArch(); // 添加 ECS 支持 + +architecture.Initialize(); +``` + +### 3. 带配置的注册 + +```csharp +var architecture = new GameArchitecture(config) + .UseArch(options => + { + options.WorldCapacity = 2000; + options.EnableStatistics = true; + options.Priority = 50; + }); + +architecture.Initialize(); +``` ```csharp using System.Runtime.InteropServices; @@ -38,7 +60,7 @@ public struct Velocity(float x, float y) } ``` -### 3. 创建系统 +### 5. 创建系统 ```csharp using Arch.Core; @@ -65,7 +87,7 @@ public sealed class MovementSystem : ArchSystemAdapter } ``` -### 4. 注册系统 +### 6. 注册系统 ```csharp public class MyArchitecture : Architecture @@ -77,7 +99,7 @@ public class MyArchitecture : Architecture } ``` -### 5. 创建实体 +### 7. 创建实体 ```csharp var world = this.GetService(); @@ -87,7 +109,7 @@ var entity = world.Create( ); ``` -### 6. 更新系统 +### 8. 更新系统 ```csharp var ecsModule = this.GetService(); @@ -96,50 +118,50 @@ ecsModule.Update(deltaTime); ## 配置选项 -可以通过配置文件或代码配置 Arch ECS: - ### 代码配置 ```csharp -services.ConfigureArch(options => -{ - options.WorldCapacity = 2000; - options.EnableStatistics = true; - options.Priority = 50; -}); +var architecture = new GameArchitecture(config) + .UseArch(options => + { + options.WorldCapacity = 2000; + options.EnableStatistics = true; + options.Priority = 50; + }); ``` -### 配置文件 +### 配置说明 -```json -{ - "GFramework": { - "Modules": { - "Arch": { - "Enabled": true, - "Priority": 50, - "WorldCapacity": 1000, - "EnableStatistics": false - } - } - } -} -``` +- `WorldCapacity` - World 初始容量(默认:1000) +- `EnableStatistics` - 是否启用统计信息(默认:false) +- `Priority` - 模块优先级(默认:50) ## 架构说明 -### 模块自动注册 +### 显式注册模式 -本包使用 `ModuleInitializer` 特性实现自动注册,无需手动配置: +本包采用 .NET 生态标准的显式注册模式,基于架构实例: +**优点:** + +- ✅ 符合 .NET 生态习惯 +- ✅ 显式、可控 +- ✅ 易于测试和调试 +- ✅ 支持配置 +- ✅ 支持链式调用 +- ✅ 避免"魔法"行为 + +**使用方式:** ```csharp -[ModuleInitializer] -public static void Initialize() -{ - ArchitectureModuleRegistry.Register(() => new ArchEcsModule(enabled: true)); -} +// 在架构初始化时添加 +var architecture = new GameArchitecture(config) + .UseArch(); // 显式注册 + +architecture.Initialize(); ``` +详见:[INTEGRATION_PATTERN.md](INTEGRATION_PATTERN.md) + ### 系统适配器 `ArchSystemAdapter` 桥接 Arch.System.ISystem 到 GFramework 架构: diff --git a/GFramework.Ecs.Arch/extensions/ArchExtensions.cs b/GFramework.Ecs.Arch/extensions/ArchExtensions.cs index 7dcd08d..811f546 100644 --- a/GFramework.Ecs.Arch/extensions/ArchExtensions.cs +++ b/GFramework.Ecs.Arch/extensions/ArchExtensions.cs @@ -1,5 +1,4 @@ using GFramework.Core.Abstractions.architecture; -using Microsoft.Extensions.DependencyInjection; namespace GFramework.Ecs.Arch.extensions; @@ -9,26 +8,24 @@ namespace GFramework.Ecs.Arch.extensions; public static class ArchExtensions { /// - /// 配置 Arch ECS 选项 + /// 添加 Arch ECS 支持到架构中 /// - public static IServiceCollection ConfigureArch( - this IServiceCollection services, - Action configure) - { - var options = new ArchOptions(); - configure(options); - services.AddSingleton(options); - return services; - } - - /// - /// 显式启用 Arch ECS 模块(备选方案) - /// - public static TArchitecture UseArch(this TArchitecture architecture) + /// 架构类型 + /// 架构实例 + /// 可选的配置委托 + /// 架构实例,支持链式调用 + public static TArchitecture UseArch( + this TArchitecture architecture, + Action? configure = null) where TArchitecture : IArchitecture { - // 此方法为显式注册提供支持 - // 实际注册由 ModuleInitializer 自动完成 + // 配置选项 + var options = new ArchOptions(); + configure?.Invoke(options); + + // 注册模块 + ArchitectureModuleRegistry.Register(() => new ArchEcsModule(enabled: true)); + return architecture; } } \ No newline at end of file From 4257d58f863f1833d015c73000a94626638e6ff8 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Sun, 8 Mar 2026 20:33:00 +0800 Subject: [PATCH 3/7] =?UTF-8?q?feat(GFramework.Ecs.Arch):=20=E6=B3=A8?= =?UTF-8?q?=E5=86=8C=E6=A8=A1=E5=9D=97=E8=87=AA=E8=BA=AB=E5=88=B0=E5=AE=B9?= =?UTF-8?q?=E5=99=A8=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 ArchEcsModule 初始化时注册模块自身到容器 - 移除不必要的空行以保持代码整洁 --- GFramework.Ecs.Arch/ArchEcsModule.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/GFramework.Ecs.Arch/ArchEcsModule.cs b/GFramework.Ecs.Arch/ArchEcsModule.cs index cad7205..7c93b4c 100644 --- a/GFramework.Ecs.Arch/ArchEcsModule.cs +++ b/GFramework.Ecs.Arch/ArchEcsModule.cs @@ -46,6 +46,9 @@ public sealed class ArchEcsModule : IArchEcsModule _container = container; + // 注册模块自身 + container.RegisterPlurality(this); + // 创建并注册 World _world = World.Create(); container.Register(_world); From ac2a9759e18a256a36243644497988665a9bdb66 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Sun, 8 Mar 2026 20:38:12 +0800 Subject: [PATCH 4/7] =?UTF-8?q?refactor(ArchEcsModule):=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E7=B3=BB=E7=BB=9F=E9=80=82=E9=85=8D=E5=99=A8=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 _systems 字段类型从 List 改为 IReadOnlyList - 使用 GetAllByPriority 方法按优先级获取适配器 - 移除手动 AddRange 操作,直接赋值适配器列表 - 销毁时将 _systems 设置为空数组而非 Clear() 操作 --- GFramework.Ecs.Arch/ArchEcsModule.cs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/GFramework.Ecs.Arch/ArchEcsModule.cs b/GFramework.Ecs.Arch/ArchEcsModule.cs index 7c93b4c..bbbdb94 100644 --- a/GFramework.Ecs.Arch/ArchEcsModule.cs +++ b/GFramework.Ecs.Arch/ArchEcsModule.cs @@ -8,9 +8,9 @@ namespace GFramework.Ecs.Arch; /// public sealed class ArchEcsModule : IArchEcsModule { - private readonly List> _systems = []; private IIocContainer? _container; private bool _isInitialized; + private IReadOnlyList> _systems = []; private World? _world; /// @@ -67,17 +67,13 @@ public sealed class ArchEcsModule : IArchEcsModule return; } - // 从容器获取所有适配器 - var adapters = _container.GetAll>(); - if (adapters.Count > 0) - { - _systems.AddRange(adapters); + // 从容器按优先级获取所有适配器 + _systems = _container.GetAllByPriority>(); - // 初始化所有系统(会调用 Arch 系统的 Initialize) - foreach (var system in _systems) - { - system.Initialize(); - } + // 初始化所有系统(会调用 Arch 系统的 Initialize) + foreach (var system in _systems) + { + system.Initialize(); } _isInitialized = true; @@ -99,7 +95,7 @@ public sealed class ArchEcsModule : IArchEcsModule system.Destroy(); } - _systems.Clear(); + _systems = []; // 销毁 World if (_world != null) From 5c5525e3e9d8c3cc483d236b4847947cbf15d0d4 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Sun, 8 Mar 2026 20:47:26 +0800 Subject: [PATCH 5/7] =?UTF-8?q?refactor(architecture):=20=E7=AE=80?= =?UTF-8?q?=E5=8C=96=E6=A8=A1=E5=9D=97=E6=B3=A8=E5=86=8C=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=B9=B6=E5=A2=9E=E5=BC=BA=E9=85=8D=E7=BD=AE=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除 RegisterBuiltInModules 方法中的 ArchitectureProperties 参数 - 更新 ArchitectureModuleRegistry 使用 ConcurrentDictionary 存储模块工厂 - 实现模块注册的幂等性检查,相同模块名只注册一次 - 为 ArchEcsModule 添加 ArchOptions 配置类支持 - 更新 UseArch 扩展方法传递配置选项给 ArchEcsModule - 移除废弃的 properties 命名空间引用 - 添加显式注册集成测试验证模块配置功能 --- .../ArchitectureModuleRegistry.cs | 15 +- .../architecture/IServiceModuleManager.cs | 4 +- .../architecture/ArchitectureServicesTests.cs | 22 +-- GFramework.Core/architecture/Architecture.cs | 4 +- .../services/ServiceModuleManager.cs | 8 +- .../integration/ExplicitRegistrationTests.cs | 135 ++++++++++++++++++ GFramework.Ecs.Arch/ArchEcsModule.cs | 11 +- GFramework.Ecs.Arch/ArchOptions.cs | 22 +++ .../extensions/ArchExtensions.cs | 4 +- 9 files changed, 190 insertions(+), 35 deletions(-) create mode 100644 GFramework.Ecs.Arch.Tests/integration/ExplicitRegistrationTests.cs create mode 100644 GFramework.Ecs.Arch/ArchOptions.cs diff --git a/GFramework.Core.Abstractions/architecture/ArchitectureModuleRegistry.cs b/GFramework.Core.Abstractions/architecture/ArchitectureModuleRegistry.cs index 1259545..ab5a7c3 100644 --- a/GFramework.Core.Abstractions/architecture/ArchitectureModuleRegistry.cs +++ b/GFramework.Core.Abstractions/architecture/ArchitectureModuleRegistry.cs @@ -1,3 +1,5 @@ +using System.Collections.Concurrent; + namespace GFramework.Core.Abstractions.architecture; /// @@ -5,15 +7,20 @@ namespace GFramework.Core.Abstractions.architecture; /// public static class ArchitectureModuleRegistry { - private static readonly List> _factories = []; + private static readonly ConcurrentDictionary> _factories = new(); /// - /// 注册模块工厂 + /// 注册模块工厂(幂等操作,相同模块名只会注册一次) /// /// 模块工厂函数 public static void Register(Func factory) { - _factories.Add(factory); + // 创建临时实例以获取模块名(用于幂等性检查) + var tempModule = factory(); + var moduleName = tempModule.ModuleName; + + // 幂等注册:相同模块名只注册一次 + _factories.TryAdd(moduleName, factory); } /// @@ -22,7 +29,7 @@ public static class ArchitectureModuleRegistry /// 模块实例集合 public static IEnumerable CreateModules() { - return _factories.Select(f => f()); + return _factories.Values.Select(f => f()); } /// diff --git a/GFramework.Core.Abstractions/architecture/IServiceModuleManager.cs b/GFramework.Core.Abstractions/architecture/IServiceModuleManager.cs index 20b7d52..8c7face 100644 --- a/GFramework.Core.Abstractions/architecture/IServiceModuleManager.cs +++ b/GFramework.Core.Abstractions/architecture/IServiceModuleManager.cs @@ -1,5 +1,4 @@ using GFramework.Core.Abstractions.ioc; -using GFramework.Core.Abstractions.properties; namespace GFramework.Core.Abstractions.architecture; @@ -18,8 +17,7 @@ public interface IServiceModuleManager /// 注册内置的服务模块。 /// /// IoC容器实例,用于解析依赖。 - /// 架构属性配置,用于模块初始化。 - void RegisterBuiltInModules(IIocContainer container, ArchitectureProperties properties); + void RegisterBuiltInModules(IIocContainer container); /// /// 获取所有已注册的服务模块。 diff --git a/GFramework.Core.Tests/architecture/ArchitectureServicesTests.cs b/GFramework.Core.Tests/architecture/ArchitectureServicesTests.cs index 936204b..5621b33 100644 --- a/GFramework.Core.Tests/architecture/ArchitectureServicesTests.cs +++ b/GFramework.Core.Tests/architecture/ArchitectureServicesTests.cs @@ -4,7 +4,6 @@ using GFramework.Core.Abstractions.environment; 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; @@ -47,8 +46,7 @@ public class ArchitectureServicesTests private void RegisterBuiltInServices() { - var properties = new ArchitectureProperties(); - _services!.ModuleManager.RegisterBuiltInModules(_services.Container, properties); + _services!.ModuleManager.RegisterBuiltInModules(_services.Container); } /// @@ -215,13 +213,11 @@ public class ArchitectureServicesTests [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)); } @@ -232,13 +228,11 @@ public class ArchitectureServicesTests [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)); } @@ -249,13 +243,11 @@ public class ArchitectureServicesTests [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)); } diff --git a/GFramework.Core/architecture/Architecture.cs b/GFramework.Core/architecture/Architecture.cs index 717c17e..0ebdff6 100644 --- a/GFramework.Core/architecture/Architecture.cs +++ b/GFramework.Core/architecture/Architecture.cs @@ -641,8 +641,8 @@ public abstract class Architecture( _logger = LoggerFactoryResolver.Provider.CreateLogger(GetType().Name); Environment.Initialize(); - // 注册内置服务模块(根据配置) - Services.ModuleManager.RegisterBuiltInModules(Container, Configuration.ArchitectureProperties); + // 注册内置服务模块 + Services.ModuleManager.RegisterBuiltInModules(Container); // 将 Environment 注册到容器(如果尚未注册) if (!Container.Contains()) diff --git a/GFramework.Core/services/ServiceModuleManager.cs b/GFramework.Core/services/ServiceModuleManager.cs index 68bd2c0..9ed0aac 100644 --- a/GFramework.Core/services/ServiceModuleManager.cs +++ b/GFramework.Core/services/ServiceModuleManager.cs @@ -2,7 +2,6 @@ using GFramework.Core.Abstractions.architecture; using GFramework.Core.Abstractions.ioc; using GFramework.Core.Abstractions.lifecycle; using GFramework.Core.Abstractions.logging; -using GFramework.Core.Abstractions.properties; using GFramework.Core.logging; using GFramework.Core.services.modules; @@ -44,11 +43,10 @@ public sealed class ServiceModuleManager : IServiceModuleManager /// /// 注册内置服务模块,并根据优先级排序后完成服务注册。 /// 内置模块包括事件总线、命令执行器、查询执行器等核心模块。 - /// 同时注册通过 ModuleInitializer 自动注册的外部模块。 + /// 同时注册通过 ArchitectureModuleRegistry 自动注册的外部模块。 /// /// IoC容器实例,用于模块服务注册。 - /// 架构属性配置。 - public void RegisterBuiltInModules(IIocContainer container, ArchitectureProperties properties) + public void RegisterBuiltInModules(IIocContainer container) { if (_builtInModulesRegistered) { @@ -62,7 +60,7 @@ public sealed class ServiceModuleManager : IServiceModuleManager RegisterModule(new QueryExecutorModule()); RegisterModule(new AsyncQueryExecutorModule()); - // 注册外部模块(通过 ModuleInitializer 自动注册) + // 注册外部模块(通过 ArchitectureModuleRegistry 自动注册) foreach (var module in ArchitectureModuleRegistry.CreateModules()) { RegisterModule(module); diff --git a/GFramework.Ecs.Arch.Tests/integration/ExplicitRegistrationTests.cs b/GFramework.Ecs.Arch.Tests/integration/ExplicitRegistrationTests.cs new file mode 100644 index 0000000..515e5c9 --- /dev/null +++ b/GFramework.Ecs.Arch.Tests/integration/ExplicitRegistrationTests.cs @@ -0,0 +1,135 @@ +using Arch.Core; +using GFramework.Core.Abstractions.architecture; +using GFramework.Core.architecture; +using GFramework.Core.ioc; +using GFramework.Ecs.Arch.Abstractions; +using GFramework.Ecs.Arch.extensions; + +namespace GFramework.Ecs.Arch.Tests.integration; + +/// +/// 显式注册集成测试 +/// +[TestFixture] +public class ExplicitRegistrationTests +{ + [SetUp] + public void Setup() + { + _container = new MicrosoftDiContainer(); + _context = new ArchitectureContext(_container); + + // 清空注册表,确保测试隔离 + ArchitectureModuleRegistry.Clear(); + } + + [TearDown] + public void TearDown() + { + _container?.Clear(); + _context = null; + ArchitectureModuleRegistry.Clear(); + GameContext.Clear(); + } + + private MicrosoftDiContainer? _container; + private ArchitectureContext? _context; + + /// + /// 测试 Arch ECS 模块显式注册 + /// + [Test] + public void ArchEcsModule_Should_Be_Explicitly_Registered() + { + // Arrange + var architecture = new TestArchitecture(); + + // Act - 显式注册 + architecture.UseArch(); + architecture.Initialize(); + + // Assert - 验证 World 已注册(由 ArchEcsModule 注册) + var world = architecture.Context.GetService(); + Assert.That(world, Is.Not.Null, "World should be registered by ArchEcsModule"); + + // 验证 IArchEcsModule 已注册 + var ecsModule = architecture.Context.GetService(); + Assert.That(ecsModule, Is.Not.Null, "IArchEcsModule should be registered"); + } + + /// + /// 测试 World 是否正确注册到容器 + /// + [Test] + public void World_Should_Be_Registered_In_Container() + { + // Arrange + var architecture = new TestArchitecture(); + + // Act - 显式注册 + architecture.UseArch(); + architecture.Initialize(); + + // Assert + var world = architecture.Context.GetService(); + Assert.That(world, Is.Not.Null, "World should be registered in container"); + } + + /// + /// 测试带配置的注册 + /// + [Test] + public void UseArch_Should_Accept_Configuration() + { + // Arrange + var architecture = new TestArchitecture(); + var configCalled = false; + + // Act + architecture.UseArch(options => + { + options.WorldCapacity = 2000; + options.EnableStatistics = true; + configCalled = true; + }); + + // Assert + Assert.That(configCalled, Is.True, "Configuration delegate should be called"); + } + + /// + /// 测试链式调用 + /// + [Test] + public void UseArch_Should_Support_Chaining() + { + // Arrange & Act + var architecture = new TestArchitecture() + .UseArch() + .UseArch(options => options.WorldCapacity = 2000); + + architecture.Initialize(); + + // Assert - 验证模块已注册 + var world = architecture.Context.GetService(); + Assert.That(world, Is.Not.Null, "World should be registered"); + + var ecsModule = architecture.Context.GetService(); + Assert.That(ecsModule, Is.Not.Null, "IArchEcsModule should be registered"); + } + + /// + /// 测试架构类,用于测试 + /// + private class TestArchitecture : Architecture + { + public TestArchitecture() : base(new ArchitectureConfiguration()) + { + } + + protected override void OnInitialize() + { + // 测试架构,无需额外初始化 + } + } +} \ No newline at end of file diff --git a/GFramework.Ecs.Arch/ArchEcsModule.cs b/GFramework.Ecs.Arch/ArchEcsModule.cs index bbbdb94..31ab8e9 100644 --- a/GFramework.Ecs.Arch/ArchEcsModule.cs +++ b/GFramework.Ecs.Arch/ArchEcsModule.cs @@ -8,6 +8,7 @@ namespace GFramework.Ecs.Arch; /// public sealed class ArchEcsModule : IArchEcsModule { + private readonly ArchOptions _options; private IIocContainer? _container; private bool _isInitialized; private IReadOnlyList> _systems = []; @@ -16,9 +17,11 @@ public sealed class ArchEcsModule : IArchEcsModule /// /// 构造函数 /// + /// 配置选项 /// 是否启用模块 - public ArchEcsModule(bool enabled = true) + public ArchEcsModule(ArchOptions? options = null, bool enabled = true) { + _options = options ?? new ArchOptions(); IsEnabled = enabled; } @@ -30,7 +33,7 @@ public sealed class ArchEcsModule : IArchEcsModule /// /// 模块优先级 /// - public int Priority => 50; + public int Priority => _options.Priority; /// /// 是否启用 @@ -49,8 +52,8 @@ public sealed class ArchEcsModule : IArchEcsModule // 注册模块自身 container.RegisterPlurality(this); - // 创建并注册 World - _world = World.Create(); + // 创建并注册 World(使用配置的容量) + _world = World.Create(_options.WorldCapacity); container.Register(_world); } diff --git a/GFramework.Ecs.Arch/ArchOptions.cs b/GFramework.Ecs.Arch/ArchOptions.cs new file mode 100644 index 0000000..adc17e4 --- /dev/null +++ b/GFramework.Ecs.Arch/ArchOptions.cs @@ -0,0 +1,22 @@ +namespace GFramework.Ecs.Arch; + +/// +/// Arch ECS 模块配置选项 +/// +public sealed class ArchOptions +{ + /// + /// World 初始容量(默认:1000) + /// + public int WorldCapacity { get; set; } = 1000; + + /// + /// 是否启用统计信息(默认:false) + /// + public bool EnableStatistics { get; set; } = false; + + /// + /// 模块优先级(默认:50) + /// + public int Priority { get; set; } = 50; +} \ No newline at end of file diff --git a/GFramework.Ecs.Arch/extensions/ArchExtensions.cs b/GFramework.Ecs.Arch/extensions/ArchExtensions.cs index 811f546..dda51b1 100644 --- a/GFramework.Ecs.Arch/extensions/ArchExtensions.cs +++ b/GFramework.Ecs.Arch/extensions/ArchExtensions.cs @@ -23,8 +23,8 @@ public static class ArchExtensions var options = new ArchOptions(); configure?.Invoke(options); - // 注册模块 - ArchitectureModuleRegistry.Register(() => new ArchEcsModule(enabled: true)); + // 注册模块(传递配置选项) + ArchitectureModuleRegistry.Register(() => new ArchEcsModule(options, enabled: true)); return architecture; } From f35c9309f3fa28e896a994fb8a7906d713070474 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Sun, 8 Mar 2026 20:55:03 +0800 Subject: [PATCH 6/7] =?UTF-8?q?docs(menu):=20=E9=87=8D=E6=9E=84=20ECS=20?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E5=AF=BC=E8=88=AA=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 ECS 相关菜单项从 Core 部分独立出来 - 新增 ECS 概述页面和 Arch ECS 集成文档 - 删除旧的 Core/ECS 集成文档文件 - 更新侧边栏配置以支持新的 ECS 导航结构 --- docs/.vitepress/config.mts | 12 +- docs/zh-CN/core/ecs.md | 1006 ------------------------------------ docs/zh-CN/ecs/arch.md | 751 +++++++++++++++++++++++++++ docs/zh-CN/ecs/index.md | 297 +++++++++++ 4 files changed, 1059 insertions(+), 1007 deletions(-) delete mode 100644 docs/zh-CN/core/ecs.md create mode 100644 docs/zh-CN/ecs/arch.md create mode 100644 docs/zh-CN/ecs/index.md diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index a6aaef0..cc889fe 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -47,6 +47,7 @@ export default defineConfig({ { text: '首页', link: '/zh-CN/' }, { text: '入门指南', link: '/zh-CN/getting-started' }, { text: 'Core', link: '/zh-CN/core/' }, + { text: 'ECS', link: '/zh-CN/ecs/' }, { text: 'Game', link: '/zh-CN/game/' }, { text: 'Godot', link: '/zh-CN/godot/' }, { text: '源码生成器', link: '/zh-CN/source-generators' }, @@ -91,7 +92,6 @@ export default defineConfig({ { text: '事件系统', link: '/zh-CN/core/events' }, { text: '属性系统', link: '/zh-CN/core/property' }, { text: 'IoC容器', link: '/zh-CN/core/ioc' }, - { text: 'ECS 系统集成', link: '/zh-CN/core/ecs' }, { text: '协程系统', link: '/zh-CN/core/coroutine' }, { text: '状态机', link: '/zh-CN/core/state-machine' }, { text: '暂停系统', link: '/zh-CN/core/pause' }, @@ -110,6 +110,16 @@ export default defineConfig({ } ], + '/zh-CN/ecs/': [ + { + text: 'ECS 系统集成', + items: [ + { text: 'ECS 概述', link: '/zh-CN/ecs/' }, + { text: 'Arch ECS 集成', link: '/zh-CN/ecs/arch' } + ] + } + ], + '/zh-CN/game/': [ { text: 'Game 游戏模块', diff --git a/docs/zh-CN/core/ecs.md b/docs/zh-CN/core/ecs.md deleted file mode 100644 index 527fa6a..0000000 --- a/docs/zh-CN/core/ecs.md +++ /dev/null @@ -1,1006 +0,0 @@ ---- -title: ECS 系统集成 -description: ECS(Entity Component System)系统集成指南,基于 Arch.Core 实现高性能的实体组件系统。 ---- - -# ECS 系统集成 - -## 概述 - -GFramework 集成了 [Arch.Core](https://github.com/genaray/Arch) ECS 框架,提供高性能的实体组件系统(Entity Component -System)架构。通过 ECS 模式,你可以构建数据驱动、高度可扩展的游戏系统。 - -**主要特性**: - -- 基于 Arch.Core 的高性能 ECS 实现 -- 与 GFramework 架构无缝集成 -- 支持组件查询和批量处理 -- 零 GC 分配的组件访问 -- 灵活的系统生命周期管理 -- 支持多线程并行处理(Arch 原生支持) - -**性能特点**: - -- 10,000 个实体更新 < 100ms -- 1,000 个实体创建 < 50ms -- 基于 Archetype 的高效内存布局 -- 支持 SIMD 优化 - -## 核心概念 - -### Entity(实体) - -实体是游戏世界中的基本对象,本质上是一个唯一标识符(ID)。实体本身不包含数据或逻辑,只是组件的容器。 - -```csharp -using Arch.Core; - -// 创建实体 -var entity = world.Create(); - -// 创建带组件的实体 -var entity = world.Create(new Position(0, 0), new Velocity(1, 1)); -``` - -### Component(组件) - -组件是纯数据结构,用于存储实体的状态。组件应该是简单的值类型(struct),不包含逻辑。 - -```csharp -using System.Runtime.InteropServices; - -/// -/// 位置组件 -/// -[StructLayout(LayoutKind.Sequential)] -public struct Position(float x, float y) -{ - public float X { get; set; } = x; - public float Y { get; set; } = y; -} - -/// -/// 速度组件 -/// -[StructLayout(LayoutKind.Sequential)] -public struct Velocity(float x, float y) -{ - public float X { get; set; } = x; - public float Y { get; set; } = y; -} -``` - -**组件设计原则**: - -- 使用 `struct` 而不是 `class` -- 只包含数据,不包含逻辑 -- 使用 `[StructLayout(LayoutKind.Sequential)]` 优化内存布局 -- 保持组件小而专注 - -### System(系统) - -系统包含游戏逻辑,负责处理具有特定组件组合的实体。在 GFramework 中,系统通过继承 `ArchSystemAdapter<T>` 来实现。 - -```csharp -using Arch.Core; -using GFramework.Core.ecs; - -/// -/// 移动系统 - 更新实体位置 -/// -public sealed class MovementSystem : ArchSystemAdapter -{ - private QueryDescription _query; - - protected override void OnArchInitialize() - { - // 创建查询:查找所有同时拥有 Position 和 Velocity 组件的实体 - _query = new QueryDescription() - .WithAll(); - } - - protected override void OnUpdate(in float deltaTime) - { - // 查询并更新所有符合条件的实体 - World.Query(in _query, (ref Position pos, ref Velocity vel) => - { - pos.X += vel.X * deltaTime; - pos.Y += vel.Y * deltaTime; - }); - } -} -``` - -### World(世界) - -World 是 ECS 的核心容器,管理所有实体和组件。GFramework 通过 `ArchEcsModule` 自动创建和管理 World。 - -```csharp -// World 由 ArchEcsModule 自动创建和注册到 IoC 容器 -// 在系统中可以直接访问 -public class MySystem : ArchSystemAdapter -{ - protected override void OnUpdate(in float deltaTime) - { - // 访问 World - var entityCount = World.Size; - } -} -``` - -### Arch.Core 集成 - -GFramework 通过以下组件桥接 Arch.Core 到框架生命周期: - -- **ArchEcsModule**:ECS 模块,管理 World 和系统生命周期 -- **ArchSystemAdapter<T>**:系统适配器,桥接 Arch 系统到 GFramework - -## 基本用法 - -### 1. 定义组件 - -```csharp -using System.Runtime.InteropServices; - -namespace MyGame.Components; - -[StructLayout(LayoutKind.Sequential)] -public struct Health(float current, float max) -{ - public float Current { get; set; } = current; - public float Max { get; set; } = max; -} - -[StructLayout(LayoutKind.Sequential)] -public struct Damage(float value) -{ - public float Value { get; set; } = value; -} - -[StructLayout(LayoutKind.Sequential)] -public struct PlayerTag -{ - // 标签组件,不需要数据 -} -``` - -### 2. 创建系统 - -```csharp -using Arch.Core; -using GFramework.Core.ecs; -using MyGame.Components; - -namespace MyGame.Systems; - -/// -/// 伤害系统 - 处理伤害逻辑 -/// -public sealed class DamageSystem : ArchSystemAdapter -{ - private QueryDescription _query; - - protected override void OnArchInitialize() - { - // 查询所有具有 Health 和 Damage 组件的实体 - _query = new QueryDescription() - .WithAll(); - } - - protected override void OnUpdate(in float deltaTime) - { - // 处理伤害 - World.Query(in _query, (Entity entity, ref Health health, ref Damage damage) => - { - health.Current -= damage.Value * deltaTime; - - // 如果生命值耗尽,移除伤害组件 - if (health.Current <= 0) - { - health.Current = 0; - World.Remove(entity); - } - }); - } -} -``` - -### 3. 注册 ECS 模块 - -```csharp -using GFramework.Core.architecture; -using GFramework.Core.ecs; -using MyGame.Systems; - -public class GameArchitecture : Architecture -{ - protected override void Init() - { - // 注册 ECS 系统 - RegisterSystem(new MovementSystem()); - RegisterSystem(new DamageSystem()); - - // 安装 ECS 模块 - InstallModule(new ArchEcsModule(enabled: true)); - } -} -``` - -### 4. 创建和管理实体 - -```csharp -using Arch.Core; -using GFramework.Core.Abstractions.controller; -using MyGame.Components; -using GFramework.Core.Abstractions.controller; -using GFramework.SourceGenerators.Abstractions.rule; - -[ContextAware] -public partial class GameController : IController -{ - private World _world; - - public void Start() - { - // 获取 World - _world = this.GetService(); - - // 创建玩家实体 - var player = _world.Create( - new Position(0, 0), - new Velocity(0, 0), - new Health(100, 100), - new PlayerTag() - ); - - // 创建敌人实体 - var enemy = _world.Create( - new Position(10, 10), - new Velocity(-1, 0), - new Health(50, 50) - ); - } - - public void ApplyDamage(Entity entity, float damageValue) - { - // 添加伤害组件 - if (_world.Has(entity)) - { - _world.Add(entity, new Damage(damageValue)); - } - } -} -``` - -### 5. 更新 ECS 系统 - -```csharp -// 在游戏主循环中更新 ECS -public class GameLoop -{ - private ArchEcsModule _ecsModule; - - public void Update(float deltaTime) - { - // 更新所有 ECS 系统 - _ecsModule.Update(deltaTime); - } -} -``` - -## 高级用法 - -### 查询实体 - -Arch 提供了强大的查询 API,支持多种过滤条件: - -```csharp -using Arch.Core; - -public class QueryExampleSystem : ArchSystemAdapter -{ - private QueryDescription _query1; - private QueryDescription _query2; - private QueryDescription _query3; - - protected override void OnArchInitialize() - { - // 查询:必须有 Position 和 Velocity - _query1 = new QueryDescription() - .WithAll(); - - // 查询:必须有 Health,但不能有 Damage - _query2 = new QueryDescription() - .WithAll() - .WithNone(); - - // 查询:必须有 Position,可选 Velocity - _query3 = new QueryDescription() - .WithAll() - .WithAny(); - } - - protected override void OnUpdate(in float deltaTime) - { - // 使用查询 1 - World.Query(in _query1, (ref Position pos, ref Velocity vel) => - { - // 处理逻辑 - }); - - // 使用查询 2 - World.Query(in _query2, (Entity entity, ref Health health) => - { - // 处理逻辑 - }); - } -} -``` - -### 系统生命周期钩子 - -`ArchSystemAdapter<T>` 提供了多个生命周期钩子: - -```csharp -public class LifecycleExampleSystem : ArchSystemAdapter -{ - protected override void OnArchInitialize() - { - // Arch 系统初始化 - // 在这里创建查询、初始化资源 - } - - protected override void OnBeforeUpdate(in float deltaTime) - { - // 更新前调用 - // 可用于预处理逻辑 - } - - protected override void OnUpdate(in float deltaTime) - { - // 主更新逻辑 - } - - protected override void OnAfterUpdate(in float deltaTime) - { - // 更新后调用 - // 可用于后处理逻辑 - } - - protected override void OnArchDispose() - { - // 资源清理 - } -} -``` - -### 组件操作 - -```csharp -using Arch.Core; - -public class ComponentOperations -{ - private World _world; - - public void Examples() - { - var entity = _world.Create(); - - // 添加组件 - _world.Add(entity, new Position(0, 0)); - _world.Add(entity, new Velocity(1, 1)); - - // 检查组件 - if (_world.Has(entity)) - { - // 获取组件引用(零 GC 分配) - ref var pos = ref _world.Get(entity); - pos.X += 10; - } - - // 设置组件(替换现有值) - _world.Set(entity, new Position(100, 100)); - - // 移除组件 - _world.Remove(entity); - - // 销毁实体 - _world.Destroy(entity); - } -} -``` - -### 批量操作 - -```csharp -public class BatchOperations -{ - private World _world; - - public void CreateMultipleEntities() - { - // 批量创建实体 - for (int i = 0; i < 1000; i++) - { - _world.Create( - new Position(i, i), - new Velocity(1, 1) - ); - } - } - - public void ClearAllEntities() - { - // 清空所有实体 - _world.Clear(); - } -} -``` - -### 实体查询和迭代 - -```csharp -public class EntityIterationSystem : ArchSystemAdapter -{ - protected override void OnUpdate(in float deltaTime) - { - // 方式 1:使用查询和 Lambda - var query = new QueryDescription().WithAll(); - World.Query(in query, (ref Position pos) => - { - // 处理每个实体 - }); - - // 方式 2:获取实体引用 - World.Query(in query, (Entity entity, ref Position pos) => - { - // 可以访问实体 ID - if (pos.X > 100) - { - World.Destroy(entity); - } - }); - - // 方式 3:多组件查询 - var multiQuery = new QueryDescription() - .WithAll(); - - World.Query(in multiQuery, ( - Entity entity, - ref Position pos, - ref Velocity vel, - ref Health health) => - { - // 处理多个组件 - }); - } -} -``` - -## 性能优化 - -### 1. 使用 struct 组件 - -```csharp -// ✅ 推荐:使用 struct -[StructLayout(LayoutKind.Sequential)] -public struct Position(float x, float y) -{ - public float X { get; set; } = x; - public float Y { get; set; } = y; -} - -// ❌ 不推荐:使用 class -public class Position -{ - public float X { get; set; } - public float Y { get; set; } -} -``` - -### 2. 缓存查询 - -```csharp -public class OptimizedSystem : ArchSystemAdapter -{ - // ✅ 推荐:缓存查询 - private QueryDescription _cachedQuery; - - protected override void OnArchInitialize() - { - _cachedQuery = new QueryDescription() - .WithAll(); - } - - protected override void OnUpdate(in float deltaTime) - { - // 使用缓存的查询 - World.Query(in _cachedQuery, (ref Position pos, ref Velocity vel) => - { - pos.X += vel.X * deltaTime; - pos.Y += vel.Y * deltaTime; - }); - } -} - -// ❌ 不推荐:每次创建新查询 -public class UnoptimizedSystem : ArchSystemAdapter -{ - protected override void OnUpdate(in float deltaTime) - { - // 每帧创建新查询(性能差) - var query = new QueryDescription().WithAll(); - World.Query(in query, (ref Position pos, ref Velocity vel) => - { - // ... - }); - } -} -``` - -### 3. 使用 ref 访问组件 - -```csharp -// ✅ 推荐:使用 ref 避免复制 -World.Query(in query, (ref Position pos, ref Velocity vel) => -{ - pos.X += vel.X; // 直接修改,零 GC -}); - -// ❌ 不推荐:不使用 ref -World.Query(in query, (Position pos, Velocity vel) => -{ - pos.X += vel.X; // 复制值,修改不会生效 -}); -``` - -### 4. 组件大小优化 - -```csharp -// ✅ 推荐:小而专注的组件 -public struct Position(float x, float y) -{ - public float X { get; set; } = x; - public float Y { get; set; } = y; -} - -public struct Velocity(float x, float y) -{ - public float X { get; set; } = x; - public float Y { get; set; } = y; -} - -// ❌ 不推荐:大而全的组件 -public struct Transform -{ - public float X, Y, Z; - public float RotationX, RotationY, RotationZ; - public float ScaleX, ScaleY, ScaleZ; - public float VelocityX, VelocityY, VelocityZ; - // ... 太多数据 -} -``` - -### 5. 批量处理 - -```csharp -public class BatchProcessingSystem : ArchSystemAdapter -{ - protected override void OnUpdate(in float deltaTime) - { - // ✅ 推荐:批量处理 - var query = new QueryDescription().WithAll(); - World.Query(in query, (ref Position pos, ref Velocity vel) => - { - // 一次查询处理所有实体 - pos.X += vel.X * deltaTime; - pos.Y += vel.Y * deltaTime; - }); - } -} - -// ❌ 不推荐:逐个处理 -public class IndividualProcessingSystem : ArchSystemAdapter -{ - private List _entities = new(); - - protected override void OnUpdate(in float deltaTime) - { - foreach (var entity in _entities) - { - ref var pos = ref World.Get(entity); - ref var vel = ref World.Get(entity); - pos.X += vel.X * deltaTime; - pos.Y += vel.Y * deltaTime; - } - } -} -``` - -## 最佳实践 - -### 1. ECS 设计模式 - -**组件组合优于继承**: - -```csharp -// ✅ 推荐:使用组件组合 -var player = world.Create( - new Position(0, 0), - new Velocity(0, 0), - new Health(100, 100), - new PlayerTag(), - new Controllable() -); - -var enemy = world.Create( - new Position(10, 10), - new Velocity(-1, 0), - new Health(50, 50), - new EnemyTag(), - new AI() -); - -// ❌ 不推荐:使用继承 -public class Player : Entity { } -public class Enemy : Entity { } -``` - -**单一职责系统**: - -```csharp -// ✅ 推荐:每个系统只负责一件事 -public class MovementSystem : ArchSystemAdapter -{ - // 只负责移动 -} - -public class CollisionSystem : ArchSystemAdapter -{ - // 只负责碰撞检测 -} - -public class DamageSystem : ArchSystemAdapter -{ - // 只负责伤害处理 -} - -// ❌ 不推荐:一个系统做太多事 -public class GameplaySystem : ArchSystemAdapter -{ - // 移动、碰撞、伤害、AI... 太多职责 -} -``` - -### 2. 与传统架构结合 - -ECS 可以与 GFramework 的传统架构(Model、System、Utility)结合使用: - -```csharp -// Model 存储全局状态 -public class GameStateModel : AbstractModel -{ - public int Score { get; set; } - public int Level { get; set; } -} - -// ECS System 处理实体逻辑 -public class EnemySpawnSystem : ArchSystemAdapter -{ - private float _spawnTimer; - - protected override void OnUpdate(in float deltaTime) - { - _spawnTimer += deltaTime; - - if (_spawnTimer >= 2.0f) - { - // 获取 Model - var gameState = this.GetModel(); - - // 根据关卡生成敌人 - var enemyCount = gameState.Level * 2; - for (int i = 0; i < enemyCount; i++) - { - World.Create( - new Position(Random.Shared.Next(0, 100), 0), - new Velocity(0, -1), - new Health(50, 50), - new EnemyTag() - ); - } - - _spawnTimer = 0; - } - } -} - -// 传统 System 处理游戏逻辑 -public class ScoreSystem : AbstractSystem -{ - protected override void OnInit() - { - // 监听敌人死亡事件 - this.RegisterEvent(OnEnemyDestroyed); - } - - private void OnEnemyDestroyed(EnemyDestroyedEvent e) - { - var gameState = this.GetModel(); - gameState.Score += 100; - } -} -``` - -### 3. 事件集成 - -ECS 系统可以发送和接收框架事件: - -```csharp -// 定义事件 -public struct EnemyDestroyedEvent -{ - public Entity Enemy { get; init; } - public int Score { get; init; } -} - -// ECS 系统发送事件 -public class HealthSystem : ArchSystemAdapter -{ - protected override void OnUpdate(in float deltaTime) - { - var query = new QueryDescription() - .WithAll(); - - World.Query(in query, (Entity entity, ref Health health) => - { - if (health.Current <= 0) - { - // 发送事件 - this.SendEvent(new EnemyDestroyedEvent - { - Enemy = entity, - Score = 100 - }); - - // 销毁实体 - World.Destroy(entity); - } - }); - } -} - -// 传统系统接收事件 -public class UISystem : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnEnemyDestroyed); - } - - private void OnEnemyDestroyed(EnemyDestroyedEvent e) - { - // 更新 UI - Console.WriteLine($"Enemy destroyed! +{e.Score} points"); - } -} -``` - -### 4. 标签组件 - -使用空结构体作为标签来分类实体: - -```csharp -// 定义标签组件 -public struct PlayerTag { } -public struct EnemyTag { } -public struct BulletTag { } -public struct DeadTag { } - -// 使用标签过滤实体 -public class PlayerMovementSystem : ArchSystemAdapter -{ - private QueryDescription _query; - - protected override void OnArchInitialize() - { - // 只处理玩家实体 - _query = new QueryDescription() - .WithAll() - .WithNone(); - } - - protected override void OnUpdate(in float deltaTime) - { - World.Query(in _query, (ref Position pos, ref Velocity vel) => - { - // 只更新活着的玩家 - pos.X += vel.X * deltaTime; - pos.Y += vel.Y * deltaTime; - }); - } -} -``` - -### 5. 组件生命周期管理 - -```csharp -public class LifecycleManagementSystem : ArchSystemAdapter -{ - protected override void OnUpdate(in float deltaTime) - { - // 处理临时效果 - var buffQuery = new QueryDescription().WithAll(); - World.Query(in buffQuery, (Entity entity, ref BuffComponent buff) => - { - buff.Duration -= deltaTime; - - if (buff.Duration <= 0) - { - // 移除过期的 Buff - World.Remove(entity); - } - }); - - // 清理死亡实体 - var deadQuery = new QueryDescription().WithAll(); - World.Query(in deadQuery, (Entity entity) => - { - World.Destroy(entity); - }); - } -} -``` - -## 常见问题 - -### Q: 如何在 ECS 系统中访问其他服务? - -A: `ArchSystemAdapter<T>` 继承自 `AbstractSystem`,可以使用所有 GFramework 的扩展方法: - -```csharp -public class ServiceAccessSystem : ArchSystemAdapter -{ - protected override void OnUpdate(in float deltaTime) - { - // 获取 Model - var playerModel = this.GetModel(); - - // 获取 Utility - var timeUtility = this.GetUtility(); - - // 发送命令 - this.SendCommand(new SaveGameCommand()); - - // 发送查询 - var score = this.SendQuery(new GetScoreQuery()); - - // 发送事件 - this.SendEvent(new GameOverEvent()); - } -} -``` - -### Q: ECS 和传统架构如何选择? - -A: 根据场景选择: - -- **使用 ECS**:大量相似实体、需要高性能批量处理(敌人、子弹、粒子) -- **使用传统架构**:全局状态、单例服务、UI 逻辑、游戏流程控制 - -### Q: 如何调试 ECS 系统? - -A: 使用以下方法: - -```csharp -public class DebugSystem : ArchSystemAdapter -{ - protected override void OnUpdate(in float deltaTime) - { - // 打印实体数量 - Console.WriteLine($"Total entities: {World.Size}"); - - // 查询特定实体 - var query = new QueryDescription().WithAll(); - var count = 0; - World.Query(in query, (Entity entity, ref Position pos) => - { - count++; - Console.WriteLine($"Entity {entity.Id}: ({pos.X}, {pos.Y})"); - }); - - Console.WriteLine($"Entities with Position: {count}"); - } -} -``` - -### Q: 如何处理实体之间的交互? - -A: 使用查询和事件: - -```csharp -public class CollisionSystem : ArchSystemAdapter -{ - protected override void OnUpdate(in float deltaTime) - { - var playerQuery = new QueryDescription() - .WithAll(); - var enemyQuery = new QueryDescription() - .WithAll(); - - // 检测玩家和敌人的碰撞 - World.Query(in playerQuery, (Entity player, ref Position playerPos) => - { - World.Query(in enemyQuery, (Entity enemy, ref Position enemyPos) => - { - var distance = Math.Sqrt( - Math.Pow(playerPos.X - enemyPos.X, 2) + - Math.Pow(playerPos.Y - enemyPos.Y, 2) - ); - - if (distance < 1.0f) - { - // 发送碰撞事件 - this.SendEvent(new CollisionEvent - { - Entity1 = player, - Entity2 = enemy - }); - } - }); - }); - } -} -``` - -### Q: 如何优化大量实体的性能? - -A: 参考性能优化章节,主要策略: - -1. 使用 struct 组件 -2. 缓存查询 -3. 使用 ref 访问组件 -4. 批量处理 -5. 合理设计组件大小 -6. 使用 Arch 的并行查询(高级特性) - -### Q: 可以在运行时动态添加/移除组件吗? - -A: 可以,Arch 支持运行时修改实体的组件: - -```csharp -public class DynamicComponentSystem : ArchSystemAdapter -{ - protected override void OnUpdate(in float deltaTime) - { - var query = new QueryDescription().WithAll(); - - World.Query(in query, (Entity entity, ref Position pos) => - { - // 动态添加组件 - if (pos.X > 100 && !World.Has(entity)) - { - World.Add(entity, new FastTag()); - } - - // 动态移除组件 - if (pos.X < 0 && World.Has(entity)) - { - World.Remove(entity); - } - }); - } -} -``` - -## 相关资源 - -- [Arch.Core 官方文档](https://github.com/genaray/Arch) -- [Architecture 包使用说明](./architecture.md) -- [System 包使用说明](./system.md) -- [事件系统](./events.md) - ---- - -**许可证**:Apache 2.0 diff --git a/docs/zh-CN/ecs/arch.md b/docs/zh-CN/ecs/arch.md new file mode 100644 index 0000000..19a1441 --- /dev/null +++ b/docs/zh-CN/ecs/arch.md @@ -0,0 +1,751 @@ +--- +title: Arch ECS 集成 +description: GFramework 的 Arch ECS 集成包使用指南,提供高性能的实体组件系统支持。 +--- + +# Arch ECS 集成 + +## 概述 + +`GFramework.Ecs.Arch` 是 GFramework 的 Arch ECS 集成包,提供开箱即用的 ECS(Entity Component +System)支持。基于 [Arch.Core](https://github.com/genaray/Arch) 实现,具有极致的性能和简洁的 API。 + +**主要特性**: + +- 🎯 **显式集成** - 符合 .NET 生态习惯的显式注册方式 +- 🔌 **零依赖** - 不使用时,Core 包无 Arch 依赖 +- 🎯 **类型安全** - 完整的类型系统和编译时检查 +- ⚡ **高性能** - 基于 Arch ECS 的高性能实现 +- 🔧 **易扩展** - 简单的系统适配器模式 +- 📊 **优先级支持** - 系统按优先级顺序执行 + +**性能特点**: + +- 10,000 个实体更新 < 100ms +- 1,000 个实体创建 < 50ms +- 基于 Archetype 的高效内存布局 +- 零 GC 分配的组件访问 + +## 安装 + +```bash +dotnet add package GeWuYou.GFramework.Ecs.Arch +``` + +## 快速开始 + +### 1. 注册 ECS 模块 + +```csharp +using GFramework.Core.architecture; +using GFramework.Ecs.Arch.extensions; + +public class GameArchitecture : Architecture +{ + public GameArchitecture() : base(new ArchitectureConfiguration()) + { + } + + protected override void OnInitialize() + { + // 显式注册 Arch ECS 模块 + this.UseArch(); + } +} + +// 初始化架构 +var architecture = new GameArchitecture(); +architecture.Initialize(); +``` + +### 2. 带配置的注册 + +```csharp +public class GameArchitecture : Architecture +{ + protected override void OnInitialize() + { + // 带配置的注册 + this.UseArch(options => + { + options.WorldCapacity = 2000; // World 初始容量 + options.EnableStatistics = true; // 启用统计信息 + options.Priority = 50; // 模块优先级 + }); + } +} +``` + +### 3. 定义组件 + +组件是纯数据结构,使用 `struct` 定义: + +```csharp +using System.Runtime.InteropServices; + +namespace MyGame.Components; + +[StructLayout(LayoutKind.Sequential)] +public struct Position(float x, float y) +{ + public float X { get; set; } = x; + public float Y { get; set; } = y; +} + +[StructLayout(LayoutKind.Sequential)] +public struct Velocity(float x, float y) +{ + public float X { get; set; } = x; + public float Y { get; set; } = y; +} + +[StructLayout(LayoutKind.Sequential)] +public struct Health(float current, float max) +{ + public float Current { get; set; } = current; + public float Max { get; set; } = max; +} +``` + +### 4. 创建系统 + +系统继承自 `ArchSystemAdapter`: + +```csharp +using Arch.Core; +using GFramework.Ecs.Arch; +using MyGame.Components; + +namespace MyGame.Systems; + +/// +/// 移动系统 - 更新实体位置 +/// +public sealed class MovementSystem : ArchSystemAdapter +{ + private QueryDescription _query; + + protected override void OnArchInitialize() + { + // 创建查询:查找所有同时拥有 Position 和 Velocity 组件的实体 + _query = new QueryDescription() + .WithAll(); + } + + protected override void OnUpdate(in float deltaTime) + { + // 查询并更新所有符合条件的实体 + World.Query(in _query, (ref Position pos, ref Velocity vel) => + { + pos.X += vel.X * deltaTime; + pos.Y += vel.Y * deltaTime; + }); + } +} +``` + +### 5. 注册系统 + +```csharp +public class GameArchitecture : Architecture +{ + protected override void OnInitialize() + { + this.UseArch(); + + // 注册 ECS 系统 + RegisterSystem(); + } +} +``` + +### 6. 创建实体 + +```csharp +using Arch.Core; +using GFramework.Core.Abstractions.rule; +using GFramework.SourceGenerators.Abstractions.rule; +using MyGame.Components; + +[ContextAware] +public partial class GameController +{ + public void Start() + { + // 获取 World + var world = this.GetService(); + + // 创建实体 + var player = world.Create( + new Position(0, 0), + new Velocity(0, 0), + new Health(100, 100) + ); + + var enemy = world.Create( + new Position(10, 10), + new Velocity(-1, 0), + new Health(50, 50) + ); + } +} +``` + +### 7. 更新系统 + +```csharp +using GFramework.Ecs.Arch.Abstractions; + +public class GameLoop +{ + private IArchEcsModule _ecsModule; + + public void Initialize() + { + // 获取 ECS 模块 + _ecsModule = architecture.Context.GetService(); + } + + public void Update(float deltaTime) + { + // 更新所有 ECS 系统 + _ecsModule.Update(deltaTime); + } +} +``` + +## 配置选项 + +### ArchOptions + +```csharp +public sealed class ArchOptions +{ + /// + /// World 初始容量(默认:1000) + /// + public int WorldCapacity { get; set; } = 1000; + + /// + /// 是否启用统计信息(默认:false) + /// + public bool EnableStatistics { get; set; } = false; + + /// + /// 模块优先级(默认:50) + /// + public int Priority { get; set; } = 50; +} +``` + +### 配置示例 + +```csharp +this.UseArch(options => +{ + // 设置 World 初始容量 + // 根据预期实体数量设置,避免频繁扩容 + options.WorldCapacity = 2000; + + // 启用统计信息(开发/调试时使用) + options.EnableStatistics = true; + + // 设置模块优先级 + // 数值越小,优先级越高 + options.Priority = 50; +}); +``` + +## 核心概念 + +### Entity(实体) + +实体是游戏世界中的基本对象,本质上是一个唯一标识符: + +```csharp +// 创建空实体 +var entity = world.Create(); + +// 创建带组件的实体 +var entity = world.Create( + new Position(0, 0), + new Velocity(1, 1) +); + +// 销毁实体 +world.Destroy(entity); +``` + +### Component(组件) + +组件是纯数据结构,用于存储实体的状态: + +```csharp +// 添加组件 +world.Add(entity, new Position(0, 0)); + +// 检查组件 +if (world.Has(entity)) +{ + // 获取组件引用(零 GC 分配) + ref var pos = ref world.Get(entity); + pos.X += 10; +} + +// 设置组件(替换现有值) +world.Set(entity, new Position(100, 100)); + +// 移除组件 +world.Remove(entity); +``` + +### System(系统) + +系统包含游戏逻辑,处理具有特定组件组合的实体: + +```csharp +public sealed class DamageSystem : ArchSystemAdapter +{ + private QueryDescription _query; + + protected override void OnArchInitialize() + { + // 初始化查询 + _query = new QueryDescription() + .WithAll(); + } + + protected override void OnUpdate(in float deltaTime) + { + // 处理伤害 + World.Query(in _query, (Entity entity, ref Health health, ref Damage damage) => + { + health.Current -= damage.Value * deltaTime; + + if (health.Current <= 0) + { + health.Current = 0; + World.Remove(entity); + } + }); + } +} +``` + +### World(世界) + +World 是 ECS 的核心容器,管理所有实体和组件: + +```csharp +// World 由 ArchEcsModule 自动创建和注册 +var world = this.GetService(); + +// 获取实体数量 +var entityCount = world.Size; + +// 清空所有实体 +world.Clear(); +``` + +## 系统适配器 + +### ArchSystemAdapter + +`ArchSystemAdapter` 桥接 Arch.System.ISystem 到 GFramework 架构: + +```csharp +public sealed class MySystem : ArchSystemAdapter +{ + // Arch 系统初始化 + protected override void OnArchInitialize() + { + // 创建查询、初始化资源 + } + + // 更新前调用 + protected override void OnBeforeUpdate(in float deltaTime) + { + // 预处理逻辑 + } + + // 主更新逻辑 + protected override void OnUpdate(in float deltaTime) + { + // 处理实体 + } + + // 更新后调用 + protected override void OnAfterUpdate(in float deltaTime) + { + // 后处理逻辑 + } + + // 资源清理 + protected override void OnArchDispose() + { + // 清理资源 + } +} +``` + +### 访问 World + +在系统中可以直接访问 `World` 属性: + +```csharp +public sealed class MySystem : ArchSystemAdapter +{ + protected override void OnUpdate(in float deltaTime) + { + // 访问 World + var entityCount = World.Size; + + // 创建实体 + var entity = World.Create(new Position(0, 0)); + + // 查询实体 + var query = new QueryDescription().WithAll(); + World.Query(in query, (ref Position pos) => + { + // 处理逻辑 + }); + } +} +``` + +### 访问框架服务 + +`ArchSystemAdapter` 继承自 `AbstractSystem`,可以使用所有 GFramework 的扩展方法: + +```csharp +public sealed class ServiceAccessSystem : ArchSystemAdapter +{ + protected override void OnUpdate(in float deltaTime) + { + // 获取 Model + var playerModel = this.GetModel(); + + // 获取 Utility + var timeUtility = this.GetUtility(); + + // 发送命令 + this.SendCommand(new SaveGameCommand()); + + // 发送查询 + var score = this.SendQuery(new GetScoreQuery()); + + // 发送事件 + this.SendEvent(new GameOverEvent()); + } +} +``` + +## 查询实体 + +### 基本查询 + +```csharp +// 查询:必须有 Position 和 Velocity +var query = new QueryDescription() + .WithAll(); + +World.Query(in query, (ref Position pos, ref Velocity vel) => +{ + pos.X += vel.X * deltaTime; + pos.Y += vel.Y * deltaTime; +}); +``` + +### 过滤查询 + +```csharp +// 查询:必须有 Health,但不能有 Damage +var query = new QueryDescription() + .WithAll() + .WithNone(); + +World.Query(in query, (ref Health health) => +{ + // 只处理没有受伤的实体 +}); +``` + +### 可选组件查询 + +```csharp +// 查询:必须有 Position,可选 Velocity +var query = new QueryDescription() + .WithAll() + .WithAny(); + +World.Query(in query, (Entity entity, ref Position pos) => +{ + // 处理逻辑 +}); +``` + +### 访问实体 ID + +```csharp +var query = new QueryDescription().WithAll(); + +World.Query(in query, (Entity entity, ref Position pos) => +{ + // 可以访问实体 ID + Console.WriteLine($"Entity {entity.Id}: ({pos.X}, {pos.Y})"); + + // 可以对实体进行操作 + if (pos.X > 100) + { + World.Destroy(entity); + } +}); +``` + +## 系统优先级 + +系统按照优先级顺序执行,数值越小优先级越高: + +```csharp +using GFramework.Core.Abstractions.bases; +using GFramework.SourceGenerators.Abstractions.bases; + +// 使用 Priority 特性设置优先级 +[Priority(10)] // 高优先级,先执行 +public sealed class InputSystem : ArchSystemAdapter +{ + // ... +} + +[Priority(20)] // 中优先级 +public sealed class MovementSystem : ArchSystemAdapter +{ + // ... +} + +[Priority(30)] // 低优先级,后执行 +public sealed class RenderSystem : ArchSystemAdapter +{ + // ... +} +``` + +执行顺序:InputSystem → MovementSystem → RenderSystem + +## 性能优化 + +### 1. 使用 struct 组件 + +```csharp +// ✅ 推荐:使用 struct +[StructLayout(LayoutKind.Sequential)] +public struct Position(float x, float y) +{ + public float X { get; set; } = x; + public float Y { get; set; } = y; +} + +// ❌ 不推荐:使用 class +public class Position +{ + public float X { get; set; } + public float Y { get; set; } +} +``` + +### 2. 缓存查询 + +```csharp +public class OptimizedSystem : ArchSystemAdapter +{ + // ✅ 推荐:缓存查询 + private QueryDescription _cachedQuery; + + protected override void OnArchInitialize() + { + _cachedQuery = new QueryDescription() + .WithAll(); + } + + protected override void OnUpdate(in float deltaTime) + { + World.Query(in _cachedQuery, (ref Position pos, ref Velocity vel) => + { + pos.X += vel.X * deltaTime; + pos.Y += vel.Y * deltaTime; + }); + } +} +``` + +### 3. 使用 ref 访问组件 + +```csharp +// ✅ 推荐:使用 ref 避免复制 +World.Query(in query, (ref Position pos, ref Velocity vel) => +{ + pos.X += vel.X; // 直接修改,零 GC +}); + +// ❌ 不推荐:不使用 ref +World.Query(in query, (Position pos, Velocity vel) => +{ + pos.X += vel.X; // 复制值,修改不会生效 +}); +``` + +### 4. 组件大小优化 + +```csharp +// ✅ 推荐:小而专注的组件 +public struct Position(float x, float y) +{ + public float X { get; set; } = x; + public float Y { get; set; } = y; +} + +public struct Velocity(float x, float y) +{ + public float X { get; set; } = x; + public float Y { get; set; } = y; +} + +// ❌ 不推荐:大而全的组件 +public struct Transform +{ + public float X, Y, Z; + public float RotationX, RotationY, RotationZ; + public float ScaleX, ScaleY, ScaleZ; + public float VelocityX, VelocityY, VelocityZ; + // ... 太多数据 +} +``` + +## 最佳实践 + +### 1. 组件设计原则 + +- 使用 `struct` 而不是 `class` +- 只包含数据,不包含逻辑 +- 使用 `[StructLayout(LayoutKind.Sequential)]` 优化内存布局 +- 保持组件小而专注 + +### 2. 系统设计原则 + +- 单一职责:每个系统只负责一件事 +- 缓存查询:在 `OnArchInitialize` 中创建查询 +- 使用 ref:访问组件时使用 ref 参数 +- 批量处理:一次查询处理所有实体 + +### 3. 标签组件 + +使用空结构体作为标签来分类实体: + +```csharp +// 定义标签组件 +public struct PlayerTag { } +public struct EnemyTag { } +public struct DeadTag { } + +// 使用标签过滤实体 +var query = new QueryDescription() + .WithAll() + .WithNone(); +``` + +### 4. 与传统架构结合 + +```csharp +// ECS 系统可以访问 Model +public class EnemySpawnSystem : ArchSystemAdapter +{ + protected override void OnUpdate(in float deltaTime) + { + var gameState = this.GetModel(); + + // 根据关卡生成敌人 + for (int i = 0; i < gameState.Level; i++) + { + World.Create( + new Position(Random.Shared.Next(0, 100), 0), + new Velocity(0, -1), + new Health(50, 50) + ); + } + } +} +``` + +## 常见问题 + +### Q: 如何在运行时动态添加/移除组件? + +A: Arch 支持运行时修改实体的组件: + +```csharp +// 动态添加组件 +if (pos.X > 100 && !World.Has(entity)) +{ + World.Add(entity, new FastTag()); +} + +// 动态移除组件 +if (pos.X < 0 && World.Has(entity)) +{ + World.Remove(entity); +} +``` + +### Q: 如何处理实体之间的交互? + +A: 使用嵌套查询或事件: + +```csharp +// 方式 1:嵌套查询 +World.Query(in playerQuery, (Entity player, ref Position playerPos) => +{ + World.Query(in enemyQuery, (Entity enemy, ref Position enemyPos) => + { + // 检测碰撞 + }); +}); + +// 方式 2:使用事件 +this.SendEvent(new CollisionEvent +{ + Entity1 = player, + Entity2 = enemy +}); +``` + +### Q: 如何调试 ECS 系统? + +A: 使用日志和统计信息: + +```csharp +protected override void OnUpdate(in float deltaTime) +{ + // 打印实体数量 + Console.WriteLine($"Total entities: {World.Size}"); + + // 查询特定实体 + var query = new QueryDescription().WithAll(); + var count = 0; + World.Query(in query, (Entity entity, ref Position pos) => + { + count++; + Console.WriteLine($"Entity {entity.Id}: ({pos.X}, {pos.Y})"); + }); +} +``` + +## 相关资源 + +- [Arch.Core 官方文档](https://github.com/genaray/Arch) +- [ECS 概述](./index.md) +- [ECS 最佳实践](./best-practices.md) +- [性能优化指南](./performance.md) + +--- + +**许可证**:MIT License diff --git a/docs/zh-CN/ecs/index.md b/docs/zh-CN/ecs/index.md new file mode 100644 index 0000000..63100e6 --- /dev/null +++ b/docs/zh-CN/ecs/index.md @@ -0,0 +1,297 @@ +--- +title: ECS 系统集成 +description: GFramework 的 ECS(Entity Component System)集成方案,支持多种 ECS 框架。 +--- + +# ECS 系统集成 + +## 概述 + +GFramework 提供了灵活的 ECS(Entity Component System)集成方案,允许你根据项目需求选择合适的 ECS 框架。ECS +是一种数据驱动的架构模式,特别适合处理大量相似实体的场景。 + +## 什么是 ECS? + +ECS(Entity Component System)是一种架构模式,将游戏对象分解为三个核心概念: + +- **Entity(实体)**:游戏世界中的基本对象,本质上是一个唯一标识符 +- **Component(组件)**:纯数据结构,存储实体的状态 +- **System(系统)**:包含游戏逻辑,处理具有特定组件组合的实体 + +### ECS 的优势 + +- **高性能**:数据局部性好,缓存友好 +- **可扩展**:通过组合组件轻松创建新实体类型 +- **并行处理**:系统之间相互独立,易于并行化 +- **数据驱动**:逻辑与数据分离,便于序列化和网络同步 + +### 何时使用 ECS? + +**适合使用 ECS 的场景**: + +- 大量相似实体(敌人、子弹、粒子) +- 需要高性能批量处理 +- 复杂的实体组合和变化 +- 需要并行处理的系统 + +**不适合使用 ECS 的场景**: + +- 全局状态管理 +- 单例服务 +- UI 逻辑 +- 游戏流程控制 + +## 支持的 ECS 框架 + +GFramework 采用可选集成的设计,你可以根据需求选择合适的 ECS 框架: + +### Arch ECS(推荐) + +[Arch](https://github.com/genaray/Arch) 是一个高性能的 C# ECS 框架,具有以下特点: + +- ✅ **极致性能**:基于 Archetype 的内存布局,零 GC 分配 +- ✅ **简单易用**:清晰的 API,易于上手 +- ✅ **功能完整**:支持查询、过滤、并行处理等高级特性 +- ✅ **活跃维护**:社区活跃,持续更新 + +**安装方式**: + +```bash +dotnet add package GeWuYou.GFramework.Ecs.Arch +``` + +**文档链接**:[Arch ECS 集成指南](./arch.md) + +### 其他 ECS 框架 + +GFramework 的设计允许集成其他 ECS 框架,未来可能支持: + +- **DefaultEcs**:轻量级 ECS 框架 +- **Entitas**:成熟的 ECS 框架,Unity 生态常用 +- **自定义 ECS**:你可以基于 GFramework 的模块系统实现自己的 ECS 集成 + +## 快速开始 + +### 1. 选择 ECS 框架 + +根据项目需求选择合适的 ECS 框架。对于大多数项目,我们推荐使用 Arch ECS。 + +### 2. 安装集成包 + +```bash +# 安装 Arch ECS 集成包 +dotnet add package GeWuYou.GFramework.Ecs.Arch +``` + +### 3. 注册 ECS 模块 + +```csharp +using GFramework.Core.architecture; +using GFramework.Ecs.Arc; + +public class GameArchitecture : Architecture +{ + public GameArchitecture() : base(new ArchitectureConfiguration()) + { + } + + protected override void OnInitialize() + { + // 显式注册 Arch ECS 模块 + this.UseArch(options => + { + options.WorldCapacity = 2000; + options.EnableStatistics = true; + }); + } +} +``` + +### 4. 定义组件 + +```csharp +using System.Runtime.InteropServices; + +[StructLayout(LayoutKind.Sequential)] +public struct Position(float x, float y) +{ + public float X { get; set; } = x; + public float Y { get; set; } = y; +} + +[StructLayout(LayoutKind.Sequential)] +public struct Velocity(float x, float y) +{ + public float X { get; set; } = x; + public float Y { get; set; } = y; +} +``` + +### 5. 创建系统 + +```csharp +using Arch.Core; +using GFramework.Ecs.Arch; + +public sealed class MovementSystem : ArchSystemAdapter +{ + private QueryDescription _query; + + protected override void OnArchInitialize() + { + _query = new QueryDescription() + .WithAll(); + } + + protected override void OnUpdate(in float deltaTime) + { + World.Query(in _query, (ref Position pos, ref Velocity vel) => + { + pos.X += vel.X * deltaTime; + pos.Y += vel.Y * deltaTime; + }); + } +} +``` + +### 6. 注册系统 + +```csharp +public class GameArchitecture : Architecture +{ + protected override void OnInitialize() + { + this.UseArch(); + + // 注册 ECS 系统 + RegisterSystem(); + } +} +``` + +## 设计理念 + +### 显式集成 + +GFramework 采用显式集成的设计,而不是自动注册: + +```csharp +// ✅ 显式注册 - 清晰、可控 +public class GameArchitecture : Architecture +{ + protected override void OnInitialize() + { + this.UseArch(); // 明确表示使用 Arch ECS + } +} + +// ❌ 自动注册 - 隐式、难以控制 +// 只需引入包,自动注册(不推荐) +``` + +**优势**: + +- 清晰的依赖关系 +- 更好的 IDE 支持 +- 易于测试和调试 +- 符合 .NET 生态习惯 + +### 零依赖原则 + +如果你不使用 ECS,GFramework.Core 包不会引入任何 ECS 相关的依赖: + +```xml + + + + + + + + + + +``` + +### 模块化设计 + +ECS 集成基于 GFramework 的模块系统: + +```csharp +// ECS 模块实现 IServiceModule 接口 +public sealed class ArchEcsModule : IArchEcsModule +{ + public string ModuleName => nameof(ArchEcsModule); + public int Priority => 50; + public bool IsEnabled { get; } + + public void Register(IIocContainer container) { } + public void Initialize() { } + public ValueTask DestroyAsync() { } + public void Update(float deltaTime) { } +} +``` + +## 与传统架构结合 + +ECS 可以与 GFramework 的传统架构(Model、System、Utility)无缝结合: + +```csharp +// Model 存储全局状态 +public class GameStateModel : AbstractModel +{ + public int Score { get; set; } + public int Level { get; set; } +} + +// ECS System 处理实体逻辑 +public class EnemySpawnSystem : ArchSystemAdapter +{ + protected override void OnUpdate(in float deltaTime) + { + // 访问 Model + var gameState = this.GetModel(); + + // 根据关卡生成敌人 + for (int i = 0; i < gameState.Level; i++) + { + World.Create( + new Position(Random.Shared.Next(0, 100), 0), + new Velocity(0, -1), + new Health(50, 50) + ); + } + } +} + +// 传统 System 处理游戏逻辑 +public class ScoreSystem : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnEnemyDestroyed); + } + + private void OnEnemyDestroyed(EnemyDestroyedEvent e) + { + var gameState = this.GetModel(); + gameState.Score += 100; + } +} +``` + +## 下一步 + +- [Arch ECS 集成指南](./arch.md) - 详细的 Arch ECS 使用文档 +- [ECS 最佳实践](./best-practices.md) - ECS 设计模式和优化技巧 +- [性能优化](./performance.md) - ECS 性能优化指南 + +## 相关资源 + +- [Architecture 架构系统](../core/architecture.md) +- [System 系统](../core/system.md) +- [事件系统](../core/events.md) + +--- + +**许可证**:MIT License From f771b7dbef27a96ae733871e522a8e32cdc8bc26 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Sun, 8 Mar 2026 21:01:13 +0800 Subject: [PATCH 7/7] =?UTF-8?q?chore(ci):=20=E6=B7=BB=E5=8A=A0=20GFramewor?= =?UTF-8?q?k.Ecs.Arch.Tests=20=E6=B5=8B=E8=AF=95=E6=AD=A5=E9=AA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为 CI 工作流添加新的测试步骤 - 配置 GFramework.Ecs.Arch.Tests 的测试运行命令 - 设置测试结果输出目录为 TestResults - 使用 trx 格式记录测试日志文件 - 保持与现有测试配置一致的结构和格式 --- .github/workflows/ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 628753b..8ec0bef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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