From 545637aaf3f06e1f15fdb7548d69170fc1e055d3 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Thu, 26 Oct 2023 16:16:28 -0700 Subject: [PATCH] unifying Ids and adding Relations --- src/Rev2/Archetype.cs | 8 +- src/Rev2/ArchetypeSignature.cs | 18 ++-- src/Rev2/ComponentId.cs | 12 --- src/Rev2/Entity.cs | 3 + src/Rev2/EntityId.cs | 4 - src/Rev2/Filter.cs | 18 ++-- src/Rev2/FilterBuilder.cs | 10 +- src/Rev2/Id.cs | 30 ++++++ src/Rev2/IdAssigner.cs | 14 +-- src/Rev2/World.cs | 164 ++++++++++++++++++++++++++------- 10 files changed, 200 insertions(+), 81 deletions(-) delete mode 100644 src/Rev2/ComponentId.cs create mode 100644 src/Rev2/Entity.cs delete mode 100644 src/Rev2/EntityId.cs create mode 100644 src/Rev2/Id.cs diff --git a/src/Rev2/Archetype.cs b/src/Rev2/Archetype.cs index c0442f0..31ae816 100644 --- a/src/Rev2/Archetype.cs +++ b/src/Rev2/Archetype.cs @@ -7,11 +7,11 @@ internal class Archetype public World World; public ArchetypeSignature Signature; public List ComponentColumns = new List(); - public List RowToEntity = new List(); + public List RowToEntity = new List(); - public Dictionary ComponentToColumnIndex = - new Dictionary(); - public SortedDictionary Edges = new SortedDictionary(); + public Dictionary ComponentToColumnIndex = + new Dictionary(); + public SortedDictionary Edges = new SortedDictionary(); public int Count => RowToEntity.Count; diff --git a/src/Rev2/ArchetypeSignature.cs b/src/Rev2/ArchetypeSignature.cs index 728d5e2..e0cf620 100644 --- a/src/Rev2/ArchetypeSignature.cs +++ b/src/Rev2/ArchetypeSignature.cs @@ -7,36 +7,36 @@ namespace MoonTools.ECS.Rev2 { public static ArchetypeSignature Empty = new ArchetypeSignature(0); - List Ids; + List Ids; public int Count => Ids.Count; - public ComponentId this[int i] => new ComponentId(Ids[i]); + public Id this[int i] => new Id(Ids[i]); public ArchetypeSignature() { - Ids = new List(); + Ids = new List(); } public ArchetypeSignature(int capacity) { - Ids = new List(capacity); + Ids = new List(capacity); } // Maintains sorted order - public void Insert(ComponentId componentId) + public void Insert(Id componentId) { - var index = Ids.BinarySearch(componentId.Id); + var index = Ids.BinarySearch(componentId.Value); if (index < 0) { - Ids.Insert(~index, componentId.Id); + Ids.Insert(~index, componentId.Value); } } - public void Remove(ComponentId componentId) + public void Remove(Id componentId) { - var index = Ids.BinarySearch(componentId.Id); + var index = Ids.BinarySearch(componentId.Value); if (index >= 0) { diff --git a/src/Rev2/ComponentId.cs b/src/Rev2/ComponentId.cs deleted file mode 100644 index 8d59b3e..0000000 --- a/src/Rev2/ComponentId.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace MoonTools.ECS.Rev2 -{ - internal readonly record struct ComponentId(int Id) : IHasId, IComparable - { - public int CompareTo(ComponentId other) - { - return Id.CompareTo(other.Id); - } - } -} diff --git a/src/Rev2/Entity.cs b/src/Rev2/Entity.cs new file mode 100644 index 0000000..2a9b435 --- /dev/null +++ b/src/Rev2/Entity.cs @@ -0,0 +1,3 @@ +namespace MoonTools.ECS.Rev2; + +public readonly record struct Entity(uint Id); diff --git a/src/Rev2/EntityId.cs b/src/Rev2/EntityId.cs deleted file mode 100644 index ee0c5d7..0000000 --- a/src/Rev2/EntityId.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace MoonTools.ECS.Rev2 -{ - public readonly record struct EntityId(int Id) : IHasId; -} diff --git a/src/Rev2/Filter.cs b/src/Rev2/Filter.cs index aea4010..62683c2 100644 --- a/src/Rev2/Filter.cs +++ b/src/Rev2/Filter.cs @@ -7,8 +7,8 @@ namespace MoonTools.ECS.Rev2 public class Filter { private Archetype EmptyArchetype; - private HashSet Included; - private HashSet Excluded; + private HashSet Included; + private HashSet Excluded; public EntityEnumerator Entities => new EntityEnumerator(this); internal ArchetypeEnumerator Archetypes => new ArchetypeEnumerator(this); @@ -47,7 +47,7 @@ namespace MoonTools.ECS.Rev2 } } - public EntityId RandomEntity + public Id RandomEntity { get { @@ -57,7 +57,7 @@ namespace MoonTools.ECS.Rev2 } // WARNING: this WILL crash if the index is out of range! - public EntityId NthEntity(int index) + public Id NthEntity(int index) { var count = 0; @@ -93,7 +93,7 @@ namespace MoonTools.ECS.Rev2 } } - internal Filter(Archetype emptyArchetype, HashSet included, HashSet excluded) + internal Filter(Archetype emptyArchetype, HashSet included, HashSet excluded) { EmptyArchetype = emptyArchetype; Included = included; @@ -165,12 +165,12 @@ namespace MoonTools.ECS.Rev2 public ref struct EntityEnumerator { - private EntityId CurrentEntity; + private Id CurrentEntity; public EntityEnumerator GetEnumerator() => this; // TODO: pool this - Queue EntityQueue = new Queue(); + Queue EntityQueue = new Queue(); internal EntityEnumerator(Filter filter) { @@ -190,7 +190,7 @@ namespace MoonTools.ECS.Rev2 return EntityQueue.TryDequeue(out CurrentEntity); } - public EntityId Current => CurrentEntity; + public Id Current => CurrentEntity; } public ref struct RandomEntityEnumerator @@ -208,7 +208,7 @@ namespace MoonTools.ECS.Rev2 } public bool MoveNext() => LinearCongruentialEnumerator.MoveNext(); - public EntityId Current => Filter.NthEntity(LinearCongruentialEnumerator.Current); + public Id Current => Filter.NthEntity(LinearCongruentialEnumerator.Current); } } } diff --git a/src/Rev2/FilterBuilder.cs b/src/Rev2/FilterBuilder.cs index 5edc8b8..b4be164 100644 --- a/src/Rev2/FilterBuilder.cs +++ b/src/Rev2/FilterBuilder.cs @@ -5,17 +5,17 @@ namespace MoonTools.ECS.Rev2 public ref struct FilterBuilder { World World; - HashSet Included; - HashSet Excluded; + HashSet Included; + HashSet Excluded; internal FilterBuilder(World world) { World = world; - Included = new HashSet(); - Excluded = new HashSet(); + Included = new HashSet(); + Excluded = new HashSet(); } - private FilterBuilder(World world, HashSet included, HashSet excluded) + private FilterBuilder(World world, HashSet included, HashSet excluded) { World = world; Included = included; diff --git a/src/Rev2/Id.cs b/src/Rev2/Id.cs new file mode 100644 index 0000000..5094ad1 --- /dev/null +++ b/src/Rev2/Id.cs @@ -0,0 +1,30 @@ +using System; + +namespace MoonTools.ECS.Rev2; + +public readonly record struct Id : 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 c797b43..f326a62 100644 --- a/src/Rev2/IdAssigner.cs +++ b/src/Rev2/IdAssigner.cs @@ -2,12 +2,12 @@ using System.Collections.Generic; namespace MoonTools.ECS.Rev2 { - internal class IdAssigner where T : struct, IHasId + internal class IdAssigner { - int Next; - Queue AvailableIds = new Queue(); + ulong Next; + Queue AvailableIds = new Queue(); - public T Assign() + public Id Assign() { if (!AvailableIds.TryDequeue(out var id)) { @@ -15,12 +15,12 @@ namespace MoonTools.ECS.Rev2 Next += 1; } - return new T { Id = id }; + return new Id(id); } - public void Unassign(T idHaver) + public void Unassign(Id id) { - AvailableIds.Enqueue(idHaver.Id); + AvailableIds.Enqueue(id.Value); } } } diff --git a/src/Rev2/World.cs b/src/Rev2/World.cs index 8e8858b..8974ace 100644 --- a/src/Rev2/World.cs +++ b/src/Rev2/World.cs @@ -8,22 +8,21 @@ namespace MoonTools.ECS.Rev2 public class World : IDisposable { // Get ComponentId from a Type - internal static Dictionary TypeToComponentId = new Dictionary(); + internal static Dictionary TypeToComponentId = new Dictionary(); // Get element size from a ComponentId - internal static Dictionary ElementSizes = new Dictionary(); + internal static Dictionary ElementSizes = new Dictionary(); // Lookup from ArchetypeSignature to Archetype internal Dictionary ArchetypeIndex = new Dictionary(); // Going from EntityId to Archetype and storage row - Dictionary EntityIndex = new Dictionary(); + Dictionary EntityIndex = new Dictionary(); // Going from ComponentId to Archetype list - Dictionary> ComponentIndex = new Dictionary>(); + Dictionary> ComponentIndex = new Dictionary>(); // ID Management - IdAssigner EntityIdAssigner = new IdAssigner(); - IdAssigner ComponentIdAssigner = new IdAssigner(); + IdAssigner IdAssigner = new IdAssigner(); internal readonly Archetype EmptyArchetype; @@ -59,9 +58,9 @@ namespace MoonTools.ECS.Rev2 return archetype; } - public EntityId CreateEntity() + public Id CreateEntity() { - var entityId = EntityIdAssigner.Assign(); + var entityId = IdAssigner.Assign(); EntityIndex.Add(entityId, new Record(EmptyArchetype, EmptyArchetype.Count)); EmptyArchetype.RowToEntity.Add(entityId); return entityId; @@ -70,27 +69,41 @@ namespace MoonTools.ECS.Rev2 // used as a fast path by snapshot restore internal void CreateEntityOnArchetype(Archetype archetype) { - var entityId = EntityIdAssigner.Assign(); + var entityId = IdAssigner.Assign(); EntityIndex.Add(entityId, new Record(archetype, archetype.Count)); archetype.RowToEntity.Add(entityId); } // used as a fast path by Archetype.ClearAll and snapshot restore - internal void FreeEntity(EntityId entityId) + internal void FreeEntity(Id entityId) { EntityIndex.Remove(entityId); - EntityIdAssigner.Unassign(entityId); + IdAssigner.Unassign(entityId); + } + + private void RegisterTypeId(Id typeId, int elementSize) + { + ComponentIndex.Add(typeId, new List()); + ElementSizes.Add(typeId, elementSize); } // FIXME: would be much more efficient to do all this at load time somehow private void RegisterComponent() where T : unmanaged { - var componentId = ComponentIdAssigner.Assign(); + 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); + } + } + private void TryRegisterComponentId() where T : unmanaged { if (!TypeToComponentId.ContainsKey(typeof(T))) @@ -99,22 +112,13 @@ namespace MoonTools.ECS.Rev2 } } - // non-generic variant for use with Transfer - internal void AddComponentIndexEntry(ComponentId componentId) - { - if (!ComponentIndex.ContainsKey(componentId)) - { - ComponentIndex.Add(componentId, new List()); - } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ComponentId GetComponentId() where T : unmanaged + private Id GetComponentId() where T : unmanaged { return TypeToComponentId[typeof(T)]; } - public bool Has(EntityId entityId) where T : unmanaged + public bool Has(Id entityId) where T : unmanaged { var componentId = GetComponentId(); var record = EntityIndex[entityId]; @@ -122,7 +126,7 @@ namespace MoonTools.ECS.Rev2 } // will throw if non-existent - public unsafe ref T Get(EntityId entityId) where T : unmanaged + public unsafe ref T Get(Id entityId) where T : unmanaged { var componentId = GetComponentId(); @@ -133,7 +137,7 @@ namespace MoonTools.ECS.Rev2 return ref ((T*) column.Elements)[record.Row]; } - public unsafe void Set(in EntityId entityId, in T component) where T : unmanaged + public unsafe void Set(in Id entityId, in T component) where T : unmanaged { TryRegisterComponentId(); var componentId = GetComponentId(); @@ -152,7 +156,7 @@ namespace MoonTools.ECS.Rev2 } } - private void Add(EntityId entityId, in T component) where T : unmanaged + private void Add(Id entityId, in T component) where T : unmanaged { Archetype? nextArchetype; @@ -191,7 +195,7 @@ namespace MoonTools.ECS.Rev2 column.Append(component); } - public void Remove(EntityId entityId) where T : unmanaged + public void Remove(Id entityId) where T : unmanaged { Archetype? nextArchetype; @@ -223,7 +227,105 @@ namespace MoonTools.ECS.Rev2 MoveEntityToLowerArchetype(entityId, row, archetype, nextArchetype, componentId); } - public void Destroy(EntityId entityId) + 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(); + + var typeId = Pair(relationDataTypeId, entityB); + + TryRegisterTypeId(typeId, Unsafe.SizeOf()); + SetRelationData(entityA, typeId, relation); + } + + public bool Related(in Id entityA, in Id entityB) where T : unmanaged + { + var relationDataTypeId = GetComponentId(); + var typeId = Pair(relationDataTypeId, entityB); + return Has(entityA, typeId); + } + + public T GetRelationData(in Id entityA, in Id entityB) where T : unmanaged + { + var relationDataTypeId = GetComponentId(); + var typeId = Pair(relationDataTypeId, entityB); + return Get(entityA, typeId); + } + + private unsafe ref T Get(Id entityId, Id typeId) where T : unmanaged + { + var record = EntityIndex[entityId]; + var columnIndex = record.Archetype.ComponentToColumnIndex[typeId]; + var column = record.Archetype.ComponentColumns[columnIndex]; + + return ref ((T*) column.Elements)[record.Row]; + } + + private bool Has(Id entityId, Id typeId) + { + var record = EntityIndex[entityId]; + return record.Archetype.ComponentToColumnIndex.ContainsKey(typeId); + } + + private void Add(Id entityId, Id typeId, in T component) where T : unmanaged + { + Archetype? nextArchetype; + + // move the entity to the new archetype + var record = EntityIndex[entityId]; + var archetype = record.Archetype; + + if (archetype.Edges.TryGetValue(typeId, 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(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); + } + + 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); + } + } + + public void Destroy(Id entityId) { var record = EntityIndex[entityId]; var archetype = record.Archetype; @@ -244,10 +346,10 @@ namespace MoonTools.ECS.Rev2 archetype.RowToEntity.RemoveAt(archetype.Count - 1); EntityIndex.Remove(entityId); - EntityIdAssigner.Unassign(entityId); + IdAssigner.Unassign(entityId); } - private void MoveEntityToHigherArchetype(EntityId entityId, int row, Archetype from, Archetype to) + private void MoveEntityToHigherArchetype(Id entityId, int row, Archetype from, Archetype to) { for (int i = 0; i < from.ComponentColumns.Count; i += 1) { @@ -276,7 +378,7 @@ namespace MoonTools.ECS.Rev2 to.RowToEntity.Add(entityId); } - private void MoveEntityToLowerArchetype(EntityId entityId, int row, Archetype from, Archetype to, ComponentId removed) + private void MoveEntityToLowerArchetype(Id entityId, int row, Archetype from, Archetype to, Id removed) { for (int i = 0; i < from.ComponentColumns.Count; i += 1) {