namespace AcDream.App.World; /// /// Strongly typed view over animation components owned by a /// . It owns only reusable traversal scratch; /// canonical component identity remains in the runtime. /// public sealed class LiveEntityAnimationRuntimeView : IEnumerable> where TAnimation : class, ILiveEntityAnimationRuntime { private readonly ILiveEntityRuntimeSource _runtime; private readonly List> _iterationSnapshot = new(); internal LiveEntityAnimationRuntimeView(ILiveEntityRuntimeSource runtime) => _runtime = runtime ?? throw new ArgumentNullException(nameof(runtime)); public int Count => _runtime.Current?.SpatialAnimationRuntimeCount ?? 0; public TAnimation this[uint localEntityId] { set { LiveEntityRuntime runtime = _runtime.Current ?? throw new InvalidOperationException("Live entity runtime is not initialized."); if (!runtime.TryGetServerGuid(localEntityId, out uint guid)) throw new InvalidOperationException($"No live entity owns local id 0x{localEntityId:X8}."); runtime.SetAnimationRuntime(guid, value); } } public bool TryGetValue(uint localEntityId, out TAnimation animation) { if (_runtime.Current?.TryGetAnimationRuntime(localEntityId, out var found) == true && found is TAnimation typed) { animation = typed; return true; } animation = null!; return false; } public bool Remove(uint localEntityId) { LiveEntityRuntime? runtime = _runtime.Current; return runtime is not null && runtime.TryGetServerGuid(localEntityId, out uint guid) && runtime.ClearAnimationRuntime(guid); } internal bool Remove(LiveEntityRecord record) { ArgumentNullException.ThrowIfNull(record); return _runtime.Current?.ClearAnimationRuntime(record) == true; } /// Copies only currently resident local IDs without boxing a /// dictionary-key enumerator. public void CopySpatialIdsTo(HashSet destination) { ArgumentNullException.ThrowIfNull(destination); LiveEntityRuntime? runtime = _runtime.Current; if (runtime is null) destination.Clear(); else runtime.CopySpatialAnimationLocalIdsTo(destination); } public Enumerator GetEnumerator() { LiveEntityRuntime? runtime = _runtime.Current; if (runtime is null) _iterationSnapshot.Clear(); else runtime.CopySpatialAnimationRuntimesTo(_iterationSnapshot); return new Enumerator(runtime, _iterationSnapshot); } IEnumerator> IEnumerable>.GetEnumerator() => GetEnumerator(); System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); /// /// Struct enumerator over a reusable frame snapshot. A runtime can rebucket /// or delete another entity from inside animation/physics callbacks without /// invalidating this traversal. Each entry is identity-checked immediately /// before it is yielded, so GUID reuse cannot advance the displaced owner. /// public struct Enumerator : IEnumerator> { private readonly LiveEntityRuntime? _runtime; private readonly List> _snapshot; private int _index; internal Enumerator( LiveEntityRuntime? runtime, List> snapshot) { _runtime = runtime; _snapshot = snapshot; _index = -1; Current = default; } public KeyValuePair Current { get; private set; } object System.Collections.IEnumerator.Current => Current; public bool MoveNext() { while (++_index < _snapshot.Count) { KeyValuePair pair = _snapshot[_index]; if (_runtime?.IsCurrentSpatialAnimation( pair.Key, pair.Value) == true) { Current = pair; return true; } } Current = default; return false; } public void Dispose() { } void System.Collections.IEnumerator.Reset() => throw new NotSupportedException(); } }