222 lines
6.1 KiB
C#
222 lines
6.1 KiB
C#
using FluentAssertions;
|
|
using Xunit;
|
|
|
|
namespace OECS.Tests;
|
|
|
|
public class QueryTests
|
|
{
|
|
private struct Position { public float X; public float Y; }
|
|
private struct Velocity { public float X; public float Y; }
|
|
private struct Health { public int Value; }
|
|
private struct Frozen { }
|
|
|
|
[Fact]
|
|
public void Query_WithSingleComponent_ReturnsMatchingEntities()
|
|
{
|
|
var world = new World();
|
|
var a = world.CreateEntity();
|
|
var b = world.CreateEntity();
|
|
var c = world.CreateEntity();
|
|
|
|
world.AddComponent(a, new Position { X = 1, Y = 2 });
|
|
world.AddComponent(b, new Position { X = 3, Y = 4 });
|
|
// c has no Position
|
|
|
|
var results = new List<Entity>();
|
|
foreach (var it in world.Select<Position>())
|
|
results.Add(it.Entity);
|
|
|
|
results.Should().BeEquivalentTo([a, b]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Query_WithMultipleComponents_ReturnsIntersection()
|
|
{
|
|
var world = new World();
|
|
var a = world.CreateEntity();
|
|
var b = world.CreateEntity();
|
|
var c = world.CreateEntity();
|
|
|
|
world.AddComponent(a, new Position { X = 1, Y = 2 });
|
|
world.AddComponent(a, new Velocity { X = 1, Y = 0 });
|
|
|
|
world.AddComponent(b, new Position { X = 3, Y = 4 });
|
|
// b has no Velocity
|
|
|
|
world.AddComponent(c, new Velocity { X = 0, Y = 1 });
|
|
// c has no Position
|
|
|
|
var results = new List<Entity>();
|
|
foreach (var it in world.Select<Position, Velocity>())
|
|
results.Add(it.Entity);
|
|
|
|
results.Should().BeEquivalentTo([a]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Query_Without_ExcludesEntities()
|
|
{
|
|
var world = new World();
|
|
var a = world.CreateEntity();
|
|
var b = world.CreateEntity();
|
|
|
|
world.AddComponent(a, new Position { X = 1, Y = 2 });
|
|
world.AddComponent(b, new Position { X = 3, Y = 4 });
|
|
world.AddComponent(b, new Frozen());
|
|
|
|
var query = new Query<Position>().Without<Frozen>();
|
|
var results = new List<Entity>();
|
|
foreach (var it in world.Select(query))
|
|
results.Add(it.Entity);
|
|
|
|
results.Should().BeEquivalentTo([a]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Query_EmptyResult_WhenNoEntitiesMatch()
|
|
{
|
|
var world = new World();
|
|
var count = 0;
|
|
foreach (var it in world.Select<Position>())
|
|
count++;
|
|
count.Should().Be(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void Query_RefMutation_IsVisible()
|
|
{
|
|
var world = new World();
|
|
var entity = world.CreateEntity();
|
|
world.AddComponent(entity, new Position { X = 0, Y = 0 });
|
|
|
|
foreach (var it in world.Select<Position>())
|
|
it.Item1.X = 42;
|
|
|
|
ref var pos = ref world.GetComponent<Position>(entity);
|
|
pos.X.Should().Be(42);
|
|
}
|
|
|
|
[Fact]
|
|
public void Query_DestroyedEntities_DoNotAppear()
|
|
{
|
|
var world = new World();
|
|
var a = world.CreateEntity();
|
|
var b = world.CreateEntity();
|
|
|
|
world.AddComponent(a, new Position { X = 1, Y = 2 });
|
|
world.AddComponent(b, new Position { X = 3, Y = 4 });
|
|
|
|
world.DestroyEntity(a);
|
|
|
|
var results = new List<Entity>();
|
|
foreach (var it in world.Select<Position>())
|
|
results.Add(it.Entity);
|
|
|
|
results.Should().BeEquivalentTo([b]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Query_ThreeComponents_Works()
|
|
{
|
|
var world = new World();
|
|
var entity = world.CreateEntity();
|
|
|
|
world.AddComponent(entity, new Position { X = 1, Y = 2 });
|
|
world.AddComponent(entity, new Velocity { X = 3, Y = 4 });
|
|
world.AddComponent(entity, new Health { Value = 100 });
|
|
|
|
var count = 0;
|
|
foreach (var it in world.Select<Position, Velocity, Health>())
|
|
{
|
|
count++;
|
|
it.Item1.X.Should().Be(1);
|
|
it.Item2.Y.Should().Be(4);
|
|
it.Item3.Value.Should().Be(100);
|
|
}
|
|
|
|
count.Should().Be(1);
|
|
}
|
|
|
|
[Fact]
|
|
public void FindEntity_ReturnsFirstMatch()
|
|
{
|
|
var world = new World();
|
|
var a = world.CreateEntity();
|
|
var b = world.CreateEntity();
|
|
|
|
world.AddComponent(a, new Position { X = 1, Y = 2 });
|
|
world.AddComponent(b, new Position { X = 3, Y = 4 });
|
|
|
|
var found = world.FindEntity<Position>();
|
|
found.Should().NotBe(Entity.Null);
|
|
}
|
|
}
|
|
|
|
public class SystemTests
|
|
{
|
|
private struct Position { public float X; public float Y; }
|
|
private struct Velocity { public float X; public float Y; }
|
|
|
|
private class MovementSystem : ITickedSystem
|
|
{
|
|
public void RunImpl(World world) => RunImpl(world, Tick.Logical());
|
|
|
|
public void RunImpl(World world, Tick tick)
|
|
{
|
|
float dt = tick.DeltaTime;
|
|
foreach (var it in world.Select<Position, Velocity>())
|
|
{
|
|
it.Item1.X += it.Item2.X * dt;
|
|
it.Item1.Y += it.Item2.Y * dt;
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void System_RunTimed_UpdatesComponents()
|
|
{
|
|
var world = new World();
|
|
var group = new SystemGroup(world);
|
|
group.Add(new MovementSystem());
|
|
|
|
var entity = world.CreateEntity();
|
|
world.AddComponent(entity, new Position { X = 0, Y = 0 });
|
|
world.AddComponent(entity, new Velocity { X = 1, Y = 2 });
|
|
|
|
group.RunTimed(0.5f);
|
|
|
|
ref var pos = ref world.GetComponent<Position>(entity);
|
|
pos.X.Should().Be(0.5f);
|
|
pos.Y.Should().Be(1.0f);
|
|
}
|
|
|
|
[Fact]
|
|
public void System_RunLogical_DoesNotUseDeltaTime()
|
|
{
|
|
var world = new World();
|
|
var group = new SystemGroup(world);
|
|
|
|
var system = new TestLogicalSystem();
|
|
group.Add(system);
|
|
|
|
group.RunLogical();
|
|
|
|
system.WasCalled.Should().BeTrue();
|
|
system.ReceivedTick.Type.Should().Be(TickType.Logical);
|
|
system.ReceivedTick.DeltaTime.Should().Be(0);
|
|
}
|
|
|
|
private class TestLogicalSystem : ITickedSystem
|
|
{
|
|
public bool WasCalled { get; private set; }
|
|
public Tick ReceivedTick { get; private set; }
|
|
|
|
public void RunImpl(World world) { }
|
|
|
|
public void RunImpl(World world, Tick tick)
|
|
{
|
|
WasCalled = true;
|
|
ReceivedTick = tick;
|
|
}
|
|
}
|
|
} |