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;
}
}