feat(core): add IGameState, IEvents, WorldEvents with replay-on-subscribe

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>
This commit is contained in:
Erik 2026-04-10 20:29:29 +02:00
parent 22f684e8c6
commit 0c0c042dca
10 changed files with 212 additions and 3 deletions

View file

@ -0,0 +1,56 @@
// src/AcDream.Core/Plugins/WorldEvents.cs
using AcDream.Plugin.Abstractions;
namespace AcDream.Core.Plugins;
public sealed class WorldEvents : IEvents
{
private readonly object _lock = new();
private readonly List<WorldEntitySnapshot> _alreadySpawned = new();
private Action<WorldEntitySnapshot>? _subscribers;
/// <summary>
/// Called by the host as each entity is hydrated into the world. Records the
/// snapshot for later replay and dispatches to current subscribers.
/// </summary>
public void FireEntitySpawned(WorldEntitySnapshot snapshot)
{
Action<WorldEntitySnapshot>? toNotify;
lock (_lock)
{
_alreadySpawned.Add(snapshot);
toNotify = _subscribers;
}
if (toNotify is null) return;
foreach (Action<WorldEntitySnapshot> handler in toNotify.GetInvocationList())
{
try { handler(snapshot); }
catch { /* plugin errors don't propagate out of event dispatch */ }
}
}
public event Action<WorldEntitySnapshot> EntitySpawned
{
add
{
WorldEntitySnapshot[] replay;
lock (_lock)
{
_subscribers += value;
replay = _alreadySpawned.ToArray();
}
// Replay outside the lock to avoid deadlock if a handler re-enters.
foreach (var s in replay)
{
try { value(s); }
catch { /* plugin errors don't propagate out of += */ }
}
}
remove
{
lock (_lock)
_subscribers -= value;
}
}
}

View file

@ -0,0 +1,14 @@
// src/AcDream.Core/Plugins/WorldGameState.cs
using AcDream.Plugin.Abstractions;
namespace AcDream.Core.Plugins;
public sealed class WorldGameState : IGameState
{
private readonly List<WorldEntitySnapshot> _entities = new();
public IReadOnlyList<WorldEntitySnapshot> Entities => _entities;
/// <summary>Called by the host as each entity is hydrated.</summary>
public void Add(WorldEntitySnapshot snapshot) => _entities.Add(snapshot);
}