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); } } }