using NUnit.Framework; using FluentAssertions; using Encompass; using System; using System.Linq; using System.Collections.Generic; using Encompass.Exceptions; namespace Tests { struct MockComponent : IComponent { public int myInt; public string myString; } public class EngineTest { static List> resultComponents; static MockComponent resultComponent; static List resultMessages; [Reads(typeof(MockComponent))] public class ReadComponentsTestEngine : Engine { public override void Update(double dt) { resultComponents = ReadComponents().ToList(); } } [Reads(typeof(MockComponent))] public class ReadComponentTestEngine : Engine { public override void Update(double dt) { resultComponent = ReadComponent().Item2; } } [TestCase(true)] [TestCase(false)] public void ReadComponents(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadComponentsTestEngine()); var entity = worldBuilder.CreateEntity(); MockComponent mockComponent; mockComponent.myInt = 0; mockComponent.myString = "hello"; MockComponent mockComponentB; mockComponentB.myInt = 1; mockComponentB.myString = "howdy"; var componentAID = worldBuilder.AddComponent(entity, mockComponent); var componentBID = worldBuilder.AddComponent(entity, mockComponentB); var inactiveComponentAID = worldBuilder.AddComponent(entity, mockComponent); worldBuilder.DeactivateComponent(inactiveComponentAID); var world = worldBuilder.Build(); if (parallelUpdate) { world.ParallelUpdate(0.01); } else { world.Update(0.01); } var resultComponentValues = resultComponents.Select((kv) => kv.Item2); resultComponentValues.Should().Contain(mockComponent); resultComponentValues.Should().Contain(mockComponentB); resultComponents.Should().NotContain((inactiveComponentAID, mockComponent)); } [TestCase(true)] [TestCase(false)] public void ReadComponent(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadComponentTestEngine()); var entity = worldBuilder.CreateEntity(); MockComponent mockComponent; mockComponent.myInt = 0; mockComponent.myString = "hello"; worldBuilder.AddComponent(entity, mockComponent); var world = worldBuilder.Build(); if (parallelUpdate) { world.ParallelUpdate(0.01); } else { world.Update(0.01f); } Assert.AreEqual(mockComponent, resultComponent); } [TestCase(true)] [TestCase(false)] public void ReadComponentWhenMultipleComponents(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadComponentTestEngine()); var entity = worldBuilder.CreateEntity(); MockComponent mockComponent; mockComponent.myInt = 0; mockComponent.myString = "hello"; MockComponent mockComponentB; mockComponentB.myInt = 1; mockComponentB.myString = "howdy"; worldBuilder.AddComponent(entity, mockComponent); worldBuilder.AddComponent(entity, mockComponentB); var world = worldBuilder.Build(); if (parallelUpdate) { Assert.Throws(() => world.ParallelUpdate(0.01)); } else { Assert.Throws(() => world.Update(0.01f)); } } [Reads(typeof(MockComponent))] [Updates(typeof(MockComponent))] public class UpdateComponentTestEngine : Engine { public override void Update(double dt) { (var componentID, var component) = ReadComponent(); component.myInt = 420; component.myString = "blaze it"; UpdateComponent(componentID, component); } } // this test needs to be improved... [TestCase(true)] [TestCase(false)] public void UpdateComponent(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new UpdateComponentTestEngine()); worldBuilder.AddEngine(new ReadComponentTestEngine()); var entity = worldBuilder.CreateEntity(); MockComponent mockComponent; mockComponent.myInt = 0; mockComponent.myString = "hello"; worldBuilder.AddComponent(entity, mockComponent); var world = worldBuilder.Build(); if (parallelUpdate) { world.ParallelUpdate(0.01); world.ParallelUpdate(0.01); } else { world.Update(0.01); world.Update(0.01); } Assert.AreEqual(420, resultComponent.myInt); Assert.AreEqual("blaze it", resultComponent.myString); } [Reads(typeof(MockComponent))] public class UndeclaredUpdateComponentTestEngine : Engine { public override void Update(double dt) { (var componentID, var component) = ReadComponent(); component.myInt = 420; component.myString = "blaze it"; UpdateComponent(componentID, component); component = ReadComponent().Item2; } } [TestCase(true)] [TestCase(false)] public void UpdateUndeclaredComponent(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new UndeclaredUpdateComponentTestEngine()); var entity = worldBuilder.CreateEntity(); MockComponent mockComponent; mockComponent.myInt = 0; mockComponent.myString = "hello"; worldBuilder.AddComponent(entity, mockComponent); var world = worldBuilder.Build(); if (parallelUpdate) { Action updateWorld = () => world.ParallelUpdate(0.01); } else { Action updateWorld = () => world.Update(0.01); } 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 = this.ReadMessages().ToList(); } } [TestCase(true)] [TestCase(false)] public void EmitAndReadMessage(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new MessageEmitEngine()); worldBuilder.AddEngine(new MessageReadEngine()); var world = worldBuilder.Build(); if (parallelUpdate) { world.ParallelUpdate(0.01f); } else { 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 IEnumerable emptyReadMessagesResult; [Receives(typeof(MockMessage))] class ReadMessagesWhenNoneExistEngine : Engine { public override void Update(double dt) { emptyReadMessagesResult = ReadMessages(); } } [TestCase(true)] [TestCase(false)] public void ReadMessagesWhenNoneHaveBeenEmitted(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadMessagesWhenNoneExistEngine()); var world = worldBuilder.Build(); if (parallelUpdate) { world.ParallelUpdate(0.01); } else { world.Update(0.01); } emptyReadMessagesResult.Should().BeEmpty(); } [TestCase(true)] [TestCase(false)] public void EmitUndeclaredMessage(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new UndeclaredMessageEmitEngine()); var world = worldBuilder.Build(); if (parallelUpdate) { var ex = Assert.Throws(() => world.ParallelUpdate(0.01)); Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredMessageEmitEngine tried to send undeclared Message MockMessage")); } else { 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(); } } [TestCase(true)] [TestCase(false)] public void SomeMessage(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new EmitMockMessageEngine()); worldBuilder.AddEngine(new SomeMessageTestEngine()); var world = worldBuilder.Build(); if (parallelUpdate) { world.ParallelUpdate(0.01f); } else { world.Update(0.01f); } Assert.That(someTest, Is.True); } class UndeclaredSomeMessageEngine : Engine { public override void Update(double dt) { someTest = this.SomeMessage(); } } [TestCase(true)] [TestCase(false)] public void UndeclaredSomeMessage(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new EmitMockMessageEngine()); worldBuilder.AddEngine(new UndeclaredSomeMessageEngine()); var world = worldBuilder.Build(); if (parallelUpdate) { Assert.Throws(() => world.ParallelUpdate(0.01f)); } else { Assert.Throws(() => world.Update(0.01f)); } } class SomeComponentTestEngine : Engine { public override void Update(double dt) { Assert.IsTrue(SomeComponent()); } } [TestCase(true)] [TestCase(false)] public void SomeComponent(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); worldBuilder.AddComponent(entity, new MockComponent()); var world = worldBuilder.Build(); if (parallelUpdate) { world.ParallelUpdate(0.01); } else { world.Update(0.01); } } static ValueTuple pairA; static ValueTuple pairB; [Reads(typeof(MockComponent))] class SameValueComponentReadEngine : Engine { public override void Update(double dt) { var components = ReadComponents(); pairA = components.First(); pairB = components.Last(); } } // Tests that components with identical values should be distinguishable by ID [TestCase(true)] [TestCase(false)] public void SameValueComponents(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new SameValueComponentReadEngine()); MockComponent componentA; componentA.myInt = 20; componentA.myString = "hello"; MockComponent componentB; componentB.myInt = 20; componentB.myString = "hello"; var entity = worldBuilder.CreateEntity(); worldBuilder.AddComponent(entity, componentA); worldBuilder.AddComponent(entity, componentB); var world = worldBuilder.Build(); if (parallelUpdate) { world.ParallelUpdate(0.01); } else { world.Update(0.01); } Assert.That(pairA, Is.Not.EqualTo(pairB)); Assert.That(pairA.Item2, Is.EqualTo(pairB.Item2)); } static IEnumerable> emptyComponentReadResult; [Reads(typeof(MockComponent))] class ReadEmptyMockComponentsEngine : Engine { public override void Update(double dt) { emptyComponentReadResult = ReadComponents(); } } [TestCase(true)] [TestCase(false)] public void ReadComponentsOfTypeWhereNoneExist(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadEmptyMockComponentsEngine()); var world = worldBuilder.Build(); if (parallelUpdate) { world.ParallelUpdate(0.01); } else { world.Update(0.01); } Assert.That(emptyComponentReadResult, Is.Empty); } struct DestroyerComponent : IComponent { } [Reads(typeof(DestroyerComponent))] class DestroyerEngine : Engine { public override void Update(double dt) { foreach (var componentPair in ReadComponents()) { var componentID = componentPair.Item1; var entityID = GetEntityIDByComponentID(componentID); Destroy(entityID); } } } static IEnumerable> results; [Reads(typeof(MockComponent))] class ReaderEngine : Engine { public override void Update(double dt) { results = ReadComponents(); } } [TestCase(true)] [TestCase(false)] public void DestroyEntity(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new DestroyerEngine()); worldBuilder.AddEngine(new ReaderEngine()); var entity = worldBuilder.CreateEntity(); var entityB = worldBuilder.CreateEntity(); DestroyerComponent destroyerComponent; MockComponent mockComponent; mockComponent.myInt = 2; mockComponent.myString = "blah"; worldBuilder.AddComponent(entity, destroyerComponent); var componentID = worldBuilder.AddComponent(entity, mockComponent); worldBuilder.AddComponent(entityB, destroyerComponent); var componentBID = worldBuilder.AddComponent(entityB, mockComponent); var world = worldBuilder.Build(); if (parallelUpdate) { world.ParallelUpdate(0.01); } else { world.Update(0.01); } Assert.That(results, Does.Not.Contain((componentID, mockComponent))); Assert.That(results, Does.Not.Contain((componentBID, mockComponent))); } [Reads(typeof(DestroyerComponent), typeof(MockComponent))] class DestroyAndAddComponentEngine : Engine { public override void Update(double dt) { foreach (var componentPair in ReadComponents()) { var componentID = componentPair.Item1; var entity = GetEntityByComponentID(componentID); var (id, _) = GetComponent(entity); RemoveComponent(id); Destroy(entity.ID); } } } [TestCase(true)] [TestCase(false)] public void DestroyEntityWhileRemovingComponent(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new DestroyAndAddComponentEngine()); worldBuilder.AddEngine(new ReaderEngine()); var entity = worldBuilder.CreateEntity(); worldBuilder.AddComponent(entity, new DestroyerComponent()); worldBuilder.AddComponent(entity, new MockComponent()); var world = worldBuilder.Build(); if (parallelUpdate) { Assert.DoesNotThrow(() => world.ParallelUpdate(0.01)); } else { Assert.DoesNotThrow(() => world.Update(0.01)); } } static Entity entityFromComponentIDResult; [Reads(typeof(MockComponent))] class GetEntityFromComponentIDEngine : Engine { public override void Update(double dt) { var componentID = ReadComponent().Item1; entityFromComponentIDResult = GetEntityByComponentID(componentID); } } [TestCase(true)] [TestCase(false)] public void GetEntityFromComponentID(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new GetEntityFromComponentIDEngine()); MockComponent component; component.myInt = 2; component.myString = "howdy"; var entity = worldBuilder.CreateEntity(); worldBuilder.AddComponent(entity, component); var world = worldBuilder.Build(); if (parallelUpdate) { world.ParallelUpdate(0.01); } else { world.Update(0.01); } Assert.That(entity, Is.EqualTo(entityFromComponentIDResult)); } static MockComponent mockComponentByIDResult; [Reads(typeof(MockComponent))] class GetComponentByIDEngine : Engine { public override void Update(double dt) { var componentID = ReadComponent().Item1; mockComponentByIDResult = GetComponentByID(componentID); } } [TestCase(true)] [TestCase(false)] public void GetComponentByID(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new GetComponentByIDEngine()); MockComponent component; component.myInt = 2; component.myString = "howdy"; var entity = worldBuilder.CreateEntity(); worldBuilder.AddComponent(entity, component); var world = worldBuilder.Build(); if (parallelUpdate) { world.ParallelUpdate(0.01); } else { world.Update(0.01); } Assert.That(component, Is.EqualTo(mockComponentByIDResult)); } struct OtherComponent : IComponent { } [Reads(typeof(MockComponent))] class GetComponentByIDWithTypeMismatchEngine : Engine { public override void Update(double dt) { var componentID = ReadComponent().Item1; GetComponentByID(componentID); } } [TestCase(true)] [TestCase(false)] public void GetComponentByIDWithTypeMismatch(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new GetComponentByIDWithTypeMismatchEngine()); MockComponent component; component.myInt = 2; component.myString = "howdy"; var entity = worldBuilder.CreateEntity(); worldBuilder.AddComponent(entity, component); var world = worldBuilder.Build(); if (parallelUpdate) { Assert.Throws(() => world.ParallelUpdate(0.01)); } else { Assert.Throws(() => world.Update(0.01)); } } struct EntityIDComponent : IComponent { public Guid entityID; } static bool hasEntity; [Reads(typeof(EntityIDComponent))] class HasEntityTestEngine : Engine { public override void Update(double dt) { foreach (var (mockComponentID, mockComponent) in ReadComponents()) { hasEntity = EntityExists(mockComponent.entityID); if (hasEntity) { Destroy(mockComponent.entityID); } } } } [TestCase(true)] [TestCase(false)] public void EntityExists(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new HasEntityTestEngine()); var entity = worldBuilder.CreateEntity(); var entityTwo = worldBuilder.CreateEntity(); EntityIDComponent entityIDComponent; entityIDComponent.entityID = entityTwo.ID; worldBuilder.AddComponent(entity, entityIDComponent); var world = worldBuilder.Build(); if (parallelUpdate) { world.ParallelUpdate(0.01); } else { world.Update(0.01); } Assert.IsTrue(hasEntity); if (parallelUpdate) { world.ParallelUpdate(0.01); } else { world.Update(0.01); } Assert.IsFalse(hasEntity); } struct MockComponentUpdateMessage : IMessage { public Guid componentID; public MockComponent mockComponent; } [Receives(typeof(MockComponentUpdateMessage))] [Updates(typeof(MockComponent))] class RepeatUpdateEngine : Engine { public override void Update(double dt) { foreach (var mockComponentUpdateMessage in ReadMessages()) { UpdateComponent(mockComponentUpdateMessage.componentID, mockComponentUpdateMessage.mockComponent); UpdateComponent(mockComponentUpdateMessage.componentID, mockComponentUpdateMessage.mockComponent); } } } [TestCase(true)] [TestCase(false)] public void EngineUpdatesComponentMultipleTimes(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new RepeatUpdateEngine()); var entity = worldBuilder.CreateEntity(); MockComponent mockComponent; mockComponent.myInt = 1; mockComponent.myString = "5"; var mockComponentID = worldBuilder.AddComponent(entity, mockComponent); MockComponentUpdateMessage mockComponentUpdateMessage; mockComponentUpdateMessage.componentID = mockComponentID; mockComponentUpdateMessage.mockComponent = mockComponent; worldBuilder.SendMessage(mockComponentUpdateMessage); var world = worldBuilder.Build(); if (parallelUpdate) { Assert.Throws(() => world.ParallelUpdate(0.01)); } else { Assert.Throws(() => world.Update(0.01)); } } } }