using System.Runtime.CompilerServices; namespace OECS; /// /// Provides zero-allocation ref struct iterators for querying entities. /// Use with foreach or manual while (iter.MoveNext()). /// Supports break, continue, and early returns. /// public static class EntityIterator { /// /// Returns an iterator over entities matching the query, with ref access /// to one component type. /// public static Select1 Select( this World world, QueryDescriptor query) where T1 : struct { return new Select1(world, query); } /// /// Returns an iterator over entities that have a component of type /// . No Without filter is applied. /// public static Select1 Select( this World world) where T1 : struct { return new Select1(world, new QueryDescriptor(new HashSet(), new HashSet())); } /// /// Returns an iterator over entities matching the query, with ref access /// to two component types. /// public static Select2 Select( this World world, QueryDescriptor query) where T1 : struct where T2 : struct { return new Select2(world, query); } /// /// Returns an iterator over entities that have both components of type /// and . /// No Without filter is applied. /// public static Select2 Select( this World world) where T1 : struct where T2 : struct { return new Select2(world, new QueryDescriptor(new HashSet(), new HashSet())); } /// /// Returns an iterator over entities matching the query, with ref access /// to three component types. /// public static Select3 Select( this World world, QueryDescriptor query) where T1 : struct where T2 : struct where T3 : struct { return new Select3(world, query); } /// /// Returns an iterator over entities that have all three components of type /// , , and /// . No Without filter is applied. /// public static Select3 Select( this World world) where T1 : struct where T2 : struct where T3 : struct { return new Select3(world, new QueryDescriptor(new HashSet(), new HashSet())); } } /// /// Ref struct iterator for queries with one component type. /// public ref struct Select1 where T1 : struct { private readonly World _world; private readonly SparseSet? _set; private readonly ComponentStore _store; private readonly IReadOnlySet _without; private int _index; private readonly int _count; internal Select1(World world, QueryDescriptor query) { _world = world; _store = world.Components; _without = query.Without; _set = _store.GetSet(typeof(T1)) as SparseSet; _count = _set?.Count ?? 0; _index = -1; _world.BeginIteration(); } public Entity CurrentEntity { get; private set; } public ref T1 Current1 => ref _set!.Get(CurrentEntity); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { if (_set == null) return false; while (++_index < _count) { CurrentEntity = _set.DenseEntities[_index]; if (CurrentEntity.Id == 1) continue; // Skip singleton. if (!PassesWithout()) continue; return true; } return false; } public Select1 GetEnumerator() => this; public void Dispose() { _world.EndIteration(); } private bool PassesWithout() { foreach (var type in _without) { var set = _store.GetSet(type); if (set != null && set.Contains(CurrentEntity)) return false; } return true; } } /// /// Ref struct iterator for queries with two component types. /// Drives iteration from the smaller sparse set to minimize probes. /// public ref struct Select2 where T1 : struct where T2 : struct { private readonly World _world; private readonly SparseSet? _set1; private readonly SparseSet? _set2; private readonly ComponentStore _store; private readonly IReadOnlySet _without; private int _index; private readonly int _count; private readonly bool _swapped; // true when driving from _set2 internal Select2(World world, QueryDescriptor query) { _world = world; _store = world.Components; _without = query.Without; _set1 = _store.GetSet(typeof(T1)) as SparseSet; _set2 = _store.GetSet(typeof(T2)) as SparseSet; int count1 = _set1?.Count ?? 0; int count2 = _set2?.Count ?? 0; _swapped = count2 < count1; _count = _swapped ? count2 : count1; _index = -1; _world.BeginIteration(); } public Entity CurrentEntity { get; private set; } public ref T1 Current1 => ref _set1!.Get(CurrentEntity); public ref T2 Current2 => ref _set2!.Get(CurrentEntity); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { if (_set1 == null || _set2 == null) return false; while (++_index < _count) { if (_swapped) { CurrentEntity = _set2!.DenseEntities[_index]; if (CurrentEntity.Id == 1) continue; if (!_set1.Contains(CurrentEntity)) continue; } else { CurrentEntity = _set1.DenseEntities[_index]; if (CurrentEntity.Id == 1) continue; if (!_set2.Contains(CurrentEntity)) continue; } if (!PassesWithout()) continue; return true; } return false; } public Select2 GetEnumerator() => this; public void Dispose() { _world.EndIteration(); } private bool PassesWithout() { foreach (var type in _without) { var set = _store.GetSet(type); if (set != null && set.Contains(CurrentEntity)) return false; } return true; } } /// /// Ref struct iterator for queries with three component types. /// Drives iteration from the smallest sparse set to minimize probes. /// public ref struct Select3 where T1 : struct where T2 : struct where T3 : struct { private readonly World _world; private readonly SparseSet? _set1; private readonly SparseSet? _set2; private readonly SparseSet? _set3; private readonly ComponentStore _store; private readonly IReadOnlySet _without; private int _index; private readonly int _count; private readonly int _driver; // 0 = _set1, 1 = _set2, 2 = _set3 internal Select3(World world, QueryDescriptor query) { _world = world; _store = world.Components; _without = query.Without; _set1 = _store.GetSet(typeof(T1)) as SparseSet; _set2 = _store.GetSet(typeof(T2)) as SparseSet; _set3 = _store.GetSet(typeof(T3)) as SparseSet; int count1 = _set1?.Count ?? 0; int count2 = _set2?.Count ?? 0; int count3 = _set3?.Count ?? 0; if (count2 <= count1 && count2 <= count3) { _driver = 1; _count = count2; } else if (count3 <= count1 && count3 <= count2) { _driver = 2; _count = count3; } else { _driver = 0; _count = count1; } _index = -1; _world.BeginIteration(); } public Entity CurrentEntity { get; private set; } public ref T1 Current1 => ref _set1!.Get(CurrentEntity); public ref T2 Current2 => ref _set2!.Get(CurrentEntity); public ref T3 Current3 => ref _set3!.Get(CurrentEntity); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { if (_set1 == null || _set2 == null || _set3 == null) return false; while (++_index < _count) { switch (_driver) { case 0: CurrentEntity = _set1.DenseEntities[_index]; if (CurrentEntity.Id == 1) continue; if (!_set2.Contains(CurrentEntity)) continue; if (!_set3.Contains(CurrentEntity)) continue; break; case 1: CurrentEntity = _set2!.DenseEntities[_index]; if (CurrentEntity.Id == 1) continue; if (!_set1.Contains(CurrentEntity)) continue; if (!_set3.Contains(CurrentEntity)) continue; break; case 2: CurrentEntity = _set3!.DenseEntities[_index]; if (CurrentEntity.Id == 1) continue; if (!_set1.Contains(CurrentEntity)) continue; if (!_set2.Contains(CurrentEntity)) continue; break; } if (!PassesWithout()) continue; return true; } return false; } public Select3 GetEnumerator() => this; public void Dispose() { _world.EndIteration(); } private bool PassesWithout() { foreach (var type in _without) { var set = _store.GetSet(type); if (set != null && set.Contains(CurrentEntity)) return false; } return true; } }