using NUnit.Framework; using FluentAssertions; using Encompass; using System; using System.Linq; using System.Collections.Generic; using Encompass.Exceptions; namespace Tests { struct MockComponent { public int myInt; } public class EngineTest { static MockComponent[] resultComponents; static MockComponent resultComponent; static MockMessage[] resultMessages; [Reads(typeof(MockComponent))] public class ReadComponentsTestEngine : Engine { public override void Update(double dt) { resultComponents = ReadComponents().ToArray(); } } static List<(MockComponent, Entity)> resultComponentsIncludingEntity = new List<(MockComponent, Entity)>(); static (MockComponent, Entity) resultComponentIncludingEntity; [Reads(typeof(MockComponent))] public class ReadComponentsIncludingEntityEngine : Engine { public override void Update(double dt) { foreach (ref readonly var entity in ReadEntities()) { ref readonly var mockComponent = ref GetComponent(entity); resultComponentsIncludingEntity.Add((mockComponent, entity)); } } } [Reads(typeof(MockComponent))] public class ReadComponentTestEngine : Engine { public override void Update(double dt) { resultComponent = ReadComponent(); } } [Reads(typeof(MockComponent))] public class ReadComponentIncludingEntityEngine : Engine { public override void Update(double dt) { ref readonly var entity = ref ReadEntity(); ref readonly var mockComponent = ref GetComponent(entity); resultComponentIncludingEntity = (mockComponent, entity); } } [Test] public void ReadComponents() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadComponentsTestEngine()); var entity = worldBuilder.CreateEntity(); var entityB = worldBuilder.CreateEntity(); MockComponent mockComponent; mockComponent.myInt = 2; MockComponent mockComponentB; mockComponentB.myInt = 1; worldBuilder.SetComponent(entity, mockComponent); worldBuilder.SetComponent(entityB, mockComponentB); var world = worldBuilder.Build(); world.Update(0.01f); resultComponents.Should().Contain(mockComponent); resultComponents.Should().Contain(mockComponentB); } [Test] public void ReadComponentsIncludingEntity() { resultComponentsIncludingEntity.Clear(); var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadComponentsIncludingEntityEngine()); var entity = worldBuilder.CreateEntity(); var entityB = worldBuilder.CreateEntity(); MockComponent mockComponent; mockComponent.myInt = 2; MockComponent mockComponentB; mockComponentB.myInt = 1; worldBuilder.SetComponent(entity, mockComponent); worldBuilder.SetComponent(entityB, mockComponentB); var world = worldBuilder.Build(); world.Update(0.01f); var resultComponentValues = resultComponentsIncludingEntity.Select((kv) => kv.Item1); resultComponentValues.Should().Contain(mockComponent); resultComponentValues.Should().Contain(mockComponentB); var resultEntities = resultComponentsIncludingEntity.Select((kv) => kv.Item2); resultEntities.Should().Contain(entity); resultEntities.Should().Contain(entityB); resultComponentsIncludingEntity.Should().Contain((mockComponent, entity)); resultComponentsIncludingEntity.Should().Contain((mockComponentB, entityB)); } [Test] public void ReadComponent() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadComponentTestEngine()); var entity = worldBuilder.CreateEntity(); MockComponent mockComponent; mockComponent.myInt = 3; worldBuilder.SetComponent(entity, mockComponent); var world = worldBuilder.Build(); world.Update(0.01f); Assert.AreEqual(mockComponent, resultComponent); } [Test] public void ReadComponentWhenMultipleComponents() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadComponentTestEngine()); var entity = worldBuilder.CreateEntity(); var entityB = worldBuilder.CreateEntity(); MockComponent mockComponent; mockComponent.myInt = 2; MockComponent mockComponentB; mockComponentB.myInt = 1; worldBuilder.SetComponent(entity, mockComponent); worldBuilder.SetComponent(entityB, mockComponentB); var world = worldBuilder.Build(); world.Update(0.01); Assert.That(resultComponent, Is.EqualTo(mockComponent).Or.EqualTo(mockComponentB)); } [Test] public void ReadComponentWithEntity() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadComponentIncludingEntityEngine()); var entity = worldBuilder.CreateEntity(); MockComponent mockComponent; mockComponent.myInt = 2; worldBuilder.SetComponent(entity, mockComponent); var world = worldBuilder.Build(); world.Update(0.01f); (mockComponent, entity).Should().BeEquivalentTo(resultComponentIncludingEntity); } [Reads(typeof(MockComponent))] [Writes(typeof(MockComponent))] public class UpdateComponentTestEngine : Engine { public override void Update(double dt) { ref readonly var entity = ref ReadEntity(); SetComponent(entity, new MockComponent { myInt = 420 }); } } // this test needs to be improved... [Test] public void UpdateComponent() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new UpdateComponentTestEngine()); worldBuilder.AddEngine(new ReadComponentTestEngine()); var entity = worldBuilder.CreateEntity(); MockComponent mockComponent; mockComponent.myInt = 3; worldBuilder.SetComponent(entity, mockComponent); var world = worldBuilder.Build(); world.Update(0.01); world.Update(0.01); Assert.AreEqual(420, resultComponent.myInt); } [Reads(typeof(MockComponent))] public class UndeclaredUpdateComponentTestEngine : Engine { public override void Update(double dt) { ref readonly var entity = ref ReadEntity(); SetComponent(entity, new MockComponent { myInt = 420 }); } } [Test] public void UpdateUndeclaredComponent() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new UndeclaredUpdateComponentTestEngine()); var entity = worldBuilder.CreateEntity(); MockComponent mockComponent; mockComponent.myInt = 3; worldBuilder.SetComponent(entity, mockComponent); var world = worldBuilder.Build(); var ex = Assert.Throws(() => world.Update(0.01f)); Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredUpdateComponentTestEngine tried to update undeclared Component MockComponent")); } struct MockMessage : IMessage { public string myString; } [Sends(typeof(MockMessage))] public class MessageEmitEngine : Engine { public override void Update(double dt) { MockMessage message; message.myString = "howdy"; this.SendMessage(message); } } [Receives(typeof(MockMessage))] public class MessageReadEngine : Engine { public override void Update(double dt) { resultMessages = ReadMessages().ToArray(); } } [Test] public void EmitAndReadMessage() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new MessageEmitEngine()); worldBuilder.AddEngine(new MessageReadEngine()); var world = worldBuilder.Build(); world.Update(0.01f); Assert.AreEqual(resultMessages.First().myString, "howdy"); } public class UndeclaredMessageEmitEngine : Engine { public override void Update(double dt) { MockMessage message; message.myString = "howdy"; this.SendMessage(message); } } static MockMessage[] emptyReadMessagesResult; [Receives(typeof(MockMessage))] class ReadMessagesWhenNoneExistEngine : Engine { public override void Update(double dt) { emptyReadMessagesResult = ReadMessages().ToArray(); } } [Test] public void ReadMessagesWhenNoneHaveBeenEmitted() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadMessagesWhenNoneExistEngine()); var world = worldBuilder.Build(); world.Update(0.01f); emptyReadMessagesResult.Should().BeEmpty(); } [Test] public void EmitUndeclaredMessage() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new UndeclaredMessageEmitEngine()); var world = worldBuilder.Build(); var ex = Assert.Throws(() => world.Update(0.01f)); Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredMessageEmitEngine tried to send undeclared Message MockMessage")); } static bool someTest; [Sends(typeof(MockMessage))] class EmitMockMessageEngine : Engine { public override void Update(double dt) { MockMessage message; message.myString = "howdy"; this.SendMessage(message); } } [Receives(typeof(MockMessage))] class SomeMessageTestEngine : Engine { public override void Update(double dt) { someTest = this.SomeMessage(); } } [Test] public void SomeMessage() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new EmitMockMessageEngine()); worldBuilder.AddEngine(new SomeMessageTestEngine()); var world = worldBuilder.Build(); world.Update(0.01f); Assert.That(someTest, Is.True); } class UndeclaredSomeMessageEngine : Engine { public override void Update(double dt) { someTest = this.SomeMessage(); } } [Test] public void UndeclaredSomeMessage() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new EmitMockMessageEngine()); worldBuilder.AddEngine(new UndeclaredSomeMessageEngine()); var world = worldBuilder.Build(); Assert.Throws(() => world.Update(0.01f)); } struct EntityMessage : IMessage, IHasEntity { public EntityMessage(Entity entity, int myInt) { Entity = entity; MyInt = myInt; } public Entity Entity { get; } public int MyInt { get; } } [Sends(typeof(EntityMessage), typeof(MockMessage))] class EntityMessageEmitterEngine : Engine { private Entity _entity; public EntityMessageEmitterEngine(Entity entity) { _entity = entity; } public override void Update(double dt) { SendMessage(new EntityMessage(_entity, 2)); SendMessage(new EntityMessage(_entity, 4)); SendMessage(new EntityMessage(_entity, 5)); SendMessage(new MockMessage()); } } static List entityMessageResults; [Receives(typeof(EntityMessage))] class EntityMessageReceiverEngine : Engine { private Entity _entity; public EntityMessageReceiverEngine(Entity entity) { _entity = entity; } public override void Update(double dt) { entityMessageResults = ReadMessagesWithEntity(_entity).ToList(); } } [Test] public void MessagesWithEntity() { var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); worldBuilder.AddEngine(new EntityMessageEmitterEngine(entity)); worldBuilder.AddEngine(new EntityMessageReceiverEngine(entity)); var world = worldBuilder.Build(); world.Update(0.01); entityMessageResults.Should().HaveCount(3); entityMessageResults.Should().ContainEquivalentOf(new EntityMessage(entity, 2)); entityMessageResults.Should().ContainEquivalentOf(new EntityMessage(entity, 4)); entityMessageResults.Should().ContainEquivalentOf(new EntityMessage(entity, 5)); } [Test] public void NoMessagesWithEntity() { var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); worldBuilder.AddEngine(new EntityMessageReceiverEngine(entity)); var world = worldBuilder.Build(); world.Update(0.01); entityMessageResults.Should().BeEmpty(); } [Sends(typeof(EntityMessage), typeof(MockMessage))] class EntityMessageSingularEmitterEngine : Engine { private Entity _entity; public EntityMessageSingularEmitterEngine(Entity entity) { _entity = entity; } public override void Update(double dt) { SendMessage(new EntityMessage(_entity, 2)); SendMessage(new MockMessage()); } } static EntityMessage entityMessageResult; [Receives(typeof(EntityMessage))] class SingularMessageWithEntityEngine : Engine { private Entity _entity; public SingularMessageWithEntityEngine(Entity entity) { _entity = entity; } public override void Update(double dt) { entityMessageResult = ReadMessageWithEntity(_entity); } } [Test] public void MessageWithEntity() { var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); worldBuilder.AddEngine(new EntityMessageSingularEmitterEngine(entity)); worldBuilder.AddEngine(new SingularMessageWithEntityEngine(entity)); var world = worldBuilder.Build(); world.Update(0.01); entityMessageResult.Should().Be(new EntityMessage(entity, 2)); } class SomeComponentTestEngine : Engine { public override void Update(double dt) { Assert.IsTrue(SomeComponent()); } } [Test] public void SomeComponent() { var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent()); var world = worldBuilder.Build(); world.Update(0.01); } static (MockComponent, Entity) pairA; static (MockComponent, Entity) pairB; [Reads(typeof(MockComponent))] class SameValueComponentReadEngine : Engine { public override void Update(double dt) { var entities = ReadEntities(); pairA = (GetComponent(entities[0]), entities[0]); pairB = (GetComponent(entities[1]), entities[1]); } } // Tests that components with identical values should be distinguishable by their entities [Test] public void SameValueComponents() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new SameValueComponentReadEngine()); MockComponent componentA; componentA.myInt = 20; MockComponent componentB; componentB.myInt = 20; var entity = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, componentA); var entityB = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entityB, componentB); var world = worldBuilder.Build(); world.Update(0.01f); Assert.That(EngineTest.pairA, Is.Not.EqualTo(EngineTest.pairB)); Assert.That(EngineTest.pairA.Item1, Is.EqualTo(EngineTest.pairB.Item1)); } [Reads(typeof(MockComponent))] class ReadEmptyMockComponentsEngine : Engine { public override void Update(double dt) { ReadEntities().ToArray().Should().BeEmpty(); } } [Test] public void ReadComponentsOfTypeWhereNoneExist() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadEmptyMockComponentsEngine()); var world = worldBuilder.Build(); world.Update(0.01f); } struct DestroyerComponent { } [Reads(typeof(DestroyerComponent))] class DestroyerEngine : Engine { public override void Update(double dt) { foreach (ref readonly var entity in ReadEntities()) { Destroy(entity); } } } static List<(MockComponent, Entity)> results = new List<(MockComponent, Entity)>(); [Reads(typeof(MockComponent))] class ReaderEngine : Engine { public override void Update(double dt) { results.Clear(); foreach (ref readonly var entity in ReadEntities()) { ref readonly var mockComponent = ref GetComponent(entity); results.Add((mockComponent, entity)); } } } [Test] public void DestroyEntity() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new DestroyerEngine()); worldBuilder.AddEngine(new ReaderEngine()); var entity = worldBuilder.CreateEntity(); var entityB = worldBuilder.CreateEntity(); var entityC = worldBuilder.CreateEntity(); DestroyerComponent destroyerComponent; MockComponent mockComponent; mockComponent.myInt = 2; worldBuilder.SetComponent(entity, destroyerComponent); worldBuilder.SetComponent(entity, mockComponent); worldBuilder.SetComponent(entityB, destroyerComponent); worldBuilder.SetComponent(entityB, mockComponent); worldBuilder.SetComponent(entityC, mockComponent); var world = worldBuilder.Build(); world.Update(0.01); world.Update(0.01); Assert.That(results, Does.Not.Contain((mockComponent, entity))); Assert.That(results, Does.Not.Contain((mockComponent, entityB))); Assert.That(results, Does.Contain((mockComponent, entityC))); } [Receives(typeof(DestroyComponentMessage))] class DestroyEntityEngine : Engine { public override void Update(double dt) { foreach (ref readonly var message in ReadMessages()) { Destroy(message.entity); } } } [Test] public void DestroyEntityWithoutID() { results.Clear(); var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new AddComponentEngine()); worldBuilder.AddEngine(new DestroyEntityEngine()); worldBuilder.AddEngine(new ReaderEngine()); var mockComponent = new MockComponent { }; var entity = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, mockComponent); worldBuilder.SendMessage(new DestroyComponentMessage { entity = entity }); var world = worldBuilder.Build(); world.Update(0.01); Assert.DoesNotThrow(() => world.Update(0.01)); Assert.That(results, Does.Not.Contain((mockComponent, entity))); } [Reads(typeof(DestroyerComponent))] [Writes(typeof(MockComponent))] class DestroyAndAddComponentEngine : Engine { public override void Update(double dt) { foreach (ref readonly var entity in ReadEntities()) { RemoveComponent(entity); Destroy(entity); } } } [Test] public void DestroyEntityWhileRemovingComponent() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new DestroyAndAddComponentEngine()); worldBuilder.AddEngine(new ReaderEngine()); var entity = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new DestroyerComponent()); worldBuilder.SetComponent(entity, new MockComponent()); var world = worldBuilder.Build(); Assert.DoesNotThrow(() => world.Update(0.01)); } [Reads(typeof(MockComponent))] [WritesImmediate(typeof(MockComponent))] [Writes(typeof(MockComponent))] class AddAndRemoveMockComponentEngine : Engine { public override void Update(double dt) { foreach (ref readonly var entity in ReadEntities()) { RemoveComponent(entity); SetComponent(entity, new MockComponent()); } } } static Entity entityResult; [ReadsImmediate(typeof(MockComponent))] class GetEntityFromImmediateReadComponents : Engine { public override void Update(double dt) { ref readonly var entity = ref ReadEntity(); } } [Test] public void GetEntityFromImmediateComponentID() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new AddAndRemoveMockComponentEngine()); worldBuilder.AddEngine(new GetEntityFromImmediateReadComponents()); var entity = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent()); var world = worldBuilder.Build(); Assert.DoesNotThrow(() => world.Update(0.01)); } [Reads(typeof(MockComponent))] [Writes(typeof(MockComponent))] class DelayedMessageEngine : Engine { public override void Update(double dt) { foreach (ref readonly var entity in ReadEntities()) { RemoveComponent(entity); SendMessage(new MockMessage { }, 1); } } } [Test] public void EngineSendMessageDelayed() { Array.Clear(resultMessages, 0, resultMessages.Length); var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ActivateTimeDilationEngine()); worldBuilder.AddEngine(new DelayedMessageEngine()); worldBuilder.AddEngine(new MessageReadEngine()); var entity = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent { }); var world = worldBuilder.Build(); world.Update(0.01); resultMessages.Should().BeEmpty(); world.Update(0.5); resultMessages.Should().BeEmpty(); world.Update(0.5); resultMessages.Should().BeEmpty(); world.Update(2); resultMessages.Should().NotBeEmpty(); resultMessages.First().Should().BeOfType(); } [Reads(typeof(MockComponent))] [Writes(typeof(MockComponent))] class DelayedMessageIgnoringTimeDilationEngine : Engine { public override void Update(double dt) { foreach (ref readonly var entity in ReadEntities()) { RemoveComponent(entity); SendMessageIgnoringTimeDilation(new MockMessage { }, 1); } } } [Test] public void EngineSendMessageDelayedIgnoringTimeDilation() { Array.Clear(resultMessages, 0, resultMessages.Length); var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ActivateTimeDilationEngine()); worldBuilder.AddEngine(new DelayedMessageIgnoringTimeDilationEngine()); worldBuilder.AddEngine(new MessageReadEngine()); var entity = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent { }); var world = worldBuilder.Build(); world.Update(0.01); resultMessages.Should().BeEmpty(); world.Update(0.5); resultMessages.Should().BeEmpty(); world.Update(0.5); resultMessages.Should().NotBeEmpty(); resultMessages.First().Should().BeOfType(); } [Receives(typeof(MockMessage))] [WritesImmediate(typeof(MockComponent))] [Writes(typeof(MockComponent), 1)] class ActivateComponentEngine : Engine { public override void Update(double dt) { foreach (ref readonly var message in ReadMessages()) { var entity = CreateEntity(); SetComponent(entity, new MockComponent { }); } } } [ReadsImmediate(typeof(MockComponent))] [Writes(typeof(MockComponent), 0)] class RemoveComponentEngine : Engine { public override void Update(double dt) { foreach (var entity in ReadEntities()) { RemoveComponent(entity); } } } [Test] public void EngineAddAndRemoveComponentSameFrameWithRemovePriority() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ActivateComponentEngine()); worldBuilder.AddEngine(new RemoveComponentEngine()); worldBuilder.AddEngine(new ReadComponentsTestEngine()); worldBuilder.SendMessage(new MockMessage { }); var world = worldBuilder.Build(); Assert.DoesNotThrow(() => world.Update(0.01)); world.Update(0.01); // update again for the read resultComponents.Should().BeEmpty(); } struct DestroyComponentMessage : IMessage { public Entity entity; } [Reads(typeof(MockComponent))] [Writes(typeof(MockComponent))] class AddComponentEngine : Engine { public override void Update(double dt) { foreach (var entity in ReadEntities()) { SetComponent(entity, new MockComponent { }); } } } static Entity readEntity; [Reads(typeof(MockComponent))] class ReadEntityByComponentTypeEngine : Engine { public override void Update(double dt) { readEntity = ReadEntity(); } } [Test] public void GetEntityByComponentType() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadEntityByComponentTypeEngine()); var entity = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent()); var world = worldBuilder.Build(); world.Update(0.01); entity.Should().BeEquivalentTo(readEntity); } struct MockComponentB { private int value; public MockComponentB(int value) { this.value = value; } } static MockComponentB getComponentResult; [Reads(typeof(MockComponent), typeof(MockComponentB))] class GetComponentEngine : Engine { public override void Update(double dt) { getComponentResult = GetComponent(ReadEntity()); } } [Test] public void GetComponent() { var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent()); worldBuilder.SetComponent(entity, new MockComponentB(3)); worldBuilder.AddEngine(new GetComponentEngine()); var world = worldBuilder.Build(); world.Update(0.01); getComponentResult.Should().BeEquivalentTo(new MockComponentB(3)); } [Reads(typeof(MockComponent), typeof(MockComponentB))] class GetComponentExceptionEngine : Engine { public override void Update(double dt) { foreach (var entity in ReadEntities()) { GetComponent(entity); } } } [Test] public void GetComponentWhenComponentIsNotOnEntity() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new GetComponentExceptionEngine()); var entity = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent()); var world = worldBuilder.Build(); Assert.Throws(() => world.Update(0.01)); } static Entity[] readEntities; [Reads(typeof(MockComponent))] class ReadEntitiesWithComponentTypeEngine : Engine { public override void Update(double dt) { readEntities = ReadEntities().ToArray(); } } [Test] public void ReadEntities() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadEntitiesWithComponentTypeEngine()); worldBuilder.AddEngine(new DestroyAllWithEngine()); var entity = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent { }); var entityB = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entityB, new MockComponent { }); var world = worldBuilder.Build(); world.Update(0.01); readEntities.Should().Contain(entity); readEntities.Should().Contain(entityB); } [Reads(typeof(MockComponent))] class DestroyWithEngine : Engine { public override void Update(double dt) { if (SomeComponent()) { DestroyWith(); } } } [Test] public void DestroyWith() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadEntitiesWithComponentTypeEngine()); worldBuilder.AddEngine(new DestroyWithEngine()); var entity = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent { }); var world = worldBuilder.Build(); world.Update(0.01); world.Update(0.01); // update twice so the read happens after destroy readEntities.Should().BeEmpty(); } [Reads(typeof(MockComponent))] class DestroyAllWithEngine : Engine { public override void Update(double dt) { DestroyAllWith(); } } [Test] public void DestroyAllWith() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadEntitiesWithComponentTypeEngine()); worldBuilder.AddEngine(new DestroyAllWithEngine()); var entity = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent { }); var entityB = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entityB, new MockComponent { }); var world = worldBuilder.Build(); world.Update(0.01); world.Update(0.01); // update twice so the read happens after destroy readEntities.Should().BeEmpty(); } [Reads(typeof(MockComponent))] [Writes(typeof(MockComponent))] class RemoveComponentByTypeEngine : Engine { public override void Update(double dt) { foreach (ref readonly var entity in ReadEntities()) { RemoveComponent(entity); } } } [Test] public void RemoveComponentByType() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadComponentsTestEngine()); worldBuilder.AddEngine(new RemoveComponentByTypeEngine()); var entity = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent { }); var entityB = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent { }); var world = worldBuilder.Build(); world.Update(0.01); world.Update(0.01); resultComponents.Should().BeEmpty(); } static double dilatedDeltaTime; class ActivateTimeDilationEngine : Engine { public override void Update(double dt) { if (!TimeDilationActive) { ActivateTimeDilation(0.2, 1, 1, 1); } else { dilatedDeltaTime = dt; } } } [Test] public void ActivateTimeDilation() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ActivateTimeDilationEngine()); var world = worldBuilder.Build(); world.Update(0.01); // activate time dilation world.Update(0.5); dilatedDeltaTime.Should().BeApproximately(0.3, 0.01); world.Update(0.5); dilatedDeltaTime.Should().BeApproximately(0.1, 0.01); world.Update(1); world.Update(0.5); dilatedDeltaTime.Should().BeApproximately(0.3, 0.01); } class ActivateTimeDilationLowerFactorEngine : Engine { private bool activated = false; public override void Update(double dt) { if (!activated) { ActivateTimeDilation(0.2, 1, 1, 1); activated = true; } } } class ActivateTimeDilationHigherFactorEngine : Engine { private bool activated = false; public override void Update(double dt) { if (!activated) { ActivateTimeDilation(0.5, 1, 1, 1); activated = true; } } } static bool timeDilationActive; class ReadDilatedDeltaTimeEngine : Engine { public override void Update(double dt) { dilatedDeltaTime = dt; timeDilationActive = TimeDilationActive; } } [Test] public void MultipleActivateTimeDilation() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadDilatedDeltaTimeEngine()); worldBuilder.AddEngine(new ActivateTimeDilationLowerFactorEngine()); worldBuilder.AddEngine(new ActivateTimeDilationHigherFactorEngine()); var world = worldBuilder.Build(); world.Update(0.01); // activate time dilation world.Update(0.5); // 0.3 and 0.375 dilatedDeltaTime.Should().BeApproximately(0.3375, 0.01); timeDilationActive.Should().BeTrue(); world.Update(5); dilatedDeltaTime.Should().BeApproximately(5, 0.01); timeDilationActive.Should().BeFalse(); } static double undilatedDeltaTime; [IgnoresTimeDilation] class IgnoresTimeDilationEngine : Engine { public override void Update(double dt) { undilatedDeltaTime = dt; } } [Test] public void IgnoresTimeDilation() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ActivateTimeDilationEngine()); worldBuilder.AddEngine(new IgnoresTimeDilationEngine()); var world = worldBuilder.Build(); world.Update(0.01); // activate time dilation world.Update(0.5); undilatedDeltaTime.Should().Be(0.5); } class AddComponentWithoutPriorityEngine : Engine { public override void Update(double dt) { var entity = CreateEntity(); AddComponent(entity, new MockComponent()); var entityB = CreateEntity(); AddComponent(entityB, new MockComponent()); } } [Test] public void AddComponent() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new AddComponentWithoutPriorityEngine()); worldBuilder.AddEngine(new ReadComponentsTestEngine()); var world = worldBuilder.Build(); world.Update(0.01); world.Update(0.01); resultComponents.Should().HaveCount(2); world.Update(0.01); resultComponents.Should().HaveCount(4); } [Reads(typeof(MockComponent))] class AddComponentToPreviouslyExistingEntityEngine : Engine { public override void Update(double dt) { ref readonly var entity = ref ReadEntity(); AddComponent(entity, new MockComponent()); } } [Test] public void AddComponentToPreviouslyExistingEntityTest() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new AddComponentToPreviouslyExistingEntityEngine()); var entity = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent()); var world = worldBuilder.Build(); Assert.Throws(() => world.Update(0.01)); } [WritesImmediate(typeof(MockComponentB))] class AddImmediateComponentEngine : Engine { public override void Update(double dt) { var entity = CreateEntity(); AddComponent(entity, new MockComponentB(5)); } } [ReadsImmediate(typeof(MockComponentB))] class ReadImmediateComponentEngine : Engine { public override void Update(double dt) { ref readonly var component = ref ReadComponent(); getComponentResult = component; } } [Test] public void AddImmediateComponentTest() { getComponentResult = default(MockComponentB); var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new AddImmediateComponentEngine()); worldBuilder.AddEngine(new ReadImmediateComponentEngine()); var world = worldBuilder.Build(); world.Update(0.01); getComponentResult.Should().Be(new MockComponentB(5)); } static bool entityExistsResult; class EntityExistsEngine : Engine { private int _id; public EntityExistsEngine(int id) { _id = id; } public override void Update(double dt) { entityExistsResult = EntityExists(_id); } } [Test] public void PruneEmptyEntities() { var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); var id = entity.ID; var world = worldBuilder.Build(); world.Update(0.01); entityExistsResult.Should().BeFalse(); } public class QueryTests { struct MockComponentB { } struct MockComponentC { } struct MockComponentD { } [Reads(typeof(MockComponent), typeof(MockComponentB))] [Writes(typeof(MockComponentB))] [QueryWith(typeof(MockComponent), typeof(MockComponentB))] class EntityQueryWithComponentsEngine : Engine { private List entities; public EntityQueryWithComponentsEngine(List entities) { this.entities = entities; } public override void Update(double dt) { entities.Clear(); foreach (var entity in TrackedEntities) { entities.Add(entity); RemoveComponent(entity); } } } [Test] public void EntitiesWithComponents() { var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); var entityB = worldBuilder.CreateEntity(); var entityC = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent()); worldBuilder.SetComponent(entity, new MockComponentB()); worldBuilder.SetComponent(entityB, new MockComponent()); worldBuilder.SetComponent(entityB, new MockComponentB()); worldBuilder.SetComponent(entityC, new MockComponentB()); var queriedEntities = new List(); worldBuilder.AddEngine(new EntityQueryWithComponentsEngine(queriedEntities)); var world = worldBuilder.Build(); world.Update(0.01); queriedEntities.Should().BeEquivalentTo(new Entity[] { entity, entityB }); world.Update(0.01); queriedEntities.Should().BeEmpty(); } [Reads(typeof(MockComponent))] [Writes(typeof(MockComponent))] [QueryWithout(typeof(MockComponent))] class EntityQueryWithoutComponentsEngine : Engine { private List entities; public EntityQueryWithoutComponentsEngine(List entities) { this.entities = entities; } public override void Update(double dt) { entities.Clear(); foreach (var entity in TrackedEntities) { entities.Add(entity); SetComponent(entity, new MockComponent()); } } } [Test] public void EntitiesWithoutComponents() { var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); var entityB = worldBuilder.CreateEntity(); var entityC = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent()); worldBuilder.SetComponent(entity, new MockComponentB()); worldBuilder.SetComponent(entityB, new MockComponent()); worldBuilder.SetComponent(entityB, new MockComponentB()); worldBuilder.SetComponent(entityC, new MockComponentB()); var queriedEntities = new List(); worldBuilder.AddEngine(new EntityQueryWithoutComponentsEngine(queriedEntities)); var world = worldBuilder.Build(); world.Update(0.01); queriedEntities.ToArray().Should().BeEquivalentTo(new Entity[] { entityC }); world.Update(0.01); queriedEntities.Should().BeEmpty(); } [Reads(typeof(MockComponent), typeof(MockComponentB), typeof(MockComponentD))] [QueryWith(typeof(MockComponent), typeof(MockComponentB))] [QueryWithout(typeof(MockComponentD))] class EntityQueryWithandWithoutComponentsEngine : Engine { private List entities; public EntityQueryWithandWithoutComponentsEngine(List entities) { this.entities = entities; } public override void Update(double dt) { entities.Clear(); entities.AddRange(TrackedEntities); } } [Test] public void EntitiesWithAndWithoutComponents() { var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); var entityB = worldBuilder.CreateEntity(); var entityC = worldBuilder.CreateEntity(); var entityD = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent()); worldBuilder.SetComponent(entity, new MockComponentB()); worldBuilder.SetComponent(entity, new MockComponentD()); worldBuilder.SetComponent(entityB, new MockComponent()); worldBuilder.SetComponent(entityC, new MockComponent()); worldBuilder.SetComponent(entityC, new MockComponentB()); worldBuilder.SetComponent(entityC, new MockComponentC()); worldBuilder.SetComponent(entityC, new MockComponentD()); worldBuilder.SetComponent(entityD, new MockComponent()); worldBuilder.SetComponent(entityD, new MockComponentB()); worldBuilder.SetComponent(entityD, new MockComponentC()); var queriedEntities = new List(); worldBuilder.AddEngine(new EntityQueryWithandWithoutComponentsEngine(queriedEntities)); var world = worldBuilder.Build(); world.Update(0.01); queriedEntities.ToArray().Should().BeEquivalentTo(new Entity[] { entityD }); } [Reads(typeof(MockComponent))] [WritesImmediate(typeof(MockComponentB))] [Writes(typeof(MockComponentB), 0)] class AddImmediateComponentEngine : Engine { public override void Update(double dt) { foreach (var entity in ReadEntities()) { SetComponent(entity, new MockComponentB()); } } } [ReadsImmediate(typeof(MockComponentB))] [QueryWith(typeof(MockComponentB))] class EntityQueryWithImmediateComponentsEngine : Engine { private List entities; public EntityQueryWithImmediateComponentsEngine(List entities) { this.entities = entities; } public override void Update(double dt) { entities.Clear(); entities.AddRange(TrackedEntities); } } [Test] public void EntitiesWithImmediateComponents() { var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); var entityB = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent()); var queriedEntities = new List(); worldBuilder.AddEngine(new AddImmediateComponentEngine()); worldBuilder.AddEngine(new EntityQueryWithImmediateComponentsEngine(queriedEntities)); var world = worldBuilder.Build(); world.Update(0.01); queriedEntities.ToArray().Should().BeEquivalentTo(new Entity[] { entity }); } [ReadsImmediate(typeof(MockComponentB))] [QueryWithout(typeof(MockComponentB))] class EntityQueryWithoutImmediateComponentsEngine : Engine { private List entities; public EntityQueryWithoutImmediateComponentsEngine(List entities) { this.entities = entities; } public override void Update(double dt) { entities.Clear(); entities.AddRange(TrackedEntities); } } [Test] public void EntitiesWithoutImmediateComponents() { var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); var entityB = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent()); var queriedEntities = new List(); worldBuilder.AddEngine(new AddImmediateComponentEngine()); worldBuilder.AddEngine(new EntityQueryWithoutImmediateComponentsEngine(queriedEntities)); var world = worldBuilder.Build(); world.Update(0.01); queriedEntities.ToArray().Should().BeEquivalentTo(new Entity[] { entityB }); } [Reads(typeof(MockComponentC), typeof(MockComponentD))] [WritesImmediate(typeof(MockComponent), typeof(MockComponentB))] [Writes(typeof(MockComponent), 0)] [Writes(typeof(MockComponentB), 0)] class ConditionallyAddImmediateComponentsEngine : Engine { public override void Update(double dt) { foreach (var entity in ReadEntities()) { SetComponent(entity, new MockComponent()); } foreach (var entity in ReadEntities()) { SetComponent(entity, new MockComponent()); SetComponent(entity, new MockComponentB()); } } } [ReadsImmediate(typeof(MockComponent), typeof(MockComponentB))] [QueryWith(typeof(MockComponent))] [QueryWithout(typeof(MockComponentB))] class EntityQueryWithAndWithoutImmediateComponentsEngine : Engine { private List entities; public EntityQueryWithAndWithoutImmediateComponentsEngine(List entities) { this.entities = entities; } public override void Update(double dt) { entities.Clear(); entities.AddRange(TrackedEntities); } } [Test] public void EntitiesWithAndWithoutImmediateComponents() { var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); var entityB = worldBuilder.CreateEntity(); var entityC = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entityB, new MockComponentC()); worldBuilder.SetComponent(entityC, new MockComponentD()); var queriedEntities = new List(); worldBuilder.AddEngine(new ConditionallyAddImmediateComponentsEngine()); worldBuilder.AddEngine(new EntityQueryWithAndWithoutImmediateComponentsEngine(queriedEntities)); var world = worldBuilder.Build(); world.Update(0.01); queriedEntities.ToArray().Should().BeEquivalentTo(new Entity[] { entityB }); } [Reads(typeof(MockComponentC))] [WritesImmediate(typeof(MockComponentB))] [Writes(typeof(MockComponentB), 0)] class ConditionallyAddImmediateComponentEngine : Engine { public override void Update(double dt) { foreach (var entity in ReadEntities()) { SetComponent(entity, new MockComponentB()); } } } [ReadsImmediate(typeof(MockComponentB))] [Reads(typeof(MockComponent))] [QueryWith(typeof(MockComponent), typeof(MockComponentB))] class EntityQueryWithImmediateAndNonImmediateComponents : Engine { private List entities; public EntityQueryWithImmediateAndNonImmediateComponents(List entities) { this.entities = entities; } public override void Update(double dt) { entities.Clear(); entities.AddRange(TrackedEntities); } } [Test] public void EntitiesWithImmediateAndNonImmediateComponents() { var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); var entityB = worldBuilder.CreateEntity(); var entityC = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entityB, new MockComponent()); worldBuilder.SetComponent(entityB, new MockComponentC()); worldBuilder.SetComponent(entityC, new MockComponentD()); var queriedEntities = new List(); worldBuilder.AddEngine(new ConditionallyAddImmediateComponentEngine()); worldBuilder.AddEngine(new EntityQueryWithImmediateAndNonImmediateComponents(queriedEntities)); var world = worldBuilder.Build(); world.Update(0.01); queriedEntities.ToArray().Should().BeEquivalentTo(new Entity[] { entityB }); } [ReadsImmediate(typeof(MockComponentB))] class ReadImmediateComponentsEngine : Engine { private List _components; public ReadImmediateComponentsEngine(List components) { _components = components; } public override void Update(double dt) { _components.AddRange(ReadComponents().ToArray()); } } [Test] public void ReadImmediateComponents() { var worldBuilder = new WorldBuilder(); var _components = new List(); var entity = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent()); worldBuilder.AddEngine(new AddImmediateComponentEngine()); worldBuilder.AddEngine(new ReadImmediateComponentsEngine(_components)); var world = worldBuilder.Build(); world.Update(0.01); _components.Should().NotBeEmpty(); } [ReadsImmediate(typeof(MockComponentB))] [Reads(typeof(MockComponent))] class HasAndGetImmediateComponentEngine : Engine { private List _components; public HasAndGetImmediateComponentEngine(List components) { _components = components; } public override void Update(double dt) { foreach (ref readonly var entity in ReadEntities()) { if (HasComponent(entity)) { _components.Add(GetComponent(entity)); } } } } [Test] public void HasAndGetImmediateComponent() { var worldBuilder = new WorldBuilder(); var _components = new List(); var entity = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent()); worldBuilder.AddEngine(new AddImmediateComponentEngine()); worldBuilder.AddEngine(new HasAndGetImmediateComponentEngine(_components)); var world = worldBuilder.Build(); world.Update(0.01); _components.Should().NotBeEmpty(); } struct MockTimerComponent { public double Timer { get; } public MockTimerComponent(double time) { Timer = time; } } [Reads(typeof(MockTimerComponent))] [Writes(typeof(MockTimerComponent))] class ReadWhileRemovingComponentsEngine : Engine { public override void Update(double dt) { foreach (ref readonly var entity in ReadEntities()) { ref readonly var component = ref GetComponent(entity); if (component.Timer - dt <= 0) { RemoveComponent(entity); } else { SetComponent(entity, new MockTimerComponent(component.Timer - dt)); } } } } [Test] public void ReadWhileRemovingComponents() { var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockTimerComponent(0.5)); var entityB = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entityB, new MockTimerComponent(0.4)); worldBuilder.AddEngine(new ReadWhileRemovingComponentsEngine()); var world = worldBuilder.Build(); Assert.DoesNotThrow(() => world.Update(0.2)); Assert.DoesNotThrow(() => world.Update(0.25)); } [Test] public void DestroyedEntitiesAreRemovedFromTracking() { var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent()); worldBuilder.AddEngine(new DestroyWithEngine()); worldBuilder.AddEngine(new ReadEntitiesWithComponentTypeEngine()); var world = worldBuilder.Build(); world.Update(0.01); world.Update(0.01); readEntities.Should().BeEmpty(); } } } }