oecs-sharp/OECS.Tests/SingletonTests.cs

165 lines
4.4 KiB
C#
Raw Normal View History

using FluentAssertions;
using Xunit;
namespace OECS.Tests;
public class SingletonTests
{
private struct GameConfig { public float Gravity; public int MaxPlayers; }
private struct TimeState { public float Elapsed; }
[Fact]
public void SetSingleton_GetSingleton_RoundTrips()
{
var world = new World();
world.SetSingleton(new GameConfig { Gravity = 9.8f, MaxPlayers = 4 });
ref var config = ref world.GetSingleton<GameConfig>();
config.Gravity.Should().Be(9.8f);
config.MaxPlayers.Should().Be(4);
}
[Fact]
public void SetSingleton_ReplacesExisting()
{
var world = new World();
world.SetSingleton(new GameConfig { Gravity = 9.8f, MaxPlayers = 4 });
world.SetSingleton(new GameConfig { Gravity = 1.6f, MaxPlayers = 2 });
ref var config = ref world.GetSingleton<GameConfig>();
config.Gravity.Should().Be(1.6f);
config.MaxPlayers.Should().Be(2);
}
[Fact]
public void HasSingleton_ReturnsCorrectly()
{
var world = new World();
world.HasSingleton<GameConfig>().Should().BeFalse();
world.SetSingleton(new GameConfig());
world.HasSingleton<GameConfig>().Should().BeTrue();
}
[Fact]
public void RemoveSingleton_RemovesIt()
{
var world = new World();
world.SetSingleton(new GameConfig { Gravity = 9.8f, MaxPlayers = 4 });
world.HasSingleton<GameConfig>().Should().BeTrue();
world.RemoveSingleton<GameConfig>();
world.HasSingleton<GameConfig>().Should().BeFalse();
}
[Fact]
public void RemoveSingleton_NoOp_WhenNotPresent()
{
var world = new World();
// Should not throw.
world.RemoveSingleton<GameConfig>();
}
[Fact]
public void SingletonEntity_DoesNotAppearInNormalQueries()
{
var world = new World();
// Set a singleton with the same component type that a regular entity has.
world.SetSingleton(new GameConfig { Gravity = 9.8f, MaxPlayers = 4 });
var regular = world.CreateEntity();
world.AddComponent(regular, new GameConfig { Gravity = 1.6f, MaxPlayers = 2 });
var query = world.Query().With<GameConfig>().Build();
var results = new List<Entity>();
world.ForEach(query, (Entity e, ref GameConfig cfg) =>
{
results.Add(e);
});
// Only the regular entity should appear, not the singleton.
results.Should().BeEquivalentTo([regular]);
}
[Fact]
public void SingletonEntity_IsAlive()
{
var world = new World();
world.SetSingleton(new GameConfig());
world.IsAlive(World.SingletonEntity).Should().BeTrue();
}
[Fact]
public void Singleton_SurvivesTickExecution()
{
var world = new World();
var group = new SystemGroup(world);
world.SetSingleton(new TimeState { Elapsed = 0f });
// System that reads and updates the singleton.
group.Add(new TimeSystem(world));
group.RunTimed(0.5f);
group.RunTimed(0.5f);
ref var time = ref world.GetSingleton<TimeState>();
time.Elapsed.Should().Be(1.0f);
}
[Fact]
public void MultipleSingletonTypes_AreIndependent()
{
var world = new World();
world.SetSingleton(new GameConfig { Gravity = 9.8f, MaxPlayers = 4 });
world.SetSingleton(new TimeState { Elapsed = 42f });
ref var config = ref world.GetSingleton<GameConfig>();
ref var time = ref world.GetSingleton<TimeState>();
config.Gravity.Should().Be(9.8f);
time.Elapsed.Should().Be(42f);
}
[Fact]
public void Singleton_Mutation_ViaRef_IsVisible()
{
var world = new World();
world.SetSingleton(new GameConfig { Gravity = 0, MaxPlayers = 0 });
ref var config = ref world.GetSingleton<GameConfig>();
config.Gravity = 9.8f;
ref var config2 = ref world.GetSingleton<GameConfig>();
config2.Gravity.Should().Be(9.8f);
}
private class TimeSystem : ITickedSystem
{
private readonly World _world;
public TimeSystem(World world)
{
_world = world;
}
public void Run(World world) => Run(world, Tick.Logical());
public void Run(World world, Tick tick)
{
ref var time = ref _world.GetSingleton<TimeState>();
time.Elapsed += tick.DeltaTime;
}
}
}