messages + rearranging
							parent
							
								
									9032eff699
								
							
						
					
					
						commit
						3d2261f739
					
				|  | @ -60,12 +60,27 @@ namespace MoonTools.ECS.Collections | ||||||
| 				return false; | 				return false; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | 			/* | ||||||
| 			var lastElement = array[Count - 1]; | 			var lastElement = array[Count - 1]; | ||||||
| 			var index = indices[element]; | 			var index = indices[element]; | ||||||
| 			array[index] = lastElement; | 			array[index] = lastElement; | ||||||
| 			indices[lastElement] = index; | 			indices[lastElement] = index; | ||||||
| 			count -= 1; | 			count -= 1; | ||||||
| 			indices.Remove(element); | 			indices.Remove(element); | ||||||
|  | 			*/ | ||||||
|  | 
 | ||||||
|  | 			// FIXME: we can probably undo this change | ||||||
|  | 
 | ||||||
|  | 			var index = indices[element]; | ||||||
|  | 
 | ||||||
|  | 			for (var i = index; i < Count - 1; i += 1) | ||||||
|  | 			{ | ||||||
|  | 				array[i] = array[i + 1]; | ||||||
|  | 				indices[array[i]] = i; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			indices.Remove(element); | ||||||
|  | 			count -= 1; | ||||||
| 
 | 
 | ||||||
| 			return true; | 			return true; | ||||||
| 		} | 		} | ||||||
|  | @ -0,0 +1,110 @@ | ||||||
|  | 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; | ||||||
|  | 	private int elementSize; | ||||||
|  | 
 | ||||||
|  | 	public int Count => count; | ||||||
|  | 
 | ||||||
|  | 	public Span<T>.Enumerator GetEnumerator() => new Span<T>(Array, count).GetEnumerator(); | ||||||
|  | 
 | ||||||
|  | 	private bool disposed; | ||||||
|  | 
 | ||||||
