From 2fc1b68f410ca86d84191dbf15926c091779ecac Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 26 Apr 2022 18:43:39 -0700 Subject: [PATCH 1/6] initial serialization structure --- src/ComponentDepot.cs | 24 +++++++++++++++++++++ src/ComponentDepotState.cs | 10 +++++++++ src/ComponentStorage.cs | 37 +++++++++++++++++++++++++++++++- src/ComponentStorageState.cs | 31 +++++++++++++++++++++++++++ src/EntityComponentReader.cs | 41 ++++++++++++++---------------------- src/Renderer.cs | 5 +---- src/System.cs | 34 ++++++++++++------------------ src/World.cs | 35 ++++++++++++++---------------- 8 files changed, 147 insertions(+), 70 deletions(-) create mode 100644 src/ComponentDepotState.cs create mode 100644 src/ComponentStorageState.cs diff --git a/src/ComponentDepot.cs b/src/ComponentDepot.cs index 482214e..bd43661 100644 --- a/src/ComponentDepot.cs +++ b/src/ComponentDepot.cs @@ -231,6 +231,30 @@ namespace MoonTools.ECS filterSignatureToEntityIDs[filterSignature].Add(entityID); } + public ComponentDepotState CreateState() + { + return new ComponentDepotState(); + } + + public void Serialize(ComponentDepotState state) + { + state.StorageStates.Clear(); + foreach (var (type, storage) in storages) + { + var storageState = storage.CreateState(); + storage.Serialize(storageState); + state.StorageStates.Add(type, storageState); + } + } + + public void Deserialize(ComponentDepotState state) + { + foreach (var (type, storageState) in state.StorageStates) + { + storages[type].Deserialize(storageState); + } + } + #if DEBUG public IEnumerable Debug_GetAllComponents(int entityID) { diff --git a/src/ComponentDepotState.cs b/src/ComponentDepotState.cs new file mode 100644 index 0000000..78481d1 --- /dev/null +++ b/src/ComponentDepotState.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace MoonTools.ECS +{ + public class ComponentDepotState + { + public Dictionary StorageStates = new Dictionary(); + } +} diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs index 15a5db2..51d5750 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; namespace MoonTools.ECS { @@ -8,9 +9,11 @@ namespace MoonTools.ECS public abstract bool Has(int entityID); public abstract void Remove(int entityID); public abstract object Debug_Get(int entityID); + public abstract ComponentStorageState CreateState(); + public abstract void Serialize(ComponentStorageState state); + public abstract void Deserialize(ComponentStorageState state); } - // FIXME: we can probably get rid of this weird entity storage system by using filters internal class ComponentStorage : ComponentStorage where TComponent : struct { private int nextID; @@ -106,5 +109,37 @@ namespace MoonTools.ECS { return new Entity(entityIDs[0]); } + + public override ComponentStorageState CreateState() + { + return ComponentStorageState.Create(nextID); + } + + public override void Serialize(ComponentStorageState serializedComponentStorage) + { + ReadOnlySpan entityIDBytes = MemoryMarshal.Cast(new ReadOnlySpan(entityIDs, 0, nextID)); + entityIDBytes.CopyTo(serializedComponentStorage.EntityIDs); + + ReadOnlySpan componentBytes = MemoryMarshal.Cast(AllComponents()); + componentBytes.CopyTo(serializedComponentStorage.Components); + + serializedComponentStorage.EntityIdToStorageIndex.Clear(); + foreach (var kvp in entityIDToStorageIndex) + { + serializedComponentStorage.EntityIdToStorageIndex[kvp.Key] = kvp.Value; + } + } + + public override void Deserialize(ComponentStorageState serializedComponentStorage) + { + serializedComponentStorage.EntityIDs.CopyTo(MemoryMarshal.Cast(entityIDs)); + serializedComponentStorage.Components.CopyTo(MemoryMarshal.Cast(components)); + + entityIDToStorageIndex.Clear(); + foreach (var kvp in serializedComponentStorage.EntityIdToStorageIndex) + { + entityIDToStorageIndex[kvp.Key] = kvp.Value; + } + } } } diff --git a/src/ComponentStorageState.cs b/src/ComponentStorageState.cs new file mode 100644 index 0000000..dd98063 --- /dev/null +++ b/src/ComponentStorageState.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace MoonTools.ECS +{ + // for saving and loading component storage + public class ComponentStorageState + { + public int Count; + public Dictionary EntityIdToStorageIndex; + public byte[] EntityIDs; + public byte[] Components; + + public static ComponentStorageState Create(int count) + { + return new ComponentStorageState( + count, + count * Marshal.SizeOf(), + count * Marshal.SizeOf() + ); + } + + private ComponentStorageState(int count, int entityIDSize, int componentSize) + { + Count = count; + EntityIdToStorageIndex = new Dictionary(count); + EntityIDs = new byte[entityIDSize]; + Components = new byte[componentSize]; + } + } +} diff --git a/src/EntityComponentReader.cs b/src/EntityComponentReader.cs index 5449d95..b39de4f 100644 --- a/src/EntityComponentReader.cs +++ b/src/EntityComponentReader.cs @@ -5,52 +5,43 @@ namespace MoonTools.ECS { public abstract class EntityComponentReader { - internal EntityStorage EntityStorage; - internal ComponentDepot ComponentDepot; - internal RelationDepot RelationDepot; + protected readonly World World; + internal EntityStorage EntityStorage => World.EntityStorage; + internal ComponentDepot ComponentDepot => World.ComponentDepot; + internal RelationDepot RelationDepot => World.RelationDepot; protected FilterBuilder FilterBuilder => new FilterBuilder(ComponentDepot); - internal void RegisterEntityStorage(EntityStorage entityStorage) + public EntityComponentReader(World world) { - EntityStorage = entityStorage; + World = world; } - internal void RegisterComponentDepot(ComponentDepot componentDepot) - { - ComponentDepot = componentDepot; - } - - internal void RegisterRelationDepot(RelationDepot relationDepot) - { - RelationDepot = relationDepot; - } - - protected ReadOnlySpan ReadComponents() where TComponent : struct + protected ReadOnlySpan ReadComponents() where TComponent : unmanaged { return ComponentDepot.ReadComponents(); } - protected bool Has(in Entity entity) where TComponent : struct + protected bool Has(in Entity entity) where TComponent : unmanaged { return ComponentDepot.Has(entity.ID); } - protected bool Some() where TComponent : struct + protected bool Some() where TComponent : unmanaged { return ComponentDepot.Some(); } - protected ref readonly TComponent Get(in Entity entity) where TComponent : struct + protected ref readonly TComponent Get(in Entity entity) where TComponent : unmanaged { return ref ComponentDepot.Get(entity.ID); } - protected ref readonly TComponent GetSingleton() where TComponent : struct + protected ref readonly TComponent GetSingleton() where TComponent : unmanaged { return ref ComponentDepot.Get(); } - protected Entity GetSingletonEntity() where TComponent : struct + protected Entity GetSingletonEntity() where TComponent : unmanaged { return ComponentDepot.GetSingletonEntity(); } @@ -60,22 +51,22 @@ namespace MoonTools.ECS return EntityStorage.Exists(entity); } - protected IEnumerable<(Entity, Entity, TRelationKind)> Relations() where TRelationKind : struct + protected IEnumerable<(Entity, Entity, TRelationKind)> Relations() where TRelationKind : unmanaged { return RelationDepot.Relations(); } - protected bool Related(in Entity a, in Entity b) where TRelationKind : struct + protected bool Related(in Entity a, in Entity b) where TRelationKind : unmanaged { return RelationDepot.Related(a.ID, b.ID); } - protected IEnumerable<(Entity, TRelationKind)> RelatedToA(in Entity entity) where TRelationKind : struct + protected IEnumerable<(Entity, TRelationKind)> RelatedToA(in Entity entity) where TRelationKind : unmanaged { return RelationDepot.RelatedToA(entity.ID); } - protected IEnumerable<(Entity, TRelationKind)> RelatedToB(in Entity entity) where TRelationKind : struct + protected IEnumerable<(Entity, TRelationKind)> RelatedToB(in Entity entity) where TRelationKind : unmanaged { return RelationDepot.RelatedToB(entity.ID); } diff --git a/src/Renderer.cs b/src/Renderer.cs index 07531f3..f6118c2 100644 --- a/src/Renderer.cs +++ b/src/Renderer.cs @@ -2,9 +2,6 @@ { public abstract class Renderer : EntityComponentReader { - public Renderer(World world) - { - world.AddRenderer(this); - } + public Renderer(World world) : base(world) { } } } diff --git a/src/System.cs b/src/System.cs index 66eee93..1983d17 100644 --- a/src/System.cs +++ b/src/System.cs @@ -5,17 +5,9 @@ namespace MoonTools.ECS { public abstract class System : EntityComponentReader { - internal MessageDepot MessageDepot; + internal MessageDepot MessageDepot => World.MessageDepot; - internal void RegisterMessageDepot(MessageDepot messageDepot) - { - MessageDepot = messageDepot; - } - - public System(World world) - { - world.AddSystem(this); - } + public System(World world) : base(world) { } public abstract void Update(TimeSpan delta); @@ -24,7 +16,7 @@ namespace MoonTools.ECS return EntityStorage.Create(); } - protected void Set(in Entity entity, in TComponent component) where TComponent : struct + protected void Set(in Entity entity, in TComponent component) where TComponent : unmanaged { #if DEBUG // check for use after destroy @@ -36,52 +28,52 @@ namespace MoonTools.ECS ComponentDepot.Set(entity.ID, component); } - protected void Remove(in Entity entity) where TComponent : struct + protected void Remove(in Entity entity) where TComponent : unmanaged { ComponentDepot.Remove(entity.ID); } - protected ReadOnlySpan ReadMessages() where TMessage : struct + protected ReadOnlySpan ReadMessages() where TMessage : unmanaged { return MessageDepot.All(); } - protected TMessage ReadMessage() where TMessage : struct + protected TMessage ReadMessage() where TMessage : unmanaged { return MessageDepot.First(); } - protected bool SomeMessage() where TMessage : struct + protected bool SomeMessage() where TMessage : unmanaged { return MessageDepot.Some(); } - protected IEnumerable ReadMessagesWithEntity(in Entity entity) where TMessage : struct, IHasEntity + protected IEnumerable ReadMessagesWithEntity(in Entity entity) where TMessage : unmanaged, IHasEntity { return MessageDepot.WithEntity(entity.ID); } - protected ref readonly TMessage ReadMessageWithEntity(in Entity entity) where TMessage : struct, IHasEntity + protected ref readonly TMessage ReadMessageWithEntity(in Entity entity) where TMessage : unmanaged, IHasEntity { return ref MessageDepot.FirstWithEntity(entity.ID); } - protected bool SomeMessageWithEntity(in Entity entity) where TMessage : struct, IHasEntity + protected bool SomeMessageWithEntity(in Entity entity) where TMessage : unmanaged, IHasEntity { return MessageDepot.SomeWithEntity(entity.ID); } - protected void Send(in TMessage message) where TMessage : struct + protected void Send(in TMessage message) where TMessage : unmanaged { MessageDepot.Add(message); } - protected void Relate(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : struct + protected void Relate(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged { RelationDepot.Add(new Relation(entityA, entityB), relationData); } - protected void Unrelate(in Entity entityA, in Entity entityB) where TRelationKind : struct + protected void Unrelate(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged { RelationDepot.Remove(new Relation(entityA, entityB)); } diff --git a/src/World.cs b/src/World.cs index 8e9e97d..1582846 100644 --- a/src/World.cs +++ b/src/World.cs @@ -2,25 +2,10 @@ { public class World { - private readonly EntityStorage EntityStorage = new EntityStorage(); - private readonly ComponentDepot ComponentDepot = new ComponentDepot(); - private readonly MessageDepot MessageDepot = new MessageDepot(); - private readonly RelationDepot RelationDepot = new RelationDepot(); - - internal void AddSystem(System system) - { - system.RegisterEntityStorage(EntityStorage); - system.RegisterComponentDepot(ComponentDepot); - system.RegisterMessageDepot(MessageDepot); - system.RegisterRelationDepot(RelationDepot); - } - - internal void AddRenderer(Renderer renderer) - { - renderer.RegisterEntityStorage(EntityStorage); - renderer.RegisterComponentDepot(ComponentDepot); - renderer.RegisterRelationDepot(RelationDepot); - } + internal readonly EntityStorage EntityStorage = new EntityStorage(); + internal readonly ComponentDepot ComponentDepot = new ComponentDepot(); + internal readonly MessageDepot MessageDepot = new MessageDepot(); + internal readonly RelationDepot RelationDepot = new RelationDepot(); public Entity CreateEntity() { @@ -41,5 +26,17 @@ { MessageDepot.Clear(); } + + public ComponentDepotState Serialize() + { + var state = ComponentDepot.CreateState(); + ComponentDepot.Serialize(state); + return state; + } + + public void Deserialize(ComponentDepotState state) + { + ComponentDepot.Deserialize(state); + } } } -- 2.25.1 From b30d53e71df679ba83c62b6bc607682797a09962 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Wed, 27 Apr 2022 12:42:43 -0700 Subject: [PATCH 2/6] resize component storage if necessary --- src/ComponentStorage.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs index 51d5750..3020bb1 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -118,9 +118,18 @@ namespace MoonTools.ECS public override void Serialize(ComponentStorageState serializedComponentStorage) { ReadOnlySpan entityIDBytes = MemoryMarshal.Cast(new ReadOnlySpan(entityIDs, 0, nextID)); + + if (entityIDBytes.Length > serializedComponentStorage.EntityIDs.Length) + { + Array.Resize(ref serializedComponentStorage.EntityIDs, entityIDBytes.Length); + } entityIDBytes.CopyTo(serializedComponentStorage.EntityIDs); ReadOnlySpan componentBytes = MemoryMarshal.Cast(AllComponents()); + if (componentBytes.Length > serializedComponentStorage.Components.Length) + { + Array.Resize(ref serializedComponentStorage.Components, componentBytes.Length); + } componentBytes.CopyTo(serializedComponentStorage.Components); serializedComponentStorage.EntityIdToStorageIndex.Clear(); -- 2.25.1 From be95e80265e662b819095e547592195d3d9f049a Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Wed, 27 Apr 2022 14:13:56 -0700 Subject: [PATCH 3/6] more serialization changes --- MoonTools.ECS.csproj | 1 + src/ComponentDepot.cs | 91 ++++++++++++++++++++---------------- src/ComponentDepotState.cs | 5 +- src/ComponentStorage.cs | 24 ++++++++-- src/ComponentStorageState.cs | 7 ++- src/FilterBuilder.cs | 4 +- src/FilterSignature.cs | 4 +- src/IndexableSet.cs | 36 +++++++++++++- src/IndexableSetState.cs | 19 ++++++++ src/World.cs | 9 +++- 10 files changed, 144 insertions(+), 56 deletions(-) create mode 100644 src/IndexableSetState.cs diff --git a/MoonTools.ECS.csproj b/MoonTools.ECS.csproj index 8867478..e7a1568 100644 --- a/MoonTools.ECS.csproj +++ b/MoonTools.ECS.csproj @@ -4,6 +4,7 @@ net6.0 enable x64 + true diff --git a/src/ComponentDepot.cs b/src/ComponentDepot.cs index bd43661..4c063ea 100644 --- a/src/ComponentDepot.cs +++ b/src/ComponentDepot.cs @@ -11,13 +11,13 @@ namespace MoonTools.ECS private Dictionary> typeToFilterSignatures = new Dictionary>(); - private Dictionary> entityComponentMap = new Dictionary>(); - #if DEBUG private Dictionary singleComponentFilters = new Dictionary(); #endif - internal void Register() where TComponent : struct + private HashSet TypesWithDisabledSerialization = new HashSet(); + + internal void Register() where TComponent : unmanaged { if (!storages.ContainsKey(typeof(TComponent))) { @@ -33,19 +33,19 @@ namespace MoonTools.ECS return storages[type]; } - private ComponentStorage Lookup() where TComponent : struct + private ComponentStorage Lookup() where TComponent : unmanaged { // TODO: is it possible to optimize this? Register(); return (ComponentStorage) storages[typeof(TComponent)]; } - public bool Some() where TComponent : struct + public bool Some() where TComponent : unmanaged { return Lookup().Any(); } - public bool Has(int entityID) where TComponent : struct + public bool Has(int entityID) where TComponent : unmanaged { return Lookup().Has(entityID); } @@ -55,29 +55,22 @@ namespace MoonTools.ECS return Lookup(type).Has(entityID); } - public ref readonly TComponent Get(int entityID) where TComponent : struct + public ref readonly TComponent Get(int entityID) where TComponent : unmanaged { return ref Lookup().Get(entityID); } - public ref readonly TComponent Get() where TComponent : struct + public ref readonly TComponent Get() where TComponent : unmanaged { return ref Lookup().Get(); } - public void Set(int entityID, in TComponent component) where TComponent : struct + public void Set(int entityID, in TComponent component) where TComponent : unmanaged { - Lookup().Set(entityID, component); - - if (!entityComponentMap.ContainsKey(entityID)) - { - entityComponentMap.Add(entityID, new HashSet()); - } - - var notFound = entityComponentMap[entityID].Add(typeof(TComponent)); + var existed = Lookup().Set(entityID, component); // update filters - if (notFound) + if (!existed) { if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures)) { @@ -89,24 +82,22 @@ namespace MoonTools.ECS } } - public Entity GetSingletonEntity() where TComponent : struct + public Entity GetSingletonEntity() where TComponent : unmanaged { return Lookup().FirstEntity(); } - public ReadOnlySpan ReadComponents() where TComponent : struct + public ReadOnlySpan ReadComponents() where TComponent : unmanaged { return Lookup().AllComponents(); } private void Remove(Type type, int entityID) { - Lookup(type).Remove(entityID); - - var found = entityComponentMap[entityID].Remove(type); + var existed = Lookup(type).Remove(entityID); // update filters - if (found) + if (existed) { if (typeToFilterSignatures.TryGetValue(type, out var filterSignatures)) { @@ -118,14 +109,12 @@ namespace MoonTools.ECS } } - public void Remove(int entityID) where TComponent : struct + public void Remove(int entityID) where TComponent : unmanaged { - Lookup().Remove(entityID); - - var found = entityComponentMap[entityID].Remove(typeof(TComponent)); + var existed = Lookup().Remove(entityID); // update filters - if (found) + if (existed) { if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures)) { @@ -137,16 +126,12 @@ namespace MoonTools.ECS } } + // TODO: is there some way to optimize this without complicating serialization? public void OnEntityDestroy(int entityID) { - if (entityComponentMap.ContainsKey(entityID)) + foreach (var type in storages.Keys) { - foreach (var type in entityComponentMap[entityID]) - { - Remove(type, entityID); - } - - entityComponentMap.Remove(entityID); + Remove(type, entityID); } } @@ -231,6 +216,11 @@ namespace MoonTools.ECS filterSignatureToEntityIDs[filterSignature].Add(entityID); } + public void DisableSerialization() where TComponent : unmanaged + { + TypesWithDisabledSerialization.Add(typeof(TComponent)); + } + public ComponentDepotState CreateState() { return new ComponentDepotState(); @@ -238,12 +228,30 @@ namespace MoonTools.ECS public void Serialize(ComponentDepotState state) { + // FIXME: this is creating garbage state.StorageStates.Clear(); foreach (var (type, storage) in storages) { - var storageState = storage.CreateState(); - storage.Serialize(storageState); - state.StorageStates.Add(type, storageState); + // FIXME: we could cache this + if (!TypesWithDisabledSerialization.Contains(type)) + { + var storageState = storage.CreateState(); + storage.Serialize(storageState); + state.StorageStates.Add(type, storageState); + } + } + + // FIXME: this is creating garbage + state.FilterStates.Clear(); + foreach (var (signature, set) in filterSignatureToEntityIDs) + { + // FIXME: we could cache this + if (!signature.Included.Overlaps(TypesWithDisabledSerialization) && !signature.Excluded.Overlaps(TypesWithDisabledSerialization)) + { + var setState = new IndexableSetState(set.Count); + set.Save(setState); + state.FilterStates[signature] = setState; + } } } @@ -253,6 +261,11 @@ namespace MoonTools.ECS { storages[type].Deserialize(storageState); } + + foreach (var (signature, setState) in state.FilterStates) + { + filterSignatureToEntityIDs[signature].Load(setState); + } } #if DEBUG diff --git a/src/ComponentDepotState.cs b/src/ComponentDepotState.cs index 78481d1..b137f24 100644 --- a/src/ComponentDepotState.cs +++ b/src/ComponentDepotState.cs @@ -3,8 +3,9 @@ using System.Collections.Generic; namespace MoonTools.ECS { - public class ComponentDepotState - { + public class ComponentDepotState + { public Dictionary StorageStates = new Dictionary(); + public Dictionary> FilterStates = new Dictionary>(); } } diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs index 3020bb1..e19af89 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -7,14 +7,14 @@ namespace MoonTools.ECS internal abstract class ComponentStorage { public abstract bool Has(int entityID); - public abstract void Remove(int entityID); + public abstract bool Remove(int entityID); public abstract object Debug_Get(int entityID); public abstract ComponentStorageState CreateState(); public abstract void Serialize(ComponentStorageState state); public abstract void Deserialize(ComponentStorageState state); } - internal class ComponentStorage : ComponentStorage where TComponent : struct + internal class ComponentStorage : ComponentStorage where TComponent : unmanaged { private int nextID; private readonly Dictionary entityIDToStorageIndex = new Dictionary(16); @@ -52,8 +52,11 @@ namespace MoonTools.ECS return ref components[0]; } - public void Set(int entityID, in TComponent component) + // Returns true if the entity already had this component. + public bool Set(int entityID, in TComponent component) { + bool result = true; + if (!entityIDToStorageIndex.ContainsKey(entityID)) { var index = nextID; @@ -67,12 +70,17 @@ namespace MoonTools.ECS entityIDToStorageIndex[entityID] = index; entityIDs[index] = entityID; + + result = false; } components[entityIDToStorageIndex[entityID]] = component; + + return result; } - public override void Remove(int entityID) + // Returns true if the entity had this component. + public override bool Remove(int entityID) { if (entityIDToStorageIndex.ContainsKey(entityID)) { @@ -91,7 +99,11 @@ namespace MoonTools.ECS } nextID -= 1; + + return true; } + + return false; } public void Clear() @@ -137,6 +149,8 @@ namespace MoonTools.ECS { serializedComponentStorage.EntityIdToStorageIndex[kvp.Key] = kvp.Value; } + + serializedComponentStorage.Count = nextID; } public override void Deserialize(ComponentStorageState serializedComponentStorage) @@ -149,6 +163,8 @@ namespace MoonTools.ECS { entityIDToStorageIndex[kvp.Key] = kvp.Value; } + + nextID = serializedComponentStorage.Count; } } } diff --git a/src/ComponentStorageState.cs b/src/ComponentStorageState.cs index dd98063..dfc3e52 100644 --- a/src/ComponentStorageState.cs +++ b/src/ComponentStorageState.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Runtime.InteropServices; namespace MoonTools.ECS { @@ -11,12 +10,12 @@ namespace MoonTools.ECS public byte[] EntityIDs; public byte[] Components; - public static ComponentStorageState Create(int count) + public unsafe static ComponentStorageState Create(int count) where TComponent : unmanaged { return new ComponentStorageState( count, - count * Marshal.SizeOf(), - count * Marshal.SizeOf() + count * sizeof(int), + count * sizeof(TComponent) ); } diff --git a/src/FilterBuilder.cs b/src/FilterBuilder.cs index 4ba782d..be75edc 100644 --- a/src/FilterBuilder.cs +++ b/src/FilterBuilder.cs @@ -23,14 +23,14 @@ namespace MoonTools.ECS Excluded = excluded; } - public FilterBuilder Include() where TComponent : struct + public FilterBuilder Include() where TComponent : unmanaged { ComponentDepot.Register(); Included.Add(typeof(TComponent)); return new FilterBuilder(ComponentDepot, Included, Excluded); } - public FilterBuilder Exclude() where TComponent : struct + public FilterBuilder Exclude() where TComponent : unmanaged { ComponentDepot.Register(); Excluded.Add(typeof(TComponent)); diff --git a/src/FilterSignature.cs b/src/FilterSignature.cs index 4857119..7508b36 100644 --- a/src/FilterSignature.cs +++ b/src/FilterSignature.cs @@ -7,8 +7,8 @@ namespace MoonTools.ECS { private const int HASH_FACTOR = 97; - public HashSet Included; - public HashSet Excluded; + public readonly HashSet Included; + public readonly HashSet Excluded; public FilterSignature(HashSet included, HashSet excluded) { diff --git a/src/IndexableSet.cs b/src/IndexableSet.cs index cf8f56c..6057cf1 100644 --- a/src/IndexableSet.cs +++ b/src/IndexableSet.cs @@ -1,10 +1,11 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Runtime.InteropServices; namespace MoonTools.ECS { - internal class IndexableSet : IEnumerable where T : notnull + internal class IndexableSet : IEnumerable where T : unmanaged { private Dictionary indices; private T[] array; @@ -78,5 +79,38 @@ namespace MoonTools.ECS yield return array[i]; } } + + public void Save(IndexableSetState state) + { + state.Indices.Clear(); + foreach (var (key, value) in indices) + { + state.Indices[key] = value; + } + + 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) + { + indices.Clear(); + foreach (var kvp in state.Indices) + { + indices[kvp.Key] = kvp.Value; + } + + state.Array.CopyTo(MemoryMarshal.Cast(array)); + + Count = state.Count; + } } } diff --git a/src/IndexableSetState.cs b/src/IndexableSetState.cs new file mode 100644 index 0000000..43b7b79 --- /dev/null +++ b/src/IndexableSetState.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace MoonTools.ECS +{ + public class IndexableSetState where T : unmanaged + { + public int Count; + public Dictionary Indices; + public byte[] Array; + + public unsafe IndexableSetState(int count) + { + Count = count; + Indices = new Dictionary(count); + Array = new byte[sizeof(T) * count]; + } + } +} diff --git a/src/World.cs b/src/World.cs index 1582846..a5dbe81 100644 --- a/src/World.cs +++ b/src/World.cs @@ -12,12 +12,12 @@ return EntityStorage.Create(); } - public void Set(Entity entity, in TComponent component) where TComponent : struct + public void Set(Entity entity, in TComponent component) where TComponent : unmanaged { ComponentDepot.Set(entity.ID, component); } - public void Send(in TMessage message) where TMessage : struct + public void Send(in TMessage message) where TMessage : unmanaged { MessageDepot.Add(message); } @@ -27,6 +27,11 @@ MessageDepot.Clear(); } + public void DisableSerialization() where TComponent : unmanaged + { + ComponentDepot.DisableSerialization(); + } + public ComponentDepotState Serialize() { var state = ComponentDepot.CreateState(); -- 2.25.1 From 190d1413cad04a9048e852d8faa47910fbe7faed Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Mon, 2 May 2022 11:07:19 -0700 Subject: [PATCH 4/6] store entity state + some garbage optimizations --- src/ComponentDepot.cs | 27 +++++----- src/EntityStorage.cs | 63 ++++++++++++++++++++++-- src/IDStorage.cs | 39 --------------- src/{ => State}/ComponentDepotState.cs | 2 +- src/{ => State}/ComponentStorageState.cs | 3 +- src/State/EntityStorageState.cs | 10 ++++ src/{ => State}/IndexableSetState.cs | 3 +- src/State/WorldState.cs | 14 ++++++ src/World.cs | 17 ++++--- 9 files changed, 107 insertions(+), 71 deletions(-) delete mode 100644 src/IDStorage.cs rename src/{ => State}/ComponentDepotState.cs (90%) rename src/{ => State}/ComponentStorageState.cs (89%) create mode 100644 src/State/EntityStorageState.cs rename src/{ => State}/IndexableSetState.cs (76%) create mode 100644 src/State/WorldState.cs diff --git a/src/ComponentDepot.cs b/src/ComponentDepot.cs index 4c063ea..d5cb094 100644 --- a/src/ComponentDepot.cs +++ b/src/ComponentDepot.cs @@ -221,36 +221,31 @@ namespace MoonTools.ECS TypesWithDisabledSerialization.Add(typeof(TComponent)); } - public ComponentDepotState CreateState() - { - return new ComponentDepotState(); - } - public void Serialize(ComponentDepotState state) { - // FIXME: this is creating garbage - state.StorageStates.Clear(); foreach (var (type, storage) in storages) { - // FIXME: we could cache this if (!TypesWithDisabledSerialization.Contains(type)) { - var storageState = storage.CreateState(); - storage.Serialize(storageState); - state.StorageStates.Add(type, storageState); + if (!state.StorageStates.ContainsKey(type)) + { + state.StorageStates.Add(type, storage.CreateState()); + } + + storage.Serialize(state.StorageStates[type]); } } - // FIXME: this is creating garbage - state.FilterStates.Clear(); foreach (var (signature, set) in filterSignatureToEntityIDs) { // FIXME: we could cache this if (!signature.Included.Overlaps(TypesWithDisabledSerialization) && !signature.Excluded.Overlaps(TypesWithDisabledSerialization)) { - var setState = new IndexableSetState(set.Count); - set.Save(setState); - state.FilterStates[signature] = setState; + if (!state.FilterStates.ContainsKey(signature)) + { + state.FilterStates[signature] = new IndexableSetState(set.Count); + } + set.Save(state.FilterStates[signature]); } } } diff --git a/src/EntityStorage.cs b/src/EntityStorage.cs index 5d48a49..6e598a4 100644 --- a/src/EntityStorage.cs +++ b/src/EntityStorage.cs @@ -1,22 +1,75 @@ -namespace MoonTools.ECS +using System.Collections.Generic; + +namespace MoonTools.ECS { internal class EntityStorage { - public IDStorage idStorage = new IDStorage(); + private int nextID = 0; + private readonly Stack availableIDs = new Stack(); + private readonly HashSet availableIDHash = new HashSet(); public Entity Create() { - return new Entity(idStorage.NextID()); + return new Entity(NextID()); } public bool Exists(in Entity entity) { - return idStorage.Taken(entity.ID); + return Taken(entity.ID); } public void Destroy(in Entity entity) { - idStorage.Release(entity.ID); + Release(entity.ID); + } + + public void Serialize(EntityStorageState state) + { + state.NextID = nextID; + state.availableIDs.Clear(); + foreach (var id in availableIDs) + { + state.availableIDs.Add(id); + } + } + + public void Deserialize(EntityStorageState state) + { + nextID = state.NextID; + availableIDs.Clear(); + availableIDHash.Clear(); + foreach (var id in state.availableIDs) + { + availableIDs.Push(id); + availableIDHash.Add(id); + } + } + + private int NextID() + { + if (availableIDs.Count > 0) + { + var id = availableIDs.Pop(); + availableIDHash.Remove(id); + return id; + } + else + { + var id = nextID; + nextID += 1; + return id; + } + } + + private bool Taken(int id) + { + return !availableIDHash.Contains(id) && id < nextID; + } + + private void Release(int id) + { + availableIDs.Push(id); + availableIDHash.Add(id); } } } diff --git a/src/IDStorage.cs b/src/IDStorage.cs deleted file mode 100644 index 93e17bf..0000000 --- a/src/IDStorage.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Collections.Generic; - -namespace MoonTools.ECS -{ - internal class IDStorage - { - private int nextID = 0; - - private readonly Stack availableIDs = new Stack(); - private readonly HashSet availableIDHash = new HashSet(); - - public int NextID() - { - if (availableIDs.Count > 0) - { - var id = availableIDs.Pop(); - availableIDHash.Remove(id); - return id; - } - else - { - var id = nextID; - nextID += 1; - return id; - } - } - - public bool Taken(int id) - { - return !availableIDHash.Contains(id) && id < nextID; - } - - public void Release(int id) - { - availableIDs.Push(id); - availableIDHash.Add(id); - } - } -} diff --git a/src/ComponentDepotState.cs b/src/State/ComponentDepotState.cs similarity index 90% rename from src/ComponentDepotState.cs rename to src/State/ComponentDepotState.cs index b137f24..2370dc6 100644 --- a/src/ComponentDepotState.cs +++ b/src/State/ComponentDepotState.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace MoonTools.ECS { - public class ComponentDepotState + internal class ComponentDepotState { public Dictionary StorageStates = new Dictionary(); public Dictionary> FilterStates = new Dictionary>(); diff --git a/src/ComponentStorageState.cs b/src/State/ComponentStorageState.cs similarity index 89% rename from src/ComponentStorageState.cs rename to src/State/ComponentStorageState.cs index dfc3e52..131f83c 100644 --- a/src/ComponentStorageState.cs +++ b/src/State/ComponentStorageState.cs @@ -2,8 +2,7 @@ using System.Collections.Generic; namespace MoonTools.ECS { - // for saving and loading component storage - public class ComponentStorageState + internal class ComponentStorageState { public int Count; public Dictionary EntityIdToStorageIndex; diff --git a/src/State/EntityStorageState.cs b/src/State/EntityStorageState.cs new file mode 100644 index 0000000..66363dc --- /dev/null +++ b/src/State/EntityStorageState.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace MoonTools.ECS +{ + internal class EntityStorageState + { + public int NextID; + public List availableIDs = new List(); + } +} diff --git a/src/IndexableSetState.cs b/src/State/IndexableSetState.cs similarity index 76% rename from src/IndexableSetState.cs rename to src/State/IndexableSetState.cs index 43b7b79..378b9de 100644 --- a/src/IndexableSetState.cs +++ b/src/State/IndexableSetState.cs @@ -1,9 +1,8 @@ using System.Collections.Generic; -using System.Runtime.InteropServices; namespace MoonTools.ECS { - public class IndexableSetState where T : unmanaged + internal class IndexableSetState where T : unmanaged { public int Count; public Dictionary Indices; diff --git a/src/State/WorldState.cs b/src/State/WorldState.cs new file mode 100644 index 0000000..dd2728a --- /dev/null +++ b/src/State/WorldState.cs @@ -0,0 +1,14 @@ +namespace MoonTools.ECS +{ + public class WorldState + { + internal ComponentDepotState ComponentDepotState; + internal EntityStorageState EntityStorageState; + + public WorldState() + { + ComponentDepotState = new ComponentDepotState(); + EntityStorageState = new EntityStorageState(); + } + } +} diff --git a/src/World.cs b/src/World.cs index a5dbe81..7ad8c4a 100644 --- a/src/World.cs +++ b/src/World.cs @@ -32,16 +32,21 @@ ComponentDepot.DisableSerialization(); } - public ComponentDepotState Serialize() + public WorldState CreateState() { - var state = ComponentDepot.CreateState(); - ComponentDepot.Serialize(state); - return state; + return new WorldState(); } - public void Deserialize(ComponentDepotState state) + public void Serialize(WorldState state) { - ComponentDepot.Deserialize(state); + ComponentDepot.Serialize(state.ComponentDepotState); + EntityStorage.Serialize(state.EntityStorageState); + } + + public void Deserialize(WorldState state) + { + ComponentDepot.Deserialize(state.ComponentDepotState); + EntityStorage.Deserialize(state.EntityStorageState); } } } -- 2.25.1 From 8f620c7a20dfee597926dd6ddcc19a625c43a263 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Mon, 2 May 2022 18:41:59 -0700 Subject: [PATCH 5/6] complete world state storage --- src/ComponentDepot.cs | 8 +- src/ComponentStorage.cs | 38 ++++----- src/EntityStorage.cs | 4 +- src/IndexableSet.cs | 18 ++--- src/RelationDepot.cs | 39 +++++++--- src/RelationStorage.cs | 121 ++++++++++++++++++++++++++--- src/State/ComponentStorageState.cs | 2 - src/State/IndexableSetState.cs | 2 - src/State/RelationDepotState.cs | 10 +++ src/State/RelationStorageState.cs | 27 +++++++ src/State/WorldState.cs | 6 +- src/System.cs | 2 +- src/World.cs | 14 ++-- 13 files changed, 217 insertions(+), 74 deletions(-) create mode 100644 src/State/RelationDepotState.cs create mode 100644 src/State/RelationStorageState.cs diff --git a/src/ComponentDepot.cs b/src/ComponentDepot.cs index d5cb094..a5ada46 100644 --- a/src/ComponentDepot.cs +++ b/src/ComponentDepot.cs @@ -221,7 +221,7 @@ namespace MoonTools.ECS TypesWithDisabledSerialization.Add(typeof(TComponent)); } - public void Serialize(ComponentDepotState state) + public void Save(ComponentDepotState state) { foreach (var (type, storage) in storages) { @@ -232,7 +232,7 @@ namespace MoonTools.ECS state.StorageStates.Add(type, storage.CreateState()); } - storage.Serialize(state.StorageStates[type]); + storage.Save(state.StorageStates[type]); } } @@ -250,11 +250,11 @@ namespace MoonTools.ECS } } - public void Deserialize(ComponentDepotState state) + public void Load(ComponentDepotState state) { foreach (var (type, storageState) in state.StorageStates) { - storages[type].Deserialize(storageState); + storages[type].Load(storageState); } foreach (var (signature, setState) in state.FilterStates) diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs index e19af89..c7d256b 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -10,8 +10,8 @@ namespace MoonTools.ECS public abstract bool Remove(int entityID); public abstract object Debug_Get(int entityID); public abstract ComponentStorageState CreateState(); - public abstract void Serialize(ComponentStorageState state); - public abstract void Deserialize(ComponentStorageState state); + public abstract void Save(ComponentStorageState state); + public abstract void Load(ComponentStorageState state); } internal class ComponentStorage : ComponentStorage where TComponent : unmanaged @@ -127,44 +127,38 @@ namespace MoonTools.ECS return ComponentStorageState.Create(nextID); } - public override void Serialize(ComponentStorageState serializedComponentStorage) + public override void Save(ComponentStorageState state) { ReadOnlySpan entityIDBytes = MemoryMarshal.Cast(new ReadOnlySpan(entityIDs, 0, nextID)); - if (entityIDBytes.Length > serializedComponentStorage.EntityIDs.Length) + if (entityIDBytes.Length > state.EntityIDs.Length) { - Array.Resize(ref serializedComponentStorage.EntityIDs, entityIDBytes.Length); + Array.Resize(ref state.EntityIDs, entityIDBytes.Length); } - entityIDBytes.CopyTo(serializedComponentStorage.EntityIDs); + entityIDBytes.CopyTo(state.EntityIDs); ReadOnlySpan componentBytes = MemoryMarshal.Cast(AllComponents()); - if (componentBytes.Length > serializedComponentStorage.Components.Length) + if (componentBytes.Length > state.Components.Length) { - Array.Resize(ref serializedComponentStorage.Components, componentBytes.Length); + Array.Resize(ref state.Components, componentBytes.Length); } - componentBytes.CopyTo(serializedComponentStorage.Components); + componentBytes.CopyTo(state.Components); - serializedComponentStorage.EntityIdToStorageIndex.Clear(); - foreach (var kvp in entityIDToStorageIndex) - { - serializedComponentStorage.EntityIdToStorageIndex[kvp.Key] = kvp.Value; - } - - serializedComponentStorage.Count = nextID; + state.Count = nextID; } - public override void Deserialize(ComponentStorageState serializedComponentStorage) + public override void Load(ComponentStorageState state) { - serializedComponentStorage.EntityIDs.CopyTo(MemoryMarshal.Cast(entityIDs)); - serializedComponentStorage.Components.CopyTo(MemoryMarshal.Cast(components)); + state.EntityIDs.CopyTo(MemoryMarshal.Cast(entityIDs)); + state.Components.CopyTo(MemoryMarshal.Cast(components)); entityIDToStorageIndex.Clear(); - foreach (var kvp in serializedComponentStorage.EntityIdToStorageIndex) + for (var i = 0; i < state.Count; i += 1) { - entityIDToStorageIndex[kvp.Key] = kvp.Value; + entityIDToStorageIndex[entityIDs[i]] = i; } - nextID = serializedComponentStorage.Count; + nextID = state.Count; } } } diff --git a/src/EntityStorage.cs b/src/EntityStorage.cs index 6e598a4..2379b5d 100644 --- a/src/EntityStorage.cs +++ b/src/EntityStorage.cs @@ -23,7 +23,7 @@ namespace MoonTools.ECS Release(entity.ID); } - public void Serialize(EntityStorageState state) + public void Save(EntityStorageState state) { state.NextID = nextID; state.availableIDs.Clear(); @@ -33,7 +33,7 @@ namespace MoonTools.ECS } } - public void Deserialize(EntityStorageState state) + public void Load(EntityStorageState state) { nextID = state.NextID; availableIDs.Clear(); diff --git a/src/IndexableSet.cs b/src/IndexableSet.cs index 6057cf1..275044a 100644 --- a/src/IndexableSet.cs +++ b/src/IndexableSet.cs @@ -82,12 +82,6 @@ namespace MoonTools.ECS public void Save(IndexableSetState state) { - state.Indices.Clear(); - foreach (var (key, value) in indices) - { - state.Indices[key] = value; - } - ReadOnlySpan arrayBytes = MemoryMarshal.Cast(array); if (arrayBytes.Length > state.Array.Length) @@ -102,14 +96,14 @@ namespace MoonTools.ECS public void Load(IndexableSetState state) { - indices.Clear(); - foreach (var kvp in state.Indices) - { - indices[kvp.Key] = kvp.Value; - } - 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/RelationDepot.cs b/src/RelationDepot.cs index 82b7114..c3a65b3 100644 --- a/src/RelationDepot.cs +++ b/src/RelationDepot.cs @@ -7,7 +7,7 @@ namespace MoonTools.ECS { private Dictionary storages = new Dictionary(); - private void Register() where TRelationKind : struct + private void Register() where TRelationKind : unmanaged { if (!storages.ContainsKey(typeof(TRelationKind))) { @@ -15,18 +15,18 @@ namespace MoonTools.ECS } } - private RelationStorage Lookup() where TRelationKind : struct + private RelationStorage Lookup() where TRelationKind : unmanaged { Register(); return (RelationStorage) storages[typeof(TRelationKind)]; } - public void Add(Relation relation, TRelationKind relationData) where TRelationKind : struct + public void Set(Relation relation, TRelationKind relationData) where TRelationKind : unmanaged { - Lookup().Add(relation, relationData); + Lookup().Set(relation, relationData); } - public void Remove(Relation relation) where TRelationKind : struct + public void Remove(Relation relation) where TRelationKind : unmanaged { Lookup().Remove(relation); } @@ -40,24 +40,45 @@ namespace MoonTools.ECS } } - public IEnumerable<(Entity, Entity, TRelationKind)> Relations() where TRelationKind : struct + public IEnumerable<(Entity, Entity, TRelationKind)> Relations() where TRelationKind : unmanaged { return Lookup().All(); } - public bool Related(int idA, int idB) where TRelationKind : struct + public bool Related(int idA, int idB) where TRelationKind : unmanaged { return Lookup().Has(new Relation(idA, idB)); } - public IEnumerable<(Entity, TRelationKind)> RelatedToA(int entityID) where TRelationKind : struct + public IEnumerable<(Entity, TRelationKind)> RelatedToA(int entityID) where TRelationKind : unmanaged { return Lookup().RelatedToA(entityID); } - public IEnumerable<(Entity, TRelationKind)> RelatedToB(int entityID) where TRelationKind : struct + public IEnumerable<(Entity, TRelationKind)> RelatedToB(int entityID) where TRelationKind : unmanaged { return Lookup().RelatedToB(entityID); } + + public void Save(RelationDepotState state) + { + foreach (var (type, storage) in storages) + { + if (!state.StorageStates.ContainsKey(type)) + { + state.StorageStates.Add(type, storage.CreateState()); + } + + storage.Save(state.StorageStates[type]); + } + } + + public void Load(RelationDepotState state) + { + foreach (var (type, storageState) in state.StorageStates) + { + storages[type].Load(storageState); + } + } } } diff --git a/src/RelationStorage.cs b/src/RelationStorage.cs index 6f82074..336cc98 100644 --- a/src/RelationStorage.cs +++ b/src/RelationStorage.cs @@ -1,32 +1,46 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; 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 OnEntityDestroy(int entityID); } // Relation is the two entities, A related to B. // TRelation is the data attached to the relation. - internal class RelationStorage : RelationStorage where TRelation : struct + internal class RelationStorage : RelationStorage where TRelation : unmanaged { - private Dictionary relations = new Dictionary(16); + private int count = 0; + private Dictionary indices = new Dictionary(16); + private Relation[] relations = new Relation[16]; + private TRelation[] relationDatas = new TRelation[16]; private Dictionary> entitiesRelatedToA = new Dictionary>(16); private Dictionary> entitiesRelatedToB = new Dictionary>(16); private Stack> listPool = new Stack>(); public IEnumerable<(Entity, Entity, TRelation)> All() { - foreach (var relationData in relations) + for (var i = 0; i < count; i += 1) { - yield return (relationData.Key.A, relationData.Key.B, relationData.Value); + var relation = relations[i]; + yield return (relation.A, relation.B, relationDatas[i]); } } - public void Add(Relation relation, TRelation relationData) + public void Set(Relation relation, TRelation relationData) { - if (relations.ContainsKey(relation)) { return; } + if (indices.ContainsKey(relation)) + { + var index = indices[relation]; + relationDatas[index] = relationData; + return; + } var idA = relation.A.ID; var idB = relation.B.ID; @@ -43,12 +57,20 @@ namespace MoonTools.ECS } entitiesRelatedToB[idB].Add(idA); - relations.Add(relation, relationData); + if (count >= relationDatas.Length) + { + Array.Resize(ref relationDatas, relationDatas.Length * 2); + } + + relations[count] = relation; + relationDatas[count] = relationData; + indices.Add(relation, count); + count += 1; } public bool Has(Relation relation) { - return relations.ContainsKey(relation); + return indices.ContainsKey(relation); } // FIXME: is there a more descriptive name for these? @@ -59,7 +81,7 @@ namespace MoonTools.ECS foreach (var id in entitiesRelatedToA[entityID]) { var relation = new Relation(entityID, id); - yield return (relation.B, relations[relation]); + yield return (relation.B, relationDatas[indices[relation]]); } } } @@ -71,7 +93,7 @@ namespace MoonTools.ECS foreach (var id in entitiesRelatedToB[entityID]) { var relation = new Relation(id, entityID); - yield return (relation.A, relations[relation]); + yield return (relation.A, relationDatas[indices[relation]]); } } } @@ -88,7 +110,26 @@ namespace MoonTools.ECS entitiesRelatedToB[relation.B.ID].Remove(relation.A.ID); } - return relations.Remove(relation); + if (indices.ContainsKey(relation)) + { + var index = indices[relation]; + var lastElementIndex = count - 1; + + // move an element into the hole + if (index != lastElementIndex) + { + var lastRelation = relations[lastElementIndex]; + indices[lastRelation] = index; + relationDatas[index] = relationDatas[lastElementIndex]; + relations[index] = lastRelation; + } + + count -= 1; + indices.Remove(relation); + return true; + } + + return false; } public override void OnEntityDestroy(int entityID) @@ -131,5 +172,61 @@ 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); + + // FIXME: incorrect comparison + 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(); + entitiesRelatedToA.Clear(); + entitiesRelatedToB.Clear(); + for (var i = 0; i < state.Count; i += 1) + { + var relation = relations[i]; + indices[relation] = i; + + if (!entitiesRelatedToA.ContainsKey(relation.A.ID)) + { + entitiesRelatedToA[relation.A.ID] = AcquireHashSetFromPool(); + } + entitiesRelatedToA[relation.A.ID].Add(relation.B.ID); + + if (!entitiesRelatedToB.ContainsKey(relation.B.ID)) + { + entitiesRelatedToB[relation.B.ID] = AcquireHashSetFromPool(); + } + entitiesRelatedToB[relation.B.ID].Add(relation.A.ID); + } + + count = state.Count; + } } } diff --git a/src/State/ComponentStorageState.cs b/src/State/ComponentStorageState.cs index 131f83c..ad4872d 100644 --- a/src/State/ComponentStorageState.cs +++ b/src/State/ComponentStorageState.cs @@ -5,7 +5,6 @@ namespace MoonTools.ECS internal class ComponentStorageState { public int Count; - public Dictionary EntityIdToStorageIndex; public byte[] EntityIDs; public byte[] Components; @@ -21,7 +20,6 @@ namespace MoonTools.ECS private ComponentStorageState(int count, int entityIDSize, int componentSize) { Count = count; - EntityIdToStorageIndex = new Dictionary(count); EntityIDs = new byte[entityIDSize]; Components = new byte[componentSize]; } diff --git a/src/State/IndexableSetState.cs b/src/State/IndexableSetState.cs index 378b9de..4367252 100644 --- a/src/State/IndexableSetState.cs +++ b/src/State/IndexableSetState.cs @@ -5,13 +5,11 @@ namespace MoonTools.ECS internal class IndexableSetState where T : unmanaged { public int Count; - public Dictionary Indices; public byte[] Array; public unsafe IndexableSetState(int count) { Count = count; - Indices = new Dictionary(count); Array = new byte[sizeof(T) * count]; } } diff --git a/src/State/RelationDepotState.cs b/src/State/RelationDepotState.cs new file mode 100644 index 0000000..58e1315 --- /dev/null +++ b/src/State/RelationDepotState.cs @@ -0,0 +1,10 @@ +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 new file mode 100644 index 0000000..d210a5d --- /dev/null +++ b/src/State/RelationStorageState.cs @@ -0,0 +1,27 @@ +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 index dd2728a..d422745 100644 --- a/src/State/WorldState.cs +++ b/src/State/WorldState.cs @@ -2,13 +2,15 @@ namespace MoonTools.ECS { public class WorldState { - internal ComponentDepotState ComponentDepotState; - internal EntityStorageState EntityStorageState; + 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 1983d17..a2749a8 100644 --- a/src/System.cs +++ b/src/System.cs @@ -70,7 +70,7 @@ namespace MoonTools.ECS protected void Relate(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged { - RelationDepot.Add(new Relation(entityA, entityB), relationData); + RelationDepot.Set(new Relation(entityA, entityB), relationData); } protected void Unrelate(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged diff --git a/src/World.cs b/src/World.cs index 7ad8c4a..939a9bd 100644 --- a/src/World.cs +++ b/src/World.cs @@ -37,16 +37,18 @@ return new WorldState(); } - public void Serialize(WorldState state) + public void Save(WorldState state) { - ComponentDepot.Serialize(state.ComponentDepotState); - EntityStorage.Serialize(state.EntityStorageState); + ComponentDepot.Save(state.ComponentDepotState); + EntityStorage.Save(state.EntityStorageState); + RelationDepot.Save(state.RelationDepotState); } - public void Deserialize(WorldState state) + public void Load(WorldState state) { - ComponentDepot.Deserialize(state.ComponentDepotState); - EntityStorage.Deserialize(state.EntityStorageState); + ComponentDepot.Load(state.ComponentDepotState); + EntityStorage.Load(state.EntityStorageState); + RelationDepot.Load(state.RelationDepotState); } } } -- 2.25.1 From 91d771249e8245768488a2b22bf2d52d4b6927cd Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Mon, 2 May 2022 21:49:39 -0700 Subject: [PATCH 6/6] never mind the comparison is fine --- src/RelationStorage.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/RelationStorage.cs b/src/RelationStorage.cs index 336cc98..9deb5a6 100644 --- a/src/RelationStorage.cs +++ b/src/RelationStorage.cs @@ -182,7 +182,6 @@ namespace MoonTools.ECS { ReadOnlySpan relationBytes = MemoryMarshal.Cast(relations); - // FIXME: incorrect comparison if (relationBytes.Length > state.Relations.Length) { Array.Resize(ref state.Relations, relationBytes.Length); -- 2.25.1