From 001b6714cc8d6f59af9fda5adb36fb70f862d971 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 3 Nov 2023 12:40:26 -0700 Subject: [PATCH 01/17] storage refactor --- src/Archetype.cs | 42 ++ src/ArchetypeEdge.cs | 3 + src/ArchetypeRecord.cs | 3 + src/ArchetypeSignature.cs | 89 +++++ src/Collections/NativeArray.cs | 123 ++++++ src/Collections/NativeArrayUntyped.cs | 137 +++++++ src/ComponentDepot.cs | 139 ------- src/ComponentStorage.cs | 166 +++----- src/DebugSystem.cs | 60 +-- src/DynamicArray.cs | 69 ---- src/Entity.cs | 41 +- src/EntityComponentReader.cs | 153 ++----- src/EntityStorage.cs | 142 ------- src/Filter.cs | 220 ++++++++-- src/FilterBuilder.cs | 37 +- src/FilterStorage.cs | 273 ------------- src/IdAssigner.cs | 31 ++ src/IndexableSet.cs | 1 + src/Manipulator.cs | 1 + src/MessageDepot.cs | 68 ---- src/MessageStorage.cs | 164 +++----- src/RelationDepot.cs | 193 --------- src/RelationStorage.cs | 498 +++++++++++------------ src/Snapshot.cs | 282 +++++++++++++ src/System.cs | 50 +-- src/TypeId.cs | 5 + src/TypeIndices.cs | 33 -- src/World.cs | 554 +++++++++++++++++++------- 28 files changed, 1719 insertions(+), 1858 deletions(-) create mode 100644 src/Archetype.cs create mode 100644 src/ArchetypeEdge.cs create mode 100644 src/ArchetypeRecord.cs create mode 100644 src/ArchetypeSignature.cs create mode 100644 src/Collections/NativeArray.cs create mode 100644 src/Collections/NativeArrayUntyped.cs delete mode 100644 src/ComponentDepot.cs delete mode 100644 src/DynamicArray.cs delete mode 100644 src/EntityStorage.cs delete mode 100644 src/FilterStorage.cs create mode 100644 src/IdAssigner.cs delete mode 100644 src/MessageDepot.cs delete mode 100644 src/RelationDepot.cs create mode 100644 src/Snapshot.cs create mode 100644 src/TypeId.cs delete mode 100644 src/TypeIndices.cs diff --git a/src/Archetype.cs b/src/Archetype.cs new file mode 100644 index 0000000..45bd718 --- /dev/null +++ b/src/Archetype.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using MoonTools.ECS.Collections; + +namespace MoonTools.ECS; + +internal class Archetype +{ + public World World; + public ArchetypeSignature Signature; + public NativeArray Entities = new NativeArray(); + + public SortedDictionary Edges = new SortedDictionary(); + + public int Count => Entities.Count; + + public Archetype(World world, ArchetypeSignature signature) + { + World = world; + Signature = signature; + } + + public int Append(Entity entity) + { + Entities.Append(entity); + return Entities.Count - 1; + } + + public int Transfer(int row, Archetype transferTo) + { + var newIndex = transferTo.Append(Entities[row]); + Entities.Delete(row); + return newIndex; + } + + public void ClearAll() + { + for (int i = Entities.Count - 1; i >= 0; i -= 1) + { + World.Destroy(Entities[i]); + } + } +} diff --git a/src/ArchetypeEdge.cs b/src/ArchetypeEdge.cs new file mode 100644 index 0000000..a10fb46 --- /dev/null +++ b/src/ArchetypeEdge.cs @@ -0,0 +1,3 @@ +namespace MoonTools.ECS; + +internal readonly record struct ArchetypeEdge(Archetype Add, Archetype Remove); diff --git a/src/ArchetypeRecord.cs b/src/ArchetypeRecord.cs new file mode 100644 index 0000000..6780c29 --- /dev/null +++ b/src/ArchetypeRecord.cs @@ -0,0 +1,3 @@ +namespace MoonTools.ECS; + +internal readonly record struct ArchetypeRecord(Archetype Archetype, int Row); diff --git a/src/ArchetypeSignature.cs b/src/ArchetypeSignature.cs new file mode 100644 index 0000000..cabaeb7 --- /dev/null +++ b/src/ArchetypeSignature.cs @@ -0,0 +1,89 @@ +using System; +using MoonTools.ECS.Collections; + +namespace MoonTools.ECS; + +internal class ArchetypeSignature : IEquatable +{ + public static ArchetypeSignature Empty = new ArchetypeSignature(0); + + IndexableSet Ids; + + public int Count => Ids.Count; + + public TypeId this[int i] => Ids[i]; + + public ArchetypeSignature() + { + Ids = new IndexableSet(); + } + + public ArchetypeSignature(int capacity) + { + Ids = new IndexableSet(capacity); + } + + // Maintains sorted order + public void Insert(TypeId componentId) + { + Ids.Add(componentId); + } + + public void Remove(TypeId componentId) + { + Ids.Remove(componentId); + } + + public bool Contains(TypeId componentId) + { + return Ids.Contains(componentId); + } + + public void CopyTo(ArchetypeSignature other) + { + foreach (var id in other.Ids.AsSpan()) + { + other.Ids.Add(id); + } + } + + public override bool Equals(object? obj) + { + return obj is ArchetypeSignature signature && Equals(signature); + } + + public bool Equals(ArchetypeSignature? other) + { + if (other == null) + { + return false; + } + + if (Ids.Count != other.Ids.Count) + { + return false; + } + + for (int i = 0; i < Ids.Count; i += 1) + { + if (Ids[i] != other.Ids[i]) + { + return false; + } + } + + return true; + } + + public override int GetHashCode() + { + var hashcode = 1; + + foreach (var id in Ids) + { + hashcode = HashCode.Combine(hashcode, id); + } + + return hashcode; + } +} diff --git a/src/Collections/NativeArray.cs b/src/Collections/NativeArray.cs new file mode 100644 index 0000000..9661de8 --- /dev/null +++ b/src/Collections/NativeArray.cs @@ -0,0 +1,123 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace MoonTools.ECS.Collections; + +public unsafe class NativeArray : IDisposable where T : unmanaged +{ + private T* Elements; + public int Count { get; private set;} + private int Capacity; + private int ElementSize; + + public Span.Enumerator GetEnumerator() => new Span(Elements, Count).GetEnumerator(); + + private bool disposed; + + public NativeArray(int capacity = 16) + { + this.Capacity = capacity; + ElementSize = Unsafe.SizeOf(); + Elements = (T*) NativeMemory.Alloc((nuint) (capacity * ElementSize)); + Count = 0; + } + + public ref T this[int i] => ref Elements[i]; + + public void Append(T item) + { + if (Count >= Capacity) + { + Capacity *= 2; + Elements = (T*) NativeMemory.Realloc(Elements, (nuint) (Capacity * Unsafe.SizeOf())); + } + + Elements[Count] = item; + Count += 1; + } + + public void RemoveLastElement() + { + Count -= 1; + } + + public bool TryPop(out T element) + { + if (Count > 0) + { + element = Elements[Count - 1]; + Count -= 1; + return true; + } + + element = default; + return false; + } + + public void Clear() + { + Count = 0; + } + + private void ResizeTo(int size) + { + Capacity = size; + Elements = (T*) NativeMemory.Realloc((void*) Elements, (nuint) (ElementSize * Capacity)); + } + + // Fills gap by copying final element to the deleted index + public void Delete(int index) + { + if (Count > 1) + { + NativeMemory.Copy( + (void*) (Elements + ((Count - 1) * ElementSize)), + (void*) (Elements + (index * ElementSize)), + (nuint) ElementSize + ); + } + + Count -= 1; + } + + public void CopyTo(NativeArray other) + { + if (Count >= other.Capacity) + { + other.ResizeTo(Count); + } + + NativeMemory.Copy( + (void*) Elements, + (void*) other.Elements, + (nuint) (ElementSize * Count) + ); + + other.Count = Count; + } + + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + NativeMemory.Free(Elements); + Elements = 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/Collections/NativeArrayUntyped.cs b/src/Collections/NativeArrayUntyped.cs new file mode 100644 index 0000000..e66336d --- /dev/null +++ b/src/Collections/NativeArrayUntyped.cs @@ -0,0 +1,137 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace MoonTools.ECS.Collections; + +internal unsafe class NativeArray : IDisposable +{ + private nint Elements; + public int Count { get; private set;} + private int Capacity; + public readonly int ElementSize; + + private bool IsDisposed; + + public NativeArray(int elementSize) + { + Capacity = 16; + Count = 0; + ElementSize = elementSize; + + Elements = (nint) NativeMemory.Alloc((nuint) (ElementSize * Capacity)); + } + + public Span ToSpan() + { + return new Span((void*) Elements, Count); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T Get(int i) where T : unmanaged + { + return ref ((T*) Elements)[i]; + } + + public void Set(int i, in T element) where T : unmanaged + { + Unsafe.Write((void*) (Elements + ElementSize * i), element); + } + + private void Resize() + { + Capacity *= 2; + Elements = (nint) NativeMemory.Realloc((void*) Elements, (nuint) (ElementSize * Capacity)); + } + + private void ResizeTo(int capacity) + { + Capacity = capacity; + Elements = (nint) NativeMemory.Realloc((void*) Elements, (nuint) (ElementSize * Capacity)); + } + + // Fills gap by copying final element to the deleted index + public void Delete(int index) + { + if (Count > 1) + { + NativeMemory.Copy( + (void*) (Elements + ((Count - 1) * ElementSize)), + (void*) (Elements + (index * ElementSize)), + (nuint) ElementSize + ); + } + + Count -= 1; + } + + public void Append(T component) where T : unmanaged + { + if (Count >= Capacity) + { + Resize(); + } + + ((T*) Elements)[Count] = component; + Count += 1; + } + + public void CopyElementToEnd(int index, NativeArray other) + { + if (other.Count >= other.Capacity) + { + other.Resize(); + } + + NativeMemory.Copy( + (void*) (Elements + (index * ElementSize)), + (void*) (other.Elements + (other.Count * ElementSize)), + (nuint) ElementSize + ); + + other.Count += 1; + } + + public void CopyAllTo(NativeArray other) + { + if (Count >= other.Capacity) + { + other.ResizeTo(Count); + } + + NativeMemory.Copy( + (void*) Elements, + (void*) other.Elements, + (nuint) (ElementSize * Count) + ); + + other.Count = Count; + } + + public void Clear() + { + Count = 0; + } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + NativeMemory.Free((void*) Elements); + IsDisposed = 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/ComponentDepot.cs b/src/ComponentDepot.cs deleted file mode 100644 index cdd4d5f..0000000 --- a/src/ComponentDepot.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; - -namespace MoonTools.ECS -{ - internal class ComponentDepot - { - private TypeIndices ComponentTypeIndices; - - private ComponentStorage[] storages = new ComponentStorage[256]; - - public ComponentDepot(TypeIndices componentTypeIndices) - { - ComponentTypeIndices = componentTypeIndices; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void Register(int index) where TComponent : unmanaged - { - if (index >= storages.Length) - { - Array.Resize(ref storages, storages.Length * 2); - } - - storages[index] = new ComponentStorage(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ComponentStorage Lookup() where TComponent : unmanaged - { - var storageIndex = ComponentTypeIndices.GetIndex(); - // TODO: is there some way to avoid this null check? - if (storageIndex >= storages.Length || storages[storageIndex] == null) - { - Register(storageIndex); - } - return (ComponentStorage) storages[storageIndex]; - } - - public bool Some() where TComponent : unmanaged - { - return Lookup().Any(); - } - - public ref readonly TComponent Get(int entityID) where TComponent : unmanaged - { - return ref Lookup().Get(entityID); - } - - public ref readonly TComponent GetFirst() where TComponent : unmanaged - { - return ref Lookup().GetFirst(); - } - - public void Set(int entityID, in TComponent component) where TComponent : unmanaged - { - Lookup().Set(entityID, component); - } - - public Entity GetSingletonEntity() where TComponent : unmanaged - { - return Lookup().FirstEntity(); - } - - public ReadOnlySpan ReadComponents() where TComponent : unmanaged - { - return Lookup().AllComponents(); - } - - public void Remove(int entityID, int storageIndex) - { - storages[storageIndex].Remove(entityID); - } - - public void Remove(int entityID) where TComponent : unmanaged - { - Lookup().Remove(entityID); - } - - public void Clear() - { - for (var i = 0; i < storages.Length; i += 1) - { - if (storages[i] != null) - { - storages[i].Clear(); - } - } - } - - // these methods used to implement transfers and debugging - - internal unsafe void* UntypedGet(int entityID, int componentTypeIndex) - { - return storages[componentTypeIndex].UntypedGet(entityID); - } - - internal unsafe void Set(int entityID, int componentTypeIndex, void* component) - { - storages[componentTypeIndex].Set(entityID, component); - } - - public void CreateMissingStorages(ComponentDepot other) - { - while (other.ComponentTypeIndices.Count >= storages.Length) - { - Array.Resize(ref storages, storages.Length * 2); - } - - while (other.ComponentTypeIndices.Count >= other.storages.Length) - { - Array.Resize(ref other.storages, other.storages.Length * 2); - } - - for (var i = 0; i < other.ComponentTypeIndices.Count; i += 1) - { - if (storages[i] == null && other.storages[i] != null) - { - storages[i] = other.storages[i].CreateStorage(); - } - } - } - - // this method is used to iterate components of an entity, only for use with a debug inspector - -#if DEBUG - public object Debug_Get(int entityID, int componentTypeIndex) - { - return storages[componentTypeIndex].Debug_Get(entityID); - } - - public IEnumerable Debug_GetEntityIDs(int componentTypeIndex) - { - return storages[componentTypeIndex].Debug_GetEntityIDs(); - } -#endif - } -} diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs index bc71ef7..7368102 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -1,112 +1,86 @@ using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; +using MoonTools.ECS.Collections; namespace MoonTools.ECS { - internal abstract class ComponentStorage + internal unsafe class ComponentStorage : IDisposable { - internal abstract unsafe void Set(int entityID, void* component); - public abstract bool Remove(int entityID); - public abstract void Clear(); + internal readonly Dictionary EntityIDToStorageIndex = new Dictionary(16); + internal readonly NativeArray Components; + internal readonly NativeArray EntityIDs; - // 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() + public ComponentStorage(int elementSize) { - components = (TComponent*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf())); - entityIDs = (int*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf())); + Components = new NativeArray(elementSize); + EntityIDs = new NativeArray(); } public bool Any() { - return count > 0; + return Components.Count > 0; } - public ref readonly TComponent Get(int entityID) + public bool Has(Entity entity) { - return ref components[entityIDToStorageIndex[entityID]]; + return EntityIDToStorageIndex.ContainsKey(entity); } - internal override unsafe void* UntypedGet(int entityID) + public ref T Get(in Entity entity) where T : unmanaged { - return &components[entityIDToStorageIndex[entityID]]; + + return ref Components.Get(EntityIDToStorageIndex[entity]); } - public ref readonly TComponent GetFirst() + public ref T GetFirst() where T : unmanaged { #if DEBUG - if (count == 0) + if (Components.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); + return ref Components.Get(0); } // Returns true if the entity had this component. - public override bool Remove(int entityID) + public bool Set(in Entity entity, in T component) where T : unmanaged { - if (entityIDToStorageIndex.TryGetValue(entityID, out int storageIndex)) + if (EntityIDToStorageIndex.TryGetValue(entity, out var index)) { - entityIDToStorageIndex.Remove(entityID); + Components.Set(index, component); + return true; + } + else + { + EntityIDToStorageIndex[entity] = Components.Count; + EntityIDs.Append(entity); + Components.Append(component); + return false; + } + } - var lastElementIndex = count - 1; + // Returns true if the entity had this component. + public bool Remove(in Entity entity) + { + if (EntityIDToStorageIndex.TryGetValue(entity, out int index)) + { + var lastElementIndex = Components.Count - 1; + + var lastEntity = EntityIDs[lastElementIndex]; // 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; - } + Components.Delete(index); + EntityIDs.Delete(index); + EntityIDToStorageIndex.Remove(entity); - count -= 1; + // update the index if it changed + if (lastElementIndex != index) + { + EntityIDToStorageIndex[lastEntity] = index; + } return true; } @@ -114,62 +88,47 @@ namespace MoonTools.ECS return false; } - public override void Clear() + public void Clear() { - count = 0; - entityIDToStorageIndex.Clear(); - } - - public ReadOnlySpan AllComponents() - { - return new ReadOnlySpan(components, count); + Components.Clear(); + EntityIDs.Clear(); + EntityIDToStorageIndex.Clear(); } public Entity FirstEntity() { #if DEBUG - if (count == 0) + if (EntityIDs.Count == 0) { throw new IndexOutOfRangeException("Component storage is empty!"); } #endif - return new Entity(entityIDs[0]); - } - - public override ComponentStorage CreateStorage() - { - return new ComponentStorage(); + return EntityIDs[0]; } #if DEBUG - internal override object Debug_Get(int entityID) + internal IEnumerable Debug_GetEntities() { - return components[entityIDToStorageIndex[entityID]]; - } - - internal override IEnumerable Debug_GetEntityIDs() - { - return entityIDToStorageIndex.Keys; + return EntityIDToStorageIndex.Keys; } +#endif protected virtual void Dispose(bool disposing) { if (!disposed) { - NativeMemory.Free(components); - NativeMemory.Free(entityIDs); - components = null; - entityIDs = null; + Components.Dispose(); + EntityIDs.Dispose(); disposed = true; } } - ~ComponentStorage() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: false); - } + // ~ComponentStorage() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } public void Dispose() { @@ -177,6 +136,5 @@ namespace MoonTools.ECS Dispose(disposing: true); GC.SuppressFinalize(this); } -#endif } } diff --git a/src/DebugSystem.cs b/src/DebugSystem.cs index fcffd9e..240614a 100644 --- a/src/DebugSystem.cs +++ b/src/DebugSystem.cs @@ -1,63 +1,17 @@ // NOTE: these methods are very inefficient // this class should only be used in debugging contexts!! - #if DEBUG using System; using System.Collections.Generic; -namespace MoonTools.ECS +namespace MoonTools.ECS; + +public abstract class DebugSystem : System { - public abstract class DebugSystem : System - { - protected DebugSystem(World world) : base(world) - { - } + protected DebugSystem(World world) : base(world) { } - protected ComponentEnumerator Debug_GetAllComponents(Entity entity) - { - return new ComponentEnumerator(ComponentDepot, entity, EntityStorage.ComponentTypeIndices(entity.ID)); - } - - protected IEnumerable Debug_GetEntities(Type componentType) - { - foreach (var entityID in ComponentDepot.Debug_GetEntityIDs(ComponentTypeIndices.GetIndex(componentType))) - { - yield return new Entity(entityID); - } - } - - protected IEnumerable Debug_SearchComponentType(string typeString) - { - foreach (var type in ComponentTypeIndices.Types) - { - if (type.ToString().ToLower().Contains(typeString.ToLower())) - { - yield return type; - } - } - } - - public ref struct ComponentEnumerator - { - private ComponentDepot ComponentDepot; - private Entity Entity; - private ReverseSpanEnumerator ComponentTypeIndices; - - public ComponentEnumerator GetEnumerator() => this; - - internal ComponentEnumerator( - ComponentDepot componentDepot, - Entity entity, - Collections.IndexableSet componentTypeIndices - ) { - ComponentDepot = componentDepot; - Entity = entity; - ComponentTypeIndices = componentTypeIndices.GetEnumerator(); - } - - public bool MoveNext() => ComponentTypeIndices.MoveNext(); - public object Current => ComponentDepot.Debug_Get(Entity.ID, ComponentTypeIndices.Current); - } - } + protected World.ComponentTypeEnumerator Debug_GetAllComponentTypes(Entity entity) => World.Debug_GetAllComponentTypes(entity); + protected IEnumerable Debug_GetEntities(Type componentType) => World.Debug_GetEntities(componentType); + protected IEnumerable Debug_SearchComponentType(string typeString) => World.Debug_SearchComponentType(typeString); } #endif diff --git a/src/DynamicArray.cs b/src/DynamicArray.cs deleted file mode 100644 index 08400fc..0000000 --- a/src/DynamicArray.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace MoonTools.ECS.Collections -{ - public unsafe class NativeArray : IDisposable where T : unmanaged - { - private T* Array; - private int count; - private int capacity; - - public int Count => count; - - public ReverseSpanEnumerator GetEnumerator() => new ReverseSpanEnumerator(new Span(Array, Count)); - - private bool disposed; - - public NativeArray(int capacity = 16) - { - this.capacity = capacity; - Array = (T*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf())); - count = 0; - } - - public ref T this[int i] => ref Array[i]; - - public void Add(T item) - { - if (count >= capacity) - { - capacity *= 2; - Array = (T*) NativeMemory.Realloc(Array, (nuint) (capacity * Unsafe.SizeOf())); - } - - Array[count] = item; - count += 1; - } - - public void Clear() - { - 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/Entity.cs b/src/Entity.cs index e91da6e..f2d75d9 100644 --- a/src/Entity.cs +++ b/src/Entity.cs @@ -2,48 +2,13 @@ namespace MoonTools.ECS { - public struct Entity : IEquatable + public readonly record struct Entity { - public int ID { get; } + public uint ID { get; } - internal Entity(int id) + internal Entity(uint id) { ID = id; } - - public override bool Equals(object? obj) - { - return obj is Entity entity && Equals(entity); - } - - public override int GetHashCode() - { - return HashCode.Combine(ID); - } - - public bool Equals(Entity other) - { - return ID == other.ID; - } - - public static bool operator ==(Entity a, Entity b) - { - return a.Equals(b); - } - - public static bool operator !=(Entity a, Entity b) - { - return !a.Equals(b); - } - - public static implicit operator int(Entity e) - { - return e.ID; - } - - public static implicit operator Entity(int i) - { - return new Entity(i); - } } } diff --git a/src/EntityComponentReader.cs b/src/EntityComponentReader.cs index 88e34a6..0012c8f 100644 --- a/src/EntityComponentReader.cs +++ b/src/EntityComponentReader.cs @@ -1,127 +1,36 @@ -using System; -using System.Collections.Generic; +namespace MoonTools.ECS; -namespace MoonTools.ECS +public abstract class EntityComponentReader { - public abstract class EntityComponentReader + protected readonly World World; + public FilterBuilder FilterBuilder => World.FilterBuilder; + + protected EntityComponentReader(World world) { - internal readonly World World; - internal EntityStorage EntityStorage => World.EntityStorage; - internal ComponentDepot ComponentDepot => World.ComponentDepot; - internal RelationDepot RelationDepot => World.RelationDepot; - protected FilterBuilder FilterBuilder => new FilterBuilder(FilterStorage, ComponentTypeIndices); - internal FilterStorage FilterStorage => World.FilterStorage; - internal TypeIndices ComponentTypeIndices => World.ComponentTypeIndices; - internal TypeIndices RelationTypeIndices => World.RelationTypeIndices; - - public EntityComponentReader(World world) - { - World = world; - } - - protected string GetTag(in Entity entity) - { - return World.GetTag(entity); - } - - protected ReadOnlySpan ReadComponents() where TComponent : unmanaged - { - return ComponentDepot.ReadComponents(); - } - - protected bool Has(in Entity entity) where TComponent : unmanaged - { - var storageIndex = ComponentTypeIndices.GetIndex(); - return EntityStorage.HasComponent(entity.ID, storageIndex); - } - - protected bool Some() where TComponent : unmanaged - { - return ComponentDepot.Some(); - } - - protected ref readonly TComponent Get(in Entity entity) where TComponent : unmanaged - { - return ref ComponentDepot.Get(entity.ID); - } - - protected ref readonly TComponent GetSingleton() where TComponent : unmanaged - { - return ref ComponentDepot.GetFirst(); - } - - protected Entity GetSingletonEntity() where TComponent : unmanaged - { - return ComponentDepot.GetSingletonEntity(); - } - - protected ReverseSpanEnumerator<(Entity, Entity)> Relations() where TRelationKind : unmanaged - { - return RelationDepot.Relations(); - } - - protected bool Related(in Entity a, in Entity b) where TRelationKind : unmanaged - { - return RelationDepot.Related(a.ID, b.ID); - } - - protected TRelationKind GetRelationData(in Entity a, in Entity b) where TRelationKind : unmanaged - { - return RelationDepot.Get(a, b); - } - - // relations go A->B, so given A, will give all entities in outgoing relations of this kind. - protected ReverseSpanEnumerator OutRelations(in Entity entity) where TRelationKind : unmanaged - { - return RelationDepot.OutRelations(entity.ID); - } - - protected Entity OutRelationSingleton(in Entity entity) where TRelationKind : unmanaged - { - return RelationDepot.OutRelationSingleton(entity.ID); - } - - // NOTE: this WILL crash if at least n + 1 relations do not exist! - protected Entity NthOutRelation(in Entity entity, int n) where TRelationKind : unmanaged - { - return RelationDepot.NthOutRelation(entity.ID, n); - } - - protected bool HasOutRelation(in Entity entity) where TRelationKind : unmanaged - { - return RelationDepot.HasOutRelation(entity.ID); - } - - protected int OutRelationCount(in Entity entity) where TRelationKind : unmanaged - { - return RelationDepot.OutRelationCount(entity.ID); - } - - // Relations go A->B, so given B, will give all entities in incoming A relations of this kind. - protected ReverseSpanEnumerator InRelations(in Entity entity) where TRelationKind : unmanaged - { - return RelationDepot.InRelations(entity.ID); - } - - protected Entity InRelationSingleton(in Entity entity) where TRelationKind : unmanaged - { - return RelationDepot.InRelationSingleton(entity.ID); - } - - // NOTE: this WILL crash if at least n + 1 relations do not exist! - protected Entity NthInRelation(in Entity entity, int n) where TRelationKind : unmanaged - { - return RelationDepot.NthInRelation(entity.ID, n); - } - - protected bool HasInRelation(in Entity entity) where TRelationKind : unmanaged - { - return RelationDepot.HasInRelation(entity.ID); - } - - protected int InRelationCount(in Entity entity) where TRelationKind : unmanaged - { - return RelationDepot.InRelationCount(entity.ID); - } + World = world; } + + protected string GetTag(in Entity entity) => World.GetTag(entity); + + protected bool Has(in Entity Entity) where T : unmanaged => World.Has(Entity); + protected bool Some() where T : unmanaged => World.Some(); + protected ref T Get(in Entity Entity) where T : unmanaged => ref World.Get(Entity); + protected ref T GetSingleton() where T : unmanaged => ref World.GetSingleton(); + protected Entity GetSingletonEntity() where T : unmanaged => World.GetSingletonEntity(); + + protected ReverseSpanEnumerator<(Entity, Entity)> Relations() where T : unmanaged => World.Relations(); + protected bool Related(in Entity entityA, in Entity entityB) where T : unmanaged => World.Related(entityA, entityB); + protected T GetRelationData(in Entity entityA, in Entity entityB) where T : unmanaged => World.GetRelationData(entityA, entityB); + + protected ReverseSpanEnumerator OutRelations(in Entity entity) where T : unmanaged => World.OutRelations(entity); + protected Entity OutRelationSingleton(in Entity entity) where T : unmanaged => World.OutRelationSingleton(entity); + protected bool HasOutRelation(in Entity entity) where T : unmanaged => World.HasOutRelation(entity); + protected int OutRelationCount(in Entity entity) where T : unmanaged => World.OutRelationCount(entity); + protected Entity NthOutRelation(in Entity entity, int n) where T : unmanaged => World.NthOutRelation(entity, n); + + protected ReverseSpanEnumerator InRelations(in Entity entity) where T : unmanaged => World.InRelations(entity); + protected Entity InRelationSingleton(in Entity entity) where T : unmanaged => World.InRelationSingleton(entity); + protected bool HasInRelation(in Entity entity) where T : unmanaged => World.HasInRelation(entity); + protected int InRelationCount(in Entity entity) where T : unmanaged => World.InRelationCount(entity); + protected Entity NthInRelation(in Entity entity, int n) where T : unmanaged => World.NthInRelation(entity, n); } diff --git a/src/EntityStorage.cs b/src/EntityStorage.cs deleted file mode 100644 index 3ff05de..0000000 --- a/src/EntityStorage.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System.Collections.Generic; -using MoonTools.ECS.Collections; - -namespace MoonTools.ECS -{ - internal class EntityStorage - { - private int nextID = 0; - // FIXME: why is this duplicated? - private readonly Stack availableIDs = new Stack(); - // FIXME: this is only needed in debug mode - private readonly HashSet availableIDHash = new HashSet(); - - private Dictionary> EntityToComponentTypeIndices = new Dictionary>(); - private Dictionary> EntityToRelationTypeIndices = new Dictionary>(); - - public int Count => nextID - availableIDs.Count; - - public Dictionary Tags = new Dictionary(); - - public Entity Create(string tag) - { - var entity = new Entity(NextID()); - - if (!EntityToComponentTypeIndices.ContainsKey(entity.ID)) - { - EntityToComponentTypeIndices.Add(entity.ID, new IndexableSet()); - } - - if (!EntityToRelationTypeIndices.ContainsKey(entity.ID)) - { - EntityToRelationTypeIndices.Add(entity.ID, new IndexableSet()); - } - - Tags[entity.ID] = tag; - - return entity; - } - - public bool Exists(in Entity entity) - { - return Taken(entity.ID); - } - - public void Tag(in Entity entity, string tag) - { - Tags[entity.ID] = tag; - } - - public void Destroy(in Entity entity) - { - EntityToComponentTypeIndices[entity.ID].Clear(); - EntityToRelationTypeIndices[entity.ID].Clear(); - Tags.Remove(entity.ID); - Release(entity.ID); - } - - // Returns true if the component is new. - public bool SetComponent(int entityID, int componentTypeIndex) - { - return EntityToComponentTypeIndices[entityID].Add(componentTypeIndex); - } - - public bool HasComponent(int entityID, int componentTypeIndex) - { - return EntityToComponentTypeIndices[entityID].Contains(componentTypeIndex); - } - - // Returns true if the component existed. - public bool RemoveComponent(int entityID, int componentTypeIndex) - { - return EntityToComponentTypeIndices[entityID].Remove(componentTypeIndex); - } - - public void AddRelationKind(int entityID, int relationIndex) - { - EntityToRelationTypeIndices[entityID].Add(relationIndex); - } - - public void RemoveRelation(int entityId, int relationIndex) - { - EntityToRelationTypeIndices[entityId].Remove(relationIndex); - } - - public string Tag(int entityID) - { - return Tags[entityID]; - } - - public IndexableSet ComponentTypeIndices(int entityID) - { - return EntityToComponentTypeIndices[entityID]; - } - - public IndexableSet RelationTypeIndices(int entityID) - { - return EntityToRelationTypeIndices[entityID]; - } - - public void Clear() - { - nextID = 0; - foreach (var componentSet in EntityToComponentTypeIndices.Values) - { - componentSet.Clear(); - } - foreach (var relationSet in EntityToRelationTypeIndices.Values) - { - relationSet.Clear(); - } - availableIDs.Clear(); - availableIDHash.Clear(); - } - - private int NextID() - { - if (availableIDs.Count > 0) - { - var id = availableIDs.Pop(); - availableIDHash.Remove(id); - return id; - } - else - { - var id = nextID; - nextID += 1; - return id; - } - } - - private bool Taken(int id) - { - return !availableIDHash.Contains(id) && id < nextID; - } - - private void Release(int id) - { - availableIDs.Push(id); - availableIDHash.Add(id); - } - } -} diff --git a/src/Filter.cs b/src/Filter.cs index 94f5ee9..9b54c75 100644 --- a/src/Filter.cs +++ b/src/Filter.cs @@ -1,38 +1,204 @@ using System; using System.Collections.Generic; -using MoonTools.ECS.Collections; -namespace MoonTools.ECS +namespace MoonTools.ECS; + +// TODO: do we want to get fancy with queries beyond Include and Exclude? +public class Filter { - public class Filter + private Archetype EmptyArchetype; + private HashSet Included; + private HashSet Excluded; + + public EntityEnumerator Entities => new EntityEnumerator(this); + internal ArchetypeEnumerator Archetypes => new ArchetypeEnumerator(this); + public RandomEntityEnumerator EntitiesInRandomOrder => new RandomEntityEnumerator(this); + + public bool Empty { - internal FilterSignature Signature; - private FilterStorage FilterStorage; - - internal Filter(FilterStorage filterStorage, IndexableSet included, IndexableSet excluded) + get { - FilterStorage = filterStorage; - Signature = new FilterSignature(included, excluded); - } + var empty = true; - public ReverseSpanEnumerator Entities => FilterStorage.FilterEntities(Signature); - public RandomEntityEnumerator EntitiesInRandomOrder => FilterStorage.FilterEntitiesRandom(Signature); - public Entity RandomEntity => FilterStorage.FilterRandomEntity(Signature); + foreach (var archetype in Archetypes) + { + if (archetype.Count > 0) + { + return false; + } + } - public int Count => FilterStorage.FilterCount(Signature); - public bool Empty => Count == 0; - - // WARNING: this WILL crash if the index is out of range! - public Entity NthEntity(int index) => FilterStorage.FilterNthEntity(Signature, index); - - public void RegisterAddCallback(Action callback) - { - FilterStorage.RegisterAddCallback(Signature, callback); - } - - public void RegisterRemoveCallback(Action callback) - { - FilterStorage.RegisterRemoveCallback(Signature, callback); + return empty; } } + + public int Count + { + get + { + var count = 0; + + foreach (var archetype in Archetypes) + { + count += archetype.Count; + } + + return count; + } + } + + public Entity RandomEntity + { + get + { + var randomIndex = RandomManager.Next(Count); + return NthEntity(randomIndex); + } + } + + // WARNING: this WILL crash if the index is out of range! + public Entity NthEntity(int index) + { + var count = 0; + + foreach (var archetype in Archetypes) + { + count += archetype.Count; + if (index < count) + { + return archetype.Entities[index]; + } + + index -= count; + } + + throw new InvalidOperationException("Filter index out of range!"); + } + + public void DestroyAllEntities() + { + foreach (var archetype in Archetypes) + { + archetype.ClearAll(); + } + } + + internal Filter(Archetype emptyArchetype, HashSet included, HashSet excluded) + { + EmptyArchetype = emptyArchetype; + Included = included; + Excluded = excluded; + } + + internal ref struct ArchetypeEnumerator + { + private Archetype CurrentArchetype; + + // TODO: pool these + private Queue ArchetypeQueue = new Queue(); + private Queue ArchetypeSearchQueue = new Queue(); + private HashSet Explored = new HashSet(); + + public ArchetypeEnumerator GetEnumerator() => this; + + public ArchetypeEnumerator(Filter filter) + { + var empty = filter.EmptyArchetype; + ArchetypeSearchQueue.Enqueue(empty); + + // TODO: can we cache this search effectively? + while (ArchetypeSearchQueue.TryDequeue(out var current)) + { + // exclude the empty archetype + var satisfiesFilter = filter.Included.Count != 0; + + foreach (var componentId in filter.Included) + { + if (!current.Signature.Contains(componentId)) + { + satisfiesFilter = false; + } + } + + foreach (var componentId in filter.Excluded) + { + if (current.Signature.Contains(componentId)) + { + satisfiesFilter = false; + } + } + + if (satisfiesFilter) + { + ArchetypeQueue.Enqueue(current); + } + + // breadth-first search + // ignore excluded component edges + foreach (var (componentId, edge) in current.Edges) + { + if (!Explored.Contains(edge.Add) && !filter.Excluded.Contains(componentId)) + { + Explored.Add(edge.Add); + ArchetypeSearchQueue.Enqueue(edge.Add); + } + } + } + } + + public bool MoveNext() + { + return ArchetypeQueue.TryDequeue(out CurrentArchetype!); + } + + public Archetype Current => CurrentArchetype; + } + + public ref struct EntityEnumerator + { + private Entity CurrentEntity; + + public EntityEnumerator GetEnumerator() => this; + + // TODO: pool this + Queue EntityQueue = new Queue(); + + internal EntityEnumerator(Filter filter) + { + var archetypeEnumerator = new ArchetypeEnumerator(filter); + + foreach (var archetype in archetypeEnumerator) + { + foreach (var entity in archetype.Entities) + { + EntityQueue.Enqueue(entity); + } + } + } + + public bool MoveNext() + { + return EntityQueue.TryDequeue(out CurrentEntity); + } + + public Entity Current => CurrentEntity; + } + + public ref struct RandomEntityEnumerator + { + private Filter Filter; + private LinearCongruentialEnumerator LinearCongruentialEnumerator; + + public RandomEntityEnumerator GetEnumerator() => this; + + internal RandomEntityEnumerator(Filter filter) + { + Filter = filter; + LinearCongruentialEnumerator = + RandomManager.LinearCongruentialSequence(filter.Count); + } + + public bool MoveNext() => LinearCongruentialEnumerator.MoveNext(); + public Entity Current => Filter.NthEntity(LinearCongruentialEnumerator.Current); + } } diff --git a/src/FilterBuilder.cs b/src/FilterBuilder.cs index 8a8a6d1..2213a0a 100644 --- a/src/FilterBuilder.cs +++ b/src/FilterBuilder.cs @@ -1,45 +1,42 @@ -using MoonTools.ECS.Collections; +using System.Collections.Generic; namespace MoonTools.ECS { public struct FilterBuilder { - private TypeIndices ComponentTypeIndices; - private FilterStorage FilterStorage; - private IndexableSet Included; - private IndexableSet Excluded; + World World; + HashSet Included; + HashSet Excluded; - internal FilterBuilder(FilterStorage filterStorage, TypeIndices componentTypeIndices) + internal FilterBuilder(World world) { - FilterStorage = filterStorage; - ComponentTypeIndices = componentTypeIndices; - Included = new IndexableSet(); - Excluded = new IndexableSet(); + World = world; + Included = new HashSet(); + Excluded = new HashSet(); } - private FilterBuilder(FilterStorage filterStorage, TypeIndices componentTypeIndices, IndexableSet included, IndexableSet excluded) + private FilterBuilder(World world, HashSet included, HashSet excluded) { - FilterStorage = filterStorage; - ComponentTypeIndices = componentTypeIndices; + World = world; Included = included; Excluded = excluded; } - public FilterBuilder Include() where TComponent : unmanaged + public FilterBuilder Include() where T : unmanaged { - Included.Add(ComponentTypeIndices.GetIndex()); - return new FilterBuilder(FilterStorage, ComponentTypeIndices, Included, Excluded); + Included.Add(World.GetTypeId()); + return new FilterBuilder(World, Included, Excluded); } - public FilterBuilder Exclude() where TComponent : unmanaged + public FilterBuilder Exclude() where T : unmanaged { - Excluded.Add(ComponentTypeIndices.GetIndex()); - return new FilterBuilder(FilterStorage, ComponentTypeIndices, Included, Excluded); + Excluded.Add(World.GetTypeId()); + return new FilterBuilder(World, Included, Excluded); } public Filter Build() { - return FilterStorage.CreateFilter(Included, Excluded); + return new Filter(World.EmptyArchetype, Included, Excluded); } } } diff --git a/src/FilterStorage.cs b/src/FilterStorage.cs deleted file mode 100644 index 7eb11f9..0000000 --- a/src/FilterStorage.cs +++ /dev/null @@ -1,273 +0,0 @@ -using System; -using System.Collections.Generic; -using MoonTools.ECS.Collections; - -namespace MoonTools.ECS -{ - internal class FilterStorage - { - private EntityStorage EntityStorage; - private TypeIndices ComponentTypeIndices; - private Dictionary> filterSignatureToEntityIDs = new Dictionary>(); - private Dictionary> typeToFilterSignatures = new Dictionary>(); - - private Dictionary> addCallbacks = new Dictionary>(); - private Dictionary> removeCallbacks = new Dictionary>(); - - public FilterStorage(EntityStorage entityStorage, TypeIndices componentTypeIndices) - { - EntityStorage = entityStorage; - ComponentTypeIndices = componentTypeIndices; - } - - private void CopyTypeCache(Dictionary> typeCache) - { - foreach (var type in typeCache.Keys) - { - if (!typeToFilterSignatures.ContainsKey(type)) - { - typeToFilterSignatures.Add(type, new List()); - - foreach (var signature in typeCache[type]) - { - typeToFilterSignatures[type].Add(signature); - } - } - } - } - - public void CreateMissingStorages(FilterStorage other) - { - foreach (var filterSignature in other.filterSignatureToEntityIDs.Keys) - { - if (!filterSignatureToEntityIDs.ContainsKey(filterSignature)) - { - filterSignatureToEntityIDs.Add(filterSignature, new IndexableSet()); - } - } - - CopyTypeCache(other.typeToFilterSignatures); - } - - public Filter CreateFilter(IndexableSet included, IndexableSet excluded) - { - var filterSignature = new FilterSignature(included, excluded); - if (!filterSignatureToEntityIDs.ContainsKey(filterSignature)) - { - filterSignatureToEntityIDs.Add(filterSignature, new IndexableSet()); - - foreach (var type in included) - { - if (!typeToFilterSignatures.ContainsKey(type)) - { - typeToFilterSignatures.Add(type, new List()); - } - - typeToFilterSignatures[type].Add(filterSignature); - } - - foreach (var type in excluded) - { - if (!typeToFilterSignatures.ContainsKey(type)) - { - typeToFilterSignatures.Add(type, new List()); - } - - typeToFilterSignatures[type].Add(filterSignature); - } - } - return new Filter(this, included, excluded); - } - - public ReverseSpanEnumerator FilterEntities(FilterSignature filterSignature) - { - return filterSignatureToEntityIDs[filterSignature].GetEnumerator(); - } - - public RandomEntityEnumerator FilterEntitiesRandom(FilterSignature filterSignature) - { - return new RandomEntityEnumerator( - this, - filterSignature, - RandomManager.LinearCongruentialSequence(FilterCount(filterSignature))); - } - - public Entity FilterNthEntity(FilterSignature filterSignature, int index) - { - return new Entity(filterSignatureToEntityIDs[filterSignature][index]); - } - - public Entity FilterRandomEntity(FilterSignature filterSignature) - { - var randomIndex = RandomManager.Next(FilterCount(filterSignature)); - return new Entity(filterSignatureToEntityIDs[filterSignature][randomIndex]); - } - - public int FilterCount(FilterSignature filterSignature) - { - return filterSignatureToEntityIDs[filterSignature].Count; - } - - public void Check(int entityID, int componentTypeIndex) - { - if (typeToFilterSignatures.TryGetValue(componentTypeIndex, out var filterSignatures)) - { - foreach (var filterSignature in filterSignatures) - { - CheckFilter(entityID, filterSignature); - } - } - } - - public void Check(int entityID) where TComponent : unmanaged - { - Check(entityID, ComponentTypeIndices.GetIndex()); - } - - public bool CheckSatisfied(int entityID, FilterSignature filterSignature) - { - foreach (var type in filterSignature.Included) - { - if (!EntityStorage.HasComponent(entityID, type)) - { - return false; - } - } - - foreach (var type in filterSignature.Excluded) - { - if (EntityStorage.HasComponent(entityID, type)) - { - return false; - } - } - - return true; - } - - private void CheckFilter(int entityID, FilterSignature filterSignature) - { - foreach (var type in filterSignature.Included) - { - if (!EntityStorage.HasComponent(entityID, type)) - { - if (filterSignatureToEntityIDs[filterSignature].Remove(entityID)) - { - if (removeCallbacks.TryGetValue(filterSignature, out var removeCallback)) - { - removeCallback(entityID); - } - } - return; - } - } - - foreach (var type in filterSignature.Excluded) - { - if (EntityStorage.HasComponent(entityID, type)) - { - if (filterSignatureToEntityIDs[filterSignature].Remove(entityID)) - { - if (removeCallbacks.TryGetValue(filterSignature, out var removeCallback)) - { - removeCallback(entityID); - } - } - return; - } - } - - if (filterSignatureToEntityIDs[filterSignature].Add(entityID)) - { - if (addCallbacks.TryGetValue(filterSignature, out var addCallback)) - { - addCallback(entityID); - } - } - } - - public void RemoveEntity(int entityID, int componentTypeIndex) - { - if (typeToFilterSignatures.TryGetValue(componentTypeIndex, out var filterSignatures)) - { - foreach (var filterSignature in filterSignatures) - { - if (filterSignatureToEntityIDs[filterSignature].Remove(entityID)) - { - if (removeCallbacks.TryGetValue(filterSignature, out var removeCallback)) - { - removeCallback(entityID); - } - } - } - } - } - - // Used by TransferEntity - public void AddEntity(FilterSignature signature, int entityID) - { - filterSignatureToEntityIDs[signature].Add(entityID); - } - - public void TransferStorage(Dictionary worldToTransferID, FilterStorage other) - { - foreach (var (filterSignature, entityIDs) in filterSignatureToEntityIDs) - { - foreach (var entity in entityIDs) - { - if (worldToTransferID.ContainsKey(entity)) - { - var otherEntityID = worldToTransferID[entity]; - other.AddEntity(filterSignature, otherEntityID); - - if (other.addCallbacks.TryGetValue(filterSignature, out var addCallback)) - { - addCallback(otherEntityID); - } - } - } - } - } - - // used by World.Clear, ignores callbacks - public void Clear() - { - foreach (var (filterSignature, entityIDs) in filterSignatureToEntityIDs) - { - entityIDs.Clear(); - } - } - - public void RegisterAddCallback(FilterSignature filterSignature, Action callback) - { - addCallbacks.Add(filterSignature, callback); - } - - public void RegisterRemoveCallback(FilterSignature filterSignature, Action callback) - { - removeCallbacks.Add(filterSignature, callback); - } - } - - public ref struct RandomEntityEnumerator - { - public RandomEntityEnumerator GetEnumerator() => this; - - private FilterStorage FilterStorage; - private FilterSignature FilterSignature; - private LinearCongruentialEnumerator LinearCongruentialEnumerator; - - internal RandomEntityEnumerator( - FilterStorage filterStorage, - FilterSignature filterSignature, - LinearCongruentialEnumerator linearCongruentialEnumerator) - { - FilterStorage = filterStorage; - FilterSignature = filterSignature; - LinearCongruentialEnumerator = linearCongruentialEnumerator; - } - - public bool MoveNext() => LinearCongruentialEnumerator.MoveNext(); - public Entity Current => FilterStorage.FilterNthEntity(FilterSignature, LinearCongruentialEnumerator.Current); - } -} diff --git a/src/IdAssigner.cs b/src/IdAssigner.cs new file mode 100644 index 0000000..4874fcc --- /dev/null +++ b/src/IdAssigner.cs @@ -0,0 +1,31 @@ +using MoonTools.ECS.Collections; + +namespace MoonTools.ECS; + +internal class IdAssigner +{ + uint Next; + NativeArray AvailableIds = new NativeArray(); + + public uint Assign() + { + if (!AvailableIds.TryPop(out var id)) + { + id = Next; + Next += 1; + } + + return id; + } + + public void Unassign(uint id) + { + AvailableIds.Append(id); + } + + public void CopyTo(IdAssigner other) + { + AvailableIds.CopyTo(other.AvailableIds); + other.Next = Next; + } +} diff --git a/src/IndexableSet.cs b/src/IndexableSet.cs index 1956756..d49c597 100644 --- a/src/IndexableSet.cs +++ b/src/IndexableSet.cs @@ -14,6 +14,7 @@ namespace MoonTools.ECS.Collections private bool disposed; public int Count => count; + public Span AsSpan() => new Span(array, count); public ReverseSpanEnumerator GetEnumerator() => new ReverseSpanEnumerator(new Span(array, count)); public IndexableSet(int capacity = 32) diff --git a/src/Manipulator.cs b/src/Manipulator.cs index 22a6780..2e7da9a 100644 --- a/src/Manipulator.cs +++ b/src/Manipulator.cs @@ -10,6 +10,7 @@ namespace MoonTools.ECS protected void Tag(Entity entity, string tag) => World.Tag(entity, tag); protected void Set(in Entity entity, in TComponent component) where TComponent : unmanaged => World.Set(entity, component); protected void Remove(in Entity entity) where TComponent : unmanaged => World.Remove(entity); + protected void Relate(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged => World.Relate(entityA, entityB, relationData); protected void Unrelate(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged => World.Unrelate(entityA, entityB); protected void UnrelateAll(in Entity entity) where TRelationKind : unmanaged => World.UnrelateAll(entity); diff --git a/src/MessageDepot.cs b/src/MessageDepot.cs deleted file mode 100644 index 63b9e2d..0000000 --- a/src/MessageDepot.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MoonTools.ECS -{ - internal class MessageDepot - { - private Dictionary storages = new Dictionary(); - - private MessageStorage Lookup() where TMessage : unmanaged - { - if (!storages.ContainsKey(typeof(TMessage))) - { - storages.Add(typeof(TMessage), new MessageStorage()); - } - - return storages[typeof(TMessage)] as MessageStorage; - } - - public void Add(in TMessage message) where TMessage : unmanaged - { - Lookup().Add(message); - } - - public void Add(int entityID, in TMessage message) where TMessage : unmanaged - { - Lookup().Add(entityID, message); - } - - public bool Some() where TMessage : unmanaged - { - return Lookup().Some(); - } - - public ReadOnlySpan All() where TMessage : unmanaged - { - return Lookup().All(); - } - - public TMessage First() where TMessage : unmanaged - { - return Lookup().First(); - } - - public ReverseSpanEnumerator WithEntity(int entityID) where TMessage : unmanaged - { - return Lookup().WithEntity(entityID); - } - - public ref readonly TMessage FirstWithEntity(int entityID) where TMessage : unmanaged - { - return ref Lookup().FirstWithEntity(entityID); - } - - public bool SomeWithEntity(int entityID) where TMessage : unmanaged - { - return Lookup().SomeWithEntity(entityID); - } - - public void Clear() - { - foreach (var storage in storages.Values) - { - storage.Clear(); - } - } - } -} diff --git a/src/MessageStorage.cs b/src/MessageStorage.cs index c5091a6..324547a 100644 --- a/src/MessageStorage.cs +++ b/src/MessageStorage.cs @@ -1,131 +1,63 @@ using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using MoonTools.ECS.Collections; -namespace MoonTools.ECS +namespace MoonTools.ECS; + +public class MessageStorage : IDisposable { - internal abstract class MessageStorage + private NativeArray Messages; + + private bool IsDisposed; + + public MessageStorage(int elementSize) { - public abstract void Clear(); + Messages = new NativeArray(elementSize); } - internal unsafe class MessageStorage : MessageStorage, IDisposable where TMessage : unmanaged + public void Add(in T message) where T : unmanaged { - private int count = 0; - private int capacity = 128; - private TMessage* messages; - // duplicating storage here for fast iteration - private Dictionary> entityToMessages = new Dictionary>(); - private bool disposed; + Messages.Append(message); + } - public MessageStorage() + public bool Some() + { + return Messages.Count > 0; + } + + public ReadOnlySpan All() where T : unmanaged + { + return Messages.ToSpan(); + } + + public T First() where T : unmanaged + { + return Messages.Get(0); + } + + public void Clear() + { + Messages.Clear(); + } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) { - messages = (TMessage*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf())); + Messages.Dispose(); + IsDisposed = true; } + } - public void Add(in TMessage message) - { - if (count == capacity) - { - capacity *= 2; - messages = (TMessage*) NativeMemory.Realloc(messages, (nuint) (capacity * Unsafe.SizeOf())); - } + // ~MessageStorage() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } - messages[count] = message; - count += 1; - } - - public void Add(int entityID, in TMessage message) - { - if (!entityToMessages.ContainsKey(entityID)) - { - entityToMessages.Add(entityID, new NativeArray()); - } - entityToMessages[entityID].Add(message); - - Add(message); - } - - public bool Some() - { - return count > 0; - } - - public ReadOnlySpan All() - { - return new ReadOnlySpan(messages, count); - } - - public TMessage First() - { - return messages[0]; - } - - public ReverseSpanEnumerator WithEntity(int entityID) - { - if (entityToMessages.TryGetValue(entityID, out var messages)) - { - return messages.GetEnumerator(); - } - else - { - return ReverseSpanEnumerator.Empty; - } - } - - public ref readonly TMessage FirstWithEntity(int entityID) - { - return ref entityToMessages[entityID][0]; - } - - public bool SomeWithEntity(int entityID) - { - return entityToMessages.ContainsKey(entityID) && entityToMessages[entityID].Count > 0; - } - - public override void Clear() - { - count = 0; - foreach (var set in entityToMessages.Values) - { - 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); - } + 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/RelationDepot.cs b/src/RelationDepot.cs deleted file mode 100644 index 7963a6c..0000000 --- a/src/RelationDepot.cs +++ /dev/null @@ -1,193 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; - -namespace MoonTools.ECS -{ - internal class RelationDepot - { - private EntityStorage EntityStorage; - private TypeIndices RelationTypeIndices; - private RelationStorage[] storages = new RelationStorage[256]; - - public RelationDepot(EntityStorage entityStorage, TypeIndices relationTypeIndices) - { - EntityStorage = entityStorage; - RelationTypeIndices = relationTypeIndices; - } - - private void Register(int index) where TRelationKind : unmanaged - { - if (index >= storages.Length) - { - Array.Resize(ref storages, storages.Length * 2); - } - - storages[index] = new RelationStorage(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private RelationStorage Lookup() where TRelationKind : unmanaged - { - var storageIndex = RelationTypeIndices.GetIndex(); - // TODO: is there some way to avoid this null check? - if (storages[storageIndex] == null) - { - Register(storageIndex); - } - return (RelationStorage) storages[storageIndex]; - } - - public void Set(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged - { - Lookup().Set(entityA, entityB, relationData); - } - - public TRelationKind Get(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged - { - return Lookup().Get(entityA, entityB); - } - - public (bool, bool) Remove(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged - { - return Lookup().Remove(entityA, entityB); - } - - public void UnrelateAll(int entityID) where TRelationKind : unmanaged - { - Lookup().UnrelateAll(entityID); - } - - public ReverseSpanEnumerator<(Entity, Entity)> Relations() where TRelationKind : unmanaged - { - return Lookup().All(); - } - - public bool Related(int idA, int idB) where TRelationKind : unmanaged - { - return Lookup().Has((idA, idB)); - } - - public ReverseSpanEnumerator OutRelations(int entityID) where TRelationKind : unmanaged - { - return Lookup().OutRelations(entityID); - } - - public Entity OutRelationSingleton(int entityID) where TRelationKind : unmanaged - { - return Lookup().OutFirst(entityID); - } - - public Entity NthOutRelation(int entityID, int n) where TRelationKind : unmanaged - { - return Lookup().OutNth(entityID, n); - } - - public int OutRelationCount(int entityID) where TRelationKind : unmanaged - { - return Lookup().OutRelationCount(entityID); - } - - public bool HasOutRelation(int entityID) where TRelationKind : unmanaged - { - return Lookup().HasOutRelation(entityID); - } - - public ReverseSpanEnumerator InRelations(int entityID) where TRelationKind : unmanaged - { - return Lookup().InRelations(entityID); - } - - public Entity NthInRelation(int entityID, int n) where TRelationKind : unmanaged - { - return Lookup().InNth(entityID, n); - } - - public Entity InRelationSingleton(int entityID) where TRelationKind : unmanaged - { - return Lookup().InFirst(entityID); - } - - public bool HasInRelation(int entityID) where TRelationKind : unmanaged - { - return Lookup().HasInRelation(entityID); - } - - public int InRelationCount(int entityID) where TRelationKind : unmanaged - { - return Lookup().InRelationCount(entityID); - } - - // untyped methods used for destroying and snapshots - - public unsafe void Set(int entityA, int entityB, int relationTypeIndex, void* relationData) - { - storages[relationTypeIndex].Set(entityA, entityB, relationData); - } - - public void UnrelateAll(int entityID, int relationTypeIndex) - { - storages[relationTypeIndex].UnrelateAll(entityID); - } - - public void Clear() - { - for (var i = 0; i < storages.Length; i += 1) - { - if (storages[i] != null) - { - storages[i].Clear(); - } - } - } - - public void CreateMissingStorages(RelationDepot other) - { - while (other.RelationTypeIndices.Count >= storages.Length) - { - Array.Resize(ref storages, storages.Length * 2); - } - - while (other.RelationTypeIndices.Count >= other.storages.Length) - { - Array.Resize(ref other.storages, other.storages.Length * 2); - } - - for (var i = 0; i < other.RelationTypeIndices.Count; i += 1) - { - if (storages[i] == null && other.storages[i] != null) - { - storages[i] = other.storages[i].CreateStorage(); - } - } - } - - public unsafe void TransferStorage(Dictionary worldToTransferID, RelationDepot other) - { - for (var i = 0; i < storages.Length; i += 1) - { - if (storages[i] != null) - { - foreach (var (a, b) in storages[i].All()) - { - if (worldToTransferID.TryGetValue(a, out var otherA)) - { - if (worldToTransferID.TryGetValue(b, out var otherB)) - { - var storageIndex = storages[i].GetStorageIndex(a, b); - var relationData = storages[i].Get(storageIndex); - other.Set(otherA, otherB, i, relationData); - other.EntityStorage.AddRelationKind(otherA, i); - other.EntityStorage.AddRelationKind(otherB, i); - } - else - { - throw new InvalidOperationException($"Missing transfer entity! {EntityStorage.Tag(a.ID)} related to {EntityStorage.Tag(b.ID)}"); - } - } - } - } - } - } - } -} diff --git a/src/RelationStorage.cs b/src/RelationStorage.cs index 50b4e43..ee70bbf 100644 --- a/src/RelationStorage.cs +++ b/src/RelationStorage.cs @@ -1,332 +1,284 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using MoonTools.ECS.Collections; -namespace MoonTools.ECS +namespace MoonTools.ECS; + +// TODO: implement this entire class with NativeMemory equivalents, can just memcpy for snapshots +internal class RelationStorage { - internal abstract class RelationStorage + internal NativeArray relations; + internal NativeArray relationDatas; + internal Dictionary<(Entity, Entity), int> indices = new Dictionary<(Entity, Entity), int>(16); + internal Dictionary> outRelations = new Dictionary>(16); + internal Dictionary> inRelations = new Dictionary>(16); + private Stack> listPool = new Stack>(); + + private bool disposed; + + public RelationStorage(int relationDataSize) { - public abstract unsafe void Set(int entityA, int entityB, void* relationData); - public abstract int GetStorageIndex(int entityA, int entityB); - public abstract unsafe void* Get(int relationStorageIndex); - public abstract void UnrelateAll(int entityID); - public abstract ReverseSpanEnumerator<(Entity, Entity)> All(); - public abstract RelationStorage CreateStorage(); - public abstract void Clear(); + relations = new NativeArray(Unsafe.SizeOf<(Entity, Entity)>()); + relationDatas = new NativeArray(relationDataSize); } - // Relation is the two entities, A related to B. - // TRelation is the data attached to the relation. - internal unsafe class RelationStorage : RelationStorage, IDisposable where TRelation : unmanaged + public ReverseSpanEnumerator<(Entity, Entity)> All() { - 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> outRelations = new Dictionary>(16); - private Dictionary> inRelations = new Dictionary>(16); - private Stack> listPool = new Stack>(); + return new ReverseSpanEnumerator<(Entity, Entity)>(relations.ToSpan<(Entity, Entity)>()); + } - private bool disposed; + public unsafe void Set(in Entity entityA, in Entity entityB, in T relationData) where T : unmanaged + { + var relation = (entityA, entityB); - public RelationStorage() + if (indices.TryGetValue(relation, out var index)) { - relations = ((Entity, Entity)*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<(Entity, Entity)>())); - relationDatas = (TRelation*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf())); + relationDatas.Set(index, relationData); + return; } - public override ReverseSpanEnumerator<(Entity, Entity)> All() + if (!outRelations.ContainsKey(entityA)) { - return new ReverseSpanEnumerator<(Entity, Entity)>(new Span<(Entity, Entity)>(relations, count)); + outRelations[entityA] = AcquireHashSetFromPool(); } + outRelations[entityA].Add(entityB); - public void Set(in Entity entityA, in Entity entityB, TRelation relationData) + if (!inRelations.ContainsKey(entityB)) { - var relation = (entityA, entityB); - - if (indices.TryGetValue(relation, out var index)) - { - relationDatas[index] = relationData; - return; - } - - var idA = entityA.ID; - var idB = entityB.ID; - - if (!outRelations.ContainsKey(idA)) - { - outRelations[idA] = AcquireHashSetFromPool(); - } - outRelations[idA].Add(idB); - - if (!inRelations.ContainsKey(idB)) - { - inRelations[idB] = AcquireHashSetFromPool(); - } - inRelations[idB].Add(idA); - - if (count >= capacity) - { - 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; - relationDatas[count] = relationData; - indices.Add(relation, count); - count += 1; + inRelations[entityB] = AcquireHashSetFromPool(); } + inRelations[entityB].Add(entityA); - public TRelation Get(in Entity entityA, in Entity entityB) + relations.Append(relation); + relationDatas.Append(relationData); + indices.Add(relation, relations.Count - 1); + } + + public ref T Get(in Entity entityA, in Entity entityB) where T : unmanaged + { + var relationIndex = indices[(entityA, entityB)]; + return ref relationDatas.Get(relationIndex); + } + + public bool Has(Entity entityA, Entity entityB) + { + return indices.ContainsKey((entityA, entityB)); + } + + public ReverseSpanEnumerator OutRelations(Entity Entity) + { + if (outRelations.TryGetValue(Entity, out var entityOutRelations)) { - return relationDatas[indices[(entityA, entityB)]]; + return entityOutRelations.GetEnumerator(); } - - public bool Has((Entity, Entity) relation) + else { - return indices.ContainsKey(relation); + return ReverseSpanEnumerator.Empty; } + } - public ReverseSpanEnumerator OutRelations(int entityID) - { - if (outRelations.TryGetValue(entityID, out var entityOutRelations)) - { - return entityOutRelations.GetEnumerator(); - } - else - { - return ReverseSpanEnumerator.Empty; - } - } + public Entity OutFirst(Entity Entity) + { + return OutNth(Entity, 0); + } - public Entity OutFirst(int entityID) - { - return OutNth(entityID, 0); - } - - public Entity OutNth(int entityID, int n) - { + public Entity OutNth(Entity Entity, int n) + { #if DEBUG - if (!outRelations.ContainsKey(entityID) || outRelations[entityID].Count == 0) - { - throw new KeyNotFoundException("No out relations to this entity!"); - } + if (!outRelations.ContainsKey(Entity) || outRelations[Entity].Count == 0) + { + throw new KeyNotFoundException("No out relations to this entity!"); + } #endif - return outRelations[entityID][n]; - } + return outRelations[Entity][n]; + } - public bool HasOutRelation(int entityID) - { - return outRelations.ContainsKey(entityID) && outRelations[entityID].Count > 0; - } + public bool HasOutRelation(Entity Entity) + { + return outRelations.ContainsKey(Entity) && outRelations[Entity].Count > 0; + } - public int OutRelationCount(int entityID) - { - return outRelations.TryGetValue(entityID, out var entityOutRelations) ? entityOutRelations.Count : 0; - } + public int OutRelationCount(Entity Entity) + { + return outRelations.TryGetValue(Entity, out var entityOutRelations) ? entityOutRelations.Count : 0; + } - public ReverseSpanEnumerator InRelations(int entityID) + public ReverseSpanEnumerator InRelations(Entity Entity) + { + if (inRelations.TryGetValue(Entity, out var entityInRelations)) { - if (inRelations.TryGetValue(entityID, out var entityInRelations)) - { - return entityInRelations.GetEnumerator(); - } - else - { - return ReverseSpanEnumerator.Empty; - } + return entityInRelations.GetEnumerator(); } - - public Entity InFirst(int entityID) + else { - return InNth(entityID, 0); + return ReverseSpanEnumerator.Empty; } + } - public Entity InNth(int entityID, int n) - { + public Entity InFirst(Entity Entity) + { + return InNth(Entity, 0); + } + + public Entity InNth(Entity Entity, int n) + { #if DEBUG - if (!inRelations.ContainsKey(entityID) || inRelations[entityID].Count == 0) - { - throw new KeyNotFoundException("No in relations to this entity!"); - } + if (!inRelations.ContainsKey(Entity) || inRelations[Entity].Count == 0) + { + throw new KeyNotFoundException("No in relations to this entity!"); + } #endif - return inRelations[entityID][n]; - } + return inRelations[Entity][n]; + } - public bool HasInRelation(int entityID) + public bool HasInRelation(Entity Entity) + { + return inRelations.ContainsKey(Entity) && inRelations[Entity].Count > 0; + } + + public int InRelationCount(Entity Entity) + { + return inRelations.TryGetValue(Entity, out var entityInRelations) ? entityInRelations.Count : 0; + } + + public (bool, bool) Remove(in Entity entityA, in Entity entityB) + { + var aEmpty = false; + var bEmpty = false; + var relation = (entityA, entityB); + + if (outRelations.TryGetValue(entityA, out var entityOutRelations)) { - return inRelations.ContainsKey(entityID) && inRelations[entityID].Count > 0; - } - - public int InRelationCount(int entityID) - { - return inRelations.TryGetValue(entityID, out var entityInRelations) ? entityInRelations.Count : 0; - } - - public (bool, bool) Remove(in Entity entityA, in Entity entityB) - { - var aEmpty = false; - var bEmpty = false; - var relation = (entityA, entityB); - - if (outRelations.TryGetValue(entityA.ID, out var entityOutRelations)) + entityOutRelations.Remove(entityB); + if (outRelations[entityA].Count == 0) { - entityOutRelations.Remove(entityB.ID); - if (outRelations[entityA.ID].Count == 0) - { - aEmpty = true; - } + 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)) + { + var lastElementIndex = relations.Count - 1; + + relationDatas.Delete(index); + relations.Delete(index); + + // move an element into the hole + if (index != lastElementIndex) + { + var lastRelation = relations.Get<(Entity, Entity)>(lastElementIndex); + indices[lastRelation] = index; } - if (inRelations.TryGetValue(entityB.ID, out var entityInRelations)) + indices.Remove(relation); + } + + return (aEmpty, bEmpty); + } + + public void RemoveEntity(in Entity entity) + { + if (outRelations.TryGetValue(entity, out var entityOutRelations)) + { + foreach (var entityB in entityOutRelations) { - entityInRelations.Remove(entityA.ID); - if (inRelations[entityB.ID].Count == 0) - { - bEmpty = true; - } + Remove(entity, entityB); } - if (indices.TryGetValue(relation, out var index)) - { - var lastElementIndex = count - 1; + ReturnHashSetToPool(entityOutRelations); + outRelations.Remove(entity); + } - // move an element into the hole - if (index != lastElementIndex) + if (inRelations.TryGetValue(entity, out var entityInRelations)) + { + foreach (var entityA in entityInRelations) + { + Remove(entityA, entity); + } + + ReturnHashSetToPool(entityInRelations); + inRelations.Remove(entity); + } + } + + internal 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() + { + indices.Clear(); + + foreach (var set in inRelations.Values) + { + ReturnHashSetToPool(set); + } + inRelations.Clear(); + + foreach (var set in outRelations.Values) + { + ReturnHashSetToPool(set); + } + outRelations.Clear(); + + relations.Clear(); + relationDatas.Clear(); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + Clear(); + + if (disposing) + { + foreach (var set in listPool) { - var lastRelation = relations[lastElementIndex]; - indices[lastRelation] = index; - relationDatas[index] = relationDatas[lastElementIndex]; - relations[index] = lastRelation; + set.Dispose(); } - count -= 1; - indices.Remove(relation); + relations.Dispose(); + relationDatas.Dispose(); } - return (aEmpty, bEmpty); + disposed = true; } + } - private IndexableSet AcquireHashSetFromPool() - { - if (listPool.Count == 0) - { - listPool.Push(new IndexableSet()); - } + // ~RelationStorage() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } - return listPool.Pop(); - } - - private void ReturnHashSetToPool(IndexableSet hashSet) - { - hashSet.Clear(); - listPool.Push(hashSet); - } - - // untyped methods used for internal implementation - - public override unsafe void Set(int entityA, int entityB, void* relationData) - { - Set(entityA, entityB, *(TRelation*) relationData); - } - - public override int GetStorageIndex(int entityA, int entityB) - { - return indices[(entityA, entityB)]; - } - - public override unsafe void* Get(int relationStorageIndex) - { - return &relationDatas[relationStorageIndex]; - } - - public override void UnrelateAll(int entityID) - { - if (outRelations.TryGetValue(entityID, out var entityOutRelations)) - { - foreach (var entityB in entityOutRelations) - { - Remove(entityID, entityB); - } - - ReturnHashSetToPool(entityOutRelations); - outRelations.Remove(entityID); - } - - if (inRelations.TryGetValue(entityID, out var entityInRelations)) - { - foreach (var entityA in entityInRelations) - { - Remove(entityA, entityID); - } - - ReturnHashSetToPool(entityInRelations); - inRelations.Remove(entityID); - } - } - - public override RelationStorage CreateStorage() - { - return new RelationStorage(); - } - - public override 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(); - } - } - - 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); - } + 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/Snapshot.cs b/src/Snapshot.cs new file mode 100644 index 0000000..32c16b4 --- /dev/null +++ b/src/Snapshot.cs @@ -0,0 +1,282 @@ +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using MoonTools.ECS.Collections; + +namespace MoonTools.ECS; + +// TODO: we should implement a NativeDictionary that can be memcopied +public class Snapshot +{ + private Dictionary ComponentSnapshots = new Dictionary(); + + private Dictionary ArchetypeSnapshots = + new Dictionary(); + + private Dictionary RelationSnapshots = + new Dictionary(); + + + private Dictionary EntityIndex = new Dictionary(); + + private Dictionary> EntityRelationIndex = + new Dictionary>(); + + private Dictionary EntityTags = new Dictionary(); + + private IdAssigner EntityIdAssigner = new IdAssigner(); + + public int Count + { + get + { + var count = 0; + + foreach (var snapshot in ArchetypeSnapshots.Values) + { + count += snapshot.Count; + } + + return count; + } + } + + public void Restore(World world) + { + // restore archetype storage + foreach (var (archetypeSignature, archetypeSnapshot) in ArchetypeSnapshots) + { + var archetype = world.ArchetypeIndex[archetypeSignature]; + archetypeSnapshot.Restore(archetype); + } + + // restore entity index + world.EntityIndex.Clear(); + foreach (var (id, ArchetypeRecord) in EntityIndex) + { + world.EntityIndex[id] = ArchetypeRecord; + } + + // restore components + foreach (var (typeId, componentSnapshot) in ComponentSnapshots) + { + var componentStorage = world.ComponentIndex[typeId]; + componentSnapshot.Restore(componentStorage); + } + + // restore id assigner state + EntityIdAssigner.CopyTo(world.EntityIdAssigner); + + // restore relation state + foreach (var (typeId, relationSnapshot) in RelationSnapshots) + { + var relationStorage = world.RelationIndex[typeId]; + relationSnapshot.Restore(relationStorage); + } + + // restore entity relation index state + // FIXME: arghhhh this is so slow + foreach (var (id, relationTypeSet) in EntityRelationIndex) + { + world.EntityRelationIndex[id].Clear(); + + foreach (var typeId in relationTypeSet) + { + world.EntityRelationIndex[id].Add(typeId); + } + } + + foreach (var (id, s) in EntityTags) + { + world.EntityTags[id] = s; + } + } + + public void Take(World world) + { + // copy id assigner state + world.EntityIdAssigner.CopyTo(EntityIdAssigner); + + // copy entity index + EntityIndex.Clear(); + foreach (var (id, ArchetypeRecord) in world.EntityIndex) + { + EntityIndex[id] = ArchetypeRecord; + } + + // copy archetypes + foreach (var archetype in world.ArchetypeIndex.Values) + { + TakeArchetypeSnapshot(archetype); + } + + // copy components + foreach (var (typeId, componentStorage) in world.ComponentIndex) + { + TakeComponentSnapshot(typeId, componentStorage); + } + + // copy relations + foreach (var (typeId, relationStorage) in world.RelationIndex) + { + TakeRelationSnapshot(typeId, relationStorage); + } + + // copy entity relation index + // FIXME: arghhhh this is so slow + foreach (var (id, relationTypeSet) in world.EntityRelationIndex) + { + if (!EntityRelationIndex.ContainsKey(id)) + { + EntityRelationIndex.Add(id, new IndexableSet()); + } + + EntityRelationIndex[id].Clear(); + + foreach (var typeId in relationTypeSet) + { + EntityRelationIndex[id].Add(typeId); + } + } + + foreach (var (id, s) in world.EntityTags) + { + EntityTags[id] = s; + } + } + + private void TakeArchetypeSnapshot(Archetype archetype) + { + if (!ArchetypeSnapshots.TryGetValue(archetype.Signature, out var archetypeSnapshot)) + { + archetypeSnapshot = new ArchetypeSnapshot(); + ArchetypeSnapshots.Add(archetype.Signature, archetypeSnapshot); + } + + archetypeSnapshot.Take(archetype); + } + + private void TakeComponentSnapshot(TypeId typeId, ComponentStorage componentStorage) + { + if (!ComponentSnapshots.TryGetValue(typeId, out var componentSnapshot)) + { + componentSnapshot = new ComponentSnapshot(componentStorage.Components.ElementSize); + ComponentSnapshots.Add(typeId, componentSnapshot); + } + + componentSnapshot.Take(componentStorage); + } + + private void TakeRelationSnapshot(TypeId typeId, RelationStorage relationStorage) + { + if (!RelationSnapshots.TryGetValue(typeId, out var snapshot)) + { + snapshot = new RelationSnapshot(relationStorage.relationDatas.ElementSize); + RelationSnapshots.Add(typeId, snapshot); + } + + snapshot.Take(relationStorage); + } + + private class ArchetypeSnapshot + { + private readonly NativeArray Entities; + public int Count => Entities.Count; + + public ArchetypeSnapshot() + { + Entities = new NativeArray(); + } + + public void Take(Archetype archetype) + { + archetype.Entities.CopyTo(Entities); + } + + public void Restore(Archetype archetype) + { + Entities.CopyTo(archetype.Entities); + } + } + + private class ComponentSnapshot + { + private readonly Dictionary EntityIDToStorageIndex = new Dictionary(); + private readonly NativeArray Components; + private readonly NativeArray EntityIDs; + + public ComponentSnapshot(int elementSize) + { + Components = new NativeArray(elementSize); + EntityIDs = new NativeArray(); + } + + public void Take(ComponentStorage componentStorage) + { + componentStorage.Components.CopyAllTo(Components); + componentStorage.EntityIDs.CopyTo(EntityIDs); + } + + public void Restore(ComponentStorage componentStorage) + { + Components.CopyAllTo(componentStorage.Components); + EntityIDs.CopyTo(componentStorage.EntityIDs); + + componentStorage.EntityIDToStorageIndex.Clear(); + for (int i = 0; i < EntityIDs.Count; i += 1) + { + var entityID = EntityIDs[i]; + componentStorage.EntityIDToStorageIndex[entityID] = i; + } + } + } + + private class RelationSnapshot + { + private NativeArray Relations; + private NativeArray RelationDatas; + + public RelationSnapshot(int elementSize) + { + Relations = new NativeArray(Unsafe.SizeOf<(Entity, Entity)>()); + RelationDatas = new NativeArray(elementSize); + } + + public void Take(RelationStorage relationStorage) + { + relationStorage.relations.CopyAllTo(Relations); + relationStorage.relationDatas.CopyAllTo(RelationDatas); + } + + public void Restore(RelationStorage relationStorage) + { + relationStorage.Clear(); + + Relations.CopyAllTo(relationStorage.relations); + RelationDatas.CopyAllTo(relationStorage.relationDatas); + + for (int index = 0; index < Relations.Count; index += 1) + { + var relation = Relations.Get<(Entity, Entity)>(index); + relationStorage.indices[relation] = index; + + relationStorage.indices[relation] = index; + + if (!relationStorage.outRelations.ContainsKey(relation.Item1)) + { + relationStorage.outRelations[relation.Item1] = + relationStorage.AcquireHashSetFromPool(); + } + + relationStorage.outRelations[relation.Item1].Add(relation.Item2); + + if (!relationStorage.inRelations.ContainsKey(relation.Item2)) + { + relationStorage.inRelations[relation.Item2] = + relationStorage.AcquireHashSetFromPool(); + } + + relationStorage.inRelations[relation.Item2].Add(relation.Item1); + } + } + } +} diff --git a/src/System.cs b/src/System.cs index 5cc10c8..1d50116 100644 --- a/src/System.cs +++ b/src/System.cs @@ -1,47 +1,15 @@ using System; -namespace MoonTools.ECS +namespace MoonTools.ECS; + +public abstract class System : Manipulator { - public abstract class System : Manipulator - { - internal MessageDepot MessageDepot => World.MessageDepot; + protected System(World world) : base(world) { } - public System(World world) : base(world) { } + public abstract void Update(); - public abstract void Update(TimeSpan delta); - - protected ReadOnlySpan ReadMessages() where TMessage : unmanaged - { - return MessageDepot.All(); - } - - protected TMessage ReadMessage() where TMessage : unmanaged - { - return MessageDepot.First(); - } - - protected bool SomeMessage() where TMessage : unmanaged - { - return MessageDepot.Some(); - } - - protected ReverseSpanEnumerator ReadMessagesWithEntity(in Entity entity) where TMessage : unmanaged - { - return MessageDepot.WithEntity(entity.ID); - } - - protected ref readonly TMessage ReadMessageWithEntity(in Entity entity) where TMessage : unmanaged - { - return ref MessageDepot.FirstWithEntity(entity.ID); - } - - protected bool SomeMessageWithEntity(in Entity entity) where TMessage : unmanaged - { - return MessageDepot.SomeWithEntity(entity.ID); - } - - protected void Send(in TMessage message) where TMessage : unmanaged => World.Send(message); - - protected void Send(in Entity entity, in TMessage message) where TMessage : unmanaged => World.Send(entity, message); - } + protected ReadOnlySpan ReadMessages() where T : unmanaged => World.ReadMessages(); + protected T ReadMessage() where T : unmanaged => World.ReadMessage(); + protected bool SomeMessage() where T : unmanaged => World.SomeMessage(); + protected void Send(T message) where T : unmanaged => World.Send(message); } diff --git a/src/TypeId.cs b/src/TypeId.cs new file mode 100644 index 0000000..f94a2a7 --- /dev/null +++ b/src/TypeId.cs @@ -0,0 +1,5 @@ +using System; + +namespace MoonTools.ECS; + +public readonly record struct TypeId(uint Value); diff --git a/src/TypeIndices.cs b/src/TypeIndices.cs deleted file mode 100644 index 2f43850..0000000 --- a/src/TypeIndices.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MoonTools.ECS -{ - public class TypeIndices - { - Dictionary TypeToIndex = new Dictionary(); - int nextID = 0; - public int Count => TypeToIndex.Count; - - public int GetIndex() where T : unmanaged - { - if (!TypeToIndex.ContainsKey(typeof(T))) - { - TypeToIndex.Add(typeof(T), nextID); - nextID += 1; - } - - return TypeToIndex[typeof(T)]; - } - - public int GetIndex(Type type) - { - return TypeToIndex[type]; - } - - -#if DEBUG - public Dictionary.KeyCollection Types => TypeToIndex.Keys; -#endif - } -} diff --git a/src/World.cs b/src/World.cs index 9c14ad4..6b69d80 100644 --- a/src/World.cs +++ b/src/World.cs @@ -1,194 +1,454 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; +using MoonTools.ECS.Collections; namespace MoonTools.ECS { public class World { - internal readonly static TypeIndices ComponentTypeIndices = new TypeIndices(); - internal readonly static TypeIndices RelationTypeIndices = new TypeIndices(); - internal readonly EntityStorage EntityStorage = new EntityStorage(); - internal readonly ComponentDepot ComponentDepot; - internal readonly MessageDepot MessageDepot = new MessageDepot(); - internal readonly RelationDepot RelationDepot; - internal readonly FilterStorage FilterStorage; - public FilterBuilder FilterBuilder => new FilterBuilder(FilterStorage, ComponentTypeIndices); + // Get TypeId from a Type + private readonly Dictionary TypeToId = new Dictionary(); + + #if DEBUG + private Dictionary IdToType = new Dictionary(); + #endif + + // Get element size from a TypeId + private readonly Dictionary ElementSizes = new Dictionary(); + + // Archetypes + internal readonly Dictionary ArchetypeIndex = new Dictionary(); + internal readonly Dictionary EntityIndex = new Dictionary(); + internal readonly Archetype EmptyArchetype; + + // TODO: can we make the tag an native array of chars at some point? + internal Dictionary EntityTags = new Dictionary(); + + // Relation Storages + internal Dictionary RelationIndex = new Dictionary(); + internal Dictionary> EntityRelationIndex = new Dictionary>(); + + // Message Storages + private Dictionary MessageIndex = new Dictionary(); + + public FilterBuilder FilterBuilder => new FilterBuilder(this); + + internal readonly Dictionary ComponentIndex = new Dictionary(); + + internal IdAssigner EntityIdAssigner = new IdAssigner(); + private IdAssigner TypeIdAssigner = new IdAssigner(); public World() { - ComponentDepot = new ComponentDepot(ComponentTypeIndices); - RelationDepot = new RelationDepot(EntityStorage, RelationTypeIndices); - FilterStorage = new FilterStorage(EntityStorage, ComponentTypeIndices); + EmptyArchetype = CreateArchetype(ArchetypeSignature.Empty); } + internal TypeId GetTypeId() where T : unmanaged + { + if (TypeToId.ContainsKey(typeof(T))) + { + return TypeToId[typeof(T)]; + } + + var typeId = new TypeId(TypeIdAssigner.Assign()); + TypeToId.Add(typeof(T), typeId); + ElementSizes.Add(typeId, Unsafe.SizeOf()); + + #if DEBUG + IdToType.Add(typeId, typeof(T)); + #endif + + return typeId; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ComponentStorage GetComponentStorage() where T : unmanaged + { + var typeId = GetTypeId(); + if (ComponentIndex.TryGetValue(typeId, out var componentStorage)) + { + return componentStorage; + } + + componentStorage = new ComponentStorage(ElementSizes[typeId]); + ComponentIndex.Add(typeId, componentStorage); + return componentStorage; + } + + private Archetype CreateArchetype(ArchetypeSignature signature) + { + var archetype = new Archetype(this, signature); + ArchetypeIndex.Add(signature, archetype); + return archetype; + } + + // ENTITIES + public Entity CreateEntity(string tag = "") { - return EntityStorage.Create(tag); + var entity = new Entity(EntityIdAssigner.Assign()); + EntityIndex.Add(entity, new ArchetypeRecord(EmptyArchetype, EmptyArchetype.Count)); + EmptyArchetype.Append(entity); + return entity; } public void Tag(Entity entity, string tag) { - EntityStorage.Tag(entity, tag); + EntityTags[entity] = tag; } public string GetTag(Entity entity) { - return EntityStorage.Tag(entity); - } - - public void Set(Entity entity, in TComponent component) where TComponent : unmanaged - { -#if DEBUG - // check for use after destroy - if (!EntityStorage.Exists(entity)) - { - throw new InvalidOperationException("This entity is not valid!"); - } -#endif - ComponentDepot.Set(entity.ID, component); - - if (EntityStorage.SetComponent(entity.ID, ComponentTypeIndices.GetIndex())) - { - FilterStorage.Check(entity.ID); - } - } - - // untyped version for Transfer - // no filter check because filter state is copied directly - internal unsafe void Set(Entity entity, int componentTypeIndex, void* component) - { - ComponentDepot.Set(entity.ID, componentTypeIndex, component); - EntityStorage.SetComponent(entity.ID, componentTypeIndex); - } - - public void Remove(in Entity entity) where TComponent : unmanaged - { - if (EntityStorage.RemoveComponent(entity.ID, ComponentTypeIndices.GetIndex())) - { - // Run filter storage update first so that the entity state is still valid in the remove callback. - FilterStorage.Check(entity.ID); - ComponentDepot.Remove(entity.ID); - } - } - - public void Relate(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged - { - RelationDepot.Set(entityA, entityB, relationData); - var relationTypeIndex = RelationTypeIndices.GetIndex(); - EntityStorage.AddRelationKind(entityA.ID, relationTypeIndex); - EntityStorage.AddRelationKind(entityB.ID, relationTypeIndex); - } - - public void Unrelate(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged - { - var (aEmpty, bEmpty) = RelationDepot.Remove(entityA, entityB); - - if (aEmpty) - { - EntityStorage.RemoveRelation(entityA.ID, RelationTypeIndices.GetIndex()); - } - - if (bEmpty) - { - EntityStorage.RemoveRelation(entityB.ID, RelationTypeIndices.GetIndex()); - } - } - - public void UnrelateAll(in Entity entity) where TRelationKind : unmanaged - { - RelationDepot.UnrelateAll(entity.ID); - EntityStorage.RemoveRelation(entity.ID, RelationTypeIndices.GetIndex()); - } - - public void Send(in TMessage message) where TMessage : unmanaged - { - MessageDepot.Add(message); - } - - public void Send(in Entity entity, in TMessage message) where TMessage : unmanaged - { - MessageDepot.Add(entity.ID, message); + return EntityTags[entity]; } public void Destroy(in Entity entity) { - foreach (var componentTypeIndex in EntityStorage.ComponentTypeIndices(entity.ID)) + var record = EntityIndex[entity]; + var archetype = record.Archetype; + var row = record.Row; + + // remove all components from storages + for (int i = 0; i < archetype.Signature.Count; i += 1) { - // Run filter storage update first so that the entity state is still valid in the remove callback. - FilterStorage.RemoveEntity(entity.ID, componentTypeIndex); - ComponentDepot.Remove(entity.ID, componentTypeIndex); + var componentStorage = ComponentIndex[archetype.Signature[i]]; + componentStorage.Remove(entity); } - foreach (var relationTypeIndex in EntityStorage.RelationTypeIndices(entity.ID)) + // remove all relations from storage + foreach (var relationTypeIndex in EntityRelationIndex[entity]) { - RelationDepot.UnrelateAll(entity.ID, relationTypeIndex); - EntityStorage.RemoveRelation(entity.ID, relationTypeIndex); + var relationStorage = RelationIndex[relationTypeIndex]; + relationStorage.RemoveEntity(entity); } - EntityStorage.Destroy(entity); + EntityRelationIndex[entity].Clear(); + + // remove from archetype + if (row != archetype.Count - 1) + { + var lastEntity = archetype.Entities[archetype.Count - 1]; + archetype.Entities[row] = lastEntity; + EntityIndex[lastEntity] = new ArchetypeRecord(archetype, row); + } + archetype.Entities.RemoveLastElement(); + EntityIndex.Remove(entity); + + // recycle ID + EntityIdAssigner.Unassign(entity.ID); } + // COMPONENTS + public bool Has(in Entity entity) where T : unmanaged + { + var storage = GetComponentStorage(); + return storage.Has(entity); + } + + public bool Some() where T : unmanaged + { + var storage = GetComponentStorage(); + return storage.Any(); + } + + public ref T Get(in Entity entity) where T : unmanaged + { + var storage = GetComponentStorage(); + return ref storage.Get(entity); + } + + public ref T GetSingleton() where T : unmanaged + { + var storage = GetComponentStorage(); + return ref storage.GetFirst(); + } + + public Entity GetSingletonEntity() where T : unmanaged + { + var storage = GetComponentStorage(); + return storage.FirstEntity(); + } + + public void Set(in Entity entity, in T component) where T : unmanaged + { + var componentStorage = GetComponentStorage(); + + if (!componentStorage.Set(entity, component)) + { + UpdateArchetype(entity, true); + } + } + + public void Remove(in Entity entity) where T : unmanaged + { + var componentStorage = GetComponentStorage(); + + if (componentStorage.Remove(entity)) + { + UpdateArchetype(entity, false); + } + } + + private void UpdateArchetype(in Entity entity, bool insert) + { + Archetype? nextArchetype; + + var componentTypeId = TypeToId[typeof(T)]; + var record = EntityIndex[entity]; + var archetype = record.Archetype; + + if (archetype.Edges.TryGetValue(TypeToId[typeof(T)], out var edge)) + { + nextArchetype = insert ? edge.Add : edge.Remove; + } + else + { + var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1); + archetype.Signature.CopyTo(nextSignature); + + if (insert) + { + nextSignature.Insert(componentTypeId); + } + else + { + nextSignature.Remove(componentTypeId); + } + + if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype)) + { + nextArchetype = CreateArchetype(nextSignature); + } + + var newEdge = new ArchetypeEdge(nextArchetype, archetype); + archetype.Edges.Add(componentTypeId, newEdge); + nextArchetype.Edges.Add(componentTypeId, newEdge); + } + + var newRow = archetype.Transfer(record.Row, nextArchetype); + EntityIndex[entity] = new ArchetypeRecord(nextArchetype, newRow); + } + + // RELATIONS + + private RelationStorage RegisterRelationType(TypeId typeId) + { + var relationStorage = new RelationStorage(ElementSizes[typeId]); + RelationIndex.Add(typeId, relationStorage); + return relationStorage; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private RelationStorage GetRelationStorage() where T : unmanaged + { + var typeId = GetTypeId(); + if (RelationIndex.TryGetValue(typeId, out var relationStorage)) + { + return relationStorage; + } + + return RegisterRelationType(typeId); + } + + public void Relate(in Entity entityA, in Entity entityB, in T relation) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + relationStorage.Set(entityA, entityB, relation); + EntityRelationIndex[entityA].Add(TypeToId[typeof(T)]); + EntityRelationIndex[entityB].Add(TypeToId[typeof(T)]); + } + + public void Unrelate(in Entity entityA, in Entity entityB) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + relationStorage.Remove(entityA, entityB); + } + + public void UnrelateAll(in Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + relationStorage.RemoveEntity(entity); + } + + public bool Related(in Entity entityA, in Entity entityB) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.Has(entityA, entityB); + } + + public T GetRelationData(in Entity entityA, in Entity entityB) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.Get(entityA, entityB); + } + + public ReverseSpanEnumerator<(Entity, Entity)> Relations() where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.All(); + } + + public ReverseSpanEnumerator OutRelations(Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.OutRelations(entity); + } + + public Entity OutRelationSingleton(in Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.OutFirst(entity); + } + + public bool HasOutRelation(in Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.HasOutRelation(entity); + } + + public int OutRelationCount(in Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.OutRelationCount(entity); + } + + public Entity NthOutRelation(in Entity entity, int n) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.OutNth(entity, n); + } + + public ReverseSpanEnumerator InRelations(Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.InRelations(entity); + } + + public Entity InRelationSingleton(in Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.InFirst(entity); + } + + public bool HasInRelation(in Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.HasInRelation(entity); + } + + public int InRelationCount(in Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.InRelationCount(entity); + } + + public Entity NthInRelation(in Entity entity, int n) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.InNth(entity, n); + } + + // MESSAGES + + private TypeId GetMessageTypeId() where T : unmanaged + { + var typeId = GetTypeId(); + + if (!MessageIndex.ContainsKey(typeId)) + { + MessageIndex.Add(typeId, new MessageStorage(Unsafe.SizeOf())); + } + + return typeId; + } + + public void Send(in T message) where T : unmanaged + { + var typeId = GetMessageTypeId(); + MessageIndex[typeId].Add(message); + } + + public bool SomeMessage() where T : unmanaged + { + var typeId = GetMessageTypeId(); + return MessageIndex[typeId].Some(); + } + + public ReadOnlySpan ReadMessages() where T : unmanaged + { + var typeId = GetMessageTypeId(); + return MessageIndex[typeId].All(); + } + + public T ReadMessage() where T : unmanaged + { + var typeId = GetMessageTypeId(); + return MessageIndex[typeId].First(); + } + + // TODO: temporary component storage? public void FinishUpdate() { - MessageDepot.Clear(); - } - - public void Clear() - { - EntityStorage.Clear(); - MessageDepot.Clear(); - RelationDepot.Clear(); - ComponentDepot.Clear(); - FilterStorage.Clear(); - } - - private Dictionary WorldToTransferID = new Dictionary(); - - /// - /// If you are using the World Transfer feature, call this once after your systems/filters have all been initialized. - /// - public void PrepareTransferTo(World other) - { - other.FilterStorage.CreateMissingStorages(FilterStorage); - } - - // FIXME: there's probably a better way to handle Filters so they are not world-bound - public unsafe void Transfer(World other, Filter filter, Filter otherFilter) - { - WorldToTransferID.Clear(); - other.ComponentDepot.CreateMissingStorages(ComponentDepot); - other.RelationDepot.CreateMissingStorages(RelationDepot); - - // destroy all entities matching the filter - foreach (var entity in otherFilter.Entities) + foreach (var (_, messageStorage) in MessageIndex) { - other.Destroy(entity); + messageStorage.Clear(); } + } - // create entities - foreach (var entity in filter.Entities) + // DEBUG + // NOTE: these methods are very inefficient + // they should only be used in debugging contexts!! + #if DEBUG + public ComponentTypeEnumerator Debug_GetAllComponentTypes(Entity entity) + { + return new ComponentTypeEnumerator(this, EntityIndex[entity]); + } + + public IEnumerable Debug_GetEntities(Type componentType) + { + var storage = ComponentIndex[TypeToId[componentType]]; + return storage.Debug_GetEntities(); + } + + public IEnumerable Debug_SearchComponentType(string typeString) + { + foreach (var type in TypeToId.Keys) { - var otherWorldEntity = other.CreateEntity(GetTag(entity)); - WorldToTransferID.Add(entity.ID, otherWorldEntity.ID); - } - - // transfer relations - RelationDepot.TransferStorage(WorldToTransferID, other.RelationDepot); - - // set components - foreach (var entity in filter.Entities) - { - var otherWorldEntity = WorldToTransferID[entity.ID]; - - foreach (var componentTypeIndex in EntityStorage.ComponentTypeIndices(entity.ID)) + if (type.ToString().ToLower().Contains(typeString.ToLower())) { - other.Set(otherWorldEntity, componentTypeIndex, ComponentDepot.UntypedGet(entity.ID, componentTypeIndex)); + yield return type; } } - - // transfer filters last so callbacks trigger correctly - FilterStorage.TransferStorage(WorldToTransferID, other.FilterStorage); } + + public ref struct ComponentTypeEnumerator + { + private World World; + private ArchetypeRecord Record; + private int ComponentIndex; + + public ComponentTypeEnumerator GetEnumerator() => this; + + internal ComponentTypeEnumerator( + World world, + ArchetypeRecord record + ) + { + World = world; + Record = record; + ComponentIndex = -1; + } + + public bool MoveNext() + { + ComponentIndex += 1; + return ComponentIndex < Record.Archetype.Signature.Count; + } + + public unsafe Type Current => World.IdToType[Record.Archetype.Signature[ComponentIndex]]; + } + #endif } } -- 2.25.1 From 8774314f625d12b8754ee91145536afba3f8e4d0 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 3 Nov 2023 15:39:30 -0700 Subject: [PATCH 02/17] misc fixes --- src/Archetype.cs | 12 +-- src/ArchetypeSignature.cs | 2 +- src/Collections/NativeArray.cs | 2 +- src/Collections/NativeArrayUntyped.cs | 2 +- src/ComponentStorage.cs | 1 - src/Filter.cs | 15 ++-- src/TypeId.cs | 8 +- src/World.cs | 103 ++++++++++++++++++-------- 8 files changed, 91 insertions(+), 54 deletions(-) diff --git a/src/Archetype.cs b/src/Archetype.cs index 45bd718..791ba96 100644 --- a/src/Archetype.cs +++ b/src/Archetype.cs @@ -9,7 +9,10 @@ internal class Archetype public ArchetypeSignature Signature; public NativeArray Entities = new NativeArray(); - public SortedDictionary Edges = new SortedDictionary(); + public SortedDictionary AddEdges = + new SortedDictionary(); + public SortedDictionary RemoveEdges = + new SortedDictionary(); public int Count => Entities.Count; @@ -25,13 +28,6 @@ internal class Archetype return Entities.Count - 1; } - public int Transfer(int row, Archetype transferTo) - { - var newIndex = transferTo.Append(Entities[row]); - Entities.Delete(row); - return newIndex; - } - public void ClearAll() { for (int i = Entities.Count - 1; i >= 0; i -= 1) diff --git a/src/ArchetypeSignature.cs b/src/ArchetypeSignature.cs index cabaeb7..5dac880 100644 --- a/src/ArchetypeSignature.cs +++ b/src/ArchetypeSignature.cs @@ -41,7 +41,7 @@ internal class ArchetypeSignature : IEquatable public void CopyTo(ArchetypeSignature other) { - foreach (var id in other.Ids.AsSpan()) + foreach (var id in Ids.AsSpan()) { other.Ids.Add(id); } diff --git a/src/Collections/NativeArray.cs b/src/Collections/NativeArray.cs index 9661de8..10e48ca 100644 --- a/src/Collections/NativeArray.cs +++ b/src/Collections/NativeArray.cs @@ -69,7 +69,7 @@ public unsafe class NativeArray : IDisposable where T : unmanaged // Fills gap by copying final element to the deleted index public void Delete(int index) { - if (Count > 1) + if (index != Count - 1) { NativeMemory.Copy( (void*) (Elements + ((Count - 1) * ElementSize)), diff --git a/src/Collections/NativeArrayUntyped.cs b/src/Collections/NativeArrayUntyped.cs index e66336d..020c5a0 100644 --- a/src/Collections/NativeArrayUntyped.cs +++ b/src/Collections/NativeArrayUntyped.cs @@ -53,7 +53,7 @@ internal unsafe class NativeArray : IDisposable // Fills gap by copying final element to the deleted index public void Delete(int index) { - if (Count > 1) + if (index != Count - 1) { NativeMemory.Copy( (void*) (Elements + ((Count - 1) * ElementSize)), diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs index 7368102..4cfb619 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -30,7 +30,6 @@ namespace MoonTools.ECS public ref T Get(in Entity entity) where T : unmanaged { - return ref Components.Get(EntityIDToStorageIndex[entity]); } diff --git a/src/Filter.cs b/src/Filter.cs index 9b54c75..3d51524 100644 --- a/src/Filter.cs +++ b/src/Filter.cs @@ -59,17 +59,14 @@ public class Filter // WARNING: this WILL crash if the index is out of range! public Entity NthEntity(int index) { - var count = 0; - foreach (var archetype in Archetypes) { - count += archetype.Count; - if (index < count) + if (index < archetype.Count) { return archetype.Entities[index]; } - index -= count; + index -= archetype.Count; } throw new InvalidOperationException("Filter index out of range!"); @@ -135,12 +132,12 @@ public class Filter // breadth-first search // ignore excluded component edges - foreach (var (componentId, edge) in current.Edges) + foreach (var (componentId, edge) in current.AddEdges) { - if (!Explored.Contains(edge.Add) && !filter.Excluded.Contains(componentId)) + if (!filter.Excluded.Contains(componentId)) { - Explored.Add(edge.Add); - ArchetypeSearchQueue.Enqueue(edge.Add); + Explored.Add(edge); + ArchetypeSearchQueue.Enqueue(edge); } } } diff --git a/src/TypeId.cs b/src/TypeId.cs index f94a2a7..62bc5d6 100644 --- a/src/TypeId.cs +++ b/src/TypeId.cs @@ -2,4 +2,10 @@ using System; namespace MoonTools.ECS; -public readonly record struct TypeId(uint Value); +public readonly record struct TypeId(uint Value) : IComparable +{ + public int CompareTo(TypeId other) + { + return Value.CompareTo(other.Value); + } +} diff --git a/src/World.cs b/src/World.cs index 6b69d80..b82ee8f 100644 --- a/src/World.cs +++ b/src/World.cs @@ -90,6 +90,14 @@ namespace MoonTools.ECS var entity = new Entity(EntityIdAssigner.Assign()); EntityIndex.Add(entity, new ArchetypeRecord(EmptyArchetype, EmptyArchetype.Count)); EmptyArchetype.Append(entity); + + if (!EntityRelationIndex.ContainsKey(entity)) + { + EntityRelationIndex.Add(entity, new IndexableSet()); + } + + EntityTags[entity] = tag; + return entity; } @@ -177,7 +185,7 @@ namespace MoonTools.ECS if (!componentStorage.Set(entity, component)) { - UpdateArchetype(entity, true); + TransferArchetype(entity, FindArchetypeByAdd(entity)); } } @@ -187,48 +195,79 @@ namespace MoonTools.ECS if (componentStorage.Remove(entity)) { - UpdateArchetype(entity, false); + TransferArchetype(entity, FindArchetypeByRemove(entity)); } } - private void UpdateArchetype(in Entity entity, bool insert) + private Archetype FindArchetypeByAdd(in Entity entity) { - Archetype? nextArchetype; - var componentTypeId = TypeToId[typeof(T)]; var record = EntityIndex[entity]; var archetype = record.Archetype; - if (archetype.Edges.TryGetValue(TypeToId[typeof(T)], out var edge)) + if (archetype.AddEdges.TryGetValue(componentTypeId, out var nextArchetype)) { - nextArchetype = insert ? edge.Add : edge.Remove; - } - else - { - var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1); - archetype.Signature.CopyTo(nextSignature); - - if (insert) - { - nextSignature.Insert(componentTypeId); - } - else - { - nextSignature.Remove(componentTypeId); - } - - if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype)) - { - nextArchetype = CreateArchetype(nextSignature); - } - - var newEdge = new ArchetypeEdge(nextArchetype, archetype); - archetype.Edges.Add(componentTypeId, newEdge); - nextArchetype.Edges.Add(componentTypeId, newEdge); + return nextArchetype; } - var newRow = archetype.Transfer(record.Row, nextArchetype); - EntityIndex[entity] = new ArchetypeRecord(nextArchetype, newRow); + var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1); + archetype.Signature.CopyTo(nextSignature); + nextSignature.Insert(componentTypeId); + + if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype)) + { + nextArchetype = CreateArchetype(nextSignature); + } + + archetype.AddEdges.Add(componentTypeId, nextArchetype); + nextArchetype.RemoveEdges.Add(componentTypeId, archetype); + + return nextArchetype; + } + + private Archetype FindArchetypeByRemove(in Entity entity) + { + var componentTypeId = TypeToId[typeof(T)]; + var record = EntityIndex[entity]; + var archetype = record.Archetype; + + if (archetype.RemoveEdges.TryGetValue(componentTypeId, out var nextArchetype)) + { + return nextArchetype; + } + + var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1); + archetype.Signature.CopyTo(nextSignature); + nextSignature.Remove(componentTypeId); + + if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype)) + { + nextArchetype = CreateArchetype(nextSignature); + } + + archetype.RemoveEdges.Add(componentTypeId, nextArchetype); + nextArchetype.AddEdges.Add(componentTypeId, archetype); + + return nextArchetype; + } + + private void TransferArchetype(in Entity entity, Archetype nextArchetype) + { + var record = EntityIndex[entity]; + var archetype = record.Archetype; + var row = record.Row; + + // fill the gap + if (row != archetype.Count - 1) + { + var lastEntity = archetype.Entities[archetype.Count - 1]; + archetype.Entities[row] = lastEntity; + EntityIndex[lastEntity] = new ArchetypeRecord(archetype, row); + } + + archetype.Entities.RemoveLastElement(); + nextArchetype.Entities.Append(entity); + EntityIndex[entity] = new ArchetypeRecord(nextArchetype, nextArchetype.Count - 1); } // RELATIONS -- 2.25.1 From 53f4124fdb5495ec309f093055cc3dbcddc9917e Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 3 Nov 2023 16:51:26 -0700 Subject: [PATCH 03/17] stupid ass pointer math --- src/Collections/NativeArray.cs | 7 ++----- src/RelationStorage.cs | 6 +++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Collections/NativeArray.cs b/src/Collections/NativeArray.cs index 10e48ca..a0af02a 100644 --- a/src/Collections/NativeArray.cs +++ b/src/Collections/NativeArray.cs @@ -11,6 +11,7 @@ public unsafe class NativeArray : IDisposable where T : unmanaged private int Capacity; private int ElementSize; + public Span ToSpan() => new Span(Elements, Count); public Span.Enumerator GetEnumerator() => new Span(Elements, Count).GetEnumerator(); private bool disposed; @@ -71,11 +72,7 @@ public unsafe class NativeArray : IDisposable where T : unmanaged { if (index != Count - 1) { - NativeMemory.Copy( - (void*) (Elements + ((Count - 1) * ElementSize)), - (void*) (Elements + (index * ElementSize)), - (nuint) ElementSize - ); + this[index] = this[Count - 1]; } Count -= 1; diff --git a/src/RelationStorage.cs b/src/RelationStorage.cs index ee70bbf..31a12fa 100644 --- a/src/RelationStorage.cs +++ b/src/RelationStorage.cs @@ -171,9 +171,6 @@ internal class RelationStorage { var lastElementIndex = relations.Count - 1; - relationDatas.Delete(index); - relations.Delete(index); - // move an element into the hole if (index != lastElementIndex) { @@ -181,6 +178,9 @@ internal class RelationStorage indices[lastRelation] = index; } + relationDatas.Delete(index); + relations.Delete(index); + indices.Remove(relation); } -- 2.25.1 From e6059a2f0a762b46b448929da8613624e2bf7c54 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 3 Nov 2023 17:47:17 -0700 Subject: [PATCH 04/17] misc fixes --- src/Filter.cs | 17 +++++++++++++---- src/World.cs | 12 ++++++++++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/Filter.cs b/src/Filter.cs index 3d51524..7d42bac 100644 --- a/src/Filter.cs +++ b/src/Filter.cs @@ -132,12 +132,21 @@ public class Filter // breadth-first search // ignore excluded component edges - foreach (var (componentId, edge) in current.AddEdges) + foreach (var (componentId, archetype) in current.AddEdges) { - if (!filter.Excluded.Contains(componentId)) + if (!Explored.Contains(archetype) && !filter.Excluded.Contains(componentId)) { - Explored.Add(edge); - ArchetypeSearchQueue.Enqueue(edge); + Explored.Add(archetype); + ArchetypeSearchQueue.Enqueue(archetype); + } + } + + foreach (var (componentId, archetype) in current.RemoveEdges) + { + if (!Explored.Contains(archetype)) + { + Explored.Add(archetype); + ArchetypeSearchQueue.Enqueue(archetype); } } } diff --git a/src/World.cs b/src/World.cs index b82ee8f..7948022 100644 --- a/src/World.cs +++ b/src/World.cs @@ -220,7 +220,11 @@ namespace MoonTools.ECS } archetype.AddEdges.Add(componentTypeId, nextArchetype); - nextArchetype.RemoveEdges.Add(componentTypeId, archetype); + + if (!nextArchetype.RemoveEdges.ContainsKey(componentTypeId)) + { + nextArchetype.RemoveEdges.Add(componentTypeId, archetype); + } return nextArchetype; } @@ -246,7 +250,11 @@ namespace MoonTools.ECS } archetype.RemoveEdges.Add(componentTypeId, nextArchetype); - nextArchetype.AddEdges.Add(componentTypeId, archetype); + + if (!nextArchetype.AddEdges.ContainsKey(componentTypeId)) + { + nextArchetype.AddEdges.Add(componentTypeId, archetype); + } return nextArchetype; } -- 2.25.1 From c6f8b65b32b1345e27e2a6add4911abf6c2d6b3c Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 7 Nov 2023 17:46:44 -0800 Subject: [PATCH 05/17] hello filters my old friend --- src/Archetype.cs | 38 ---- src/ArchetypeEdge.cs | 3 - src/ArchetypeRecord.cs | 3 - src/ArchetypeSignature.cs | 89 ---------- src/{ => Collections}/IndexableSet.cs | 0 src/Collections/NativeArrayUntyped.cs | 16 -- src/ComponentStorage.cs | 4 +- src/Filter.cs | 200 +++++----------------- src/FilterBuilder.cs | 18 +- src/FilterSignature.cs | 7 +- src/IdAssigner.cs | 11 +- src/Snapshot.cs | 130 +++++++------- src/World.cs | 238 +++++++++++++------------- 13 files changed, 241 insertions(+), 516 deletions(-) delete mode 100644 src/Archetype.cs delete mode 100644 src/ArchetypeEdge.cs delete mode 100644 src/ArchetypeRecord.cs delete mode 100644 src/ArchetypeSignature.cs rename src/{ => Collections}/IndexableSet.cs (100%) diff --git a/src/Archetype.cs b/src/Archetype.cs deleted file mode 100644 index 791ba96..0000000 --- a/src/Archetype.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Collections.Generic; -using MoonTools.ECS.Collections; - -namespace MoonTools.ECS; - -internal class Archetype -{ - public World World; - public ArchetypeSignature Signature; - public NativeArray Entities = new NativeArray(); - - public SortedDictionary AddEdges = - new SortedDictionary(); - public SortedDictionary RemoveEdges = - new SortedDictionary(); - - public int Count => Entities.Count; - - public Archetype(World world, ArchetypeSignature signature) - { - World = world; - Signature = signature; - } - - public int Append(Entity entity) - { - Entities.Append(entity); - return Entities.Count - 1; - } - - public void ClearAll() - { - for (int i = Entities.Count - 1; i >= 0; i -= 1) - { - World.Destroy(Entities[i]); - } - } -} diff --git a/src/ArchetypeEdge.cs b/src/ArchetypeEdge.cs deleted file mode 100644 index a10fb46..0000000 --- a/src/ArchetypeEdge.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace MoonTools.ECS; - -internal readonly record struct ArchetypeEdge(Archetype Add, Archetype Remove); diff --git a/src/ArchetypeRecord.cs b/src/ArchetypeRecord.cs deleted file mode 100644 index 6780c29..0000000 --- a/src/ArchetypeRecord.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace MoonTools.ECS; - -internal readonly record struct ArchetypeRecord(Archetype Archetype, int Row); diff --git a/src/ArchetypeSignature.cs b/src/ArchetypeSignature.cs deleted file mode 100644 index 5dac880..0000000 --- a/src/ArchetypeSignature.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using MoonTools.ECS.Collections; - -namespace MoonTools.ECS; - -internal class ArchetypeSignature : IEquatable -{ - public static ArchetypeSignature Empty = new ArchetypeSignature(0); - - IndexableSet Ids; - - public int Count => Ids.Count; - - public TypeId this[int i] => Ids[i]; - - public ArchetypeSignature() - { - Ids = new IndexableSet(); - } - - public ArchetypeSignature(int capacity) - { - Ids = new IndexableSet(capacity); - } - - // Maintains sorted order - public void Insert(TypeId componentId) - { - Ids.Add(componentId); - } - - public void Remove(TypeId componentId) - { - Ids.Remove(componentId); - } - - public bool Contains(TypeId componentId) - { - return Ids.Contains(componentId); - } - - public void CopyTo(ArchetypeSignature other) - { - foreach (var id in Ids.AsSpan()) - { - other.Ids.Add(id); - } - } - - public override bool Equals(object? obj) - { - return obj is ArchetypeSignature signature && Equals(signature); - } - - public bool Equals(ArchetypeSignature? other) - { - if (other == null) - { - return false; - } - - if (Ids.Count != other.Ids.Count) - { - return false; - } - - for (int i = 0; i < Ids.Count; i += 1) - { - if (Ids[i] != other.Ids[i]) - { - return false; - } - } - - return true; - } - - public override int GetHashCode() - { - var hashcode = 1; - - foreach (var id in Ids) - { - hashcode = HashCode.Combine(hashcode, id); - } - - return hashcode; - } -} diff --git a/src/IndexableSet.cs b/src/Collections/IndexableSet.cs similarity index 100% rename from src/IndexableSet.cs rename to src/Collections/IndexableSet.cs diff --git a/src/Collections/NativeArrayUntyped.cs b/src/Collections/NativeArrayUntyped.cs index 020c5a0..070effd 100644 --- a/src/Collections/NativeArrayUntyped.cs +++ b/src/Collections/NativeArrayUntyped.cs @@ -76,22 +76,6 @@ internal unsafe class NativeArray : IDisposable Count += 1; } - public void CopyElementToEnd(int index, NativeArray other) - { - if (other.Count >= other.Capacity) - { - other.Resize(); - } - - NativeMemory.Copy( - (void*) (Elements + (index * ElementSize)), - (void*) (other.Elements + (other.Count * ElementSize)), - (nuint) ElementSize - ); - - other.Count += 1; - } - public void CopyAllTo(NativeArray other) { if (Count >= other.Capacity) diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs index 4cfb619..2571446 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -9,13 +9,15 @@ namespace MoonTools.ECS internal readonly Dictionary EntityIDToStorageIndex = new Dictionary(16); internal readonly NativeArray Components; internal readonly NativeArray EntityIDs; + internal readonly TypeId TypeId; private bool disposed; - public ComponentStorage(int elementSize) + public ComponentStorage(TypeId typeId, int elementSize) { Components = new NativeArray(elementSize); EntityIDs = new NativeArray(); + TypeId = typeId; } public bool Any() diff --git a/src/Filter.cs b/src/Filter.cs index 7d42bac..a3b48ac 100644 --- a/src/Filter.cs +++ b/src/Filter.cs @@ -1,193 +1,77 @@ -using System; -using System.Collections.Generic; +using MoonTools.ECS.Collections; namespace MoonTools.ECS; // TODO: do we want to get fancy with queries beyond Include and Exclude? public class Filter { - private Archetype EmptyArchetype; - private HashSet Included; - private HashSet Excluded; + private World World; + internal FilterSignature Signature; - public EntityEnumerator Entities => new EntityEnumerator(this); - internal ArchetypeEnumerator Archetypes => new ArchetypeEnumerator(this); - public RandomEntityEnumerator EntitiesInRandomOrder => new RandomEntityEnumerator(this); + internal IndexableSet EntitySet = new IndexableSet(); - public bool Empty - { - get - { - var empty = true; + public ReverseSpanEnumerator Entities => EntitySet.GetEnumerator(); - foreach (var archetype in Archetypes) - { - if (archetype.Count > 0) - { - return false; - } - } - - return empty; - } - } - - public int Count - { - get - { - var count = 0; - - foreach (var archetype in Archetypes) - { - count += archetype.Count; - } - - return count; - } - } - - public Entity RandomEntity - { - get - { - var randomIndex = RandomManager.Next(Count); - return NthEntity(randomIndex); - } - } + public bool Empty => EntitySet.Count == 0; + public int Count => EntitySet.Count; // WARNING: this WILL crash if the index is out of range! - public Entity NthEntity(int index) + public Entity NthEntity(int index) => EntitySet[index]; + + // WARNING: this WILL crash if the filter is empty! + public Entity RandomEntity => EntitySet[RandomManager.Next(EntitySet.Count)]; + public RandomEntityEnumerator EntitiesInRandomOrder => new RandomEntityEnumerator(this); + + internal Filter(World world, FilterSignature signature) { - foreach (var archetype in Archetypes) - { - if (index < archetype.Count) - { - return archetype.Entities[index]; - } - - index -= archetype.Count; - } - - throw new InvalidOperationException("Filter index out of range!"); + World = world; + Signature = signature; } public void DestroyAllEntities() { - foreach (var archetype in Archetypes) + foreach (var entity in EntitySet) { - archetype.ClearAll(); + World.Destroy(entity); } } - internal Filter(Archetype emptyArchetype, HashSet included, HashSet excluded) + internal void Check(Entity entity) { - EmptyArchetype = emptyArchetype; - Included = included; - Excluded = excluded; - } - - internal ref struct ArchetypeEnumerator - { - private Archetype CurrentArchetype; - - // TODO: pool these - private Queue ArchetypeQueue = new Queue(); - private Queue ArchetypeSearchQueue = new Queue(); - private HashSet Explored = new HashSet(); - - public ArchetypeEnumerator GetEnumerator() => this; - - public ArchetypeEnumerator(Filter filter) + foreach (var type in Signature.Included) { - var empty = filter.EmptyArchetype; - ArchetypeSearchQueue.Enqueue(empty); - - // TODO: can we cache this search effectively? - while (ArchetypeSearchQueue.TryDequeue(out var current)) + if (!World.Has(entity, type)) { - // exclude the empty archetype - var satisfiesFilter = filter.Included.Count != 0; - - foreach (var componentId in filter.Included) - { - if (!current.Signature.Contains(componentId)) - { - satisfiesFilter = false; - } - } - - foreach (var componentId in filter.Excluded) - { - if (current.Signature.Contains(componentId)) - { - satisfiesFilter = false; - } - } - - if (satisfiesFilter) - { - ArchetypeQueue.Enqueue(current); - } - - // breadth-first search - // ignore excluded component edges - foreach (var (componentId, archetype) in current.AddEdges) - { - if (!Explored.Contains(archetype) && !filter.Excluded.Contains(componentId)) - { - Explored.Add(archetype); - ArchetypeSearchQueue.Enqueue(archetype); - } - } - - foreach (var (componentId, archetype) in current.RemoveEdges) - { - if (!Explored.Contains(archetype)) - { - Explored.Add(archetype); - ArchetypeSearchQueue.Enqueue(archetype); - } - } + EntitySet.Remove(entity); + return; } } - public bool MoveNext() + foreach (var type in Signature.Excluded) { - return ArchetypeQueue.TryDequeue(out CurrentArchetype!); - } - - public Archetype Current => CurrentArchetype; - } - - public ref struct EntityEnumerator - { - private Entity CurrentEntity; - - public EntityEnumerator GetEnumerator() => this; - - // TODO: pool this - Queue EntityQueue = new Queue(); - - internal EntityEnumerator(Filter filter) - { - var archetypeEnumerator = new ArchetypeEnumerator(filter); - - foreach (var archetype in archetypeEnumerator) + if (World.Has(entity, type)) { - foreach (var entity in archetype.Entities) - { - EntityQueue.Enqueue(entity); - } + EntitySet.Remove(entity); + return; } } - public bool MoveNext() - { - return EntityQueue.TryDequeue(out CurrentEntity); - } + EntitySet.Add(entity); + } - public Entity Current => CurrentEntity; + internal void AddEntity(in Entity entity) + { + EntitySet.Add(entity); + } + + internal void RemoveEntity(in Entity entity) + { + EntitySet.Remove(entity); + } + + internal void Clear() + { + EntitySet.Clear(); } public ref struct RandomEntityEnumerator diff --git a/src/FilterBuilder.cs b/src/FilterBuilder.cs index 2213a0a..316052e 100644 --- a/src/FilterBuilder.cs +++ b/src/FilterBuilder.cs @@ -1,21 +1,22 @@ using System.Collections.Generic; +using MoonTools.ECS.Collections; namespace MoonTools.ECS { public struct FilterBuilder { World World; - HashSet Included; - HashSet Excluded; + IndexableSet Included; + IndexableSet Excluded; internal FilterBuilder(World world) { World = world; - Included = new HashSet(); - Excluded = new HashSet(); + Included = new IndexableSet(); + Excluded = new IndexableSet(); } - private FilterBuilder(World world, HashSet included, HashSet excluded) + private FilterBuilder(World world, IndexableSet included, IndexableSet excluded) { World = world; Included = included; @@ -24,19 +25,20 @@ namespace MoonTools.ECS public FilterBuilder Include() where T : unmanaged { - Included.Add(World.GetTypeId()); + Included.Add(World.GetComponentTypeId()); return new FilterBuilder(World, Included, Excluded); } public FilterBuilder Exclude() where T : unmanaged { - Excluded.Add(World.GetTypeId()); + Excluded.Add(World.GetComponentTypeId()); return new FilterBuilder(World, Included, Excluded); } public Filter Build() { - return new Filter(World.EmptyArchetype, Included, Excluded); + var signature = new FilterSignature(Included, Excluded); + return World.GetFilter(signature); } } } diff --git a/src/FilterSignature.cs b/src/FilterSignature.cs index 7e94922..e48fa83 100644 --- a/src/FilterSignature.cs +++ b/src/FilterSignature.cs @@ -1,15 +1,14 @@ using System; -using System.Collections.Generic; using MoonTools.ECS.Collections; namespace MoonTools.ECS { public struct FilterSignature : IEquatable { - public readonly IndexableSet Included; - public readonly IndexableSet Excluded; + public readonly IndexableSet Included; + public readonly IndexableSet Excluded; - public FilterSignature(IndexableSet included, IndexableSet excluded) + public FilterSignature(IndexableSet included, IndexableSet excluded) { Included = included; Excluded = excluded; diff --git a/src/IdAssigner.cs b/src/IdAssigner.cs index 4874fcc..0f11307 100644 --- a/src/IdAssigner.cs +++ b/src/IdAssigner.cs @@ -7,14 +7,17 @@ internal class IdAssigner uint Next; NativeArray AvailableIds = new NativeArray(); - public uint Assign() + public uint Assign(out bool recycled) { - if (!AvailableIds.TryPop(out var id)) + recycled = AvailableIds.TryPop(out var id); + + if (recycled) { - id = Next; - Next += 1; + return id; } + id = Next; + Next += 1; return id; } diff --git a/src/Snapshot.cs b/src/Snapshot.cs index 32c16b4..61bde14 100644 --- a/src/Snapshot.cs +++ b/src/Snapshot.cs @@ -9,51 +9,38 @@ public class Snapshot { private Dictionary ComponentSnapshots = new Dictionary(); - private Dictionary ArchetypeSnapshots = - new Dictionary(); + private Dictionary> Filters = new Dictionary>(); private Dictionary RelationSnapshots = new Dictionary(); - - private Dictionary EntityIndex = new Dictionary(); - private Dictionary> EntityRelationIndex = new Dictionary>(); + private Dictionary> EntityComponentIndex = + new Dictionary>(); + private Dictionary EntityTags = new Dictionary(); private IdAssigner EntityIdAssigner = new IdAssigner(); - public int Count - { - get - { - var count = 0; - - foreach (var snapshot in ArchetypeSnapshots.Values) - { - count += snapshot.Count; - } - - return count; - } - } - public void Restore(World world) { - // restore archetype storage - foreach (var (archetypeSignature, archetypeSnapshot) in ArchetypeSnapshots) - { - var archetype = world.ArchetypeIndex[archetypeSignature]; - archetypeSnapshot.Restore(archetype); - } + // restore id assigner state + EntityIdAssigner.CopyTo(world.EntityIdAssigner); - // restore entity index - world.EntityIndex.Clear(); - foreach (var (id, ArchetypeRecord) in EntityIndex) + // restore filter states + // this could be sped up if we figured out a direct IndexableSet copy + foreach (var (signature, entityList) in Filters) { - world.EntityIndex[id] = ArchetypeRecord; + var filter = world.FilterIndex[signature]; + + filter.Clear(); + + foreach (var entity in entityList) + { + filter.AddEntity(entity); + } } // restore components @@ -63,9 +50,6 @@ public class Snapshot componentSnapshot.Restore(componentStorage); } - // restore id assigner state - EntityIdAssigner.CopyTo(world.EntityIdAssigner); - // restore relation state foreach (var (typeId, relationSnapshot) in RelationSnapshots) { @@ -85,6 +69,19 @@ public class Snapshot } } + // restore entity component index state + // FIXME: arrghghhh this is so slow + foreach (var (id, componentTypeSet) in EntityComponentIndex) + { + world.EntityComponentIndex[id].Clear(); + + foreach (var typeId in componentTypeSet) + { + world.EntityComponentIndex[id].Add(typeId); + } + } + + // restore entity tags foreach (var (id, s) in EntityTags) { world.EntityTags[id] = s; @@ -96,17 +93,10 @@ public class Snapshot // copy id assigner state world.EntityIdAssigner.CopyTo(EntityIdAssigner); - // copy entity index - EntityIndex.Clear(); - foreach (var (id, ArchetypeRecord) in world.EntityIndex) + // copy filter states + foreach (var (_, filter) in world.FilterIndex) { - EntityIndex[id] = ArchetypeRecord; - } - - // copy archetypes - foreach (var archetype in world.ArchetypeIndex.Values) - { - TakeArchetypeSnapshot(archetype); + TakeFilterSnapshot(filter); } // copy components @@ -138,21 +128,44 @@ public class Snapshot } } + // copy entity component index + // FIXME: arghhhh this is so slow + foreach (var (id, componentTypeSet) in world.EntityComponentIndex) + { + if (!EntityComponentIndex.ContainsKey(id)) + { + EntityComponentIndex.Add(id, new IndexableSet()); + } + + EntityComponentIndex[id].Clear(); + + foreach (var typeId in componentTypeSet) + { + EntityComponentIndex[id].Add(typeId); + } + } + + // copy entity tags foreach (var (id, s) in world.EntityTags) { EntityTags[id] = s; } } - private void TakeArchetypeSnapshot(Archetype archetype) + private void TakeFilterSnapshot(Filter filter) { - if (!ArchetypeSnapshots.TryGetValue(archetype.Signature, out var archetypeSnapshot)) + if (!Filters.TryGetValue(filter.Signature, out var entities)) { - archetypeSnapshot = new ArchetypeSnapshot(); - ArchetypeSnapshots.Add(archetype.Signature, archetypeSnapshot); + entities = new List(); + Filters.Add(filter.Signature, entities); } - archetypeSnapshot.Take(archetype); + entities.Clear(); + + foreach (var entity in filter.EntitySet.AsSpan()) + { + entities.Add(entity); + } } private void TakeComponentSnapshot(TypeId typeId, ComponentStorage componentStorage) @@ -177,27 +190,6 @@ public class Snapshot snapshot.Take(relationStorage); } - private class ArchetypeSnapshot - { - private readonly NativeArray Entities; - public int Count => Entities.Count; - - public ArchetypeSnapshot() - { - Entities = new NativeArray(); - } - - public void Take(Archetype archetype) - { - archetype.Entities.CopyTo(Entities); - } - - public void Restore(Archetype archetype) - { - Entities.CopyTo(archetype.Entities); - } - } - private class ComponentSnapshot { private readonly Dictionary EntityIDToStorageIndex = new Dictionary(); diff --git a/src/World.cs b/src/World.cs index 7948022..0dcc689 100644 --- a/src/World.cs +++ b/src/World.cs @@ -5,7 +5,7 @@ using MoonTools.ECS.Collections; namespace MoonTools.ECS { - public class World + public class World : IDisposable { // Get TypeId from a Type private readonly Dictionary TypeToId = new Dictionary(); @@ -17,10 +17,9 @@ namespace MoonTools.ECS // Get element size from a TypeId private readonly Dictionary ElementSizes = new Dictionary(); - // Archetypes - internal readonly Dictionary ArchetypeIndex = new Dictionary(); - internal readonly Dictionary EntityIndex = new Dictionary(); - internal readonly Archetype EmptyArchetype; + // Filters + internal readonly Dictionary FilterIndex = new Dictionary(); + private readonly Dictionary> TypeToFilter = new Dictionary>(); // TODO: can we make the tag an native array of chars at some point? internal Dictionary EntityTags = new Dictionary(); @@ -35,14 +34,12 @@ namespace MoonTools.ECS public FilterBuilder FilterBuilder => new FilterBuilder(this); internal readonly Dictionary ComponentIndex = new Dictionary(); + internal Dictionary> EntityComponentIndex = new Dictionary>(); internal IdAssigner EntityIdAssigner = new IdAssigner(); private IdAssigner TypeIdAssigner = new IdAssigner(); - public World() - { - EmptyArchetype = CreateArchetype(ArchetypeSignature.Empty); - } + private bool IsDisposed; internal TypeId GetTypeId() where T : unmanaged { @@ -51,7 +48,7 @@ namespace MoonTools.ECS return TypeToId[typeof(T)]; } - var typeId = new TypeId(TypeIdAssigner.Assign()); + var typeId = new TypeId(TypeIdAssigner.Assign(out var _)); TypeToId.Add(typeof(T), typeId); ElementSizes.Add(typeId, Unsafe.SizeOf()); @@ -62,6 +59,20 @@ namespace MoonTools.ECS return typeId; } + internal TypeId GetComponentTypeId() where T : unmanaged + { + var typeId = GetTypeId(); + if (ComponentIndex.TryGetValue(typeId, out var componentStorage)) + { + return typeId; + } + + componentStorage = new ComponentStorage(typeId, ElementSizes[typeId]); + ComponentIndex.Add(typeId, componentStorage); + TypeToFilter.Add(typeId, new List()); + return typeId; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private ComponentStorage GetComponentStorage() where T : unmanaged { @@ -71,29 +82,44 @@ namespace MoonTools.ECS return componentStorage; } - componentStorage = new ComponentStorage(ElementSizes[typeId]); + componentStorage = new ComponentStorage(typeId, ElementSizes[typeId]); ComponentIndex.Add(typeId, componentStorage); + TypeToFilter.Add(typeId, new List()); return componentStorage; } - private Archetype CreateArchetype(ArchetypeSignature signature) + // FILTERS + + internal Filter GetFilter(FilterSignature signature) { - var archetype = new Archetype(this, signature); - ArchetypeIndex.Add(signature, archetype); - return archetype; + if (!FilterIndex.TryGetValue(signature, out var filter)) + { + filter = new Filter(this, signature); + + foreach (var typeId in signature.Included) + { + TypeToFilter[typeId].Add(filter); + } + + foreach (var typeId in signature.Excluded) + { + TypeToFilter[typeId].Add(filter); + } + } + + return filter; } // ENTITIES public Entity CreateEntity(string tag = "") { - var entity = new Entity(EntityIdAssigner.Assign()); - EntityIndex.Add(entity, new ArchetypeRecord(EmptyArchetype, EmptyArchetype.Count)); - EmptyArchetype.Append(entity); + var entity = new Entity(EntityIdAssigner.Assign(out var recycled)); - if (!EntityRelationIndex.ContainsKey(entity)) + if (!recycled) { EntityRelationIndex.Add(entity, new IndexableSet()); + EntityComponentIndex.Add(entity, new IndexableSet()); } EntityTags[entity] = tag; @@ -113,15 +139,16 @@ namespace MoonTools.ECS public void Destroy(in Entity entity) { - var record = EntityIndex[entity]; - var archetype = record.Archetype; - var row = record.Row; - // remove all components from storages - for (int i = 0; i < archetype.Signature.Count; i += 1) + foreach (var componentTypeIndex in EntityComponentIndex[entity]) { - var componentStorage = ComponentIndex[archetype.Signature[i]]; + var componentStorage = ComponentIndex[componentTypeIndex]; componentStorage.Remove(entity); + + foreach (var filter in TypeToFilter[componentTypeIndex]) + { + filter.RemoveEntity(entity); + } } // remove all relations from storage @@ -131,18 +158,9 @@ namespace MoonTools.ECS relationStorage.RemoveEntity(entity); } + EntityComponentIndex[entity].Clear(); EntityRelationIndex[entity].Clear(); - // remove from archetype - if (row != archetype.Count - 1) - { - var lastEntity = archetype.Entities[archetype.Count - 1]; - archetype.Entities[row] = lastEntity; - EntityIndex[lastEntity] = new ArchetypeRecord(archetype, row); - } - archetype.Entities.RemoveLastElement(); - EntityIndex.Remove(entity); - // recycle ID EntityIdAssigner.Unassign(entity.ID); } @@ -155,6 +173,11 @@ namespace MoonTools.ECS return storage.Has(entity); } + internal bool Has(in Entity entity, in TypeId typeId) + { + return EntityComponentIndex[entity].Contains(typeId); + } + public bool Some() where T : unmanaged { var storage = GetComponentStorage(); @@ -185,7 +208,12 @@ namespace MoonTools.ECS if (!componentStorage.Set(entity, component)) { - TransferArchetype(entity, FindArchetypeByAdd(entity)); + EntityComponentIndex[entity].Add(componentStorage.TypeId); + + foreach (var filter in TypeToFilter[componentStorage.TypeId]) + { + filter.Check(entity); + } } } @@ -195,89 +223,15 @@ namespace MoonTools.ECS if (componentStorage.Remove(entity)) { - TransferArchetype(entity, FindArchetypeByRemove(entity)); + EntityComponentIndex[entity].Remove(componentStorage.TypeId); + + foreach (var filter in TypeToFilter[componentStorage.TypeId]) + { + filter.Check(entity); + } } } - private Archetype FindArchetypeByAdd(in Entity entity) - { - var componentTypeId = TypeToId[typeof(T)]; - var record = EntityIndex[entity]; - var archetype = record.Archetype; - - if (archetype.AddEdges.TryGetValue(componentTypeId, out var nextArchetype)) - { - return nextArchetype; - } - - var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1); - archetype.Signature.CopyTo(nextSignature); - nextSignature.Insert(componentTypeId); - - if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype)) - { - nextArchetype = CreateArchetype(nextSignature); - } - - archetype.AddEdges.Add(componentTypeId, nextArchetype); - - if (!nextArchetype.RemoveEdges.ContainsKey(componentTypeId)) - { - nextArchetype.RemoveEdges.Add(componentTypeId, archetype); - } - - return nextArchetype; - } - - private Archetype FindArchetypeByRemove(in Entity entity) - { - var componentTypeId = TypeToId[typeof(T)]; - var record = EntityIndex[entity]; - var archetype = record.Archetype; - - if (archetype.RemoveEdges.TryGetValue(componentTypeId, out var nextArchetype)) - { - return nextArchetype; - } - - var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1); - archetype.Signature.CopyTo(nextSignature); - nextSignature.Remove(componentTypeId); - - if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype)) - { - nextArchetype = CreateArchetype(nextSignature); - } - - archetype.RemoveEdges.Add(componentTypeId, nextArchetype); - - if (!nextArchetype.AddEdges.ContainsKey(componentTypeId)) - { - nextArchetype.AddEdges.Add(componentTypeId, archetype); - } - - return nextArchetype; - } - - private void TransferArchetype(in Entity entity, Archetype nextArchetype) - { - var record = EntityIndex[entity]; - var archetype = record.Archetype; - var row = record.Row; - - // fill the gap - if (row != archetype.Count - 1) - { - var lastEntity = archetype.Entities[archetype.Count - 1]; - archetype.Entities[row] = lastEntity; - EntityIndex[lastEntity] = new ArchetypeRecord(archetype, row); - } - - archetype.Entities.RemoveLastElement(); - nextArchetype.Entities.Append(entity); - EntityIndex[entity] = new ArchetypeRecord(nextArchetype, nextArchetype.Count - 1); - } - // RELATIONS private RelationStorage RegisterRelationType(TypeId typeId) @@ -450,7 +404,7 @@ namespace MoonTools.ECS #if DEBUG public ComponentTypeEnumerator Debug_GetAllComponentTypes(Entity entity) { - return new ComponentTypeEnumerator(this, EntityIndex[entity]); + return new ComponentTypeEnumerator(this, EntityComponentIndex[entity]); } public IEnumerable Debug_GetEntities(Type componentType) @@ -473,29 +427,67 @@ namespace MoonTools.ECS public ref struct ComponentTypeEnumerator { private World World; - private ArchetypeRecord Record; + private IndexableSet Types; private int ComponentIndex; public ComponentTypeEnumerator GetEnumerator() => this; internal ComponentTypeEnumerator( World world, - ArchetypeRecord record + IndexableSet types ) { World = world; - Record = record; + Types = types; ComponentIndex = -1; } public bool MoveNext() { ComponentIndex += 1; - return ComponentIndex < Record.Archetype.Signature.Count; + return ComponentIndex < Types.Count; } - public unsafe Type Current => World.IdToType[Record.Archetype.Signature[ComponentIndex]]; + public unsafe Type Current => World.IdToType[Types[ComponentIndex]]; } - #endif + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + // TODO: dispose managed state (managed objects) + foreach (var componentStorage in ComponentIndex.Values) + { + componentStorage.Dispose(); + } + + foreach (var relationStorage in RelationIndex.Values) + { + relationStorage.Dispose(); + } + } + + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + IsDisposed = true; + } + } + + // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + // ~World() + // { + // // 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 } } -- 2.25.1 From 0551aa0a073af5f32320cece0ea3ba17c647633d Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 7 Nov 2023 18:12:42 -0800 Subject: [PATCH 06/17] don't copy array data over itself --- src/Collections/IndexableSet.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Collections/IndexableSet.cs b/src/Collections/IndexableSet.cs index d49c597..140f9f5 100644 --- a/src/Collections/IndexableSet.cs +++ b/src/Collections/IndexableSet.cs @@ -61,10 +61,14 @@ namespace MoonTools.ECS.Collections return false; } - var lastElement = array[Count - 1]; var index = indices[element]; - array[index] = lastElement; - indices[lastElement] = index; + + if (index != Count - 1) + { + var lastElement = array[Count - 1]; + array[index] = lastElement; + indices[lastElement] = index; + } count -= 1; indices.Remove(element); -- 2.25.1 From 95b38cb092229229172dcbb37b12760fe2a4dc8c Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 7 Nov 2023 21:41:55 -0800 Subject: [PATCH 07/17] clear entity sets on snapshot --- src/Snapshot.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Snapshot.cs b/src/Snapshot.cs index 61bde14..6d124cb 100644 --- a/src/Snapshot.cs +++ b/src/Snapshot.cs @@ -59,10 +59,14 @@ public class Snapshot // restore entity relation index state // FIXME: arghhhh this is so slow + + foreach (var (id, relationTypeSet) in world.EntityRelationIndex) + { + relationTypeSet.Clear(); + } + foreach (var (id, relationTypeSet) in EntityRelationIndex) { - world.EntityRelationIndex[id].Clear(); - foreach (var typeId in relationTypeSet) { world.EntityRelationIndex[id].Add(typeId); @@ -71,10 +75,14 @@ public class Snapshot // restore entity component index state // FIXME: arrghghhh this is so slow + + foreach (var (id, componentTypeSet) in world.EntityComponentIndex) + { + componentTypeSet.Clear(); + } + foreach (var (id, componentTypeSet) in EntityComponentIndex) { - world.EntityComponentIndex[id].Clear(); - foreach (var typeId in componentTypeSet) { world.EntityComponentIndex[id].Add(typeId); @@ -192,7 +200,6 @@ public class Snapshot private class ComponentSnapshot { - private readonly Dictionary EntityIDToStorageIndex = new Dictionary(); private readonly NativeArray Components; private readonly NativeArray EntityIDs; -- 2.25.1 From 523b6e3ef798fe7e98aa7780fb35e4257c0f8f41 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Wed, 8 Nov 2023 10:17:22 -0800 Subject: [PATCH 08/17] actually add filters to filter index --- src/World.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/World.cs b/src/World.cs index 0dcc689..f139353 100644 --- a/src/World.cs +++ b/src/World.cs @@ -105,6 +105,8 @@ namespace MoonTools.ECS { TypeToFilter[typeId].Add(filter); } + + FilterIndex.Add(signature, filter); } return filter; -- 2.25.1 From f849db955c1071c36ff1509c081b58f88ba993c8 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Wed, 15 Nov 2023 15:10:56 -0800 Subject: [PATCH 09/17] add World.ClearMessages --- src/World.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/World.cs b/src/World.cs index f139353..c7c5d6f 100644 --- a/src/World.cs +++ b/src/World.cs @@ -391,6 +391,12 @@ namespace MoonTools.ECS return MessageIndex[typeId].First(); } + public void ClearMessages() where T : unmanaged + { + var typeId = GetMessageTypeId(); + MessageIndex[typeId].Clear(); + } + // TODO: temporary component storage? public void FinishUpdate() { -- 2.25.1 From 2d333d0651edcb6f0c5cc6b23bbd078a45a0e73e Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Thu, 16 Nov 2023 10:36:11 -0800 Subject: [PATCH 10/17] fix crash on post-snapshot entity ID recycle --- src/IdAssigner.cs | 6 ++---- src/World.cs | 6 +++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/IdAssigner.cs b/src/IdAssigner.cs index 0f11307..5cb4143 100644 --- a/src/IdAssigner.cs +++ b/src/IdAssigner.cs @@ -7,11 +7,9 @@ internal class IdAssigner uint Next; NativeArray AvailableIds = new NativeArray(); - public uint Assign(out bool recycled) + public uint Assign() { - recycled = AvailableIds.TryPop(out var id); - - if (recycled) + if (AvailableIds.TryPop(out var id)) { return id; } diff --git a/src/World.cs b/src/World.cs index c7c5d6f..f6a2ee0 100644 --- a/src/World.cs +++ b/src/World.cs @@ -48,7 +48,7 @@ namespace MoonTools.ECS return TypeToId[typeof(T)]; } - var typeId = new TypeId(TypeIdAssigner.Assign(out var _)); + var typeId = new TypeId(TypeIdAssigner.Assign()); TypeToId.Add(typeof(T), typeId); ElementSizes.Add(typeId, Unsafe.SizeOf()); @@ -116,9 +116,9 @@ namespace MoonTools.ECS public Entity CreateEntity(string tag = "") { - var entity = new Entity(EntityIdAssigner.Assign(out var recycled)); + var entity = new Entity(EntityIdAssigner.Assign()); - if (!recycled) + if (!EntityComponentIndex.ContainsKey(entity)) { EntityRelationIndex.Add(entity, new IndexableSet()); EntityComponentIndex.Add(entity, new IndexableSet()); -- 2.25.1 From d8d021060e181feaef6ffb932b0afcfe5223f12c Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Thu, 16 Nov 2023 12:58:51 -0800 Subject: [PATCH 11/17] handle case where storages are created after snapshot --- src/Snapshot.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Snapshot.cs b/src/Snapshot.cs index 6d124cb..10a080d 100644 --- a/src/Snapshot.cs +++ b/src/Snapshot.cs @@ -43,6 +43,20 @@ public class Snapshot } } + // clear all component storages in case any were created after snapshot + // FIXME: this can be eliminated via component discovery + foreach (var (typeId, componentStorage) in world.ComponentIndex) + { + componentStorage.Clear(); + } + + // clear all relation storages in case any were created after snapshot + // FIXME: this can be eliminated via component discovery + foreach (var (typeId, relationStorage) in world.RelationIndex) + { + relationStorage.Clear(); + } + // restore components foreach (var (typeId, componentSnapshot) in ComponentSnapshots) { -- 2.25.1 From 91da645d7140d288438655cf4a633d8c3a14dc70 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Thu, 16 Nov 2023 14:46:29 -0800 Subject: [PATCH 12/17] EXPERIMENT: try preserving order of indexable set --- src/Collections/IndexableSet.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Collections/IndexableSet.cs b/src/Collections/IndexableSet.cs index 140f9f5..6eaee89 100644 --- a/src/Collections/IndexableSet.cs +++ b/src/Collections/IndexableSet.cs @@ -61,6 +61,7 @@ namespace MoonTools.ECS.Collections return false; } + /* var index = indices[element]; if (index != Count - 1) @@ -71,6 +72,18 @@ namespace MoonTools.ECS.Collections } count -= 1; indices.Remove(element); + */ + + var index = indices[element]; + + for (var i = index; i < Count - 1; i += 1) + { + array[i] = array[i + 1]; + indices[array[i]] = i; + } + + indices.Remove(element); + count -= 1; return true; } -- 2.25.1 From 93c058951ccd1d585d651d34f57645c82a08efe7 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Thu, 16 Nov 2023 15:38:28 -0800 Subject: [PATCH 13/17] commit indexable set change --- src/Collections/IndexableSet.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/Collections/IndexableSet.cs b/src/Collections/IndexableSet.cs index 6eaee89..418749f 100644 --- a/src/Collections/IndexableSet.cs +++ b/src/Collections/IndexableSet.cs @@ -61,19 +61,6 @@ namespace MoonTools.ECS.Collections return false; } - /* - var index = indices[element]; - - if (index != Count - 1) - { - var lastElement = array[Count - 1]; - array[index] = lastElement; - indices[lastElement] = index; - } - count -= 1; - indices.Remove(element); - */ - var index = indices[element]; for (var i = index; i < Count - 1; i += 1) -- 2.25.1 From 61364506179331e05111c1cdd0f97e9757c59638 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Mon, 20 Nov 2023 11:13:41 -0800 Subject: [PATCH 14/17] fix not compiling in release mode --- src/World.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/World.cs b/src/World.cs index f6a2ee0..3ede9ef 100644 --- a/src/World.cs +++ b/src/World.cs @@ -458,6 +458,7 @@ namespace MoonTools.ECS public unsafe Type Current => World.IdToType[Types[ComponentIndex]]; } + #endif protected virtual void Dispose(bool disposing) { @@ -496,6 +497,5 @@ namespace MoonTools.ECS Dispose(disposing: true); GC.SuppressFinalize(this); } -#endif } } -- 2.25.1 From b27875b9a83e206a597fc7225f99c9b4b4df643b Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Mon, 20 Nov 2023 15:11:17 -0800 Subject: [PATCH 15/17] complete World.Dispose --- src/Filter.cs | 33 +++++++++++++++++++++++++++++++-- src/IdAssigner.cs | 32 +++++++++++++++++++++++++++++++- src/World.cs | 26 +++++++++++++++++++++++--- 3 files changed, 85 insertions(+), 6 deletions(-) diff --git a/src/Filter.cs b/src/Filter.cs index a3b48ac..2fd2f19 100644 --- a/src/Filter.cs +++ b/src/Filter.cs @@ -1,8 +1,8 @@ -using MoonTools.ECS.Collections; +using System; +using MoonTools.ECS.Collections; namespace MoonTools.ECS; -// TODO: do we want to get fancy with queries beyond Include and Exclude? public class Filter { private World World; @@ -10,6 +10,8 @@ public class Filter internal IndexableSet EntitySet = new IndexableSet(); + private bool IsDisposed; + public ReverseSpanEnumerator Entities => EntitySet.GetEnumerator(); public bool Empty => EntitySet.Count == 0; @@ -91,4 +93,31 @@ public class Filter public bool MoveNext() => LinearCongruentialEnumerator.MoveNext(); public Entity Current => Filter.NthEntity(LinearCongruentialEnumerator.Current); } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + EntitySet.Dispose(); + } + + IsDisposed = true; + } + } + + // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + // ~Filter() + // { + // // 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/IdAssigner.cs b/src/IdAssigner.cs index 5cb4143..a31e73b 100644 --- a/src/IdAssigner.cs +++ b/src/IdAssigner.cs @@ -1,12 +1,15 @@ +using System; using MoonTools.ECS.Collections; namespace MoonTools.ECS; -internal class IdAssigner +internal class IdAssigner : IDisposable { uint Next; NativeArray AvailableIds = new NativeArray(); + private bool IsDisposed; + public uint Assign() { if (AvailableIds.TryPop(out var id)) @@ -29,4 +32,31 @@ internal class IdAssigner AvailableIds.CopyTo(other.AvailableIds); other.Next = Next; } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + AvailableIds.Dispose(); + } + + IsDisposed = true; + } + } + + // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + // ~IdAssigner() + // { + // // 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/World.cs b/src/World.cs index 3ede9ef..4653a34 100644 --- a/src/World.cs +++ b/src/World.cs @@ -466,7 +466,6 @@ namespace MoonTools.ECS { if (disposing) { - // TODO: dispose managed state (managed objects) foreach (var componentStorage in ComponentIndex.Values) { componentStorage.Dispose(); @@ -476,10 +475,31 @@ namespace MoonTools.ECS { relationStorage.Dispose(); } + + foreach (var messageStorage in MessageIndex.Values) + { + messageStorage.Dispose(); + } + + foreach (var typeSet in EntityComponentIndex.Values) + { + typeSet.Dispose(); + } + + foreach (var typeSet in EntityRelationIndex.Values) + { + typeSet.Dispose(); + } + + foreach (var filter in FilterIndex.Values) + { + filter.Dispose(); + } + + EntityIdAssigner.Dispose(); + TypeIdAssigner.Dispose(); } - // TODO: free unmanaged resources (unmanaged objects) and override finalizer - // TODO: set large fields to null IsDisposed = true; } } -- 2.25.1 From 3b1dafdebdb8fbe3e4617312dd90a452378b3af8 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Mon, 20 Nov 2023 23:13:04 -0800 Subject: [PATCH 16/17] file-scoped namespaces + formatting --- src/Collections/IndexableSet.cs | 156 ++-- src/Collections/NativeArray.cs | 6 +- src/ComponentStorage.cs | 211 +++-- src/Entity.cs | 15 +- src/Enumerators/ReverseSpanEnumerator.cs | 51 +- src/FilterBuilder.cs | 3 +- src/FilterSignature.cs | 91 ++- src/Manipulator.cs | 29 +- src/MessageStorage.cs | 2 +- src/Random.cs | 367 +++++---- src/RandomManager.cs | 129 ++-- src/RelationStorage.cs | 130 ++-- src/Renderer.cs | 9 +- src/Snapshot.cs | 26 +- src/World.cs | 945 +++++++++++------------ 15 files changed, 1074 insertions(+), 1096 deletions(-) diff --git a/src/Collections/IndexableSet.cs b/src/Collections/IndexableSet.cs index 418749f..dcf3fbb 100644 --- a/src/Collections/IndexableSet.cs +++ b/src/Collections/IndexableSet.cs @@ -3,106 +3,104 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace MoonTools.ECS.Collections +namespace MoonTools.ECS.Collections; + +public unsafe class IndexableSet : IDisposable where T : unmanaged { - public unsafe class IndexableSet : IDisposable where T : unmanaged + private Dictionary Indices; + private T* Array; + public int Count { get; private set; } + private int Capacity; + private bool IsDisposed; + + public Span AsSpan() => new Span(Array, Count); + public ReverseSpanEnumerator GetEnumerator() => new ReverseSpanEnumerator(new Span(Array, Count)); + + public IndexableSet(int capacity = 32) { - private Dictionary indices; - private T* array; - private int count; - private int capacity; - private bool disposed; + this.Capacity = capacity; + Count = 0; - public int Count => count; - public Span AsSpan() => new Span(array, count); - public ReverseSpanEnumerator GetEnumerator() => new ReverseSpanEnumerator(new Span(array, count)); + Indices = new Dictionary(capacity); + Array = (T*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf())); + } - public IndexableSet(int capacity = 32) + public T this[int i] => Array[i]; + + public bool Contains(T element) + { + return Indices.ContainsKey(element); + } + + public bool Add(T element) + { + if (!Contains(element)) { - this.capacity = capacity; - count = 0; + Indices.Add(element, Count); - indices = new Dictionary(capacity); - array = (T*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf())); - } - - public T this[int i] => array[i]; - - public bool Contains(T element) - { - return indices.ContainsKey(element); - } - - public bool Add(T element) - { - if (!Contains(element)) + if (Count >= Capacity) { - indices.Add(element, count); - - if (count >= capacity) - { - capacity *= 2; - array = (T*) NativeMemory.Realloc(array, (nuint) (capacity * Unsafe.SizeOf())); - } - - array[count] = element; - count += 1; - - return true; + Capacity *= 2; + Array = (T*) NativeMemory.Realloc(Array, (nuint) (Capacity * Unsafe.SizeOf())); } - return false; - } - - public bool Remove(T element) - { - if (!Contains(element)) - { - return false; - } - - var index = indices[element]; - - for (var i = index; i < Count - 1; i += 1) - { - array[i] = array[i + 1]; - indices[array[i]] = i; - } - - indices.Remove(element); - count -= 1; + Array[Count] = element; + Count += 1; return true; } - public void Clear() + return false; + } + + public bool Remove(T element) + { + if (!Contains(element)) { - indices.Clear(); - count = 0; + return false; } - protected virtual void Dispose(bool disposing) - { - if (!disposed) - { - NativeMemory.Free(array); - array = null; + var index = Indices[element]; - disposed = true; - } + for (var i = index; i < Count - 1; i += 1) + { + Array[i] = Array[i + 1]; + Indices[Array[i]] = i; } - ~IndexableSet() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: false); - } + Indices.Remove(element); + Count -= 1; - public void Dispose() + return true; + } + + public void Clear() + { + Indices.Clear(); + Count = 0; + } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); + NativeMemory.Free(Array); + Array = null; + + IsDisposed = true; } } + + ~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/Collections/NativeArray.cs b/src/Collections/NativeArray.cs index a0af02a..eb897a6 100644 --- a/src/Collections/NativeArray.cs +++ b/src/Collections/NativeArray.cs @@ -14,7 +14,7 @@ public unsafe class NativeArray : IDisposable where T : unmanaged public Span ToSpan() => new Span(Elements, Count); public Span.Enumerator GetEnumerator() => new Span(Elements, Count).GetEnumerator(); - private bool disposed; + private bool IsDisposed; public NativeArray(int capacity = 16) { @@ -96,12 +96,12 @@ public unsafe class NativeArray : IDisposable where T : unmanaged protected virtual void Dispose(bool disposing) { - if (!disposed) + if (!IsDisposed) { NativeMemory.Free(Elements); Elements = null; - disposed = true; + IsDisposed = true; } } diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs index 2571446..f53a8a0 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -2,140 +2,139 @@ using System.Collections.Generic; using MoonTools.ECS.Collections; -namespace MoonTools.ECS +namespace MoonTools.ECS; + +internal class ComponentStorage : IDisposable { - internal unsafe class ComponentStorage : IDisposable + internal readonly Dictionary EntityIDToStorageIndex = new Dictionary(16); + internal readonly NativeArray Components; + internal readonly NativeArray EntityIDs; + internal readonly TypeId TypeId; + + private bool IsDisposed; + + public ComponentStorage(TypeId typeId, int elementSize) { - internal readonly Dictionary EntityIDToStorageIndex = new Dictionary(16); - internal readonly NativeArray Components; - internal readonly NativeArray EntityIDs; - internal readonly TypeId TypeId; + Components = new NativeArray(elementSize); + EntityIDs = new NativeArray(); + TypeId = typeId; + } - private bool disposed; + public bool Any() + { + return Components.Count > 0; + } - public ComponentStorage(TypeId typeId, int elementSize) - { - Components = new NativeArray(elementSize); - EntityIDs = new NativeArray(); - TypeId = typeId; - } + public bool Has(Entity entity) + { + return EntityIDToStorageIndex.ContainsKey(entity); + } - public bool Any() - { - return Components.Count > 0; - } + public ref T Get(in Entity entity) where T : unmanaged + { + return ref Components.Get(EntityIDToStorageIndex[entity]); + } - public bool Has(Entity entity) - { - return EntityIDToStorageIndex.ContainsKey(entity); - } - - public ref T Get(in Entity entity) where T : unmanaged - { - return ref Components.Get(EntityIDToStorageIndex[entity]); - } - - public ref T GetFirst() where T : unmanaged - { + public ref T GetFirst() where T : unmanaged + { #if DEBUG - if (Components.Count == 0) - { - throw new IndexOutOfRangeException("Component storage is empty!"); - } + if (Components.Count == 0) + { + throw new IndexOutOfRangeException("Component storage is empty!"); + } #endif - return ref Components.Get(0); - } + return ref Components.Get(0); + } - // Returns true if the entity had this component. - public bool Set(in Entity entity, in T component) where T : unmanaged + // Returns true if the entity had this component. + public bool Set(in Entity entity, in T component) where T : unmanaged + { + if (EntityIDToStorageIndex.TryGetValue(entity, out var index)) { - if (EntityIDToStorageIndex.TryGetValue(entity, out var index)) - { - Components.Set(index, component); - return true; - } - else - { - EntityIDToStorageIndex[entity] = Components.Count; - EntityIDs.Append(entity); - Components.Append(component); - return false; - } + Components.Set(index, component); + return true; } - - // Returns true if the entity had this component. - public bool Remove(in Entity entity) + else { - if (EntityIDToStorageIndex.TryGetValue(entity, out int index)) - { - var lastElementIndex = Components.Count - 1; - - var lastEntity = EntityIDs[lastElementIndex]; - - // move a component into the hole to maintain contiguous memory - Components.Delete(index); - EntityIDs.Delete(index); - EntityIDToStorageIndex.Remove(entity); - - // update the index if it changed - if (lastElementIndex != index) - { - EntityIDToStorageIndex[lastEntity] = index; - } - - return true; - } - + EntityIDToStorageIndex[entity] = Components.Count; + EntityIDs.Append(entity); + Components.Append(component); return false; } + } - public void Clear() + // Returns true if the entity had this component. + public bool Remove(in Entity entity) + { + if (EntityIDToStorageIndex.TryGetValue(entity, out int index)) { - Components.Clear(); - EntityIDs.Clear(); - EntityIDToStorageIndex.Clear(); - } + var lastElementIndex = Components.Count - 1; - public Entity FirstEntity() - { -#if DEBUG - if (EntityIDs.Count == 0) + var lastEntity = EntityIDs[lastElementIndex]; + + // move a component into the hole to maintain contiguous memory + Components.Delete(index); + EntityIDs.Delete(index); + EntityIDToStorageIndex.Remove(entity); + + // update the index if it changed + if (lastElementIndex != index) { - throw new IndexOutOfRangeException("Component storage is empty!"); + EntityIDToStorageIndex[lastEntity] = index; } -#endif - return EntityIDs[0]; + + return true; } + return false; + } + + public void Clear() + { + Components.Clear(); + EntityIDs.Clear(); + EntityIDToStorageIndex.Clear(); + } + + public Entity FirstEntity() + { #if DEBUG - internal IEnumerable Debug_GetEntities() + if (EntityIDs.Count == 0) { - return EntityIDToStorageIndex.Keys; + throw new IndexOutOfRangeException("Component storage is empty!"); } #endif + return EntityIDs[0]; + } - protected virtual void Dispose(bool disposing) +#if DEBUG + internal IEnumerable Debug_GetEntities() + { + return EntityIDToStorageIndex.Keys; + } +#endif + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) { - if (!disposed) - { - Components.Dispose(); - EntityIDs.Dispose(); + Components.Dispose(); + EntityIDs.Dispose(); - 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); + IsDisposed = 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); + } } diff --git a/src/Entity.cs b/src/Entity.cs index f2d75d9..02ad024 100644 --- a/src/Entity.cs +++ b/src/Entity.cs @@ -1,14 +1,3 @@ -using System; +namespace MoonTools.ECS; -namespace MoonTools.ECS -{ - public readonly record struct Entity - { - public uint ID { get; } - - internal Entity(uint id) - { - ID = id; - } - } -} +public readonly record struct Entity(uint ID); diff --git a/src/Enumerators/ReverseSpanEnumerator.cs b/src/Enumerators/ReverseSpanEnumerator.cs index ce526a5..84563a9 100644 --- a/src/Enumerators/ReverseSpanEnumerator.cs +++ b/src/Enumerators/ReverseSpanEnumerator.cs @@ -1,36 +1,35 @@ using System; using System.Runtime.CompilerServices; -namespace MoonTools.ECS +namespace MoonTools.ECS; + +public ref struct ReverseSpanEnumerator { - public ref struct ReverseSpanEnumerator + private ReadOnlySpan Span; + private int Index; + + public ReverseSpanEnumerator GetEnumerator() => this; + + public T Current => Span[Index]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() { - private ReadOnlySpan Span; - private int index; - - public ReverseSpanEnumerator GetEnumerator() => this; - - public T Current => Span[index]; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() + if (Index > 0) { - if (index > 0) - { - index -= 1; - return true; - } - - return false; + Index -= 1; + return true; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReverseSpanEnumerator(Span span) - { - Span = span; - index = span.Length; - } - - public static ReverseSpanEnumerator Empty => new ReverseSpanEnumerator(); + return false; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReverseSpanEnumerator(Span span) + { + Span = span; + Index = span.Length; + } + + public static ReverseSpanEnumerator Empty => new ReverseSpanEnumerator(); } diff --git a/src/FilterBuilder.cs b/src/FilterBuilder.cs index 316052e..3e3ef6d 100644 --- a/src/FilterBuilder.cs +++ b/src/FilterBuilder.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using MoonTools.ECS.Collections; +using MoonTools.ECS.Collections; namespace MoonTools.ECS { diff --git a/src/FilterSignature.cs b/src/FilterSignature.cs index e48fa83..8a27433 100644 --- a/src/FilterSignature.cs +++ b/src/FilterSignature.cs @@ -1,70 +1,69 @@ using System; using MoonTools.ECS.Collections; -namespace MoonTools.ECS +namespace MoonTools.ECS; + +public struct FilterSignature : IEquatable { - public struct FilterSignature : IEquatable + public readonly IndexableSet Included; + public readonly IndexableSet Excluded; + + public FilterSignature(IndexableSet included, IndexableSet excluded) { - public readonly IndexableSet Included; - public readonly IndexableSet Excluded; + Included = included; + Excluded = excluded; + } - public FilterSignature(IndexableSet included, IndexableSet excluded) - { - Included = included; - Excluded = excluded; - } + public override bool Equals(object? obj) + { + return obj is FilterSignature signature && Equals(signature); + } - public override bool Equals(object? obj) + public bool Equals(FilterSignature other) + { + foreach (var included in Included) { - return obj is FilterSignature signature && Equals(signature); - } - - public bool Equals(FilterSignature other) - { - foreach (var included in Included) + if (!other.Included.Contains(included)) { - if (!other.Included.Contains(included)) - { - return false; - } + return false; } - - foreach (var excluded in Excluded) - { - if (!other.Excluded.Contains(excluded)) - { - return false; - } - } - - return true; } - public override int GetHashCode() + foreach (var excluded in Excluded) { - var hashcode = 1; - - foreach (var type in Included) + if (!other.Excluded.Contains(excluded)) { - hashcode = HashCode.Combine(hashcode, type); + return false; } - - foreach (var type in Excluded) - { - hashcode = HashCode.Combine(hashcode, type); - } - - return hashcode; } - public static bool operator ==(FilterSignature left, FilterSignature right) + return true; + } + + public override int GetHashCode() + { + var hashcode = 1; + + foreach (var type in Included) { - return left.Equals(right); + hashcode = HashCode.Combine(hashcode, type); } - public static bool operator !=(FilterSignature left, FilterSignature right) + foreach (var type in Excluded) { - return !(left == right); + hashcode = HashCode.Combine(hashcode, type); } + + return hashcode; + } + + public static bool operator ==(FilterSignature left, FilterSignature right) + { + return left.Equals(right); + } + + public static bool operator !=(FilterSignature left, FilterSignature right) + { + return !(left == right); } } diff --git a/src/Manipulator.cs b/src/Manipulator.cs index 2e7da9a..1e8b6cf 100644 --- a/src/Manipulator.cs +++ b/src/Manipulator.cs @@ -1,19 +1,18 @@ -namespace MoonTools.ECS +namespace MoonTools.ECS; + +public abstract class Manipulator : EntityComponentReader { - public abstract class Manipulator : EntityComponentReader + public Manipulator(World world) : base(world) { - public Manipulator(World world) : base(world) - { - } - - protected Entity CreateEntity(string tag = "") => World.CreateEntity(tag); - protected void Tag(Entity entity, string tag) => World.Tag(entity, tag); - protected void Set(in Entity entity, in TComponent component) where TComponent : unmanaged => World.Set(entity, component); - protected void Remove(in Entity entity) where TComponent : unmanaged => World.Remove(entity); - - protected void Relate(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged => World.Relate(entityA, entityB, relationData); - protected void Unrelate(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged => World.Unrelate(entityA, entityB); - protected void UnrelateAll(in Entity entity) where TRelationKind : unmanaged => World.UnrelateAll(entity); - protected void Destroy(in Entity entity) => World.Destroy(entity); } + + protected Entity CreateEntity(string tag = "") => World.CreateEntity(tag); + protected void Tag(Entity entity, string tag) => World.Tag(entity, tag); + protected void Set(in Entity entity, in TComponent component) where TComponent : unmanaged => World.Set(entity, component); + protected void Remove(in Entity entity) where TComponent : unmanaged => World.Remove(entity); + + protected void Relate(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged => World.Relate(entityA, entityB, relationData); + protected void Unrelate(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged => World.Unrelate(entityA, entityB); + protected void UnrelateAll(in Entity entity) where TRelationKind : unmanaged => World.UnrelateAll(entity); + protected void Destroy(in Entity entity) => World.Destroy(entity); } diff --git a/src/MessageStorage.cs b/src/MessageStorage.cs index 324547a..f7e31d3 100644 --- a/src/MessageStorage.cs +++ b/src/MessageStorage.cs @@ -39,7 +39,7 @@ public class MessageStorage : IDisposable Messages.Clear(); } - protected virtual void Dispose(bool disposing) + private void Dispose(bool disposing) { if (!IsDisposed) { diff --git a/src/Random.cs b/src/Random.cs index 6f8b258..ea20df1 100644 --- a/src/Random.cs +++ b/src/Random.cs @@ -1,209 +1,208 @@ using System; using System.Runtime.CompilerServices; -namespace MoonTools.ECS +namespace MoonTools.ECS; + +/// +/// This class implements the well equidistributed long-period linear pseudorandom number generator. +/// Code taken from Chris Lomont: http://lomont.org/papers/2008/Lomont_PRNG_2008.pdf +/// +public class Random { + public const int STATE_BYTE_COUNT = 68; // 16 state ints + 1 index int + + uint[] State = new uint[16]; + uint Index = 0; + uint Seed; + /// - /// This class implements the well equidistributed long-period linear pseudorandom number generator. - /// Code taken from Chris Lomont: http://lomont.org/papers/2008/Lomont_PRNG_2008.pdf + /// Initializes the RNG with an arbitrary seed. /// - public class Random + public Random() { - public const int STATE_BYTE_COUNT = 68; // 16 state ints + 1 index int + Init((uint) Environment.TickCount); + } - uint[] State = new uint[16]; - uint Index = 0; - uint Seed; - - /// - /// Initializes the RNG with an arbitrary seed. - /// - public Random() + /// + /// Initializes the RNG with a given seed. + /// + public void Init(uint seed) + { + Seed = seed; + uint s = seed; + for (int i = 0; i < 16; i++) { - Init((uint) Environment.TickCount); + s = (((s * 214013 + 2531011) >> 16) & 0x7fffffff) | 0; + State[i] = ~ ~s; //i ; } + Index = 0; + } - /// - /// Initializes the RNG with a given seed. - /// - public void Init(uint seed) + /// + /// Returns the seed that was used to initialize the RNG. + /// + public uint GetSeed() + { + return Seed; + } + + /// + /// Returns the entire state of the RNG as a string. + /// + public string PrintState() + { + var s = ""; + for (var i = 0; i < 16; i += 1) { - Seed = seed; - uint s = seed; - for (int i = 0; i < 16; i++) - { - s = (((s * 214013 + 2531011) >> 16) & 0x7fffffff) | 0; - State[i] = ~ ~s; //i ; - } - Index = 0; + s += State[i]; } + s += Index; + return s; + } - /// - /// Returns the seed that was used to initialize the RNG. - /// - public uint GetSeed() + /// + /// Saves the entire state of the RNG to a Span. + /// + /// Must be a span of at least STATE_BYTE_COUNT bytes. + /// Thrown if the byte span is too short. + public unsafe void SaveState(Span bytes) + { +#if DEBUG + if (bytes.Length < STATE_BYTE_COUNT) { - return Seed; + throw new ArgumentException("Byte span too short!"); } +#endif - /// - /// Returns the entire state of the RNG as a string. - /// - public string PrintState() + fixed (byte* ptr = bytes) { - var s = ""; + var offset = 0; for (var i = 0; i < 16; i += 1) { - s += State[i]; + Unsafe.Write(ptr + offset, State[i]); + offset += 4; } - s += Index; - return s; - } - /// - /// Saves the entire state of the RNG to a Span. - /// - /// Must be a span of at least STATE_BYTE_COUNT bytes. - /// Thrown if the byte span is too short. - public unsafe void SaveState(Span bytes) - { - #if DEBUG - if (bytes.Length < STATE_BYTE_COUNT) - { - throw new ArgumentException("Byte span too short!"); - } - #endif - - fixed (byte* ptr = bytes) - { - var offset = 0; - for (var i = 0; i < 16; i += 1) - { - Unsafe.Write(ptr + offset, State[i]); - offset += 4; - } - - Unsafe.Write(ptr + offset, Index); - } - } - - /// - /// Loads the entire state of the RNG from a Span. - /// - /// Must be a span of at least STATE_BYTE_COUNT bytes. - /// Thrown if the byte span is too short. - public unsafe void LoadState(Span bytes) - { - #if DEBUG - if (bytes.Length < STATE_BYTE_COUNT) - { - throw new ArgumentException("Byte span too short!"); - } - #endif - - fixed (byte* ptr = bytes) - { - var offset = 0; - - for (var i = 0; i < 16; i += 1) - { - State[i] = Unsafe.Read(ptr + offset); - offset += 4; - } - - Index = Unsafe.Read(ptr + offset); - } - } - - private uint NextInternal() - { - uint a, b, c, d; - a = State[Index]; - c = State[(Index+13)&15]; - b = a^c^(a<<16)^(c<<15); - c = State[(Index+9)&15]; - c ^= (c>>11); - a = State[Index] = b^c; - d = (uint) (a ^((a<<5)&0xDA442D24UL)); - Index = (Index + 15)&15; - a = State[Index]; - State[Index] = a^b^d^(a<<2)^(b<<18)^(c<<28); - return State[Index]; - } - - /// - /// Returns a non-negative signed integer. - /// - public int Next() - { - return (int) (NextInternal() >>> 1); // unsigned bitshift right to get rid of signed bit - } - - /// - /// Returns a non-negative signed integer less than max. - /// - public int Next(int max) - { - return (int) (((double) Next()) * max / int.MaxValue); - } - - /// - /// Returns a signed integer greater than or equal to min and less than max. - /// - public int Next(int min, int max) - { - var diff = max - min; - var next = Next(diff); - return min + next; - } - - /// - /// Returns a non-negative signed 64 bit integer. - /// - public long NextInt64() - { - long next = NextInternal(); - next <<= 32; - next |= NextInternal(); - next >>>= 1; - return next; - } - - /// - /// Returns a non-negative signed 64 bit integer less than max. - /// - public long NextInt64(long max) - { - var next = NextInt64(); - return (long) (((double) next) * max / long.MaxValue); - } - - /// - /// Returns a non-negative signed 64 bit integer greater than or equal to min and less than max. - /// - public long NextInt64(long min, long max) - { - var diff = max - min; - var next = NextInt64(diff); - return min + next; - } - - /// - /// Returns a single-precision floating point value between 0 and 1. - /// - public float NextSingle() - { - var n = NextInternal(); - return ((float) n) / uint.MaxValue; - } - - /// - /// Returns a double-precision floating point value between 0 and 1. - /// - public double NextDouble() - { - var n = NextInternal(); - return ((double) n) / uint.MaxValue; + Unsafe.Write(ptr + offset, Index); } } + + /// + /// Loads the entire state of the RNG from a Span. + /// + /// Must be a span of at least STATE_BYTE_COUNT bytes. + /// Thrown if the byte span is too short. + public unsafe void LoadState(Span bytes) + { +#if DEBUG + if (bytes.Length < STATE_BYTE_COUNT) + { + throw new ArgumentException("Byte span too short!"); + } +#endif + + fixed (byte* ptr = bytes) + { + var offset = 0; + + for (var i = 0; i < 16; i += 1) + { + State[i] = Unsafe.Read(ptr + offset); + offset += 4; + } + + Index = Unsafe.Read(ptr + offset); + } + } + + private uint NextInternal() + { + uint a, b, c, d; + a = State[Index]; + c = State[(Index+13)&15]; + b = a^c^(a<<16)^(c<<15); + c = State[(Index+9)&15]; + c ^= (c>>11); + a = State[Index] = b^c; + d = (uint) (a ^((a<<5)&0xDA442D24UL)); + Index = (Index + 15)&15; + a = State[Index]; + State[Index] = a^b^d^(a<<2)^(b<<18)^(c<<28); + return State[Index]; + } + + /// + /// Returns a non-negative signed integer. + /// + public int Next() + { + return (int) (NextInternal() >>> 1); // unsigned bitshift right to get rid of signed bit + } + + /// + /// Returns a non-negative signed integer less than max. + /// + public int Next(int max) + { + return (int) (((double) Next()) * max / int.MaxValue); + } + + /// + /// Returns a signed integer greater than or equal to min and less than max. + /// + public int Next(int min, int max) + { + var diff = max - min; + var next = Next(diff); + return min + next; + } + + /// + /// Returns a non-negative signed 64 bit integer. + /// + public long NextInt64() + { + long next = NextInternal(); + next <<= 32; + next |= NextInternal(); + next >>>= 1; + return next; + } + + /// + /// Returns a non-negative signed 64 bit integer less than max. + /// + public long NextInt64(long max) + { + var next = NextInt64(); + return (long) (((double) next) * max / long.MaxValue); + } + + /// + /// Returns a non-negative signed 64 bit integer greater than or equal to min and less than max. + /// + public long NextInt64(long min, long max) + { + var diff = max - min; + var next = NextInt64(diff); + return min + next; + } + + /// + /// Returns a single-precision floating point value between 0 and 1. + /// + public float NextSingle() + { + var n = NextInternal(); + return ((float) n) / uint.MaxValue; + } + + /// + /// Returns a double-precision floating point value between 0 and 1. + /// + public double NextDouble() + { + var n = NextInternal(); + return ((double) n) / uint.MaxValue; + } } diff --git a/src/RandomManager.cs b/src/RandomManager.cs index cf90d75..b0d00dd 100644 --- a/src/RandomManager.cs +++ b/src/RandomManager.cs @@ -1,82 +1,81 @@ using System.Runtime.CompilerServices; -namespace MoonTools.ECS +namespace MoonTools.ECS; + +public static class RandomManager { - public static class RandomManager + private static Random Random = new Random(); + + private static int[] Primes = { - private static Random Random = new Random(); + 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997,1009,1013,1019,1021,1031,1033,1039,1049,1051,1061,1063,1069,1087,1091,1093,1097,1103,1109,1117,1123,1129,1151,1153,1163,1171,1181,1187,1193,1201,1213,1217,1223,1229,1231,1237,1249,1259,1277,1279,1283,1289,1291,1297,1301,1303,1307,1319,1321,1327,1361,1367,1373,1381,1399,1409,1423,1427,1429,1433,1439,1447,1451,1453,1459,1471,1481,1483,1487,1489,1493,1499,1511,1523,1531,1543,1549,1553,1559,1567,1571,1579,1583,1597,1601,1607,1609,1613,1619,1621,1627,1637,1657,1663,1667,1669,1693,1697,1699,1709,1721,1723,1733,1741,1747,1753,1759,1777,1783,1787,1789,1801,1811,1823,1831,1847,1861,1867,1871,1873,1877,1879,1889,1901,1907,1913,1931,1933,1949,1951,1973,1979,1987,1993,1997,1999,2003,2011,2017,2027,2029,2039,2053,2063,2069,2081,2083,2087,2089,2099,2111,2113,2129,2131,2137,2141,2143,2153,2161,2179,2203,2207,2213,2221,2237,2239,2243,2251,2267,2269,2273,2281,2287,2293,2297,2309,2311,2333,2339,2341,2347,2351,2357,2371,2377,2381,2383,2389,2393,2399,2411,2417,2423,2437,2441,2447,2459,2467,2473,2477,2503,2521,2531,2539,2543,2549,2551,2557,2579,2591,2593,2609,2617,2621,2633,2647,2657,2659,2663,2671,2677,2683,2687,2689,2693,2699,2707,2711,2713,2719,2729,2731,2741,2749,2753,2767,2777,2789,2791,2797,2801,2803,2819,2833,2837,2843,2851,2857,2861,2879,2887,2897,2903,2909,2917,2927,2939,2953,2957,2963,2969,2971,2999,3001,3011,3019,3023,3037,3041,3049,3061,3067,3079,3083,3089,3109,3119,3121,3137,3163,3167,3169,3181,3187,3191,3203,3209,3217,3221,3229,3251,3253,3257,3259,3271,3299,3301,3307,3313,3319,3323,3329,3331,3343,3347,3359,3361,3371,3373,3389,3391,3407,3413,3433,3449,3457,3461,3463,3467,3469,3491,3499,3511,3517,3527,3529,3533,3539,3541,3547,3557,3559,3571,3581,3583,3593,3607,3613,3617,3623,3631,3637,3643,3659,3671,3673,3677,3691,3697,3701,3709,3719,3727,3733,3739,3761,3767,3769,3779,3793,3797,3803,3821,3823,3833,3847,3851,3853,3863,3877,3881,3889,3907,3911,3917,3919,3923,3929,3931,3943,3947,3967,3989,4001,4003,4007,4013,4019,4021,4027,4049,4051,4057,4073,4079,4091,4093,4099,4111,4127,4129,4133,4139,4153,4157,4159,4177,4201,4211,4217,4219,4229,4231,4241,4243,4253,4259,4261,4271,4273,4283,4289,4297,4327,4337,4339,4349,4357,4363,4373,4391,4397,4409,4421,4423,4441,4447,4451,4457,4463,4481,4483,4493,4507,4513,4517,4519,4523,4547,4549,4561,4567,4583,4591,4597,4603,4621,4637,4639,4643,4649,4651,4657,4663,4673,4679,4691,4703,4721,4723,4729,4733,4751,4759,4783,4787,4789,4793,4799,4801,4813,4817,4831,4861,4871,4877,4889,4903,4909,4919,4931,4933,4937,4943,4951,4957,4967,4969,4973,4987,4993,4999,5003,5009,5011,5021,5023,5039,5051,5059,5077,5081,5087,5099,5101,5107,5113,5119,5147,5153,5167,5171,5179,5189,5197,5209,5227,5231,5233,5237,5261,5273,5279,5281,5297,5303,5309,5323,5333,5347,5351,5381,5387,5393,5399,5407,5413,5417,5419,5431,5437,5441,5443,5449,5471,5477,5479,5483,5501,5503,5507,5519,5521,5527,5531,5557,5563,5569,5573,5581,5591,5623,5639,5641,5647,5651,5653,5657,5659,5669,5683,5689,5693,5701,5711,5717,5737,5741,5743,5749,5779,5783,5791,5801,5807,5813,5821,5827,5839,5843,5849,5851,5857,5861,5867,5869,5879,5881,5897,5903,5923,5927,5939,5953,5981,5987,6007,6011,6029,6037,6043,6047,6053,6067,6073,6079,6089,6091,6101,6113,6121,6131,6133,6143,6151,6163,6173,6197,6199,6203,6211,6217,6221,6229,6247,6257,6263,6269,6271,6277,6287,6299,6301,6311,6317,6323,6329,6337,6343,6353,6359,6361,6367,6373,6379,6389,6397,6421,6427,6449,6451,6469,6473,6481,6491,6521,6529,6547,6551,6553,6563,6569,6571,6577,6581,6599,6607,6619,6637,6653,6659,6661,6673,6679,6689,6691,6701,6703,6709,6719,6733,6737,6761,6763,6779,6781,6791,6793,6803,6823,6827,6829,6833,6841,6857,6863,6869,6871,6883,6899,6907,6911,6917,6947,6949,6959,6961,6967,6971,6977,6983,6991,6997,7001,7013,7019,7027,7039,7043,7057,7069,7079,7103,7109,7121,7127,7129,7151,7159,7177,7187,7193,7207,7211,7213,7219,7229,7237,7243,7247,7253,7283,7297,7307,7309,7321,7331,7333,7349,7351,7369,7393,7411,7417,7433,7451,7457,7459,7477,7481,7487,7489,7499,7507,7517,7523,7529,7537,7541,7547,7549,7559,7561,7573,7577,7583,7589,7591,7603,7607,7621,7639,7643,7649,7669,7673,7681,7687,7691,7699,7703,7717,7723,7727,7741,7753,7757,7759,7789,7793,7817,7823,7829,7841,7853,7867,7873,7877,7879,7883,7901,7907,7919 + }; - private static int[] Primes = - { - 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997,1009,1013,1019,1021,1031,1033,1039,1049,1051,1061,1063,1069,1087,1091,1093,1097,1103,1109,1117,1123,1129,1151,1153,1163,1171,1181,1187,1193,1201,1213,1217,1223,1229,1231,1237,1249,1259,1277,1279,1283,1289,1291,1297,1301,1303,1307,1319,1321,1327,1361,1367,1373,1381,1399,1409,1423,1427,1429,1433,1439,1447,1451,1453,1459,1471,1481,1483,1487,1489,1493,1499,1511,1523,1531,1543,1549,1553,1559,1567,1571,1579,1583,1597,1601,1607,1609,1613,1619,1621,1627,1637,1657,1663,1667,1669,1693,1697,1699,1709,1721,1723,1733,1741,1747,1753,1759,1777,1783,1787,1789,1801,1811,1823,1831,1847,1861,1867,1871,1873,1877,1879,1889,1901,1907,1913,1931,1933,1949,1951,1973,1979,1987,1993,1997,1999,2003,2011,2017,2027,2029,2039,2053,2063,2069,2081,2083,2087,2089,2099,2111,2113,2129,2131,2137,2141,2143,2153,2161,2179,2203,2207,2213,2221,2237,2239,2243,2251,2267,2269,2273,2281,2287,2293,2297,2309,2311,2333,2339,2341,2347,2351,2357,2371,2377,2381,2383,2389,2393,2399,2411,2417,2423,2437,2441,2447,2459,2467,2473,2477,2503,2521,2531,2539,2543,2549,2551,2557,2579,2591,2593,2609,2617,2621,2633,2647,2657,2659,2663,2671,2677,2683,2687,2689,2693,2699,2707,2711,2713,2719,2729,2731,2741,2749,2753,2767,2777,2789,2791,2797,2801,2803,2819,2833,2837,2843,2851,2857,2861,2879,2887,2897,2903,2909,2917,2927,2939,2953,2957,2963,2969,2971,2999,3001,3011,3019,3023,3037,3041,3049,3061,3067,3079,3083,3089,3109,3119,3121,3137,3163,3167,3169,3181,3187,3191,3203,3209,3217,3221,3229,3251,3253,3257,3259,3271,3299,3301,3307,3313,3319,3323,3329,3331,3343,3347,3359,3361,3371,3373,3389,3391,3407,3413,3433,3449,3457,3461,3463,3467,3469,3491,3499,3511,3517,3527,3529,3533,3539,3541,3547,3557,3559,3571,3581,3583,3593,3607,3613,3617,3623,3631,3637,3643,3659,3671,3673,3677,3691,3697,3701,3709,3719,3727,3733,3739,3761,3767,3769,3779,3793,3797,3803,3821,3823,3833,3847,3851,3853,3863,3877,3881,3889,3907,3911,3917,3919,3923,3929,3931,3943,3947,3967,3989,4001,4003,4007,4013,4019,4021,4027,4049,4051,4057,4073,4079,4091,4093,4099,4111,4127,4129,4133,4139,4153,4157,4159,4177,4201,4211,4217,4219,4229,4231,4241,4243,4253,4259,4261,4271,4273,4283,4289,4297,4327,4337,4339,4349,4357,4363,4373,4391,4397,4409,4421,4423,4441,4447,4451,4457,4463,4481,4483,4493,4507,4513,4517,4519,4523,4547,4549,4561,4567,4583,4591,4597,4603,4621,4637,4639,4643,4649,4651,4657,4663,4673,4679,4691,4703,4721,4723,4729,4733,4751,4759,4783,4787,4789,4793,4799,4801,4813,4817,4831,4861,4871,4877,4889,4903,4909,4919,4931,4933,4937,4943,4951,4957,4967,4969,4973,4987,4993,4999,5003,5009,5011,5021,5023,5039,5051,5059,5077,5081,5087,5099,5101,5107,5113,5119,5147,5153,5167,5171,5179,5189,5197,5209,5227,5231,5233,5237,5261,5273,5279,5281,5297,5303,5309,5323,5333,5347,5351,5381,5387,5393,5399,5407,5413,5417,5419,5431,5437,5441,5443,5449,5471,5477,5479,5483,5501,5503,5507,5519,5521,5527,5531,5557,5563,5569,5573,5581,5591,5623,5639,5641,5647,5651,5653,5657,5659,5669,5683,5689,5693,5701,5711,5717,5737,5741,5743,5749,5779,5783,5791,5801,5807,5813,5821,5827,5839,5843,5849,5851,5857,5861,5867,5869,5879,5881,5897,5903,5923,5927,5939,5953,5981,5987,6007,6011,6029,6037,6043,6047,6053,6067,6073,6079,6089,6091,6101,6113,6121,6131,6133,6143,6151,6163,6173,6197,6199,6203,6211,6217,6221,6229,6247,6257,6263,6269,6271,6277,6287,6299,6301,6311,6317,6323,6329,6337,6343,6353,6359,6361,6367,6373,6379,6389,6397,6421,6427,6449,6451,6469,6473,6481,6491,6521,6529,6547,6551,6553,6563,6569,6571,6577,6581,6599,6607,6619,6637,6653,6659,6661,6673,6679,6689,6691,6701,6703,6709,6719,6733,6737,6761,6763,6779,6781,6791,6793,6803,6823,6827,6829,6833,6841,6857,6863,6869,6871,6883,6899,6907,6911,6917,6947,6949,6959,6961,6967,6971,6977,6983,6991,6997,7001,7013,7019,7027,7039,7043,7057,7069,7079,7103,7109,7121,7127,7129,7151,7159,7177,7187,7193,7207,7211,7213,7219,7229,7237,7243,7247,7253,7283,7297,7307,7309,7321,7331,7333,7349,7351,7369,7393,7411,7417,7433,7451,7457,7459,7477,7481,7487,7489,7499,7507,7517,7523,7529,7537,7541,7547,7549,7559,7561,7573,7577,7583,7589,7591,7603,7607,7621,7639,7643,7649,7669,7673,7681,7687,7691,7699,7703,7717,7723,7727,7741,7753,7757,7759,7789,7793,7817,7823,7829,7841,7853,7867,7873,7877,7879,7883,7901,7907,7919 - }; - - public static void SetRandom(Random random) - { - Random = random; - } - - internal static int Next(int maxValue) - { - return Random.Next(maxValue); - } - - /// - /// A psuedorandom nonrepeating sequence of integers from 0 to n. - /// - internal static LinearCongruentialEnumerator LinearCongruentialSequence(int n) - { - if (n == 0) - { - // bail out, empty enumerator - return new LinearCongruentialEnumerator(0, 0, 0); - } - - var x = Primes[Random.Next(Primes.Length - 1)]; - while (x % n == 0) - { - // not coprime, try again - x = Primes[Random.Next(Primes.Length - 1)]; - } - - return new LinearCongruentialEnumerator(Random.Next(n), x, n); - } + public static void SetRandom(Random random) + { + Random = random; } - public struct LinearCongruentialEnumerator + internal static int Next(int maxValue) { - private readonly int start; - private readonly int count; - private readonly int prime; - private int current; + return Random.Next(maxValue); + } - public LinearCongruentialEnumerator GetEnumerator() => this; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal LinearCongruentialEnumerator(int start, int prime, int count) + /// + /// A psuedorandom nonrepeating sequence of integers from 0 to n. + /// + internal static LinearCongruentialEnumerator LinearCongruentialSequence(int n) + { + if (n == 0) { - current = start; - this.start = start; - this.prime = prime; - this.count = count; + // bail out, empty enumerator + return new LinearCongruentialEnumerator(0, 0, 0); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() + var x = Primes[Random.Next(Primes.Length - 1)]; + while (x % n == 0) { - current += 1; - if (current < start + count) - { - return true; - } - - return false; + // not coprime, try again + x = Primes[Random.Next(Primes.Length - 1)]; } - public int Current - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (current * prime) % count; - } + return new LinearCongruentialEnumerator(Random.Next(n), x, n); + } +} + +public struct LinearCongruentialEnumerator +{ + private readonly int start; + private readonly int count; + private readonly int prime; + private int current; + + public LinearCongruentialEnumerator GetEnumerator() => this; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal LinearCongruentialEnumerator(int start, int prime, int count) + { + current = start; + this.start = start; + this.prime = prime; + this.count = count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + current += 1; + if (current < start + count) + { + return true; + } + + return false; + } + + public int Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (current * prime) % count; } } diff --git a/src/RelationStorage.cs b/src/RelationStorage.cs index 31a12fa..40b64ef 100644 --- a/src/RelationStorage.cs +++ b/src/RelationStorage.cs @@ -8,67 +8,67 @@ namespace MoonTools.ECS; // TODO: implement this entire class with NativeMemory equivalents, can just memcpy for snapshots internal class RelationStorage { - internal NativeArray relations; - internal NativeArray relationDatas; - internal Dictionary<(Entity, Entity), int> indices = new Dictionary<(Entity, Entity), int>(16); - internal Dictionary> outRelations = new Dictionary>(16); - internal Dictionary> inRelations = new Dictionary>(16); - private Stack> listPool = new Stack>(); + internal NativeArray Relations; + internal NativeArray RelationDatas; + internal Dictionary<(Entity, Entity), int> Indices = new Dictionary<(Entity, Entity), int>(16); + internal Dictionary> OutRelationSets = new Dictionary>(16); + internal Dictionary> InRelationSets = new Dictionary>(16); + private Stack> ListPool = new Stack>(); - private bool disposed; + private bool IsDisposed; public RelationStorage(int relationDataSize) { - relations = new NativeArray(Unsafe.SizeOf<(Entity, Entity)>()); - relationDatas = new NativeArray(relationDataSize); + Relations = new NativeArray(Unsafe.SizeOf<(Entity, Entity)>()); + RelationDatas = new NativeArray(relationDataSize); } public ReverseSpanEnumerator<(Entity, Entity)> All() { - return new ReverseSpanEnumerator<(Entity, Entity)>(relations.ToSpan<(Entity, Entity)>()); + return new ReverseSpanEnumerator<(Entity, Entity)>(Relations.ToSpan<(Entity, Entity)>()); } public unsafe void Set(in Entity entityA, in Entity entityB, in T relationData) where T : unmanaged { var relation = (entityA, entityB); - if (indices.TryGetValue(relation, out var index)) + if (Indices.TryGetValue(relation, out var index)) { - relationDatas.Set(index, relationData); + RelationDatas.Set(index, relationData); return; } - if (!outRelations.ContainsKey(entityA)) + if (!OutRelationSets.ContainsKey(entityA)) { - outRelations[entityA] = AcquireHashSetFromPool(); + OutRelationSets[entityA] = AcquireHashSetFromPool(); } - outRelations[entityA].Add(entityB); + OutRelationSets[entityA].Add(entityB); - if (!inRelations.ContainsKey(entityB)) + if (!InRelationSets.ContainsKey(entityB)) { - inRelations[entityB] = AcquireHashSetFromPool(); + InRelationSets[entityB] = AcquireHashSetFromPool(); } - inRelations[entityB].Add(entityA); + InRelationSets[entityB].Add(entityA); - relations.Append(relation); - relationDatas.Append(relationData); - indices.Add(relation, relations.Count - 1); + Relations.Append(relation); + RelationDatas.Append(relationData); + Indices.Add(relation, Relations.Count - 1); } public ref T Get(in Entity entityA, in Entity entityB) where T : unmanaged { - var relationIndex = indices[(entityA, entityB)]; - return ref relationDatas.Get(relationIndex); + var relationIndex = Indices[(entityA, entityB)]; + return ref RelationDatas.Get(relationIndex); } public bool Has(Entity entityA, Entity entityB) { - return indices.ContainsKey((entityA, entityB)); + return Indices.ContainsKey((entityA, entityB)); } public ReverseSpanEnumerator OutRelations(Entity Entity) { - if (outRelations.TryGetValue(Entity, out var entityOutRelations)) + if (OutRelationSets.TryGetValue(Entity, out var entityOutRelations)) { return entityOutRelations.GetEnumerator(); } @@ -86,27 +86,27 @@ internal class RelationStorage public Entity OutNth(Entity Entity, int n) { #if DEBUG - if (!outRelations.ContainsKey(Entity) || outRelations[Entity].Count == 0) + if (!OutRelationSets.ContainsKey(Entity) || OutRelationSets[Entity].Count == 0) { throw new KeyNotFoundException("No out relations to this entity!"); } #endif - return outRelations[Entity][n]; + return OutRelationSets[Entity][n]; } public bool HasOutRelation(Entity Entity) { - return outRelations.ContainsKey(Entity) && outRelations[Entity].Count > 0; + return OutRelationSets.ContainsKey(Entity) && OutRelationSets[Entity].Count > 0; } public int OutRelationCount(Entity Entity) { - return outRelations.TryGetValue(Entity, out var entityOutRelations) ? entityOutRelations.Count : 0; + return OutRelationSets.TryGetValue(Entity, out var entityOutRelations) ? entityOutRelations.Count : 0; } public ReverseSpanEnumerator InRelations(Entity Entity) { - if (inRelations.TryGetValue(Entity, out var entityInRelations)) + if (InRelationSets.TryGetValue(Entity, out var entityInRelations)) { return entityInRelations.GetEnumerator(); } @@ -124,23 +124,23 @@ internal class RelationStorage public Entity InNth(Entity Entity, int n) { #if DEBUG - if (!inRelations.ContainsKey(Entity) || inRelations[Entity].Count == 0) + if (!InRelationSets.ContainsKey(Entity) || InRelationSets[Entity].Count == 0) { throw new KeyNotFoundException("No in relations to this entity!"); } #endif - return inRelations[Entity][n]; + return InRelationSets[Entity][n]; } public bool HasInRelation(Entity Entity) { - return inRelations.ContainsKey(Entity) && inRelations[Entity].Count > 0; + return InRelationSets.ContainsKey(Entity) && InRelationSets[Entity].Count > 0; } public int InRelationCount(Entity Entity) { - return inRelations.TryGetValue(Entity, out var entityInRelations) ? entityInRelations.Count : 0; + return InRelationSets.TryGetValue(Entity, out var entityInRelations) ? entityInRelations.Count : 0; } public (bool, bool) Remove(in Entity entityA, in Entity entityB) @@ -149,39 +149,39 @@ internal class RelationStorage var bEmpty = false; var relation = (entityA, entityB); - if (outRelations.TryGetValue(entityA, out var entityOutRelations)) + if (OutRelationSets.TryGetValue(entityA, out var entityOutRelations)) { entityOutRelations.Remove(entityB); - if (outRelations[entityA].Count == 0) + if (OutRelationSets[entityA].Count == 0) { aEmpty = true; } } - if (inRelations.TryGetValue(entityB, out var entityInRelations)) + if (InRelationSets.TryGetValue(entityB, out var entityInRelations)) { entityInRelations.Remove(entityA); - if (inRelations[entityB].Count == 0) + if (InRelationSets[entityB].Count == 0) { bEmpty = true; } } - if (indices.TryGetValue(relation, out var index)) + if (Indices.TryGetValue(relation, out var index)) { - var lastElementIndex = relations.Count - 1; + var lastElementIndex = Relations.Count - 1; // move an element into the hole if (index != lastElementIndex) { - var lastRelation = relations.Get<(Entity, Entity)>(lastElementIndex); - indices[lastRelation] = index; + var lastRelation = Relations.Get<(Entity, Entity)>(lastElementIndex); + Indices[lastRelation] = index; } - relationDatas.Delete(index); - relations.Delete(index); + RelationDatas.Delete(index); + Relations.Delete(index); - indices.Remove(relation); + Indices.Remove(relation); } return (aEmpty, bEmpty); @@ -189,7 +189,7 @@ internal class RelationStorage public void RemoveEntity(in Entity entity) { - if (outRelations.TryGetValue(entity, out var entityOutRelations)) + if (OutRelationSets.TryGetValue(entity, out var entityOutRelations)) { foreach (var entityB in entityOutRelations) { @@ -197,10 +197,10 @@ internal class RelationStorage } ReturnHashSetToPool(entityOutRelations); - outRelations.Remove(entity); + OutRelationSets.Remove(entity); } - if (inRelations.TryGetValue(entity, out var entityInRelations)) + if (InRelationSets.TryGetValue(entity, out var entityInRelations)) { foreach (var entityA in entityInRelations) { @@ -208,64 +208,64 @@ internal class RelationStorage } ReturnHashSetToPool(entityInRelations); - inRelations.Remove(entity); + InRelationSets.Remove(entity); } } internal IndexableSet AcquireHashSetFromPool() { - if (listPool.Count == 0) + if (ListPool.Count == 0) { - listPool.Push(new IndexableSet()); + ListPool.Push(new IndexableSet()); } - return listPool.Pop(); + return ListPool.Pop(); } private void ReturnHashSetToPool(IndexableSet hashSet) { hashSet.Clear(); - listPool.Push(hashSet); + ListPool.Push(hashSet); } public void Clear() { - indices.Clear(); + Indices.Clear(); - foreach (var set in inRelations.Values) + foreach (var set in InRelationSets.Values) { ReturnHashSetToPool(set); } - inRelations.Clear(); + InRelationSets.Clear(); - foreach (var set in outRelations.Values) + foreach (var set in OutRelationSets.Values) { ReturnHashSetToPool(set); } - outRelations.Clear(); + OutRelationSets.Clear(); - relations.Clear(); - relationDatas.Clear(); + Relations.Clear(); + RelationDatas.Clear(); } protected virtual void Dispose(bool disposing) { - if (!disposed) + if (!IsDisposed) { Clear(); if (disposing) { - foreach (var set in listPool) + foreach (var set in ListPool) { set.Dispose(); } - relations.Dispose(); - relationDatas.Dispose(); + Relations.Dispose(); + RelationDatas.Dispose(); } - disposed = true; + IsDisposed = true; } } diff --git a/src/Renderer.cs b/src/Renderer.cs index f6118c2..4bccf51 100644 --- a/src/Renderer.cs +++ b/src/Renderer.cs @@ -1,7 +1,6 @@ -namespace MoonTools.ECS +namespace MoonTools.ECS; + +public abstract class Renderer : EntityComponentReader { - public abstract class Renderer : EntityComponentReader - { - public Renderer(World world) : base(world) { } - } + public Renderer(World world) : base(world) { } } diff --git a/src/Snapshot.cs b/src/Snapshot.cs index 10a080d..5a64679 100644 --- a/src/Snapshot.cs +++ b/src/Snapshot.cs @@ -205,7 +205,7 @@ public class Snapshot { if (!RelationSnapshots.TryGetValue(typeId, out var snapshot)) { - snapshot = new RelationSnapshot(relationStorage.relationDatas.ElementSize); + snapshot = new RelationSnapshot(relationStorage.RelationDatas.ElementSize); RelationSnapshots.Add(typeId, snapshot); } @@ -256,39 +256,39 @@ public class Snapshot public void Take(RelationStorage relationStorage) { - relationStorage.relations.CopyAllTo(Relations); - relationStorage.relationDatas.CopyAllTo(RelationDatas); + relationStorage.Relations.CopyAllTo(Relations); + relationStorage.RelationDatas.CopyAllTo(RelationDatas); } public void Restore(RelationStorage relationStorage) { relationStorage.Clear(); - Relations.CopyAllTo(relationStorage.relations); - RelationDatas.CopyAllTo(relationStorage.relationDatas); + Relations.CopyAllTo(relationStorage.Relations); + RelationDatas.CopyAllTo(relationStorage.RelationDatas); for (int index = 0; index < Relations.Count; index += 1) { var relation = Relations.Get<(Entity, Entity)>(index); - relationStorage.indices[relation] = index; + relationStorage.Indices[relation] = index; - relationStorage.indices[relation] = index; + relationStorage.Indices[relation] = index; - if (!relationStorage.outRelations.ContainsKey(relation.Item1)) + if (!relationStorage.OutRelationSets.ContainsKey(relation.Item1)) { - relationStorage.outRelations[relation.Item1] = + relationStorage.OutRelationSets[relation.Item1] = relationStorage.AcquireHashSetFromPool(); } - relationStorage.outRelations[relation.Item1].Add(relation.Item2); + relationStorage.OutRelationSets[relation.Item1].Add(relation.Item2); - if (!relationStorage.inRelations.ContainsKey(relation.Item2)) + if (!relationStorage.InRelationSets.ContainsKey(relation.Item2)) { - relationStorage.inRelations[relation.Item2] = + relationStorage.InRelationSets[relation.Item2] = relationStorage.AcquireHashSetFromPool(); } - relationStorage.inRelations[relation.Item2].Add(relation.Item1); + relationStorage.InRelationSets[relation.Item2].Add(relation.Item1); } } } diff --git a/src/World.cs b/src/World.cs index 4653a34..70aad7d 100644 --- a/src/World.cs +++ b/src/World.cs @@ -3,519 +3,518 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using MoonTools.ECS.Collections; -namespace MoonTools.ECS +namespace MoonTools.ECS; + +public class World : IDisposable { - public class World : IDisposable + // Get TypeId from a Type + private readonly Dictionary TypeToId = new Dictionary(); + +#if DEBUG + private Dictionary IdToType = new Dictionary(); +#endif + + // Get element size from a TypeId + private readonly Dictionary ElementSizes = new Dictionary(); + + // Filters + internal readonly Dictionary FilterIndex = new Dictionary(); + private readonly Dictionary> TypeToFilter = new Dictionary>(); + + // TODO: can we make the tag an native array of chars at some point? + internal Dictionary EntityTags = new Dictionary(); + + // Relation Storages + internal Dictionary RelationIndex = new Dictionary(); + internal Dictionary> EntityRelationIndex = new Dictionary>(); + + // Message Storages + private Dictionary MessageIndex = new Dictionary(); + + public FilterBuilder FilterBuilder => new FilterBuilder(this); + + internal readonly Dictionary ComponentIndex = new Dictionary(); + internal Dictionary> EntityComponentIndex = new Dictionary>(); + + internal IdAssigner EntityIdAssigner = new IdAssigner(); + private IdAssigner TypeIdAssigner = new IdAssigner(); + + private bool IsDisposed; + + internal TypeId GetTypeId() where T : unmanaged { - // Get TypeId from a Type - private readonly Dictionary TypeToId = new Dictionary(); - - #if DEBUG - private Dictionary IdToType = new Dictionary(); - #endif - - // Get element size from a TypeId - private readonly Dictionary ElementSizes = new Dictionary(); - - // Filters - internal readonly Dictionary FilterIndex = new Dictionary(); - private readonly Dictionary> TypeToFilter = new Dictionary>(); - - // TODO: can we make the tag an native array of chars at some point? - internal Dictionary EntityTags = new Dictionary(); - - // Relation Storages - internal Dictionary RelationIndex = new Dictionary(); - internal Dictionary> EntityRelationIndex = new Dictionary>(); - - // Message Storages - private Dictionary MessageIndex = new Dictionary(); - - public FilterBuilder FilterBuilder => new FilterBuilder(this); - - internal readonly Dictionary ComponentIndex = new Dictionary(); - internal Dictionary> EntityComponentIndex = new Dictionary>(); - - internal IdAssigner EntityIdAssigner = new IdAssigner(); - private IdAssigner TypeIdAssigner = new IdAssigner(); - - private bool IsDisposed; - - internal TypeId GetTypeId() where T : unmanaged + if (TypeToId.ContainsKey(typeof(T))) { - if (TypeToId.ContainsKey(typeof(T))) - { - return TypeToId[typeof(T)]; - } + return TypeToId[typeof(T)]; + } - var typeId = new TypeId(TypeIdAssigner.Assign()); - TypeToId.Add(typeof(T), typeId); - ElementSizes.Add(typeId, Unsafe.SizeOf()); + var typeId = new TypeId(TypeIdAssigner.Assign()); + TypeToId.Add(typeof(T), typeId); + ElementSizes.Add(typeId, Unsafe.SizeOf()); - #if DEBUG - IdToType.Add(typeId, typeof(T)); - #endif +#if DEBUG + IdToType.Add(typeId, typeof(T)); +#endif + return typeId; + } + + internal TypeId GetComponentTypeId() where T : unmanaged + { + var typeId = GetTypeId(); + if (ComponentIndex.TryGetValue(typeId, out var componentStorage)) + { return typeId; } - internal TypeId GetComponentTypeId() where T : unmanaged + componentStorage = new ComponentStorage(typeId, ElementSizes[typeId]); + ComponentIndex.Add(typeId, componentStorage); + TypeToFilter.Add(typeId, new List()); + return typeId; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ComponentStorage GetComponentStorage() where T : unmanaged + { + var typeId = GetTypeId(); + if (ComponentIndex.TryGetValue(typeId, out var componentStorage)) { - var typeId = GetTypeId(); - if (ComponentIndex.TryGetValue(typeId, out var componentStorage)) - { - return typeId; - } - - componentStorage = new ComponentStorage(typeId, ElementSizes[typeId]); - ComponentIndex.Add(typeId, componentStorage); - TypeToFilter.Add(typeId, new List()); - return typeId; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ComponentStorage GetComponentStorage() where T : unmanaged - { - var typeId = GetTypeId(); - if (ComponentIndex.TryGetValue(typeId, out var componentStorage)) - { - return componentStorage; - } - - componentStorage = new ComponentStorage(typeId, ElementSizes[typeId]); - ComponentIndex.Add(typeId, componentStorage); - TypeToFilter.Add(typeId, new List()); return componentStorage; } - // FILTERS + componentStorage = new ComponentStorage(typeId, ElementSizes[typeId]); + ComponentIndex.Add(typeId, componentStorage); + TypeToFilter.Add(typeId, new List()); + return componentStorage; + } - internal Filter GetFilter(FilterSignature signature) + // FILTERS + + internal Filter GetFilter(FilterSignature signature) + { + if (!FilterIndex.TryGetValue(signature, out var filter)) { - if (!FilterIndex.TryGetValue(signature, out var filter)) + filter = new Filter(this, signature); + + foreach (var typeId in signature.Included) { - filter = new Filter(this, signature); - - foreach (var typeId in signature.Included) - { - TypeToFilter[typeId].Add(filter); - } - - foreach (var typeId in signature.Excluded) - { - TypeToFilter[typeId].Add(filter); - } - - FilterIndex.Add(signature, filter); + TypeToFilter[typeId].Add(filter); } - return filter; - } - - // ENTITIES - - public Entity CreateEntity(string tag = "") - { - var entity = new Entity(EntityIdAssigner.Assign()); - - if (!EntityComponentIndex.ContainsKey(entity)) + foreach (var typeId in signature.Excluded) { - EntityRelationIndex.Add(entity, new IndexableSet()); - EntityComponentIndex.Add(entity, new IndexableSet()); + TypeToFilter[typeId].Add(filter); } - EntityTags[entity] = tag; - - return entity; + FilterIndex.Add(signature, filter); } - public void Tag(Entity entity, string tag) + return filter; + } + + // ENTITIES + + public Entity CreateEntity(string tag = "") + { + var entity = new Entity(EntityIdAssigner.Assign()); + + if (!EntityComponentIndex.ContainsKey(entity)) { - EntityTags[entity] = tag; + EntityRelationIndex.Add(entity, new IndexableSet()); + EntityComponentIndex.Add(entity, new IndexableSet()); } - public string GetTag(Entity entity) - { - return EntityTags[entity]; - } + EntityTags[entity] = tag; - public void Destroy(in Entity entity) + return entity; + } + + public void Tag(Entity entity, string tag) + { + EntityTags[entity] = tag; + } + + public string GetTag(Entity entity) + { + return EntityTags[entity]; + } + + public void Destroy(in Entity entity) + { + // remove all components from storages + foreach (var componentTypeIndex in EntityComponentIndex[entity]) { - // remove all components from storages - foreach (var componentTypeIndex in EntityComponentIndex[entity]) + var componentStorage = ComponentIndex[componentTypeIndex]; + componentStorage.Remove(entity); + + foreach (var filter in TypeToFilter[componentTypeIndex]) { - var componentStorage = ComponentIndex[componentTypeIndex]; - componentStorage.Remove(entity); - - foreach (var filter in TypeToFilter[componentTypeIndex]) - { - filter.RemoveEntity(entity); - } - } - - // remove all relations from storage - foreach (var relationTypeIndex in EntityRelationIndex[entity]) - { - var relationStorage = RelationIndex[relationTypeIndex]; - relationStorage.RemoveEntity(entity); - } - - EntityComponentIndex[entity].Clear(); - EntityRelationIndex[entity].Clear(); - - // recycle ID - EntityIdAssigner.Unassign(entity.ID); - } - - // COMPONENTS - - public bool Has(in Entity entity) where T : unmanaged - { - var storage = GetComponentStorage(); - return storage.Has(entity); - } - - internal bool Has(in Entity entity, in TypeId typeId) - { - return EntityComponentIndex[entity].Contains(typeId); - } - - public bool Some() where T : unmanaged - { - var storage = GetComponentStorage(); - return storage.Any(); - } - - public ref T Get(in Entity entity) where T : unmanaged - { - var storage = GetComponentStorage(); - return ref storage.Get(entity); - } - - public ref T GetSingleton() where T : unmanaged - { - var storage = GetComponentStorage(); - return ref storage.GetFirst(); - } - - public Entity GetSingletonEntity() where T : unmanaged - { - var storage = GetComponentStorage(); - return storage.FirstEntity(); - } - - public void Set(in Entity entity, in T component) where T : unmanaged - { - var componentStorage = GetComponentStorage(); - - if (!componentStorage.Set(entity, component)) - { - EntityComponentIndex[entity].Add(componentStorage.TypeId); - - foreach (var filter in TypeToFilter[componentStorage.TypeId]) - { - filter.Check(entity); - } + filter.RemoveEntity(entity); } } - public void Remove(in Entity entity) where T : unmanaged + // remove all relations from storage + foreach (var relationTypeIndex in EntityRelationIndex[entity]) { - var componentStorage = GetComponentStorage(); - - if (componentStorage.Remove(entity)) - { - EntityComponentIndex[entity].Remove(componentStorage.TypeId); - - foreach (var filter in TypeToFilter[componentStorage.TypeId]) - { - filter.Check(entity); - } - } - } - - // RELATIONS - - private RelationStorage RegisterRelationType(TypeId typeId) - { - var relationStorage = new RelationStorage(ElementSizes[typeId]); - RelationIndex.Add(typeId, relationStorage); - return relationStorage; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private RelationStorage GetRelationStorage() where T : unmanaged - { - var typeId = GetTypeId(); - if (RelationIndex.TryGetValue(typeId, out var relationStorage)) - { - return relationStorage; - } - - return RegisterRelationType(typeId); - } - - public void Relate(in Entity entityA, in Entity entityB, in T relation) where T : unmanaged - { - var relationStorage = GetRelationStorage(); - relationStorage.Set(entityA, entityB, relation); - EntityRelationIndex[entityA].Add(TypeToId[typeof(T)]); - EntityRelationIndex[entityB].Add(TypeToId[typeof(T)]); - } - - public void Unrelate(in Entity entityA, in Entity entityB) where T : unmanaged - { - var relationStorage = GetRelationStorage(); - relationStorage.Remove(entityA, entityB); - } - - public void UnrelateAll(in Entity entity) where T : unmanaged - { - var relationStorage = GetRelationStorage(); + var relationStorage = RelationIndex[relationTypeIndex]; relationStorage.RemoveEntity(entity); } - public bool Related(in Entity entityA, in Entity entityB) where T : unmanaged + EntityComponentIndex[entity].Clear(); + EntityRelationIndex[entity].Clear(); + + // recycle ID + EntityIdAssigner.Unassign(entity.ID); + } + + // COMPONENTS + + public bool Has(in Entity entity) where T : unmanaged + { + var storage = GetComponentStorage(); + return storage.Has(entity); + } + + internal bool Has(in Entity entity, in TypeId typeId) + { + return EntityComponentIndex[entity].Contains(typeId); + } + + public bool Some() where T : unmanaged + { + var storage = GetComponentStorage(); + return storage.Any(); + } + + public ref T Get(in Entity entity) where T : unmanaged + { + var storage = GetComponentStorage(); + return ref storage.Get(entity); + } + + public ref T GetSingleton() where T : unmanaged + { + var storage = GetComponentStorage(); + return ref storage.GetFirst(); + } + + public Entity GetSingletonEntity() where T : unmanaged + { + var storage = GetComponentStorage(); + return storage.FirstEntity(); + } + + public void Set(in Entity entity, in T component) where T : unmanaged + { + var componentStorage = GetComponentStorage(); + + if (!componentStorage.Set(entity, component)) { - var relationStorage = GetRelationStorage(); - return relationStorage.Has(entityA, entityB); - } + EntityComponentIndex[entity].Add(componentStorage.TypeId); - public T GetRelationData(in Entity entityA, in Entity entityB) where T : unmanaged - { - var relationStorage = GetRelationStorage(); - return relationStorage.Get(entityA, entityB); - } - - public ReverseSpanEnumerator<(Entity, Entity)> Relations() where T : unmanaged - { - var relationStorage = GetRelationStorage(); - return relationStorage.All(); - } - - public ReverseSpanEnumerator OutRelations(Entity entity) where T : unmanaged - { - var relationStorage = GetRelationStorage(); - return relationStorage.OutRelations(entity); - } - - public Entity OutRelationSingleton(in Entity entity) where T : unmanaged - { - var relationStorage = GetRelationStorage(); - return relationStorage.OutFirst(entity); - } - - public bool HasOutRelation(in Entity entity) where T : unmanaged - { - var relationStorage = GetRelationStorage(); - return relationStorage.HasOutRelation(entity); - } - - public int OutRelationCount(in Entity entity) where T : unmanaged - { - var relationStorage = GetRelationStorage(); - return relationStorage.OutRelationCount(entity); - } - - public Entity NthOutRelation(in Entity entity, int n) where T : unmanaged - { - var relationStorage = GetRelationStorage(); - return relationStorage.OutNth(entity, n); - } - - public ReverseSpanEnumerator InRelations(Entity entity) where T : unmanaged - { - var relationStorage = GetRelationStorage(); - return relationStorage.InRelations(entity); - } - - public Entity InRelationSingleton(in Entity entity) where T : unmanaged - { - var relationStorage = GetRelationStorage(); - return relationStorage.InFirst(entity); - } - - public bool HasInRelation(in Entity entity) where T : unmanaged - { - var relationStorage = GetRelationStorage(); - return relationStorage.HasInRelation(entity); - } - - public int InRelationCount(in Entity entity) where T : unmanaged - { - var relationStorage = GetRelationStorage(); - return relationStorage.InRelationCount(entity); - } - - public Entity NthInRelation(in Entity entity, int n) where T : unmanaged - { - var relationStorage = GetRelationStorage(); - return relationStorage.InNth(entity, n); - } - - // MESSAGES - - private TypeId GetMessageTypeId() where T : unmanaged - { - var typeId = GetTypeId(); - - if (!MessageIndex.ContainsKey(typeId)) + foreach (var filter in TypeToFilter[componentStorage.TypeId]) { - MessageIndex.Add(typeId, new MessageStorage(Unsafe.SizeOf())); + filter.Check(entity); } - - return typeId; - } - - public void Send(in T message) where T : unmanaged - { - var typeId = GetMessageTypeId(); - MessageIndex[typeId].Add(message); - } - - public bool SomeMessage() where T : unmanaged - { - var typeId = GetMessageTypeId(); - return MessageIndex[typeId].Some(); - } - - public ReadOnlySpan ReadMessages() where T : unmanaged - { - var typeId = GetMessageTypeId(); - return MessageIndex[typeId].All(); - } - - public T ReadMessage() where T : unmanaged - { - var typeId = GetMessageTypeId(); - return MessageIndex[typeId].First(); - } - - public void ClearMessages() where T : unmanaged - { - var typeId = GetMessageTypeId(); - MessageIndex[typeId].Clear(); - } - - // TODO: temporary component storage? - public void FinishUpdate() - { - foreach (var (_, messageStorage) in MessageIndex) - { - messageStorage.Clear(); - } - } - - // DEBUG - // NOTE: these methods are very inefficient - // they should only be used in debugging contexts!! - #if DEBUG - public ComponentTypeEnumerator Debug_GetAllComponentTypes(Entity entity) - { - return new ComponentTypeEnumerator(this, EntityComponentIndex[entity]); - } - - public IEnumerable Debug_GetEntities(Type componentType) - { - var storage = ComponentIndex[TypeToId[componentType]]; - return storage.Debug_GetEntities(); - } - - public IEnumerable Debug_SearchComponentType(string typeString) - { - foreach (var type in TypeToId.Keys) - { - if (type.ToString().ToLower().Contains(typeString.ToLower())) - { - yield return type; - } - } - } - - public ref struct ComponentTypeEnumerator - { - private World World; - private IndexableSet Types; - private int ComponentIndex; - - public ComponentTypeEnumerator GetEnumerator() => this; - - internal ComponentTypeEnumerator( - World world, - IndexableSet types - ) - { - World = world; - Types = types; - ComponentIndex = -1; - } - - public bool MoveNext() - { - ComponentIndex += 1; - return ComponentIndex < Types.Count; - } - - public unsafe Type Current => World.IdToType[Types[ComponentIndex]]; - } - #endif - - protected virtual void Dispose(bool disposing) - { - if (!IsDisposed) - { - if (disposing) - { - foreach (var componentStorage in ComponentIndex.Values) - { - componentStorage.Dispose(); - } - - foreach (var relationStorage in RelationIndex.Values) - { - relationStorage.Dispose(); - } - - foreach (var messageStorage in MessageIndex.Values) - { - messageStorage.Dispose(); - } - - foreach (var typeSet in EntityComponentIndex.Values) - { - typeSet.Dispose(); - } - - foreach (var typeSet in EntityRelationIndex.Values) - { - typeSet.Dispose(); - } - - foreach (var filter in FilterIndex.Values) - { - filter.Dispose(); - } - - EntityIdAssigner.Dispose(); - TypeIdAssigner.Dispose(); - } - - IsDisposed = true; - } - } - - // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources - // ~World() - // { - // // 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); } } + + public void Remove(in Entity entity) where T : unmanaged + { + var componentStorage = GetComponentStorage(); + + if (componentStorage.Remove(entity)) + { + EntityComponentIndex[entity].Remove(componentStorage.TypeId); + + foreach (var filter in TypeToFilter[componentStorage.TypeId]) + { + filter.Check(entity); + } + } + } + + // RELATIONS + + private RelationStorage RegisterRelationType(TypeId typeId) + { + var relationStorage = new RelationStorage(ElementSizes[typeId]); + RelationIndex.Add(typeId, relationStorage); + return relationStorage; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private RelationStorage GetRelationStorage() where T : unmanaged + { + var typeId = GetTypeId(); + if (RelationIndex.TryGetValue(typeId, out var relationStorage)) + { + return relationStorage; + } + + return RegisterRelationType(typeId); + } + + public void Relate(in Entity entityA, in Entity entityB, in T relation) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + relationStorage.Set(entityA, entityB, relation); + EntityRelationIndex[entityA].Add(TypeToId[typeof(T)]); + EntityRelationIndex[entityB].Add(TypeToId[typeof(T)]); + } + + public void Unrelate(in Entity entityA, in Entity entityB) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + relationStorage.Remove(entityA, entityB); + } + + public void UnrelateAll(in Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + relationStorage.RemoveEntity(entity); + } + + public bool Related(in Entity entityA, in Entity entityB) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.Has(entityA, entityB); + } + + public T GetRelationData(in Entity entityA, in Entity entityB) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.Get(entityA, entityB); + } + + public ReverseSpanEnumerator<(Entity, Entity)> Relations() where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.All(); + } + + public ReverseSpanEnumerator OutRelations(Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.OutRelations(entity); + } + + public Entity OutRelationSingleton(in Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.OutFirst(entity); + } + + public bool HasOutRelation(in Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.HasOutRelation(entity); + } + + public int OutRelationCount(in Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.OutRelationCount(entity); + } + + public Entity NthOutRelation(in Entity entity, int n) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.OutNth(entity, n); + } + + public ReverseSpanEnumerator InRelations(Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.InRelations(entity); + } + + public Entity InRelationSingleton(in Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.InFirst(entity); + } + + public bool HasInRelation(in Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.HasInRelation(entity); + } + + public int InRelationCount(in Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.InRelationCount(entity); + } + + public Entity NthInRelation(in Entity entity, int n) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.InNth(entity, n); + } + + // MESSAGES + + private TypeId GetMessageTypeId() where T : unmanaged + { + var typeId = GetTypeId(); + + if (!MessageIndex.ContainsKey(typeId)) + { + MessageIndex.Add(typeId, new MessageStorage(Unsafe.SizeOf())); + } + + return typeId; + } + + public void Send(in T message) where T : unmanaged + { + var typeId = GetMessageTypeId(); + MessageIndex[typeId].Add(message); + } + + public bool SomeMessage() where T : unmanaged + { + var typeId = GetMessageTypeId(); + return MessageIndex[typeId].Some(); + } + + public ReadOnlySpan ReadMessages() where T : unmanaged + { + var typeId = GetMessageTypeId(); + return MessageIndex[typeId].All(); + } + + public T ReadMessage() where T : unmanaged + { + var typeId = GetMessageTypeId(); + return MessageIndex[typeId].First(); + } + + public void ClearMessages() where T : unmanaged + { + var typeId = GetMessageTypeId(); + MessageIndex[typeId].Clear(); + } + + // TODO: temporary component storage? + public void FinishUpdate() + { + foreach (var (_, messageStorage) in MessageIndex) + { + messageStorage.Clear(); + } + } + + // DEBUG + // NOTE: these methods are very inefficient + // they should only be used in debugging contexts!! +#if DEBUG + public ComponentTypeEnumerator Debug_GetAllComponentTypes(Entity entity) + { + return new ComponentTypeEnumerator(this, EntityComponentIndex[entity]); + } + + public IEnumerable Debug_GetEntities(Type componentType) + { + var storage = ComponentIndex[TypeToId[componentType]]; + return storage.Debug_GetEntities(); + } + + public IEnumerable Debug_SearchComponentType(string typeString) + { + foreach (var type in TypeToId.Keys) + { + if (type.ToString().ToLower().Contains(typeString.ToLower())) + { + yield return type; + } + } + } + + public ref struct ComponentTypeEnumerator + { + private World World; + private IndexableSet Types; + private int ComponentIndex; + + public ComponentTypeEnumerator GetEnumerator() => this; + + internal ComponentTypeEnumerator( + World world, + IndexableSet types + ) + { + World = world; + Types = types; + ComponentIndex = -1; + } + + public bool MoveNext() + { + ComponentIndex += 1; + return ComponentIndex < Types.Count; + } + + public Type Current => World.IdToType[Types[ComponentIndex]]; + } +#endif + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + foreach (var componentStorage in ComponentIndex.Values) + { + componentStorage.Dispose(); + } + + foreach (var relationStorage in RelationIndex.Values) + { + relationStorage.Dispose(); + } + + foreach (var messageStorage in MessageIndex.Values) + { + messageStorage.Dispose(); + } + + foreach (var typeSet in EntityComponentIndex.Values) + { + typeSet.Dispose(); + } + + foreach (var typeSet in EntityRelationIndex.Values) + { + typeSet.Dispose(); + } + + foreach (var filter in FilterIndex.Values) + { + filter.Dispose(); + } + + EntityIdAssigner.Dispose(); + TypeIdAssigner.Dispose(); + } + + IsDisposed = true; + } + } + + // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + // ~World() + // { + // // 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); + } } -- 2.25.1 From 32e341cbc4b47fbc192128c687eea17fc981bee2 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 21 Nov 2023 11:47:58 -0800 Subject: [PATCH 17/17] implement Snapshot.Dispose --- src/Snapshot.cs | 95 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 3 deletions(-) diff --git a/src/Snapshot.cs b/src/Snapshot.cs index 5a64679..0421406 100644 --- a/src/Snapshot.cs +++ b/src/Snapshot.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using MoonTools.ECS.Collections; @@ -5,7 +6,7 @@ using MoonTools.ECS.Collections; namespace MoonTools.ECS; // TODO: we should implement a NativeDictionary that can be memcopied -public class Snapshot +public class Snapshot : IDisposable { private Dictionary ComponentSnapshots = new Dictionary(); @@ -24,6 +25,8 @@ public class Snapshot private IdAssigner EntityIdAssigner = new IdAssigner(); + private bool IsDisposed; + public void Restore(World world) { // restore id assigner state @@ -212,11 +215,13 @@ public class Snapshot snapshot.Take(relationStorage); } - private class ComponentSnapshot + private class ComponentSnapshot : IDisposable { private readonly NativeArray Components; private readonly NativeArray EntityIDs; + private bool IsDisposed; + public ComponentSnapshot(int elementSize) { Components = new NativeArray(elementSize); @@ -241,13 +246,36 @@ public class Snapshot componentStorage.EntityIDToStorageIndex[entityID] = i; } } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + Components.Dispose(); + EntityIDs.Dispose(); + } + + IsDisposed = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } - private class RelationSnapshot + private class RelationSnapshot : IDisposable { private NativeArray Relations; private NativeArray RelationDatas; + private bool IsDisposed; + public RelationSnapshot(int elementSize) { Relations = new NativeArray(Unsafe.SizeOf<(Entity, Entity)>()); @@ -291,5 +319,66 @@ public class Snapshot relationStorage.InRelationSets[relation.Item2].Add(relation.Item1); } } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + Relations.Dispose(); + RelationDatas.Dispose(); + } + + IsDisposed = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + foreach (var componentSnapshot in ComponentSnapshots.Values) + { + componentSnapshot.Dispose(); + } + + foreach (var relationSnapshot in RelationSnapshots.Values) + { + relationSnapshot.Dispose(); + } + + foreach (var componentSet in EntityComponentIndex.Values) + { + componentSet.Dispose(); + } + + foreach (var relationSet in EntityRelationIndex.Values) + { + relationSet.Dispose(); + } + + EntityIdAssigner.Dispose(); + } + + IsDisposed = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); } } -- 2.25.1