encompass-cs/test/WorldBuilderTest.cs

615 lines
18 KiB
C#

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<EngineCycleException>(() => 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<EngineCycleException>(() => 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<EngineWriteConflictException>(() => worldBuilder.Build());
}
}
public class MultipleEngineWriteWithPriority
{
struct SetMessage : IMessage
{
public Entity entity;
}
struct AComponent : IComponent
{
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<SetMessage>())
{
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<SetMessage>())
{
SetComponent(setMessage.entity, new AComponent { myInt = 1 });
}
}
}
static AComponent resultComponent;
[ReadsImmediate(typeof(AComponent))]
class ReadComponentEngine : Engine
{
public override void Update(double dt)
{
resultComponent = ReadComponent<AComponent>();
}
}
[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 : IComponent
{
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<SetMessage>())
{
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<SetMessage>())
{
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<SetMessage>())
{
SetComponent(setMessage.entity, new AComponent { myInt = 3 });
}
}
}
static AComponent resultComponent;
[ReadsImmediate(typeof(AComponent))]
class ReadComponentEngine : Engine
{
public override void Update(double dt)
{
resultComponent = ReadComponent<AComponent>();
}
}
[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<EngineSelfCycleException>(() => 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<IllegalSendTypeException>(() => 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<EngineWriteConflictException>(() => 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<EngineWriteConflictException>(() => worldBuilder.Build());
}
}
public class LegalEngines
{
static List<Engine> order = new List<Engine>();
struct AComponent : IComponent { }
struct BComponent : IComponent { }
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<AMessage>().ToArray();
}
}
[Test]
public void SendMessageDelayed()
{
resultMessages = Array.Empty<AMessage>();
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<AMessage>();
}
}
public class MultipleMessagesBetweenEngines
{
static List<Engine> order = new List<Engine>();
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)));
}
}
public class DrawLayerRegister
{
struct AComponent : IComponent, IDrawableComponent
{
public int Layer { get; }
}
struct BComponent : IComponent, IDrawableComponent
{
public int Layer { get => 3; }
}
class ARenderer : OrderedRenderer<AComponent>
{
public override void Render(Entity entity, in AComponent drawComponent) { }
}
class BRenderer : OrderedRenderer<BComponent>
{
public override void Render(Entity entity, in BComponent drawComponent) { }
}
[Test]
public void DrawLayerRegisterAfterOrderedRendererRegisterThrows()
{
var worldBuilder = new WorldBuilder();
var rendererA = worldBuilder.AddOrderedRenderer(new ARenderer());
Assert.Throws<IllegalDrawLayerException>(() => worldBuilder.RegisterDrawLayer(1));
}
[Test]
public void DrawLayerRegisterBeforeOrderedRendererDoesNotThrow()
{
var worldBuilder = new WorldBuilder();
Assert.DoesNotThrow(() => worldBuilder.RegisterDrawLayer(1));
Assert.DoesNotThrow(() => worldBuilder.AddOrderedRenderer(new ARenderer()));
}
[Test]
public void DrawLayerWithProperty()
{
var worldBuilder = new WorldBuilder();
var rendererB = worldBuilder.AddOrderedRenderer(new BRenderer());
Assert.DoesNotThrow(() => worldBuilder.Build());
}
}
}
}