advanced query system

pull/5/head
Evan Hemsley 2019-12-22 01:15:58 -08:00
parent 728109bfc6
commit 50974c181a
10 changed files with 317 additions and 99 deletions

View File

@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Encompass
@ -6,7 +7,12 @@ namespace Encompass
internal class ComponentStore
{
private Dictionary<Type, TypedComponentStore> Stores = new Dictionary<Type, TypedComponentStore>(512);
private ComponentBitSet componentBitSet = new ComponentBitSet();
private ComponentBitSet componentBitSet;
public ComponentStore(Dictionary<Type, int> typeToIndex)
{
componentBitSet = new ComponentBitSet(typeToIndex);
}
public IEnumerable<(Type, TypedComponentStore)> StoresEnumerable()
{
@ -22,7 +28,6 @@ namespace Encompass
{
var store = new TypedComponentStore<TComponent>();
Stores.Add(typeof(TComponent), store);
componentBitSet.RegisterType<TComponent>();
}
}
@ -47,9 +52,9 @@ namespace Encompass
return Stores.ContainsKey(type) && Stores[type].Has(entity);
}
public IEnumerable<Entity> EntitiesWithComponents(IEnumerable<Type> types)
public BitArray EntityBitArray(Entity entity)
{
return componentBitSet.EntitiesWithComponents(types);
return componentBitSet.EntityBitArray(entity);
}
public TComponent Get<TComponent>(Entity entity) where TComponent : struct, IComponent

View File

@ -8,21 +8,25 @@ namespace Encompass
{
BitArrayPool bitArrayPool = new BitArrayPool(32768); // todo: set entity cap
Dictionary<Entity, BitArray> entities = new Dictionary<Entity, BitArray>();
Dictionary<Type, int> typeToIndex = new Dictionary<Type, int>();
BitArray queryArray;
Dictionary<Type, int> TypeToIndex { get; set; }
BitArray withQueryArray;
BitArray withoutQueryArray;
BitArray emptyArray;
public void RegisterType<TComponent>() where TComponent : struct, IComponent
public ComponentBitSet(Dictionary<Type, int> typeToIndex)
{
typeToIndex.Add(typeof(TComponent), typeToIndex.Count);
foreach (var kvp in entities)
{
kvp.Value.Length = typeToIndex.Count;
}
TypeToIndex = typeToIndex;
}
public void FinishRegistering()
{
queryArray = new BitArray(typeToIndex.Count);
withQueryArray = new BitArray(TypeToIndex.Count);
withoutQueryArray = new BitArray(TypeToIndex.Count);
emptyArray = new BitArray(TypeToIndex.Count);
foreach (var kvp in entities)
{
kvp.Value.Length = TypeToIndex.Count;
}
}
public void Clear()
@ -36,19 +40,19 @@ namespace Encompass
public void AddEntity(Entity entity)
{
var bitArray = bitArrayPool.Obtain(typeToIndex.Count);
var bitArray = bitArrayPool.Obtain(TypeToIndex.Count);
entities.Add(entity, bitArray);
}
public void Set<TComponent>(Entity entity) where TComponent : struct, IComponent
{
if (!entities.ContainsKey(entity)) { AddEntity(entity); }
entities[entity].Set(typeToIndex[typeof(TComponent)], true);
entities[entity].Set(TypeToIndex[typeof(TComponent)], true);
}
public void RemoveComponent<TComponent>(Entity entity) where TComponent : struct, IComponent
{
entities[entity].Set(typeToIndex[typeof(TComponent)], false);
entities[entity].Set(TypeToIndex[typeof(TComponent)], false);
}
public void RemoveEntity(Entity entity)
@ -60,26 +64,9 @@ namespace Encompass
}
}
public IEnumerable<Entity> EntitiesWithComponents(IEnumerable<Type> types)
public BitArray EntityBitArray(Entity entity)
{
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; }
}
return entities.ContainsKey(entity) ? entities[entity] : emptyArray;
}
}
}

View File

@ -8,13 +8,14 @@ namespace Encompass
private readonly DrawLayerManager drawLayerManager;
private readonly ComponentUpdateManager componentUpdateManager;
private readonly ComponentStore componentStore = new ComponentStore();
private readonly ComponentStore componentStore;
private readonly HashSet<Entity> entitiesMarkedForRemoval = new HashSet<Entity>();
public ComponentManager(DrawLayerManager drawLayerManager, ComponentUpdateManager componentUpdateManager)
public ComponentManager(DrawLayerManager drawLayerManager, ComponentUpdateManager componentUpdateManager, Dictionary<Type, int> typeToIndex)
{
this.drawLayerManager = drawLayerManager;
this.componentUpdateManager = componentUpdateManager;
componentStore = new ComponentStore(typeToIndex);
}
public void RegisterComponentType<TComponent>() where TComponent : struct, IComponent
@ -96,10 +97,5 @@ namespace Encompass
componentUpdateManager.Remove<TComponent>(entity);
drawLayerManager.UnRegisterComponentWithLayer<TComponent>(entity);
}
public IEnumerable<Entity> EntitiesWithComponents(IEnumerable<Type> types)
{
return componentStore.EntitiesWithComponents(types);
}
}
}

