MoonTools.ECS/src/Filter.cs

211 lines
4.4 KiB
C#
Raw Normal View History

2022-04-08 05:52:03 +00:00
using System;
using System.Collections.Generic;
2022-03-06 06:12:27 +00:00
2023-11-03 19:40:26 +00:00
namespace MoonTools.ECS;
// TODO: do we want to get fancy with queries beyond Include and Exclude?
public class Filter
2022-03-06 06:12:27 +00:00
{
2023-11-03 19:40:26 +00:00
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
2022-03-06 06:12:27 +00:00
{
2023-11-03 19:40:26 +00:00
get
{
var empty = true;
foreach (var archetype in Archetypes)
{
if (archetype.Count > 0)
{
return false;
}
}
return empty;
}
}
2022-03-06 06:12:27 +00:00
2023-11-03 19:40:26 +00:00
public int Count
{
get
2022-04-08 05:52:03 +00:00
{
2023-11-03 19:40:26 +00:00
var count = 0;
foreach (var archetype in Archetypes)
{
count += archetype.Count;
}
return count;
2022-04-08 05:52:03 +00:00
}
2023-11-03 19:40:26 +00:00
}
2023-11-03 19:40:26 +00:00
public Entity RandomEntity
{
get
{
var randomIndex = RandomManager.Next(Count);
return NthEntity(randomIndex);
}
}
2022-04-08 05:52:03 +00:00
2023-11-03 19:40:26 +00:00
// WARNING: this WILL crash if the index is out of range!
public Entity NthEntity(int index)
{
foreach (var archetype in Archetypes)
{
2023-11-03 22:39:30 +00:00
if (index < archetype.Count)
2023-11-03 19:40:26 +00:00
{
return archetype.Entities[index];
}
2023-11-03 22:39:30 +00:00
index -= archetype.Count;
2023-11-03 19:40:26 +00:00
}
2022-08-17 22:29:38 +00:00
2023-11-03 19:40:26 +00:00
throw new InvalidOperationException("Filter index out of range!");
}
2023-01-27 00:34:15 +00:00
2023-11-03 19:40:26 +00:00
public void DestroyAllEntities()
{
foreach (var archetype in Archetypes)
2023-01-27 00:34:15 +00:00
{
2023-11-03 19:40:26 +00:00
archetype.ClearAll();
2023-01-27 00:34:15 +00:00
}
2023-11-03 19:40:26 +00:00
}
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;
2023-01-27 00:34:15 +00:00
2023-11-03 19:40:26 +00:00
public ArchetypeEnumerator(Filter filter)
2023-01-27 00:34:15 +00:00
{
2023-11-03 19:40:26 +00:00
var empty = filter.EmptyArchetype;
ArchetypeSearchQueue.Enqueue(empty);
// TODO: can we cache this search effectively?
while (ArchetypeSearchQueue.TryDequeue(out var current))
{
// exclude the empty archetype
var satisfiesFilter = filter.Included.Count != 0;
foreach (var componentId in filter.Included)
{
if (!current.Signature.Contains(componentId))
{
satisfiesFilter = false;
}
}
foreach (var componentId in filter.Excluded)
{
if (current.Signature.Contains(componentId))
{
satisfiesFilter = false;
}
}
if (satisfiesFilter)
{
ArchetypeQueue.Enqueue(current);
}
// breadth-first search
// ignore excluded component edges
2023-11-04 00:47:17 +00:00
foreach (var (componentId, archetype) in current.AddEdges)
2023-11-03 19:40:26 +00:00
{
2023-11-04 00:47:17 +00:00
if (!Explored.Contains(archetype) && !filter.Excluded.Contains(componentId))
2023-11-03 19:40:26 +00:00
{
2023-11-04 00:47:17 +00:00
Explored.Add(archetype);
ArchetypeSearchQueue.Enqueue(archetype);
}
}
foreach (var (componentId, archetype) in current.RemoveEdges)
{
if (!Explored.Contains(archetype))
{
Explored.Add(archetype);
ArchetypeSearchQueue.Enqueue(archetype);
2023-11-03 19:40:26 +00:00
}
}
}
}
public bool MoveNext()
{
return ArchetypeQueue.TryDequeue(out CurrentArchetype!);
}
public Archetype Current => CurrentArchetype;
}
public ref struct EntityEnumerator
{
private Entity CurrentEntity;
public EntityEnumerator GetEnumerator() => this;
// TODO: pool this
Queue<Entity> EntityQueue = new Queue<Entity>();
internal EntityEnumerator(Filter filter)
{
var archetypeEnumerator = new ArchetypeEnumerator(filter);
foreach (var archetype in archetypeEnumerator)
{
foreach (var entity in archetype.Entities)
{
EntityQueue.Enqueue(entity);
}
}
}
public bool MoveNext()
{
return EntityQueue.TryDequeue(out CurrentEntity);
2023-01-27 00:34:15 +00:00
}
2023-11-03 19:40:26 +00:00
public Entity 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 Entity Current => Filter.NthEntity(LinearCongruentialEnumerator.Current);
2022-04-08 05:52:03 +00:00
}
2022-03-06 06:12:27 +00:00
}