namespace AcDream.App.World; /// /// Serializes retail network/event mutations on the update thread. /// /// /// Retail SmartBox dispatch and CPhysics::UseTime never interleave two packet /// bodies on one CPhysicsObj call stack. App callbacks are synchronous, so an /// observer can otherwise re-enter a session event while an older packet or /// object quantum is still unwinding. Nested work is retained in arrival /// order and drained only after the current operation reaches its complete /// retail tail. This preserves the protocol's independent timestamp channels: /// an accepted State never cancels an accepted Position merely because their /// callbacks touched the same object. /// internal sealed class RetailInboundEventDispatcher { private interface IPendingOperation { void Invoke(); } private sealed class PendingAction(Action operation) : IPendingOperation { public void Invoke() => operation(); } private sealed class PendingStateOperation( TReceiver receiver, TState state, Action operation) : IPendingOperation { public void Invoke() => operation(receiver, state); } private readonly Queue _pending = new(); private bool _isDraining; internal int PendingCount => _pending.Count; internal bool IsDraining => _isDraining; internal void Run(Action operation) { ArgumentNullException.ThrowIfNull(operation); if (_isDraining) { _pending.Enqueue(new PendingAction(operation)); return; } _isDraining = true; try { operation(); DrainPending(); } catch { // A failed packet cannot leave later packets stranded for an // unrelated future frame, or replay them after partial teardown. _pending.Clear(); throw; } finally { _isDraining = false; } } /// /// Allocation-free non-reentrant dispatch for hot frame and packet paths. /// A heterogeneous wrapper is created only when an operation genuinely /// re-enters the active retail FIFO and therefore must outlive this call. /// internal void Run( TReceiver receiver, TState state, Action operation) { ArgumentNullException.ThrowIfNull(operation); if (_isDraining) { _pending.Enqueue( new PendingStateOperation( receiver, state, operation)); return; } _isDraining = true; try { operation(receiver, state); DrainPending(); } catch { _pending.Clear(); throw; } finally { _isDraining = false; } } private void DrainPending() { while (_pending.TryDequeue(out IPendingOperation? next)) next.Invoke(); } internal void Clear() { if (_isDraining) { throw new InvalidOperationException( "Inbound event work cannot be cleared while its retail FIFO is active."); } _pending.Clear(); } }