implement Filter

rev2
cosmonaut 2023-10-24 13:13:14 -07:00
parent 272fd6b492
commit b3ff7e3f1c
3 changed files with 219 additions and 123 deletions

View File

@ -13,13 +13,12 @@ namespace MoonTools.ECS.Rev2
new Dictionary<ComponentId, int>();
public SortedDictionary<ComponentId, ArchetypeEdge> Edges = new SortedDictionary<ComponentId, ArchetypeEdge>();
public int Count;
public int Count => RowToEntity.Count;
public Archetype(ArchetypeId id, ArchetypeSignature signature)
{
Id = id;
Signature = signature;
Count = 0;
}
}
}

View File

@ -1,36 +1,161 @@
using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
namespace MoonTools.ECS.Rev2
{
// TODO: do we want to get fancy with queries beyond Include and Exclude?
// TODO: need an edge iterator as part of this nested horseshit
public class Filter
{
private Archetype Start;
private Archetype EmptyArchetype;
private HashSet<ComponentId> Included;
private HashSet<ComponentId> Excluded;
public EntityEnumerator Entities => new EntityEnumerator(Start);
public EntityEnumerator Entities => new EntityEnumerator(this);
internal ArchetypeEnumerator Archetypes => new ArchetypeEnumerator(this);
public RandomEntityEnumerator EntitiesInRandomOrder => new RandomEntityEnumerator(this);
private ref struct FilterEnumerator
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;
}
}
// 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 EntityId RandomEntity
{
get
{
var randomIndex = RandomManager.Next(Count);
return NthEntity(randomIndex);
}
}
public Filter(Archetype emptyArchetype, HashSet<ComponentId> included, HashSet<ComponentId> excluded)
{
EmptyArchetype = emptyArchetype;
Included = included;
Excluded = excluded;
}
internal ref struct ArchetypeEnumerator
{
private Archetype CurrentArchetype;
private bool Active;
public FilterEnumerator(Archetype start)
// 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)
{
CurrentArchetype = start;
Active = false;
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);
}
// if the current archetype satisfies the filter, we need to add all edges that
// do not have an excluded component
// if the current archetype does not satisfy the filter, we need to add all edges that
// include an included component
foreach (var (componentId, edge) in current.Edges)
{
if (satisfiesFilter)
{
if (!filter.Excluded.Contains(componentId) && !Explored.Contains(edge.Add))
{
Explored.Add(edge.Add);
ArchetypeSearchQueue.Enqueue(edge.Add);
}
}
else
{
if (filter.Included.Contains(componentId) && !Explored.Contains(edge.Add))
{
Explored.Add(edge.Add);
ArchetypeSearchQueue.Enqueue(edge.Add);
}
}
}
}
}
public bool MoveNext()
{
if (!Active)
{
Active = true;
return true;
}
// TODO: go to next available edge
return ArchetypeQueue.TryDequeue(out CurrentArchetype!);
}
public Archetype Current => CurrentArchetype;
@ -38,39 +163,50 @@ namespace MoonTools.ECS.Rev2
public ref struct EntityEnumerator
{
private FilterEnumerator FilterEnumerator;
private ReverseSpanEnumerator<EntityId> EntityListEnumerator;
private bool EntityListEnumeratorActive;
private EntityId CurrentEntity;
public EntityEnumerator GetEnumerator() => this;
public EntityEnumerator(Archetype start)
// TODO: pool this
Queue<EntityId> EntityQueue = new Queue<EntityId>();
internal EntityEnumerator(Filter filter)
{
FilterEnumerator = new FilterEnumerator(start);
var archetypeEnumerator = new ArchetypeEnumerator(filter);
foreach (var archetype in archetypeEnumerator)
{
foreach (var entity in archetype.RowToEntity)
{
EntityQueue.Enqueue(entity);
}
}
}
public bool MoveNext()
{
if (!EntityListEnumeratorActive || !EntityListEnumerator.MoveNext())
return EntityQueue.TryDequeue(out CurrentEntity);
}
public EntityId Current => CurrentEntity;
}
public ref struct RandomEntityEnumerator
{
if (!FilterEnumerator.MoveNext())
private Filter Filter;
private LinearCongruentialEnumerator LinearCongruentialEnumerator;
public RandomEntityEnumerator GetEnumerator() => this;
internal RandomEntityEnumerator(Filter filter)
{
return false;
Filter = filter;
LinearCongruentialEnumerator =
RandomManager.LinearCongruentialSequence(filter.Count);
}
if (FilterEnumerator.Current.RowToEntity.Count != 0)
{
EntityListEnumerator = new ReverseSpanEnumerator<EntityId>(CollectionsMarshal.AsSpan(FilterEnumerator.Current.RowToEntity));
EntityListEnumeratorActive = true;
}
return MoveNext();
}
return true;
}
public EntityId Current => EntityListEnumerator.Current;
public bool MoveNext() => LinearCongruentialEnumerator.MoveNext();
public EntityId Current => Filter.NthEntity(LinearCongruentialEnumerator.Current);
}
}
}

