From 91cf93e1c96a8e2cbb51fa75c88ec8ee51dbf8fd Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 2 Dec 2022 23:43:54 -0800 Subject: [PATCH 01/14] 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); - } } } -- 2.25.1 From 7c6410275fbbc6ade5ae9b15665a9fa718a1d8b3 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 2 Dec 2022 23:51:54 -0800 Subject: [PATCH 02/14] remove unnecessary method --- src/ComponentDepot.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/ComponentDepot.cs b/src/ComponentDepot.cs index 8c1c4e9..e0a4618 100644 --- a/src/ComponentDepot.cs +++ b/src/ComponentDepot.cs @@ -25,12 +25,6 @@ namespace MoonTools.ECS storages[index] = new ComponentStorage(); } - // FIXME: is this necessary? - private ComponentStorage Lookup(Type type) - { - return storages[ComponentTypeIndices.GetIndex(type)]; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private ComponentStorage Lookup() where TComponent : unmanaged { -- 2.25.1 From f045335881092a9533752a510a09da2779074a24 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Mon, 5 Dec 2022 17:46:18 -0800 Subject: [PATCH 03/14] initial template system --- src/ComponentDepot.cs | 14 +++++++++----- src/ComponentStorage.cs | 23 +++++++++++++---------- src/DebugSystem.cs | 4 +--- src/EntityComponentReader.cs | 2 ++ src/EntityStorage.cs | 12 ++++++------ src/System.cs | 22 ++++++++++++++++++++++ src/TemplateStorage.cs | 15 +++++++++++++++ src/World.cs | 21 ++++++++++++++------- 8 files changed, 82 insertions(+), 31 deletions(-) diff --git a/src/ComponentDepot.cs b/src/ComponentDepot.cs index e0a4618..94487ec 100644 --- a/src/ComponentDepot.cs +++ b/src/ComponentDepot.cs @@ -15,7 +15,7 @@ namespace MoonTools.ECS } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Register(int index) where TComponent : unmanaged + internal void Register(int index) where TComponent : unmanaged { if (index >= storages.Length) { @@ -47,12 +47,11 @@ namespace MoonTools.ECS return ref Lookup().Get(entityID); } -#if DEBUG - public object Debug_Get(int entityID, int componentTypeIndex) + // used for debugging and template instantiation + internal object UntypedGet(int entityID, int componentTypeIndex) { - return storages[componentTypeIndex].Debug_Get(entityID); + return storages[componentTypeIndex].UntypedGet(entityID); } -#endif public ref readonly TComponent GetFirst() where TComponent : unmanaged { @@ -64,6 +63,11 @@ namespace MoonTools.ECS Lookup().Set(entityID, component); } + internal void Set(int entityID, int componentTypeIndex, object component) + { + storages[componentTypeIndex].Set(entityID, component); + } + public Entity GetSingletonEntity() where TComponent : unmanaged { return Lookup().FirstEntity(); diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs index 35d6f5b..6714b90 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -6,14 +6,14 @@ namespace MoonTools.ECS { internal abstract class ComponentStorage { + internal abstract void Set(int entityID, object component); public abstract bool Remove(int entityID); public abstract ComponentStorageState CreateState(); public abstract void Save(ComponentStorageState state); public abstract void Load(ComponentStorageState state); -#if DEBUG - public abstract object Debug_Get(int entityID); -#endif + // used for debugging and template instantiation + internal abstract object UntypedGet(int entityID); } internal class ComponentStorage : ComponentStorage where TComponent : unmanaged @@ -33,6 +33,11 @@ namespace MoonTools.ECS return ref components[entityIDToStorageIndex[entityID]]; } + internal override object UntypedGet(int entityID) + { + return components[entityIDToStorageIndex[entityID]]; + } + public ref readonly TComponent GetFirst() { #if DEBUG @@ -64,6 +69,11 @@ namespace MoonTools.ECS components[entityIDToStorageIndex[entityID]] = component; } + internal override void Set(int entityID, object component) + { + Set(entityID, (TComponent) component); + } + // Returns true if the entity had this component. public override bool Remove(int entityID) { @@ -151,12 +161,5 @@ 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 a8e0002..36aada6 100644 --- a/src/DebugSystem.cs +++ b/src/DebugSystem.cs @@ -9,9 +9,7 @@ namespace MoonTools.ECS { public abstract class DebugSystem : System { -#if DEBUG private Dictionary singleComponentFilters = new Dictionary(); -#endif protected DebugSystem(World world) : base(world) { @@ -21,7 +19,7 @@ namespace MoonTools.ECS { foreach (var typeIndex in EntityStorage.ComponentTypeIndices(entity.ID)) { - yield return ComponentDepot.Debug_Get(entity.ID, typeIndex); + yield return ComponentDepot.UntypedGet(entity.ID, typeIndex); } } diff --git a/src/EntityComponentReader.cs b/src/EntityComponentReader.cs index caf1ba7..91fa1ac 100644 --- a/src/EntityComponentReader.cs +++ b/src/EntityComponentReader.cs @@ -13,6 +13,8 @@ namespace MoonTools.ECS internal FilterStorage FilterStorage => World.FilterStorage; internal TypeIndices ComponentTypeIndices => World.ComponentTypeIndices; internal TypeIndices RelationTypeIndices => World.RelationTypeIndices; + internal TemplateStorage TemplateStorage => World.TemplateStorage; + internal ComponentDepot TemplateComponentDepot => World.TemplateComponentDepot; public EntityComponentReader(World world) { diff --git a/src/EntityStorage.cs b/src/EntityStorage.cs index 39aa9b9..44fd0a9 100644 --- a/src/EntityStorage.cs +++ b/src/EntityStorage.cs @@ -33,20 +33,20 @@ namespace MoonTools.ECS } // Returns true if the component is new. - public bool SetComponent(int entityID, int storageIndex) + public bool SetComponent(int entityID, int componentTypeIndex) { - return EntityToComponentTypeIndices[entityID].Add(storageIndex); + return EntityToComponentTypeIndices[entityID].Add(componentTypeIndex); } - public bool HasComponent(int entityID, int storageIndex) + public bool HasComponent(int entityID, int componentTypeIndex) { - return EntityToComponentTypeIndices[entityID].Contains(storageIndex); + return EntityToComponentTypeIndices[entityID].Contains(componentTypeIndex); } // Returns true if the component existed. - public bool RemoveComponent(int entityID, int storageIndex) + public bool RemoveComponent(int entityID, int componentTypeIndex) { - return EntityToComponentTypeIndices[entityID].Remove(storageIndex); + return EntityToComponentTypeIndices[entityID].Remove(componentTypeIndex); } public void AddRelation(int entityID, int relationIndex) diff --git a/src/System.cs b/src/System.cs index 15ca930..888a9bd 100644 --- a/src/System.cs +++ b/src/System.cs @@ -42,6 +42,28 @@ namespace MoonTools.ECS } } + protected void Set(in Template template, in TComponent component) where TComponent : unmanaged + { + var componentTypeIndex = ComponentTypeIndices.GetIndex(); + TemplateStorage.SetComponent(template.ID, componentTypeIndex); + TemplateComponentDepot.Set(template.ID, component); + ComponentDepot.Register(componentTypeIndex); + } + + protected Entity Instantiate(in Template template) + { + var entity = EntityStorage.Create(); + + foreach (var componentTypeIndex in TemplateStorage.ComponentTypeIndices(template.ID)) + { + EntityStorage.SetComponent(entity.ID, componentTypeIndex); + FilterStorage.Check(entity.ID, componentTypeIndex); + ComponentDepot.Set(entity.ID, componentTypeIndex, TemplateComponentDepot.UntypedGet(template.ID, componentTypeIndex)); + } + + return entity; + } + protected ReadOnlySpan ReadMessages() where TMessage : unmanaged { return MessageDepot.All(); diff --git a/src/TemplateStorage.cs b/src/TemplateStorage.cs index e3b36f7..c45f32f 100644 --- a/src/TemplateStorage.cs +++ b/src/TemplateStorage.cs @@ -1,14 +1,29 @@ +using System.Collections.Generic; + namespace MoonTools.ECS { public class TemplateStorage { private int nextID = 0; + private Dictionary> TemplateToComponentTypeIndices = new Dictionary>(); + public Template Create() { + TemplateToComponentTypeIndices.Add(nextID, new HashSet()); return new Template(NextID()); } + public bool SetComponent(int templateID, int componentTypeIndex) + { + return TemplateToComponentTypeIndices[templateID].Add(componentTypeIndex); + } + + public IEnumerable ComponentTypeIndices(int templateID) + { + return TemplateToComponentTypeIndices[templateID]; + } + private int NextID() { var id = nextID; diff --git a/src/World.cs b/src/World.cs index 0517633..d0b4834 100644 --- a/src/World.cs +++ b/src/World.cs @@ -10,16 +10,16 @@ internal readonly RelationDepot RelationDepot; internal readonly FilterStorage FilterStorage; - /* internal readonly TemplateStorage TemplateStorage = new TemplateStorage(); - internal readonly ComponentDepot TemplateComponentDepot = new ComponentDepot(); - */ + internal readonly ComponentDepot TemplateComponentDepot; + public World() { ComponentDepot = new ComponentDepot(ComponentTypeIndices); RelationDepot = new RelationDepot(RelationTypeIndices); FilterStorage = new FilterStorage(EntityStorage, ComponentTypeIndices); + TemplateComponentDepot = new ComponentDepot(ComponentTypeIndices); } public Entity CreateEntity() @@ -37,22 +37,29 @@ ComponentDepot.Set(entity.ID, component); } - /* public Template CreateTemplate() { return TemplateStorage.Create(); } - public void Set(Template template, in TComponent component) where TComponent : unmanaged + public void Set(in Template template, in TComponent component) where TComponent : unmanaged { + TemplateStorage.SetComponent(template.ID, ComponentTypeIndices.GetIndex()); TemplateComponentDepot.Set(template.ID, component); } - */ - public Entity Instantiate(Template template) + // TODO: TEST ME!!! + public Entity Instantiate(in Template template) { var entity = EntityStorage.Create(); + foreach (var componentTypeIndex in TemplateStorage.ComponentTypeIndices(template.ID)) + { + EntityStorage.SetComponent(entity.ID, componentTypeIndex); + FilterStorage.Check(entity.ID, componentTypeIndex); + ComponentDepot.Set(entity.ID, componentTypeIndex, TemplateComponentDepot.UntypedGet(template.ID, componentTypeIndex)); + } + return entity; } -- 2.25.1 From 80615901958e54627813a776e0b6e522b4941d90 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 6 Dec 2022 01:59:22 -0800 Subject: [PATCH 04/14] initial snapshot system --- src/ComponentDepot.cs | 23 +++++++++++ src/ComponentStorage.cs | 46 +++------------------ src/EntityComponentReader.cs | 5 --- src/EntityStorage.cs | 18 ++++++++ src/IndexableSet.cs | 27 ------------ src/RelationStorage.cs | 58 -------------------------- src/Snapshot.cs | 66 ++++++++++++++++++++++++++++++ src/State/ComponentDepotState.cs | 11 ----- src/State/ComponentStorageState.cs | 27 ------------ src/State/EntityStorageState.cs | 13 ------ src/State/IndexableSetState.cs | 16 -------- src/State/RelationDepotState.cs | 10 ----- src/State/RelationStorageState.cs | 27 ------------ src/State/WorldState.cs | 16 -------- src/System.cs | 60 +++------------------------ src/TypeIndices.cs | 2 + src/World.cs | 39 +++++++++++++++--- 17 files changed, 153 insertions(+), 311 deletions(-) create mode 100644 src/Snapshot.cs delete mode 100644 src/State/ComponentDepotState.cs delete mode 100644 src/State/ComponentStorageState.cs delete mode 100644 src/State/EntityStorageState.cs delete mode 100644 src/State/IndexableSetState.cs delete mode 100644 src/State/RelationDepotState.cs delete mode 100644 src/State/RelationStorageState.cs delete mode 100644 src/State/WorldState.cs diff --git a/src/ComponentDepot.cs b/src/ComponentDepot.cs index 94487ec..7338fcf 100644 --- a/src/ComponentDepot.cs +++ b/src/ComponentDepot.cs @@ -87,5 +87,28 @@ namespace MoonTools.ECS { Lookup().Remove(entityID); } + + public void Clear() + { + for (var i = 0; i < ComponentTypeIndices.Count; i += 1) + { + if (storages[i] != null) + { + storages[i].Clear(); + } + } + } + + // used to fill snapshot depot with correct storages + public void FillMissingStorages(ComponentDepot other) + { + for (var i = 0; i < ComponentTypeIndices.Count; i += 1) + { + if (storages[i] == null && other.storages[i] != null) + { + storages[i] = other.storages[i].CreateStorage(); + } + } + } } } diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs index 6714b90..bd3fd66 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -8,12 +8,12 @@ namespace MoonTools.ECS { internal abstract void Set(int entityID, object component); public abstract bool Remove(int entityID); - public abstract ComponentStorageState CreateState(); - public abstract void Save(ComponentStorageState state); - public abstract void Load(ComponentStorageState state); + public abstract void Clear(); // used for debugging and template instantiation internal abstract object UntypedGet(int entityID); + // used to create correctly typed storage on snapshot + public abstract ComponentStorage CreateStorage(); } internal class ComponentStorage : ComponentStorage where TComponent : unmanaged @@ -101,7 +101,7 @@ namespace MoonTools.ECS return false; } - public void Clear() + public override void Clear() { nextID = 0; entityIDToStorageIndex.Clear(); @@ -123,43 +123,9 @@ namespace MoonTools.ECS return new Entity(entityIDs[0]); } - public override ComponentStorageState CreateState() + public override ComponentStorage CreateStorage() { - return ComponentStorageState.Create(nextID); - } - - public override void Save(ComponentStorageState state) - { - ReadOnlySpan entityIDBytes = MemoryMarshal.Cast(new ReadOnlySpan(entityIDs, 0, nextID)); - - if (entityIDBytes.Length > state.EntityIDs.Length) - { - Array.Resize(ref state.EntityIDs, entityIDBytes.Length); - } - entityIDBytes.CopyTo(state.EntityIDs); - - ReadOnlySpan componentBytes = MemoryMarshal.Cast(AllComponents()); - if (componentBytes.Length > state.Components.Length) - { - Array.Resize(ref state.Components, componentBytes.Length); - } - componentBytes.CopyTo(state.Components); - - state.Count = nextID; - } - - public override void Load(ComponentStorageState state) - { - state.EntityIDs.CopyTo(MemoryMarshal.Cast(entityIDs)); - state.Components.CopyTo(MemoryMarshal.Cast(components)); - - entityIDToStorageIndex.Clear(); - for (var i = 0; i < state.Count; i += 1) - { - entityIDToStorageIndex[entityIDs[i]] = i; - } - - nextID = state.Count; + return new ComponentStorage(); } } } diff --git a/src/EntityComponentReader.cs b/src/EntityComponentReader.cs index 91fa1ac..1678317 100644 --- a/src/EntityComponentReader.cs +++ b/src/EntityComponentReader.cs @@ -52,11 +52,6 @@ namespace MoonTools.ECS return ComponentDepot.GetSingletonEntity(); } - protected bool Exists(in Entity entity) - { - return EntityStorage.Exists(entity); - } - protected IEnumerable<(Entity, Entity, TRelationKind)> Relations() where TRelationKind : unmanaged { return RelationDepot.Relations(); diff --git a/src/EntityStorage.cs b/src/EntityStorage.cs index 44fd0a9..e803243 100644 --- a/src/EntityStorage.cs +++ b/src/EntityStorage.cs @@ -7,11 +7,14 @@ namespace MoonTools.ECS private int nextID = 0; // FIXME: why is this duplicated? private readonly Stack availableIDs = new Stack(); + // FIXME: this is only needed in debug mode private readonly HashSet availableIDHash = new HashSet(); private Dictionary> EntityToComponentTypeIndices = new Dictionary>(); private Dictionary> EntityToRelationTypeIndices = new Dictionary>(); + public int Count => nextID - availableIDs.Count; + public Entity Create() { var entity = new Entity(NextID()); @@ -70,6 +73,21 @@ namespace MoonTools.ECS return EntityToRelationTypeIndices[entityID]; } + public void Clear() + { + nextID = 0; + foreach (var componentSet in EntityToComponentTypeIndices.Values) + { + componentSet.Clear(); + } + foreach (var relationSet in EntityToRelationTypeIndices.Values) + { + relationSet.Clear(); + } + availableIDs.Clear(); + availableIDHash.Clear(); + } + private int NextID() { if (availableIDs.Count > 0) diff --git a/src/IndexableSet.cs b/src/IndexableSet.cs index bacb429..35ca411 100644 --- a/src/IndexableSet.cs +++ b/src/IndexableSet.cs @@ -84,32 +84,5 @@ namespace MoonTools.ECS { Count = 0; } - - public void Save(IndexableSetState state) - { - ReadOnlySpan arrayBytes = MemoryMarshal.Cast(array); - - if (arrayBytes.Length > state.Array.Length) - { - Array.Resize(ref state.Array, arrayBytes.Length); - } - - arrayBytes.CopyTo(state.Array); - - state.Count = Count; - } - - public void Load(IndexableSetState state) - { - state.Array.CopyTo(MemoryMarshal.Cast(array)); - - indices.Clear(); - for (var i = 0; i < state.Count; i += 1) - { - indices[array[i]] = i; - } - - Count = state.Count; - } } } diff --git a/src/RelationStorage.cs b/src/RelationStorage.cs index 05110eb..52907e3 100644 --- a/src/RelationStorage.cs +++ b/src/RelationStorage.cs @@ -6,9 +6,6 @@ namespace MoonTools.ECS { internal abstract class RelationStorage { - public abstract RelationStorageState CreateState(); - public abstract void Save(RelationStorageState state); - public abstract void Load(RelationStorageState state); public abstract void UnrelateAll(int entityID); } @@ -228,60 +225,5 @@ namespace MoonTools.ECS hashSet.Clear(); listPool.Push(hashSet); } - - public override RelationStorageState CreateState() - { - return RelationStorageState.Create(count); - } - - public override void Save(RelationStorageState state) - { - ReadOnlySpan relationBytes = MemoryMarshal.Cast(relations); - - if (relationBytes.Length > state.Relations.Length) - { - Array.Resize(ref state.Relations, relationBytes.Length); - } - relationBytes.CopyTo(state.Relations); - - ReadOnlySpan relationDataBytes = MemoryMarshal.Cast(relationDatas); - - if (relationDataBytes.Length > state.RelationDatas.Length) - { - Array.Resize(ref state.RelationDatas, relationDataBytes.Length); - } - relationDataBytes.CopyTo(state.RelationDatas); - - state.Count = count; - } - - public override void Load(RelationStorageState state) - { - state.Relations.CopyTo(MemoryMarshal.Cast(relations)); - state.RelationDatas.CopyTo(MemoryMarshal.Cast(relationDatas)); - - indices.Clear(); - outRelations.Clear(); - inRelations.Clear(); - for (var i = 0; i < state.Count; i += 1) - { - var relation = relations[i]; - indices[relation] = i; - - if (!outRelations.ContainsKey(relation.A.ID)) - { - outRelations[relation.A.ID] = AcquireHashSetFromPool(); - } - outRelations[relation.A.ID].Add(relation.B.ID); - - if (!inRelations.ContainsKey(relation.B.ID)) - { - inRelations[relation.B.ID] = AcquireHashSetFromPool(); - } - inRelations[relation.B.ID].Add(relation.A.ID); - } - - count = state.Count; - } } } diff --git a/src/Snapshot.cs b/src/Snapshot.cs new file mode 100644 index 0000000..d7f06aa --- /dev/null +++ b/src/Snapshot.cs @@ -0,0 +1,66 @@ +namespace MoonTools.ECS +{ + public class Snapshot + { + private World World; + private Filter? Filter; + + private EntityStorage SnapshotEntityStorage; + private ComponentDepot SnapshotComponentDepot; + + internal Snapshot(World world) + { + World = world; + SnapshotEntityStorage = new EntityStorage(); + SnapshotComponentDepot = new ComponentDepot(World.ComponentTypeIndices); + } + + public void Take(Filter filter) + { + Clear(); + Filter = filter; + SnapshotComponentDepot.FillMissingStorages(World.ComponentDepot); + + foreach (var worldEntity in filter.Entities) + { + var snapshotEntity = SnapshotEntityStorage.Create(); + foreach (var componentTypeIndex in World.EntityStorage.ComponentTypeIndices(worldEntity.ID)) + { + SnapshotEntityStorage.SetComponent(snapshotEntity.ID, componentTypeIndex); + SnapshotComponentDepot.Set(snapshotEntity.ID, componentTypeIndex, World.ComponentDepot.UntypedGet(worldEntity.ID, componentTypeIndex)); + } + } + } + + public void Restore() + { + if (Filter == null) + { + return; + } + + foreach (var entity in Filter.Entities) + { + World.Destroy(entity); + } + + for (var i = 0; i < SnapshotEntityStorage.Count; i += 1) + { + var entity = World.CreateEntity(); + + foreach (var componentTypeIndex in SnapshotEntityStorage.ComponentTypeIndices(i)) + { + World.EntityStorage.SetComponent(entity.ID, componentTypeIndex); + World.FilterStorage.Check(entity.ID, componentTypeIndex); + World.ComponentDepot.Set(entity.ID, componentTypeIndex, SnapshotComponentDepot.UntypedGet(i, componentTypeIndex)); + } + } + } + + private void Clear() + { + SnapshotEntityStorage.Clear(); + SnapshotComponentDepot.Clear(); + } + } +} diff --git a/src/State/ComponentDepotState.cs b/src/State/ComponentDepotState.cs deleted file mode 100644 index 2370dc6..0000000 --- a/src/State/ComponentDepotState.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MoonTools.ECS -{ - internal class ComponentDepotState - { - public Dictionary StorageStates = new Dictionary(); - public Dictionary> FilterStates = new Dictionary>(); - } -} diff --git a/src/State/ComponentStorageState.cs b/src/State/ComponentStorageState.cs deleted file mode 100644 index ad4872d..0000000 --- a/src/State/ComponentStorageState.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; - -namespace MoonTools.ECS -{ - internal class ComponentStorageState - { - public int Count; - public byte[] EntityIDs; - public byte[] Components; - - public unsafe static ComponentStorageState Create(int count) where TComponent : unmanaged - { - return new ComponentStorageState( - count, - count * sizeof(int), - count * sizeof(TComponent) - ); - } - - private ComponentStorageState(int count, int entityIDSize, int componentSize) - { - Count = count; - EntityIDs = new byte[entityIDSize]; - Components = new byte[componentSize]; - } - } -} diff --git a/src/State/EntityStorageState.cs b/src/State/EntityStorageState.cs deleted file mode 100644 index aebfec3..0000000 --- a/src/State/EntityStorageState.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; - -namespace MoonTools.ECS -{ - internal class EntityStorageState - { - public int NextID; - public List availableIDs = new List(); - - public Dictionary> EntityToComponentTypeIndices = new Dictionary>(); - public Dictionary> EntityToRelationTypeIndices = new Dictionary>(); - } -} diff --git a/src/State/IndexableSetState.cs b/src/State/IndexableSetState.cs deleted file mode 100644 index 4367252..0000000 --- a/src/State/IndexableSetState.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Collections.Generic; - -namespace MoonTools.ECS -{ - internal class IndexableSetState where T : unmanaged - { - public int Count; - public byte[] Array; - - public unsafe IndexableSetState(int count) - { - Count = count; - Array = new byte[sizeof(T) * count]; - } - } -} diff --git a/src/State/RelationDepotState.cs b/src/State/RelationDepotState.cs deleted file mode 100644 index 58e1315..0000000 --- a/src/State/RelationDepotState.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MoonTools.ECS -{ - internal class RelationDepotState - { - public Dictionary StorageStates = new Dictionary(); - } -} diff --git a/src/State/RelationStorageState.cs b/src/State/RelationStorageState.cs deleted file mode 100644 index d210a5d..0000000 --- a/src/State/RelationStorageState.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; - -namespace MoonTools.ECS -{ - internal class RelationStorageState - { - public int Count; - public byte[] Relations; - public byte[] RelationDatas; - - public unsafe static RelationStorageState Create(int count) where TRelation : unmanaged - { - return new RelationStorageState( - count, - count * sizeof(Relation), - count * sizeof(TRelation) - ); - } - - private RelationStorageState(int count, int relationSize, int relationDataSize) - { - Count = count; - Relations = new byte[relationSize]; - RelationDatas = new byte[relationDataSize]; - } - } -} diff --git a/src/State/WorldState.cs b/src/State/WorldState.cs deleted file mode 100644 index d422745..0000000 --- a/src/State/WorldState.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace MoonTools.ECS -{ - public class WorldState - { - internal readonly ComponentDepotState ComponentDepotState; - internal readonly EntityStorageState EntityStorageState; - internal readonly RelationDepotState RelationDepotState; - - public WorldState() - { - ComponentDepotState = new ComponentDepotState(); - EntityStorageState = new EntityStorageState(); - RelationDepotState = new RelationDepotState(); - } - } -} diff --git a/src/System.cs b/src/System.cs index 888a9bd..7f8bdee 100644 --- a/src/System.cs +++ b/src/System.cs @@ -11,27 +11,9 @@ namespace MoonTools.ECS public abstract void Update(TimeSpan delta); - protected Entity CreateEntity() - { - return EntityStorage.Create(); - } + protected Entity CreateEntity() => World.CreateEntity(); - protected void Set(in Entity entity, in TComponent component) where TComponent : unmanaged - { -#if DEBUG - // check for use after destroy - if (!Exists(entity)) - { - throw new ArgumentException("This entity is not valid!"); - } -#endif - if (EntityStorage.SetComponent(entity.ID, ComponentTypeIndices.GetIndex())) - { - FilterStorage.Check(entity.ID); - } - - ComponentDepot.Set(entity.ID, component); - } + protected void Set(in Entity entity, in TComponent component) where TComponent : unmanaged => World.Set(entity, component); protected void Remove(in Entity entity) where TComponent : unmanaged { @@ -42,27 +24,9 @@ namespace MoonTools.ECS } } - protected void Set(in Template template, in TComponent component) where TComponent : unmanaged - { - var componentTypeIndex = ComponentTypeIndices.GetIndex(); - TemplateStorage.SetComponent(template.ID, componentTypeIndex); - TemplateComponentDepot.Set(template.ID, component); - ComponentDepot.Register(componentTypeIndex); - } + protected void Set(in Template template, in TComponent component) where TComponent : unmanaged => World.Set(template, component); - protected Entity Instantiate(in Template template) - { - var entity = EntityStorage.Create(); - - foreach (var componentTypeIndex in TemplateStorage.ComponentTypeIndices(template.ID)) - { - EntityStorage.SetComponent(entity.ID, componentTypeIndex); - FilterStorage.Check(entity.ID, componentTypeIndex); - ComponentDepot.Set(entity.ID, componentTypeIndex, TemplateComponentDepot.UntypedGet(template.ID, componentTypeIndex)); - } - - return entity; - } + protected Entity Instantiate(in Template template) => World.Instantiate(template); protected ReadOnlySpan ReadMessages() where TMessage : unmanaged { @@ -128,20 +92,6 @@ namespace MoonTools.ECS EntityStorage.RemoveRelation(entity.ID, RelationTypeIndices.GetIndex()); } - protected void Destroy(in Entity entity) - { - foreach (var componentTypeIndex in EntityStorage.ComponentTypeIndices(entity.ID)) - { - ComponentDepot.Remove(entity.ID, componentTypeIndex); - FilterStorage.RemoveEntity(entity.ID, componentTypeIndex); - } - - foreach (var relationTypeIndex in EntityStorage.RelationTypeIndices(entity.ID)) - { - RelationDepot.UnrelateAll(entity.ID, relationTypeIndex); - } - - EntityStorage.Destroy(entity); - } + protected void Destroy(in Entity entity) => World.Destroy(entity); } } diff --git a/src/TypeIndices.cs b/src/TypeIndices.cs index 60c1adb..53bf700 100644 --- a/src/TypeIndices.cs +++ b/src/TypeIndices.cs @@ -7,6 +7,7 @@ namespace MoonTools.ECS { Dictionary TypeToIndex = new Dictionary(); int nextID = 0; + public int Count => TypeToIndex.Count; public int GetIndex() where T : unmanaged { @@ -24,6 +25,7 @@ namespace MoonTools.ECS return TypeToIndex[type]; } + #if DEBUG public IEnumerable Types => TypeToIndex.Keys; #endif diff --git a/src/World.cs b/src/World.cs index d0b4834..85873c0 100644 --- a/src/World.cs +++ b/src/World.cs @@ -1,4 +1,6 @@ -namespace MoonTools.ECS +using System; + +namespace MoonTools.ECS { public class World { @@ -9,11 +11,11 @@ internal readonly MessageDepot MessageDepot = new MessageDepot(); internal readonly RelationDepot RelationDepot; internal readonly FilterStorage FilterStorage; + public FilterBuilder FilterBuilder => new FilterBuilder(FilterStorage, ComponentTypeIndices); internal readonly TemplateStorage TemplateStorage = new TemplateStorage(); internal readonly ComponentDepot TemplateComponentDepot; - public World() { ComponentDepot = new ComponentDepot(ComponentTypeIndices); @@ -29,6 +31,13 @@ public void Set(Entity entity, in TComponent component) where TComponent : unmanaged { +#if DEBUG + // check for use after destroy + if (!EntityStorage.Exists(entity)) + { + throw new InvalidOperationException("This entity is not valid!"); + } +#endif if (EntityStorage.SetComponent(entity.ID, ComponentTypeIndices.GetIndex())) { FilterStorage.Check(entity.ID); @@ -44,11 +53,12 @@ public void Set(in Template template, in TComponent component) where TComponent : unmanaged { - TemplateStorage.SetComponent(template.ID, ComponentTypeIndices.GetIndex()); + var componentTypeIndex = ComponentTypeIndices.GetIndex(); + TemplateStorage.SetComponent(template.ID, componentTypeIndex); TemplateComponentDepot.Set(template.ID, component); + ComponentDepot.Register(componentTypeIndex); } - // TODO: TEST ME!!! public Entity Instantiate(in Template template) { var entity = EntityStorage.Create(); @@ -68,14 +78,31 @@ MessageDepot.Add(message); } + public void Destroy(in Entity entity) + { + foreach (var componentTypeIndex in EntityStorage.ComponentTypeIndices(entity.ID)) + { + ComponentDepot.Remove(entity.ID, componentTypeIndex); + FilterStorage.RemoveEntity(entity.ID, componentTypeIndex); + } + + foreach (var relationTypeIndex in EntityStorage.RelationTypeIndices(entity.ID)) + { + RelationDepot.UnrelateAll(entity.ID, relationTypeIndex); + } + + EntityStorage.Destroy(entity); + } + + public void FinishUpdate() { MessageDepot.Clear(); } - public WorldState CreateState() + public Snapshot CreateSnapshot() { - return new WorldState(); + return new Snapshot(this); } } } -- 2.25.1 From f6287350256e34649042893013792d47cec73741 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 6 Dec 2022 22:06:02 -0800 Subject: [PATCH 05/14] snapshot refinements --- src/ComponentDepot.cs | 28 +++++++++------- src/EntityStorage.cs | 2 +- src/FilterSignature.cs | 5 --- src/FilterStorage.cs | 21 ++++++++++++ src/RelationDepot.cs | 49 +++++++++++++++++++++++++--- src/RelationStorage.cs | 69 +++++++++++++++++++++++++++++++++------ src/Snapshot.cs | 73 +++++++++++++++++++++++++++++++++++++++++- src/System.cs | 4 +-- 8 files changed, 216 insertions(+), 35 deletions(-) diff --git a/src/ComponentDepot.cs b/src/ComponentDepot.cs index 7338fcf..04e1d03 100644 --- a/src/ComponentDepot.cs +++ b/src/ComponentDepot.cs @@ -47,12 +47,6 @@ namespace MoonTools.ECS return ref Lookup().Get(entityID); } - // used for debugging and template instantiation - internal object UntypedGet(int entityID, int componentTypeIndex) - { - return storages[componentTypeIndex].UntypedGet(entityID); - } - public ref readonly TComponent GetFirst() where TComponent : unmanaged { return ref Lookup().GetFirst(); @@ -63,10 +57,7 @@ namespace MoonTools.ECS Lookup().Set(entityID, component); } - internal void Set(int entityID, int componentTypeIndex, object component) - { - storages[componentTypeIndex].Set(entityID, component); - } + public Entity GetSingletonEntity() where TComponent : unmanaged { @@ -99,8 +90,21 @@ namespace MoonTools.ECS } } - // used to fill snapshot depot with correct storages - public void FillMissingStorages(ComponentDepot other) + // these methods used to implement snapshots, templates, and debugging + + // FIXME: use unsafe pointers instead of object + internal object UntypedGet(int entityID, int componentTypeIndex) + { + return storages[componentTypeIndex].UntypedGet(entityID); + } + + // FIXME: use unsafe pointers instead of object + internal void Set(int entityID, int componentTypeIndex, object component) + { + storages[componentTypeIndex].Set(entityID, component); + } + + public void CreateMissingStorages(ComponentDepot other) { for (var i = 0; i < ComponentTypeIndices.Count; i += 1) { diff --git a/src/EntityStorage.cs b/src/EntityStorage.cs index e803243..99d8b6c 100644 --- a/src/EntityStorage.cs +++ b/src/EntityStorage.cs @@ -52,7 +52,7 @@ namespace MoonTools.ECS return EntityToComponentTypeIndices[entityID].Remove(componentTypeIndex); } - public void AddRelation(int entityID, int relationIndex) + public void AddRelationKind(int entityID, int relationIndex) { EntityToRelationTypeIndices[entityID].Add(relationIndex); } diff --git a/src/FilterSignature.cs b/src/FilterSignature.cs index 66f7fc3..4965cd1 100644 --- a/src/FilterSignature.cs +++ b/src/FilterSignature.cs @@ -24,11 +24,6 @@ namespace MoonTools.ECS return Included.SetEquals(other.Included) && Excluded.SetEquals(other.Excluded); } - private int GuidToInt(Guid guid) - { - return BitConverter.ToInt32(guid.ToByteArray()); - } - public override int GetHashCode() { var hashcode = 1; diff --git a/src/FilterStorage.cs b/src/FilterStorage.cs index 1b5af12..e380234 100644 --- a/src/FilterStorage.cs +++ b/src/FilterStorage.cs @@ -94,6 +94,27 @@ namespace MoonTools.ECS Check(entityID, ComponentTypeIndices.GetIndex()); } + public bool CheckSatisfied(int entityID, FilterSignature filterSignature) + { + foreach (var type in filterSignature.Included) + { + if (!EntityStorage.HasComponent(entityID, type)) + { + return false; + } + } + + foreach (var type in filterSignature.Excluded) + { + if (EntityStorage.HasComponent(entityID, type)) + { + return false; + } + } + + return true; + } + private void CheckFilter(int entityID, FilterSignature filterSignature) { foreach (var type in filterSignature.Included) diff --git a/src/RelationDepot.cs b/src/RelationDepot.cs index 8c150ea..e3b9225 100644 --- a/src/RelationDepot.cs +++ b/src/RelationDepot.cs @@ -51,11 +51,6 @@ namespace MoonTools.ECS Lookup().UnrelateAll(entityID); } - public void UnrelateAll(int entityID, int relationStorageIndex) - { - storages[relationStorageIndex].UnrelateAll(entityID); - } - public IEnumerable<(Entity, Entity, TRelationKind)> Relations() where TRelationKind : unmanaged { return Lookup().All(); @@ -105,5 +100,49 @@ namespace MoonTools.ECS { return Lookup().InRelationCount(entityID); } + + // untyped methods used for destroying and snapshots + + public void Set(int entityA, int entityB, int relationTypeIndex, object relationData) + { + storages[relationTypeIndex].Set(entityA, entityB, relationData); + } + + public void UnrelateAll(int entityID, int relationTypeIndex) + { + storages[relationTypeIndex].UnrelateAll(entityID); + } + + public IEnumerable<(int, object)> InRelations(int entityID, int relationTypeIndex) + { + return storages[relationTypeIndex].UntypedInRelations(entityID); + } + + public IEnumerable<(int, object)> OutRelations(int entityID, int relationTypeIndex) + { + return storages[relationTypeIndex].UntypedOutRelations(entityID); + } + + public void Clear() + { + for (var i = 0; i < RelationTypeIndices.Count; i += 1) + { + if (storages[i] != null) + { + storages[i].Clear(); + } + } + } + + public void CreateMissingStorages(RelationDepot other) + { + for (var i = 0; i < RelationTypeIndices.Count; i += 1) + { + if (storages[i] == null && other.storages[i] != null) + { + storages[i] = other.storages[i].CreateStorage(); + } + } + } } } diff --git a/src/RelationStorage.cs b/src/RelationStorage.cs index 52907e3..9a2a887 100644 --- a/src/RelationStorage.cs +++ b/src/RelationStorage.cs @@ -1,12 +1,17 @@ using System; using System.Collections.Generic; -using System.Runtime.InteropServices; namespace MoonTools.ECS { internal abstract class RelationStorage { + public abstract void Set(int entityA, int entityB, object relationData); public abstract void UnrelateAll(int entityID); + public abstract IEnumerable<(int, object)> UntypedInRelations(int entityID); + public abstract IEnumerable<(int, object)> UntypedOutRelations(int entityID); + // used to create correctly typed storage on snapshot + public abstract RelationStorage CreateStorage(); + public abstract void Clear(); } // Relation is the two entities, A related to B. @@ -185,6 +190,29 @@ namespace MoonTools.ECS return (aEmpty, bEmpty); } + private IndexableSet AcquireHashSetFromPool() + { + if (listPool.Count == 0) + { + listPool.Push(new IndexableSet()); + } + + return listPool.Pop(); + } + + private void ReturnHashSetToPool(IndexableSet hashSet) + { + hashSet.Clear(); + listPool.Push(hashSet); + } + + // untyped methods used for internal implementation + + public override void Set(int entityA, int entityB, object relationData) + { + Set(new Relation(entityA, entityB), (TRelation) relationData); + } + public override void UnrelateAll(int entityID) { if (outRelations.ContainsKey(entityID)) @@ -210,20 +238,43 @@ namespace MoonTools.ECS } } - private IndexableSet AcquireHashSetFromPool() + public override IEnumerable<(int, object)> UntypedInRelations(int entityID) { - if (listPool.Count == 0) + foreach (var (entity, relationData) in InRelations(entityID)) { - listPool.Push(new IndexableSet()); + yield return (entity.ID, relationData); } - - return listPool.Pop(); } - private void ReturnHashSetToPool(IndexableSet hashSet) + public override IEnumerable<(int, object)> UntypedOutRelations(int entityID) { - hashSet.Clear(); - listPool.Push(hashSet); + foreach (var (entity, relationData) in OutRelations(entityID)) + { + yield return (entity.ID, relationData); + } + } + + public override RelationStorage CreateStorage() + { + return new RelationStorage(); + } + + public override void Clear() + { + count = 0; + indices.Clear(); + + foreach (var set in inRelations.Values) + { + ReturnHashSetToPool(set); + } + inRelations.Clear(); + + foreach (var set in outRelations.Values) + { + ReturnHashSetToPool(set); + } + outRelations.Clear(); } } } diff --git a/src/Snapshot.cs b/src/Snapshot.cs index d7f06aa..992f121 100644 --- a/src/Snapshot.cs +++ b/src/Snapshot.cs @@ -1,3 +1,6 @@ +using System; +using System.Collections.Generic; + namespace MoonTools.ECS { public class Snapshot @@ -7,29 +10,71 @@ namespace MoonTools.ECS private EntityStorage SnapshotEntityStorage; private ComponentDepot SnapshotComponentDepot; + private RelationDepot SnapshotRelationDepot; + + private List SnapshotToWorldID = new List(); + private Dictionary WorldToSnapshotID = new Dictionary(); internal Snapshot(World world) { World = world; SnapshotEntityStorage = new EntityStorage(); SnapshotComponentDepot = new ComponentDepot(World.ComponentTypeIndices); + SnapshotRelationDepot = new RelationDepot(World.RelationTypeIndices); } public void Take(Filter filter) { Clear(); Filter = filter; - SnapshotComponentDepot.FillMissingStorages(World.ComponentDepot); + SnapshotComponentDepot.CreateMissingStorages(World.ComponentDepot); + SnapshotRelationDepot.CreateMissingStorages(World.RelationDepot); foreach (var worldEntity in filter.Entities) { var snapshotEntity = SnapshotEntityStorage.Create(); + WorldToSnapshotID.Add(worldEntity.ID, snapshotEntity.ID); + foreach (var componentTypeIndex in World.EntityStorage.ComponentTypeIndices(worldEntity.ID)) { SnapshotEntityStorage.SetComponent(snapshotEntity.ID, componentTypeIndex); SnapshotComponentDepot.Set(snapshotEntity.ID, componentTypeIndex, World.ComponentDepot.UntypedGet(worldEntity.ID, componentTypeIndex)); } } + + foreach (var worldEntity in filter.Entities) + { + var snapshotEntityID = WorldToSnapshotID[worldEntity.ID]; + + foreach (var relationTypeIndex in World.EntityStorage.RelationTypeIndices(worldEntity.ID)) + { + SnapshotEntityStorage.AddRelationKind(snapshotEntityID, relationTypeIndex); + + foreach (var (otherEntityID, relationData) in World.RelationDepot.InRelations(worldEntity.ID, relationTypeIndex)) + { +#if DEBUG + if (!World.FilterStorage.CheckSatisfied(otherEntityID, Filter.Signature)) + { + throw new InvalidOperationException($"Snapshot entity {worldEntity.ID} is related to non-snapshot entity {otherEntityID}!"); + } +#endif + var otherSnapshotID = WorldToSnapshotID[otherEntityID]; + SnapshotRelationDepot.Set(otherSnapshotID, snapshotEntityID, relationTypeIndex, relationData); + } + + foreach (var (otherEntityID, relationData) in World.RelationDepot.OutRelations(worldEntity.ID, relationTypeIndex)) + { +#if DEBUG + if (!World.FilterStorage.CheckSatisfied(otherEntityID, Filter.Signature)) + { + throw new InvalidOperationException($"Snapshot entity {worldEntity.ID} is related to non-snapshot entity {otherEntityID}!"); + } +#endif + var otherSnapshotID = WorldToSnapshotID[otherEntityID]; + SnapshotRelationDepot.Set(snapshotEntityID, otherSnapshotID, relationTypeIndex, relationData); + } + } + } } public void Restore() @@ -47,6 +92,7 @@ namespace MoonTools.ECS for (var i = 0; i < SnapshotEntityStorage.Count; i += 1) { var entity = World.CreateEntity(); + SnapshotToWorldID.Add(entity.ID); foreach (var componentTypeIndex in SnapshotEntityStorage.ComponentTypeIndices(i)) { @@ -55,12 +101,37 @@ namespace MoonTools.ECS World.ComponentDepot.Set(entity.ID, componentTypeIndex, SnapshotComponentDepot.UntypedGet(i, componentTypeIndex)); } } + + for (var i = 0; i < SnapshotEntityStorage.Count; i += 1) + { + var worldID = SnapshotToWorldID[i]; + + foreach (var relationTypeIndex in SnapshotEntityStorage.RelationTypeIndices(i)) + { + World.EntityStorage.AddRelationKind(worldID, relationTypeIndex); + + foreach (var (otherEntityID, relationData) in SnapshotRelationDepot.InRelations(i, relationTypeIndex)) + { + var otherEntityWorldID = SnapshotToWorldID[otherEntityID]; + World.RelationDepot.Set(otherEntityWorldID, worldID, relationTypeIndex, relationData); + } + + foreach (var (otherEntityID, relationData) in SnapshotRelationDepot.OutRelations(i, relationTypeIndex)) + { + var otherEntityWorldID = SnapshotToWorldID[otherEntityID]; + World.RelationDepot.Set(worldID, otherEntityWorldID, relationTypeIndex, relationData); + } + } + } } private void Clear() { SnapshotEntityStorage.Clear(); SnapshotComponentDepot.Clear(); + SnapshotRelationDepot.Clear(); + SnapshotToWorldID.Clear(); + WorldToSnapshotID.Clear(); } } } diff --git a/src/System.cs b/src/System.cs index 7f8bdee..f903de7 100644 --- a/src/System.cs +++ b/src/System.cs @@ -67,8 +67,8 @@ namespace MoonTools.ECS { RelationDepot.Set(new Relation(entityA, entityB), relationData); var relationTypeIndex = RelationTypeIndices.GetIndex(); - EntityStorage.AddRelation(entityA.ID, relationTypeIndex); - EntityStorage.AddRelation(entityB.ID, relationTypeIndex); + EntityStorage.AddRelationKind(entityA.ID, relationTypeIndex); + EntityStorage.AddRelationKind(entityB.ID, relationTypeIndex); } protected void Unrelate(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged -- 2.25.1 From 9bc6822f385f9f43c2df1ebc0b5e64ac47da8071 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Wed, 7 Dec 2022 10:17:07 -0800 Subject: [PATCH 06/14] optimize snapshot --- src/Snapshot.cs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/Snapshot.cs b/src/Snapshot.cs index 992f121..52ec53f 100644 --- a/src/Snapshot.cs +++ b/src/Snapshot.cs @@ -50,18 +50,6 @@ namespace MoonTools.ECS { SnapshotEntityStorage.AddRelationKind(snapshotEntityID, relationTypeIndex); - foreach (var (otherEntityID, relationData) in World.RelationDepot.InRelations(worldEntity.ID, relationTypeIndex)) - { -#if DEBUG - if (!World.FilterStorage.CheckSatisfied(otherEntityID, Filter.Signature)) - { - throw new InvalidOperationException($"Snapshot entity {worldEntity.ID} is related to non-snapshot entity {otherEntityID}!"); - } -#endif - var otherSnapshotID = WorldToSnapshotID[otherEntityID]; - SnapshotRelationDepot.Set(otherSnapshotID, snapshotEntityID, relationTypeIndex, relationData); - } - foreach (var (otherEntityID, relationData) in World.RelationDepot.OutRelations(worldEntity.ID, relationTypeIndex)) { #if DEBUG @@ -71,6 +59,7 @@ namespace MoonTools.ECS } #endif var otherSnapshotID = WorldToSnapshotID[otherEntityID]; + SnapshotEntityStorage.AddRelationKind(otherSnapshotID, relationTypeIndex); SnapshotRelationDepot.Set(snapshotEntityID, otherSnapshotID, relationTypeIndex, relationData); } } @@ -110,12 +99,6 @@ namespace MoonTools.ECS { World.EntityStorage.AddRelationKind(worldID, relationTypeIndex); - foreach (var (otherEntityID, relationData) in SnapshotRelationDepot.InRelations(i, relationTypeIndex)) - { - var otherEntityWorldID = SnapshotToWorldID[otherEntityID]; - World.RelationDepot.Set(otherEntityWorldID, worldID, relationTypeIndex, relationData); - } - foreach (var (otherEntityID, relationData) in SnapshotRelationDepot.OutRelations(i, relationTypeIndex)) { var otherEntityWorldID = SnapshotToWorldID[otherEntityID]; -- 2.25.1 From 8d1274cba0a50c412407bc13f2fb940c59d537c1 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Wed, 7 Dec 2022 13:53:23 -0800 Subject: [PATCH 07/14] more untyped storage granularity --- src/ComponentDepot.cs | 23 +++++++++++++++++------ src/ComponentStorage.cs | 32 +++++++++++++++++++++++++------- src/DebugSystem.cs | 11 ++++------- src/RelationDepot.cs | 16 ++++++++-------- src/RelationStorage.cs | 37 ++++++++++++++++++++----------------- src/Snapshot.cs | 12 ++++++------ src/World.cs | 2 +- 7 files changed, 81 insertions(+), 52 deletions(-) diff --git a/src/ComponentDepot.cs b/src/ComponentDepot.cs index 04e1d03..6f746fa 100644 --- a/src/ComponentDepot.cs +++ b/src/ComponentDepot.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Runtime.CompilerServices; namespace MoonTools.ECS @@ -57,8 +58,6 @@ namespace MoonTools.ECS Lookup().Set(entityID, component); } - - public Entity GetSingletonEntity() where TComponent : unmanaged { return Lookup().FirstEntity(); @@ -92,14 +91,12 @@ namespace MoonTools.ECS // these methods used to implement snapshots, templates, and debugging - // FIXME: use unsafe pointers instead of object - internal object UntypedGet(int entityID, int componentTypeIndex) + internal unsafe void* UntypedGet(int entityID, int componentTypeIndex) { return storages[componentTypeIndex].UntypedGet(entityID); } - // FIXME: use unsafe pointers instead of object - internal void Set(int entityID, int componentTypeIndex, object component) + internal unsafe void Set(int entityID, int componentTypeIndex, void* component) { storages[componentTypeIndex].Set(entityID, component); } @@ -114,5 +111,19 @@ namespace MoonTools.ECS } } } + + // this method is used to iterate components of an entity, only for use with a debug inspector + +#if DEBUG + public object Debug_Get(int entityID, int componentTypeIndex) + { + return storages[componentTypeIndex].Debug_Get(entityID); + } + + public IEnumerable Debug_GetEntityIDs(int componentTypeIndex) + { + return storages[componentTypeIndex].Debug_GetEntityIDs(); + } +#endif } } diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs index bd3fd66..5ea989f 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -1,19 +1,22 @@ using System; using System.Collections.Generic; -using System.Runtime.InteropServices; namespace MoonTools.ECS { internal abstract class ComponentStorage { - internal abstract void Set(int entityID, object component); + internal abstract unsafe void Set(int entityID, void* component); public abstract bool Remove(int entityID); public abstract void Clear(); // used for debugging and template instantiation - internal abstract object UntypedGet(int entityID); + internal abstract unsafe void* UntypedGet(int entityID); // used to create correctly typed storage on snapshot public abstract ComponentStorage CreateStorage(); +#if DEBUG + internal abstract object Debug_Get(int entityID); + internal abstract IEnumerable Debug_GetEntityIDs(); +#endif } internal class ComponentStorage : ComponentStorage where TComponent : unmanaged @@ -33,9 +36,12 @@ namespace MoonTools.ECS return ref components[entityIDToStorageIndex[entityID]]; } - internal override object UntypedGet(int entityID) + internal override unsafe void* UntypedGet(int entityID) { - return components[entityIDToStorageIndex[entityID]]; + fixed (void* p = &components[entityIDToStorageIndex[entityID]]) + { + return p; + } } public ref readonly TComponent GetFirst() @@ -69,9 +75,9 @@ namespace MoonTools.ECS components[entityIDToStorageIndex[entityID]] = component; } - internal override void Set(int entityID, object component) + internal override unsafe void Set(int entityID, void* component) { - Set(entityID, (TComponent) component); + Set(entityID, *((TComponent*) component)); } // Returns true if the entity had this component. @@ -127,5 +133,17 @@ namespace MoonTools.ECS { return new ComponentStorage(); } + +#if DEBUG + internal override object Debug_Get(int entityID) + { + return components[entityIDToStorageIndex[entityID]]; + } + + internal override IEnumerable Debug_GetEntityIDs() + { + return entityIDToStorageIndex.Keys; + } +#endif } } diff --git a/src/DebugSystem.cs b/src/DebugSystem.cs index 36aada6..de9027a 100644 --- a/src/DebugSystem.cs +++ b/src/DebugSystem.cs @@ -9,27 +9,24 @@ namespace MoonTools.ECS { public abstract class DebugSystem : System { - private Dictionary singleComponentFilters = new Dictionary(); - protected DebugSystem(World world) : base(world) { } - protected IEnumerable Debug_GetAllComponents(Entity entity) + protected IEnumerable Debug_GetAllComponents(Entity entity) { foreach (var typeIndex in EntityStorage.ComponentTypeIndices(entity.ID)) { - yield return ComponentDepot.UntypedGet(entity.ID, typeIndex); + yield return ComponentDepot.Debug_Get(entity.ID, typeIndex); } } protected IEnumerable Debug_GetEntities(Type componentType) { - if (!singleComponentFilters.ContainsKey(componentType)) + foreach (var entityID in ComponentDepot.Debug_GetEntityIDs(ComponentTypeIndices.GetIndex(componentType))) { - singleComponentFilters.Add(componentType, new Filter(FilterStorage, new HashSet(ComponentTypeIndices.GetIndex(componentType)), new HashSet())); + yield return new Entity(entityID); } - return singleComponentFilters[componentType].Entities; } protected IEnumerable Debug_SearchComponentType(string typeString) diff --git a/src/RelationDepot.cs b/src/RelationDepot.cs index e3b9225..4baa35c 100644 --- a/src/RelationDepot.cs +++ b/src/RelationDepot.cs @@ -103,24 +103,24 @@ namespace MoonTools.ECS // untyped methods used for destroying and snapshots - public void Set(int entityA, int entityB, int relationTypeIndex, object relationData) + public unsafe void Set(int entityA, int entityB, int relationTypeIndex, void* relationData) { storages[relationTypeIndex].Set(entityA, entityB, relationData); } + public unsafe void* Get(int relationTypeIndex, int relationStorageIndex) + { + return storages[relationTypeIndex].Get(relationStorageIndex); + } + public void UnrelateAll(int entityID, int relationTypeIndex) { storages[relationTypeIndex].UnrelateAll(entityID); } - public IEnumerable<(int, object)> InRelations(int entityID, int relationTypeIndex) + public IEnumerable<(int, int)> OutRelationIndices(int entityID, int relationTypeIndex) { - return storages[relationTypeIndex].UntypedInRelations(entityID); - } - - public IEnumerable<(int, object)> OutRelations(int entityID, int relationTypeIndex) - { - return storages[relationTypeIndex].UntypedOutRelations(entityID); + return storages[relationTypeIndex].OutRelationIndices(entityID); } public void Clear() diff --git a/src/RelationStorage.cs b/src/RelationStorage.cs index 9a2a887..c7ed957 100644 --- a/src/RelationStorage.cs +++ b/src/RelationStorage.cs @@ -5,11 +5,10 @@ namespace MoonTools.ECS { internal abstract class RelationStorage { - public abstract void Set(int entityA, int entityB, object relationData); + public abstract unsafe void Set(int entityA, int entityB, void* relationData); + public abstract unsafe void* Get(int relationStorageIndex); public abstract void UnrelateAll(int entityID); - public abstract IEnumerable<(int, object)> UntypedInRelations(int entityID); - public abstract IEnumerable<(int, object)> UntypedOutRelations(int entityID); - // used to create correctly typed storage on snapshot + public abstract IEnumerable<(int, int)> OutRelationIndices(int entityID); public abstract RelationStorage CreateStorage(); public abstract void Clear(); } @@ -208,9 +207,17 @@ namespace MoonTools.ECS // untyped methods used for internal implementation - public override void Set(int entityA, int entityB, object relationData) + public override unsafe void Set(int entityA, int entityB, void* relationData) { - Set(new Relation(entityA, entityB), (TRelation) relationData); + Set(new Relation(entityA, entityB), *((TRelation*) relationData)); + } + + public override unsafe void* Get(int relationStorageIndex) + { + fixed (void* p = &relations[relationStorageIndex]) + { + return p; + } } public override void UnrelateAll(int entityID) @@ -238,19 +245,15 @@ namespace MoonTools.ECS } } - public override IEnumerable<(int, object)> UntypedInRelations(int entityID) + public override IEnumerable<(int, int)> OutRelationIndices(int entityID) { - foreach (var (entity, relationData) in InRelations(entityID)) + if (outRelations.ContainsKey(entityID)) { - yield return (entity.ID, relationData); - } - } - - public override IEnumerable<(int, object)> UntypedOutRelations(int entityID) - { - foreach (var (entity, relationData) in OutRelations(entityID)) - { - yield return (entity.ID, relationData); + foreach (var id in outRelations[entityID]) + { + var relation = new Relation(entityID, id); + yield return (id, indices[relation]); + } } } diff --git a/src/Snapshot.cs b/src/Snapshot.cs index 52ec53f..6c8be13 100644 --- a/src/Snapshot.cs +++ b/src/Snapshot.cs @@ -23,7 +23,7 @@ namespace MoonTools.ECS SnapshotRelationDepot = new RelationDepot(World.RelationTypeIndices); } - public void Take(Filter filter) + public unsafe void Take(Filter filter) { Clear(); Filter = filter; @@ -50,7 +50,7 @@ namespace MoonTools.ECS { SnapshotEntityStorage.AddRelationKind(snapshotEntityID, relationTypeIndex); - foreach (var (otherEntityID, relationData) in World.RelationDepot.OutRelations(worldEntity.ID, relationTypeIndex)) + foreach (var (otherEntityID, relationStorageIndex) in World.RelationDepot.OutRelationIndices(worldEntity.ID, relationTypeIndex)) { #if DEBUG if (!World.FilterStorage.CheckSatisfied(otherEntityID, Filter.Signature)) @@ -60,13 +60,13 @@ namespace MoonTools.ECS #endif var otherSnapshotID = WorldToSnapshotID[otherEntityID]; SnapshotEntityStorage.AddRelationKind(otherSnapshotID, relationTypeIndex); - SnapshotRelationDepot.Set(snapshotEntityID, otherSnapshotID, relationTypeIndex, relationData); + SnapshotRelationDepot.Set(snapshotEntityID, otherSnapshotID, relationTypeIndex, World.RelationDepot.Get(relationTypeIndex, relationStorageIndex)); } } } } - public void Restore() + public unsafe void Restore() { if (Filter == null) { @@ -99,10 +99,10 @@ namespace MoonTools.ECS { World.EntityStorage.AddRelationKind(worldID, relationTypeIndex); - foreach (var (otherEntityID, relationData) in SnapshotRelationDepot.OutRelations(i, relationTypeIndex)) + foreach (var (otherEntityID, relationStorageIndex) in SnapshotRelationDepot.OutRelationIndices(i, relationTypeIndex)) { var otherEntityWorldID = SnapshotToWorldID[otherEntityID]; - World.RelationDepot.Set(worldID, otherEntityWorldID, relationTypeIndex, relationData); + World.RelationDepot.Set(worldID, otherEntityWorldID, relationTypeIndex, SnapshotRelationDepot.Get(relationTypeIndex, relationStorageIndex)); } } } diff --git a/src/World.cs b/src/World.cs index 85873c0..17f487f 100644 --- a/src/World.cs +++ b/src/World.cs @@ -59,7 +59,7 @@ namespace MoonTools.ECS ComponentDepot.Register(componentTypeIndex); } - public Entity Instantiate(in Template template) + public unsafe Entity Instantiate(in Template template) { var entity = EntityStorage.Create(); -- 2.25.1 From b6485f345b932c56f5c88a9a246966f285bd1ce7 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Thu, 8 Dec 2022 12:08:35 -0800 Subject: [PATCH 08/14] value type enumerator on IndexableSet --- src/IndexableSet.cs | 60 +++++++++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/src/IndexableSet.cs b/src/IndexableSet.cs index 35ca411..06be701 100644 --- a/src/IndexableSet.cs +++ b/src/IndexableSet.cs @@ -1,15 +1,15 @@ using System; -using System.Collections; using System.Collections.Generic; -using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; namespace MoonTools.ECS { - internal class IndexableSet : IEnumerable where T : unmanaged + internal class IndexableSet where T : unmanaged { private Dictionary indices; private T[] array; public int Count { get; private set; } + public Enumerator GetEnumerator() => new Enumerator(this); public IndexableSet(int size = 32) { @@ -64,25 +64,47 @@ namespace MoonTools.ECS return true; } - public IEnumerator GetEnumerator() - { - for (var i = Count - 1; i >= 0; i -= 1) - { - yield return array[i]; - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - for (var i = Count - 1; i >= 0; i -= 1) - { - yield return array[i]; - } - } - public void Clear() { Count = 0; } + + public struct Enumerator + { + /// The set being enumerated. + private readonly IndexableSet _set; + /// The next index to yield. + private int _index; + + /// Initialize the enumerator. + /// The set to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Enumerator(IndexableSet set) + { + _set = set; + _index = _set.Count; + } + + /// Advances the enumerator to the next element of the span. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + int index = _index - 1; + if (index >= 0) + { + _index = index; + return true; + } + + return false; + } + + /// Gets the element at the current position of the enumerator. + public T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _set[_index]; + } + } } } -- 2.25.1 From 11488eba601900aa05e5dbc259d195f52604ff7c Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Thu, 8 Dec 2022 12:09:29 -0800 Subject: [PATCH 09/14] fix spacing --- src/IndexableSet.cs | 62 ++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/IndexableSet.cs b/src/IndexableSet.cs index 06be701..14689e1 100644 --- a/src/IndexableSet.cs +++ b/src/IndexableSet.cs @@ -9,7 +9,7 @@ namespace MoonTools.ECS private Dictionary indices; private T[] array; public int Count { get; private set; } - public Enumerator GetEnumerator() => new Enumerator(this); + public Enumerator GetEnumerator() => new Enumerator(this); public IndexableSet(int size = 32) { @@ -70,41 +70,41 @@ namespace MoonTools.ECS } public struct Enumerator - { - /// The set being enumerated. - private readonly IndexableSet _set; - /// The next index to yield. - private int _index; + { + /// The set being enumerated. + private readonly IndexableSet _set; + /// The next index to yield. + private int _index; - /// Initialize the enumerator. - /// The set to enumerate. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Enumerator(IndexableSet set) - { + /// Initialize the enumerator. + /// The set to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Enumerator(IndexableSet set) + { _set = set; _index = _set.Count; - } + } - /// Advances the enumerator to the next element of the span. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() - { - int index = _index - 1; - if (index >= 0) - { - _index = index; - return true; - } + /// Advances the enumerator to the next element of the span. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + int index = _index - 1; + if (index >= 0) + { + _index = index; + return true; + } - return false; - } + return false; + } - /// Gets the element at the current position of the enumerator. - public T Current - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _set[_index]; - } - } + /// Gets the element at the current position of the enumerator. + public T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _set[_index]; + } + } } } -- 2.25.1 From 307577d3992cae96248c76318b116af009f96e08 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 9 Dec 2022 10:35:19 -0800 Subject: [PATCH 10/14] FilterSignature IEquatable --- src/FilterSignature.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/FilterSignature.cs b/src/FilterSignature.cs index 4965cd1..8fd65ec 100644 --- a/src/FilterSignature.cs +++ b/src/FilterSignature.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace MoonTools.ECS { - public struct FilterSignature + public struct FilterSignature : IEquatable { public readonly HashSet Included; public readonly HashSet Excluded; @@ -40,5 +40,15 @@ namespace MoonTools.ECS return hashcode; } + + public static bool operator ==(FilterSignature left, FilterSignature right) + { + return left.Equals(right); + } + + public static bool operator !=(FilterSignature left, FilterSignature right) + { + return !(left == right); + } } } -- 2.25.1 From 01a9211ff48336a4f613357b4e5819916bc7a0d3 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 9 Dec 2022 11:29:43 -0800 Subject: [PATCH 11/14] remove IHasEntity --- src/IHasEntity.cs | 7 ------- src/MessageDepot.cs | 21 +++++++++++++-------- src/MessageStorage.cs | 24 ++++++++++++------------ src/System.cs | 11 ++++++++--- 4 files changed, 33 insertions(+), 30 deletions(-) delete mode 100644 src/IHasEntity.cs diff --git a/src/IHasEntity.cs b/src/IHasEntity.cs deleted file mode 100644 index 35a676f..0000000 --- a/src/IHasEntity.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MoonTools.ECS -{ - public interface IHasEntity - { - Entity Entity { get; } - } -} diff --git a/src/MessageDepot.cs b/src/MessageDepot.cs index 5f925ff..5af175f 100644 --- a/src/MessageDepot.cs +++ b/src/MessageDepot.cs @@ -7,7 +7,7 @@ namespace MoonTools.ECS { private Dictionary storages = new Dictionary(); - private MessageStorage Lookup() where TMessage : struct + private MessageStorage Lookup() where TMessage : unmanaged { if (!storages.ContainsKey(typeof(TMessage))) { @@ -17,37 +17,42 @@ namespace MoonTools.ECS return storages[typeof(TMessage)] as MessageStorage; } - public void Add(in TMessage message) where TMessage : struct + public void Add(in TMessage message) where TMessage : unmanaged { Lookup().Add(message); } - public bool Some() where TMessage : struct + public void Add(int entityID, in TMessage message) where TMessage : unmanaged + { + Lookup().Add(entityID, message); + } + + public bool Some() where TMessage : unmanaged { return Lookup().Some(); } - public ReadOnlySpan All() where TMessage : struct + public ReadOnlySpan All() where TMessage : unmanaged { return Lookup().All(); } - public TMessage First() where TMessage : struct + public TMessage First() where TMessage : unmanaged { return Lookup().First(); } - public IEnumerable WithEntity(int entityID) where TMessage : struct, IHasEntity + public IEnumerable WithEntity(int entityID) where TMessage : unmanaged { return Lookup().WithEntity(entityID); } - public ref readonly TMessage FirstWithEntity(int entityID) where TMessage : struct, IHasEntity + public ref readonly TMessage FirstWithEntity(int entityID) where TMessage : unmanaged { return ref Lookup().FirstWithEntity(entityID); } - public bool SomeWithEntity(int entityID) where TMessage : struct, IHasEntity + public bool SomeWithEntity(int entityID) where TMessage : unmanaged { return Lookup().SomeWithEntity(entityID); } diff --git a/src/MessageStorage.cs b/src/MessageStorage.cs index 0e29a1a..545da25 100644 --- a/src/MessageStorage.cs +++ b/src/MessageStorage.cs @@ -8,7 +8,7 @@ namespace MoonTools.ECS public abstract void Clear(); } - internal class MessageStorage : MessageStorage where TMessage : struct + internal class MessageStorage : MessageStorage where TMessage : unmanaged { private int count = 0; private int capacity = 128; @@ -29,20 +29,20 @@ namespace MoonTools.ECS } messages[count] = message; - - if (message is IHasEntity entityMessage) - { - if (!entityToIndices.ContainsKey(entityMessage.Entity.ID)) - { - entityToIndices.Add(entityMessage.Entity.ID, new List()); - } - - entityToIndices[entityMessage.Entity.ID].Add(count); - } - count += 1; } + public void Add(int entityID, in TMessage message) + { + if (!entityToIndices.ContainsKey(entityID)) + { + entityToIndices.Add(entityID, new List()); + } + entityToIndices[entityID].Add(count); + + Add(message); + } + public bool Some() { return count > 0; diff --git a/src/System.cs b/src/System.cs index f903de7..9e98e6a 100644 --- a/src/System.cs +++ b/src/System.cs @@ -43,17 +43,17 @@ namespace MoonTools.ECS return MessageDepot.Some(); } - protected IEnumerable ReadMessagesWithEntity(in Entity entity) where TMessage : unmanaged, IHasEntity + protected IEnumerable ReadMessagesWithEntity(in Entity entity) where TMessage : unmanaged { return MessageDepot.WithEntity(entity.ID); } - protected ref readonly TMessage ReadMessageWithEntity(in Entity entity) where TMessage : unmanaged, IHasEntity + protected ref readonly TMessage ReadMessageWithEntity(in Entity entity) where TMessage : unmanaged { return ref MessageDepot.FirstWithEntity(entity.ID); } - protected bool SomeMessageWithEntity(in Entity entity) where TMessage : unmanaged, IHasEntity + protected bool SomeMessageWithEntity(in Entity entity) where TMessage : unmanaged { return MessageDepot.SomeWithEntity(entity.ID); } @@ -63,6 +63,11 @@ namespace MoonTools.ECS MessageDepot.Add(message); } + protected void Send(in Entity entity, in TMessage message) where TMessage : unmanaged + { + MessageDepot.Add(entity.ID, message); + } + protected void Relate(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged { RelationDepot.Set(new Relation(entityA, entityB), relationData); -- 2.25.1 From 2f46af30fba8613fb084efd1557c7b84b24b995f Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 13 Dec 2022 00:34:35 -0800 Subject: [PATCH 12/14] garbage collection optimizations --- src/ComponentStorage.cs | 3 +- src/DynamicArray.cs | 40 +++++++++ src/Entity.cs | 10 +++ src/EntityComponentReader.cs | 17 ++-- src/EntityStorage.cs | 5 +- src/Enumerators/ReverseSpanEnumerator.cs | 36 ++++++++ src/Filter.cs | 7 +- src/FilterStorage.cs | 28 +++--- src/IndexableSet.cs | 2 +- src/MessageDepot.cs | 2 +- src/MessageStorage.cs | 28 +++--- src/Random.cs | 42 +++++++-- src/Relation.cs | 6 -- src/RelationDepot.cs | 22 +++-- src/RelationStorage.cs | 110 +++++++++++------------ src/Snapshot.cs | 8 +- src/System.cs | 2 +- src/TypeIndices.cs | 4 +- 18 files changed, 245 insertions(+), 127 deletions(-) create mode 100644 src/DynamicArray.cs create mode 100644 src/Enumerators/ReverseSpanEnumerator.cs diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs index 5ea989f..41117bc 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -83,9 +83,8 @@ namespace MoonTools.ECS // Returns true if the entity had this component. public override bool Remove(int entityID) { - if (entityIDToStorageIndex.ContainsKey(entityID)) + if (entityIDToStorageIndex.TryGetValue(entityID, out int storageIndex)) { - var storageIndex = entityIDToStorageIndex[entityID]; entityIDToStorageIndex.Remove(entityID); var lastElementIndex = nextID - 1; diff --git a/src/DynamicArray.cs b/src/DynamicArray.cs new file mode 100644 index 0000000..c16b697 --- /dev/null +++ b/src/DynamicArray.cs @@ -0,0 +1,40 @@ +using System; + +namespace MoonTools.ECS +{ + public class DynamicArray where T : unmanaged + { + private T[] Array; + public int Count { get; private set; } + + public Span ToSpan() => new Span(Array, 0, Count); + public ReverseSpanEnumerator GetEnumerator() => new ReverseSpanEnumerator(new Span(Array, 0, Count)); + + public DynamicArray(int capacity = 16) + { + Array = new T[capacity]; + Count = 0; + } + + public ref T this[int i] + { + get { return ref Array[i]; } + } + + public void Add(T item) + { + if (Count >= Array.Length) + { + global::System.Array.Resize(ref Array, Array.Length * 2); + } + + Array[Count] = item; + Count += 1; + } + + public void Clear() + { + Count = 0; + } + } +} diff --git a/src/Entity.cs b/src/Entity.cs index ba87a1e..e91da6e 100644 --- a/src/Entity.cs +++ b/src/Entity.cs @@ -35,5 +35,15 @@ namespace MoonTools.ECS { return !a.Equals(b); } + + public static implicit operator int(Entity e) + { + return e.ID; + } + + public static implicit operator Entity(int i) + { + return new Entity(i); + } } } diff --git a/src/EntityComponentReader.cs b/src/EntityComponentReader.cs index 1678317..efdf50d 100644 --- a/src/EntityComponentReader.cs +++ b/src/EntityComponentReader.cs @@ -62,13 +62,18 @@ namespace MoonTools.ECS return RelationDepot.Related(a.ID, b.ID); } - // relations go A->B, so given A, will give all outgoing B relations. - protected IEnumerable<(Entity, TRelationKind)> OutRelations(in Entity entity) where TRelationKind : unmanaged + protected TRelationKind GetRelationData(in Entity a, in Entity b) where TRelationKind : unmanaged + { + return RelationDepot.Get(new Relation(a.ID, b.ID)); + } + + // relations go A->B, so given A, will give all entities in outgoing relations of this kind. + protected ReverseSpanEnumerator OutRelations(in Entity entity) where TRelationKind : unmanaged { return RelationDepot.OutRelations(entity.ID); } - protected (Entity, TRelationKind) OutRelationSingleton(in Entity entity) where TRelationKind : unmanaged + protected Entity OutRelationSingleton(in Entity entity) where TRelationKind : unmanaged { return RelationDepot.OutRelationSingleton(entity.ID); } @@ -83,13 +88,13 @@ namespace MoonTools.ECS return RelationDepot.OutRelationCount(entity.ID); } - // Relations go A->B, so given B, will give all incoming A relations. - protected IEnumerable<(Entity, TRelationKind)> InRelations(in Entity entity) where TRelationKind : unmanaged + // Relations go A->B, so given B, will give all entities in incoming A relations of this kind. + protected ReverseSpanEnumerator InRelations(in Entity entity) where TRelationKind : unmanaged { return RelationDepot.InRelations(entity.ID); } - protected (Entity, TRelationKind) InRelationSingleton(in Entity entity) where TRelationKind : unmanaged + protected Entity InRelationSingleton(in Entity entity) where TRelationKind : unmanaged { return RelationDepot.InRelationSingleton(entity.ID); } diff --git a/src/EntityStorage.cs b/src/EntityStorage.cs index 99d8b6c..d2f912d 100644 --- a/src/EntityStorage.cs +++ b/src/EntityStorage.cs @@ -62,13 +62,12 @@ namespace MoonTools.ECS EntityToRelationTypeIndices[entityId].Remove(relationIndex); } - // TODO: should these ints be ID types? - public IEnumerable ComponentTypeIndices(int entityID) + public HashSet ComponentTypeIndices(int entityID) { return EntityToComponentTypeIndices[entityID]; } - public IEnumerable RelationTypeIndices(int entityID) + public HashSet RelationTypeIndices(int entityID) { return EntityToRelationTypeIndices[entityID]; } diff --git a/src/Enumerators/ReverseSpanEnumerator.cs b/src/Enumerators/ReverseSpanEnumerator.cs new file mode 100644 index 0000000..ce526a5 --- /dev/null +++ b/src/Enumerators/ReverseSpanEnumerator.cs @@ -0,0 +1,36 @@ +using System; +using System.Runtime.CompilerServices; + +namespace MoonTools.ECS +{ + public ref struct ReverseSpanEnumerator + { + private ReadOnlySpan Span; + private int index; + + public ReverseSpanEnumerator GetEnumerator() => this; + + public T Current => Span[index]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + if (index > 0) + { + index -= 1; + return true; + } + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReverseSpanEnumerator(Span span) + { + Span = span; + index = span.Length; + } + + public static ReverseSpanEnumerator Empty => new ReverseSpanEnumerator(); + } +} diff --git a/src/Filter.cs b/src/Filter.cs index ef70da6..4124428 100644 --- a/src/Filter.cs +++ b/src/Filter.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace MoonTools.ECS { @@ -13,8 +14,8 @@ namespace MoonTools.ECS Signature = new FilterSignature(included, excluded); } - public IEnumerable Entities => FilterStorage.FilterEntities(Signature); - public IEnumerable EntitiesInRandomOrder => FilterStorage.FilterEntitiesRandom(Signature); + public ReverseSpanEnumerator Entities => FilterStorage.FilterEntities(Signature); + public LinearCongruentialEnumerator EntitiesInRandomOrder => FilterStorage.FilterEntitiesRandom(Signature); public Entity RandomEntity => FilterStorage.FilterRandomEntity(Signature); public int Count => FilterStorage.FilterCount(Signature); diff --git a/src/FilterStorage.cs b/src/FilterStorage.cs index e380234..b8ea8f3 100644 --- a/src/FilterStorage.cs +++ b/src/FilterStorage.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace MoonTools.ECS @@ -7,7 +7,7 @@ namespace MoonTools.ECS { private EntityStorage EntityStorage; private TypeIndices ComponentTypeIndices; - private Dictionary> filterSignatureToEntityIDs = new Dictionary>(); + private Dictionary> filterSignatureToEntityIDs = new Dictionary>(); private Dictionary> typeToFilterSignatures = new Dictionary>(); public FilterStorage(EntityStorage entityStorage, TypeIndices componentTypeIndices) @@ -21,7 +21,7 @@ namespace MoonTools.ECS var filterSignature = new FilterSignature(included, excluded); if (!filterSignatureToEntityIDs.ContainsKey(filterSignature)) { - filterSignatureToEntityIDs.Add(filterSignature, new IndexableSet()); + filterSignatureToEntityIDs.Add(filterSignature, new IndexableSet()); foreach (var type in included) { @@ -46,20 +46,14 @@ namespace MoonTools.ECS return new Filter(this, included, excluded); } - public IEnumerable FilterEntities(FilterSignature filterSignature) + public ReverseSpanEnumerator FilterEntities(FilterSignature filterSignature) { - foreach (var id in filterSignatureToEntityIDs[filterSignature]) - { - yield return new Entity(id); - } + return filterSignatureToEntityIDs[filterSignature].GetEnumerator(); } - public IEnumerable FilterEntitiesRandom(FilterSignature filterSignature) + public LinearCongruentialEnumerator FilterEntitiesRandom(FilterSignature filterSignature) { - foreach (var index in RandomGenerator.LinearCongruentialGenerator(FilterCount(filterSignature))) - { - yield return new Entity(filterSignatureToEntityIDs[filterSignature][index]); - } + return RandomGenerator.LinearCongruentialGenerator(FilterCount(filterSignature)); } public Entity FilterNthEntity(FilterSignature filterSignature, int index) @@ -80,9 +74,9 @@ namespace MoonTools.ECS public void Check(int entityID, int componentTypeIndex) { - if (typeToFilterSignatures.ContainsKey(componentTypeIndex)) + if (typeToFilterSignatures.TryGetValue(componentTypeIndex, out var filterSignatures)) { - foreach (var filterSignature in typeToFilterSignatures[componentTypeIndex]) + foreach (var filterSignature in filterSignatures) { CheckFilter(entityID, filterSignature); } @@ -140,9 +134,9 @@ namespace MoonTools.ECS public void RemoveEntity(int entityID, int componentTypeIndex) { - if (typeToFilterSignatures.ContainsKey(componentTypeIndex)) + if (typeToFilterSignatures.TryGetValue(componentTypeIndex, out var filterSignatures)) { - foreach (var filterSignature in typeToFilterSignatures[componentTypeIndex]) + foreach (var filterSignature in filterSignatures) { filterSignatureToEntityIDs[filterSignature].Remove(entityID); } diff --git a/src/IndexableSet.cs b/src/IndexableSet.cs index 14689e1..1a79f71 100644 --- a/src/IndexableSet.cs +++ b/src/IndexableSet.cs @@ -9,7 +9,7 @@ namespace MoonTools.ECS private Dictionary indices; private T[] array; public int Count { get; private set; } - public Enumerator GetEnumerator() => new Enumerator(this); + public ReverseSpanEnumerator GetEnumerator() => new ReverseSpanEnumerator(new Span(array, 0, Count)); public IndexableSet(int size = 32) { diff --git a/src/MessageDepot.cs b/src/MessageDepot.cs index 5af175f..63b9e2d 100644 --- a/src/MessageDepot.cs +++ b/src/MessageDepot.cs @@ -42,7 +42,7 @@ namespace MoonTools.ECS return Lookup().First(); } - public IEnumerable WithEntity(int entityID) where TMessage : unmanaged + public ReverseSpanEnumerator WithEntity(int entityID) where TMessage : unmanaged { return Lookup().WithEntity(entityID); } diff --git a/src/MessageStorage.cs b/src/MessageStorage.cs index 545da25..b2f29d0 100644 --- a/src/MessageStorage.cs +++ b/src/MessageStorage.cs @@ -13,7 +13,8 @@ namespace MoonTools.ECS private int count = 0; private int capacity = 128; private TMessage[] messages; - private Dictionary> entityToIndices = new Dictionary>(); + // duplicating storage here for fast iteration + private Dictionary> entityToMessages = new Dictionary>(); public MessageStorage() { @@ -34,11 +35,11 @@ namespace MoonTools.ECS public void Add(int entityID, in TMessage message) { - if (!entityToIndices.ContainsKey(entityID)) + if (!entityToMessages.ContainsKey(entityID)) { - entityToIndices.Add(entityID, new List()); + entityToMessages.Add(entityID, new DynamicArray()); } - entityToIndices[entityID].Add(count); + entityToMessages[entityID].Add(message); Add(message); } @@ -58,31 +59,32 @@ namespace MoonTools.ECS return messages[0]; } - public IEnumerable WithEntity(int entityID) + public ReverseSpanEnumerator WithEntity(int entityID) { - if (entityToIndices.ContainsKey(entityID)) + if (entityToMessages.TryGetValue(entityID, out var messages)) { - foreach (var index in entityToIndices[entityID]) - { - yield return messages[index]; - } + return messages.GetEnumerator(); + } + else + { + return ReverseSpanEnumerator.Empty; } } public ref readonly TMessage FirstWithEntity(int entityID) { - return ref messages[entityToIndices[entityID][0]]; + return ref entityToMessages[entityID][0]; } public bool SomeWithEntity(int entityID) { - return entityToIndices.ContainsKey(entityID) && entityToIndices[entityID].Count > 0; + return entityToMessages.ContainsKey(entityID) && entityToMessages[entityID].Count > 0; } public override void Clear() { count = 0; - foreach (var set in entityToIndices.Values) + foreach (var set in entityToMessages.Values) { set.Clear(); } diff --git a/src/Random.cs b/src/Random.cs index 97c0ddd..324eccd 100644 --- a/src/Random.cs +++ b/src/Random.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace MoonTools.ECS { @@ -20,7 +20,7 @@ namespace MoonTools.ECS /// /// A psuedorandom nonrepeating sequence of integers from 0 to n. /// - public static IEnumerable LinearCongruentialGenerator(int n) + public static LinearCongruentialEnumerator LinearCongruentialGenerator(int n) { var x = Primes[random.Next(Primes.Length - 1)]; while (x % n == 0) @@ -29,12 +29,44 @@ namespace MoonTools.ECS x = Primes[random.Next(Primes.Length - 1)]; } - var start = random.Next(n); + return new LinearCongruentialEnumerator(random.Next(n), x, n); + } + } - for (var i = start; i < start + n; i++) + public struct LinearCongruentialEnumerator + { + private readonly int start; + private readonly int count; + private readonly int prime; + private int current; + + public LinearCongruentialEnumerator GetEnumerator() => this; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal LinearCongruentialEnumerator(int start, int prime, int count) + { + current = start; + this.start = start; + this.prime = prime; + this.count = count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + current += 1; + if (current < start + count) { - yield return (i * x) % n; + return true; } + + return false; + } + + public int Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (current * prime) % count; } } } diff --git a/src/Relation.cs b/src/Relation.cs index 165aca6..6dccd3a 100644 --- a/src/Relation.cs +++ b/src/Relation.cs @@ -13,12 +13,6 @@ namespace MoonTools.ECS B = entityB; } - internal Relation(int idA, int idB) - { - A = new Entity(idA); - B = new Entity(idB); - } - public override bool Equals(object? obj) { return obj is Relation relation && Equals(relation); diff --git a/src/RelationDepot.cs b/src/RelationDepot.cs index 4baa35c..abea455 100644 --- a/src/RelationDepot.cs +++ b/src/RelationDepot.cs @@ -41,6 +41,11 @@ namespace MoonTools.ECS Lookup().Set(relation, relationData); } + public TRelationKind Get(Relation relation) where TRelationKind : unmanaged + { + return Lookup().Get(relation); + } + public (bool, bool) Remove(Relation relation) where TRelationKind : unmanaged { return Lookup().Remove(relation); @@ -61,12 +66,12 @@ namespace MoonTools.ECS return Lookup().Has(new Relation(idA, idB)); } - public IEnumerable<(Entity, TRelationKind)> OutRelations(int entityID) where TRelationKind : unmanaged + public ReverseSpanEnumerator OutRelations(int entityID) where TRelationKind : unmanaged { return Lookup().OutRelations(entityID); } - public (Entity, TRelationKind) OutRelationSingleton(int entityID) where TRelationKind : unmanaged + public Entity OutRelationSingleton(int entityID) where TRelationKind : unmanaged { return Lookup().OutFirst(entityID); } @@ -81,12 +86,12 @@ namespace MoonTools.ECS return Lookup().HasOutRelation(entityID); } - public IEnumerable<(Entity, TRelationKind)> InRelations(int entityID) where TRelationKind : unmanaged + public ReverseSpanEnumerator InRelations(int entityID) where TRelationKind : unmanaged { return Lookup().InRelations(entityID); } - public (Entity, TRelationKind) InRelationSingleton(int entityID) where TRelationKind : unmanaged + public Entity InRelationSingleton(int entityID) where TRelationKind : unmanaged { return Lookup().InFirst(entityID); } @@ -108,6 +113,11 @@ namespace MoonTools.ECS storages[relationTypeIndex].Set(entityA, entityB, relationData); } + public int GetStorageIndex(int relationTypeIndex, int entityA, int entityB) + { + return storages[relationTypeIndex].GetStorageIndex(entityA, entityB); + } + public unsafe void* Get(int relationTypeIndex, int relationStorageIndex) { return storages[relationTypeIndex].Get(relationStorageIndex); @@ -118,9 +128,9 @@ namespace MoonTools.ECS storages[relationTypeIndex].UnrelateAll(entityID); } - public IEnumerable<(int, int)> OutRelationIndices(int entityID, int relationTypeIndex) + public ReverseSpanEnumerator OutRelations(int entityID, int relationTypeIndex) { - return storages[relationTypeIndex].OutRelationIndices(entityID); + return storages[relationTypeIndex].OutRelations(entityID); } public void Clear() diff --git a/src/RelationStorage.cs b/src/RelationStorage.cs index c7ed957..7c522c5 100644 --- a/src/RelationStorage.cs +++ b/src/RelationStorage.cs @@ -6,9 +6,10 @@ namespace MoonTools.ECS internal abstract class RelationStorage { public abstract unsafe void Set(int entityA, int entityB, void* relationData); + public abstract int GetStorageIndex(int entityA, int entityB); public abstract unsafe void* Get(int relationStorageIndex); public abstract void UnrelateAll(int entityID); - public abstract IEnumerable<(int, int)> OutRelationIndices(int entityID); + public abstract ReverseSpanEnumerator OutRelations(int entityID); public abstract RelationStorage CreateStorage(); public abstract void Clear(); } @@ -21,9 +22,9 @@ namespace MoonTools.ECS private Dictionary indices = new Dictionary(16); private Relation[] relations = new Relation[16]; private TRelation[] relationDatas = new TRelation[16]; - private Dictionary> outRelations = new Dictionary>(16); - private Dictionary> inRelations = new Dictionary>(16); - private Stack> listPool = new Stack>(); + private Dictionary> outRelations = new Dictionary>(16); + private Dictionary> inRelations = new Dictionary>(16); + private Stack> listPool = new Stack>(); public IEnumerable<(Entity, Entity, TRelation)> All() { @@ -36,9 +37,8 @@ namespace MoonTools.ECS public void Set(Relation relation, TRelation relationData) { - if (indices.ContainsKey(relation)) + if (indices.TryGetValue(relation, out var index)) { - var index = indices[relation]; relationDatas[index] = relationData; return; } @@ -70,34 +70,37 @@ namespace MoonTools.ECS count += 1; } + public TRelation Get(Relation relation) + { + return relationDatas[indices[relation]]; + } + public bool Has(Relation relation) { return indices.ContainsKey(relation); } - // FIXME: creating the new Relation in here is slightly deranged - public IEnumerable<(Entity, TRelation)> OutRelations(int entityID) + public override ReverseSpanEnumerator OutRelations(int entityID) { - if (outRelations.ContainsKey(entityID)) + if (outRelations.TryGetValue(entityID, out var entityOutRelations)) { - foreach (var id in outRelations[entityID]) - { - var relation = new Relation(entityID, id); - yield return (relation.B, relationDatas[indices[relation]]); - } + return entityOutRelations.GetEnumerator(); + } + else + { + return ReverseSpanEnumerator.Empty; } } - public (Entity, TRelation) OutFirst(int entityID) + public Entity OutFirst(int entityID) { #if DEBUG - if (!outRelations.ContainsKey(entityID)) + if (!outRelations.ContainsKey(entityID) || outRelations[entityID].Count == 0) { throw new KeyNotFoundException("No out relations to this entity!"); } #endif - var relation = new Relation(entityID, outRelations[entityID][0]); - return (relation.B, relationDatas[indices[relation]]); + return outRelations[entityID][0]; } public bool HasOutRelation(int entityID) @@ -107,32 +110,31 @@ namespace MoonTools.ECS public int OutRelationCount(int entityID) { - return outRelations.ContainsKey(entityID) ? outRelations[entityID].Count : 0; + return outRelations.TryGetValue(entityID, out var entityOutRelations) ? entityOutRelations.Count : 0; } - public IEnumerable<(Entity, TRelation)> InRelations(int entityID) + public ReverseSpanEnumerator InRelations(int entityID) { - if (inRelations.ContainsKey(entityID)) + if (inRelations.TryGetValue(entityID, out var entityInRelations)) { - foreach (var id in inRelations[entityID]) - { - var relation = new Relation(id, entityID); - yield return (relation.A, relationDatas[indices[relation]]); - } + return entityInRelations.GetEnumerator(); + } + else + { + return ReverseSpanEnumerator.Empty; } } - public (Entity, TRelation) InFirst(int entityID) + public Entity InFirst(int entityID) { #if DEBUG - if (!inRelations.ContainsKey(entityID)) + if (!inRelations.ContainsKey(entityID) || inRelations[entityID].Count == 0) { throw new KeyNotFoundException("No out relations to this entity!"); } #endif - var relation = new Relation(inRelations[entityID][0], entityID); - return (relation.A, relationDatas[indices[relation]]); + return inRelations[entityID][0]; } public bool HasInRelation(int entityID) @@ -142,7 +144,7 @@ namespace MoonTools.ECS public int InRelationCount(int entityID) { - return inRelations.ContainsKey(entityID) ? inRelations[entityID].Count : 0; + return inRelations.TryGetValue(entityID, out var entityInRelations) ? entityInRelations.Count : 0; } public (bool, bool) Remove(Relation relation) @@ -150,27 +152,26 @@ namespace MoonTools.ECS var aEmpty = false; var bEmpty = false; - if (outRelations.ContainsKey(relation.A.ID)) + if (outRelations.TryGetValue(relation.A.ID, out var entityOutRelations)) { - outRelations[relation.A.ID].Remove(relation.B.ID); + entityOutRelations.Remove(relation.B.ID); if (outRelations[relation.A.ID].Count == 0) { aEmpty = true; } } - if (inRelations.ContainsKey(relation.B.ID)) + if (inRelations.TryGetValue(relation.B.ID, out var entityInRelations)) { - inRelations[relation.B.ID].Remove(relation.A.ID); + entityInRelations.Remove(relation.A.ID); if (inRelations[relation.B.ID].Count == 0) { bEmpty = true; } } - if (indices.ContainsKey(relation)) + if (indices.TryGetValue(relation, out var index)) { - var index = indices[relation]; var lastElementIndex = count - 1; // move an element into the hole @@ -189,17 +190,17 @@ namespace MoonTools.ECS return (aEmpty, bEmpty); } - private IndexableSet AcquireHashSetFromPool() + private IndexableSet AcquireHashSetFromPool() { if (listPool.Count == 0) { - listPool.Push(new IndexableSet()); + listPool.Push(new IndexableSet()); } return listPool.Pop(); } - private void ReturnHashSetToPool(IndexableSet hashSet) + private void ReturnHashSetToPool(IndexableSet hashSet) { hashSet.Clear(); listPool.Push(hashSet); @@ -212,6 +213,11 @@ namespace MoonTools.ECS Set(new Relation(entityA, entityB), *((TRelation*) relationData)); } + public override int GetStorageIndex(int entityA, int entityB) + { + return indices[new Relation(entityA, entityB)]; + } + public override unsafe void* Get(int relationStorageIndex) { fixed (void* p = &relations[relationStorageIndex]) @@ -222,41 +228,29 @@ namespace MoonTools.ECS public override void UnrelateAll(int entityID) { - if (outRelations.ContainsKey(entityID)) + if (outRelations.TryGetValue(entityID, out var entityOutRelations)) { - foreach (var entityB in outRelations[entityID]) + foreach (var entityB in entityOutRelations) { Remove(new Relation(entityID, entityB)); } - ReturnHashSetToPool(outRelations[entityID]); + ReturnHashSetToPool(entityOutRelations); outRelations.Remove(entityID); } - if (inRelations.ContainsKey(entityID)) + if (inRelations.TryGetValue(entityID, out var entityInRelations)) { - foreach (var entityA in inRelations[entityID]) + foreach (var entityA in entityInRelations) { Remove(new Relation(entityA, entityID)); } - ReturnHashSetToPool(inRelations[entityID]); + ReturnHashSetToPool(entityInRelations); inRelations.Remove(entityID); } } - public override IEnumerable<(int, int)> OutRelationIndices(int entityID) - { - if (outRelations.ContainsKey(entityID)) - { - foreach (var id in outRelations[entityID]) - { - var relation = new Relation(entityID, id); - yield return (id, indices[relation]); - } - } - } - public override RelationStorage CreateStorage() { return new RelationStorage(); diff --git a/src/Snapshot.cs b/src/Snapshot.cs index 6c8be13..6fd6bcb 100644 --- a/src/Snapshot.cs +++ b/src/Snapshot.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace MoonTools.ECS @@ -50,7 +50,7 @@ namespace MoonTools.ECS { SnapshotEntityStorage.AddRelationKind(snapshotEntityID, relationTypeIndex); - foreach (var (otherEntityID, relationStorageIndex) in World.RelationDepot.OutRelationIndices(worldEntity.ID, relationTypeIndex)) + foreach (var otherEntityID in World.RelationDepot.OutRelations(worldEntity.ID, relationTypeIndex)) { #if DEBUG if (!World.FilterStorage.CheckSatisfied(otherEntityID, Filter.Signature)) @@ -58,6 +58,7 @@ namespace MoonTools.ECS throw new InvalidOperationException($"Snapshot entity {worldEntity.ID} is related to non-snapshot entity {otherEntityID}!"); } #endif + var relationStorageIndex = World.RelationDepot.GetStorageIndex(relationTypeIndex, worldEntity, otherEntityID); var otherSnapshotID = WorldToSnapshotID[otherEntityID]; SnapshotEntityStorage.AddRelationKind(otherSnapshotID, relationTypeIndex); SnapshotRelationDepot.Set(snapshotEntityID, otherSnapshotID, relationTypeIndex, World.RelationDepot.Get(relationTypeIndex, relationStorageIndex)); @@ -99,8 +100,9 @@ namespace MoonTools.ECS { World.EntityStorage.AddRelationKind(worldID, relationTypeIndex); - foreach (var (otherEntityID, relationStorageIndex) in SnapshotRelationDepot.OutRelationIndices(i, relationTypeIndex)) + foreach (var otherEntityID in SnapshotRelationDepot.OutRelations(i, relationTypeIndex)) { + var relationStorageIndex = SnapshotRelationDepot.GetStorageIndex(relationTypeIndex, i, otherEntityID); var otherEntityWorldID = SnapshotToWorldID[otherEntityID]; World.RelationDepot.Set(worldID, otherEntityWorldID, relationTypeIndex, SnapshotRelationDepot.Get(relationTypeIndex, relationStorageIndex)); } diff --git a/src/System.cs b/src/System.cs index 9e98e6a..35c44a6 100644 --- a/src/System.cs +++ b/src/System.cs @@ -43,7 +43,7 @@ namespace MoonTools.ECS return MessageDepot.Some(); } - protected IEnumerable ReadMessagesWithEntity(in Entity entity) where TMessage : unmanaged + protected ReverseSpanEnumerator ReadMessagesWithEntity(in Entity entity) where TMessage : unmanaged { return MessageDepot.WithEntity(entity.ID); } diff --git a/src/TypeIndices.cs b/src/TypeIndices.cs index 53bf700..2f43850 100644 --- a/src/TypeIndices.cs +++ b/src/TypeIndices.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace MoonTools.ECS @@ -27,7 +27,7 @@ namespace MoonTools.ECS #if DEBUG - public IEnumerable Types => TypeToIndex.Keys; + public Dictionary.KeyCollection Types => TypeToIndex.Keys; #endif } } -- 2.25.1 From 93ed5c8dd288effc0a0f9fb0d67bf2814196f5e6 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 13 Dec 2022 15:14:27 -0800 Subject: [PATCH 13/14] more garbage optimization --- src/EntityComponentReader.cs | 4 +-- src/Relation.cs | 31 ----------------------- src/RelationDepot.cs | 16 ++++++------ src/RelationStorage.cs | 49 ++++++++++++++++++------------------ src/System.cs | 4 +-- src/TemplateStorage.cs | 4 +-- 6 files changed, 38 insertions(+), 70 deletions(-) delete mode 100644 src/Relation.cs diff --git a/src/EntityComponentReader.cs b/src/EntityComponentReader.cs index efdf50d..e55f6dd 100644 --- a/src/EntityComponentReader.cs +++ b/src/EntityComponentReader.cs @@ -52,7 +52,7 @@ namespace MoonTools.ECS return ComponentDepot.GetSingletonEntity(); } - protected IEnumerable<(Entity, Entity, TRelationKind)> Relations() where TRelationKind : unmanaged + protected ReverseSpanEnumerator<(Entity, Entity)> Relations() where TRelationKind : unmanaged { return RelationDepot.Relations(); } @@ -64,7 +64,7 @@ namespace MoonTools.ECS protected TRelationKind GetRelationData(in Entity a, in Entity b) where TRelationKind : unmanaged { - return RelationDepot.Get(new Relation(a.ID, b.ID)); + return RelationDepot.Get(a, b); } // relations go A->B, so given A, will give all entities in outgoing relations of this kind. diff --git a/src/Relation.cs b/src/Relation.cs deleted file mode 100644 index 6dccd3a..0000000 --- a/src/Relation.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; - -namespace MoonTools.ECS -{ - internal struct Relation : IEquatable - { - public Entity A { get; } - public Entity B { get; } - - internal Relation(Entity entityA, Entity entityB) - { - A = entityA; - B = entityB; - } - - public override bool Equals(object? obj) - { - return obj is Relation relation && Equals(relation); - } - - public bool Equals(Relation other) - { - return A.ID == other.A.ID && B.ID == other.B.ID; - } - - public override int GetHashCode() - { - return HashCode.Combine(A.ID, B.ID); - } - } -} diff --git a/src/RelationDepot.cs b/src/RelationDepot.cs index abea455..06bb2fe 100644 --- a/src/RelationDepot.cs +++ b/src/RelationDepot.cs @@ -36,19 +36,19 @@ namespace MoonTools.ECS return (RelationStorage) storages[storageIndex]; } - public void Set(Relation relation, TRelationKind relationData) where TRelationKind : unmanaged + public void Set(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged { - Lookup().Set(relation, relationData); + Lookup().Set(entityA, entityB, relationData); } - public TRelationKind Get(Relation relation) where TRelationKind : unmanaged + public TRelationKind Get(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged { - return Lookup().Get(relation); + return Lookup().Get(entityA, entityB); } - public (bool, bool) Remove(Relation relation) where TRelationKind : unmanaged + public (bool, bool) Remove(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged { - return Lookup().Remove(relation); + return Lookup().Remove(entityA, entityB); } public void UnrelateAll(int entityID) where TRelationKind : unmanaged @@ -56,14 +56,14 @@ namespace MoonTools.ECS Lookup().UnrelateAll(entityID); } - public IEnumerable<(Entity, Entity, TRelationKind)> Relations() where TRelationKind : unmanaged + public ReverseSpanEnumerator<(Entity, Entity)> Relations() where TRelationKind : unmanaged { return Lookup().All(); } public bool Related(int idA, int idB) where TRelationKind : unmanaged { - return Lookup().Has(new Relation(idA, idB)); + return Lookup().Has((idA, idB)); } public ReverseSpanEnumerator OutRelations(int entityID) where TRelationKind : unmanaged diff --git a/src/RelationStorage.cs b/src/RelationStorage.cs index 7c522c5..a069768 100644 --- a/src/RelationStorage.cs +++ b/src/RelationStorage.cs @@ -19,32 +19,30 @@ namespace MoonTools.ECS internal class RelationStorage : RelationStorage where TRelation : unmanaged { private int count = 0; - private Dictionary indices = new Dictionary(16); - private Relation[] relations = new Relation[16]; + private Dictionary<(Entity, Entity), int> indices = new Dictionary<(Entity, Entity), int>(16); + private (Entity, Entity)[] relations = new (Entity, Entity)[16]; private TRelation[] relationDatas = new TRelation[16]; private Dictionary> outRelations = new Dictionary>(16); private Dictionary> inRelations = new Dictionary>(16); private Stack> listPool = new Stack>(); - public IEnumerable<(Entity, Entity, TRelation)> All() + public ReverseSpanEnumerator<(Entity, Entity)> All() { - for (var i = 0; i < count; i += 1) - { - var relation = relations[i]; - yield return (relation.A, relation.B, relationDatas[i]); - } + return new ReverseSpanEnumerator<(Entity, Entity)>(new Span<(Entity, Entity)>(relations, 0, count)); } - public void Set(Relation relation, TRelation relationData) + public void Set(in Entity entityA, in Entity entityB, TRelation relationData) { + var relation = (entityA, entityB); + if (indices.TryGetValue(relation, out var index)) { relationDatas[index] = relationData; return; } - var idA = relation.A.ID; - var idB = relation.B.ID; + var idA = entityA.ID; + var idB = entityB.ID; if (!outRelations.ContainsKey(idA)) { @@ -70,12 +68,12 @@ namespace MoonTools.ECS count += 1; } - public TRelation Get(Relation relation) + public TRelation Get(in Entity entityA, in Entity entityB) { - return relationDatas[indices[relation]]; + return relationDatas[indices[(entityA, entityB)]]; } - public bool Has(Relation relation) + public bool Has((Entity, Entity) relation) { return indices.ContainsKey(relation); } @@ -147,24 +145,25 @@ namespace MoonTools.ECS return inRelations.TryGetValue(entityID, out var entityInRelations) ? entityInRelations.Count : 0; } - public (bool, bool) Remove(Relation relation) + public (bool, bool) Remove(in Entity entityA, in Entity entityB) { var aEmpty = false; var bEmpty = false; + var relation = (entityA, entityB); - if (outRelations.TryGetValue(relation.A.ID, out var entityOutRelations)) + if (outRelations.TryGetValue(entityA.ID, out var entityOutRelations)) { - entityOutRelations.Remove(relation.B.ID); - if (outRelations[relation.A.ID].Count == 0) + entityOutRelations.Remove(entityB.ID); + if (outRelations[entityA.ID].Count == 0) { aEmpty = true; } } - if (inRelations.TryGetValue(relation.B.ID, out var entityInRelations)) + if (inRelations.TryGetValue(entityB.ID, out var entityInRelations)) { - entityInRelations.Remove(relation.A.ID); - if (inRelations[relation.B.ID].Count == 0) + entityInRelations.Remove(entityA.ID); + if (inRelations[entityB.ID].Count == 0) { bEmpty = true; } @@ -210,12 +209,12 @@ namespace MoonTools.ECS public override unsafe void Set(int entityA, int entityB, void* relationData) { - Set(new Relation(entityA, entityB), *((TRelation*) relationData)); + Set(entityA, entityB, *((TRelation*) relationData)); } public override int GetStorageIndex(int entityA, int entityB) { - return indices[new Relation(entityA, entityB)]; + return indices[(entityA, entityB)]; } public override unsafe void* Get(int relationStorageIndex) @@ -232,7 +231,7 @@ namespace MoonTools.ECS { foreach (var entityB in entityOutRelations) { - Remove(new Relation(entityID, entityB)); + Remove(entityID, entityB); } ReturnHashSetToPool(entityOutRelations); @@ -243,7 +242,7 @@ namespace MoonTools.ECS { foreach (var entityA in entityInRelations) { - Remove(new Relation(entityA, entityID)); + Remove(entityA, entityID); } ReturnHashSetToPool(entityInRelations); diff --git a/src/System.cs b/src/System.cs index 35c44a6..37dd234 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.Set(new Relation(entityA, entityB), relationData); + RelationDepot.Set(entityA, entityB, relationData); var relationTypeIndex = RelationTypeIndices.GetIndex(); EntityStorage.AddRelationKind(entityA.ID, relationTypeIndex); EntityStorage.AddRelationKind(entityB.ID, relationTypeIndex); @@ -78,7 +78,7 @@ namespace MoonTools.ECS protected void Unrelate(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged { - var (aEmpty, bEmpty) = RelationDepot.Remove(new Relation(entityA, entityB)); + var (aEmpty, bEmpty) = RelationDepot.Remove(entityA, entityB); if (aEmpty) { diff --git a/src/TemplateStorage.cs b/src/TemplateStorage.cs index c45f32f..fb9dfa0 100644 --- a/src/TemplateStorage.cs +++ b/src/TemplateStorage.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace MoonTools.ECS { @@ -19,7 +19,7 @@ namespace MoonTools.ECS return TemplateToComponentTypeIndices[templateID].Add(componentTypeIndex); } - public IEnumerable ComponentTypeIndices(int templateID) + public HashSet ComponentTypeIndices(int templateID) { return TemplateToComponentTypeIndices[templateID]; } -- 2.25.1 From d1476d641500b7930db3f4629bc3160b9c98e2b6 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Mon, 9 Jan 2023 16:36:11 -0800 Subject: [PATCH 14/14] make note about template feature --- src/System.cs | 1 + src/Template.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/System.cs b/src/System.cs index 37dd234..6d1d4dd 100644 --- a/src/System.cs +++ b/src/System.cs @@ -26,6 +26,7 @@ namespace MoonTools.ECS protected void Set(in Template template, in TComponent component) where TComponent : unmanaged => World.Set(template, component); + // This feature is EXPERIMENTAL. USe at your own risk!! protected Entity Instantiate(in Template template) => World.Instantiate(template); protected ReadOnlySpan ReadMessages() where TMessage : unmanaged diff --git a/src/Template.cs b/src/Template.cs index c2c25b7..7654b7a 100644 --- a/src/Template.cs +++ b/src/Template.cs @@ -1,5 +1,6 @@ namespace MoonTools.ECS { + // This feature is EXPERIMENTAL. Use at your own risk!! public struct Template { public int ID { get; } -- 2.25.1