From f6287350256e34649042893013792d47cec73741 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 6 Dec 2022 22:06:02 -0800 Subject: [PATCH] snapshot refinements --- src/ComponentDepot.cs | 28 +++++++++------- src/EntityStorage.cs | 2 +- src/FilterSignature.cs | 5 --- src/FilterStorage.cs | 21 ++++++++++++ src/RelationDepot.cs | 49 +++++++++++++++++++++++++--- src/RelationStorage.cs | 69 +++++++++++++++++++++++++++++++++------ src/Snapshot.cs | 73 +++++++++++++++++++++++++++++++++++++++++- src/System.cs | 4 +-- 8 files changed, 216 insertions(+), 35 deletions(-) diff --git a/src/ComponentDepot.cs b/src/ComponentDepot.cs index 7338fcf..04e1d03 100644 --- a/src/ComponentDepot.cs +++ b/src/ComponentDepot.cs @@ -47,12 +47,6 @@ namespace MoonTools.ECS return ref Lookup().Get(entityID); } - // used for debugging and template instantiation - internal object UntypedGet(int entityID, int componentTypeIndex) - { - return storages[componentTypeIndex].UntypedGet(entityID); - } - public ref readonly TComponent GetFirst() where TComponent : unmanaged { return ref Lookup().GetFirst(); @@ -63,10 +57,7 @@ namespace MoonTools.ECS Lookup().Set(entityID, component); } - internal void Set(int entityID, int componentTypeIndex, object component) - { - storages[componentTypeIndex].Set(entityID, component); - } + public Entity GetSingletonEntity() where TComponent : unmanaged { @@ -99,8 +90,21 @@ namespace MoonTools.ECS } } - // used to fill snapshot depot with correct storages - public void FillMissingStorages(ComponentDepot other) + // these methods used to implement snapshots, templates, and debugging + + // FIXME: use unsafe pointers instead of object + internal object UntypedGet(int entityID, int componentTypeIndex) + { + return storages[componentTypeIndex].UntypedGet(entityID); + } + + // FIXME: use unsafe pointers instead of object + internal void Set(int entityID, int componentTypeIndex, object component) + { + storages[componentTypeIndex].Set(entityID, component); + } + + public void CreateMissingStorages(ComponentDepot other) { for (var i = 0; i < ComponentTypeIndices.Count; i += 1) { diff --git a/src/EntityStorage.cs b/src/EntityStorage.cs index e803243..99d8b6c 100644 --- a/src/EntityStorage.cs +++ b/src/EntityStorage.cs @@ -52,7 +52,7 @@ namespace MoonTools.ECS return EntityToComponentTypeIndices[entityID].Remove(componentTypeIndex); } - public void AddRelation(int entityID, int relationIndex) + public void AddRelationKind(int entityID, int relationIndex) { EntityToRelationTypeIndices[entityID].Add(relationIndex); } diff --git a/src/FilterSignature.cs b/src/FilterSignature.cs index 66f7fc3..4965cd1 100644 --- a/src/FilterSignature.cs +++ b/src/FilterSignature.cs @@ -24,11 +24,6 @@ namespace MoonTools.ECS return Included.SetEquals(other.Included) && Excluded.SetEquals(other.Excluded); } - private int GuidToInt(Guid guid) - { - return BitConverter.ToInt32(guid.ToByteArray()); - } - public override int GetHashCode() { var hashcode = 1; diff --git a/src/FilterStorage.cs b/src/FilterStorage.cs index 1b5af12..e380234 100644 --- a/src/FilterStorage.cs +++ b/src/FilterStorage.cs @@ -94,6 +94,27 @@ namespace MoonTools.ECS Check(entityID, ComponentTypeIndices.GetIndex()); } + public bool CheckSatisfied(int entityID, FilterSignature filterSignature) + { + foreach (var type in filterSignature.Included) + { + if (!EntityStorage.HasComponent(entityID, type)) + { + return false; + } + } + + foreach (var type in filterSignature.Excluded) + { + if (EntityStorage.HasComponent(entityID, type)) + { + return false; + } + } + + return true; + } + private void CheckFilter(int entityID, FilterSignature filterSignature) { foreach (var type in filterSignature.Included) diff --git a/src/RelationDepot.cs b/src/RelationDepot.cs index 8c150ea..e3b9225 100644 --- a/src/RelationDepot.cs +++ b/src/RelationDepot.cs @@ -51,11 +51,6 @@ namespace MoonTools.ECS Lookup().UnrelateAll(entityID); } - public void UnrelateAll(int entityID, int relationStorageIndex) - { - storages[relationStorageIndex].UnrelateAll(entityID); - } - public IEnumerable<(Entity, Entity, TRelationKind)> Relations() where TRelationKind : unmanaged { return Lookup().All(); @@ -105,5 +100,49 @@ namespace MoonTools.ECS { return Lookup().InRelationCount(entityID); } + + // untyped methods used for destroying and snapshots + + public void Set(int entityA, int entityB, int relationTypeIndex, object relationData) + { + storages[relationTypeIndex].Set(entityA, entityB, relationData); + } + + public void UnrelateAll(int entityID, int relationTypeIndex) + { + storages[relationTypeIndex].UnrelateAll(entityID); + } + + public IEnumerable<(int, object)> InRelations(int entityID, int relationTypeIndex) + { + return storages[relationTypeIndex].UntypedInRelations(entityID); + } + + public IEnumerable<(int, object)> OutRelations(int entityID, int relationTypeIndex) + { + return storages[relationTypeIndex].UntypedOutRelations(entityID); + } + + public void Clear() + { + for (var i = 0; i < RelationTypeIndices.Count; i += 1) + { + if (storages[i] != null) + { + storages[i].Clear(); + } + } + } + + public void CreateMissingStorages(RelationDepot other) + { + for (var i = 0; i < RelationTypeIndices.Count; i += 1) + { + if (storages[i] == null && other.storages[i] != null) + { + storages[i] = other.storages[i].CreateStorage(); + } + } + } } } diff --git a/src/RelationStorage.cs b/src/RelationStorage.cs index 52907e3..9a2a887 100644 --- a/src/RelationStorage.cs +++ b/src/RelationStorage.cs @@ -1,12 +1,17 @@ using System; using System.Collections.Generic; -using System.Runtime.InteropServices; namespace MoonTools.ECS { internal abstract class RelationStorage { + public abstract void Set(int entityA, int entityB, object relationData); public abstract void UnrelateAll(int entityID); + public abstract IEnumerable<(int, object)> UntypedInRelations(int entityID); + public abstract IEnumerable<(int, object)> UntypedOutRelations(int entityID); + // used to create correctly typed storage on snapshot + public abstract RelationStorage CreateStorage(); + public abstract void Clear(); } // Relation is the two entities, A related to B. @@ -185,6 +190,29 @@ namespace MoonTools.ECS return (aEmpty, bEmpty); } + private IndexableSet AcquireHashSetFromPool() + { + if (listPool.Count == 0) + { + listPool.Push(new IndexableSet()); + } + + return listPool.Pop(); + } + + private void ReturnHashSetToPool(IndexableSet hashSet) + { + hashSet.Clear(); + listPool.Push(hashSet); + } + + // untyped methods used for internal implementation + + public override void Set(int entityA, int entityB, object relationData) + { + Set(new Relation(entityA, entityB), (TRelation) relationData); + } + public override void UnrelateAll(int entityID) { if (outRelations.ContainsKey(entityID)) @@ -210,20 +238,43 @@ namespace MoonTools.ECS } } - private IndexableSet AcquireHashSetFromPool() + public override IEnumerable<(int, object)> UntypedInRelations(int entityID) { - if (listPool.Count == 0) + foreach (var (entity, relationData) in InRelations(entityID)) { - listPool.Push(new IndexableSet()); + yield return (entity.ID, relationData); } - - return listPool.Pop(); } - private void ReturnHashSetToPool(IndexableSet hashSet) + public override IEnumerable<(int, object)> UntypedOutRelations(int entityID) { - hashSet.Clear(); - listPool.Push(hashSet); + foreach (var (entity, relationData) in OutRelations(entityID)) + { + yield return (entity.ID, relationData); + } + } + + public override RelationStorage CreateStorage() + { + return new RelationStorage(); + } + + public override void Clear() + { + count = 0; + indices.Clear(); + + foreach (var set in inRelations.Values) + { + ReturnHashSetToPool(set); + } + inRelations.Clear(); + + foreach (var set in outRelations.Values) + { + ReturnHashSetToPool(set); + } + outRelations.Clear(); } } } diff --git a/src/Snapshot.cs b/src/Snapshot.cs index d7f06aa..992f121 100644 --- a/src/Snapshot.cs +++ b/src/Snapshot.cs @@ -1,3 +1,6 @@ +using System; +using System.Collections.Generic; + namespace MoonTools.ECS { public class Snapshot @@ -7,29 +10,71 @@ namespace MoonTools.ECS private EntityStorage SnapshotEntityStorage; private ComponentDepot SnapshotComponentDepot; + private RelationDepot SnapshotRelationDepot; + + private List SnapshotToWorldID = new List(); + private Dictionary WorldToSnapshotID = new Dictionary(); internal Snapshot(World world) { World = world; SnapshotEntityStorage = new EntityStorage(); SnapshotComponentDepot = new ComponentDepot(World.ComponentTypeIndices); + SnapshotRelationDepot = new RelationDepot(World.RelationTypeIndices); } public void Take(Filter filter) { Clear(); Filter = filter; - SnapshotComponentDepot.FillMissingStorages(World.ComponentDepot); + SnapshotComponentDepot.CreateMissingStorages(World.ComponentDepot); + SnapshotRelationDepot.CreateMissingStorages(World.RelationDepot); foreach (var worldEntity in filter.Entities) { var snapshotEntity = SnapshotEntityStorage.Create(); + WorldToSnapshotID.Add(worldEntity.ID, snapshotEntity.ID); + foreach (var componentTypeIndex in World.EntityStorage.ComponentTypeIndices(worldEntity.ID)) { SnapshotEntityStorage.SetComponent(snapshotEntity.ID, componentTypeIndex); SnapshotComponentDepot.Set(snapshotEntity.ID, componentTypeIndex, World.ComponentDepot.UntypedGet(worldEntity.ID, componentTypeIndex)); } } + + foreach (var worldEntity in filter.Entities) + { + var snapshotEntityID = WorldToSnapshotID[worldEntity.ID]; + + foreach (var relationTypeIndex in World.EntityStorage.RelationTypeIndices(worldEntity.ID)) + { + SnapshotEntityStorage.AddRelationKind(snapshotEntityID, relationTypeIndex); + + foreach (var (otherEntityID, relationData) in World.RelationDepot.InRelations(worldEntity.ID, relationTypeIndex)) + { +#if DEBUG + if (!World.FilterStorage.CheckSatisfied(otherEntityID, Filter.Signature)) + { + throw new InvalidOperationException($"Snapshot entity {worldEntity.ID} is related to non-snapshot entity {otherEntityID}!"); + } +#endif + var otherSnapshotID = WorldToSnapshotID[otherEntityID]; + SnapshotRelationDepot.Set(otherSnapshotID, snapshotEntityID, relationTypeIndex, relationData); + } + + foreach (var (otherEntityID, relationData) in World.RelationDepot.OutRelations(worldEntity.ID, relationTypeIndex)) + { +#if DEBUG + if (!World.FilterStorage.CheckSatisfied(otherEntityID, Filter.Signature)) + { + throw new InvalidOperationException($"Snapshot entity {worldEntity.ID} is related to non-snapshot entity {otherEntityID}!"); + } +#endif + var otherSnapshotID = WorldToSnapshotID[otherEntityID]; + SnapshotRelationDepot.Set(snapshotEntityID, otherSnapshotID, relationTypeIndex, relationData); + } + } + } } public void Restore() @@ -47,6 +92,7 @@ namespace MoonTools.ECS for (var i = 0; i < SnapshotEntityStorage.Count; i += 1) { var entity = World.CreateEntity(); + SnapshotToWorldID.Add(entity.ID); foreach (var componentTypeIndex in SnapshotEntityStorage.ComponentTypeIndices(i)) { @@ -55,12 +101,37 @@ namespace MoonTools.ECS World.ComponentDepot.Set(entity.ID, componentTypeIndex, SnapshotComponentDepot.UntypedGet(i, componentTypeIndex)); } } + + for (var i = 0; i < SnapshotEntityStorage.Count; i += 1) + { + var worldID = SnapshotToWorldID[i]; + + foreach (var relationTypeIndex in SnapshotEntityStorage.RelationTypeIndices(i)) + { + World.EntityStorage.AddRelationKind(worldID, relationTypeIndex); + + foreach (var (otherEntityID, relationData) in SnapshotRelationDepot.InRelations(i, relationTypeIndex)) + { + var otherEntityWorldID = SnapshotToWorldID[otherEntityID]; + World.RelationDepot.Set(otherEntityWorldID, worldID, relationTypeIndex, relationData); + } + + foreach (var (otherEntityID, relationData) in SnapshotRelationDepot.OutRelations(i, relationTypeIndex)) + { + var otherEntityWorldID = SnapshotToWorldID[otherEntityID]; + World.RelationDepot.Set(worldID, otherEntityWorldID, relationTypeIndex, relationData); + } + } + } } private void Clear() { SnapshotEntityStorage.Clear(); SnapshotComponentDepot.Clear(); + SnapshotRelationDepot.Clear(); + SnapshotToWorldID.Clear(); + WorldToSnapshotID.Clear(); } } } diff --git a/src/System.cs b/src/System.cs index 7f8bdee..f903de7 100644 --- a/src/System.cs +++ b/src/System.cs @@ -67,8 +67,8 @@ namespace MoonTools.ECS { RelationDepot.Set(new Relation(entityA, entityB), relationData); var relationTypeIndex = RelationTypeIndices.GetIndex(); - EntityStorage.AddRelation(entityA.ID, relationTypeIndex); - EntityStorage.AddRelation(entityB.ID, relationTypeIndex); + EntityStorage.AddRelationKind(entityA.ID, relationTypeIndex); + EntityStorage.AddRelationKind(entityB.ID, relationTypeIndex); } protected void Unrelate(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged