messages + rearranging
							parent
							
								
									9032eff699
								
							
						
					
					
						commit
						3d2261f739
					
				|  | @ -60,12 +60,27 @@ namespace MoonTools.ECS.Collections | |||
| 				return false; | ||||
| 			} | ||||
| 
 | ||||
| 			/* | ||||
| 			var lastElement = array[Count - 1]; | ||||
| 			var index = indices[element]; | ||||
| 			array[index] = lastElement; | ||||
| 			indices[lastElement] = index; | ||||
| 			count -= 1; | ||||
| 			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; | ||||
| 		} | ||||
|  | @ -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(); | ||||
| 		} | ||||
| 
 | ||||
| 		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); | ||||
| 		} | ||||
|  |  | |||
|  | @ -41,7 +41,7 @@ namespace MoonTools.ECS | |||
| 			return count > 0; | ||||
| 		} | ||||
| 
 | ||||
| 		public ref readonly TComponent Get(int entityID) | ||||
| 		public ref TComponent Get(int entityID) | ||||
| 		{ | ||||
| 			return ref components[entityIDToStorageIndex[entityID]]; | ||||
| 		} | ||||
|  | @ -151,6 +151,7 @@ namespace MoonTools.ECS | |||
| 		{ | ||||
| 			return entityIDToStorageIndex.Keys; | ||||
| 		} | ||||
| #endif | ||||
| 
 | ||||
| 		protected virtual void Dispose(bool disposing) | ||||
| 		{ | ||||
|  | @ -177,6 +178,5 @@ namespace MoonTools.ECS | |||
| 			Dispose(disposing: true); | ||||
| 			GC.SuppressFinalize(this); | ||||
| 		} | ||||
| #endif | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -42,7 +42,7 @@ namespace MoonTools.ECS | |||
| 			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); | ||||
| 		} | ||||
|  |  | |||
|  | @ -63,7 +63,7 @@ namespace MoonTools.ECS | |||
| 			return messages[0]; | ||||
| 		} | ||||
| 
 | ||||
| 		public ReverseSpanEnumerator<TMessage> WithEntity(int entityID) | ||||
| 		public Span<TMessage>.Enumerator WithEntity(int entityID) | ||||
| 		{ | ||||
| 			if (entityToMessages.TryGetValue(entityID, out var messages)) | ||||
| 			{ | ||||
|  | @ -71,7 +71,7 @@ namespace MoonTools.ECS | |||
| 			} | ||||
| 			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 ArchetypeSignature Signature; | ||||
| 	public Column[] ComponentColumns; | ||||
| 	public NativeArray[] ComponentColumns; | ||||
| 	public NativeArray<Id> RowToEntity = new NativeArray<Id>(); | ||||
| 
 | ||||
| 	public Dictionary<Id, int> ComponentToColumnIndex = | ||||
|  | @ -20,7 +20,7 @@ internal class Archetype | |||
| 	{ | ||||
| 		World = world; | ||||
| 		Signature = signature; | ||||
| 		ComponentColumns = new Column[signature.Count]; | ||||
| 		ComponentColumns = new NativeArray[signature.Count]; | ||||
| 	} | ||||
| 
 | ||||
| 	public void ClearAll() | ||||
|  |  | |||
|  | @ -1,4 +1,3 @@ | |||
| namespace MoonTools.ECS.Rev2 | ||||
| { | ||||
| 	internal readonly record struct ArchetypeEdge(Archetype Add, Archetype Remove); | ||||
| } | ||||
| namespace MoonTools.ECS.Rev2; | ||||
| 
 | ||||
| internal readonly record struct ArchetypeEdge(Archetype Add, Archetype Remove); | ||||
|  |  | |||
|  | @ -1,92 +1,91 @@ | |||
| using System; | ||||
| 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]); | ||||
| 
 | ||||
| 		public ArchetypeSignature() | ||||
| 		if (index < 0) | ||||
| 		{ | ||||
| 			Ids = new List<uint>(); | ||||
| 		} | ||||
| 
 | ||||
| 		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; | ||||
| 			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; | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -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.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? | ||||
| 	public class Filter | ||||
| 	private Archetype EmptyArchetype; | ||||
| 	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; | ||||
| 		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 | ||||
| 		{ | ||||
| 			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 | ||||
| 		{ | ||||
| 			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) | ||||
| 	public int Count | ||||
| 	{ | ||||
| 		get | ||||
| 		{ | ||||
| 			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); | ||||
| 			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; | ||||
| 
 | ||||
| 		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; | ||||
| 
 | ||||
| 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; | ||||
| 		HashSet<Id> Included; | ||||
| 		HashSet<Id> Excluded; | ||||
| 		World = world; | ||||
| 		Included = new HashSet<Id>(); | ||||
| 		Excluded = new HashSet<Id>(); | ||||
| 	} | ||||
| 
 | ||||
| 		internal FilterBuilder(World world) | ||||
| 		{ | ||||
| 			World = world; | ||||
| 			Included = new HashSet<Id>(); | ||||
| 			Excluded = new HashSet<Id>(); | ||||
| 		} | ||||
| 	private FilterBuilder(World world, HashSet<Id> included, HashSet<Id> excluded) | ||||
| 	{ | ||||
| 		World = world; | ||||
| 		Included = included; | ||||
| 		Excluded = excluded; | ||||
| 	} | ||||
| 
 | ||||
| 		private FilterBuilder(World world, HashSet<Id> included, HashSet<Id> excluded) | ||||
| 		{ | ||||
| 			World = world; | ||||
| 			Included = included; | ||||
| 			Excluded = excluded; | ||||
| 		} | ||||
| 	public FilterBuilder Include<T>() where T : unmanaged | ||||
| 	{ | ||||
| 		World.GetTypeId<T>(); | ||||
| 		Included.Add(World.TypeToId[typeof(T)]); | ||||
| 		return new FilterBuilder(World, Included, Excluded); | ||||
| 	} | ||||
| 
 | ||||
| 		public FilterBuilder Include<T>() where T : unmanaged | ||||
| 		{ | ||||
| 			World.TryRegisterTypeId<T>(); | ||||
| 			Included.Add(World.TypeToId[typeof(T)]); | ||||
| 			return new FilterBuilder(World, Included, Excluded); | ||||
| 		} | ||||
| 	public FilterBuilder Exclude<T>() where T : unmanaged | ||||
| 	{ | ||||
| 		World.GetTypeId<T>(); | ||||
| 		Excluded.Add(World.TypeToId[typeof(T)]); | ||||
| 		return new FilterBuilder(World, Included, Excluded); | ||||
| 	} | ||||
| 
 | ||||
| 		public FilterBuilder Exclude<T>() where T : unmanaged | ||||
| 		{ | ||||
| 			World.TryRegisterTypeId<T>(); | ||||
| 			Excluded.Add(World.TypeToId[typeof(T)]); | ||||
| 			return new FilterBuilder(World, Included, Excluded); | ||||
| 		} | ||||
| 
 | ||||
| 		public Filter Build() | ||||
| 		{ | ||||
| 			return new Filter(World.EmptyArchetype, 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 | ||||
| { | ||||
| 	internal readonly record struct Record(Archetype Archetype, int Row); | ||||
| } | ||||
| namespace MoonTools.ECS.Rev2; | ||||
| 
 | ||||
| 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 | ||||
| internal class RelationStorage | ||||
| { | ||||
| 	internal Column relations; | ||||
| 	internal Column relationDatas; | ||||
| 	internal NativeArray relations; | ||||
| 	internal NativeArray relationDatas; | ||||
| 	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>> inRelations = new Dictionary<Id, IndexableSet<Id>>(16); | ||||
|  | @ -19,8 +19,8 @@ internal class RelationStorage | |||
| 
 | ||||
| 	public RelationStorage(int relationDataSize) | ||||
| 	{ | ||||
| 		relations = new Column(Unsafe.SizeOf<(Id, Id)>()); | ||||
| 		relationDatas = new Column(relationDataSize); | ||||
| 		relations = new NativeArray(Unsafe.SizeOf<(Id, Id)>()); | ||||
| 		relationDatas = new NativeArray(relationDataSize); | ||||
| 	} | ||||
| 
 | ||||
| 	public ReverseSpanEnumerator<(Id, Id)> All() | ||||
|  |  | |||
|  | @ -139,20 +139,20 @@ public class Snapshot | |||
| 
 | ||||
| 	private class ArchetypeSnapshot | ||||
| 	{ | ||||
| 		private readonly Column[] ComponentColumns; | ||||
| 		private readonly NativeArray[] ComponentColumns; | ||||
| 		private readonly NativeArray<Id> RowToEntity; | ||||
| 
 | ||||
| 		public int Count => RowToEntity.Count; | ||||
| 
 | ||||
| 		public ArchetypeSnapshot(ArchetypeSignature signature) | ||||
| 		{ | ||||
| 			ComponentColumns = new Column[signature.Count]; | ||||
| 			ComponentColumns = new NativeArray[signature.Count]; | ||||
| 			RowToEntity = new NativeArray<Id>(); | ||||
| 
 | ||||
| 			for (int i = 0; i < signature.Count; i += 1) | ||||
| 			{ | ||||
| 				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 Column Relations; | ||||
| 		private Column RelationDatas; | ||||
| 		private NativeArray Relations; | ||||
| 		private NativeArray RelationDatas; | ||||
| 
 | ||||
| 		public RelationSnapshot(int elementSize) | ||||
| 		{ | ||||
| 			Relations = new Column(Unsafe.SizeOf<(Id, Id)>()); | ||||
| 			RelationDatas = new Column(elementSize); | ||||
| 			Relations = new NativeArray(Unsafe.SizeOf<(Id, Id)>()); | ||||
| 			RelationDatas = new NativeArray(elementSize); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Take(RelationStorage relationStorage) | ||||
|  |  | |||
|  | @ -1,478 +1,522 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Runtime.CompilerServices; | ||||
| using System.Runtime.InteropServices; | ||||
| 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 | ||||
| 		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>(); | ||||
| 		// Create the Empty Archetype | ||||
| 		EmptyArchetype = CreateArchetype(ArchetypeSignature.Empty); | ||||
| 	} | ||||
| 
 | ||||
| 		// Lookup from ArchetypeSignature to Archetype | ||||
| 		internal Dictionary<ArchetypeSignature, Archetype> ArchetypeIndex = new Dictionary<ArchetypeSignature, Archetype>(); | ||||
| 	internal Archetype CreateArchetype(ArchetypeSignature signature) | ||||
| 	{ | ||||
| 		var archetype = new Archetype(this, signature); | ||||
| 
 | ||||
| 		// Going from EntityId to Archetype and storage row | ||||
| 		internal Dictionary<Id, Record> EntityIndex = new Dictionary<Id, Record>(); | ||||
| 		ArchetypeIndex.Add(signature, archetype); | ||||
| 
 | ||||
| 		// 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>>(); | ||||
| 
 | ||||
| 		// 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() | ||||
| 		for (int i = 0; i < signature.Count; i += 1) | ||||
| 		{ | ||||
| 			// Create the Empty Archetype | ||||
| 			EmptyArchetype = CreateArchetype(ArchetypeSignature.Empty); | ||||
| 			var componentId = signature[i]; | ||||
| 			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); | ||||
| 
 | ||||
| 			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; | ||||
| 			EntityRelationIndex.Add(entityId, new IndexableSet<Id>()); | ||||
| 		} | ||||
| 
 | ||||
| 		public Id CreateEntity() | ||||
| 		{ | ||||
| 			var entityId = IdAssigner.Assign(); | ||||
| 			EntityIndex.Add(entityId, new Record(EmptyArchetype, EmptyArchetype.Count)); | ||||
| 			EmptyArchetype.RowToEntity.Add(entityId); | ||||
| 		return entityId; | ||||
| 	} | ||||
| 
 | ||||
| 			if (!EntityRelationIndex.ContainsKey(entityId)) | ||||
| 			{ | ||||
| 				EntityRelationIndex.Add(entityId, new IndexableSet<Id>()); | ||||
| 			} | ||||
| 
 | ||||
| 			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 | ||||
| 	internal Id GetTypeId<T>() where T : unmanaged | ||||
| 	{ | ||||
| 		if (TypeToId.ContainsKey(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>(); | ||||
| 			var typeId = TypeToId[typeof(T)]; | ||||
| 			if (!RelationIndex.ContainsKey(typeId)) | ||||
| 			{ | ||||
| 				RegisterRelationType(typeId); | ||||
| 			} | ||||
| 			messageStorage.Clear(); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 		[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 		private RelationStorage GetRelationStorage<T>() where T : unmanaged | ||||
| 	// Components | ||||
| 	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 columnIndex = record.Archetype.ComponentToColumnIndex[componentId]; | ||||
| 			var column = record.Archetype.ComponentColumns[columnIndex]; | ||||
| 
 | ||||
| 			return ref ((T*) column.Elements)[record.Row]; | ||||
| 			((T*) column.Elements)[record.Row] = component; | ||||
| 		} | ||||
| 
 | ||||
| 		public unsafe void Set<T>(in Id entityId, in T component) where T : unmanaged | ||||
| 		else | ||||
| 		{ | ||||
| 			TryRegisterComponentId<T>(); | ||||
| 			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); | ||||
| 			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); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ namespace MoonTools.ECS | |||
| 			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); | ||||
| 		} | ||||
|  |  | |||
|  | @ -36,6 +36,11 @@ namespace MoonTools.ECS | |||
| 			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 | ||||
| 		{ | ||||
| #if DEBUG | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue