From 4ef7cb4302262df0cfc2e8166bda1abd1a27f762 Mon Sep 17 00:00:00 2001
From: cosmonaut <evan@moonside.games>
Date: Fri, 13 Oct 2023 20:42:22 +0000
Subject: [PATCH] Determinism fixes (#5)

- Making sure that transferring from one world to another and back preserves original order of filters and relations
- Provides a MoonTools.ECS.Random class
- MoonTools.ECS.RandomManager class can be used to synchronize your Random instance with the ECS randomness
- Refactors some collections to use NativeMemory for performance and to ensure memory safety on world transfer
- Exposes some internal collections to public

Reviewed-on: https://gitea.moonside.games/MoonsideGames/MoonTools.ECS/pulls/5
---
 src/ComponentDepot.cs   |   2 +-
 src/ComponentStorage.cs |  76 +++++++++----
 src/DebugSystem.cs      |  29 ++++-
 src/DynamicArray.cs     |  65 +++++++----
 src/EntityStorage.cs    |  13 +--
 src/Filter.cs           |   3 +-
 src/FilterBuilder.cs    |  13 ++-
 src/FilterSignature.cs  |   9 +-
 src/FilterStorage.cs    |  76 +++++++++++--
 src/IndexableSet.cs     |  93 ++++++++--------
 src/MessageStorage.cs   |  52 +++++++--
 src/Random.cs           | 234 +++++++++++++++++++++++++++++++---------
 src/RandomManager.cs    |  82 ++++++++++++++
 src/RelationDepot.cs    |  47 +++++---
 src/RelationStorage.cs  |  76 ++++++++++---
 src/World.cs            |  54 +++-------
 16 files changed, 674 insertions(+), 250 deletions(-)
 create mode 100644 src/RandomManager.cs

diff --git a/src/ComponentDepot.cs b/src/ComponentDepot.cs
index 8a6b37f..cdd4d5f 100644
--- a/src/ComponentDepot.cs
+++ b/src/ComponentDepot.cs
@@ -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)
 		{
diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs
index 41117bc..bc71ef7 100644
--- a/src/ComponentStorage.cs
+++ b/src/ComponentStorage.cs
@@ -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
 	}
 }
diff --git a/src/DebugSystem.cs b/src/DebugSystem.cs
index de9027a..fcffd9e 100644
--- a/src/DebugSystem.cs
+++ b/src/DebugSystem.cs
@@ -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
diff --git a/src/DynamicArray.cs b/src/DynamicArray.cs
index c16b697..08400fc 100644
--- a/src/DynamicArray.cs
+++ b/src/DynamicArray.cs
@@ -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);
 		}
 	}
 }
diff --git a/src/EntityStorage.cs b/src/EntityStorage.cs
index b73ad05..3ff05de 100644
--- a/src/EntityStorage.cs
+++ b/src/EntityStorage.cs
@@ -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];
 		}
diff --git a/src/Filter.cs b/src/Filter.cs
index f841c39..94f5ee9 100644
--- a/src/Filter.cs
+++ b/src/Filter.cs
@@ -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);
diff --git a/src/FilterBuilder.cs b/src/FilterBuilder.cs
index ed1562e..8a8a6d1 100644
--- a/src/FilterBuilder.cs
+++ b/src/FilterBuilder.cs
@@ -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;
diff --git a/src/FilterSignature.cs b/src/FilterSignature.cs
index 3877391..7e94922 100644
--- a/src/FilterSignature.cs
+++ b/src/FilterSignature.cs
@@ -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))
diff --git a/src/FilterStorage.cs b/src/FilterStorage.cs
index aad179b..7eb11f9 100644
--- a/src/FilterStorage.cs
+++ b/src/FilterStorage.cs
@@ -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()
 		{
diff --git a/src/IndexableSet.cs b/src/IndexableSet.cs
index 1a79f71..1956756 100644
--- a/src/IndexableSet.cs
+++ b/src/IndexableSet.cs
@@ -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);
 		}
 	}
 }
diff --git a/src/MessageStorage.cs b/src/MessageStorage.cs
index b2f29d0..c5091a6 100644
--- a/src/MessageStorage.cs
+++ b/src/MessageStorage.cs
@@ -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);
+		}
 	}
 }
diff --git a/src/Random.cs b/src/Random.cs
index acb62bb..6f8b258 100644
--- a/src/Random.cs
+++ b/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;
 		}
 	}
 }
diff --git a/src/RandomManager.cs b/src/RandomManager.cs
new file mode 100644
index 0000000..cf90d75
--- /dev/null
+++ b/src/RandomManager.cs
@@ -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;
+		}
+	}
+}
diff --git a/src/RelationDepot.cs b/src/RelationDepot.cs
index c22493b..7963a6c 100644
--- a/src/RelationDepot.cs
+++ b/src/RelationDepot.cs
@@ -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)}");
+							}
+						}
+					}
+				}
+			}
+		}
 	}
 }
diff --git a/src/RelationStorage.cs b/src/RelationStorage.cs
index 009ffe3..50b4e43 100644
--- a/src/RelationStorage.cs
+++ b/src/RelationStorage.cs
@@ -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);
+		}
 	}
 }
diff --git a/src/World.cs b/src/World.cs
index 79fa68f..9c14ad4 100644
--- a/src/World.cs
+++ b/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);
 		}
 	}
 }