diff --git a/encompass-cs/Collections/ComponentStore.cs b/encompass-cs/Collections/ComponentStore.cs index 3283baf..f5c3b31 100644 --- a/encompass-cs/Collections/ComponentStore.cs +++ b/encompass-cs/Collections/ComponentStore.cs @@ -6,6 +6,7 @@ namespace Encompass internal class ComponentStore { private Dictionary Stores = new Dictionary(512); + private ComponentBitSet componentBitSet = new ComponentBitSet(); public IEnumerable<(Type, TypedComponentStore)> StoresEnumerable() { @@ -19,12 +20,17 @@ namespace Encompass { if (!Stores.ContainsKey(typeof(TComponent))) { - System.Console.WriteLine("register component type in component store"); var store = new TypedComponentStore(); Stores.Add(typeof(TComponent), store); + componentBitSet.RegisterType(); } } + public void FinishRegistering() + { + componentBitSet.FinishRegistering(); + } + private TypedComponentStore Lookup() where TComponent : struct, IComponent { //RegisterComponentType(); @@ -41,6 +47,11 @@ namespace Encompass return Stores.ContainsKey(type) && Stores[type].Has(entity); } + public IEnumerable EntitiesWithComponents(IEnumerable types) + { + return componentBitSet.EntitiesWithComponents(types); + } + public TComponent Get(Entity entity) where TComponent : struct, IComponent { return Lookup().Get(entity); @@ -49,15 +60,18 @@ namespace Encompass public void Set(Entity entity, TComponent component) where TComponent : struct, IComponent { Lookup().Set(entity, component); + componentBitSet.Set(entity); } public bool Set(Entity entity, TComponent component, int priority) where TComponent : struct, IComponent { + componentBitSet.Set(entity); return Lookup().Set(entity, component, priority); } public void Remove(Entity entity) where TComponent : struct, IComponent { + componentBitSet.RemoveComponent(entity); Lookup().Remove(entity); } @@ -67,6 +81,7 @@ namespace Encompass { entry.Remove(entity); } + componentBitSet.DestroyEntity(entity); } public bool Any() where TComponent : struct, IComponent @@ -97,6 +112,7 @@ namespace Encompass public void ClearAll() { + componentBitSet.Clear(); foreach (var store in Stores.Values) { store.Clear(); @@ -106,6 +122,7 @@ namespace Encompass public void SwapWith(ComponentStore other) { (Stores, other.Stores) = (other.Stores, Stores); + (componentBitSet, other.componentBitSet) = (other.componentBitSet, componentBitSet); } } } diff --git a/encompass-cs/ComponentBitSet.cs b/encompass-cs/ComponentBitSet.cs new file mode 100644 index 0000000..ea44dc4 --- /dev/null +++ b/encompass-cs/ComponentBitSet.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Encompass +{ + internal class ComponentBitSet + { + Dictionary entities = new Dictionary(); + Dictionary typeToIndex = new Dictionary(); + BitArray queryArray; + + public void RegisterType() where TComponent : struct, IComponent + { + typeToIndex.Add(typeof(TComponent), typeToIndex.Count); + foreach (var kvp in entities) + { + kvp.Value.Length = typeToIndex.Count; + } + } + + public void FinishRegistering() + { + queryArray = new BitArray(typeToIndex.Count); + } + + public void Clear() + { + entities.Clear(); + } + + public void AddEntity(Entity entity) + { + var bitArray = new BitArray(typeToIndex.Count); + entities.Add(entity, bitArray); // this is gonna create garbage!! fuck!! + } + + public void Set(Entity entity) where TComponent : struct, IComponent + { + if (!entities.ContainsKey(entity)) { AddEntity(entity); } + entities[entity].Set(typeToIndex[typeof(TComponent)], true); + } + + public void RemoveComponent(Entity entity) where TComponent : struct, IComponent + { + entities[entity].Set(typeToIndex[typeof(TComponent)], false); + } + + public void DestroyEntity(Entity entity) + { + entities.Remove(entity); + } + + public IEnumerable EntitiesWithComponents(IEnumerable types) + { + foreach (var kvp in entities) + { + queryArray.SetAll(false); + foreach (var type in types) + { + queryArray.Set(typeToIndex[type], true); + } + queryArray.And(kvp.Value); + var hasComponents = true; + foreach (var type in types) + { + if (!queryArray.Get(typeToIndex[type])) { + hasComponents = false; + break; + } + } + if (hasComponents) { yield return kvp.Key; } + } + } + } +} diff --git a/encompass-cs/ComponentManager.cs b/encompass-cs/ComponentManager.cs index cf105c5..bc8f304 100644 --- a/encompass-cs/ComponentManager.cs +++ b/encompass-cs/ComponentManager.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace Encompass @@ -21,6 +22,11 @@ namespace Encompass componentStore.RegisterComponentType(); } + public void FinishRegistering() + { + componentStore.FinishRegistering(); + } + internal void SetComponentStore(ComponentStore componentStore) { this.componentStore.SwapWith(componentStore); @@ -90,5 +96,10 @@ namespace Encompass componentUpdateManager.Remove(entity); drawLayerManager.UnRegisterComponentWithLayer(entity); } + + public IEnumerable EntitiesWithComponents(IEnumerable types) + { + return componentStore.EntitiesWithComponents(types); + } } } diff --git a/encompass-cs/ComponentUpdateManager.cs b/encompass-cs/ComponentUpdateManager.cs index 5897b13..5c01b96 100644 --- a/encompass-cs/ComponentUpdateManager.cs +++ b/encompass-cs/ComponentUpdateManager.cs @@ -20,6 +20,14 @@ namespace Encompass UpToDateComponentStore.RegisterComponentType(); } + public void FinishRegistering() + { + existingAndPendingComponentStore.FinishRegistering(); + existingComponentStore.FinishRegistering(); + pendingComponentStore.FinishRegistering(); + UpToDateComponentStore.FinishRegistering(); + } + internal void Clear() { existingAndPendingComponentStore.ClearAll(); diff --git a/encompass-cs/Engine.cs b/encompass-cs/Engine.cs index 56a5c1a..e89ea34 100644 --- a/encompass-cs/Engine.cs +++ b/encompass-cs/Engine.cs @@ -611,5 +611,10 @@ namespace Encompass { timeManager.ActivateTimeDilation(factor, easeInTime, easeInFunction, activeTime, easeOutTime, easeOutFunction); } + + public IEnumerable EntitiesWithComponents(IEnumerable types) + { + return componentManager.EntitiesWithComponents(types); + } } } diff --git a/encompass-cs/WorldBuilder.cs b/encompass-cs/WorldBuilder.cs index 90b1eea..664ee8b 100644 --- a/encompass-cs/WorldBuilder.cs +++ b/encompass-cs/WorldBuilder.cs @@ -363,6 +363,11 @@ namespace Encompass PreloadJIT(componentTypesToRegister, messageTypes); + componentManager.FinishRegistering(); + componentUpdateManager.FinishRegistering(); + startingComponentStoreForComponentManager.FinishRegistering(); + startingComponentStoreForComponentUpdateManager.FinishRegistering(); + foreach (var engine in engineGraph.TopologicalSort()) { engineOrder.Add(engine); @@ -387,7 +392,7 @@ namespace Encompass /// /// This is necessary because Encompass heavily uses generic methods with value types, /// so the first time any code path runs the JIT gets smashed. This method warms up the runtime. - /// It does so by grabbing all component and message types known to the WorldBuilder and + /// It does so by grabbing all component and message types known to the WorldBuilder and /// executing every possible generic method that could be executed with those types. /// private void PreloadJIT(IEnumerable componentTypes, IEnumerable messageTypes) diff --git a/test/EngineTest.cs b/test/EngineTest.cs index 1edd31f..0a0464c 100644 --- a/test/EngineTest.cs +++ b/test/EngineTest.cs @@ -1087,5 +1087,43 @@ namespace Tests undilatedDeltaTime.Should().Be(0.5); } + + struct MockComponentB : IComponent { } + + static Entity[] queriedEntities; + + class EntityQueryEngine : Engine + { + public override void Update(double dt) + { + queriedEntities = EntitiesWithComponents(new Type[] { typeof(MockComponent), typeof(MockComponentB) }).ToArray(); + } + } + + [Test] + public void EntitiesWithComponents() + { + var worldBuilder = new WorldBuilder(); + + var entity = worldBuilder.CreateEntity(); + var entityB = worldBuilder.CreateEntity(); + var entityC = worldBuilder.CreateEntity(); + + worldBuilder.SetComponent(entity, new MockComponent()); + worldBuilder.SetComponent(entity, new MockComponentB()); + + worldBuilder.SetComponent(entityB, new MockComponent()); + worldBuilder.SetComponent(entityB, new MockComponentB()); + + worldBuilder.SetComponent(entityC, new MockComponentB()); + + worldBuilder.AddEngine(new EntityQueryEngine()); + + var world = worldBuilder.Build(); + + world.Update(0.01); + + queriedEntities.Should().BeEquivalentTo(new Entity[] { entity, entityB }); + } } }