From 4722445f11beafbcda11728585b798eda0351fe5 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Mon, 9 Oct 2023 12:03:48 -0700 Subject: [PATCH] refactor collections to use NativeMemory --- src/ComponentDepot.cs | 2 +- src/ComponentStorage.cs | 76 +++++++++++++++++++++++---------- src/DynamicArray.cs | 65 ++++++++++++++++++++-------- src/FilterStorage.cs | 1 + src/IndexableSet.cs | 93 +++++++++++++++++++---------------------- src/MessageStorage.cs | 52 +++++++++++++++++++---- src/RelationStorage.cs | 70 +++++++++++++++++++++++++------ 7 files changed, 250 insertions(+), 109 deletions(-) diff --git a/src/ComponentDepot.cs b/src/ComponentDepot.cs index 8a6b37f..cdd4d5f 100644 --- a/src/ComponentDepot.cs +++ b/src/ComponentDepot.cs @@ -89,7 +89,7 @@ namespace MoonTools.ECS } } - // these methods used to implement snapshots, templates, and debugging + // these methods used to implement transfers and debugging internal unsafe void* UntypedGet(int entityID, int componentTypeIndex) { diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs index 41117bc..bc71ef7 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace MoonTools.ECS { @@ -19,16 +21,24 @@ namespace MoonTools.ECS #endif } - internal class ComponentStorage : ComponentStorage where TComponent : unmanaged + internal unsafe class ComponentStorage : ComponentStorage, IDisposable where TComponent : unmanaged { - private int nextID; private readonly Dictionary entityIDToStorageIndex = new Dictionary(16); - private int[] entityIDs = new int[16]; - private TComponent[] components = new TComponent[16]; + private TComponent* components; + private int* entityIDs; + private int count = 0; + private int capacity = 16; + private bool disposed; + + public ComponentStorage() + { + components = (TComponent*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf())); + entityIDs = (int*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf())); + } public bool Any() { - return nextID > 0; + return count > 0; } public ref readonly TComponent Get(int entityID) @@ -38,16 +48,13 @@ namespace MoonTools.ECS internal override unsafe void* UntypedGet(int entityID) { - fixed (void* p = &components[entityIDToStorageIndex[entityID]]) - { - return p; - } + return &components[entityIDToStorageIndex[entityID]]; } public ref readonly TComponent GetFirst() { #if DEBUG - if (nextID == 0) + if (count == 0) { throw new IndexOutOfRangeException("Component storage is empty!"); } @@ -59,13 +66,14 @@ namespace MoonTools.ECS { if (!entityIDToStorageIndex.ContainsKey(entityID)) { - var index = nextID; - nextID += 1; + var index = count; + count += 1; - if (index >= components.Length) + if (index >= capacity) { - Array.Resize(ref components, components.Length * 2); - Array.Resize(ref entityIDs, entityIDs.Length * 2); + capacity *= 2; + components = (TComponent*) NativeMemory.Realloc(components, (nuint) (capacity * Unsafe.SizeOf())); + entityIDs = (int*) NativeMemory.Realloc(entityIDs, (nuint) (capacity * Unsafe.SizeOf())); } entityIDToStorageIndex[entityID] = index; @@ -77,7 +85,7 @@ namespace MoonTools.ECS internal override unsafe void Set(int entityID, void* component) { - Set(entityID, *((TComponent*) component)); + Set(entityID, *(TComponent*) component); } // Returns true if the entity had this component. @@ -87,7 +95,7 @@ namespace MoonTools.ECS { entityIDToStorageIndex.Remove(entityID); - var lastElementIndex = nextID - 1; + var lastElementIndex = count - 1; // move a component into the hole to maintain contiguous memory if (lastElementIndex != storageIndex) @@ -98,7 +106,7 @@ namespace MoonTools.ECS entityIDs[storageIndex] = lastEntityID; } - nextID -= 1; + count -= 1; return true; } @@ -108,19 +116,19 @@ namespace MoonTools.ECS public override void Clear() { - nextID = 0; + count = 0; entityIDToStorageIndex.Clear(); } public ReadOnlySpan AllComponents() { - return new ReadOnlySpan(components, 0, nextID); + return new ReadOnlySpan(components, count); } public Entity FirstEntity() { #if DEBUG - if (nextID == 0) + if (count == 0) { throw new IndexOutOfRangeException("Component storage is empty!"); } @@ -143,6 +151,32 @@ namespace MoonTools.ECS { return entityIDToStorageIndex.Keys; } + + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + NativeMemory.Free(components); + NativeMemory.Free(entityIDs); + components = null; + entityIDs = null; + + disposed = true; + } + } + + ~ComponentStorage() + { + // 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); + } #endif } } diff --git a/src/DynamicArray.cs b/src/DynamicArray.cs index c16b697..08400fc 100644 --- a/src/DynamicArray.cs +++ b/src/DynamicArray.cs @@ -1,40 +1,69 @@ using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; -namespace MoonTools.ECS +namespace MoonTools.ECS.Collections { - public class DynamicArray where T : unmanaged + public unsafe class NativeArray : IDisposable where T : unmanaged { - private T[] Array; - public int Count { get; private set; } + private T* Array; + private int count; + private int capacity; - public Span ToSpan() => new Span(Array, 0, Count); - public ReverseSpanEnumerator GetEnumerator() => new ReverseSpanEnumerator(new Span(Array, 0, Count)); + public int Count => count; - public DynamicArray(int capacity = 16) + public ReverseSpanEnumerator GetEnumerator() => new ReverseSpanEnumerator(new Span(Array, Count)); + + private bool disposed; + + public NativeArray(int capacity = 16) { - Array = new T[capacity]; - Count = 0; + this.capacity = capacity; + Array = (T*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf())); + count = 0; } - public ref T this[int i] - { - get { return ref Array[i]; } - } + public ref T this[int i] => ref Array[i]; public void Add(T item) { - if (Count >= Array.Length) + if (count >= capacity) { - global::System.Array.Resize(ref Array, Array.Length * 2); + capacity *= 2; + Array = (T*) NativeMemory.Realloc(Array, (nuint) (capacity * Unsafe.SizeOf())); } - Array[Count] = item; - Count += 1; + Array[count] = item; + count += 1; } public void Clear() { - Count = 0; + count = 0; + } + + 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/FilterStorage.cs b/src/FilterStorage.cs index e4780c5..fc7718f 100644 --- a/src/FilterStorage.cs +++ b/src/FilterStorage.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using MoonTools.ECS.Collections; namespace MoonTools.ECS { diff --git a/src/IndexableSet.cs b/src/IndexableSet.cs index 1a79f71..1956756 100644 --- a/src/IndexableSet.cs +++ b/src/IndexableSet.cs @@ -1,26 +1,31 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; -namespace MoonTools.ECS +namespace MoonTools.ECS.Collections { - internal class IndexableSet where T : unmanaged + public unsafe class IndexableSet : IDisposable where T : unmanaged { private Dictionary indices; - private T[] array; - public int Count { get; private set; } - public ReverseSpanEnumerator GetEnumerator() => new ReverseSpanEnumerator(new Span(array, 0, Count)); + private T* array; + private int count; + private int capacity; + private bool disposed; - public IndexableSet(int size = 32) + public int Count => count; + public ReverseSpanEnumerator GetEnumerator() => new ReverseSpanEnumerator(new Span(array, count)); + + public IndexableSet(int capacity = 32) { - indices = new Dictionary(size); - array = new T[size]; + this.capacity = capacity; + count = 0; + + indices = new Dictionary(capacity); + array = (T*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf())); } - public T this[int i] - { - get { return array[i]; } - } + public T this[int i] => array[i]; public bool Contains(T element) { @@ -31,15 +36,16 @@ namespace MoonTools.ECS { if (!Contains(element)) { - indices.Add(element, Count); + indices.Add(element, count); - if (Count >= array.Length) + if (count >= capacity) { - Array.Resize(ref array, array.Length * 2); + capacity *= 2; + array = (T*) NativeMemory.Realloc(array, (nuint) (capacity * Unsafe.SizeOf())); } - array[Count] = element; - Count += 1; + array[count] = element; + count += 1; return true; } @@ -58,7 +64,7 @@ namespace MoonTools.ECS var index = indices[element]; array[index] = lastElement; indices[lastElement] = index; - Count -= 1; + count -= 1; indices.Remove(element); return true; @@ -66,45 +72,32 @@ namespace MoonTools.ECS public void Clear() { - Count = 0; + indices.Clear(); + count = 0; } - public struct Enumerator + protected virtual void Dispose(bool disposing) { - /// The set being enumerated. - private readonly IndexableSet _set; - /// The next index to yield. - private int _index; - - /// Initialize the enumerator. - /// The set to enumerate. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Enumerator(IndexableSet set) + if (!disposed) { - _set = set; - _index = _set.Count; - } + NativeMemory.Free(array); + array = null; - /// Advances the enumerator to the next element of the span. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() - { - int index = _index - 1; - if (index >= 0) - { - _index = index; - return true; - } - - return false; + disposed = true; } + } - /// Gets the element at the current position of the enumerator. - public T Current - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _set[_index]; - } + ~IndexableSet() + { + // 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/MessageStorage.cs b/src/MessageStorage.cs index b2f29d0..c5091a6 100644 --- a/src/MessageStorage.cs +++ b/src/MessageStorage.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using MoonTools.ECS.Collections; namespace MoonTools.ECS { @@ -8,17 +11,18 @@ namespace MoonTools.ECS public abstract void Clear(); } - internal class MessageStorage : MessageStorage where TMessage : unmanaged + internal unsafe class MessageStorage : MessageStorage, IDisposable where TMessage : unmanaged { private int count = 0; private int capacity = 128; - private TMessage[] messages; + private TMessage* messages; // duplicating storage here for fast iteration - private Dictionary> entityToMessages = new Dictionary>(); + private Dictionary> entityToMessages = new Dictionary>(); + private bool disposed; public MessageStorage() { - messages = new TMessage[capacity]; + messages = (TMessage*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf())); } public void Add(in TMessage message) @@ -26,7 +30,7 @@ namespace MoonTools.ECS if (count == capacity) { capacity *= 2; - Array.Resize(ref messages, capacity); + messages = (TMessage*) NativeMemory.Realloc(messages, (nuint) (capacity * Unsafe.SizeOf())); } messages[count] = message; @@ -37,7 +41,7 @@ namespace MoonTools.ECS { if (!entityToMessages.ContainsKey(entityID)) { - entityToMessages.Add(entityID, new DynamicArray()); + entityToMessages.Add(entityID, new NativeArray()); } entityToMessages[entityID].Add(message); @@ -51,7 +55,7 @@ namespace MoonTools.ECS public ReadOnlySpan All() { - return new ReadOnlySpan(messages, 0, count); + return new ReadOnlySpan(messages, count); } public TMessage First() @@ -89,5 +93,39 @@ namespace MoonTools.ECS set.Clear(); } } + + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + Clear(); + + if (disposing) + { + foreach (var array in entityToMessages.Values) + { + array.Dispose(); + } + } + + NativeMemory.Free(messages); + messages = null; + + disposed = 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/RelationStorage.cs b/src/RelationStorage.cs index ec990f8..166f3c7 100644 --- a/src/RelationStorage.cs +++ b/src/RelationStorage.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using MoonTools.ECS.Collections; namespace MoonTools.ECS { @@ -16,19 +19,28 @@ namespace MoonTools.ECS // Relation is the two entities, A related to B. // TRelation is the data attached to the relation. - internal class RelationStorage : RelationStorage where TRelation : unmanaged + internal unsafe class RelationStorage : RelationStorage, IDisposable where TRelation : unmanaged { private int count = 0; + private int capacity = 16; + private (Entity, Entity)* relations; + private TRelation* relationDatas; private Dictionary<(Entity, Entity), int> indices = new Dictionary<(Entity, Entity), int>(16); - private (Entity, Entity)[] relations = new (Entity, Entity)[16]; - private TRelation[] relationDatas = new TRelation[16]; private Dictionary> outRelations = new Dictionary>(16); private Dictionary> inRelations = new Dictionary>(16); private Stack> listPool = new Stack>(); + private bool disposed; + + public RelationStorage() + { + relations = ((Entity, Entity)*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<(Entity, Entity)>())); + relationDatas = (TRelation*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf())); + } + public ReverseSpanEnumerator<(Entity, Entity)> All() { - return new ReverseSpanEnumerator<(Entity, Entity)>(new Span<(Entity, Entity)>(relations, 0, count)); + return new ReverseSpanEnumerator<(Entity, Entity)>(new Span<(Entity, Entity)>(relations, count)); } public void Set(in Entity entityA, in Entity entityB, TRelation relationData) @@ -56,10 +68,11 @@ namespace MoonTools.ECS } inRelations[idB].Add(idA); - if (count >= relationDatas.Length) + if (count >= capacity) { - Array.Resize(ref relations, relations.Length * 2); - Array.Resize(ref relationDatas, relationDatas.Length * 2); + capacity *= 2; + relations = ((Entity, Entity)*) NativeMemory.Realloc(relations, (nuint) (capacity * Unsafe.SizeOf<(Entity, Entity)>())); + relationDatas = (TRelation*) NativeMemory.Realloc(relationDatas, (nuint) (capacity * Unsafe.SizeOf())); } relations[count] = relation; @@ -219,7 +232,7 @@ namespace MoonTools.ECS public override unsafe void Set(int entityA, int entityB, void* relationData) { - Set(entityA, entityB, *((TRelation*) relationData)); + Set(entityA, entityB, *(TRelation*) relationData); } public override int GetStorageIndex(int entityA, int entityB) @@ -229,10 +242,7 @@ namespace MoonTools.ECS public override unsafe void* Get(int relationStorageIndex) { - fixed (void* p = &relationDatas[relationStorageIndex]) - { - return p; - } + return &relationDatas[relationStorageIndex]; } public override void UnrelateAll(int entityID) @@ -282,5 +292,41 @@ namespace MoonTools.ECS } outRelations.Clear(); } + + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + Clear(); + + if (disposing) + { + foreach (var set in listPool) + { + set.Dispose(); + } + } + + NativeMemory.Free(relations); + NativeMemory.Free(relationDatas); + 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); + } } }