From 001b6714cc8d6f59af9fda5adb36fb70f862d971 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 3 Nov 2023 12:40:26 -0700 Subject: [PATCH] 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 } }