using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace MoonTools.ECS { internal abstract class ComponentStorage { internal abstract unsafe void Set(int entityID, void* component); public abstract bool Remove(int entityID); public abstract void Clear(); // used for debugging and template instantiation internal abstract unsafe void* UntypedGet(int entityID); // used to create correctly typed storage on snapshot public abstract ComponentStorage CreateStorage(); #if DEBUG internal abstract object Debug_Get(int entityID); internal abstract IEnumerable Debug_GetEntityIDs(); #endif } internal unsafe class ComponentStorage : ComponentStorage, IDisposable where TComponent : unmanaged { private readonly Dictionary entityIDToStorageIndex = new Dictionary(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 count > 0; } public ref readonly TComponent Get(int entityID) { return ref components[entityIDToStorageIndex[entityID]]; } internal override unsafe void* UntypedGet(int entityID) { return &components[entityIDToStorageIndex[entityID]]; } public ref readonly TComponent GetFirst() { #if DEBUG if (count == 0) { throw new IndexOutOfRangeException("Component storage is empty!"); } #endif return ref components[0]; } public void Set(int entityID, in TComponent component) { if (!entityIDToStorageIndex.ContainsKey(entityID)) { var index = count; count += 1; if (index >= capacity) { capacity *= 2; components = (TComponent*) NativeMemory.Realloc(components, (nuint) (capacity * Unsafe.SizeOf())); entityIDs = (int*) NativeMemory.Realloc(entityIDs, (nuint) (capacity * Unsafe.SizeOf())); } entityIDToStorageIndex[entityID] = index; entityIDs[index] = entityID; } components[entityIDToStorageIndex[entityID]] = component; } internal override unsafe void Set(int entityID, void* component) { Set(entityID, *(TComponent*) component); } // Returns true if the entity had this component. public override bool Remove(int entityID) { if (entityIDToStorageIndex.TryGetValue(entityID, out int storageIndex)) { entityIDToStorageIndex.Remove(entityID); var lastElementIndex = count - 1; // move a component into the hole to maintain contiguous memory if (lastElementIndex != storageIndex) { var lastEntityID = entityIDs[lastElementIndex]; entityIDToStorageIndex[lastEntityID] = storageIndex; components[storageIndex] = components[lastElementIndex]; entityIDs[storageIndex] = lastEntityID; } count -= 1; return true; } return false; } public override void Clear() { count = 0; entityIDToStorageIndex.Clear(); } public ReadOnlySpan AllComponents() { return new ReadOnlySpan(components, count); } public Entity FirstEntity() { #if DEBUG if (count == 0) { throw new IndexOutOfRangeException("Component storage is empty!"); } #endif return new Entity(entityIDs[0]); } public override ComponentStorage CreateStorage() { return new ComponentStorage(); } #if DEBUG internal override object Debug_Get(int entityID) { return components[entityIDToStorageIndex[entityID]]; } internal override IEnumerable Debug_GetEntityIDs() { 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 } }