diff --git a/GFramework.Core.Tests/architecture/ArchitectureServicesTests.cs b/GFramework.Core.Tests/architecture/ArchitectureServicesTests.cs index 1baf59f..041bfa0 100644 --- a/GFramework.Core.Tests/architecture/ArchitectureServicesTests.cs +++ b/GFramework.Core.Tests/architecture/ArchitectureServicesTests.cs @@ -1,6 +1,5 @@ using GFramework.Core.Abstractions.architecture; using GFramework.Core.Abstractions.command; -using GFramework.Core.Abstractions.ecs; using GFramework.Core.Abstractions.environment; using GFramework.Core.Abstractions.events; using GFramework.Core.Abstractions.ioc; @@ -428,16 +427,6 @@ public class TestArchitectureContextV3 : IArchitectureContext { return _environment; } - - public IEcsWorld GetEcsWorld() - { - throw new NotImplementedException("ECS not implemented in test context"); - } - - public void RegisterEcsSystem() where T : class, IEcsSystem - { - throw new NotImplementedException("ECS not implemented in test context"); - } } #endregion \ No newline at end of file diff --git a/GFramework.Core.Tests/architecture/GameContextTests.cs b/GFramework.Core.Tests/architecture/GameContextTests.cs index 9db9d72..a3ed70f 100644 --- a/GFramework.Core.Tests/architecture/GameContextTests.cs +++ b/GFramework.Core.Tests/architecture/GameContextTests.cs @@ -1,6 +1,5 @@ using GFramework.Core.Abstractions.architecture; using GFramework.Core.Abstractions.command; -using GFramework.Core.Abstractions.ecs; using GFramework.Core.Abstractions.environment; using GFramework.Core.Abstractions.events; using GFramework.Core.Abstractions.ioc; @@ -452,14 +451,4 @@ public class TestArchitectureContext : IArchitectureContext { return Environment; } - - public IEcsWorld GetEcsWorld() - { - throw new NotImplementedException("ECS not implemented in test context"); - } - - public void RegisterEcsSystem() where T : class, IEcsSystem - { - throw new NotImplementedException("ECS not implemented in test context"); - } } \ No newline at end of file diff --git a/GFramework.Core.Tests/ecs/EcsAdvancedTests.cs b/GFramework.Core.Tests/ecs/EcsAdvancedTests.cs index 53582ef..298e0e6 100644 --- a/GFramework.Core.Tests/ecs/EcsAdvancedTests.cs +++ b/GFramework.Core.Tests/ecs/EcsAdvancedTests.cs @@ -1,303 +1,158 @@ -using System.Reflection; +using System.Diagnostics.CodeAnalysis; using Arch.Core; -using GFramework.Core.Abstractions.ecs; 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 GFramework.Core.logging; using NUnit.Framework; namespace GFramework.Core.Tests.ecs; +/// +/// ECS 高级功能测试类 - 使用 Arch 原生 API +/// [TestFixture] +[Experimental("GFrameworkECS")] public class EcsAdvancedTests { [SetUp] public void Setup() { - LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider(); - _container = new MicrosoftDiContainer(); - var loggerField = typeof(MicrosoftDiContainer).GetField("_logger", - BindingFlags.NonPublic | BindingFlags.Instance); - loggerField?.SetValue(_container, - LoggerFactoryResolver.Provider.CreateLogger(nameof(EcsAdvancedTests))); - _context = new ArchitectureContext(_container); } [TearDown] public void TearDown() { - _ecsWorld?.Dispose(); - _ecsWorld = null; + if (_world != null) + { + World.Destroy(_world); + _world = null; + } + _container?.Clear(); _context = null; + _ecsModule = null; } private MicrosoftDiContainer? _container; private ArchitectureContext? _context; - private EcsWorld? _ecsWorld; + private World? _world; + private ArchEcsModule? _ecsModule; - private void InitializeEcsWithSystems(params Type[] systemTypes) + private void InitializeEcsModule() { - _ecsWorld = new EcsWorld(); - _container!.Register(_ecsWorld); - _container.Register(_ecsWorld as IEcsWorld); - - var systems = new List(); - foreach (var systemType in systemTypes) - { - var system = (IEcsSystem)Activator.CreateInstance(systemType)!; - ((IContextAware)system).SetContext(_context!); - system.Initialize(); - systems.Add(system); - _container.RegisterPlurality(system); - } - - _container.Register(systems as IReadOnlyList); - } - - private EcsSystemRunner CreateRunner() - { - var runner = new EcsSystemRunner(); - ((IContextAware)runner).SetContext(_context!); - runner.Initialize(); - return runner; - } - - [Test] - public void EcsWorld_Dispose_Should_Be_Idempotent() - { - _ecsWorld = new EcsWorld(); - _ecsWorld.CreateEntity(typeof(Position)); - - Assert.DoesNotThrow(() => - { - _ecsWorld.Dispose(); - _ecsWorld.Dispose(); - }); - - _ecsWorld = null; - } - - [Test] - public void EcsWorld_CreateEntity_WithNoComponents_Should_Work() - { - _ecsWorld = new EcsWorld(); - var entity = _ecsWorld.CreateEntity(); - - Assert.That(_ecsWorld.EntityCount, Is.EqualTo(1)); - Assert.That(_ecsWorld.IsAlive(entity), Is.True); - } - - [Test] - public void EcsWorld_CreateEntity_WithMultipleComponents_Should_Work() - { - _ecsWorld = new EcsWorld(); - var entity = _ecsWorld.CreateEntity(typeof(Position), typeof(Velocity)); - - var world = _ecsWorld.InternalWorld; - Assert.That(world.Has(entity), Is.True); - Assert.That(world.Has(entity), Is.True); - } - - [Test] - public void EcsWorld_IsAlive_AfterDestroy_Should_ReturnFalse() - { - _ecsWorld = new EcsWorld(); - var entity = _ecsWorld.CreateEntity(typeof(Position)); - - Assert.That(_ecsWorld.IsAlive(entity), Is.True); - - _ecsWorld.DestroyEntity(entity); - - Assert.That(_ecsWorld.IsAlive(entity), Is.False); - } - - [Test] - public void EcsSystemRunner_Update_WithoutStart_Should_NotUpdate() - { - InitializeEcsWithSystems(typeof(MovementSystem)); - - var entity = _ecsWorld!.CreateEntity(typeof(Position), typeof(Velocity)); - var world = _ecsWorld.InternalWorld; - world.Set(entity, new Position(0, 0)); - world.Set(entity, new Velocity(10, 5)); - - var runner = CreateRunner(); - - runner.Update(1.0f); - - ref var pos = ref world.Get(entity); - Assert.That(pos.X, Is.EqualTo(0), "Position should not change without Start()"); - Assert.That(pos.Y, Is.EqualTo(0), "Position should not change without Start()"); - } - - [Test] - public void EcsSystemRunner_StartStop_Should_ControlUpdates() - { - InitializeEcsWithSystems(typeof(MovementSystem)); - - var entity = _ecsWorld!.CreateEntity(typeof(Position), typeof(Velocity)); - var world = _ecsWorld.InternalWorld; - world.Set(entity, new Position(0, 0)); - world.Set(entity, new Velocity(10, 5)); - - var runner = CreateRunner(); - - runner.Start(); - runner.Update(1.0f); - runner.Stop(); - runner.Update(1.0f); - - ref var pos = ref world.Get(entity); - Assert.That(pos.X, Is.EqualTo(10).Within(0.001f), "Only first update should apply"); - Assert.That(pos.Y, Is.EqualTo(5).Within(0.001f), "Only first update should apply"); - } - - [Test] - public void EcsSystemRunner_WithNoSystems_Should_NotThrow() - { - _ecsWorld = new EcsWorld(); - _container!.Register(_ecsWorld); - _container.Register(new List() as IReadOnlyList); - - var runner = CreateRunner(); - - Assert.DoesNotThrow(() => - { - runner.Start(); - runner.Update(1.0f); - runner.Stop(); - }); - } - - [Test] - public void EcsSystemRunner_OnDestroy_Should_ClearSystems() - { - InitializeEcsWithSystems(typeof(MovementSystem)); - - var entity = _ecsWorld!.CreateEntity(typeof(Position), typeof(Velocity)); - var world = _ecsWorld.InternalWorld; - world.Set(entity, new Position(0, 0)); - world.Set(entity, new Velocity(10, 5)); - - var runner = CreateRunner(); - runner.Start(); - - // 销毁前先更新一次,记录初始位置 - runner.Update(1.0f); - ref var posBeforeDestroy = ref world.Get(entity); - var xBefore = posBeforeDestroy.X; - var yBefore = posBeforeDestroy.Y; - - runner.Destroy(); - - // 销毁后再更新,位置应该保持不变 - runner.Update(1.0f); - - ref var posAfterDestroy = ref world.Get(entity); - Assert.That(posAfterDestroy.X, Is.EqualTo(xBefore), "Position should not change after Destroy()"); - Assert.That(posAfterDestroy.Y, Is.EqualTo(yBefore), "Position should not change after Destroy()"); - } - - [Test] - public void MultipleSystems_Should_ExecuteInPriorityOrder() - { - var executionOrder = new List(); - - _ecsWorld = new EcsWorld(); - _container!.Register(_ecsWorld); - - var systemA = new OrderTrackingSystem("A", 10, executionOrder); - var systemB = new OrderTrackingSystem("B", -10, executionOrder); - var systemC = new OrderTrackingSystem("C", 0, executionOrder); - - foreach (var system in new[] { systemA, systemB, systemC }) - { - ((IContextAware)system).SetContext(_context!); - system.Initialize(); - _container.RegisterPlurality(system); - } - - _container.Register(new List { systemA, systemB, systemC } as IReadOnlyList); - - var runner = CreateRunner(); - runner.Start(); - runner.Update(1.0f); - - Assert.That(executionOrder, Is.EqualTo(["B", "C", "A"]), - "Systems should execute in priority order (B=-10, C=0, A=10)"); - } - - [Test] - public void ChainedSystems_Should_PassDataBetweenSystems() - { - _ecsWorld = new EcsWorld(); - _container!.Register(_ecsWorld); - - var entity = _ecsWorld.CreateEntity(typeof(Position), typeof(Velocity)); - var world = _ecsWorld.InternalWorld; - world.Set(entity, new Position(0, 0)); - world.Set(entity, new Velocity(10, 0)); - var movementSystem = new MovementSystem(); ((IContextAware)movementSystem).SetContext(_context!); - movementSystem.Initialize(); - _container.RegisterPlurality(movementSystem); + _container!.RegisterPlurality(movementSystem); - _container.Register(new List { movementSystem } as IReadOnlyList); + _ecsModule = new ArchEcsModule(enabled: true); + _ecsModule.Register(_container); + _ecsModule.Initialize(); - var runner = CreateRunner(); - runner.Start(); - runner.Update(1.0f); - runner.Update(1.0f); + _world = _container.Get(); + } - ref var pos = ref world.Get(entity); + [Test] + public void World_Destroy_Should_Be_Safe() + { + InitializeEcsModule(); + _world!.Create(new Position(0, 0)); + + Assert.DoesNotThrow(() => + { + World.Destroy(_world); + _world = null; + }); + } + + [Test] + public void World_CreateEntity_WithNoComponents_Should_Work() + { + InitializeEcsModule(); + var entity = _world!.Create(); + + Assert.That(_world.Size, Is.EqualTo(1)); + Assert.That(_world.IsAlive(entity), Is.True); + } + + [Test] + public void World_CreateEntity_WithMultipleComponents_Should_Work() + { + InitializeEcsModule(); + var entity = _world!.Create(new Position(0, 0), new Velocity(1, 1)); + + Assert.That(_world.Has(entity), Is.True); + Assert.That(_world.Has(entity), Is.True); + } + + [Test] + public void World_IsAlive_AfterDestroy_Should_ReturnFalse() + { + InitializeEcsModule(); + var entity = _world!.Create(new Position(0, 0)); + + Assert.That(_world.IsAlive(entity), Is.True); + + _world.Destroy(entity); + + Assert.That(_world.IsAlive(entity), Is.False); + } + + [Test] + public void ArchEcsModule_Update_Should_UpdateSystems() + { + InitializeEcsModule(); + + var entity = _world!.Create(new Position(0, 0), new Velocity(10, 5)); + + _ecsModule!.Update(1.0f); + + ref var pos = ref _world.Get(entity); + Assert.That(pos.X, Is.EqualTo(10).Within(0.001f)); + Assert.That(pos.Y, Is.EqualTo(5).Within(0.001f)); + } + + [Test] + public void ArchEcsModule_WithNoSystems_Should_NotThrow() + { + // 不注册任何系统 + _ecsModule = new ArchEcsModule(enabled: true); + _ecsModule.Register(_container!); + _ecsModule.Initialize(); + + _world = _container!.Get(); + + Assert.DoesNotThrow(() => { _ecsModule.Update(1.0f); }); + } + + [Test] + public void ChainedUpdates_Should_AccumulateChanges() + { + InitializeEcsModule(); + + var entity = _world!.Create(new Position(0, 0), new Velocity(10, 0)); + + _ecsModule!.Update(1.0f); + _ecsModule.Update(1.0f); + + ref var pos = ref _world.Get(entity); Assert.That(pos.X, Is.EqualTo(20).Within(0.001f), "Position should accumulate over multiple updates"); } - [Test] - public void InitializeEcs_CalledTwice_Should_BeIdempotent() - { - _ecsWorld = new EcsWorld(); - _container!.Register(_ecsWorld); - _container.Register(_ecsWorld); - - var ecsWorld1 = _context!.GetEcsWorld(); - var ecsWorld2 = _context.GetEcsWorld(); - - Assert.That(ecsWorld2, Is.SameAs(ecsWorld1), "Should return same world instance"); - } - - [Test] - public void GetEcsWorld_Should_ReturnIEcsWorld() - { - _ecsWorld = new EcsWorld(); - _container!.Register(_ecsWorld); - _container.Register(_ecsWorld); - - var ecsWorld = _context!.GetEcsWorld(); - - Assert.That(ecsWorld, Is.InstanceOf()); - Assert.That(ecsWorld, Is.InstanceOf()); - } - [Test] public void Component_AddAfterCreation_Should_Work() { - _ecsWorld = new EcsWorld(); - var entity = _ecsWorld.CreateEntity(Array.Empty()); - var world = _ecsWorld.InternalWorld; + InitializeEcsModule(); + var entity = _world!.Create(); - world.Add(entity, new Position(5, 10)); + _world.Add(entity, new Position(5, 10)); - Assert.That(world.Has(entity), Is.True); - ref var pos = ref world.Get(entity); + Assert.That(_world.Has(entity), Is.True); + ref var pos = ref _world.Get(entity); Assert.That(pos.X, Is.EqualTo(5)); Assert.That(pos.Y, Is.EqualTo(10)); } @@ -305,42 +160,57 @@ public class EcsAdvancedTests [Test] public void Component_Remove_Should_Work() { - _ecsWorld = new EcsWorld(); - var entity = _ecsWorld.CreateEntity(typeof(Position), typeof(Velocity)); - var world = _ecsWorld.InternalWorld; + InitializeEcsModule(); + var entity = _world!.Create(new Position(0, 0), new Velocity(1, 1)); - world.Remove(entity); + _world.Remove(entity); - Assert.That(world.Has(entity), Is.True); - Assert.That(world.Has(entity), Is.False); + Assert.That(_world.Has(entity), Is.True); + Assert.That(_world.Has(entity), Is.False); } [Test] public void Component_Replace_Should_Work() { - _ecsWorld = new EcsWorld(); - var entity = _ecsWorld.CreateEntity(typeof(Position)); - var world = _ecsWorld.InternalWorld; + InitializeEcsModule(); + var entity = _world!.Create(new Position(1, 1)); - world.Set(entity, new Position(1, 1)); - world.Set(entity, new Position(100, 200)); + _world.Set(entity, new Position(100, 200)); - ref var pos = ref world.Get(entity); + ref var pos = ref _world.Get(entity); Assert.That(pos.X, Is.EqualTo(100)); Assert.That(pos.Y, Is.EqualTo(200)); } -} -internal class OrderTrackingSystem(string name, int priority, List executionOrder) : EcsSystemBase -{ - public override int Priority { get; } = priority; - - protected override void OnEcsInit() + [Test] + public async Task ArchEcsModule_DestroyAsync_Should_CleanupResources() { + InitializeEcsModule(); + + _world!.Create(new Position(0, 0)); + Assert.That(_world.Size, Is.EqualTo(1)); + + await _ecsModule!.DestroyAsync(); + + // World 应该已经被销毁 + _world = null; } - public override void Update(float deltaTime) + [Test] + public void MultipleEntities_WithDifferentComponents_Should_CoExist() { - executionOrder.Add(name); + InitializeEcsModule(); + + var entity1 = _world!.Create(new Position(0, 0), new Velocity(1, 1)); + var entity2 = _world.Create(new Position(10, 10)); + var entity3 = _world.Create(new Velocity(5, 5)); + + Assert.That(_world.Size, Is.EqualTo(3)); + Assert.That(_world.Has(entity1), Is.True); + Assert.That(_world.Has(entity1), Is.True); + Assert.That(_world.Has(entity2), Is.True); + Assert.That(_world.Has(entity2), Is.False); + Assert.That(_world.Has(entity3), Is.False); + Assert.That(_world.Has(entity3), Is.True); } } \ No newline at end of file diff --git a/GFramework.Core.Tests/ecs/EcsBasicTests.cs b/GFramework.Core.Tests/ecs/EcsBasicTests.cs index 036345f..c1d9f52 100644 --- a/GFramework.Core.Tests/ecs/EcsBasicTests.cs +++ b/GFramework.Core.Tests/ecs/EcsBasicTests.cs @@ -1,229 +1,161 @@ using System.Diagnostics.CodeAnalysis; -using System.Reflection; using Arch.Core; -using GFramework.Core.Abstractions.ecs; +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 GFramework.Core.logging; using NUnit.Framework; namespace GFramework.Core.Tests.ecs; /// -/// ECS基础功能测试类,用于验证ECS系统的核心功能。 -/// 包括实体创建、组件设置、系统更新、实体销毁等基本操作。 +/// ECS 基础功能测试类 - 使用 Arch 原生 API /// [TestFixture] [Experimental("GFrameworkECS")] public class EcsBasicTests { - /// - /// 测试初始化方法,在每个测试方法执行前运行。 - /// 负责初始化日志工厂、依赖注入容器和架构上下文。 - /// [SetUp] public void Setup() { - LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider(); - _container = new MicrosoftDiContainer(); - var loggerField = typeof(MicrosoftDiContainer).GetField("_logger", - BindingFlags.NonPublic | BindingFlags.Instance); - loggerField?.SetValue(_container, - LoggerFactoryResolver.Provider.CreateLogger(nameof(EcsBasicTests))); - _context = new ArchitectureContext(_container); } - /// - /// 测试清理方法,在每个测试方法执行后运行。 - /// 负责释放ECS世界资源并清空容器和上下文。 - /// [TearDown] public void TearDown() { - _ecsWorld?.Dispose(); - _ecsWorld = null; + if (_world != null) + { + World.Destroy(_world); + _world = null; + } + _container?.Clear(); _context = null; + _ecsModule = null; } private MicrosoftDiContainer? _container; private ArchitectureContext? _context; - private EcsWorld? _ecsWorld; + private World? _world; + private ArchEcsModule? _ecsModule; /// - /// 初始化ECS系统并注册指定类型的系统实例。 + /// 初始化 ECS 模块 /// - /// 需要注册的系统类型数组 - private void InitializeEcsWithSystems(params Type[] systemTypes) + private void InitializeEcsModule() { - _ecsWorld = new EcsWorld(); - _container!.Register(_ecsWorld); - _container.Register(_ecsWorld as IEcsWorld); + // 注册系统(直接继承 ArchSystemAdapter,它继承自 AbstractSystem) + var movementSystem = new MovementSystem(); + ((IContextAware)movementSystem).SetContext(_context!); + _container!.RegisterPlurality(movementSystem); - var systems = new List(); - foreach (var systemType in systemTypes) - { - var system = (IEcsSystem)Activator.CreateInstance(systemType)!; - system.SetContext(_context!); - system.Initialize(); - systems.Add(system); - _container.RegisterPlurality(system); - } + // 创建并注册 ArchEcsModule + _ecsModule = new ArchEcsModule(enabled: true); + _ecsModule.Register(_container); + _ecsModule.Initialize(); - _container.Register(systems as IReadOnlyList); + // 获取 World + _world = _container.Get(); } - /// - /// 测试ECS初始化功能,验证是否能正确创建EcsWorld实例。 - /// [Test] - [Experimental("GFrameworkECS")] - public void Test_01_InitializeEcs_Should_Create_EcsWorld() + public void Test_01_InitializeEcs_Should_Create_World() { - _ecsWorld = new EcsWorld(); - _container!.Register(_ecsWorld); - _container.Register(_ecsWorld); + InitializeEcsModule(); - var ecsWorld = _context!.GetEcsWorld(); - - Assert.That(ecsWorld, Is.Not.Null, "EcsWorld should be created"); - Assert.That(ecsWorld.EntityCount, Is.EqualTo(0), "Initial entity count should be 0"); + Assert.That(_world, Is.Not.Null, "World should be created"); + Assert.That(_world!.Size, Is.EqualTo(0), "Initial entity count should be 0"); } - /// - /// 测试实体创建功能,验证能否成功创建带有指定组件的实体。 - /// [Test] public void Test_02_CreateEntity_Should_Work() { - _ecsWorld = new EcsWorld(); - var entity = _ecsWorld.CreateEntity(typeof(Position), typeof(Velocity)); + InitializeEcsModule(); - Assert.That(_ecsWorld.EntityCount, Is.EqualTo(1), "Entity count should be 1"); - Assert.That(_ecsWorld.IsAlive(entity), Is.True, "Entity should be alive"); + var entity = _world!.Create(new Position(0, 0), new Velocity(1, 1)); + + Assert.That(_world.Size, Is.EqualTo(1), "Entity count should be 1"); + Assert.That(_world.IsAlive(entity), Is.True, "Entity should be alive"); } - /// - /// 测试组件设置功能,验证能否正确存储和获取组件数据。 - /// [Test] public void Test_03_SetComponent_Should_Store_Data() { - _ecsWorld = new EcsWorld(); - var entity = _ecsWorld.CreateEntity(typeof(Position)); - var world = _ecsWorld.InternalWorld; + InitializeEcsModule(); - world.Set(entity, new Position(10, 20)); + var entity = _world!.Create(new Position(10, 20)); - Assert.That(world.Has(entity), Is.True, "Entity should have Position component"); - ref var pos = ref world.Get(entity); + Assert.That(_world.Has(entity), Is.True, "Entity should have Position component"); + ref var pos = ref _world.Get(entity); Assert.That(pos.X, Is.EqualTo(10), "Position.X should be 10"); Assert.That(pos.Y, Is.EqualTo(20), "Position.Y should be 20"); } - /// - /// 测试移动系统功能,验证系统能否正确更新实体位置。 - /// [Test] public void Test_04_MovementSystem_Should_Update_Position() { - InitializeEcsWithSystems(typeof(MovementSystem)); + InitializeEcsModule(); - var entity = _ecsWorld!.CreateEntity(typeof(Position), typeof(Velocity)); + var entity = _world!.Create(new Position(0, 0), new Velocity(10, 5)); - var world = _ecsWorld.InternalWorld; - world.Set(entity, new Position(0, 0)); - world.Set(entity, new Velocity(10, 5)); + // 更新系统 + _ecsModule!.Update(1.0f); - var systems = _container!.Get>(); - Assert.That(systems, Is.Not.Null); - Assert.That(systems!.Count, Is.GreaterThan(0)); - - var movementSystem = systems.First(s => s is MovementSystem) as MovementSystem; - Assert.That(movementSystem, Is.Not.Null); - - movementSystem!.Update(1.0f); - - ref var pos = ref world.Get(entity); + ref var pos = ref _world.Get(entity); Assert.That(pos.X, Is.EqualTo(10).Within(0.001f), "X position should be 10"); Assert.That(pos.Y, Is.EqualTo(5).Within(0.001f), "Y position should be 5"); } - /// - /// 测试实体销毁功能,验证能否正确销毁实体并更新实体计数。 - /// [Test] public void Test_05_DestroyEntity_Should_Work() { - _ecsWorld = new EcsWorld(); - var entity = _ecsWorld.CreateEntity(typeof(Position)); + InitializeEcsModule(); - _ecsWorld.DestroyEntity(entity); + var entity = _world!.Create(new Position(0, 0)); + _world.Destroy(entity); - Assert.That(_ecsWorld.EntityCount, Is.EqualTo(0), "Entity count should be 0"); - Assert.That(_ecsWorld.IsAlive(entity), Is.False, "Entity should not be alive"); + Assert.That(_world.Size, Is.EqualTo(0), "Entity count should be 0"); + Assert.That(_world.IsAlive(entity), Is.False, "Entity should not be alive"); } - /// - /// 测试世界清理功能,验证能否清除所有实体。 - /// [Test] public void Test_06_ClearWorld_Should_Remove_All_Entities() { - _ecsWorld = new EcsWorld(); + InitializeEcsModule(); + for (int i = 0; i < 10; i++) { - _ecsWorld.CreateEntity(typeof(Position)); + _world!.Create(new Position(0, 0)); } - _ecsWorld.Clear(); + _world!.Clear(); - Assert.That(_ecsWorld.EntityCount, Is.EqualTo(0), "Entity count should be 0 after clear"); + Assert.That(_world.Size, Is.EqualTo(0), "Entity count should be 0 after clear"); } - /// - /// 测试多个实体的批量更新功能,验证系统能否正确处理多个实体的更新。 - /// [Test] public void Test_07_Multiple_Entities_Should_Update_Correctly() { - InitializeEcsWithSystems(typeof(MovementSystem)); + InitializeEcsModule(); - var world = _ecsWorld!.InternalWorld; var entities = new Entity[10]; - for (var i = 0; i < 10; i++) { - entities[i] = _ecsWorld.CreateEntity(typeof(Position), typeof(Velocity)); - world.Set(entities[i], new Position(0, 0)); - world.Set(entities[i], new Velocity(i, i * 2)); + entities[i] = _world!.Create(new Position(0, 0), new Velocity(i, i * 2)); } - var systems = _container!.Get>(); - var movementSystem = systems!.First(s => s is MovementSystem) as MovementSystem; - - movementSystem!.Update(1.0f); + // 更新系统 + _ecsModule!.Update(1.0f); for (int i = 0; i < 10; i++) { - ref var pos = ref world.Get(entities[i]); + ref var pos = ref _world!.Get(entities[i]); Assert.That(pos.X, Is.EqualTo(i).Within(0.001f), $"Entity {i} X position should be {i}"); Assert.That(pos.Y, Is.EqualTo(i * 2).Within(0.001f), $"Entity {i} Y position should be {i * 2}"); } } - - /// - /// 测试未初始化情况下获取ECS世界的异常处理。 - /// - [Test] - public void Test_08_GetEcsWorld_Without_Initialize_Should_Throw() - { - Assert.Throws(() => { _context!.GetEcsWorld(); }, - "ECS World not initialized. Enable ECS in configuration."); - } } \ No newline at end of file diff --git a/GFramework.Core.Tests/ecs/EcsIntegrationTests.cs b/GFramework.Core.Tests/ecs/EcsIntegrationTests.cs index e670393..55287a9 100644 --- a/GFramework.Core.Tests/ecs/EcsIntegrationTests.cs +++ b/GFramework.Core.Tests/ecs/EcsIntegrationTests.cs @@ -1,358 +1,205 @@ using System.Diagnostics.CodeAnalysis; -using System.Reflection; using Arch.Core; -using GFramework.Core.Abstractions.ecs; +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 GFramework.Core.logging; using NUnit.Framework; namespace GFramework.Core.Tests.ecs; /// -/// ECS集成测试类,用于验证ECS系统的整体功能和性能表现。 -/// 包括实体管理、组件操作、系统调度、优先级控制以及性能基准测试。 +/// ECS 集成测试类 - 使用 Arch 原生 API /// [TestFixture] [Experimental("GFrameworkECS")] public class EcsIntegrationTests { - /// - /// 测试初始化方法,在每个测试方法执行前运行。 - /// 负责初始化日志工厂、依赖注入容器和架构上下文。 - /// [SetUp] public void Setup() { - LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider(); - _container = new MicrosoftDiContainer(); - var loggerField = typeof(MicrosoftDiContainer).GetField("_logger", - BindingFlags.NonPublic | BindingFlags.Instance); - loggerField?.SetValue(_container, - LoggerFactoryResolver.Provider.CreateLogger(nameof(EcsIntegrationTests))); - _context = new ArchitectureContext(_container); } - /// - /// 测试清理方法,在每个测试方法执行后运行。 - /// 负责释放ECS世界资源并清空容器和上下文。 - /// [TearDown] public void TearDown() { - _ecsWorld?.Dispose(); - _ecsWorld = null; + if (_world != null) + { + World.Destroy(_world); + _world = null; + } + _container?.Clear(); _context = null; + _ecsModule = null; } private MicrosoftDiContainer? _container; private ArchitectureContext? _context; - private EcsWorld? _ecsWorld; + private World? _world; + private ArchEcsModule? _ecsModule; /// - /// 初始化ECS系统并注册指定类型的系统实例。 + /// 初始化 ECS 模块 /// - /// 需要注册的系统类型数组 - private void InitializeEcsWithSystems(params Type[] systemTypes) + private void InitializeEcsModule() { - _ecsWorld = new EcsWorld(); - _container!.Register(_ecsWorld); - _container.Register(_ecsWorld as IEcsWorld); + // 注册系统 + var movementSystem = new MovementSystem(); + ((IContextAware)movementSystem).SetContext(_context!); + _container!.RegisterPlurality(movementSystem); - var systems = new List(); - foreach (var systemType in systemTypes) - { - var system = (IEcsSystem)Activator.CreateInstance(systemType)!; - system.SetContext(_context!); - system.Initialize(); - systems.Add(system); - _container.RegisterPlurality(system); - } + // 创建并注册 ArchEcsModule + _ecsModule = new ArchEcsModule(enabled: true); + _ecsModule.Register(_container); + _ecsModule.Initialize(); - _container.Register(systems as IReadOnlyList); + // 获取 World + _world = _container.Get(); } - /// - /// 测试ECS初始化功能,验证是否能正确创建EcsWorld实例。 - /// [Test] - public void InitializeEcs_Should_Create_EcsWorld() + public void InitializeEcs_Should_Create_World() { - _ecsWorld = new EcsWorld(); - _container!.Register(_ecsWorld); - _container.Register(_ecsWorld); + InitializeEcsModule(); - var ecsWorld = _context!.GetEcsWorld(); - - Assert.That(ecsWorld, Is.Not.Null); - Assert.That(ecsWorld.EntityCount, Is.EqualTo(0)); + Assert.That(_world, Is.Not.Null); + Assert.That(_world!.Size, Is.EqualTo(0)); } - /// - /// 测试实体创建功能,验证创建实体后实体计数是否正确增加。 - /// [Test] public void CreateEntity_Should_Increase_EntityCount() { - _ecsWorld = new EcsWorld(); - var entity = _ecsWorld.CreateEntity(typeof(Position), typeof(Velocity)); + InitializeEcsModule(); - Assert.That(_ecsWorld.EntityCount, Is.EqualTo(1)); - Assert.That(_ecsWorld.IsAlive(entity), Is.True); + var entity = _world!.Create(new Position(0, 0), new Velocity(1, 1)); + + Assert.That(_world.Size, Is.EqualTo(1)); + Assert.That(_world.IsAlive(entity), Is.True); } - /// - /// 测试实体销毁功能,验证销毁实体后实体计数是否正确减少。 - /// [Test] public void DestroyEntity_Should_Decrease_EntityCount() { - _ecsWorld = new EcsWorld(); - var entity = _ecsWorld.CreateEntity(typeof(Position)); + InitializeEcsModule(); - _ecsWorld.DestroyEntity(entity); + var entity = _world!.Create(new Position(0, 0)); + _world.Destroy(entity); - Assert.That(_ecsWorld.EntityCount, Is.EqualTo(0)); - Assert.That(_ecsWorld.IsAlive(entity), Is.False); + Assert.That(_world.Size, Is.EqualTo(0)); + Assert.That(_world.IsAlive(entity), Is.False); } - /// - /// 测试组件设置功能,验证能否正确存储和获取组件数据。 - /// [Test] public void SetComponent_Should_Store_ComponentData() { - _ecsWorld = new EcsWorld(); - var entity = _ecsWorld.CreateEntity(typeof(Position)); + InitializeEcsModule(); - var world = _ecsWorld.InternalWorld; - world.Set(entity, new Position(10, 20)); + var entity = _world!.Create(new Position(10, 20)); - Assert.That(world.Has(entity), Is.True); - ref var pos = ref world.Get(entity); + Assert.That(_world.Has(entity), Is.True); + ref var pos = ref _world.Get(entity); Assert.That(pos.X, Is.EqualTo(10)); Assert.That(pos.Y, Is.EqualTo(20)); } - /// - /// 测试世界清理功能,验证能否清除所有实体。 - /// [Test] public void ClearWorld_Should_Remove_All_Entities() { - _ecsWorld = new EcsWorld(); + InitializeEcsModule(); + for (int i = 0; i < 10; i++) { - _ecsWorld.CreateEntity(typeof(Position)); + _world!.Create(new Position(0, 0)); } - _ecsWorld.Clear(); + _world!.Clear(); - Assert.That(_ecsWorld.EntityCount, Is.EqualTo(0)); + Assert.That(_world.Size, Is.EqualTo(0)); } - /// - /// 测试ECS系统注册功能,验证系统能否正确添加到运行器中。 - /// [Test] - public void RegisterEcsSystem_Should_Add_System_To_Runner() + public void RegisterEcsSystem_Should_Add_System_To_Module() { - InitializeEcsWithSystems(typeof(MovementSystem)); + InitializeEcsModule(); - var systems = _container!.Get>(); - Assert.That(systems, Is.Not.Null); - Assert.That(systems!.Count, Is.EqualTo(1)); - Assert.That(systems[0], Is.InstanceOf()); + var adapters = _container!.GetAll>(); + Assert.That(adapters, Is.Not.Null); + Assert.That(adapters.Count, Is.EqualTo(1)); + Assert.That(adapters[0], Is.InstanceOf()); } - /// - /// 测试移动系统功能,验证系统能否正确更新单个实体的位置。 - /// [Test] public void MovementSystem_Should_Update_Position() { - InitializeEcsWithSystems(typeof(MovementSystem)); + InitializeEcsModule(); - var entity = _ecsWorld!.CreateEntity(typeof(Position), typeof(Velocity)); + var entity = _world!.Create(new Position(0, 0), new Velocity(10, 5)); - var world = _ecsWorld.InternalWorld; - world.Set(entity, new Position(0, 0)); - world.Set(entity, new Velocity(10, 5)); + _ecsModule!.Update(1.0f); - var systems = _container!.Get>(); - var movementSystem = systems!.First(s => s is MovementSystem) as MovementSystem; - - movementSystem!.Update(1.0f); - - ref var pos = ref world.Get(entity); + ref var pos = ref _world.Get(entity); Assert.That(pos.X, Is.EqualTo(10).Within(0.001f)); Assert.That(pos.Y, Is.EqualTo(5).Within(0.001f)); } - /// - /// 测试移动系统功能,验证系统能否正确批量更新多个实体的位置。 - /// [Test] public void MovementSystem_Should_Update_Multiple_Entities() { - InitializeEcsWithSystems(typeof(MovementSystem)); + InitializeEcsModule(); - var world = _ecsWorld!.InternalWorld; var entities = new Entity[100]; - for (var i = 0; i < 100; i++) { - entities[i] = _ecsWorld.CreateEntity(typeof(Position), typeof(Velocity)); - world.Set(entities[i], new Position(0, 0)); - world.Set(entities[i], new Velocity(i, i * 2)); + entities[i] = _world!.Create(new Position(0, 0), new Velocity(i, i * 2)); } - var systems = _container!.Get>(); - var movementSystem = systems!.First(s => s is MovementSystem) as MovementSystem; - - movementSystem!.Update(0.5f); + _ecsModule!.Update(0.5f); for (var i = 0; i < 100; i++) { - ref var pos = ref world.Get(entities[i]); + ref var pos = ref _world!.Get(entities[i]); Assert.That(pos.X, Is.EqualTo(i * 0.5f).Within(0.001f)); Assert.That(pos.Y, Is.EqualTo(i * 2 * 0.5f).Within(0.001f)); } } - /// - /// 测试ECS系统运行器的优先级调度功能,验证系统是否按优先级顺序执行。 - /// - [Test] - public void EcsSystemRunner_Should_Respect_Priority() - { - InitializeEcsWithSystems(typeof(LowPrioritySystem), typeof(HighPrioritySystem)); - - var systems = _container!.Get>(); - Assert.That(systems, Is.Not.Null); - Assert.That(systems!.Count, Is.EqualTo(2)); - - var sortedSystems = systems.OrderBy(s => s.Priority).ToList(); - Assert.That(sortedSystems[0], Is.InstanceOf()); - Assert.That(sortedSystems[1], Is.InstanceOf()); - } - - /// - /// 测试未初始化情况下获取ECS世界的异常处理。 - /// - [Test] - public void GetEcsWorld_Without_Initialize_Should_Throw() - { - Assert.Throws(() => { _context!.GetEcsWorld(); }, - "ECS World not initialized. Enable ECS in configuration."); - } - - /// - /// 性能基准测试:验证更新10000个实体的性能表现。 - /// [Test] public void Performance_Test_10000_Entities() { - InitializeEcsWithSystems(typeof(MovementSystem)); - - var world = _ecsWorld!.InternalWorld; + InitializeEcsModule(); for (int i = 0; i < 10000; i++) { - var entity = _ecsWorld.CreateEntity(typeof(Position), typeof(Velocity)); - world.Set(entity, new Position(0, 0)); - world.Set(entity, new Velocity(1, 1)); + _world!.Create(new Position(0, 0), new Velocity(1, 1)); } - var systems = _container!.Get>(); - var movementSystem = systems!.First(s => s is MovementSystem) as MovementSystem; - var startTime = DateTime.UtcNow; - movementSystem!.Update(0.016f); + _ecsModule!.Update(0.016f); var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds; - Assert.That(_ecsWorld.EntityCount, Is.EqualTo(10000)); + Assert.That(_world!.Size, Is.EqualTo(10000)); Assert.That(elapsed, Is.LessThan(100), $"Updating 10000 entities took: {elapsed}ms"); } - /// - /// 性能基准测试:验证创建1000个实体的性能表现。 - /// [Test] public void Performance_Test_1000_Entities_Creation() { - _ecsWorld = new EcsWorld(); - var world = _ecsWorld.InternalWorld; + InitializeEcsModule(); var startTime = DateTime.UtcNow; for (int i = 0; i < 1000; i++) { - var entity = _ecsWorld.CreateEntity(typeof(Position), typeof(Velocity)); - world.Set(entity, new Position(0, 0)); - world.Set(entity, new Velocity(1, 1)); + _world!.Create(new Position(0, 0), new Velocity(1, 1)); } var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds; - Assert.That(_ecsWorld.EntityCount, Is.EqualTo(1000)); + Assert.That(_world!.Size, Is.EqualTo(1000)); Assert.That(elapsed, Is.LessThan(50), $"Creating 1000 entities took: {elapsed}ms"); } -} - -/// -/// 高优先级系统示例,用于测试系统调度优先级功能。 -/// -public class HighPrioritySystem : EcsSystemBase -{ - /// - /// 获取系统优先级,数值越小优先级越高。 - /// - public override int Priority => -100; - - /// - /// ECS初始化回调方法。 - /// - protected override void OnEcsInit() - { - } - - /// - /// 系统更新方法。 - /// - /// 帧间隔时间 - public override void Update(float deltaTime) - { - } -} - -/// -/// 低优先级系统示例,用于测试系统调度优先级功能。 -/// -public class LowPrioritySystem : EcsSystemBase -{ - /// - /// 获取系统优先级,数值越大优先级越低。 - /// - public override int Priority => 100; - - /// - /// ECS初始化回调方法。 - /// - protected override void OnEcsInit() - { - } - - /// - /// 系统更新方法。 - /// - /// 帧间隔时间 - public override void Update(float deltaTime) - { - } } \ No newline at end of file diff --git a/GFramework.Core/ecs/systems/MovementSystem.cs b/GFramework.Core/ecs/systems/MovementSystem.cs index 3c33b44..897ca8b 100644 --- a/GFramework.Core/ecs/systems/MovementSystem.cs +++ b/GFramework.Core/ecs/systems/MovementSystem.cs @@ -4,7 +4,7 @@ using GFramework.Core.ecs.components; namespace GFramework.Core.ecs.systems; /// -/// 移动系统 - Arch 原生实现 +/// 移动系统 - 继承 ArchSystemAdapter /// 负责更新具有位置和速度组件的实体的位置 /// public sealed class MovementSystem : ArchSystemAdapter @@ -12,28 +12,27 @@ public sealed class MovementSystem : ArchSystemAdapter private QueryDescription _query; /// - /// 初始化系统 + /// Arch 系统初始化 /// - public void Initialize(World world) + protected override void OnArchInitialize() { // 创建查询:查找所有同时拥有Position和Velocity组件的实体 _query = new QueryDescription() .WithAll(); } - /// /// 系统更新方法,每帧调用一次 /// - /// ECS 世界 /// 帧间隔时间 - public void Update(World world, float deltaTime) + protected override void OnUpdate(in float deltaTime) { // 查询并更新所有符合条件的实体 - world.Query(in _query, (ref Position pos, ref Velocity vel) => + var f = deltaTime; + World.Query(in _query, (ref Position pos, ref Velocity vel) => { - pos.X += vel.X * deltaTime; - pos.Y += vel.Y * deltaTime; + pos.X += vel.X * f; + pos.Y += vel.Y * f; }); } } \ No newline at end of file