From 80615901958e54627813a776e0b6e522b4941d90 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 6 Dec 2022 01:59:22 -0800 Subject: [PATCH] initial snapshot system --- src/ComponentDepot.cs | 23 +++++++++++ src/ComponentStorage.cs | 46 +++------------------ src/EntityComponentReader.cs | 5 --- src/EntityStorage.cs | 18 ++++++++ src/IndexableSet.cs | 27 ------------ src/RelationStorage.cs | 58 -------------------------- src/Snapshot.cs | 66 ++++++++++++++++++++++++++++++ src/State/ComponentDepotState.cs | 11 ----- src/State/ComponentStorageState.cs | 27 ------------ src/State/EntityStorageState.cs | 13 ------ src/State/IndexableSetState.cs | 16 -------- src/State/RelationDepotState.cs | 10 ----- src/State/RelationStorageState.cs | 27 ------------ src/State/WorldState.cs | 16 -------- src/System.cs | 60 +++------------------------ src/TypeIndices.cs | 2 + src/World.cs | 39 +++++++++++++++--- 17 files changed, 153 insertions(+), 311 deletions(-) create mode 100644 src/Snapshot.cs delete mode 100644 src/State/ComponentDepotState.cs delete mode 100644 src/State/ComponentStorageState.cs delete mode 100644 src/State/EntityStorageState.cs delete mode 100644 src/State/IndexableSetState.cs delete mode 100644 src/State/RelationDepotState.cs delete mode 100644 src/State/RelationStorageState.cs delete mode 100644 src/State/WorldState.cs diff --git a/src/ComponentDepot.cs b/src/ComponentDepot.cs index 94487ec..7338fcf 100644 --- a/src/ComponentDepot.cs +++ b/src/ComponentDepot.cs @@ -87,5 +87,28 @@ namespace MoonTools.ECS { Lookup().Remove(entityID); } + + public void Clear() + { + for (var i = 0; i < ComponentTypeIndices.Count; i += 1) + { + if (storages[i] != null) + { + storages[i].Clear(); + } + } + } + + // used to fill snapshot depot with correct storages + public void FillMissingStorages(ComponentDepot other) + { + for (var i = 0; i < ComponentTypeIndices.Count; i += 1) + { + if (storages[i] == null && other.storages[i] != null) + { + storages[i] = other.storages[i].CreateStorage(); + } + } + } } } diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs index 6714b90..bd3fd66 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -8,12 +8,12 @@ namespace MoonTools.ECS { internal abstract void Set(int entityID, object component); public abstract bool Remove(int entityID); - public abstract ComponentStorageState CreateState(); - public abstract void Save(ComponentStorageState state); - public abstract void Load(ComponentStorageState state); + public abstract void Clear(); // used for debugging and template instantiation internal abstract object UntypedGet(int entityID); + // used to create correctly typed storage on snapshot + public abstract ComponentStorage CreateStorage(); } internal class ComponentStorage : ComponentStorage where TComponent : unmanaged @@ -101,7 +101,7 @@ namespace MoonTools.ECS return false; } - public void Clear() + public override void Clear() { nextID = 0; entityIDToStorageIndex.Clear(); @@ -123,43 +123,9 @@ namespace MoonTools.ECS return new Entity(entityIDs[0]); } - public override ComponentStorageState CreateState() + public override ComponentStorage CreateStorage() { - return ComponentStorageState.Create(nextID); - } - - public override void Save(ComponentStorageState state) - { - ReadOnlySpan entityIDBytes = MemoryMarshal.Cast(new ReadOnlySpan(entityIDs, 0, nextID)); - - if (entityIDBytes.Length > state.EntityIDs.Length) - { - Array.Resize(ref state.EntityIDs, entityIDBytes.Length); - } - entityIDBytes.CopyTo(state.EntityIDs); - - ReadOnlySpan componentBytes = MemoryMarshal.Cast(AllComponents()); - if (componentBytes.Length > state.Components.Length) - { - Array.Resize(ref state.Components, componentBytes.Length); - } - componentBytes.CopyTo(state.Components); - - state.Count = nextID; - } - - public override void Load(ComponentStorageState state) - { - state.EntityIDs.CopyTo(MemoryMarshal.Cast(entityIDs)); - state.Components.CopyTo(MemoryMarshal.Cast(components)); - - entityIDToStorageIndex.Clear(); - for (var i = 0; i < state.Count; i += 1) - { - entityIDToStorageIndex[entityIDs[i]] = i; - } - - nextID = state.Count; + return new ComponentStorage(); } } } diff --git a/src/EntityComponentReader.cs b/src/EntityComponentReader.cs index 91fa1ac..1678317 100644 --- a/src/EntityComponentReader.cs +++ b/src/EntityComponentReader.cs @@ -52,11 +52,6 @@ namespace MoonTools.ECS return ComponentDepot.GetSingletonEntity(); } - protected bool Exists(in Entity entity) - { - return EntityStorage.Exists(entity); - } - protected IEnumerable<(Entity, Entity, TRelationKind)> Relations() where TRelationKind : unmanaged { return RelationDepot.Relations(); diff --git a/src/EntityStorage.cs b/src/EntityStorage.cs index 44fd0a9..e803243 100644 --- a/src/EntityStorage.cs +++ b/src/EntityStorage.cs @@ -7,11 +7,14 @@ namespace MoonTools.ECS private int nextID = 0; // FIXME: why is this duplicated? private readonly Stack availableIDs = new Stack(); + // FIXME: this is only needed in debug mode private readonly HashSet availableIDHash = new HashSet(); private Dictionary> EntityToComponentTypeIndices = new Dictionary>(); private Dictionary> EntityToRelationTypeIndices = new Dictionary>(); + public int Count => nextID - availableIDs.Count; + public Entity Create() { var entity = new Entity(NextID()); @@ -70,6 +73,21 @@ namespace MoonTools.ECS return EntityToRelationTypeIndices[entityID]; } + public void Clear() + { + nextID = 0; + foreach (var componentSet in EntityToComponentTypeIndices.Values) + { + componentSet.Clear(); + } + foreach (var relationSet in EntityToRelationTypeIndices.Values) + { + relationSet.Clear(); + } + availableIDs.Clear(); + availableIDHash.Clear(); + } + private int NextID() { if (availableIDs.Count > 0) diff --git a/src/IndexableSet.cs b/src/IndexableSet.cs index bacb429..35ca411 100644 --- a/src/IndexableSet.cs +++ b/src/IndexableSet.cs @@ -84,32 +84,5 @@ namespace MoonTools.ECS { Count = 0; } - - public void Save(IndexableSetState state) - { - ReadOnlySpan arrayBytes = MemoryMarshal.Cast(array); - - if (arrayBytes.Length > state.Array.Length) - { - Array.Resize(ref state.Array, arrayBytes.Length); - } - - arrayBytes.CopyTo(state.Array); - - state.Count = Count; - } - - public void Load(IndexableSetState state) - { - state.Array.CopyTo(MemoryMarshal.Cast(array)); - - indices.Clear(); - for (var i = 0; i < state.Count; i += 1) - { - indices[array[i]] = i; - } - - Count = state.Count; - } } } diff --git a/src/RelationStorage.cs b/src/RelationStorage.cs index 05110eb..52907e3 100644 --- a/src/RelationStorage.cs +++ b/src/RelationStorage.cs @@ -6,9 +6,6 @@ namespace MoonTools.ECS { internal abstract class RelationStorage { - public abstract RelationStorageState CreateState(); - public abstract void Save(RelationStorageState state); - public abstract void Load(RelationStorageState state); public abstract void UnrelateAll(int entityID); } @@ -228,60 +225,5 @@ namespace MoonTools.ECS hashSet.Clear(); listPool.Push(hashSet); } - - public override RelationStorageState CreateState() - { - return RelationStorageState.Create(count); - } - - public override void Save(RelationStorageState state) - { - ReadOnlySpan relationBytes = MemoryMarshal.Cast(relations); - - if (relationBytes.Length > state.Relations.Length) - { - Array.Resize(ref state.Relations, relationBytes.Length); - } - relationBytes.CopyTo(state.Relations); - - ReadOnlySpan relationDataBytes = MemoryMarshal.Cast(relationDatas); - - if (relationDataBytes.Length > state.RelationDatas.Length) - { - Array.Resize(ref state.RelationDatas, relationDataBytes.Length); - } - relationDataBytes.CopyTo(state.RelationDatas); - - state.Count = count; - } - - public override void Load(RelationStorageState state) - { - state.Relations.CopyTo(MemoryMarshal.Cast(relations)); - state.RelationDatas.CopyTo(MemoryMarshal.Cast(relationDatas)); - - indices.Clear(); - outRelations.Clear(); - inRelations.Clear(); - for (var i = 0; i < state.Count; i += 1) - { - var relation = relations[i]; - indices[relation] = i; - - if (!outRelations.ContainsKey(relation.A.ID)) - { - outRelations[relation.A.ID] = AcquireHashSetFromPool(); - } - outRelations[relation.A.ID].Add(relation.B.ID); - - if (!inRelations.ContainsKey(relation.B.ID)) - { - inRelations[relation.B.ID] = AcquireHashSetFromPool(); - } - inRelations[relation.B.ID].Add(relation.A.ID); - } - - count = state.Count; - } } } diff --git a/src/Snapshot.cs b/src/Snapshot.cs new file mode 100644 index 0000000..d7f06aa --- /dev/null +++ b/src/Snapshot.cs @@ -0,0 +1,66 @@ +namespace MoonTools.ECS +{ + public class Snapshot + { + private World World; + private Filter? Filter; + + private EntityStorage SnapshotEntityStorage; + private ComponentDepot SnapshotComponentDepot; + + internal Snapshot(World world) + { + World = world; + SnapshotEntityStorage = new EntityStorage(); + SnapshotComponentDepot = new ComponentDepot(World.ComponentTypeIndices); + } + + public void Take(Filter filter) + { + Clear(); + Filter = filter; + SnapshotComponentDepot.FillMissingStorages(World.ComponentDepot); + + foreach (var worldEntity in filter.Entities) + { + var snapshotEntity = SnapshotEntityStorage.Create(); + 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)); + } + } + } + + public 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(); + + 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)); + } + } + } + + private void Clear() + { + SnapshotEntityStorage.Clear(); + SnapshotComponentDepot.Clear(); + } + } +} diff --git a/src/State/ComponentDepotState.cs b/src/State/ComponentDepotState.cs deleted file mode 100644 index 2370dc6..0000000 --- a/src/State/ComponentDepotState.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MoonTools.ECS -{ - internal class ComponentDepotState - { - public Dictionary StorageStates = new Dictionary(); - public Dictionary> FilterStates = new Dictionary>(); - } -} diff --git a/src/State/ComponentStorageState.cs b/src/State/ComponentStorageState.cs deleted file mode 100644 index ad4872d..0000000 --- a/src/State/ComponentStorageState.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; - -namespace MoonTools.ECS -{ - internal class ComponentStorageState - { - public int Count; - public byte[] EntityIDs; - public byte[] Components; - - public unsafe static ComponentStorageState Create(int count) where TComponent : unmanaged - { - return new ComponentStorageState( - count, - count * sizeof(int), - count * sizeof(TComponent) - ); - } - - private ComponentStorageState(int count, int entityIDSize, int componentSize) - { - Count = count; - EntityIDs = new byte[entityIDSize]; - Components = new byte[componentSize]; - } - } -} diff --git a/src/State/EntityStorageState.cs b/src/State/EntityStorageState.cs deleted file mode 100644 index aebfec3..0000000 --- a/src/State/EntityStorageState.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; - -namespace MoonTools.ECS -{ - internal class EntityStorageState - { - public int NextID; - public List availableIDs = new List(); - - public Dictionary> EntityToComponentTypeIndices = new Dictionary>(); - public Dictionary> EntityToRelationTypeIndices = new Dictionary>(); - } -} diff --git a/src/State/IndexableSetState.cs b/src/State/IndexableSetState.cs deleted file mode 100644 index 4367252..0000000 --- a/src/State/IndexableSetState.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Collections.Generic; - -namespace MoonTools.ECS -{ - internal class IndexableSetState where T : unmanaged - { - public int Count; - public byte[] Array; - - public unsafe IndexableSetState(int count) - { - Count = count; - Array = new byte[sizeof(T) * count]; - } - } -} diff --git a/src/State/RelationDepotState.cs b/src/State/RelationDepotState.cs deleted file mode 100644 index 58e1315..0000000 --- a/src/State/RelationDepotState.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MoonTools.ECS -{ - internal class RelationDepotState - { - public Dictionary StorageStates = new Dictionary(); - } -} diff --git a/src/State/RelationStorageState.cs b/src/State/RelationStorageState.cs deleted file mode 100644 index d210a5d..0000000 --- a/src/State/RelationStorageState.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; - -namespace MoonTools.ECS -{ - internal class RelationStorageState - { - public int Count; - public byte[] Relations; - public byte[] RelationDatas; - - public unsafe static RelationStorageState Create(int count) where TRelation : unmanaged - { - return new RelationStorageState( - count, - count * sizeof(Relation), - count * sizeof(TRelation) - ); - } - - private RelationStorageState(int count, int relationSize, int relationDataSize) - { - Count = count; - Relations = new byte[relationSize]; - RelationDatas = new byte[relationDataSize]; - } - } -} diff --git a/src/State/WorldState.cs b/src/State/WorldState.cs deleted file mode 100644 index d422745..0000000 --- a/src/State/WorldState.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace MoonTools.ECS -{ - public class WorldState - { - internal readonly ComponentDepotState ComponentDepotState; - internal readonly EntityStorageState EntityStorageState; - internal readonly RelationDepotState RelationDepotState; - - public WorldState() - { - ComponentDepotState = new ComponentDepotState(); - EntityStorageState = new EntityStorageState(); - RelationDepotState = new RelationDepotState(); - } - } -} diff --git a/src/System.cs b/src/System.cs index 888a9bd..7f8bdee 100644 --- a/src/System.cs +++ b/src/System.cs @@ -11,27 +11,9 @@ namespace MoonTools.ECS public abstract void Update(TimeSpan delta); - protected Entity CreateEntity() - { - return EntityStorage.Create(); - } + protected Entity CreateEntity() => World.CreateEntity(); - protected void Set(in Entity entity, in TComponent component) where TComponent : unmanaged - { -#if DEBUG - // check for use after destroy - if (!Exists(entity)) - { - throw new ArgumentException("This entity is not valid!"); - } -#endif - if (EntityStorage.SetComponent(entity.ID, ComponentTypeIndices.GetIndex())) - { - FilterStorage.Check(entity.ID); - } - - ComponentDepot.Set(entity.ID, component); - } + 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 { @@ -42,27 +24,9 @@ namespace MoonTools.ECS } } - protected void Set(in Template template, in TComponent component) where TComponent : unmanaged - { - var componentTypeIndex = ComponentTypeIndices.GetIndex(); - TemplateStorage.SetComponent(template.ID, componentTypeIndex); - TemplateComponentDepot.Set(template.ID, component); - ComponentDepot.Register(componentTypeIndex); - } + protected void Set(in Template template, in TComponent component) where TComponent : unmanaged => World.Set(template, component); - protected Entity Instantiate(in Template template) - { - var entity = EntityStorage.Create(); - - foreach (var componentTypeIndex in TemplateStorage.ComponentTypeIndices(template.ID)) - { - EntityStorage.SetComponent(entity.ID, componentTypeIndex); - FilterStorage.Check(entity.ID, componentTypeIndex); - ComponentDepot.Set(entity.ID, componentTypeIndex, TemplateComponentDepot.UntypedGet(template.ID, componentTypeIndex)); - } - - return entity; - } + protected Entity Instantiate(in Template template) => World.Instantiate(template); protected ReadOnlySpan ReadMessages() where TMessage : unmanaged { @@ -128,20 +92,6 @@ namespace MoonTools.ECS EntityStorage.RemoveRelation(entity.ID, RelationTypeIndices.GetIndex()); } - protected void Destroy(in Entity entity) - { - foreach (var componentTypeIndex in EntityStorage.ComponentTypeIndices(entity.ID)) - { - ComponentDepot.Remove(entity.ID, componentTypeIndex); - FilterStorage.RemoveEntity(entity.ID, componentTypeIndex); - } - - foreach (var relationTypeIndex in EntityStorage.RelationTypeIndices(entity.ID)) - { - RelationDepot.UnrelateAll(entity.ID, relationTypeIndex); - } - - EntityStorage.Destroy(entity); - } + protected void Destroy(in Entity entity) => World.Destroy(entity); } } diff --git a/src/TypeIndices.cs b/src/TypeIndices.cs index 60c1adb..53bf700 100644 --- a/src/TypeIndices.cs +++ b/src/TypeIndices.cs @@ -7,6 +7,7 @@ namespace MoonTools.ECS { Dictionary TypeToIndex = new Dictionary(); int nextID = 0; + public int Count => TypeToIndex.Count; public int GetIndex() where T : unmanaged { @@ -24,6 +25,7 @@ namespace MoonTools.ECS return TypeToIndex[type]; } + #if DEBUG public IEnumerable Types => TypeToIndex.Keys; #endif diff --git a/src/World.cs b/src/World.cs index d0b4834..85873c0 100644 --- a/src/World.cs +++ b/src/World.cs @@ -1,4 +1,6 @@ -namespace MoonTools.ECS +using System; + +namespace MoonTools.ECS { public class World { @@ -9,11 +11,11 @@ internal readonly MessageDepot MessageDepot = new MessageDepot(); internal readonly RelationDepot RelationDepot; internal readonly FilterStorage FilterStorage; + public FilterBuilder FilterBuilder => new FilterBuilder(FilterStorage, ComponentTypeIndices); internal readonly TemplateStorage TemplateStorage = new TemplateStorage(); internal readonly ComponentDepot TemplateComponentDepot; - public World() { ComponentDepot = new ComponentDepot(ComponentTypeIndices); @@ -29,6 +31,13 @@ public void Set(Entity entity, in TComponent component) where TComponent : unmanaged { +#if DEBUG + // check for use after destroy + if (!EntityStorage.Exists(entity)) + { + throw new InvalidOperationException("This entity is not valid!"); + } +#endif if (EntityStorage.SetComponent(entity.ID, ComponentTypeIndices.GetIndex())) { FilterStorage.Check(entity.ID); @@ -44,11 +53,12 @@ public void Set(in Template template, in TComponent component) where TComponent : unmanaged { - TemplateStorage.SetComponent(template.ID, ComponentTypeIndices.GetIndex()); + var componentTypeIndex = ComponentTypeIndices.GetIndex(); + TemplateStorage.SetComponent(template.ID, componentTypeIndex); TemplateComponentDepot.Set(template.ID, component); + ComponentDepot.Register(componentTypeIndex); } - // TODO: TEST ME!!! public Entity Instantiate(in Template template) { var entity = EntityStorage.Create(); @@ -68,14 +78,31 @@ MessageDepot.Add(message); } + public void Destroy(in Entity entity) + { + foreach (var componentTypeIndex in EntityStorage.ComponentTypeIndices(entity.ID)) + { + ComponentDepot.Remove(entity.ID, componentTypeIndex); + FilterStorage.RemoveEntity(entity.ID, componentTypeIndex); + } + + foreach (var relationTypeIndex in EntityStorage.RelationTypeIndices(entity.ID)) + { + RelationDepot.UnrelateAll(entity.ID, relationTypeIndex); + } + + EntityStorage.Destroy(entity); + } + + public void FinishUpdate() { MessageDepot.Clear(); } - public WorldState CreateState() + public Snapshot CreateSnapshot() { - return new WorldState(); + return new Snapshot(this); } } }