Determinism fixes #5
			
				
			
		
		
		
	|  | @ -89,7 +89,7 @@ namespace MoonTools.ECS | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// these methods used to implement snapshots, templates, and debugging | ||||
| 		// these methods used to implement transfers and debugging | ||||
| 
 | ||||
| 		internal unsafe void* UntypedGet(int entityID, int componentTypeIndex) | ||||
| 		{ | ||||
|  |  | |||
|  | @ -1,5 +1,7 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Runtime.CompilerServices; | ||||
| using System.Runtime.InteropServices; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
|  | @ -19,16 +21,24 @@ namespace MoonTools.ECS | |||
| #endif | ||||
| 	} | ||||
| 
 | ||||
| 	internal class ComponentStorage<TComponent> : ComponentStorage where TComponent : unmanaged | ||||
| 	internal unsafe class ComponentStorage<TComponent> : ComponentStorage, IDisposable where TComponent : unmanaged | ||||
| 	{ | ||||
| 		private int nextID; | ||||
| 		private readonly Dictionary<int, int> entityIDToStorageIndex = new Dictionary<int, int>(16); | ||||
| 		private int[] entityIDs = new int[16]; | ||||
| 		private TComponent[] components = new TComponent[16]; | ||||
| 		private TComponent* components; | ||||
| 		private int* entityIDs; | ||||
| 		private int count = 0; | ||||
| 		private int capacity = 16; | ||||
| 		private bool disposed; | ||||
| 
 | ||||
| 		public ComponentStorage() | ||||
| 		{ | ||||
| 			components = (TComponent*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<TComponent>())); | ||||
| 			entityIDs = (int*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<int>())); | ||||
| 		} | ||||
| 
 | ||||
| 		public bool Any() | ||||
| 		{ | ||||
| 			return nextID > 0; | ||||
| 			return count > 0; | ||||
| 		} | ||||
| 
 | ||||
| 		public ref readonly TComponent Get(int entityID) | ||||
|  | @ -38,16 +48,13 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 		internal override unsafe void* UntypedGet(int entityID) | ||||
| 		{ | ||||
| 			fixed (void* p = &components[entityIDToStorageIndex[entityID]]) | ||||
| 			{ | ||||
| 				return p; | ||||
| 			} | ||||
| 			return &components[entityIDToStorageIndex[entityID]]; | ||||
| 		} | ||||
| 
 | ||||