View File

@ -1,16 +1,27 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Encompass
{
internal class ComponentUpdateManager
{
private readonly ComponentStore existingAndPendingComponentStore = new ComponentStore();
private readonly ComponentStore existingComponentStore = new ComponentStore();
private readonly ComponentStore pendingComponentStore = new ComponentStore();
private readonly ComponentStore existingAndPendingComponentStore;
private readonly ComponentStore existingComponentStore;
private readonly ComponentStore pendingComponentStore;
private readonly Dictionary<Type, Dictionary<Entity, int>> typeToEntityToPendingComponentPriority = new Dictionary<Type, Dictionary<Entity, int>>(128);
private Dictionary<Type, int> typeToIndex;
public ComponentStore UpToDateComponentStore { get; private set; }
public ComponentStore UpToDateComponentStore { get; private set; } = new ComponentStore();
public ComponentUpdateManager(Dictionary<Type, int> typeToIndex)
{
existingAndPendingComponentStore = new ComponentStore(typeToIndex);
existingComponentStore = new ComponentStore(typeToIndex);
pendingComponentStore = new ComponentStore(typeToIndex);
UpToDateComponentStore = new ComponentStore(typeToIndex);
this.typeToIndex = typeToIndex;
}
public void RegisterComponentType<TComponent>() where TComponent : struct, IComponent
{
@ -183,5 +194,63 @@ namespace Encompass
{
UpToDateComponentStore.Remove<TComponent>(entity);
}
internal IEnumerable<Entity> QueryEntities(IEnumerable<Entity> entities, HashSet<Type> readTypes, HashSet<Type> readPendingTypes, IEnumerable<Type> withTypes, IEnumerable<Type> withoutTypes)
{
var pendingMask = new BitArray(typeToIndex.Count);
foreach (var type in readPendingTypes)
{
pendingMask.Set(typeToIndex[type], true);
}
var readMask = new BitArray(typeToIndex.Count);
foreach (var type in readTypes)
{
readMask.Set(typeToIndex[type], true);
}
var withMask = new BitArray(typeToIndex.Count);
foreach (var type in withTypes)
{
withMask.Set(typeToIndex[type], true);
}
var withoutMask = new BitArray(typeToIndex.Count);
foreach (var type in withoutTypes)
{
withoutMask.Set(typeToIndex[type], true);
}
foreach (var entity in entities)
{
var pendingEntity = pendingComponentStore.EntityBitArray(entity);
var existingEntity = existingComponentStore.EntityBitArray(entity);
if (VerifyTypes(pendingEntity, existingEntity, readMask, pendingMask, withMask, withoutMask)) { yield return entity; }
}
}
internal bool VerifyTypes(BitArray pendingEntity, BitArray existingEntity, BitArray readMask, BitArray pendingMask, BitArray withMask, BitArray withoutMask)
{
var arrayA = new BitArray(typeToIndex.Count);
var arrayB = new BitArray(typeToIndex.Count);
var arrayC = new BitArray(typeToIndex.Count);
var notWithMask = new BitArray(typeToIndex.Count);
notWithMask.Or(withMask).Not();
arrayA.Or(pendingMask).And(withMask).And(pendingEntity);
arrayB.Or(readMask).And(withMask).And(existingEntity);
arrayA.Or(arrayB).Or(notWithMask);
arrayB.SetAll(false);
arrayB.Or(pendingMask).And(withoutMask).And(pendingEntity).Not();
arrayC.Or(readMask).And(withoutMask).And(existingEntity).Not();
arrayB.And(arrayC);
arrayA.And(arrayB);
return !arrayA.Cast<bool>().Contains(false);
}
}
}

View File

@ -13,11 +13,12 @@ namespace Encompass
private readonly Dictionary<int, HashSet<GeneralRenderer>> layerIndexToGeneralRenderers = new Dictionary<int, HashSet<GeneralRenderer>>(512);
private readonly Dictionary<Type, Dictionary<Entity, int>> typeToEntityToLayer = new Dictionary<Type, Dictionary<Entity, int>>(512);
private Dictionary<Type, int> typeToIndex;
public IEnumerable<int> LayerOrder { get { return layerOrder.Values; } }
public DrawLayerManager()
public DrawLayerManager(Dictionary<Type, int> typeToIndex)
{
this.typeToIndex = typeToIndex;
RegisterDrawLayer(0);
}
@ -27,7 +28,15 @@ namespace Encompass
{
layerOrder.Add(layer, layer);
layerIndexToGeneralRenderers.Add(layer, new HashSet<GeneralRenderer>());
layerIndexToComponentStore.Add(layer, new ComponentStore());
layerIndexToComponentStore.Add(layer, new ComponentStore(typeToIndex));
}
}
public void FinishRegistering()
{
foreach (var store in layerIndexToComponentStore.Values)
{
store.FinishRegistering();
}
}

