// 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 _alreadySpawned = new(); private Action? _subscribers; /// /// Called by the host as each entity is hydrated into the world. Records the /// snapshot for later replay and dispatches to current subscribers. /// public void FireEntitySpawned(WorldEntitySnapshot snapshot) { Action? toNotify; lock (_lock) { _alreadySpawned.Add(snapshot); toNotify = _subscribers; } if (toNotify is null) return; foreach (Action handler in toNotify.GetInvocationList()) { try { handler(snapshot); } catch { /* plugin errors don't propagate out of event dispatch */ } } } public event Action 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; } } }