diff --git a/src/Rev2/ArchetypeSignature.cs b/src/Rev2/ArchetypeSignature.cs index e0cf620..72cdc12 100644 --- a/src/Rev2/ArchetypeSignature.cs +++ b/src/Rev2/ArchetypeSignature.cs @@ -7,7 +7,7 @@ namespace MoonTools.ECS.Rev2 { public static ArchetypeSignature Empty = new ArchetypeSignature(0); - List Ids; + List Ids; public int Count => Ids.Count; @@ -15,12 +15,12 @@ namespace MoonTools.ECS.Rev2 public ArchetypeSignature() { - Ids = new List(); + Ids = new List(); } public ArchetypeSignature(int capacity) { - Ids = new List(capacity); + Ids = new List(capacity); } // Maintains sorted order diff --git a/src/Rev2/Column.cs b/src/Rev2/Column.cs index a7efe94..2612107 100644 --- a/src/Rev2/Column.cs +++ b/src/Rev2/Column.cs @@ -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 ToSpan() + { + return new Span((void*) Elements, Count); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T Get(int i) where T : unmanaged { diff --git a/src/Rev2/FilterBuilder.cs b/src/Rev2/FilterBuilder.cs index b4be164..79af550 100644 --- a/src/Rev2/FilterBuilder.cs +++ b/src/Rev2/FilterBuilder.cs @@ -24,13 +24,15 @@ namespace MoonTools.ECS.Rev2 public FilterBuilder Include() where T : unmanaged { - Included.Add(World.TypeToComponentId[typeof(T)]); + World.TryRegisterTypeId(); + Included.Add(World.TypeToId[typeof(T)]); return new FilterBuilder(World, Included, Excluded); } public FilterBuilder Exclude() where T : unmanaged { - Excluded.Add(World.TypeToComponentId[typeof(T)]); + World.TryRegisterTypeId(); + Excluded.Add(World.TypeToId[typeof(T)]); return new FilterBuilder(World, Included, Excluded); } diff --git a/src/Rev2/Id.cs b/src/Rev2/Id.cs index 5094ad1..cc291de 100644 --- a/src/Rev2/Id.cs +++ b/src/Rev2/Id.cs @@ -2,27 +2,8 @@ namespace MoonTools.ECS.Rev2; -public readonly record struct Id : IComparable +public readonly record struct Id(uint Value) : IComparable { - 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); diff --git a/src/Rev2/IdAssigner.cs b/src/Rev2/IdAssigner.cs index 4f40105..3b0a482 100644 --- a/src/Rev2/IdAssigner.cs +++ b/src/Rev2/IdAssigner.cs @@ -4,8 +4,8 @@ namespace MoonTools.ECS.Rev2; internal class IdAssigner { - ulong Next; - NativeArray AvailableIds = new NativeArray(); + uint Next; + NativeArray AvailableIds = new NativeArray(); public Id Assign() { diff --git a/src/Rev2/Relation.cs b/src/Rev2/RelationStorage.cs similarity index 76% rename from src/Rev2/Relation.cs rename to src/Rev2/RelationStorage.cs index f747f1e..806f4f6 100644 --- a/src/Rev2/Relation.cs +++ b/src/Rev2/RelationStorage.cs @@ -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> outRelations = new Dictionary>(16); - private Dictionary> inRelations = new Dictionary>(16); + internal Column relations; + internal Column relationDatas; + internal Dictionary<(Id, Id), int> indices = new Dictionary<(Id, Id), int>(16); + internal Dictionary> outRelations = new Dictionary>(16); + internal Dictionary> inRelations = new Dictionary>(16); private Stack> listPool = new Stack>(); 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(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(in Id entityA, in Id entityB) where T : unmanaged @@ -69,9 +61,9 @@ internal class Relation return ref relationDatas.Get(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 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 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 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) diff --git a/src/Rev2/Snapshot.cs b/src/Rev2/Snapshot.cs index 5c21251..6be8e83 100644 --- a/src/Rev2/Snapshot.cs +++ b/src/Rev2/Snapshot.cs @@ -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 ArchetypeSnapshots = new Dictionary(); + private Dictionary RelationSnapshots = + new Dictionary(); + private Dictionary EntityIndex = new Dictionary(); + + private Dictionary> EntityRelationIndex = + new Dictionary>(); + 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()); + } + + 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 RowToEntity; @@ -92,7 +146,6 @@ public class Snapshot public ArchetypeSnapshot(ArchetypeSignature signature) { - Signature = signature; ComponentColumns = new Column[signature.Count]; RowToEntity = new NativeArray(); @@ -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); + } + } + } } diff --git a/src/Rev2/World.cs b/src/Rev2/World.cs index d02d063..d8d9079 100644 --- a/src/Rev2/World.cs +++ b/src/Rev2/World.cs @@ -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 TypeToComponentId = new Dictionary(); + internal static Dictionary TypeToId = new Dictionary(); // Get element size from a ComponentId internal static Dictionary ElementSizes = new Dictionary(); @@ -20,7 +22,16 @@ namespace MoonTools.ECS.Rev2 // Going from ComponentId to Archetype list Dictionary> ComponentIndex = new Dictionary>(); + // Relation Storages + internal Dictionary RelationIndex = + new Dictionary(); + + // Entity Relation Tracking + internal Dictionary> EntityRelationIndex = + new Dictionary>(); + // 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()); + } + return entityId; } - // used as a fast path by snapshot restore - internal void CreateEntityOnArchetype(Archetype archetype) + internal void TryRegisterTypeId() 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()); - ElementSizes.Add(typeId, elementSize); + if (!TypeToId.ContainsKey(typeof(T))) + { + var typeId = IdAssigner.Assign(); + TypeToId.Add(typeof(T), typeId); + ElementSizes.Add(typeId, Unsafe.SizeOf()); + } } // FIXME: would be much more efficient to do all this at load time somehow - private void RegisterComponent() where T : unmanaged + private void RegisterComponent(Id typeId) { - var componentId = IdAssigner.Assign(); - TypeToComponentId.Add(typeof(T), componentId); - ComponentIndex.Add(componentId, new List()); - ElementSizes.Add(componentId, Unsafe.SizeOf()); - } - - private void TryRegisterTypeId(Id typeId, int elementSize) - { - if (!ComponentIndex.ContainsKey(typeId)) - { - RegisterTypeId(typeId, elementSize); - } + ComponentIndex.Add(typeId, new List()); } private void TryRegisterComponentId() where T : unmanaged { - if (!TypeToComponentId.ContainsKey(typeof(T))) + TryRegisterTypeId(); + var typeId = TypeToId[typeof(T)]; + if (!ComponentIndex.ContainsKey(typeId)) { - RegisterComponent(); + RegisterComponent(typeId); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private Id GetComponentId() 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() where T : unmanaged + { + TryRegisterTypeId(); + var typeId = TypeToId[typeof(T)]; + if (!RelationIndex.ContainsKey(typeId)) + { + RegisterRelationType(typeId); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private RelationStorage GetRelationStorage() where T : unmanaged + { + return RelationIndex[TypeToId[typeof(T)]]; } public bool Has(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(in Id entityA, in Id entityB, in T relation) where T : unmanaged { - TryRegisterComponentId(); - var relationDataTypeId = GetComponentId(); + TryRegisterRelationType(); + var relationStorage = GetRelationStorage(); + 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()); - SetRelationData(entityA, typeId, relation); + public void Unrelate(in Id entityA, in Id entityB) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + relationStorage.Remove(entityA, entityB); } public bool Related(in Id entityA, in Id entityB) where T : unmanaged { - var relationDataTypeId = GetComponentId(); - var typeId = Pair(relationDataTypeId, entityB); - return Has(entityA, typeId); + var relationStorage = GetRelationStorage(); + return relationStorage.Has(entityA, entityB); } public T GetRelationData(in Id entityA, in Id entityB) where T : unmanaged { - var relationDataTypeId = GetComponentId(); - var typeId = Pair(relationDataTypeId, entityB); - return Get(entityA, typeId); + var relationStorage = GetRelationStorage(); + return relationStorage.Get(entityA, entityB); } - private unsafe ref T Get(Id entityId, Id typeId) where T : unmanaged + public ReverseSpanEnumerator<(Id, Id)> Relations() where T : unmanaged { - var record = EntityIndex[entityId]; - var columnIndex = record.Archetype.ComponentToColumnIndex[typeId]; - var column = record.Archetype.ComponentColumns[columnIndex]; + var relationStorage = GetRelationStorage(); + return relationStorage.All(); + } - return ref ((T*) column.Elements)[record.Row]; + public ReverseSpanEnumerator OutRelations(Id entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + return relationStorage.OutRelations(entity); + } + + public ReverseSpanEnumerator InRelations(Id entity) where T : unmanaged + { + var relationStorage = GetRelationStorage(); + 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(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(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)