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>
56 lines
1.6 KiB
C#
56 lines
1.6 KiB
C#
// 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;
|
|
}
|
|
}
|
|
}
|