oecs-sharp/tests/OECS.Tests/QueryAndSystemTests.cs

307 lines
8.2 KiB
C#
Raw Normal View History

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 query = world.Query().With<Position>().Build();
var results = new List<Entity>();
world.ForEach(query, (Entity entity, ref Position pos) =>
{
results.Add(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 query = world.Query().With<Position>().With<Velocity>().Build();
var results = new List<Entity>();
world.ForEach(query, (Entity entity, ref Position pos, ref Velocity vel) =>
{
results.Add(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 = world.Query().With<Position>().Without<Frozen>().Build();
var results = new List<Entity>();
world.ForEach(query, (Entity entity, ref Position pos) =>
{
results.Add(entity);
});
results.Should().BeEquivalentTo([a]);
}
[Fact]
public void Query_EmptyResult_WhenNoEntitiesMatch()
{
var world = new World();
var query = world.Query().With<Position>().Build();
var count = 0;
world.ForEach(query, (Entity entity, ref Position pos) =>
{
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 });
var query = world.Query().With<Position>().Build();
world.ForEach(query, (Entity e, ref Position pos) =>
{
pos.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 query = world.Query().With<Position>().Build();
var results = new List<Entity>();
world.ForEach(query, (Entity entity, ref Position pos) =>
{
results.Add(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 query = world.Query()
.With<Position>()
.With<Velocity>()
.With<Health>()
.Build();
var count = 0;
world.ForEach(query, (Entity e, ref Position pos, ref Velocity vel, ref Health hp) =>
{
count++;
pos.X.Should().Be(1);
vel.Y.Should().Be(4);
hp.Value.Should().Be(100);
});
count.Should().Be(1);
}
}
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 QueryDescriptor Query { get; }
public MovementSystem(World world)
{
Query = world.Query().With<Position>().With<Velocity>().Build();
}
public void Run(World world) => Run(world, Tick.Logical());
public void Run(World world, Tick tick)
{
float dt = tick.DeltaTime;
world.ForEach(Query, (Entity entity, ref Position pos, ref Velocity vel) =>
{
pos.X += vel.X * dt;
pos.Y += vel.Y * dt;
});
}
}
[Fact]
public void System_RunTimed_UpdatesComponents()
{
var world = new World();
var group = new SystemGroup(world);
var system = new MovementSystem(world);
group.Add(system);
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(world);
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 QueryDescriptor Query { get; }
public bool WasCalled { get; private set; }
public Tick ReceivedTick { get; private set; }
public TestLogicalSystem(World world)
{
Query = world.Query().With<Position>().Build();
}
public void Run(World world) => Run(world, Tick.Logical());
public void Run(World world, Tick tick)
{
WasCalled = true;
ReceivedTick = tick;
}
}
[Fact]
public void Systems_RunInRegistrationOrder()
{
var world = new World();
var group = new SystemGroup(world);
var order = new List<int>();
group.Add(new OrderTrackingSystem(world, 1, order));
group.Add(new OrderTrackingSystem(world, 2, order));
group.Add(new OrderTrackingSystem(world, 3, order));
group.RunLogical();
order.Should().BeInAscendingOrder();
order.Should().BeEquivalentTo([1, 2, 3]);
}
private class OrderTrackingSystem : ISystem
{
private readonly int _id;
private readonly List<int> _order;
public QueryDescriptor Query { get; }
public OrderTrackingSystem(World world, int id, List<int> order)
{
_id = id;
_order = order;
Query = world.Query().With<Position>().Build();
}
public void Run(World world)
{
_order.Add(_id);
}
}
[Fact]
public void System_CanBeRemoved()
{
var world = new World();
var group = new SystemGroup(world);
var system = new TestLogicalSystem(world);
group.Add(system);
group.Count.Should().Be(1);
group.Remove(system);
group.Count.Should().Be(0);
group.RunLogical();
system.WasCalled.Should().BeFalse();
}
}