snapshot and restore relations
							parent
							
								
									4d40103a6f
								
							
						
					
					
						commit
						9032eff699
					
				|  | @ -7,7 +7,7 @@ namespace MoonTools.ECS.Rev2 | |||
| 	{ | ||||
| 		public static ArchetypeSignature Empty = new ArchetypeSignature(0); | ||||
| 
 | ||||
| 		List<ulong> Ids; | ||||
| 		List<uint> Ids; | ||||
| 
 | ||||
| 		public int Count => Ids.Count; | ||||
| 
 | ||||
|  | @ -15,12 +15,12 @@ namespace MoonTools.ECS.Rev2 | |||
| 
 | ||||
| 		public ArchetypeSignature() | ||||
| 		{ | ||||
| 			Ids = new List<ulong>(); | ||||
| 			Ids = new List<uint>(); | ||||
| 		} | ||||
| 
 | ||||
| 		public ArchetypeSignature(int capacity) | ||||
| 		{ | ||||
| 			Ids = new List<ulong>(capacity); | ||||
| 			Ids = new List<uint>(capacity); | ||||
| 		} | ||||
| 
 | ||||
| 		// Maintains sorted order | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ namespace MoonTools.ECS.Rev2 | |||
| 		public int Count; | ||||
| 
 | ||||
| 		private int Capacity; | ||||
| 		private readonly int ElementSize; | ||||
| 		public readonly int ElementSize; | ||||
| 
 | ||||
| 		private bool IsDisposed; | ||||
| 
 | ||||
|  | @ -23,6 +23,11 @@ namespace MoonTools.ECS.Rev2 | |||
| 			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 | ||||
| 		{ | ||||
|  |  | |||
|  | @ -24,13 +24,15 @@ namespace MoonTools.ECS.Rev2 | |||
| 
 | ||||
| 		public FilterBuilder Include<T>() where T : unmanaged | ||||
| 		{ | ||||
| 			Included.Add(World.TypeToComponentId[typeof(T)]); | ||||
| 			World.TryRegisterTypeId<T>(); | ||||
| 			Included.Add(World.TypeToId[typeof(T)]); | ||||
| 			return new FilterBuilder(World, Included, Excluded); | ||||
| 		} | ||||
| 
 | ||||
| 		public FilterBuilder Exclude<T>() where T : unmanaged | ||||
| 		{ | ||||
| 			Excluded.Add(World.TypeToComponentId[typeof(T)]); | ||||
| 			World.TryRegisterTypeId<T>(); | ||||
| 			Excluded.Add(World.TypeToId[typeof(T)]); | ||||
| 			return new FilterBuilder(World, Included, Excluded); | ||||
| 		} | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,27 +2,8 @@ | |||
| 
 | ||||
| namespace MoonTools.ECS.Rev2; | ||||
| 
 | ||||
| public readonly record struct Id : IComparable<Id> | ||||
| public readonly record struct Id(uint Value) : IComparable<Id> | ||||
| { | ||||
| 	public readonly ulong Value; | ||||
| 
 | ||||
| 	private const ulong HI = 0xFFFFFFFF00000000; | ||||
| 	private const ulong LO = 0xFFFFFFFF; | ||||
| 
 | ||||
| 	public bool IsPair => (HI & Value) != 0; | ||||
| 	public uint Low => (uint)(LO & Value); | ||||
| 	public uint High => (uint)((HI & Value) >>> 32); | ||||
| 
 | ||||
| 	public Id(ulong value) | ||||
| 	{ | ||||
| 		Value = value; | ||||
| 	} | ||||
| 
 | ||||
| 	public Id(uint relation, uint target) | ||||
| 	{ | ||||
| 		Value = (relation << 31) | target; | ||||
| 	} | ||||
| 
 | ||||
| 	public int CompareTo(Id other) | ||||
| 	{ | ||||
| 		return Value.CompareTo(other.Value); | ||||
|  |  | |||
|  | @ -4,8 +4,8 @@ namespace MoonTools.ECS.Rev2; | |||
| 
 | ||||
| internal class IdAssigner | ||||
| { | ||||
| 	ulong Next; | ||||
| 	NativeArray<ulong> AvailableIds = new NativeArray<ulong>(); | ||||
| 	uint Next; | ||||
| 	NativeArray<uint> AvailableIds = new NativeArray<uint>(); | ||||
| 
 | ||||
| 	public Id Assign() | ||||
| 	{ | ||||
|  |  | |||
|  | @ -5,28 +5,27 @@ using MoonTools.ECS.Collections; | |||
| 
 | ||||
| namespace MoonTools.ECS.Rev2; | ||||
| 
 | ||||
| internal class Relation | ||||
| // TODO: implement this entire class with NativeMemory equivalents, can just memcpy for snapshots | ||||
| internal class RelationStorage | ||||
| { | ||||
| 	private int count = 0; | ||||
| 	private int capacity = 16; | ||||
| 	private Column relations; | ||||
| 	private Column relationDatas; | ||||
| 	private Dictionary<(Id, Id), int> indices = new Dictionary<(Id, Id), int>(16); | ||||
| 	private Dictionary<Id, IndexableSet<Id>> outRelations = new Dictionary<Id, IndexableSet<Id>>(16); | ||||
| 	private Dictionary<Id, IndexableSet<Id>> inRelations = new Dictionary<Id, IndexableSet<Id>>(16); | ||||
| 	internal Column relations; | ||||
| 	internal Column 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); | ||||
| 	private Stack<IndexableSet<Id>> listPool = new Stack<IndexableSet<Id>>(); | ||||
| 
 | ||||
| 	private bool disposed; | ||||
| 
 | ||||
| 	public Relation(int relationDataSize) | ||||
| 	public RelationStorage(int relationDataSize) | ||||
| 	{ | ||||
| 		relations = new Column(Unsafe.SizeOf<(Id, Id)>()); | ||||
| 		relationDatas = new Column(relationDataSize); | ||||
| 	} | ||||
| 
 | ||||
| 	public unsafe ReverseSpanEnumerator<(Id, Id)> All() | ||||
| 	public ReverseSpanEnumerator<(Id, Id)> All() | ||||
| 	{ | ||||
| 		return new ReverseSpanEnumerator<(Id, Id)>(new Span<(Id, Id)>((void*) relations.Elements, count)); | ||||
| 		return new ReverseSpanEnumerator<(Id, Id)>(relations.ToSpan<(Id, Id)>()); | ||||
| 	} | ||||
| 
 | ||||
| 	public unsafe void Set<T>(in Id entityA, in Id entityB, in T relationData) where T : unmanaged | ||||
|  | @ -51,16 +50,9 @@ internal class Relation | |||
| 		} | ||||
| 		inRelations[entityB].Add(entityA); | ||||
| 
 | ||||
| 		if (count >= capacity) | ||||
| 		{ | ||||
| 			relations.Resize(); | ||||
| 			relationDatas.Resize(); | ||||
| 		} | ||||
| 
 | ||||
| 		(((Id, Id)*) relationDatas.Elements)[count] = relation; | ||||
| 		((T*) relationDatas.Elements)[count] = relationData; | ||||
| 		indices.Add(relation, count); | ||||
| 		count += 1; | ||||
| 		relations.Append(relation); | ||||
| 		relationDatas.Append(relationData); | ||||
| 		indices.Add(relation, relations.Count - 1); | ||||
| 	} | ||||
| 
 | ||||
| 	public ref T Get<T>(in Id entityA, in Id entityB) where T : unmanaged | ||||
|  | @ -69,9 +61,9 @@ internal class Relation | |||
| 		return ref relationDatas.Get<T>(relationIndex); | ||||
| 	} | ||||
| 
 | ||||
| 	public bool Has((Id, Id) relation) | ||||
| 	public bool Has(Id entityA, Id entityB) | ||||
| 	{ | ||||
| 		return indices.ContainsKey(relation); | ||||
| 		return indices.ContainsKey((entityA, entityB)); | ||||
| 	} | ||||
| 
 | ||||
| 	public ReverseSpanEnumerator<Id> OutRelations(Id entityID) | ||||
|  | @ -177,11 +169,11 @@ internal class Relation | |||
| 
 | ||||
| 		if (indices.TryGetValue(relation, out var index)) | ||||
| 		{ | ||||
| 			var lastElementIndex = relations.Count - 1; | ||||
| 
 | ||||
| 			relationDatas.Delete(index); | ||||
| 			relations.Delete(index); | ||||
| 
 | ||||
| 			var lastElementIndex = count - 1; | ||||
| 
 | ||||
| 			// move an element into the hole | ||||
| 			if (index != lastElementIndex) | ||||
| 			{ | ||||
|  | @ -189,14 +181,38 @@ internal class Relation | |||
| 				indices[lastRelation] = index; | ||||
| 			} | ||||
| 
 | ||||
| 			count -= 1; | ||||
| 			indices.Remove(relation); | ||||
| 		} | ||||
| 
 | ||||
| 		return (aEmpty, bEmpty); | ||||
| 	} | ||||
| 
 | ||||
| 	private IndexableSet<Id> AcquireHashSetFromPool() | ||||
| 	public void RemoveEntity(Id entity) | ||||
| 	{ | ||||
| 		if (outRelations.TryGetValue(entity, out var entityOutRelations)) | ||||
| 		{ | ||||
| 			foreach (var entityB in entityOutRelations) | ||||
| 			{ | ||||
| 				Remove(entity, entityB); | ||||
| 			} | ||||
| 
 | ||||
| 			ReturnHashSetToPool(entityOutRelations); | ||||
| 			outRelations.Remove(entity); | ||||
| 		} | ||||
| 
 | ||||
| 		if (inRelations.TryGetValue(entity, out var entityInRelations)) | ||||
| 		{ | ||||
| 			foreach (var entityA in entityInRelations) | ||||
| 			{ | ||||
| 				Remove(entityA, entity); | ||||
| 			} | ||||
| 
 | ||||
| 			ReturnHashSetToPool(entityInRelations); | ||||
| 			inRelations.Remove(entity); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	internal IndexableSet<Id> AcquireHashSetFromPool() | ||||
| 	{ | ||||
| 		if (listPool.Count == 0) | ||||
| 		{ | ||||
|  | @ -214,7 +230,6 @@ internal class Relation | |||
| 
 | ||||
| 	public void Clear() | ||||
| 	{ | ||||
| 		count = 0; | ||||
| 		indices.Clear(); | ||||
| 
 | ||||
| 		foreach (var set in inRelations.Values) | ||||
|  | @ -228,6 +243,9 @@ internal class Relation | |||
| 			ReturnHashSetToPool(set); | ||||
| 		} | ||||
| 		outRelations.Clear(); | ||||
| 
 | ||||
| 		relations.Count = 0; | ||||
| 		relationDatas.Count = 0; | ||||
| 	} | ||||
| 
 | ||||
| 	protected virtual void Dispose(bool disposing) | ||||
|  | @ -1,4 +1,5 @@ | |||
| using System.Collections.Generic; | ||||
| using System.Runtime.CompilerServices; | ||||
| using MoonTools.ECS.Collections; | ||||
| 
 | ||||
| namespace MoonTools.ECS.Rev2; | ||||
|  | @ -8,7 +9,14 @@ public class Snapshot | |||
| 	private Dictionary<ArchetypeSignature, ArchetypeSnapshot> ArchetypeSnapshots = | ||||
| 		new Dictionary<ArchetypeSignature, ArchetypeSnapshot>(); | ||||
| 
 | ||||
| 	private Dictionary<Id, RelationSnapshot> RelationSnapshots = | ||||
| 		new Dictionary<Id, RelationSnapshot>(); | ||||
| 
 | ||||
| 	private Dictionary<Id, Record> EntityIndex = new Dictionary<Id, Record>(); | ||||
| 
 | ||||
| 	private Dictionary<Id, IndexableSet<Id>> EntityRelationIndex = | ||||
| 		new Dictionary<Id, IndexableSet<Id>>(); | ||||
| 
 | ||||
| 	private IdAssigner IdAssigner = new IdAssigner(); | ||||
| 
 | ||||
| 	public int Count | ||||
|  | @ -32,7 +40,7 @@ public class Snapshot | |||
| 		foreach (var (archetypeSignature, archetypeSnapshot) in ArchetypeSnapshots) | ||||
| 		{ | ||||
| 			var archetype = world.ArchetypeIndex[archetypeSignature]; | ||||
| 			RestoreArchetypeSnapshot(archetype); | ||||
| 			archetypeSnapshot.Restore(archetype); | ||||
| 		} | ||||
| 
 | ||||
| 		// restore entity index | ||||
|  | @ -44,6 +52,25 @@ public class Snapshot | |||
| 
 | ||||
| 		// restore id assigner state | ||||
| 		IdAssigner.CopyTo(world.IdAssigner); | ||||
| 
 | ||||
| 		// restore relation state | ||||
| 		foreach (var (typeId, relationSnapshot) in RelationSnapshots) | ||||
| 		{ | ||||
| 			var relationStorage = world.RelationIndex[typeId]; | ||||
| 			relationSnapshot.Restore(relationStorage); | ||||
| 		} | ||||
| 
 | ||||
| 		// restore entity relation index state | ||||
| 		// FIXME: arghhhh this is so slow | ||||
| 		foreach (var (id, relationTypeSet) in EntityRelationIndex) | ||||
| 		{ | ||||
| 			world.EntityRelationIndex[id].Clear(); | ||||
| 
 | ||||
| 			foreach (var typeId in relationTypeSet) | ||||
| 			{ | ||||
| 				world.EntityRelationIndex[id].Add(typeId); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	public void Take(World world) | ||||
|  | @ -63,9 +90,32 @@ public class Snapshot | |||
| 		{ | ||||
| 			TakeArchetypeSnapshot(archetype); | ||||
| 		} | ||||
| 
 | ||||
| 		// copy relations | ||||
| 		foreach (var (typeId, relationStorage) in world.RelationIndex) | ||||
| 		{ | ||||
| 			TakeRelationSnapshot(typeId, relationStorage); | ||||
| 		} | ||||
| 
 | ||||
| 		// copy entity relation index | ||||
| 		// FIXME: arghhhh this is so slow | ||||
| 		foreach (var (id, relationTypeSet) in world.EntityRelationIndex) | ||||
| 		{ | ||||
| 			if (!EntityRelationIndex.ContainsKey(id)) | ||||
| 			{ | ||||
| 				EntityRelationIndex.Add(id, new IndexableSet<Id>()); | ||||
| 			} | ||||
| 
 | ||||
| 			EntityRelationIndex[id].Clear(); | ||||
| 
 | ||||
| 			foreach (var typeId in relationTypeSet) | ||||
| 			{ | ||||
| 				EntityRelationIndex[id].Add(typeId); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	internal void TakeArchetypeSnapshot(Archetype archetype) | ||||
| 	private void TakeArchetypeSnapshot(Archetype archetype) | ||||
| 	{ | ||||
| 		if (!ArchetypeSnapshots.TryGetValue(archetype.Signature, out var archetypeSnapshot)) | ||||
| 		{ | ||||
|  | @ -76,15 +126,19 @@ public class Snapshot | |||
| 		archetypeSnapshot.Take(archetype); | ||||
| 	} | ||||
| 
 | ||||
| 	private void RestoreArchetypeSnapshot(Archetype archetype) | ||||
| 	private void TakeRelationSnapshot(Id typeId, RelationStorage relationStorage) | ||||
| 	{ | ||||
| 		var archetypeSnapshot = ArchetypeSnapshots[archetype.Signature]; | ||||
| 		archetypeSnapshot.Restore(archetype); | ||||
| 		if (!RelationSnapshots.TryGetValue(typeId, out var snapshot)) | ||||
| 		{ | ||||
| 			snapshot = new RelationSnapshot(World.ElementSizes[typeId]); | ||||
| 			RelationSnapshots.Add(typeId, snapshot); | ||||
| 		} | ||||
| 
 | ||||
| 		snapshot.Take(relationStorage); | ||||
| 	} | ||||
| 
 | ||||
| 	private class ArchetypeSnapshot | ||||
| 	{ | ||||
| 		public ArchetypeSignature Signature; | ||||
| 		private readonly Column[] ComponentColumns; | ||||
| 		private readonly NativeArray<Id> RowToEntity; | ||||
| 
 | ||||
|  | @ -92,7 +146,6 @@ public class Snapshot | |||
| 
 | ||||
| 		public ArchetypeSnapshot(ArchetypeSignature signature) | ||||
| 		{ | ||||
| 			Signature = signature; | ||||
| 			ComponentColumns = new Column[signature.Count]; | ||||
| 			RowToEntity = new NativeArray<Id>(); | ||||
| 
 | ||||
|  | @ -103,11 +156,6 @@ public class Snapshot | |||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		public void Clear() | ||||
| 		{ | ||||
| 			RowToEntity.Clear(); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Take(Archetype archetype) | ||||
| 		{ | ||||
| 			for (int i = 0; i < ComponentColumns.Length; i += 1) | ||||
|  | @ -129,4 +177,54 @@ public class Snapshot | |||
| 			RowToEntity.CopyTo(archetype.RowToEntity); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	private class RelationSnapshot | ||||
| 	{ | ||||
| 		private Column Relations; | ||||
| 		private Column RelationDatas; | ||||
| 
 | ||||
| 		public RelationSnapshot(int elementSize) | ||||
| 		{ | ||||
| 			Relations = new Column(Unsafe.SizeOf<(Id, Id)>()); | ||||
| 			RelationDatas = new Column(elementSize); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Take(RelationStorage relationStorage) | ||||
| 		{ | ||||
| 			relationStorage.relations.CopyAllTo(Relations); | ||||
| 			relationStorage.relationDatas.CopyAllTo(RelationDatas); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Restore(RelationStorage relationStorage) | ||||
| 		{ | ||||
| 			relationStorage.Clear(); | ||||
| 
 | ||||
| 			Relations.CopyAllTo(relationStorage.relations); | ||||
| 			RelationDatas.CopyAllTo(relationStorage.relationDatas); | ||||
| 
 | ||||
| 			for (int index = 0; index < Relations.Count; index += 1) | ||||
| 			{ | ||||
| 				var relation = Relations.Get<(Id, Id)>(index); | ||||
| 				relationStorage.indices[relation] = index; | ||||
| 
 | ||||
| 				relationStorage.indices[relation] = index; | ||||
| 
 | ||||
| 				if (!relationStorage.outRelations.ContainsKey(relation.Item1)) | ||||
| 				{ | ||||
| 					relationStorage.outRelations[relation.Item1] = | ||||
| 						relationStorage.AcquireHashSetFromPool(); | ||||
| 				} | ||||
| 
 | ||||
| 				relationStorage.outRelations[relation.Item1].Add(relation.Item2); | ||||
| 
 | ||||
| 				if (!relationStorage.inRelations.ContainsKey(relation.Item2)) | ||||
| 				{ | ||||
| 					relationStorage.inRelations[relation.Item2] = | ||||
| 						relationStorage.AcquireHashSetFromPool(); | ||||
| 				} | ||||
| 
 | ||||
| 				relationStorage.inRelations[relation.Item2].Add(relation.Item1); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -1,13 +1,15 @@ | |||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Runtime.CompilerServices; | ||||
| using System.Runtime.InteropServices; | ||||
| using MoonTools.ECS.Collections; | ||||
| 
 | ||||
| namespace MoonTools.ECS.Rev2 | ||||
| { | ||||
| 	public class World : IDisposable | ||||
| 	{ | ||||
| 		// Get ComponentId from a Type | ||||
| 		internal static Dictionary<Type, Id> TypeToComponentId = new Dictionary<Type, Id>(); | ||||
| 		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>(); | ||||
| 
 | ||||
|  | @ -20,7 +22,16 @@ namespace MoonTools.ECS.Rev2 | |||
| 		// 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; | ||||
|  | @ -59,58 +70,66 @@ namespace MoonTools.ECS.Rev2 | |||
| 			var entityId = IdAssigner.Assign(); | ||||
| 			EntityIndex.Add(entityId, new Record(EmptyArchetype, EmptyArchetype.Count)); | ||||
| 			EmptyArchetype.RowToEntity.Add(entityId); | ||||
| 
 | ||||
| 			if (!EntityRelationIndex.ContainsKey(entityId)) | ||||
| 			{ | ||||
| 				EntityRelationIndex.Add(entityId, new IndexableSet<Id>()); | ||||
| 			} | ||||
| 
 | ||||
| 			return entityId; | ||||
| 		} | ||||
| 
 | ||||
| 		// used as a fast path by snapshot restore | ||||
| 		internal void CreateEntityOnArchetype(Archetype archetype) | ||||
| 		internal void TryRegisterTypeId<T>() where T : unmanaged | ||||
| 		{ | ||||
| 			var entityId = IdAssigner.Assign(); | ||||
| 			archetype.RowToEntity.Add(entityId); | ||||
| 		} | ||||
| 
 | ||||
| 		// used as a fast path by Archetype.ClearAll and snapshot restore | ||||
| 		internal void FreeEntity(Id entityId) | ||||
| 		{ | ||||
| 			EntityIndex.Remove(entityId); | ||||
| 			IdAssigner.Unassign(entityId); | ||||
| 		} | ||||
| 
 | ||||
| 		private void RegisterTypeId(Id typeId, int elementSize) | ||||
| 		{ | ||||
| 			ComponentIndex.Add(typeId, new List<Archetype>()); | ||||
| 			ElementSizes.Add(typeId, elementSize); | ||||
| 			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<T>() where T : unmanaged | ||||
| 		private void RegisterComponent(Id typeId) | ||||
| 		{ | ||||
| 			var componentId = IdAssigner.Assign(); | ||||
| 			TypeToComponentId.Add(typeof(T), componentId); | ||||
| 			ComponentIndex.Add(componentId, new List<Archetype>()); | ||||
| 			ElementSizes.Add(componentId, Unsafe.SizeOf<T>()); | ||||
| 		} | ||||
| 
 | ||||
| 		private void TryRegisterTypeId(Id typeId, int elementSize) | ||||
| 		{ | ||||
| 			if (!ComponentIndex.ContainsKey(typeId)) | ||||
| 			{ | ||||
| 				RegisterTypeId(typeId, elementSize); | ||||
| 			} | ||||
| 			ComponentIndex.Add(typeId, new List<Archetype>()); | ||||
| 		} | ||||
| 
 | ||||
| 		private void TryRegisterComponentId<T>() where T : unmanaged | ||||
| 		{ | ||||
| 			if (!TypeToComponentId.ContainsKey(typeof(T))) | ||||
| 			TryRegisterTypeId<T>(); | ||||
| 			var typeId = TypeToId[typeof(T)]; | ||||
| 			if (!ComponentIndex.ContainsKey(typeId)) | ||||
| 			{ | ||||
| 				RegisterComponent<T>(); | ||||
| 				RegisterComponent(typeId); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 		private Id GetComponentId<T>() where T : unmanaged | ||||
| 		{ | ||||
| 			return TypeToComponentId[typeof(T)]; | ||||
| 			return TypeToId[typeof(T)]; | ||||
| 		} | ||||
| 
 | ||||
| 		private void RegisterRelationType(Id typeId) | ||||
| 		{ | ||||
| 			RelationIndex.Add(typeId, new RelationStorage(ElementSizes[typeId])); | ||||
| 		} | ||||
| 
 | ||||
| 		private void TryRegisterRelationType<T>() where T : unmanaged | ||||
| 		{ | ||||
| 			TryRegisterTypeId<T>(); | ||||
| 			var typeId = TypeToId[typeof(T)]; | ||||
| 			if (!RelationIndex.ContainsKey(typeId)) | ||||
| 			{ | ||||
| 				RegisterRelationType(typeId); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| 		private RelationStorage GetRelationStorage<T>() where T : unmanaged | ||||
| 		{ | ||||
| 			return RelationIndex[TypeToId[typeof(T)]]; | ||||
| 		} | ||||
| 
 | ||||
| 		public bool Has<T>(Id entityId) where T : unmanaged | ||||
|  | @ -222,43 +241,49 @@ namespace MoonTools.ECS.Rev2 | |||
| 			MoveEntityToLowerArchetype(entityId, row, archetype, nextArchetype, componentId); | ||||
| 		} | ||||
| 
 | ||||
| 		private Id Pair(in Id relation, in Id target) | ||||
| 		{ | ||||
| 			return new Id(relation.Low, target.Low); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Relate<T>(in Id entityA, in Id entityB, in T relation) where T : unmanaged | ||||
| 		{ | ||||
| 			TryRegisterComponentId<T>(); | ||||
| 			var relationDataTypeId = GetComponentId<T>(); | ||||
| 			TryRegisterRelationType<T>(); | ||||
| 			var relationStorage = GetRelationStorage<T>(); | ||||
| 			relationStorage.Set(entityA, entityB, relation); | ||||
| 			EntityRelationIndex[entityA].Add(TypeToId[typeof(T)]); | ||||
| 			EntityRelationIndex[entityB].Add(TypeToId[typeof(T)]); | ||||
| 		} | ||||
| 
 | ||||
| 			var typeId = Pair(relationDataTypeId, entityB); | ||||
| 
 | ||||
| 			TryRegisterTypeId(typeId, Unsafe.SizeOf<T>()); | ||||
| 			SetRelationData(entityA, typeId, relation); | ||||
| 		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 relationDataTypeId = GetComponentId<T>(); | ||||
| 			var typeId = Pair(relationDataTypeId, entityB); | ||||
| 			return Has(entityA, typeId); | ||||
| 			var relationStorage = GetRelationStorage<T>(); | ||||
| 			return relationStorage.Has(entityA, entityB); | ||||
| 		} | ||||
| 
 | ||||
| 		public T GetRelationData<T>(in Id entityA, in Id entityB) where T : unmanaged | ||||
| 		{ | ||||
| 			var relationDataTypeId = GetComponentId<T>(); | ||||
| 			var typeId = Pair(relationDataTypeId, entityB); | ||||
| 			return Get<T>(entityA, typeId); | ||||
| 			var relationStorage = GetRelationStorage<T>(); | ||||
| 			return relationStorage.Get<T>(entityA, entityB); | ||||
| 		} | ||||
| 
 | ||||
| 		private unsafe ref T Get<T>(Id entityId, Id typeId) where T : unmanaged | ||||
| 		public ReverseSpanEnumerator<(Id, Id)> Relations<T>() where T : unmanaged | ||||
| 		{ | ||||
| 			var record = EntityIndex[entityId]; | ||||
| 			var columnIndex = record.Archetype.ComponentToColumnIndex[typeId]; | ||||
| 			var column = record.Archetype.ComponentColumns[columnIndex]; | ||||
| 			var relationStorage = GetRelationStorage<T>(); | ||||
| 			return relationStorage.All(); | ||||
| 		} | ||||
| 
 | ||||
| 			return ref ((T*) column.Elements)[record.Row]; | ||||
| 		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) | ||||
|  | @ -267,57 +292,19 @@ namespace MoonTools.ECS.Rev2 | |||
| 			return record.Archetype.ComponentToColumnIndex.ContainsKey(typeId); | ||||
| 		} | ||||
| 
 | ||||
| 		private void Add<T>(Id entityId, Id typeId, in T component) where T : unmanaged | ||||
| 		// used as a fast path by Archetype.ClearAll and snapshot restore | ||||
| 		internal void FreeEntity(Id entityId) | ||||
| 		{ | ||||
| 			Archetype? nextArchetype; | ||||
| 			EntityIndex.Remove(entityId); | ||||
| 			IdAssigner.Unassign(entityId); | ||||
| 
 | ||||
| 			// move the entity to the new archetype | ||||
| 			var record = EntityIndex[entityId]; | ||||
| 			var archetype = record.Archetype; | ||||
| 
 | ||||
| 			if (archetype.Edges.TryGetValue(typeId, out var edge)) | ||||
| 			foreach (var relationTypeIndex in EntityRelationIndex[entityId]) | ||||
| 			{ | ||||
| 				nextArchetype = edge.Add; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				// FIXME: pool the signatures | ||||
| 				var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1); | ||||
| 				archetype.Signature.CopyTo(nextSignature); | ||||
| 				nextSignature.Insert(typeId); | ||||
| 
 | ||||
| 				if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype)) | ||||
| 				{ | ||||
| 					nextArchetype = CreateArchetype(nextSignature); | ||||
| 				} | ||||
| 
 | ||||
| 				var newEdge = new ArchetypeEdge(nextArchetype, archetype); | ||||
| 				archetype.Edges.Add(typeId, newEdge); | ||||
| 				nextArchetype.Edges.Add(typeId, newEdge); | ||||
| 				var relationStorage = RelationIndex[relationTypeIndex]; | ||||
| 				relationStorage.RemoveEntity(entityId); | ||||
| 			} | ||||
| 
 | ||||
| 			MoveEntityToHigherArchetype(entityId, record.Row, archetype, nextArchetype); | ||||
| 
 | ||||
| 			// add the new component to the new archetype | ||||
| 			var columnIndex = nextArchetype.ComponentToColumnIndex[typeId]; | ||||
| 			var column = nextArchetype.ComponentColumns[columnIndex]; | ||||
| 			column.Append(component); | ||||
| 		} | ||||
| 
 | ||||
| 		private unsafe void SetRelationData<T>(in Id entityId, in Id typeId, T data) where T : unmanaged | ||||
| 		{ | ||||
| 			if (Has(entityId, typeId)) | ||||
| 			{ | ||||
| 				var record = EntityIndex[entityId]; | ||||
| 				var columnIndex = record.Archetype.ComponentToColumnIndex[typeId]; | ||||
| 				var column = record.Archetype.ComponentColumns[columnIndex]; | ||||
| 
 | ||||
| 				((T*) column.Elements)[record.Row] = data; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				Add(entityId, typeId, data); | ||||
| 			} | ||||
| 			EntityRelationIndex[entityId].Clear(); | ||||
| 		} | ||||
| 
 | ||||
| 		public void Destroy(Id entityId) | ||||
|  | @ -342,6 +329,14 @@ namespace MoonTools.ECS.Rev2 | |||
| 			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) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue