using NUnit.Framework; using Encompass; using System.Collections.Generic; using Encompass.Exceptions; using System.Linq; using FluentAssertions; using System; namespace Tests { public class WorldBuilderTest { public class EngineCycleSimple { struct AMessage : IMessage { } struct BMessage : IMessage { } [Receives(typeof(AMessage))] [Sends(typeof(BMessage))] class AEngine : Engine { public override void Update(double dt) { BMessage message; this.SendMessage(message); } } [Receives(typeof(BMessage))] [Sends(typeof(AMessage))] class BEngine : Engine { public override void Update(double dt) { AMessage message; this.SendMessage(message); } } [Test] public void EngineCycle() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new AEngine()); worldBuilder.AddEngine(new BEngine()); Assert.Throws(() => worldBuilder.Build()); } } public class EngineCycleComplex { struct AMessage : IMessage { } struct BMessage : IMessage { } struct CMessage : IMessage { } struct DMessage : IMessage { } [Receives(typeof(AMessage))] [Sends(typeof(BMessage))] class AEngine : Engine { public override void Update(double dt) { BMessage message; this.SendMessage(message); } } [Receives(typeof(BMessage))] [Sends(typeof(CMessage))] class BEngine : Engine { public override void Update(double dt) { CMessage message; this.SendMessage(message); } } [Receives(typeof(CMessage))] [Sends(typeof(DMessage))] class CEngine : Engine { public override void Update(double dt) { DMessage message; this.SendMessage(message); } } [Receives(typeof(DMessage))] [Sends(typeof(AMessage))] class DEngine : Engine { public override void Update(double dt) { AMessage message; this.SendMessage(message); } } [Test] public void EngineCycle() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new AEngine()); worldBuilder.AddEngine(new BEngine()); worldBuilder.AddEngine(new CEngine()); worldBuilder.AddEngine(new DEngine()); Assert.Throws(() => worldBuilder.Build()); } } public class MultipleEngineWriteConflict { struct AComponent { } [Writes(typeof(AComponent))] class AEngine : Engine { public override void Update(double dt) { } } [Writes(typeof(AComponent))] class BEngine : Engine { public override void Update(double dt) { } } [Test] public void EngineWriteConflictException() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new AEngine()); worldBuilder.AddEngine(new BEngine()); Assert.Throws(() => worldBuilder.Build()); } } public class MultipleEngineWriteWithPriority { struct SetMessage : IMessage { public Entity entity; } struct AComponent { public int myInt; } [Receives(typeof(SetMessage))] [Writes(typeof(AComponent), 0)] [WritesImmediate(typeof(AComponent))] class AEngine : Engine { public override void Update(double dt) { foreach (ref readonly var setMessage in ReadMessages()) { SetComponent(setMessage.entity, new AComponent { myInt = 0 }); } } } [Receives(typeof(SetMessage))] [Writes(typeof(AComponent), 1)] [WritesImmediate(typeof(AComponent))] class BEngine : Engine { public override void Update(double dt) { foreach (ref readonly var setMessage in ReadMessages()) { SetComponent(setMessage.entity, new AComponent { myInt = 1 }); } } } static AComponent resultComponent; [ReadsImmediate(typeof(AComponent))] class ReadComponentEngine : Engine { public override void Update(double dt) { resultComponent = ReadComponent(); } } [Test] public void LowerPriorityWrites() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new AEngine()); worldBuilder.AddEngine(new BEngine()); var entity = worldBuilder.CreateEntity(); worldBuilder.SendMessage(new SetMessage { entity = entity }); var world = worldBuilder.Build(); world.Update(0.01); Assert.That(resultComponent.myInt, Is.EqualTo(0)); } } public class DefaultWritePriority { struct SetMessage : IMessage { public Entity entity; } struct AComponent { public int myInt; } [Receives(typeof(SetMessage))] [Writes(typeof(AComponent))] [WritesImmediate(typeof(AComponent))] [Encompass.DefaultWritePriority(4)] class AEngine : Engine { public override void Update(double dt) { foreach (ref readonly var setMessage in ReadMessages()) { SetComponent(setMessage.entity, new AComponent { myInt = 5 }); } } } [Receives(typeof(SetMessage))] [Writes(typeof(AComponent), 3)] [WritesImmediate(typeof(AComponent))] class BEngine : Engine { public override void Update(double dt) { foreach (ref readonly var setMessage in ReadMessages()) { SetComponent(setMessage.entity, new AComponent { myInt = 1 }); } } } [Receives(typeof(SetMessage))] [Writes(typeof(AComponent), 2)] [WritesImmediate(typeof(AComponent))] class CEngine : Engine { public override void Update(double dt) { foreach (ref readonly var setMessage in ReadMessages()) { SetComponent(setMessage.entity, new AComponent { myInt = 3 }); } } } static AComponent resultComponent; [ReadsImmediate(typeof(AComponent))] class ReadComponentEngine : Engine { public override void Update(double dt) { resultComponent = ReadComponent(); } } [Test] public void LowerPriorityWrites() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new AEngine()); worldBuilder.AddEngine(new BEngine()); worldBuilder.AddEngine(new CEngine()); worldBuilder.AddEngine(new ReadComponentEngine()); var entity = worldBuilder.CreateEntity(); worldBuilder.SendMessage(new SetMessage { entity = entity }); var world = worldBuilder.Build(); world.Update(0.01); Assert.That(resultComponent.myInt, Is.EqualTo(3)); } } public class EngineMessageSelfCycle { struct AMessage : IMessage { } [Receives(typeof(AMessage))] [Sends(typeof(AMessage))] class AEngine : Engine { public override void Update(double dt) { } } [Test] public void ThrowsError() { var worldBuilder = new WorldBuilder(); Assert.Throws(() => worldBuilder.AddEngine(new AEngine()), "Engine both sends and receives Message AMessage"); } } public class IllegalWriteType { struct ANonMessage { } [Sends(typeof(ANonMessage))] class MyEngine : Engine { public override void Update(double dt) { } } [Test] public void ThrowsError() { var worldBuilder = new WorldBuilder(); Assert.Throws(() => worldBuilder.AddEngine(new MyEngine()), "ANonMessage must be a Message or Component"); } } public class PriorityConflict { [Writes(typeof(MockComponent), 2)] class AEngine : Engine { public override void Update(double dt) { } } [Writes(typeof(MockComponent), 2)] class BEngine : Engine { public override void Update(double dt) { } } [Test] public void PriorityConflictTest() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new AEngine()); worldBuilder.AddEngine(new BEngine()); Assert.Throws(() => worldBuilder.Build()); } } public class EngineWriteConflict { [Writes(typeof(MockComponent))] class AEngine : Engine { public override void Update(double dt) { } } [Writes(typeof(MockComponent), 2)] class BEngine : Engine { public override void Update(double dt) { } } [Test] public void EngineWriteConflictPriorityAndNoPriorityTest() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new AEngine()); worldBuilder.AddEngine(new BEngine()); Assert.Throws(() => worldBuilder.Build()); } } public class LegalEngines { static List order = new List(); struct AComponent { } struct BComponent { } struct AMessage : IMessage { } struct BMessage : IMessage { } struct CMessage : IMessage { } struct DMessage : IMessage { } [Sends(typeof(AMessage))] class AEngine : Engine { public override void Update(double dt) { order.Add(this); } } [Sends(typeof(BMessage))] class BEngine : Engine { public override void Update(double dt) { order.Add(this); } } [Receives(typeof(AMessage), typeof(BMessage))] [Sends(typeof(DMessage))] class CEngine : Engine { public override void Update(double dt) { order.Add(this); } } [Receives(typeof(DMessage))] class DEngine : Engine { public override void Update(double dt) { order.Add(this); } } [Test] public void EngineOrder() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new AEngine()); worldBuilder.AddEngine(new BEngine()); worldBuilder.AddEngine(new CEngine()); worldBuilder.AddEngine(new DEngine()); Assert.DoesNotThrow(() => worldBuilder.Build()); worldBuilder = new WorldBuilder(); var engineA = worldBuilder.AddEngine(new AEngine()); var engineB = worldBuilder.AddEngine(new BEngine()); var engineC = worldBuilder.AddEngine(new CEngine()); var engineD = worldBuilder.AddEngine(new DEngine()); var world = worldBuilder.Build(); world.Update(0.01f); Assert.That(order.IndexOf(engineA), Is.LessThan(order.IndexOf(engineC))); Assert.That(order.IndexOf(engineB), Is.LessThan(order.IndexOf(engineC))); Assert.That(order.IndexOf(engineC), Is.LessThan(order.IndexOf(engineD))); } static AMessage[] resultMessages; [Receives(typeof(AMessage))] class ReadMessageEngine : Engine { public override void Update(double dt) { resultMessages = ReadMessages().ToArray(); } } [Test] public void SendMessageDelayed() { resultMessages = Array.Empty(); var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadMessageEngine()); worldBuilder.SendMessage(new AMessage { }, 0.5); var world = worldBuilder.Build(); resultMessages.Should().BeEmpty(); world.Update(0.25); resultMessages.Should().BeEmpty(); world.Update(0.25); resultMessages.Should().NotBeEmpty(); resultMessages.First().Should().BeOfType(); } } public class MultipleMessagesBetweenEngines { static List order = new List(); struct AMessage : IMessage { } struct BMessage : IMessage { } [Sends(typeof(AMessage), typeof(BMessage))] class AEngine : Engine { public override void Update(double dt) { order.Add(this); } } [Receives(typeof(AMessage), typeof(BMessage))] class BEngine : Engine { public override void Update(double dt) { order.Add(this); } } [Test] public void WorldBuilderDoesNotThrowError() { var worldBuilder = new WorldBuilder(); var engineA = worldBuilder.AddEngine(new AEngine()); var engineB = worldBuilder.AddEngine(new BEngine()); Assert.DoesNotThrow(() => worldBuilder.Build()); var world = worldBuilder.Build(); world.Update(0.01f); Assert.That(order.IndexOf(engineA), Is.LessThan(order.IndexOf(engineB))); } } } }