From 4d45d056183fd93aef5e2678233f469b91ba56cc Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Mon, 10 Jul 2023 22:36:34 +0000 Subject: [PATCH] World Transfer + Entity Tags (#4) - removed Snapshot system - Entities can now be transferred between Worlds along with their Components and Relations using a Filter - Entities can now be given a string tag on creation - Manipulators can update an Entity's string tag --- src/ComponentDepot.cs | 12 +++- src/EntityComponentReader.cs | 5 ++ src/EntityStorage.cs | 20 +++++- src/Manipulator.cs | 3 +- src/RelationDepot.cs | 12 +++- src/Snapshot.cs | 122 ----------------------------------- src/World.cs | 96 +++++++++++++++++++++++++-- 7 files changed, 138 insertions(+), 132 deletions(-) delete mode 100644 src/Snapshot.cs diff --git a/src/ComponentDepot.cs b/src/ComponentDepot.cs index b93300d..c908931 100644 --- a/src/ComponentDepot.cs +++ b/src/ComponentDepot.cs @@ -103,7 +103,17 @@ namespace MoonTools.ECS public void CreateMissingStorages(ComponentDepot other) { - for (var i = 0; i < ComponentTypeIndices.Count; i += 1) + while (other.ComponentTypeIndices.Count >= storages.Length) + { + Array.Resize(ref storages, storages.Length * 2); + } + + while (other.ComponentTypeIndices.Count >= other.storages.Length) + { + Array.Resize(ref other.storages, other.storages.Length * 2); + } + + for (var i = 0; i < other.ComponentTypeIndices.Count; i += 1) { if (storages[i] == null && other.storages[i] != null) { diff --git a/src/EntityComponentReader.cs b/src/EntityComponentReader.cs index 976d1c1..b669043 100644 --- a/src/EntityComponentReader.cs +++ b/src/EntityComponentReader.cs @@ -20,6 +20,11 @@ namespace MoonTools.ECS World = world; } + protected string GetTag(in Entity entity) + { + return World.GetTag(entity); + } + protected ReadOnlySpan ReadComponents() where TComponent : unmanaged { return ComponentDepot.ReadComponents(); diff --git a/src/EntityStorage.cs b/src/EntityStorage.cs index b0a7b3e..b73ad05 100644 --- a/src/EntityStorage.cs +++ b/src/EntityStorage.cs @@ -15,17 +15,24 @@ namespace MoonTools.ECS public int Count => nextID - availableIDs.Count; - public Entity Create() + public Dictionary Tags = new Dictionary(); + + public Entity Create(string tag) { var entity = new Entity(NextID()); + if (!EntityToComponentTypeIndices.ContainsKey(entity.ID)) { EntityToComponentTypeIndices.Add(entity.ID, new HashSet()); } + if (!EntityToRelationTypeIndices.ContainsKey(entity.ID)) { EntityToRelationTypeIndices.Add(entity.ID, new HashSet()); } + + Tags[entity.ID] = tag; + return entity; } @@ -34,10 +41,16 @@ namespace MoonTools.ECS return Taken(entity.ID); } + public void Tag(in Entity entity, string tag) + { + Tags[entity.ID] = tag; + } + public void Destroy(in Entity entity) { EntityToComponentTypeIndices[entity.ID].Clear(); EntityToRelationTypeIndices[entity.ID].Clear(); + Tags.Remove(entity.ID); Release(entity.ID); } @@ -68,6 +81,11 @@ namespace MoonTools.ECS EntityToRelationTypeIndices[entityId].Remove(relationIndex); } + public string Tag(int entityID) + { + return Tags[entityID]; + } + public HashSet ComponentTypeIndices(int entityID) { return EntityToComponentTypeIndices[entityID]; diff --git a/src/Manipulator.cs b/src/Manipulator.cs index 4b336c1..22a6780 100644 --- a/src/Manipulator.cs +++ b/src/Manipulator.cs @@ -6,7 +6,8 @@ namespace MoonTools.ECS { } - protected Entity CreateEntity() => World.CreateEntity(); + protected Entity CreateEntity(string tag = "") => World.CreateEntity(tag); + protected void Tag(Entity entity, string tag) => World.Tag(entity, tag); protected void Set(in Entity entity, in TComponent component) where TComponent : unmanaged => World.Set(entity, component); protected void Remove(in Entity entity) where TComponent : unmanaged => World.Remove(entity); protected void Relate(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged => World.Relate(entityA, entityB, relationData); diff --git a/src/RelationDepot.cs b/src/RelationDepot.cs index 860826d..67ae436 100644 --- a/src/RelationDepot.cs +++ b/src/RelationDepot.cs @@ -156,7 +156,17 @@ namespace MoonTools.ECS public void CreateMissingStorages(RelationDepot other) { - for (var i = 0; i < RelationTypeIndices.Count; i += 1) + while (other.RelationTypeIndices.Count >= storages.Length) + { + Array.Resize(ref storages, storages.Length * 2); + } + + while (other.RelationTypeIndices.Count >= other.storages.Length) + { + Array.Resize(ref other.storages, other.storages.Length * 2); + } + + for (var i = 0; i < other.RelationTypeIndices.Count; i += 1) { if (storages[i] == null && other.storages[i] != null) { diff --git a/src/Snapshot.cs b/src/Snapshot.cs deleted file mode 100644 index 6fd6bcb..0000000 --- a/src/Snapshot.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MoonTools.ECS -{ - public class Snapshot - { - private World World; - private Filter? Filter; - - 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 unsafe void Take(Filter filter) - { - Clear(); - Filter = filter; - 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 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 relationStorageIndex = World.RelationDepot.GetStorageIndex(relationTypeIndex, worldEntity, otherEntityID); - var otherSnapshotID = WorldToSnapshotID[otherEntityID]; - SnapshotEntityStorage.AddRelationKind(otherSnapshotID, relationTypeIndex); - SnapshotRelationDepot.Set(snapshotEntityID, otherSnapshotID, relationTypeIndex, World.RelationDepot.Get(relationTypeIndex, relationStorageIndex)); - } - } - } - } - - public unsafe void Restore() - { - if (Filter == null) - { - return; - } - - foreach (var entity in Filter.Entities) - { - World.Destroy(entity); - } - - for (var i = 0; i < SnapshotEntityStorage.Count; i += 1) - { - var entity = World.CreateEntity(); - SnapshotToWorldID.Add(entity.ID); - - foreach (var componentTypeIndex in SnapshotEntityStorage.ComponentTypeIndices(i)) - { - World.EntityStorage.SetComponent(entity.ID, componentTypeIndex); - World.FilterStorage.Check(entity.ID, componentTypeIndex); - 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 in SnapshotRelationDepot.OutRelations(i, relationTypeIndex)) - { - var relationStorageIndex = SnapshotRelationDepot.GetStorageIndex(relationTypeIndex, i, otherEntityID); - var otherEntityWorldID = SnapshotToWorldID[otherEntityID]; - World.RelationDepot.Set(worldID, otherEntityWorldID, relationTypeIndex, SnapshotRelationDepot.Get(relationTypeIndex, relationStorageIndex)); - } - } - } - } - - private void Clear() - { - SnapshotEntityStorage.Clear(); - SnapshotComponentDepot.Clear(); - SnapshotRelationDepot.Clear(); - SnapshotToWorldID.Clear(); - WorldToSnapshotID.Clear(); - } - } -} diff --git a/src/World.cs b/src/World.cs index 43c26c8..81a8ca5 100644 --- a/src/World.cs +++ b/src/World.cs @@ -1,11 +1,12 @@ using System; +using System.Collections.Generic; namespace MoonTools.ECS { public class World { - internal readonly TypeIndices ComponentTypeIndices = new TypeIndices(); - internal readonly TypeIndices RelationTypeIndices = new TypeIndices(); + internal readonly static TypeIndices ComponentTypeIndices = new TypeIndices(); + internal readonly static TypeIndices RelationTypeIndices = new TypeIndices(); internal readonly EntityStorage EntityStorage = new EntityStorage(); internal readonly ComponentDepot ComponentDepot; internal readonly MessageDepot MessageDepot = new MessageDepot(); @@ -23,9 +24,19 @@ namespace MoonTools.ECS TemplateComponentDepot = new ComponentDepot(ComponentTypeIndices); } - public Entity CreateEntity() + public Entity CreateEntity(string tag = "") { - return EntityStorage.Create(); + return EntityStorage.Create(tag); + } + + public void Tag(Entity entity, string tag) + { + EntityStorage.Tag(entity, tag); + } + + public string GetTag(Entity entity) + { + return EntityStorage.Tag(entity); } public void Set(Entity entity, in TComponent component) where TComponent : unmanaged @@ -45,6 +56,17 @@ namespace MoonTools.ECS } } + // untyped version for Transfer + internal unsafe void Set(Entity entity, int componentTypeIndex, void* component) + { + ComponentDepot.Set(entity.ID, componentTypeIndex, component); + + if (EntityStorage.SetComponent(entity.ID, componentTypeIndex)) + { + FilterStorage.Check(entity.ID, componentTypeIndex); + } + } + public void Remove(in Entity entity) where TComponent : unmanaged { if (EntityStorage.RemoveComponent(entity.ID, ComponentTypeIndices.GetIndex())) @@ -63,6 +85,14 @@ namespace MoonTools.ECS EntityStorage.AddRelationKind(entityB.ID, relationTypeIndex); } + // untyped version for Transfer + internal unsafe void Relate(Entity entityA, Entity entityB, int relationTypeIndex, void* relationData) + { + RelationDepot.Set(entityA, entityB, relationTypeIndex, relationData); + EntityStorage.AddRelationKind(entityA.ID, relationTypeIndex); + EntityStorage.AddRelationKind(entityB.ID, relationTypeIndex); + } + public void Unrelate(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged { var (aEmpty, bEmpty) = RelationDepot.Remove(entityA, entityB); @@ -118,9 +148,63 @@ namespace MoonTools.ECS MessageDepot.Clear(); } - public Snapshot CreateSnapshot() + private Dictionary WorldToTransferID = new Dictionary(); + + // FIXME: there's probably a better way to handle Filters so they are not world-bound + public unsafe void Transfer(World other, Filter filter, Filter otherFilter) { - return new Snapshot(this); + WorldToTransferID.Clear(); + other.ComponentDepot.CreateMissingStorages(ComponentDepot); + other.RelationDepot.CreateMissingStorages(RelationDepot); + + // destroy all entities matching the filter + foreach (var entity in otherFilter.Entities) + { + other.Destroy(entity); + } + + // create entities + foreach (var entity in filter.Entities) + { + var otherWorldEntity = other.CreateEntity(GetTag(entity)); + WorldToTransferID.Add(entity.ID, otherWorldEntity.ID); + } + + // set relations before components so filters don't freak out + foreach (var entity in filter.Entities) + { + var otherWorldEntityA = WorldToTransferID[entity.ID]; + + foreach (var relationTypeIndex in EntityStorage.RelationTypeIndices(entity.ID)) + { + foreach (var entityB in RelationDepot.OutRelations(entity.ID, relationTypeIndex)) + { + var storageIndex = RelationDepot.GetStorageIndex(relationTypeIndex, entity.ID, entityB); + + int otherWorldEntityB; + if (WorldToTransferID.TryGetValue(entityB, out otherWorldEntityB)) + { + other.Relate(otherWorldEntityA, otherWorldEntityB, relationTypeIndex, RelationDepot.Get(relationTypeIndex, storageIndex)); + } + else + { + // related entity is not in the filter + throw new Exception($"Missing transfer entity! {EntityStorage.Tag(entity.ID)} related to {EntityStorage.Tag(entityB.ID)}"); + } + } + } + } + + // set components + foreach (var entity in filter.Entities) + { + var otherWorldEntity = WorldToTransferID[entity.ID]; + + foreach (var componentTypeIndex in EntityStorage.ComponentTypeIndices(entity.ID)) + { + other.Set(otherWorldEntity, componentTypeIndex, ComponentDepot.UntypedGet(entity.ID, componentTypeIndex)); + } + } } } }