From 453426a232707aa2db175327a39f67f799dda03d Mon Sep 17 00:00:00 2001
From: cosmonaut <evan@moonside.games>
Date: Tue, 9 Aug 2022 14:41:31 -0700
Subject: [PATCH] rename and add new relation lookup methods

---
 src/ComponentDepot.cs        |   3 +
 src/ComponentStorage.cs      |   8 ++-
 src/EntityComponentReader.cs |  40 +++++++++--
 src/IndexableSet.cs          |   5 ++
 src/RelationDepot.cs         |  45 ++++++++++--
 src/RelationStorage.cs       | 130 ++++++++++++++++++++++++-----------
 src/System.cs                |   5 ++
 7 files changed, 187 insertions(+), 49 deletions(-)

diff --git a/src/ComponentDepot.cs b/src/ComponentDepot.cs
index a5ada46..c6eca73 100644
--- a/src/ComponentDepot.cs
+++ b/src/ComponentDepot.cs
@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Runtime.CompilerServices;
 
 namespace MoonTools.ECS
 {
@@ -17,6 +18,7 @@ namespace MoonTools.ECS
 
 		private HashSet<Type> TypesWithDisabledSerialization = new HashSet<Type>();
 
+		[MethodImpl(MethodImplOptions.AggressiveInlining)]
 		internal void Register<TComponent>() where TComponent : unmanaged
 		{
 			if (!storages.ContainsKey(typeof(TComponent)))
@@ -33,6 +35,7 @@ namespace MoonTools.ECS
 			return storages[type];
 		}
 
+		[MethodImpl(MethodImplOptions.AggressiveInlining)]
 		private ComponentStorage<TComponent> Lookup<TComponent>() where TComponent : unmanaged
 		{
 			// TODO: is it possible to optimize this?
diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs
index c7d256b..8324558 100644
--- a/src/ComponentStorage.cs
+++ b/src/ComponentStorage.cs
@@ -46,7 +46,7 @@ namespace MoonTools.ECS
 #if DEBUG
 			if (nextID == 0)
 			{
-				throw new ArgumentOutOfRangeException("Component storage is empty!");
+				throw new IndexOutOfRangeException("Component storage is empty!");
 			}
 #endif
 			return ref components[0];
@@ -119,6 +119,12 @@ namespace MoonTools.ECS
 
 		public Entity FirstEntity()
 		{
+#if DEBUG
+			if (nextID == 0)
+			{
+				throw new IndexOutOfRangeException("Component storage is empty!");
+			}
+#endif
 			return new Entity(entityIDs[0]);
 		}
 
diff --git a/src/EntityComponentReader.cs b/src/EntityComponentReader.cs
index 46b49f9..81b2c76 100644
--- a/src/EntityComponentReader.cs
+++ b/src/EntityComponentReader.cs
@@ -61,14 +61,46 @@ namespace MoonTools.ECS
 			return RelationDepot.Related<TRelationKind>(a.ID, b.ID);
 		}
 
-		protected IEnumerable<(Entity, TRelationKind)> RelatedToA<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
+		// relations go A->B, so given A, will give all outgoing B relations.
+		protected IEnumerable<(Entity, TRelationKind)> OutRelations<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
 		{
-			return RelationDepot.RelatedToA<TRelationKind>(entity.ID);
+			return RelationDepot.OutRelations<TRelationKind>(entity.ID);
 		}
 
-		protected IEnumerable<(Entity, TRelationKind)> RelatedToB<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
+		protected (Entity, TRelationKind) OutRelationSingleton<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
 		{
-			return RelationDepot.RelatedToB<TRelationKind>(entity.ID);
+			return RelationDepot.OutRelationSingleton<TRelationKind>(entity.ID);
+		}
+
+		protected bool HasOutRelation<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
+		{
+			return RelationDepot.HasOutRelation<TRelationKind>(entity.ID);
+		}
+
+		protected int OutRelationCount<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
+		{
+			return RelationDepot.OutRelationCount<TRelationKind>(entity.ID);
+		}
+
+		// Relations go A->B, so given B, will give all incoming A relations.
+		protected IEnumerable<(Entity, TRelationKind)> InRelations<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
+		{
+			return RelationDepot.InRelations<TRelationKind>(entity.ID);
+		}
+
+		protected (Entity, TRelationKind) InRelationSingleton<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
+		{
+			return RelationDepot.InRelationSingleton<TRelationKind>(entity.ID);
+		}
+
+		protected bool HasInRelation<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
+		{
+			return RelationDepot.HasInRelation<TRelationKind>(entity.ID);
+		}
+
+		protected int InRelationCount<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
+		{
+			return RelationDepot.InRelationCount<TRelationKind>(entity.ID);
 		}
 	}
 }
diff --git a/src/IndexableSet.cs b/src/IndexableSet.cs
index ea54652..bacb429 100644
--- a/src/IndexableSet.cs
+++ b/src/IndexableSet.cs
@@ -80,6 +80,11 @@ namespace MoonTools.ECS
 			}
 		}
 
+		public void Clear()
+		{
+			Count = 0;
+		}
+
 		public void Save(IndexableSetState<T> state)
 		{
 			ReadOnlySpan<byte> arrayBytes = MemoryMarshal.Cast<T, byte>(array);
diff --git a/src/RelationDepot.cs b/src/RelationDepot.cs
index c3a65b3..4b8652c 100644
--- a/src/RelationDepot.cs
+++ b/src/RelationDepot.cs
@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Runtime.CompilerServices;
 
 namespace MoonTools.ECS
 {
@@ -15,6 +16,7 @@ namespace MoonTools.ECS
 			}
 		}
 
+		[MethodImpl(MethodImplOptions.AggressiveInlining)]
 		private RelationStorage<TRelationKind> Lookup<TRelationKind>() where TRelationKind : unmanaged
 		{
 			Register<TRelationKind>();
@@ -31,6 +33,11 @@ namespace MoonTools.ECS
 			Lookup<TRelationKind>().Remove(relation);
 		}
 
+		public void UnrelateAll<TRelationKind>(int entityID) where TRelationKind : unmanaged
+		{
+			Lookup<TRelationKind>().UnrelateAll(entityID);
+		}
+
 		// FIXME: optimize this
 		public void OnEntityDestroy(int entityID)
 		{
@@ -50,14 +57,44 @@ namespace MoonTools.ECS
 			return Lookup<TRelationKind>().Has(new Relation(idA, idB));
 		}
 
-		public IEnumerable<(Entity, TRelationKind)> RelatedToA<TRelationKind>(int entityID) where TRelationKind : unmanaged
+		public IEnumerable<(Entity, TRelationKind)> OutRelations<TRelationKind>(int entityID) where TRelationKind : unmanaged
 		{
-			return Lookup<TRelationKind>().RelatedToA(entityID);
+			return Lookup<TRelationKind>().OutRelations(entityID);
 		}
 
-		public IEnumerable<(Entity, TRelationKind)> RelatedToB<TRelationKind>(int entityID) where TRelationKind : unmanaged
+		public (Entity, TRelationKind) OutRelationSingleton<TRelationKind>(int entityID) where TRelationKind : unmanaged
 		{
-			return Lookup<TRelationKind>().RelatedToB(entityID);
+			return Lookup<TRelationKind>().OutFirst(entityID);
+		}
+
+		public int OutRelationCount<TRelationKind>(int entityID) where TRelationKind : unmanaged
+		{
+			return Lookup<TRelationKind>().OutRelationCount(entityID);
+		}
+
+		public bool HasOutRelation<TRelationKind>(int entityID) where TRelationKind : unmanaged
+		{
+			return Lookup<TRelationKind>().HasOutRelation(entityID);
+		}
+
+		public IEnumerable<(Entity, TRelationKind)> InRelations<TRelationKind>(int entityID) where TRelationKind : unmanaged
+		{
+			return Lookup<TRelationKind>().InRelations(entityID);
+		}
+
+		public (Entity, TRelationKind) InRelationSingleton<TRelationKind>(int entityID) where TRelationKind : unmanaged
+		{
+			return Lookup<TRelationKind>().InFirst(entityID);
+		}
+
+		public bool HasInRelation<TRelationKind>(int entityID) where TRelationKind : unmanaged
+		{
+			return Lookup<TRelationKind>().HasInRelation(entityID);
+		}
+
+		public int InRelationCount<TRelationKind>(int entityID) where TRelationKind : unmanaged
+		{
+			return Lookup<TRelationKind>().InRelationCount(entityID);
 		}
 
 		public void Save(RelationDepotState state)
diff --git a/src/RelationStorage.cs b/src/RelationStorage.cs
index 7abf778..bf86f9d 100644
--- a/src/RelationStorage.cs
+++ b/src/RelationStorage.cs
@@ -20,9 +20,9 @@ namespace MoonTools.ECS
 		private Dictionary<Relation, int> indices = new Dictionary<Relation, int>(16);
 		private Relation[] relations = new Relation[16];
 		private TRelation[] relationDatas = new TRelation[16];