View File

@ -612,9 +612,12 @@ namespace Encompass
timeManager.ActivateTimeDilation(factor, easeInTime, easeInFunction, activeTime, easeOutTime, easeOutFunction);
}
/// <summary>
/// Returns an empty EntitySetQuery. Can be modified and iterated over to obtain Entities that fit the given criteria.
/// </summary>
protected EntitySetQuery QueryEntities()
{
return new EntitySetQuery(componentManager);
return new EntitySetQuery(entityManager, componentUpdateManager, readTypes, readPendingTypes);
}
}
}

View File

@ -7,12 +7,17 @@ namespace Encompass
{
private readonly int entityCapacity;
private readonly IDManager idManager = new IDManager();
private readonly HashSet<int> IDs = new HashSet<int>();
private readonly Dictionary<int, Entity> IDs = new Dictionary<int, Entity>(32768);
private readonly HashSet<Entity> entitiesMarkedForDestroy = new HashSet<Entity>();
private readonly ComponentManager componentManager;
public IEnumerable<Entity> Entities
{
get { return IDs.Values; }
}
public EntityManager(ComponentManager componentManager, int entityCapacity)
{
this.componentManager = componentManager;
@ -30,7 +35,7 @@ namespace Encompass
{
var id = NextID();
var entity = new Entity(id);
IDs.Add(id);
IDs.Add(id, entity);
return entity;
}
else
@ -41,7 +46,7 @@ namespace Encompass
public bool EntityExists(int id)
{
return IDs.Contains(id);
return IDs.ContainsKey(id);
}
public void MarkForDestroy(Entity entity)

View File

@ -5,39 +5,57 @@ using System.Collections.Immutable;
namespace Encompass
{
/// <summary>
/// EntitySetQuery is used to efficiently obtain a set of Entities that have all required Components and do not have any forbidden Components.
/// </summary>
public struct EntitySetQuery : IEnumerable<Entity>
{
private EntitySetQuery(ComponentManager componentManager, ImmutableArray<Type> includes, ImmutableArray<Type> excludes)
private EntitySetQuery(EntityManager entityManager, ComponentUpdateManager componentUpdateManager, HashSet<Type> readTypes, HashSet<Type> readPendingTypes, ImmutableArray<Type> includes, ImmutableArray<Type> excludes)
{
ComponentManager = componentManager;
EntityManager = entityManager;
ComponentUpdateManager = componentUpdateManager;
ReadTypes = readTypes;
ReadPendingTypes = readPendingTypes;
Includes = includes;
Excludes = excludes;
}
internal EntitySetQuery(ComponentManager componentManager)
internal EntitySetQuery(EntityManager entityManager, ComponentUpdateManager componentUpdateManager, HashSet<Type> readTypes, HashSet<Type> readPendingTypes)
{
ComponentManager = componentManager;
EntityManager = entityManager;
ComponentUpdateManager = componentUpdateManager;
ReadTypes = readTypes;
ReadPendingTypes = readPendingTypes;
Includes = ImmutableArray.Create<Type>();
Excludes = ImmutableArray.Create<Type>();
}
private ComponentManager ComponentManager { get; }
private EntityManager EntityManager { get; }
private ComponentUpdateManager ComponentUpdateManager { get; }
private HashSet<Type> ReadTypes { get; }
private HashSet<Type> ReadPendingTypes { get; }
private ImmutableArray<Type> Includes { get; }
private ImmutableArray<Type> Excludes { get; }
/// <summary>
/// Designates that the given component type is required.
/// </summary>
public EntitySetQuery With<TComponent>() where TComponent : struct, IComponent
{
return new EntitySetQuery(ComponentManager, Includes.Add(typeof(TComponent)), Excludes);
return new EntitySetQuery(EntityManager, ComponentUpdateManager, ReadTypes, ReadPendingTypes, Includes.Add(typeof(TComponent)), Excludes);
}
/// <summary>
/// Designates that the given component type is forbidden.
/// </summary>
public EntitySetQuery Without<TComponent>() where TComponent : struct, IComponent
{
return new EntitySetQuery(ComponentManager, Includes, Excludes.Add(typeof(TComponent)));
return new EntitySetQuery(EntityManager, ComponentUpdateManager, ReadTypes, ReadPendingTypes, Includes, Excludes.Add(typeof(TComponent)));
}
public IEnumerator<Entity> GetEnumerator()
{
return ComponentManager.EntitiesWithComponents(Includes).GetEnumerator();
return ComponentUpdateManager.QueryEntities(EntityManager.Entities, ReadTypes, ReadPendingTypes, Includes, Excludes).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()

View File

@ -21,8 +21,8 @@ namespace Encompass
private readonly int entityCapacity;
private readonly List<Engine> engines = new List<Engine>();
private readonly DirectedGraph<Engine, Unit> engineGraph = GraphBuilder.DirectedGraph<Engine>();
private readonly ComponentStore startingComponentStoreForComponentManager = new ComponentStore();
private readonly ComponentStore startingComponentStoreForComponentUpdateManager = new ComponentStore();
private readonly ComponentStore startingComponentStoreForComponentManager;
private readonly ComponentStore startingComponentStoreForComponentUpdateManager;
private readonly ComponentManager componentManager;
private readonly EntityManager entityManager;
@ -40,16 +40,21 @@ namespace Encompass
private readonly HashSet<Type> messageTypes = new HashSet<Type>();
private readonly Dictionary<Type, int> typeToIndex = new Dictionary<Type, int>();
public WorldBuilder(int entityCapacity = 32768)
{
this.entityCapacity = entityCapacity;
drawLayerManager = new DrawLayerManager();
drawLayerManager = new DrawLayerManager(typeToIndex);
timeManager = new TimeManager();
componentUpdateManager = new ComponentUpdateManager();
componentManager = new ComponentManager(drawLayerManager, componentUpdateManager);
componentUpdateManager = new ComponentUpdateManager(typeToIndex);
componentManager = new ComponentManager(drawLayerManager, componentUpdateManager, typeToIndex);
messageManager = new MessageManager(timeManager);
entityManager = new EntityManager(componentManager, entityCapacity);
renderManager = new RenderManager(drawLayerManager);
startingComponentStoreForComponentManager = new ComponentStore(typeToIndex);
startingComponentStoreForComponentUpdateManager = new ComponentStore(typeToIndex);
}
/// <summary>
@ -84,7 +89,9 @@ namespace Encompass
RegisterComponentType<TComponent>();
componentTypesToRegister.Add(typeof(TComponent));
startingComponentStoreForComponentManager.FinishRegistering();
startingComponentStoreForComponentManager.Set(entity, component);
startingComponentStoreForComponentUpdateManager.FinishRegistering();
startingComponentStoreForComponentUpdateManager.Set(entity, component);
if (component is IDrawableComponent drawableComponent)
@ -96,11 +103,15 @@ namespace Encompass
internal void RegisterComponentType<TComponent>() where TComponent : struct, IComponent
{
if (!typeToIndex.ContainsKey(typeof(TComponent)))
{
typeToIndex.Add(typeof(TComponent), typeToIndex.Count);
componentManager.RegisterComponentType<TComponent>();
componentUpdateManager.RegisterComponentType<TComponent>();
startingComponentStoreForComponentManager.RegisterComponentType<TComponent>();
startingComponentStoreForComponentUpdateManager.RegisterComponentType<TComponent>();
}
}
internal void RegisterMessageTypes(IEnumerable<Type> types)
{
@ -363,13 +374,14 @@ namespace Encompass
engineOrder.Add(emitterEngine);
}
PreloadJIT(componentTypesToRegister, messageTypes);
componentManager.FinishRegistering();
componentUpdateManager.FinishRegistering();
drawLayerManager.FinishRegistering();
startingComponentStoreForComponentManager.FinishRegistering();
startingComponentStoreForComponentUpdateManager.FinishRegistering();
PreloadJIT(componentTypesToRegister, messageTypes);
foreach (var engine in engineGraph.TopologicalSort())
{
engineOrder.Add(engine);
@ -401,9 +413,9 @@ namespace Encompass
{
var dummyTimeManager = new TimeManager();
var dummyMessageManager = new MessageManager(dummyTimeManager);
var dummyDrawLayerManager = new DrawLayerManager();
var dummyComponentUpdateManager = new ComponentUpdateManager();
var dummyComponentManager = new ComponentManager(dummyDrawLayerManager, dummyComponentUpdateManager);
var dummyDrawLayerManager = new DrawLayerManager(typeToIndex);
var dummyComponentUpdateManager = new ComponentUpdateManager(typeToIndex);
var dummyComponentManager = new ComponentManager(dummyDrawLayerManager, dummyComponentUpdateManager, typeToIndex);
var dummyEntityManager = new EntityManager(dummyComponentManager, entityCapacity);
var dummyRenderManager = new RenderManager(dummyDrawLayerManager);

View File

@ -1088,15 +1088,27 @@ namespace Tests
undilatedDeltaTime.Should().Be(0.5);
}
struct MockComponentB : IComponent { }
static Entity[] queriedEntities;
class EntityQueryEngine : Engine
public class QueryTests
{
struct MockComponentB : IComponent { }
struct MockComponentC : IComponent { }
struct MockComponentD : IComponent { }
[Reads(typeof(MockComponent), typeof(MockComponentB))]
class EntityQueryWithComponentsEngine : Engine
{
private List<Entity> entities;
public EntityQueryWithComponentsEngine(List<Entity> entities)
{
this.entities = entities;
}
public override void Update(double dt)
{
queriedEntities = QueryEntities().With<MockComponent>().With<MockComponentB>().ToArray();
entities.Clear();
entities.AddRange(QueryEntities().With<MockComponent>().With<MockComponentB>());
}
}
@ -1117,7 +1129,8 @@ namespace Tests
worldBuilder.SetComponent(entityC, new MockComponentB());
worldBuilder.AddEngine(new EntityQueryEngine());
var queriedEntities = new List<Entity>();
worldBuilder.AddEngine(new EntityQueryWithComponentsEngine(queriedEntities));
var world = worldBuilder.Build();
@ -1125,5 +1138,106 @@ namespace Tests
queriedEntities.Should().BeEquivalentTo(new Entity[] { entity, entityB });
}
[Reads(typeof(MockComponent))]
class EntityQueryWithoutComponentsEngine : Engine
{
private List<Entity> entities;
public EntityQueryWithoutComponentsEngine(List<Entity> entities)
{
this.entities = entities;
}
public override void Update(double dt)
{
entities.Clear();
entities.AddRange(QueryEntities().Without<MockComponent>());
}
}
[Test]
public void EntitiesWithoutComponents()
{
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());
var queriedEntities = new List<Entity>();
worldBuilder.AddEngine(new EntityQueryWithoutComponentsEngine(queriedEntities));
var world = worldBuilder.Build();
world.Update(0.01);
queriedEntities.ToArray().Should().BeEquivalentTo(new Entity[] { entityC });
}
[Reads(typeof(MockComponent), typeof(MockComponentB), typeof(MockComponentD))]
class EntityQueryWithandWithoutComponentsEngine : Engine
{
private List<Entity> entities;
public EntityQueryWithandWithoutComponentsEngine(List<Entity> entities)
{
this.entities = entities;
}
public override void Update(double dt)
{
entities.Clear();
entities.AddRange(QueryEntities().With<MockComponent>()
.With<MockComponentB>()
.Without<MockComponentD>());
}
}
[Test]
public void EntitiesWithAndWithoutComponents()
{
var worldBuilder = new WorldBuilder();
var entity = worldBuilder.CreateEntity();
var entityB = worldBuilder.CreateEntity();
var entityC = worldBuilder.CreateEntity();
var entityD = worldBuilder.CreateEntity();
worldBuilder.SetComponent(entity, new MockComponent());
worldBuilder.SetComponent(entity, new MockComponentB());
worldBuilder.SetComponent(entity, new MockComponentD());
worldBuilder.SetComponent(entityB, new MockComponent());
worldBuilder.SetComponent(entityC, new MockComponent());
worldBuilder.SetComponent(entityC, new MockComponentB());
worldBuilder.SetComponent(entityC, new MockComponentC());
worldBuilder.SetComponent(entityC, new MockComponentD());
worldBuilder.SetComponent(entityD, new MockComponent());
worldBuilder.SetComponent(entityD, new MockComponentB());
worldBuilder.SetComponent(entityD, new MockComponentC());
var queriedEntities = new List<Entity>();
worldBuilder.AddEngine(new EntityQueryWithandWithoutComponentsEngine(queriedEntities));
var world = worldBuilder.Build();
world.Update(0.01);
queriedEntities.ToArray().Should().BeEquivalentTo(new Entity[] { entityD });
}
}
}
}