| 		public ref readonly TComponent GetFirst() | ||||
| 		{ | ||||
| #if DEBUG | ||||
| 			if (nextID == 0) | ||||
| 			if (count == 0) | ||||
| 			{ | ||||
| 				throw new IndexOutOfRangeException("Component storage is empty!"); | ||||
| 			} | ||||
|  | @ -59,13 +66,14 @@ namespace MoonTools.ECS | |||
| 		{ | ||||
| 			if (!entityIDToStorageIndex.ContainsKey(entityID)) | ||||
| 			{ | ||||
| 				var index = nextID; | ||||
| 				nextID += 1; | ||||
| 				var index = count; | ||||
| 				count += 1; | ||||
| 
 | ||||
| 				if (index >= components.Length) | ||||
| 				if (index >= capacity) | ||||
| 				{ | ||||
| 					Array.Resize(ref components, components.Length * 2); | ||||
| 					Array.Resize(ref entityIDs, entityIDs.Length * 2); | ||||
| 					capacity *= 2; | ||||
| 					components = (TComponent*) NativeMemory.Realloc(components, (nuint) (capacity * Unsafe.SizeOf<TComponent>())); | ||||
| 					entityIDs = (int*) NativeMemory.Realloc(entityIDs, (nuint) (capacity * Unsafe.SizeOf<int>())); | ||||
| 				} | ||||
| 
 | ||||
| 				entityIDToStorageIndex[entityID] = index; | ||||
|  | @ -77,7 +85,7 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 		internal override unsafe void Set(int entityID, void* component) | ||||
| 		{ | ||||
| 			Set(entityID, *((TComponent*) component)); | ||||
| 			Set(entityID, *(TComponent*) component); | ||||
| 		} | ||||
| 
 | ||||
| 		// Returns true if the entity had this component. | ||||
|  | @ -87,7 +95,7 @@ namespace MoonTools.ECS | |||
| 			{ | ||||
| 				entityIDToStorageIndex.Remove(entityID); | ||||
| 
 | ||||
| 				var lastElementIndex = nextID - 1; | ||||
| 				var lastElementIndex = count - 1; | ||||
| 
 | ||||
| 				// move a component into the hole to maintain contiguous memory | ||||
| 				if (lastElementIndex != storageIndex) | ||||
|  | @ -98,7 +106,7 @@ namespace MoonTools.ECS | |||
| 					entityIDs[storageIndex] = lastEntityID; | ||||
| 				} | ||||
| 
 | ||||
| 				nextID -= 1; | ||||
| 				count -= 1; | ||||
| 
 | ||||
| 				return true; | ||||
| 			} | ||||
|  | @ -108,19 +116,19 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 		public override void Clear() | ||||
| 		{ | ||||
| 			nextID = 0; | ||||
| 			count = 0; | ||||
| 			entityIDToStorageIndex.Clear(); | ||||
| 		} | ||||
| 
 | ||||
| 		public ReadOnlySpan<TComponent> AllComponents() | ||||
| 		{ | ||||
| 			return new ReadOnlySpan<TComponent>(components, 0, nextID); | ||||
| 			return new ReadOnlySpan<TComponent>(components, count); | ||||
| 		} | ||||
| 
 | ||||
| 		public Entity FirstEntity() | ||||
| 		{ | ||||
| #if DEBUG | ||||
| 			if (nextID == 0) | ||||
| 			if (count == 0) | ||||
| 			{ | ||||
| 				throw new IndexOutOfRangeException("Component storage is empty!"); | ||||
| 			} | ||||
|  | @ -143,6 +151,32 @@ namespace MoonTools.ECS | |||
| 		{ | ||||
| 			return entityIDToStorageIndex.Keys; | ||||
| 		} | ||||
| 
 | ||||
| 		protected virtual void Dispose(bool disposing) | ||||
| 		{ | ||||
| 			if (!disposed) | ||||
| 			{ | ||||
| 				NativeMemory.Free(components); | ||||
| 				NativeMemory.Free(entityIDs); | ||||
| 				components = null; | ||||
| 				entityIDs = null; | ||||
| 
 | ||||
| 				disposed = true; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		~ComponentStorage() | ||||
| 		{ | ||||
| 			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||
| 			Dispose(disposing: false); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Dispose() | ||||
| 		{ | ||||
| 			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||
| 			Dispose(disposing: true); | ||||
| 			GC.SuppressFinalize(this); | ||||
| 		} | ||||
| #endif | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -13,12 +13,9 @@ namespace MoonTools.ECS | |||
| 		{ | ||||
| 		} | ||||
| 
 | ||||
| 		protected IEnumerable<dynamic> Debug_GetAllComponents(Entity entity) | ||||
| 		protected ComponentEnumerator Debug_GetAllComponents(Entity entity) | ||||
| 		{ | ||||
| 			foreach (var typeIndex in EntityStorage.ComponentTypeIndices(entity.ID)) | ||||
| 			{ | ||||
| 				yield return ComponentDepot.Debug_Get(entity.ID, typeIndex); | ||||
| 			} | ||||
| 			return new ComponentEnumerator(ComponentDepot, entity, EntityStorage.ComponentTypeIndices(entity.ID)); | ||||
| 		} | ||||
| 
 | ||||
| 		protected IEnumerable<Entity> Debug_GetEntities(Type componentType) | ||||
|  | @ -39,6 +36,28 @@ namespace MoonTools.ECS | |||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public ref struct ComponentEnumerator | ||||
| 		{ | ||||
| 			private ComponentDepot ComponentDepot; | ||||
| 			private Entity Entity; | ||||
| 			private ReverseSpanEnumerator<int> ComponentTypeIndices; | ||||
| 
 | ||||
| 			public ComponentEnumerator GetEnumerator() => this; | ||||
| 
 | ||||
| 			internal ComponentEnumerator( | ||||
| 				ComponentDepot componentDepot, | ||||
| 				Entity entity, | ||||
| 				Collections.IndexableSet<int> componentTypeIndices | ||||
| 			) { | ||||
| 				ComponentDepot = componentDepot; | ||||
| 				Entity = entity; | ||||
| 				ComponentTypeIndices = componentTypeIndices.GetEnumerator(); | ||||
| 			} | ||||
| 
 | ||||
| 			public bool MoveNext() => ComponentTypeIndices.MoveNext(); | ||||
| 			public object Current => ComponentDepot.Debug_Get(Entity.ID, ComponentTypeIndices.Current); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| #endif | ||||
|  |  | |||
|  | @ -1,40 +1,69 @@ | |||
| using System; | ||||
| using System.Runtime.CompilerServices; | ||||
| using System.Runtime.InteropServices; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| namespace MoonTools.ECS.Collections | ||||
| { | ||||
| 	public class DynamicArray<T> where T : unmanaged | ||||
| 	public unsafe class NativeArray<T> : IDisposable where T : unmanaged | ||||
| 	{ | ||||
| 		private T[] Array; | ||||
| 		public int Count { get; private set; } | ||||
| 		private T* Array; | ||||
| 		private int count; | ||||
| 		private int capacity; | ||||
| 
 | ||||
| 		public Span<T> ToSpan() => new Span<T>(Array, 0, Count); | ||||
| 		public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(Array, 0, Count)); | ||||
| 		public int Count => count; | ||||
| 
 | ||||
| 		public DynamicArray(int capacity = 16) | ||||
| 		public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(Array, Count)); | ||||
| 
 | ||||
| 		private bool disposed; | ||||
| 
 | ||||
| 		public NativeArray(int capacity = 16) | ||||
| 		{ | ||||
| 			Array = new T[capacity]; | ||||
| 			Count = 0; | ||||
| 			this.capacity = capacity; | ||||
| 			Array = (T*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<T>())); | ||||
| 			count = 0; | ||||
| 		} | ||||
| 
 | ||||
| 		public ref T this[int i] | ||||
| 		{ | ||||
| 			get { return ref Array[i]; } | ||||
| 		} | ||||
| 		public ref T this[int i] => ref Array[i]; | ||||
| 
 | ||||
| 		public void Add(T item) | ||||
| 		{ | ||||
| 			if (Count >= Array.Length) | ||||
| 			if (count >= capacity) | ||||
| 			{ | ||||
| 				global::System.Array.Resize(ref Array, Array.Length * 2); | ||||
| 				capacity *= 2; | ||||
| 				Array = (T*) NativeMemory.Realloc(Array, (nuint) (capacity * Unsafe.SizeOf<T>())); | ||||
| 			} | ||||
| 
 | ||||
| 			Array[Count] = item; | ||||
| 			Count += 1; | ||||
| 			Array[count] = item; | ||||
| 			count += 1; | ||||
| 		} | ||||
| 
 | ||||
| 		public void Clear() | ||||
| 		{ | ||||
| 			Count = 0; | ||||
| 			count = 0; | ||||
| 		} | ||||
| 
 | ||||
| 		protected virtual void Dispose(bool disposing) | ||||
| 		{ | ||||
| 			if (!disposed) | ||||
| 			{ | ||||
| 				NativeMemory.Free(Array); | ||||
| 				Array = null; | ||||
| 
 | ||||
| 				disposed = true; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		~NativeArray() | ||||
| 		{ | ||||
| 			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||
| 			Dispose(disposing: false); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Dispose() | ||||
| 		{ | ||||
| 			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||
| 			Dispose(disposing: true); | ||||
| 			GC.SuppressFinalize(this); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| using System.Collections.Generic; | ||||
| using MoonTools.ECS.Collections; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
|  | @ -10,8 +11,8 @@ namespace MoonTools.ECS | |||
| 		// FIXME: this is only needed in debug mode | ||||
| 		private readonly HashSet<int> availableIDHash = new HashSet<int>(); | ||||
| 
 | ||||
| 		private Dictionary<int, HashSet<int>> EntityToComponentTypeIndices = new Dictionary<int, HashSet<int>>(); | ||||
| 		private Dictionary<int, HashSet<int>> EntityToRelationTypeIndices = new Dictionary<int, HashSet<int>>(); | ||||
| 		private Dictionary<int, IndexableSet<int>> EntityToComponentTypeIndices = new Dictionary<int, IndexableSet<int>>(); | ||||
| 		private Dictionary<int, IndexableSet<int>> EntityToRelationTypeIndices = new Dictionary<int, IndexableSet<int>>(); | ||||
| 
 | ||||
| 		public int Count => nextID - availableIDs.Count; | ||||
| 
 | ||||
|  | @ -23,12 +24,12 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 			if (!EntityToComponentTypeIndices.ContainsKey(entity.ID)) | ||||
| 			{ | ||||
| 				EntityToComponentTypeIndices.Add(entity.ID, new HashSet<int>()); | ||||
| 				EntityToComponentTypeIndices.Add(entity.ID, new IndexableSet<int>()); | ||||
| 			} | ||||
| 
 | ||||
| 			if (!EntityToRelationTypeIndices.ContainsKey(entity.ID)) | ||||
| 			{ | ||||
| 				EntityToRelationTypeIndices.Add(entity.ID, new HashSet<int>()); | ||||
| 				EntityToRelationTypeIndices.Add(entity.ID, new IndexableSet<int>()); | ||||
| 			} | ||||
| 
 | ||||
| 			Tags[entity.ID] = tag; | ||||
|  | @ -86,12 +87,12 @@ namespace MoonTools.ECS | |||
| 			return Tags[entityID]; | ||||
| 		} | ||||
| 
 | ||||
| 		public HashSet<int> ComponentTypeIndices(int entityID) | ||||
| 		public IndexableSet<int> ComponentTypeIndices(int entityID) | ||||
| 		{ | ||||
| 			return EntityToComponentTypeIndices[entityID]; | ||||
| 		} | ||||
| 
 | ||||
| 		public HashSet<int> RelationTypeIndices(int entityID) | ||||
| 		public IndexableSet<int> RelationTypeIndices(int entityID) | ||||
| 		{ | ||||
| 			return EntityToRelationTypeIndices[entityID]; | ||||
| 		} | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using MoonTools.ECS.Collections; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
|  | @ -8,7 +9,7 @@ namespace MoonTools.ECS | |||
| 		internal FilterSignature Signature; | ||||
| 		private FilterStorage FilterStorage; | ||||
| 
 | ||||
| 		internal Filter(FilterStorage filterStorage, HashSet<int> included, HashSet<int> excluded) | ||||
| 		internal Filter(FilterStorage filterStorage, IndexableSet<int> included, IndexableSet<int> excluded) | ||||
| 		{ | ||||
| 			FilterStorage = filterStorage; | ||||
| 			Signature = new FilterSignature(included, excluded); | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using MoonTools.ECS.Collections; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
|  | @ -7,18 +6,18 @@ namespace MoonTools.ECS | |||
| 	{ | ||||
| 		private TypeIndices ComponentTypeIndices; | ||||
| 		private FilterStorage FilterStorage; | ||||
| 		private HashSet<int> Included; | ||||
| 		private HashSet<int> Excluded; | ||||
| 		private IndexableSet<int> Included; | ||||
| 		private IndexableSet<int> Excluded; | ||||
| 
 | ||||
| 		internal FilterBuilder(FilterStorage filterStorage, TypeIndices componentTypeIndices) | ||||
| 		{ | ||||
| 			FilterStorage = filterStorage; | ||||
| 			ComponentTypeIndices = componentTypeIndices; | ||||
| 			Included = new HashSet<int>(); | ||||
| 			Excluded = new HashSet<int>(); | ||||
| 			Included = new IndexableSet<int>(); | ||||
| 			Excluded = new IndexableSet<int>(); | ||||
| 		} | ||||
| 
 | ||||
| 		private FilterBuilder(FilterStorage filterStorage, TypeIndices componentTypeIndices, HashSet<int> included, HashSet<int> excluded) | ||||
| 		private FilterBuilder(FilterStorage filterStorage, TypeIndices componentTypeIndices, IndexableSet<int> included, IndexableSet<int> excluded) | ||||
| 		{ | ||||
| 			FilterStorage = filterStorage; | ||||
| 			ComponentTypeIndices = componentTypeIndices; | ||||
|  |  | |||
|  | @ -1,14 +1,15 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using MoonTools.ECS.Collections; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	public struct FilterSignature : IEquatable<FilterSignature> | ||||
| 	{ | ||||
| 		public readonly HashSet<int> Included; | ||||
| 		public readonly HashSet<int> Excluded; | ||||
| 		public readonly IndexableSet<int> Included; | ||||
| 		public readonly IndexableSet<int> Excluded; | ||||
| 
 | ||||
| 		public FilterSignature(HashSet<int> included, HashSet<int> excluded) | ||||
| 		public FilterSignature(IndexableSet<int> included, IndexableSet<int> excluded) | ||||
| 		{ | ||||
| 			Included = included; | ||||
| 			Excluded = excluded; | ||||
|  | @ -21,8 +22,6 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 		public bool Equals(FilterSignature other) | ||||
| 		{ | ||||
| 			// workaround for HashSet<T>.SetEquals generating garbage | ||||
| 			// maybe fixed in .NET 8? | ||||
| 			foreach (var included in Included) | ||||
| 			{ | ||||
| 				if (!other.Included.Contains(included)) | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using MoonTools.ECS.Collections; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
|  | @ -8,7 +9,7 @@ namespace MoonTools.ECS | |||
| 		private EntityStorage EntityStorage; | ||||
| 		private TypeIndices ComponentTypeIndices; | ||||
| 		private Dictionary<FilterSignature, IndexableSet<Entity>> filterSignatureToEntityIDs = new Dictionary<FilterSignature, IndexableSet<Entity>>(); | ||||
| 		private Dictionary<int, HashSet<FilterSignature>> typeToFilterSignatures = new Dictionary<int, HashSet<FilterSignature>>(); | ||||
| 		private Dictionary<int, List<FilterSignature>> typeToFilterSignatures = new Dictionary<int, List<FilterSignature>>(); | ||||
| 
 | ||||
| 		private Dictionary<FilterSignature, Action<Entity>> addCallbacks = new Dictionary<FilterSignature, Action<Entity>>(); | ||||
| 		private Dictionary<FilterSignature, Action<Entity>> removeCallbacks = new Dictionary<FilterSignature, Action<Entity>>(); | ||||
|  | @ -19,7 +20,36 @@ namespace MoonTools.ECS | |||
| 			ComponentTypeIndices = componentTypeIndices; | ||||
| 		} | ||||
| 
 | ||||
| 		public Filter CreateFilter(HashSet<int> included, HashSet<int> excluded) | ||||
| 		private void CopyTypeCache(Dictionary<int, List<FilterSignature>> typeCache) | ||||
| 		{ | ||||
| 			foreach (var type in typeCache.Keys) | ||||
| 			{ | ||||
| 				if (!typeToFilterSignatures.ContainsKey(type)) | ||||
| 				{ | ||||
| 					typeToFilterSignatures.Add(type, new List<FilterSignature>()); | ||||
| 
 | ||||
| 					foreach (var signature in typeCache[type]) | ||||
| 					{ | ||||
| 						typeToFilterSignatures[type].Add(signature); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public void CreateMissingStorages(FilterStorage other) | ||||
| 		{ | ||||
| 			foreach (var filterSignature in other.filterSignatureToEntityIDs.Keys) | ||||
| 			{ | ||||
| 				if (!filterSignatureToEntityIDs.ContainsKey(filterSignature)) | ||||
| 				{ | ||||
| 					filterSignatureToEntityIDs.Add(filterSignature, new IndexableSet<Entity>()); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			CopyTypeCache(other.typeToFilterSignatures); | ||||
| 		} | ||||
| 
 | ||||
| 		public Filter CreateFilter(IndexableSet<int> included, IndexableSet<int> excluded) | ||||
| 		{ | ||||
| 			var filterSignature = new FilterSignature(included, excluded); | ||||
| 			if (!filterSignatureToEntityIDs.ContainsKey(filterSignature)) | ||||
|  | @ -30,7 +60,7 @@ namespace MoonTools.ECS | |||
| 				{ | ||||
| 					if (!typeToFilterSignatures.ContainsKey(type)) | ||||
| 					{ | ||||
| 						typeToFilterSignatures.Add(type, new HashSet<FilterSignature>()); | ||||
| 						typeToFilterSignatures.Add(type, new List<FilterSignature>()); | ||||
| 					} | ||||
| 
 | ||||
| 					typeToFilterSignatures[type].Add(filterSignature); | ||||
|  | @ -40,7 +70,7 @@ namespace MoonTools.ECS | |||
| 				{ | ||||
| 					if (!typeToFilterSignatures.ContainsKey(type)) | ||||
| 					{ | ||||
| 						typeToFilterSignatures.Add(type, new HashSet<FilterSignature>()); | ||||
| 						typeToFilterSignatures.Add(type, new List<FilterSignature>()); | ||||
| 					} | ||||
| 
 | ||||
| 					typeToFilterSignatures[type].Add(filterSignature); | ||||
|  | @ -59,7 +89,7 @@ namespace MoonTools.ECS | |||
| 			return new RandomEntityEnumerator( | ||||
| 				this, | ||||
| 				filterSignature, | ||||
| 				RandomGenerator.LinearCongruentialGenerator(FilterCount(filterSignature))); | ||||
| 				RandomManager.LinearCongruentialSequence(FilterCount(filterSignature))); | ||||
| 		} | ||||
| 
 | ||||
| 		public Entity FilterNthEntity(FilterSignature filterSignature, int index) | ||||
|  | @ -69,7 +99,7 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 		public Entity FilterRandomEntity(FilterSignature filterSignature) | ||||
| 		{ | ||||
| 			var randomIndex = RandomGenerator.Next(FilterCount(filterSignature)); | ||||
| 			var randomIndex = RandomManager.Next(FilterCount(filterSignature)); | ||||
| 			return new Entity(filterSignatureToEntityIDs[filterSignature][randomIndex]); | ||||
| 		} | ||||
| 
 | ||||
|  | @ -147,10 +177,12 @@ namespace MoonTools.ECS | |||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			filterSignatureToEntityIDs[filterSignature].Add(entityID); | ||||
| 			if (addCallbacks.TryGetValue(filterSignature, out var addCallback)) | ||||
| 			if (filterSignatureToEntityIDs[filterSignature].Add(entityID)) | ||||
| 			{ | ||||
| 				addCallback(entityID); | ||||
| 				if (addCallbacks.TryGetValue(filterSignature, out var addCallback)) | ||||
| 				{ | ||||
| 					addCallback(entityID); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
|  | @ -171,6 +203,32 @@ namespace MoonTools.ECS | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// Used by TransferEntity | ||||
| 		public void AddEntity(FilterSignature signature, int entityID) | ||||
| 		{ | ||||
| 			filterSignatureToEntityIDs[signature].Add(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		public void TransferStorage(Dictionary<int, int> worldToTransferID, FilterStorage other) | ||||
| 		{ | ||||
| 			foreach (var (filterSignature, entityIDs) in filterSignatureToEntityIDs) | ||||
| 			{ | ||||
| 				foreach (var entity in entityIDs) | ||||
| 				{ | ||||
| 					if (worldToTransferID.ContainsKey(entity)) | ||||
| 					{ | ||||
| 						var otherEntityID = worldToTransferID[entity]; | ||||
| 						other.AddEntity(filterSignature, otherEntityID); | ||||
| 
 | ||||
| 						if (other.addCallbacks.TryGetValue(filterSignature, out var addCallback)) | ||||
| 						{ | ||||
| 							addCallback(otherEntityID); | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// used by World.Clear, ignores callbacks | ||||
| 		public void Clear() | ||||
| 		{ | ||||
|  |  | |||
|  | @ -1,26 +1,31 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Runtime.CompilerServices; | ||||
| using System.Runtime.InteropServices; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| namespace MoonTools.ECS.Collections | ||||
| { | ||||
| 	internal class IndexableSet<T> where T : unmanaged | ||||
| 	public unsafe class IndexableSet<T> : IDisposable where T : unmanaged | ||||
| 	{ | ||||
| 		private Dictionary<T, int> indices; | ||||
| 		private T[] array; | ||||
| 		public int Count { get; private set; } | ||||
| 		public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(array, 0, Count)); | ||||
| 		private T* array; | ||||
| 		private int count; | ||||
| 		private int capacity; | ||||
| 		private bool disposed; | ||||
| 
 | ||||
| 		public IndexableSet(int size = 32) | ||||
| 		public int Count => count; | ||||
| 		public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(array, count)); | ||||
| 
 | ||||
| 		public IndexableSet(int capacity = 32) | ||||
| 		{ | ||||
| 			indices = new Dictionary<T, int>(size); | ||||
| 			array = new T[size]; | ||||
| 			this.capacity = capacity; | ||||
| 			count = 0; | ||||
| 
 | ||||
| 			indices = new Dictionary<T, int>(capacity); | ||||
| 			array = (T*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<T>())); | ||||
| 		} | ||||
| 
 | ||||
| 		public T this[int i] | ||||
| 		{ | ||||
| 			get { return array[i]; } | ||||
| 		} | ||||
| 		public T this[int i] => array[i]; | ||||
| 
 | ||||
| 		public bool Contains(T element) | ||||
| 		{ | ||||
|  | @ -31,15 +36,16 @@ namespace MoonTools.ECS | |||
| 		{ | ||||
| 			if (!Contains(element)) | ||||
| 			{ | ||||
| 				indices.Add(element, Count); | ||||
| 				indices.Add(element, count); | ||||
| 
 | ||||
| 				if (Count >= array.Length) | ||||
| 				if (count >= capacity) | ||||
| 				{ | ||||
| 					Array.Resize(ref array, array.Length * 2); | ||||
| 					capacity *= 2; | ||||
| 					array = (T*) NativeMemory.Realloc(array, (nuint) (capacity * Unsafe.SizeOf<T>())); | ||||
| 				} | ||||
| 
 | ||||
| 				array[Count] = element; | ||||
| 				Count += 1; | ||||
| 				array[count] = element; | ||||
| 				count += 1; | ||||
| 
 | ||||
| 				return true; | ||||
| 			} | ||||
|  | @ -58,7 +64,7 @@ namespace MoonTools.ECS | |||
| 			var index = indices[element]; | ||||
| 			array[index] = lastElement; | ||||
| 			indices[lastElement] = index; | ||||
| 			Count -= 1; | ||||
| 			count -= 1; | ||||
| 			indices.Remove(element); | ||||
| 
 | ||||
| 			return true; | ||||
|  | @ -66,45 +72,32 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 		public void Clear() | ||||
| 		{ | ||||
| 			Count = 0; | ||||
| 			indices.Clear(); | ||||
| 			count = 0; | ||||
| 		} | ||||
| 
 | ||||
| 		public struct Enumerator | ||||
| 		protected virtual void Dispose(bool disposing) | ||||
| 		{ | ||||
| 			/// <summary>The set being enumerated.</summary> | ||||
| 			private readonly IndexableSet<T> _set; | ||||
| 			/// <summary>The next index to yield.</summary> | ||||
| 			private int _index; | ||||
| 
 | ||||
| 			/// <summary>Initialize the enumerator.</summary> | ||||
| 			/// <param name="set">The set to enumerate.</param> | ||||
| 			[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 			internal Enumerator(IndexableSet<T> set) | ||||
| 			if (!disposed) | ||||
| 			{ | ||||
| 				_set = set; | ||||
| 				_index = _set.Count; | ||||
| 			} | ||||
| 				NativeMemory.Free(array); | ||||
| 				array = null; | ||||
| 
 | ||||
| 			/// <summary>Advances the enumerator to the next element of the span.</summary> | ||||
| 			[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 			public bool MoveNext() | ||||
| 			{ | ||||
| 				int index = _index - 1; | ||||
| 				if (index >= 0) | ||||
| 				{ | ||||
| 					_index = index; | ||||
| 					return true; | ||||
| 				} | ||||
| 
 | ||||
| 				return false; | ||||
| 				disposed = true; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 			/// <summary>Gets the element at the current position of the enumerator.</summary> | ||||
| 			public T Current | ||||
| 			{ | ||||
| 				[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 				get => _set[_index]; | ||||
| 			} | ||||
| 		~IndexableSet() | ||||
| 		{ | ||||
| 			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||
| 			Dispose(disposing: false); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Dispose() | ||||
| 		{ | ||||
| 			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||
| 			Dispose(disposing: true); | ||||
| 			GC.SuppressFinalize(this); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,8 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Runtime.CompilerServices; | ||||
| using System.Runtime.InteropServices; | ||||
| using MoonTools.ECS.Collections; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
|  | @ -8,17 +11,18 @@ namespace MoonTools.ECS | |||
| 		public abstract void Clear(); | ||||
| 	} | ||||
| 
 | ||||
| 	internal class MessageStorage<TMessage> : MessageStorage where TMessage : unmanaged | ||||
| 	internal unsafe class MessageStorage<TMessage> : MessageStorage, IDisposable where TMessage : unmanaged | ||||
| 	{ | ||||
| 		private int count = 0; | ||||
| 		private int capacity = 128; | ||||
| 		private TMessage[] messages; | ||||
| 		private TMessage* messages; | ||||
| 		// duplicating storage here for fast iteration | ||||
| 		private Dictionary<int, DynamicArray<TMessage>> entityToMessages = new Dictionary<int, DynamicArray<TMessage>>(); | ||||
| 		private Dictionary<int, NativeArray<TMessage>> entityToMessages = new Dictionary<int, NativeArray<TMessage>>(); | ||||
| 		private bool disposed; | ||||
| 
 | ||||
| 		public MessageStorage() | ||||
| 		{ | ||||
| 			messages = new TMessage[capacity]; | ||||
| 			messages = (TMessage*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<TMessage>())); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Add(in TMessage message) | ||||
|  | @ -26,7 +30,7 @@ namespace MoonTools.ECS | |||
| 			if (count == capacity) | ||||
| 			{ | ||||
| 				capacity *= 2; | ||||
| 				Array.Resize(ref messages, capacity); | ||||
| 				messages = (TMessage*) NativeMemory.Realloc(messages, (nuint) (capacity * Unsafe.SizeOf<TMessage>())); | ||||
| 			} | ||||
| 
 | ||||
| 			messages[count] = message; | ||||
|  | @ -37,7 +41,7 @@ namespace MoonTools.ECS | |||
| 		{ | ||||
| 			if (!entityToMessages.ContainsKey(entityID)) | ||||
| 			{ | ||||
| 				entityToMessages.Add(entityID, new DynamicArray<TMessage>()); | ||||
| 				entityToMessages.Add(entityID, new NativeArray<TMessage>()); | ||||
| 			} | ||||
| 			entityToMessages[entityID].Add(message); | ||||
| 
 | ||||
|  | @ -51,7 +55,7 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 		public ReadOnlySpan<TMessage> All() | ||||
| 		{ | ||||
| 			return new ReadOnlySpan<TMessage>(messages, 0, count); | ||||
| 			return new ReadOnlySpan<TMessage>(messages, count); | ||||
| 		} | ||||
| 
 | ||||
| 		public TMessage First() | ||||
|  | @ -89,5 +93,39 @@ namespace MoonTools.ECS | |||
| 				set.Clear(); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		protected virtual void Dispose(bool disposing) | ||||
| 		{ | ||||
| 			if (!disposed) | ||||
| 			{ | ||||
| 				Clear(); | ||||
| 
 | ||||
| 				if (disposing) | ||||
| 				{ | ||||
| 					foreach (var array in entityToMessages.Values) | ||||
| 					{ | ||||
| 						array.Dispose(); | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				NativeMemory.Free(messages); | ||||
| 				messages = null; | ||||
| 
 | ||||
| 				disposed = true; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		~MessageStorage() | ||||
| 		{ | ||||
| 			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||
| 			Dispose(disposing: false); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Dispose() | ||||
| 		{ | ||||
| 			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||
| 			Dispose(disposing: true); | ||||
| 			GC.SuppressFinalize(this); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
							
								
								
									
										234
									
								
								src/Random.cs
								
								
								
								
							
							
						
						
									
										234
									
								
								src/Random.cs
								
								
								
								
							|  | @ -1,77 +1,209 @@ | |||
| using System; | ||||
| using System; | ||||
| using System.Runtime.CompilerServices; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	public static class RandomGenerator | ||||
| 	/// <summary> | ||||
| 	/// This class implements the well equidistributed long-period linear pseudorandom number generator. | ||||
| 	/// Code taken from Chris Lomont: http://lomont.org/papers/2008/Lomont_PRNG_2008.pdf | ||||
| 	/// </summary> | ||||
| 	public class Random | ||||
| 	{ | ||||
| 		private static Random random = new Random(); | ||||
| 		public const int STATE_BYTE_COUNT = 68; // 16 state ints + 1 index int | ||||
| 
 | ||||
| 		private static int[] Primes = | ||||
| 		{ | ||||
| 			2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997,1009,1013,1019,1021,1031,1033,1039,1049,1051,1061,1063,1069,1087,1091,1093,1097,1103,1109,1117,1123,1129,1151,1153,1163,1171,1181,1187,1193,1201,1213,1217,1223,1229,1231,1237,1249,1259,1277,1279,1283,1289,1291,1297,1301,1303,1307,1319,1321,1327,1361,1367,1373,1381,1399,1409,1423,1427,1429,1433,1439,1447,1451,1453,1459,1471,1481,1483,1487,1489,1493,1499,1511,1523,1531,1543,1549,1553,1559,1567,1571,1579,1583,1597,1601,1607,1609,1613,1619,1621,1627,1637,1657,1663,1667,1669,1693,1697,1699,1709,1721,1723,1733,1741,1747,1753,1759,1777,1783,1787,1789,1801,1811,1823,1831,1847,1861,1867,1871,1873,1877,1879,1889,1901,1907,1913,1931,1933,1949,1951,1973,1979,1987,1993,1997,1999,2003,2011,2017,2027,2029,2039,2053,2063,2069,2081,2083,2087,2089,2099,2111,2113,2129,2131,2137,2141,2143,2153,2161,2179,2203,2207,2213,2221,2237,2239,2243,2251,2267,2269,2273,2281,2287,2293,2297,2309,2311,2333,2339,2341,2347,2351,2357,2371,2377,2381,2383,2389,2393,2399,2411,2417,2423,2437,2441,2447,2459,2467,2473,2477,2503,2521,2531,2539,2543,2549,2551,2557,2579,2591,2593,2609,2617,2621,2633,2647,2657,2659,2663,2671,2677,2683,2687,2689,2693,2699,2707,2711,2713,2719,2729,2731,2741,2749,2753,2767,2777,2789,2791,2797,2801,2803,2819,2833,2837,2843,2851,2857,2861,2879,2887,2897,2903,2909,2917,2927,2939,2953,2957,2963,2969,2971,2999,3001,3011,3019,3023,3037,3041,3049,3061,3067,3079,3083,3089,3109,3119,3121,3137,3163,3167,3169,3181,3187,3191,3203,3209,3217,3221,3229,3251,3253,3257,3259,3271,3299,3301,3307,3313,3319,3323,3329,3331,3343,3347,3359,3361,3371,3373,3389,3391,3407,3413,3433,3449,3457,3461,3463,3467,3469,3491,3499,3511,3517,3527,3529,3533,3539,3541,3547,3557,3559,3571,3581,3583,3593,3607,3613,3617,3623,3631,3637,3643,3659,3671,3673,3677,3691,3697,3701,3709,3719,3727,3733,3739,3761,3767,3769,3779,3793,3797,3803,3821,3823,3833,3847,3851,3853,3863,3877,3881,3889,3907,3911,3917,3919,3923,3929,3931,3943,3947,3967,3989,4001,4003,4007,4013,4019,4021,4027,4049,4051,4057,4073,4079,4091,4093,4099,4111,4127,4129,4133,4139,4153,4157,4159,4177,4201,4211,4217,4219,4229,4231,4241,4243,4253,4259,4261,4271,4273,4283,4289,4297,4327,4337,4339,4349,4357,4363,4373,4391,4397,4409,4421,4423,4441,4447,4451,4457,4463,4481,4483,4493,4507,4513,4517,4519,4523,4547,4549,4561,4567,4583,4591,4597,4603,4621,4637,4639,4643,4649,4651,4657,4663,4673,4679,4691,4703,4721,4723,4729,4733,4751,4759,4783,4787,4789,4793,4799,4801,4813,4817,4831,4861,4871,4877,4889,4903,4909,4919,4931,4933,4937,4943,4951,4957,4967,4969,4973,4987,4993,4999,5003,5009,5011,5021,5023,5039,5051,5059,5077,5081,5087,5099,5101,5107,5113,5119,5147,5153,5167,5171,5179,5189,5197,5209,5227,5231,5233,5237,5261,5273,5279,5281,5297,5303,5309,5323,5333,5347,5351,5381,5387,5393,5399,5407,5413,5417,5419,5431,5437,5441,5443,5449,5471,5477,5479,5483,5501,5503,5507,5519,5521,5527,5531,5557,5563,5569,5573,5581,5591,5623,5639,5641,5647,5651,5653,5657,5659,5669,5683,5689,5693,5701,5711,5717,5737,5741,5743,5749,5779,5783,5791,5801,5807,5813,5821,5827,5839,5843,5849,5851,5857,5861,5867,5869,5879,5881,5897,5903,5923,5927,5939,5953,5981,5987,6007,6011,6029,6037,6043,6047,6053,6067,6073,6079,6089,6091,6101,6113,6121,6131,6133,6143,6151,6163,6173,6197,6199,6203,6211,6217,6221,6229,6247,6257,6263,6269,6271,6277,6287,6299,6301,6311,6317,6323,6329,6337,6343,6353,6359,6361,6367,6373,6379,6389,6397,6421,6427,6449,6451,6469,6473,6481,6491,6521,6529,6547,6551,6553,6563,6569,6571,6577,6581,6599,6607,6619,6637,6653,6659,6661,6673,6679,6689,6691,6701,6703,6709,6719,6733,6737,6761,6763,6779,6781,6791,6793,6803,6823,6827,6829,6833,6841,6857,6863,6869,6871,6883,6899,6907,6911,6917,6947,6949,6959,6961,6967,6971,6977,6983,6991,6997,7001,7013,7019,7027,7039,7043,7057,7069,7079,7103,7109,7121,7127,7129,7151,7159,7177,7187,7193,7207,7211,7213,7219,7229,7237,7243,7247,7253,7283,7297,7307,7309,7321,7331,7333,7349,7351,7369,7393,7411,7417,7433,7451,7457,7459,7477,7481,7487,7489,7499,7507,7517,7523,7529,7537,7541,7547,7549,7559,7561,7573,7577,7583,7589,7591,7603,7607,7621,7639,7643,7649,7669,7673,7681,7687,7691,7699,7703,7717,7723,7727,7741,7753,7757,7759,7789,7793,7817,7823,7829,7841,7853,7867,7873,7877,7879,7883,7901,7907,7919 | ||||
| 		}; | ||||
| 		uint[] State = new uint[16]; | ||||
| 		uint Index = 0; | ||||
| 		uint Seed; | ||||
| 
 | ||||
| 		public static void SetSeed(int seed) | ||||
| 		/// <summary> | ||||
| 		/// Initializes the RNG with an arbitrary seed. | ||||
| 		/// </summary> | ||||
| 		public Random() | ||||
| 		{ | ||||
| 			random = new Random(seed); | ||||
| 		} | ||||
| 
 | ||||
| 		internal static int Next(int maxValue) | ||||
| 		{ | ||||
| 			return random.Next(maxValue); | ||||
| 			Init((uint) Environment.TickCount); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// A psuedorandom nonrepeating sequence of integers from 0 to n. | ||||
| 		/// Initializes the RNG with a given seed. | ||||
| 		/// </summary> | ||||
| 		internal static LinearCongruentialEnumerator LinearCongruentialGenerator(int n) | ||||
| 		public void Init(uint seed) | ||||
| 		{ | ||||
| 			var x = Primes[random.Next(Primes.Length - 1)]; | ||||
| 			while (x % n == 0) | ||||
| 			Seed = seed; | ||||
| 			uint s = seed; | ||||
| 			for (int i = 0; i < 16; i++) | ||||
| 			{ | ||||
| 				// not coprime, try again | ||||
| 				x = Primes[random.Next(Primes.Length - 1)]; | ||||
| 				s = (((s * 214013 + 2531011) >> 16) & 0x7fffffff) | 0; | ||||
| 				State[i] = ~ ~s; //i ; | ||||
| 			} | ||||
| 
 | ||||
| 			return new LinearCongruentialEnumerator(random.Next(n), x, n); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	public struct LinearCongruentialEnumerator | ||||
| 	{ | ||||
| 		private readonly int start; | ||||
| 		private readonly int count; | ||||
| 		private readonly int prime; | ||||
| 		private int current; | ||||
| 
 | ||||
| 		public LinearCongruentialEnumerator GetEnumerator() => this; | ||||
| 
 | ||||
| 		[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 		internal LinearCongruentialEnumerator(int start, int prime, int count) | ||||
| 		{ | ||||
| 			current = start; | ||||
| 			this.start = start; | ||||
| 			this.prime = prime; | ||||
| 			this.count = count; | ||||
| 			Index = 0; | ||||
| 		} | ||||
| 
 | ||||
| 		[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 		public bool MoveNext() | ||||
| 		/// <summary> | ||||
| 		/// Returns the seed that was used to initialize the RNG. | ||||
| 		/// </summary> | ||||
| 		public uint GetSeed() | ||||
| 		{ | ||||
| 			current += 1; | ||||
| 			if (current < start + count) | ||||
| 			return Seed; | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Returns the entire state of the RNG as a string. | ||||
| 		/// </summary> | ||||
| 		public string PrintState() | ||||
| 		{ | ||||
| 			var s = ""; | ||||
| 			for (var i = 0; i < 16; i += 1) | ||||
| 			{ | ||||
| 				return true; | ||||
| 				s += State[i]; | ||||
| 			} | ||||
| 
 | ||||
| 			return false; | ||||
| 			s += Index; | ||||
| 			return s; | ||||
| 		} | ||||
| 
 | ||||
| 		public int Current | ||||
| 		/// <summary> | ||||
| 		/// Saves the entire state of the RNG to a Span. | ||||
| 		/// </summary> | ||||
| 		/// <param name="bytes">Must be a span of at least STATE_BYTE_COUNT bytes.</param> | ||||
| 		/// <exception cref="ArgumentException">Thrown if the byte span is too short.</exception> | ||||
| 		public unsafe void SaveState(Span<byte> bytes) | ||||
| 		{ | ||||
| 			[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 			get => (current * prime) % count; | ||||
| 			#if DEBUG | ||||
| 			if (bytes.Length < STATE_BYTE_COUNT) | ||||
| 			{ | ||||
| 				throw new ArgumentException("Byte span too short!"); | ||||
| 			} | ||||
| 			#endif | ||||
| 
 | ||||
| 			fixed (byte* ptr = bytes) | ||||
| 			{ | ||||
| 				var offset = 0; | ||||
| 				for (var i = 0; i < 16; i += 1) | ||||
| 				{ | ||||
| 					Unsafe.Write(ptr + offset, State[i]); | ||||
| 					offset += 4; | ||||
| 				} | ||||
| 
 | ||||
| 				Unsafe.Write(ptr + offset, Index); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Loads the entire state of the RNG from a Span. | ||||
| 		/// </summary> | ||||
| 		/// <param name="bytes">Must be a span of at least STATE_BYTE_COUNT bytes.</param> | ||||
| 		/// <exception cref="ArgumentException">Thrown if the byte span is too short.</exception> | ||||
| 		public unsafe void LoadState(Span<byte> bytes) | ||||
| 		{ | ||||
| 			#if DEBUG | ||||
| 			if (bytes.Length < STATE_BYTE_COUNT) | ||||
| 			{ | ||||
| 				throw new ArgumentException("Byte span too short!"); | ||||
| 			} | ||||
| 			#endif | ||||
| 
 | ||||
| 			fixed (byte* ptr = bytes) | ||||
| 			{ | ||||
| 				var offset = 0; | ||||
| 
 | ||||
| 				for (var i = 0; i < 16; i += 1) | ||||
| 				{ | ||||
| 					State[i] = Unsafe.Read<uint>(ptr + offset); | ||||
| 					offset += 4; | ||||
| 				} | ||||
| 
 | ||||
| 				Index = Unsafe.Read<uint>(ptr + offset); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		private uint NextInternal() | ||||
| 		{ | ||||
| 			uint a, b, c, d; | ||||
| 			a = State[Index]; | ||||
| 			c = State[(Index+13)&15]; | ||||
| 			b = a^c^(a<<16)^(c<<15); | ||||
| 			c = State[(Index+9)&15]; | ||||
| 			c ^= (c>>11); | ||||
| 			a = State[Index] = b^c; | ||||
| 			d = (uint) (a ^((a<<5)&0xDA442D24UL)); | ||||
| 			Index = (Index + 15)&15; | ||||
| 			a = State[Index]; | ||||
| 			State[Index] = a^b^d^(a<<2)^(b<<18)^(c<<28); | ||||
| 			return State[Index]; | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Returns a non-negative signed integer. | ||||
| 		/// </summary> | ||||
| 		public int Next() | ||||
| 		{ | ||||
| 			return (int) (NextInternal() >>> 1); // unsigned bitshift right to get rid of signed bit | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Returns a non-negative signed integer less than max. | ||||
| 		/// </summary> | ||||
| 		public int Next(int max) | ||||
| 		{ | ||||
| 			return (int) (((double) Next()) * max / int.MaxValue); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Returns a signed integer greater than or equal to min and less than max. | ||||
| 		/// </summary> | ||||
| 		public int Next(int min, int max) | ||||
| 		{ | ||||
| 			var diff = max - min; | ||||
| 			var next = Next(diff); | ||||
| 			return min + next; | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Returns a non-negative signed 64 bit integer. | ||||
| 		/// </summary> | ||||
| 		public long NextInt64() | ||||
| 		{ | ||||
| 			long next = NextInternal(); | ||||
| 			next <<= 32; | ||||
| 			next |= NextInternal(); | ||||
| 			next >>>= 1; | ||||
| 			return next; | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Returns a non-negative signed 64 bit integer less than max. | ||||
| 		/// </summary> | ||||
| 		public long NextInt64(long max) | ||||
| 		{ | ||||
| 			var next = NextInt64(); | ||||
| 			return (long) (((double) next) * max / long.MaxValue); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Returns a non-negative signed 64 bit integer greater than or equal to min and less than max. | ||||
| 		/// </summary> | ||||
| 		public long NextInt64(long min, long max) | ||||
| 		{ | ||||
| 			var diff = max - min; | ||||
| 			var next = NextInt64(diff); | ||||
| 			return min + next; | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Returns a single-precision floating point value between 0 and 1. | ||||
| 		/// </summary> | ||||
| 		public float NextSingle() | ||||
| 		{ | ||||
| 			var n = NextInternal(); | ||||
| 			return ((float) n) / uint.MaxValue; | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// Returns a double-precision floating point value between 0 and 1. | ||||
| 		/// </summary> | ||||
| 		public double NextDouble() | ||||
| 		{ | ||||
| 			var n = NextInternal(); | ||||
| 			return ((double) n) / uint.MaxValue; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,82 @@ | |||
| using System.Runtime.CompilerServices; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
| 	public static class RandomManager | ||||
| 	{ | ||||
| 		private static Random Random = new Random(); | ||||
| 
 | ||||
| 		private static int[] Primes = | ||||
| 		{ | ||||
| 			2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997,1009,1013,1019,1021,1031,1033,1039,1049,1051,1061,1063,1069,1087,1091,1093,1097,1103,1109,1117,1123,1129,1151,1153,1163,1171,1181,1187,1193,1201,1213,1217,1223,1229,1231,1237,1249,1259,1277,1279,1283,1289,1291,1297,1301,1303,1307,1319,1321,1327,1361,1367,1373,1381,1399,1409,1423,1427,1429,1433,1439,1447,1451,1453,1459,1471,1481,1483,1487,1489,1493,1499,1511,1523,1531,1543,1549,1553,1559,1567,1571,1579,1583,1597,1601,1607,1609,1613,1619,1621,1627,1637,1657,1663,1667,1669,1693,1697,1699,1709,1721,1723,1733,1741,1747,1753,1759,1777,1783,1787,1789,1801,1811,1823,1831,1847,1861,1867,1871,1873,1877,1879,1889,1901,1907,1913,1931,1933,1949,1951,1973,1979,1987,1993,1997,1999,2003,2011,2017,2027,2029,2039,2053,2063,2069,2081,2083,2087,2089,2099,2111,2113,2129,2131,2137,2141,2143,2153,2161,2179,2203,2207,2213,2221,2237,2239,2243,2251,2267,2269,2273,2281,2287,2293,2297,2309,2311,2333,2339,2341,2347,2351,2357,2371,2377,2381,2383,2389,2393,2399,2411,2417,2423,2437,2441,2447,2459,2467,2473,2477,2503,2521,2531,2539,2543,2549,2551,2557,2579,2591,2593,2609,2617,2621,2633,2647,2657,2659,2663,2671,2677,2683,2687,2689,2693,2699,2707,2711,2713,2719,2729,2731,2741,2749,2753,2767,2777,2789,2791,2797,2801,2803,2819,2833,2837,2843,2851,2857,2861,2879,2887,2897,2903,2909,2917,2927,2939,2953,2957,2963,2969,2971,2999,3001,3011,3019,3023,3037,3041,3049,3061,3067,3079,3083,3089,3109,3119,3121,3137,3163,3167,3169,3181,3187,3191,3203,3209,3217,3221,3229,3251,3253,3257,3259,3271,3299,3301,3307,3313,3319,3323,3329,3331,3343,3347,3359,3361,3371,3373,3389,3391,3407,3413,3433,3449,3457,3461,3463,3467,3469,3491,3499,3511,3517,3527,3529,3533,3539,3541,3547,3557,3559,3571,3581,3583,3593,3607,3613,3617,3623,3631,3637,3643,3659,3671,3673,3677,3691,3697,3701,3709,3719,3727,3733,3739,3761,3767,3769,3779,3793,3797,3803,3821,3823,3833,3847,3851,3853,3863,3877,3881,3889,3907,3911,3917,3919,3923,3929,3931,3943,3947,3967,3989,4001,4003,4007,4013,4019,4021,4027,4049,4051,4057,4073,4079,4091,4093,4099,4111,4127,4129,4133,4139,4153,4157,4159,4177,4201,4211,4217,4219,4229,4231,4241,4243,4253,4259,4261,4271,4273,4283,4289,4297,4327,4337,4339,4349,4357,4363,4373,4391,4397,4409,4421,4423,4441,4447,4451,4457,4463,4481,4483,4493,4507,4513,4517,4519,4523,4547,4549,4561,4567,4583,4591,4597,4603,4621,4637,4639,4643,4649,4651,4657,4663,4673,4679,4691,4703,4721,4723,4729,4733,4751,4759,4783,4787,4789,4793,4799,4801,4813,4817,4831,4861,4871,4877,4889,4903,4909,4919,4931,4933,4937,4943,4951,4957,4967,4969,4973,4987,4993,4999,5003,5009,5011,5021,5023,5039,5051,5059,5077,5081,5087,5099,5101,5107,5113,5119,5147,5153,5167,5171,5179,5189,5197,5209,5227,5231,5233,5237,5261,5273,5279,5281,5297,5303,5309,5323,5333,5347,5351,5381,5387,5393,5399,5407,5413,5417,5419,5431,5437,5441,5443,5449,5471,5477,5479,5483,5501,5503,5507,5519,5521,5527,5531,5557,5563,5569,5573,5581,5591,5623,5639,5641,5647,5651,5653,5657,5659,5669,5683,5689,5693,5701,5711,5717,5737,5741,5743,5749,5779,5783,5791,5801,5807,5813,5821,5827,5839,5843,5849,5851,5857,5861,5867,5869,5879,5881,5897,5903,5923,5927,5939,5953,5981,5987,6007,6011,6029,6037,6043,6047,6053,6067,6073,6079,6089,6091,6101,6113,6121,6131,6133,6143,6151,6163,6173,6197,6199,6203,6211,6217,6221,6229,6247,6257,6263,6269,6271,6277,6287,6299,6301,6311,6317,6323,6329,6337,6343,6353,6359,6361,6367,6373,6379,6389,6397,6421,6427,6449,6451,6469,6473,6481,6491,6521,6529,6547,6551,6553,6563,6569,6571,6577,6581,6599,6607,6619,6637,6653,6659,6661,6673,6679,6689,6691,6701,6703,6709,6719,6733,6737,6761,6763,6779,6781,6791,6793,6803,6823,6827,6829,6833,6841,6857,6863,6869,6871,6883,6899,6907,6911,6917,6947,6949,6959,6961,6967,6971,6977,6983,6991,6997,7001,7013,7019,7027,7039,7043,7057,7069,7079,7103,7109,7121,7127,7129,7151,7159,7177,7187,7193,7207,7211,7213,7219,7229,7237,7243,7247,7253,7283,7297,7307,7309,7321,7331,7333,7349,7351,7369,7393,7411,7417,7433,7451,7457,7459,7477,7481,7487,7489,7499,7507,7517,7523,7529,7537,7541,7547,7549,7559,7561,7573,7577,7583,7589,7591,7603,7607,7621,7639,7643,7649,7669,7673,7681,7687,7691,7699,7703,7717,7723,7727,7741,7753,7757,7759,7789,7793,7817,7823,7829,7841,7853,7867,7873,7877,7879,7883,7901,7907,7919 | ||||
| 		}; | ||||
| 
 | ||||
| 		public static void SetRandom(Random random) | ||||
| 		{ | ||||
| 			Random = random; | ||||
| 		} | ||||
| 
 | ||||
| 		internal static int Next(int maxValue) | ||||
| 		{ | ||||
| 			return Random.Next(maxValue); | ||||
| 		} | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// A psuedorandom nonrepeating sequence of integers from 0 to n. | ||||
| 		/// </summary> | ||||
| 		internal static LinearCongruentialEnumerator LinearCongruentialSequence(int n) | ||||
| 		{ | ||||
| 			if (n == 0) | ||||
| 			{ | ||||
| 				// bail out, empty enumerator | ||||
| 				return new LinearCongruentialEnumerator(0, 0, 0); | ||||
| 			} | ||||
| 
 | ||||
| 			var x = Primes[Random.Next(Primes.Length - 1)]; | ||||
| 			while (x % n == 0) | ||||
| 			{ | ||||
| 				// not coprime, try again | ||||
| 				x = Primes[Random.Next(Primes.Length - 1)]; | ||||
| 			} | ||||
| 
 | ||||
| 			return new LinearCongruentialEnumerator(Random.Next(n), x, n); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	public struct LinearCongruentialEnumerator | ||||
| 	{ | ||||
| 		private readonly int start; | ||||
| 		private readonly int count; | ||||
| 		private readonly int prime; | ||||
| 		private int current; | ||||
| 
 | ||||
| 		public LinearCongruentialEnumerator GetEnumerator() => this; | ||||
| 
 | ||||
| 		[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 		internal LinearCongruentialEnumerator(int start, int prime, int count) | ||||
| 		{ | ||||
| 			current = start; | ||||
| 			this.start = start; | ||||
| 			this.prime = prime; | ||||
| 			this.count = count; | ||||
| 		} | ||||
| 
 | ||||
| 		[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 		public bool MoveNext() | ||||
| 		{ | ||||
| 			current += 1; | ||||
| 			if (current < start + count) | ||||
| 			{ | ||||
| 				return true; | ||||
| 			} | ||||
| 
 | ||||
| 			return false; | ||||
| 		} | ||||
| 
 | ||||
| 		public int Current | ||||
| 		{ | ||||
| 			[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 			get => (current * prime) % count; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -6,11 +6,13 @@ namespace MoonTools.ECS | |||
| { | ||||
| 	internal class RelationDepot | ||||
| 	{ | ||||
| 		private EntityStorage EntityStorage; | ||||
| 		private TypeIndices RelationTypeIndices; | ||||
| 		private RelationStorage[] storages = new RelationStorage[256]; | ||||
| 
 | ||||
| 		public RelationDepot(TypeIndices relationTypeIndices) | ||||
| 		public RelationDepot(EntityStorage entityStorage, TypeIndices relationTypeIndices) | ||||
| 		{ | ||||
| 			EntityStorage = entityStorage; | ||||
| 			RelationTypeIndices = relationTypeIndices; | ||||
| 		} | ||||
| 
 | ||||
|  | @ -123,26 +125,11 @@ namespace MoonTools.ECS | |||
| 			storages[relationTypeIndex].Set(entityA, entityB, relationData); | ||||
| 		} | ||||
| 
 | ||||
| 		public int GetStorageIndex(int relationTypeIndex, int entityA, int entityB) | ||||
| 		{ | ||||
| 			return storages[relationTypeIndex].GetStorageIndex(entityA, entityB); | ||||
| 		} | ||||
| 
 | ||||
| 		public unsafe void* Get(int relationTypeIndex, int relationStorageIndex) | ||||
| 		{ | ||||
| 			return storages[relationTypeIndex].Get(relationStorageIndex); | ||||
| 		} | ||||
| 
 | ||||
| 		public void UnrelateAll(int entityID, int relationTypeIndex) | ||||
| 		{ | ||||
| 			storages[relationTypeIndex].UnrelateAll(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		public ReverseSpanEnumerator<Entity> OutRelations(int entityID, int relationTypeIndex) | ||||
| 		{ | ||||
| 			return storages[relationTypeIndex].OutRelations(entityID); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Clear() | ||||
| 		{ | ||||
| 			for (var i = 0; i < storages.Length; i += 1) | ||||
|  | @ -174,5 +161,33 @@ namespace MoonTools.ECS | |||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public unsafe void TransferStorage(Dictionary<int, int> worldToTransferID, RelationDepot other) | ||||
| 		{ | ||||
| 			for (var i = 0; i < storages.Length; i += 1) | ||||
| 			{ | ||||
| 				if (storages[i] != null) | ||||
| 				{ | ||||
| 					foreach (var (a, b) in storages[i].All()) | ||||
| 					{ | ||||
| 						if (worldToTransferID.TryGetValue(a, out var otherA)) | ||||
| 						{ | ||||
| 							if (worldToTransferID.TryGetValue(b, out var otherB)) | ||||
| 							{ | ||||
| 								var storageIndex = storages[i].GetStorageIndex(a, b); | ||||
| 								var relationData = storages[i].Get(storageIndex); | ||||
| 								other.Set(otherA, otherB, i, relationData); | ||||
| 								other.EntityStorage.AddRelationKind(otherA, i); | ||||
| 								other.EntityStorage.AddRelationKind(otherB, i); | ||||
| 							} | ||||
| 							else | ||||
| 							{ | ||||
| 								throw new InvalidOperationException($"Missing transfer entity! {EntityStorage.Tag(a.ID)} related to {EntityStorage.Tag(b.ID)}"); | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,8 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Runtime.CompilerServices; | ||||
| using System.Runtime.InteropServices; | ||||
| using MoonTools.ECS.Collections; | ||||
| 
 | ||||
| namespace MoonTools.ECS | ||||
| { | ||||
|  | @ -9,26 +12,35 @@ namespace MoonTools.ECS | |||
| 		public abstract int GetStorageIndex(int entityA, int entityB); | ||||
| 		public abstract unsafe void* Get(int relationStorageIndex); | ||||
| 		public abstract void UnrelateAll(int entityID); | ||||
| 		public abstract ReverseSpanEnumerator<Entity> OutRelations(int entityID); | ||||
| 		public abstract ReverseSpanEnumerator<(Entity, Entity)> All(); | ||||
| 		public abstract RelationStorage CreateStorage(); | ||||
| 		public abstract void Clear(); | ||||
| 	} | ||||
| 
 | ||||
| 	// Relation is the two entities, A related to B. | ||||
| 	// TRelation is the data attached to the relation. | ||||
| 	internal class RelationStorage<TRelation> : RelationStorage where TRelation : unmanaged | ||||
| 	internal unsafe class RelationStorage<TRelation> : RelationStorage, IDisposable where TRelation : unmanaged | ||||
| 	{ | ||||
| 		private int count = 0; | ||||
| 		private int capacity = 16; | ||||
| 		private (Entity, Entity)* relations; | ||||
| 		private TRelation* relationDatas; | ||||
| 		private Dictionary<(Entity, Entity), int> indices = new Dictionary<(Entity, Entity), int>(16); | ||||
| 		private (Entity, Entity)[] relations = new (Entity, Entity)[16]; | ||||
| 		private TRelation[] relationDatas = new TRelation[16]; | ||||
| 		private Dictionary<int, IndexableSet<Entity>> outRelations = new Dictionary<int, IndexableSet<Entity>>(16); | ||||
| 		private Dictionary<int, IndexableSet<Entity>> inRelations = new Dictionary<int, IndexableSet<Entity>>(16); | ||||
| 		private Stack<IndexableSet<Entity>> listPool = new Stack<IndexableSet<Entity>>(); | ||||
| 
 | ||||
| 		public ReverseSpanEnumerator<(Entity, Entity)> All() | ||||
| 		private bool disposed; | ||||
| 
 | ||||
| 		public RelationStorage() | ||||
| 		{ | ||||
| 			return new ReverseSpanEnumerator<(Entity, Entity)>(new Span<(Entity, Entity)>(relations, 0, count)); | ||||
| 			relations = ((Entity, Entity)*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<(Entity, Entity)>())); | ||||
| 			relationDatas = (TRelation*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<TRelation>())); | ||||
| 		} | ||||
| 
 | ||||
| 		public override ReverseSpanEnumerator<(Entity, Entity)> All() | ||||
| 		{ | ||||
| 			return new ReverseSpanEnumerator<(Entity, Entity)>(new Span<(Entity, Entity)>(relations, count)); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Set(in Entity entityA, in Entity entityB, TRelation relationData) | ||||
|  | @ -56,10 +68,11 @@ namespace MoonTools.ECS | |||
| 			} | ||||
| 			inRelations[idB].Add(idA); | ||||
| 
 | ||||
| 			if (count >= relationDatas.Length) | ||||
| 			if (count >= capacity) | ||||
| 			{ | ||||
| 				Array.Resize(ref relations, relations.Length * 2); | ||||
| 				Array.Resize(ref relationDatas, relationDatas.Length * 2); | ||||
| 				capacity *= 2; | ||||
| 				relations = ((Entity, Entity)*) NativeMemory.Realloc(relations, (nuint) (capacity * Unsafe.SizeOf<(Entity, Entity)>())); | ||||
| 				relationDatas = (TRelation*) NativeMemory.Realloc(relationDatas, (nuint) (capacity * Unsafe.SizeOf<TRelation>())); | ||||
| 			} | ||||
| 
 | ||||
| 			relations[count] = relation; | ||||
|  | @ -78,7 +91,7 @@ namespace MoonTools.ECS | |||
| 			return indices.ContainsKey(relation); | ||||
| 		} | ||||
| 
 | ||||
| 		public override ReverseSpanEnumerator<Entity> OutRelations(int entityID) | ||||
| 		public ReverseSpanEnumerator<Entity> OutRelations(int entityID) | ||||
| 		{ | ||||
| 			if (outRelations.TryGetValue(entityID, out var entityOutRelations)) | ||||
| 			{ | ||||
|  | @ -219,7 +232,7 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 		public override unsafe void Set(int entityA, int entityB, void* relationData) | ||||
| 		{ | ||||
| 			Set(entityA, entityB, *((TRelation*) relationData)); | ||||
| 			Set(entityA, entityB, *(TRelation*) relationData); | ||||
| 		} | ||||
| 
 | ||||
| 		public override int GetStorageIndex(int entityA, int entityB) | ||||
|  | @ -229,10 +242,7 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 		public override unsafe void* Get(int relationStorageIndex) | ||||
| 		{ | ||||
| 			fixed (void* p = &relations[relationStorageIndex]) | ||||
| 			{ | ||||
| 				return p; | ||||
| 			} | ||||
| 			return &relationDatas[relationStorageIndex]; | ||||
| 		} | ||||
| 
 | ||||
| 		public override void UnrelateAll(int entityID) | ||||
|  | @ -282,5 +292,41 @@ namespace MoonTools.ECS | |||
| 			} | ||||
| 			outRelations.Clear(); | ||||
| 		} | ||||
| 
 | ||||
| 		protected virtual void Dispose(bool disposing) | ||||
| 		{ | ||||
| 			if (!disposed) | ||||
| 			{ | ||||
| 				Clear(); | ||||
| 
 | ||||
| 				if (disposing) | ||||
| 				{ | ||||
| 					foreach (var set in listPool) | ||||
| 					{ | ||||
| 						set.Dispose(); | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				NativeMemory.Free(relations); | ||||
| 				NativeMemory.Free(relationDatas); | ||||
| 				relations = null; | ||||
| 				relationDatas = null; | ||||
| 
 | ||||
| 				disposed = true; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		~RelationStorage() | ||||
| 		{ | ||||
| 			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||
| 			Dispose(disposing: false); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Dispose() | ||||
| 		{ | ||||
| 			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||||
| 			Dispose(disposing: true); | ||||
| 			GC.SuppressFinalize(this); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
							
								
								
									
										54
									
								
								src/World.cs
								
								
								
								
							
							
						
						
									
										54
									
								
								src/World.cs
								
								
								
								
							|  | @ -17,7 +17,7 @@ namespace MoonTools.ECS | |||
| 		public World() | ||||
| 		{ | ||||
| 			ComponentDepot = new ComponentDepot(ComponentTypeIndices); | ||||
| 			RelationDepot = new RelationDepot(RelationTypeIndices); | ||||
| 			RelationDepot = new RelationDepot(EntityStorage, RelationTypeIndices); | ||||
| 			FilterStorage = new FilterStorage(EntityStorage, ComponentTypeIndices); | ||||
| 		} | ||||
| 
 | ||||
|  | @ -54,14 +54,11 @@ namespace MoonTools.ECS | |||
| 		} | ||||
| 
 | ||||
| 		// untyped version for Transfer | ||||
| 		// no filter check because filter state is copied directly | ||||
| 		internal unsafe void Set(Entity entity, int componentTypeIndex, void* component) | ||||
| 		{ | ||||
| 			ComponentDepot.Set(entity.ID, componentTypeIndex, component); | ||||
| 
 | ||||
| 			if (EntityStorage.SetComponent(entity.ID, componentTypeIndex)) | ||||
| 			{ | ||||
| 				FilterStorage.Check(entity.ID, componentTypeIndex); | ||||
| 			} | ||||
| 			EntityStorage.SetComponent(entity.ID, componentTypeIndex); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Remove<TComponent>(in Entity entity) where TComponent : unmanaged | ||||
|  | @ -82,14 +79,6 @@ namespace MoonTools.ECS | |||
| 			EntityStorage.AddRelationKind(entityB.ID, relationTypeIndex); | ||||
| 		} | ||||
| 
 | ||||
| 		// untyped version for Transfer | ||||
| 		internal unsafe void Relate(Entity entityA, Entity entityB, int relationTypeIndex, void* relationData) | ||||
| 		{ | ||||
| 			RelationDepot.Set(entityA, entityB, relationTypeIndex, relationData); | ||||
| 			EntityStorage.AddRelationKind(entityA.ID, relationTypeIndex); | ||||
| 			EntityStorage.AddRelationKind(entityB.ID, relationTypeIndex); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Unrelate<TRelationKind>(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged | ||||
| 		{ | ||||
| 			var (aEmpty, bEmpty) = RelationDepot.Remove<TRelationKind>(entityA, entityB); | ||||
|  | @ -156,6 +145,14 @@ namespace MoonTools.ECS | |||
| 
 | ||||
| 		private Dictionary<int, int> WorldToTransferID = new Dictionary<int, int>(); | ||||
| 
 | ||||
| 		/// <summary> | ||||
| 		/// If you are using the World Transfer feature, call this once after your systems/filters have all been initialized. | ||||
| 		/// </summary> | ||||
| 		public void PrepareTransferTo(World other) | ||||
| 		{ | ||||
| 			other.FilterStorage.CreateMissingStorages(FilterStorage); | ||||
| 		} | ||||
| 
 | ||||
| 		// FIXME: there's probably a better way to handle Filters so they are not world-bound | ||||
| 		public unsafe void Transfer(World other, Filter filter, Filter otherFilter) | ||||
| 		{ | ||||
|  | @ -176,30 +173,8 @@ namespace MoonTools.ECS | |||
| 				WorldToTransferID.Add(entity.ID, otherWorldEntity.ID); | ||||
| 			} | ||||
| 
 | ||||
| 			// set relations before components so filters don't freak out | ||||
| 			foreach (var entity in filter.Entities) | ||||
| 			{ | ||||
| 				var otherWorldEntityA = WorldToTransferID[entity.ID]; | ||||
| 
 | ||||
| 				foreach (var relationTypeIndex in EntityStorage.RelationTypeIndices(entity.ID)) | ||||
| 				{ | ||||
| 					foreach (var entityB in RelationDepot.OutRelations(entity.ID, relationTypeIndex)) | ||||
| 					{ | ||||
| 						var storageIndex = RelationDepot.GetStorageIndex(relationTypeIndex, entity.ID, entityB); | ||||
| 
 | ||||
| 						int otherWorldEntityB; | ||||
| 						if (WorldToTransferID.TryGetValue(entityB, out otherWorldEntityB)) | ||||
| 						{ | ||||
| 							other.Relate(otherWorldEntityA, otherWorldEntityB, relationTypeIndex, RelationDepot.Get(relationTypeIndex, storageIndex)); | ||||
| 						} | ||||
| 						else | ||||
| 						{ | ||||
| 							// related entity is not in the filter | ||||
| 							throw new Exception($"Missing transfer entity! {EntityStorage.Tag(entity.ID)} related to {EntityStorage.Tag(entityB.ID)}"); | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			// transfer relations | ||||
| 			RelationDepot.TransferStorage(WorldToTransferID, other.RelationDepot); | ||||
| 
 | ||||
| 			// set components | ||||
| 			foreach (var entity in filter.Entities) | ||||
|  | @ -211,6 +186,9 @@ namespace MoonTools.ECS | |||
| 					other.Set(otherWorldEntity, componentTypeIndex, ComponentDepot.UntypedGet(entity.ID, componentTypeIndex)); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			// transfer filters last so callbacks trigger correctly | ||||
| 			FilterStorage.TransferStorage(WorldToTransferID, other.FilterStorage); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue