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 = new List(); [Reads(typeof(MockComponent))] public class ReadComponentsTestEngine : Engine { public override void Update(double dt) { resultComponents = ReadComponents().ToList(); } } static List<(MockComponent, Entity)> resultComponentsIncludingEntity; static (MockComponent, Entity) resultComponentIncludingEntity; [Reads(typeof(MockComponent))] public class ReadComponentsIncludingEntityEngine : Engine { public override void Update(double dt) { resultComponentsIncludingEntity = ReadComponentsIncludingEntity().ToList(); } } [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) { resultComponentIncludingEntity = ReadComponentIncludingEntity(); } } [Test] public void ReadComponents() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadComponentsTestEngine()); var entity = worldBuilder.CreateEntity(); var entityB = worldBuilder.CreateEntity(); MockComponent mockComponent; mockComponent.myInt = 0; mockComponent.myString = "hello"; MockComponent mockComponentB; mockComponentB.myInt = 1; mockComponentB.myString = "howdy"; 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() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadComponentsIncludingEntityEngine()); var entity = worldBuilder.CreateEntity(); var entityB = worldBuilder.CreateEntity(); MockComponent mockComponent; mockComponent.myInt = 0; mockComponent.myString = "hello"; MockComponent mockComponentB; mockComponentB.myInt = 1; mockComponentB.myString = "howdy"; 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 = 0; mockComponent.myString = "hello"; 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 = 0; mockComponent.myString = "hello"; MockComponent mockComponentB; mockComponentB.myInt = 1; mockComponentB.myString = "howdy"; 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 = 0; mockComponent.myString = "hello"; 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) { var (component, entity) = ReadComponentIncludingEntity(); component.myInt = 420; component.myString = "blaze it"; SetComponent(entity, component); } } // 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 = 0; mockComponent.myString = "hello"; worldBuilder.SetComponent(entity, mockComponent); var world = worldBuilder.Build(); 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 (component, entity) = ReadComponentIncludingEntity(); component.myInt = 420; component.myString = "blaze it"; SetComponent(entity, component); component = ReadComponent(); } } [Test] public void UpdateUndeclaredComponent() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new UndeclaredUpdateComponentTestEngine()); var entity = worldBuilder.CreateEntity(); MockComponent mockComponent; mockComponent.myInt = 0; mockComponent.myString = "hello"; 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 = this.ReadMessages().ToList(); } } [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 IEnumerable emptyReadMessagesResult; [Receives(typeof(MockMessage))] class ReadMessagesWhenNoneExistEngine : Engine { public override void Update(double dt) { emptyReadMessagesResult = ReadMessages(); } } [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)); } 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 components = ReadComponentsIncludingEntity(); pairA = components.First(); pairB = components.Last(); } } // 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; componentA.myString = "hello"; MockComponent componentB; componentB.myInt = 20; componentB.myString = "hello"; 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)); } static IEnumerable<(MockComponent, Entity)> emptyComponentReadResult; [Reads(typeof(MockComponent))] class ReadEmptyMockComponentsEngine : Engine { public override void Update(double dt) { emptyComponentReadResult = ReadComponentsIncludingEntity(); } } [Test] public void ReadComponentsOfTypeWhereNoneExist() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadEmptyMockComponentsEngine()); var world = worldBuilder.Build(); world.Update(0.01f); Assert.That(emptyComponentReadResult, Is.Empty); } struct DestroyerComponent : IComponent { } [Reads(typeof(DestroyerComponent))] class DestroyerEngine : Engine { public override void Update(double dt) { foreach (var (component, entity) in ReadComponentsIncludingEntity()) { Destroy(entity); } } } static List<(MockComponent, Entity)> results; [Reads(typeof(MockComponent))] class ReaderEngine : Engine { public override void Update(double dt) { results = ReadComponentsIncludingEntity().ToList(); } } [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; mockComponent.myString = "blah"; 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, entity))); Assert.That(results, Does.Contain((mockComponent, entityC))); } [Receives(typeof(DestroyComponentMessage))] class DestroyEntityEngine : Engine { public override void Update(double dt) { foreach (var message in ReadMessages()) { Destroy(message.entity); } } } [Test] public void DestroyEntityWithoutID() { 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), typeof(MockComponent))] class DestroyAndAddComponentEngine : Engine { public override void Update(double dt) { foreach (var (componentPair, entity) in ReadComponentsIncludingEntity()) { 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))] [WritesPending(typeof(MockComponent))] [Writes(typeof(MockComponent))] class AddAndRemoveMockComponentEngine : Engine { public override void Update(double dt) { foreach (var (mockComponent, entity) in ReadComponentsIncludingEntity()) { RemoveComponent(entity); SetComponent(entity, new MockComponent()); } } } static Entity entityResult; [ReadsPending(typeof(MockComponent))] class GetEntityFromPendingReadComponents : Engine { public override void Update(double dt) { var (_, entity) = ReadComponentIncludingEntity(); } } [Test] public void GetEntityFromPendingComponentID() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new AddAndRemoveMockComponentEngine()); worldBuilder.AddEngine(new GetEntityFromPendingReadComponents()); var entity = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent()); var world = worldBuilder.Build(); Assert.DoesNotThrow(() => world.Update(0.01)); } [Reads(typeof(MockComponent))] class DelayedMessageEngine : Engine { public override void Update(double dt) { foreach (var (component, entity) in ReadComponentsIncludingEntity()) { RemoveComponent(entity); SendMessageDelayed(new MockMessage { }, 1); } } } [Test] public void EngineSendMessageDelayed() { resultMessages.Clear(); var worldBuilder = new WorldBuilder(); 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().NotBeEmpty(); resultMessages.First().Should().BeOfType(); } [Receives(typeof(MockMessage))] [WritesPending(typeof(MockComponent))] [Writes(typeof(MockComponent))] class ActivateComponentEngine : Engine { public override void Update(double dt) { foreach (var message in ReadMessages()) { var entity = CreateEntity(); SetComponent(entity, new MockComponent { }); } } } [ReadsPending(typeof(MockComponent))] class RemoveComponentEngine : Engine { public override void Update(double dt) { foreach (var entity in ReadEntities()) { RemoveComponent(entity); } } } [Test] public void EngineAddAndRemoveComponentSameFrame() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ActivateComponentEngine()); worldBuilder.AddEngine(new RemoveComponentEngine()); worldBuilder.SendMessage(new MockMessage { }); var world = worldBuilder.Build(); Assert.DoesNotThrow(() => world.Update(0.01)); } 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); } 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))] class RemoveComponentByTypeEngine : Engine { public override void Update(double dt) { foreach (var (_, entity) in ReadComponentsIncludingEntity()) { 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(); } } }