From 3d2261f7394c3c1cd77eaf89233d89676cc0c380 Mon Sep 17 00:00:00 2001
From: cosmonaut <evan@moonside.games>
Date: Tue, 31 Oct 2023 11:10:42 -0700
Subject: [PATCH] messages + rearranging

---
 src/{ => Collections}/IndexableSet.cs |  15 +
 src/Collections/NativeArray.cs        | 110 +++
 src/Collections/NativeArrayUntyped.cs | 128 ++++
 src/ComponentDepot.cs                 |   2 +-
 src/ComponentStorage.cs               |   4 +-
 src/MessageDepot.cs                   |   2 +-
 src/MessageStorage.cs                 |   4 +-
 src/NativeArray.cs                    | 111 ---
 src/Rev2/Archetype.cs                 |   4 +-
 src/Rev2/ArchetypeEdge.cs             |   7 +-
 src/Rev2/ArchetypeSignature.cs        | 159 +++--
 src/Rev2/Column.cs                    | 129 ----
 src/Rev2/Filter.cs                    | 361 +++++-----
 src/Rev2/FilterBuilder.cs             |  67 +-
 src/Rev2/HasId.cs                     |   6 -
 src/Rev2/MessageStorage.cs            |  63 ++
 src/Rev2/Record.cs                    |   7 +-
 src/Rev2/RelationStorage.cs           |   8 +-
 src/Rev2/Snapshot.cs                  |  14 +-
 src/Rev2/World.cs                     | 926 ++++++++++++++------------
 src/System.cs                         |   2 +-
 src/World.cs                          |   5 +
 22 files changed, 1124 insertions(+), 1010 deletions(-)
 rename src/{ => Collections}/IndexableSet.cs (88%)
 create mode 100644 src/Collections/NativeArray.cs
 create mode 100644 src/Collections/NativeArrayUntyped.cs
 delete mode 100644 src/NativeArray.cs
 delete mode 100644 src/Rev2/Column.cs
 delete mode 100644 src/Rev2/HasId.cs
 create mode 100644 src/Rev2/MessageStorage.cs

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