using System;
using System.Collections.Generic;
using System.Numerics;
using DatReaderWriter.Types;
namespace AcDream.Core.Physics;
///
/// Composite — fans hooks out to any
/// number of registered sinks. Each downstream subsystem (audio,
/// particles, combat, etc.) registers once at startup and the router
/// broadcasts every hook.
///
///
/// Ordering matters only if a sink mutates shared state that a later
/// sink reads; the default order is registration order. Register
/// audio + particles before combat so they fire even if combat
/// chooses to ignore / modify the hook.
///
///
///
/// Thread-safety: mutators () are locked; the hot
/// path iterates a snapshot array to avoid
/// allocation and lock contention in the render thread.
///
///
public sealed class AnimationHookRouter : IAnimationHookSink
{
private readonly object _gate = new();
private IAnimationHookSink[] _sinks = Array.Empty();
///
/// Register a sink. Idempotent — adding the same instance twice is a no-op.
///
public void Register(IAnimationHookSink sink)
{
ArgumentNullException.ThrowIfNull(sink);
lock (_gate)
{
foreach (var existing in _sinks)
if (ReferenceEquals(existing, sink)) return;
var updated = new IAnimationHookSink[_sinks.Length + 1];
Array.Copy(_sinks, updated, _sinks.Length);
updated[_sinks.Length] = sink;
_sinks = updated;
}
}
///
/// Unregister a sink. No-op if not registered.
///
public void Unregister(IAnimationHookSink sink)
{
if (sink is null) return;
lock (_gate)
{
int idx = -1;
for (int i = 0; i < _sinks.Length; i++)
if (ReferenceEquals(_sinks[i], sink)) { idx = i; break; }
if (idx < 0) return;
var updated = new IAnimationHookSink[_sinks.Length - 1];
for (int i = 0, j = 0; i < _sinks.Length; i++)
if (i != idx) updated[j++] = _sinks[i];
_sinks = updated;
}
}
///
/// Snapshot of currently-registered sinks (for diagnostics / tests).
///
public IReadOnlyList Sinks => _sinks;
///
public void OnHook(uint entityId, Vector3 entityWorldPosition, AnimationHook hook)
{
// Snapshot — no lock in the hot path (render thread).
var sinks = _sinks;
for (int i = 0; i < sinks.Length; i++)
{
try
{
sinks[i].OnHook(entityId, entityWorldPosition, hook);
}
catch
{
// Swallow — one misbehaving sink must not take down the
// entire animation tick. Individual subsystems can log
// their own errors internally.
}
}
}
}