View File

@ -27,6 +27,8 @@ namespace MoonTools.ECS.Rev2
IdAssigner<EntityId> EntityIdAssigner = new IdAssigner<EntityId>();
IdAssigner<ComponentId> ComponentIdAssigner = new IdAssigner<ComponentId>();
public readonly Archetype EmptyArchetype;
private bool IsDisposed;
public delegate void RefAction<T1, T2>(ref T1 arg1, ref T2 arg2);
@ -34,7 +36,7 @@ namespace MoonTools.ECS.Rev2
public World()
{
// Create the Empty Archetype
CreateArchetype(ArchetypeSignature.Empty);
EmptyArchetype = CreateArchetype(ArchetypeSignature.Empty);
}
private Archetype CreateArchetype(ArchetypeSignature signature)
@ -62,7 +64,7 @@ namespace MoonTools.ECS.Rev2
{
var entityId = EntityIdAssigner.Assign();
var emptyArchetype = ArchetypeIndex[ArchetypeSignature.Empty];
EntityIndex.Add(entityId, new Record(emptyArchetype, 0));
EntityIndex.Add(entityId, new Record(emptyArchetype, emptyArchetype.Count));
emptyArchetype.RowToEntity.Add(entityId);
return entityId;
}
@ -210,14 +212,15 @@ namespace MoonTools.ECS.Rev2
archetype.Components[i].Delete(row);
}
if (archetype.Count > 1)
if (row != archetype.Count - 1)
{
// update row to entity lookup on archetype
archetype.RowToEntity[row] = archetype.RowToEntity[archetype.Count - 1];
archetype.RowToEntity.RemoveAt(archetype.Count - 1);
// move last row entity to open spot
var lastRowEntity = archetype.RowToEntity[archetype.Count - 1];
archetype.RowToEntity[row] = lastRowEntity;
EntityIndex[lastRowEntity] = new Record(archetype, row);
}
archetype.Count -= 1;
archetype.RowToEntity.RemoveAt(archetype.Count - 1);
EntityIndex.Remove(entityId);
EntityIdAssigner.Unassign(entityId);
}
@ -236,20 +239,19 @@ namespace MoonTools.ECS.Rev2
from.Components[i].Delete(row);
}
if (from.Count > 1)
if (row != from.Count - 1)
{
// update row to entity lookup on from archetype
from.RowToEntity[row] = from.RowToEntity[from.Count - 1];
from.RowToEntity.RemoveAt(from.Count - 1);
EntityIndex[from.RowToEntity[row]] = new Record(from, row);
// move last row entity to open spot
var lastRowEntity = from.RowToEntity[from.Count - 1];
from.RowToEntity[row] = lastRowEntity;
EntityIndex[lastRowEntity] = new Record(from, row);
}
from.RowToEntity.RemoveAt(from.Count - 1);
// update row to entity lookup on to archetype
EntityIndex[entityId] = new Record(to, to.Count);
to.RowToEntity.Add(entityId);
to.Count += 1;
from.Count -= 1;
}
private void MoveEntityToLowerArchetype(EntityId entityId, int row, Archetype from, Archetype to, ComponentId removed)
@ -269,32 +271,31 @@ namespace MoonTools.ECS.Rev2
}
}
if (from.Count > 1)
if (row != from.Count - 1)
{
// update row to entity lookup on from archetype
from.RowToEntity[row] = from.RowToEntity[from.Count - 1];
from.RowToEntity.RemoveAt(from.Count - 1);
EntityIndex[from.RowToEntity[row]] = new Record(from, row);
var lastRowEntity = from.RowToEntity[from.Count - 1];
from.RowToEntity[row] = lastRowEntity;
EntityIndex[lastRowEntity] = new Record(from, row);
}
from.RowToEntity.RemoveAt(from.Count - 1);
// update row to entity lookup on to archetype
EntityIndex[entityId] = new Record(to, to.Count);
to.RowToEntity.Add(entityId);
to.Count += 1;
from.Count -= 1;
}
public unsafe void ForEachEntity<T, T1, T2>(ArchetypeSignature signature,
public unsafe void ForEachEntity<T, T1, T2>(Filter filter,
T rowForEachContainer) where T : IForEach<T1, T2> where T1 : unmanaged where T2 : unmanaged
{
var archetype = ArchetypeIndex[signature];
var componentIdOne = signature[0];
foreach (var archetype in filter.Archetypes)
{
var componentIdOne = archetype.Signature[0];
var columnIndexOne = archetype.ComponentToColumnIndex[componentIdOne];
var columnOneElements = archetype.Components[columnIndexOne].Elements;
var componentIdTwo = signature[1];
var componentIdTwo = archetype.Signature[1];
var columnIndexTwo = archetype.ComponentToColumnIndex[componentIdTwo];
var columnTwoElements = archetype.Components[columnIndexTwo].Elements;
@ -302,25 +303,18 @@ namespace MoonTools.ECS.Rev2
{
rowForEachContainer.Update(ref ((T1*) columnOneElements)[i], ref ((T2*) columnTwoElements)[i]);
}
foreach (var edge in archetype.Edges.Values)
{
if (edge.Add != archetype)
{
ForEachEntity<T, T1, T2>(edge.Add.Signature, rowForEachContainer);
}
}
}
public unsafe void ForEachEntity<T1, T2>(ArchetypeSignature signature, RefAction<T1, T2> rowAction) where T1 : unmanaged where T2 : unmanaged
public unsafe void ForEachEntity<T1, T2>(Filter filter, RefAction<T1, T2> rowAction) where T1 : unmanaged where T2 : unmanaged
{
var archetype = ArchetypeIndex[signature];
var componentIdOne = signature[0];
foreach (var archetype in filter.Archetypes)
{
var componentIdOne = archetype.Signature[0];
var columnIndexOne = archetype.ComponentToColumnIndex[componentIdOne];
var columnOneElements = archetype.Components[columnIndexOne].Elements;
var componentIdTwo = signature[1];
var componentIdTwo = archetype.Signature[1];
var columnIndexTwo = archetype.ComponentToColumnIndex[componentIdTwo];
var columnTwoElements = archetype.Components[columnIndexTwo].Elements;
@ -328,41 +322,8 @@ namespace MoonTools.ECS.Rev2
{
rowAction(ref ((T1*) columnOneElements)[i], ref ((T2*) columnTwoElements)[i]);
}
foreach (var edge in archetype.Edges.Values)
{
if (edge.Add != archetype)
{
ForEachEntity(edge.Add.Signature, rowAction);
}
}
}
public void ForEachEntity(ArchetypeSignature signature, Action<EntityId> rowAction)
{
var archetype = ArchetypeIndex[signature];
for (int i = 0; i < archetype.Count; i += 1)
{
rowAction(archetype.RowToEntity[i]);
}
// recursion might get too hairy here
foreach (var edge in archetype.Edges.Values)
{
if (edge.Add != archetype)
{
ForEachEntity(edge.Add.Signature, rowAction);
}
}
}
public ReverseSpanEnumerator<EntityId> Entities(ArchetypeSignature signature)
{
var archetype = ArchetypeIndex[signature];
return new ReverseSpanEnumerator<EntityId>(
CollectionsMarshal.AsSpan(archetype.RowToEntity));
}
protected virtual void Dispose(bool disposing)
{