From b2b32b0424537effe699975d9b9efb110c6496d3 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Sat, 5 Mar 2022 22:12:27 -0800 Subject: [PATCH] initial filter implementation --- src/ComponentDepot.cs | 101 +++++++++++++++++++++++++++++++++++++++- src/ComponentStorage.cs | 3 +- src/Filter.cs | 15 ++++++ src/FilterBuilder.cs | 39 ++++++++++++++++ src/FilterSignature.cs | 47 +++++++++++++++++++ src/System.cs | 10 ++++ src/World.cs | 17 +++---- 7 files changed, 221 insertions(+), 11 deletions(-) create mode 100644 src/Filter.cs create mode 100644 src/FilterBuilder.cs create mode 100644 src/FilterSignature.cs diff --git a/src/ComponentDepot.cs b/src/ComponentDepot.cs index 1e2aeea..9ab499c 100644 --- a/src/ComponentDepot.cs +++ b/src/ComponentDepot.cs @@ -4,8 +4,17 @@ internal class ComponentDepot { private Dictionary storages = new Dictionary(); + private Dictionary> filterSignatureToEntityIDs = new Dictionary>(); + + private Dictionary> typeToFilterSignatures = new Dictionary>(); + private Dictionary> entityComponentMap = new Dictionary>(); + private ComponentStorage Lookup(Type type) + { + return storages[type]; + } + private ComponentStorage Lookup() where TComponent : struct { // TODO: is it possible to optimize this? @@ -27,6 +36,11 @@ internal class ComponentDepot return Lookup().Has(entityID); } + private bool Has(Type type, int entityID) + { + return Lookup(type).Has(entityID); + } + public ref readonly TComponent Get(int entityID) where TComponent : struct { return ref Lookup().Get(entityID); @@ -41,7 +55,19 @@ internal class ComponentDepot entityComponentMap.Add(entityID, new HashSet()); } - 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 ReadEntities() where TComponent : struct @@ -58,7 +84,19 @@ internal class ComponentDepot { Lookup().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 included, HashSet excluded) + { + var filterSignature = new FilterSignature(included, excluded); + if (!filterSignatureToEntityIDs.ContainsKey(filterSignature)) + { + filterSignatureToEntityIDs.Add(filterSignature, new HashSet()); + + foreach (var type in included) + { + if (!typeToFilterSignatures.ContainsKey(type)) + { + typeToFilterSignatures.Add(type, new HashSet()); + } + + typeToFilterSignatures[type].Add(filterSignature); + } + + foreach (var type in excluded) + { + if (!typeToFilterSignatures.ContainsKey(type)) + { + typeToFilterSignatures.Add(type, new HashSet()); + } + + typeToFilterSignatures[type].Add(filterSignature); + } + } + return new Filter(this, included, excluded); + } + + public IEnumerable 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; + } } diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs index c2a736a..fcbd3a5 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -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 : ComponentStorage where TComponent return nextID > 0; } - public bool Has(int entityID) + public override bool Has(int entityID) { return entityIDToStorageIndex.ContainsKey(entityID); } diff --git a/src/Filter.cs b/src/Filter.cs new file mode 100644 index 0000000..8fc6812 --- /dev/null +++ b/src/Filter.cs @@ -0,0 +1,15 @@ +namespace MoonTools.ECS; + +public class Filter +{ + internal FilterSignature Signature; + private ComponentDepot ComponentDepot; + + internal Filter(ComponentDepot componentDepot, HashSet included, HashSet excluded) + { + ComponentDepot = componentDepot; + Signature = new FilterSignature(included, excluded); + } + + public IEnumerable Entities => ComponentDepot.FilterEntities(this); +} diff --git a/src/FilterBuilder.cs b/src/FilterBuilder.cs new file mode 100644 index 0000000..aec045d --- /dev/null +++ b/src/FilterBuilder.cs @@ -0,0 +1,39 @@ +namespace MoonTools.ECS; + +public struct FilterBuilder +{ + private ComponentDepot ComponentDepot; + private HashSet Included; + private HashSet Excluded; + + internal FilterBuilder(ComponentDepot componentDepot) + { + ComponentDepot = componentDepot; + Included = new HashSet(); + Excluded = new HashSet(); + } + + private FilterBuilder(ComponentDepot componentDepot, HashSet included, HashSet excluded) + { + ComponentDepot = componentDepot; + Included = included; + Excluded = excluded; + } + + public FilterBuilder Include() where TComponent : struct + { + Included.Add(typeof(TComponent)); + return new FilterBuilder(ComponentDepot, Included, Excluded); + } + + public FilterBuilder Exclude() where TComponent : struct + { + Excluded.Add(typeof(TComponent)); + return new FilterBuilder(ComponentDepot, Included, Excluded); + } + + public Filter Build() + { + return ComponentDepot.CreateFilter(Included, Excluded); + } +} diff --git a/src/FilterSignature.cs b/src/FilterSignature.cs new file mode 100644 index 0000000..0ce7ae2 --- /dev/null +++ b/src/FilterSignature.cs @@ -0,0 +1,47 @@ +namespace MoonTools.ECS; + +public struct FilterSignature +{ + private const int HASH_FACTOR = 97; + + public HashSet Included; + public HashSet Excluded; + + public FilterSignature(HashSet included, HashSet 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; + } +} diff --git a/src/System.cs b/src/System.cs index d3d7273..3f6f5db 100644 --- a/src/System.cs +++ b/src/System.cs @@ -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(in Entity entity, in TComponent component) where TComponent : struct { ComponentDepot.Set(entity.ID, component); diff --git a/src/World.cs b/src/World.cs index b0f7b87..acf0886 100644 --- a/src/World.cs +++ b/src/World.cs @@ -2,28 +2,29 @@ public class World { - private readonly List systems = new List(); - private readonly List renderers = new List(); + private readonly List Systems = new List(); + private readonly List Renderers = new List(); + private readonly List Filters = new List(); 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); }