MoonTools.ECS/src/Rev2/Filter.cs

204 lines
4.2 KiB
C#

using System;
using System.Collections.Generic;
namespace MoonTools.ECS.Rev2;
// TODO: do we want to get fancy with queries beyond Include and Exclude?
public class Filter
{
private Archetype EmptyArchetype;
private HashSet<TypeId> Included;
private HashSet<TypeId> Excluded;
public EntityEnumerator Entities => new EntityEnumerator(this);
internal ArchetypeEnumerator Archetypes => new ArchetypeEnumerator(this);
public RandomEntityEnumerator EntitiesInRandomOrder => new RandomEntityEnumerator(this);
public bool Empty
{
get
{
var empty = true;
foreach (var archetype in Archetypes)
{
if (archetype.Count > 0)
{
return false;
}
}
return empty;
}
}
public int Count
{
get
{
var count = 0;
foreach (var archetype in Archetypes)
{
count += archetype.Count;
}
return count;
}
}
public EntityId RandomEntity
{
get
{
var randomIndex = RandomManager.Next(Count);
return NthEntity(randomIndex);
}
}
// WARNING: this WILL crash if the index is out of range!
public EntityId NthEntity(int index)
{
var count = 0;
foreach (var archetype in Archetypes)
{
count += archetype.Count;
if (index < count)
{
return archetype.RowToEntity[index];
}
index -= count;
}
throw new InvalidOperationException("Filter index out of range!");
}
public void DestroyAllEntities()
{
foreach (var archetype in Archetypes)
{
archetype.ClearAll();
}
}
internal Filter(Archetype emptyArchetype, HashSet<TypeId> included, HashSet<TypeId> excluded)
{
EmptyArchetype = emptyArchetype;
Included = included;
Excluded = excluded;
}
internal ref struct ArchetypeEnumerator
{
private Archetype CurrentArchetype;
// TODO: pool these
private Queue<Archetype> ArchetypeQueue = new Queue<Archetype>();
private Queue<Archetype> ArchetypeSearchQueue = new Queue<Archetype>();
private HashSet<Archetype> Explored = new HashSet<Archetype>();
public ArchetypeEnumerator GetEnumerator() => this;
public ArchetypeEnumerator(Filter filter)
{
var empty = filter.EmptyArchetype;
ArchetypeSearchQueue.Enqueue(empty);
while (ArchetypeSearchQueue.TryDequeue(out var current))
{
// exclude the empty archetype
var satisfiesFilter = filter.Included.Count != 0;
foreach (var componentId in filter.Included)
{
if (!current.ComponentToColumnIndex.ContainsKey(componentId))
{
satisfiesFilter = false;
}
}
foreach (var componentId in filter.Excluded)
{
if (current.ComponentToColumnIndex.ContainsKey(componentId))
{
satisfiesFilter = false;
}
}
if (satisfiesFilter)
{
ArchetypeQueue.Enqueue(current);
}
// breadth-first search
// ignore excluded component edges
foreach (var (componentId, edge) in current.Edges)
{
if (!Explored.Contains(edge.Add) && !filter.Excluded.Contains(componentId))
{
Explored.Add(edge.Add);
ArchetypeSearchQueue.Enqueue(edge.Add);
}
}
}
}
public bool MoveNext()
{
return ArchetypeQueue.TryDequeue(out CurrentArchetype!);
}
public Archetype Current => CurrentArchetype;
}
public ref struct EntityEnumerator
{
private EntityId CurrentEntity;
public EntityEnumerator GetEnumerator() => this;
// TODO: pool this
Queue<EntityId> EntityQueue = new Queue<EntityId>();
internal EntityEnumerator(Filter filter)
{
var archetypeEnumerator = new ArchetypeEnumerator(filter);
foreach (var archetype in archetypeEnumerator)
{
foreach (var entity in archetype.RowToEntity)
{
EntityQueue.Enqueue(entity);
}
}
}
public bool MoveNext()
{
return EntityQueue.TryDequeue(out CurrentEntity);
}
public EntityId Current => CurrentEntity;
}
public ref struct RandomEntityEnumerator
{
private Filter Filter;
private LinearCongruentialEnumerator LinearCongruentialEnumerator;
public RandomEntityEnumerator GetEnumerator() => this;
internal RandomEntityEnumerator(Filter filter)
{
Filter = filter;
LinearCongruentialEnumerator =
RandomManager.LinearCongruentialSequence(filter.Count);
}
public bool MoveNext() => LinearCongruentialEnumerator.MoveNext();
public EntityId Current => Filter.NthEntity(LinearCongruentialEnumerator.Current);
}
}