-		private Dictionary<int, HashSet<int>> entitiesRelatedToA = new Dictionary<int, HashSet<int>>(16);
-		private Dictionary<int, HashSet<int>> entitiesRelatedToB = new Dictionary<int, HashSet<int>>(16);
-		private Stack<HashSet<int>> listPool = new Stack<HashSet<int>>();
+		private Dictionary<int, IndexableSet<int>> outRelations = new Dictionary<int, IndexableSet<int>>(16);
+		private Dictionary<int, IndexableSet<int>> inRelations = new Dictionary<int, IndexableSet<int>>(16);
+		private Stack<IndexableSet<int>> listPool = new Stack<IndexableSet<int>>();
 
 		public IEnumerable<(Entity, Entity, TRelation)> All()
 		{
@@ -45,17 +45,17 @@ namespace MoonTools.ECS
 			var idA = relation.A.ID;
 			var idB = relation.B.ID;
 
-			if (!entitiesRelatedToA.ContainsKey(idA))
+			if (!outRelations.ContainsKey(idA))
 			{
-				entitiesRelatedToA[idA] = AcquireHashSetFromPool();
+				outRelations[idA] = AcquireHashSetFromPool();
 			}
-			entitiesRelatedToA[idA].Add(idB);
+			outRelations[idA].Add(idB);
 
-			if (!entitiesRelatedToB.ContainsKey(idB))
+			if (!inRelations.ContainsKey(idB))
 			{
-				entitiesRelatedToB[idB] = AcquireHashSetFromPool();
+				inRelations[idB] = AcquireHashSetFromPool();
 			}
-			entitiesRelatedToB[idB].Add(idA);
+			inRelations[idB].Add(idA);
 
 			if (count >= relationDatas.Length)
 			{
@@ -74,12 +74,12 @@ namespace MoonTools.ECS
 			return indices.ContainsKey(relation);
 		}
 
-		// FIXME: is there a more descriptive name for these?
-		public IEnumerable<(Entity, TRelation)> RelatedToA(int entityID)
+		// FIXME: creating the new Relation in here is slightly deranged
+		public IEnumerable<(Entity, TRelation)> OutRelations(int entityID)
 		{
-			if (entitiesRelatedToA.ContainsKey(entityID))
+			if (outRelations.ContainsKey(entityID))
 			{
-				foreach (var id in entitiesRelatedToA[entityID])
+				foreach (var id in outRelations[entityID])
 				{
 					var relation = new Relation(entityID, id);
 					yield return (relation.B, relationDatas[indices[relation]]);
@@ -87,11 +87,33 @@ namespace MoonTools.ECS
 			}
 		}
 
-		public IEnumerable<(Entity, TRelation)> RelatedToB(int entityID)
+		public (Entity, TRelation) OutFirst(int entityID)
 		{
-			if (entitiesRelatedToB.ContainsKey(entityID))
+#if DEBUG
+			if (!outRelations.ContainsKey(entityID))
 			{
-				foreach (var id in entitiesRelatedToB[entityID])
+				throw new KeyNotFoundException("No out relations to this entity!");
+			}
+#endif
+			var relation = new Relation(entityID, outRelations[entityID][0]);
+			return (relation.B, relationDatas[indices[relation]]);
+		}
+
+		public bool HasOutRelation(int entityID)
+		{
+			return outRelations.ContainsKey(entityID) && outRelations[entityID].Count > 0;
+		}
+
+		public int OutRelationCount(int entityID)
+		{
+			return outRelations.ContainsKey(entityID) ? outRelations[entityID].Count : 0;
+		}
+
+		public IEnumerable<(Entity, TRelation)> InRelations(int entityID)
+		{
+			if (inRelations.ContainsKey(entityID))
+			{
+				foreach (var id in inRelations[entityID])
 				{
 					var relation = new Relation(id, entityID);
 					yield return (relation.A, relationDatas[indices[relation]]);
@@ -99,16 +121,39 @@ namespace MoonTools.ECS
 			}
 		}
 
+		public (Entity, TRelation) InFirst(int entityID)
+		{
+#if DEBUG
+			if (!inRelations.ContainsKey(entityID))
+			{
+				throw new KeyNotFoundException("No out relations to this entity!");
+			}
+#endif
+
+			var relation = new Relation(inRelations[entityID][0], entityID);
+			return (relation.A, relationDatas[indices[relation]]);
+		}
+
+		public bool HasInRelation(int entityID)
+		{
+			return inRelations.ContainsKey(entityID) && inRelations[entityID].Count > 0;
+		}
+
+		public int InRelationCount(int entityID)
+		{
+			return inRelations.ContainsKey(entityID) ? inRelations[entityID].Count : 0;
+		}
+
 		public bool Remove(Relation relation)
 		{
-			if (entitiesRelatedToA.ContainsKey(relation.A.ID))
+			if (outRelations.ContainsKey(relation.A.ID))
 			{
-				entitiesRelatedToA[relation.A.ID].Remove(relation.B.ID);
+				outRelations[relation.A.ID].Remove(relation.B.ID);
 			}
 
-			if (entitiesRelatedToB.ContainsKey(relation.B.ID))
+			if (inRelations.ContainsKey(relation.B.ID))
 			{
-				entitiesRelatedToB[relation.B.ID].Remove(relation.A.ID);
+				inRelations[relation.B.ID].Remove(relation.A.ID);
 			}
 
 			if (indices.ContainsKey(relation))
@@ -133,42 +178,47 @@ namespace MoonTools.ECS
 			return false;
 		}
 
-		public override void OnEntityDestroy(int entityID)
+		public void UnrelateAll(int entityID)
 		{
-			if (entitiesRelatedToA.ContainsKey(entityID))
+			if (outRelations.ContainsKey(entityID))
 			{
-				foreach (var entityB in entitiesRelatedToA[entityID])
+				foreach (var entityB in outRelations[entityID])
 				{
 					Remove(new Relation(entityID, entityB));
 				}
 
-				ReturnHashSetToPool(entitiesRelatedToA[entityID]);
-				entitiesRelatedToA.Remove(entityID);
+				ReturnHashSetToPool(outRelations[entityID]);
+				outRelations.Remove(entityID);
 			}
 
-			if (entitiesRelatedToB.ContainsKey(entityID))
+			if (inRelations.ContainsKey(entityID))
 			{
-				foreach (var entityA in entitiesRelatedToB[entityID])
+				foreach (var entityA in inRelations[entityID])
 				{
 					Remove(new Relation(entityA, entityID));
 				}
 
-				ReturnHashSetToPool(entitiesRelatedToB[entityID]);
-				entitiesRelatedToB.Remove(entityID);
+				ReturnHashSetToPool(inRelations[entityID]);
+				inRelations.Remove(entityID);
 			}
 		}
 
-		private HashSet<int> AcquireHashSetFromPool()
+		public override void OnEntityDestroy(int entityID)
+		{
+			UnrelateAll(entityID);
+		}
+
+		private IndexableSet<int> AcquireHashSetFromPool()
 		{
 			if (listPool.Count == 0)
 			{
-				listPool.Push(new HashSet<int>());
+				listPool.Push(new IndexableSet<int>());
 			}
 
 			return listPool.Pop();
 		}
 
-		private void ReturnHashSetToPool(HashSet<int> hashSet)
+		private void ReturnHashSetToPool(IndexableSet<int> hashSet)
 		{
 			hashSet.Clear();
 			listPool.Push(hashSet);
@@ -206,24 +256,24 @@ namespace MoonTools.ECS
 			state.RelationDatas.CopyTo(MemoryMarshal.Cast<TRelation, byte>(relationDatas));
 
 			indices.Clear();
-			entitiesRelatedToA.Clear();
-			entitiesRelatedToB.Clear();
+			outRelations.Clear();
+			inRelations.Clear();
 			for (var i = 0; i < state.Count; i += 1)
 			{
 				var relation = relations[i];
 				indices[relation] = i;
 
-				if (!entitiesRelatedToA.ContainsKey(relation.A.ID))
+				if (!outRelations.ContainsKey(relation.A.ID))
 				{
-					entitiesRelatedToA[relation.A.ID] = AcquireHashSetFromPool();
+					outRelations[relation.A.ID] = AcquireHashSetFromPool();
 				}
-				entitiesRelatedToA[relation.A.ID].Add(relation.B.ID);
+				outRelations[relation.A.ID].Add(relation.B.ID);
 
-				if (!entitiesRelatedToB.ContainsKey(relation.B.ID))
+				if (!inRelations.ContainsKey(relation.B.ID))
 				{
-					entitiesRelatedToB[relation.B.ID] = AcquireHashSetFromPool();
+					inRelations[relation.B.ID] = AcquireHashSetFromPool();
 				}
-				entitiesRelatedToB[relation.B.ID].Add(relation.A.ID);
+				inRelations[relation.B.ID].Add(relation.A.ID);
 			}
 
 			count = state.Count;
diff --git a/src/System.cs b/src/System.cs
index 1fd33d4..1a993ec 100644
--- a/src/System.cs
+++ b/src/System.cs
@@ -78,6 +78,11 @@ namespace MoonTools.ECS
 			RelationDepot.Remove<TRelationKind>(new Relation(entityA, entityB));
 		}
 
+		protected void UnrelateAll<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
+		{
+			RelationDepot.UnrelateAll<TRelationKind>(entity.ID);
+		}
+
 		// FIXME: this is insanely inefficient
 		protected void Destroy(in Entity entity)
 		{