|  | 	public NativeArray(int capacity = 16) | ||||||
|  | 	{ | ||||||
|  | 		this.capacity = capacity; | ||||||
|  | 		elementSize = Unsafe.SizeOf<T>(); | ||||||
|  | 		Array = (T*) NativeMemory.Alloc((nuint) (capacity * elementSize)); | ||||||
|  | 		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 RemoveLastElement() | ||||||
|  | 	{ | ||||||
|  | 		count -= 1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public bool TryPop(out T element) | ||||||
|  | 	{ | ||||||
|  | 		if (count > 0) | ||||||
|  | 		{ | ||||||
|  | 			element = Array[count - 1]; | ||||||
|  | 			count -= 1; | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		element = default; | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void Clear() | ||||||
|  | 	{ | ||||||
|  | 		count = 0; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private void ResizeTo(int size) | ||||||
|  | 	{ | ||||||
|  | 		capacity = size; | ||||||
|  | 		Array = (T*) NativeMemory.Realloc((void*) Array, (nuint) (elementSize * capacity)); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void CopyTo(NativeArray<T> other) | ||||||
|  | 	{ | ||||||
|  | 		if (count >= other.capacity) | ||||||
|  | 		{ | ||||||
|  | 			other.ResizeTo(Count); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		NativeMemory.Copy( | ||||||
|  | 			(void*) Array, | ||||||
|  | 			(void*) other.Array, | ||||||
|  | 			(nuint) (elementSize * Count) | ||||||
|  | 		); | ||||||
|  | 
 | ||||||
|  | 		other.count = count; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	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); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,128 @@ | ||||||
|  | using System; | ||||||
|  | using System.Runtime.CompilerServices; | ||||||
|  | using System.Runtime.InteropServices; | ||||||
|  | 
 | ||||||
|  | namespace MoonTools.ECS.Collections; | ||||||
|  | 
 | ||||||
|  | internal unsafe class NativeArray : IDisposable | ||||||
|  | { | ||||||
|  | 	public nint Elements; | ||||||
|  | 	public int Count; | ||||||
|  | 
 | ||||||
|  | 	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]; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	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; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	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); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -43,7 +43,7 @@ namespace MoonTools.ECS | ||||||
| 			return Lookup<TComponent>().Any(); | 			return Lookup<TComponent>().Any(); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public ref readonly TComponent Get<TComponent>(int entityID) where TComponent : unmanaged | 		public ref TComponent Get<TComponent>(int entityID) where TComponent : unmanaged | ||||||
| 		{ | 		{ | ||||||
| 			return ref Lookup<TComponent>().Get(entityID); | 			return ref Lookup<TComponent>().Get(entityID); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -41,7 +41,7 @@ namespace MoonTools.ECS | ||||||
| 			return count > 0; | 			return count > 0; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public ref readonly TComponent Get(int entityID) | 		public ref TComponent Get(int entityID) | ||||||
| 		{ | 		{ | ||||||
| 			return ref components[entityIDToStorageIndex[entityID]]; | 			return ref components[entityIDToStorageIndex[entityID]]; | ||||||
| 		} | 		} | ||||||
|  | @ -151,6 +151,7 @@ namespace MoonTools.ECS | ||||||
| 		{ | 		{ | ||||||
| 			return entityIDToStorageIndex.Keys; | 			return entityIDToStorageIndex.Keys; | ||||||
| 		} | 		} | ||||||
|  | #endif | ||||||
| 
 | 
 | ||||||
| 		protected virtual void Dispose(bool disposing) | 		protected virtual void Dispose(bool disposing) | ||||||
| 		{ | 		{ | ||||||
|  | @ -177,6 +178,5 @@ namespace MoonTools.ECS | ||||||
| 			Dispose(disposing: true); | 			Dispose(disposing: true); | ||||||
| 			GC.SuppressFinalize(this); | 			GC.SuppressFinalize(this); | ||||||
| 		} | 		} | ||||||
| #endif |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -42,7 +42,7 @@ namespace MoonTools.ECS | ||||||
| 			return Lookup<TMessage>().First(); | 			return Lookup<TMessage>().First(); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public ReverseSpanEnumerator<TMessage> WithEntity<TMessage>(int entityID) where TMessage : unmanaged | 		public Span<TMessage>.Enumerator WithEntity<TMessage>(int entityID) where TMessage : unmanaged | ||||||
| 		{ | 		{ | ||||||
| 			return Lookup<TMessage>().WithEntity(entityID); | 			return Lookup<TMessage>().WithEntity(entityID); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -63,7 +63,7 @@ namespace MoonTools.ECS | ||||||
| 			return messages[0]; | 			return messages[0]; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public ReverseSpanEnumerator<TMessage> WithEntity(int entityID) | 		public Span<TMessage>.Enumerator WithEntity(int entityID) | ||||||
| 		{ | 		{ | ||||||
| 			if (entityToMessages.TryGetValue(entityID, out var messages)) | 			if (entityToMessages.TryGetValue(entityID, out var messages)) | ||||||
| 			{ | 			{ | ||||||
|  | @ -71,7 +71,7 @@ namespace MoonTools.ECS | ||||||
| 			} | 			} | ||||||
| 			else | 			else | ||||||
| 			{ | 			{ | ||||||
| 				return ReverseSpanEnumerator<TMessage>.Empty; | 				return Span<TMessage>.Empty.GetEnumerator(); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,111 +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; |  | ||||||
| 		private int elementSize; |  | ||||||
| 
 |  | ||||||
| 		public int Count => count; |  | ||||||
| 
 |  | ||||||
| 		public Span<T>.Enumerator GetEnumerator() => new Span<T>(Array, count).GetEnumerator(); |  | ||||||
| 
 |  | ||||||
| 		private bool disposed; |  | ||||||
| 
 |  | ||||||
| 		public NativeArray(int capacity = 16) |  | ||||||
| 		{ |  | ||||||
| 			this.capacity = capacity; |  | ||||||
| 			elementSize = Unsafe.SizeOf<T>(); |  | ||||||
| 			Array = (T*) NativeMemory.Alloc((nuint) (capacity * elementSize)); |  | ||||||
| 			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 RemoveLastElement() |  | ||||||
| 		{ |  | ||||||
| 			count -= 1; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public bool TryPop(out T element) |  | ||||||
| 		{ |  | ||||||
| 			if (count > 0) |  | ||||||
| 			{ |  | ||||||
| 				element = Array[count - 1]; |  | ||||||
| 				count -= 1; |  | ||||||
| 				return true; |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			element = default; |  | ||||||
| 			return false; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Clear() |  | ||||||
| 		{ |  | ||||||
| 			count = 0; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		private void ResizeTo(int size) |  | ||||||
| 		{ |  | ||||||
| 			capacity = size; |  | ||||||
| 			Array = (T*) NativeMemory.Realloc((void*) Array, (nuint) (elementSize * capacity)); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void CopyTo(NativeArray<T> other) |  | ||||||
| 		{ |  | ||||||
| 			if (count >= other.capacity) |  | ||||||
| 			{ |  | ||||||
| 				other.ResizeTo(Count); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			NativeMemory.Copy( |  | ||||||
| 				(void*) Array, |  | ||||||
| 				(void*) other.Array, |  | ||||||
| 				(nuint) (elementSize * Count) |  | ||||||
| 			); |  | ||||||
| 
 |  | ||||||
| 			other.count = count; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		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); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | @ -7,7 +7,7 @@ internal class Archetype | ||||||
| { | { | ||||||
| 	public World World; | 	public World World; | ||||||
| 	public ArchetypeSignature Signature; | 	public ArchetypeSignature Signature; | ||||||
| 	public Column[] ComponentColumns; | 	public NativeArray[] ComponentColumns; | ||||||
| 	public NativeArray<Id> RowToEntity = new NativeArray<Id>(); | 	public NativeArray<Id> RowToEntity = new NativeArray<Id>(); | ||||||
| 
 | 
 | ||||||
| 	public Dictionary<Id, int> ComponentToColumnIndex = | 	public Dictionary<Id, int> ComponentToColumnIndex = | ||||||
|  | @ -20,7 +20,7 @@ internal class Archetype | ||||||
| 	{ | 	{ | ||||||
| 		World = world; | 		World = world; | ||||||
| 		Signature = signature; | 		Signature = signature; | ||||||
| 		ComponentColumns = new Column[signature.Count]; | 		ComponentColumns = new NativeArray[signature.Count]; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public void ClearAll() | 	public void ClearAll() | ||||||
|  |  | ||||||
|  | @ -1,4 +1,3 @@ | ||||||
| namespace MoonTools.ECS.Rev2 | namespace MoonTools.ECS.Rev2; | ||||||
| { | 
 | ||||||
| 	internal readonly record struct ArchetypeEdge(Archetype Add, Archetype Remove); | internal readonly record struct ArchetypeEdge(Archetype Add, Archetype Remove); | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -1,92 +1,91 @@ | ||||||
| using System; | using System; | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| 
 | 
 | ||||||
| namespace MoonTools.ECS.Rev2 | namespace MoonTools.ECS.Rev2; | ||||||
|  | 
 | ||||||
|  | internal class ArchetypeSignature : IEquatable<ArchetypeSignature> | ||||||
| { | { | ||||||
| 	internal class ArchetypeSignature : IEquatable<ArchetypeSignature> | 	public static ArchetypeSignature Empty = new ArchetypeSignature(0); | ||||||
|  | 
 | ||||||
|  | 	List<uint> Ids; | ||||||
|  | 
 | ||||||
|  | 	public int Count => Ids.Count; | ||||||
|  | 
 | ||||||
|  | 	public Id this[int i] => new Id(Ids[i]); | ||||||
|  | 
 | ||||||
|  | 	public ArchetypeSignature() | ||||||
| 	{ | 	{ | ||||||
| 		public static ArchetypeSignature Empty = new ArchetypeSignature(0); | 		Ids = new List<uint>(); | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 		List<uint> Ids; | 	public ArchetypeSignature(int capacity) | ||||||
|  | 	{ | ||||||
|  | 		Ids = new List<uint>(capacity); | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 		public int Count => Ids.Count; | 	// Maintains sorted order | ||||||
|  | 	public void Insert(Id componentId) | ||||||
|  | 	{ | ||||||
|  | 		var index = Ids.BinarySearch(componentId.Value); | ||||||
| 
 | 
 | ||||||
| 		public Id this[int i] => new Id(Ids[i]); | 		if (index < 0) | ||||||
| 
 |  | ||||||
| 		public ArchetypeSignature() |  | ||||||
| 		{ | 		{ | ||||||
| 			Ids = new List<uint>(); | 			Ids.Insert(~index, componentId.Value); | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public ArchetypeSignature(int capacity) |  | ||||||
| 		{ |  | ||||||
| 			Ids = new List<uint>(capacity); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Maintains sorted order |  | ||||||
| 		public void Insert(Id componentId) |  | ||||||
| 		{ |  | ||||||
| 			var index = Ids.BinarySearch(componentId.Value); |  | ||||||
| 
 |  | ||||||
| 			if (index < 0) |  | ||||||
| 			{ |  | ||||||
| 				Ids.Insert(~index, componentId.Value); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Remove(Id componentId) |  | ||||||
| 		{ |  | ||||||
| 			var index = Ids.BinarySearch(componentId.Value); |  | ||||||
| 
 |  | ||||||
| 			if (index >= 0) |  | ||||||
| 			{ |  | ||||||
| 				Ids.RemoveAt(index); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void CopyTo(ArchetypeSignature other) |  | ||||||
| 		{ |  | ||||||
| 			other.Ids.AddRange(Ids); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		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; |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	public void Remove(Id componentId) | ||||||
|  | 	{ | ||||||
|  | 		var index = Ids.BinarySearch(componentId.Value); | ||||||
|  | 
 | ||||||
|  | 		if (index >= 0) | ||||||
|  | 		{ | ||||||
|  | 			Ids.RemoveAt(index); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void CopyTo(ArchetypeSignature other) | ||||||
|  | 	{ | ||||||
|  | 		other.Ids.AddRange(Ids); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	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; | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,129 +0,0 @@ | ||||||
| using System; |  | ||||||
| using System.Runtime.CompilerServices; |  | ||||||
| using System.Runtime.InteropServices; |  | ||||||
| 
 |  | ||||||
| namespace MoonTools.ECS.Rev2 |  | ||||||
| { |  | ||||||
| 	internal unsafe class Column : IDisposable |  | ||||||
| 	{ |  | ||||||
| 		public nint Elements; |  | ||||||
| 		public int Count; |  | ||||||
| 
 |  | ||||||
| 		private int Capacity; |  | ||||||
| 		public readonly int ElementSize; |  | ||||||
| 
 |  | ||||||
| 		private bool IsDisposed; |  | ||||||
| 
 |  | ||||||
| 		public Column(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 Resize() |  | ||||||
| 		{ |  | ||||||
| 			Capacity *= 2; |  | ||||||
| 			Elements = (nint) NativeMemory.Realloc((void*) Elements, (nuint) (ElementSize * Capacity)); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public 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, Column 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(Column other) |  | ||||||
| 		{ |  | ||||||
| 			if (Count >= other.Capacity) |  | ||||||
| 			{ |  | ||||||
| 				other.ResizeTo(Count); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			NativeMemory.Copy( |  | ||||||
| 				(void*) Elements, |  | ||||||
| 				(void*) other.Elements, |  | ||||||
| 				(nuint) (ElementSize * Count) |  | ||||||
| 			); |  | ||||||
| 
 |  | ||||||
| 			other.Count = Count; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected virtual void Dispose(bool disposing) |  | ||||||
| 		{ |  | ||||||
| 			if (!IsDisposed) |  | ||||||
| 			{ |  | ||||||
| 				NativeMemory.Free((void*) Elements); |  | ||||||
| 				IsDisposed = true; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		~Column() |  | ||||||
| 		{ |  | ||||||
| 		    // 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,204 +1,203 @@ | ||||||
| using System; | using System; | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| 
 | 
 | ||||||
| namespace MoonTools.ECS.Rev2 | namespace MoonTools.ECS.Rev2; | ||||||
|  | 
 | ||||||
|  | // TODO: do we want to get fancy with queries beyond Include and Exclude? | ||||||
|  | public class Filter | ||||||
| { | { | ||||||
| 	// TODO: do we want to get fancy with queries beyond Include and Exclude? | 	private Archetype EmptyArchetype; | ||||||
| 	public class Filter | 	private HashSet<Id> Included; | ||||||
|  | 	private HashSet<Id> Excluded; | ||||||
|  | 
 | ||||||
|  | 	public EntityEnumerator Entities => new EntityEnumerator(this); | ||||||
|  | 	internal ArchetypeEnumerator Archetypes => new ArchetypeEnumerator(this); | ||||||
|  | 	public RandomEntityEnumerator EntitiesInRandomOrder => new RandomEntityEnumerator(this); | ||||||
|  | 
 | ||||||
|  | 	public bool Empty | ||||||
| 	{ | 	{ | ||||||
| 		private Archetype EmptyArchetype; | 		get | ||||||
| 		private HashSet<Id> Included; |  | ||||||
| 		private HashSet<Id> Excluded; |  | ||||||
| 
 |  | ||||||
| 		public EntityEnumerator Entities => new EntityEnumerator(this); |  | ||||||
| 		internal ArchetypeEnumerator Archetypes => new ArchetypeEnumerator(this); |  | ||||||
| 		public RandomEntityEnumerator EntitiesInRandomOrder => new RandomEntityEnumerator(this); |  | ||||||
| 
 |  | ||||||
| 		public bool Empty |  | ||||||
| 		{ | 		{ | ||||||
| 			get | 			var empty = true; | ||||||
| 			{ |  | ||||||
| 				var empty = true; |  | ||||||
| 
 | 
 | ||||||
| 				foreach (var archetype in Archetypes) | 			foreach (var archetype in Archetypes) | ||||||
|  | 			{ | ||||||
|  | 				if (archetype.Count > 0) | ||||||
| 				{ | 				{ | ||||||
| 					if (archetype.Count > 0) | 					return false; | ||||||
| 					{ |  | ||||||
| 						return false; |  | ||||||
| 					} |  | ||||||
| 				} | 				} | ||||||
| 
 |  | ||||||
| 				return empty; |  | ||||||
| 			} | 			} | ||||||
|  | 
 | ||||||
|  | 			return empty; | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 		public int Count | 	public int Count | ||||||
| 		{ | 	{ | ||||||
| 			get | 		get | ||||||
| 			{ |  | ||||||
| 				var count = 0; |  | ||||||
| 
 |  | ||||||
| 				foreach (var archetype in Archetypes) |  | ||||||
| 				{ |  | ||||||
| 					count += archetype.Count; |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				return count; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public Id RandomEntity |  | ||||||
| 		{ |  | ||||||
| 			get |  | ||||||
| 			{ |  | ||||||
| 				var randomIndex = RandomManager.Next(Count); |  | ||||||
| 				return NthEntity(randomIndex); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// WARNING: this WILL crash if the index is out of range! |  | ||||||
| 		public Id NthEntity(int index) |  | ||||||
| 		{ | 		{ | ||||||
| 			var count = 0; | 			var count = 0; | ||||||
| 
 | 
 | ||||||
| 			foreach (var archetype in Archetypes) | 			foreach (var archetype in Archetypes) | ||||||
| 			{ | 			{ | ||||||
| 				count += archetype.Count; | 				count += archetype.Count; | ||||||
| 				if (index < count) |  | ||||||
| 				{ |  | ||||||
| 					return archetype.RowToEntity[index]; |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				index -= count; |  | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			throw new InvalidOperationException("Filter index out of range!"); | 			return count; | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void DestroyAllEntities() |  | ||||||
| 		{ |  | ||||||
| 			foreach (var archetype in Archetypes) |  | ||||||
| 			{ |  | ||||||
| 				archetype.ClearAll(); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		internal Filter(Archetype emptyArchetype, HashSet<Id> included, HashSet<Id> 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); |  | ||||||
| 
 |  | ||||||
| 				while (ArchetypeSearchQueue.TryDequeue(out var current)) |  | ||||||
| 				{ |  | ||||||
| 					// exclude the empty archetype |  | ||||||
| 					var satisfiesFilter = filter.Included.Count != 0; |  | ||||||
| 
 |  | ||||||
| 					foreach (var componentId in filter.Included) |  | ||||||
| 					{ |  | ||||||
| 						if (!current.ComponentToColumnIndex.ContainsKey(componentId)) |  | ||||||
| 						{ |  | ||||||
| 							satisfiesFilter = false; |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					foreach (var componentId in filter.Excluded) |  | ||||||
| 					{ |  | ||||||
| 						if (current.ComponentToColumnIndex.ContainsKey(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 Id CurrentEntity; |  | ||||||
| 
 |  | ||||||
| 			public EntityEnumerator GetEnumerator() => this; |  | ||||||
| 
 |  | ||||||
| 			// TODO: pool this |  | ||||||
| 			Queue<Id> EntityQueue = new Queue<Id>(); |  | ||||||
| 
 |  | ||||||
| 			internal EntityEnumerator(Filter filter) |  | ||||||
| 			{ |  | ||||||
| 				var archetypeEnumerator = new ArchetypeEnumerator(filter); |  | ||||||
| 
 |  | ||||||
| 				foreach (var archetype in archetypeEnumerator) |  | ||||||
| 				{ |  | ||||||
| 					foreach (var entity in archetype.RowToEntity) |  | ||||||
| 					{ |  | ||||||
| 						EntityQueue.Enqueue(entity); |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			public bool MoveNext() |  | ||||||
| 			{ |  | ||||||
| 				return EntityQueue.TryDequeue(out CurrentEntity); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			public Id 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 Id Current => Filter.NthEntity(LinearCongruentialEnumerator.Current); |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	public Id RandomEntity | ||||||
|  | 	{ | ||||||
|  | 		get | ||||||
|  | 		{ | ||||||
|  | 			var randomIndex = RandomManager.Next(Count); | ||||||
|  | 			return NthEntity(randomIndex); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// WARNING: this WILL crash if the index is out of range! | ||||||
|  | 	public Id NthEntity(int index) | ||||||
|  | 	{ | ||||||
|  | 		var count = 0; | ||||||
|  | 
 | ||||||
|  | 		foreach (var archetype in Archetypes) | ||||||
|  | 		{ | ||||||
|  | 			count += archetype.Count; | ||||||
|  | 			if (index < count) | ||||||
|  | 			{ | ||||||
|  | 				return archetype.RowToEntity[index]; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			index -= count; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		throw new InvalidOperationException("Filter index out of range!"); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void DestroyAllEntities() | ||||||
|  | 	{ | ||||||
|  | 		foreach (var archetype in Archetypes) | ||||||
|  | 		{ | ||||||
|  | 			archetype.ClearAll(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	internal Filter(Archetype emptyArchetype, HashSet<Id> included, HashSet<Id> 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); | ||||||
|  | 
 | ||||||
|  | 			while (ArchetypeSearchQueue.TryDequeue(out var current)) | ||||||
|  | 			{ | ||||||
|  | 				// exclude the empty archetype | ||||||
|  | 				var satisfiesFilter = filter.Included.Count != 0; | ||||||
|  | 
 | ||||||
|  | 				foreach (var componentId in filter.Included) | ||||||
|  | 				{ | ||||||
|  | 					if (!current.ComponentToColumnIndex.ContainsKey(componentId)) | ||||||
|  | 					{ | ||||||
|  | 						satisfiesFilter = false; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				foreach (var componentId in filter.Excluded) | ||||||
|  | 				{ | ||||||
|  | 					if (current.ComponentToColumnIndex.ContainsKey(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 Id CurrentEntity; | ||||||
|  | 
 | ||||||
|  | 		public EntityEnumerator GetEnumerator() => this; | ||||||
|  | 
 | ||||||
|  | 		// TODO: pool this | ||||||
|  | 		Queue<Id> EntityQueue = new Queue<Id>(); | ||||||
|  | 
 | ||||||
|  | 		internal EntityEnumerator(Filter filter) | ||||||
|  | 		{ | ||||||
|  | 			var archetypeEnumerator = new ArchetypeEnumerator(filter); | ||||||
|  | 
 | ||||||
|  | 			foreach (var archetype in archetypeEnumerator) | ||||||
|  | 			{ | ||||||
|  | 				foreach (var entity in archetype.RowToEntity) | ||||||
|  | 				{ | ||||||
|  | 					EntityQueue.Enqueue(entity); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public bool MoveNext() | ||||||
|  | 		{ | ||||||
|  | 			return EntityQueue.TryDequeue(out CurrentEntity); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public Id 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 Id Current => Filter.NthEntity(LinearCongruentialEnumerator.Current); | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,44 +1,43 @@ | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| 
 | 
 | ||||||
| namespace MoonTools.ECS.Rev2 | namespace MoonTools.ECS.Rev2; | ||||||
|  | 
 | ||||||
|  | public ref struct FilterBuilder | ||||||
| { | { | ||||||
| 	public ref struct FilterBuilder | 	World World; | ||||||
|  | 	HashSet<Id> Included; | ||||||
|  | 	HashSet<Id> Excluded; | ||||||
|  | 
 | ||||||
|  | 	internal FilterBuilder(World world) | ||||||
| 	{ | 	{ | ||||||
| 		World World; | 		World = world; | ||||||
| 		HashSet<Id> Included; | 		Included = new HashSet<Id>(); | ||||||
| 		HashSet<Id> Excluded; | 		Excluded = new HashSet<Id>(); | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 		internal FilterBuilder(World world) | 	private FilterBuilder(World world, HashSet<Id> included, HashSet<Id> excluded) | ||||||
| 		{ | 	{ | ||||||
| 			World = world; | 		World = world; | ||||||
| 			Included = new HashSet<Id>(); | 		Included = included; | ||||||
| 			Excluded = new HashSet<Id>(); | 		Excluded = excluded; | ||||||
| 		} | 	} | ||||||
| 
 | 
 | ||||||
| 		private FilterBuilder(World world, HashSet<Id> included, HashSet<Id> excluded) | 	public FilterBuilder Include<T>() where T : unmanaged | ||||||
| 		{ | 	{ | ||||||
| 			World = world; | 		World.GetTypeId<T>(); | ||||||
| 			Included = included; | 		Included.Add(World.TypeToId[typeof(T)]); | ||||||
| 			Excluded = excluded; | 		return new FilterBuilder(World, Included, Excluded); | ||||||
| 		} | 	} | ||||||
| 
 | 
 | ||||||
| 		public FilterBuilder Include<T>() where T : unmanaged | 	public FilterBuilder Exclude<T>() where T : unmanaged | ||||||
| 		{ | 	{ | ||||||
| 			World.TryRegisterTypeId<T>(); | 		World.GetTypeId<T>(); | ||||||
| 			Included.Add(World.TypeToId[typeof(T)]); | 		Excluded.Add(World.TypeToId[typeof(T)]); | ||||||
| 			return new FilterBuilder(World, Included, Excluded); | 		return new FilterBuilder(World, Included, Excluded); | ||||||
| 		} | 	} | ||||||
| 
 | 
 | ||||||
| 		public FilterBuilder Exclude<T>() where T : unmanaged | 	public Filter Build() | ||||||
| 		{ | 	{ | ||||||
| 			World.TryRegisterTypeId<T>(); | 		return new Filter(World.EmptyArchetype, Included, Excluded); | ||||||
| 			Excluded.Add(World.TypeToId[typeof(T)]); |  | ||||||
| 			return new FilterBuilder(World, Included, Excluded); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public Filter Build() |  | ||||||
| 		{ |  | ||||||
| 			return new Filter(World.EmptyArchetype, Included, Excluded); |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,6 +0,0 @@ | ||||||
| namespace MoonTools.ECS.Rev2; |  | ||||||
| 
 |  | ||||||
| public interface IHasId |  | ||||||
| { |  | ||||||
| 	public int Id { get; init; } |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,63 @@ | ||||||
|  | using System; | ||||||
|  | using MoonTools.ECS.Collections; | ||||||
|  | 
 | ||||||
|  | namespace MoonTools.ECS.Rev2; | ||||||
|  | 
 | ||||||
|  | public class MessageStorage : IDisposable | ||||||
|  | { | ||||||
|  | 	private NativeArray Messages; | ||||||
|  | 
 | ||||||
|  | 	private bool IsDisposed; | ||||||
|  | 
 | ||||||
|  | 	public MessageStorage(int elementSize) | ||||||
|  | 	{ | ||||||
|  | 		Messages = new NativeArray(elementSize); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void Add<T>(in T message) where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		Messages.Append(message); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public bool Some() | ||||||
|  | 	{ | ||||||
|  | 		return Messages.Count > 0; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public ReadOnlySpan<T> All<T>() where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		return Messages.ToSpan<T>(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public T First<T>() where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		return Messages.Get<T>(0); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void Clear() | ||||||
|  | 	{ | ||||||
|  | 		Messages.Count = 0; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	protected virtual void Dispose(bool disposing) | ||||||
|  | 	{ | ||||||
|  | 		if (!IsDisposed) | ||||||
|  | 		{ | ||||||
|  | 			Messages.Dispose(); | ||||||
|  | 			IsDisposed = true; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// ~MessageStorage() | ||||||
|  | 	// { | ||||||
|  | 	// 	// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||||
|  | 	// 	Dispose(disposing: false); | ||||||
|  | 	// } | ||||||
|  | 
 | ||||||
|  | 	public void Dispose() | ||||||
|  | 	{ | ||||||
|  | 		// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||||
|  | 		Dispose(disposing: true); | ||||||
|  | 		GC.SuppressFinalize(this); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -1,4 +1,3 @@ | ||||||
| namespace MoonTools.ECS.Rev2 | namespace MoonTools.ECS.Rev2; | ||||||
| { | 
 | ||||||
| 	internal readonly record struct Record(Archetype Archetype, int Row); | internal readonly record struct Record(Archetype Archetype, int Row); | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -8,8 +8,8 @@ namespace MoonTools.ECS.Rev2; | ||||||
| // TODO: implement this entire class with NativeMemory equivalents, can just memcpy for snapshots | // TODO: implement this entire class with NativeMemory equivalents, can just memcpy for snapshots | ||||||
| internal class RelationStorage | internal class RelationStorage | ||||||
| { | { | ||||||
| 	internal Column relations; | 	internal NativeArray relations; | ||||||
| 	internal Column relationDatas; | 	internal NativeArray relationDatas; | ||||||
| 	internal Dictionary<(Id, Id), int> indices = new Dictionary<(Id, Id), int>(16); | 	internal Dictionary<(Id, Id), int> indices = new Dictionary<(Id, Id), int>(16); | ||||||
| 	internal Dictionary<Id, IndexableSet<Id>> outRelations = new Dictionary<Id, IndexableSet<Id>>(16); | 	internal Dictionary<Id, IndexableSet<Id>> outRelations = new Dictionary<Id, IndexableSet<Id>>(16); | ||||||
| 	internal Dictionary<Id, IndexableSet<Id>> inRelations = new Dictionary<Id, IndexableSet<Id>>(16); | 	internal Dictionary<Id, IndexableSet<Id>> inRelations = new Dictionary<Id, IndexableSet<Id>>(16); | ||||||
|  | @ -19,8 +19,8 @@ internal class RelationStorage | ||||||
| 
 | 
 | ||||||
| 	public RelationStorage(int relationDataSize) | 	public RelationStorage(int relationDataSize) | ||||||
| 	{ | 	{ | ||||||
| 		relations = new Column(Unsafe.SizeOf<(Id, Id)>()); | 		relations = new NativeArray(Unsafe.SizeOf<(Id, Id)>()); | ||||||
| 		relationDatas = new Column(relationDataSize); | 		relationDatas = new NativeArray(relationDataSize); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	public ReverseSpanEnumerator<(Id, Id)> All() | 	public ReverseSpanEnumerator<(Id, Id)> All() | ||||||
|  |  | ||||||
|  | @ -139,20 +139,20 @@ public class Snapshot | ||||||
| 
 | 
 | ||||||
| 	private class ArchetypeSnapshot | 	private class ArchetypeSnapshot | ||||||
| 	{ | 	{ | ||||||
| 		private readonly Column[] ComponentColumns; | 		private readonly NativeArray[] ComponentColumns; | ||||||
| 		private readonly NativeArray<Id> RowToEntity; | 		private readonly NativeArray<Id> RowToEntity; | ||||||
| 
 | 
 | ||||||
| 		public int Count => RowToEntity.Count; | 		public int Count => RowToEntity.Count; | ||||||
| 
 | 
 | ||||||
| 		public ArchetypeSnapshot(ArchetypeSignature signature) | 		public ArchetypeSnapshot(ArchetypeSignature signature) | ||||||
| 		{ | 		{ | ||||||
| 			ComponentColumns = new Column[signature.Count]; | 			ComponentColumns = new NativeArray[signature.Count]; | ||||||
| 			RowToEntity = new NativeArray<Id>(); | 			RowToEntity = new NativeArray<Id>(); | ||||||
| 
 | 
 | ||||||
| 			for (int i = 0; i < signature.Count; i += 1) | 			for (int i = 0; i < signature.Count; i += 1) | ||||||
| 			{ | 			{ | ||||||
| 				var componentId = signature[i]; | 				var componentId = signature[i]; | ||||||
| 				ComponentColumns[i] = new Column(World.ElementSizes[componentId]); | 				ComponentColumns[i] = new NativeArray(World.ElementSizes[componentId]); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -180,13 +180,13 @@ public class Snapshot | ||||||
| 
 | 
 | ||||||
| 	private class RelationSnapshot | 	private class RelationSnapshot | ||||||
| 	{ | 	{ | ||||||
| 		private Column Relations; | 		private NativeArray Relations; | ||||||
| 		private Column RelationDatas; | 		private NativeArray RelationDatas; | ||||||
| 
 | 
 | ||||||
| 		public RelationSnapshot(int elementSize) | 		public RelationSnapshot(int elementSize) | ||||||
| 		{ | 		{ | ||||||
| 			Relations = new Column(Unsafe.SizeOf<(Id, Id)>()); | 			Relations = new NativeArray(Unsafe.SizeOf<(Id, Id)>()); | ||||||
| 			RelationDatas = new Column(elementSize); | 			RelationDatas = new NativeArray(elementSize); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public void Take(RelationStorage relationStorage) | 		public void Take(RelationStorage relationStorage) | ||||||
|  |  | ||||||
|  | @ -1,478 +1,522 @@ | ||||||
| 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.Rev2 | namespace MoonTools.ECS.Rev2; | ||||||
|  | 
 | ||||||
|  | public class World : IDisposable | ||||||
| { | { | ||||||
| 	public class World : IDisposable | 	// Get ComponentId from a Type | ||||||
|  | 	internal static Dictionary<Type, Id> TypeToId = new Dictionary<Type, Id>(); | ||||||
|  | 	// Get element size from a ComponentId | ||||||
|  | 	internal static Dictionary<Id, int> ElementSizes = new Dictionary<Id, int>(); | ||||||
|  | 
 | ||||||
|  | 	// Lookup from ArchetypeSignature to Archetype | ||||||
|  | 	internal Dictionary<ArchetypeSignature, Archetype> ArchetypeIndex = new Dictionary<ArchetypeSignature, Archetype>(); | ||||||
|  | 
 | ||||||
|  | 	// Going from EntityId to Archetype and storage row | ||||||
|  | 	internal Dictionary<Id, Record> EntityIndex = new Dictionary<Id, Record>(); | ||||||
|  | 
 | ||||||
|  | 	// Going from ComponentId to Archetype list | ||||||
|  | 	Dictionary<Id, List<Archetype>> ComponentIndex = new Dictionary<Id, List<Archetype>>(); | ||||||
|  | 
 | ||||||
|  | 	// Relation Storages | ||||||
|  | 	internal Dictionary<Id, RelationStorage> RelationIndex = | ||||||
|  | 		new Dictionary<Id, RelationStorage>(); | ||||||
|  | 
 | ||||||
|  | 	// Entity Relation Tracking | ||||||
|  | 	internal Dictionary<Id, IndexableSet<Id>> EntityRelationIndex = | ||||||
|  | 		new Dictionary<Id, IndexableSet<Id>>(); | ||||||
|  | 
 | ||||||
|  | 	// Message Storages | ||||||
|  | 	private Dictionary<Id, MessageStorage> MessageIndex = | ||||||
|  | 		new Dictionary<Id, MessageStorage>(); | ||||||
|  | 
 | ||||||
|  | 	// ID Management | ||||||
|  | 	// FIXME: Entity and Type Ids should be separated | ||||||
|  | 	internal IdAssigner IdAssigner = new IdAssigner(); | ||||||
|  | 
 | ||||||
|  | 	internal readonly Archetype EmptyArchetype; | ||||||
|  | 
 | ||||||
|  | 	public FilterBuilder FilterBuilder => new FilterBuilder(this); | ||||||
|  | 
 | ||||||
|  | 	public delegate void RefAction<T1, T2>(ref T1 arg1, ref T2 arg2); | ||||||
|  | 
 | ||||||
|  | 	private bool IsDisposed; | ||||||
|  | 
 | ||||||
|  | 	public World() | ||||||
| 	{ | 	{ | ||||||
| 		// Get ComponentId from a Type | 		// Create the Empty Archetype | ||||||
| 		internal static Dictionary<Type, Id> TypeToId = new Dictionary<Type, Id>(); | 		EmptyArchetype = CreateArchetype(ArchetypeSignature.Empty); | ||||||
| 		// Get element size from a ComponentId | 	} | ||||||
| 		internal static Dictionary<Id, int> ElementSizes = new Dictionary<Id, int>(); |  | ||||||
| 
 | 
 | ||||||
| 		// Lookup from ArchetypeSignature to Archetype | 	internal Archetype CreateArchetype(ArchetypeSignature signature) | ||||||
| 		internal Dictionary<ArchetypeSignature, Archetype> ArchetypeIndex = new Dictionary<ArchetypeSignature, Archetype>(); | 	{ | ||||||
|  | 		var archetype = new Archetype(this, signature); | ||||||
| 
 | 
 | ||||||
| 		// Going from EntityId to Archetype and storage row | 		ArchetypeIndex.Add(signature, archetype); | ||||||
| 		internal Dictionary<Id, Record> EntityIndex = new Dictionary<Id, Record>(); |  | ||||||
| 
 | 
 | ||||||
| 		// Going from ComponentId to Archetype list | 		for (int i = 0; i < signature.Count; i += 1) | ||||||
| 		Dictionary<Id, List<Archetype>> ComponentIndex = new Dictionary<Id, List<Archetype>>(); |  | ||||||
| 
 |  | ||||||
| 		// Relation Storages |  | ||||||
| 		internal Dictionary<Id, RelationStorage> RelationIndex = |  | ||||||
| 			new Dictionary<Id, RelationStorage>(); |  | ||||||
| 
 |  | ||||||
| 		// Entity Relation Tracking |  | ||||||
| 		internal Dictionary<Id, IndexableSet<Id>> EntityRelationIndex = |  | ||||||
| 			new Dictionary<Id, IndexableSet<Id>>(); |  | ||||||
| 
 |  | ||||||
| 		// ID Management |  | ||||||
| 		// FIXME: Entity and Type Ids should be separated |  | ||||||
| 		internal IdAssigner IdAssigner = new IdAssigner(); |  | ||||||
| 
 |  | ||||||
| 		internal readonly Archetype EmptyArchetype; |  | ||||||
| 
 |  | ||||||
| 		public FilterBuilder FilterBuilder => new FilterBuilder(this); |  | ||||||
| 
 |  | ||||||
| 		public delegate void RefAction<T1, T2>(ref T1 arg1, ref T2 arg2); |  | ||||||
| 
 |  | ||||||
| 		private bool IsDisposed; |  | ||||||
| 
 |  | ||||||
| 		public World() |  | ||||||
| 		{ | 		{ | ||||||
| 			// Create the Empty Archetype | 			var componentId = signature[i]; | ||||||
| 			EmptyArchetype = CreateArchetype(ArchetypeSignature.Empty); | 			ComponentIndex[componentId].Add(archetype); | ||||||
|  | 			archetype.ComponentToColumnIndex.Add(componentId, i); | ||||||
|  | 			archetype.ComponentColumns[i] = new NativeArray(ElementSizes[componentId]); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		internal Archetype CreateArchetype(ArchetypeSignature signature) | 		return archetype; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public Id CreateEntity() | ||||||
|  | 	{ | ||||||
|  | 		var entityId = IdAssigner.Assign(); | ||||||
|  | 		EntityIndex.Add(entityId, new Record(EmptyArchetype, EmptyArchetype.Count)); | ||||||
|  | 		EmptyArchetype.RowToEntity.Add(entityId); | ||||||
|  | 
 | ||||||
|  | 		if (!EntityRelationIndex.ContainsKey(entityId)) | ||||||
| 		{ | 		{ | ||||||
| 			var archetype = new Archetype(this, signature); | 			EntityRelationIndex.Add(entityId, new IndexableSet<Id>()); | ||||||
| 
 |  | ||||||
| 			ArchetypeIndex.Add(signature, archetype); |  | ||||||
| 
 |  | ||||||
| 			for (int i = 0; i < signature.Count; i += 1) |  | ||||||
| 			{ |  | ||||||
| 				var componentId = signature[i]; |  | ||||||
| 				ComponentIndex[componentId].Add(archetype); |  | ||||||
| 				archetype.ComponentToColumnIndex.Add(componentId, i); |  | ||||||
| 				archetype.ComponentColumns[i] = new Column(ElementSizes[componentId]); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			return archetype; |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public Id CreateEntity() | 		return entityId; | ||||||
| 		{ | 	} | ||||||
| 			var entityId = IdAssigner.Assign(); |  | ||||||
| 			EntityIndex.Add(entityId, new Record(EmptyArchetype, EmptyArchetype.Count)); |  | ||||||
| 			EmptyArchetype.RowToEntity.Add(entityId); |  | ||||||
| 
 | 
 | ||||||
| 			if (!EntityRelationIndex.ContainsKey(entityId)) | 	internal Id GetTypeId<T>() where T : unmanaged | ||||||
| 			{ | 	{ | ||||||
| 				EntityRelationIndex.Add(entityId, new IndexableSet<Id>()); | 		if (TypeToId.ContainsKey(typeof(T))) | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			return entityId; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		internal void TryRegisterTypeId<T>() where T : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			if (!TypeToId.ContainsKey(typeof(T))) |  | ||||||
| 			{ |  | ||||||
| 				var typeId = IdAssigner.Assign(); |  | ||||||
| 				TypeToId.Add(typeof(T), typeId); |  | ||||||
| 				ElementSizes.Add(typeId, Unsafe.SizeOf<T>()); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// FIXME: would be much more efficient to do all this at load time somehow |  | ||||||
| 		private void RegisterComponent(Id typeId) |  | ||||||
| 		{ |  | ||||||
| 			ComponentIndex.Add(typeId, new List<Archetype>()); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		private void TryRegisterComponentId<T>() where T : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			TryRegisterTypeId<T>(); |  | ||||||
| 			var typeId = TypeToId[typeof(T)]; |  | ||||||
| 			if (!ComponentIndex.ContainsKey(typeId)) |  | ||||||
| 			{ |  | ||||||
| 				RegisterComponent(typeId); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		[MethodImpl(MethodImplOptions.AggressiveInlining)] |  | ||||||
| 		private Id GetComponentId<T>() where T : unmanaged |  | ||||||
| 		{ | 		{ | ||||||
| 			return TypeToId[typeof(T)]; | 			return TypeToId[typeof(T)]; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		private void RegisterRelationType(Id typeId) | 		var typeId = IdAssigner.Assign(); | ||||||
|  | 		TypeToId.Add(typeof(T), typeId); | ||||||
|  | 		ElementSizes.Add(typeId, Unsafe.SizeOf<T>()); | ||||||
|  | 		return typeId; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private void TryRegisterComponentId<T>() where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		var typeId = GetTypeId<T>(); | ||||||
|  | 		if (!ComponentIndex.ContainsKey(typeId)) | ||||||
| 		{ | 		{ | ||||||
| 			RelationIndex.Add(typeId, new RelationStorage(ElementSizes[typeId])); | 			ComponentIndex.Add(typeId, new List<Archetype>()); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  | 	private Id GetComponentId<T>() where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		return TypeToId[typeof(T)]; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private void RegisterRelationType(Id typeId) | ||||||
|  | 	{ | ||||||
|  | 		RelationIndex.Add(typeId, new RelationStorage(ElementSizes[typeId])); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private void TryRegisterRelationType<T>() where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		var typeId = GetTypeId<T>(); | ||||||
|  | 		if (!RelationIndex.ContainsKey(typeId)) | ||||||
|  | 		{ | ||||||
|  | 			RegisterRelationType(typeId); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||
|  | 	private RelationStorage GetRelationStorage<T>() where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		return RelationIndex[TypeToId[typeof(T)]]; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Messages | ||||||
|  | 
 | ||||||
|  | 	private Id GetMessageTypeId<T>() where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		var typeId = GetTypeId<T>(); | ||||||
|  | 
 | ||||||
|  | 		if (!MessageIndex.ContainsKey(typeId)) | ||||||
|  | 		{ | ||||||
|  | 			MessageIndex.Add(typeId, new MessageStorage(Unsafe.SizeOf<T>())); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		private void TryRegisterRelationType<T>() where T : unmanaged | 		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>(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void ClearMessages() | ||||||
|  | 	{ | ||||||
|  | 		foreach (var (_, messageStorage) in MessageIndex) | ||||||
| 		{ | 		{ | ||||||
| 			TryRegisterTypeId<T>(); | 			messageStorage.Clear(); | ||||||
| 			var typeId = TypeToId[typeof(T)]; |  | ||||||
| 			if (!RelationIndex.ContainsKey(typeId)) |  | ||||||
| 			{ |  | ||||||
| 				RegisterRelationType(typeId); |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 		[MethodImpl(MethodImplOptions.AggressiveInlining)] | 	// Components | ||||||
| 		private RelationStorage GetRelationStorage<T>() where T : unmanaged | 	public bool Has<T>(Id entityId) where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		var componentId = GetComponentId<T>(); | ||||||
|  | 		var record = EntityIndex[entityId]; | ||||||
|  | 		return record.Archetype.ComponentToColumnIndex.ContainsKey(componentId); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// will throw if non-existent | ||||||
|  | 	public unsafe ref T Get<T>(Id entityId) where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		var componentId = GetComponentId<T>(); | ||||||
|  | 
 | ||||||
|  | 		var record = EntityIndex[entityId]; | ||||||
|  | 		var columnIndex = record.Archetype.ComponentToColumnIndex[componentId]; | ||||||
|  | 		var column = record.Archetype.ComponentColumns[columnIndex]; | ||||||
|  | 
 | ||||||
|  | 		return ref ((T*) column.Elements)[record.Row]; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public unsafe void Set<T>(in Id entityId, in T component) where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		TryRegisterComponentId<T>(); | ||||||
|  | 		var componentId = GetComponentId<T>(); | ||||||
|  | 
 | ||||||
|  | 		if (Has<T>(entityId)) | ||||||
| 		{ | 		{ | ||||||
| 			return RelationIndex[TypeToId[typeof(T)]]; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public bool Has<T>(Id entityId) where T : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			var componentId = GetComponentId<T>(); |  | ||||||
| 			var record = EntityIndex[entityId]; |  | ||||||
| 			return record.Archetype.ComponentToColumnIndex.ContainsKey(componentId); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// will throw if non-existent |  | ||||||
| 		public unsafe ref T Get<T>(Id entityId) where T : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			var componentId = GetComponentId<T>(); |  | ||||||
| 
 |  | ||||||
| 			var record = EntityIndex[entityId]; | 			var record = EntityIndex[entityId]; | ||||||
| 			var columnIndex = record.Archetype.ComponentToColumnIndex[componentId]; | 			var columnIndex = record.Archetype.ComponentToColumnIndex[componentId]; | ||||||
| 			var column = record.Archetype.ComponentColumns[columnIndex]; | 			var column = record.Archetype.ComponentColumns[columnIndex]; | ||||||
| 
 | 
 | ||||||
| 			return ref ((T*) column.Elements)[record.Row]; | 			((T*) column.Elements)[record.Row] = component; | ||||||
| 		} | 		} | ||||||
| 
 | 		else | ||||||
| 		public unsafe void Set<T>(in Id entityId, in T component) where T : unmanaged |  | ||||||
| 		{ | 		{ | ||||||
| 			TryRegisterComponentId<T>(); | 			Add(entityId, component); | ||||||
| 			var componentId = GetComponentId<T>(); |  | ||||||
| 
 |  | ||||||
| 			if (Has<T>(entityId)) |  | ||||||
| 			{ |  | ||||||
| 				var record = EntityIndex[entityId]; |  | ||||||
| 				var columnIndex = record.Archetype.ComponentToColumnIndex[componentId]; |  | ||||||
| 				var column = record.Archetype.ComponentColumns[columnIndex]; |  | ||||||
| 
 |  | ||||||
| 				((T*) column.Elements)[record.Row] = component; |  | ||||||
| 			} |  | ||||||
| 			else |  | ||||||
| 			{ |  | ||||||
| 				Add(entityId, component); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		private void Add<T>(Id entityId, in T component) where T : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			Archetype? nextArchetype; |  | ||||||
| 
 |  | ||||||
| 			var componentId = GetComponentId<T>(); |  | ||||||
| 
 |  | ||||||
| 			// move the entity to the new archetype |  | ||||||
| 			var record = EntityIndex[entityId]; |  | ||||||
| 			var archetype = record.Archetype; |  | ||||||
| 
 |  | ||||||
| 			if (archetype.Edges.TryGetValue(componentId, out var edge)) |  | ||||||
| 			{ |  | ||||||
| 				nextArchetype = edge.Add; |  | ||||||
| 			} |  | ||||||
| 			else |  | ||||||
| 			{ |  | ||||||
| 				// FIXME: pool the signatures |  | ||||||
| 				var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1); |  | ||||||
| 				archetype.Signature.CopyTo(nextSignature); |  | ||||||
| 				nextSignature.Insert(componentId); |  | ||||||
| 
 |  | ||||||
| 				if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype)) |  | ||||||
| 				{ |  | ||||||
| 					nextArchetype = CreateArchetype(nextSignature); |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				var newEdge = new ArchetypeEdge(nextArchetype, archetype); |  | ||||||
| 				archetype.Edges.Add(componentId, newEdge); |  | ||||||
| 				nextArchetype.Edges.Add(componentId, newEdge); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			MoveEntityToHigherArchetype(entityId, record.Row, archetype, nextArchetype); |  | ||||||
| 
 |  | ||||||
| 			// add the new component to the new archetype |  | ||||||
| 			var columnIndex = nextArchetype.ComponentToColumnIndex[componentId]; |  | ||||||
| 			var column = nextArchetype.ComponentColumns[columnIndex]; |  | ||||||
| 			column.Append(component); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Remove<T>(Id entityId) where T : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			Archetype? nextArchetype; |  | ||||||
| 
 |  | ||||||
| 			var componentId = GetComponentId<T>(); |  | ||||||
| 
 |  | ||||||
| 			var (archetype, row) = EntityIndex[entityId]; |  | ||||||
| 
 |  | ||||||
| 			if (archetype.Edges.TryGetValue(componentId, out var edge)) |  | ||||||
| 			{ |  | ||||||
| 				nextArchetype = edge.Remove; |  | ||||||
| 			} |  | ||||||
| 			else |  | ||||||
| 			{ |  | ||||||
| 				// FIXME: pool the signatures |  | ||||||
| 				var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1); |  | ||||||
| 				archetype.Signature.CopyTo(nextSignature); |  | ||||||
| 				nextSignature.Remove(componentId); |  | ||||||
| 
 |  | ||||||
| 				if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype)) |  | ||||||
| 				{ |  | ||||||
| 					nextArchetype = CreateArchetype(nextSignature); |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				var newEdge = new ArchetypeEdge(nextArchetype, archetype); |  | ||||||
| 				archetype.Edges.Add(componentId, newEdge); |  | ||||||
| 				nextArchetype.Edges.Add(componentId, newEdge); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			MoveEntityToLowerArchetype(entityId, row, archetype, nextArchetype, componentId); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Relate<T>(in Id entityA, in Id entityB, in T relation) where T : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			TryRegisterRelationType<T>(); |  | ||||||
| 			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 Id entityA, in Id entityB) where T : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			var relationStorage = GetRelationStorage<T>(); |  | ||||||
| 			relationStorage.Remove(entityA, entityB); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public bool Related<T>(in Id entityA, in Id entityB) where T : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			var relationStorage = GetRelationStorage<T>(); |  | ||||||
| 			return relationStorage.Has(entityA, entityB); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public T GetRelationData<T>(in Id entityA, in Id entityB) where T : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			var relationStorage = GetRelationStorage<T>(); |  | ||||||
| 			return relationStorage.Get<T>(entityA, entityB); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public ReverseSpanEnumerator<(Id, Id)> Relations<T>() where T : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			var relationStorage = GetRelationStorage<T>(); |  | ||||||
| 			return relationStorage.All(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public ReverseSpanEnumerator<Id> OutRelations<T>(Id entity) where T : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			var relationStorage = GetRelationStorage<T>(); |  | ||||||
| 			return relationStorage.OutRelations(entity); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public ReverseSpanEnumerator<Id> InRelations<T>(Id entity) where T : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			var relationStorage = GetRelationStorage<T>(); |  | ||||||
| 			return relationStorage.InRelations(entity); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		private bool Has(Id entityId, Id typeId) |  | ||||||
| 		{ |  | ||||||
| 			var record = EntityIndex[entityId]; |  | ||||||
| 			return record.Archetype.ComponentToColumnIndex.ContainsKey(typeId); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// used as a fast path by Archetype.ClearAll and snapshot restore |  | ||||||
| 		internal void FreeEntity(Id entityId) |  | ||||||
| 		{ |  | ||||||
| 			EntityIndex.Remove(entityId); |  | ||||||
| 			IdAssigner.Unassign(entityId); |  | ||||||
| 
 |  | ||||||
| 			foreach (var relationTypeIndex in EntityRelationIndex[entityId]) |  | ||||||
| 			{ |  | ||||||
| 				var relationStorage = RelationIndex[relationTypeIndex]; |  | ||||||
| 				relationStorage.RemoveEntity(entityId); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			EntityRelationIndex[entityId].Clear(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public void Destroy(Id entityId) |  | ||||||
| 		{ |  | ||||||
| 			var record = EntityIndex[entityId]; |  | ||||||
| 			var archetype = record.Archetype; |  | ||||||
| 			var row = record.Row; |  | ||||||
| 
 |  | ||||||
| 			for (int i = 0; i < archetype.Signature.Count; i += 1) |  | ||||||
| 			{ |  | ||||||
| 				archetype.ComponentColumns[i].Delete(row); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			if (row != archetype.Count - 1) |  | ||||||
| 			{ |  | ||||||
| 				// move last row entity to open spot |  | ||||||
| 				var lastRowEntity = archetype.RowToEntity[archetype.Count - 1]; |  | ||||||
| 				archetype.RowToEntity[row] = lastRowEntity; |  | ||||||
| 				EntityIndex[lastRowEntity] = new Record(archetype, row); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			archetype.RowToEntity.RemoveLastElement(); |  | ||||||
| 			EntityIndex.Remove(entityId); |  | ||||||
| 			IdAssigner.Unassign(entityId); |  | ||||||
| 
 |  | ||||||
| 			foreach (var relationTypeIndex in EntityRelationIndex[entityId]) |  | ||||||
| 			{ |  | ||||||
| 				var relationStorage = RelationIndex[relationTypeIndex]; |  | ||||||
| 				relationStorage.RemoveEntity(entityId); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			EntityRelationIndex[entityId].Clear(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		private void MoveEntityToHigherArchetype(Id entityId, int row, Archetype from, Archetype to) |  | ||||||
| 		{ |  | ||||||
| 			for (int i = 0; i < from.Signature.Count; i += 1) |  | ||||||
| 			{ |  | ||||||
| 				var componentId = from.Signature[i]; |  | ||||||
| 				var destinationColumnIndex = to.ComponentToColumnIndex[componentId]; |  | ||||||
| 
 |  | ||||||
| 				// copy all components to higher archetype |  | ||||||
| 				from.ComponentColumns[i].CopyElementToEnd(row, to.ComponentColumns[destinationColumnIndex]); |  | ||||||
| 
 |  | ||||||
| 				// delete row on from archetype |  | ||||||
| 				from.ComponentColumns[i].Delete(row); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			if (row != from.Count - 1) |  | ||||||
| 			{ |  | ||||||
| 				// move last row entity to open spot |  | ||||||
| 				var lastRowEntity = from.RowToEntity[from.Count - 1]; |  | ||||||
| 				from.RowToEntity[row] = lastRowEntity; |  | ||||||
| 				EntityIndex[lastRowEntity] = new Record(from, row); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			from.RowToEntity.RemoveLastElement(); |  | ||||||
| 
 |  | ||||||
| 			// update row to entity lookup on to archetype |  | ||||||
| 			EntityIndex[entityId] = new Record(to, to.Count); |  | ||||||
| 			to.RowToEntity.Add(entityId); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		private void MoveEntityToLowerArchetype(Id entityId, int row, Archetype from, Archetype to, Id removed) |  | ||||||
| 		{ |  | ||||||
| 			for (int i = 0; i < from.Signature.Count; i += 1) |  | ||||||
| 			{ |  | ||||||
| 				var componentId = from.Signature[i]; |  | ||||||
| 
 |  | ||||||
| 				// delete the row |  | ||||||
| 				from.ComponentColumns[i].Delete(row); |  | ||||||
| 
 |  | ||||||
| 				// if this isn't the removed component, copy to the lower archetype |  | ||||||
| 				if (componentId != removed) |  | ||||||
| 				{ |  | ||||||
| 					var destinationColumnIndex = to.ComponentToColumnIndex[componentId]; |  | ||||||
| 					from.ComponentColumns[i].CopyElementToEnd(row, to.ComponentColumns[destinationColumnIndex]); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			if (row != from.Count - 1) |  | ||||||
| 			{ |  | ||||||
| 				// update row to entity lookup on from archetype |  | ||||||
| 				var lastRowEntity = from.RowToEntity[from.Count - 1]; |  | ||||||
| 				from.RowToEntity[row] = lastRowEntity; |  | ||||||
| 				EntityIndex[lastRowEntity] = new Record(from, row); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			from.RowToEntity.RemoveLastElement(); |  | ||||||
| 
 |  | ||||||
| 			// update row to entity lookup on to archetype |  | ||||||
| 			EntityIndex[entityId] = new Record(to, to.Count); |  | ||||||
| 			to.RowToEntity.Add(entityId); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public unsafe void ForEachEntity<T, T1, T2>(Filter filter, |  | ||||||
| 			T rowForEachContainer) where T : IForEach<T1, T2> where T1 : unmanaged where T2 : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			foreach (var archetype in filter.Archetypes) |  | ||||||
| 			{ |  | ||||||
| 				var componentIdOne = archetype.Signature[0]; |  | ||||||
| 				var columnIndexOne = archetype.ComponentToColumnIndex[componentIdOne]; |  | ||||||
| 				var columnOneElements = archetype.ComponentColumns[columnIndexOne].Elements; |  | ||||||
| 
 |  | ||||||
| 				var componentIdTwo = archetype.Signature[1]; |  | ||||||
| 				var columnIndexTwo = archetype.ComponentToColumnIndex[componentIdTwo]; |  | ||||||
| 				var columnTwoElements = archetype.ComponentColumns[columnIndexTwo].Elements; |  | ||||||
| 
 |  | ||||||
| 				for (int i = archetype.Count - 1; i >= 0; i -= 1) |  | ||||||
| 				{ |  | ||||||
| 					rowForEachContainer.Update(ref ((T1*) columnOneElements)[i], ref ((T2*) columnTwoElements)[i]); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		public unsafe void ForEachEntity<T1, T2>(Filter filter, RefAction<T1, T2> rowAction) where T1 : unmanaged where T2 : unmanaged |  | ||||||
| 		{ |  | ||||||
| 			foreach (var archetype in filter.Archetypes) |  | ||||||
| 			{ |  | ||||||
| 				var componentIdOne = archetype.Signature[0]; |  | ||||||
| 				var columnIndexOne = archetype.ComponentToColumnIndex[componentIdOne]; |  | ||||||
| 				var columnOneElements = archetype.ComponentColumns[columnIndexOne].Elements; |  | ||||||
| 
 |  | ||||||
| 				var componentIdTwo = archetype.Signature[1]; |  | ||||||
| 				var columnIndexTwo = archetype.ComponentToColumnIndex[componentIdTwo]; |  | ||||||
| 				var columnTwoElements = archetype.ComponentColumns[columnIndexTwo].Elements; |  | ||||||
| 
 |  | ||||||
| 				for (int i = archetype.Count - 1; i >= 0; i -= 1) |  | ||||||
| 				{ |  | ||||||
| 					rowAction(ref ((T1*) columnOneElements)[i], ref ((T2*) columnTwoElements)[i]); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		protected virtual void Dispose(bool disposing) |  | ||||||
| 		{ |  | ||||||
| 			if (!IsDisposed) |  | ||||||
| 			{ |  | ||||||
| 				if (disposing) |  | ||||||
| 				{ |  | ||||||
| 					// dispose managed state (managed objects) |  | ||||||
| 					foreach (var archetype in ArchetypeIndex.Values) |  | ||||||
| 					{ |  | ||||||
| 						for (var i = 0; i < archetype.Signature.Count; i += 1) |  | ||||||
| 						{ |  | ||||||
| 							archetype.ComponentColumns[i].Dispose(); |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				// TODO: free unmanaged resources (unmanaged objects) and override finalizer |  | ||||||
| 				// TODO: set large fields to null |  | ||||||
| 				IsDisposed = true; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources |  | ||||||
| 		// ~World() |  | ||||||
| 		// { |  | ||||||
| 		//     // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method |  | ||||||
| 		//     Dispose(disposing: false); |  | ||||||
| 		// } |  | ||||||
| 
 |  | ||||||
| 		public void Dispose() |  | ||||||
| 		{ |  | ||||||
| 			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method |  | ||||||
| 			Dispose(disposing: true); |  | ||||||
| 			GC.SuppressFinalize(this); |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	private void Add<T>(Id entityId, in T component) where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		Archetype? nextArchetype; | ||||||
|  | 
 | ||||||
|  | 		var componentId = GetComponentId<T>(); | ||||||
|  | 
 | ||||||
|  | 		// move the entity to the new archetype | ||||||
|  | 		var record = EntityIndex[entityId]; | ||||||
|  | 		var archetype = record.Archetype; | ||||||
|  | 
 | ||||||
|  | 		if (archetype.Edges.TryGetValue(componentId, out var edge)) | ||||||
|  | 		{ | ||||||
|  | 			nextArchetype = edge.Add; | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			// FIXME: pool the signatures | ||||||
|  | 			var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1); | ||||||
|  | 			archetype.Signature.CopyTo(nextSignature); | ||||||
|  | 			nextSignature.Insert(componentId); | ||||||
|  | 
 | ||||||
|  | 			if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype)) | ||||||
|  | 			{ | ||||||
|  | 				nextArchetype = CreateArchetype(nextSignature); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			var newEdge = new ArchetypeEdge(nextArchetype, archetype); | ||||||
|  | 			archetype.Edges.Add(componentId, newEdge); | ||||||
|  | 			nextArchetype.Edges.Add(componentId, newEdge); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		MoveEntityToHigherArchetype(entityId, record.Row, archetype, nextArchetype); | ||||||
|  | 
 | ||||||
|  | 		// add the new component to the new archetype | ||||||
|  | 		var columnIndex = nextArchetype.ComponentToColumnIndex[componentId]; | ||||||
|  | 		var column = nextArchetype.ComponentColumns[columnIndex]; | ||||||
|  | 		column.Append(component); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void Remove<T>(Id entityId) where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		Archetype? nextArchetype; | ||||||
|  | 
 | ||||||
|  | 		var componentId = GetComponentId<T>(); | ||||||
|  | 
 | ||||||
|  | 		var (archetype, row) = EntityIndex[entityId]; | ||||||
|  | 
 | ||||||
|  | 		if (archetype.Edges.TryGetValue(componentId, out var edge)) | ||||||
|  | 		{ | ||||||
|  | 			nextArchetype = edge.Remove; | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			// FIXME: pool the signatures | ||||||
|  | 			var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1); | ||||||
|  | 			archetype.Signature.CopyTo(nextSignature); | ||||||
|  | 			nextSignature.Remove(componentId); | ||||||
|  | 
 | ||||||
|  | 			if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype)) | ||||||
|  | 			{ | ||||||
|  | 				nextArchetype = CreateArchetype(nextSignature); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			var newEdge = new ArchetypeEdge(nextArchetype, archetype); | ||||||
|  | 			archetype.Edges.Add(componentId, newEdge); | ||||||
|  | 			nextArchetype.Edges.Add(componentId, newEdge); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		MoveEntityToLowerArchetype(entityId, row, archetype, nextArchetype, componentId); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void Relate<T>(in Id entityA, in Id entityB, in T relation) where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		TryRegisterRelationType<T>(); | ||||||
|  | 		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 Id entityA, in Id entityB) where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 		relationStorage.Remove(entityA, entityB); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public bool Related<T>(in Id entityA, in Id entityB) where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 		return relationStorage.Has(entityA, entityB); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public T GetRelationData<T>(in Id entityA, in Id entityB) where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 		return relationStorage.Get<T>(entityA, entityB); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public ReverseSpanEnumerator<(Id, Id)> Relations<T>() where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 		return relationStorage.All(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public ReverseSpanEnumerator<Id> OutRelations<T>(Id entity) where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 		return relationStorage.OutRelations(entity); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public ReverseSpanEnumerator<Id> InRelations<T>(Id entity) where T : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		var relationStorage = GetRelationStorage<T>(); | ||||||
|  | 		return relationStorage.InRelations(entity); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private bool Has(Id entityId, Id typeId) | ||||||
|  | 	{ | ||||||
|  | 		var record = EntityIndex[entityId]; | ||||||
|  | 		return record.Archetype.ComponentToColumnIndex.ContainsKey(typeId); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// used as a fast path by Archetype.ClearAll and snapshot restore | ||||||
|  | 	internal void FreeEntity(Id entityId) | ||||||
|  | 	{ | ||||||
|  | 		EntityIndex.Remove(entityId); | ||||||
|  | 		IdAssigner.Unassign(entityId); | ||||||
|  | 
 | ||||||
|  | 		foreach (var relationTypeIndex in EntityRelationIndex[entityId]) | ||||||
|  | 		{ | ||||||
|  | 			var relationStorage = RelationIndex[relationTypeIndex]; | ||||||
|  | 			relationStorage.RemoveEntity(entityId); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		EntityRelationIndex[entityId].Clear(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public void Destroy(Id entityId) | ||||||
|  | 	{ | ||||||
|  | 		var record = EntityIndex[entityId]; | ||||||
|  | 		var archetype = record.Archetype; | ||||||
|  | 		var row = record.Row; | ||||||
|  | 
 | ||||||
|  | 		for (int i = 0; i < archetype.Signature.Count; i += 1) | ||||||
|  | 		{ | ||||||
|  | 			archetype.ComponentColumns[i].Delete(row); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (row != archetype.Count - 1) | ||||||
|  | 		{ | ||||||
|  | 			// move last row entity to open spot | ||||||
|  | 			var lastRowEntity = archetype.RowToEntity[archetype.Count - 1]; | ||||||
|  | 			archetype.RowToEntity[row] = lastRowEntity; | ||||||
|  | 			EntityIndex[lastRowEntity] = new Record(archetype, row); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		archetype.RowToEntity.RemoveLastElement(); | ||||||
|  | 		EntityIndex.Remove(entityId); | ||||||
|  | 		IdAssigner.Unassign(entityId); | ||||||
|  | 
 | ||||||
|  | 		foreach (var relationTypeIndex in EntityRelationIndex[entityId]) | ||||||
|  | 		{ | ||||||
|  | 			var relationStorage = RelationIndex[relationTypeIndex]; | ||||||
|  | 			relationStorage.RemoveEntity(entityId); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		EntityRelationIndex[entityId].Clear(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private void MoveEntityToHigherArchetype(Id entityId, int row, Archetype from, Archetype to) | ||||||
|  | 	{ | ||||||
|  | 		for (int i = 0; i < from.Signature.Count; i += 1) | ||||||
|  | 		{ | ||||||
|  | 			var componentId = from.Signature[i]; | ||||||
|  | 			var destinationColumnIndex = to.ComponentToColumnIndex[componentId]; | ||||||
|  | 
 | ||||||
|  | 			// copy all components to higher archetype | ||||||
|  | 			from.ComponentColumns[i].CopyElementToEnd(row, to.ComponentColumns[destinationColumnIndex]); | ||||||
|  | 
 | ||||||
|  | 			// delete row on from archetype | ||||||
|  | 			from.ComponentColumns[i].Delete(row); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (row != from.Count - 1) | ||||||
|  | 		{ | ||||||
|  | 			// move last row entity to open spot | ||||||
|  | 			var lastRowEntity = from.RowToEntity[from.Count - 1]; | ||||||
|  | 			from.RowToEntity[row] = lastRowEntity; | ||||||
|  | 			EntityIndex[lastRowEntity] = new Record(from, row); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		from.RowToEntity.RemoveLastElement(); | ||||||
|  | 
 | ||||||
|  | 		// update row to entity lookup on to archetype | ||||||
|  | 		EntityIndex[entityId] = new Record(to, to.Count); | ||||||
|  | 		to.RowToEntity.Add(entityId); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private void MoveEntityToLowerArchetype(Id entityId, int row, Archetype from, Archetype to, Id removed) | ||||||
|  | 	{ | ||||||
|  | 		for (int i = 0; i < from.Signature.Count; i += 1) | ||||||
|  | 		{ | ||||||
|  | 			var componentId = from.Signature[i]; | ||||||
|  | 
 | ||||||
|  | 			// delete the row | ||||||
|  | 			from.ComponentColumns[i].Delete(row); | ||||||
|  | 
 | ||||||
|  | 			// if this isn't the removed component, copy to the lower archetype | ||||||
|  | 			if (componentId != removed) | ||||||
|  | 			{ | ||||||
|  | 				var destinationColumnIndex = to.ComponentToColumnIndex[componentId]; | ||||||
|  | 				from.ComponentColumns[i].CopyElementToEnd(row, to.ComponentColumns[destinationColumnIndex]); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (row != from.Count - 1) | ||||||
|  | 		{ | ||||||
|  | 			// update row to entity lookup on from archetype | ||||||
|  | 			var lastRowEntity = from.RowToEntity[from.Count - 1]; | ||||||
|  | 			from.RowToEntity[row] = lastRowEntity; | ||||||
|  | 			EntityIndex[lastRowEntity] = new Record(from, row); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		from.RowToEntity.RemoveLastElement(); | ||||||
|  | 
 | ||||||
|  | 		// update row to entity lookup on to archetype | ||||||
|  | 		EntityIndex[entityId] = new Record(to, to.Count); | ||||||
|  | 		to.RowToEntity.Add(entityId); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public unsafe void ForEachEntity<T, T1, T2>(Filter filter, | ||||||
|  | 		T rowForEachContainer) where T : IForEach<T1, T2> where T1 : unmanaged where T2 : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		foreach (var archetype in filter.Archetypes) | ||||||
|  | 		{ | ||||||
|  | 			var componentIdOne = archetype.Signature[0]; | ||||||
|  | 			var columnIndexOne = archetype.ComponentToColumnIndex[componentIdOne]; | ||||||
|  | 			var columnOneElements = archetype.ComponentColumns[columnIndexOne].Elements; | ||||||
|  | 
 | ||||||
|  | 			var componentIdTwo = archetype.Signature[1]; | ||||||
|  | 			var columnIndexTwo = archetype.ComponentToColumnIndex[componentIdTwo]; | ||||||
|  | 			var columnTwoElements = archetype.ComponentColumns[columnIndexTwo].Elements; | ||||||
|  | 
 | ||||||
|  | 			for (int i = archetype.Count - 1; i >= 0; i -= 1) | ||||||
|  | 			{ | ||||||
|  | 				rowForEachContainer.Update(ref ((T1*) columnOneElements)[i], ref ((T2*) columnTwoElements)[i]); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public unsafe void ForEachEntity<T1, T2>(Filter filter, RefAction<T1, T2> rowAction) where T1 : unmanaged where T2 : unmanaged | ||||||
|  | 	{ | ||||||
|  | 		foreach (var archetype in filter.Archetypes) | ||||||
|  | 		{ | ||||||
|  | 			var componentIdOne = archetype.Signature[0]; | ||||||
|  | 			var columnIndexOne = archetype.ComponentToColumnIndex[componentIdOne]; | ||||||
|  | 			var columnOneElements = archetype.ComponentColumns[columnIndexOne].Elements; | ||||||
|  | 
 | ||||||
|  | 			var componentIdTwo = archetype.Signature[1]; | ||||||
|  | 			var columnIndexTwo = archetype.ComponentToColumnIndex[componentIdTwo]; | ||||||
|  | 			var columnTwoElements = archetype.ComponentColumns[columnIndexTwo].Elements; | ||||||
|  | 
 | ||||||
|  | 			for (int i = archetype.Count - 1; i >= 0; i -= 1) | ||||||
|  | 			{ | ||||||
|  | 				rowAction(ref ((T1*) columnOneElements)[i], ref ((T2*) columnTwoElements)[i]); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	protected virtual void Dispose(bool disposing) | ||||||
|  | 	{ | ||||||
|  | 		if (!IsDisposed) | ||||||
|  | 		{ | ||||||
|  | 			if (disposing) | ||||||
|  | 			{ | ||||||
|  | 				// dispose managed state (managed objects) | ||||||
|  | 				foreach (var archetype in ArchetypeIndex.Values) | ||||||
|  | 				{ | ||||||
|  | 					for (var i = 0; i < archetype.Signature.Count; i += 1) | ||||||
|  | 					{ | ||||||
|  | 						archetype.ComponentColumns[i].Dispose(); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// TODO: free unmanaged resources (unmanaged objects) and override finalizer | ||||||
|  | 			// TODO: set large fields to null | ||||||
|  | 			IsDisposed = true; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources | ||||||
|  | 	// ~World() | ||||||
|  | 	// { | ||||||
|  | 	//     // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||||
|  | 	//     Dispose(disposing: false); | ||||||
|  | 	// } | ||||||
|  | 
 | ||||||
|  | 	public void Dispose() | ||||||
|  | 	{ | ||||||
|  | 		// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||||
|  | 		Dispose(disposing: true); | ||||||
|  | 		GC.SuppressFinalize(this); | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -25,7 +25,7 @@ namespace MoonTools.ECS | ||||||
| 			return MessageDepot.Some<TMessage>(); | 			return MessageDepot.Some<TMessage>(); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		protected ReverseSpanEnumerator<TMessage> ReadMessagesWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged | 		protected Span<TMessage>.Enumerator ReadMessagesWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged | ||||||
| 		{ | 		{ | ||||||
| 			return MessageDepot.WithEntity<TMessage>(entity.ID); | 			return MessageDepot.WithEntity<TMessage>(entity.ID); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -36,6 +36,11 @@ namespace MoonTools.ECS | ||||||
| 			return EntityStorage.Tag(entity); | 			return EntityStorage.Tag(entity); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		public ref TComponent Get<TComponent>(in Entity entity) where TComponent : unmanaged | ||||||
|  | 		{ | ||||||
|  | 			return ref ComponentDepot.Get<TComponent>(entity.ID); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		public void Set<TComponent>(Entity entity, in TComponent component) where TComponent : unmanaged | 		public void Set<TComponent>(Entity entity, in TComponent component) where TComponent : unmanaged | ||||||
| 		{ | 		{ | ||||||
| #if DEBUG | #if DEBUG | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue