From 94c195e920400d1cb28b4d57131032a98775055f Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 19 Dec 2023 15:31:10 -0800 Subject: [PATCH] sparse set --- src/Collections/NativeArray.cs | 2 +- src/ComponentStorage.cs | 71 +++++++++++++++++++--------------- src/Snapshot.cs | 32 +++++++++------ src/TypeId.cs | 1 + src/World.cs | 4 +- 5 files changed, 63 insertions(+), 47 deletions(-) diff --git a/src/Collections/NativeArray.cs b/src/Collections/NativeArray.cs index 8ceba1d..33f0daf 100644 --- a/src/Collections/NativeArray.cs +++ b/src/Collections/NativeArray.cs @@ -8,7 +8,7 @@ public unsafe class NativeArray : IDisposable where T : unmanaged { private T* Elements; public int Count { get; private set; } - private int Capacity; + public int Capacity { get; private set; } private int ElementSize; public Span ToSpan() => new Span(Elements, Count); diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs index 5407282..90dc29d 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -1,4 +1,6 @@ using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using MoonTools.ECS.Collections; namespace MoonTools.ECS; @@ -6,36 +8,34 @@ namespace MoonTools.ECS; public class ComponentStorage : IDisposable { internal readonly NativeArray DenseArray = new NativeArray(); - internal readonly NativeArray SparseArray = new NativeArray(); - internal readonly NativeArray ElementArray; + internal readonly NativeArray SparseArray = new NativeArray(); + internal nint ElementArray; + internal int ElementArrayCapacity; private bool IsDisposed; public int ElementSize { get; private set; } public int Count => DenseArray.Count; - public ComponentStorage(int elementSize) + public unsafe ComponentStorage(int elementSize) { for (var i = 0; i < 16; i += 1) { - SparseArray.Append(Entity.Null); // sentinel value + SparseArray.Append(Entity.Null.ID); // sentinel value } - ElementArray = new NativeArray(elementSize); + ElementArrayCapacity = 16; + ElementArray = (nint) NativeMemory.Alloc((nuint) (elementSize * ElementArrayCapacity)); ElementSize = elementSize; } public bool Any() => DenseArray.Count > 0; - public ref T Get(Entity entity) where T : unmanaged + public unsafe ref T Get(Entity entity) where T : unmanaged { - if (entity.ID >= ElementArray.Capacity) - { - throw new Exception("oh noes"); - } - return ref ElementArray.Get(entity.ID); + return ref ((T*) ElementArray)[SparseArray[entity.ID]]; } - public ref T GetFirst() where T : unmanaged + public unsafe ref T GetFirst() where T : unmanaged { #if DEBUG if (DenseArray.Count == 0) @@ -43,12 +43,12 @@ public class ComponentStorage : IDisposable throw new IndexOutOfRangeException("Component storage is empty!"); } #endif - return ref ElementArray.Get(DenseArray[0].ID); + return ref ((T*) ElementArray)[0]; } - public bool Set(Entity entity, T component) where T : unmanaged + public unsafe bool Set(Entity entity, T component) where T : unmanaged { - var newEntity = SparseArray[entity.ID] == Entity.Null; + var newEntity = entity.ID >= SparseArray.Count || SparseArray[entity.ID] == Entity.Null.ID; if (newEntity) { // the entity is being added! let's do some record keeping @@ -58,40 +58,47 @@ public class ComponentStorage : IDisposable { var oldCount = SparseArray.Count; SparseArray.ResizeTo(entity.ID + 1); - for (var i = oldCount; i < SparseArray.Count; i += 1) + for (var i = oldCount; i < SparseArray.Capacity; i += 1) { - SparseArray.Append(Entity.Null); // sentinel value + SparseArray.Append(Entity.Null.ID); // sentinel value } } - SparseArray[entity.ID] = entity; + SparseArray[entity.ID] = index; - // FIXME: something is not right here - if (entity.ID >= ElementArray.Count) + if (entity.ID >= ElementArrayCapacity) { - ElementArray.ResizeTo(entity.ID + 1); + ElementArrayCapacity = entity.ID + 1; + ElementArray = (nint) NativeMemory.Realloc((void*) ElementArray, (nuint) (ElementArrayCapacity * ElementSize)); } } - ElementArray.Set(entity.ID, component); + Unsafe.Write((void*) (ElementArray + ElementSize * SparseArray[entity.ID]), component); return !newEntity; } public bool Has(Entity entity) { - return SparseArray[entity.ID] != Entity.Null; + return entity.ID < SparseArray.Count && SparseArray[entity.ID] != Entity.Null.ID; } - public bool Remove(Entity entity) + public unsafe bool Remove(Entity entity) { if (Has(entity)) { var denseIndex = SparseArray[entity.ID]; var lastItem = DenseArray[DenseArray.Count - 1]; - DenseArray[denseIndex.ID] = lastItem; + DenseArray[denseIndex] = lastItem; SparseArray[lastItem.ID] = denseIndex; - SparseArray[entity.ID] = Entity.Null; // sentinel value + SparseArray[entity.ID] = Entity.Null.ID; // sentinel value + + if (denseIndex != DenseArray.Count - 1) + { + NativeMemory.Copy((void*) (ElementArray + ElementSize * (DenseArray.Count - 1)), (void*) (ElementArray + ElementSize * denseIndex), (nuint) ElementSize); + } + DenseArray.RemoveLastElement(); - ElementArray.RemoveLastElement(); + + return true; } @@ -102,10 +109,9 @@ public class ComponentStorage : IDisposable public void Clear() { DenseArray.Clear(); - ElementArray.Clear(); - for (var i = 0; i < SparseArray.Count; i += 1) + for (var i = 0; i < SparseArray.Capacity; i += 1) { - SparseArray[i] = Entity.Null; + SparseArray[i] = Entity.Null.ID; } } @@ -127,7 +133,7 @@ public class ComponentStorage : IDisposable } #endif - protected virtual void Dispose(bool disposing) + protected unsafe virtual void Dispose(bool disposing) { if (!IsDisposed) { @@ -135,9 +141,10 @@ public class ComponentStorage : IDisposable { DenseArray.Dispose(); SparseArray.Dispose(); - ElementArray.Dispose(); } + NativeMemory.Free((void*) ElementArray); + IsDisposed = true; } } diff --git a/src/Snapshot.cs b/src/Snapshot.cs index def9fc9..9c9251c 100644 --- a/src/Snapshot.cs +++ b/src/Snapshot.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using MoonTools.ECS.Collections; namespace MoonTools.ECS; @@ -206,34 +207,40 @@ public class Snapshot : IDisposable private class ComponentSnapshot : IDisposable { - private readonly NativeArray DenseArray = new NativeArray(); - private readonly NativeArray SparseArray = new NativeArray(); - private readonly NativeArray ElementArray; + private readonly NativeArray DenseArray; + private readonly NativeArray SparseArray; + private nint ElementArray; + private int ElementArrayCapacity; private bool IsDisposed; - public ComponentSnapshot(int elementSize) + public unsafe ComponentSnapshot(int elementSize) { - ElementArray = new NativeArray(elementSize); + ElementArray = (nint) NativeMemory.Alloc((nuint) (16 * elementSize)); DenseArray = new NativeArray(elementSize); - SparseArray = new NativeArray(elementSize); + SparseArray = new NativeArray(elementSize); } - public void Take(ComponentStorage componentStorage) + public unsafe void Take(ComponentStorage componentStorage) { componentStorage.DenseArray.CopyTo(DenseArray); componentStorage.SparseArray.CopyTo(SparseArray); - componentStorage.ElementArray.CopyAllTo(ElementArray); + if (componentStorage.ElementArrayCapacity > ElementArrayCapacity) + { + ElementArrayCapacity = componentStorage.ElementArrayCapacity; + NativeMemory.Realloc((void*) ElementArray, (nuint) (componentStorage.ElementSize * ElementArrayCapacity)); + } + NativeMemory.Copy((void*) componentStorage.ElementArray, (void*) ElementArray, (nuint) (ElementArrayCapacity * componentStorage.ElementSize)); } - public void Restore(ComponentStorage componentStorage) + public unsafe void Restore(ComponentStorage componentStorage) { DenseArray.CopyTo(componentStorage.DenseArray); SparseArray.CopyTo(componentStorage.SparseArray); - ElementArray.CopyAllTo(componentStorage.ElementArray); + NativeMemory.Copy((void*) ElementArray, (void*) componentStorage.ElementArray, (nuint) (ElementArrayCapacity * componentStorage.ElementSize)); } - protected virtual void Dispose(bool disposing) + protected unsafe virtual void Dispose(bool disposing) { if (!IsDisposed) { @@ -241,9 +248,10 @@ public class Snapshot : IDisposable { DenseArray.Dispose(); SparseArray.Dispose(); - ElementArray.Dispose(); } + NativeMemory.Free((void*) ElementArray); + IsDisposed = true; } } diff --git a/src/TypeId.cs b/src/TypeId.cs index 92f3598..53c785f 100644 --- a/src/TypeId.cs +++ b/src/TypeId.cs @@ -36,6 +36,7 @@ public class ComponentTypeIdAssigner : ComponentTypeIdAssigner #if DEBUG World.ComponentTypeToId[typeof(T)] = new TypeId(Id); + World.ComponentTypeIdToType.Add(typeof(T)); #endif } } diff --git a/src/World.cs b/src/World.cs index 0f975d7..5841580 100644 --- a/src/World.cs +++ b/src/World.cs @@ -148,12 +148,12 @@ public class World : IDisposable public bool Has(in Entity entity) where T : unmanaged { - return EntityComponentIndex[entity].Contains(new TypeId(ComponentTypeIdAssigner.Id)); + return GetComponentStorage().Has(entity); } internal bool Has(in Entity entity, in TypeId typeId) { - return EntityComponentIndex[entity].Contains(typeId); + return ComponentIndex[(int) typeId.Value].Has(entity); } public bool Some() where T : unmanaged