From b3ff7e3f1c9a7f8153605ea597cc16c12a0e306d Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 24 Oct 2023 13:13:14 -0700 Subject: [PATCH] implement Filter --- src/Rev2/Archetype.cs | 3 +- src/Rev2/Filter.cs | 210 ++++++++++++++++++++++++++++++++++-------- src/Rev2/World.cs | 129 +++++++++----------------- 3 files changed, 219 insertions(+), 123 deletions(-) diff --git a/src/Rev2/Archetype.cs b/src/Rev2/Archetype.cs index 9f6b777..36aeafb 100644 --- a/src/Rev2/Archetype.cs +++ b/src/Rev2/Archetype.cs @@ -13,13 +13,12 @@ namespace MoonTools.ECS.Rev2 new Dictionary(); public SortedDictionary Edges = new SortedDictionary(); - public int Count; + public int Count => RowToEntity.Count; public Archetype(ArchetypeId id, ArchetypeSignature signature) { Id = id; Signature = signature; - Count = 0; } } } diff --git a/src/Rev2/Filter.cs b/src/Rev2/Filter.cs index 3bdb9ee..b438076 100644 --- a/src/Rev2/Filter.cs +++ b/src/Rev2/Filter.cs @@ -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 Included; + private HashSet 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 included, HashSet 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 ArchetypeQueue = new Queue(); + private Queue ArchetypeSearchQueue = new Queue(); + private HashSet Explored = new HashSet(); + + 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 EntityListEnumerator; - private bool EntityListEnumeratorActive; + private EntityId CurrentEntity; public EntityEnumerator GetEnumerator() => this; - public EntityEnumerator(Archetype start) + // TODO: pool this + Queue EntityQueue = new Queue(); + + 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()) - { - if (!FilterEnumerator.MoveNext()) - { - return false; - } - - if (FilterEnumerator.Current.RowToEntity.Count != 0) - { - EntityListEnumerator = new ReverseSpanEnumerator(CollectionsMarshal.AsSpan(FilterEnumerator.Current.RowToEntity)); - EntityListEnumeratorActive = true; - } - - return MoveNext(); - } - - return true; + return EntityQueue.TryDequeue(out CurrentEntity); } - public EntityId Current => EntityListEnumerator.Current; + 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); } } } diff --git a/src/Rev2/World.cs b/src/Rev2/World.cs index 9f4d86a..cbdedfe 100644 --- a/src/Rev2/World.cs +++ b/src/Rev2/World.cs @@ -27,6 +27,8 @@ namespace MoonTools.ECS.Rev2 IdAssigner EntityIdAssigner = new IdAssigner(); IdAssigner ComponentIdAssigner = new IdAssigner(); + public readonly Archetype EmptyArchetype; + private bool IsDisposed; public delegate void RefAction(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,101 +271,60 @@ 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(ArchetypeSignature signature, + public unsafe void ForEachEntity(Filter filter, T rowForEachContainer) where T : IForEach where T1 : unmanaged where T2 : unmanaged { - var archetype = ArchetypeIndex[signature]; - - var componentIdOne = signature[0]; - var columnIndexOne = archetype.ComponentToColumnIndex[componentIdOne]; - var columnOneElements = archetype.Components[columnIndexOne].Elements; - - var componentIdTwo = signature[1]; - var columnIndexTwo = archetype.ComponentToColumnIndex[componentIdTwo]; - var columnTwoElements = archetype.Components[columnIndexTwo].Elements; - - for (int i = archetype.Count - 1; i >= 0; i -= 1) + foreach (var archetype in filter.Archetypes) { - rowForEachContainer.Update(ref ((T1*) columnOneElements)[i], ref ((T2*) columnTwoElements)[i]); - } + var componentIdOne = archetype.Signature[0]; + var columnIndexOne = archetype.ComponentToColumnIndex[componentIdOne]; + var columnOneElements = archetype.Components[columnIndexOne].Elements; - foreach (var edge in archetype.Edges.Values) - { - if (edge.Add != archetype) + var componentIdTwo = archetype.Signature[1]; + var columnIndexTwo = archetype.ComponentToColumnIndex[componentIdTwo]; + var columnTwoElements = archetype.Components[columnIndexTwo].Elements; + + for (int i = archetype.Count - 1; i >= 0; i -= 1) { - ForEachEntity(edge.Add.Signature, rowForEachContainer); + rowForEachContainer.Update(ref ((T1*) columnOneElements)[i], ref ((T2*) columnTwoElements)[i]); } } } - public unsafe void ForEachEntity(ArchetypeSignature signature, RefAction rowAction) where T1 : unmanaged where T2 : unmanaged + public unsafe void ForEachEntity(Filter filter, RefAction rowAction) where T1 : unmanaged where T2 : unmanaged { - var archetype = ArchetypeIndex[signature]; - - var componentIdOne = signature[0]; - var columnIndexOne = archetype.ComponentToColumnIndex[componentIdOne]; - var columnOneElements = archetype.Components[columnIndexOne].Elements; - - var componentIdTwo = signature[1]; - var columnIndexTwo = archetype.ComponentToColumnIndex[componentIdTwo]; - var columnTwoElements = archetype.Components[columnIndexTwo].Elements; - - for (int i = archetype.Count - 1; i >= 0; i -= 1) + foreach (var archetype in filter.Archetypes) { - rowAction(ref ((T1*) columnOneElements)[i], ref ((T2*) columnTwoElements)[i]); - } + var componentIdOne = archetype.Signature[0]; + var columnIndexOne = archetype.ComponentToColumnIndex[componentIdOne]; + var columnOneElements = archetype.Components[columnIndexOne].Elements; - foreach (var edge in archetype.Edges.Values) - { - if (edge.Add != archetype) + var componentIdTwo = archetype.Signature[1]; + var columnIndexTwo = archetype.ComponentToColumnIndex[componentIdTwo]; + var columnTwoElements = archetype.Components[columnIndexTwo].Elements; + + for (int i = archetype.Count - 1; i >= 0; i -= 1) { - ForEachEntity(edge.Add.Signature, rowAction); + rowAction(ref ((T1*) columnOneElements)[i], ref ((T2*) columnTwoElements)[i]); } } } - public void ForEachEntity(ArchetypeSignature signature, Action 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 Entities(ArchetypeSignature signature) - { - var archetype = ArchetypeIndex[signature]; - return new ReverseSpanEnumerator( - CollectionsMarshal.AsSpan(archetype.RowToEntity)); - } - protected virtual void Dispose(bool disposing) { if (!IsDisposed)