diff --git a/src/IndexableSet.cs b/src/Collections/IndexableSet.cs similarity index 88% rename from src/IndexableSet.cs rename to src/Collections/IndexableSet.cs index 1956756..02e985c 100644 --- a/src/IndexableSet.cs +++ b/src/Collections/IndexableSet.cs @@ -60,12 +60,27 @@ namespace MoonTools.ECS.Collections return false; } + /* var lastElement = array[Count - 1]; var index = indices[element]; array[index] = lastElement; indices[lastElement] = index; count -= 1; indices.Remove(element); + */ + + // FIXME: we can probably undo this change + + var index = indices[element]; + + for (var i = index; i < Count - 1; i += 1) + { + array[i] = array[i + 1]; + indices[array[i]] = i; + } + + indices.Remove(element); + count -= 1; return true; } diff --git a/src/Collections/NativeArray.cs b/src/Collections/NativeArray.cs new file mode 100644 index 0000000..cb4e81b --- /dev/null +++ b/src/Collections/NativeArray.cs @@ -0,0 +1,110 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace MoonTools.ECS.Collections; + +public unsafe class NativeArray : IDisposable where T : unmanaged +{ + private T* Array; + private int count; + private int capacity; + private int elementSize; + + public int Count => count; + + public Span.Enumerator GetEnumerator() => new Span(Array, count).GetEnumerator(); + + private bool disposed; + + public NativeArray(int capacity = 16) + { + this.capacity = capacity; + elementSize = Unsafe.SizeOf(); + Array = (T*) NativeMemory.Alloc((nuint) (capacity * elementSize)); + count = 0; + } + + public ref T this[int i] => ref Array[i]; + + public void Add(T item) + { + if (count >= capacity) + { + capacity *= 2; + Array = (T*) NativeMemory.Realloc(Array, (nuint) (capacity * Unsafe.SizeOf())); + } + + Array[count] = item; + count += 1; + } + + public void RemoveLastElement() + { + count -= 1; + } + + public bool TryPop(out T element) + { + if (count > 0) + { + element = Array[count - 1]; + count -= 1; + return true; + } + + element = default; + return false; + } + + public void Clear() + { + count = 0; + } + + private void ResizeTo(int size) + { + capacity = size; + Array = (T*) NativeMemory.Realloc((void*) Array, (nuint) (elementSize * capacity)); + } + + public void CopyTo(NativeArray other) + { + if (count >= other.capacity) + { + other.ResizeTo(Count); + } + + NativeMemory.Copy( + (void*) Array, + (void*) other.Array, + (nuint) (elementSize * Count) + ); + + other.count = count; + } + + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + NativeMemory.Free(Array); + Array = null; + + disposed = true; + } + } + + ~NativeArray() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: false); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} diff --git a/src/Collections/NativeArrayUntyped.cs b/src/Collections/NativeArrayUntyped.cs new file mode 100644 index 0000000..5cf2fd8 --- /dev/null +++ b/src/Collections/NativeArrayUntyped.cs @@ -0,0 +1,128 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace MoonTools.ECS.Collections; + +internal unsafe class NativeArray : IDisposable +{ + public nint Elements; + public int Count; + + private int Capacity; + public readonly int ElementSize; + + private bool IsDisposed; + + public NativeArray(int elementSize) + { + Capacity = 16; + Count = 0; + ElementSize = elementSize; + + Elements = (nint) NativeMemory.Alloc((nuint) (ElementSize * Capacity)); + } + + public Span ToSpan() + { + return new Span((void*) Elements, Count); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T Get(int i) where T : unmanaged + { + return ref ((T*) Elements)[i]; + } + + private void Resize() + { + Capacity *= 2; + Elements = (nint) NativeMemory.Realloc((void*) Elements, (nuint) (ElementSize * Capacity)); + } + + private void ResizeTo(int capacity) + { + Capacity = capacity; + Elements = (nint) NativeMemory.Realloc((void*) Elements, (nuint) (ElementSize * Capacity)); + } + + // Fills gap by copying final element to the deleted index + public void Delete(int index) + { + if (Count > 1) + { + NativeMemory.Copy( + (void*) (Elements + ((Count - 1) * ElementSize)), + (void*) (Elements + (index * ElementSize)), + (nuint) ElementSize + ); + } + + Count -= 1; + } + + public void Append(T component) where T : unmanaged + { + if (Count >= Capacity) + { + Resize(); + } + + ((T*) Elements)[Count] = component; + Count += 1; + } + + public void CopyElementToEnd(int index, NativeArray other) + { + if (other.Count >= other.Capacity) + { + other.Resize(); + } + + NativeMemory.Copy( + (void*) (Elements + (index * ElementSize)), + (void*) (other.Elements + (other.Count * ElementSize)), + (nuint) ElementSize + ); + + other.Count += 1; + } + + public void CopyAllTo(NativeArray other) + { + if (Count >= other.Capacity) + { + other.ResizeTo(Count); + } + + NativeMemory.Copy( + (void*) Elements, + (void*) other.Elements, + (nuint) (ElementSize * Count) + ); + + other.Count = Count; + } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + NativeMemory.Free((void*) Elements); + IsDisposed = true; + } + } + + ~NativeArray() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: false); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} diff --git a/src/ComponentDepot.cs b/src/ComponentDepot.cs index cdd4d5f..4c10ce5 100644 --- a/src/ComponentDepot.cs +++ b/src/ComponentDepot.cs @@ -43,7 +43,7 @@ namespace MoonTools.ECS return Lookup().Any(); } - public ref readonly TComponent Get(int entityID) where TComponent : unmanaged + public ref TComponent Get(int entityID) where TComponent : unmanaged { return ref Lookup().Get(entityID); } diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs index bc71ef7..527f1da 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -41,7 +41,7 @@ namespace MoonTools.ECS return count > 0; } - public ref readonly TComponent Get(int entityID) + public ref TComponent Get(int entityID) { return ref components[entityIDToStorageIndex[entityID]]; } @@ -151,6 +151,7 @@ namespace MoonTools.ECS { return entityIDToStorageIndex.Keys; } +#endif protected virtual void Dispose(bool disposing) { @@ -177,6 +178,5 @@ namespace MoonTools.ECS Dispose(disposing: true); GC.SuppressFinalize(this); } -#endif } } diff --git a/src/MessageDepot.cs b/src/MessageDepot.cs index 63b9e2d..ce60ca1 100644 --- a/src/MessageDepot.cs +++ b/src/MessageDepot.cs @@ -42,7 +42,7 @@ namespace MoonTools.ECS return Lookup().First(); } - public ReverseSpanEnumerator WithEntity(int entityID) where TMessage : unmanaged + public Span.Enumerator WithEntity(int entityID) where TMessage : unmanaged { return Lookup().WithEntity(entityID); } diff --git a/src/MessageStorage.cs b/src/MessageStorage.cs index c5091a6..ccd6e33 100644 --- a/src/MessageStorage.cs +++ b/src/MessageStorage.cs @@ -63,7 +63,7 @@ namespace MoonTools.ECS return messages[0]; } - public ReverseSpanEnumerator WithEntity(int entityID) + public Span.Enumerator WithEntity(int entityID) { if (entityToMessages.TryGetValue(entityID, out var messages)) { @@ -71,7 +71,7 @@ namespace MoonTools.ECS } else { - return ReverseSpanEnumerator.Empty; + return Span.Empty.GetEnumerator(); } } diff --git a/src/NativeArray.cs b/src/NativeArray.cs deleted file mode 100644 index 9cc855f..0000000 --- a/src/NativeArray.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace MoonTools.ECS.Collections -{ - public unsafe class NativeArray : IDisposable where T : unmanaged - { - private T* Array; - private int count; - private int capacity; - private int elementSize; - - public int Count => count; - - public Span.Enumerator GetEnumerator() => new Span(Array, count).GetEnumerator(); - - private bool disposed; - - public NativeArray(int capacity = 16) - { - this.capacity = capacity; - elementSize = Unsafe.SizeOf(); - Array = (T*) NativeMemory.Alloc((nuint) (capacity * elementSize)); - count = 0; - } - - public ref T this[int i] => ref Array[i]; - - public void Add(T item) - { - if (count >= capacity) - { - capacity *= 2; - Array = (T*) NativeMemory.Realloc(Array, (nuint) (capacity * Unsafe.SizeOf())); - } - - Array[count] = item; - count += 1; - } - - public void RemoveLastElement() - { - count -= 1; - } - - public bool TryPop(out T element) - { - if (count > 0) - { - element = Array[count - 1]; - count -= 1; - return true; - } - - element = default; - return false; - } - - public void Clear() - { - count = 0; - } - - private void ResizeTo(int size) - { - capacity = size; - Array = (T*) NativeMemory.Realloc((void*) Array, (nuint) (elementSize * capacity)); - } - - public void CopyTo(NativeArray other) - { - if (count >= other.capacity) - { - other.ResizeTo(Count); - } - - NativeMemory.Copy( - (void*) Array, - (void*) other.Array, - (nuint) (elementSize * Count) - ); - - other.count = count; - } - - protected virtual void Dispose(bool disposing) - { - if (!disposed) - { - NativeMemory.Free(Array); - Array = null; - - disposed = true; - } - } - - ~NativeArray() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: false); - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - } -} diff --git a/src/Rev2/Archetype.cs b/src/Rev2/Archetype.cs index 65ee1c1..0780bfa 100644 --- a/src/Rev2/Archetype.cs +++ b/src/Rev2/Archetype.cs @@ -7,7 +7,7 @@ internal class Archetype { public World World; public ArchetypeSignature Signature; - public Column[] ComponentColumns; + public NativeArray[] ComponentColumns; public NativeArray RowToEntity = new NativeArray(); public Dictionary ComponentToColumnIndex = @@ -20,7 +20,7 @@ internal class Archetype { World = world; Signature = signature; - ComponentColumns = new Column[signature.Count]; + ComponentColumns = new NativeArray[signature.Count]; } public void ClearAll() diff --git a/src/Rev2/ArchetypeEdge.cs b/src/Rev2/ArchetypeEdge.cs index 3898911..1cafb6f 100644 --- a/src/Rev2/ArchetypeEdge.cs +++ b/src/Rev2/ArchetypeEdge.cs @@ -1,4 +1,3 @@ -namespace MoonTools.ECS.Rev2 -{ - internal readonly record struct ArchetypeEdge(Archetype Add, Archetype Remove); -} +namespace MoonTools.ECS.Rev2; + +internal readonly record struct ArchetypeEdge(Archetype Add, Archetype Remove); diff --git a/src/Rev2/ArchetypeSignature.cs b/src/Rev2/ArchetypeSignature.cs index 72cdc12..5a33783 100644 --- a/src/Rev2/ArchetypeSignature.cs +++ b/src/Rev2/ArchetypeSignature.cs @@ -1,92 +1,91 @@ using System; using System.Collections.Generic; -namespace MoonTools.ECS.Rev2 +namespace MoonTools.ECS.Rev2; + +internal class ArchetypeSignature : IEquatable { - internal class ArchetypeSignature : IEquatable + public static ArchetypeSignature Empty = new ArchetypeSignature(0); + + List Ids; + + public int Count => Ids.Count; + + public Id this[int i] => new Id(Ids[i]); + + public ArchetypeSignature() { - public static ArchetypeSignature Empty = new ArchetypeSignature(0); + Ids = new List(); + } - List Ids; + public ArchetypeSignature(int capacity) + { + Ids = new List(capacity); + } - public int Count => Ids.Count; + // Maintains sorted order + public void Insert(Id componentId) + { + var index = Ids.BinarySearch(componentId.Value); - public Id this[int i] => new Id(Ids[i]); - - public ArchetypeSignature() + if (index < 0) { - Ids = new List(); - } - - public ArchetypeSignature(int capacity) - { - Ids = new List(capacity); - } - - // Maintains sorted order - public void Insert(Id componentId) - { - var index = Ids.BinarySearch(componentId.Value); - - if (index < 0) - { - Ids.Insert(~index, componentId.Value); - } - } - - public void Remove(Id componentId) - { - var index = Ids.BinarySearch(componentId.Value); - - if (index >= 0) - { - Ids.RemoveAt(index); - } - } - - public void CopyTo(ArchetypeSignature other) - { - other.Ids.AddRange(Ids); - } - - public override bool Equals(object? obj) - { - return obj is ArchetypeSignature signature && Equals(signature); - } - - public bool Equals(ArchetypeSignature? other) - { - if (other == null) - { - return false; - } - - if (Ids.Count != other.Ids.Count) - { - return false; - } - - for (int i = 0; i < Ids.Count; i += 1) - { - if (Ids[i] != other.Ids[i]) - { - return false; - } - } - - return true; - } - - public override int GetHashCode() - { - var hashcode = 1; - - foreach (var id in Ids) - { - hashcode = HashCode.Combine(hashcode, id); - } - - return hashcode; + Ids.Insert(~index, componentId.Value); } } + + public void Remove(Id componentId) + { + var index = Ids.BinarySearch(componentId.Value); + + if (index >= 0) + { + Ids.RemoveAt(index); + } + } + + public void CopyTo(ArchetypeSignature other) + { + other.Ids.AddRange(Ids); + } + + public override bool Equals(object? obj) + { + return obj is ArchetypeSignature signature && Equals(signature); + } + + public bool Equals(ArchetypeSignature? other) + { + if (other == null) + { + return false; + } + + if (Ids.Count != other.Ids.Count) + { + return false; + } + + for (int i = 0; i < Ids.Count; i += 1) + { + if (Ids[i] != other.Ids[i]) + { + return false; + } + } + + return true; + } + + public override int GetHashCode() + { + var hashcode = 1; + + foreach (var id in Ids) + { + hashcode = HashCode.Combine(hashcode, id); + } + + return hashcode; + } } diff --git a/src/Rev2/Column.cs b/src/Rev2/Column.cs deleted file mode 100644 index 2612107..0000000 --- a/src/Rev2/Column.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace MoonTools.ECS.Rev2 -{ - internal unsafe class Column : IDisposable - { - public nint Elements; - public int Count; - - private int Capacity; - public readonly int ElementSize; - - private bool IsDisposed; - - public Column(int elementSize) - { - Capacity = 16; - Count = 0; - ElementSize = elementSize; - - Elements = (nint) NativeMemory.Alloc((nuint) (ElementSize * Capacity)); - } - - public Span ToSpan() - { - return new Span((void*) Elements, Count); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T Get(int i) where T : unmanaged - { - return ref ((T*) Elements)[i]; - } - - public void Resize() - { - Capacity *= 2; - Elements = (nint) NativeMemory.Realloc((void*) Elements, (nuint) (ElementSize * Capacity)); - } - - public void ResizeTo(int capacity) - { - Capacity = capacity; - Elements = (nint) NativeMemory.Realloc((void*) Elements, (nuint) (ElementSize * Capacity)); - } - - // Fills gap by copying final element to the deleted index - public void Delete(int index) - { - if (Count > 1) - { - NativeMemory.Copy( - (void*) (Elements + ((Count - 1) * ElementSize)), - (void*) (Elements + (index * ElementSize)), - (nuint) ElementSize - ); - } - - Count -= 1; - } - - public void Append(T component) where T : unmanaged - { - if (Count >= Capacity) - { - Resize(); - } - - ((T*) Elements)[Count] = component; - Count += 1; - } - - public void CopyElementToEnd(int index, Column other) - { - if (other.Count >= other.Capacity) - { - other.Resize(); - } - - NativeMemory.Copy( - (void*) (Elements + (index * ElementSize)), - (void*) (other.Elements + (other.Count * ElementSize)), - (nuint) ElementSize - ); - - other.Count += 1; - } - - public void CopyAllTo(Column other) - { - if (Count >= other.Capacity) - { - other.ResizeTo(Count); - } - - NativeMemory.Copy( - (void*) Elements, - (void*) other.Elements, - (nuint) (ElementSize * Count) - ); - - other.Count = Count; - } - - protected virtual void Dispose(bool disposing) - { - if (!IsDisposed) - { - NativeMemory.Free((void*) Elements); - IsDisposed = true; - } - } - - ~Column() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: false); - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - } -} diff --git a/src/Rev2/Filter.cs b/src/Rev2/Filter.cs index eab57cc..aabe087 100644 --- a/src/Rev2/Filter.cs +++ b/src/Rev2/Filter.cs @@ -1,204 +1,203 @@ using System; using System.Collections.Generic; -namespace MoonTools.ECS.Rev2 +namespace MoonTools.ECS.Rev2; + +// TODO: do we want to get fancy with queries beyond Include and Exclude? +public class Filter { - // TODO: do we want to get fancy with queries beyond Include and Exclude? - public class Filter + private Archetype EmptyArchetype; + private HashSet Included; + private HashSet Excluded; + + public EntityEnumerator Entities => new EntityEnumerator(this); + internal ArchetypeEnumerator Archetypes => new ArchetypeEnumerator(this); + public RandomEntityEnumerator EntitiesInRandomOrder => new RandomEntityEnumerator(this); + + public bool Empty { - private Archetype EmptyArchetype; - private HashSet Included; - private HashSet Excluded; - - public EntityEnumerator Entities => new EntityEnumerator(this); - internal ArchetypeEnumerator Archetypes => new ArchetypeEnumerator(this); - public RandomEntityEnumerator EntitiesInRandomOrder => new RandomEntityEnumerator(this); - - public bool Empty + get { - get - { - var empty = true; + var empty = true; - foreach (var archetype in Archetypes) + foreach (var archetype in Archetypes) + { + if (archetype.Count > 0) { - if (archetype.Count > 0) - { - return false; - } + return false; } - - return empty; } + + return empty; } + } - public int Count - { - get - { - var count = 0; - - foreach (var archetype in Archetypes) - { - count += archetype.Count; - } - - return count; - } - } - - public Id RandomEntity - { - get - { - var randomIndex = RandomManager.Next(Count); - return NthEntity(randomIndex); - } - } - - // WARNING: this WILL crash if the index is out of range! - public Id NthEntity(int index) + public int Count + { + get { 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 included, HashSet excluded) - { - EmptyArchetype = emptyArchetype; - Included = included; - Excluded = excluded; - } - - internal ref struct ArchetypeEnumerator - { - private Archetype CurrentArchetype; - - // 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) - { - 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 Id CurrentEntity; - - public EntityEnumerator GetEnumerator() => this; - - // TODO: pool this - Queue EntityQueue = new Queue(); - - 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 Id 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 Id Current => Filter.NthEntity(LinearCongruentialEnumerator.Current); + return count; } } + + public Id RandomEntity + { + get + { + var randomIndex = RandomManager.Next(Count); + return NthEntity(randomIndex); + } + } + + // WARNING: this WILL crash if the index is out of range! + public Id 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 included, HashSet excluded) + { + EmptyArchetype = emptyArchetype; + Included = included; + Excluded = excluded; + } + + internal ref struct ArchetypeEnumerator + { + private Archetype CurrentArchetype; + + // 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) + { + 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 Id CurrentEntity; + + public EntityEnumerator GetEnumerator() => this; + + // TODO: pool this + Queue EntityQueue = new Queue(); + + 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 Id 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 Id Current => Filter.NthEntity(LinearCongruentialEnumerator.Current); + } } diff --git a/src/Rev2/FilterBuilder.cs b/src/Rev2/FilterBuilder.cs index 79af550..1a986c7 100644 --- a/src/Rev2/FilterBuilder.cs +++ b/src/Rev2/FilterBuilder.cs @@ -1,44 +1,43 @@ using System.Collections.Generic; -namespace MoonTools.ECS.Rev2 +namespace MoonTools.ECS.Rev2; + +public ref struct FilterBuilder { - public ref struct FilterBuilder + World World; + HashSet Included; + HashSet Excluded; + + internal FilterBuilder(World world) { - World World; - HashSet Included; - HashSet Excluded; + World = world; + Included = new HashSet(); + Excluded = new HashSet(); + } - internal FilterBuilder(World world) - { - World = world; - Included = new HashSet(); - Excluded = new HashSet(); - } + private FilterBuilder(World world, HashSet included, HashSet excluded) + { + World = world; + Included = included; + Excluded = excluded; + } - private FilterBuilder(World world, HashSet included, HashSet excluded) - { - World = world; - Included = included; - Excluded = excluded; - } + public FilterBuilder Include() where T : unmanaged + { + World.GetTypeId(); + Included.Add(World.TypeToId[typeof(T)]); + return new FilterBuilder(World, Included, Excluded); + } - public FilterBuilder Include() where T : unmanaged - { - World.TryRegisterTypeId(); - Included.Add(World.TypeToId[typeof(T)]); - return new FilterBuilder(World, Included, Excluded); - } + public FilterBuilder Exclude() where T : unmanaged + { + World.GetTypeId(); + Excluded.Add(World.TypeToId[typeof(T)]); + return new FilterBuilder(World, Included, Excluded); + } - public FilterBuilder Exclude() where T : unmanaged - { - World.TryRegisterTypeId(); - Excluded.Add(World.TypeToId[typeof(T)]); - return new FilterBuilder(World, Included, Excluded); - } - - public Filter Build() - { - return new Filter(World.EmptyArchetype, Included, Excluded); - } + public Filter Build() + { + return new Filter(World.EmptyArchetype, Included, Excluded); } } diff --git a/src/Rev2/HasId.cs b/src/Rev2/HasId.cs deleted file mode 100644 index 69e824a..0000000 --- a/src/Rev2/HasId.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace MoonTools.ECS.Rev2; - -public interface IHasId -{ - public int Id { get; init; } -} diff --git a/src/Rev2/MessageStorage.cs b/src/Rev2/MessageStorage.cs new file mode 100644 index 0000000..73b7ce4 --- /dev/null +++ b/src/Rev2/MessageStorage.cs @@ -0,0 +1,63 @@ +using System; +using MoonTools.ECS.Collections; + +namespace MoonTools.ECS.Rev2; + +public class MessageStorage : IDisposable +{ + private NativeArray Messages; + + private bool IsDisposed; + + public MessageStorage(int elementSize) + { + Messages = new NativeArray(elementSize); + } + + public void Add(in T message) where T : unmanaged + { + Messages.Append(message); + } + + public bool Some() + { + return Messages.Count > 0; + } + + public ReadOnlySpan All() where T : unmanaged + { + return Messages.ToSpan(); + } + + public T First() where T : unmanaged + { + return Messages.Get(0); + } + + public void Clear() + { + Messages.Count = 0; + } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + Messages.Dispose(); + IsDisposed = true; + } + } + + // ~MessageStorage() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} diff --git a/src/Rev2/Record.cs b/src/Rev2/Record.cs index f997344..d7f8196 100644 --- a/src/Rev2/Record.cs +++ b/src/Rev2/Record.cs @@ -1,4 +1,3 @@ -namespace MoonTools.ECS.Rev2 -{ - internal readonly record struct Record(Archetype Archetype, int Row); -} +namespace MoonTools.ECS.Rev2; + +internal readonly record struct Record(Archetype Archetype, int Row); diff --git a/src/Rev2/RelationStorage.cs b/src/Rev2/RelationStorage.cs index 806f4f6..ec56601 100644 --- a/src/Rev2/RelationStorage.cs +++ b/src/Rev2/RelationStorage.cs @@ -8,8 +8,8 @@ namespace MoonTools.ECS.Rev2; // TODO: implement this entire class with NativeMemory equivalents, can just memcpy for snapshots internal class RelationStorage { - internal Column relations; - internal Column relationDatas; + internal NativeArray relations; + internal NativeArray relationDatas; internal Dictionary<(Id, Id), int> indices = new Dictionary<(Id, Id), int>(16); internal Dictionary> outRelations = new Dictionary>(16); internal Dictionary> inRelations = new Dictionary>(16); @@ -19,8 +19,8 @@ internal class RelationStorage public RelationStorage(int relationDataSize) { - relations = new Column(Unsafe.SizeOf<(Id, Id)>()); - relationDatas = new Column(relationDataSize); + relations = new NativeArray(Unsafe.SizeOf<(Id, Id)>()); + relationDatas = new NativeArray(relationDataSize); } public ReverseSpanEnumerator<(Id, Id)> All() diff --git a/src/Rev2/Snapshot.cs b/src/Rev2/Snapshot.cs index 6be8e83..6dd7231 100644 --- a/src/Rev2/Snapshot.cs +++ b/src/Rev2/Snapshot.cs @@ -139,20 +139,20 @@ public class Snapshot private class ArchetypeSnapshot { - private readonly Column[] ComponentColumns; + private readonly NativeArray[] ComponentColumns; private readonly NativeArray RowToEntity; public int Count => RowToEntity.Count; public ArchetypeSnapshot(ArchetypeSignature signature) { - ComponentColumns = new Column[signature.Count]; + ComponentColumns = new NativeArray[signature.Count]; RowToEntity = new NativeArray(); for (int i = 0; i < signature.Count; i += 1) { var componentId = signature[i]; - ComponentColumns[i] = new Column(World.ElementSizes[componentId]); + ComponentColumns[i] = new NativeArray(World.ElementSizes[componentId]); } } @@ -180,13 +180,13 @@ public class Snapshot private class RelationSnapshot { - private Column Relations; - private Column RelationDatas; + private NativeArray Relations; + private NativeArray RelationDatas; public RelationSnapshot(int elementSize) { - Relations = new Column(Unsafe.SizeOf<(Id, Id)>()); - RelationDatas = new Column(elementSize); + Relations = new NativeArray(Unsafe.SizeOf<(Id, Id)>()); + RelationDatas = new NativeArray(elementSize); } public void Take(RelationStorage relationStorage) diff --git a/src/Rev2/World.cs b/src/Rev2/World.cs index d8d9079..0ff71f9 100644 --- a/src/Rev2/World.cs +++ b/src/Rev2/World.cs @@ -1,478 +1,522 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using MoonTools.ECS.Collections; -namespace MoonTools.ECS.Rev2 +namespace MoonTools.ECS.Rev2; + +public class World : IDisposable { - public class World : IDisposable + // Get ComponentId from a Type + internal static Dictionary TypeToId = new Dictionary(); + // Get element size from a ComponentId + internal static Dictionary ElementSizes = new Dictionary(); + + // Lookup from ArchetypeSignature to Archetype + internal Dictionary ArchetypeIndex = new Dictionary(); + + // Going from EntityId to Archetype and storage row + internal Dictionary EntityIndex = new Dictionary(); + + // Going from ComponentId to Archetype list + Dictionary> ComponentIndex = new Dictionary>(); + + // Relation Storages + internal Dictionary RelationIndex = + new Dictionary(); + + // Entity Relation Tracking + internal Dictionary> EntityRelationIndex = + new Dictionary>(); + + // Message Storages + private Dictionary MessageIndex = + new Dictionary(); + + // ID Management + // FIXME: Entity and Type Ids should be separated + internal IdAssigner IdAssigner = new IdAssigner(); + + internal readonly Archetype EmptyArchetype; + + public FilterBuilder FilterBuilder => new FilterBuilder(this); + + public delegate void RefAction(ref T1 arg1, ref T2 arg2); + + private bool IsDisposed; + + public World() { - // Get ComponentId from a Type - internal static Dictionary TypeToId = new Dictionary(); - // Get element size from a ComponentId - internal static Dictionary ElementSizes = new Dictionary(); + // Create the Empty Archetype + EmptyArchetype = CreateArchetype(ArchetypeSignature.Empty); + } - // Lookup from ArchetypeSignature to Archetype - internal Dictionary ArchetypeIndex = new Dictionary(); + internal Archetype CreateArchetype(ArchetypeSignature signature) + { + var archetype = new Archetype(this, signature); - // Going from EntityId to Archetype and storage row - internal Dictionary EntityIndex = new Dictionary(); + ArchetypeIndex.Add(signature, archetype); - // Going from ComponentId to Archetype list - Dictionary> ComponentIndex = new Dictionary>(); - - // Relation Storages - internal Dictionary RelationIndex = - new Dictionary(); - - // Entity Relation Tracking - internal Dictionary> EntityRelationIndex = - new Dictionary>(); - - // ID Management - // FIXME: Entity and Type Ids should be separated - internal IdAssigner IdAssigner = new IdAssigner(); - - internal readonly Archetype EmptyArchetype; - - public FilterBuilder FilterBuilder => new FilterBuilder(this); - - public delegate void RefAction(ref T1 arg1, ref T2 arg2); - - private bool IsDisposed; - - public World() + for (int i = 0; i < signature.Count; i += 1) { - // Create the Empty Archetype - EmptyArchetype = CreateArchetype(ArchetypeSignature.Empty); + var componentId = signature[i]; + ComponentIndex[componentId].Add(archetype); + archetype.ComponentToColumnIndex.Add(componentId, i); + archetype.ComponentColumns[i] = new NativeArray(ElementSizes[componentId]); } - internal Archetype CreateArchetype(ArchetypeSignature signature) + return archetype; + } + + public Id CreateEntity() + { + var entityId = IdAssigner.Assign(); + EntityIndex.Add(entityId, new Record(EmptyArchetype, EmptyArchetype.Count)); + EmptyArchetype.RowToEntity.Add(entityId); + + if (!EntityRelationIndex.ContainsKey(entityId)) { - var archetype = new Archetype(this, signature); - - ArchetypeIndex.Add(signature, archetype); - - for (int i = 0; i < signature.Count; i += 1) - { - var componentId = signature[i]; - ComponentIndex[componentId].Add(archetype); - archetype.ComponentToColumnIndex.Add(componentId, i); - archetype.ComponentColumns[i] = new Column(ElementSizes[componentId]); - } - - return archetype; + EntityRelationIndex.Add(entityId, new IndexableSet()); } - public Id CreateEntity() - { - var entityId = IdAssigner.Assign(); - EntityIndex.Add(entityId, new Record(EmptyArchetype, EmptyArchetype.Count)); - EmptyArchetype.RowToEntity.Add(entityId); + return entityId; + } - if (!EntityRelationIndex.ContainsKey(entityId)) - { - EntityRelationIndex.Add(entityId, new IndexableSet()); - } - - return entityId; - } - - internal void TryRegisterTypeId() where T : unmanaged - { - if (!TypeToId.ContainsKey(typeof(T))) - { - var typeId = IdAssigner.Assign(); - TypeToId.Add(typeof(T), typeId); - ElementSizes.Add(typeId, Unsafe.SizeOf()); - } - } - - // FIXME: would be much more efficient to do all this at load time somehow - private void RegisterComponent(Id typeId) - { - ComponentIndex.Add(typeId, new List()); - } - - private void TryRegisterComponentId() where T : unmanaged - { - TryRegisterTypeId(); - var typeId = TypeToId[typeof(T)]; - if (!ComponentIndex.ContainsKey(typeId)) - { - RegisterComponent(typeId); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Id GetComponentId() where T : unmanaged + internal Id GetTypeId() where T : unmanaged + { + if (TypeToId.ContainsKey(typeof(T))) { return TypeToId[typeof(T)]; } - private void RegisterRelationType(Id typeId) + var typeId = IdAssigner.Assign(); + TypeToId.Add(typeof(T), typeId); + ElementSizes.Add(typeId, Unsafe.SizeOf()); + return typeId; + } + + private void TryRegisterComponentId() where T : unmanaged + { + var typeId = GetTypeId(); + if (!ComponentIndex.ContainsKey(typeId)) { - RelationIndex.Add(typeId, new RelationStorage(ElementSizes[typeId])); + ComponentIndex.Add(typeId, new List()); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Id GetComponentId() where T : unmanaged + { + return TypeToId[typeof(T)]; + } + + private void RegisterRelationType(Id typeId) + { + RelationIndex.Add(typeId, new RelationStorage(ElementSizes[typeId])); + } + + private void TryRegisterRelationType() where T : unmanaged + { + var typeId = GetTypeId(); + if (!RelationIndex.ContainsKey(typeId)) + { + RegisterRelationType(typeId); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private RelationStorage GetRelationStorage() where T : unmanaged + { + return RelationIndex[TypeToId[typeof(T)]]; + } + + // Messages + + private Id GetMessageTypeId() where T : unmanaged + { + var typeId = GetTypeId(); + + if (!MessageIndex.ContainsKey(typeId)) + { + MessageIndex.Add(typeId, new MessageStorage(Unsafe.SizeOf())); } - private void TryRegisterRelationType() where T : unmanaged + return typeId; + } + + public void Send(in T message) where T : unmanaged + { + var typeId = GetMessageTypeId(); + MessageIndex[typeId].Add(message); + } + + public bool SomeMessage() where T : unmanaged + { + var typeId = GetMessageTypeId(); + return MessageIndex[typeId].Some(); + } + + public ReadOnlySpan ReadMessages() where T : unmanaged + { + var typeId = GetMessageTypeId(); + return MessageIndex[typeId].All(); + } + + public T ReadMessage() where T : unmanaged + { + var typeId = GetMessageTypeId(); + return MessageIndex[typeId].First(); + } + + public void ClearMessages() + { + foreach (var (_, messageStorage) in MessageIndex) { - TryRegisterTypeId(); - var typeId = TypeToId[typeof(T)]; - if (!RelationIndex.ContainsKey(typeId)) - { - RegisterRelationType(typeId); - } + messageStorage.Clear(); } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private RelationStorage GetRelationStorage() where T : unmanaged + // Components + public bool Has(Id entityId) where T : unmanaged + { + var componentId = GetComponentId(); + var record = EntityIndex[entityId]; + return record.Archetype.ComponentToColumnIndex.ContainsKey(componentId); + } + + // will throw if non-existent + public unsafe ref T Get(Id entityId) where T : unmanaged + { + var componentId = GetComponentId(); + + var record = EntityIndex[entityId]; + var columnIndex = record.Archetype.ComponentToColumnIndex[componentId]; + var column = record.Archetype.ComponentColumns[columnIndex]; + + return ref ((T*) column.Elements)[record.Row]; + } + + public unsafe void Set(in Id entityId, in T component) where T : unmanaged + { + TryRegisterComponentId(); + var componentId = GetComponentId(); + + if (Has(entityId)) { - return RelationIndex[TypeToId[typeof(T)]]; - } - - public bool Has(Id entityId) where T : unmanaged - { - var componentId = GetComponentId(); - var record = EntityIndex[entityId]; - return record.Archetype.ComponentToColumnIndex.ContainsKey(componentId); - } - - // will throw if non-existent - public unsafe ref T Get(Id entityId) where T : unmanaged - { - var componentId = GetComponentId(); - var record = EntityIndex[entityId]; var columnIndex = record.Archetype.ComponentToColumnIndex[componentId]; var column = record.Archetype.ComponentColumns[columnIndex]; - return ref ((T*) column.Elements)[record.Row]; + ((T*) column.Elements)[record.Row] = component; } - - public unsafe void Set(in Id entityId, in T component) where T : unmanaged + else { - TryRegisterComponentId(); - var componentId = GetComponentId(); - - if (Has(entityId)) - { - var record = EntityIndex[entityId]; - var columnIndex = record.Archetype.ComponentToColumnIndex[componentId]; - var column = record.Archetype.ComponentColumns[columnIndex]; - - ((T*) column.Elements)[record.Row] = component; - } - else - { - Add(entityId, component); - } - } - - private void Add(Id entityId, in T component) where T : unmanaged - { - Archetype? nextArchetype; - - var componentId = GetComponentId(); - - // move the entity to the new archetype - var record = EntityIndex[entityId]; - var archetype = record.Archetype; - - if (archetype.Edges.TryGetValue(componentId, out var edge)) - { - nextArchetype = edge.Add; - } - else - { - // FIXME: pool the signatures - var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1); - archetype.Signature.CopyTo(nextSignature); - nextSignature.Insert(componentId); - - if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype)) - { - nextArchetype = CreateArchetype(nextSignature); - } - - var newEdge = new ArchetypeEdge(nextArchetype, archetype); - archetype.Edges.Add(componentId, newEdge); - nextArchetype.Edges.Add(componentId, newEdge); - } - - MoveEntityToHigherArchetype(entityId, record.Row, archetype, nextArchetype); - - // add the new component to the new archetype - var columnIndex = nextArchetype.ComponentToColumnIndex[componentId]; - var column = nextArchetype.ComponentColumns[columnIndex]; - column.Append(component); - } - - public void Remove(Id entityId) where T : unmanaged - { - Archetype? nextArchetype; - - var componentId = GetComponentId(); - - var (archetype, row) = EntityIndex[entityId]; - - if (archetype.Edges.TryGetValue(componentId, out var edge)) - { - nextArchetype = edge.Remove; - } - else - { - // FIXME: pool the signatures - var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1); - archetype.Signature.CopyTo(nextSignature); - nextSignature.Remove(componentId); - - if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype)) - { - nextArchetype = CreateArchetype(nextSignature); - } - - var newEdge = new ArchetypeEdge(nextArchetype, archetype); - archetype.Edges.Add(componentId, newEdge); - nextArchetype.Edges.Add(componentId, newEdge); - } - - MoveEntityToLowerArchetype(entityId, row, archetype, nextArchetype, componentId); - } - - public void Relate(in Id entityA, in Id entityB, in T relation) where T : unmanaged - { - TryRegisterRelationType(); - var relationStorage = GetRelationStorage(); - relationStorage.Set(entityA, entityB, relation); - EntityRelationIndex[entityA].Add(TypeToId[typeof(T)]); - EntityRelationIndex[entityB].Add(TypeToId[typeof(T)]); - } - - public void Unrelate(in Id entityA, in Id entityB) where T : unmanaged - { - var relationStorage = GetRelationStorage(); - relationStorage.Remove(entityA, entityB); - } - - public bool Related(in Id entityA, in Id entityB) where T : unmanaged - { - var relationStorage = GetRelationStorage(); - return relationStorage.Has(entityA, entityB); - } - - public T GetRelationData(in Id entityA, in Id entityB) where T : unmanaged - { - var relationStorage = GetRelationStorage(); - return relationStorage.Get(entityA, entityB); - } - - public ReverseSpanEnumerator<(Id, Id)> Relations() where T : unmanaged - { - var relationStorage = GetRelationStorage(); - return relationStorage.All(); - } - - public ReverseSpanEnumerator OutRelations(Id entity) where T : unmanaged - { - var relationStorage = GetRelationStorage(); - return relationStorage.OutRelations(entity); - } - - public ReverseSpanEnumerator InRelations(Id entity) where T : unmanaged - { - var relationStorage = GetRelationStorage(); - return relationStorage.InRelations(entity); - } - - private bool Has(Id entityId, Id typeId) - { - var record = EntityIndex[entityId]; - return record.Archetype.ComponentToColumnIndex.ContainsKey(typeId); - } - - // used as a fast path by Archetype.ClearAll and snapshot restore - internal void FreeEntity(Id entityId) - { - EntityIndex.Remove(entityId); - IdAssigner.Unassign(entityId); - - foreach (var relationTypeIndex in EntityRelationIndex[entityId]) - { - var relationStorage = RelationIndex[relationTypeIndex]; - relationStorage.RemoveEntity(entityId); - } - - EntityRelationIndex[entityId].Clear(); - } - - public void Destroy(Id entityId) - { - var record = EntityIndex[entityId]; - var archetype = record.Archetype; - var row = record.Row; - - for (int i = 0; i < archetype.Signature.Count; i += 1) - { - archetype.ComponentColumns[i].Delete(row); - } - - if (row != 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.RowToEntity.RemoveLastElement(); - EntityIndex.Remove(entityId); - IdAssigner.Unassign(entityId); - - foreach (var relationTypeIndex in EntityRelationIndex[entityId]) - { - var relationStorage = RelationIndex[relationTypeIndex]; - relationStorage.RemoveEntity(entityId); - } - - EntityRelationIndex[entityId].Clear(); - } - - private void MoveEntityToHigherArchetype(Id entityId, int row, Archetype from, Archetype to) - { - for (int i = 0; i < from.Signature.Count; i += 1) - { - var componentId = from.Signature[i]; - var destinationColumnIndex = to.ComponentToColumnIndex[componentId]; - - // copy all components to higher archetype - from.ComponentColumns[i].CopyElementToEnd(row, to.ComponentColumns[destinationColumnIndex]); - - // delete row on from archetype - from.ComponentColumns[i].Delete(row); - } - - if (row != from.Count - 1) - { - // 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.RemoveLastElement(); - - // update row to entity lookup on to archetype - EntityIndex[entityId] = new Record(to, to.Count); - to.RowToEntity.Add(entityId); - } - - private void MoveEntityToLowerArchetype(Id entityId, int row, Archetype from, Archetype to, Id removed) - { - for (int i = 0; i < from.Signature.Count; i += 1) - { - var componentId = from.Signature[i]; - - // delete the row - from.ComponentColumns[i].Delete(row); - - // if this isn't the removed component, copy to the lower archetype - if (componentId != removed) - { - var destinationColumnIndex = to.ComponentToColumnIndex[componentId]; - from.ComponentColumns[i].CopyElementToEnd(row, to.ComponentColumns[destinationColumnIndex]); - } - } - - if (row != from.Count - 1) - { - // update row to entity lookup on from archetype - var lastRowEntity = from.RowToEntity[from.Count - 1]; - from.RowToEntity[row] = lastRowEntity; - EntityIndex[lastRowEntity] = new Record(from, row); - } - - from.RowToEntity.RemoveLastElement(); - - // update row to entity lookup on to archetype - EntityIndex[entityId] = new Record(to, to.Count); - to.RowToEntity.Add(entityId); - } - - public unsafe void ForEachEntity(Filter filter, - T rowForEachContainer) where T : IForEach where T1 : unmanaged where T2 : unmanaged - { - foreach (var archetype in filter.Archetypes) - { - var componentIdOne = archetype.Signature[0]; - var columnIndexOne = archetype.ComponentToColumnIndex[componentIdOne]; - var columnOneElements = archetype.ComponentColumns[columnIndexOne].Elements; - - var componentIdTwo = archetype.Signature[1]; - var columnIndexTwo = archetype.ComponentToColumnIndex[componentIdTwo]; - var columnTwoElements = archetype.ComponentColumns[columnIndexTwo].Elements; - - for (int i = archetype.Count - 1; i >= 0; i -= 1) - { - rowForEachContainer.Update(ref ((T1*) columnOneElements)[i], ref ((T2*) columnTwoElements)[i]); - } - } - } - - public unsafe void ForEachEntity(Filter filter, RefAction rowAction) where T1 : unmanaged where T2 : unmanaged - { - foreach (var archetype in filter.Archetypes) - { - var componentIdOne = archetype.Signature[0]; - var columnIndexOne = archetype.ComponentToColumnIndex[componentIdOne]; - var columnOneElements = archetype.ComponentColumns[columnIndexOne].Elements; - - var componentIdTwo = archetype.Signature[1]; - var columnIndexTwo = archetype.ComponentToColumnIndex[componentIdTwo]; - var columnTwoElements = archetype.ComponentColumns[columnIndexTwo].Elements; - - for (int i = archetype.Count - 1; i >= 0; i -= 1) - { - rowAction(ref ((T1*) columnOneElements)[i], ref ((T2*) columnTwoElements)[i]); - } - } - } - - protected virtual void Dispose(bool disposing) - { - if (!IsDisposed) - { - if (disposing) - { - // dispose managed state (managed objects) - foreach (var archetype in ArchetypeIndex.Values) - { - for (var i = 0; i < archetype.Signature.Count; i += 1) - { - archetype.ComponentColumns[i].Dispose(); - } - } - } - - // TODO: free unmanaged resources (unmanaged objects) and override finalizer - // TODO: set large fields to null - IsDisposed = true; - } - } - - // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources - // ~World() - // { - // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - // Dispose(disposing: false); - // } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); + Add(entityId, component); } } + + private void Add(Id entityId, in T component) where T : unmanaged + { + Archetype? nextArchetype; + + var componentId = GetComponentId(); + + // move the entity to the new archetype + var record = EntityIndex[entityId]; + var archetype = record.Archetype; + + if (archetype.Edges.TryGetValue(componentId, out var edge)) + { + nextArchetype = edge.Add; + } + else + { + // FIXME: pool the signatures + var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1); + archetype.Signature.CopyTo(nextSignature); + nextSignature.Insert(componentId); + + if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype)) + { + nextArchetype = CreateArchetype(nextSignature); + } + + var newEdge = new ArchetypeEdge(nextArchetype, archetype); + archetype.Edges.Add(componentId, newEdge); + nextArchetype.Edges.Add(componentId, newEdge); + } + + MoveEntityToHigherArchetype(entityId, record.Row, archetype, nextArchetype); + + // add the new component to the new archetype + var columnIndex = nextArchetype.ComponentToColumnIndex[componentId]; + var column = nextArchetype.ComponentColumns[columnIndex]; + column.Append(component); + } + + public void Remove(Id entityId) where T : unmanaged + { + Archetype? nextArchetype; + + var componentId = GetComponentId(); + + var (archetype, row) = EntityIndex[entityId]; + + if (archetype.Edges.TryGetValue(componentId, out var edge)) + { + nextArchetype = edge.Remove; + } + else + { + // FIXME: pool the signatures + var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1); + archetype.Signature.CopyTo(nextSignature); + nextSignature.Remove(componentId); + + if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype)) + { + nextArchetype = CreateArchetype(nextSignature); + } + + var newEdge = new ArchetypeEdge(nextArchetype, archetype); + archetype.Edges.Add(componentId, newEdge); + nextArchetype.Edges.Add(componentId, newEdge); + } + + MoveEntityToLowerArchetype(entityId, row, archetype, nextArchetype, componentId); + } + + public void Relate(in Id entityA, in Id entityB, in T relation) where T : unmanaged + { + TryRegisterRelationType(); + var relationStorage = GetRelationStorage(); + relationStorage.Set(entityA, entityB, relation); + EntityRelationIndex[entityA].Add(TypeToId[typeof(T)]); + EntityRelationIndex[entityB].Add(TypeToId[typeof(T)]); + } + + public void Unrelate(in Id entityA, in Id entityB) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + relationStorage.Remove(entityA, entityB); + } + + public bool Related(in Id entityA, in Id entityB) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.Has(entityA, entityB); + } + + public T GetRelationData(in Id entityA, in Id entityB) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.Get(entityA, entityB); + } + + public ReverseSpanEnumerator<(Id, Id)> Relations() where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.All(); + } + + public ReverseSpanEnumerator OutRelations(Id entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.OutRelations(entity); + } + + public ReverseSpanEnumerator InRelations(Id entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.InRelations(entity); + } + + private bool Has(Id entityId, Id typeId) + { + var record = EntityIndex[entityId]; + return record.Archetype.ComponentToColumnIndex.ContainsKey(typeId); + } + + // used as a fast path by Archetype.ClearAll and snapshot restore + internal void FreeEntity(Id entityId) + { + EntityIndex.Remove(entityId); + IdAssigner.Unassign(entityId); + + foreach (var relationTypeIndex in EntityRelationIndex[entityId]) + { + var relationStorage = RelationIndex[relationTypeIndex]; + relationStorage.RemoveEntity(entityId); + } + + EntityRelationIndex[entityId].Clear(); + } + + public void Destroy(Id entityId) + { + var record = EntityIndex[entityId]; + var archetype = record.Archetype; + var row = record.Row; + + for (int i = 0; i < archetype.Signature.Count; i += 1) + { + archetype.ComponentColumns[i].Delete(row); + } + + if (row != 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.RowToEntity.RemoveLastElement(); + EntityIndex.Remove(entityId); + IdAssigner.Unassign(entityId); + + foreach (var relationTypeIndex in EntityRelationIndex[entityId]) + { + var relationStorage = RelationIndex[relationTypeIndex]; + relationStorage.RemoveEntity(entityId); + } + + EntityRelationIndex[entityId].Clear(); + } + + private void MoveEntityToHigherArchetype(Id entityId, int row, Archetype from, Archetype to) + { + for (int i = 0; i < from.Signature.Count; i += 1) + { + var componentId = from.Signature[i]; + var destinationColumnIndex = to.ComponentToColumnIndex[componentId]; + + // copy all components to higher archetype + from.ComponentColumns[i].CopyElementToEnd(row, to.ComponentColumns[destinationColumnIndex]); + + // delete row on from archetype + from.ComponentColumns[i].Delete(row); + } + + if (row != from.Count - 1) + { + // 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.RemoveLastElement(); + + // update row to entity lookup on to archetype + EntityIndex[entityId] = new Record(to, to.Count); + to.RowToEntity.Add(entityId); + } + + private void MoveEntityToLowerArchetype(Id entityId, int row, Archetype from, Archetype to, Id removed) + { + for (int i = 0; i < from.Signature.Count; i += 1) + { + var componentId = from.Signature[i]; + + // delete the row + from.ComponentColumns[i].Delete(row); + + // if this isn't the removed component, copy to the lower archetype + if (componentId != removed) + { + var destinationColumnIndex = to.ComponentToColumnIndex[componentId]; + from.ComponentColumns[i].CopyElementToEnd(row, to.ComponentColumns[destinationColumnIndex]); + } + } + + if (row != from.Count - 1) + { + // update row to entity lookup on from archetype + var lastRowEntity = from.RowToEntity[from.Count - 1]; + from.RowToEntity[row] = lastRowEntity; + EntityIndex[lastRowEntity] = new Record(from, row); + } + + from.RowToEntity.RemoveLastElement(); + + // update row to entity lookup on to archetype + EntityIndex[entityId] = new Record(to, to.Count); + to.RowToEntity.Add(entityId); + } + + public unsafe void ForEachEntity(Filter filter, + T rowForEachContainer) where T : IForEach where T1 : unmanaged where T2 : unmanaged + { + foreach (var archetype in filter.Archetypes) + { + var componentIdOne = archetype.Signature[0]; + var columnIndexOne = archetype.ComponentToColumnIndex[componentIdOne]; + var columnOneElements = archetype.ComponentColumns[columnIndexOne].Elements; + + var componentIdTwo = archetype.Signature[1]; + var columnIndexTwo = archetype.ComponentToColumnIndex[componentIdTwo]; + var columnTwoElements = archetype.ComponentColumns[columnIndexTwo].Elements; + + for (int i = archetype.Count - 1; i >= 0; i -= 1) + { + rowForEachContainer.Update(ref ((T1*) columnOneElements)[i], ref ((T2*) columnTwoElements)[i]); + } + } + } + + public unsafe void ForEachEntity(Filter filter, RefAction rowAction) where T1 : unmanaged where T2 : unmanaged + { + foreach (var archetype in filter.Archetypes) + { + var componentIdOne = archetype.Signature[0]; + var columnIndexOne = archetype.ComponentToColumnIndex[componentIdOne]; + var columnOneElements = archetype.ComponentColumns[columnIndexOne].Elements; + + var componentIdTwo = archetype.Signature[1]; + var columnIndexTwo = archetype.ComponentToColumnIndex[componentIdTwo]; + var columnTwoElements = archetype.ComponentColumns[columnIndexTwo].Elements; + + for (int i = archetype.Count - 1; i >= 0; i -= 1) + { + rowAction(ref ((T1*) columnOneElements)[i], ref ((T2*) columnTwoElements)[i]); + } + } + } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + // dispose managed state (managed objects) + foreach (var archetype in ArchetypeIndex.Values) + { + for (var i = 0; i < archetype.Signature.Count; i += 1) + { + archetype.ComponentColumns[i].Dispose(); + } + } + } + + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + IsDisposed = true; + } + } + + // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + // ~World() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } diff --git a/src/System.cs b/src/System.cs index 5cc10c8..6e07eaa 100644 --- a/src/System.cs +++ b/src/System.cs @@ -25,7 +25,7 @@ namespace MoonTools.ECS return MessageDepot.Some(); } - protected ReverseSpanEnumerator ReadMessagesWithEntity(in Entity entity) where TMessage : unmanaged + protected Span.Enumerator ReadMessagesWithEntity(in Entity entity) where TMessage : unmanaged { return MessageDepot.WithEntity(entity.ID); } diff --git a/src/World.cs b/src/World.cs index 9c14ad4..7fb633e 100644 --- a/src/World.cs +++ b/src/World.cs @@ -36,6 +36,11 @@ namespace MoonTools.ECS return EntityStorage.Tag(entity); } + public ref TComponent Get(in Entity entity) where TComponent : unmanaged + { + return ref ComponentDepot.Get(entity.ID); + } + public void Set(Entity entity, in TComponent component) where TComponent : unmanaged { #if DEBUG