Adds WorldEntitySnapshot, IGameState, IEvents abstractions; WorldEvents implements replay-on-subscribe with per-handler exception swallowing; WorldGameState tracks entities; AppPluginHost exposes all three; stubs wired in Program.cs to keep build green ahead of Task 9 live wiring. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
87 lines
2.5 KiB
C#
87 lines
2.5 KiB
C#
// tests/AcDream.Core.Tests/Plugins/WorldEventsTests.cs
|
|
using System.Numerics;
|
|
using AcDream.Core.Plugins;
|
|
using AcDream.Plugin.Abstractions;
|
|
|
|
namespace AcDream.Core.Tests.Plugins;
|
|
|
|
public class WorldEventsTests
|
|
{
|
|
private static WorldEntitySnapshot S(uint id) => new(id, SourceId: 0x01000000u, Position: Vector3.Zero, Rotation: Quaternion.Identity);
|
|
|
|
[Fact]
|
|
public void FireBeforeAnySubscriber_LateSubscribeReceivesReplay()
|
|
{
|
|
var events = new WorldEvents();
|
|
events.FireEntitySpawned(S(1));
|
|
events.FireEntitySpawned(S(2));
|
|
events.FireEntitySpawned(S(3));
|
|
|
|
var seen = new List<uint>();
|
|
events.EntitySpawned += e => seen.Add(e.Id);
|
|
|
|
Assert.Equal(new uint[] { 1, 2, 3 }, seen);
|
|
}
|
|
|
|
[Fact]
|
|
public void FireAfterSubscribe_ReachesSubscriber()
|
|
{
|
|
var events = new WorldEvents();
|
|
var seen = new List<uint>();
|
|
events.EntitySpawned += e => seen.Add(e.Id);
|
|
|
|
events.FireEntitySpawned(S(10));
|
|
events.FireEntitySpawned(S(20));
|
|
|
|
Assert.Equal(new uint[] { 10, 20 }, seen);
|
|
}
|
|
|
|
[Fact]
|
|
public void ReplayPlusLive_DeliversExactlyOnceEach()
|
|
{
|
|
var events = new WorldEvents();
|
|
events.FireEntitySpawned(S(1)); // pre-subscribe
|
|
|
|
var seen = new List<uint>();
|
|
events.EntitySpawned += e => seen.Add(e.Id); // replay fires 1
|
|
|
|
events.FireEntitySpawned(S(2)); // live fires 2
|
|
|
|
Assert.Equal(new uint[] { 1, 2 }, seen);
|
|
}
|
|
|
|
[Fact]
|
|
public void Unsubscribe_StopsLiveDelivery()
|
|
{
|
|
var events = new WorldEvents();
|
|
var seen = new List<uint>();
|
|
Action<WorldEntitySnapshot> handler = e => seen.Add(e.Id);
|
|
|
|
events.EntitySpawned += handler;
|
|
events.FireEntitySpawned(S(1));
|
|
events.EntitySpawned -= handler;
|
|
events.FireEntitySpawned(S(2));
|
|
|
|
Assert.Equal(new uint[] { 1 }, seen);
|
|
}
|
|
|
|
[Fact]
|
|
public void HandlerThrowsDuringReplay_OtherReplayEntriesStillDelivered()
|
|
{
|
|
var events = new WorldEvents();
|
|
events.FireEntitySpawned(S(1));
|
|
events.FireEntitySpawned(S(2));
|
|
events.FireEntitySpawned(S(3));
|
|
|
|
var seen = new List<uint>();
|
|
events.EntitySpawned += e =>
|
|
{
|
|
if (e.Id == 2) throw new InvalidOperationException("boom");
|
|
seen.Add(e.Id);
|
|
};
|
|
|
|
// No exception propagates out of the += add; 1 and 3 were still delivered.
|
|
Assert.Contains(1u, seen);
|
|
Assert.Contains(3u, seen);
|
|
}
|
|
}
|