diff --git a/encompass-cs/Attributes/QueryWith.cs b/encompass-cs/Attributes/QueryWith.cs new file mode 100644 index 0000000..3ae11a6 --- /dev/null +++ b/encompass-cs/Attributes/QueryWith.cs @@ -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 queryWithTypes = new HashSet(); + + 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); + } + } + } +} diff --git a/encompass-cs/Attributes/QueryWithout.cs b/encompass-cs/Attributes/QueryWithout.cs new file mode 100644 index 0000000..77f1e9f --- /dev/null +++ b/encompass-cs/Attributes/QueryWithout.cs @@ -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 queryWithoutTypes = new HashSet(); + + 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); + } + } + } +} diff --git a/encompass-cs/Attributes/ReadsImmediate.cs b/encompass-cs/Attributes/ReadsImmediate.cs new file mode 100644 index 0000000..e47d4ba --- /dev/null +++ b/encompass-cs/Attributes/ReadsImmediate.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Encompass.Exceptions; + +namespace Encompass +{ + [AttributeUsage(AttributeTargets.Class)] + public class ReadsImmediate : Attribute + { + public readonly HashSet readImmediateTypes = new HashSet(); + + public ReadsImmediate(params Type[] readImmediateTypes) + { + foreach (var readImmediateType in readImmediateTypes) + { + var isComponent = readImmediateType.GetInterfaces().Contains(typeof(IComponent)); + + if (!isComponent) + { + throw new IllegalReadTypeException("{0} must be a Component", readImmediateType.Name); + } + + this.readImmediateTypes.Add(readImmediateType); + } + } + } +} diff --git a/encompass-cs/Attributes/ReadsPending.cs b/encompass-cs/Attributes/ReadsPending.cs deleted file mode 100644 index e7e8653..0000000 --- a/encompass-cs/Attributes/ReadsPending.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Encompass.Exceptions; - -namespace Encompass -{ - [AttributeUsage(AttributeTargets.Class)] - public class ReadsPending : Attribute - { - public readonly HashSet readPendingTypes = new HashSet(); - - public ReadsPending(params Type[] readPendingTypes) - { - foreach (var readPendingType in readPendingTypes) - { - var isComponent = readPendingType.GetInterfaces().Contains(typeof(IComponent)); - - if (!isComponent) - { - throw new IllegalReadTypeException("{0} must be a Component", readPendingType.Name); - } - - this.readPendingTypes.Add(readPendingType); - } - } - } -} \ No newline at end of file diff --git a/encompass-cs/Attributes/WritesImmediate.cs b/encompass-cs/Attributes/WritesImmediate.cs new file mode 100644 index 0000000..66a46a6 --- /dev/null +++ b/encompass-cs/Attributes/WritesImmediate.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Encompass.Exceptions; + +namespace Encompass +{ + [AttributeUsage(AttributeTargets.Class)] + public class WritesImmediate : Attribute + { + public readonly HashSet writeImmediateTypes = new HashSet(); + + public WritesImmediate(params Type[] writeImmediateTypes) + { + foreach (var writeImmediateType in writeImmediateTypes) + { + var isComponent = writeImmediateType.GetInterfaces().Contains(typeof(IComponent)); + if (!isComponent) + { + throw new IllegalWriteImmediateTypeException("{0} must be a Component", writeImmediateType.Name); + } + + this.writeImmediateTypes.Add(writeImmediateType); + } + } + } +} diff --git a/encompass-cs/Attributes/WritesPending.cs b/encompass-cs/Attributes/WritesPending.cs deleted file mode 100644 index ea5a370..0000000 --- a/encompass-cs/Attributes/WritesPending.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Encompass.Exceptions; - -namespace Encompass -{ - public class WritesPending : Attribute - { - public readonly HashSet writePendingTypes = new HashSet(); - - public WritesPending(params Type[] writePendingTypes) - { - foreach (var writePendingType in writePendingTypes) - { - var isComponent = writePendingType.GetInterfaces().Contains(typeof(IComponent)); - if (!isComponent) - { - throw new IllegalWritePendingTypeException("{0} must be a Component", writePendingType.Name); - } - - this.writePendingTypes.Add(writePendingType); - } - } - } -} \ No newline at end of file diff --git a/encompass-cs/Collections/ComponentBitSet.cs b/encompass-cs/Collections/ComponentBitSet.cs new file mode 100644 index 0000000..1d0c39d --- /dev/null +++ b/encompass-cs/Collections/ComponentBitSet.cs @@ -0,0 +1,54 @@ +using MoonTools.FastCollections; +using System; +using System.Collections.Generic; + +namespace Encompass +{ + internal class ComponentBitSet + { + Dictionary entities = new Dictionary(); + Dictionary TypeToIndex { get; } + + public ComponentBitSet(Dictionary typeToIndex) + { + TypeToIndex = typeToIndex; + } + + public void Clear() + { + entities.Clear(); + } + + public void AddEntity(int entityID) + { + entities.Add(entityID, BitSet512.Zero); + } + + public void Set(int entityID) where TComponent : struct, IComponent + { + if (!entities.ContainsKey(entityID)) { AddEntity(entityID); } + entities[entityID] = entities[entityID].Set(TypeToIndex[typeof(TComponent)]); + } + + public void RemoveComponent(int entityID) where TComponent : struct, IComponent + { + if (entities.ContainsKey(entityID)) + { + entities[entityID] = entities[entityID].UnSet(TypeToIndex[typeof(TComponent)]); + } + } + + public void RemoveEntity(int entityID) + { + if (entities.ContainsKey(entityID)) + { + entities.Remove(entityID); + } + } + + public BitSet512 EntityBitArray(int entityID) + { + return entities.ContainsKey(entityID) ? entities[entityID] : BitSet512.Zero; + } + } +} diff --git a/encompass-cs/Collections/ComponentDeltaStore.cs b/encompass-cs/Collections/ComponentDeltaStore.cs new file mode 100644 index 0000000..9b212d0 --- /dev/null +++ b/encompass-cs/Collections/ComponentDeltaStore.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; + +namespace Encompass +{ + internal class ComponentDeltaStore : ComponentStore + { + private readonly Dictionary _replayers = new Dictionary(); + private readonly HashSet _currentReplayers = new HashSet(); + + public IEnumerable CurrentReplayers { get { return _currentReplayers; } } + + public ComponentDeltaStore(Dictionary typeToIndex) : base(typeToIndex) + { + } + + public override void RegisterComponentType() + { + base.RegisterComponentType(); + if (!_replayers.ContainsKey(typeof(TComponent))) + { + _replayers.Add(typeof(TComponent), new Replayer(this)); + } + } + + public override void Set(int entityID, TComponent component) + { + base.Set(entityID, component); + var replayer = _replayers[typeof(TComponent)]; + _currentReplayers.Add(replayer); + replayer.UnMarkRemoval(entityID); + } + + public override bool Set(int entityID, TComponent component, int priority) + { + var result = base.Set(entityID, component, priority); + if (result) + { + var replayer = _replayers[typeof(TComponent)]; + _currentReplayers.Add(replayer); + replayer.UnMarkRemoval(entityID); + } + return result; + } + + public override bool Remove(int entityID, int priority) + { + var result = base.Remove(entityID, priority); + if (result) + { + var replayer = _replayers[typeof(TComponent)]; + _currentReplayers.Add(replayer); + replayer.MarkRemoval(entityID); + } + return result; + } + + public override void Remove(int entityID) + { + base.Remove(entityID); + foreach (var replayer in CurrentReplayers) + { + replayer.MarkRemoval(entityID); + } + } + + public override void ClearAll() + { + base.ClearAll(); + foreach (var replayer in _currentReplayers) + { + replayer.Clear(); + } + _currentReplayers.Clear(); + } + } +} diff --git a/encompass-cs/Collections/ComponentStore.cs b/encompass-cs/Collections/ComponentStore.cs index 3283baf..0645510 100644 --- a/encompass-cs/Collections/ComponentStore.cs +++ b/encompass-cs/Collections/ComponentStore.cs @@ -1,4 +1,5 @@ -using System; +using MoonTools.FastCollections; +using System; using System.Collections.Generic; namespace Encompass @@ -6,6 +7,12 @@ namespace Encompass internal class ComponentStore { private Dictionary Stores = new Dictionary(512); + public ComponentBitSet ComponentBitSet { get; private set; } + + public ComponentStore(Dictionary typeToIndex) + { + ComponentBitSet = new ComponentBitSet(typeToIndex); + } public IEnumerable<(Type, TypedComponentStore)> StoresEnumerable() { @@ -15,11 +22,10 @@ namespace Encompass } } - public void RegisterComponentType() where TComponent : struct, IComponent + public virtual void RegisterComponentType() where TComponent : struct, IComponent { if (!Stores.ContainsKey(typeof(TComponent))) { - System.Console.WriteLine("register component type in component store"); var store = new TypedComponentStore(); Stores.Add(typeof(TComponent), store); } @@ -31,42 +37,65 @@ namespace Encompass return Stores[typeof(TComponent)] as TypedComponentStore; } - public bool Has(Entity entity) where TComponent : struct, IComponent + public bool Has(int entityID) where TComponent : struct, IComponent { - return Lookup().Has(entity); + return Lookup().Has(entityID); } - public bool Has(Type type, Entity entity) + public bool Has(Type type, int entityID) { - return Stores.ContainsKey(type) && Stores[type].Has(entity); + return Stores.ContainsKey(type) && Stores[type].Has(entityID); } - public TComponent Get(Entity entity) where TComponent : struct, IComponent + public BitSet512 EntityBitArray(int entityID) { - return Lookup().Get(entity); + return ComponentBitSet.EntityBitArray(entityID); } - public void Set(Entity entity, TComponent component) where TComponent : struct, IComponent + public TComponent Get(int entityID) where TComponent : struct, IComponent { - Lookup().Set(entity, component); + return Lookup().Get(entityID); } - public bool Set(Entity entity, TComponent component, int priority) where TComponent : struct, IComponent + public virtual void Set(int entityID, TComponent component) where TComponent : struct, IComponent { - return Lookup().Set(entity, component, priority); + Lookup().Set(entityID, component); + ComponentBitSet.Set(entityID); } - public void Remove(Entity entity) where TComponent : struct, IComponent + public virtual bool Set(int entityID, TComponent component, int priority) where TComponent : struct, IComponent { - Lookup().Remove(entity); + if (Lookup().Set(entityID, component, priority)) + { + ComponentBitSet.Set(entityID); + return true; + } + return false; } - public void Remove(Entity entity) + public virtual bool Remove(int entityID, int priority) where TComponent : struct, IComponent + { + if (Lookup().Remove(entityID, priority)) + { + ComponentBitSet.RemoveComponent(entityID); + return true; + } + return false; + } + + public void ForceRemove(int entityID) where TComponent : struct, IComponent + { + Lookup().ForceRemove(entityID); + ComponentBitSet.RemoveComponent(entityID); + } + + public virtual void Remove(int entityID) { foreach (var entry in Stores.Values) { - entry.Remove(entity); + entry.ForceRemove(entityID); } + ComponentBitSet.RemoveEntity(entityID); } public bool Any() where TComponent : struct, IComponent @@ -74,7 +103,7 @@ namespace Encompass return Lookup().Count > 0; } - public IEnumerable<(Entity, Type, IComponent)> AllInterfaceTyped() + public IEnumerable<(int, Type, IComponent)> AllInterfaceTyped() { foreach (var store in Stores.Values) { @@ -85,7 +114,7 @@ namespace Encompass } } - public IEnumerable<(TComponent, Entity)> All() where TComponent : struct, IComponent + public IEnumerable<(TComponent, int)> All() where TComponent : struct, IComponent { return Lookup().All(); } @@ -95,8 +124,17 @@ namespace Encompass Lookup().Clear(); } - public void ClearAll() + public virtual void ClearAllPriorities() { + foreach (var store in Stores.Values) + { + store.ClearPriorities(); + } + } + + public virtual void ClearAll() + { + ComponentBitSet.Clear(); foreach (var store in Stores.Values) { store.Clear(); @@ -106,6 +144,15 @@ namespace Encompass public void SwapWith(ComponentStore other) { (Stores, other.Stores) = (other.Stores, Stores); + (ComponentBitSet, other.ComponentBitSet) = (other.ComponentBitSet, ComponentBitSet); + } + + public void UpdateUsing(ComponentDeltaStore delta) + { + foreach (var replayer in delta.CurrentReplayers) + { + replayer.Replay(this); + } } } } diff --git a/encompass-cs/Collections/MessageStore.cs b/encompass-cs/Collections/MessageStore.cs index 12aef7b..9fb586a 100644 --- a/encompass-cs/Collections/MessageStore.cs +++ b/encompass-cs/Collections/MessageStore.cs @@ -48,6 +48,11 @@ namespace Encompass return Lookup().Any(); } + public IEnumerable WithEntity(int entityID) where TMessage : struct, IMessage, IHasEntity + { + return Lookup().WithEntity(entityID); + } + public void ProcessDelayedMessages(double dilatedDelta, double realtimeDelta) { foreach (var store in Stores.Values) diff --git a/encompass-cs/Collections/Replayer.cs b/encompass-cs/Collections/Replayer.cs new file mode 100644 index 0000000..b5cd3f7 --- /dev/null +++ b/encompass-cs/Collections/Replayer.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; + +namespace Encompass +{ + internal abstract class Replayer + { + public abstract void Replay(ComponentStore store); + public abstract void MarkRemoval(int entityID); + public abstract void UnMarkRemoval(int entityID); + public abstract void Clear(); + } + + internal class Replayer : Replayer where TComponent : struct, IComponent + { + private readonly ComponentDeltaStore _deltaStore; + private readonly HashSet _removals = new HashSet(); + + public Replayer(ComponentDeltaStore componentStore) + { + _deltaStore = componentStore; + } + + public override void Replay(ComponentStore store) + { + foreach (var (component, entityID) in _deltaStore.All()) + { + store.Set(entityID, component); + } + + foreach (var entityID in _removals) + { + store.ForceRemove(entityID); + } + } + + public override void Clear() + { + _removals.Clear(); + } + + public override void MarkRemoval(int entityID) + { + _removals.Add(entityID); + } + + public override void UnMarkRemoval(int entityID) + { + _removals.Remove(entityID); + } + } +} diff --git a/encompass-cs/Collections/TypedComponentStore.cs b/encompass-cs/Collections/TypedComponentStore.cs index 63409a2..78a8f20 100644 --- a/encompass-cs/Collections/TypedComponentStore.cs +++ b/encompass-cs/Collections/TypedComponentStore.cs @@ -6,43 +6,66 @@ namespace Encompass internal abstract class TypedComponentStore { public abstract int Count { get; } - public abstract IEnumerable<(Entity, Type, IComponent)> AllInterfaceTyped(); - public abstract bool Has(Entity entity); - public abstract void Remove(Entity entity); + public abstract IEnumerable<(int, Type, IComponent)> AllInterfaceTyped(); + public abstract bool Has(int entity); + public abstract bool Remove(int entity, int priority); + public abstract void ForceRemove(int entity); public abstract void Clear(); + public abstract void ClearPriorities(); } internal class TypedComponentStore : TypedComponentStore where TComponent : struct, IComponent { - private readonly Dictionary store = new Dictionary(128); - private readonly Dictionary priorities = new Dictionary(128); + private readonly Dictionary store = new Dictionary(128); + private readonly Dictionary priorities = new Dictionary(128); public override int Count { get => store.Count; } - public TComponent Get(Entity entity) + public TComponent Get(int entityID) { - return store[entity]; + if (!store.ContainsKey(entityID)) { throw new Exceptions.NoComponentOfTypeOnEntityException("No component of type {0} exists on Entity with ID {1}", typeof(TComponent), entityID); } + return store[entityID]; } - public void Set(Entity entity, TComponent component) + public void Set(int entityID, TComponent component) { - store[entity] = component; + store[entityID] = component; } - public bool Set(Entity entity, TComponent component, int priority) + public bool Set(int entityID, TComponent component, int priority) { - if (!priorities.ContainsKey(entity) || priority < priorities[entity]) { - store[entity] = component; - priorities[entity] = priority; + if (!priorities.ContainsKey(entityID) || priority < priorities[entityID]) + { + store[entityID] = component; + priorities[entityID] = priority; return true; } return false; } - public override bool Has(Entity entity) + public override bool Remove(int entityID, int priority) { - return store.ContainsKey(entity); + if (!priorities.ContainsKey(entityID) || priority < priorities[entityID]) + { + priorities[entityID] = priority; + store.Remove(entityID); + priorities.Remove(entityID); + return true; + } + + return false; + } + + public override void ForceRemove(int entityID) + { + store.Remove(entityID); + priorities.Remove(entityID); + } + + public override bool Has(int entityID) + { + return store.ContainsKey(entityID); } public override void Clear() @@ -51,7 +74,12 @@ namespace Encompass priorities.Clear(); } - public IEnumerable<(TComponent, Entity)> All() + public override void ClearPriorities() + { + priorities.Clear(); + } + + public IEnumerable<(TComponent, int)> All() { foreach (var kvp in store) { @@ -59,18 +87,12 @@ namespace Encompass } } - public override IEnumerable<(Entity, Type, IComponent)> AllInterfaceTyped() + public override IEnumerable<(int, Type, IComponent)> AllInterfaceTyped() { foreach (var kvp in store) { yield return (kvp.Key, typeof(TComponent), (IComponent)kvp.Value); } } - - public override void Remove(Entity entity) - { - store.Remove(entity); - priorities.Remove(entity); - } } } diff --git a/encompass-cs/Collections/TypedMessageStore.cs b/encompass-cs/Collections/TypedMessageStore.cs index 06fc648..54aebe6 100644 --- a/encompass-cs/Collections/TypedMessageStore.cs +++ b/encompass-cs/Collections/TypedMessageStore.cs @@ -13,6 +13,7 @@ namespace Encompass private readonly List store = new List(128); private readonly List<(TMessage, double)> delayedStore = new List<(TMessage, double)>(128); private readonly List<(TMessage, double)> delayedStoreIgnoringTimeDilation = new List<(TMessage, double)>(128); + private readonly Dictionary> entityToMessage = new Dictionary>(); public override void ProcessDelayedMessages(double dilatedDelta, double realtimeDelta) { @@ -54,6 +55,12 @@ namespace Encompass public void Add(TMessage message) { store.Add(message); + if (message is IHasEntity entityMessage) + { + var entityID = entityMessage.Entity.ID; + if (!entityToMessage.ContainsKey(entityID)) { entityToMessage.Add(entityID, new List()); } + entityToMessage[entityID].Add(message); + } } public void Add(TMessage message, double time) @@ -81,9 +88,18 @@ namespace Encompass return store; } + public IEnumerable WithEntity(int entityID) + { + return entityToMessage.ContainsKey(entityID) ? entityToMessage[entityID] : System.Linq.Enumerable.Empty(); + } + public override void Clear() { store.Clear(); + foreach (var set in entityToMessage.Values) + { + set.Clear(); + } } } } diff --git a/encompass-cs/ComponentManager.cs b/encompass-cs/ComponentManager.cs index cf105c5..ded956e 100644 --- a/encompass-cs/ComponentManager.cs +++ b/encompass-cs/ComponentManager.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace Encompass @@ -5,90 +6,248 @@ namespace Encompass internal class ComponentManager { private readonly DrawLayerManager drawLayerManager; - private readonly ComponentUpdateManager componentUpdateManager; - private readonly ComponentStore componentStore = new ComponentStore(); - private readonly HashSet entitiesMarkedForRemoval = new HashSet(); + private readonly ComponentStore existingComponentStore; + private readonly ComponentStore immediateComponentStore; + private readonly ComponentDeltaStore replayStore; + private ComponentStore upToDateComponentStore; - public ComponentManager(DrawLayerManager drawLayerManager, ComponentUpdateManager componentUpdateManager) + public Dictionary TypeToIndex { get; } + + private readonly HashSet entitiesMarkedForRemoval = new HashSet(); + + internal ComponentBitSet ImmediateBits { get { return immediateComponentStore.ComponentBitSet; } } + internal ComponentBitSet ExistingBits { get { return existingComponentStore.ComponentBitSet; } } + + public ComponentManager(DrawLayerManager drawLayerManager, Dictionary typeToIndex) { this.drawLayerManager = drawLayerManager; - this.componentUpdateManager = componentUpdateManager; + existingComponentStore = new ComponentStore(typeToIndex); + immediateComponentStore = new ComponentStore(typeToIndex); + replayStore = new ComponentDeltaStore(typeToIndex); + upToDateComponentStore = new ComponentStore(typeToIndex); + TypeToIndex = typeToIndex; } public void RegisterComponentType() where TComponent : struct, IComponent { - componentStore.RegisterComponentType(); + existingComponentStore.RegisterComponentType(); + immediateComponentStore.RegisterComponentType(); + replayStore.RegisterComponentType(); + upToDateComponentStore.RegisterComponentType(); } - internal void SetComponentStore(ComponentStore componentStore) + internal void SetExistingComponentStore(ComponentStore componentStore) { - this.componentStore.SwapWith(componentStore); + existingComponentStore.SwapWith(componentStore); } - internal void RegisterDrawableComponent(Entity entity, TComponent component, int layer) where TComponent : struct, IComponent + internal void SetUpToDateComponentStore(ComponentStore componentStore) { - drawLayerManager.RegisterComponentWithLayer(entity, component, layer); + upToDateComponentStore.SwapWith(componentStore); } - internal void AddComponent(Entity entity, TComponent component) where TComponent : struct, IComponent + internal void RegisterDrawableComponent(int entityID, TComponent component, int layer) where TComponent : struct, IComponent { - componentStore.Set(entity, component); + drawLayerManager.RegisterComponentWithLayer(entityID, component, layer); } internal void WriteComponents() { - componentStore.SwapWith(componentUpdateManager.UpToDateComponentStore); + existingComponentStore.UpdateUsing(replayStore); + existingComponentStore.ClearAllPriorities(); + upToDateComponentStore.ClearAllPriorities(); + immediateComponentStore.ClearAll(); + replayStore.ClearAll(); } - internal IEnumerable<(TComponent, Entity)> GetComponentsIncludingEntity() where TComponent : struct, IComponent + internal bool AddImmediateComponent(int entityID, TComponent component, int priority) where TComponent : struct, IComponent { - return componentStore.All(); + if (immediateComponentStore.Set(entityID, component, priority)) + { + replayStore.Set(entityID, component); + upToDateComponentStore.Set(entityID, component); + return true; + } + + return false; + } + + public bool UpdateComponent(int entityID, TComponent component, int priority) where TComponent : struct, IComponent + { + var result = upToDateComponentStore.Set(entityID, component, priority); + if (result) + { + replayStore.Set(entityID, component); + } + return result; + } + + // existing or immediate reads + + internal IEnumerable<(TComponent, int)> ReadExistingAndImmediateComponentsByType() where TComponent : struct, IComponent + { + return upToDateComponentStore.All(); + } + + internal (TComponent, int) ReadFirstExistingOrImmediateComponentByType() where TComponent : struct, IComponent + { + if (!SomeExistingOrImmediateComponent()) { throw new Exceptions.NoComponentOfTypeException($"No Component with type {typeof(TComponent)} exists"); } + var enumerator = ReadExistingAndImmediateComponentsByType().GetEnumerator(); + enumerator.MoveNext(); + return enumerator.Current; + } + + internal bool SomeExistingOrImmediateComponent() where TComponent : struct, IComponent + { + return upToDateComponentStore.Any(); + } + + // existing reads + + internal (TComponent, int) ReadFirstExistingComponentByType() where TComponent : struct, IComponent + { + if (!SomeExistingComponent()) { throw new Exceptions.NoComponentOfTypeException($"No Component with type {typeof(TComponent)} exists"); } + var enumerator = GetComponentsIncludingEntity().GetEnumerator(); + enumerator.MoveNext(); + return enumerator.Current; + } + + internal bool SomeExistingComponent() where TComponent : struct, IComponent + { + return existingComponentStore.Any(); + } + + // immediate reads + + internal IEnumerable<(TComponent, int)> ReadImmediateComponentsByType() where TComponent : struct, IComponent + { + return immediateComponentStore.All(); + } + + internal (TComponent, int) ReadFirstImmediateComponentByType() where TComponent : struct, IComponent + { + if (!SomeImmediateComponent()) { throw new Exceptions.NoComponentOfTypeException($"No Component with type {typeof(TComponent)} exists"); } + var enumerator = ReadImmediateComponentsByType().GetEnumerator(); + enumerator.MoveNext(); + return enumerator.Current; + } + + internal bool SomeImmediateComponent() where TComponent : struct, IComponent + { + return immediateComponentStore.Any(); + } + + // component getters + + internal TComponent ReadImmediateOrExistingComponentByEntityAndType(int entityID) where TComponent : struct, IComponent + { + return upToDateComponentStore.Get(entityID); + } + + internal TComponent ReadExistingComponentByEntityAndType(int entityID) where TComponent : struct, IComponent + { + return existingComponentStore.Get(entityID); + } + + internal TComponent ReadImmediateComponentByEntityAndType(int entityID) where TComponent : struct, IComponent + { + return immediateComponentStore.Get(entityID); + } + + // has checkers + + internal bool HasExistingOrImmediateComponent(int entityID) where TComponent : struct, IComponent + { + return upToDateComponentStore.Has(entityID); + } + + internal bool HasExistingOrImmediateComponent(int entityID, Type type) + { + return upToDateComponentStore.Has(type, entityID); + } + + internal bool HasExistingComponent(int entityID) where TComponent : struct, IComponent + { + return existingComponentStore.Has(entityID); + } + + internal bool HasExistingComponent(int entityID, Type type) + { + return existingComponentStore.Has(type, entityID); + } + + internal bool HasImmediateComponent(int entityID) where TComponent : struct, IComponent + { + return immediateComponentStore.Has(entityID); + } + + internal bool HasImmediateComponent(int entityID, Type type) + { + return immediateComponentStore.Has(type, entityID); + } + + internal IEnumerable<(TComponent, int)> GetComponentsIncludingEntity() where TComponent : struct, IComponent + { + return existingComponentStore.All(); } internal IEnumerable GetComponentsByType() where TComponent : struct, IComponent { - foreach (var pair in componentStore.All()) + foreach (var pair in existingComponentStore.All()) { yield return pair.Item1; } } - internal TComponent GetComponentByEntityAndType(Entity entity) where TComponent : struct, IComponent + internal TComponent GetComponentByEntityAndType(int entityID) where TComponent : struct, IComponent { - return componentStore.Get(entity); + return existingComponentStore.Get(entityID); } - internal bool EntityHasComponentOfType(Entity entity) where TComponent : struct, IComponent + internal bool EntityHasComponentOfType(int entityID) where TComponent : struct, IComponent { - return componentStore.Has(entity); + return existingComponentStore.Has(entityID); } - internal bool ComponentOfTypeExists() where TComponent : struct, IComponent + internal void MarkAllComponentsOnEntityForRemoval(int entityID) { - return componentStore.Any(); - } - - internal void MarkAllComponentsOnEntityForRemoval(Entity entity) - { - entitiesMarkedForRemoval.Add(entity); + entitiesMarkedForRemoval.Add(entityID); } internal void RemoveMarkedComponents() { - foreach (var entity in entitiesMarkedForRemoval) + foreach (var entityID in entitiesMarkedForRemoval) { - componentStore.Remove(entity); - drawLayerManager.UnRegisterEntityWithLayer(entity); + existingComponentStore.Remove(entityID); + immediateComponentStore.Remove(entityID); + replayStore.Remove(entityID); + upToDateComponentStore.Remove(entityID); + drawLayerManager.UnRegisterEntityWithLayer(entityID); } entitiesMarkedForRemoval.Clear(); } - public void Remove(Entity entity) where TComponent : struct, IComponent + public bool RemoveImmediate(int entityID, int priority) where TComponent : struct, IComponent { - componentUpdateManager.Remove(entity); - drawLayerManager.UnRegisterComponentWithLayer(entity); + if (immediateComponentStore.Remove(entityID, priority)) + { + replayStore.Remove(entityID, priority); + upToDateComponentStore.Remove(entityID, priority); + drawLayerManager.UnRegisterComponentWithLayer(entityID); + return true; + } + return false; + } + + public void Remove(int entityID, int priority) where TComponent : struct, IComponent + { + if (upToDateComponentStore.Remove(entityID, priority)) + { + replayStore.Remove(entityID, priority); + drawLayerManager.UnRegisterComponentWithLayer(entityID); + } } } } diff --git a/encompass-cs/ComponentUpdateManager.cs b/encompass-cs/ComponentUpdateManager.cs deleted file mode 100644 index 5897b13..0000000 --- a/encompass-cs/ComponentUpdateManager.cs +++ /dev/null @@ -1,179 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Encompass -{ - internal class ComponentUpdateManager - { - private readonly ComponentStore existingAndPendingComponentStore = new ComponentStore(); - private readonly ComponentStore existingComponentStore = new ComponentStore(); - private readonly ComponentStore pendingComponentStore = new ComponentStore(); - private readonly Dictionary> typeToEntityToPendingComponentPriority = new Dictionary>(128); - - public ComponentStore UpToDateComponentStore { get; private set; } = new ComponentStore(); - - public void RegisterComponentType() where TComponent : struct, IComponent - { - existingAndPendingComponentStore.RegisterComponentType(); - existingComponentStore.RegisterComponentType(); - pendingComponentStore.RegisterComponentType(); - UpToDateComponentStore.RegisterComponentType(); - } - - internal void Clear() - { - existingAndPendingComponentStore.ClearAll(); - existingComponentStore.ClearAll(); - pendingComponentStore.ClearAll(); - UpToDateComponentStore.ClearAll(); - - foreach (var dictionary in typeToEntityToPendingComponentPriority.Values) - { - dictionary.Clear(); - } - } - - internal void SetStartingComponentStore(ComponentStore componentStore) - { - UpToDateComponentStore = componentStore; - } - - internal void AddExistingComponent(Entity entity, TComponent component) where TComponent : struct, IComponent - { - RegisterExistingOrPendingComponentMessage(entity, component); - - existingComponentStore.Set(entity, component); - } - - internal bool AddPendingComponent(Entity entity, TComponent component, int priority) where TComponent : struct, IComponent - { - if (pendingComponentStore.Set(entity, component, priority)) - { - RegisterExistingOrPendingComponentMessage(entity, component); - return true; - } - - return false; - } - - private void RegisterExistingOrPendingComponentMessage(Entity entity, TComponent component) where TComponent : struct, IComponent - { - existingAndPendingComponentStore.Set(entity, component); - UpToDateComponentStore.Set(entity, component); - } - - public bool UpdateComponent(Entity entity, TComponent component, int priority) where TComponent : struct, IComponent - { - return UpToDateComponentStore.Set(entity, component, priority); - } - - // general component reads by type - - internal IEnumerable<(TComponent, Entity)> ReadExistingAndPendingComponentsByType() where TComponent : struct, IComponent - { - return existingAndPendingComponentStore.All(); - } - - internal IEnumerable<(TComponent, Entity)> ReadExistingComponentsByType() where TComponent : struct, IComponent - { - return existingComponentStore.All(); - } - - internal IEnumerable<(TComponent, Entity)> ReadPendingComponentsByType() where TComponent : struct, IComponent - { - return pendingComponentStore.All(); - } - - // singular component reads by type - - internal (TComponent, Entity) ReadFirstExistingOrPendingComponentByType() where TComponent : struct, IComponent - { - if (!SomeExistingOrPendingComponent()) { throw new Exceptions.NoComponentOfTypeException($"No Component with type {typeof(TComponent)} exists"); } - var enumerator = ReadExistingAndPendingComponentsByType().GetEnumerator(); - enumerator.MoveNext(); - return enumerator.Current; - } - - internal (TComponent, Entity) ReadFirstExistingComponentByType() where TComponent : struct, IComponent - { - if (!SomeExistingComponent()) { throw new Exceptions.NoComponentOfTypeException($"No Component with type {typeof(TComponent)} exists"); } - var enumerator = ReadExistingComponentsByType().GetEnumerator(); - enumerator.MoveNext(); - return enumerator.Current; - } - - internal (TComponent, Entity) ReadFirstPendingComponentByType() where TComponent : struct, IComponent - { - if (!SomePendingComponent()) { throw new Exceptions.NoComponentOfTypeException($"No Component with type {typeof(TComponent)} exists"); } - var enumerator = ReadPendingComponentsByType().GetEnumerator(); - enumerator.MoveNext(); - return enumerator.Current; - } - - // check if some component of type exists in the world - - internal bool SomeExistingOrPendingComponent() where TComponent : struct, IComponent - { - return existingAndPendingComponentStore.Any(); - } - - internal bool SomeExistingComponent() where TComponent : struct, IComponent - { - return existingComponentStore.Any(); - } - - internal bool SomePendingComponent() where TComponent : struct, IComponent - { - return pendingComponentStore.Any(); - } - - // read components by entity and type - - internal TComponent ReadExistingComponentByEntityAndType(Entity entity) where TComponent : struct, IComponent - { - return existingComponentStore.Get(entity); - } - - internal TComponent ReadPendingComponentByEntityAndType(Entity entity) where TComponent : struct, IComponent - { - return pendingComponentStore.Get(entity); - } - - // check if entity has component of type - - internal bool HasExistingOrPendingComponent(Entity entity) where TComponent : struct, IComponent - { - return existingAndPendingComponentStore.Has(entity); - } - - internal bool HasExistingOrPendingComponent(Entity entity, Type type) - { - return existingAndPendingComponentStore.Has(type, entity); - } - - internal bool HasExistingComponent(Entity entity) where TComponent : struct, IComponent - { - return existingComponentStore.Has(entity); - } - - internal bool HasExistingComponent(Entity entity, Type type) - { - return existingComponentStore.Has(type, entity); - } - - internal bool HasPendingComponent(Entity entity) where TComponent : struct, IComponent - { - return pendingComponentStore.Has(entity); - } - - internal bool HasPendingComponent(Entity entity, Type type) - { - return pendingComponentStore.Has(type, entity); - } - - internal void Remove(Entity entity) where TComponent : struct, IComponent - { - UpToDateComponentStore.Remove(entity); - } - } -} diff --git a/encompass-cs/DrawLayerManager.cs b/encompass-cs/DrawLayerManager.cs index 7c9a68f..b738fce 100644 --- a/encompass-cs/DrawLayerManager.cs +++ b/encompass-cs/DrawLayerManager.cs @@ -12,12 +12,13 @@ namespace Encompass private readonly Dictionary layerIndexToComponentStore = new Dictionary(512); private readonly Dictionary> layerIndexToGeneralRenderers = new Dictionary>(512); - private readonly Dictionary> typeToEntityToLayer = new Dictionary>(512); - + private readonly Dictionary> typeToEntityToLayer = new Dictionary>(512); + private Dictionary typeToIndex; public IEnumerable LayerOrder { get { return layerOrder.Values; } } - public DrawLayerManager() + public DrawLayerManager(Dictionary typeToIndex) { + this.typeToIndex = typeToIndex; RegisterDrawLayer(0); } @@ -27,7 +28,7 @@ namespace Encompass { layerOrder.Add(layer, layer); layerIndexToGeneralRenderers.Add(layer, new HashSet()); - layerIndexToComponentStore.Add(layer, new ComponentStore()); + layerIndexToComponentStore.Add(layer, new ComponentStore(typeToIndex)); } } @@ -35,7 +36,7 @@ namespace Encompass { if (!typeToEntityToLayer.ContainsKey(typeof(TComponent))) { - typeToEntityToLayer.Add(typeof(TComponent), new Dictionary(128)); + typeToEntityToLayer.Add(typeof(TComponent), new Dictionary(128)); } foreach (var pair in layerIndexToComponentStore) @@ -65,38 +66,38 @@ namespace Encompass RegisterGeneralRendererWithLayer(renderer, newLayer); } - public void RegisterComponentWithLayer(Entity entity, TComponent component, int layer) where TComponent : struct, IComponent + public void RegisterComponentWithLayer(int entityID, TComponent component, int layer) where TComponent : struct, IComponent { if (!layerIndexToComponentStore.ContainsKey(layer)) { throw new UndefinedLayerException("Layer {0} is not defined. Use WorldBuilder.RegisterDrawLayer to register the layer.", layer); } - if (typeToEntityToLayer[typeof(TComponent)].ContainsKey(entity)) { UnRegisterComponentWithLayer(entity); } + if (typeToEntityToLayer[typeof(TComponent)].ContainsKey(entityID)) { UnRegisterComponentWithLayer(entityID); } var set = layerIndexToComponentStore[layer]; - set.Set(entity, component); + set.Set(entityID, component); - typeToEntityToLayer[typeof(TComponent)].Add(entity, layer); + typeToEntityToLayer[typeof(TComponent)].Add(entityID, layer); } - public void UnRegisterComponentWithLayer(Entity entity) where TComponent : struct, IComponent + public void UnRegisterComponentWithLayer(int entityID) where TComponent : struct, IComponent { if (!typeToEntityToLayer.ContainsKey(typeof(TComponent))) { return; } - if (typeToEntityToLayer[typeof(TComponent)].ContainsKey(entity)) + if (typeToEntityToLayer[typeof(TComponent)].ContainsKey(entityID)) { - var layer = typeToEntityToLayer[typeof(TComponent)][entity]; - layerIndexToComponentStore[layer].Remove(entity); + var layer = typeToEntityToLayer[typeof(TComponent)][entityID]; + layerIndexToComponentStore[layer].ForceRemove(entityID); } - typeToEntityToLayer[typeof(TComponent)].Remove(entity); + typeToEntityToLayer[typeof(TComponent)].Remove(entityID); } - public void UnRegisterEntityWithLayer(Entity entity) + public void UnRegisterEntityWithLayer(int entityID) { foreach (var store in layerIndexToComponentStore.Values) { - store.Remove(entity); + store.Remove(entityID); } } @@ -107,11 +108,11 @@ namespace Encompass Enumerable.Empty(); } - public IEnumerable<(Entity, Type, IComponent)> AllInLayer(int layer) + public IEnumerable<(int, Type, IComponent)> AllInLayer(int layer) { return layerIndexToComponentStore.ContainsKey(layer) ? layerIndexToComponentStore[layer].AllInterfaceTyped() : - Enumerable.Empty<(Entity, Type, IComponent)>(); + Enumerable.Empty<(int, Type, IComponent)>(); } } } diff --git a/encompass-cs/Engine.cs b/encompass-cs/Engine.cs index 56a5c1a..aca9b61 100644 --- a/encompass-cs/Engine.cs +++ b/encompass-cs/Engine.cs @@ -3,6 +3,7 @@ using System.Reflection; using System.Collections.Generic; using System.Linq; using Encompass.Exceptions; +using MoonTools.FastCollections; namespace Encompass { @@ -16,11 +17,13 @@ namespace Encompass internal Guid ID; internal readonly HashSet readTypes = new HashSet(); - internal readonly HashSet readPendingTypes = new HashSet(); + internal readonly HashSet readImmediateTypes = new HashSet(); internal readonly HashSet sendTypes = new HashSet(); internal readonly HashSet receiveTypes = new HashSet(); internal readonly HashSet writeTypes = new HashSet(); - internal readonly HashSet writePendingTypes = new HashSet(); + internal readonly HashSet writeImmediateTypes = new HashSet(); + internal readonly HashSet queryWithTypes = new HashSet(); + internal readonly HashSet queryWithoutTypes = new HashSet(); internal readonly Dictionary writePriorities = new Dictionary(); internal readonly int defaultWritePriority = 0; @@ -33,8 +36,22 @@ namespace Encompass private EntityManager entityManager; private MessageManager messageManager; private ComponentManager componentManager; - private ComponentUpdateManager componentUpdateManager; private TimeManager timeManager; + private TrackingManager trackingManager; + + private EntitySetQuery entityQuery; + + private HashSet _trackedEntities = new HashSet(); + protected IEnumerable TrackedEntities + { + get + { + foreach (var entityID in _trackedEntities) + { + yield return entityManager.GetEntity(entityID); + } + } + } protected Engine() { @@ -46,10 +63,10 @@ namespace Encompass sendTypes = sendsAttribute.sendTypes; } - var activatesAttribute = GetType().GetCustomAttribute(false); + var activatesAttribute = GetType().GetCustomAttribute(false); if (activatesAttribute != null) { - writePendingTypes = activatesAttribute.writePendingTypes; + writeImmediateTypes = activatesAttribute.writeImmediateTypes; } var defaultWritePriorityAttribute = GetType().GetCustomAttribute(false); @@ -77,10 +94,22 @@ namespace Encompass readTypes = readsAttribute.readTypes; } - var readsPendingAttribute = GetType().GetCustomAttribute(false); - if (readsPendingAttribute != null) + var readsImmediateAttribute = GetType().GetCustomAttribute(false); + if (readsImmediateAttribute != null) { - readPendingTypes = readsPendingAttribute.readPendingTypes; + readImmediateTypes = readsImmediateAttribute.readImmediateTypes; + } + + var queryWithAttribute = GetType().GetCustomAttribute(false); + if (queryWithAttribute != null) + { + queryWithTypes = queryWithAttribute.queryWithTypes; + } + + var queryWithoutAttribute = GetType().GetCustomAttribute(false); + if (queryWithoutAttribute != null) + { + queryWithoutTypes = queryWithoutAttribute.queryWithoutTypes; } } @@ -119,16 +148,16 @@ namespace Encompass this.messageManager = messageManager; } - internal void AssignComponentUpdateManager(ComponentUpdateManager componentUpdateManager) - { - this.componentUpdateManager = componentUpdateManager; - } - internal void AssignTimeManager(TimeManager timeManager) { this.timeManager = timeManager; } + internal void AssignTrackingManager(TrackingManager trackingManager) + { + this.trackingManager = trackingManager; + } + /// /// Runs once per World update with the calculated delta-time. /// @@ -156,7 +185,7 @@ namespace Encompass /// protected Entity ReadEntity() where TComponent : struct, IComponent { - return ReadComponentHelper().Item2; + return entityManager.GetEntity(ReadComponentHelper().Item2); } /// @@ -166,7 +195,7 @@ namespace Encompass { foreach (var pair in ReadComponentsHelper()) { - yield return pair.Item2; + yield return entityManager.GetEntity(pair.Item2); } } @@ -177,21 +206,21 @@ namespace Encompass return componentManager.GetComponentsByType(); } - private IEnumerable<(TComponent, Entity)> ReadComponentsHelper() where TComponent : struct, IComponent + private IEnumerable<(TComponent, int)> ReadComponentsHelper() where TComponent : struct, IComponent { - var pendingRead = readPendingTypes.Contains(typeof(TComponent)); + var immediateRead = readImmediateTypes.Contains(typeof(TComponent)); var existingRead = readTypes.Contains(typeof(TComponent)); - if (existingRead && pendingRead) + if (existingRead && immediateRead) { - return componentUpdateManager.ReadExistingAndPendingComponentsByType(); + return componentManager.ReadExistingAndImmediateComponentsByType(); } else if (existingRead) { - return componentUpdateManager.ReadExistingComponentsByType(); + return componentManager.GetComponentsIncludingEntity(); } - else if (pendingRead) + else if (immediateRead) { - return componentUpdateManager.ReadPendingComponentsByType(); + return componentManager.ReadImmediateComponentsByType(); } else { @@ -215,29 +244,27 @@ namespace Encompass /// protected IEnumerable<(TComponent, Entity)> ReadComponentsIncludingEntity() where TComponent : struct, IComponent { - return ReadComponentsHelper(); - } - - internal IEnumerable<(TComponent, Entity)> InternalRead() where TComponent : struct, IComponent - { - return componentManager.GetComponentsIncludingEntity(); - } - - private (TComponent, Entity) ReadComponentHelper() where TComponent : struct, IComponent - { - var pendingRead = readPendingTypes.Contains(typeof(TComponent)); - var existingRead = readTypes.Contains(typeof(TComponent)); - if (existingRead && pendingRead) + foreach (var (component, id) in ReadComponentsHelper()) { - return componentUpdateManager.ReadFirstExistingOrPendingComponentByType(); + yield return (component, entityManager.GetEntity(id)); + } + } + + private (TComponent, int) ReadComponentHelper() where TComponent : struct, IComponent + { + var immediateRead = readImmediateTypes.Contains(typeof(TComponent)); + var existingRead = readTypes.Contains(typeof(TComponent)); + if (existingRead && immediateRead) + { + return componentManager.ReadFirstExistingOrImmediateComponentByType(); } else if (existingRead) { - return componentUpdateManager.ReadFirstExistingComponentByType(); + return componentManager.ReadFirstExistingComponentByType(); } - else if (pendingRead) + else if (immediateRead) { - return componentUpdateManager.ReadFirstPendingComponentByType(); + return componentManager.ReadFirstImmediateComponentByType(); } else { @@ -258,7 +285,8 @@ namespace Encompass /// protected (TComponent, Entity) ReadComponentIncludingEntity() where TComponent : struct, IComponent { - return ReadComponentHelper(); + var (component, id) = ReadComponentHelper(); + return (component, entityManager.GetEntity(id)); } /// @@ -266,19 +294,19 @@ namespace Encompass /// protected bool SomeComponent() where TComponent : struct, IComponent { - var pendingRead = readPendingTypes.Contains(typeof(TComponent)); + var immediateRead = readImmediateTypes.Contains(typeof(TComponent)); var existingRead = readTypes.Contains(typeof(TComponent)); - if (existingRead && pendingRead) + if (existingRead && immediateRead) { - return componentUpdateManager.SomeExistingOrPendingComponent(); + return componentManager.SomeExistingOrImmediateComponent(); } else if (existingRead) { - return componentUpdateManager.SomeExistingComponent(); + return componentManager.SomeExistingComponent(); } - else if (pendingRead) + else if (immediateRead) { - return componentUpdateManager.SomePendingComponent(); + return componentManager.SomeImmediateComponent(); } else { @@ -286,32 +314,21 @@ namespace Encompass } } - private TComponent GetComponentHelper(Entity entity) where TComponent : struct, IComponent + private TComponent GetComponentHelper(int entityID) where TComponent : struct, IComponent { - var pendingRead = readPendingTypes.Contains(typeof(TComponent)); + var immediateRead = readImmediateTypes.Contains(typeof(TComponent)); var existingRead = readTypes.Contains(typeof(TComponent)); - if (existingRead && pendingRead) + if (existingRead && immediateRead) { - if (componentUpdateManager.HasPendingComponent(entity)) - { - return componentUpdateManager.ReadPendingComponentByEntityAndType(entity); - } - else if (componentUpdateManager.HasExistingComponent(entity)) - { - return componentUpdateManager.ReadExistingComponentByEntityAndType(entity); - } - else - { - throw new NoComponentOfTypeOnEntityException("No Component of type {0} exists on Entity {1}", typeof(TComponent).Name, entity.ID); - } + return componentManager.ReadImmediateOrExistingComponentByEntityAndType(entityID); } else if (existingRead) { - return componentUpdateManager.ReadExistingComponentByEntityAndType(entity); + return componentManager.ReadExistingComponentByEntityAndType(entityID); } - else if (pendingRead) + else if (immediateRead) { - return componentUpdateManager.ReadPendingComponentByEntityAndType(entity); + return componentManager.ReadImmediateComponentByEntityAndType(entityID); } else { @@ -330,7 +347,7 @@ namespace Encompass /// protected TComponent GetComponent(Entity entity) where TComponent : struct, IComponent { - return GetComponentHelper(entity); + return GetComponentHelper(entity.ID); } /// @@ -341,20 +358,20 @@ namespace Encompass /// protected bool HasComponent(Entity entity) where TComponent : struct, IComponent { - var pendingRead = readPendingTypes.Contains(typeof(TComponent)); + var immediateRead = readImmediateTypes.Contains(typeof(TComponent)); var existingRead = readTypes.Contains(typeof(TComponent)); - if (pendingRead && existingRead) + if (immediateRead && existingRead) { - return componentUpdateManager.HasExistingOrPendingComponent(entity); + return componentManager.HasExistingOrImmediateComponent(entity.ID); } else if (existingRead) { - return componentUpdateManager.HasExistingComponent(entity); + return componentManager.HasExistingComponent(entity.ID); } - else if (pendingRead) + else if (immediateRead) { - return componentUpdateManager.HasPendingComponent(entity); + return componentManager.HasImmediateComponent(entity.ID); } else { @@ -370,20 +387,20 @@ namespace Encompass /// protected bool HasComponent(Entity entity, Type type) { - var pendingRead = readPendingTypes.Contains(type); + var immediateRead = readImmediateTypes.Contains(type); var existingRead = readTypes.Contains(type); - if (pendingRead && existingRead) + if (immediateRead && existingRead) { - return componentUpdateManager.HasExistingOrPendingComponent(entity, type); + return componentManager.HasExistingOrImmediateComponent(entity.ID, type); } else if (existingRead) { - return componentUpdateManager.HasExistingComponent(entity, type); + return componentManager.HasExistingComponent(entity.ID, type); } - else if (pendingRead) + else if (immediateRead) { - return componentUpdateManager.HasPendingComponent(entity, type); + return componentManager.HasImmediateComponent(entity.ID, type); } else { @@ -407,18 +424,27 @@ namespace Encompass } bool written; - if (writePendingTypes.Contains(typeof(TComponent))) + if (writeImmediateTypes.Contains(typeof(TComponent))) { - written = AddPendingComponent(entity, component, priority); + written = componentManager.AddImmediateComponent(entity.ID, component, priority); + if (written) + { + trackingManager.ImmediateUpdateTracking(entity.ID, typeof(TComponent)); + } } else { - written = componentUpdateManager.UpdateComponent(entity, component, priority); + written = componentManager.UpdateComponent(entity.ID, component, priority); + } + + if (!componentManager.HasExistingComponent(entity.ID)) + { + trackingManager.RegisterAddition(entity.ID, typeof(TComponent)); } if (written && component is IDrawableComponent drawableComponent) { - componentManager.RegisterDrawableComponent(entity, component, drawableComponent.Layer); + componentManager.RegisterDrawableComponent(entity.ID, component, drawableComponent.Layer); } } @@ -456,16 +482,6 @@ namespace Encompass messageManager.AddMessageIgnoringTimeDilation(message, time); } - internal void AddExistingComponent(Entity entity, TComponent component) where TComponent : struct, IComponent - { - componentUpdateManager.AddExistingComponent(entity, component); - } - - internal bool AddPendingComponent(Entity entity, TComponent component, int priority) where TComponent : struct, IComponent - { - return componentUpdateManager.AddPendingComponent(entity, component, priority); - } - /// /// Reads all messages of the specified Type. /// @@ -515,7 +531,7 @@ namespace Encompass /// protected void Destroy(Entity entity) { - entityManager.MarkForDestroy(entity); + entityManager.MarkForDestroy(entity.ID); } /// @@ -544,12 +560,31 @@ namespace Encompass /// Note that the Engine must Read the Component type that is being removed. /// If a Component with the specified type does not exist on the Entity, returns false and does not mutate the Entity. /// - protected bool RemoveComponent(Entity entity) where TComponent : struct, IComponent + protected void RemoveComponent(Entity entity) where TComponent : struct, IComponent { - if (!HasComponent(entity)) { return false; } + var priority = writePriorities.ContainsKey(typeof(TComponent)) ? writePriorities[typeof(TComponent)] : defaultWritePriority; - componentManager.Remove(entity); - return true; + if (!writeTypes.Contains(typeof(TComponent))) + { + throw new IllegalWriteException("Engine {0} tried to remove undeclared Component {1}. Declare with Writes attribute.", GetType().Name, typeof(TComponent).Name); + } + + if (writeImmediateTypes.Contains(typeof(TComponent))) + { + if (componentManager.RemoveImmediate(entity.ID, priority)) + { + trackingManager.ImmediateUpdateTracking(entity.ID, typeof(TComponent)); + } + } + else + { + componentManager.Remove(entity.ID, priority); + } + + if (componentManager.HasExistingComponent(entity.ID)) + { + trackingManager.RegisterRemoval(entity.ID, typeof(TComponent)); + } } /// @@ -561,7 +596,7 @@ namespace Encompass /// The time that will elapse before time is fully dilated, in real time. /// The length of real time that time will be fully dilated. /// The time that will elapse before time is fully undilated. - public void ActivateTimeDilation(double factor, double easeInTime, double activeTime, double easeOutTime) + protected void ActivateTimeDilation(double factor, double easeInTime, double activeTime, double easeOutTime) { timeManager.ActivateTimeDilation(factor, easeInTime, activeTime, easeOutTime); } @@ -576,7 +611,7 @@ namespace Encompass /// An easing function for the easing in of time dilation. /// The length of real time that time will be fully dilated. /// The time that will elapse before time is fully undilated. - public void ActivateTimeDilation(double factor, double easeInTime, System.Func easeInFunction, double activeTime, double easeOutTime) + protected void ActivateTimeDilation(double factor, double easeInTime, System.Func easeInFunction, double activeTime, double easeOutTime) { timeManager.ActivateTimeDilation(factor, easeInTime, easeInFunction, activeTime, easeOutTime); } @@ -591,7 +626,7 @@ namespace Encompass /// The length of real time that time will be fully dilated. /// The time that will elapse before time is fully undilated. /// An easing function for the easing out of time dilation. - public void ActivateTimeDilation(double factor, double easeInTime, double activeTime, double easeOutTime, System.Func easeOutFunction) + protected void ActivateTimeDilation(double factor, double easeInTime, double activeTime, double easeOutTime, System.Func easeOutFunction) { timeManager.ActivateTimeDilation(factor, easeInTime, activeTime, easeOutTime, easeOutFunction); } @@ -607,9 +642,87 @@ namespace Encompass /// The length of real time that time will be fully dilated. /// The time that will elapse before time is fully undilated. /// An easing function for the easing out of time dilation. - public void ActivateTimeDilation(double factor, double easeInTime, System.Func easeInFunction, double activeTime, double easeOutTime, System.Func easeOutFunction) + protected void ActivateTimeDilation(double factor, double easeInTime, System.Func easeInFunction, double activeTime, double easeOutTime, System.Func easeOutFunction) { timeManager.ActivateTimeDilation(factor, easeInTime, easeInFunction, activeTime, easeOutTime, easeOutFunction); } + + /// + /// Efficiently reads Messages of a given type that all reference the same Entity. + /// + /// The Message subtype. + /// The entity that all messages in the IEnumerable refer to. + /// + protected IEnumerable ReadMessagesWithEntity(Entity entity) where TMessage : struct, IMessage, IHasEntity + { + return messageManager.WithEntity(entity.ID); + } + + internal void CheckAndUpdateTracking(int entityID) + { + if (_trackedEntities.Contains(entityID) && !entityQuery.CheckEntity(entityID, componentManager.ExistingBits)) + { + _trackedEntities.Remove(entityID); + } + else if (!_trackedEntities.Contains(entityID) && entityQuery.CheckEntity(entityID, componentManager.ExistingBits)) + { + _trackedEntities.Add(entityID); + } + } + + internal void ImmediateCheckAndUpdateTracking(int entityID) + { + if (_trackedEntities.Contains(entityID) && !entityQuery.ImmediateCheckEntity(entityID, componentManager.ImmediateBits, componentManager.ExistingBits)) + { + _trackedEntities.Remove(entityID); + } + else if (!_trackedEntities.Contains(entityID) && entityQuery.ImmediateCheckEntity(entityID, componentManager.ImmediateBits, componentManager.ExistingBits)) + { + _trackedEntities.Add(entityID); + } + } + + /// + /// Returns an empty EntitySetQuery. Can be modified and iterated over to obtain Entities that fit the given criteria. + /// + internal void BuildEntityQuery() + { + var withMask = BitSet512.Zero; + foreach (var type in queryWithTypes) + { + withMask = withMask.Set(componentManager.TypeToIndex[type]); + } + + var withoutMask = BitSet512.Zero; + foreach (var type in queryWithoutTypes) + { + withoutMask = withoutMask.Set(componentManager.TypeToIndex[type]); + } + + var immediateMask = BitSet512.Zero; + foreach (var type in readImmediateTypes) + { + immediateMask = immediateMask.Set(componentManager.TypeToIndex[type]); + } + + var existingMask = BitSet512.Zero; + foreach (var type in readTypes) + { + existingMask = existingMask.Set(componentManager.TypeToIndex[type]); + } + + entityQuery = new EntitySetQuery( + withMask & immediateMask, + withMask & existingMask, + withoutMask & immediateMask, + withoutMask & existingMask, + ~withMask + ); + } + + internal void RegisterDestroyedEntity(int entityID) + { + _trackedEntities.Remove(entityID); + } } } diff --git a/encompass-cs/Engines/ComponentEmitter.cs b/encompass-cs/Engines/ComponentEmitter.cs deleted file mode 100644 index 95b61b0..0000000 --- a/encompass-cs/Engines/ComponentEmitter.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Encompass -{ - internal class ComponentEmitter : Engine where TComponent : struct, IComponent - { - public override void Update(double dt) - { - foreach (var (component, entity) in InternalRead()) - { - AddExistingComponent(entity, component); - } - } - } -} diff --git a/encompass-cs/Entity.cs b/encompass-cs/Entity.cs index a08954c..7287499 100644 --- a/encompass-cs/Entity.cs +++ b/encompass-cs/Entity.cs @@ -8,21 +8,16 @@ namespace Encompass /// public struct Entity : IEquatable { - public readonly Guid ID; + public readonly int ID; - internal Entity(Guid id) + internal Entity(int id) { ID = id; } public override bool Equals(object obj) { - if (obj is Entity entity) - { - return Equals(entity); - } - - return false; + return obj is Entity entity && Equals(entity); } public bool Equals(Entity other) diff --git a/encompass-cs/EntityManager.cs b/encompass-cs/EntityManager.cs index 919d4c1..d9e9337 100644 --- a/encompass-cs/EntityManager.cs +++ b/encompass-cs/EntityManager.cs @@ -1,53 +1,80 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; +using Encompass.Exceptions; namespace Encompass { internal class EntityManager { - private readonly HashSet IDs = new HashSet(); - - private readonly HashSet entitiesMarkedForDestroy = new HashSet(); + private readonly int entityCapacity; + private readonly IDManager idManager = new IDManager(); + private readonly HashSet IDs = new HashSet(); + + private readonly HashSet entitiesMarkedForDestroy = new HashSet(); private readonly ComponentManager componentManager; - public EntityManager(ComponentManager componentManager) + public IEnumerable EntityIDs + { + get { return IDs; } + } + + public EntityManager(ComponentManager componentManager, int entityCapacity) { this.componentManager = componentManager; + this.entityCapacity = entityCapacity; + } + + private int NextID() + { + return idManager.NextID(); } public Entity CreateEntity() { - var id = NextID(); - var entity = new Entity(id); - IDs.Add(id); - return entity; + if (IDs.Count < entityCapacity) + { + var id = NextID(); + var entity = new Entity(id); + IDs.Add(id); + return entity; + } + else + { + throw new EntityOverflowException("The number of entities has exceeded the entity capacity of {0}", entityCapacity); + } } - public bool EntityExists(Guid id) + public bool EntityExists(int id) { return IDs.Contains(id); } - public void MarkForDestroy(Entity entity) + public Entity GetEntity(int id) { - entitiesMarkedForDestroy.Add(entity); + if (!EntityExists(id)) + { + throw new Encompass.Exceptions.EntityNotFoundException("Entity with id {0} does not exist.", id); + } + + return new Entity(id); } - public void DestroyMarkedEntities() + public void MarkForDestroy(int entityID) { - foreach (var entity in entitiesMarkedForDestroy) + entitiesMarkedForDestroy.Add(entityID); + } + + public void DestroyMarkedEntities(IEnumerable engines) + { + foreach (var entityID in entitiesMarkedForDestroy) { - componentManager.MarkAllComponentsOnEntityForRemoval(entity); - IDs.Remove(entity.ID); + foreach (var engine in engines) { engine.RegisterDestroyedEntity(entityID); } + componentManager.MarkAllComponentsOnEntityForRemoval(entityID); + IDs.Remove(entityID); + idManager.Free(entityID); } entitiesMarkedForDestroy.Clear(); } - - private Guid NextID() - { - return Guid.NewGuid(); - } } } diff --git a/encompass-cs/EntitySetQuery.cs b/encompass-cs/EntitySetQuery.cs new file mode 100644 index 0000000..978a13b --- /dev/null +++ b/encompass-cs/EntitySetQuery.cs @@ -0,0 +1,48 @@ +using MoonTools.FastCollections; + +namespace Encompass +{ + internal struct EntitySetQuery + { + private BitSet512 WithImmediateMask { get; } + private BitSet512 WithExistingMask { get; } + private BitSet512 WithoutImmediateMask { get; } + private BitSet512 WithoutExistingMask { get; } + private BitSet512 NotWithMask { get; } + + internal EntitySetQuery(BitSet512 withImmediateMask, BitSet512 withExistingMask, BitSet512 withoutImmediateMask, BitSet512 withoutExistingMask, BitSet512 notWithMask) + { + WithImmediateMask = withImmediateMask; + WithExistingMask = withExistingMask; + WithoutImmediateMask = withoutImmediateMask; + WithoutExistingMask = withoutExistingMask; + NotWithMask = notWithMask; + } + + public bool CheckEntity(int entityID, ComponentBitSet componentBitSet) + { + var existingBits = componentBitSet.EntityBitArray(entityID); + var existing = (WithExistingMask & existingBits) | NotWithMask; + + var existingForbidden = ~(WithoutExistingMask & existingBits); + + return (existing & existingForbidden).AllTrue(); + } + + public bool ImmediateCheckEntity(int entityID, ComponentBitSet immediateBitLookup, ComponentBitSet existingBitLookup) + { + var immediateBits = immediateBitLookup.EntityBitArray(entityID); + var existingBits = existingBitLookup.EntityBitArray(entityID); + + var immediate = WithImmediateMask & immediateBits; + var existing = WithExistingMask & existingBits; + var withCheck = immediate | existing | NotWithMask; + + var immediateForbidden = ~(WithoutImmediateMask & immediateBits); + var existingForbidden = ~(WithoutExistingMask & existingBits); + var withoutCheck = immediateForbidden & existingForbidden; + + return (withCheck & withoutCheck).AllTrue(); + } + } +} diff --git a/encompass-cs/Exceptions/ComponentNotFoundException.cs b/encompass-cs/Exceptions/EntityOverflowException.cs similarity index 63% rename from encompass-cs/Exceptions/ComponentNotFoundException.cs rename to encompass-cs/Exceptions/EntityOverflowException.cs index 5f1a988..37b942c 100644 --- a/encompass-cs/Exceptions/ComponentNotFoundException.cs +++ b/encompass-cs/Exceptions/EntityOverflowException.cs @@ -2,9 +2,9 @@ using System; namespace Encompass.Exceptions { - public class ComponentNotFoundException : Exception + public class EntityOverflowException : Exception { - public ComponentNotFoundException( + public EntityOverflowException( string format, params object[] args ) : base(string.Format(format, args)) { } diff --git a/encompass-cs/Exceptions/ComponentTypeMismatchException.cs b/encompass-cs/Exceptions/IllegalWriteImmediateException.cs similarity index 62% rename from encompass-cs/Exceptions/ComponentTypeMismatchException.cs rename to encompass-cs/Exceptions/IllegalWriteImmediateException.cs index 17caf96..3d991af 100644 --- a/encompass-cs/Exceptions/ComponentTypeMismatchException.cs +++ b/encompass-cs/Exceptions/IllegalWriteImmediateException.cs @@ -2,9 +2,9 @@ namespace Encompass.Exceptions { - public class ComponentTypeMismatchException : Exception + public class IllegalWriteImmediateException : Exception { - public ComponentTypeMismatchException( + public IllegalWriteImmediateException( string format, params object[] args ) : base(string.Format(format, args)) { } diff --git a/encompass-cs/Exceptions/IllegalWritePendingException.cs b/encompass-cs/Exceptions/IllegalWritePendingException.cs deleted file mode 100644 index 3983c23..0000000 --- a/encompass-cs/Exceptions/IllegalWritePendingException.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Encompass.Exceptions -{ - public class IllegalWritePendingException : Exception - { - public IllegalWritePendingException( - string format, - params object[] args - ) : base(string.Format(format, args)) { } - } -} diff --git a/encompass-cs/Exceptions/IllegalWritePendingTypeException.cs b/encompass-cs/Exceptions/IllegalWritePendingTypeException.cs index 19554be..3d388f0 100644 --- a/encompass-cs/Exceptions/IllegalWritePendingTypeException.cs +++ b/encompass-cs/Exceptions/IllegalWritePendingTypeException.cs @@ -2,9 +2,9 @@ using System; namespace Encompass.Exceptions { - public class IllegalWritePendingTypeException : Exception + public class IllegalWriteImmediateTypeException : Exception { - public IllegalWritePendingTypeException( + public IllegalWriteImmediateTypeException( string format, params object[] args ) : base(string.Format(format, args)) { } diff --git a/encompass-cs/Exceptions/RepeatUpdateComponentException.cs b/encompass-cs/Exceptions/RepeatUpdateComponentException.cs deleted file mode 100644 index 4b178fb..0000000 --- a/encompass-cs/Exceptions/RepeatUpdateComponentException.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Encompass.Exceptions -{ - public class RepeatUpdateComponentException : Exception - { - public RepeatUpdateComponentException( - string format, - params object[] args - ) : base(string.Format(format, args)) { } - } -} diff --git a/encompass-cs/IDManager.cs b/encompass-cs/IDManager.cs new file mode 100644 index 0000000..01bb529 --- /dev/null +++ b/encompass-cs/IDManager.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; + +namespace Encompass +{ + internal class IDManager + { + int nextID = 0; + + private Stack availableIDs = new Stack(); + + public int NextID() + { + if (availableIDs.Count > 0) + { + return availableIDs.Pop(); + } + else + { + return nextID++; + } + } + + public void Free(int ID) + { + availableIDs.Push(ID); + } + } +} diff --git a/encompass-cs/Interfaces/IHasEntity.cs b/encompass-cs/Interfaces/IHasEntity.cs new file mode 100644 index 0000000..9d3493e --- /dev/null +++ b/encompass-cs/Interfaces/IHasEntity.cs @@ -0,0 +1,7 @@ +namespace Encompass +{ + public interface IHasEntity + { + Entity Entity { get; } + } +} diff --git a/encompass-cs/MessageManager.cs b/encompass-cs/MessageManager.cs index 7da56bf..1256946 100644 --- a/encompass-cs/MessageManager.cs +++ b/encompass-cs/MessageManager.cs @@ -14,7 +14,7 @@ namespace Encompass internal void AddMessage(TMessage message) where TMessage : struct, IMessage { - messageStore.AddMessage(message); + messageStore.AddMessage(message); } internal void AddMessage(TMessage message, double time) where TMessage : struct, IMessage @@ -51,5 +51,10 @@ namespace Encompass { return messageStore.First(); } + + internal IEnumerable WithEntity(int entityID) where TMessage : struct, IMessage, IHasEntity + { + return messageStore.WithEntity(entityID); + } } } diff --git a/encompass-cs/RenderManager.cs b/encompass-cs/RenderManager.cs index c1f7e92..529a431 100644 --- a/encompass-cs/RenderManager.cs +++ b/encompass-cs/RenderManager.cs @@ -5,12 +5,14 @@ namespace Encompass { internal class RenderManager { + private readonly EntityManager entityManager; private readonly DrawLayerManager drawLayerManager; private readonly Dictionary> drawComponentTypeToOrderedRenderer = new Dictionary>(256); - public RenderManager(DrawLayerManager drawLayerManager) + public RenderManager(EntityManager entityManager, DrawLayerManager drawLayerManager) { + this.entityManager = entityManager; this.drawLayerManager = drawLayerManager; } @@ -31,12 +33,12 @@ namespace Encompass { var generalRendererSet = drawLayerManager.GeneralRenderersByLayer(layer); - foreach (var (entity, componentType, component) in drawLayerManager.AllInLayer(layer)) + foreach (var (entityID, componentType, component) in drawLayerManager.AllInLayer(layer)) { if (drawComponentTypeToOrderedRenderer.ContainsKey(componentType)) { var internalRenderAction = drawComponentTypeToOrderedRenderer[componentType]; - internalRenderAction(entity, component); + internalRenderAction(entityManager.GetEntity(entityID), component); } } diff --git a/encompass-cs/Renderer.cs b/encompass-cs/Renderer.cs index c4a1dab..3dba03c 100644 --- a/encompass-cs/Renderer.cs +++ b/encompass-cs/Renderer.cs @@ -37,7 +37,10 @@ namespace Encompass protected IEnumerable<(TComponent, Entity)> ReadComponentsIncludingEntity() where TComponent : struct, IComponent { - return componentManager.GetComponentsIncludingEntity(); + foreach (var (component, id) in componentManager.GetComponentsIncludingEntity()) + { + yield return (component, entityManager.GetEntity(id)); + } } protected TComponent ReadComponent() where TComponent : struct, IComponent @@ -56,17 +59,17 @@ namespace Encompass protected TComponent GetComponent(Entity entity) where TComponent : struct, IComponent { - return componentManager.GetComponentByEntityAndType(entity); + return componentManager.GetComponentByEntityAndType(entity.ID); } protected bool HasComponent(Entity entity) where TComponent : struct, IComponent { - return componentManager.EntityHasComponentOfType(entity); + return componentManager.EntityHasComponentOfType(entity.ID); } protected bool SomeComponent() where TComponent : struct, IComponent { - return componentManager.ComponentOfTypeExists(); + return componentManager.SomeExistingComponent(); } } } diff --git a/encompass-cs/TrackingManager.cs b/encompass-cs/TrackingManager.cs new file mode 100644 index 0000000..c9e7830 --- /dev/null +++ b/encompass-cs/TrackingManager.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; + +namespace Encompass +{ + internal class TrackingManager + { + private Dictionary> _immediateComponentTypesToEngines = new Dictionary>(); + private Dictionary> _componentTypesToEngines = new Dictionary>(); + + private HashSet<(int, Type)> _additions = new HashSet<(int, Type)>(); + private HashSet<(int, Type)> _removals = new HashSet<(int, Type)>(); + + private HashSet<(int, Engine)> _pairsToCheck = new HashSet<(int, Engine)>(); + + public void RegisterComponentTypeToEngine(Type type, Engine engine) + { + if (!_componentTypesToEngines.ContainsKey(type)) { _componentTypesToEngines.Add(type, new HashSet()); } + _componentTypesToEngines[type].Add(engine); + } + + public void RegisterImmediateComponentTypeToEngine(Type type, Engine engine) + { + if (!_immediateComponentTypesToEngines.ContainsKey(type)) { _immediateComponentTypesToEngines.Add(type, new HashSet()); } + _immediateComponentTypesToEngines[type].Add(engine); + } + + public void RegisterAddition(int entityID, Type type) + { + _additions.Add((entityID, type)); + } + + public void RegisterRemoval(int entityID, Type type) + { + _removals.Add((entityID, type)); + } + + public void InitializeTracking(IEnumerable entityIDs) + { + foreach (var entityID in entityIDs) + { + foreach (var engineSet in _componentTypesToEngines.Values) + { + foreach (var engine in engineSet) + { + engine.CheckAndUpdateTracking(entityID); + } + } + } + } + + public void ImmediateUpdateTracking(int entityID, Type componentType) + { + if (_immediateComponentTypesToEngines.ContainsKey(componentType)) + { + foreach (var engine in _componentTypesToEngines[componentType]) + { + engine.ImmediateCheckAndUpdateTracking(entityID); + } + } + } + + public void UpdateTracking() + { + // TODO: optimize so we only check each entity/engine pair once + foreach (var (entity, componentType) in _additions) + { + if (_componentTypesToEngines.ContainsKey(componentType)) + { + foreach (var engine in _componentTypesToEngines[componentType]) + { + _pairsToCheck.Add((entity, engine)); + } + } + } + _additions.Clear(); + + foreach (var (entity, componentType) in _removals) + { + if (_componentTypesToEngines.ContainsKey(componentType)) + { + foreach (var engine in _componentTypesToEngines[componentType]) + { + _pairsToCheck.Add((entity, engine)); + } + } + } + _removals.Clear(); + + foreach (var (entity, engine) in _pairsToCheck) + { + engine.CheckAndUpdateTracking(entity); + } + _pairsToCheck.Clear(); + } + } +} diff --git a/encompass-cs/UberEngine.cs b/encompass-cs/UberEngine.cs index 7566b82..7abfc7d 100644 --- a/encompass-cs/UberEngine.cs +++ b/encompass-cs/UberEngine.cs @@ -9,9 +9,9 @@ namespace Encompass { private IEnumerable _componentTypes; private IEnumerable _messageTypes; - private Entity _entity; + public Entity Entity { get; private set; } - public UberEngine(Entity entity, IEnumerable componentTypes, IEnumerable messageTypes) + public UberEngine(IEnumerable componentTypes, IEnumerable messageTypes) { _componentTypes = componentTypes; _messageTypes = messageTypes; @@ -19,14 +19,15 @@ namespace Encompass writeTypes.UnionWith(componentTypes); sendTypes.UnionWith(messageTypes); receiveTypes.UnionWith(messageTypes); - _entity = entity; } public void Write() { + Entity = CreateEntity(); + foreach (var type in _componentTypes) { - var instanceParam = new object[] { _entity, Activator.CreateInstance(type) }; + var instanceParam = new object[] { Entity, Activator.CreateInstance(type) }; var setComponentMethod = typeof(Engine).GetMethod("SetComponent", BindingFlags.NonPublic | BindingFlags.Instance); var genericSetComponentMethod = setComponentMethod.MakeGenericMethod(type); genericSetComponentMethod.Invoke(this, instanceParam); @@ -43,12 +44,12 @@ namespace Encompass CallGenericMethod(type, "ReadComponentsIncludingEntity", null); CallGenericMethod(type, "ReadEntity", null); CallGenericMethod(type, "ReadEntities", null); - CallGenericMethod(type, "GetComponent", new Type[] { typeof(Entity) }, new object[] { _entity }); - CallGenericMethod(type, "HasComponent", new Type[] { typeof(Entity) }, new object[] { _entity }); + CallGenericMethod(type, "GetComponent", new Type[] { typeof(Entity) }, new object[] { Entity }); + CallGenericMethod(type, "HasComponent", new Type[] { typeof(Entity) }, new object[] { Entity }); CallGenericMethod(type, "SomeComponent", null); - CallGenericMethod(type, "RemoveComponent", new Type[] { typeof(Entity) }, new object[] { _entity }); CallGenericMethod(type, "DestroyWith", null); CallGenericMethod(type, "DestroyAllWith", null); + CallGenericMethod(type, "RemoveComponent", new Type[] { typeof(Entity) }, new object[] { Entity }); } foreach (var type in _messageTypes) @@ -59,6 +60,10 @@ namespace Encompass CallGenericMethod(type, "ReadMessage", null); CallGenericMethod(type, "ReadMessages", null); CallGenericMethod(type, "SomeMessage", null); + if (typeof(IHasEntity).IsAssignableFrom(type)) + { + CallGenericMethod(type, "ReadMessagesWithEntity", new Type[] { typeof(Entity) }, new object[] { Entity }); + } } } diff --git a/encompass-cs/UberRenderer.cs b/encompass-cs/UberRenderer.cs index be84634..be4bde4 100644 --- a/encompass-cs/UberRenderer.cs +++ b/encompass-cs/UberRenderer.cs @@ -6,13 +6,17 @@ namespace Encompass { class UberRenderer : Renderer { - private Entity _entity; private IEnumerable _componentTypes; + private Entity _entity; - public UberRenderer(Entity entity, IEnumerable componentTypes) + public UberRenderer(IEnumerable componentTypes) + { + _componentTypes = componentTypes; + } + + public void SetEntity(Entity entity) { _entity = entity; - _componentTypes = componentTypes; } public void Render() diff --git a/encompass-cs/World.cs b/encompass-cs/World.cs index 1154ca3..677bf8f 100644 --- a/encompass-cs/World.cs +++ b/encompass-cs/World.cs @@ -10,8 +10,8 @@ namespace Encompass private readonly List enginesInOrder; private readonly EntityManager entityManager; private readonly ComponentManager componentManager; + private readonly TrackingManager trackingManager; private readonly MessageManager messageManager; - private readonly ComponentUpdateManager componentUpdateManager; private readonly TimeManager timeManager; private readonly RenderManager renderManager; @@ -19,8 +19,8 @@ namespace Encompass List enginesInOrder, EntityManager entityManager, ComponentManager componentManager, + TrackingManager trackingManager, MessageManager messageManager, - ComponentUpdateManager componentUpdateManager, TimeManager timeManager, RenderManager renderManager ) @@ -28,8 +28,8 @@ namespace Encompass this.enginesInOrder = enginesInOrder; this.entityManager = entityManager; this.componentManager = componentManager; + this.trackingManager = trackingManager; this.messageManager = messageManager; - this.componentUpdateManager = componentUpdateManager; this.timeManager = timeManager; this.renderManager = renderManager; } @@ -40,6 +40,7 @@ namespace Encompass /// The time in seconds that has passed since the previous frame. public void Update(double dt) { + trackingManager.UpdateTracking(); messageManager.ProcessDelayedMessages(dt); timeManager.Update(dt); @@ -56,12 +57,10 @@ namespace Encompass } messageManager.ClearMessages(); - entityManager.DestroyMarkedEntities(); + entityManager.DestroyMarkedEntities(enginesInOrder); - componentManager.WriteComponents(); componentManager.RemoveMarkedComponents(); - - componentUpdateManager.Clear(); + componentManager.WriteComponents(); } /// diff --git a/encompass-cs/WorldBuilder.cs b/encompass-cs/WorldBuilder.cs index 90b1eea..368d694 100644 --- a/encompass-cs/WorldBuilder.cs +++ b/encompass-cs/WorldBuilder.cs @@ -18,18 +18,19 @@ namespace Encompass /// public class WorldBuilder { + private readonly int entityCapacity; private readonly List engines = new List(); private readonly DirectedGraph engineGraph = GraphBuilder.DirectedGraph(); - private readonly ComponentStore startingComponentStoreForComponentManager = new ComponentStore(); - private readonly ComponentStore startingComponentStoreForComponentUpdateManager = new ComponentStore(); + private readonly ComponentStore startingExistingComponentStore; + private readonly ComponentStore startingUpToDateComponentStore; private readonly ComponentManager componentManager; private readonly EntityManager entityManager; private readonly MessageManager messageManager; - private readonly ComponentUpdateManager componentUpdateManager; private readonly TimeManager timeManager; private readonly DrawLayerManager drawLayerManager; private readonly RenderManager renderManager; + private readonly TrackingManager trackingManager; private readonly Dictionary> typeToReaders = new Dictionary>(); @@ -39,15 +40,21 @@ namespace Encompass private readonly HashSet messageTypes = new HashSet(); - public WorldBuilder() + private readonly Dictionary typeToIndex = new Dictionary(); + + public WorldBuilder(int entityCapacity = 32768) { - drawLayerManager = new DrawLayerManager(); + this.entityCapacity = entityCapacity; + drawLayerManager = new DrawLayerManager(typeToIndex); timeManager = new TimeManager(); - componentUpdateManager = new ComponentUpdateManager(); - componentManager = new ComponentManager(drawLayerManager, componentUpdateManager); + trackingManager = new TrackingManager(); + componentManager = new ComponentManager(drawLayerManager, typeToIndex); messageManager = new MessageManager(timeManager); - entityManager = new EntityManager(componentManager); - renderManager = new RenderManager(drawLayerManager); + entityManager = new EntityManager(componentManager, entityCapacity); + renderManager = new RenderManager(entityManager, drawLayerManager); + + startingExistingComponentStore = new ComponentStore(typeToIndex); + startingUpToDateComponentStore = new ComponentStore(typeToIndex); } /// @@ -82,22 +89,25 @@ namespace Encompass RegisterComponentType(); componentTypesToRegister.Add(typeof(TComponent)); - startingComponentStoreForComponentManager.Set(entity, component); - startingComponentStoreForComponentUpdateManager.Set(entity, component); + startingExistingComponentStore.Set(entity.ID, component); + startingUpToDateComponentStore.Set(entity.ID, component); if (component is IDrawableComponent drawableComponent) { - componentManager.RegisterDrawableComponent(entity, component, drawableComponent.Layer); + componentManager.RegisterDrawableComponent(entity.ID, component, drawableComponent.Layer); drawLayerManager.RegisterOrderedDrawable(); } } internal void RegisterComponentType() where TComponent : struct, IComponent { - componentManager.RegisterComponentType(); - componentUpdateManager.RegisterComponentType(); - startingComponentStoreForComponentManager.RegisterComponentType(); - startingComponentStoreForComponentUpdateManager.RegisterComponentType(); + if (!typeToIndex.ContainsKey(typeof(TComponent))) + { + typeToIndex.Add(typeof(TComponent), typeToIndex.Count); + componentManager.RegisterComponentType(); + startingExistingComponentStore.RegisterComponentType(); + startingUpToDateComponentStore.RegisterComponentType(); + } } internal void RegisterMessageTypes(IEnumerable types) @@ -119,8 +129,8 @@ namespace Encompass engine.AssignEntityManager(entityManager); engine.AssignComponentManager(componentManager); engine.AssignMessageManager(messageManager); - engine.AssignComponentUpdateManager(componentUpdateManager); engine.AssignTimeManager(timeManager); + engine.AssignTrackingManager(trackingManager); engines.Add(engine); engineGraph.AddNode(engine); @@ -130,9 +140,9 @@ namespace Encompass RegisterMessageTypes(engine.receiveTypes.Union(engine.sendTypes)); - foreach (var writePendingType in engine.writePendingTypes.Intersect(engine.readPendingTypes)) + foreach (var writeImmediateType in engine.writeImmediateTypes.Intersect(engine.readImmediateTypes)) { - throw new EngineSelfCycleException("Engine {0} both writes and reads pending Component {1}", engine.GetType().Name, writePendingType.Name); + throw new EngineSelfCycleException("Engine {0} both writes and reads immediate Component {1}", engine.GetType().Name, writeImmediateType.Name); } foreach (var messageType in messageReceiveTypes.Intersect(messageSendTypes)) @@ -140,17 +150,26 @@ namespace Encompass throw new EngineSelfCycleException("Engine {0} both receives and sends Message {1}", engine.GetType().Name, messageType.Name); } - if (messageSendTypes.Count > 0 || engine.writePendingTypes.Count > 0) + if (messageSendTypes.Count > 0 || engine.writeImmediateTypes.Count > 0) { senders.Add(engine); } - foreach (var componentType in engine.readTypes.Union(engine.writeTypes).Union(engine.readPendingTypes)) + foreach (var componentType in engine.queryWithTypes.Union(engine.queryWithoutTypes)) + { + trackingManager.RegisterComponentTypeToEngine(componentType, engine); + if (engine.readImmediateTypes.Contains(componentType)) + { + trackingManager.RegisterImmediateComponentTypeToEngine(componentType, engine); + } + } + + foreach (var componentType in engine.readTypes.Union(engine.writeTypes).Union(engine.readImmediateTypes)) { AddComponentTypeToRegister(componentType); } - foreach (var receiveType in engine.receiveTypes.Union(engine.readPendingTypes)) + foreach (var receiveType in engine.receiveTypes.Union(engine.readImmediateTypes)) { if (!typeToReaders.ContainsKey(receiveType)) { @@ -160,9 +179,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; } @@ -206,7 +222,7 @@ namespace Encompass { foreach (var senderEngine in senders) { - foreach (var messageType in senderEngine.sendTypes.Union(senderEngine.writePendingTypes)) + foreach (var messageType in senderEngine.sendTypes.Union(senderEngine.writeImmediateTypes)) { if (typeToReaders.ContainsKey(messageType)) { @@ -355,10 +371,6 @@ namespace Encompass var method = typeof(WorldBuilder).GetMethod("RegisterComponentType", BindingFlags.NonPublic | BindingFlags.Instance); var generic = method.MakeGenericMethod(registeredComponentType); generic.Invoke(this, null); - - var emitterEngine = (Engine)Activator.CreateInstance(typeof(ComponentEmitter<>).MakeGenericType(registeredComponentType)); - AddEngine(emitterEngine); - engineOrder.Add(emitterEngine); } PreloadJIT(componentTypesToRegister, messageTypes); @@ -366,20 +378,23 @@ namespace Encompass foreach (var engine in engineGraph.TopologicalSort()) { engineOrder.Add(engine); + engine.BuildEntityQuery(); } var world = new World( engineOrder, entityManager, componentManager, + trackingManager, messageManager, - componentUpdateManager, timeManager, renderManager ); - componentUpdateManager.SetStartingComponentStore(startingComponentStoreForComponentUpdateManager); - componentManager.SetComponentStore(startingComponentStoreForComponentManager); + componentManager.SetExistingComponentStore(startingExistingComponentStore); + componentManager.SetUpToDateComponentStore(startingUpToDateComponentStore); + + trackingManager.InitializeTracking(entityManager.EntityIDs); return world; } @@ -387,31 +402,30 @@ namespace Encompass /// /// This is necessary because Encompass heavily uses generic methods with value types, /// so the first time any code path runs the JIT gets smashed. This method warms up the runtime. - /// It does so by grabbing all component and message types known to the WorldBuilder and + /// It does so by grabbing all component and message types known to the WorldBuilder and /// executing every possible generic method that could be executed with those types. /// private void PreloadJIT(IEnumerable componentTypes, IEnumerable messageTypes) { var dummyTimeManager = new TimeManager(); var dummyMessageManager = new MessageManager(dummyTimeManager); - var dummyDrawLayerManager = new DrawLayerManager(); - var dummyComponentUpdateManager = new ComponentUpdateManager(); - var dummyComponentManager = new ComponentManager(dummyDrawLayerManager, dummyComponentUpdateManager); - var dummyEntityManager = new EntityManager(dummyComponentManager); - var dummyRenderManager = new RenderManager(dummyDrawLayerManager); + var dummyDrawLayerManager = new DrawLayerManager(typeToIndex); + var dummyTrackingManager = new TrackingManager(); + var dummyComponentManager = new ComponentManager(dummyDrawLayerManager, typeToIndex); + var dummyEntityManager = new EntityManager(dummyComponentManager, entityCapacity); + var dummyRenderManager = new RenderManager(dummyEntityManager, dummyDrawLayerManager); var prepEngineOrder = new List(); - var entity = dummyEntityManager.CreateEntity(); - var uberEngine = new UberEngine(entity, componentTypes, messageTypes); + var uberEngine = new UberEngine(componentTypes, messageTypes); uberEngine.AssignEntityManager(dummyEntityManager); uberEngine.AssignComponentManager(dummyComponentManager); uberEngine.AssignMessageManager(dummyMessageManager); - uberEngine.AssignComponentUpdateManager(dummyComponentUpdateManager); uberEngine.AssignTimeManager(dummyTimeManager); + uberEngine.AssignTrackingManager(dummyTrackingManager); - var uberRenderer = new UberRenderer(entity, componentTypes); + var uberRenderer = new UberRenderer(componentTypes); uberRenderer.AssignComponentManager(dummyComponentManager); uberRenderer.AssignEntityManager(dummyEntityManager); @@ -421,25 +435,12 @@ namespace Encompass var componentManagerRegisterGenericMethod = componentManagerRegisterMethod.MakeGenericMethod(type); componentManagerRegisterGenericMethod.Invoke(dummyComponentManager, null); - var componentUpdateManagerRegisterMethod = typeof(ComponentUpdateManager).GetMethod("RegisterComponentType"); - var componentUpdateManagerRegisterGenericMethod = componentUpdateManagerRegisterMethod.MakeGenericMethod(type); - componentUpdateManagerRegisterGenericMethod.Invoke(dummyComponentUpdateManager, null); - if (type.GetInterface("IDrawableComponent") != null) { var drawLayerManagerRegisterMethod = typeof(DrawLayerManager).GetMethod("RegisterOrderedDrawable"); var drawLayerManagerRegisterGenericMethod = drawLayerManagerRegisterMethod.MakeGenericMethod(type); drawLayerManagerRegisterGenericMethod.Invoke(dummyDrawLayerManager, null); } - - var emitterEngine = (Engine)Activator.CreateInstance(typeof(ComponentEmitter<>).MakeGenericType(type)); - emitterEngine.AssignEntityManager(dummyEntityManager); - emitterEngine.AssignComponentManager(dummyComponentManager); - emitterEngine.AssignMessageManager(dummyMessageManager); - emitterEngine.AssignComponentUpdateManager(dummyComponentUpdateManager); - emitterEngine.AssignTimeManager(dummyTimeManager); - - prepEngineOrder.Add(emitterEngine); } prepEngineOrder.Add(uberEngine); @@ -448,22 +449,21 @@ namespace Encompass prepEngineOrder, dummyEntityManager, dummyComponentManager, + dummyTrackingManager, dummyMessageManager, - dummyComponentUpdateManager, dummyTimeManager, dummyRenderManager ); uberEngine.Write(); dummyComponentManager.WriteComponents(); - dummyComponentUpdateManager.Clear(); dummyWorld.Update(1); uberEngine.Write(); dummyComponentManager.WriteComponents(); - dummyComponentUpdateManager.Clear(); + uberRenderer.SetEntity(uberEngine.Entity); uberRenderer.Render(); } } diff --git a/encompass-cs/encompass-cs.csproj b/encompass-cs/encompass-cs.csproj index be72ace..37a5ba2 100644 --- a/encompass-cs/encompass-cs.csproj +++ b/encompass-cs/encompass-cs.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 Encompass @@ -24,5 +24,7 @@ + + - + \ No newline at end of file diff --git a/test/ComponentTest.cs b/test/ComponentTest.cs index febbd0b..1b94e51 100644 --- a/test/ComponentTest.cs +++ b/test/ComponentTest.cs @@ -75,6 +75,7 @@ namespace Tests var world = worldBuilder.Build(); world.Update(0.01); + world.Update(0.01); } [Test] @@ -97,7 +98,6 @@ namespace Tests } [Reads(typeof(MockComponent))] - [WritesPending(typeof(MockComponent))] [Writes(typeof(MockComponent))] class OverwriteEngine : Engine { @@ -105,12 +105,11 @@ namespace Tests { foreach (var (mockComponent, entity) in ReadComponentsIncludingEntity()) { - SetComponent(entity, new MockComponent { myInt = 420 }); + SetComponent(entity, new MockComponent { myInt = mockComponent.myInt + 1 }); } } } - [ReadsPending(typeof(MockComponent))] [Reads(typeof(MockComponent))] class ReadMockComponentEngine : Engine { @@ -128,12 +127,20 @@ namespace Tests worldBuilder.AddEngine(new ReadMockComponentEngine()); var entity = worldBuilder.CreateEntity(); - worldBuilder.SetComponent(entity, new MockComponent { }); + worldBuilder.SetComponent(entity, new MockComponent { myInt = 420 }); var world = worldBuilder.Build(); world.Update(0.01); Assert.That(gottenMockComponent.myInt, Is.EqualTo(420)); + + world.Update(0.01); + + Assert.That(gottenMockComponent.myInt, Is.EqualTo(421)); + + world.Update(0.01); + + Assert.That(gottenMockComponent.myInt, Is.EqualTo(422)); } [Reads(typeof(MockComponent))] @@ -193,7 +200,7 @@ namespace Tests } } - [WritesPending(typeof(MockComponent))] + [WritesImmediate(typeof(MockComponent))] [Receives(typeof(AddMockComponentMessage))] [Writes(typeof(MockComponent))] class AddMockComponentEngine : Engine @@ -207,7 +214,7 @@ namespace Tests } } - [ReadsPending(typeof(MockComponent))] + [ReadsImmediate(typeof(MockComponent))] class HasMockComponentEngine : Engine { private Entity entity; @@ -258,6 +265,7 @@ namespace Tests var world = worldBuilder.Build(); + world.Update(0.01); world.Update(0.01); Assert.AreEqual(mockComponent, gottenMockComponent); @@ -369,6 +377,7 @@ namespace Tests [Reads(typeof(MockComponent))] [Receives(typeof(RemoveComponentTestMessage))] + [Writes(typeof(MockComponent))] class RemoveComponentTestEngine : Engine { public override void Update(double dt) @@ -463,8 +472,8 @@ namespace Tests } [Receives(typeof(CheckHasMockComponentMessage))] - [ReadsPending(typeof(MockComponent))] - class CheckHasPendingMockComponentEngine : Engine + [ReadsImmediate(typeof(MockComponent))] + class CheckHasImmediateMockComponentEngine : Engine { public override void Update(double dt) { diff --git a/test/EngineTest.cs b/test/EngineTest.cs index 1edd31f..de4d956 100644 --- a/test/EngineTest.cs +++ b/test/EngineTest.cs @@ -415,6 +415,88 @@ namespace Tests Assert.Throws(() => world.Update(0.01f)); } + struct EntityMessage : IMessage, IHasEntity + { + public EntityMessage(Entity entity, int myInt) + { + Entity = entity; + MyInt = myInt; + } + + public Entity Entity { get; } + public int MyInt { get; } + } + + [Sends(typeof(EntityMessage), typeof(MockMessage))] + class EntityMessageEmitterEngine : Engine + { + private Entity _entity; + + public EntityMessageEmitterEngine(Entity entity) + { + _entity = entity; + } + + public override void Update(double dt) + { + SendMessage(new EntityMessage(_entity, 2)); + SendMessage(new EntityMessage(_entity, 4)); + SendMessage(new EntityMessage(_entity, 5)); + SendMessage(new MockMessage()); + } + } + + static List entityMessageResults; + + [Receives(typeof(EntityMessage))] + class EntityMessageReceiverEngine : Engine + { + private Entity _entity; + + public EntityMessageReceiverEngine(Entity entity) + { + _entity = entity; + } + + public override void Update(double dt) + { + entityMessageResults = ReadMessagesWithEntity(_entity).ToList(); + } + } + + [Test] + public void MessagesWithEntity() + { + var worldBuilder = new WorldBuilder(); + + var entity = worldBuilder.CreateEntity(); + worldBuilder.AddEngine(new EntityMessageEmitterEngine(entity)); + worldBuilder.AddEngine(new EntityMessageReceiverEngine(entity)); + + var world = worldBuilder.Build(); + + world.Update(0.01); + + entityMessageResults.Should().HaveCount(3); + entityMessageResults.Should().ContainEquivalentOf(new EntityMessage(entity, 2)); + entityMessageResults.Should().ContainEquivalentOf(new EntityMessage(entity, 4)); + entityMessageResults.Should().ContainEquivalentOf(new EntityMessage(entity, 5)); + } + + [Test] + public void NoMessagesWithEntity() + { + var worldBuilder = new WorldBuilder(); + + var entity = worldBuilder.CreateEntity(); + worldBuilder.AddEngine(new EntityMessageReceiverEngine(entity)); + + var world = worldBuilder.Build(); + world.Update(0.01); + + entityMessageResults.Should().BeEmpty(); + } + class SomeComponentTestEngine : Engine { public override void Update(double dt) @@ -593,7 +675,8 @@ namespace Tests Assert.That(results, Does.Not.Contain((mockComponent, entity))); } - [Reads(typeof(DestroyerComponent), typeof(MockComponent))] + [Reads(typeof(DestroyerComponent))] + [Writes(typeof(MockComponent))] class DestroyAndAddComponentEngine : Engine { public override void Update(double dt) @@ -624,7 +707,7 @@ namespace Tests } [Reads(typeof(MockComponent))] - [WritesPending(typeof(MockComponent))] + [WritesImmediate(typeof(MockComponent))] [Writes(typeof(MockComponent))] class AddAndRemoveMockComponentEngine : Engine { @@ -640,8 +723,8 @@ namespace Tests static Entity entityResult; - [ReadsPending(typeof(MockComponent))] - class GetEntityFromPendingReadComponents : Engine + [ReadsImmediate(typeof(MockComponent))] + class GetEntityFromImmediateReadComponents : Engine { public override void Update(double dt) { @@ -650,11 +733,11 @@ namespace Tests } [Test] - public void GetEntityFromPendingComponentID() + public void GetEntityFromImmediateComponentID() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new AddAndRemoveMockComponentEngine()); - worldBuilder.AddEngine(new GetEntityFromPendingReadComponents()); + worldBuilder.AddEngine(new GetEntityFromImmediateReadComponents()); var entity = worldBuilder.CreateEntity(); worldBuilder.SetComponent(entity, new MockComponent()); @@ -665,6 +748,7 @@ namespace Tests } [Reads(typeof(MockComponent))] + [Writes(typeof(MockComponent))] class DelayedMessageEngine : Engine { public override void Update(double dt) @@ -711,6 +795,7 @@ namespace Tests } [Reads(typeof(MockComponent))] + [Writes(typeof(MockComponent))] class DelayedMessageIgnoringTimeDilationEngine : Engine { public override void Update(double dt) @@ -753,8 +838,8 @@ namespace Tests } [Receives(typeof(MockMessage))] - [WritesPending(typeof(MockComponent))] - [Writes(typeof(MockComponent))] + [WritesImmediate(typeof(MockComponent))] + [Writes(typeof(MockComponent), 1)] class ActivateComponentEngine : Engine { public override void Update(double dt) @@ -767,7 +852,8 @@ namespace Tests } } - [ReadsPending(typeof(MockComponent))] + [ReadsImmediate(typeof(MockComponent))] + [Writes(typeof(MockComponent), 0)] class RemoveComponentEngine : Engine { public override void Update(double dt) @@ -780,17 +866,20 @@ namespace Tests } [Test] - public void EngineAddAndRemoveComponentSameFrame() + public void EngineAddAndRemoveComponentSameFrameWithRemovePriority() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ActivateComponentEngine()); worldBuilder.AddEngine(new RemoveComponentEngine()); + worldBuilder.AddEngine(new ReadComponentsTestEngine()); worldBuilder.SendMessage(new MockMessage { }); var world = worldBuilder.Build(); Assert.DoesNotThrow(() => world.Update(0.01)); + world.Update(0.01); // update again for the read + resultComponents.Should().BeEmpty(); } struct DestroyComponentMessage : IMessage { public Entity entity; } @@ -826,7 +915,7 @@ namespace Tests worldBuilder.AddEngine(new ReadEntityByComponentTypeEngine()); var entity = worldBuilder.CreateEntity(); - worldBuilder.SetComponent(entity, new MockComponent { }); + worldBuilder.SetComponent(entity, new MockComponent()); var world = worldBuilder.Build(); world.Update(0.01); @@ -834,7 +923,69 @@ namespace Tests entity.Should().BeEquivalentTo(readEntity); } + struct MockComponentB : IComponent + { + public MockComponentB(int value) + { + this.value = value; + } + int value; + } + + static MockComponentB getComponentResult; + + [Reads(typeof(MockComponent), typeof(MockComponentB))] + class GetComponentEngine : Engine + { + public override void Update(double dt) + { + getComponentResult = GetComponent(ReadEntity()); + } + } + + [Test] + public void GetComponent() + { + var worldBuilder = new WorldBuilder(); + + var entity = worldBuilder.CreateEntity(); + + worldBuilder.SetComponent(entity, new MockComponent()); + worldBuilder.SetComponent(entity, new MockComponentB(3)); + worldBuilder.AddEngine(new GetComponentEngine()); + + var world = worldBuilder.Build(); + + world.Update(0.01); + + getComponentResult.Should().BeEquivalentTo(new MockComponentB(3)); + } + + [Reads(typeof(MockComponent), typeof(MockComponentB))] + class GetComponentExceptionEngine : Engine + { + public override void Update(double dt) + { + foreach (var entity in ReadEntities()) + { + GetComponent(entity); + } + } + } + + [Test] + public void GetComponentWhenComponentIsNotOnEntity() + { + var worldBuilder = new WorldBuilder(); + worldBuilder.AddEngine(new GetComponentExceptionEngine()); + + var entity = worldBuilder.CreateEntity(); + worldBuilder.SetComponent(entity, new MockComponent()); + + var world = worldBuilder.Build(); + Assert.Throws(() => world.Update(0.01)); + } static Entity[] readEntities; @@ -926,6 +1077,7 @@ namespace Tests } [Reads(typeof(MockComponent))] + [Writes(typeof(MockComponent))] class RemoveComponentByTypeEngine : Engine { public override void Update(double dt) @@ -1087,5 +1239,536 @@ namespace Tests undilatedDeltaTime.Should().Be(0.5); } + + public class QueryTests + { + struct MockComponentB : IComponent { } + struct MockComponentC : IComponent { } + struct MockComponentD : IComponent { } + + [Reads(typeof(MockComponent), typeof(MockComponentB))] + [Writes(typeof(MockComponentB))] + [QueryWith(typeof(MockComponent), typeof(MockComponentB))] + class EntityQueryWithComponentsEngine : Engine + { + private List entities; + + public EntityQueryWithComponentsEngine(List entities) + { + this.entities = entities; + } + + public override void Update(double dt) + { + entities.Clear(); + foreach (var entity in TrackedEntities) + { + entities.Add(entity); + RemoveComponent(entity); + } + } + } + + [Test] + public void EntitiesWithComponents() + { + var worldBuilder = new WorldBuilder(); + + var entity = worldBuilder.CreateEntity(); + var entityB = worldBuilder.CreateEntity(); + var entityC = worldBuilder.CreateEntity(); + + worldBuilder.SetComponent(entity, new MockComponent()); + worldBuilder.SetComponent(entity, new MockComponentB()); + + worldBuilder.SetComponent(entityB, new MockComponent()); + worldBuilder.SetComponent(entityB, new MockComponentB()); + + worldBuilder.SetComponent(entityC, new MockComponentB()); + + var queriedEntities = new List(); + worldBuilder.AddEngine(new EntityQueryWithComponentsEngine(queriedEntities)); + + var world = worldBuilder.Build(); + + world.Update(0.01); + + queriedEntities.Should().BeEquivalentTo(new Entity[] { entity, entityB }); + + world.Update(0.01); + + queriedEntities.Should().BeEmpty(); + } + + [Reads(typeof(MockComponent))] + [Writes(typeof(MockComponent))] + [QueryWithout(typeof(MockComponent))] + class EntityQueryWithoutComponentsEngine : Engine + { + private List entities; + + public EntityQueryWithoutComponentsEngine(List entities) + { + this.entities = entities; + } + + public override void Update(double dt) + { + entities.Clear(); + foreach (var entity in TrackedEntities) + { + entities.Add(entity); + SetComponent(entity, new MockComponent()); + } + } + } + + [Test] + public void EntitiesWithoutComponents() + { + var worldBuilder = new WorldBuilder(); + + var entity = worldBuilder.CreateEntity(); + var entityB = worldBuilder.CreateEntity(); + var entityC = worldBuilder.CreateEntity(); + + worldBuilder.SetComponent(entity, new MockComponent()); + worldBuilder.SetComponent(entity, new MockComponentB()); + + worldBuilder.SetComponent(entityB, new MockComponent()); + worldBuilder.SetComponent(entityB, new MockComponentB()); + + worldBuilder.SetComponent(entityC, new MockComponentB()); + + var queriedEntities = new List(); + worldBuilder.AddEngine(new EntityQueryWithoutComponentsEngine(queriedEntities)); + + var world = worldBuilder.Build(); + + world.Update(0.01); + + queriedEntities.ToArray().Should().BeEquivalentTo(new Entity[] { entityC }); + + world.Update(0.01); + + queriedEntities.Should().BeEmpty(); + } + + [Reads(typeof(MockComponent), typeof(MockComponentB), typeof(MockComponentD))] + [QueryWith(typeof(MockComponent), typeof(MockComponentB))] + [QueryWithout(typeof(MockComponentD))] + class EntityQueryWithandWithoutComponentsEngine : Engine + { + private List entities; + + public EntityQueryWithandWithoutComponentsEngine(List entities) + { + this.entities = entities; + } + + public override void Update(double dt) + { + entities.Clear(); + + entities.AddRange(TrackedEntities); + } + } + + [Test] + public void EntitiesWithAndWithoutComponents() + { + var worldBuilder = new WorldBuilder(); + + var entity = worldBuilder.CreateEntity(); + var entityB = worldBuilder.CreateEntity(); + var entityC = worldBuilder.CreateEntity(); + var entityD = worldBuilder.CreateEntity(); + + worldBuilder.SetComponent(entity, new MockComponent()); + worldBuilder.SetComponent(entity, new MockComponentB()); + worldBuilder.SetComponent(entity, new MockComponentD()); + + worldBuilder.SetComponent(entityB, new MockComponent()); + + worldBuilder.SetComponent(entityC, new MockComponent()); + worldBuilder.SetComponent(entityC, new MockComponentB()); + worldBuilder.SetComponent(entityC, new MockComponentC()); + worldBuilder.SetComponent(entityC, new MockComponentD()); + + worldBuilder.SetComponent(entityD, new MockComponent()); + worldBuilder.SetComponent(entityD, new MockComponentB()); + worldBuilder.SetComponent(entityD, new MockComponentC()); + + var queriedEntities = new List(); + worldBuilder.AddEngine(new EntityQueryWithandWithoutComponentsEngine(queriedEntities)); + + var world = worldBuilder.Build(); + + world.Update(0.01); + + queriedEntities.ToArray().Should().BeEquivalentTo(new Entity[] { entityD }); + } + + [Reads(typeof(MockComponent))] + [WritesImmediate(typeof(MockComponentB))] + [Writes(typeof(MockComponentB), 0)] + class AddImmediateComponentEngine : Engine + { + public override void Update(double dt) + { + foreach (var entity in ReadEntities()) + { + SetComponent(entity, new MockComponentB()); + } + } + } + + [ReadsImmediate(typeof(MockComponentB))] + [QueryWith(typeof(MockComponentB))] + class EntityQueryWithImmediateComponentsEngine : Engine + { + private List entities; + + public EntityQueryWithImmediateComponentsEngine(List entities) + { + this.entities = entities; + } + + public override void Update(double dt) + { + entities.Clear(); + entities.AddRange(TrackedEntities); + } + } + + [Test] + public void EntitiesWithImmediateComponents() + { + var worldBuilder = new WorldBuilder(); + + var entity = worldBuilder.CreateEntity(); + var entityB = worldBuilder.CreateEntity(); + + worldBuilder.SetComponent(entity, new MockComponent()); + + var queriedEntities = new List(); + worldBuilder.AddEngine(new AddImmediateComponentEngine()); + worldBuilder.AddEngine(new EntityQueryWithImmediateComponentsEngine(queriedEntities)); + + var world = worldBuilder.Build(); + + world.Update(0.01); + + queriedEntities.ToArray().Should().BeEquivalentTo(new Entity[] { entity }); + } + + [ReadsImmediate(typeof(MockComponentB))] + [QueryWithout(typeof(MockComponentB))] + class EntityQueryWithoutImmediateComponentsEngine : Engine + { + private List entities; + + public EntityQueryWithoutImmediateComponentsEngine(List entities) + { + this.entities = entities; + } + + public override void Update(double dt) + { + entities.Clear(); + entities.AddRange(TrackedEntities); + } + } + + [Test] + public void EntitiesWithoutImmediateComponents() + { + var worldBuilder = new WorldBuilder(); + + var entity = worldBuilder.CreateEntity(); + var entityB = worldBuilder.CreateEntity(); + + worldBuilder.SetComponent(entity, new MockComponent()); + + var queriedEntities = new List(); + worldBuilder.AddEngine(new AddImmediateComponentEngine()); + worldBuilder.AddEngine(new EntityQueryWithoutImmediateComponentsEngine(queriedEntities)); + + var world = worldBuilder.Build(); + + world.Update(0.01); + + queriedEntities.ToArray().Should().BeEquivalentTo(new Entity[] { entityB }); + } + + [Reads(typeof(MockComponentC), typeof(MockComponentD))] + [WritesImmediate(typeof(MockComponent), typeof(MockComponentB))] + [Writes(typeof(MockComponent), 0)] + [Writes(typeof(MockComponentB), 0)] + class ConditionallyAddImmediateComponentsEngine : Engine + { + public override void Update(double dt) + { + foreach (var entity in ReadEntities()) + { + SetComponent(entity, new MockComponent()); + } + + foreach (var entity in ReadEntities()) + { + SetComponent(entity, new MockComponent()); + SetComponent(entity, new MockComponentB()); + } + } + } + + [ReadsImmediate(typeof(MockComponent), typeof(MockComponentB))] + [QueryWith(typeof(MockComponent))] + [QueryWithout(typeof(MockComponentB))] + class EntityQueryWithAndWithoutImmediateComponentsEngine : Engine + { + private List entities; + + public EntityQueryWithAndWithoutImmediateComponentsEngine(List entities) + { + this.entities = entities; + } + + public override void Update(double dt) + { + entities.Clear(); + + entities.AddRange(TrackedEntities); + } + } + + [Test] + public void EntitiesWithAndWithoutImmediateComponents() + { + var worldBuilder = new WorldBuilder(); + + var entity = worldBuilder.CreateEntity(); + var entityB = worldBuilder.CreateEntity(); + var entityC = worldBuilder.CreateEntity(); + + worldBuilder.SetComponent(entityB, new MockComponentC()); + worldBuilder.SetComponent(entityC, new MockComponentD()); + + var queriedEntities = new List(); + worldBuilder.AddEngine(new ConditionallyAddImmediateComponentsEngine()); + worldBuilder.AddEngine(new EntityQueryWithAndWithoutImmediateComponentsEngine(queriedEntities)); + + var world = worldBuilder.Build(); + + world.Update(0.01); + + queriedEntities.ToArray().Should().BeEquivalentTo(new Entity[] { entityB }); + } + + [Reads(typeof(MockComponentC))] + [WritesImmediate(typeof(MockComponentB))] + [Writes(typeof(MockComponentB), 0)] + class ConditionallyAddImmediateComponentEngine : Engine + { + public override void Update(double dt) + { + foreach (var entity in ReadEntities()) + { + SetComponent(entity, new MockComponentB()); + } + } + } + + [ReadsImmediate(typeof(MockComponentB))] + [Reads(typeof(MockComponent))] + [QueryWith(typeof(MockComponent), typeof(MockComponentB))] + class EntityQueryWithImmediateAndNonImmediateComponents : Engine + { + private List entities; + + public EntityQueryWithImmediateAndNonImmediateComponents(List entities) + { + this.entities = entities; + } + + public override void Update(double dt) + { + entities.Clear(); + entities.AddRange(TrackedEntities); + } + } + + [Test] + public void EntitiesWithImmediateAndNonImmediateComponents() + { + var worldBuilder = new WorldBuilder(); + + var entity = worldBuilder.CreateEntity(); + var entityB = worldBuilder.CreateEntity(); + var entityC = worldBuilder.CreateEntity(); + + worldBuilder.SetComponent(entityB, new MockComponent()); + worldBuilder.SetComponent(entityB, new MockComponentC()); + worldBuilder.SetComponent(entityC, new MockComponentD()); + + var queriedEntities = new List(); + worldBuilder.AddEngine(new ConditionallyAddImmediateComponentEngine()); + worldBuilder.AddEngine(new EntityQueryWithImmediateAndNonImmediateComponents(queriedEntities)); + + var world = worldBuilder.Build(); + + world.Update(0.01); + + queriedEntities.ToArray().Should().BeEquivalentTo(new Entity[] { entityB }); + } + + [ReadsImmediate(typeof(MockComponentB))] + class ReadImmediateComponentsEngine : Engine + { + private List _components; + + public ReadImmediateComponentsEngine(List components) + { + _components = components; + } + public override void Update(double dt) + { + _components.AddRange(ReadComponents()); + } + } + + [Test] + public void ReadImmediateComponents() + { + var worldBuilder = new WorldBuilder(); + + var _components = new List(); + + var entity = worldBuilder.CreateEntity(); + worldBuilder.SetComponent(entity, new MockComponent()); + + worldBuilder.AddEngine(new AddImmediateComponentEngine()); + worldBuilder.AddEngine(new ReadImmediateComponentsEngine(_components)); + + var world = worldBuilder.Build(); + world.Update(0.01); + + _components.Should().NotBeEmpty(); + } + + [ReadsImmediate(typeof(MockComponentB))] + [Reads(typeof(MockComponent))] + class HasAndGetImmediateComponentEngine : Engine + { + private List _components; + + public HasAndGetImmediateComponentEngine(List components) + { + _components = components; + } + + public override void Update(double dt) + { + foreach (var (component, entity) in ReadComponentsIncludingEntity()) + { + if (HasComponent(entity)) + { + _components.Add(GetComponent(entity)); + + } + } + } + } + + [Test] + public void HasAndGetImmediateComponent() + { + var worldBuilder = new WorldBuilder(); + + var _components = new List(); + + var entity = worldBuilder.CreateEntity(); + worldBuilder.SetComponent(entity, new MockComponent()); + + worldBuilder.AddEngine(new AddImmediateComponentEngine()); + worldBuilder.AddEngine(new HasAndGetImmediateComponentEngine(_components)); + + var world = worldBuilder.Build(); + world.Update(0.01); + + _components.Should().NotBeEmpty(); + } + + struct MockTimerComponent : IComponent + { + public MockTimerComponent(double time) + { + Timer = time; + } + + public double Timer { get; set; } + } + + [Reads(typeof(MockTimerComponent))] + [Writes(typeof(MockTimerComponent))] + class ReadWhileRemovingComponentsEngine : Engine + { + public override void Update(double dt) + { + foreach (var (component, entity) in ReadComponentsIncludingEntity()) + { + var updatedComponent = component; + updatedComponent.Timer -= dt; + + if (updatedComponent.Timer <= 0) + { + RemoveComponent(entity); + + } + else + { + SetComponent(entity, updatedComponent); + } + } + } + } + + [Test] + public void ReadWhileRemovingComponents() + { + var worldBuilder = new WorldBuilder(); + + var entity = worldBuilder.CreateEntity(); + worldBuilder.SetComponent(entity, new MockTimerComponent(0.5)); + + var entityB = worldBuilder.CreateEntity(); + worldBuilder.SetComponent(entityB, new MockTimerComponent(0.4)); + + worldBuilder.AddEngine(new ReadWhileRemovingComponentsEngine()); + + var world = worldBuilder.Build(); + Assert.DoesNotThrow(() => world.Update(0.2)); + Assert.DoesNotThrow(() => world.Update(0.25)); + } + + [Test] + public void DestroyedEntitiesAreRemovedFromTracking() + { + var worldBuilder = new WorldBuilder(); + + var entity = worldBuilder.CreateEntity(); + worldBuilder.SetComponent(entity, new MockComponent()); + + worldBuilder.AddEngine(new DestroyWithEngine()); + worldBuilder.AddEngine(new ReadEntitiesWithComponentTypeEngine()); + + var world = worldBuilder.Build(); + + world.Update(0.01); + world.Update(0.01); + + readEntities.Should().BeEmpty(); + } + } } } diff --git a/test/EntityTest.cs b/test/EntityTest.cs index 89b0520..afced33 100644 --- a/test/EntityTest.cs +++ b/test/EntityTest.cs @@ -2,6 +2,7 @@ using NUnit.Framework; using FluentAssertions; using Encompass; +using Encompass.Exceptions; namespace Tests { @@ -20,5 +21,18 @@ namespace Tests Assert.AreEqual(entity, entity); Assert.IsTrue(entity == copyEntity); } + + [Test] + public void EntityOverflowException() + { + var worldBuilder = new WorldBuilder(16); + + for (var i = 0; i < 16; i++) + { + worldBuilder.CreateEntity(); + } + + Assert.Throws(() => worldBuilder.CreateEntity()); + } } -} \ No newline at end of file +} diff --git a/test/SpawnerTest.cs b/test/SpawnerTest.cs index a7f8158..c0c5f0c 100644 --- a/test/SpawnerTest.cs +++ b/test/SpawnerTest.cs @@ -19,7 +19,7 @@ namespace Tests } } - [WritesPending(typeof(TestComponent))] + [WritesImmediate(typeof(TestComponent))] [Writes(typeof(TestComponent))] class TestSpawner : Spawner { diff --git a/test/WorldBuilderTest.cs b/test/WorldBuilderTest.cs index 8e75f8a..2fd62fd 100644 --- a/test/WorldBuilderTest.cs +++ b/test/WorldBuilderTest.cs @@ -153,7 +153,7 @@ namespace Tests [Receives(typeof(SetMessage))] [Writes(typeof(AComponent), 0)] - [WritesPending(typeof(AComponent))] + [WritesImmediate(typeof(AComponent))] class AEngine : Engine { public override void Update(double dt) @@ -167,7 +167,7 @@ namespace Tests [Receives(typeof(SetMessage))] [Writes(typeof(AComponent), 1)] - [WritesPending(typeof(AComponent))] + [WritesImmediate(typeof(AComponent))] class BEngine : Engine { public override void Update(double dt) @@ -181,7 +181,7 @@ namespace Tests static AComponent resultComponent; - [ReadsPending(typeof(AComponent))] + [ReadsImmediate(typeof(AComponent))] class ReadComponentEngine : Engine { public override void Update(double dt) @@ -222,7 +222,7 @@ namespace Tests [Receives(typeof(SetMessage))] [Writes(typeof(AComponent))] - [WritesPending(typeof(AComponent))] + [WritesImmediate(typeof(AComponent))] [Encompass.DefaultWritePriority(4)] class AEngine : Engine { @@ -238,7 +238,7 @@ namespace Tests [Receives(typeof(SetMessage))] [Writes(typeof(AComponent), 3)] - [WritesPending(typeof(AComponent))] + [WritesImmediate(typeof(AComponent))] class BEngine : Engine { public override void Update(double dt) @@ -252,7 +252,7 @@ namespace Tests [Receives(typeof(SetMessage))] [Writes(typeof(AComponent), 2)] - [WritesPending(typeof(AComponent))] + [WritesImmediate(typeof(AComponent))] class CEngine : Engine { public override void Update(double dt) @@ -266,7 +266,7 @@ namespace Tests static AComponent resultComponent; - [ReadsPending(typeof(AComponent))] + [ReadsImmediate(typeof(AComponent))] class ReadComponentEngine : Engine { public override void Update(double dt)