storage refactor
							parent
							
								
									4ef7cb4302
								
							
						
					
					
						commit
						001b6714cc
					
				|  | @ -0,0 +1,42 @@ | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using MoonTools.ECS.Collections; | ||||||
|  | 
 | ||||||
|  | namespace MoonTools.ECS; | ||||||
|  | 
 | ||||||
|  | internal class Archetype | ||||||
|  | { | ||||||
|  | 	public World World; | ||||||
|  | 	public ArchetypeSignature Signature; | ||||||
|  | 	public NativeArray<Entity> Entities = new NativeArray<Entity>(); | ||||||
|  | 
 | ||||||
|  | 	public SortedDictionary<TypeId, ArchetypeEdge> Edges = new SortedDictionary<TypeId, ArchetypeEdge>(); | ||||||
|  | 
 | ||||||
|  | 	public int Count => Entities.Count; | ||||||
|  | 
 | ||||||
|  | 	public Archetype(World world, ArchetypeSignature signature) | ||||||
|  | 	{ | ||||||
|  | 		World = world; | ||||||
|  | 		Signature = signature; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public int Append(Entity entity) | ||||||
|  | 	{ | ||||||
|  | 		Entities.Append(entity); | ||||||
|  | 		return Entities.Count - 1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public int Transfer(int row, Archetype transferTo) | ||||||
|  | 	{ | ||||||
|  | 		var newIndex = transferTo.Append(Entities[row]); | ||||||
|  | 		Entities.Delete(row); | ||||||
|  | 		return newIndex; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void ClearAll() | ||||||
|  | 	{ | ||||||
|  | 		for (int i = Entities.Count - 1; i >= 0; i -= 1) | ||||||
|  | 		{ | ||||||
|  | 			World.Destroy(Entities[i]); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,3 @@ | ||||||
|  | namespace MoonTools.ECS; | ||||||
|  | 
 | ||||||
|  | internal readonly record struct ArchetypeEdge(Archetype Add, Archetype Remove); | ||||||
|  | @ -0,0 +1,3 @@ | ||||||
|  | namespace MoonTools.ECS; | ||||||
|  | 
 | ||||||
|  | internal readonly record struct ArchetypeRecord(Archetype Archetype, int Row); | ||||||
|  | @ -0,0 +1,89 @@ | ||||||
|  | using System; | ||||||
|  | using MoonTools.ECS.Collections; | ||||||
|  | 
 | ||||||
|  | namespace MoonTools.ECS; | ||||||
|  | 
 | ||||||
|  | internal class ArchetypeSignature : IEquatable<ArchetypeSignature> | ||||||
|  | { | ||||||
|  | 	public static ArchetypeSignature Empty = new ArchetypeSignature(0); | ||||||
|  | 
 | ||||||
|  | 	IndexableSet<TypeId> Ids; | ||||||
|  | 
 | ||||||
|  | 	public int Count => Ids.Count; | ||||||
|  | 
 | ||||||
|  | 	public TypeId this[int i] => Ids[i]; | ||||||
|  | 
 | ||||||
|  | 	public ArchetypeSignature() | ||||||
|  | 	{ | ||||||
|  | 		Ids = new IndexableSet<TypeId>(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public ArchetypeSignature(int capacity) | ||||||
|  | 	{ | ||||||
|  | 		Ids = new IndexableSet<TypeId>(capacity); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Maintains sorted order | ||||||
|  | 	public void Insert(TypeId componentId) | ||||||
|  | 	{ | ||||||
|  | 		Ids.Add(componentId); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void Remove(TypeId componentId) | ||||||
|  | 	{ | ||||||
|  | 		Ids.Remove(componentId); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public bool Contains(TypeId componentId) | ||||||
|  | 	{ | ||||||
|  | 		return Ids.Contains(componentId); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void CopyTo(ArchetypeSignature other) | ||||||
|  | 	{ | ||||||
|  | 		foreach (var id in other.Ids.AsSpan()) | ||||||
|  | 		{ | ||||||
|  | 			other.Ids.Add(id); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public override bool Equals(object? obj) | ||||||
|  | 	{ | ||||||
|  | 		return obj is ArchetypeSignature signature && Equals(signature); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public bool Equals(ArchetypeSignature? other) | ||||||
|  | 	{ | ||||||
|  | 		if (other == null) | ||||||
|  | 		{ | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (Ids.Count != other.Ids.Count) | ||||||
|  | 		{ | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		for (int i = 0; i < Ids.Count; i += 1) | ||||||
|  | 		{ | ||||||
|  | 			if (Ids[i] != other.Ids[i]) | ||||||
|  | 			{ | ||||||
|  | 				return false; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public override int GetHashCode() | ||||||
|  | 	{ | ||||||
|  | 		var hashcode = 1; | ||||||
|  | 
 | ||||||
|  | 		foreach (var id in Ids) | ||||||
|  | 		{ | ||||||
|  | 			hashcode = HashCode.Combine(hashcode, id); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return hashcode; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,123 @@ | ||||||
|  | using System; | ||||||
|  | using System.Runtime.CompilerServices; | ||||||
|  | using System.Runtime.InteropServices; | ||||||
|  | 
 | ||||||
|  | namespace MoonTools.ECS.Collections; | ||||||
|  | 
 | ||||||
|  | public unsafe class NativeArray<T> : IDisposable where T : unmanaged | ||||||
|  | { | ||||||
|  | 	private T* Elements; | ||||||
|  | 	public int Count { get; private set;} | ||||||
|  | 	private int Capacity; | ||||||
|  | 	private int ElementSize; | ||||||
|  | 
 | ||||||
|  | 	public Span<T>.Enumerator GetEnumerator() => new Span<T>(Elements, Count).GetEnumerator(); | ||||||
|  | 
 | ||||||
|  | 	private bool disposed; | ||||||
|  | 
 | ||||||
|  | 	public NativeArray(int capacity = 16) | ||||||
|  | 	{ | ||||||
|  | 		this.Capacity = capacity; | ||||||
|  | 		ElementSize = Unsafe.SizeOf<T>(); | ||||||
|  | 		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<T>())); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		Elements[Count] = item; | ||||||
|  | 		Count += 1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void RemoveLastElement() | ||||||
|  | 	{ | ||||||
|  | 		Count -= 1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public bool TryPop(out T element) | ||||||
|  | 	{ | ||||||
|  | 		if (Count > 0) | ||||||
|  | 		{ | ||||||
|  | 			element = Elements[Count - 1]; | ||||||
|  | 			Count -= 1; | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		element = default; | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void Clear() | ||||||
|  | 	{ | ||||||
|  | 		Count = 0; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private void ResizeTo(int size) | ||||||
|  | 	{ | ||||||
|  | 		Capacity = size; | ||||||
|  | 		Elements = (T*) NativeMemory.Realloc((void*) Elements, (nuint) (ElementSize * Capacity)); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Fills gap by copying final element to the deleted index | ||||||
|  | 	public void Delete(int index) | ||||||
|  | 	{ | ||||||
|  | 		if (Count > 1) | ||||||
|  | 		{ | ||||||
|  | 			NativeMemory.Copy( | ||||||
|  | 				(void*) (Elements + ((Count - 1) * ElementSize)), | ||||||
|  | 				(void*) (Elements + (index * ElementSize)), | ||||||
|  | 				(nuint) ElementSize | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		Count -= 1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void CopyTo(NativeArray<T> other) | ||||||
|  | 	{ | ||||||
|  | 		if (Count >= other.Capacity) | ||||||
|  | 		{ | ||||||
|  | 			other.ResizeTo(Count); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		NativeMemory.Copy( | ||||||
|  | 			(void*) Elements, | ||||||
|  | 			(void*) other.Elements, | ||||||
|  | 			(nuint) (ElementSize * Count) | ||||||
|  | 		); | ||||||
|  | 
 | ||||||
|  | 		other.Count = Count; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	protected virtual void Dispose(bool disposing) | ||||||
|  | 	{ | ||||||
|  | 		if (!disposed) | ||||||
|  | 		{ | ||||||
|  | 			NativeMemory.Free(Elements); | ||||||
|  | 			Elements = null; | ||||||
|  | 
 | ||||||
|  | 			disposed = true; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	~NativeArray() | ||||||
|  | 	{ | ||||||
|  | 		// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||||
|  | 		Dispose(disposing: false); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void Dispose() | ||||||
|  | 	{ | ||||||
|  | 		// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||||
|  | 		Dispose(disposing: true); | ||||||
|  | 		GC.SuppressFinalize(this); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,137 @@ | ||||||
|  | using System; | ||||||
|  | using System.Runtime.CompilerServices; | ||||||
|  | using System.Runtime.InteropServices; | ||||||
|  | 
 | ||||||
|  | namespace MoonTools.ECS.Collections; | ||||||
|  | 
 | ||||||
|  | internal unsafe class NativeArray : IDisposable | ||||||
|  | { | ||||||
|  | 	private nint Elements; | ||||||
|  | 	public int Count { get; private set;} | ||||||
|  | 	private int Capacity; | ||||||
|  | 	public readonly int ElementSize; | ||||||
|  | 
 | ||||||
|  | 	private bool IsDisposed; | ||||||
|  | 
 | ||||||
|  | 	public NativeArray(int elementSize) | ||||||
|  | 	{ | ||||||
|  | 		Capacity = 16; | ||||||
|  | 		Count = 0; | ||||||
|  | 		ElementSize = elementSize; | ||||||
|  | 
 | ||||||
|  | 		Elements = (nint) NativeMemory.Alloc((nuint) (ElementSize * Capacity)); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public Span<T> ToSpan<T>() | ||||||
|  | 	{ | ||||||
|  | 		return new Span<T>((void*) Elements, Count); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  | 	public ref T Get<T>(int i) where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		return ref ((T*) Elements)[i]; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void Set<T>(int i, in T element) where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		Unsafe.Write((void*) (Elements + ElementSize * i), element); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private void Resize() | ||||||
|  | 	{ | ||||||
|  | 		Capacity *= 2; | ||||||
|  | 		Elements = (nint) NativeMemory.Realloc((void*) Elements, (nuint) (ElementSize * Capacity)); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private void ResizeTo(int capacity) | ||||||
|  | 	{ | ||||||
|  | 		Capacity = capacity; | ||||||
|  | 		Elements = (nint) NativeMemory.Realloc((void*) Elements, (nuint) (ElementSize * Capacity)); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Fills gap by copying final element to the deleted index | ||||||
|  | 	public void Delete(int index) | ||||||
|  | 	{ | ||||||
|  | 		if (Count > 1) | ||||||
|  | 		{ | ||||||
|  | 			NativeMemory.Copy( | ||||||
|  | 				(void*) (Elements + ((Count - 1) * ElementSize)), | ||||||
|  | 				(void*) (Elements + (index * ElementSize)), | ||||||
|  | 				(nuint) ElementSize | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		Count -= 1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void Append<T>(T component) where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		if (Count >= Capacity) | ||||||
|  | 		{ | ||||||
|  | 			Resize(); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		((T*) Elements)[Count] = component; | ||||||
|  | 		Count += 1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void CopyElementToEnd(int index, NativeArray other) | ||||||
|  | 	{ | ||||||
|  | 		if (other.Count >= other.Capacity) | ||||||
|  | 		{ | ||||||
|  | 			other.Resize(); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		NativeMemory.Copy( | ||||||
|  | 			(void*) (Elements + (index * ElementSize)), | ||||||
|  | 			(void*) (other.Elements + (other.Count * ElementSize)), | ||||||
|  | 			(nuint) ElementSize | ||||||
|  | 		); | ||||||
|  | 
 | ||||||
|  | 		other.Count += 1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void CopyAllTo(NativeArray other) | ||||||
|  | 	{ | ||||||
|  | 		if (Count >= other.Capacity) | ||||||
|  | 		{ | ||||||
|  | 			other.ResizeTo(Count); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		NativeMemory.Copy( | ||||||
|  | 			(void*) Elements, | ||||||
|  | 			(void*) other.Elements, | ||||||
|  | 			(nuint) (ElementSize * Count) | ||||||
|  | 		); | ||||||
|  | 
 | ||||||
|  | 		other.Count = Count; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void Clear() | ||||||
|  | 	{ | ||||||
|  | 		Count = 0; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	protected virtual void Dispose(bool disposing) | ||||||
|  | 	{ | ||||||
|  | 		if (!IsDisposed) | ||||||
|  | 		{ | ||||||
|  | 			NativeMemory.Free((void*) Elements); | ||||||
|  | 			IsDisposed = true; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	~NativeArray() | ||||||
|  | 	{ | ||||||
|  | 		// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||||
|  | 		Dispose(disposing: false); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void Dispose() | ||||||
|  | 	{ | ||||||
|  | 		// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||||
|  | 		Dispose(disposing: true); | ||||||
|  | 		GC.SuppressFinalize(this); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -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<TComponent>(int index) where TComponent : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			if (index >= storages.Length) |  | ||||||
| 			{ |  | ||||||
| 				Array.Resize(ref storages, storages.Length * 2); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			storages[index] = new ComponentStorage<TComponent>(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		[MethodImpl(MethodImplOptions.AggressiveInlining)] |  | ||||||
| 		private ComponentStorage<TComponent> Lookup<TComponent>() where TComponent : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			var storageIndex = ComponentTypeIndices.GetIndex<TComponent>(); |  | ||||||
| 			// TODO: is there some way to avoid this null check? |  | ||||||
| 			if (storageIndex >= storages.Length || storages[storageIndex] == null) |  | ||||||
| 			{ |  | ||||||
| 				Register<TComponent>(storageIndex); |  | ||||||
| 			} |  | ||||||
| 			return (ComponentStorage<TComponent>) storages[storageIndex]; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public bool Some<TComponent>() where TComponent : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TComponent>().Any(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public ref readonly TComponent Get<TComponent>(int entityID) where TComponent : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return ref Lookup<TComponent>().Get(entityID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public ref readonly TComponent GetFirst<TComponent>() where TComponent : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return ref Lookup<TComponent>().GetFirst(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Set<TComponent>(int entityID, in TComponent component) where TComponent : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			Lookup<TComponent>().Set(entityID, component); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public Entity GetSingletonEntity<TComponent>() where TComponent : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TComponent>().FirstEntity(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public ReadOnlySpan<TComponent> ReadComponents<TComponent>() where TComponent : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TComponent>().AllComponents(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Remove(int entityID, int storageIndex) |  | ||||||
| 		{ |  | ||||||
| 			storages[storageIndex].Remove(entityID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Remove<TComponent>(int entityID) where TComponent : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			Lookup<TComponent>().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<int> Debug_GetEntityIDs(int componentTypeIndex) |  | ||||||
| 		{ |  | ||||||
| 			return storages[componentTypeIndex].Debug_GetEntityIDs(); |  | ||||||
| 		} |  | ||||||
| #endif |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -1,112 +1,86 @@ | ||||||
| using System; | using System; | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| using System.Runtime.CompilerServices; | using MoonTools.ECS.Collections; | ||||||
| using System.Runtime.InteropServices; |  | ||||||
| 
 | 
 | ||||||
| namespace MoonTools.ECS | namespace MoonTools.ECS | ||||||
| { | { | ||||||
| 	internal abstract class ComponentStorage | 	internal unsafe class ComponentStorage : IDisposable | ||||||
| 	{ | 	{ | ||||||
| 		internal abstract unsafe void Set(int entityID, void* component); | 		internal readonly Dictionary<Entity, int> EntityIDToStorageIndex = new Dictionary<Entity, int>(16); | ||||||
| 		public abstract bool Remove(int entityID); | 		internal readonly NativeArray Components; | ||||||
| 		public abstract void Clear(); | 		internal readonly NativeArray<Entity> EntityIDs; | ||||||
| 
 | 
 | ||||||
| 		// used for debugging and template instantiation |  | ||||||
| 		internal abstract unsafe void* UntypedGet(int entityID); |  | ||||||
| 		// used to create correctly typed storage on snapshot |  | ||||||
| 		public abstract ComponentStorage CreateStorage(); |  | ||||||
| #if DEBUG |  | ||||||
| 		internal abstract object Debug_Get(int entityID); |  | ||||||
| 		internal abstract IEnumerable<int> Debug_GetEntityIDs(); |  | ||||||
| #endif |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	internal unsafe class ComponentStorage<TComponent> : ComponentStorage, IDisposable where TComponent : unmanaged |  | ||||||
| 	{ |  | ||||||
| 		private readonly Dictionary<int, int> entityIDToStorageIndex = new Dictionary<int, int>(16); |  | ||||||
| 		private TComponent* components; |  | ||||||
| 		private int* entityIDs; |  | ||||||
| 		private int count = 0; |  | ||||||
| 		private int capacity = 16; |  | ||||||
| 		private bool disposed; | 		private bool disposed; | ||||||
| 
 | 
 | ||||||
| 		public ComponentStorage() | 		public ComponentStorage(int elementSize) | ||||||
| 		{ | 		{ | ||||||
| 			components = (TComponent*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<TComponent>())); | 			Components = new NativeArray(elementSize); | ||||||
| 			entityIDs = (int*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<int>())); | 			EntityIDs = new NativeArray<Entity>(); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public bool Any() | 		public bool Any() | ||||||
| 		{ | 		{ | ||||||
| 			return count > 0; | 			return Components.Count > 0; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public ref readonly TComponent Get(int entityID) | 		public bool Has(Entity entity) | ||||||
| 		{ | 		{ | ||||||
| 			return ref components[entityIDToStorageIndex[entityID]]; | 			return EntityIDToStorageIndex.ContainsKey(entity); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		internal override unsafe void* UntypedGet(int entityID) | 		public ref T Get<T>(in Entity entity) where T : unmanaged | ||||||
| 		{ | 		{ | ||||||
| 			return &components[entityIDToStorageIndex[entityID]]; | 
 | ||||||
|  | 			return ref Components.Get<T>(EntityIDToStorageIndex[entity]); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public ref readonly TComponent GetFirst() | 		public ref T GetFirst<T>() where T : unmanaged | ||||||
| 		{ | 		{ | ||||||
| #if DEBUG | #if DEBUG | ||||||
| 			if (count == 0) | 			if (Components.Count == 0) | ||||||
| 			{ | 			{ | ||||||
| 				throw new IndexOutOfRangeException("Component storage is empty!"); | 				throw new IndexOutOfRangeException("Component storage is empty!"); | ||||||
| 			} | 			} | ||||||
| #endif | #endif | ||||||
| 			return ref components[0]; | 			return ref Components.Get<T>(0); | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Set(int entityID, in TComponent component) |  | ||||||
| 		{ |  | ||||||
| 			if (!entityIDToStorageIndex.ContainsKey(entityID)) |  | ||||||
| 			{ |  | ||||||
| 				var index = count; |  | ||||||
| 				count += 1; |  | ||||||
| 
 |  | ||||||
| 				if (index >= capacity) |  | ||||||
| 				{ |  | ||||||
| 					capacity *= 2; |  | ||||||
| 					components = (TComponent*) NativeMemory.Realloc(components, (nuint) (capacity * Unsafe.SizeOf<TComponent>())); |  | ||||||
| 					entityIDs = (int*) NativeMemory.Realloc(entityIDs, (nuint) (capacity * Unsafe.SizeOf<int>())); |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				entityIDToStorageIndex[entityID] = index; |  | ||||||
| 				entityIDs[index] = entityID; |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			components[entityIDToStorageIndex[entityID]] = component; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		internal override unsafe void Set(int entityID, void* component) |  | ||||||
| 		{ |  | ||||||
| 			Set(entityID, *(TComponent*) component); |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// Returns true if the entity had this component. | 		// Returns true if the entity had this component. | ||||||
| 		public override bool Remove(int entityID) | 		public bool Set<T>(in Entity entity, in T component) where T : unmanaged | ||||||
| 		{ | 		{ | ||||||
| 			if (entityIDToStorageIndex.TryGetValue(entityID, out int storageIndex)) | 			if (EntityIDToStorageIndex.TryGetValue(entity, out var index)) | ||||||
| 			{ | 			{ | ||||||
| 				entityIDToStorageIndex.Remove(entityID); | 				Components.Set(index, component); | ||||||
| 
 | 				return true; | ||||||
| 				var lastElementIndex = count - 1; | 			} | ||||||
| 
 | 			else | ||||||
| 				// move a component into the hole to maintain contiguous memory |  | ||||||
| 				if (lastElementIndex != storageIndex) |  | ||||||
| 			{ | 			{ | ||||||
| 					var lastEntityID = entityIDs[lastElementIndex]; | 				EntityIDToStorageIndex[entity] = Components.Count; | ||||||
| 					entityIDToStorageIndex[lastEntityID] = storageIndex; | 				EntityIDs.Append(entity); | ||||||
| 					components[storageIndex] = components[lastElementIndex]; | 				Components.Append(component); | ||||||
| 					entityIDs[storageIndex] = lastEntityID; | 				return false; | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 				count -= 1; | 		// Returns true if the entity had this component. | ||||||
|  | 		public bool Remove(in Entity entity) | ||||||
|  | 		{ | ||||||
|  | 			if (EntityIDToStorageIndex.TryGetValue(entity, out int index)) | ||||||
|  | 			{ | ||||||
|  | 				var lastElementIndex = Components.Count - 1; | ||||||
|  | 
 | ||||||
|  | 				var lastEntity = EntityIDs[lastElementIndex]; | ||||||
|  | 
 | ||||||
|  | 				// move a component into the hole to maintain contiguous memory | ||||||
|  | 				Components.Delete(index); | ||||||
|  | 				EntityIDs.Delete(index); | ||||||
|  | 				EntityIDToStorageIndex.Remove(entity); | ||||||
|  | 
 | ||||||
|  | 				// update the index if it changed | ||||||
|  | 				if (lastElementIndex != index) | ||||||
|  | 				{ | ||||||
|  | 					EntityIDToStorageIndex[lastEntity] = index; | ||||||
|  | 				} | ||||||
| 
 | 
 | ||||||
| 				return true; | 				return true; | ||||||
| 			} | 			} | ||||||
|  | @ -114,62 +88,47 @@ namespace MoonTools.ECS | ||||||
| 			return false; | 			return false; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public override void Clear() | 		public void Clear() | ||||||
| 		{ | 		{ | ||||||
| 			count = 0; | 			Components.Clear(); | ||||||
| 			entityIDToStorageIndex.Clear(); | 			EntityIDs.Clear(); | ||||||
| 		} | 			EntityIDToStorageIndex.Clear(); | ||||||
| 
 |  | ||||||
| 		public ReadOnlySpan<TComponent> AllComponents() |  | ||||||
| 		{ |  | ||||||
| 			return new ReadOnlySpan<TComponent>(components, count); |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public Entity FirstEntity() | 		public Entity FirstEntity() | ||||||
| 		{ | 		{ | ||||||
| #if DEBUG | #if DEBUG | ||||||
| 			if (count == 0) | 			if (EntityIDs.Count == 0) | ||||||
| 			{ | 			{ | ||||||
| 				throw new IndexOutOfRangeException("Component storage is empty!"); | 				throw new IndexOutOfRangeException("Component storage is empty!"); | ||||||
| 			} | 			} | ||||||
| #endif | #endif | ||||||
| 			return new Entity(entityIDs[0]); | 			return EntityIDs[0]; | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public override ComponentStorage<TComponent> CreateStorage() |  | ||||||
| 		{ |  | ||||||
| 			return new ComponentStorage<TComponent>(); |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| #if DEBUG | #if DEBUG | ||||||
| 		internal override object Debug_Get(int entityID) | 		internal IEnumerable<Entity> Debug_GetEntities() | ||||||
| 		{ | 		{ | ||||||
| 			return components[entityIDToStorageIndex[entityID]]; | 			return EntityIDToStorageIndex.Keys; | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		internal override IEnumerable<int> Debug_GetEntityIDs() |  | ||||||
| 		{ |  | ||||||
| 			return entityIDToStorageIndex.Keys; |  | ||||||
| 		} | 		} | ||||||
|  | #endif | ||||||
| 
 | 
 | ||||||
| 		protected virtual void Dispose(bool disposing) | 		protected virtual void Dispose(bool disposing) | ||||||
| 		{ | 		{ | ||||||
| 			if (!disposed) | 			if (!disposed) | ||||||
| 			{ | 			{ | ||||||
| 				NativeMemory.Free(components); | 				Components.Dispose(); | ||||||
| 				NativeMemory.Free(entityIDs); | 				EntityIDs.Dispose(); | ||||||
| 				components = null; |  | ||||||
| 				entityIDs = null; |  | ||||||
| 
 | 
 | ||||||
| 				disposed = true; | 				disposed = true; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		~ComponentStorage() | 		// ~ComponentStorage() | ||||||
| 		{ | 		// { | ||||||
| 			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | 		// 	// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||||
| 			Dispose(disposing: false); | 		// 	Dispose(disposing: false); | ||||||
| 		} | 		// } | ||||||
| 
 | 
 | ||||||
| 		public void Dispose() | 		public void Dispose() | ||||||
| 		{ | 		{ | ||||||
|  | @ -177,6 +136,5 @@ namespace MoonTools.ECS | ||||||
| 			Dispose(disposing: true); | 			Dispose(disposing: true); | ||||||
| 			GC.SuppressFinalize(this); | 			GC.SuppressFinalize(this); | ||||||
| 		} | 		} | ||||||
| #endif |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,63 +1,17 @@ | ||||||
| // NOTE: these methods are very inefficient | // NOTE: these methods are very inefficient | ||||||
| // this class should only be used in debugging contexts!! | // this class should only be used in debugging contexts!! | ||||||
| 
 |  | ||||||
| #if DEBUG | #if DEBUG | ||||||
| using System; | using System; | ||||||
| using System.Collections.Generic; | 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) | 	protected World.ComponentTypeEnumerator Debug_GetAllComponentTypes(Entity entity) => World.Debug_GetAllComponentTypes(entity); | ||||||
| 		{ | 	protected IEnumerable<Entity> Debug_GetEntities(Type componentType) => World.Debug_GetEntities(componentType); | ||||||
| 			return new ComponentEnumerator(ComponentDepot, entity, EntityStorage.ComponentTypeIndices(entity.ID)); | 	protected IEnumerable<Type> Debug_SearchComponentType(string typeString) => World.Debug_SearchComponentType(typeString); | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected IEnumerable<Entity> Debug_GetEntities(Type componentType) |  | ||||||
| 		{ |  | ||||||
| 			foreach (var entityID in ComponentDepot.Debug_GetEntityIDs(ComponentTypeIndices.GetIndex(componentType))) |  | ||||||
| 			{ |  | ||||||
| 				yield return new Entity(entityID); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected IEnumerable<Type> 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<int> ComponentTypeIndices; |  | ||||||
| 
 |  | ||||||
| 			public ComponentEnumerator GetEnumerator() => this; |  | ||||||
| 
 |  | ||||||
| 			internal ComponentEnumerator( |  | ||||||
| 				ComponentDepot componentDepot, |  | ||||||
| 				Entity entity, |  | ||||||
| 				Collections.IndexableSet<int> componentTypeIndices |  | ||||||
| 			) { |  | ||||||
| 				ComponentDepot = componentDepot; |  | ||||||
| 				Entity = entity; |  | ||||||
| 				ComponentTypeIndices = componentTypeIndices.GetEnumerator(); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			public bool MoveNext() => ComponentTypeIndices.MoveNext(); |  | ||||||
| 			public object Current => ComponentDepot.Debug_Get(Entity.ID, ComponentTypeIndices.Current); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | @ -1,69 +0,0 @@ | ||||||
| using System; |  | ||||||
| using System.Runtime.CompilerServices; |  | ||||||
| using System.Runtime.InteropServices; |  | ||||||
| 
 |  | ||||||
| namespace MoonTools.ECS.Collections |  | ||||||
| { |  | ||||||
| 	public unsafe class NativeArray<T> : IDisposable where T : unmanaged |  | ||||||
| 	{ |  | ||||||
| 		private T* Array; |  | ||||||
| 		private int count; |  | ||||||
| 		private int capacity; |  | ||||||
| 
 |  | ||||||
| 		public int Count => count; |  | ||||||
| 
 |  | ||||||
| 		public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(Array, Count)); |  | ||||||
| 
 |  | ||||||
| 		private bool disposed; |  | ||||||
| 
 |  | ||||||
| 		public NativeArray(int capacity = 16) |  | ||||||
| 		{ |  | ||||||
| 			this.capacity = capacity; |  | ||||||
| 			Array = (T*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<T>())); |  | ||||||
| 			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<T>())); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			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); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -2,48 +2,13 @@ | ||||||
| 
 | 
 | ||||||
| namespace MoonTools.ECS | namespace MoonTools.ECS | ||||||
| { | { | ||||||
| 	public struct Entity : IEquatable<Entity> | 	public readonly record struct Entity | ||||||
| 	{ | 	{ | ||||||
| 		public int ID { get; } | 		public uint ID { get; } | ||||||
| 
 | 
 | ||||||
| 		internal Entity(int id) | 		internal Entity(uint id) | ||||||
| 		{ | 		{ | ||||||
| 			ID = 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); |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,127 +1,36 @@ | ||||||
| using System; | namespace MoonTools.ECS; | ||||||
| using System.Collections.Generic; |  | ||||||
| 
 | 
 | ||||||
| namespace MoonTools.ECS |  | ||||||
| { |  | ||||||
| public abstract class EntityComponentReader | public abstract class EntityComponentReader | ||||||
| { | { | ||||||
| 		internal readonly World World; | 	protected readonly World World; | ||||||
| 		internal EntityStorage EntityStorage => World.EntityStorage; | 	public FilterBuilder FilterBuilder => World.FilterBuilder; | ||||||
| 		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) | 	protected EntityComponentReader(World world) | ||||||
| 	{ | 	{ | ||||||
| 		World = world; | 		World = world; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		protected string GetTag(in Entity entity) | 	protected string GetTag(in Entity entity) => World.GetTag(entity); | ||||||
| 		{ |  | ||||||
| 			return World.GetTag(entity); |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		protected ReadOnlySpan<TComponent> ReadComponents<TComponent>() where TComponent : unmanaged | 	protected bool Has<T>(in Entity Entity) where T : unmanaged => World.Has<T>(Entity); | ||||||
| 		{ | 	protected bool Some<T>() where T : unmanaged => World.Some<T>(); | ||||||
| 			return ComponentDepot.ReadComponents<TComponent>(); | 	protected ref T Get<T>(in Entity Entity) where T : unmanaged => ref World.Get<T>(Entity); | ||||||
| 		} | 	protected ref T GetSingleton<T>() where T : unmanaged => ref World.GetSingleton<T>(); | ||||||
|  | 	protected Entity GetSingletonEntity<T>() where T : unmanaged => World.GetSingletonEntity<T>(); | ||||||
| 
 | 
 | ||||||
| 		protected bool Has<TComponent>(in Entity entity) where TComponent : unmanaged | 	protected ReverseSpanEnumerator<(Entity, Entity)> Relations<T>() where T : unmanaged => World.Relations<T>(); | ||||||
| 		{ | 	protected bool Related<T>(in Entity entityA, in Entity entityB) where T : unmanaged => World.Related<T>(entityA, entityB); | ||||||
| 			var storageIndex = ComponentTypeIndices.GetIndex<TComponent>(); | 	protected T GetRelationData<T>(in Entity entityA, in Entity entityB) where T : unmanaged => World.GetRelationData<T>(entityA, entityB); | ||||||
| 			return EntityStorage.HasComponent(entity.ID, storageIndex); |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		protected bool Some<TComponent>() where TComponent : unmanaged | 	protected ReverseSpanEnumerator<Entity> OutRelations<T>(in Entity entity) where T : unmanaged => World.OutRelations<T>(entity); | ||||||
| 		{ | 	protected Entity OutRelationSingleton<T>(in Entity entity) where T : unmanaged => World.OutRelationSingleton<T>(entity); | ||||||
| 			return ComponentDepot.Some<TComponent>(); | 	protected bool HasOutRelation<T>(in Entity entity) where T : unmanaged => World.HasOutRelation<T>(entity); | ||||||
| 		} | 	protected int OutRelationCount<T>(in Entity entity) where T : unmanaged => World.OutRelationCount<T>(entity); | ||||||
|  | 	protected Entity NthOutRelation<T>(in Entity entity, int n) where T : unmanaged => World.NthOutRelation<T>(entity, n); | ||||||
| 
 | 
 | ||||||
| 		protected ref readonly TComponent Get<TComponent>(in Entity entity) where TComponent : unmanaged | 	protected ReverseSpanEnumerator<Entity> InRelations<T>(in Entity entity) where T : unmanaged => World.InRelations<T>(entity); | ||||||
| 		{ | 	protected Entity InRelationSingleton<T>(in Entity entity) where T : unmanaged => World.InRelationSingleton<T>(entity); | ||||||
| 			return ref ComponentDepot.Get<TComponent>(entity.ID); | 	protected bool HasInRelation<T>(in Entity entity) where T : unmanaged => World.HasInRelation<T>(entity); | ||||||
| 		} | 	protected int InRelationCount<T>(in Entity entity) where T : unmanaged => World.InRelationCount<T>(entity); | ||||||
| 
 | 	protected Entity NthInRelation<T>(in Entity entity, int n) where T : unmanaged => World.NthInRelation<T>(entity, n); | ||||||
| 		protected ref readonly TComponent GetSingleton<TComponent>() where TComponent : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return ref ComponentDepot.GetFirst<TComponent>(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected Entity GetSingletonEntity<TComponent>() where TComponent : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return ComponentDepot.GetSingletonEntity<TComponent>(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected ReverseSpanEnumerator<(Entity, Entity)> Relations<TRelationKind>() where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return RelationDepot.Relations<TRelationKind>(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected bool Related<TRelationKind>(in Entity a, in Entity b) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return RelationDepot.Related<TRelationKind>(a.ID, b.ID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected TRelationKind GetRelationData<TRelationKind>(in Entity a, in Entity b) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return RelationDepot.Get<TRelationKind>(a, b); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// relations go A->B, so given A, will give all entities in outgoing relations of this kind. |  | ||||||
| 		protected ReverseSpanEnumerator<Entity> OutRelations<TRelationKind>(in Entity entity) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return RelationDepot.OutRelations<TRelationKind>(entity.ID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected Entity OutRelationSingleton<TRelationKind>(in Entity entity) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return RelationDepot.OutRelationSingleton<TRelationKind>(entity.ID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// NOTE: this WILL crash if at least n + 1 relations do not exist! |  | ||||||
| 		protected Entity NthOutRelation<TRelationKind>(in Entity entity, int n) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return RelationDepot.NthOutRelation<TRelationKind>(entity.ID, n); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected bool HasOutRelation<TRelationKind>(in Entity entity) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return RelationDepot.HasOutRelation<TRelationKind>(entity.ID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected int OutRelationCount<TRelationKind>(in Entity entity) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return RelationDepot.OutRelationCount<TRelationKind>(entity.ID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Relations go A->B, so given B, will give all entities in incoming A relations of this kind. |  | ||||||
| 		protected ReverseSpanEnumerator<Entity> InRelations<TRelationKind>(in Entity entity) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return RelationDepot.InRelations<TRelationKind>(entity.ID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected Entity InRelationSingleton<TRelationKind>(in Entity entity) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return RelationDepot.InRelationSingleton<TRelationKind>(entity.ID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// NOTE: this WILL crash if at least n + 1 relations do not exist! |  | ||||||
| 		protected Entity NthInRelation<TRelationKind>(in Entity entity, int n) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return RelationDepot.NthInRelation<TRelationKind>(entity.ID, n); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected bool HasInRelation<TRelationKind>(in Entity entity) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return RelationDepot.HasInRelation<TRelationKind>(entity.ID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected int InRelationCount<TRelationKind>(in Entity entity) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return RelationDepot.InRelationCount<TRelationKind>(entity.ID); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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<int> availableIDs = new Stack<int>(); |  | ||||||
| 		// FIXME: this is only needed in debug mode |  | ||||||
| 		private readonly HashSet<int> availableIDHash = new HashSet<int>(); |  | ||||||
| 
 |  | ||||||
| 		private Dictionary<int, IndexableSet<int>> EntityToComponentTypeIndices = new Dictionary<int, IndexableSet<int>>(); |  | ||||||
| 		private Dictionary<int, IndexableSet<int>> EntityToRelationTypeIndices = new Dictionary<int, IndexableSet<int>>(); |  | ||||||
| 
 |  | ||||||
| 		public int Count => nextID - availableIDs.Count; |  | ||||||
| 
 |  | ||||||
| 		public Dictionary<int, string> Tags = new Dictionary<int, string>(); |  | ||||||
| 
 |  | ||||||
| 		public Entity Create(string tag) |  | ||||||
| 		{ |  | ||||||
| 			var entity = new Entity(NextID()); |  | ||||||
| 
 |  | ||||||
| 			if (!EntityToComponentTypeIndices.ContainsKey(entity.ID)) |  | ||||||
| 			{ |  | ||||||
| 				EntityToComponentTypeIndices.Add(entity.ID, new IndexableSet<int>()); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			if (!EntityToRelationTypeIndices.ContainsKey(entity.ID)) |  | ||||||
| 			{ |  | ||||||
| 				EntityToRelationTypeIndices.Add(entity.ID, new IndexableSet<int>()); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			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<int> ComponentTypeIndices(int entityID) |  | ||||||
| 		{ |  | ||||||
| 			return EntityToComponentTypeIndices[entityID]; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public IndexableSet<int> 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); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
							
								
								
									
										204
									
								
								src/Filter.cs
								
								
								
								
							
							
						
						
									
										204
									
								
								src/Filter.cs
								
								
								
								
							|  | @ -1,38 +1,204 @@ | ||||||
| using System; | using System; | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| using MoonTools.ECS.Collections; |  | ||||||
| 
 | 
 | ||||||
| namespace MoonTools.ECS | namespace MoonTools.ECS; | ||||||
| { | 
 | ||||||
|  | // TODO: do we want to get fancy with queries beyond Include and Exclude? | ||||||
| public class Filter | public class Filter | ||||||
| { | { | ||||||
| 		internal FilterSignature Signature; | 	private Archetype EmptyArchetype; | ||||||
| 		private FilterStorage FilterStorage; | 	private HashSet<TypeId> Included; | ||||||
|  | 	private HashSet<TypeId> Excluded; | ||||||
| 
 | 
 | ||||||
| 		internal Filter(FilterStorage filterStorage, IndexableSet<int> included, IndexableSet<int> excluded) | 	public EntityEnumerator Entities => new EntityEnumerator(this); | ||||||
|  | 	internal ArchetypeEnumerator Archetypes => new ArchetypeEnumerator(this); | ||||||
|  | 	public RandomEntityEnumerator EntitiesInRandomOrder => new RandomEntityEnumerator(this); | ||||||
|  | 
 | ||||||
|  | 	public bool Empty | ||||||
| 	{ | 	{ | ||||||
| 			FilterStorage = filterStorage; | 		get | ||||||
| 			Signature = new FilterSignature(included, excluded); | 		{ | ||||||
|  | 			var empty = true; | ||||||
|  | 
 | ||||||
|  | 			foreach (var archetype in Archetypes) | ||||||
|  | 			{ | ||||||
|  | 				if (archetype.Count > 0) | ||||||
|  | 				{ | ||||||
|  | 					return false; | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 		public ReverseSpanEnumerator<Entity> Entities => FilterStorage.FilterEntities(Signature); | 			return empty; | ||||||
| 		public RandomEntityEnumerator EntitiesInRandomOrder => FilterStorage.FilterEntitiesRandom(Signature); | 		} | ||||||
| 		public Entity RandomEntity => FilterStorage.FilterRandomEntity(Signature); | 	} | ||||||
| 
 | 
 | ||||||
| 		public int Count => FilterStorage.FilterCount(Signature); | 	public int Count | ||||||
| 		public bool Empty => Count == 0; | 	{ | ||||||
|  | 		get | ||||||
|  | 		{ | ||||||
|  | 			var count = 0; | ||||||
|  | 
 | ||||||
|  | 			foreach (var archetype in Archetypes) | ||||||
|  | 			{ | ||||||
|  | 				count += archetype.Count; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			return count; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public Entity RandomEntity | ||||||
|  | 	{ | ||||||
|  | 		get | ||||||
|  | 		{ | ||||||
|  | 			var randomIndex = RandomManager.Next(Count); | ||||||
|  | 			return NthEntity(randomIndex); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	// WARNING: this WILL crash if the index is out of range! | 	// WARNING: this WILL crash if the index is out of range! | ||||||
| 		public Entity NthEntity(int index) => FilterStorage.FilterNthEntity(Signature, index); | 	public Entity NthEntity(int index) | ||||||
| 
 |  | ||||||
| 		public void RegisterAddCallback(Action<Entity> callback) |  | ||||||
| 	{ | 	{ | ||||||
| 			FilterStorage.RegisterAddCallback(Signature, callback); | 		var count = 0; | ||||||
|  | 
 | ||||||
|  | 		foreach (var archetype in Archetypes) | ||||||
|  | 		{ | ||||||
|  | 			count += archetype.Count; | ||||||
|  | 			if (index < count) | ||||||
|  | 			{ | ||||||
|  | 				return archetype.Entities[index]; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 		public void RegisterRemoveCallback(Action<Entity> callback) | 			index -= count; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		throw new InvalidOperationException("Filter index out of range!"); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void DestroyAllEntities() | ||||||
| 	{ | 	{ | ||||||
| 			FilterStorage.RegisterRemoveCallback(Signature, callback); | 		foreach (var archetype in Archetypes) | ||||||
|  | 		{ | ||||||
|  | 			archetype.ClearAll(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	internal Filter(Archetype emptyArchetype, HashSet<TypeId> included, HashSet<TypeId> excluded) | ||||||
|  | 	{ | ||||||
|  | 		EmptyArchetype = emptyArchetype; | ||||||
|  | 		Included = included; | ||||||
|  | 		Excluded = excluded; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	internal ref struct ArchetypeEnumerator | ||||||
|  | 	{ | ||||||
|  | 		private Archetype CurrentArchetype; | ||||||
|  | 
 | ||||||
|  | 		// TODO: pool these | ||||||
|  | 		private Queue<Archetype> ArchetypeQueue = new Queue<Archetype>(); | ||||||
|  | 		private Queue<Archetype> ArchetypeSearchQueue = new Queue<Archetype>(); | ||||||
|  | 		private HashSet<Archetype> Explored = new HashSet<Archetype>(); | ||||||
|  | 
 | ||||||
|  | 		public ArchetypeEnumerator GetEnumerator() => this; | ||||||
|  | 
 | ||||||
|  | 		public ArchetypeEnumerator(Filter filter) | ||||||
|  | 		{ | ||||||
|  | 			var empty = filter.EmptyArchetype; | ||||||
|  | 			ArchetypeSearchQueue.Enqueue(empty); | ||||||
|  | 
 | ||||||
|  | 			// TODO: can we cache this search effectively? | ||||||
|  | 			while (ArchetypeSearchQueue.TryDequeue(out var current)) | ||||||
|  | 			{ | ||||||
|  | 				// exclude the empty archetype | ||||||
|  | 				var satisfiesFilter = filter.Included.Count != 0; | ||||||
|  | 
 | ||||||
|  | 				foreach (var componentId in filter.Included) | ||||||
|  | 				{ | ||||||
|  | 					if (!current.Signature.Contains(componentId)) | ||||||
|  | 					{ | ||||||
|  | 						satisfiesFilter = false; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				foreach (var componentId in filter.Excluded) | ||||||
|  | 				{ | ||||||
|  | 					if (current.Signature.Contains(componentId)) | ||||||
|  | 					{ | ||||||
|  | 						satisfiesFilter = false; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if (satisfiesFilter) | ||||||
|  | 				{ | ||||||
|  | 					ArchetypeQueue.Enqueue(current); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				// breadth-first search | ||||||
|  | 				// ignore excluded component edges | ||||||
|  | 				foreach (var (componentId, edge) in current.Edges) | ||||||
|  | 				{ | ||||||
|  | 					if (!Explored.Contains(edge.Add) && !filter.Excluded.Contains(componentId)) | ||||||
|  | 					{ | ||||||
|  | 						Explored.Add(edge.Add); | ||||||
|  | 						ArchetypeSearchQueue.Enqueue(edge.Add); | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public bool MoveNext() | ||||||
|  | 		{ | ||||||
|  | 			return ArchetypeQueue.TryDequeue(out CurrentArchetype!); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public Archetype Current => CurrentArchetype; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public ref struct EntityEnumerator | ||||||
|  | 	{ | ||||||
|  | 		private Entity CurrentEntity; | ||||||
|  | 
 | ||||||
|  | 		public EntityEnumerator GetEnumerator() => this; | ||||||
|  | 
 | ||||||
|  | 		// TODO: pool this | ||||||
|  | 		Queue<Entity> EntityQueue = new Queue<Entity>(); | ||||||
|  | 
 | ||||||
|  | 		internal EntityEnumerator(Filter filter) | ||||||
|  | 		{ | ||||||
|  | 			var archetypeEnumerator = new ArchetypeEnumerator(filter); | ||||||
|  | 
 | ||||||
|  | 			foreach (var archetype in archetypeEnumerator) | ||||||
|  | 			{ | ||||||
|  | 				foreach (var entity in archetype.Entities) | ||||||
|  | 				{ | ||||||
|  | 					EntityQueue.Enqueue(entity); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public bool MoveNext() | ||||||
|  | 		{ | ||||||
|  | 			return EntityQueue.TryDequeue(out CurrentEntity); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public Entity Current => CurrentEntity; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public ref struct RandomEntityEnumerator | ||||||
|  | 	{ | ||||||
|  | 		private Filter Filter; | ||||||
|  | 		private LinearCongruentialEnumerator LinearCongruentialEnumerator; | ||||||
|  | 
 | ||||||
|  | 		public RandomEntityEnumerator GetEnumerator() => this; | ||||||
|  | 
 | ||||||
|  | 		internal RandomEntityEnumerator(Filter filter) | ||||||
|  | 		{ | ||||||
|  | 			Filter = filter; | ||||||
|  | 			LinearCongruentialEnumerator = | ||||||
|  | 				RandomManager.LinearCongruentialSequence(filter.Count); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public bool MoveNext() => LinearCongruentialEnumerator.MoveNext(); | ||||||
|  | 		public Entity Current => Filter.NthEntity(LinearCongruentialEnumerator.Current); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -1,45 +1,42 @@ | ||||||
| using MoonTools.ECS.Collections; | using System.Collections.Generic; | ||||||
| 
 | 
 | ||||||
| namespace MoonTools.ECS | namespace MoonTools.ECS | ||||||
| { | { | ||||||
| 	public struct FilterBuilder | 	public struct FilterBuilder | ||||||
| 	{ | 	{ | ||||||
| 		private TypeIndices ComponentTypeIndices; | 		World World; | ||||||
| 		private FilterStorage FilterStorage; | 		HashSet<TypeId> Included; | ||||||
| 		private IndexableSet<int> Included; | 		HashSet<TypeId> Excluded; | ||||||
| 		private IndexableSet<int> Excluded; |  | ||||||
| 
 | 
 | ||||||
| 		internal FilterBuilder(FilterStorage filterStorage, TypeIndices componentTypeIndices) | 		internal FilterBuilder(World world) | ||||||
| 		{ | 		{ | ||||||
| 			FilterStorage = filterStorage; | 			World = world; | ||||||
| 			ComponentTypeIndices = componentTypeIndices; | 			Included = new HashSet<TypeId>(); | ||||||
| 			Included = new IndexableSet<int>(); | 			Excluded = new HashSet<TypeId>(); | ||||||
| 			Excluded = new IndexableSet<int>(); |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		private FilterBuilder(FilterStorage filterStorage, TypeIndices componentTypeIndices, IndexableSet<int> included, IndexableSet<int> excluded) | 		private FilterBuilder(World world, HashSet<TypeId> included, HashSet<TypeId> excluded) | ||||||
| 		{ | 		{ | ||||||
| 			FilterStorage = filterStorage; | 			World = world; | ||||||
| 			ComponentTypeIndices = componentTypeIndices; |  | ||||||
| 			Included = included; | 			Included = included; | ||||||
| 			Excluded = excluded; | 			Excluded = excluded; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public FilterBuilder Include<TComponent>() where TComponent : unmanaged | 		public FilterBuilder Include<T>() where T : unmanaged | ||||||
| 		{ | 		{ | ||||||
| 			Included.Add(ComponentTypeIndices.GetIndex<TComponent>()); | 			Included.Add(World.GetTypeId<T>()); | ||||||
| 			return new FilterBuilder(FilterStorage, ComponentTypeIndices, Included, Excluded); | 			return new FilterBuilder(World, Included, Excluded); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public FilterBuilder Exclude<TComponent>() where TComponent : unmanaged | 		public FilterBuilder Exclude<T>() where T : unmanaged | ||||||
| 		{ | 		{ | ||||||
| 			Excluded.Add(ComponentTypeIndices.GetIndex<TComponent>()); | 			Excluded.Add(World.GetTypeId<T>()); | ||||||
| 			return new FilterBuilder(FilterStorage, ComponentTypeIndices, Included, Excluded); | 			return new FilterBuilder(World, Included, Excluded); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public Filter Build() | 		public Filter Build() | ||||||
| 		{ | 		{ | ||||||
| 			return FilterStorage.CreateFilter(Included, Excluded); | 			return new Filter(World.EmptyArchetype, Included, Excluded); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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<FilterSignature, IndexableSet<Entity>> filterSignatureToEntityIDs = new Dictionary<FilterSignature, IndexableSet<Entity>>(); |  | ||||||
| 		private Dictionary<int, List<FilterSignature>> typeToFilterSignatures = new Dictionary<int, List<FilterSignature>>(); |  | ||||||
| 
 |  | ||||||
| 		private Dictionary<FilterSignature, Action<Entity>> addCallbacks = new Dictionary<FilterSignature, Action<Entity>>(); |  | ||||||
| 		private Dictionary<FilterSignature, Action<Entity>> removeCallbacks = new Dictionary<FilterSignature, Action<Entity>>(); |  | ||||||
| 
 |  | ||||||
| 		public FilterStorage(EntityStorage entityStorage, TypeIndices componentTypeIndices) |  | ||||||
| 		{ |  | ||||||
| 			EntityStorage = entityStorage; |  | ||||||
| 			ComponentTypeIndices = componentTypeIndices; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		private void CopyTypeCache(Dictionary<int, List<FilterSignature>> typeCache) |  | ||||||
| 		{ |  | ||||||
| 			foreach (var type in typeCache.Keys) |  | ||||||
| 			{ |  | ||||||
| 				if (!typeToFilterSignatures.ContainsKey(type)) |  | ||||||
| 				{ |  | ||||||
| 					typeToFilterSignatures.Add(type, new List<FilterSignature>()); |  | ||||||
| 
 |  | ||||||
| 					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<Entity>()); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			CopyTypeCache(other.typeToFilterSignatures); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public Filter CreateFilter(IndexableSet<int> included, IndexableSet<int> excluded) |  | ||||||
| 		{ |  | ||||||
| 			var filterSignature = new FilterSignature(included, excluded); |  | ||||||
| 			if (!filterSignatureToEntityIDs.ContainsKey(filterSignature)) |  | ||||||
| 			{ |  | ||||||
| 				filterSignatureToEntityIDs.Add(filterSignature, new IndexableSet<Entity>()); |  | ||||||
| 
 |  | ||||||
| 				foreach (var type in included) |  | ||||||
| 				{ |  | ||||||
| 					if (!typeToFilterSignatures.ContainsKey(type)) |  | ||||||
| 					{ |  | ||||||
| 						typeToFilterSignatures.Add(type, new List<FilterSignature>()); |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					typeToFilterSignatures[type].Add(filterSignature); |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				foreach (var type in excluded) |  | ||||||
| 				{ |  | ||||||
| 					if (!typeToFilterSignatures.ContainsKey(type)) |  | ||||||
| 					{ |  | ||||||
| 						typeToFilterSignatures.Add(type, new List<FilterSignature>()); |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					typeToFilterSignatures[type].Add(filterSignature); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			return new Filter(this, included, excluded); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public ReverseSpanEnumerator<Entity> 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<TComponent>(int entityID) where TComponent : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			Check(entityID, ComponentTypeIndices.GetIndex<TComponent>()); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		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<int, int> 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<Entity> callback) |  | ||||||
| 		{ |  | ||||||
| 			addCallbacks.Add(filterSignature, callback); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void RegisterRemoveCallback(FilterSignature filterSignature, Action<Entity> 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); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,31 @@ | ||||||
|  | using MoonTools.ECS.Collections; | ||||||
|  | 
 | ||||||
|  | namespace MoonTools.ECS; | ||||||
|  | 
 | ||||||
|  | internal class IdAssigner | ||||||
|  | { | ||||||
|  | 	uint Next; | ||||||
|  | 	NativeArray<uint> AvailableIds = new NativeArray<uint>(); | ||||||
|  | 
 | ||||||
|  | 	public uint Assign() | ||||||
|  | 	{ | ||||||
|  | 		if (!AvailableIds.TryPop(out var id)) | ||||||
|  | 		{ | ||||||
|  | 			id = Next; | ||||||
|  | 			Next += 1; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return id; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void Unassign(uint id) | ||||||
|  | 	{ | ||||||
|  | 		AvailableIds.Append(id); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void CopyTo(IdAssigner other) | ||||||
|  | 	{ | ||||||
|  | 		AvailableIds.CopyTo(other.AvailableIds); | ||||||
|  | 		other.Next = Next; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -14,6 +14,7 @@ namespace MoonTools.ECS.Collections | ||||||
| 		private bool disposed; | 		private bool disposed; | ||||||
| 
 | 
 | ||||||
| 		public int Count => count; | 		public int Count => count; | ||||||
|  | 		public Span<T> AsSpan() => new Span<T>(array, count); | ||||||
| 		public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(array, count)); | 		public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(array, count)); | ||||||
| 
 | 
 | ||||||
| 		public IndexableSet(int capacity = 32) | 		public IndexableSet(int capacity = 32) | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ namespace MoonTools.ECS | ||||||
| 		protected void Tag(Entity entity, string tag) => World.Tag(entity, tag); | 		protected void Tag(Entity entity, string tag) => World.Tag(entity, tag); | ||||||
| 		protected void Set<TComponent>(in Entity entity, in TComponent component) where TComponent : unmanaged => World.Set<TComponent>(entity, component); | 		protected void Set<TComponent>(in Entity entity, in TComponent component) where TComponent : unmanaged => World.Set<TComponent>(entity, component); | ||||||
| 		protected void Remove<TComponent>(in Entity entity) where TComponent : unmanaged => World.Remove<TComponent>(entity); | 		protected void Remove<TComponent>(in Entity entity) where TComponent : unmanaged => World.Remove<TComponent>(entity); | ||||||
|  | 
 | ||||||
| 		protected void Relate<TRelationKind>(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged => World.Relate(entityA, entityB, relationData); | 		protected void Relate<TRelationKind>(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged => World.Relate(entityA, entityB, relationData); | ||||||
| 		protected void Unrelate<TRelationKind>(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged => World.Unrelate<TRelationKind>(entityA, entityB); | 		protected void Unrelate<TRelationKind>(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged => World.Unrelate<TRelationKind>(entityA, entityB); | ||||||
| 		protected void UnrelateAll<TRelationKind>(in Entity entity) where TRelationKind : unmanaged => World.UnrelateAll<TRelationKind>(entity); | 		protected void UnrelateAll<TRelationKind>(in Entity entity) where TRelationKind : unmanaged => World.UnrelateAll<TRelationKind>(entity); | ||||||
|  |  | ||||||
|  | @ -1,68 +0,0 @@ | ||||||
| using System; |  | ||||||
| using System.Collections.Generic; |  | ||||||
| 
 |  | ||||||
| namespace MoonTools.ECS |  | ||||||
| { |  | ||||||
| 	internal class MessageDepot |  | ||||||
| 	{ |  | ||||||
| 		private Dictionary<Type, MessageStorage> storages = new Dictionary<Type, MessageStorage>(); |  | ||||||
| 
 |  | ||||||
| 		private MessageStorage<TMessage> Lookup<TMessage>() where TMessage : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			if (!storages.ContainsKey(typeof(TMessage))) |  | ||||||
| 			{ |  | ||||||
| 				storages.Add(typeof(TMessage), new MessageStorage<TMessage>()); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			return storages[typeof(TMessage)] as MessageStorage<TMessage>; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Add<TMessage>(in TMessage message) where TMessage : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			Lookup<TMessage>().Add(message); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Add<TMessage>(int entityID, in TMessage message) where TMessage : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			Lookup<TMessage>().Add(entityID, message); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public bool Some<TMessage>() where TMessage : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TMessage>().Some(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public ReadOnlySpan<TMessage> All<TMessage>() where TMessage : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TMessage>().All(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public TMessage First<TMessage>() where TMessage : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TMessage>().First(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public ReverseSpanEnumerator<TMessage> WithEntity<TMessage>(int entityID) where TMessage : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TMessage>().WithEntity(entityID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public ref readonly TMessage FirstWithEntity<TMessage>(int entityID) where TMessage : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return ref Lookup<TMessage>().FirstWithEntity(entityID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public bool SomeWithEntity<TMessage>(int entityID) where TMessage : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TMessage>().SomeWithEntity(entityID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Clear() |  | ||||||
| 		{ |  | ||||||
| 			foreach (var storage in storages.Values) |  | ||||||
| 			{ |  | ||||||
| 				storage.Clear(); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -1,125 +1,58 @@ | ||||||
| using System; | using System; | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Runtime.CompilerServices; |  | ||||||
| using System.Runtime.InteropServices; |  | ||||||
| using MoonTools.ECS.Collections; | 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<TMessage> : MessageStorage, IDisposable where TMessage : unmanaged | 	public void Add<T>(in T message) where T : unmanaged | ||||||
| 	{ | 	{ | ||||||
| 		private int count = 0; | 		Messages.Append(message); | ||||||
| 		private int capacity = 128; |  | ||||||
| 		private TMessage* messages; |  | ||||||
| 		// duplicating storage here for fast iteration |  | ||||||
| 		private Dictionary<int, NativeArray<TMessage>> entityToMessages = new Dictionary<int, NativeArray<TMessage>>(); |  | ||||||
| 		private bool disposed; |  | ||||||
| 
 |  | ||||||
| 		public MessageStorage() |  | ||||||
| 		{ |  | ||||||
| 			messages = (TMessage*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<TMessage>())); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Add(in TMessage message) |  | ||||||
| 		{ |  | ||||||
| 			if (count == capacity) |  | ||||||
| 			{ |  | ||||||
| 				capacity *= 2; |  | ||||||
| 				messages = (TMessage*) NativeMemory.Realloc(messages, (nuint) (capacity * Unsafe.SizeOf<TMessage>())); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			messages[count] = message; |  | ||||||
| 			count += 1; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Add(int entityID, in TMessage message) |  | ||||||
| 		{ |  | ||||||
| 			if (!entityToMessages.ContainsKey(entityID)) |  | ||||||
| 			{ |  | ||||||
| 				entityToMessages.Add(entityID, new NativeArray<TMessage>()); |  | ||||||
| 			} |  | ||||||
| 			entityToMessages[entityID].Add(message); |  | ||||||
| 
 |  | ||||||
| 			Add(message); |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public bool Some() | 	public bool Some() | ||||||
| 	{ | 	{ | ||||||
| 			return count > 0; | 		return Messages.Count > 0; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		public ReadOnlySpan<TMessage> All() | 	public ReadOnlySpan<T> All<T>() where T : unmanaged | ||||||
| 	{ | 	{ | ||||||
| 			return new ReadOnlySpan<TMessage>(messages, count); | 		return Messages.ToSpan<T>(); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		public TMessage First() | 	public T First<T>() where T : unmanaged | ||||||
| 	{ | 	{ | ||||||
| 			return messages[0]; | 		return Messages.Get<T>(0); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		public ReverseSpanEnumerator<TMessage> WithEntity(int entityID) | 	public void Clear() | ||||||
| 	{ | 	{ | ||||||
| 			if (entityToMessages.TryGetValue(entityID, out var messages)) | 		Messages.Clear(); | ||||||
| 			{ |  | ||||||
| 				return messages.GetEnumerator(); |  | ||||||
| 			} |  | ||||||
| 			else |  | ||||||
| 			{ |  | ||||||
| 				return ReverseSpanEnumerator<TMessage>.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) | 	protected virtual void Dispose(bool disposing) | ||||||
| 	{ | 	{ | ||||||
| 			if (!disposed) | 		if (!IsDisposed) | ||||||
| 		{ | 		{ | ||||||
| 				Clear(); | 			Messages.Dispose(); | ||||||
| 
 | 			IsDisposed = true; | ||||||
| 				if (disposing) |  | ||||||
| 				{ |  | ||||||
| 					foreach (var array in entityToMessages.Values) |  | ||||||
| 					{ |  | ||||||
| 						array.Dispose(); |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 				NativeMemory.Free(messages); | 	// ~MessageStorage() | ||||||
| 				messages = null; | 	// { | ||||||
| 
 | 	// 	// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||||
| 				disposed = true; | 	// 	Dispose(disposing: false); | ||||||
| 			} | 	// } | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		~MessageStorage() |  | ||||||
| 		{ |  | ||||||
| 			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method |  | ||||||
| 			Dispose(disposing: false); |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 	public void Dispose() | 	public void Dispose() | ||||||
| 	{ | 	{ | ||||||
|  | @ -128,4 +61,3 @@ namespace MoonTools.ECS | ||||||
| 		GC.SuppressFinalize(this); | 		GC.SuppressFinalize(this); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -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<TRelationKind>(int index) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			if (index >= storages.Length) |  | ||||||
| 			{ |  | ||||||
| 				Array.Resize(ref storages, storages.Length * 2); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			storages[index] = new RelationStorage<TRelationKind>(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		[MethodImpl(MethodImplOptions.AggressiveInlining)] |  | ||||||
| 		private RelationStorage<TRelationKind> Lookup<TRelationKind>() where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			var storageIndex = RelationTypeIndices.GetIndex<TRelationKind>(); |  | ||||||
| 			// TODO: is there some way to avoid this null check? |  | ||||||
| 			if (storages[storageIndex] == null) |  | ||||||
| 			{ |  | ||||||
| 				Register<TRelationKind>(storageIndex); |  | ||||||
| 			} |  | ||||||
| 			return (RelationStorage<TRelationKind>) storages[storageIndex]; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Set<TRelationKind>(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			Lookup<TRelationKind>().Set(entityA, entityB, relationData); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public TRelationKind Get<TRelationKind>(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TRelationKind>().Get(entityA, entityB); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public (bool, bool) Remove<TRelationKind>(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TRelationKind>().Remove(entityA, entityB); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void UnrelateAll<TRelationKind>(int entityID) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			Lookup<TRelationKind>().UnrelateAll(entityID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public ReverseSpanEnumerator<(Entity, Entity)> Relations<TRelationKind>() where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TRelationKind>().All(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public bool Related<TRelationKind>(int idA, int idB) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TRelationKind>().Has((idA, idB)); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public ReverseSpanEnumerator<Entity> OutRelations<TRelationKind>(int entityID) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TRelationKind>().OutRelations(entityID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public Entity OutRelationSingleton<TRelationKind>(int entityID) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TRelationKind>().OutFirst(entityID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public Entity NthOutRelation<TRelationKind>(int entityID, int n) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TRelationKind>().OutNth(entityID, n); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public int OutRelationCount<TRelationKind>(int entityID) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TRelationKind>().OutRelationCount(entityID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public bool HasOutRelation<TRelationKind>(int entityID) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TRelationKind>().HasOutRelation(entityID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public ReverseSpanEnumerator<Entity> InRelations<TRelationKind>(int entityID) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TRelationKind>().InRelations(entityID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public Entity NthInRelation<TRelationKind>(int entityID, int n) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TRelationKind>().InNth(entityID, n); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public Entity InRelationSingleton<TRelationKind>(int entityID) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TRelationKind>().InFirst(entityID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public bool HasInRelation<TRelationKind>(int entityID) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TRelationKind>().HasInRelation(entityID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public int InRelationCount<TRelationKind>(int entityID) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return Lookup<TRelationKind>().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<int, int> 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)}"); |  | ||||||
| 							} |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -1,99 +1,74 @@ | ||||||
| using System; | using System; | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| using System.Runtime.CompilerServices; | using System.Runtime.CompilerServices; | ||||||
| using System.Runtime.InteropServices; |  | ||||||
| using MoonTools.ECS.Collections; | using MoonTools.ECS.Collections; | ||||||
| 
 | 
 | ||||||
| namespace MoonTools.ECS | namespace MoonTools.ECS; | ||||||
| { |  | ||||||
| 	internal abstract class RelationStorage |  | ||||||
| 	{ |  | ||||||
| 		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(); |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	// Relation is the two entities, A related to B. | // TODO: implement this entire class with NativeMemory equivalents, can just memcpy for snapshots | ||||||
| 	// TRelation is the data attached to the relation. | internal class RelationStorage | ||||||
| 	internal unsafe class RelationStorage<TRelation> : RelationStorage, IDisposable where TRelation : unmanaged |  | ||||||
| { | { | ||||||
| 		private int count = 0; | 	internal NativeArray relations; | ||||||
| 		private int capacity = 16; | 	internal NativeArray relationDatas; | ||||||
| 		private (Entity, Entity)* relations; | 	internal Dictionary<(Entity, Entity), int> indices = new Dictionary<(Entity, Entity), int>(16); | ||||||
| 		private TRelation* relationDatas; | 	internal Dictionary<Entity, IndexableSet<Entity>> outRelations = new Dictionary<Entity, IndexableSet<Entity>>(16); | ||||||
| 		private Dictionary<(Entity, Entity), int> indices = new Dictionary<(Entity, Entity), int>(16); | 	internal Dictionary<Entity, IndexableSet<Entity>> inRelations = new Dictionary<Entity, IndexableSet<Entity>>(16); | ||||||
| 		private Dictionary<int, IndexableSet<Entity>> outRelations = new Dictionary<int, IndexableSet<Entity>>(16); |  | ||||||
| 		private Dictionary<int, IndexableSet<Entity>> inRelations = new Dictionary<int, IndexableSet<Entity>>(16); |  | ||||||
| 	private Stack<IndexableSet<Entity>> listPool = new Stack<IndexableSet<Entity>>(); | 	private Stack<IndexableSet<Entity>> listPool = new Stack<IndexableSet<Entity>>(); | ||||||
| 
 | 
 | ||||||
| 	private bool disposed; | 	private bool disposed; | ||||||
| 
 | 
 | ||||||
| 		public RelationStorage() | 	public RelationStorage(int relationDataSize) | ||||||
| 	{ | 	{ | ||||||
| 			relations = ((Entity, Entity)*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<(Entity, Entity)>())); | 		relations = new NativeArray(Unsafe.SizeOf<(Entity, Entity)>()); | ||||||
| 			relationDatas = (TRelation*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<TRelation>())); | 		relationDatas = new NativeArray(relationDataSize); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		public override ReverseSpanEnumerator<(Entity, Entity)> All() | 	public ReverseSpanEnumerator<(Entity, Entity)> All() | ||||||
| 	{ | 	{ | ||||||
| 			return new ReverseSpanEnumerator<(Entity, Entity)>(new Span<(Entity, Entity)>(relations, count)); | 		return new ReverseSpanEnumerator<(Entity, Entity)>(relations.ToSpan<(Entity, Entity)>()); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		public void Set(in Entity entityA, in Entity entityB, TRelation relationData) | 	public unsafe void Set<T>(in Entity entityA, in Entity entityB, in T relationData) where T : unmanaged | ||||||
| 	{ | 	{ | ||||||
| 		var relation = (entityA, entityB); | 		var relation = (entityA, entityB); | ||||||
| 
 | 
 | ||||||
| 		if (indices.TryGetValue(relation, out var index)) | 		if (indices.TryGetValue(relation, out var index)) | ||||||
| 		{ | 		{ | ||||||
| 				relationDatas[index] = relationData; | 			relationDatas.Set(index, relationData); | ||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 			var idA = entityA.ID; | 		if (!outRelations.ContainsKey(entityA)) | ||||||
| 			var idB = entityB.ID; |  | ||||||
| 
 |  | ||||||
| 			if (!outRelations.ContainsKey(idA)) |  | ||||||
| 		{ | 		{ | ||||||
| 				outRelations[idA] = AcquireHashSetFromPool(); | 			outRelations[entityA] = AcquireHashSetFromPool(); | ||||||
| 		} | 		} | ||||||
| 			outRelations[idA].Add(idB); | 		outRelations[entityA].Add(entityB); | ||||||
| 
 | 
 | ||||||
| 			if (!inRelations.ContainsKey(idB)) | 		if (!inRelations.ContainsKey(entityB)) | ||||||
| 		{ | 		{ | ||||||
| 				inRelations[idB] = AcquireHashSetFromPool(); | 			inRelations[entityB] = AcquireHashSetFromPool(); | ||||||
| 		} | 		} | ||||||
| 			inRelations[idB].Add(idA); | 		inRelations[entityB].Add(entityA); | ||||||
| 
 | 
 | ||||||
| 			if (count >= capacity) | 		relations.Append(relation); | ||||||
| 			{ | 		relationDatas.Append(relationData); | ||||||
| 				capacity *= 2; | 		indices.Add(relation, relations.Count - 1); | ||||||
| 				relations = ((Entity, Entity)*) NativeMemory.Realloc(relations, (nuint) (capacity * Unsafe.SizeOf<(Entity, Entity)>())); |  | ||||||
| 				relationDatas = (TRelation*) NativeMemory.Realloc(relationDatas, (nuint) (capacity * Unsafe.SizeOf<TRelation>())); |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 			relations[count] = relation; | 	public ref T Get<T>(in Entity entityA, in Entity entityB) where T : unmanaged | ||||||
| 			relationDatas[count] = relationData; | 	{ | ||||||
| 			indices.Add(relation, count); | 		var relationIndex = indices[(entityA, entityB)]; | ||||||
| 			count += 1; | 		return ref relationDatas.Get<T>(relationIndex); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		public TRelation Get(in Entity entityA, in Entity entityB) | 	public bool Has(Entity entityA, Entity entityB) | ||||||
| 	{ | 	{ | ||||||
| 			return relationDatas[indices[(entityA, entityB)]]; | 		return indices.ContainsKey((entityA, entityB)); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		public bool Has((Entity, Entity) relation) | 	public ReverseSpanEnumerator<Entity> OutRelations(Entity Entity) | ||||||
| 	{ | 	{ | ||||||
| 			return indices.ContainsKey(relation); | 		if (outRelations.TryGetValue(Entity, out var entityOutRelations)) | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public ReverseSpanEnumerator<Entity> OutRelations(int entityID) |  | ||||||
| 		{ |  | ||||||
| 			if (outRelations.TryGetValue(entityID, out var entityOutRelations)) |  | ||||||
| 		{ | 		{ | ||||||
| 			return entityOutRelations.GetEnumerator(); | 			return entityOutRelations.GetEnumerator(); | ||||||
| 		} | 		} | ||||||
|  | @ -103,35 +78,35 @@ namespace MoonTools.ECS | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		public Entity OutFirst(int entityID) | 	public Entity OutFirst(Entity Entity) | ||||||
| 	{ | 	{ | ||||||
| 			return OutNth(entityID, 0); | 		return OutNth(Entity, 0); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		public Entity OutNth(int entityID, int n) | 	public Entity OutNth(Entity Entity, int n) | ||||||
| 	{ | 	{ | ||||||
| #if DEBUG | #if DEBUG | ||||||
| 			if (!outRelations.ContainsKey(entityID) || outRelations[entityID].Count == 0) | 		if (!outRelations.ContainsKey(Entity) || outRelations[Entity].Count == 0) | ||||||
| 		{ | 		{ | ||||||
| 			throw new KeyNotFoundException("No out relations to this entity!"); | 			throw new KeyNotFoundException("No out relations to this entity!"); | ||||||
| 		} | 		} | ||||||
| #endif | #endif | ||||||
| 			return outRelations[entityID][n]; | 		return outRelations[Entity][n]; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		public bool HasOutRelation(int entityID) | 	public bool HasOutRelation(Entity Entity) | ||||||
| 	{ | 	{ | ||||||
| 			return outRelations.ContainsKey(entityID) && outRelations[entityID].Count > 0; | 		return outRelations.ContainsKey(Entity) && outRelations[Entity].Count > 0; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		public int OutRelationCount(int entityID) | 	public int OutRelationCount(Entity Entity) | ||||||
| 	{ | 	{ | ||||||
| 			return outRelations.TryGetValue(entityID, out var entityOutRelations) ? entityOutRelations.Count : 0; | 		return outRelations.TryGetValue(Entity, out var entityOutRelations) ? entityOutRelations.Count : 0; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		public ReverseSpanEnumerator<Entity> InRelations(int entityID) | 	public ReverseSpanEnumerator<Entity> InRelations(Entity Entity) | ||||||
| 	{ | 	{ | ||||||
| 			if (inRelations.TryGetValue(entityID, out var entityInRelations)) | 		if (inRelations.TryGetValue(Entity, out var entityInRelations)) | ||||||
| 		{ | 		{ | ||||||
| 			return entityInRelations.GetEnumerator(); | 			return entityInRelations.GetEnumerator(); | ||||||
| 		} | 		} | ||||||
|  | @ -141,31 +116,31 @@ namespace MoonTools.ECS | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		public Entity InFirst(int entityID) | 	public Entity InFirst(Entity Entity) | ||||||
| 	{ | 	{ | ||||||
| 			return InNth(entityID, 0); | 		return InNth(Entity, 0); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		public Entity InNth(int entityID, int n) | 	public Entity InNth(Entity Entity, int n) | ||||||
| 	{ | 	{ | ||||||
| #if DEBUG | #if DEBUG | ||||||
| 			if (!inRelations.ContainsKey(entityID) || inRelations[entityID].Count == 0) | 		if (!inRelations.ContainsKey(Entity) || inRelations[Entity].Count == 0) | ||||||
| 		{ | 		{ | ||||||
| 			throw new KeyNotFoundException("No in relations to this entity!"); | 			throw new KeyNotFoundException("No in relations to this entity!"); | ||||||
| 		} | 		} | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| 			return inRelations[entityID][n]; | 		return inRelations[Entity][n]; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		public bool HasInRelation(int entityID) | 	public bool HasInRelation(Entity Entity) | ||||||
| 	{ | 	{ | ||||||
| 			return inRelations.ContainsKey(entityID) && inRelations[entityID].Count > 0; | 		return inRelations.ContainsKey(Entity) && inRelations[Entity].Count > 0; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		public int InRelationCount(int entityID) | 	public int InRelationCount(Entity Entity) | ||||||
| 	{ | 	{ | ||||||
| 			return inRelations.TryGetValue(entityID, out var entityInRelations) ? entityInRelations.Count : 0; | 		return inRelations.TryGetValue(Entity, out var entityInRelations) ? entityInRelations.Count : 0; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public (bool, bool) Remove(in Entity entityA, in Entity entityB) | 	public (bool, bool) Remove(in Entity entityA, in Entity entityB) | ||||||
|  | @ -174,19 +149,19 @@ namespace MoonTools.ECS | ||||||
| 		var bEmpty = false; | 		var bEmpty = false; | ||||||
| 		var relation = (entityA, entityB); | 		var relation = (entityA, entityB); | ||||||
| 
 | 
 | ||||||
| 			if (outRelations.TryGetValue(entityA.ID, out var entityOutRelations)) | 		if (outRelations.TryGetValue(entityA, out var entityOutRelations)) | ||||||
| 		{ | 		{ | ||||||
| 				entityOutRelations.Remove(entityB.ID); | 			entityOutRelations.Remove(entityB); | ||||||
| 				if (outRelations[entityA.ID].Count == 0) | 			if (outRelations[entityA].Count == 0) | ||||||
| 			{ | 			{ | ||||||
| 				aEmpty = true; | 				aEmpty = true; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 			if (inRelations.TryGetValue(entityB.ID, out var entityInRelations)) | 		if (inRelations.TryGetValue(entityB, out var entityInRelations)) | ||||||
| 		{ | 		{ | ||||||
| 				entityInRelations.Remove(entityA.ID); | 			entityInRelations.Remove(entityA); | ||||||
| 				if (inRelations[entityB.ID].Count == 0) | 			if (inRelations[entityB].Count == 0) | ||||||
| 			{ | 			{ | ||||||
| 				bEmpty = true; | 				bEmpty = true; | ||||||
| 			} | 			} | ||||||
|  | @ -194,25 +169,50 @@ namespace MoonTools.ECS | ||||||
| 
 | 
 | ||||||
| 		if (indices.TryGetValue(relation, out var index)) | 		if (indices.TryGetValue(relation, out var index)) | ||||||
| 		{ | 		{ | ||||||
| 				var lastElementIndex = count - 1; | 			var lastElementIndex = relations.Count - 1; | ||||||
|  | 
 | ||||||
|  | 			relationDatas.Delete(index); | ||||||
|  | 			relations.Delete(index); | ||||||
| 
 | 
 | ||||||
| 			// move an element into the hole | 			// move an element into the hole | ||||||
| 			if (index != lastElementIndex) | 			if (index != lastElementIndex) | ||||||
| 			{ | 			{ | ||||||
| 					var lastRelation = relations[lastElementIndex]; | 				var lastRelation = relations.Get<(Entity, Entity)>(lastElementIndex); | ||||||
| 				indices[lastRelation] = index; | 				indices[lastRelation] = index; | ||||||
| 					relationDatas[index] = relationDatas[lastElementIndex]; |  | ||||||
| 					relations[index] = lastRelation; |  | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 				count -= 1; |  | ||||||
| 			indices.Remove(relation); | 			indices.Remove(relation); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		return (aEmpty, bEmpty); | 		return (aEmpty, bEmpty); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		private IndexableSet<Entity> AcquireHashSetFromPool() | 	public void RemoveEntity(in Entity entity) | ||||||
|  | 	{ | ||||||
|  | 		if (outRelations.TryGetValue(entity, out var entityOutRelations)) | ||||||
|  | 		{ | ||||||
|  | 			foreach (var entityB in entityOutRelations) | ||||||
|  | 			{ | ||||||
|  | 				Remove(entity, entityB); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			ReturnHashSetToPool(entityOutRelations); | ||||||
|  | 			outRelations.Remove(entity); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (inRelations.TryGetValue(entity, out var entityInRelations)) | ||||||
|  | 		{ | ||||||
|  | 			foreach (var entityA in entityInRelations) | ||||||
|  | 			{ | ||||||
|  | 				Remove(entityA, entity); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			ReturnHashSetToPool(entityInRelations); | ||||||
|  | 			inRelations.Remove(entity); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	internal IndexableSet<Entity> AcquireHashSetFromPool() | ||||||
| 	{ | 	{ | ||||||
| 		if (listPool.Count == 0) | 		if (listPool.Count == 0) | ||||||
| 		{ | 		{ | ||||||
|  | @ -228,56 +228,8 @@ namespace MoonTools.ECS | ||||||
| 		listPool.Push(hashSet); | 		listPool.Push(hashSet); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		// untyped methods used for internal implementation | 	public void Clear() | ||||||
| 
 |  | ||||||
| 		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<TRelation> CreateStorage() |  | ||||||
| 		{ |  | ||||||
| 			return new RelationStorage<TRelation>(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public override void Clear() |  | ||||||
| 		{ |  | ||||||
| 			count = 0; |  | ||||||
| 		indices.Clear(); | 		indices.Clear(); | ||||||
| 
 | 
 | ||||||
| 		foreach (var set in inRelations.Values) | 		foreach (var set in inRelations.Values) | ||||||
|  | @ -291,6 +243,9 @@ namespace MoonTools.ECS | ||||||
| 			ReturnHashSetToPool(set); | 			ReturnHashSetToPool(set); | ||||||
| 		} | 		} | ||||||
| 		outRelations.Clear(); | 		outRelations.Clear(); | ||||||
|  | 
 | ||||||
|  | 		relations.Clear(); | ||||||
|  | 		relationDatas.Clear(); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	protected virtual void Dispose(bool disposing) | 	protected virtual void Dispose(bool disposing) | ||||||
|  | @ -305,22 +260,20 @@ namespace MoonTools.ECS | ||||||
| 				{ | 				{ | ||||||
| 					set.Dispose(); | 					set.Dispose(); | ||||||
| 				} | 				} | ||||||
| 				} |  | ||||||
| 
 | 
 | ||||||
| 				NativeMemory.Free(relations); | 				relations.Dispose(); | ||||||
| 				NativeMemory.Free(relationDatas); | 				relationDatas.Dispose(); | ||||||
| 				relations = null; | 			} | ||||||
| 				relationDatas = null; |  | ||||||
| 
 | 
 | ||||||
| 			disposed = true; | 			disposed = true; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		~RelationStorage() | 	// ~RelationStorage() | ||||||
| 		{ | 	// { | ||||||
| 			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | 	// 	// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||||
| 			Dispose(disposing: false); | 	// 	Dispose(disposing: false); | ||||||
| 		} | 	// } | ||||||
| 
 | 
 | ||||||
| 	public void Dispose() | 	public void Dispose() | ||||||
| 	{ | 	{ | ||||||
|  | @ -329,4 +282,3 @@ namespace MoonTools.ECS | ||||||
| 		GC.SuppressFinalize(this); | 		GC.SuppressFinalize(this); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -0,0 +1,282 @@ | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Runtime.CompilerServices; | ||||||
|  | using MoonTools.ECS.Collections; | ||||||
|  | 
 | ||||||
|  | namespace MoonTools.ECS; | ||||||
|  | 
 | ||||||
|  | // TODO: we should implement a NativeDictionary that can be memcopied | ||||||
|  | public class Snapshot | ||||||
|  | { | ||||||
|  | 	private Dictionary<TypeId, ComponentSnapshot> ComponentSnapshots = new Dictionary<TypeId, ComponentSnapshot>(); | ||||||
|  | 
 | ||||||
|  | 	private Dictionary<ArchetypeSignature, ArchetypeSnapshot> ArchetypeSnapshots = | ||||||
|  | 		new Dictionary<ArchetypeSignature, ArchetypeSnapshot>(); | ||||||
|  | 
 | ||||||
|  | 	private Dictionary<TypeId, RelationSnapshot> RelationSnapshots = | ||||||
|  | 		new Dictionary<TypeId, RelationSnapshot>(); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	private Dictionary<Entity, ArchetypeRecord> EntityIndex = new Dictionary<Entity, ArchetypeRecord>(); | ||||||
|  | 
 | ||||||
|  | 	private Dictionary<Entity, IndexableSet<TypeId>> EntityRelationIndex = | ||||||
|  | 		new Dictionary<Entity, IndexableSet<TypeId>>(); | ||||||
|  | 
 | ||||||
|  | 	private Dictionary<Entity, string> EntityTags = new Dictionary<Entity, string>(); | ||||||
|  | 
 | ||||||
|  | 	private IdAssigner EntityIdAssigner = new IdAssigner(); | ||||||
|  | 
 | ||||||
|  | 	public int Count | ||||||
|  | 	{ | ||||||
|  | 		get | ||||||
|  | 		{ | ||||||
|  | 			var count = 0; | ||||||
|  | 
 | ||||||
|  | 			foreach (var snapshot in ArchetypeSnapshots.Values) | ||||||
|  | 			{ | ||||||
|  | 				count += snapshot.Count; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			return count; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void Restore(World world) | ||||||
|  | 	{ | ||||||
|  | 		// restore archetype storage | ||||||
|  | 		foreach (var (archetypeSignature, archetypeSnapshot) in ArchetypeSnapshots) | ||||||
|  | 		{ | ||||||
|  | 			var archetype = world.ArchetypeIndex[archetypeSignature]; | ||||||
|  | 			archetypeSnapshot.Restore(archetype); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// restore entity index | ||||||
|  | 		world.EntityIndex.Clear(); | ||||||
|  | 		foreach (var (id, ArchetypeRecord) in EntityIndex) | ||||||
|  | 		{ | ||||||
|  | 			world.EntityIndex[id] = ArchetypeRecord; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// restore components | ||||||
|  | 		foreach (var (typeId, componentSnapshot) in ComponentSnapshots) | ||||||
|  | 		{ | ||||||
|  | 			var componentStorage = world.ComponentIndex[typeId]; | ||||||
|  | 			componentSnapshot.Restore(componentStorage); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// restore id assigner state | ||||||
|  | 		EntityIdAssigner.CopyTo(world.EntityIdAssigner); | ||||||
|  | 
 | ||||||
|  | 		// restore relation state | ||||||
|  | 		foreach (var (typeId, relationSnapshot) in RelationSnapshots) | ||||||
|  | 		{ | ||||||
|  | 			var relationStorage = world.RelationIndex[typeId]; | ||||||
|  | 			relationSnapshot.Restore(relationStorage); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// restore entity relation index state | ||||||
|  | 		// FIXME: arghhhh this is so slow | ||||||
|  | 		foreach (var (id, relationTypeSet) in EntityRelationIndex) | ||||||
|  | 		{ | ||||||
|  | 			world.EntityRelationIndex[id].Clear(); | ||||||
|  | 
 | ||||||
|  | 			foreach (var typeId in relationTypeSet) | ||||||
|  | 			{ | ||||||
|  | 				world.EntityRelationIndex[id].Add(typeId); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		foreach (var (id, s) in EntityTags) | ||||||
|  | 		{ | ||||||
|  | 			world.EntityTags[id] = s; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void Take(World world) | ||||||
|  | 	{ | ||||||
|  | 		// copy id assigner state | ||||||
|  | 		world.EntityIdAssigner.CopyTo(EntityIdAssigner); | ||||||
|  | 
 | ||||||
|  | 		// copy entity index | ||||||
|  | 		EntityIndex.Clear(); | ||||||
|  | 		foreach (var (id, ArchetypeRecord) in world.EntityIndex) | ||||||
|  | 		{ | ||||||
|  | 			EntityIndex[id] = ArchetypeRecord; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// copy archetypes | ||||||
|  | 		foreach (var archetype in world.ArchetypeIndex.Values) | ||||||
|  | 		{ | ||||||
|  | 			TakeArchetypeSnapshot(archetype); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// copy components | ||||||
|  | 		foreach (var (typeId, componentStorage) in world.ComponentIndex) | ||||||
|  | 		{ | ||||||
|  | 			TakeComponentSnapshot(typeId, componentStorage); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// copy relations | ||||||
|  | 		foreach (var (typeId, relationStorage) in world.RelationIndex) | ||||||
|  | 		{ | ||||||
|  | 			TakeRelationSnapshot(typeId, relationStorage); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// copy entity relation index | ||||||
|  | 		// FIXME: arghhhh this is so slow | ||||||
|  | 		foreach (var (id, relationTypeSet) in world.EntityRelationIndex) | ||||||
|  | 		{ | ||||||
|  | 			if (!EntityRelationIndex.ContainsKey(id)) | ||||||
|  | 			{ | ||||||
|  | 				EntityRelationIndex.Add(id, new IndexableSet<TypeId>()); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			EntityRelationIndex[id].Clear(); | ||||||
|  | 
 | ||||||
|  | 			foreach (var typeId in relationTypeSet) | ||||||
|  | 			{ | ||||||
|  | 				EntityRelationIndex[id].Add(typeId); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		foreach (var (id, s) in world.EntityTags) | ||||||
|  | 		{ | ||||||
|  | 			EntityTags[id] = s; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private void TakeArchetypeSnapshot(Archetype archetype) | ||||||
|  | 	{ | ||||||
|  | 		if (!ArchetypeSnapshots.TryGetValue(archetype.Signature, out var archetypeSnapshot)) | ||||||
|  | 		{ | ||||||
|  | 			archetypeSnapshot = new ArchetypeSnapshot(); | ||||||
|  | 			ArchetypeSnapshots.Add(archetype.Signature, archetypeSnapshot); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		archetypeSnapshot.Take(archetype); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private void TakeComponentSnapshot(TypeId typeId, ComponentStorage componentStorage) | ||||||
|  | 	{ | ||||||
|  | 		if (!ComponentSnapshots.TryGetValue(typeId, out var componentSnapshot)) | ||||||
|  | 		{ | ||||||
|  | 			componentSnapshot = new ComponentSnapshot(componentStorage.Components.ElementSize); | ||||||
|  | 			ComponentSnapshots.Add(typeId, componentSnapshot); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		componentSnapshot.Take(componentStorage); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private void TakeRelationSnapshot(TypeId typeId, RelationStorage relationStorage) | ||||||
|  | 	{ | ||||||
|  | 		if (!RelationSnapshots.TryGetValue(typeId, out var snapshot)) | ||||||
|  | 		{ | ||||||
|  | 			snapshot = new RelationSnapshot(relationStorage.relationDatas.ElementSize); | ||||||
|  | 			RelationSnapshots.Add(typeId, snapshot); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		snapshot.Take(relationStorage); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private class ArchetypeSnapshot | ||||||
|  | 	{ | ||||||
|  | 		private readonly NativeArray<Entity> Entities; | ||||||
|  | 		public int Count => Entities.Count; | ||||||
|  | 
 | ||||||
|  | 		public ArchetypeSnapshot() | ||||||
|  | 		{ | ||||||
|  | 			Entities = new NativeArray<Entity>(); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void Take(Archetype archetype) | ||||||
|  | 		{ | ||||||
|  | 			archetype.Entities.CopyTo(Entities); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void Restore(Archetype archetype) | ||||||
|  | 		{ | ||||||
|  | 			Entities.CopyTo(archetype.Entities); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private class ComponentSnapshot | ||||||
|  | 	{ | ||||||
|  | 		private readonly Dictionary<Entity, int> EntityIDToStorageIndex = new Dictionary<Entity, int>(); | ||||||
|  | 		private readonly NativeArray Components; | ||||||
|  | 		private readonly NativeArray<Entity> EntityIDs; | ||||||
|  | 
 | ||||||
|  | 		public ComponentSnapshot(int elementSize) | ||||||
|  | 		{ | ||||||
|  | 			Components = new NativeArray(elementSize); | ||||||
|  | 			EntityIDs = new NativeArray<Entity>(); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void Take(ComponentStorage componentStorage) | ||||||
|  | 		{ | ||||||
|  | 			componentStorage.Components.CopyAllTo(Components); | ||||||
|  | 			componentStorage.EntityIDs.CopyTo(EntityIDs); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void Restore(ComponentStorage componentStorage) | ||||||
|  | 		{ | ||||||
|  | 			Components.CopyAllTo(componentStorage.Components); | ||||||
|  | 			EntityIDs.CopyTo(componentStorage.EntityIDs); | ||||||
|  | 
 | ||||||
|  | 			componentStorage.EntityIDToStorageIndex.Clear(); | ||||||
|  | 			for (int i = 0; i < EntityIDs.Count; i += 1) | ||||||
|  | 			{ | ||||||
|  | 				var entityID = EntityIDs[i]; | ||||||
|  | 				componentStorage.EntityIDToStorageIndex[entityID] = i; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private class RelationSnapshot | ||||||
|  | 	{ | ||||||
|  | 		private NativeArray Relations; | ||||||
|  | 		private NativeArray RelationDatas; | ||||||
|  | 
 | ||||||
|  | 		public RelationSnapshot(int elementSize) | ||||||
|  | 		{ | ||||||
|  | 			Relations = new NativeArray(Unsafe.SizeOf<(Entity, Entity)>()); | ||||||
|  | 			RelationDatas = new NativeArray(elementSize); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void Take(RelationStorage relationStorage) | ||||||
|  | 		{ | ||||||
|  | 			relationStorage.relations.CopyAllTo(Relations); | ||||||
|  | 			relationStorage.relationDatas.CopyAllTo(RelationDatas); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void Restore(RelationStorage relationStorage) | ||||||
|  | 		{ | ||||||
|  | 			relationStorage.Clear(); | ||||||
|  | 
 | ||||||
|  | 			Relations.CopyAllTo(relationStorage.relations); | ||||||
|  | 			RelationDatas.CopyAllTo(relationStorage.relationDatas); | ||||||
|  | 
 | ||||||
|  | 			for (int index = 0; index < Relations.Count; index += 1) | ||||||
|  | 			{ | ||||||
|  | 				var relation = Relations.Get<(Entity, Entity)>(index); | ||||||
|  | 				relationStorage.indices[relation] = index; | ||||||
|  | 
 | ||||||
|  | 				relationStorage.indices[relation] = index; | ||||||
|  | 
 | ||||||
|  | 				if (!relationStorage.outRelations.ContainsKey(relation.Item1)) | ||||||
|  | 				{ | ||||||
|  | 					relationStorage.outRelations[relation.Item1] = | ||||||
|  | 						relationStorage.AcquireHashSetFromPool(); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				relationStorage.outRelations[relation.Item1].Add(relation.Item2); | ||||||
|  | 
 | ||||||
|  | 				if (!relationStorage.inRelations.ContainsKey(relation.Item2)) | ||||||
|  | 				{ | ||||||
|  | 					relationStorage.inRelations[relation.Item2] = | ||||||
|  | 						relationStorage.AcquireHashSetFromPool(); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				relationStorage.inRelations[relation.Item2].Add(relation.Item1); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -1,47 +1,15 @@ | ||||||
| using System; | 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<T> ReadMessages<T>() where T : unmanaged => World.ReadMessages<T>(); | ||||||
| 
 | 	protected T ReadMessage<T>() where T : unmanaged => World.ReadMessage<T>(); | ||||||
| 		protected ReadOnlySpan<TMessage> ReadMessages<TMessage>() where TMessage : unmanaged | 	protected bool SomeMessage<T>() where T : unmanaged => World.SomeMessage<T>(); | ||||||
| 		{ | 	protected void Send<T>(T message) where T : unmanaged => World.Send(message); | ||||||
| 			return MessageDepot.All<TMessage>(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected TMessage ReadMessage<TMessage>() where TMessage : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return MessageDepot.First<TMessage>(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected bool SomeMessage<TMessage>() where TMessage : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return MessageDepot.Some<TMessage>(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected ReverseSpanEnumerator<TMessage> ReadMessagesWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return MessageDepot.WithEntity<TMessage>(entity.ID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected ref readonly TMessage ReadMessageWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return ref MessageDepot.FirstWithEntity<TMessage>(entity.ID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected bool SomeMessageWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			return MessageDepot.SomeWithEntity<TMessage>(entity.ID); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected void Send<TMessage>(in TMessage message) where TMessage : unmanaged => World.Send(message); |  | ||||||
| 
 |  | ||||||
| 		protected void Send<TMessage>(in Entity entity, in TMessage message) where TMessage : unmanaged => World.Send(entity, message); |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | using System; | ||||||
|  | 
 | ||||||
|  | namespace MoonTools.ECS; | ||||||
|  | 
 | ||||||
|  | public readonly record struct TypeId(uint Value); | ||||||
|  | @ -1,33 +0,0 @@ | ||||||
| using System; |  | ||||||
| using System.Collections.Generic; |  | ||||||
| 
 |  | ||||||
| namespace MoonTools.ECS |  | ||||||
| { |  | ||||||
| 	public class TypeIndices |  | ||||||
| 	{ |  | ||||||
| 		Dictionary<Type, int> TypeToIndex = new Dictionary<Type, int>(); |  | ||||||
| 		int nextID = 0; |  | ||||||
| 		public int Count => TypeToIndex.Count; |  | ||||||
| 
 |  | ||||||
| 		public int GetIndex<T>() 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<Type, int>.KeyCollection Types => TypeToIndex.Keys; |  | ||||||
| #endif |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
							
								
								
									
										540
									
								
								src/World.cs
								
								
								
								
							
							
						
						
									
										540
									
								
								src/World.cs
								
								
								
								
							|  | @ -1,194 +1,454 @@ | ||||||
| using System; | using System; | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
|  | using System.Runtime.CompilerServices; | ||||||
|  | using MoonTools.ECS.Collections; | ||||||
| 
 | 
 | ||||||
| namespace MoonTools.ECS | namespace MoonTools.ECS | ||||||
| { | { | ||||||
| 	public class World | 	public class World | ||||||
| 	{ | 	{ | ||||||
| 		internal readonly static TypeIndices ComponentTypeIndices = new TypeIndices(); | 		// Get TypeId from a Type | ||||||
| 		internal readonly static TypeIndices RelationTypeIndices = new TypeIndices(); | 		private readonly Dictionary<Type, TypeId> TypeToId = new Dictionary<Type, TypeId>(); | ||||||
| 		internal readonly EntityStorage EntityStorage = new EntityStorage(); | 
 | ||||||
| 		internal readonly ComponentDepot ComponentDepot; | 		#if DEBUG | ||||||
| 		internal readonly MessageDepot MessageDepot = new MessageDepot(); | 		private Dictionary<TypeId, Type> IdToType = new Dictionary<TypeId, Type>(); | ||||||
| 		internal readonly RelationDepot RelationDepot; | 		#endif | ||||||
| 		internal readonly FilterStorage FilterStorage; | 
 | ||||||
| 		public FilterBuilder FilterBuilder => new FilterBuilder(FilterStorage, ComponentTypeIndices); | 		// Get element size from a TypeId | ||||||
|  | 		private readonly Dictionary<TypeId, int> ElementSizes = new Dictionary<TypeId, int>(); | ||||||
|  | 
 | ||||||
|  | 		// Archetypes | ||||||
|  | 		internal readonly Dictionary<ArchetypeSignature, Archetype> ArchetypeIndex = new Dictionary<ArchetypeSignature, Archetype>(); | ||||||
|  | 		internal readonly Dictionary<Entity, ArchetypeRecord> EntityIndex = new Dictionary<Entity, ArchetypeRecord>(); | ||||||
|  | 		internal readonly Archetype EmptyArchetype; | ||||||
|  | 
 | ||||||
|  | 		// TODO: can we make the tag an native array of chars at some point? | ||||||
|  | 		internal Dictionary<Entity, string> EntityTags = new Dictionary<Entity, string>(); | ||||||
|  | 
 | ||||||
|  | 		// Relation Storages | ||||||
|  | 		internal Dictionary<TypeId, RelationStorage> RelationIndex = new Dictionary<TypeId, RelationStorage>(); | ||||||
|  | 		internal Dictionary<Entity, IndexableSet<TypeId>> EntityRelationIndex = new Dictionary<Entity, IndexableSet<TypeId>>(); | ||||||
|  | 
 | ||||||
|  | 		// Message Storages | ||||||
|  | 		private Dictionary<TypeId, MessageStorage> MessageIndex = new Dictionary<TypeId, MessageStorage>(); | ||||||
|  | 
 | ||||||
|  | 		public FilterBuilder FilterBuilder => new FilterBuilder(this); | ||||||
|  | 
 | ||||||
|  | 		internal readonly Dictionary<TypeId, ComponentStorage> ComponentIndex = new Dictionary<TypeId, ComponentStorage>(); | ||||||
|  | 
 | ||||||
|  | 		internal IdAssigner EntityIdAssigner = new IdAssigner(); | ||||||
|  | 		private IdAssigner TypeIdAssigner = new IdAssigner(); | ||||||
| 
 | 
 | ||||||
| 		public World() | 		public World() | ||||||
| 		{ | 		{ | ||||||
| 			ComponentDepot = new ComponentDepot(ComponentTypeIndices); | 			EmptyArchetype = CreateArchetype(ArchetypeSignature.Empty); | ||||||
| 			RelationDepot = new RelationDepot(EntityStorage, RelationTypeIndices); |  | ||||||
| 			FilterStorage = new FilterStorage(EntityStorage, ComponentTypeIndices); |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		internal TypeId GetTypeId<T>() 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<T>()); | ||||||
|  | 
 | ||||||
|  | 			#if DEBUG | ||||||
|  | 			IdToType.Add(typeId, typeof(T)); | ||||||
|  | 			#endif | ||||||
|  | 
 | ||||||
|  | 			return typeId; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  | 		private ComponentStorage GetComponentStorage<T>() where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var typeId = GetTypeId<T>(); | ||||||
|  | 			if (ComponentIndex.TryGetValue(typeId, out var componentStorage)) | ||||||
|  | 			{ | ||||||
|  | 				return componentStorage; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			componentStorage = new ComponentStorage(ElementSizes[typeId]); | ||||||
|  | 			ComponentIndex.Add(typeId, componentStorage); | ||||||
|  | 			return componentStorage; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		private Archetype CreateArchetype(ArchetypeSignature signature) | ||||||
|  | 		{ | ||||||
|  | 			var archetype = new Archetype(this, signature); | ||||||
|  | 			ArchetypeIndex.Add(signature, archetype); | ||||||
|  | 			return archetype; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// ENTITIES | ||||||
|  | 
 | ||||||
| 		public Entity CreateEntity(string tag = "") | 		public Entity CreateEntity(string tag = "") | ||||||
| 		{ | 		{ | ||||||
| 			return EntityStorage.Create(tag); | 			var entity = new Entity(EntityIdAssigner.Assign()); | ||||||
|  | 			EntityIndex.Add(entity, new ArchetypeRecord(EmptyArchetype, EmptyArchetype.Count)); | ||||||
|  | 			EmptyArchetype.Append(entity); | ||||||
|  | 			return entity; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public void Tag(Entity entity, string tag) | 		public void Tag(Entity entity, string tag) | ||||||
| 		{ | 		{ | ||||||
| 			EntityStorage.Tag(entity, tag); | 			EntityTags[entity] = tag; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public string GetTag(Entity entity) | 		public string GetTag(Entity entity) | ||||||
| 		{ | 		{ | ||||||
| 			return EntityStorage.Tag(entity); | 			return EntityTags[entity]; | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Set<TComponent>(Entity entity, in TComponent component) where TComponent : unmanaged |  | ||||||
| 		{ |  | ||||||
| #if DEBUG |  | ||||||
| 			// check for use after destroy |  | ||||||
| 			if (!EntityStorage.Exists(entity)) |  | ||||||
| 			{ |  | ||||||
| 				throw new InvalidOperationException("This entity is not valid!"); |  | ||||||
| 			} |  | ||||||
| #endif |  | ||||||
| 			ComponentDepot.Set<TComponent>(entity.ID, component); |  | ||||||
| 
 |  | ||||||
| 			if (EntityStorage.SetComponent(entity.ID, ComponentTypeIndices.GetIndex<TComponent>())) |  | ||||||
| 			{ |  | ||||||
| 				FilterStorage.Check<TComponent>(entity.ID); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// untyped version for Transfer |  | ||||||
| 		// no filter check because filter state is copied directly |  | ||||||
| 		internal unsafe void Set(Entity entity, int componentTypeIndex, void* component) |  | ||||||
| 		{ |  | ||||||
| 			ComponentDepot.Set(entity.ID, componentTypeIndex, component); |  | ||||||
| 			EntityStorage.SetComponent(entity.ID, componentTypeIndex); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Remove<TComponent>(in Entity entity) where TComponent : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			if (EntityStorage.RemoveComponent(entity.ID, ComponentTypeIndices.GetIndex<TComponent>())) |  | ||||||
| 			{ |  | ||||||
| 				// Run filter storage update first so that the entity state is still valid in the remove callback. |  | ||||||
| 				FilterStorage.Check<TComponent>(entity.ID); |  | ||||||
| 				ComponentDepot.Remove<TComponent>(entity.ID); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Relate<TRelationKind>(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			RelationDepot.Set(entityA, entityB, relationData); |  | ||||||
| 			var relationTypeIndex = RelationTypeIndices.GetIndex<TRelationKind>(); |  | ||||||
| 			EntityStorage.AddRelationKind(entityA.ID, relationTypeIndex); |  | ||||||
| 			EntityStorage.AddRelationKind(entityB.ID, relationTypeIndex); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Unrelate<TRelationKind>(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			var (aEmpty, bEmpty) = RelationDepot.Remove<TRelationKind>(entityA, entityB); |  | ||||||
| 
 |  | ||||||
| 			if (aEmpty) |  | ||||||
| 			{ |  | ||||||
| 				EntityStorage.RemoveRelation(entityA.ID, RelationTypeIndices.GetIndex<TRelationKind>()); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			if (bEmpty) |  | ||||||
| 			{ |  | ||||||
| 				EntityStorage.RemoveRelation(entityB.ID, RelationTypeIndices.GetIndex<TRelationKind>()); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void UnrelateAll<TRelationKind>(in Entity entity) where TRelationKind : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			RelationDepot.UnrelateAll<TRelationKind>(entity.ID); |  | ||||||
| 			EntityStorage.RemoveRelation(entity.ID, RelationTypeIndices.GetIndex<TRelationKind>()); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Send<TMessage>(in TMessage message) where TMessage : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			MessageDepot.Add(message); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Send<TMessage>(in Entity entity, in TMessage message) where TMessage : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			MessageDepot.Add(entity.ID, message); |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public void Destroy(in Entity entity) | 		public void Destroy(in Entity entity) | ||||||
| 		{ | 		{ | ||||||
| 			foreach (var componentTypeIndex in EntityStorage.ComponentTypeIndices(entity.ID)) | 			var record = EntityIndex[entity]; | ||||||
|  | 			var archetype = record.Archetype; | ||||||
|  | 			var row = record.Row; | ||||||
|  | 
 | ||||||
|  | 			// remove all components from storages | ||||||
|  | 			for (int i = 0; i < archetype.Signature.Count; i += 1) | ||||||
| 			{ | 			{ | ||||||
| 				// Run filter storage update first so that the entity state is still valid in the remove callback. | 				var componentStorage = ComponentIndex[archetype.Signature[i]]; | ||||||
| 				FilterStorage.RemoveEntity(entity.ID, componentTypeIndex); | 				componentStorage.Remove(entity); | ||||||
| 				ComponentDepot.Remove(entity.ID, componentTypeIndex); |  | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			foreach (var relationTypeIndex in EntityStorage.RelationTypeIndices(entity.ID)) | 			// remove all relations from storage | ||||||
|  | 			foreach (var relationTypeIndex in EntityRelationIndex[entity]) | ||||||
| 			{ | 			{ | ||||||
| 				RelationDepot.UnrelateAll(entity.ID, relationTypeIndex); | 				var relationStorage = RelationIndex[relationTypeIndex]; | ||||||
| 				EntityStorage.RemoveRelation(entity.ID, relationTypeIndex); | 				relationStorage.RemoveEntity(entity); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			EntityStorage.Destroy(entity); | 			EntityRelationIndex[entity].Clear(); | ||||||
|  | 
 | ||||||
|  | 			// remove from archetype | ||||||
|  | 			if (row != archetype.Count - 1) | ||||||
|  | 			{ | ||||||
|  | 				var lastEntity = archetype.Entities[archetype.Count - 1]; | ||||||
|  | 				archetype.Entities[row] = lastEntity; | ||||||
|  | 				EntityIndex[lastEntity] = new ArchetypeRecord(archetype, row); | ||||||
|  | 			} | ||||||
|  | 			archetype.Entities.RemoveLastElement(); | ||||||
|  | 			EntityIndex.Remove(entity); | ||||||
|  | 
 | ||||||
|  | 			// recycle ID | ||||||
|  | 			EntityIdAssigner.Unassign(entity.ID); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		// COMPONENTS | ||||||
| 
 | 
 | ||||||
|  | 		public bool Has<T>(in Entity entity) where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var storage = GetComponentStorage<T>(); | ||||||
|  | 			return storage.Has(entity); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public bool Some<T>() where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var storage = GetComponentStorage<T>(); | ||||||
|  | 			return storage.Any(); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public ref T Get<T>(in Entity entity) where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var storage = GetComponentStorage<T>(); | ||||||
|  | 			return ref storage.Get<T>(entity); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public ref T GetSingleton<T>() where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var storage = GetComponentStorage<T>(); | ||||||
|  | 			return ref storage.GetFirst<T>(); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public Entity GetSingletonEntity<T>() where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var storage = GetComponentStorage<T>(); | ||||||
|  | 			return storage.FirstEntity(); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void Set<T>(in Entity entity, in T component) where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var componentStorage = GetComponentStorage<T>(); | ||||||
|  | 
 | ||||||
|  | 			if (!componentStorage.Set(entity, component)) | ||||||
|  | 			{ | ||||||
|  | 				UpdateArchetype<T>(entity, true); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void Remove<T>(in Entity entity) where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var componentStorage = GetComponentStorage<T>(); | ||||||
|  | 
 | ||||||
|  | 			if (componentStorage.Remove(entity)) | ||||||
|  | 			{ | ||||||
|  | 				UpdateArchetype<T>(entity, false); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		private void UpdateArchetype<T>(in Entity entity, bool insert) | ||||||
|  | 		{ | ||||||
|  | 			Archetype? nextArchetype; | ||||||
|  | 
 | ||||||
|  | 			var componentTypeId = TypeToId[typeof(T)]; | ||||||
|  | 			var record = EntityIndex[entity]; | ||||||
|  | 			var archetype = record.Archetype; | ||||||
|  | 
 | ||||||
|  | 			if (archetype.Edges.TryGetValue(TypeToId[typeof(T)], out var edge)) | ||||||
|  | 			{ | ||||||
|  | 				nextArchetype = insert ? edge.Add : edge.Remove; | ||||||
|  | 			} | ||||||
|  | 			else | ||||||
|  | 			{ | ||||||
|  | 				var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1); | ||||||
|  | 				archetype.Signature.CopyTo(nextSignature); | ||||||
|  | 
 | ||||||
|  | 				if (insert) | ||||||
|  | 				{ | ||||||
|  | 					nextSignature.Insert(componentTypeId); | ||||||
|  | 				} | ||||||
|  | 				else | ||||||
|  | 				{ | ||||||
|  | 					nextSignature.Remove(componentTypeId); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype)) | ||||||
|  | 				{ | ||||||
|  | 					nextArchetype = CreateArchetype(nextSignature); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				var newEdge = new ArchetypeEdge(nextArchetype, archetype); | ||||||
|  | 				archetype.Edges.Add(componentTypeId, newEdge); | ||||||
|  | 				nextArchetype.Edges.Add(componentTypeId, newEdge); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			var newRow = archetype.Transfer(record.Row, nextArchetype); | ||||||
|  | 			EntityIndex[entity] = new ArchetypeRecord(nextArchetype, newRow); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// RELATIONS | ||||||
|  | 
 | ||||||
|  | 		private RelationStorage RegisterRelationType(TypeId typeId) | ||||||
|  | 		{ | ||||||
|  | 			var relationStorage = new RelationStorage(ElementSizes[typeId]); | ||||||
|  | 			RelationIndex.Add(typeId, relationStorage); | ||||||
|  | 			return relationStorage; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  | 		private RelationStorage GetRelationStorage<T>() where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var typeId = GetTypeId<T>(); | ||||||
|  | 			if (RelationIndex.TryGetValue(typeId, out var relationStorage)) | ||||||
|  | 			{ | ||||||
|  | 				return relationStorage; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			return RegisterRelationType(typeId); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void Relate<T>(in Entity entityA, in Entity entityB, in T relation) where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 			relationStorage.Set(entityA, entityB, relation); | ||||||
|  | 			EntityRelationIndex[entityA].Add(TypeToId[typeof(T)]); | ||||||
|  | 			EntityRelationIndex[entityB].Add(TypeToId[typeof(T)]); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void Unrelate<T>(in Entity entityA, in Entity entityB) where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 			relationStorage.Remove(entityA, entityB); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void UnrelateAll<T>(in Entity entity) where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 			relationStorage.RemoveEntity(entity); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public bool Related<T>(in Entity entityA, in Entity entityB) where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 			return relationStorage.Has(entityA, entityB); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public T GetRelationData<T>(in Entity entityA, in Entity entityB) where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 			return relationStorage.Get<T>(entityA, entityB); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public ReverseSpanEnumerator<(Entity, Entity)> Relations<T>() where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 			return relationStorage.All(); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public ReverseSpanEnumerator<Entity> OutRelations<T>(Entity entity) where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 			return relationStorage.OutRelations(entity); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public Entity OutRelationSingleton<T>(in Entity entity) where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 			return relationStorage.OutFirst(entity); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public bool HasOutRelation<T>(in Entity entity) where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 			return relationStorage.HasOutRelation(entity); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public int OutRelationCount<T>(in Entity entity) where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 			return relationStorage.OutRelationCount(entity); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public Entity NthOutRelation<T>(in Entity entity, int n) where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 			return relationStorage.OutNth(entity, n); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public ReverseSpanEnumerator<Entity> InRelations<T>(Entity entity) where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 			return relationStorage.InRelations(entity); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public Entity InRelationSingleton<T>(in Entity entity) where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 			return relationStorage.InFirst(entity); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public bool HasInRelation<T>(in Entity entity) where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 			return relationStorage.HasInRelation(entity); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public int InRelationCount<T>(in Entity entity) where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 			return relationStorage.InRelationCount(entity); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public Entity NthInRelation<T>(in Entity entity, int n) where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 			return relationStorage.InNth(entity, n); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// MESSAGES | ||||||
|  | 
 | ||||||
|  | 		private TypeId GetMessageTypeId<T>() where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var typeId = GetTypeId<T>(); | ||||||
|  | 
 | ||||||
|  | 			if (!MessageIndex.ContainsKey(typeId)) | ||||||
|  | 			{ | ||||||
|  | 				MessageIndex.Add(typeId, new MessageStorage(Unsafe.SizeOf<T>())); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			return typeId; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void Send<T>(in T message) where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var typeId = GetMessageTypeId<T>(); | ||||||
|  | 			MessageIndex[typeId].Add(message); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public bool SomeMessage<T>() where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var typeId = GetMessageTypeId<T>(); | ||||||
|  | 			return MessageIndex[typeId].Some(); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public ReadOnlySpan<T> ReadMessages<T>() where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var typeId = GetMessageTypeId<T>(); | ||||||
|  | 			return MessageIndex[typeId].All<T>(); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public T ReadMessage<T>() where T : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			var typeId = GetMessageTypeId<T>(); | ||||||
|  | 			return MessageIndex[typeId].First<T>(); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// TODO: temporary component storage? | ||||||
| 		public void FinishUpdate() | 		public void FinishUpdate() | ||||||
| 		{ | 		{ | ||||||
| 			MessageDepot.Clear(); | 			foreach (var (_, messageStorage) in MessageIndex) | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Clear() |  | ||||||
| 			{ | 			{ | ||||||
| 			EntityStorage.Clear(); | 				messageStorage.Clear(); | ||||||
| 			MessageDepot.Clear(); | 			} | ||||||
| 			RelationDepot.Clear(); |  | ||||||
| 			ComponentDepot.Clear(); |  | ||||||
| 			FilterStorage.Clear(); |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		private Dictionary<int, int> WorldToTransferID = new Dictionary<int, int>(); | 		// DEBUG | ||||||
| 
 | 		// NOTE: these methods are very inefficient | ||||||
| 		/// <summary> | 		// they should only be used in debugging contexts!! | ||||||
| 		/// If you are using the World Transfer feature, call this once after your systems/filters have all been initialized. | 		#if DEBUG | ||||||
| 		/// </summary> | 		public ComponentTypeEnumerator Debug_GetAllComponentTypes(Entity entity) | ||||||
| 		public void PrepareTransferTo(World other) |  | ||||||
| 		{ | 		{ | ||||||
| 			other.FilterStorage.CreateMissingStorages(FilterStorage); | 			return new ComponentTypeEnumerator(this, EntityIndex[entity]); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// FIXME: there's probably a better way to handle Filters so they are not world-bound | 		public IEnumerable<Entity> Debug_GetEntities(Type componentType) | ||||||
| 		public unsafe void Transfer(World other, Filter filter, Filter otherFilter) |  | ||||||
| 		{ | 		{ | ||||||
| 			WorldToTransferID.Clear(); | 			var storage = ComponentIndex[TypeToId[componentType]]; | ||||||
| 			other.ComponentDepot.CreateMissingStorages(ComponentDepot); | 			return storage.Debug_GetEntities(); | ||||||
| 			other.RelationDepot.CreateMissingStorages(RelationDepot); | 		} | ||||||
| 
 | 
 | ||||||
| 			// destroy all entities matching the filter | 		public IEnumerable<Type> Debug_SearchComponentType(string typeString) | ||||||
| 			foreach (var entity in otherFilter.Entities) |  | ||||||
| 		{ | 		{ | ||||||
| 				other.Destroy(entity); | 			foreach (var type in TypeToId.Keys) | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			// create entities |  | ||||||
| 			foreach (var entity in filter.Entities) |  | ||||||
| 			{ | 			{ | ||||||
| 				var otherWorldEntity = other.CreateEntity(GetTag(entity)); | 				if (type.ToString().ToLower().Contains(typeString.ToLower())) | ||||||
| 				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]; | 					yield return type; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 				foreach (var componentTypeIndex in EntityStorage.ComponentTypeIndices(entity.ID)) | 		public ref struct ComponentTypeEnumerator | ||||||
| 		{ | 		{ | ||||||
| 					other.Set(otherWorldEntity, componentTypeIndex, ComponentDepot.UntypedGet(entity.ID, componentTypeIndex)); | 			private World World; | ||||||
| 				} | 			private ArchetypeRecord Record; | ||||||
|  | 			private int ComponentIndex; | ||||||
|  | 
 | ||||||
|  | 			public ComponentTypeEnumerator GetEnumerator() => this; | ||||||
|  | 
 | ||||||
|  | 			internal ComponentTypeEnumerator( | ||||||
|  | 				World world, | ||||||
|  | 				ArchetypeRecord record | ||||||
|  | 			) | ||||||
|  | 			{ | ||||||
|  | 				World = world; | ||||||
|  | 				Record = record; | ||||||
|  | 				ComponentIndex = -1; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			// transfer filters last so callbacks trigger correctly | 			public bool MoveNext() | ||||||
| 			FilterStorage.TransferStorage(WorldToTransferID, other.FilterStorage); | 			{ | ||||||
| 		} | 				ComponentIndex += 1; | ||||||
|  | 				return ComponentIndex < Record.Archetype.Signature.Count; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			public unsafe Type Current => World.IdToType[Record.Archetype.Signature[ComponentIndex]]; | ||||||
|  | 		} | ||||||
|  | 		#endif | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue