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 482214e..a5ada46 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,53 @@ namespace MoonTools.ECS filterSignatureToEntityIDs[filterSignature].Add(entityID); } + public void DisableSerialization() where TComponent : unmanaged + { + TypesWithDisabledSerialization.Add(typeof(TComponent)); + } + + public void Save(ComponentDepotState state) + { + foreach (var (type, storage) in storages) + { + if (!TypesWithDisabledSerialization.Contains(type)) + { + if (!state.StorageStates.ContainsKey(type)) + { + state.StorageStates.Add(type, storage.CreateState()); + } + + storage.Save(state.StorageStates[type]); + } + } + + foreach (var (signature, set) in filterSignatureToEntityIDs) + { + // FIXME: we could cache this + if (!signature.Included.Overlaps(TypesWithDisabledSerialization) && !signature.Excluded.Overlaps(TypesWithDisabledSerialization)) + { + if (!state.FilterStates.ContainsKey(signature)) + { + state.FilterStates[signature] = new IndexableSetState(set.Count); + } + set.Save(state.FilterStates[signature]); + } + } + } + + public void Load(ComponentDepotState state) + { + foreach (var (type, storageState) in state.StorageStates) + { + storages[type].Load(storageState); + } + + foreach (var (signature, setState) in state.FilterStates) + { + filterSignatureToEntityIDs[signature].Load(setState); + } + } + #if DEBUG public IEnumerable Debug_GetAllComponents(int entityID) { diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs index 15a5db2..c7d256b 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -1,17 +1,20 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; 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 Save(ComponentStorageState state); + public abstract void Load(ComponentStorageState state); } - // FIXME: we can probably get rid of this weird entity storage system by using filters - internal class ComponentStorage : ComponentStorage where TComponent : struct + internal class ComponentStorage : ComponentStorage where TComponent : unmanaged { private int nextID; private readonly Dictionary entityIDToStorageIndex = new Dictionary(16); @@ -49,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; @@ -64,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)) { @@ -88,7 +99,11 @@ namespace MoonTools.ECS } nextID -= 1; + + return true; } + + return false; } public void Clear() @@ -106,5 +121,44 @@ namespace MoonTools.ECS { return new Entity(entityIDs[0]); } + + public override ComponentStorageState CreateState() + { + 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; + } } } 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/EntityStorage.cs b/src/EntityStorage.cs index 5d48a49..2379b5d 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 Save(EntityStorageState state) + { + state.NextID = nextID; + state.availableIDs.Clear(); + foreach (var id in availableIDs) + { + state.availableIDs.Add(id); + } + } + + public void Load(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/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/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/IndexableSet.cs b/src/IndexableSet.cs index cf8f56c..275044a 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,32 @@ namespace MoonTools.ECS yield return array[i]; } } + + 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/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..9deb5a6 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,60 @@ 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(); + 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/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/State/ComponentDepotState.cs b/src/State/ComponentDepotState.cs new file mode 100644 index 0000000..2370dc6 --- /dev/null +++ b/src/State/ComponentDepotState.cs @@ -0,0 +1,11 @@ +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 new file mode 100644 index 0000000..ad4872d --- /dev/null +++ b/src/State/ComponentStorageState.cs @@ -0,0 +1,27 @@ +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 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/State/IndexableSetState.cs b/src/State/IndexableSetState.cs new file mode 100644 index 0000000..4367252 --- /dev/null +++ b/src/State/IndexableSetState.cs @@ -0,0 +1,16 @@ +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 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 new file mode 100644 index 0000000..d422745 --- /dev/null +++ b/src/State/WorldState.cs @@ -0,0 +1,16 @@ +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 66eee93..a2749a8 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); + RelationDepot.Set(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..939a9bd 100644 --- a/src/World.cs +++ b/src/World.cs @@ -2,37 +2,22 @@ { 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() { 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); } @@ -41,5 +26,29 @@ { MessageDepot.Clear(); } + + public void DisableSerialization() where TComponent : unmanaged + { + ComponentDepot.DisableSerialization(); + } + + public WorldState CreateState() + { + return new WorldState(); + } + + public void Save(WorldState state) + { + ComponentDepot.Save(state.ComponentDepotState); + EntityStorage.Save(state.EntityStorageState); + RelationDepot.Save(state.RelationDepotState); + } + + public void Load(WorldState state) + { + ComponentDepot.Load(state.ComponentDepotState); + EntityStorage.Load(state.EntityStorageState); + RelationDepot.Load(state.RelationDepotState); + } } }