initial filter implementation
parent
4ea5277912
commit
b2b32b0424
|
@ -4,8 +4,17 @@ internal class ComponentDepot
|
||||||
{
|
{
|
||||||
private Dictionary<Type, ComponentStorage> storages = new Dictionary<Type, ComponentStorage>();
|
private Dictionary<Type, ComponentStorage> storages = new Dictionary<Type, ComponentStorage>();
|
||||||
|
|
||||||
|
private Dictionary<FilterSignature, HashSet<int>> filterSignatureToEntityIDs = new Dictionary<FilterSignature, HashSet<int>>();
|
||||||
|
|
||||||
|
private Dictionary<Type, HashSet<FilterSignature>> typeToFilterSignatures = new Dictionary<Type, HashSet<FilterSignature>>();
|
||||||
|
|
||||||
private Dictionary<int, HashSet<Type>> entityComponentMap = new Dictionary<int, HashSet<Type>>();
|
private Dictionary<int, HashSet<Type>> entityComponentMap = new Dictionary<int, HashSet<Type>>();
|
||||||
|
|
||||||
|
private ComponentStorage Lookup(Type type)
|
||||||
|
{
|
||||||
|
return storages[type];
|
||||||
|
}
|
||||||
|
|
||||||
private ComponentStorage<TComponent> Lookup<TComponent>() where TComponent : struct
|
private ComponentStorage<TComponent> Lookup<TComponent>() where TComponent : struct
|
||||||
{
|
{
|
||||||
// TODO: is it possible to optimize this?
|
// TODO: is it possible to optimize this?
|
||||||
|
@ -27,6 +36,11 @@ internal class ComponentDepot
|
||||||
return Lookup<TComponent>().Has(entityID);
|
return Lookup<TComponent>().Has(entityID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool Has(Type type, int entityID)
|
||||||
|
{
|
||||||
|
return Lookup(type).Has(entityID);
|
||||||
|
}
|
||||||
|
|
||||||
public ref readonly TComponent Get<TComponent>(int entityID) where TComponent : struct
|
public ref readonly TComponent Get<TComponent>(int entityID) where TComponent : struct
|
||||||
{
|
{
|
||||||
return ref Lookup<TComponent>().Get(entityID);
|
return ref Lookup<TComponent>().Get(entityID);
|
||||||
|
@ -41,7 +55,19 @@ internal class ComponentDepot
|
||||||
entityComponentMap.Add(entityID, new HashSet<Type>());
|
entityComponentMap.Add(entityID, new HashSet<Type>());
|
||||||
}
|
}
|
||||||
|
|
||||||
entityComponentMap[entityID].Add(typeof(TComponent));
|
var alreadyExists = entityComponentMap[entityID].Add(typeof(TComponent));
|
||||||
|
|
||||||
|
// update filters
|
||||||
|
if (!alreadyExists)
|
||||||
|
{
|
||||||
|
if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures))
|
||||||
|
{
|
||||||
|
foreach (var filterSignature in filterSignatures)
|
||||||
|
{
|
||||||
|
CheckFilter(filterSignature, entityID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<Entity> ReadEntities<TComponent>() where TComponent : struct
|
public ReadOnlySpan<Entity> ReadEntities<TComponent>() where TComponent : struct
|
||||||
|
@ -58,7 +84,19 @@ internal class ComponentDepot
|
||||||
{
|
{
|
||||||
Lookup<TComponent>().Remove(entityID);
|
Lookup<TComponent>().Remove(entityID);
|
||||||
|
|
||||||
entityComponentMap[entityID].Remove(typeof(TComponent));
|
var found = entityComponentMap[entityID].Remove(typeof(TComponent));
|
||||||
|
|
||||||
|
// update filters
|
||||||
|
if (found)
|
||||||
|
{
|
||||||
|
if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures))
|
||||||
|
{
|
||||||
|
foreach (var filterSignature in filterSignatures)
|
||||||
|
{
|
||||||
|
CheckFilter(filterSignature, entityID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnEntityDestroy(int entityID)
|
public void OnEntityDestroy(int entityID)
|
||||||
|
@ -73,4 +111,63 @@ internal class ComponentDepot
|
||||||
entityComponentMap.Remove(entityID);
|
entityComponentMap.Remove(entityID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Filter CreateFilter(HashSet<Type> included, HashSet<Type> excluded)
|
||||||
|
{
|
||||||
|
var filterSignature = new FilterSignature(included, excluded);
|
||||||
|
if (!filterSignatureToEntityIDs.ContainsKey(filterSignature))
|
||||||
|
{
|
||||||
|
filterSignatureToEntityIDs.Add(filterSignature, new HashSet<int>());
|
||||||
|
|
||||||
|
foreach (var type in included)
|
||||||
|
{
|
||||||
|
if (!typeToFilterSignatures.ContainsKey(type))
|
||||||
|
{
|
||||||
|
typeToFilterSignatures.Add(type, new HashSet<FilterSignature>());
|
||||||
|
}
|
||||||
|
|
||||||
|
typeToFilterSignatures[type].Add(filterSignature);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var type in excluded)
|
||||||
|
{
|
||||||
|
if (!typeToFilterSignatures.ContainsKey(type))
|
||||||
|
{
|
||||||
|
typeToFilterSignatures.Add(type, new HashSet<FilterSignature>());
|
||||||
|
}
|
||||||
|
|
||||||
|
typeToFilterSignatures[type].Add(filterSignature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Filter(this, included, excluded);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Entity> FilterEntities(Filter filter)
|
||||||
|
{
|
||||||
|
foreach (var id in filterSignatureToEntityIDs[filter.Signature])
|
||||||
|
{
|
||||||
|
yield return new Entity(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckFilter(FilterSignature filterSignature, int entityID)
|
||||||
|
{
|
||||||
|
foreach (var type in filterSignature.Included)
|
||||||
|
{
|
||||||
|
if (!Has(type, entityID))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var type in filterSignature.Excluded)
|
||||||
|
{
|
||||||
|
if (Has(type, entityID))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
internal abstract class ComponentStorage
|
internal abstract class ComponentStorage
|
||||||
{
|
{
|
||||||
|
public abstract bool Has(int entityID);
|
||||||
public abstract void Remove(int entityID);
|
public abstract void Remove(int entityID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ internal class ComponentStorage<TComponent> : ComponentStorage where TComponent
|
||||||
return nextID > 0;
|
return nextID > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Has(int entityID)
|
public override bool Has(int entityID)
|
||||||
{
|
{
|
||||||
return entityIDToStorageIndex.ContainsKey(entityID);
|
return entityIDToStorageIndex.ContainsKey(entityID);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
namespace MoonTools.ECS;
|
||||||
|
|
||||||
|
public class Filter
|
||||||
|
{
|
||||||
|
internal FilterSignature Signature;
|
||||||
|
private ComponentDepot ComponentDepot;
|
||||||
|
|
||||||
|
internal Filter(ComponentDepot componentDepot, HashSet<Type> included, HashSet<Type> excluded)
|
||||||
|
{
|
||||||
|
ComponentDepot = componentDepot;
|
||||||
|
Signature = new FilterSignature(included, excluded);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Entity> Entities => ComponentDepot.FilterEntities(this);
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
namespace MoonTools.ECS;
|
||||||
|
|
||||||
|
public struct FilterBuilder
|
||||||
|
{
|
||||||
|
private ComponentDepot ComponentDepot;
|
||||||
|
private HashSet<Type> Included;
|
||||||
|
private HashSet<Type> Excluded;
|
||||||
|
|
||||||
|
internal FilterBuilder(ComponentDepot componentDepot)
|
||||||
|
{
|
||||||
|
ComponentDepot = componentDepot;
|
||||||
|
Included = new HashSet<Type>();
|
||||||
|
Excluded = new HashSet<Type>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private FilterBuilder(ComponentDepot componentDepot, HashSet<Type> included, HashSet<Type> excluded)
|
||||||
|
{
|
||||||
|
ComponentDepot = componentDepot;
|
||||||
|
Included = included;
|
||||||
|
Excluded = excluded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilterBuilder Include<TComponent>() where TComponent : struct
|
||||||
|
{
|
||||||
|
Included.Add(typeof(TComponent));
|
||||||
|
return new FilterBuilder(ComponentDepot, Included, Excluded);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilterBuilder Exclude<TComponent>() where TComponent : struct
|
||||||
|
{
|
||||||
|
Excluded.Add(typeof(TComponent));
|
||||||
|
return new FilterBuilder(ComponentDepot, Included, Excluded);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Filter Build()
|
||||||
|
{
|
||||||
|
return ComponentDepot.CreateFilter(Included, Excluded);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
namespace MoonTools.ECS;
|
||||||
|
|
||||||
|
public struct FilterSignature
|
||||||
|
{
|
||||||
|
private const int HASH_FACTOR = 97;
|
||||||
|
|
||||||
|
public HashSet<Type> Included;
|
||||||
|
public HashSet<Type> Excluded;
|
||||||
|
|
||||||
|
public FilterSignature(HashSet<Type> included, HashSet<Type> excluded)
|
||||||
|
{
|
||||||
|
Included = included;
|
||||||
|
Excluded = excluded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
return obj is FilterSignature signature && Equals(signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(FilterSignature other)
|
||||||
|
{
|
||||||
|
return Included.SetEquals(other.Included) && Excluded.SetEquals(other.Excluded);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GuidToInt(Guid guid)
|
||||||
|
{
|
||||||
|
return BitConverter.ToInt32(guid.ToByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
int result = 1;
|
||||||
|
foreach (var type in Included)
|
||||||
|
{
|
||||||
|
result *= HASH_FACTOR + GuidToInt(type.GUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Is there a way to avoid collisions when this is the same set as included?
|
||||||
|
foreach (var type in Excluded)
|
||||||
|
{
|
||||||
|
result *= HASH_FACTOR + GuidToInt(type.GUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,11 +4,21 @@ public abstract class System : EntityComponentReader
|
||||||
{
|
{
|
||||||
public abstract void Update(TimeSpan delta);
|
public abstract void Update(TimeSpan delta);
|
||||||
|
|
||||||
|
public System(World world)
|
||||||
|
{
|
||||||
|
world.AddSystem(this);
|
||||||
|
}
|
||||||
|
|
||||||
protected Entity CreateEntity()
|
protected Entity CreateEntity()
|
||||||
{
|
{
|
||||||
return EntityStorage.Create();
|
return EntityStorage.Create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected FilterBuilder CreateFilterBuilder()
|
||||||
|
{
|
||||||
|
return new FilterBuilder(ComponentDepot);
|
||||||
|
}
|
||||||
|
|
||||||
protected void Set<TComponent>(in Entity entity, in TComponent component) where TComponent : struct
|
protected void Set<TComponent>(in Entity entity, in TComponent component) where TComponent : struct
|
||||||
{
|
{
|
||||||
ComponentDepot.Set<TComponent>(entity.ID, component);
|
ComponentDepot.Set<TComponent>(entity.ID, component);
|
||||||
|
|
17
src/World.cs
17
src/World.cs
|
@ -2,28 +2,29 @@
|
||||||
|
|
||||||
public class World
|
public class World
|
||||||
{
|
{
|
||||||
private readonly List<System> systems = new List<System>();
|
private readonly List<System> Systems = new List<System>();
|
||||||
private readonly List<Renderer> renderers = new List<Renderer>();
|
private readonly List<Renderer> Renderers = new List<Renderer>();
|
||||||
|
private readonly List<Filter> Filters = new List<Filter>();
|
||||||
private EntityStorage EntityStorage { get; } = new EntityStorage();
|
private EntityStorage EntityStorage { get; } = new EntityStorage();
|
||||||
private ComponentDepot ComponentDepot { get; } = new ComponentDepot();
|
private ComponentDepot ComponentDepot { get; } = new ComponentDepot();
|
||||||
|
|
||||||
public void AddSystem(System system)
|
internal void AddSystem(System system)
|
||||||
{
|
{
|
||||||
system.RegisterEntityStorage(EntityStorage);
|
system.RegisterEntityStorage(EntityStorage);
|
||||||
system.RegisterComponentDepot(ComponentDepot);
|
system.RegisterComponentDepot(ComponentDepot);
|
||||||
systems.Add(system);
|
Systems.Add(system);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddRenderer(Renderer renderer)
|
internal void AddRenderer(Renderer renderer)
|
||||||
{
|
{
|
||||||
renderer.RegisterEntityStorage(EntityStorage);
|
renderer.RegisterEntityStorage(EntityStorage);
|
||||||
renderer.RegisterComponentDepot(ComponentDepot);
|
renderer.RegisterComponentDepot(ComponentDepot);
|
||||||
renderers.Add(renderer);
|
Renderers.Add(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(TimeSpan delta)
|
public void Update(TimeSpan delta)
|
||||||
{
|
{
|
||||||
foreach (var system in systems)
|
foreach (var system in Systems)
|
||||||
{
|
{
|
||||||
system.Update(delta);
|
system.Update(delta);
|
||||||
}
|
}
|
||||||
|
@ -31,7 +32,7 @@ public class World
|
||||||
|
|
||||||
public void Draw(TimeSpan delta)
|
public void Draw(TimeSpan delta)
|
||||||
{
|
{
|
||||||
foreach (var renderer in renderers)
|
foreach (var renderer in Renderers)
|
||||||
{
|
{
|
||||||
renderer.Draw(delta);
|
renderer.Draw(delta);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue