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