restructure entity queries to enable caching

pull/5/head
Evan Hemsley 2019-12-22 18:13:35 -08:00
parent 4e3aaa7d47
commit 1e73351b07
9 changed files with 156 additions and 133 deletions

View File

@ -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<Type> queryWithTypes = new HashSet<Type>();
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);
}
}
}
}

View File

@ -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<Type> queryWithoutTypes = new HashSet<Type>();
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);
}
}
}
}

View File

@ -7,11 +7,11 @@ namespace Encompass
internal class ComponentStore
{
private Dictionary<Type, TypedComponentStore> Stores = new Dictionary<Type, TypedComponentStore>(512);
private ComponentBitSet componentBitSet;
public ComponentBitSet ComponentBitSet { get; private set; }
public ComponentStore(Dictionary<Type, int> 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<TComponent> Lookup<TComponent>() 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<TComponent>(Entity entity) where TComponent : struct, IComponent
@ -65,18 +65,18 @@ namespace Encompass
public void Set<TComponent>(Entity entity, TComponent component) where TComponent : struct, IComponent
{
Lookup<TComponent>().Set(entity, component);
componentBitSet.Set<TComponent>(entity);
ComponentBitSet.Set<TComponent>(entity);
}
public bool Set<TComponent>(Entity entity, TComponent component, int priority) where TComponent : struct, IComponent
{
componentBitSet.Set<TComponent>(entity);
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);
ComponentBitSet.RemoveComponent<TComponent>(entity);
Lookup<TComponent>().Remove(entity);
}
@ -86,7 +86,7 @@ namespace Encompass
{
entry.Remove(entity);
}
componentBitSet.RemoveEntity(entity);
ComponentBitSet.RemoveEntity(entity);
}
public bool Any<TComponent>() 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);
}
}
}

View File

@ -197,14 +197,7 @@ namespace Encompass
UpToDateComponentStore.Remove<TComponent>(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; } }
}
}

View File

@ -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<Type> receiveTypes = new HashSet<Type>();
internal readonly HashSet<Type> writeTypes = new HashSet<Type>();
internal readonly HashSet<Type> writePendingTypes = new HashSet<Type>();
internal readonly HashSet<Type> queryWithTypes = new HashSet<Type>();
internal readonly HashSet<Type> queryWithoutTypes = new HashSet<Type>();
internal readonly Dictionary<Type, int> writePriorities = new Dictionary<Type, int>();
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<QueryWith>(false);
if (queryWithAttribute != null)
{
queryWithTypes = queryWithAttribute.queryWithTypes;
}
var queryWithoutAttribute = GetType().GetCustomAttribute<QueryWithout>(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<Entity> QueryEntities()
{
foreach (var entity in entityQuery.FilterEntities(entityManager.Entities, componentUpdateManager.PendingBits, componentUpdateManager.ExistingBits))
{
yield return entity;
}
}
/// <summary>
/// Returns an empty EntitySetQuery. Can be modified and iterated over to obtain Entities that fit the given criteria.
/// </summary>
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()
);
}
}

View File

@ -4,20 +4,16 @@ using Encompass.Collections;
namespace Encompass
{
public struct EntitySetQuery : IEnumerable<Entity>
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<Entity> GetEnumerator()
public IEnumerable<Entity> FilterEntities(IEnumerable<Entity> 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();
}
}
}

View File

@ -1,70 +0,0 @@
using System;
using System.Collections.Generic;
using Encompass.Collections;
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 class EntitySetQueryBuilder
{
internal EntitySetQueryBuilder(EntityManager entityManager, ComponentUpdateManager componentUpdateManager, IEnumerable<Type> pendingTypes, IEnumerable<Type> 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; }
/// <summary>
/// Designates that the given component type is required.
/// </summary>
public EntitySetQueryBuilder With<TComponent>() where TComponent : struct, IComponent
{
WithMask = WithMask.Set(ComponentUpdateManager.TypeToIndex[typeof(TComponent)]);
return this;
}
/// <summary>
/// Designates that the given component type is forbidden.
/// </summary>
public EntitySetQueryBuilder Without<TComponent>() 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()
);
}
}
}

View File

@ -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(

View File

@ -1095,6 +1095,7 @@ namespace Tests
struct MockComponentD : IComponent { }
[Reads(typeof(MockComponent), typeof(MockComponentB))]
[QueryWith(typeof(MockComponent), typeof(MockComponentB))]
class EntityQueryWithComponentsEngine : Engine
{
private List<Entity> entities;
@ -1107,8 +1108,10 @@ namespace Tests
public override void Update(double dt)
{
entities.Clear();
entities.AddRange(QueryEntities().With<MockComponent>().With<MockComponentB>());
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<Entity> entities;
@ -1152,8 +1156,7 @@ namespace Tests
public override void Update(double dt)
{
entities.Clear();
entities.AddRange(QueryEntities().Without<MockComponent>());
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<Entity> entities;
@ -1198,9 +1203,7 @@ namespace Tests
{
entities.Clear();
entities.AddRange(QueryEntities().With<MockComponent>()
.With<MockComponentB>()
.Without<MockComponentD>());
entities.AddRange(QueryEntities());
}
}
@ -1246,7 +1249,7 @@ namespace Tests
{
public override void Update(double dt)
{
foreach (var entity in QueryEntities().With<MockComponent>())
foreach (var entity in ReadEntities<MockComponent>())
{
SetComponent(entity, new MockComponentB());
}
@ -1254,6 +1257,7 @@ namespace Tests
}
[ReadsPending(typeof(MockComponentB))]
[QueryWith(typeof(MockComponentB))]
class EntityQueryWithPendingComponentsEngine : Engine
{
private List<Entity> entities;
@ -1266,8 +1270,7 @@ namespace Tests
public override void Update(double dt)
{
entities.Clear();
entities.AddRange(QueryEntities().With<MockComponentB>());
entities.AddRange(QueryEntities());
}
}
@ -1293,6 +1296,7 @@ namespace Tests
}
[ReadsPending(typeof(MockComponentB))]
[QueryWithout(typeof(MockComponentB))]
class EntityQueryWithoutPendingComponentsEngine : Engine
{
private List<Entity> entities;
@ -1305,8 +1309,7 @@ namespace Tests
public override void Update(double dt)
{
entities.Clear();
entities.AddRange(QueryEntities().Without<MockComponentB>());
entities.AddRange(QueryEntities());
}
}
@ -1339,12 +1342,12 @@ namespace Tests
{
public override void Update(double dt)
{
foreach (var entity in QueryEntities().With<MockComponentC>())
foreach (var entity in ReadEntities<MockComponentC>())
{
SetComponent(entity, new MockComponent());
}
foreach (var entity in QueryEntities().With<MockComponentD>())
foreach (var entity in ReadEntities<MockComponentD>())
{
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<Entity> entities;
@ -1366,7 +1371,7 @@ namespace Tests
{
entities.Clear();
entities.AddRange(QueryEntities().With<MockComponent>().Without<MockComponentB>());
entities.AddRange(QueryEntities());
}
}
@ -1400,7 +1405,7 @@ namespace Tests
{
public override void Update(double dt)
{
foreach (var entity in QueryEntities().With<MockComponentC>())
foreach (var entity in ReadEntities<MockComponentC>())
{
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<Entity> entities;
@ -1421,8 +1427,7 @@ namespace Tests
public override void Update(double dt)
{
entities.Clear();
entities.AddRange(QueryEntities().With<MockComponent>().With<MockComponentB>());
entities.AddRange(QueryEntities());
}
}