refactor collections to use NativeMemory

pull/5/head
cosmonaut 2023-10-09 12:03:48 -07:00
parent 231575757e
commit 4722445f11
7 changed files with 250 additions and 109 deletions

View File

@ -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) internal unsafe void* UntypedGet(int entityID, int componentTypeIndex)
{ {

View File

@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace MoonTools.ECS namespace MoonTools.ECS
{ {
@ -19,16 +21,24 @@ namespace MoonTools.ECS
#endif #endif
} }
internal class ComponentStorage<TComponent> : ComponentStorage where TComponent : unmanaged internal unsafe class ComponentStorage<TComponent> : ComponentStorage, IDisposable where TComponent : unmanaged
{ {
private int nextID;
private readonly Dictionary<int, int> entityIDToStorageIndex = new Dictionary<int, int>(16); private readonly Dictionary<int, int> entityIDToStorageIndex = new Dictionary<int, int>(16);
private int[] entityIDs = new int[16]; private TComponent* components;
private TComponent[] components = new TComponent[16]; private int* entityIDs;
private int count = 0;
private int capacity = 16;
private bool disposed;
public ComponentStorage()
{
components = (TComponent*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<TComponent>()));
entityIDs = (int*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<int>()));
}
public bool Any() public bool Any()
{ {
return nextID > 0; return count > 0;
} }
public ref readonly TComponent Get(int entityID) public ref readonly TComponent Get(int entityID)
@ -38,16 +48,13 @@ namespace MoonTools.ECS
internal override unsafe void* UntypedGet(int entityID) internal override unsafe void* UntypedGet(int entityID)
{ {
fixed (void* p = &components[entityIDToStorageIndex[entityID]]) return &components[entityIDToStorageIndex[entityID]];
{
return p;
}
} }
public ref readonly TComponent GetFirst() public ref readonly TComponent GetFirst()
{ {
#if DEBUG #if DEBUG
if (nextID == 0) if (count == 0)
{ {
throw new IndexOutOfRangeException("Component storage is empty!"); throw new IndexOutOfRangeException("Component storage is empty!");
} }
@ -59,13 +66,14 @@ namespace MoonTools.ECS
{ {
if (!entityIDToStorageIndex.ContainsKey(entityID)) if (!entityIDToStorageIndex.ContainsKey(entityID))
{ {
var index = nextID; var index = count;
nextID += 1; count += 1;
if (index >= components.Length) if (index >= capacity)
{ {
Array.Resize(ref components, components.Length * 2); capacity *= 2;
Array.Resize(ref entityIDs, entityIDs.Length * 2); components = (TComponent*) NativeMemory.Realloc(components, (nuint) (capacity * Unsafe.SizeOf<TComponent>()));
entityIDs = (int*) NativeMemory.Realloc(entityIDs, (nuint) (capacity * Unsafe.SizeOf<int>()));
} }
entityIDToStorageIndex[entityID] = index; entityIDToStorageIndex[entityID] = index;
@ -77,7 +85,7 @@ namespace MoonTools.ECS
internal override unsafe void Set(int entityID, void* component) 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. // Returns true if the entity had this component.
@ -87,7 +95,7 @@ namespace MoonTools.ECS
{ {
entityIDToStorageIndex.Remove(entityID); entityIDToStorageIndex.Remove(entityID);
var lastElementIndex = nextID - 1; var lastElementIndex = count - 1;
// move a component into the hole to maintain contiguous memory // move a component into the hole to maintain contiguous memory
if (lastElementIndex != storageIndex) if (lastElementIndex != storageIndex)
@ -98,7 +106,7 @@ namespace MoonTools.ECS
entityIDs[storageIndex] = lastEntityID; entityIDs[storageIndex] = lastEntityID;
} }
nextID -= 1; count -= 1;
return true; return true;
} }
@ -108,19 +116,19 @@ namespace MoonTools.ECS
public override void Clear() public override void Clear()
{ {
nextID = 0; count = 0;
entityIDToStorageIndex.Clear(); entityIDToStorageIndex.Clear();
} }
public ReadOnlySpan<TComponent> AllComponents() public ReadOnlySpan<TComponent> AllComponents()
{ {
return new ReadOnlySpan<TComponent>(components, 0, nextID); return new ReadOnlySpan<TComponent>(components, count);
} }
public Entity FirstEntity() public Entity FirstEntity()
{ {
#if DEBUG #if DEBUG
if (nextID == 0) if (count == 0)
{ {
throw new IndexOutOfRangeException("Component storage is empty!"); throw new IndexOutOfRangeException("Component storage is empty!");
} }
@ -143,6 +151,32 @@ namespace MoonTools.ECS
{ {
return entityIDToStorageIndex.Keys; 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 #endif
} }
} }

View File

@ -1,40 +1,69 @@
using System; using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace MoonTools.ECS namespace MoonTools.ECS.Collections
{ {
public class DynamicArray<T> where T : unmanaged public unsafe class NativeArray<T> : IDisposable where T : unmanaged
{ {
private T[] Array; private T* Array;
public int Count { get; private set; } private int count;
private int capacity;
public Span<T> ToSpan() => new Span<T>(Array, 0, Count); public int Count => count;
public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(Array, 0, Count));
public DynamicArray(int capacity = 16) public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(Array, Count));
private bool disposed;
public NativeArray(int capacity = 16)
{ {
Array = new T[capacity]; this.capacity = capacity;
Count = 0; Array = (T*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<T>()));
count = 0;
} }
public ref T this[int i] public ref T this[int i] => ref Array[i];
{
get { return ref Array[i]; }
}
public void Add(T item) 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<T>()));
} }
Array[Count] = item; Array[count] = item;
Count += 1; count += 1;
} }
public void Clear() 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);
} }
} }
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS namespace MoonTools.ECS
{ {

View File

@ -1,26 +1,31 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace MoonTools.ECS namespace MoonTools.ECS.Collections
{ {
internal class IndexableSet<T> where T : unmanaged public unsafe class IndexableSet<T> : IDisposable where T : unmanaged
{ {
private Dictionary<T, int> indices; private Dictionary<T, int> indices;
private T[] array; private T* array;
public int Count { get; private set; } private int count;
public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(array, 0, Count)); private int capacity;
private bool disposed;
public IndexableSet(int size = 32) public int Count => count;
public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(array, count));
public IndexableSet(int capacity = 32)
{ {
indices = new Dictionary<T, int>(size); this.capacity = capacity;
array = new T[size]; count = 0;
indices = new Dictionary<T, int>(capacity);
array = (T*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<T>()));
} }
public T this[int i] public T this[int i] => array[i];
{
get { return array[i]; }
}
public bool Contains(T element) public bool Contains(T element)
{ {
@ -31,15 +36,16 @@ namespace MoonTools.ECS
{ {
if (!Contains(element)) 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<T>()));
} }
array[Count] = element; array[count] = element;
Count += 1; count += 1;
return true; return true;
} }
@ -58,7 +64,7 @@ namespace MoonTools.ECS
var index = indices[element]; var index = indices[element];
array[index] = lastElement; array[index] = lastElement;
indices[lastElement] = index; indices[lastElement] = index;
Count -= 1; count -= 1;
indices.Remove(element); indices.Remove(element);
return true; return true;
@ -66,45 +72,32 @@ namespace MoonTools.ECS
public void Clear() public void Clear()
{ {
Count = 0; indices.Clear();
count = 0;
} }
public struct Enumerator protected virtual void Dispose(bool disposing)
{ {
/// <summary>The set being enumerated.</summary> if (!disposed)
private readonly IndexableSet<T> _set;
/// <summary>The next index to yield.</summary>
private int _index;
/// <summary>Initialize the enumerator.</summary>
/// <param name="set">The set to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Enumerator(IndexableSet<T> set)
{ {
_set = set; NativeMemory.Free(array);
_index = _set.Count; array = null;
}
/// <summary>Advances the enumerator to the next element of the span.</summary> disposed = true;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
int index = _index - 1;
if (index >= 0)
{
_index = index;
return true;
}
return false;
} }
}
/// <summary>Gets the element at the current position of the enumerator.</summary> ~IndexableSet()
public T Current {
{ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
[MethodImpl(MethodImplOptions.AggressiveInlining)] Dispose(disposing: false);
get => _set[_index]; }
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
} }
} }
} }

View File

@ -1,5 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS namespace MoonTools.ECS
{ {
@ -8,17 +11,18 @@ namespace MoonTools.ECS
public abstract void Clear(); public abstract void Clear();
} }
internal class MessageStorage<TMessage> : MessageStorage where TMessage : unmanaged internal unsafe class MessageStorage<TMessage> : MessageStorage, IDisposable where TMessage : unmanaged
{ {
private int count = 0; private int count = 0;
private int capacity = 128; private int capacity = 128;
private TMessage[] messages; private TMessage* messages;
// duplicating storage here for fast iteration // duplicating storage here for fast iteration
private Dictionary<int, DynamicArray<TMessage>> entityToMessages = new Dictionary<int, DynamicArray<TMessage>>(); private Dictionary<int, NativeArray<TMessage>> entityToMessages = new Dictionary<int, NativeArray<TMessage>>();
private bool disposed;
public MessageStorage() public MessageStorage()
{ {
messages = new TMessage[capacity]; messages = (TMessage*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<TMessage>()));
} }
public void Add(in TMessage message) public void Add(in TMessage message)
@ -26,7 +30,7 @@ namespace MoonTools.ECS
if (count == capacity) if (count == capacity)
{ {
capacity *= 2; capacity *= 2;
Array.Resize(ref messages, capacity); messages = (TMessage*) NativeMemory.Realloc(messages, (nuint) (capacity * Unsafe.SizeOf<TMessage>()));
} }
messages[count] = message; messages[count] = message;
@ -37,7 +41,7 @@ namespace MoonTools.ECS
{ {
if (!entityToMessages.ContainsKey(entityID)) if (!entityToMessages.ContainsKey(entityID))
{ {
entityToMessages.Add(entityID, new DynamicArray<TMessage>()); entityToMessages.Add(entityID, new NativeArray<TMessage>());
} }
entityToMessages[entityID].Add(message); entityToMessages[entityID].Add(message);
@ -51,7 +55,7 @@ namespace MoonTools.ECS
public ReadOnlySpan<TMessage> All() public ReadOnlySpan<TMessage> All()
{ {
return new ReadOnlySpan<TMessage>(messages, 0, count); return new ReadOnlySpan<TMessage>(messages, count);
} }
public TMessage First() public TMessage First()
@ -89,5 +93,39 @@ namespace MoonTools.ECS
set.Clear(); 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);
}
} }
} }

View File

@ -1,5 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS namespace MoonTools.ECS
{ {
@ -16,19 +19,28 @@ namespace MoonTools.ECS
// Relation is the two entities, A related to B. // Relation is the two entities, A related to B.
// TRelation is the data attached to the relation. // TRelation is the data attached to the relation.
internal class RelationStorage<TRelation> : RelationStorage where TRelation : unmanaged internal unsafe class RelationStorage<TRelation> : RelationStorage, IDisposable where TRelation : unmanaged
{ {
private int count = 0; 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 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<int, IndexableSet<Entity>> outRelations = new Dictionary<int, IndexableSet<Entity>>(16); private Dictionary<int, IndexableSet<Entity>> outRelations = new Dictionary<int, IndexableSet<Entity>>(16);
private Dictionary<int, IndexableSet<Entity>> inRelations = new Dictionary<int, IndexableSet<Entity>>(16); private Dictionary<int, IndexableSet<Entity>> inRelations = new Dictionary<int, IndexableSet<Entity>>(16);
private Stack<IndexableSet<Entity>> listPool = new Stack<IndexableSet<Entity>>(); private Stack<IndexableSet<Entity>> listPool = new Stack<IndexableSet<Entity>>();
private bool disposed;
public RelationStorage()
{
relations = ((Entity, Entity)*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<(Entity, Entity)>()));
relationDatas = (TRelation*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<TRelation>()));
}
public ReverseSpanEnumerator<(Entity, Entity)> All() 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) public void Set(in Entity entityA, in Entity entityB, TRelation relationData)
@ -56,10 +68,11 @@ namespace MoonTools.ECS
} }
inRelations[idB].Add(idA); inRelations[idB].Add(idA);
if (count >= relationDatas.Length) if (count >= capacity)
{ {
Array.Resize(ref relations, relations.Length * 2); capacity *= 2;
Array.Resize(ref relationDatas, relationDatas.Length * 2); relations = ((Entity, Entity)*) NativeMemory.Realloc(relations, (nuint) (capacity * Unsafe.SizeOf<(Entity, Entity)>()));
relationDatas = (TRelation*) NativeMemory.Realloc(relationDatas, (nuint) (capacity * Unsafe.SizeOf<TRelation>()));
} }
relations[count] = relation; relations[count] = relation;
@ -219,7 +232,7 @@ namespace MoonTools.ECS
public override unsafe void Set(int entityA, int entityB, void* relationData) 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) public override int GetStorageIndex(int entityA, int entityB)
@ -229,10 +242,7 @@ namespace MoonTools.ECS
public override unsafe void* Get(int relationStorageIndex) public override unsafe void* Get(int relationStorageIndex)
{ {
fixed (void* p = &relationDatas[relationStorageIndex]) return &relationDatas[relationStorageIndex];
{
return p;
}
} }
public override void UnrelateAll(int entityID) public override void UnrelateAll(int entityID)
@ -282,5 +292,41 @@ namespace MoonTools.ECS
} }
outRelations.Clear(); 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);
}
} }
} }