From 91cf93e1c96a8e2cbb51fa75c88ec8ee51dbf8fd Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 2 Dec 2022 23:43:54 -0800 Subject: [PATCH] storage optimization --- src/ComponentDepot.cs | 284 +++++--------------------------- src/ComponentStorage.cs | 34 ++-- src/DebugSystem.cs | 23 ++- src/EntityComponentReader.cs | 10 +- src/EntityStorage.cs | 59 +++++-- src/Filter.cs | 19 +-- src/FilterBuilder.cs | 33 ++-- src/FilterSignature.cs | 18 +- src/FilterStorage.cs | 131 +++++++++++++++ src/RelationDepot.cs | 58 +++---- src/RelationStorage.cs | 25 +-- src/State/EntityStorageState.cs | 3 + src/System.cs | 41 ++++- src/Template.cs | 12 ++ src/TemplateStorage.cs | 19 +++ src/TypeIndices.cs | 31 ++++ src/World.cs | 64 ++++--- 17 files changed, 465 insertions(+), 399 deletions(-) create mode 100644 src/FilterStorage.cs create mode 100644 src/Template.cs create mode 100644 src/TemplateStorage.cs create mode 100644 src/TypeIndices.cs diff --git a/src/ComponentDepot.cs b/src/ComponentDepot.cs index c9c84f1..8c1c4e9 100644 --- a/src/ComponentDepot.cs +++ b/src/ComponentDepot.cs @@ -1,46 +1,46 @@ using System; -using System.Collections.Generic; using System.Runtime.CompilerServices; namespace MoonTools.ECS { internal class ComponentDepot { - private Dictionary storages = new Dictionary(); + private TypeIndices ComponentTypeIndices; - private Dictionary> filterSignatureToEntityIDs = new Dictionary>(); + private ComponentStorage[] storages = new ComponentStorage[256]; - private Dictionary> typeToFilterSignatures = new Dictionary>(); - -#if DEBUG - private Dictionary singleComponentFilters = new Dictionary(); -#endif - - private HashSet TypesWithDisabledSerialization = new HashSet(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void Register() where TComponent : unmanaged + public ComponentDepot(TypeIndices componentTypeIndices) { - if (!storages.ContainsKey(typeof(TComponent))) - { - storages.Add(typeof(TComponent), new ComponentStorage()); -#if DEBUG - singleComponentFilters.Add(typeof(TComponent), CreateFilter(new HashSet() { typeof(TComponent) }, new HashSet())); -#endif - } + ComponentTypeIndices = componentTypeIndices; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Register(int index) where TComponent : unmanaged + { + if (index >= storages.Length) + { + Array.Resize(ref storages, storages.Length * 2); + } + + storages[index] = new ComponentStorage(); + } + + // FIXME: is this necessary? private ComponentStorage Lookup(Type type) { - return storages[type]; + return storages[ComponentTypeIndices.GetIndex(type)]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private ComponentStorage Lookup() where TComponent : unmanaged { - // TODO: is it possible to optimize this? - Register(); - return (ComponentStorage) storages[typeof(TComponent)]; + var storageIndex = ComponentTypeIndices.GetIndex(); + // TODO: is there some way to avoid this null check? + if (storages[storageIndex] == null) + { + Register(storageIndex); + } + return (ComponentStorage) storages[storageIndex]; } public bool Some() where TComponent : unmanaged @@ -48,41 +48,26 @@ namespace MoonTools.ECS return Lookup().Any(); } - public bool Has(int entityID) where TComponent : unmanaged - { - return Lookup().Has(entityID); - } - - private bool Has(Type type, int entityID) - { - return Lookup(type).Has(entityID); - } - public ref readonly TComponent Get(int entityID) where TComponent : unmanaged { return ref Lookup().Get(entityID); } - public ref readonly TComponent Get() where TComponent : unmanaged +#if DEBUG + public object Debug_Get(int entityID, int componentTypeIndex) { - return ref Lookup().Get(); + return storages[componentTypeIndex].Debug_Get(entityID); + } +#endif + + public ref readonly TComponent GetFirst() where TComponent : unmanaged + { + return ref Lookup().GetFirst(); } public void Set(int entityID, in TComponent component) where TComponent : unmanaged { - var existed = Lookup().Set(entityID, component); - - // update filters - if (!existed) - { - if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures)) - { - foreach (var filterSignature in filterSignatures) - { - CheckFilter(filterSignature, entityID); - } - } - } + Lookup().Set(entityID, component); } public Entity GetSingletonEntity() where TComponent : unmanaged @@ -95,209 +80,14 @@ namespace MoonTools.ECS return Lookup().AllComponents(); } - private void Remove(Type type, int entityID) + public void Remove(int entityID, int storageIndex) { - var existed = Lookup(type).Remove(entityID); - - // update filters - if (existed) - { - if (typeToFilterSignatures.TryGetValue(type, out var filterSignatures)) - { - foreach (var filterSignature in filterSignatures) - { - CheckFilter(filterSignature, entityID); - } - } - } + storages[storageIndex].Remove(entityID); } public void Remove(int entityID) where TComponent : unmanaged { - var existed = Lookup().Remove(entityID); - - // update filters - if (existed) - { - if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures)) - { - foreach (var filterSignature in filterSignatures) - { - CheckFilter(filterSignature, entityID); - } - } - } + Lookup().Remove(entityID); } - - // TODO: is there some way to optimize this without complicating serialization? - public void OnEntityDestroy(int entityID) - { - foreach (var type in storages.Keys) - { - Remove(type, entityID); - } - } - - public Filter CreateFilter(HashSet included, HashSet excluded) - { - var filterSignature = new FilterSignature(included, excluded); - if (!filterSignatureToEntityIDs.ContainsKey(filterSignature)) - { - filterSignatureToEntityIDs.Add(filterSignature, new IndexableSet()); - - foreach (var type in included) - { - if (!typeToFilterSignatures.ContainsKey(type)) - { - typeToFilterSignatures.Add(type, new HashSet()); - } - - typeToFilterSignatures[type].Add(filterSignature); - } - - foreach (var type in excluded) - { - if (!typeToFilterSignatures.ContainsKey(type)) - { - typeToFilterSignatures.Add(type, new HashSet()); - } - - typeToFilterSignatures[type].Add(filterSignature); - } - } - return new Filter(this, included, excluded); - } - - // FIXME: this dictionary should probably just store entities - public IEnumerable FilterEntities(Filter filter) - { - foreach (var id in filterSignatureToEntityIDs[filter.Signature]) - { - yield return new Entity(id); - } - } - - public IEnumerable FilterEntitiesRandom(Filter filter) - { - foreach (var index in RandomGenerator.LinearCongruentialGenerator(FilterCount(filter))) - { - yield return new Entity(filterSignatureToEntityIDs[filter.Signature][index]); - } - } - - public Entity FilterNthEntity(Filter filter, int index) - { - return new Entity(filterSignatureToEntityIDs[filter.Signature][index]); - } - - public Entity FilterRandomEntity(Filter filter) - { - var randomIndex = RandomGenerator.Next(FilterCount(filter)); - return new Entity(filterSignatureToEntityIDs[filter.Signature][randomIndex]); - } - - public int FilterCount(Filter filter) - { - return filterSignatureToEntityIDs[filter.Signature].Count; - } - - private void CheckFilter(FilterSignature filterSignature, int entityID) - { - foreach (var type in filterSignature.Included) - { - if (!Has(type, entityID)) - { - filterSignatureToEntityIDs[filterSignature].Remove(entityID); - return; - } - } - - foreach (var type in filterSignature.Excluded) - { - if (Has(type, entityID)) - { - filterSignatureToEntityIDs[filterSignature].Remove(entityID); - return; - } - } - - 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) - { - foreach (var (type, storage) in storages) - { - if (storage.Has(entityID)) - { - yield return storage.Debug_Get(entityID); - } - } - } - - public IEnumerable Debug_GetEntities(Type componentType) - { - return singleComponentFilters[componentType].Entities; - } - - public IEnumerable Debug_SearchComponentType(string typeString) - { - foreach (var type in storages.Keys) - { - if (type.ToString().ToLower().Contains(typeString.ToLower())) - { - yield return type; - } - } - } -#endif } } diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs index 8324558..35d6f5b 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -6,12 +6,14 @@ namespace MoonTools.ECS { internal abstract class ComponentStorage { - public abstract bool Has(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); + +#if DEBUG + public abstract object Debug_Get(int entityID); +#endif } internal class ComponentStorage : ComponentStorage where TComponent : unmanaged @@ -26,22 +28,12 @@ namespace MoonTools.ECS return nextID > 0; } - public override bool Has(int entityID) - { - return entityIDToStorageIndex.ContainsKey(entityID); - } - public ref readonly TComponent Get(int entityID) { return ref components[entityIDToStorageIndex[entityID]]; } - public override object Debug_Get(int entityID) - { - return components[entityIDToStorageIndex[entityID]]; - } - - public ref readonly TComponent Get() + public ref readonly TComponent GetFirst() { #if DEBUG if (nextID == 0) @@ -52,11 +44,8 @@ namespace MoonTools.ECS return ref components[0]; } - // Returns true if the entity already had this component. - public bool Set(int entityID, in TComponent component) + public void Set(int entityID, in TComponent component) { - bool result = true; - if (!entityIDToStorageIndex.ContainsKey(entityID)) { var index = nextID; @@ -70,13 +59,9 @@ namespace MoonTools.ECS entityIDToStorageIndex[entityID] = index; entityIDs[index] = entityID; - - result = false; } components[entityIDToStorageIndex[entityID]] = component; - - return result; } // Returns true if the entity had this component. @@ -166,5 +151,12 @@ namespace MoonTools.ECS nextID = state.Count; } + +#if DEBUG + public override object Debug_Get(int entityID) + { + return components[entityIDToStorageIndex[entityID]]; + } +#endif } } diff --git a/src/DebugSystem.cs b/src/DebugSystem.cs index 2db79cc..a8e0002 100644 --- a/src/DebugSystem.cs +++ b/src/DebugSystem.cs @@ -9,23 +9,40 @@ namespace MoonTools.ECS { public abstract class DebugSystem : System { +#if DEBUG + private Dictionary singleComponentFilters = new Dictionary(); +#endif + protected DebugSystem(World world) : base(world) { } protected IEnumerable Debug_GetAllComponents(Entity entity) { - return ComponentDepot.Debug_GetAllComponents(entity.ID); + foreach (var typeIndex in EntityStorage.ComponentTypeIndices(entity.ID)) + { + yield return ComponentDepot.Debug_Get(entity.ID, typeIndex); + } } protected IEnumerable Debug_GetEntities(Type componentType) { - return ComponentDepot.Debug_GetEntities(componentType); + if (!singleComponentFilters.ContainsKey(componentType)) + { + singleComponentFilters.Add(componentType, new Filter(FilterStorage, new HashSet(ComponentTypeIndices.GetIndex(componentType)), new HashSet())); + } + return singleComponentFilters[componentType].Entities; } protected IEnumerable Debug_SearchComponentType(string typeString) { - return ComponentDepot.Debug_SearchComponentType(typeString); + foreach (var type in ComponentTypeIndices.Types) + { + if (type.ToString().ToLower().Contains(typeString.ToLower())) + { + yield return type; + } + } } } } diff --git a/src/EntityComponentReader.cs b/src/EntityComponentReader.cs index 81b2c76..caf1ba7 100644 --- a/src/EntityComponentReader.cs +++ b/src/EntityComponentReader.cs @@ -9,7 +9,10 @@ namespace MoonTools.ECS internal EntityStorage EntityStorage => World.EntityStorage; internal ComponentDepot ComponentDepot => World.ComponentDepot; internal RelationDepot RelationDepot => World.RelationDepot; - protected FilterBuilder FilterBuilder => new FilterBuilder(ComponentDepot); + protected FilterBuilder FilterBuilder => new FilterBuilder(FilterStorage, ComponentTypeIndices); + internal FilterStorage FilterStorage => World.FilterStorage; + internal TypeIndices ComponentTypeIndices => World.ComponentTypeIndices; + internal TypeIndices RelationTypeIndices => World.RelationTypeIndices; public EntityComponentReader(World world) { @@ -23,7 +26,8 @@ namespace MoonTools.ECS protected bool Has(in Entity entity) where TComponent : unmanaged { - return ComponentDepot.Has(entity.ID); + var storageIndex = ComponentTypeIndices.GetIndex(); + return EntityStorage.HasComponent(entity.ID, storageIndex); } protected bool Some() where TComponent : unmanaged @@ -38,7 +42,7 @@ namespace MoonTools.ECS protected ref readonly TComponent GetSingleton() where TComponent : unmanaged { - return ref ComponentDepot.Get(); + return ref ComponentDepot.GetFirst(); } protected Entity GetSingletonEntity() where TComponent : unmanaged diff --git a/src/EntityStorage.cs b/src/EntityStorage.cs index 2379b5d..39aa9b9 100644 --- a/src/EntityStorage.cs +++ b/src/EntityStorage.cs @@ -5,12 +5,19 @@ namespace MoonTools.ECS internal class EntityStorage { private int nextID = 0; + // FIXME: why is this duplicated? private readonly Stack availableIDs = new Stack(); private readonly HashSet availableIDHash = new HashSet(); + private Dictionary> EntityToComponentTypeIndices = new Dictionary>(); + private Dictionary> EntityToRelationTypeIndices = new Dictionary>(); + public Entity Create() { - return new Entity(NextID()); + var entity = new Entity(NextID()); + EntityToComponentTypeIndices.TryAdd(entity.ID, new HashSet()); + EntityToRelationTypeIndices.TryAdd(entity.ID, new HashSet()); + return entity; } public bool Exists(in Entity entity) @@ -20,29 +27,47 @@ namespace MoonTools.ECS public void Destroy(in Entity entity) { + EntityToComponentTypeIndices[entity.ID].Clear(); + EntityToRelationTypeIndices[entity.ID].Clear(); Release(entity.ID); } - public void Save(EntityStorageState state) + // Returns true if the component is new. + public bool SetComponent(int entityID, int storageIndex) { - state.NextID = nextID; - state.availableIDs.Clear(); - foreach (var id in availableIDs) - { - state.availableIDs.Add(id); - } + return EntityToComponentTypeIndices[entityID].Add(storageIndex); } - public void Load(EntityStorageState state) + public bool HasComponent(int entityID, int storageIndex) { - nextID = state.NextID; - availableIDs.Clear(); - availableIDHash.Clear(); - foreach (var id in state.availableIDs) - { - availableIDs.Push(id); - availableIDHash.Add(id); - } + return EntityToComponentTypeIndices[entityID].Contains(storageIndex); + } + + // Returns true if the component existed. + public bool RemoveComponent(int entityID, int storageIndex) + { + return EntityToComponentTypeIndices[entityID].Remove(storageIndex); + } + + public void AddRelation(int entityID, int relationIndex) + { + EntityToRelationTypeIndices[entityID].Add(relationIndex); + } + + public void RemoveRelation(int entityId, int relationIndex) + { + EntityToRelationTypeIndices[entityId].Remove(relationIndex); + } + + // TODO: should these ints be ID types? + public IEnumerable ComponentTypeIndices(int entityID) + { + return EntityToComponentTypeIndices[entityID]; + } + + public IEnumerable RelationTypeIndices(int entityID) + { + return EntityToRelationTypeIndices[entityID]; } private int NextID() diff --git a/src/Filter.cs b/src/Filter.cs index b229728..ef70da6 100644 --- a/src/Filter.cs +++ b/src/Filter.cs @@ -1,27 +1,26 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace MoonTools.ECS { public class Filter { internal FilterSignature Signature; - private ComponentDepot ComponentDepot; + private FilterStorage FilterStorage; - internal Filter(ComponentDepot componentDepot, HashSet included, HashSet excluded) + internal Filter(FilterStorage filterStorage, HashSet included, HashSet excluded) { - ComponentDepot = componentDepot; + FilterStorage = filterStorage; Signature = new FilterSignature(included, excluded); } - public IEnumerable Entities => ComponentDepot.FilterEntities(this); - public IEnumerable EntitiesInRandomOrder => ComponentDepot.FilterEntitiesRandom(this); - public Entity RandomEntity => ComponentDepot.FilterRandomEntity(this); + public IEnumerable Entities => FilterStorage.FilterEntities(Signature); + public IEnumerable EntitiesInRandomOrder => FilterStorage.FilterEntitiesRandom(Signature); + public Entity RandomEntity => FilterStorage.FilterRandomEntity(Signature); - public int Count => ComponentDepot.FilterCount(this); + public int Count => FilterStorage.FilterCount(Signature); public bool Empty => Count == 0; // WARNING: this WILL crash if the index is out of range! - public Entity NthEntity(int index) => ComponentDepot.FilterNthEntity(this, index); + public Entity NthEntity(int index) => FilterStorage.FilterNthEntity(Signature, index); } } diff --git a/src/FilterBuilder.cs b/src/FilterBuilder.cs index be75edc..ed1562e 100644 --- a/src/FilterBuilder.cs +++ b/src/FilterBuilder.cs @@ -5,41 +5,42 @@ namespace MoonTools.ECS { public struct FilterBuilder { - private ComponentDepot ComponentDepot; - private HashSet Included; - private HashSet Excluded; + private TypeIndices ComponentTypeIndices; + private FilterStorage FilterStorage; + private HashSet Included; + private HashSet Excluded; - internal FilterBuilder(ComponentDepot componentDepot) + internal FilterBuilder(FilterStorage filterStorage, TypeIndices componentTypeIndices) { - ComponentDepot = componentDepot; - Included = new HashSet(); - Excluded = new HashSet(); + FilterStorage = filterStorage; + ComponentTypeIndices = componentTypeIndices; + Included = new HashSet(); + Excluded = new HashSet(); } - private FilterBuilder(ComponentDepot componentDepot, HashSet included, HashSet excluded) + private FilterBuilder(FilterStorage filterStorage, TypeIndices componentTypeIndices, HashSet included, HashSet excluded) { - ComponentDepot = componentDepot; + FilterStorage = filterStorage; + ComponentTypeIndices = componentTypeIndices; Included = included; Excluded = excluded; } public FilterBuilder Include() where TComponent : unmanaged { - ComponentDepot.Register(); - Included.Add(typeof(TComponent)); - return new FilterBuilder(ComponentDepot, Included, Excluded); + Included.Add(ComponentTypeIndices.GetIndex()); + return new FilterBuilder(FilterStorage, ComponentTypeIndices, Included, Excluded); } public FilterBuilder Exclude() where TComponent : unmanaged { - ComponentDepot.Register(); - Excluded.Add(typeof(TComponent)); - return new FilterBuilder(ComponentDepot, Included, Excluded); + Excluded.Add(ComponentTypeIndices.GetIndex()); + return new FilterBuilder(FilterStorage, ComponentTypeIndices, Included, Excluded); } public Filter Build() { - return ComponentDepot.CreateFilter(Included, Excluded); + return FilterStorage.CreateFilter(Included, Excluded); } } } diff --git a/src/FilterSignature.cs b/src/FilterSignature.cs index 7508b36..66f7fc3 100644 --- a/src/FilterSignature.cs +++ b/src/FilterSignature.cs @@ -5,12 +5,10 @@ namespace MoonTools.ECS { public struct FilterSignature { - private const int HASH_FACTOR = 97; + public readonly HashSet Included; + public readonly HashSet Excluded; - public readonly HashSet Included; - public readonly HashSet Excluded; - - public FilterSignature(HashSet included, HashSet excluded) + public FilterSignature(HashSet included, HashSet excluded) { Included = included; Excluded = excluded; @@ -33,19 +31,19 @@ namespace MoonTools.ECS public override int GetHashCode() { - int result = 1; + var hashcode = 1; + foreach (var type in Included) { - result *= HASH_FACTOR + GuidToInt(type.GUID); + hashcode = HashCode.Combine(hashcode, type); } - // FIXME: Is there a way to avoid collisions when this is the same set as included? foreach (var type in Excluded) { - result *= HASH_FACTOR + GuidToInt(type.GUID); + hashcode = HashCode.Combine(hashcode, type); } - return result; + return hashcode; } } } diff --git a/src/FilterStorage.cs b/src/FilterStorage.cs new file mode 100644 index 0000000..1b5af12 --- /dev/null +++ b/src/FilterStorage.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; + +namespace MoonTools.ECS +{ + internal class FilterStorage + { + private EntityStorage EntityStorage; + private TypeIndices ComponentTypeIndices; + private Dictionary> filterSignatureToEntityIDs = new Dictionary>(); + private Dictionary> typeToFilterSignatures = new Dictionary>(); + + public FilterStorage(EntityStorage entityStorage, TypeIndices componentTypeIndices) + { + EntityStorage = entityStorage; + ComponentTypeIndices = componentTypeIndices; + } + + public Filter CreateFilter(HashSet included, HashSet excluded) + { + var filterSignature = new FilterSignature(included, excluded); + if (!filterSignatureToEntityIDs.ContainsKey(filterSignature)) + { + filterSignatureToEntityIDs.Add(filterSignature, new IndexableSet()); + + foreach (var type in included) + { + if (!typeToFilterSignatures.ContainsKey(type)) + { + typeToFilterSignatures.Add(type, new HashSet()); + } + + typeToFilterSignatures[type].Add(filterSignature); + } + + foreach (var type in excluded) + { + if (!typeToFilterSignatures.ContainsKey(type)) + { + typeToFilterSignatures.Add(type, new HashSet()); + } + + typeToFilterSignatures[type].Add(filterSignature); + } + } + return new Filter(this, included, excluded); + } + + public IEnumerable FilterEntities(FilterSignature filterSignature) + { + foreach (var id in filterSignatureToEntityIDs[filterSignature]) + { + yield return new Entity(id); + } + } + + public IEnumerable FilterEntitiesRandom(FilterSignature filterSignature) + { + foreach (var index in RandomGenerator.LinearCongruentialGenerator(FilterCount(filterSignature))) + { + yield return new Entity(filterSignatureToEntityIDs[filterSignature][index]); + } + } + + public Entity FilterNthEntity(FilterSignature filterSignature, int index) + { + return new Entity(filterSignatureToEntityIDs[filterSignature][index]); + } + + public Entity FilterRandomEntity(FilterSignature filterSignature) + { + var randomIndex = RandomGenerator.Next(FilterCount(filterSignature)); + return new Entity(filterSignatureToEntityIDs[filterSignature][randomIndex]); + } + + public int FilterCount(FilterSignature filterSignature) + { + return filterSignatureToEntityIDs[filterSignature].Count; + } + + public void Check(int entityID, int componentTypeIndex) + { + if (typeToFilterSignatures.ContainsKey(componentTypeIndex)) + { + foreach (var filterSignature in typeToFilterSignatures[componentTypeIndex]) + { + CheckFilter(entityID, filterSignature); + } + } + } + + public void Check(int entityID) where TComponent : unmanaged + { + Check(entityID, ComponentTypeIndices.GetIndex()); + } + + private void CheckFilter(int entityID, FilterSignature filterSignature) + { + foreach (var type in filterSignature.Included) + { + if (!EntityStorage.HasComponent(entityID, type)) + { + filterSignatureToEntityIDs[filterSignature].Remove(entityID); + return; + } + } + + foreach (var type in filterSignature.Excluded) + { + if (EntityStorage.HasComponent(entityID, type)) + { + filterSignatureToEntityIDs[filterSignature].Remove(entityID); + return; + } + } + + filterSignatureToEntityIDs[filterSignature].Add(entityID); + } + + public void RemoveEntity(int entityID, int componentTypeIndex) + { + if (typeToFilterSignatures.ContainsKey(componentTypeIndex)) + { + foreach (var filterSignature in typeToFilterSignatures[componentTypeIndex]) + { + filterSignatureToEntityIDs[filterSignature].Remove(entityID); + } + } + } + } +} diff --git a/src/RelationDepot.cs b/src/RelationDepot.cs index 4b8652c..8c150ea 100644 --- a/src/RelationDepot.cs +++ b/src/RelationDepot.cs @@ -6,21 +6,34 @@ namespace MoonTools.ECS { internal class RelationDepot { - private Dictionary storages = new Dictionary(); + private TypeIndices RelationTypeIndices; + private RelationStorage[] storages = new RelationStorage[256]; - private void Register() where TRelationKind : unmanaged + public RelationDepot(TypeIndices relationTypeIndices) { - if (!storages.ContainsKey(typeof(TRelationKind))) + RelationTypeIndices = relationTypeIndices; + } + + private void Register(int index) where TRelationKind : unmanaged + { + if (index >= storages.Length) { - storages.Add(typeof(TRelationKind), new RelationStorage()); + Array.Resize(ref storages, storages.Length * 2); } + + storages[index] = new RelationStorage(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private RelationStorage Lookup() where TRelationKind : unmanaged { - Register(); - return (RelationStorage) storages[typeof(TRelationKind)]; + var storageIndex = RelationTypeIndices.GetIndex(); + // TODO: is there some way to avoid this null check? + if (storages[storageIndex] == null) + { + Register(storageIndex); + } + return (RelationStorage) storages[storageIndex]; } public void Set(Relation relation, TRelationKind relationData) where TRelationKind : unmanaged @@ -28,9 +41,9 @@ namespace MoonTools.ECS Lookup().Set(relation, relationData); } - public void Remove(Relation relation) where TRelationKind : unmanaged + public (bool, bool) Remove(Relation relation) where TRelationKind : unmanaged { - Lookup().Remove(relation); + return Lookup().Remove(relation); } public void UnrelateAll(int entityID) where TRelationKind : unmanaged @@ -38,13 +51,9 @@ namespace MoonTools.ECS Lookup().UnrelateAll(entityID); } - // FIXME: optimize this - public void OnEntityDestroy(int entityID) + public void UnrelateAll(int entityID, int relationStorageIndex) { - foreach (var storage in storages.Values) - { - storage.OnEntityDestroy(entityID); - } + storages[relationStorageIndex].UnrelateAll(entityID); } public IEnumerable<(Entity, Entity, TRelationKind)> Relations() where TRelationKind : unmanaged @@ -96,26 +105,5 @@ namespace MoonTools.ECS { return Lookup().InRelationCount(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 bf86f9d..05110eb 100644 --- a/src/RelationStorage.cs +++ b/src/RelationStorage.cs @@ -9,7 +9,7 @@ namespace MoonTools.ECS public abstract RelationStorageState CreateState(); public abstract void Save(RelationStorageState state); public abstract void Load(RelationStorageState state); - public abstract void OnEntityDestroy(int entityID); + public abstract void UnrelateAll(int entityID); } // Relation is the two entities, A related to B. @@ -144,16 +144,27 @@ namespace MoonTools.ECS return inRelations.ContainsKey(entityID) ? inRelations[entityID].Count : 0; } - public bool Remove(Relation relation) + public (bool, bool) Remove(Relation relation) { + var aEmpty = false; + var bEmpty = false; + if (outRelations.ContainsKey(relation.A.ID)) { outRelations[relation.A.ID].Remove(relation.B.ID); + if (outRelations[relation.A.ID].Count == 0) + { + aEmpty = true; + } } if (inRelations.ContainsKey(relation.B.ID)) { inRelations[relation.B.ID].Remove(relation.A.ID); + if (inRelations[relation.B.ID].Count == 0) + { + bEmpty = true; + } } if (indices.ContainsKey(relation)) @@ -172,13 +183,12 @@ namespace MoonTools.ECS count -= 1; indices.Remove(relation); - return true; } - return false; + return (aEmpty, bEmpty); } - public void UnrelateAll(int entityID) + public override void UnrelateAll(int entityID) { if (outRelations.ContainsKey(entityID)) { @@ -203,11 +213,6 @@ namespace MoonTools.ECS } } - public override void OnEntityDestroy(int entityID) - { - UnrelateAll(entityID); - } - private IndexableSet AcquireHashSetFromPool() { if (listPool.Count == 0) diff --git a/src/State/EntityStorageState.cs b/src/State/EntityStorageState.cs index 66363dc..aebfec3 100644 --- a/src/State/EntityStorageState.cs +++ b/src/State/EntityStorageState.cs @@ -6,5 +6,8 @@ namespace MoonTools.ECS { public int NextID; public List availableIDs = new List(); + + public Dictionary> EntityToComponentTypeIndices = new Dictionary>(); + public Dictionary> EntityToRelationTypeIndices = new Dictionary>(); } } diff --git a/src/System.cs b/src/System.cs index 1a993ec..15ca930 100644 --- a/src/System.cs +++ b/src/System.cs @@ -25,12 +25,21 @@ namespace MoonTools.ECS throw new ArgumentException("This entity is not valid!"); } #endif + if (EntityStorage.SetComponent(entity.ID, ComponentTypeIndices.GetIndex())) + { + FilterStorage.Check(entity.ID); + } + ComponentDepot.Set(entity.ID, component); } protected void Remove(in Entity entity) where TComponent : unmanaged { - ComponentDepot.Remove(entity.ID); + if (EntityStorage.RemoveComponent(entity.ID, ComponentTypeIndices.GetIndex())) + { + ComponentDepot.Remove(entity.ID); + FilterStorage.Check(entity.ID); + } } protected ReadOnlySpan ReadMessages() where TMessage : unmanaged @@ -71,23 +80,45 @@ namespace MoonTools.ECS protected void Relate(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged { RelationDepot.Set(new Relation(entityA, entityB), relationData); + var relationTypeIndex = RelationTypeIndices.GetIndex(); + EntityStorage.AddRelation(entityA.ID, relationTypeIndex); + EntityStorage.AddRelation(entityB.ID, relationTypeIndex); } protected void Unrelate(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged { - RelationDepot.Remove(new Relation(entityA, entityB)); + var (aEmpty, bEmpty) = RelationDepot.Remove(new Relation(entityA, entityB)); + + if (aEmpty) + { + EntityStorage.RemoveRelation(entityA.ID, RelationTypeIndices.GetIndex()); + } + + if (bEmpty) + { + EntityStorage.RemoveRelation(entityB.ID, RelationTypeIndices.GetIndex()); + } } protected void UnrelateAll(in Entity entity) where TRelationKind : unmanaged { RelationDepot.UnrelateAll(entity.ID); + EntityStorage.RemoveRelation(entity.ID, RelationTypeIndices.GetIndex()); } - // FIXME: this is insanely inefficient protected void Destroy(in Entity entity) { - ComponentDepot.OnEntityDestroy(entity.ID); - RelationDepot.OnEntityDestroy(entity.ID); + foreach (var componentTypeIndex in EntityStorage.ComponentTypeIndices(entity.ID)) + { + ComponentDepot.Remove(entity.ID, componentTypeIndex); + FilterStorage.RemoveEntity(entity.ID, componentTypeIndex); + } + + foreach (var relationTypeIndex in EntityStorage.RelationTypeIndices(entity.ID)) + { + RelationDepot.UnrelateAll(entity.ID, relationTypeIndex); + } + EntityStorage.Destroy(entity); } } diff --git a/src/Template.cs b/src/Template.cs new file mode 100644 index 0000000..c2c25b7 --- /dev/null +++ b/src/Template.cs @@ -0,0 +1,12 @@ +namespace MoonTools.ECS +{ + public struct Template + { + public int ID { get; } + + internal Template(int id) + { + ID = id; + } + } +} diff --git a/src/TemplateStorage.cs b/src/TemplateStorage.cs new file mode 100644 index 0000000..e3b36f7 --- /dev/null +++ b/src/TemplateStorage.cs @@ -0,0 +1,19 @@ +namespace MoonTools.ECS +{ + public class TemplateStorage + { + private int nextID = 0; + + public Template Create() + { + return new Template(NextID()); + } + + private int NextID() + { + var id = nextID; + nextID += 1; + return id; + } + } +} diff --git a/src/TypeIndices.cs b/src/TypeIndices.cs new file mode 100644 index 0000000..60c1adb --- /dev/null +++ b/src/TypeIndices.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; + +namespace MoonTools.ECS +{ + public class TypeIndices + { + Dictionary TypeToIndex = new Dictionary(); + int nextID = 0; + + public int GetIndex() where T : unmanaged + { + if (!TypeToIndex.ContainsKey(typeof(T))) + { + TypeToIndex.Add(typeof(T), nextID); + nextID += 1; + } + + return TypeToIndex[typeof(T)]; + } + + public int GetIndex(Type type) + { + return TypeToIndex[type]; + } + +#if DEBUG + public IEnumerable Types => TypeToIndex.Keys; +#endif + } +} diff --git a/src/World.cs b/src/World.cs index 939a9bd..0517633 100644 --- a/src/World.cs +++ b/src/World.cs @@ -2,10 +2,25 @@ { public class World { + internal readonly TypeIndices ComponentTypeIndices = new TypeIndices(); + internal readonly TypeIndices RelationTypeIndices = new TypeIndices(); internal readonly EntityStorage EntityStorage = new EntityStorage(); - internal readonly ComponentDepot ComponentDepot = new ComponentDepot(); + internal readonly ComponentDepot ComponentDepot; internal readonly MessageDepot MessageDepot = new MessageDepot(); - internal readonly RelationDepot RelationDepot = new RelationDepot(); + internal readonly RelationDepot RelationDepot; + internal readonly FilterStorage FilterStorage; + + /* + internal readonly TemplateStorage TemplateStorage = new TemplateStorage(); + internal readonly ComponentDepot TemplateComponentDepot = new ComponentDepot(); + */ + + public World() + { + ComponentDepot = new ComponentDepot(ComponentTypeIndices); + RelationDepot = new RelationDepot(RelationTypeIndices); + FilterStorage = new FilterStorage(EntityStorage, ComponentTypeIndices); + } public Entity CreateEntity() { @@ -14,7 +29,31 @@ public void Set(Entity entity, in TComponent component) where TComponent : unmanaged { - ComponentDepot.Set(entity.ID, component); + if (EntityStorage.SetComponent(entity.ID, ComponentTypeIndices.GetIndex())) + { + FilterStorage.Check(entity.ID); + } + + ComponentDepot.Set(entity.ID, component); + } + + /* + public Template CreateTemplate() + { + return TemplateStorage.Create(); + } + + public void Set(Template template, in TComponent component) where TComponent : unmanaged + { + TemplateComponentDepot.Set(template.ID, component); + } + */ + + public Entity Instantiate(Template template) + { + var entity = EntityStorage.Create(); + + return entity; } public void Send(in TMessage message) where TMessage : unmanaged @@ -27,28 +66,9 @@ 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); - } } }