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<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 ComponentStorage Lookup(Type type)
 | 
			
		||||
	{
 | 
			
		||||
		return storages[type];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private ComponentStorage<TComponent> Lookup<TComponent>() where TComponent : struct
 | 
			
		||||
	{
 | 
			
		||||
		// TODO: is it possible to optimize this?
 | 
			
		||||
| 
						 | 
				
			
			@ -27,6 +36,11 @@ internal class ComponentDepot
 | 
			
		|||
		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
 | 
			
		||||
	{
 | 
			
		||||
		return ref Lookup<TComponent>().Get(entityID);
 | 
			
		||||
| 
						 | 
				
			
			@ -41,7 +55,19 @@ internal class ComponentDepot
 | 
			
		|||
			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
 | 
			
		||||
| 
						 | 
				
			
			@ -58,7 +84,19 @@ internal class ComponentDepot
 | 
			
		|||
	{
 | 
			
		||||
		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)
 | 
			
		||||
| 
						 | 
				
			
			@ -73,4 +111,63 @@ internal class ComponentDepot
 | 
			
		|||
			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
 | 
			
		||||
{
 | 
			
		||||
	public abstract bool Has(int entityID);
 | 
			
		||||
	public abstract void Remove(int entityID);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -18,7 +19,7 @@ internal class ComponentStorage<TComponent> : ComponentStorage where TComponent
 | 
			
		|||
		return nextID > 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public bool Has(int entityID)
 | 
			
		||||
	public override bool Has(int 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 System(World world)
 | 
			
		||||
	{
 | 
			
		||||
		world.AddSystem(this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected Entity CreateEntity()
 | 
			
		||||
	{
 | 
			
		||||
		return EntityStorage.Create();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected FilterBuilder CreateFilterBuilder()
 | 
			
		||||
	{
 | 
			
		||||
		return new FilterBuilder(ComponentDepot);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected void Set<TComponent>(in Entity entity, in TComponent component) where TComponent : struct
 | 
			
		||||
	{
 | 
			
		||||
		ComponentDepot.Set<TComponent>(entity.ID, component);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										17
									
								
								src/World.cs
								
								
								
								
							
							
						
						
									
										17
									
								
								src/World.cs
								
								
								
								
							| 
						 | 
				
			
			@ -2,28 +2,29 @@
 | 
			
		|||
 | 
			
		||||
public class World
 | 
			
		||||
{
 | 
			
		||||
	private readonly List<System> systems = new List<System>();
 | 
			
		||||
	private readonly List<Renderer> renderers = new List<Renderer>();
 | 
			
		||||
	private readonly List<System> Systems = new List<System>();
 | 
			
		||||
	private readonly List<Renderer> Renderers = new List<Renderer>();
 | 
			
		||||
	private readonly List<Filter> Filters = new List<Filter>();
 | 
			
		||||
	private EntityStorage EntityStorage { get; } = new EntityStorage();
 | 
			
		||||
	private ComponentDepot ComponentDepot { get; } = new ComponentDepot();
 | 
			
		||||
 | 
			
		||||
	public void AddSystem(System system)
 | 
			
		||||
	internal void AddSystem(System system)
 | 
			
		||||
	{
 | 
			
		||||
		system.RegisterEntityStorage(EntityStorage);
 | 
			
		||||
		system.RegisterComponentDepot(ComponentDepot);
 | 
			
		||||
		systems.Add(system);
 | 
			
		||||
		Systems.Add(system);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void AddRenderer(Renderer renderer)
 | 
			
		||||
	internal void AddRenderer(Renderer renderer)
 | 
			
		||||
	{
 | 
			
		||||
		renderer.RegisterEntityStorage(EntityStorage);
 | 
			
		||||
		renderer.RegisterComponentDepot(ComponentDepot);
 | 
			
		||||
		renderers.Add(renderer);
 | 
			
		||||
		Renderers.Add(renderer);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void Update(TimeSpan delta)
 | 
			
		||||
	{
 | 
			
		||||
		foreach (var system in systems)
 | 
			
		||||
		foreach (var system in Systems)
 | 
			
		||||
		{
 | 
			
		||||
			system.Update(delta);
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -31,7 +32,7 @@ public class World
 | 
			
		|||
 | 
			
		||||
	public void Draw(TimeSpan delta)
 | 
			
		||||
	{
 | 
			
		||||
		foreach (var renderer in renderers)
 | 
			
		||||
		foreach (var renderer in Renderers)
 | 
			
		||||
		{
 | 
			
		||||
			renderer.Draw(delta);
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue