From 1e73351b07e9f796df7d579e22037b2ce4000d09 Mon Sep 17 00:00:00 2001 From: Evan Hemsley <2342303+ehemsley@users.noreply.github.com> Date: Sun, 22 Dec 2019 18:13:35 -0800 Subject: [PATCH] restructure entity queries to enable caching --- encompass-cs/Attributes/QueryWith.cs | 28 +++++++++ encompass-cs/Attributes/QueryWithout.cs | 28 +++++++++ encompass-cs/Collections/ComponentStore.cs | 20 +++---- encompass-cs/ComponentUpdateManager.cs | 11 +--- encompass-cs/Engine.cs | 62 +++++++++++++++++-- encompass-cs/EntitySetQuery.cs | 25 +++----- encompass-cs/EntitySetQueryBuilder.cs | 70 ---------------------- encompass-cs/WorldBuilder.cs | 4 +- test/EngineTest.cs | 41 +++++++------ 9 files changed, 156 insertions(+), 133 deletions(-) create mode 100644 encompass-cs/Attributes/QueryWith.cs create mode 100644 encompass-cs/Attributes/QueryWithout.cs delete mode 100644 encompass-cs/EntitySetQueryBuilder.cs diff --git a/encompass-cs/Attributes/QueryWith.cs b/encompass-cs/Attributes/QueryWith.cs new file mode 100644 index 0000000..3ae11a6 --- /dev/null +++ b/encompass-cs/Attributes/QueryWith.cs @@ -0,0 +1,28 @@ +using Encompass.Exceptions; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Encompass +{ + [AttributeUsage(AttributeTargets.Class)] + public class QueryWith : Attribute + { + public readonly HashSet queryWithTypes = new HashSet(); + + public QueryWith(params Type[] queryWithTypes) + { + foreach (var queryWithType in queryWithTypes) + { + var isComponent = queryWithType.GetInterfaces().Contains(typeof(IComponent)); + + if (!isComponent) + { + throw new IllegalReadTypeException("{0} must be a Component", queryWithType.Name); + } + + this.queryWithTypes.Add(queryWithType); + } + } + } +} diff --git a/encompass-cs/Attributes/QueryWithout.cs b/encompass-cs/Attributes/QueryWithout.cs new file mode 100644 index 0000000..77f1e9f --- /dev/null +++ b/encompass-cs/Attributes/QueryWithout.cs @@ -0,0 +1,28 @@ +using Encompass.Exceptions; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Encompass +{ + [AttributeUsage(AttributeTargets.Class)] + public class QueryWithout : Attribute + { + public readonly HashSet queryWithoutTypes = new HashSet(); + + public QueryWithout(params Type[] queryWithoutTypes) + { + foreach (var type in queryWithoutTypes) + { + var isComponent = type.GetInterfaces().Contains(typeof(IComponent)); + + if (!isComponent) + { + throw new IllegalReadTypeException("{0} must be a Component", type.Name); + } + + this.queryWithoutTypes.Add(type); + } + } + } +} diff --git a/encompass-cs/Collections/ComponentStore.cs b/encompass-cs/Collections/ComponentStore.cs index 6e44131..5d3f09a 100644 --- a/encompass-cs/Collections/ComponentStore.cs +++ b/encompass-cs/Collections/ComponentStore.cs @@ -7,11 +7,11 @@ namespace Encompass internal class ComponentStore { private Dictionary Stores = new Dictionary(512); - private ComponentBitSet componentBitSet; + public ComponentBitSet ComponentBitSet { get; private set; } public ComponentStore(Dictionary typeToIndex) { - componentBitSet = new ComponentBitSet(typeToIndex); + ComponentBitSet = new ComponentBitSet(typeToIndex); } public IEnumerable<(Type, TypedComponentStore)> StoresEnumerable() @@ -33,7 +33,7 @@ namespace Encompass public void FinishRegistering() { - componentBitSet.FinishRegistering(); + ComponentBitSet.FinishRegistering(); } private TypedComponentStore Lookup() where TComponent : struct, IComponent @@ -54,7 +54,7 @@ namespace Encompass public BitSet1024 EntityBitArray(Entity entity) { - return componentBitSet.EntityBitArray(entity); + return ComponentBitSet.EntityBitArray(entity); } public TComponent Get(Entity entity) where TComponent : struct, IComponent @@ -65,18 +65,18 @@ namespace Encompass public void Set(Entity entity, TComponent component) where TComponent : struct, IComponent { Lookup().Set(entity, component); - componentBitSet.Set(entity); + ComponentBitSet.Set(entity); } public bool Set(Entity entity, TComponent component, int priority) where TComponent : struct, IComponent { - componentBitSet.Set(entity); + ComponentBitSet.Set(entity); return Lookup().Set(entity, component, priority); } public void Remove(Entity entity) where TComponent : struct, IComponent { - componentBitSet.RemoveComponent(entity); + ComponentBitSet.RemoveComponent(entity); Lookup().Remove(entity); } @@ -86,7 +86,7 @@ namespace Encompass { entry.Remove(entity); } - componentBitSet.RemoveEntity(entity); + ComponentBitSet.RemoveEntity(entity); } public bool Any() where TComponent : struct, IComponent @@ -117,7 +117,7 @@ namespace Encompass public void ClearAll() { - componentBitSet.Clear(); + ComponentBitSet.Clear(); foreach (var store in Stores.Values) { store.Clear(); @@ -127,7 +127,7 @@ namespace Encompass public void SwapWith(ComponentStore other) { (Stores, other.Stores) = (other.Stores, Stores); - (componentBitSet, other.componentBitSet) = (other.componentBitSet, componentBitSet); + (ComponentBitSet, other.ComponentBitSet) = (other.ComponentBitSet, ComponentBitSet); } } } diff --git a/encompass-cs/ComponentUpdateManager.cs b/encompass-cs/ComponentUpdateManager.cs index 8b371ee..d0ee527 100644 --- a/encompass-cs/ComponentUpdateManager.cs +++ b/encompass-cs/ComponentUpdateManager.cs @@ -197,14 +197,7 @@ namespace Encompass UpToDateComponentStore.Remove(entity); } - internal BitSet1024 PendingBits(Entity entity) - { - return pendingComponentStore.EntityBitArray(entity); - } - - internal BitSet1024 ExistingBits(Entity entity) - { - return existingComponentStore.EntityBitArray(entity); - } + internal ComponentBitSet PendingBits { get { return pendingComponentStore.ComponentBitSet; } } + internal ComponentBitSet ExistingBits { get { return existingComponentStore.ComponentBitSet; } } } } diff --git a/encompass-cs/Engine.cs b/encompass-cs/Engine.cs index dfb3f35..8e0a8ae 100644 --- a/encompass-cs/Engine.cs +++ b/encompass-cs/Engine.cs @@ -3,6 +3,7 @@ using System.Reflection; using System.Collections.Generic; using System.Linq; using Encompass.Exceptions; +using Encompass.Collections; namespace Encompass { @@ -21,6 +22,8 @@ namespace Encompass internal readonly HashSet receiveTypes = new HashSet(); internal readonly HashSet writeTypes = new HashSet(); internal readonly HashSet writePendingTypes = new HashSet(); + internal readonly HashSet queryWithTypes = new HashSet(); + internal readonly HashSet queryWithoutTypes = new HashSet(); internal readonly Dictionary writePriorities = new Dictionary(); internal readonly int defaultWritePriority = 0; @@ -36,6 +39,8 @@ namespace Encompass private ComponentUpdateManager componentUpdateManager; private TimeManager timeManager; + private EntitySetQuery entityQuery; + protected Engine() { ID = Guid.NewGuid(); @@ -82,6 +87,18 @@ namespace Encompass { readPendingTypes = readsPendingAttribute.readPendingTypes; } + + var queryWithAttribute = GetType().GetCustomAttribute(false); + if (queryWithAttribute != null) + { + queryWithTypes = queryWithAttribute.queryWithTypes; + } + + var queryWithoutAttribute = GetType().GetCustomAttribute(false); + if (queryWithoutAttribute != null) + { + queryWithoutTypes = queryWithoutAttribute.queryWithoutTypes; + } } public override bool Equals(object obj) @@ -612,16 +629,49 @@ namespace Encompass timeManager.ActivateTimeDilation(factor, easeInTime, easeInFunction, activeTime, easeOutTime, easeOutFunction); } + protected IEnumerable QueryEntities() + { + foreach (var entity in entityQuery.FilterEntities(entityManager.Entities, componentUpdateManager.PendingBits, componentUpdateManager.ExistingBits)) + { + yield return entity; + } + } + /// /// Returns an empty EntitySetQuery. Can be modified and iterated over to obtain Entities that fit the given criteria. /// - protected EntitySetQueryBuilder EntityQueryBuilder() + internal void BuildEntityQuery() { - return new EntitySetQueryBuilder( - entityManager, - componentUpdateManager, - readPendingTypes, - readTypes + var withMask = BitSet1024Builder.Zeroes(); + foreach (var type in queryWithTypes) + { + withMask = withMask.Set(componentUpdateManager.TypeToIndex[type]); + } + + var withoutMask = BitSet1024Builder.Zeroes(); + foreach (var type in queryWithoutTypes) + { + withoutMask = withoutMask.Set(componentUpdateManager.TypeToIndex[type]); + } + + var pendingMask = BitSet1024Builder.Zeroes(); + foreach (var type in readPendingTypes) + { + pendingMask = pendingMask.Set(componentUpdateManager.TypeToIndex[type]); + } + + var existingMask = BitSet1024Builder.Zeroes(); + foreach (var type in readTypes) + { + existingMask = existingMask.Set(componentUpdateManager.TypeToIndex[type]); + } + + entityQuery = new EntitySetQuery( + withMask.And(pendingMask), + withMask.And(existingMask), + withoutMask.And(pendingMask), + withoutMask.And(existingMask), + withMask.Not() ); } } diff --git a/encompass-cs/EntitySetQuery.cs b/encompass-cs/EntitySetQuery.cs index 60bd110..899ddcc 100644 --- a/encompass-cs/EntitySetQuery.cs +++ b/encompass-cs/EntitySetQuery.cs @@ -4,20 +4,16 @@ using Encompass.Collections; namespace Encompass { - public struct EntitySetQuery : IEnumerable + internal struct EntitySetQuery { - private EntityManager EntityManager { get; } - private ComponentUpdateManager ComponentUpdateManager { get; } private BitSet1024 WithPendingMask { get; } private BitSet1024 WithExistingMask { get; } private BitSet1024 WithoutPendingMask { get; } private BitSet1024 WithoutExistingMask { get; } private BitSet1024 NotWithMask { get; } - internal EntitySetQuery(EntityManager entityManager, ComponentUpdateManager componentUpdateManager, BitSet1024 withPendingMask, BitSet1024 withExistingMask, BitSet1024 withoutPendingMask, BitSet1024 withoutExistingMask, BitSet1024 notWithMask) + internal EntitySetQuery(BitSet1024 withPendingMask, BitSet1024 withExistingMask, BitSet1024 withoutPendingMask, BitSet1024 withoutExistingMask, BitSet1024 notWithMask) { - EntityManager = entityManager; - ComponentUpdateManager = componentUpdateManager; WithPendingMask = withPendingMask; WithExistingMask = withExistingMask; WithoutPendingMask = withoutPendingMask; @@ -25,28 +21,23 @@ namespace Encompass NotWithMask = notWithMask; } - public IEnumerator GetEnumerator() + public IEnumerable FilterEntities(IEnumerable entities, ComponentBitSet pendingBitLookup, ComponentBitSet existingBitLookup) { - foreach (var entity in EntityManager.Entities) + foreach (var entity in entities) { - var pendingBits = ComponentUpdateManager.PendingBits(entity); - var existingBits = ComponentUpdateManager.ExistingBits(entity); + var pendingBits = pendingBitLookup.EntityBitArray(entity); + var existingBits = existingBitLookup.EntityBitArray(entity); var pending = WithPendingMask.And(pendingBits); var existing = WithExistingMask.And(existingBits); var withCheck = pending.Or(existing).Or(NotWithMask); - var pendingForbidden = WithoutPendingMask.And(pendingBits); - var existingForbidden = WithoutExistingMask.And(existingBits); + var pendingForbidden = WithoutPendingMask.And(pendingBits).Not(); + var existingForbidden = WithoutExistingMask.And(existingBits).Not(); var withoutCheck = pendingForbidden.And(existingForbidden); if (withCheck.And(withoutCheck).AllTrue()) { yield return entity; } } } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } } } diff --git a/encompass-cs/EntitySetQueryBuilder.cs b/encompass-cs/EntitySetQueryBuilder.cs deleted file mode 100644 index 00452c5..0000000 --- a/encompass-cs/EntitySetQueryBuilder.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Collections.Generic; -using Encompass.Collections; - -namespace Encompass -{ - /// - /// EntitySetQuery is used to efficiently obtain a set of Entities that have all required Components and do not have any forbidden Components. - /// - public class EntitySetQueryBuilder - { - internal EntitySetQueryBuilder(EntityManager entityManager, ComponentUpdateManager componentUpdateManager, IEnumerable pendingTypes, IEnumerable existingTypes) - { - EntityManager = entityManager; - ComponentUpdateManager = componentUpdateManager; - WithMask = BitSet1024Builder.Zeroes(); - WithoutMask = BitSet1024Builder.Zeroes(); - - PendingMask = BitSet1024Builder.Zeroes(); - foreach (var type in pendingTypes) - { - PendingMask = PendingMask.Set(componentUpdateManager.TypeToIndex[type]); - } - - ExistingMask = BitSet1024Builder.Zeroes(); - foreach (var type in existingTypes) - { - ExistingMask = ExistingMask.Set(componentUpdateManager.TypeToIndex[type]); - } - } - - private EntityManager EntityManager { get; } - private ComponentUpdateManager ComponentUpdateManager { get; } - private BitSet1024 WithMask { get; set; } - private BitSet1024 WithoutMask { get; set; } - private BitSet1024 PendingMask { get; set; } - private BitSet1024 ExistingMask { get; set; } - - /// - /// Designates that the given component type is required. - /// - public EntitySetQueryBuilder With() where TComponent : struct, IComponent - { - WithMask = WithMask.Set(ComponentUpdateManager.TypeToIndex[typeof(TComponent)]); - return this; - } - - /// - /// Designates that the given component type is forbidden. - /// - public EntitySetQueryBuilder Without() where TComponent : struct, IComponent - { - WithoutMask = WithoutMask.Set(ComponentUpdateManager.TypeToIndex[typeof(TComponent)]); - return this; - } - - public EntitySetQuery Build() - { - return new EntitySetQuery( - EntityManager, - ComponentUpdateManager, - WithMask.And(PendingMask), - WithMask.And(ExistingMask), - WithoutMask.And(PendingMask), - WithoutMask.And(ExistingMask), - WithMask.Not() - ); - } - } -} diff --git a/encompass-cs/WorldBuilder.cs b/encompass-cs/WorldBuilder.cs index 1e88a88..3c82f8c 100644 --- a/encompass-cs/WorldBuilder.cs +++ b/encompass-cs/WorldBuilder.cs @@ -173,9 +173,6 @@ namespace Encompass typeToReaders[receiveType].Add(engine); } - // System.Runtime.CompilerServices.RuntimeHelpers.PrepareMethod(typeof(TEngine).GetRuntimeMethod("Update", new Type[] { typeof(double) }).MethodHandle); - // typeof(TEngine).GetMethod("Update", new Type[] { typeof(double) }).MethodHandle.GetFunctionPointer(); - return engine; } @@ -385,6 +382,7 @@ namespace Encompass foreach (var engine in engineGraph.TopologicalSort()) { engineOrder.Add(engine); + engine.BuildEntityQuery(); } var world = new World( diff --git a/test/EngineTest.cs b/test/EngineTest.cs index b36541d..1b36ce6 100644 --- a/test/EngineTest.cs +++ b/test/EngineTest.cs @@ -1095,6 +1095,7 @@ namespace Tests struct MockComponentD : IComponent { } [Reads(typeof(MockComponent), typeof(MockComponentB))] + [QueryWith(typeof(MockComponent), typeof(MockComponentB))] class EntityQueryWithComponentsEngine : Engine { private List entities; @@ -1107,8 +1108,10 @@ namespace Tests public override void Update(double dt) { entities.Clear(); - - entities.AddRange(QueryEntities().With().With()); + foreach (var entity in QueryEntities()) + { + entities.Add(entity); + } } } @@ -1140,6 +1143,7 @@ namespace Tests } [Reads(typeof(MockComponent))] + [QueryWithout(typeof(MockComponent))] class EntityQueryWithoutComponentsEngine : Engine { private List entities; @@ -1152,8 +1156,7 @@ namespace Tests public override void Update(double dt) { entities.Clear(); - - entities.AddRange(QueryEntities().Without()); + entities.AddRange(QueryEntities()); } } @@ -1185,6 +1188,8 @@ namespace Tests } [Reads(typeof(MockComponent), typeof(MockComponentB), typeof(MockComponentD))] + [QueryWith(typeof(MockComponent), typeof(MockComponentB))] + [QueryWithout(typeof(MockComponentD))] class EntityQueryWithandWithoutComponentsEngine : Engine { private List entities; @@ -1198,9 +1203,7 @@ namespace Tests { entities.Clear(); - entities.AddRange(QueryEntities().With() - .With() - .Without()); + entities.AddRange(QueryEntities()); } } @@ -1246,7 +1249,7 @@ namespace Tests { public override void Update(double dt) { - foreach (var entity in QueryEntities().With()) + foreach (var entity in ReadEntities()) { SetComponent(entity, new MockComponentB()); } @@ -1254,6 +1257,7 @@ namespace Tests } [ReadsPending(typeof(MockComponentB))] + [QueryWith(typeof(MockComponentB))] class EntityQueryWithPendingComponentsEngine : Engine { private List entities; @@ -1266,8 +1270,7 @@ namespace Tests public override void Update(double dt) { entities.Clear(); - - entities.AddRange(QueryEntities().With()); + entities.AddRange(QueryEntities()); } } @@ -1293,6 +1296,7 @@ namespace Tests } [ReadsPending(typeof(MockComponentB))] + [QueryWithout(typeof(MockComponentB))] class EntityQueryWithoutPendingComponentsEngine : Engine { private List entities; @@ -1305,8 +1309,7 @@ namespace Tests public override void Update(double dt) { entities.Clear(); - - entities.AddRange(QueryEntities().Without()); + entities.AddRange(QueryEntities()); } } @@ -1339,12 +1342,12 @@ namespace Tests { public override void Update(double dt) { - foreach (var entity in QueryEntities().With()) + foreach (var entity in ReadEntities()) { SetComponent(entity, new MockComponent()); } - foreach (var entity in QueryEntities().With()) + foreach (var entity in ReadEntities()) { SetComponent(entity, new MockComponent()); SetComponent(entity, new MockComponentB()); @@ -1353,6 +1356,8 @@ namespace Tests } [ReadsPending(typeof(MockComponent), typeof(MockComponentB))] + [QueryWith(typeof(MockComponent))] + [QueryWithout(typeof(MockComponentB))] class EntityQueryWithAndWithoutPendingComponentsEngine : Engine { private List entities; @@ -1366,7 +1371,7 @@ namespace Tests { entities.Clear(); - entities.AddRange(QueryEntities().With().Without()); + entities.AddRange(QueryEntities()); } } @@ -1400,7 +1405,7 @@ namespace Tests { public override void Update(double dt) { - foreach (var entity in QueryEntities().With()) + foreach (var entity in ReadEntities()) { SetComponent(entity, new MockComponentB()); } @@ -1409,6 +1414,7 @@ namespace Tests [ReadsPending(typeof(MockComponentB))] [Reads(typeof(MockComponent))] + [QueryWith(typeof(MockComponent), typeof(MockComponentB))] class EntityQueryWithPendingAndNonPendingComponents : Engine { private List entities; @@ -1421,8 +1427,7 @@ namespace Tests public override void Update(double dt) { entities.Clear(); - - entities.AddRange(QueryEntities().With().With()); + entities.AddRange(QueryEntities()); } }