started system for efficiently doing entity queries

pull/5/head
Evan Hemsley 2019-12-20 11:10:42 -08:00
parent 664f08c446
commit 494a583240
7 changed files with 162 additions and 2 deletions

View File

@ -6,6 +6,7 @@ namespace Encompass
internal class ComponentStore
{
private Dictionary<Type, TypedComponentStore> Stores = new Dictionary<Type, TypedComponentStore>(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<TComponent>();
Stores.Add(typeof(TComponent), store);
componentBitSet.RegisterType<TComponent>();
}
}
public void FinishRegistering()
{
componentBitSet.FinishRegistering();
}
private TypedComponentStore<TComponent> Lookup<TComponent>() where TComponent : struct, IComponent
{
//RegisterComponentType<TComponent>();
@ -41,6 +47,11 @@ namespace Encompass
return Stores.ContainsKey(type) && Stores[type].Has(entity);
}
public IEnumerable<Entity> EntitiesWithComponents(IEnumerable<Type> types)
{
return componentBitSet.EntitiesWithComponents(types);
}
public TComponent Get<TComponent>(Entity entity) where TComponent : struct, IComponent
{
return Lookup<TComponent>().Get(entity);
@ -49,15 +60,18 @@ namespace Encompass
public void Set<TComponent>(Entity entity, TComponent component) where TComponent : struct, IComponent
{
Lookup<TComponent>().Set(entity, component);
componentBitSet.Set<TComponent>(entity);
}
public bool Set<TComponent>(Entity entity, TComponent component, int priority) where TComponent : struct, IComponent
{
componentBitSet.Set<TComponent>(entity);
return Lookup<TComponent>().Set(entity, component, priority);
}
public void Remove<TComponent>(Entity entity) where TComponent : struct, IComponent
{
componentBitSet.RemoveComponent<TComponent>(entity);
Lookup<TComponent>().Remove(entity);
}
@ -67,6 +81,7 @@ namespace Encompass
{
entry.Remove(entity);
}
componentBitSet.DestroyEntity(entity);
}
public bool Any<TComponent>() 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);
}
}
}

View File

@ -0,0 +1,76 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Encompass
{
internal class ComponentBitSet
{
Dictionary<Entity, BitArray> entities = new Dictionary<Entity, BitArray>();
Dictionary<Type, int> typeToIndex = new Dictionary<Type, int>();
BitArray queryArray;
public void RegisterType<TComponent>() 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<TComponent>(Entity entity) where TComponent : struct, IComponent
{
if (!entities.ContainsKey(entity)) { AddEntity(entity); }
entities[entity].Set(typeToIndex[typeof(TComponent)], true);
}
public void RemoveComponent<TComponent>(Entity entity) where TComponent : struct, IComponent
{
entities[entity].Set(typeToIndex[typeof(TComponent)], false);
}
public void DestroyEntity(Entity entity)
{
entities.Remove(entity);
}
public IEnumerable<Entity> EntitiesWithComponents(IEnumerable<Type> 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; }
}
}
}
}

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
namespace Encompass
@ -21,6 +22,11 @@ namespace Encompass
componentStore.RegisterComponentType<TComponent>();
}
public void FinishRegistering()
{
componentStore.FinishRegistering();
}
internal void SetComponentStore(ComponentStore componentStore)
{
this.componentStore.SwapWith(componentStore);
@ -90,5 +96,10 @@ namespace Encompass
componentUpdateManager.Remove<TComponent>(entity);
drawLayerManager.UnRegisterComponentWithLayer<TComponent>(entity);
}
public IEnumerable<Entity> EntitiesWithComponents(IEnumerable<Type> types)
{
return componentStore.EntitiesWithComponents(types);
}
}
}

View File

@ -20,6 +20,14 @@ namespace Encompass
UpToDateComponentStore.RegisterComponentType<TComponent>();
}
public void FinishRegistering()
{
existingAndPendingComponentStore.FinishRegistering();
existingComponentStore.FinishRegistering();
pendingComponentStore.FinishRegistering();
UpToDateComponentStore.FinishRegistering();
}
internal void Clear()
{
existingAndPendingComponentStore.ClearAll();

View File

@ -611,5 +611,10 @@ namespace Encompass
{
timeManager.ActivateTimeDilation(factor, easeInTime, easeInFunction, activeTime, easeOutTime, easeOutFunction);
}
public IEnumerable<Entity> EntitiesWithComponents(IEnumerable<Type> types)
{
return componentManager.EntitiesWithComponents(types);
}
}
}

View File

@ -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
/// <summary>
/// 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.
/// </summary>
private void PreloadJIT(IEnumerable<Type> componentTypes, IEnumerable<Type> messageTypes)

View File

@ -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 });
}
}
}