diff --git a/src/Rev2/Column.cs b/src/Rev2/Column.cs index 632a66c..a7efe94 100644 --- a/src/Rev2/Column.cs +++ b/src/Rev2/Column.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace MoonTools.ECS.Rev2 @@ -22,13 +23,19 @@ namespace MoonTools.ECS.Rev2 Elements = (nint) NativeMemory.Alloc((nuint) (ElementSize * Capacity)); } - private void Resize() + [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)); } - private void ResizeTo(int capacity) + public void ResizeTo(int capacity) { Capacity = capacity; Elements = (nint) NativeMemory.Realloc((void*) Elements, (nuint) (ElementSize * Capacity)); diff --git a/src/Rev2/Entity.cs b/src/Rev2/Entity.cs deleted file mode 100644 index 2a9b435..0000000 --- a/src/Rev2/Entity.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace MoonTools.ECS.Rev2; - -public readonly record struct Entity(uint Id); diff --git a/src/Rev2/Relation.cs b/src/Rev2/Relation.cs new file mode 100644 index 0000000..f747f1e --- /dev/null +++ b/src/Rev2/Relation.cs @@ -0,0 +1,269 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using MoonTools.ECS.Collections; + +namespace MoonTools.ECS.Rev2; + +internal class Relation +{ + private int count = 0; + private int capacity = 16; + private Column relations; + private Column relationDatas; + private Dictionary<(Id, Id), int> indices = new Dictionary<(Id, Id), int>(16); + private Dictionary> outRelations = new Dictionary>(16); + private Dictionary> inRelations = new Dictionary>(16); + private Stack> listPool = new Stack>(); + + private bool disposed; + + public Relation(int relationDataSize) + { + relations = new Column(Unsafe.SizeOf<(Id, Id)>()); + relationDatas = new Column(relationDataSize); + } + + public unsafe ReverseSpanEnumerator<(Id, Id)> All() + { + return new ReverseSpanEnumerator<(Id, Id)>(new Span<(Id, Id)>((void*) relations.Elements, count)); + } + + public unsafe void Set(in Id entityA, in Id entityB, in T relationData) where T : unmanaged + { + var relation = (entityA, entityB); + + if (indices.TryGetValue(relation, out var index)) + { + ((T*) relationDatas.Elements)[index] = relationData; + return; + } + + if (!outRelations.ContainsKey(entityA)) + { + outRelations[entityA] = AcquireHashSetFromPool(); + } + outRelations[entityA].Add(entityB); + + if (!inRelations.ContainsKey(entityB)) + { + inRelations[entityB] = AcquireHashSetFromPool(); + } + inRelations[entityB].Add(entityA); + + if (count >= capacity) + { + relations.Resize(); + relationDatas.Resize(); + } + + (((Id, Id)*) relationDatas.Elements)[count] = relation; + ((T*) relationDatas.Elements)[count] = relationData; + indices.Add(relation, count); + count += 1; + } + + public ref T Get(in Id entityA, in Id entityB) where T : unmanaged + { + var relationIndex = indices[(entityA, entityB)]; + return ref relationDatas.Get(relationIndex); + } + + public bool Has((Id, Id) relation) + { + return indices.ContainsKey(relation); + } + + public ReverseSpanEnumerator OutRelations(Id entityID) + { + if (outRelations.TryGetValue(entityID, out var entityOutRelations)) + { + return entityOutRelations.GetEnumerator(); + } + else + { + return ReverseSpanEnumerator.Empty; + } + } + + public Id OutFirst(Id entityID) + { + return OutNth(entityID, 0); + } + + public Id OutNth(Id entityID, int n) + { +#if DEBUG + if (!outRelations.ContainsKey(entityID) || outRelations[entityID].Count == 0) + { + throw new KeyNotFoundException("No out relations to this entity!"); + } +#endif + return outRelations[entityID][n]; + } + + public bool HasOutRelation(Id entityID) + { + return outRelations.ContainsKey(entityID) && outRelations[entityID].Count > 0; + } + + public int OutRelationCount(Id entityID) + { + return outRelations.TryGetValue(entityID, out var entityOutRelations) ? entityOutRelations.Count : 0; + } + + public ReverseSpanEnumerator InRelations(Id entityID) + { + if (inRelations.TryGetValue(entityID, out var entityInRelations)) + { + return entityInRelations.GetEnumerator(); + } + else + { + return ReverseSpanEnumerator.Empty; + } + } + + public Id InFirst(Id entityID) + { + return InNth(entityID, 0); + } + + public Id InNth(Id entityID, int n) + { +#if DEBUG + if (!inRelations.ContainsKey(entityID) || inRelations[entityID].Count == 0) + { + throw new KeyNotFoundException("No in relations to this entity!"); + } +#endif + + return inRelations[entityID][n]; + } + + public bool HasInRelation(Id entityID) + { + return inRelations.ContainsKey(entityID) && inRelations[entityID].Count > 0; + } + + public int InRelationCount(Id entityID) + { + return inRelations.TryGetValue(entityID, out var entityInRelations) ? entityInRelations.Count : 0; + } + + public (bool, bool) Remove(in Id entityA, in Id entityB) + { + var aEmpty = false; + var bEmpty = false; + var relation = (entityA, entityB); + + if (outRelations.TryGetValue(entityA, out var entityOutRelations)) + { + entityOutRelations.Remove(entityB); + if (outRelations[entityA].Count == 0) + { + aEmpty = true; + } + } + + if (inRelations.TryGetValue(entityB, out var entityInRelations)) + { + entityInRelations.Remove(entityA); + if (inRelations[entityB].Count == 0) + { + bEmpty = true; + } + } + + if (indices.TryGetValue(relation, out var index)) + { + relationDatas.Delete(index); + relations.Delete(index); + + var lastElementIndex = count - 1; + + // move an element into the hole + if (index != lastElementIndex) + { + var lastRelation = relations.Get<(Id, Id)>(lastElementIndex); + indices[lastRelation] = index; + } + + count -= 1; + indices.Remove(relation); + } + + return (aEmpty, bEmpty); + } + + private IndexableSet AcquireHashSetFromPool() + { + if (listPool.Count == 0) + { + listPool.Push(new IndexableSet()); + } + + return listPool.Pop(); + } + + private void ReturnHashSetToPool(IndexableSet hashSet) + { + hashSet.Clear(); + listPool.Push(hashSet); + } + + public void Clear() + { + count = 0; + indices.Clear(); + + foreach (var set in inRelations.Values) + { + ReturnHashSetToPool(set); + } + inRelations.Clear(); + + foreach (var set in outRelations.Values) + { + ReturnHashSetToPool(set); + } + outRelations.Clear(); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + Clear(); + + if (disposing) + { + foreach (var set in listPool) + { + set.Dispose(); + } + + relations.Dispose(); + relationDatas.Dispose(); + + relations = null; + relationDatas = null; + } + + disposed = true; + } + } + + // ~RelationStorage() + // { + // // 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/World.cs b/src/Rev2/World.cs index 8974ace..dd4c00d 100644 --- a/src/Rev2/World.cs +++ b/src/Rev2/World.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace MoonTools.ECS.Rev2 {