From fc2120cca960cb64be3aa1020d385784b219a20c Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 21 Nov 2023 19:50:17 +0000 Subject: [PATCH] Storage Rewrite (#6) - add Snapshot with Take and Restore methods - remove World.Transfer - components can now be accessed via `ref` - clients should call World.Dispose when they are done with a World - rewrite ECS storage to use fewer classes - all ECS storage methods now exposed in World - removed filter callbacks - removed MessageWithEntity methods - various data structure optimizations Reviewed-on: https://gitea.moonside.games/MoonsideGames/MoonTools.ECS/pulls/6 --- src/Collections/IndexableSet.cs | 106 ++++ src/Collections/NativeArray.cs | 120 ++++ src/Collections/NativeArrayUntyped.cs | 121 ++++ src/ComponentDepot.cs | 139 ----- src/ComponentStorage.cs | 254 ++++----- src/DebugSystem.cs | 60 +- src/DynamicArray.cs | 69 --- src/Entity.cs | 50 +- src/EntityComponentReader.cs | 153 ++---- src/EntityStorage.cs | 142 ----- src/Enumerators/ReverseSpanEnumerator.cs | 51 +- src/Filter.cs | 141 ++++- src/FilterBuilder.cs | 36 +- src/FilterSignature.cs | 92 ++-- src/FilterStorage.cs | 273 --------- src/IdAssigner.cs | 62 +++ src/IndexableSet.cs | 103 ---- src/Manipulator.cs | 30 +- src/MessageDepot.cs | 68 --- src/MessageStorage.cs | 164 ++---- src/Random.cs | 367 ++++++------- src/RandomManager.cs | 129 +++-- src/RelationDepot.cs | 193 ------- src/RelationStorage.cs | 498 ++++++++--------- src/Renderer.cs | 9 +- src/Snapshot.cs | 384 +++++++++++++ src/System.cs | 50 +- src/TypeId.cs | 11 + src/TypeIndices.cs | 33 -- src/World.cs | 672 +++++++++++++++++------ 30 files changed, 2197 insertions(+), 2383 deletions(-) create mode 100644 src/Collections/IndexableSet.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/IndexableSet.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/Collections/IndexableSet.cs b/src/Collections/IndexableSet.cs new file mode 100644 index 0000000..dcf3fbb --- /dev/null +++ b/src/Collections/IndexableSet.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace MoonTools.ECS.Collections; + +public unsafe class IndexableSet : IDisposable where T : unmanaged +{ + private Dictionary Indices; + private T* Array; + public int Count { get; private set; } + private int Capacity; + private bool IsDisposed; + + public Span AsSpan() => new Span(Array, Count); + public ReverseSpanEnumerator GetEnumerator() => new ReverseSpanEnumerator(new Span(Array, Count)); + + public IndexableSet(int capacity = 32) + { + this.Capacity = capacity; + Count = 0; + + Indices = new Dictionary(capacity); + Array = (T*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf())); + } + + public T this[int i] => Array[i]; + + public bool Contains(T element) + { + return Indices.ContainsKey(element); + } + + public bool Add(T element) + { + if (!Contains(element)) + { + Indices.Add(element, Count); + + if (Count >= Capacity) + { + Capacity *= 2; + Array = (T*) NativeMemory.Realloc(Array, (nuint) (Capacity * Unsafe.SizeOf())); + } + + Array[Count] = element; + Count += 1; + + return true; + } + + return false; + } + + public bool Remove(T element) + { + if (!Contains(element)) + { + return false; + } + + var index = Indices[element]; + + for (var i = index; i < Count - 1; i += 1) + { + Array[i] = Array[i + 1]; + Indices[Array[i]] = i; + } + + Indices.Remove(element); + Count -= 1; + + return true; + } + + public void Clear() + { + Indices.Clear(); + Count = 0; + } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + NativeMemory.Free(Array); + Array = null; + + IsDisposed = true; + } + } + + ~IndexableSet() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: false); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} diff --git a/src/Collections/NativeArray.cs b/src/Collections/NativeArray.cs new file mode 100644 index 0000000..eb897a6 --- /dev/null +++ b/src/Collections/NativeArray.cs @@ -0,0 +1,120 @@ +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 ToSpan() => new Span(Elements, Count); + public Span.Enumerator GetEnumerator() => new Span(Elements, Count).GetEnumerator(); + + private bool IsDisposed; + + 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 (index != Count - 1) + { + this[index] = this[Count - 1]; + } + + 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 (!IsDisposed) + { + NativeMemory.Free(Elements); + Elements = null; + + 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/Collections/NativeArrayUntyped.cs b/src/Collections/NativeArrayUntyped.cs new file mode 100644 index 0000000..070effd --- /dev/null +++ b/src/Collections/NativeArrayUntyped.cs @@ -0,0 +1,121 @@ +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 (index != 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 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..f53a8a0 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -1,182 +1,140 @@ using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; +using MoonTools.ECS.Collections; -namespace MoonTools.ECS +namespace MoonTools.ECS; + +internal class ComponentStorage : IDisposable { - internal abstract class ComponentStorage - { - 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; + internal readonly TypeId TypeId; - // 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 + private bool IsDisposed; + + public ComponentStorage(TypeId typeId, int elementSize) + { + Components = new NativeArray(elementSize); + EntityIDs = new NativeArray(); + TypeId = typeId; } - internal unsafe class ComponentStorage : ComponentStorage, IDisposable where TComponent : unmanaged + public bool Any() { - private readonly Dictionary entityIDToStorageIndex = new Dictionary(16); - private TComponent* components; - private int* entityIDs; - private int count = 0; - private int capacity = 16; - private bool disposed; + return Components.Count > 0; + } - public ComponentStorage() - { - components = (TComponent*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf())); - entityIDs = (int*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf())); - } + public bool Has(Entity entity) + { + return EntityIDToStorageIndex.ContainsKey(entity); + } - public bool Any() - { - return count > 0; - } + public ref T Get(in Entity entity) where T : unmanaged + { + return ref Components.Get(EntityIDToStorageIndex[entity]); + } - public ref readonly TComponent Get(int entityID) - { - return ref components[entityIDToStorageIndex[entityID]]; - } - - internal override unsafe void* UntypedGet(int entityID) - { - return &components[entityIDToStorageIndex[entityID]]; - } - - public ref readonly TComponent GetFirst() - { + public ref T GetFirst() where T : unmanaged + { #if DEBUG - if (count == 0) - { - throw new IndexOutOfRangeException("Component storage is empty!"); - } + if (Components.Count == 0) + { + throw new IndexOutOfRangeException("Component storage is empty!"); + } #endif - return ref components[0]; - } + return ref Components.Get(0); + } - public void Set(int entityID, in TComponent component) + // Returns true if the entity had this component. + public bool Set(in Entity entity, in T component) where T : unmanaged + { + if (EntityIDToStorageIndex.TryGetValue(entity, out var index)) { - if (!entityIDToStorageIndex.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; + Components.Set(index, component); + return true; } - - internal override unsafe void Set(int entityID, void* component) + else { - Set(entityID, *(TComponent*) component); - } - - // Returns true if the entity had this component. - public override bool Remove(int entityID) - { - if (entityIDToStorageIndex.TryGetValue(entityID, out int storageIndex)) - { - entityIDToStorageIndex.Remove(entityID); - - var lastElementIndex = count - 1; - - // move a component into the hole to maintain contiguous memory - if (lastElementIndex != storageIndex) - { - var lastEntityID = entityIDs[lastElementIndex]; - entityIDToStorageIndex[lastEntityID] = storageIndex; - components[storageIndex] = components[lastElementIndex]; - entityIDs[storageIndex] = lastEntityID; - } - - count -= 1; - - return true; - } - + EntityIDToStorageIndex[entity] = Components.Count; + EntityIDs.Append(entity); + Components.Append(component); return false; } + } - public override void Clear() + // Returns true if the entity had this component. + public bool Remove(in Entity entity) + { + if (EntityIDToStorageIndex.TryGetValue(entity, out int index)) { - count = 0; - entityIDToStorageIndex.Clear(); - } + var lastElementIndex = Components.Count - 1; - public ReadOnlySpan AllComponents() - { - return new ReadOnlySpan(components, count); - } + var lastEntity = EntityIDs[lastElementIndex]; - public Entity FirstEntity() - { -#if DEBUG - if (count == 0) + // move a component into the hole to maintain contiguous memory + Components.Delete(index); + EntityIDs.Delete(index); + EntityIDToStorageIndex.Remove(entity); + + // update the index if it changed + if (lastElementIndex != index) { - throw new IndexOutOfRangeException("Component storage is empty!"); + EntityIDToStorageIndex[lastEntity] = index; } -#endif - return new Entity(entityIDs[0]); + + return true; } - public override ComponentStorage CreateStorage() - { - return new ComponentStorage(); - } + return false; + } + public void Clear() + { + Components.Clear(); + EntityIDs.Clear(); + EntityIDToStorageIndex.Clear(); + } + + public Entity FirstEntity() + { #if DEBUG - internal override object Debug_Get(int entityID) + if (EntityIDs.Count == 0) { - return components[entityIDToStorageIndex[entityID]]; - } - - internal override IEnumerable Debug_GetEntityIDs() - { - return entityIDToStorageIndex.Keys; - } - - protected virtual void Dispose(bool disposing) - { - if (!disposed) - { - NativeMemory.Free(components); - NativeMemory.Free(entityIDs); - components = null; - entityIDs = null; - - disposed = true; - } - } - - ~ComponentStorage() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: false); - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); + throw new IndexOutOfRangeException("Component storage is empty!"); } #endif + return EntityIDs[0]; + } + +#if DEBUG + internal IEnumerable Debug_GetEntities() + { + return EntityIDToStorageIndex.Keys; + } +#endif + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + Components.Dispose(); + EntityIDs.Dispose(); + + IsDisposed = true; + } + } + + // ~ComponentStorage() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); } } diff --git a/src/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..02ad024 100644 --- a/src/Entity.cs +++ b/src/Entity.cs @@ -1,49 +1,3 @@ -using System; +namespace MoonTools.ECS; -namespace MoonTools.ECS -{ - public struct Entity : IEquatable - { - public int ID { get; } - - internal Entity(int 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); - } - } -} +public readonly record struct Entity(uint ID); 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/Enumerators/ReverseSpanEnumerator.cs b/src/Enumerators/ReverseSpanEnumerator.cs index ce526a5..84563a9 100644 --- a/src/Enumerators/ReverseSpanEnumerator.cs +++ b/src/Enumerators/ReverseSpanEnumerator.cs @@ -1,36 +1,35 @@ using System; using System.Runtime.CompilerServices; -namespace MoonTools.ECS +namespace MoonTools.ECS; + +public ref struct ReverseSpanEnumerator { - public ref struct ReverseSpanEnumerator + private ReadOnlySpan Span; + private int Index; + + public ReverseSpanEnumerator GetEnumerator() => this; + + public T Current => Span[Index]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() { - private ReadOnlySpan Span; - private int index; - - public ReverseSpanEnumerator GetEnumerator() => this; - - public T Current => Span[index]; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() + if (Index > 0) { - if (index > 0) - { - index -= 1; - return true; - } - - return false; + Index -= 1; + return true; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReverseSpanEnumerator(Span span) - { - Span = span; - index = span.Length; - } - - public static ReverseSpanEnumerator Empty => new ReverseSpanEnumerator(); + return false; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReverseSpanEnumerator(Span span) + { + Span = span; + Index = span.Length; + } + + public static ReverseSpanEnumerator Empty => new ReverseSpanEnumerator(); } diff --git a/src/Filter.cs b/src/Filter.cs index 94f5ee9..2fd2f19 100644 --- a/src/Filter.cs +++ b/src/Filter.cs @@ -1,38 +1,123 @@ using System; -using System.Collections.Generic; using MoonTools.ECS.Collections; -namespace MoonTools.ECS +namespace MoonTools.ECS; + +public class Filter { - public class Filter + private World World; + internal FilterSignature Signature; + + internal IndexableSet EntitySet = new IndexableSet(); + + private bool IsDisposed; + + public ReverseSpanEnumerator Entities => EntitySet.GetEnumerator(); + + public bool Empty => EntitySet.Count == 0; + public int Count => EntitySet.Count; + + // WARNING: this WILL crash if the index is out of range! + public Entity NthEntity(int index) => EntitySet[index]; + + // WARNING: this WILL crash if the filter is empty! + public Entity RandomEntity => EntitySet[RandomManager.Next(EntitySet.Count)]; + public RandomEntityEnumerator EntitiesInRandomOrder => new RandomEntityEnumerator(this); + + internal Filter(World world, FilterSignature signature) { - internal FilterSignature Signature; - private FilterStorage FilterStorage; + World = world; + Signature = signature; + } - internal Filter(FilterStorage filterStorage, IndexableSet included, IndexableSet excluded) + public void DestroyAllEntities() + { + foreach (var entity in EntitySet) { - FilterStorage = filterStorage; - Signature = new FilterSignature(included, excluded); - } - - public ReverseSpanEnumerator Entities => FilterStorage.FilterEntities(Signature); - public RandomEntityEnumerator EntitiesInRandomOrder => FilterStorage.FilterEntitiesRandom(Signature); - public Entity RandomEntity => FilterStorage.FilterRandomEntity(Signature); - - 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); + World.Destroy(entity); } } + + internal void Check(Entity entity) + { + foreach (var type in Signature.Included) + { + if (!World.Has(entity, type)) + { + EntitySet.Remove(entity); + return; + } + } + + foreach (var type in Signature.Excluded) + { + if (World.Has(entity, type)) + { + EntitySet.Remove(entity); + return; + } + } + + EntitySet.Add(entity); + } + + internal void AddEntity(in Entity entity) + { + EntitySet.Add(entity); + } + + internal void RemoveEntity(in Entity entity) + { + EntitySet.Remove(entity); + } + + internal void Clear() + { + EntitySet.Clear(); + } + + public ref struct RandomEntityEnumerator + { + 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); + } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + EntitySet.Dispose(); + } + + IsDisposed = true; + } + } + + // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + // ~Filter() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } diff --git a/src/FilterBuilder.cs b/src/FilterBuilder.cs index 8a8a6d1..3e3ef6d 100644 --- a/src/FilterBuilder.cs +++ b/src/FilterBuilder.cs @@ -4,42 +4,40 @@ namespace MoonTools.ECS { public struct FilterBuilder { - private TypeIndices ComponentTypeIndices; - private FilterStorage FilterStorage; - private IndexableSet Included; - private IndexableSet Excluded; + World World; + IndexableSet Included; + IndexableSet 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 IndexableSet(); + Excluded = new IndexableSet(); } - private FilterBuilder(FilterStorage filterStorage, TypeIndices componentTypeIndices, IndexableSet included, IndexableSet excluded) + private FilterBuilder(World world, IndexableSet included, IndexableSet 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.GetComponentTypeId()); + 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.GetComponentTypeId()); + return new FilterBuilder(World, Included, Excluded); } public Filter Build() { - return FilterStorage.CreateFilter(Included, Excluded); + var signature = new FilterSignature(Included, Excluded); + return World.GetFilter(signature); } } } diff --git a/src/FilterSignature.cs b/src/FilterSignature.cs index 7e94922..8a27433 100644 --- a/src/FilterSignature.cs +++ b/src/FilterSignature.cs @@ -1,71 +1,69 @@ using System; -using System.Collections.Generic; using MoonTools.ECS.Collections; -namespace MoonTools.ECS +namespace MoonTools.ECS; + +public struct FilterSignature : IEquatable { - public struct FilterSignature : IEquatable + public readonly IndexableSet Included; + public readonly IndexableSet Excluded; + + public FilterSignature(IndexableSet included, IndexableSet excluded) { - public readonly IndexableSet Included; - public readonly IndexableSet Excluded; + Included = included; + Excluded = excluded; + } - public FilterSignature(IndexableSet included, IndexableSet excluded) - { - Included = included; - Excluded = excluded; - } + public override bool Equals(object? obj) + { + return obj is FilterSignature signature && Equals(signature); + } - public override bool Equals(object? obj) + public bool Equals(FilterSignature other) + { + foreach (var included in Included) { - return obj is FilterSignature signature && Equals(signature); - } - - public bool Equals(FilterSignature other) - { - foreach (var included in Included) + if (!other.Included.Contains(included)) { - if (!other.Included.Contains(included)) - { - return false; - } + return false; } - - foreach (var excluded in Excluded) - { - if (!other.Excluded.Contains(excluded)) - { - return false; - } - } - - return true; } - public override int GetHashCode() + foreach (var excluded in Excluded) { - var hashcode = 1; - - foreach (var type in Included) + if (!other.Excluded.Contains(excluded)) { - hashcode = HashCode.Combine(hashcode, type); + return false; } - - foreach (var type in Excluded) - { - hashcode = HashCode.Combine(hashcode, type); - } - - return hashcode; } - public static bool operator ==(FilterSignature left, FilterSignature right) + return true; + } + + public override int GetHashCode() + { + var hashcode = 1; + + foreach (var type in Included) { - return left.Equals(right); + hashcode = HashCode.Combine(hashcode, type); } - public static bool operator !=(FilterSignature left, FilterSignature right) + foreach (var type in Excluded) { - return !(left == right); + hashcode = HashCode.Combine(hashcode, type); } + + return hashcode; + } + + public static bool operator ==(FilterSignature left, FilterSignature right) + { + return left.Equals(right); + } + + public static bool operator !=(FilterSignature left, FilterSignature right) + { + return !(left == right); } } diff --git a/src/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..a31e73b --- /dev/null +++ b/src/IdAssigner.cs @@ -0,0 +1,62 @@ +using System; +using MoonTools.ECS.Collections; + +namespace MoonTools.ECS; + +internal class IdAssigner : IDisposable +{ + uint Next; + NativeArray AvailableIds = new NativeArray(); + + private bool IsDisposed; + + public uint Assign() + { + if (AvailableIds.TryPop(out var id)) + { + return 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; + } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + AvailableIds.Dispose(); + } + + IsDisposed = true; + } + } + + // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + // ~IdAssigner() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} diff --git a/src/IndexableSet.cs b/src/IndexableSet.cs deleted file mode 100644 index 1956756..0000000 --- a/src/IndexableSet.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace MoonTools.ECS.Collections -{ - public unsafe class IndexableSet : IDisposable where T : unmanaged - { - private Dictionary indices; - private T* array; - private int count; - private int capacity; - private bool disposed; - - public int Count => count; - public ReverseSpanEnumerator GetEnumerator() => new ReverseSpanEnumerator(new Span(array, count)); - - public IndexableSet(int capacity = 32) - { - this.capacity = capacity; - count = 0; - - indices = new Dictionary(capacity); - array = (T*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf())); - } - - public T this[int i] => array[i]; - - public bool Contains(T element) - { - return indices.ContainsKey(element); - } - - public bool Add(T element) - { - if (!Contains(element)) - { - indices.Add(element, count); - - if (count >= capacity) - { - capacity *= 2; - array = (T*) NativeMemory.Realloc(array, (nuint) (capacity * Unsafe.SizeOf())); - } - - array[count] = element; - count += 1; - - return true; - } - - return false; - } - - public bool Remove(T element) - { - if (!Contains(element)) - { - return false; - } - - var lastElement = array[Count - 1]; - var index = indices[element]; - array[index] = lastElement; - indices[lastElement] = index; - count -= 1; - indices.Remove(element); - - return true; - } - - public void Clear() - { - indices.Clear(); - count = 0; - } - - protected virtual void Dispose(bool disposing) - { - if (!disposed) - { - NativeMemory.Free(array); - array = null; - - disposed = true; - } - } - - ~IndexableSet() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: false); - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - } -} diff --git a/src/Manipulator.cs b/src/Manipulator.cs index 22a6780..1e8b6cf 100644 --- a/src/Manipulator.cs +++ b/src/Manipulator.cs @@ -1,18 +1,18 @@ -namespace MoonTools.ECS -{ - public abstract class Manipulator : EntityComponentReader - { - public Manipulator(World world) : base(world) - { - } +namespace MoonTools.ECS; - protected Entity CreateEntity(string tag = "") => World.CreateEntity(tag); - protected void Tag(Entity entity, string tag) => World.Tag(entity, tag); - protected void Set(in Entity entity, in TComponent component) where TComponent : unmanaged => World.Set(entity, component); - protected void Remove(in Entity entity) where TComponent : unmanaged => World.Remove(entity); - protected void Relate(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged => World.Relate(entityA, entityB, relationData); - protected void Unrelate(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged => World.Unrelate(entityA, entityB); - protected void UnrelateAll(in Entity entity) where TRelationKind : unmanaged => World.UnrelateAll(entity); - protected void Destroy(in Entity entity) => World.Destroy(entity); +public abstract class Manipulator : EntityComponentReader +{ + public Manipulator(World world) : base(world) + { } + + protected Entity CreateEntity(string tag = "") => World.CreateEntity(tag); + protected void Tag(Entity entity, string tag) => World.Tag(entity, tag); + protected void Set(in Entity entity, in TComponent component) where TComponent : unmanaged => World.Set(entity, component); + protected void Remove(in Entity entity) where TComponent : unmanaged => World.Remove(entity); + + protected void Relate(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged => World.Relate(entityA, entityB, relationData); + protected void Unrelate(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged => World.Unrelate(entityA, entityB); + protected void UnrelateAll(in Entity entity) where TRelationKind : unmanaged => World.UnrelateAll(entity); + protected void Destroy(in Entity entity) => World.Destroy(entity); } 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..f7e31d3 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(); + } + + private 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/Random.cs b/src/Random.cs index 6f8b258..ea20df1 100644 --- a/src/Random.cs +++ b/src/Random.cs @@ -1,209 +1,208 @@ using System; using System.Runtime.CompilerServices; -namespace MoonTools.ECS +namespace MoonTools.ECS; + +/// +/// This class implements the well equidistributed long-period linear pseudorandom number generator. +/// Code taken from Chris Lomont: http://lomont.org/papers/2008/Lomont_PRNG_2008.pdf +/// +public class Random { + public const int STATE_BYTE_COUNT = 68; // 16 state ints + 1 index int + + uint[] State = new uint[16]; + uint Index = 0; + uint Seed; + /// - /// This class implements the well equidistributed long-period linear pseudorandom number generator. - /// Code taken from Chris Lomont: http://lomont.org/papers/2008/Lomont_PRNG_2008.pdf + /// Initializes the RNG with an arbitrary seed. /// - public class Random + public Random() { - public const int STATE_BYTE_COUNT = 68; // 16 state ints + 1 index int + Init((uint) Environment.TickCount); + } - uint[] State = new uint[16]; - uint Index = 0; - uint Seed; - - /// - /// Initializes the RNG with an arbitrary seed. - /// - public Random() + /// + /// Initializes the RNG with a given seed. + /// + public void Init(uint seed) + { + Seed = seed; + uint s = seed; + for (int i = 0; i < 16; i++) { - Init((uint) Environment.TickCount); + s = (((s * 214013 + 2531011) >> 16) & 0x7fffffff) | 0; + State[i] = ~ ~s; //i ; } + Index = 0; + } - /// - /// Initializes the RNG with a given seed. - /// - public void Init(uint seed) + /// + /// Returns the seed that was used to initialize the RNG. + /// + public uint GetSeed() + { + return Seed; + } + + /// + /// Returns the entire state of the RNG as a string. + /// + public string PrintState() + { + var s = ""; + for (var i = 0; i < 16; i += 1) { - Seed = seed; - uint s = seed; - for (int i = 0; i < 16; i++) - { - s = (((s * 214013 + 2531011) >> 16) & 0x7fffffff) | 0; - State[i] = ~ ~s; //i ; - } - Index = 0; + s += State[i]; } + s += Index; + return s; + } - /// - /// Returns the seed that was used to initialize the RNG. - /// - public uint GetSeed() + /// + /// Saves the entire state of the RNG to a Span. + /// + /// Must be a span of at least STATE_BYTE_COUNT bytes. + /// Thrown if the byte span is too short. + public unsafe void SaveState(Span bytes) + { +#if DEBUG + if (bytes.Length < STATE_BYTE_COUNT) { - return Seed; + throw new ArgumentException("Byte span too short!"); } +#endif - /// - /// Returns the entire state of the RNG as a string. - /// - public string PrintState() + fixed (byte* ptr = bytes) { - var s = ""; + var offset = 0; for (var i = 0; i < 16; i += 1) { - s += State[i]; + Unsafe.Write(ptr + offset, State[i]); + offset += 4; } - s += Index; - return s; - } - /// - /// Saves the entire state of the RNG to a Span. - /// - /// Must be a span of at least STATE_BYTE_COUNT bytes. - /// Thrown if the byte span is too short. - public unsafe void SaveState(Span bytes) - { - #if DEBUG - if (bytes.Length < STATE_BYTE_COUNT) - { - throw new ArgumentException("Byte span too short!"); - } - #endif - - fixed (byte* ptr = bytes) - { - var offset = 0; - for (var i = 0; i < 16; i += 1) - { - Unsafe.Write(ptr + offset, State[i]); - offset += 4; - } - - Unsafe.Write(ptr + offset, Index); - } - } - - /// - /// Loads the entire state of the RNG from a Span. - /// - /// Must be a span of at least STATE_BYTE_COUNT bytes. - /// Thrown if the byte span is too short. - public unsafe void LoadState(Span bytes) - { - #if DEBUG - if (bytes.Length < STATE_BYTE_COUNT) - { - throw new ArgumentException("Byte span too short!"); - } - #endif - - fixed (byte* ptr = bytes) - { - var offset = 0; - - for (var i = 0; i < 16; i += 1) - { - State[i] = Unsafe.Read(ptr + offset); - offset += 4; - } - - Index = Unsafe.Read(ptr + offset); - } - } - - private uint NextInternal() - { - uint a, b, c, d; - a = State[Index]; - c = State[(Index+13)&15]; - b = a^c^(a<<16)^(c<<15); - c = State[(Index+9)&15]; - c ^= (c>>11); - a = State[Index] = b^c; - d = (uint) (a ^((a<<5)&0xDA442D24UL)); - Index = (Index + 15)&15; - a = State[Index]; - State[Index] = a^b^d^(a<<2)^(b<<18)^(c<<28); - return State[Index]; - } - - /// - /// Returns a non-negative signed integer. - /// - public int Next() - { - return (int) (NextInternal() >>> 1); // unsigned bitshift right to get rid of signed bit - } - - /// - /// Returns a non-negative signed integer less than max. - /// - public int Next(int max) - { - return (int) (((double) Next()) * max / int.MaxValue); - } - - /// - /// Returns a signed integer greater than or equal to min and less than max. - /// - public int Next(int min, int max) - { - var diff = max - min; - var next = Next(diff); - return min + next; - } - - /// - /// Returns a non-negative signed 64 bit integer. - /// - public long NextInt64() - { - long next = NextInternal(); - next <<= 32; - next |= NextInternal(); - next >>>= 1; - return next; - } - - /// - /// Returns a non-negative signed 64 bit integer less than max. - /// - public long NextInt64(long max) - { - var next = NextInt64(); - return (long) (((double) next) * max / long.MaxValue); - } - - /// - /// Returns a non-negative signed 64 bit integer greater than or equal to min and less than max. - /// - public long NextInt64(long min, long max) - { - var diff = max - min; - var next = NextInt64(diff); - return min + next; - } - - /// - /// Returns a single-precision floating point value between 0 and 1. - /// - public float NextSingle() - { - var n = NextInternal(); - return ((float) n) / uint.MaxValue; - } - - /// - /// Returns a double-precision floating point value between 0 and 1. - /// - public double NextDouble() - { - var n = NextInternal(); - return ((double) n) / uint.MaxValue; + Unsafe.Write(ptr + offset, Index); } } + + /// + /// Loads the entire state of the RNG from a Span. + /// + /// Must be a span of at least STATE_BYTE_COUNT bytes. + /// Thrown if the byte span is too short. + public unsafe void LoadState(Span bytes) + { +#if DEBUG + if (bytes.Length < STATE_BYTE_COUNT) + { + throw new ArgumentException("Byte span too short!"); + } +#endif + + fixed (byte* ptr = bytes) + { + var offset = 0; + + for (var i = 0; i < 16; i += 1) + { + State[i] = Unsafe.Read(ptr + offset); + offset += 4; + } + + Index = Unsafe.Read(ptr + offset); + } + } + + private uint NextInternal() + { + uint a, b, c, d; + a = State[Index]; + c = State[(Index+13)&15]; + b = a^c^(a<<16)^(c<<15); + c = State[(Index+9)&15]; + c ^= (c>>11); + a = State[Index] = b^c; + d = (uint) (a ^((a<<5)&0xDA442D24UL)); + Index = (Index + 15)&15; + a = State[Index]; + State[Index] = a^b^d^(a<<2)^(b<<18)^(c<<28); + return State[Index]; + } + + /// + /// Returns a non-negative signed integer. + /// + public int Next() + { + return (int) (NextInternal() >>> 1); // unsigned bitshift right to get rid of signed bit + } + + /// + /// Returns a non-negative signed integer less than max. + /// + public int Next(int max) + { + return (int) (((double) Next()) * max / int.MaxValue); + } + + /// + /// Returns a signed integer greater than or equal to min and less than max. + /// + public int Next(int min, int max) + { + var diff = max - min; + var next = Next(diff); + return min + next; + } + + /// + /// Returns a non-negative signed 64 bit integer. + /// + public long NextInt64() + { + long next = NextInternal(); + next <<= 32; + next |= NextInternal(); + next >>>= 1; + return next; + } + + /// + /// Returns a non-negative signed 64 bit integer less than max. + /// + public long NextInt64(long max) + { + var next = NextInt64(); + return (long) (((double) next) * max / long.MaxValue); + } + + /// + /// Returns a non-negative signed 64 bit integer greater than or equal to min and less than max. + /// + public long NextInt64(long min, long max) + { + var diff = max - min; + var next = NextInt64(diff); + return min + next; + } + + /// + /// Returns a single-precision floating point value between 0 and 1. + /// + public float NextSingle() + { + var n = NextInternal(); + return ((float) n) / uint.MaxValue; + } + + /// + /// Returns a double-precision floating point value between 0 and 1. + /// + public double NextDouble() + { + var n = NextInternal(); + return ((double) n) / uint.MaxValue; + } } diff --git a/src/RandomManager.cs b/src/RandomManager.cs index cf90d75..b0d00dd 100644 --- a/src/RandomManager.cs +++ b/src/RandomManager.cs @@ -1,82 +1,81 @@ using System.Runtime.CompilerServices; -namespace MoonTools.ECS +namespace MoonTools.ECS; + +public static class RandomManager { - public static class RandomManager + private static Random Random = new Random(); + + private static int[] Primes = { - private static Random Random = new Random(); + 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997,1009,1013,1019,1021,1031,1033,1039,1049,1051,1061,1063,1069,1087,1091,1093,1097,1103,1109,1117,1123,1129,1151,1153,1163,1171,1181,1187,1193,1201,1213,1217,1223,1229,1231,1237,1249,1259,1277,1279,1283,1289,1291,1297,1301,1303,1307,1319,1321,1327,1361,1367,1373,1381,1399,1409,1423,1427,1429,1433,1439,1447,1451,1453,1459,1471,1481,1483,1487,1489,1493,1499,1511,1523,1531,1543,1549,1553,1559,1567,1571,1579,1583,1597,1601,1607,1609,1613,1619,1621,1627,1637,1657,1663,1667,1669,1693,1697,1699,1709,1721,1723,1733,1741,1747,1753,1759,1777,1783,1787,1789,1801,1811,1823,1831,1847,1861,1867,1871,1873,1877,1879,1889,1901,1907,1913,1931,1933,1949,1951,1973,1979,1987,1993,1997,1999,2003,2011,2017,2027,2029,2039,2053,2063,2069,2081,2083,2087,2089,2099,2111,2113,2129,2131,2137,2141,2143,2153,2161,2179,2203,2207,2213,2221,2237,2239,2243,2251,2267,2269,2273,2281,2287,2293,2297,2309,2311,2333,2339,2341,2347,2351,2357,2371,2377,2381,2383,2389,2393,2399,2411,2417,2423,2437,2441,2447,2459,2467,2473,2477,2503,2521,2531,2539,2543,2549,2551,2557,2579,2591,2593,2609,2617,2621,2633,2647,2657,2659,2663,2671,2677,2683,2687,2689,2693,2699,2707,2711,2713,2719,2729,2731,2741,2749,2753,2767,2777,2789,2791,2797,2801,2803,2819,2833,2837,2843,2851,2857,2861,2879,2887,2897,2903,2909,2917,2927,2939,2953,2957,2963,2969,2971,2999,3001,3011,3019,3023,3037,3041,3049,3061,3067,3079,3083,3089,3109,3119,3121,3137,3163,3167,3169,3181,3187,3191,3203,3209,3217,3221,3229,3251,3253,3257,3259,3271,3299,3301,3307,3313,3319,3323,3329,3331,3343,3347,3359,3361,3371,3373,3389,3391,3407,3413,3433,3449,3457,3461,3463,3467,3469,3491,3499,3511,3517,3527,3529,3533,3539,3541,3547,3557,3559,3571,3581,3583,3593,3607,3613,3617,3623,3631,3637,3643,3659,3671,3673,3677,3691,3697,3701,3709,3719,3727,3733,3739,3761,3767,3769,3779,3793,3797,3803,3821,3823,3833,3847,3851,3853,3863,3877,3881,3889,3907,3911,3917,3919,3923,3929,3931,3943,3947,3967,3989,4001,4003,4007,4013,4019,4021,4027,4049,4051,4057,4073,4079,4091,4093,4099,4111,4127,4129,4133,4139,4153,4157,4159,4177,4201,4211,4217,4219,4229,4231,4241,4243,4253,4259,4261,4271,4273,4283,4289,4297,4327,4337,4339,4349,4357,4363,4373,4391,4397,4409,4421,4423,4441,4447,4451,4457,4463,4481,4483,4493,4507,4513,4517,4519,4523,4547,4549,4561,4567,4583,4591,4597,4603,4621,4637,4639,4643,4649,4651,4657,4663,4673,4679,4691,4703,4721,4723,4729,4733,4751,4759,4783,4787,4789,4793,4799,4801,4813,4817,4831,4861,4871,4877,4889,4903,4909,4919,4931,4933,4937,4943,4951,4957,4967,4969,4973,4987,4993,4999,5003,5009,5011,5021,5023,5039,5051,5059,5077,5081,5087,5099,5101,5107,5113,5119,5147,5153,5167,5171,5179,5189,5197,5209,5227,5231,5233,5237,5261,5273,5279,5281,5297,5303,5309,5323,5333,5347,5351,5381,5387,5393,5399,5407,5413,5417,5419,5431,5437,5441,5443,5449,5471,5477,5479,5483,5501,5503,5507,5519,5521,5527,5531,5557,5563,5569,5573,5581,5591,5623,5639,5641,5647,5651,5653,5657,5659,5669,5683,5689,5693,5701,5711,5717,5737,5741,5743,5749,5779,5783,5791,5801,5807,5813,5821,5827,5839,5843,5849,5851,5857,5861,5867,5869,5879,5881,5897,5903,5923,5927,5939,5953,5981,5987,6007,6011,6029,6037,6043,6047,6053,6067,6073,6079,6089,6091,6101,6113,6121,6131,6133,6143,6151,6163,6173,6197,6199,6203,6211,6217,6221,6229,6247,6257,6263,6269,6271,6277,6287,6299,6301,6311,6317,6323,6329,6337,6343,6353,6359,6361,6367,6373,6379,6389,6397,6421,6427,6449,6451,6469,6473,6481,6491,6521,6529,6547,6551,6553,6563,6569,6571,6577,6581,6599,6607,6619,6637,6653,6659,6661,6673,6679,6689,6691,6701,6703,6709,6719,6733,6737,6761,6763,6779,6781,6791,6793,6803,6823,6827,6829,6833,6841,6857,6863,6869,6871,6883,6899,6907,6911,6917,6947,6949,6959,6961,6967,6971,6977,6983,6991,6997,7001,7013,7019,7027,7039,7043,7057,7069,7079,7103,7109,7121,7127,7129,7151,7159,7177,7187,7193,7207,7211,7213,7219,7229,7237,7243,7247,7253,7283,7297,7307,7309,7321,7331,7333,7349,7351,7369,7393,7411,7417,7433,7451,7457,7459,7477,7481,7487,7489,7499,7507,7517,7523,7529,7537,7541,7547,7549,7559,7561,7573,7577,7583,7589,7591,7603,7607,7621,7639,7643,7649,7669,7673,7681,7687,7691,7699,7703,7717,7723,7727,7741,7753,7757,7759,7789,7793,7817,7823,7829,7841,7853,7867,7873,7877,7879,7883,7901,7907,7919 + }; - private static int[] Primes = - { - 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997,1009,1013,1019,1021,1031,1033,1039,1049,1051,1061,1063,1069,1087,1091,1093,1097,1103,1109,1117,1123,1129,1151,1153,1163,1171,1181,1187,1193,1201,1213,1217,1223,1229,1231,1237,1249,1259,1277,1279,1283,1289,1291,1297,1301,1303,1307,1319,1321,1327,1361,1367,1373,1381,1399,1409,1423,1427,1429,1433,1439,1447,1451,1453,1459,1471,1481,1483,1487,1489,1493,1499,1511,1523,1531,1543,1549,1553,1559,1567,1571,1579,1583,1597,1601,1607,1609,1613,1619,1621,1627,1637,1657,1663,1667,1669,1693,1697,1699,1709,1721,1723,1733,1741,1747,1753,1759,1777,1783,1787,1789,1801,1811,1823,1831,1847,1861,1867,1871,1873,1877,1879,1889,1901,1907,1913,1931,1933,1949,1951,1973,1979,1987,1993,1997,1999,2003,2011,2017,2027,2029,2039,2053,2063,2069,2081,2083,2087,2089,2099,2111,2113,2129,2131,2137,2141,2143,2153,2161,2179,2203,2207,2213,2221,2237,2239,2243,2251,2267,2269,2273,2281,2287,2293,2297,2309,2311,2333,2339,2341,2347,2351,2357,2371,2377,2381,2383,2389,2393,2399,2411,2417,2423,2437,2441,2447,2459,2467,2473,2477,2503,2521,2531,2539,2543,2549,2551,2557,2579,2591,2593,2609,2617,2621,2633,2647,2657,2659,2663,2671,2677,2683,2687,2689,2693,2699,2707,2711,2713,2719,2729,2731,2741,2749,2753,2767,2777,2789,2791,2797,2801,2803,2819,2833,2837,2843,2851,2857,2861,2879,2887,2897,2903,2909,2917,2927,2939,2953,2957,2963,2969,2971,2999,3001,3011,3019,3023,3037,3041,3049,3061,3067,3079,3083,3089,3109,3119,3121,3137,3163,3167,3169,3181,3187,3191,3203,3209,3217,3221,3229,3251,3253,3257,3259,3271,3299,3301,3307,3313,3319,3323,3329,3331,3343,3347,3359,3361,3371,3373,3389,3391,3407,3413,3433,3449,3457,3461,3463,3467,3469,3491,3499,3511,3517,3527,3529,3533,3539,3541,3547,3557,3559,3571,3581,3583,3593,3607,3613,3617,3623,3631,3637,3643,3659,3671,3673,3677,3691,3697,3701,3709,3719,3727,3733,3739,3761,3767,3769,3779,3793,3797,3803,3821,3823,3833,3847,3851,3853,3863,3877,3881,3889,3907,3911,3917,3919,3923,3929,3931,3943,3947,3967,3989,4001,4003,4007,4013,4019,4021,4027,4049,4051,4057,4073,4079,4091,4093,4099,4111,4127,4129,4133,4139,4153,4157,4159,4177,4201,4211,4217,4219,4229,4231,4241,4243,4253,4259,4261,4271,4273,4283,4289,4297,4327,4337,4339,4349,4357,4363,4373,4391,4397,4409,4421,4423,4441,4447,4451,4457,4463,4481,4483,4493,4507,4513,4517,4519,4523,4547,4549,4561,4567,4583,4591,4597,4603,4621,4637,4639,4643,4649,4651,4657,4663,4673,4679,4691,4703,4721,4723,4729,4733,4751,4759,4783,4787,4789,4793,4799,4801,4813,4817,4831,4861,4871,4877,4889,4903,4909,4919,4931,4933,4937,4943,4951,4957,4967,4969,4973,4987,4993,4999,5003,5009,5011,5021,5023,5039,5051,5059,5077,5081,5087,5099,5101,5107,5113,5119,5147,5153,5167,5171,5179,5189,5197,5209,5227,5231,5233,5237,5261,5273,5279,5281,5297,5303,5309,5323,5333,5347,5351,5381,5387,5393,5399,5407,5413,5417,5419,5431,5437,5441,5443,5449,5471,5477,5479,5483,5501,5503,5507,5519,5521,5527,5531,5557,5563,5569,5573,5581,5591,5623,5639,5641,5647,5651,5653,5657,5659,5669,5683,5689,5693,5701,5711,5717,5737,5741,5743,5749,5779,5783,5791,5801,5807,5813,5821,5827,5839,5843,5849,5851,5857,5861,5867,5869,5879,5881,5897,5903,5923,5927,5939,5953,5981,5987,6007,6011,6029,6037,6043,6047,6053,6067,6073,6079,6089,6091,6101,6113,6121,6131,6133,6143,6151,6163,6173,6197,6199,6203,6211,6217,6221,6229,6247,6257,6263,6269,6271,6277,6287,6299,6301,6311,6317,6323,6329,6337,6343,6353,6359,6361,6367,6373,6379,6389,6397,6421,6427,6449,6451,6469,6473,6481,6491,6521,6529,6547,6551,6553,6563,6569,6571,6577,6581,6599,6607,6619,6637,6653,6659,6661,6673,6679,6689,6691,6701,6703,6709,6719,6733,6737,6761,6763,6779,6781,6791,6793,6803,6823,6827,6829,6833,6841,6857,6863,6869,6871,6883,6899,6907,6911,6917,6947,6949,6959,6961,6967,6971,6977,6983,6991,6997,7001,7013,7019,7027,7039,7043,7057,7069,7079,7103,7109,7121,7127,7129,7151,7159,7177,7187,7193,7207,7211,7213,7219,7229,7237,7243,7247,7253,7283,7297,7307,7309,7321,7331,7333,7349,7351,7369,7393,7411,7417,7433,7451,7457,7459,7477,7481,7487,7489,7499,7507,7517,7523,7529,7537,7541,7547,7549,7559,7561,7573,7577,7583,7589,7591,7603,7607,7621,7639,7643,7649,7669,7673,7681,7687,7691,7699,7703,7717,7723,7727,7741,7753,7757,7759,7789,7793,7817,7823,7829,7841,7853,7867,7873,7877,7879,7883,7901,7907,7919 - }; - - public static void SetRandom(Random random) - { - Random = random; - } - - internal static int Next(int maxValue) - { - return Random.Next(maxValue); - } - - /// - /// A psuedorandom nonrepeating sequence of integers from 0 to n. - /// - internal static LinearCongruentialEnumerator LinearCongruentialSequence(int n) - { - if (n == 0) - { - // bail out, empty enumerator - return new LinearCongruentialEnumerator(0, 0, 0); - } - - var x = Primes[Random.Next(Primes.Length - 1)]; - while (x % n == 0) - { - // not coprime, try again - x = Primes[Random.Next(Primes.Length - 1)]; - } - - return new LinearCongruentialEnumerator(Random.Next(n), x, n); - } + public static void SetRandom(Random random) + { + Random = random; } - public struct LinearCongruentialEnumerator + internal static int Next(int maxValue) { - private readonly int start; - private readonly int count; - private readonly int prime; - private int current; + return Random.Next(maxValue); + } - public LinearCongruentialEnumerator GetEnumerator() => this; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal LinearCongruentialEnumerator(int start, int prime, int count) + /// + /// A psuedorandom nonrepeating sequence of integers from 0 to n. + /// + internal static LinearCongruentialEnumerator LinearCongruentialSequence(int n) + { + if (n == 0) { - current = start; - this.start = start; - this.prime = prime; - this.count = count; + // bail out, empty enumerator + return new LinearCongruentialEnumerator(0, 0, 0); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() + var x = Primes[Random.Next(Primes.Length - 1)]; + while (x % n == 0) { - current += 1; - if (current < start + count) - { - return true; - } - - return false; + // not coprime, try again + x = Primes[Random.Next(Primes.Length - 1)]; } - public int Current - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (current * prime) % count; - } + return new LinearCongruentialEnumerator(Random.Next(n), x, n); + } +} + +public struct LinearCongruentialEnumerator +{ + private readonly int start; + private readonly int count; + private readonly int prime; + private int current; + + public LinearCongruentialEnumerator GetEnumerator() => this; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal LinearCongruentialEnumerator(int start, int prime, int count) + { + current = start; + this.start = start; + this.prime = prime; + this.count = count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + current += 1; + if (current < start + count) + { + return true; + } + + return false; + } + + public int Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (current * prime) % count; } } diff --git a/src/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..40b64ef 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> OutRelationSets = new Dictionary>(16); + internal Dictionary> InRelationSets = new Dictionary>(16); + private Stack> ListPool = new Stack>(); + + private bool IsDisposed; + + 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 (!OutRelationSets.ContainsKey(entityA)) { - return new ReverseSpanEnumerator<(Entity, Entity)>(new Span<(Entity, Entity)>(relations, count)); + OutRelationSets[entityA] = AcquireHashSetFromPool(); } + OutRelationSets[entityA].Add(entityB); - public void Set(in Entity entityA, in Entity entityB, TRelation relationData) + if (!InRelationSets.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; + InRelationSets[entityB] = AcquireHashSetFromPool(); } + InRelationSets[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 (OutRelationSets.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 (!OutRelationSets.ContainsKey(Entity) || OutRelationSets[Entity].Count == 0) + { + throw new KeyNotFoundException("No out relations to this entity!"); + } #endif - return outRelations[entityID][n]; - } + return OutRelationSets[Entity][n]; + } - public bool HasOutRelation(int entityID) - { - return outRelations.ContainsKey(entityID) && outRelations[entityID].Count > 0; - } + public bool HasOutRelation(Entity Entity) + { + return OutRelationSets.ContainsKey(Entity) && OutRelationSets[Entity].Count > 0; + } - public int OutRelationCount(int entityID) - { - return outRelations.TryGetValue(entityID, out var entityOutRelations) ? entityOutRelations.Count : 0; - } + public int OutRelationCount(Entity Entity) + { + return OutRelationSets.TryGetValue(Entity, out var entityOutRelations) ? entityOutRelations.Count : 0; + } - public ReverseSpanEnumerator InRelations(int entityID) + public ReverseSpanEnumerator InRelations(Entity Entity) + { + if (InRelationSets.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 (!InRelationSets.ContainsKey(Entity) || InRelationSets[Entity].Count == 0) + { + throw new KeyNotFoundException("No in relations to this entity!"); + } #endif - return inRelations[entityID][n]; - } + return InRelationSets[Entity][n]; + } - public bool HasInRelation(int entityID) + public bool HasInRelation(Entity Entity) + { + return InRelationSets.ContainsKey(Entity) && InRelationSets[Entity].Count > 0; + } + + public int InRelationCount(Entity Entity) + { + return InRelationSets.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 (OutRelationSets.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 (OutRelationSets[entityA].Count == 0) { - entityOutRelations.Remove(entityB.ID); - if (outRelations[entityA.ID].Count == 0) - { - aEmpty = true; - } + aEmpty = true; + } + } + + if (InRelationSets.TryGetValue(entityB, out var entityInRelations)) + { + entityInRelations.Remove(entityA); + if (InRelationSets[entityB].Count == 0) + { + bEmpty = true; + } + } + + if (Indices.TryGetValue(relation, out var index)) + { + var lastElementIndex = Relations.Count - 1; + + // 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)) + RelationDatas.Delete(index); + Relations.Delete(index); + + Indices.Remove(relation); + } + + return (aEmpty, bEmpty); + } + + public void RemoveEntity(in Entity entity) + { + if (OutRelationSets.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); + OutRelationSets.Remove(entity); + } - // move an element into the hole - if (index != lastElementIndex) + if (InRelationSets.TryGetValue(entity, out var entityInRelations)) + { + foreach (var entityA in entityInRelations) + { + Remove(entityA, entity); + } + + ReturnHashSetToPool(entityInRelations); + InRelationSets.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 InRelationSets.Values) + { + ReturnHashSetToPool(set); + } + InRelationSets.Clear(); + + foreach (var set in OutRelationSets.Values) + { + ReturnHashSetToPool(set); + } + OutRelationSets.Clear(); + + Relations.Clear(); + RelationDatas.Clear(); + } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + 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); + IsDisposed = 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/Renderer.cs b/src/Renderer.cs index f6118c2..4bccf51 100644 --- a/src/Renderer.cs +++ b/src/Renderer.cs @@ -1,7 +1,6 @@ -namespace MoonTools.ECS +namespace MoonTools.ECS; + +public abstract class Renderer : EntityComponentReader { - public abstract class Renderer : EntityComponentReader - { - public Renderer(World world) : base(world) { } - } + public Renderer(World world) : base(world) { } } diff --git a/src/Snapshot.cs b/src/Snapshot.cs new file mode 100644 index 0000000..0421406 --- /dev/null +++ b/src/Snapshot.cs @@ -0,0 +1,384 @@ +using System; +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 : IDisposable +{ + private Dictionary ComponentSnapshots = new Dictionary(); + + private Dictionary> Filters = new Dictionary>(); + + private Dictionary RelationSnapshots = + new Dictionary(); + + private Dictionary> EntityRelationIndex = + new Dictionary>(); + + private Dictionary> EntityComponentIndex = + new Dictionary>(); + + private Dictionary EntityTags = new Dictionary(); + + private IdAssigner EntityIdAssigner = new IdAssigner(); + + private bool IsDisposed; + + public void Restore(World world) + { + // restore id assigner state + EntityIdAssigner.CopyTo(world.EntityIdAssigner); + + // restore filter states + // this could be sped up if we figured out a direct IndexableSet copy + foreach (var (signature, entityList) in Filters) + { + var filter = world.FilterIndex[signature]; + + filter.Clear(); + + foreach (var entity in entityList) + { + filter.AddEntity(entity); + } + } + + // clear all component storages in case any were created after snapshot + // FIXME: this can be eliminated via component discovery + foreach (var (typeId, componentStorage) in world.ComponentIndex) + { + componentStorage.Clear(); + } + + // clear all relation storages in case any were created after snapshot + // FIXME: this can be eliminated via component discovery + foreach (var (typeId, relationStorage) in world.RelationIndex) + { + relationStorage.Clear(); + } + + // restore components + foreach (var (typeId, componentSnapshot) in ComponentSnapshots) + { + var componentStorage = world.ComponentIndex[typeId]; + componentSnapshot.Restore(componentStorage); + } + + // 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 world.EntityRelationIndex) + { + relationTypeSet.Clear(); + } + + foreach (var (id, relationTypeSet) in EntityRelationIndex) + { + foreach (var typeId in relationTypeSet) + { + world.EntityRelationIndex[id].Add(typeId); + } + } + + // restore entity component index state + // FIXME: arrghghhh this is so slow + + foreach (var (id, componentTypeSet) in world.EntityComponentIndex) + { + componentTypeSet.Clear(); + } + + foreach (var (id, componentTypeSet) in EntityComponentIndex) + { + foreach (var typeId in componentTypeSet) + { + world.EntityComponentIndex[id].Add(typeId); + } + } + + // restore entity tags + foreach (var (id, s) in EntityTags) + { + world.EntityTags[id] = s; + } + } + + public void Take(World world) + { + // copy id assigner state + world.EntityIdAssigner.CopyTo(EntityIdAssigner); + + // copy filter states + foreach (var (_, filter) in world.FilterIndex) + { + TakeFilterSnapshot(filter); + } + + // 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); + } + } + + // copy entity component index + // FIXME: arghhhh this is so slow + foreach (var (id, componentTypeSet) in world.EntityComponentIndex) + { + if (!EntityComponentIndex.ContainsKey(id)) + { + EntityComponentIndex.Add(id, new IndexableSet()); + } + + EntityComponentIndex[id].Clear(); + + foreach (var typeId in componentTypeSet) + { + EntityComponentIndex[id].Add(typeId); + } + } + + // copy entity tags + foreach (var (id, s) in world.EntityTags) + { + EntityTags[id] = s; + } + } + + private void TakeFilterSnapshot(Filter filter) + { + if (!Filters.TryGetValue(filter.Signature, out var entities)) + { + entities = new List(); + Filters.Add(filter.Signature, entities); + } + + entities.Clear(); + + foreach (var entity in filter.EntitySet.AsSpan()) + { + entities.Add(entity); + } + } + + 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 ComponentSnapshot : IDisposable + { + private readonly NativeArray Components; + private readonly NativeArray EntityIDs; + + private bool IsDisposed; + + 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; + } + } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + Components.Dispose(); + EntityIDs.Dispose(); + } + + IsDisposed = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } + + private class RelationSnapshot : IDisposable + { + private NativeArray Relations; + private NativeArray RelationDatas; + + private bool IsDisposed; + + 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.OutRelationSets.ContainsKey(relation.Item1)) + { + relationStorage.OutRelationSets[relation.Item1] = + relationStorage.AcquireHashSetFromPool(); + } + + relationStorage.OutRelationSets[relation.Item1].Add(relation.Item2); + + if (!relationStorage.InRelationSets.ContainsKey(relation.Item2)) + { + relationStorage.InRelationSets[relation.Item2] = + relationStorage.AcquireHashSetFromPool(); + } + + relationStorage.InRelationSets[relation.Item2].Add(relation.Item1); + } + } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + Relations.Dispose(); + RelationDatas.Dispose(); + } + + IsDisposed = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + foreach (var componentSnapshot in ComponentSnapshots.Values) + { + componentSnapshot.Dispose(); + } + + foreach (var relationSnapshot in RelationSnapshots.Values) + { + relationSnapshot.Dispose(); + } + + foreach (var componentSet in EntityComponentIndex.Values) + { + componentSet.Dispose(); + } + + foreach (var relationSet in EntityRelationIndex.Values) + { + relationSet.Dispose(); + } + + EntityIdAssigner.Dispose(); + } + + IsDisposed = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} 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..62bc5d6 --- /dev/null +++ b/src/TypeId.cs @@ -0,0 +1,11 @@ +using System; + +namespace MoonTools.ECS; + +public readonly record struct TypeId(uint Value) : IComparable +{ + public int CompareTo(TypeId other) + { + return Value.CompareTo(other.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..70aad7d 100644 --- a/src/World.cs +++ b/src/World.cs @@ -1,194 +1,520 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; +using MoonTools.ECS.Collections; -namespace MoonTools.ECS +namespace MoonTools.ECS; + +public class World : IDisposable { - public class World - { - 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(); - public World() - { - ComponentDepot = new ComponentDepot(ComponentTypeIndices); - RelationDepot = new RelationDepot(EntityStorage, RelationTypeIndices); - FilterStorage = new FilterStorage(EntityStorage, ComponentTypeIndices); - } - - public Entity CreateEntity(string tag = "") - { - return EntityStorage.Create(tag); - } - - public void Tag(Entity entity, string tag) - { - EntityStorage.Tag(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!"); - } + private Dictionary IdToType = new Dictionary(); #endif - ComponentDepot.Set(entity.ID, component); - if (EntityStorage.SetComponent(entity.ID, ComponentTypeIndices.GetIndex())) + // Get element size from a TypeId + private readonly Dictionary ElementSizes = new Dictionary(); + + // Filters + internal readonly Dictionary FilterIndex = new Dictionary(); + private readonly Dictionary> TypeToFilter = new Dictionary>(); + + // TODO: can we make the tag an native array of chars at some point? + internal Dictionary EntityTags = new Dictionary(); + + // Relation Storages + internal Dictionary RelationIndex = new Dictionary(); + internal Dictionary> EntityRelationIndex = new Dictionary>(); + + // Message Storages + private Dictionary MessageIndex = new Dictionary(); + + public FilterBuilder FilterBuilder => new FilterBuilder(this); + + internal readonly Dictionary ComponentIndex = new Dictionary(); + internal Dictionary> EntityComponentIndex = new Dictionary>(); + + internal IdAssigner EntityIdAssigner = new IdAssigner(); + private IdAssigner TypeIdAssigner = new IdAssigner(); + + private bool IsDisposed; + + internal TypeId GetTypeId() where T : unmanaged + { + if (TypeToId.ContainsKey(typeof(T))) + { + 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; + } + + internal TypeId GetComponentTypeId() where T : unmanaged + { + var typeId = GetTypeId(); + if (ComponentIndex.TryGetValue(typeId, out var componentStorage)) + { + return typeId; + } + + componentStorage = new ComponentStorage(typeId, ElementSizes[typeId]); + ComponentIndex.Add(typeId, componentStorage); + TypeToFilter.Add(typeId, new List()); + return typeId; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ComponentStorage GetComponentStorage() where T : unmanaged + { + var typeId = GetTypeId(); + if (ComponentIndex.TryGetValue(typeId, out var componentStorage)) + { + return componentStorage; + } + + componentStorage = new ComponentStorage(typeId, ElementSizes[typeId]); + ComponentIndex.Add(typeId, componentStorage); + TypeToFilter.Add(typeId, new List()); + return componentStorage; + } + + // FILTERS + + internal Filter GetFilter(FilterSignature signature) + { + if (!FilterIndex.TryGetValue(signature, out var filter)) + { + filter = new Filter(this, signature); + + foreach (var typeId in signature.Included) { - FilterStorage.Check(entity.ID); + TypeToFilter[typeId].Add(filter); + } + + foreach (var typeId in signature.Excluded) + { + TypeToFilter[typeId].Add(filter); + } + + FilterIndex.Add(signature, filter); + } + + return filter; + } + + // ENTITIES + + public Entity CreateEntity(string tag = "") + { + var entity = new Entity(EntityIdAssigner.Assign()); + + if (!EntityComponentIndex.ContainsKey(entity)) + { + EntityRelationIndex.Add(entity, new IndexableSet()); + EntityComponentIndex.Add(entity, new IndexableSet()); + } + + EntityTags[entity] = tag; + + return entity; + } + + public void Tag(Entity entity, string tag) + { + EntityTags[entity] = tag; + } + + public string GetTag(Entity entity) + { + return EntityTags[entity]; + } + + public void Destroy(in Entity entity) + { + // remove all components from storages + foreach (var componentTypeIndex in EntityComponentIndex[entity]) + { + var componentStorage = ComponentIndex[componentTypeIndex]; + componentStorage.Remove(entity); + + foreach (var filter in TypeToFilter[componentTypeIndex]) + { + filter.RemoveEntity(entity); } } - // untyped version for Transfer - // no filter check because filter state is copied directly - internal unsafe void Set(Entity entity, int componentTypeIndex, void* component) + // remove all relations from storage + foreach (var relationTypeIndex in EntityRelationIndex[entity]) { - ComponentDepot.Set(entity.ID, componentTypeIndex, component); - EntityStorage.SetComponent(entity.ID, componentTypeIndex); + var relationStorage = RelationIndex[relationTypeIndex]; + relationStorage.RemoveEntity(entity); } - public void Remove(in Entity entity) where TComponent : unmanaged + EntityComponentIndex[entity].Clear(); + EntityRelationIndex[entity].Clear(); + + // recycle ID + EntityIdAssigner.Unassign(entity.ID); + } + + // COMPONENTS + + public bool Has(in Entity entity) where T : unmanaged + { + var storage = GetComponentStorage(); + return storage.Has(entity); + } + + internal bool Has(in Entity entity, in TypeId typeId) + { + return EntityComponentIndex[entity].Contains(typeId); + } + + public bool Some() where T : unmanaged + { + var storage = GetComponentStorage(); + return storage.Any(); + } + + public ref T Get(in Entity entity) where T : unmanaged + { + var storage = GetComponentStorage(); + return ref storage.Get(entity); + } + + public ref T GetSingleton() where T : unmanaged + { + var storage = GetComponentStorage(); + return ref storage.GetFirst(); + } + + public Entity GetSingletonEntity() where T : unmanaged + { + var storage = GetComponentStorage(); + return storage.FirstEntity(); + } + + public void Set(in Entity entity, in T component) where T : unmanaged + { + var componentStorage = GetComponentStorage(); + + if (!componentStorage.Set(entity, component)) { - if (EntityStorage.RemoveComponent(entity.ID, ComponentTypeIndices.GetIndex())) + EntityComponentIndex[entity].Add(componentStorage.TypeId); + + foreach (var filter in TypeToFilter[componentStorage.TypeId]) { - // 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); + filter.Check(entity); } } - - 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); - } - - public void Destroy(in Entity entity) - { - foreach (var componentTypeIndex in EntityStorage.ComponentTypeIndices(entity.ID)) - { - // 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); - } - - foreach (var relationTypeIndex in EntityStorage.RelationTypeIndices(entity.ID)) - { - RelationDepot.UnrelateAll(entity.ID, relationTypeIndex); - EntityStorage.RemoveRelation(entity.ID, relationTypeIndex); - } - - EntityStorage.Destroy(entity); - } - - - 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) - { - other.Destroy(entity); - } - - // create entities - foreach (var entity in filter.Entities) - { - 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)) - { - other.Set(otherWorldEntity, componentTypeIndex, ComponentDepot.UntypedGet(entity.ID, componentTypeIndex)); - } - } - - // transfer filters last so callbacks trigger correctly - FilterStorage.TransferStorage(WorldToTransferID, other.FilterStorage); - } + } + + public void Remove(in Entity entity) where T : unmanaged + { + var componentStorage = GetComponentStorage(); + + if (componentStorage.Remove(entity)) + { + EntityComponentIndex[entity].Remove(componentStorage.TypeId); + + foreach (var filter in TypeToFilter[componentStorage.TypeId]) + { + filter.Check(entity); + } + } + } + + // RELATIONS + + private RelationStorage RegisterRelationType(TypeId typeId) + { + var relationStorage = new RelationStorage(ElementSizes[typeId]); + RelationIndex.Add(typeId, relationStorage); + return relationStorage; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private RelationStorage GetRelationStorage() where T : unmanaged + { + var typeId = GetTypeId(); + if (RelationIndex.TryGetValue(typeId, out var relationStorage)) + { + return relationStorage; + } + + return RegisterRelationType(typeId); + } + + public void Relate(in Entity entityA, in Entity entityB, in T relation) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + relationStorage.Set(entityA, entityB, relation); + EntityRelationIndex[entityA].Add(TypeToId[typeof(T)]); + EntityRelationIndex[entityB].Add(TypeToId[typeof(T)]); + } + + public void Unrelate(in Entity entityA, in Entity entityB) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + relationStorage.Remove(entityA, entityB); + } + + public void UnrelateAll(in Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + relationStorage.RemoveEntity(entity); + } + + public bool Related(in Entity entityA, in Entity entityB) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.Has(entityA, entityB); + } + + public T GetRelationData(in Entity entityA, in Entity entityB) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.Get(entityA, entityB); + } + + public ReverseSpanEnumerator<(Entity, Entity)> Relations() where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.All(); + } + + public ReverseSpanEnumerator OutRelations(Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.OutRelations(entity); + } + + public Entity OutRelationSingleton(in Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.OutFirst(entity); + } + + public bool HasOutRelation(in Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.HasOutRelation(entity); + } + + public int OutRelationCount(in Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.OutRelationCount(entity); + } + + public Entity NthOutRelation(in Entity entity, int n) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.OutNth(entity, n); + } + + public ReverseSpanEnumerator InRelations(Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.InRelations(entity); + } + + public Entity InRelationSingleton(in Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.InFirst(entity); + } + + public bool HasInRelation(in Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.HasInRelation(entity); + } + + public int InRelationCount(in Entity entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.InRelationCount(entity); + } + + public Entity NthInRelation(in Entity entity, int n) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.InNth(entity, n); + } + + // MESSAGES + + private TypeId GetMessageTypeId() where T : unmanaged + { + var typeId = GetTypeId(); + + if (!MessageIndex.ContainsKey(typeId)) + { + MessageIndex.Add(typeId, new MessageStorage(Unsafe.SizeOf())); + } + + return typeId; + } + + public void Send(in T message) where T : unmanaged + { + var typeId = GetMessageTypeId(); + MessageIndex[typeId].Add(message); + } + + public bool SomeMessage() where T : unmanaged + { + var typeId = GetMessageTypeId(); + return MessageIndex[typeId].Some(); + } + + public ReadOnlySpan ReadMessages() where T : unmanaged + { + var typeId = GetMessageTypeId(); + return MessageIndex[typeId].All(); + } + + public T ReadMessage() where T : unmanaged + { + var typeId = GetMessageTypeId(); + return MessageIndex[typeId].First(); + } + + public void ClearMessages() where T : unmanaged + { + var typeId = GetMessageTypeId(); + MessageIndex[typeId].Clear(); + } + + // TODO: temporary component storage? + public void FinishUpdate() + { + foreach (var (_, messageStorage) in MessageIndex) + { + messageStorage.Clear(); + } + } + + // DEBUG + // NOTE: these methods are very inefficient + // they should only be used in debugging contexts!! +#if DEBUG + public ComponentTypeEnumerator Debug_GetAllComponentTypes(Entity entity) + { + return new ComponentTypeEnumerator(this, EntityComponentIndex[entity]); + } + + public IEnumerable Debug_GetEntities(Type componentType) + { + var storage = ComponentIndex[TypeToId[componentType]]; + return storage.Debug_GetEntities(); + } + + public IEnumerable Debug_SearchComponentType(string typeString) + { + foreach (var type in TypeToId.Keys) + { + if (type.ToString().ToLower().Contains(typeString.ToLower())) + { + yield return type; + } + } + } + + public ref struct ComponentTypeEnumerator + { + private World World; + private IndexableSet Types; + private int ComponentIndex; + + public ComponentTypeEnumerator GetEnumerator() => this; + + internal ComponentTypeEnumerator( + World world, + IndexableSet types + ) + { + World = world; + Types = types; + ComponentIndex = -1; + } + + public bool MoveNext() + { + ComponentIndex += 1; + return ComponentIndex < Types.Count; + } + + public Type Current => World.IdToType[Types[ComponentIndex]]; + } +#endif + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + foreach (var componentStorage in ComponentIndex.Values) + { + componentStorage.Dispose(); + } + + foreach (var relationStorage in RelationIndex.Values) + { + relationStorage.Dispose(); + } + + foreach (var messageStorage in MessageIndex.Values) + { + messageStorage.Dispose(); + } + + foreach (var typeSet in EntityComponentIndex.Values) + { + typeSet.Dispose(); + } + + foreach (var typeSet in EntityRelationIndex.Values) + { + typeSet.Dispose(); + } + + foreach (var filter in FilterIndex.Values) + { + filter.Dispose(); + } + + EntityIdAssigner.Dispose(); + TypeIdAssigner.Dispose(); + } + + IsDisposed = true; + } + } + + // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + // ~World() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); } }