From 9813c260ae2472a0c90411681ca8811f1ca443a1 Mon Sep 17 00:00:00 2001 From: thatcosmonaut Date: Tue, 20 Aug 2019 19:25:59 -0700 Subject: [PATCH 01/13] started the component write priority system --- TODO | 2 + encompass-cs/Attributes/Activates.cs | 26 ----- encompass-cs/Attributes/Updates.cs | 27 ----- encompass-cs/Attributes/Writes.cs | 40 +++++++ encompass-cs/Attributes/WritesPending.cs | 26 +++++ encompass-cs/ComponentManager.cs | 54 ++++++--- encompass-cs/ComponentMessageManager.cs | 23 +++- encompass-cs/Engine.cs | 106 ++++++++++-------- encompass-cs/Engines/ComponentUpdater.cs | 4 +- .../EngineUpdateConflictException.cs | 4 +- ...eException.cs => IllegalWriteException.cs} | 4 +- ...ion.cs => IllegalWritePendingException.cs} | 4 +- ...cs => IllegalWritePendingTypeException.cs} | 4 +- ...eption.cs => IllegalWriteTypeException.cs} | 4 +- ...ateMessage.cs => ComponentWriteMessage.cs} | 2 +- .../Messages/PendingComponentMessage.cs | 1 + encompass-cs/World.cs | 2 +- encompass-cs/WorldBuilder.cs | 53 +++++---- test/ComponentTest.cs | 11 +- test/EngineTest.cs | 25 +++-- test/SpawnerTest.cs | 5 +- test/WorldBuilderTest.cs | 79 ++++++++++++- 22 files changed, 335 insertions(+), 171 deletions(-) delete mode 100644 encompass-cs/Attributes/Activates.cs delete mode 100644 encompass-cs/Attributes/Updates.cs create mode 100644 encompass-cs/Attributes/Writes.cs create mode 100644 encompass-cs/Attributes/WritesPending.cs rename encompass-cs/Exceptions/{IllegalUpdateException.cs => IllegalWriteException.cs} (65%) rename encompass-cs/Exceptions/{IllegalUpdateTypeException.cs => IllegalWritePendingException.cs} (62%) rename encompass-cs/Exceptions/{IllegalActivateTypeException.cs => IllegalWritePendingTypeException.cs} (60%) rename encompass-cs/Exceptions/{IllegalActivateException.cs => IllegalWriteTypeException.cs} (63%) rename encompass-cs/Messages/{ComponentUpdateMessage.cs => ComponentWriteMessage.cs} (53%) diff --git a/TODO b/TODO index 4e18f97..7324834 100644 --- a/TODO +++ b/TODO @@ -1,2 +1,4 @@ - look at test coverage - docs + +- WritesPending and writes redundant? \ No newline at end of file diff --git a/encompass-cs/Attributes/Activates.cs b/encompass-cs/Attributes/Activates.cs deleted file mode 100644 index d566633..0000000 --- a/encompass-cs/Attributes/Activates.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Encompass.Exceptions; - -namespace Encompass -{ - public class Activates : Attribute - { - public readonly HashSet activateTypes = new HashSet(); - - public Activates(params Type[] activateTypes) - { - foreach (var activateType in activateTypes) - { - var isComponent = activateType.GetInterfaces().Contains(typeof(IComponent)); - if (!isComponent) - { - throw new IllegalActivateTypeException("{0} must be a Component", activateType.Name); - } - - this.activateTypes.Add(typeof(PendingComponentMessage<>).MakeGenericType(activateType)); - } - } - } -} \ No newline at end of file diff --git a/encompass-cs/Attributes/Updates.cs b/encompass-cs/Attributes/Updates.cs deleted file mode 100644 index 302769b..0000000 --- a/encompass-cs/Attributes/Updates.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Encompass.Exceptions; - -namespace Encompass -{ - [AttributeUsage(AttributeTargets.Class)] - public class Updates : Attribute - { - public readonly HashSet updateTypes = new HashSet(); - - public Updates(params Type[] updateTypes) - { - foreach (var updateType in updateTypes) - { - var isComponent = updateType.GetInterfaces().Contains(typeof(IComponent)); - if (!isComponent) - { - throw new IllegalUpdateTypeException("{0} must be a Component", updateType.Name); - } - - this.updateTypes.Add(typeof(ComponentUpdateMessage<>).MakeGenericType(updateType)); - } - } - } -} \ No newline at end of file diff --git a/encompass-cs/Attributes/Writes.cs b/encompass-cs/Attributes/Writes.cs new file mode 100644 index 0000000..18c7e1f --- /dev/null +++ b/encompass-cs/Attributes/Writes.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Encompass.Exceptions; + +namespace Encompass +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class Writes : Attribute + { + public readonly HashSet writeTypes = new HashSet(); + public Dictionary priorities = new Dictionary(); + + public Writes(params Type[] writeTypes) + { + foreach (var writeType in writeTypes) + { + var isComponent = writeType.GetInterfaces().Contains(typeof(IComponent)); + if (!isComponent) + { + throw new IllegalWriteTypeException("{0} must be a Component", writeType.Name); + } + + this.writeTypes.Add(typeof(ComponentWriteMessage<>).MakeGenericType(writeType)); + } + } + + public Writes(Type writeType, int priority) + { + var isComponent = writeType.GetInterfaces().Contains(typeof(IComponent)); + if (!isComponent) + { + throw new IllegalWriteTypeException("{0} must be a Component", writeType.Name); + } + + this.writeTypes.Add(typeof(ComponentWriteMessage<>).MakeGenericType(writeType)); + this.priorities.Add(writeType, priority); + } + } +} \ No newline at end of file diff --git a/encompass-cs/Attributes/WritesPending.cs b/encompass-cs/Attributes/WritesPending.cs new file mode 100644 index 0000000..0983aab --- /dev/null +++ b/encompass-cs/Attributes/WritesPending.cs @@ -0,0 +1,26 @@ +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(typeof(PendingComponentMessage<>).MakeGenericType(writePendingType)); + } + } + } +} \ No newline at end of file diff --git a/encompass-cs/ComponentManager.cs b/encompass-cs/ComponentManager.cs index 7c34369..5210381 100644 --- a/encompass-cs/ComponentManager.cs +++ b/encompass-cs/ComponentManager.cs @@ -19,8 +19,9 @@ namespace Encompass private readonly Dictionary> typeToComponentIDs = new Dictionary>(); - private readonly List<(Entity, Type, Guid, IComponent)> componentAddData = new List<(Entity, Type, Guid, IComponent)>(); - private readonly HashSet componentIDsMarkedForAdd = new HashSet(); + private readonly List<(Entity, Type, Guid, IComponent)> componentWriteData = new List<(Entity, Type, Guid, IComponent)>(); + private Dictionary<(Entity, Type), int> componentWritePriorities = new Dictionary<(Entity, Type), int>(); + private readonly HashSet componentIDsMarkedForWrite = new HashSet(); private readonly HashSet componentsMarkedForRemoval = new HashSet(); private readonly Dictionary pendingUpdates = new Dictionary(); @@ -40,17 +41,41 @@ namespace Encompass return Guid.NewGuid(); } - internal Guid MarkComponentForAdd(Entity entity, TComponent component) where TComponent : struct, IComponent + internal Guid MarkComponentForWrite(Entity entity, TComponent component, int priority) where TComponent : struct, IComponent { - var id = NextID(); - componentAddData.Add((entity, typeof(TComponent), id, component)); - componentIDsMarkedForAdd.Add(id); + Guid id; + if (EntityHasComponentOfType(entity)) + { + id = GetComponentByEntityAndType(entity).Item1; + } + else + { + id = NextID(); + } + + if (componentWritePriorities.ContainsKey((entity, typeof(TComponent)))) + { + var currentPriority = componentWritePriorities[(entity, typeof(TComponent))]; + if (priority < currentPriority) + { + componentWriteData.Add((entity, typeof(TComponent), id, component)); + componentWritePriorities[(entity, typeof(TComponent))] = priority; + componentIDsMarkedForWrite.Add(id); + } + } + else + { + componentWriteData.Add((entity, typeof(TComponent), id, component)); + componentWritePriorities[(entity, typeof(TComponent))] = priority; + componentIDsMarkedForWrite.Add(id); + } + return id; } - internal Guid MarkDrawComponentForAdd(Entity entity, TComponent component, int layer = 0) where TComponent : struct, IComponent + internal Guid MarkDrawComponentForWrite(Entity entity, TComponent component, int priority, int layer = 0) where TComponent : struct, IComponent { - var id = MarkComponentForAdd(entity, component); + var id = MarkComponentForWrite(entity, component, priority); drawLayerManager.RegisterComponentWithLayer(id, layer); return id; } @@ -78,15 +103,16 @@ namespace Encompass } } - internal void AddMarkedComponents() + internal void WriteComponents() { - foreach (var (entity, type, componentID, component) in componentAddData) + foreach (var (entity, type, componentID, component) in componentWriteData) { AddComponent(entity, type, componentID, component); } - componentAddData.Clear(); - componentIDsMarkedForAdd.Clear(); + componentWriteData.Clear(); + componentIDsMarkedForWrite.Clear(); + componentWritePriorities.Clear(); } internal IEnumerable GetComponentIDsByEntityID(Guid entityID) @@ -184,9 +210,9 @@ namespace Encompass { foreach (var componentID in componentsMarkedForRemoval) { - if (componentIDsMarkedForAdd.Contains(componentID)) + if (componentIDsMarkedForWrite.Contains(componentID)) { - componentIDsMarkedForAdd.Remove(componentID); + componentIDsMarkedForWrite.Remove(componentID); } else { diff --git a/encompass-cs/ComponentMessageManager.cs b/encompass-cs/ComponentMessageManager.cs index 0464b4e..029d11a 100644 --- a/encompass-cs/ComponentMessageManager.cs +++ b/encompass-cs/ComponentMessageManager.cs @@ -21,10 +21,13 @@ namespace Encompass private readonly Dictionary> entityToTypeToPendingComponentID = new Dictionary>(); private readonly Dictionary> entityToTypeToComponentID = new Dictionary>(); + private readonly Dictionary> entityToTypeToPendingComponentPriority = new Dictionary>(); + internal void RegisterEntity(Entity entity) { entityToTypeToComponentID[entity] = new PooledDictionary(); entityToTypeToPendingComponentID[entity] = new PooledDictionary(); + entityToTypeToPendingComponentPriority[entity] = new PooledDictionary(); entityToTypeToExistingComponentID[entity] = new PooledDictionary(); } @@ -36,6 +39,9 @@ namespace Encompass entityToTypeToPendingComponentID[entity].Dispose(); entityToTypeToPendingComponentID.Remove(entity); + entityToTypeToPendingComponentPriority[entity].Dispose(); + entityToTypeToPendingComponentPriority.Remove(entity); + entityToTypeToExistingComponentID[entity].Dispose(); entityToTypeToExistingComponentID.Remove(entity); } @@ -75,6 +81,11 @@ namespace Encompass { dictionary.Clear(); } + + foreach (var dictionary in entityToTypeToPendingComponentPriority.Values) + { + dictionary.Clear(); + } } internal void AddExistingComponentMessage(ComponentMessage componentMessage) where TComponent : struct, IComponent @@ -107,15 +118,21 @@ namespace Encompass componentMessageTypeToPendingComponentIDs.Add(typeof(TComponent), new HashSet()); } - componentMessageTypeToPendingComponentIDs[typeof(TComponent)].Add(pendingComponentMessage.componentID); - if (!entityToTypeToPendingComponentID[pendingComponentMessage.entity].ContainsKey(typeof(TComponent))) { entityToTypeToPendingComponentID[pendingComponentMessage.entity].Add(typeof(TComponent), pendingComponentMessage.componentID); + entityToTypeToPendingComponentPriority[pendingComponentMessage.entity].Add(typeof(TComponent), pendingComponentMessage.priority); + componentMessageTypeToPendingComponentIDs[typeof(TComponent)].Add(pendingComponentMessage.componentID); } else { - throw new MultipleComponentOfSameTypeException("Entity {0} cannot have multiple components of type {1}", pendingComponentMessage.entity.ID, typeof(TComponent).Name); + if (pendingComponentMessage.priority < entityToTypeToPendingComponentPriority[pendingComponentMessage.entity][typeof(TComponent)]) + { + componentMessageTypeToPendingComponentIDs[typeof(TComponent)].Remove(entityToTypeToPendingComponentID[pendingComponentMessage.entity][typeof(TComponent)]); + entityToTypeToPendingComponentID[pendingComponentMessage.entity][typeof(TComponent)] = pendingComponentMessage.componentID; + entityToTypeToPendingComponentPriority[pendingComponentMessage.entity][typeof(TComponent)] = pendingComponentMessage.priority; + componentMessageTypeToPendingComponentIDs[typeof(TComponent)].Add(pendingComponentMessage.componentID); + } } } diff --git a/encompass-cs/Engine.cs b/encompass-cs/Engine.cs index 0ae92a8..cfe6149 100644 --- a/encompass-cs/Engine.cs +++ b/encompass-cs/Engine.cs @@ -10,6 +10,7 @@ namespace Encompass { internal readonly HashSet sendTypes = new HashSet(); internal readonly HashSet receiveTypes = new HashSet(); + internal readonly Dictionary writePriorities = new Dictionary(); private EntityManager entityManager; private MessageManager messageManager; @@ -24,16 +25,17 @@ namespace Encompass sendTypes = sendsAttribute.sendTypes; } - var activatesAttribute = GetType().GetCustomAttribute(false); + var activatesAttribute = GetType().GetCustomAttribute(false); if (activatesAttribute != null) { - sendTypes.UnionWith(activatesAttribute.activateTypes); + sendTypes.UnionWith(activatesAttribute.writePendingTypes); } - var updatesAttribute = GetType().GetCustomAttribute(false); - if (updatesAttribute != null) + var writesAttribute = GetType().GetCustomAttribute(false); + if (writesAttribute != null) { - sendTypes.UnionWith(updatesAttribute.updateTypes); + sendTypes.UnionWith(writesAttribute.writeTypes); + writePriorities = writesAttribute.priorities; } var receivesAttribute = GetType().GetCustomAttribute(false); @@ -124,40 +126,6 @@ namespace Encompass return GetEntity(componentManager.GetEntityIDByComponentID(componentID)); } - protected Guid AddComponent(Entity entity, TComponent component) where TComponent : struct, IComponent - { - var componentID = componentManager.MarkComponentForAdd(entity, component); - - if (sendTypes.Contains(typeof(PendingComponentMessage))) - { - PendingComponentMessage newComponentMessage; - newComponentMessage.entity = entity; - newComponentMessage.componentID = componentID; - newComponentMessage.component = component; - SendMessage(newComponentMessage); - SendPendingComponentMessage(newComponentMessage); - } - - return componentID; - } - - protected Guid AddDrawComponent(Entity entity, TComponent component, int layer = 0) where TComponent : struct, IComponent - { - var componentID = componentManager.MarkDrawComponentForAdd(entity, component, layer); - - if (sendTypes.Contains(typeof(PendingComponentMessage))) - { - PendingComponentMessage newComponentMessage; - newComponentMessage.entity = entity; - newComponentMessage.componentID = componentID; - newComponentMessage.component = component; - SendMessage(newComponentMessage); - SendPendingComponentMessage(newComponentMessage); - } - - return componentID; - } - protected IEnumerable> ReadComponents() where TComponent : struct, IComponent { var pendingRead = receiveTypes.Contains(typeof(PendingComponentMessage)); @@ -285,17 +253,67 @@ namespace Encompass componentManager.AddUpdateComponentOperation(componentID, newComponent); } - protected void UpdateComponent(Guid componentID, TComponent newComponentValue) where TComponent : struct, IComponent + protected Guid SetComponent(Entity entity, TComponent component) where TComponent : struct, IComponent { - if (!sendTypes.Contains(typeof(ComponentUpdateMessage))) + var priority = writePriorities.ContainsKey(typeof(TComponent)) ? writePriorities[typeof(TComponent)] : 0; + + var componentID = componentManager.MarkComponentForWrite(entity, component, priority); + + if (!sendTypes.Contains(typeof(ComponentWriteMessage))) { - throw new IllegalUpdateException("Engine {0} tried to update undeclared Component {1}", GetType().Name, typeof(TComponent).Name); + throw new IllegalWriteException("Engine {0} tried to update undeclared Component {1}", GetType().Name, typeof(TComponent).Name); } - ComponentUpdateMessage componentUpdateMessage; + if (sendTypes.Contains(typeof(PendingComponentMessage))) + { + PendingComponentMessage newComponentMessage; + newComponentMessage.entity = entity; + newComponentMessage.componentID = componentID; + newComponentMessage.component = component; + newComponentMessage.priority = priority; + SendPendingComponentMessage(newComponentMessage); + } + + ComponentWriteMessage componentUpdateMessage; componentUpdateMessage.componentID = componentID; - componentUpdateMessage.component = newComponentValue; + componentUpdateMessage.component = component; SendMessage(componentUpdateMessage); + + return componentID; + } + + protected Guid SetComponent(Guid componentID, TComponent component) where TComponent : struct, IComponent + { + return SetComponent(GetEntityByComponentID(componentID), component); + } + + protected Guid SetDrawComponent(Entity entity, TComponent component, int layer = 0) where TComponent : struct, IComponent + { + var priority = writePriorities.ContainsKey(typeof(TComponent)) ? writePriorities[typeof(TComponent)] : 0; + + var componentID = componentManager.MarkDrawComponentForWrite(entity, component, priority, layer); + + if (!sendTypes.Contains(typeof(ComponentWriteMessage))) + { + throw new IllegalWriteException("Engine {0} tried to write undeclared Component {1}", GetType().Name, typeof(TComponent).Name); + } + + if (sendTypes.Contains(typeof(PendingComponentMessage))) + { + PendingComponentMessage newComponentMessage; + newComponentMessage.entity = entity; + newComponentMessage.componentID = componentID; + newComponentMessage.component = component; + newComponentMessage.priority = priority; + SendPendingComponentMessage(newComponentMessage); + } + + ComponentWriteMessage componentUpdateMessage; + componentUpdateMessage.componentID = componentID; + componentUpdateMessage.component = component; + SendMessage(componentUpdateMessage); + + return componentID; } protected void SendMessage(TMessage message) where TMessage : struct, IMessage diff --git a/encompass-cs/Engines/ComponentUpdater.cs b/encompass-cs/Engines/ComponentUpdater.cs index 2656c7e..3cf9b7e 100644 --- a/encompass-cs/Engines/ComponentUpdater.cs +++ b/encompass-cs/Engines/ComponentUpdater.cs @@ -4,12 +4,12 @@ namespace Encompass.Engines { public ComponentUpdater() : base() { - receiveTypes.Add(typeof(ComponentUpdateMessage)); + receiveTypes.Add(typeof(ComponentWriteMessage)); } public override void Update(double dt) { - foreach (var componentUpdateMessage in ReadMessages>()) + foreach (var componentUpdateMessage in ReadMessages>()) { UpdateComponentInWorld(componentUpdateMessage.componentID, componentUpdateMessage.component); } diff --git a/encompass-cs/Exceptions/EngineUpdateConflictException.cs b/encompass-cs/Exceptions/EngineUpdateConflictException.cs index 3046f3d..ab1ac08 100644 --- a/encompass-cs/Exceptions/EngineUpdateConflictException.cs +++ b/encompass-cs/Exceptions/EngineUpdateConflictException.cs @@ -2,9 +2,9 @@ using System; namespace Encompass.Exceptions { - public class EngineUpdateConflictException : Exception + public class EngineWriteConflictException : Exception { - public EngineUpdateConflictException( + public EngineWriteConflictException( string format, params object[] args ) : base(string.Format(format, args)) { } diff --git a/encompass-cs/Exceptions/IllegalUpdateException.cs b/encompass-cs/Exceptions/IllegalWriteException.cs similarity index 65% rename from encompass-cs/Exceptions/IllegalUpdateException.cs rename to encompass-cs/Exceptions/IllegalWriteException.cs index 82c5387..4c7db90 100644 --- a/encompass-cs/Exceptions/IllegalUpdateException.cs +++ b/encompass-cs/Exceptions/IllegalWriteException.cs @@ -2,9 +2,9 @@ using System; namespace Encompass.Exceptions { - public class IllegalUpdateException : Exception + public class IllegalWriteException : Exception { - public IllegalUpdateException( + public IllegalWriteException( string format, params object[] args ) : base(string.Format(format, args)) { } diff --git a/encompass-cs/Exceptions/IllegalUpdateTypeException.cs b/encompass-cs/Exceptions/IllegalWritePendingException.cs similarity index 62% rename from encompass-cs/Exceptions/IllegalUpdateTypeException.cs rename to encompass-cs/Exceptions/IllegalWritePendingException.cs index 0a63dad..3983c23 100644 --- a/encompass-cs/Exceptions/IllegalUpdateTypeException.cs +++ b/encompass-cs/Exceptions/IllegalWritePendingException.cs @@ -2,9 +2,9 @@ using System; namespace Encompass.Exceptions { - public class IllegalUpdateTypeException : Exception + public class IllegalWritePendingException : Exception { - public IllegalUpdateTypeException( + public IllegalWritePendingException( string format, params object[] args ) : base(string.Format(format, args)) { } diff --git a/encompass-cs/Exceptions/IllegalActivateTypeException.cs b/encompass-cs/Exceptions/IllegalWritePendingTypeException.cs similarity index 60% rename from encompass-cs/Exceptions/IllegalActivateTypeException.cs rename to encompass-cs/Exceptions/IllegalWritePendingTypeException.cs index f167151..19554be 100644 --- a/encompass-cs/Exceptions/IllegalActivateTypeException.cs +++ b/encompass-cs/Exceptions/IllegalWritePendingTypeException.cs @@ -2,9 +2,9 @@ using System; namespace Encompass.Exceptions { - public class IllegalActivateTypeException : Exception + public class IllegalWritePendingTypeException : Exception { - public IllegalActivateTypeException( + public IllegalWritePendingTypeException( string format, params object[] args ) : base(string.Format(format, args)) { } diff --git a/encompass-cs/Exceptions/IllegalActivateException.cs b/encompass-cs/Exceptions/IllegalWriteTypeException.cs similarity index 63% rename from encompass-cs/Exceptions/IllegalActivateException.cs rename to encompass-cs/Exceptions/IllegalWriteTypeException.cs index f13c9e8..3c720b6 100644 --- a/encompass-cs/Exceptions/IllegalActivateException.cs +++ b/encompass-cs/Exceptions/IllegalWriteTypeException.cs @@ -2,9 +2,9 @@ using System; namespace Encompass.Exceptions { - public class IllegalActivateException : Exception + public class IllegalWriteTypeException : Exception { - public IllegalActivateException( + public IllegalWriteTypeException( string format, params object[] args ) : base(string.Format(format, args)) { } diff --git a/encompass-cs/Messages/ComponentUpdateMessage.cs b/encompass-cs/Messages/ComponentWriteMessage.cs similarity index 53% rename from encompass-cs/Messages/ComponentUpdateMessage.cs rename to encompass-cs/Messages/ComponentWriteMessage.cs index 0221bbb..424fa7b 100644 --- a/encompass-cs/Messages/ComponentUpdateMessage.cs +++ b/encompass-cs/Messages/ComponentWriteMessage.cs @@ -2,7 +2,7 @@ using System; namespace Encompass { - internal struct ComponentUpdateMessage : IMessage where TComponent : struct, IComponent + internal struct ComponentWriteMessage : IMessage where TComponent : struct, IComponent { public Guid componentID; public TComponent component; diff --git a/encompass-cs/Messages/PendingComponentMessage.cs b/encompass-cs/Messages/PendingComponentMessage.cs index 5fd0166..4a051b4 100644 --- a/encompass-cs/Messages/PendingComponentMessage.cs +++ b/encompass-cs/Messages/PendingComponentMessage.cs @@ -7,5 +7,6 @@ namespace Encompass public Entity entity; public Guid componentID; public TComponent component; + public int priority; } } \ No newline at end of file diff --git a/encompass-cs/World.cs b/encompass-cs/World.cs index efbf537..fe5104d 100644 --- a/encompass-cs/World.cs +++ b/encompass-cs/World.cs @@ -43,7 +43,7 @@ namespace Encompass componentManager.PerformComponentUpdates(); componentManager.RemoveMarkedComponents(); - componentManager.AddMarkedComponents(); + componentManager.WriteComponents(); } public void Draw() diff --git a/encompass-cs/WorldBuilder.cs b/encompass-cs/WorldBuilder.cs index 3dd2603..8a2100e 100644 --- a/encompass-cs/WorldBuilder.cs +++ b/encompass-cs/WorldBuilder.cs @@ -52,12 +52,12 @@ namespace Encompass public Guid AddComponent(Entity entity, TComponent component) where TComponent : struct, IComponent { - return componentManager.MarkComponentForAdd(entity, component); + return componentManager.MarkComponentForWrite(entity, component, 0); } public Guid AddDrawComponent(Entity entity, TComponent component, int layer = 0) where TComponent : struct, IComponent { - return componentManager.MarkDrawComponentForAdd(entity, component, layer); + return componentManager.MarkDrawComponentForWrite(entity, component, layer); } internal void RegisterComponent(Type componentType) @@ -133,7 +133,7 @@ namespace Encompass if (sendType.IsGenericType) { var genericTypeDefinition = sendType.GetGenericTypeDefinition(); - if (genericTypeDefinition == typeof(ComponentUpdateMessage<>)) + if (genericTypeDefinition == typeof(ComponentWriteMessage<>)) { var componentType = sendType.GetGenericArguments().Single(); RegisterNewComponentUpdater(componentType); @@ -201,45 +201,56 @@ namespace Encompass throw new EngineCycleException(errorString); } - var mutatedComponentTypes = new HashSet(); - var duplicateMutations = new List(); - var updateMessageToEngines = new Dictionary>(); + var writtenComponentTypesWithoutPriority = new HashSet(); + var writtenComponentTypesWithPriority = new HashSet(); + var duplicateWritesWithoutPriority = new List(); + var writeMessageToEngines = new Dictionary>(); foreach (var engine in engines) { - var updateTypes = engine.sendTypes.Where((type) => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ComponentUpdateMessage<>)); + var writeTypes = engine.sendTypes.Where((type) => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ComponentWriteMessage<>)); - foreach (var updateType in updateTypes) + foreach (var writeType in writeTypes) { - if (mutatedComponentTypes.Contains(updateType)) + var componentType = writeType.GetGenericArguments()[0]; + + if (!engine.writePriorities.ContainsKey(componentType)) { - duplicateMutations.Add(updateType); + if (writtenComponentTypesWithoutPriority.Contains(componentType) || writtenComponentTypesWithPriority.Contains(componentType)) + { + duplicateWritesWithoutPriority.Add(componentType); + } + else + { + writtenComponentTypesWithoutPriority.Add(componentType); + } } else { - mutatedComponentTypes.Add(updateType); + writtenComponentTypesWithPriority.Add(componentType); } - if (!updateMessageToEngines.ContainsKey(updateType)) + if (!writeMessageToEngines.ContainsKey(componentType)) { - updateMessageToEngines[updateType] = new List(); + writeMessageToEngines[componentType] = new List(); } - updateMessageToEngines[updateType].Add(engine); + writeMessageToEngines[componentType].Add(engine); } } - if (duplicateMutations.Count > 0) + if (duplicateWritesWithoutPriority.Count > 0) { - var errorString = "Multiple Engines update the same Component: "; - foreach (var componentType in duplicateMutations) + var errorString = "Multiple Engines write the same Component without declaring priority: "; + foreach (var componentType in duplicateWritesWithoutPriority) { errorString += "\n" + - componentType.Name + " updated by: " + - string.Join(", ", updateMessageToEngines[componentType].Select((engine) => engine.GetType().Name)); + componentType.Name + " written by: " + + string.Join(", ", writeMessageToEngines[componentType].Select((engine) => engine.GetType().Name)); } + errorString += "\nTo resolve the conflict, add priority arguments to the Writes declarations."; - throw new EngineUpdateConflictException(errorString); + throw new EngineWriteConflictException(errorString); } var engineOrder = new List(); @@ -259,7 +270,7 @@ namespace Encompass componentManager.PerformComponentUpdates(); componentManager.RemoveMarkedComponents(); - componentManager.AddMarkedComponents(); + componentManager.WriteComponents(); return world; } diff --git a/test/ComponentTest.cs b/test/ComponentTest.cs index 2d14aba..08a8cf2 100644 --- a/test/ComponentTest.cs +++ b/test/ComponentTest.cs @@ -99,6 +99,7 @@ namespace Tests } [Reads(typeof(MockComponent))] + [Writes(typeof(MockComponent))] class MultipleAddEngine : Engine { public override void Update(double dt) @@ -107,7 +108,7 @@ namespace Tests { var entity = GetEntityByComponentID(mockComponentID); - AddComponent(entity, new MockComponent()); + SetComponent(entity, new MockComponent()); } } } @@ -127,6 +128,7 @@ namespace Tests } [Reads(typeof(MockComponent))] + [Writes(typeof(MockComponent))] class AddAndRemoveComponentEngine : Engine { public override void Update(double dt) @@ -134,7 +136,7 @@ namespace Tests foreach (var (mockComponentID, mockComponent) in ReadComponents()) { var entity = GetEntityByComponentID(mockComponentID); - AddComponent(entity, mockComponent); + SetComponent(entity, mockComponent); RemoveComponent(mockComponentID); } } @@ -183,15 +185,16 @@ namespace Tests } } - [Activates(typeof(MockComponent))] + [WritesPending(typeof(MockComponent))] [Receives(typeof(AddMockComponentMessage))] + [Writes(typeof(MockComponent))] class AddMockComponentEngine : Engine { public override void Update(double dt) { foreach (var message in ReadMessages()) { - AddComponent(message.entity, message.mockComponent); + SetComponent(message.entity, message.mockComponent); } } } diff --git a/test/EngineTest.cs b/test/EngineTest.cs index 79ad717..f947fcd 100644 --- a/test/EngineTest.cs +++ b/test/EngineTest.cs @@ -120,7 +120,7 @@ namespace Tests } [Reads(typeof(MockComponent))] - [Updates(typeof(MockComponent))] + [Writes(typeof(MockComponent))] public class UpdateComponentTestEngine : Engine { public override void Update(double dt) @@ -129,7 +129,7 @@ namespace Tests component.myInt = 420; component.myString = "blaze it"; - UpdateComponent(componentID, component); + SetComponent(componentID, component); } } @@ -161,6 +161,7 @@ namespace Tests } [Reads(typeof(MockComponent))] + [Writes(typeof(MockComponent))] public class UndeclaredUpdateComponentTestEngine : Engine { public override void Update(double dt) @@ -169,7 +170,7 @@ namespace Tests component.myInt = 420; component.myString = "blaze it"; - UpdateComponent(componentID, component); + SetComponent(componentID, component); component = ReadComponent().Item2; } @@ -191,7 +192,7 @@ namespace Tests var world = worldBuilder.Build(); - var ex = Assert.Throws(() => world.Update(0.01f)); + var ex = Assert.Throws(() => world.Update(0.01f)); Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredUpdateComponentTestEngine tried to update undeclared Component MockComponent")); } @@ -551,8 +552,9 @@ namespace Tests Assert.That(entity, Is.EqualTo(entityFromComponentIDResult)); } - [Activates(typeof(MockComponent))] [Reads(typeof(MockComponent))] + [WritesPending(typeof(MockComponent))] + [Writes(typeof(MockComponent))] class AddAndRemoveMockComponentEngine : Engine { public override void Update(double dt) @@ -561,7 +563,7 @@ namespace Tests { var entity = GetEntityByComponentID(mockComponentID); RemoveComponent(mockComponentID); - AddComponent(entity, new MockComponent()); + SetComponent(entity, new MockComponent()); } } } @@ -728,15 +730,15 @@ namespace Tests } [Receives(typeof(MockComponentUpdateMessage))] - [Updates(typeof(MockComponent))] + [Writes(typeof(MockComponent))] class RepeatUpdateEngine : Engine { public override void Update(double dt) { foreach (var mockComponentUpdateMessage in ReadMessages()) { - UpdateComponent(mockComponentUpdateMessage.componentID, mockComponentUpdateMessage.mockComponent); - UpdateComponent(mockComponentUpdateMessage.componentID, mockComponentUpdateMessage.mockComponent); + SetComponent(mockComponentUpdateMessage.componentID, mockComponentUpdateMessage.mockComponent); + SetComponent(mockComponentUpdateMessage.componentID, mockComponentUpdateMessage.mockComponent); } } } @@ -806,7 +808,8 @@ namespace Tests } [Receives(typeof(MockMessage))] - [Activates(typeof(MockComponent))] + [WritesPending(typeof(MockComponent))] + [Writes(typeof(MockComponent))] class ActivateComponentEngine : Engine { public override void Update(double dt) @@ -814,7 +817,7 @@ namespace Tests foreach (var message in ReadMessages()) { var entity = CreateEntity(); - AddComponent(entity, new MockComponent {}); + SetComponent(entity, new MockComponent {}); } } } diff --git a/test/SpawnerTest.cs b/test/SpawnerTest.cs index 1ca1a00..a5cfedb 100644 --- a/test/SpawnerTest.cs +++ b/test/SpawnerTest.cs @@ -23,13 +23,14 @@ namespace Tests } } - [Activates(typeof(TestComponent))] + [WritesPending(typeof(TestComponent))] + [Writes(typeof(TestComponent))] class TestSpawner : Spawner { protected override void Spawn(SpawnMessageA message) { resultEntity = CreateEntity(); - AddComponent(resultEntity, new TestComponent()); + SetComponent(resultEntity, new TestComponent()); Assert.Pass(); } } diff --git a/test/WorldBuilderTest.cs b/test/WorldBuilderTest.cs index 91777d2..a93b1b5 100644 --- a/test/WorldBuilderTest.cs +++ b/test/WorldBuilderTest.cs @@ -112,30 +112,99 @@ namespace Tests } } - public class MutationConflict + public class MultipleEngineWriteConflict { struct AComponent : IComponent { } - [Updates(typeof(AComponent))] + [Writes(typeof(AComponent))] class AEngine : Engine { public override void Update(double dt) { } } - [Updates(typeof(AComponent))] + [Writes(typeof(AComponent))] class BEngine : Engine { public override void Update(double dt) { } } [Test] - public void MutationConflictException() + public void EngineWriteConflictException() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new AEngine()); worldBuilder.AddEngine(new BEngine()); - Assert.Throws(() => worldBuilder.Build()); + Assert.Throws(() => worldBuilder.Build()); + } + } + + public class MultipleEngineWriteWithPriority + { + struct SetMessage : IMessage + { + public Entity entity; + } + + struct AComponent : IComponent + { + public int myInt; + } + + [Receives(typeof(SetMessage))] + [Writes(typeof(AComponent), 0)] + [WritesPending(typeof(AComponent))] + class AEngine : Engine + { + public override void Update(double dt) + { + foreach (var setMessage in ReadMessages()) + { + SetComponent(setMessage.entity, new AComponent { myInt = 0 }); + } + } + } + + [Receives(typeof(SetMessage))] + [Writes(typeof(AComponent), 1)] + [WritesPending(typeof(AComponent))] + class BEngine : Engine + { + public override void Update(double dt) + { + foreach (var setMessage in ReadMessages()) + { + SetComponent(setMessage.entity, new AComponent { myInt = 1 }); + } + } + } + + static AComponent resultComponent; + + [ReadsPending(typeof(AComponent))] + class ReadComponentEngine : Engine + { + public override void Update(double dt) + { + resultComponent = ReadComponent().Item2; + } + } + + [Test] + public void LowerPriorityWrites() + { + var worldBuilder = new WorldBuilder(); + worldBuilder.AddEngine(new AEngine()); + worldBuilder.AddEngine(new BEngine()); + + var entity = worldBuilder.CreateEntity(); + worldBuilder.SendMessage(new SetMessage { entity = entity }); + + var world = worldBuilder.Build(); + + world.Update(0.01); + + Assert.That(resultComponent.myInt, Is.EqualTo(0)); } } From 971a663cf99e5c21141856ba143a3f12db0841a7 Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Wed, 21 Aug 2019 15:13:48 -0700 Subject: [PATCH 02/13] write priority system and require read declaration for entity lookups by component --- encompass-cs/ComponentManager.cs | 79 ++++++++++-------------- encompass-cs/Engine.cs | 39 ++++++------ encompass-cs/Engines/ComponentUpdater.cs | 18 ------ encompass-cs/World.cs | 1 - encompass-cs/WorldBuilder.cs | 27 ++------ test/ComponentTest.cs | 62 ++++++++++++------- test/EngineTest.cs | 74 +++++++++++----------- test/GeneralRendererTest.cs | 6 +- test/OrderedRendererTest.cs | 10 +-- test/WorldTest.cs | 16 ++--- 10 files changed, 149 insertions(+), 183 deletions(-) delete mode 100644 encompass-cs/Engines/ComponentUpdater.cs diff --git a/encompass-cs/ComponentManager.cs b/encompass-cs/ComponentManager.cs index 5210381..3f9371f 100644 --- a/encompass-cs/ComponentManager.cs +++ b/encompass-cs/ComponentManager.cs @@ -19,11 +19,10 @@ namespace Encompass private readonly Dictionary> typeToComponentIDs = new Dictionary>(); - private readonly List<(Entity, Type, Guid, IComponent)> componentWriteData = new List<(Entity, Type, Guid, IComponent)>(); - private Dictionary<(Entity, Type), int> componentWritePriorities = new Dictionary<(Entity, Type), int>(); + private readonly Dictionary<(Entity, Type), (Guid, IComponent)> componentWriteData = new Dictionary<(Entity, Type), (Guid, IComponent)>(); + private readonly Dictionary<(Entity, Type), int> componentWritePriorities = new Dictionary<(Entity, Type), int>(); private readonly HashSet componentIDsMarkedForWrite = new HashSet(); private readonly HashSet componentsMarkedForRemoval = new HashSet(); - private readonly Dictionary pendingUpdates = new Dictionary(); public ComponentManager(DrawLayerManager drawLayerManager) { @@ -53,19 +52,19 @@ namespace Encompass id = NextID(); } - if (componentWritePriorities.ContainsKey((entity, typeof(TComponent)))) + if (componentWriteData.ContainsKey((entity, typeof(TComponent)))) { var currentPriority = componentWritePriorities[(entity, typeof(TComponent))]; if (priority < currentPriority) { - componentWriteData.Add((entity, typeof(TComponent), id, component)); + componentWriteData[(entity, typeof(TComponent))] = (id, component); componentWritePriorities[(entity, typeof(TComponent))] = priority; componentIDsMarkedForWrite.Add(id); } } else { - componentWriteData.Add((entity, typeof(TComponent), id, component)); + componentWriteData.Add((entity, typeof(TComponent)), (id, component)); componentWritePriorities[(entity, typeof(TComponent))] = priority; componentIDsMarkedForWrite.Add(id); } @@ -82,32 +81,40 @@ namespace Encompass internal void AddComponent(Entity entity, Type type, Guid componentID, IComponent component) { - if (!entityIDToComponentTypeToComponentID.ContainsKey(entity.ID)) { return; } + IDToComponent[componentID] = component; + componentIDToEntityID[componentID] = entity.ID; + componentIDToType[componentID] = type; + entityIDToComponentTypeToComponentID[entity.ID][type] = componentID; + if (!typeToComponentIDs.ContainsKey(type)) + { + typeToComponentIDs.Add(type, new HashSet()); + } + typeToComponentIDs[type].Add(componentID); + entityIDToComponentIDs[entity.ID].Add(componentID); + } - if (!entityIDToComponentTypeToComponentID[entity.ID].ContainsKey(type)) - { - IDToComponent[componentID] = component; - componentIDToEntityID[componentID] = entity.ID; - componentIDToType[componentID] = type; - entityIDToComponentTypeToComponentID[entity.ID][type] = componentID; - if (!typeToComponentIDs.ContainsKey(type)) - { - typeToComponentIDs.Add(type, new HashSet()); - } - typeToComponentIDs[type].Add(componentID); - entityIDToComponentIDs[entity.ID].Add(componentID); - } - else - { - throw new MultipleComponentOfSameTypeException("Entity {0} cannot have multiple components of type {1}", entity.ID, type.Name); - } + internal void UpdateComponent(Guid componentID, IComponent component) + { + IDToComponent[componentID] = component; } internal void WriteComponents() { - foreach (var (entity, type, componentID, component) in componentWriteData) + foreach (var keyValuePair in componentWriteData) { - AddComponent(entity, type, componentID, component); + var (entity, type) = keyValuePair.Key; + var (componentID, component) = keyValuePair.Value; + + if (!entityIDToComponentTypeToComponentID.ContainsKey(entity.ID)) { continue; } + + if (entityIDToComponentTypeToComponentID[entity.ID].ContainsKey(type)) + { + UpdateComponent(componentID, component); + } + else + { + AddComponent(entity, type, componentID, component); + } } componentWriteData.Clear(); @@ -173,26 +180,6 @@ namespace Encompass return componentIDToEntityID[componentID]; } - internal void AddUpdateComponentOperation(Guid componentID, TComponent newComponentValue) where TComponent : struct, IComponent - { - if (pendingUpdates.ContainsKey(componentID)) - { - throw new RepeatUpdateComponentException("Component {0} with ID {1} was updated multiple times this frame", typeof(TComponent).Name, componentID); - } - - pendingUpdates.Add(componentID, newComponentValue); - } - - internal void PerformComponentUpdates() - { - foreach (var idPair in pendingUpdates) - { - IDToComponent[idPair.Key] = idPair.Value; - } - - pendingUpdates.Clear(); - } - internal void MarkAllComponentsOnEntityForRemoval(Guid entityID) { foreach (var componentID in GetComponentIDsByEntityID(entityID)) diff --git a/encompass-cs/Engine.cs b/encompass-cs/Engine.cs index cfe6149..4579b29 100644 --- a/encompass-cs/Engine.cs +++ b/encompass-cs/Engine.cs @@ -94,18 +94,34 @@ namespace Encompass return entityManager.GetEntity(entityID); } - protected Guid GetEntityIDByComponentID(Guid componentID) + protected Guid GetEntityIDByComponentID(Guid componentID) where TComponent : struct, IComponent { + var pendingRead = receiveTypes.Contains(typeof(PendingComponentMessage)); + var existingRead = receiveTypes.Contains(typeof(ComponentMessage)); + + if (!pendingRead && !existingRead) + { + throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", GetType().Name, typeof(TComponent).Name); + } + return componentMessageManager.GetEntityIDByComponentID(componentID); } - protected Entity GetEntityByComponentID(Guid componentID) + protected Entity GetEntityByComponentID(Guid componentID) where TComponent : struct, IComponent { - return GetEntity(GetEntityIDByComponentID(componentID)); + return GetEntity(GetEntityIDByComponentID(componentID)); } protected TComponent GetComponentByID(Guid componentID) where TComponent : struct, IComponent { + var pendingRead = receiveTypes.Contains(typeof(PendingComponentMessage)); + var existingRead = receiveTypes.Contains(typeof(ComponentMessage)); + + if (!pendingRead && !existingRead) + { + throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", GetType().Name, typeof(TComponent).Name); + } + if (componentMessageManager.GetComponentTypeByID(componentID) != typeof(TComponent)) { throw new ComponentTypeMismatchException("Expected Component to be of type {0} but was actually of type {1}", typeof(TComponent).Name, componentMessageManager.GetComponentTypeByID(componentID).Name); @@ -248,11 +264,6 @@ namespace Encompass } } - internal void UpdateComponentInWorld(Guid componentID, TComponent newComponent) where TComponent : struct, IComponent - { - componentManager.AddUpdateComponentOperation(componentID, newComponent); - } - protected Guid SetComponent(Entity entity, TComponent component) where TComponent : struct, IComponent { var priority = writePriorities.ContainsKey(typeof(TComponent)) ? writePriorities[typeof(TComponent)] : 0; @@ -274,17 +285,12 @@ namespace Encompass SendPendingComponentMessage(newComponentMessage); } - ComponentWriteMessage componentUpdateMessage; - componentUpdateMessage.componentID = componentID; - componentUpdateMessage.component = component; - SendMessage(componentUpdateMessage); - return componentID; } protected Guid SetComponent(Guid componentID, TComponent component) where TComponent : struct, IComponent { - return SetComponent(GetEntityByComponentID(componentID), component); + return SetComponent(GetEntityByComponentID(componentID), component); } protected Guid SetDrawComponent(Entity entity, TComponent component, int layer = 0) where TComponent : struct, IComponent @@ -308,11 +314,6 @@ namespace Encompass SendPendingComponentMessage(newComponentMessage); } - ComponentWriteMessage componentUpdateMessage; - componentUpdateMessage.componentID = componentID; - componentUpdateMessage.component = component; - SendMessage(componentUpdateMessage); - return componentID; } diff --git a/encompass-cs/Engines/ComponentUpdater.cs b/encompass-cs/Engines/ComponentUpdater.cs deleted file mode 100644 index 3cf9b7e..0000000 --- a/encompass-cs/Engines/ComponentUpdater.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Encompass.Engines -{ - internal class ComponentUpdater : Engine where TComponent : struct, IComponent - { - public ComponentUpdater() : base() - { - receiveTypes.Add(typeof(ComponentWriteMessage)); - } - - public override void Update(double dt) - { - foreach (var componentUpdateMessage in ReadMessages>()) - { - UpdateComponentInWorld(componentUpdateMessage.componentID, componentUpdateMessage.component); - } - } - } -} \ No newline at end of file diff --git a/encompass-cs/World.cs b/encompass-cs/World.cs index fe5104d..576c51e 100644 --- a/encompass-cs/World.cs +++ b/encompass-cs/World.cs @@ -41,7 +41,6 @@ namespace Encompass componentMessageManager.ClearMessages(); entityManager.DestroyMarkedEntities(); - componentManager.PerformComponentUpdates(); componentManager.RemoveMarkedComponents(); componentManager.WriteComponents(); } diff --git a/encompass-cs/WorldBuilder.cs b/encompass-cs/WorldBuilder.cs index 8a2100e..1e8de7e 100644 --- a/encompass-cs/WorldBuilder.cs +++ b/encompass-cs/WorldBuilder.cs @@ -50,14 +50,14 @@ namespace Encompass messageManager.AddMessageDelayed(message, time); } - public Guid AddComponent(Entity entity, TComponent component) where TComponent : struct, IComponent + public Guid SetComponent(Entity entity, TComponent component, int priority = 0) where TComponent : struct, IComponent { - return componentManager.MarkComponentForWrite(entity, component, 0); + return componentManager.MarkComponentForWrite(entity, component, priority); } - public Guid AddDrawComponent(Entity entity, TComponent component, int layer = 0) where TComponent : struct, IComponent + public Guid SetDrawComponent(Entity entity, TComponent component, int priority = 0, int layer = 0) where TComponent : struct, IComponent { - return componentManager.MarkDrawComponentForWrite(entity, component, layer); + return componentManager.MarkDrawComponentForWrite(entity, component, priority, layer); } internal void RegisterComponent(Type componentType) @@ -66,11 +66,6 @@ namespace Encompass AddEngine((Engine)Activator.CreateInstance(typeof(ComponentMessageEmitter<>).MakeGenericType(componentType))); } - internal void RegisterNewComponentUpdater(Type componentType) - { - AddEngine((Engine)Activator.CreateInstance(typeof(ComponentUpdater<>).MakeGenericType(componentType))); - } - public Engine AddEngine(TEngine engine) where TEngine : Engine { engine.AssignEntityManager(entityManager); @@ -128,19 +123,6 @@ namespace Encompass typeToReaders[receiveType].Add(engine); } - foreach (var sendType in engine.sendTypes) - { - if (sendType.IsGenericType) - { - var genericTypeDefinition = sendType.GetGenericTypeDefinition(); - if (genericTypeDefinition == typeof(ComponentWriteMessage<>)) - { - var componentType = sendType.GetGenericArguments().Single(); - RegisterNewComponentUpdater(componentType); - } - } - } - return engine; } @@ -268,7 +250,6 @@ namespace Encompass renderManager ); - componentManager.PerformComponentUpdates(); componentManager.RemoveMarkedComponents(); componentManager.WriteComponents(); diff --git a/test/ComponentTest.cs b/test/ComponentTest.cs index 08a8cf2..3cd553b 100644 --- a/test/ComponentTest.cs +++ b/test/ComponentTest.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using NUnit.Framework; using FluentAssertions; @@ -11,7 +12,7 @@ namespace Tests { public class ComponentTests { - struct MockComponent : IComponent + struct MockComponent : Encompass.IComponent { public string myString; public int myInt; @@ -22,7 +23,6 @@ namespace Tests public Entity entity; } - static IEnumerable<(Guid, MockComponent)> gottenMockComponentIDPairs = Enumerable.Empty<(Guid, MockComponent)>(); static (Guid, MockComponent) gottenMockComponentIDPair; [Receives(typeof(EntityMessage))] @@ -70,7 +70,7 @@ namespace Tests mockComponent.myInt = 3; mockComponent.myString = "hello"; - worldBuilder.AddComponent(entity, mockComponent); + worldBuilder.SetComponent(entity, mockComponent); AddComponentTestMessage addComponentTestMessage; addComponentTestMessage.entity = entity; @@ -83,48 +83,64 @@ namespace Tests } [Test] - public void AddMultipleComponentOfSameTypeToEntity() + public void SetMultipleComponentOfSameTypeOnEntity() { var worldBuilder = new WorldBuilder(); - - MockComponent mockComponent; - mockComponent.myInt = 3; - mockComponent.myString = "hello"; + worldBuilder.AddEngine(new ReadMockComponentEngine()); var entity = worldBuilder.CreateEntity(); - worldBuilder.AddComponent(entity, mockComponent); - worldBuilder.AddComponent(entity, mockComponent); + worldBuilder.SetComponent(entity, new MockComponent { myInt = 20, myString = "what" }, 2); + worldBuilder.SetComponent(entity, new MockComponent { myInt = 50, myString = "hi" }, 0); + worldBuilder.SetComponent(entity, new MockComponent { myInt = 40, myString = "wassup" }, 1); - Assert.Throws(() => worldBuilder.Build()); + var world = worldBuilder.Build(); + + world.Update(0.01); + + Assert.That(gottenMockComponentIDPair.Item2.myInt, Is.EqualTo(50)); + Assert.That(gottenMockComponentIDPair.Item2.myString, Is.EqualTo("hi")); } [Reads(typeof(MockComponent))] + [WritesPending(typeof(MockComponent))] [Writes(typeof(MockComponent))] - class MultipleAddEngine : Engine + class OverwriteEngine : Engine { public override void Update(double dt) { foreach (var (mockComponentID, mockComponent) in ReadComponents()) { - var entity = GetEntityByComponentID(mockComponentID); + var entity = GetEntityByComponentID(mockComponentID); - SetComponent(entity, new MockComponent()); + SetComponent(entity, new MockComponent { myInt = 420 }); } } } + [ReadsPending(typeof(MockComponent))] + [Reads(typeof(MockComponent))] + class ReadMockComponentEngine : Engine + { + public override void Update(double dt) + { + gottenMockComponentIDPair = ReadComponent(); + } + } + [Test] - public void EngineAddMultipleComponentOfSameTypeToEntity() + public void EngineOverwriteComponent() { var worldBuilder = new WorldBuilder(); - worldBuilder.AddEngine(new MultipleAddEngine()); + worldBuilder.AddEngine(new OverwriteEngine()); + worldBuilder.AddEngine(new ReadMockComponentEngine()); var entity = worldBuilder.CreateEntity(); - worldBuilder.AddComponent(entity, new MockComponent()); + worldBuilder.SetComponent(entity, new MockComponent {}); var world = worldBuilder.Build(); + world.Update(0.01); - Assert.Throws(() => world.Update(0.01)); + Assert.That(gottenMockComponentIDPair.Item2.myInt, Is.EqualTo(420)); } [Reads(typeof(MockComponent))] @@ -135,7 +151,7 @@ namespace Tests { foreach (var (mockComponentID, mockComponent) in ReadComponents()) { - var entity = GetEntityByComponentID(mockComponentID); + var entity = GetEntityByComponentID(mockComponentID); SetComponent(entity, mockComponent); RemoveComponent(mockComponentID); } @@ -149,7 +165,7 @@ namespace Tests worldBuilder.AddEngine(new AddAndRemoveComponentEngine()); var entity = worldBuilder.CreateEntity(); - worldBuilder.AddComponent(entity, new MockComponent()); + worldBuilder.SetComponent(entity, new MockComponent()); var world = worldBuilder.Build(); @@ -242,7 +258,7 @@ namespace Tests mockComponent.myInt = 3; mockComponent.myString = "hello"; - var componentID = worldBuilder.AddComponent(entity, mockComponent); + var componentID = worldBuilder.SetComponent(entity, mockComponent); EntityMessage entityMessage; entityMessage.entity = entity; @@ -285,7 +301,7 @@ namespace Tests mockComponent.myInt = 3; mockComponent.myString = "hello"; - worldBuilder.AddComponent(entity, mockComponent); + worldBuilder.SetComponent(entity, mockComponent); HasComponentTestMessage hasComponentTestMessage; hasComponentTestMessage.entity = entity; @@ -371,7 +387,7 @@ namespace Tests mockComponent.myInt = 3; mockComponent.myString = "hello"; - var componentID = worldBuilder.AddComponent(entity, mockComponent); + var componentID = worldBuilder.SetComponent(entity, mockComponent); RemoveComponentTestMessage removeComponentMessage; removeComponentMessage.entity = entity; diff --git a/test/EngineTest.cs b/test/EngineTest.cs index f947fcd..6fb829a 100644 --- a/test/EngineTest.cs +++ b/test/EngineTest.cs @@ -59,8 +59,8 @@ namespace Tests mockComponentB.myInt = 1; mockComponentB.myString = "howdy"; - var componentAID = worldBuilder.AddComponent(entity, mockComponent); - var componentBID = worldBuilder.AddComponent(entityB, mockComponentB); + var componentAID = worldBuilder.SetComponent(entity, mockComponent); + var componentBID = worldBuilder.SetComponent(entityB, mockComponentB); var world = worldBuilder.Build(); @@ -83,7 +83,7 @@ namespace Tests mockComponent.myInt = 0; mockComponent.myString = "hello"; - worldBuilder.AddComponent(entity, mockComponent); + worldBuilder.SetComponent(entity, mockComponent); var world = worldBuilder.Build(); @@ -109,8 +109,8 @@ namespace Tests mockComponentB.myInt = 1; mockComponentB.myString = "howdy"; - worldBuilder.AddComponent(entity, mockComponent); - worldBuilder.AddComponent(entityB, mockComponentB); + worldBuilder.SetComponent(entity, mockComponent); + worldBuilder.SetComponent(entityB, mockComponentB); var world = worldBuilder.Build(); @@ -129,7 +129,7 @@ namespace Tests component.myInt = 420; component.myString = "blaze it"; - SetComponent(componentID, component); + SetComponent(GetEntityByComponentID(componentID), component); } } @@ -149,7 +149,7 @@ namespace Tests mockComponent.myInt = 0; mockComponent.myString = "hello"; - worldBuilder.AddComponent(entity, mockComponent); + worldBuilder.SetComponent(entity, mockComponent); var world = worldBuilder.Build(); @@ -161,7 +161,6 @@ namespace Tests } [Reads(typeof(MockComponent))] - [Writes(typeof(MockComponent))] public class UndeclaredUpdateComponentTestEngine : Engine { public override void Update(double dt) @@ -188,7 +187,7 @@ namespace Tests mockComponent.myInt = 0; mockComponent.myString = "hello"; - worldBuilder.AddComponent(entity, mockComponent); + worldBuilder.SetComponent(entity, mockComponent); var world = worldBuilder.Build(); @@ -354,7 +353,7 @@ namespace Tests var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); - worldBuilder.AddComponent(entity, new MockComponent()); + worldBuilder.SetComponent(entity, new MockComponent()); var world = worldBuilder.Build(); @@ -392,10 +391,10 @@ namespace Tests componentB.myString = "hello"; var entity = worldBuilder.CreateEntity(); - worldBuilder.AddComponent(entity, componentA); + worldBuilder.SetComponent(entity, componentA); var entityB = worldBuilder.CreateEntity(); - worldBuilder.AddComponent(entityB, componentB); + worldBuilder.SetComponent(entityB, componentB); var world = worldBuilder.Build(); world.Update(0.01f); @@ -437,7 +436,7 @@ namespace Tests foreach (var componentPair in ReadComponents()) { var componentID = componentPair.Item1; - var entityID = GetEntityIDByComponentID(componentID); + var entityID = GetEntityIDByComponentID(componentID); Destroy(entityID); } } @@ -470,13 +469,13 @@ namespace Tests mockComponent.myInt = 2; mockComponent.myString = "blah"; - worldBuilder.AddComponent(entity, destroyerComponent); - var componentID = worldBuilder.AddComponent(entity, mockComponent); + worldBuilder.SetComponent(entity, destroyerComponent); + var componentID = worldBuilder.SetComponent(entity, mockComponent); - worldBuilder.AddComponent(entityB, destroyerComponent); - var componentBID = worldBuilder.AddComponent(entityB, mockComponent); + worldBuilder.SetComponent(entityB, destroyerComponent); + var componentBID = worldBuilder.SetComponent(entityB, mockComponent); - var componentCID = worldBuilder.AddComponent(entityC, mockComponent); + var componentCID = worldBuilder.SetComponent(entityC, mockComponent); var world = worldBuilder.Build(); @@ -496,7 +495,7 @@ namespace Tests foreach (var componentPair in ReadComponents()) { var componentID = componentPair.Item1; - var entity = GetEntityByComponentID(componentID); + var entity = GetEntityByComponentID(componentID); var (id, _) = GetComponent(entity); RemoveComponent(id); Destroy(entity.ID); @@ -513,8 +512,8 @@ namespace Tests var entity = worldBuilder.CreateEntity(); - worldBuilder.AddComponent(entity, new DestroyerComponent()); - worldBuilder.AddComponent(entity, new MockComponent()); + worldBuilder.SetComponent(entity, new DestroyerComponent()); + worldBuilder.SetComponent(entity, new MockComponent()); var world = worldBuilder.Build(); @@ -529,7 +528,7 @@ namespace Tests public override void Update(double dt) { var componentID = ReadComponent().Item1; - entityFromComponentIDResult = GetEntityByComponentID(componentID); + entityFromComponentIDResult = GetEntityByComponentID(componentID); } } @@ -544,7 +543,7 @@ namespace Tests component.myString = "howdy"; var entity = worldBuilder.CreateEntity(); - worldBuilder.AddComponent(entity, component); + worldBuilder.SetComponent(entity, component); var world = worldBuilder.Build(); world.Update(0.01); @@ -561,7 +560,7 @@ namespace Tests { foreach (var (mockComponentID, mockComponent) in ReadComponents()) { - var entity = GetEntityByComponentID(mockComponentID); + var entity = GetEntityByComponentID(mockComponentID); RemoveComponent(mockComponentID); SetComponent(entity, new MockComponent()); } @@ -575,7 +574,7 @@ namespace Tests { foreach (var (mockComponentID, mockComponent) in ReadComponents()) { - entityFromComponentIDResult = GetEntityByComponentID(mockComponentID); + entityFromComponentIDResult = GetEntityByComponentID(mockComponentID); } } } @@ -588,7 +587,7 @@ namespace Tests worldBuilder.AddEngine(new GetEntityFromPendingComponentIDEngine()); var entity = worldBuilder.CreateEntity(); - worldBuilder.AddComponent(entity, new MockComponent()); + worldBuilder.SetComponent(entity, new MockComponent()); var world = worldBuilder.Build(); @@ -615,7 +614,7 @@ namespace Tests worldBuilder.AddEngine(new GetPendingComponentFromIDEngine()); var entity = worldBuilder.CreateEntity(); - worldBuilder.AddComponent(entity, new MockComponent()); + worldBuilder.SetComponent(entity, new MockComponent()); var world = worldBuilder.Build(); @@ -644,7 +643,7 @@ namespace Tests component.myString = "howdy"; var entity = worldBuilder.CreateEntity(); - worldBuilder.AddComponent(entity, component); + worldBuilder.SetComponent(entity, component); var world = worldBuilder.Build(); world.Update(0.01f); @@ -654,7 +653,7 @@ namespace Tests struct OtherComponent : IComponent { } - [Reads(typeof(MockComponent))] + [Reads(typeof(MockComponent), typeof(OtherComponent))] class GetComponentByIDWithTypeMismatchEngine : Engine { public override void Update(double dt) @@ -675,7 +674,7 @@ namespace Tests component.myString = "howdy"; var entity = worldBuilder.CreateEntity(); - worldBuilder.AddComponent(entity, component); + worldBuilder.SetComponent(entity, component); var world = worldBuilder.Build(); @@ -710,7 +709,7 @@ namespace Tests EntityIDComponent entityIDComponent; entityIDComponent.entityID = entityTwo.ID; - worldBuilder.AddComponent(entity, entityIDComponent); + worldBuilder.SetComponent(entity, entityIDComponent); var world = worldBuilder.Build(); @@ -729,9 +728,10 @@ namespace Tests public MockComponent mockComponent; } + [Reads(typeof(MockComponent))] [Receives(typeof(MockComponentUpdateMessage))] [Writes(typeof(MockComponent))] - class RepeatUpdateEngine : Engine + class UpdateByComponentIDEngine : Engine { public override void Update(double dt) { @@ -744,10 +744,10 @@ namespace Tests } [Test] - public void EngineUpdatesComponentMultipleTimes() + public void EngineUpdateByComponentID() { var worldBuilder = new WorldBuilder(); - worldBuilder.AddEngine(new RepeatUpdateEngine()); + worldBuilder.AddEngine(new UpdateByComponentIDEngine()); var entity = worldBuilder.CreateEntity(); @@ -755,7 +755,7 @@ namespace Tests mockComponent.myInt = 1; mockComponent.myString = "5"; - var mockComponentID = worldBuilder.AddComponent(entity, mockComponent); + var mockComponentID = worldBuilder.SetComponent(entity, mockComponent); MockComponentUpdateMessage mockComponentUpdateMessage; mockComponentUpdateMessage.componentID = mockComponentID; @@ -763,7 +763,7 @@ namespace Tests worldBuilder.SendMessage(mockComponentUpdateMessage); var world = worldBuilder.Build(); - Assert.Throws(() => world.Update(0.01)); + Assert.DoesNotThrow(() => world.Update(0.01)); } [Reads(typeof(MockComponent))] @@ -789,7 +789,7 @@ namespace Tests worldBuilder.AddEngine(new MessageReadEngine()); var entity = worldBuilder.CreateEntity(); - worldBuilder.AddComponent(entity, new MockComponent {}); + worldBuilder.SetComponent(entity, new MockComponent {}); var world = worldBuilder.Build(); diff --git a/test/GeneralRendererTest.cs b/test/GeneralRendererTest.cs index bd4b4ca..a508bab 100644 --- a/test/GeneralRendererTest.cs +++ b/test/GeneralRendererTest.cs @@ -32,7 +32,7 @@ namespace Tests AComponent aComponent; var entity = worldBuilder.CreateEntity(); - var componentID = worldBuilder.AddComponent(entity, aComponent); + var componentID = worldBuilder.SetComponent(entity, aComponent); var world = worldBuilder.Build(); @@ -52,10 +52,10 @@ namespace Tests AComponent aComponentTwo; var entity = worldBuilder.CreateEntity(); - var componentID = worldBuilder.AddComponent(entity, aComponent); + var componentID = worldBuilder.SetComponent(entity, aComponent); var entityB = worldBuilder.CreateEntity(); - var componentTwoID = worldBuilder.AddComponent(entityB, aComponentTwo); + var componentTwoID = worldBuilder.SetComponent(entityB, aComponentTwo); var world = worldBuilder.Build(); world.Update(0.01f); diff --git a/test/OrderedRendererTest.cs b/test/OrderedRendererTest.cs index 84f3870..a6e7b19 100644 --- a/test/OrderedRendererTest.cs +++ b/test/OrderedRendererTest.cs @@ -52,9 +52,9 @@ namespace Tests TestDrawComponent testDrawComponent; var entity = worldBuilder.CreateEntity(); - worldBuilder.AddComponent(entity, aComponent); - worldBuilder.AddComponent(entity, cComponent); - var testDrawComponentID = worldBuilder.AddDrawComponent(entity, testDrawComponent, 2); + worldBuilder.SetComponent(entity, aComponent); + worldBuilder.SetComponent(entity, cComponent); + var testDrawComponentID = worldBuilder.SetDrawComponent(entity, testDrawComponent, 2); var world = worldBuilder.Build(); @@ -72,7 +72,7 @@ namespace Tests { foreach (var (componentID, component) in ReadComponents()) { - Destroy(GetEntityIDByComponentID(componentID)); + Destroy(GetEntityIDByComponentID(componentID)); } } } @@ -88,7 +88,7 @@ namespace Tests TestDrawComponent testDrawComponent; var entity = worldBuilder.CreateEntity(); - var testDrawComponentID = worldBuilder.AddDrawComponent(entity, testDrawComponent, 1); + var testDrawComponentID = worldBuilder.SetDrawComponent(entity, testDrawComponent, 1); var world = worldBuilder.Build(); diff --git a/test/WorldTest.cs b/test/WorldTest.cs index 9864f5a..e43dda0 100644 --- a/test/WorldTest.cs +++ b/test/WorldTest.cs @@ -43,26 +43,26 @@ namespace Tests TestDrawComponent testDrawComponent = default(TestDrawComponent); var entity = worldBuilder.CreateEntity(); - worldBuilder.AddComponent(entity, testComponent); - var testDrawComponentOneID = worldBuilder.AddDrawComponent(entity, testDrawComponent, 3); + worldBuilder.SetComponent(entity, testComponent); + var testDrawComponentOneID = worldBuilder.SetDrawComponent(entity, testDrawComponent, 3); TestDrawComponent testDrawComponentTwo = default(TestDrawComponent); var entityTwo = worldBuilder.CreateEntity(); - worldBuilder.AddComponent(entityTwo, testComponent); - var testDrawComponentTwoID = worldBuilder.AddDrawComponent(entityTwo, testDrawComponentTwo, 1); + worldBuilder.SetComponent(entityTwo, testComponent); + var testDrawComponentTwoID = worldBuilder.SetDrawComponent(entityTwo, testDrawComponentTwo, 1); TestDrawComponent testDrawComponentThree = default(TestDrawComponent); var entityThree = worldBuilder.CreateEntity(); - worldBuilder.AddComponent(entityThree, testComponent); - var testDrawComponentThreeID = worldBuilder.AddDrawComponent(entityThree, testDrawComponentThree, 5); + worldBuilder.SetComponent(entityThree, testComponent); + var testDrawComponentThreeID = worldBuilder.SetDrawComponent(entityThree, testDrawComponentThree, 5); TestDrawComponent testDrawComponentFour = default(TestDrawComponent); var entityFour = worldBuilder.CreateEntity(); - worldBuilder.AddComponent(entityFour, testComponent); - var testDrawComponentFourID = worldBuilder.AddDrawComponent(entityFour, testDrawComponentFour, -5); + worldBuilder.SetComponent(entityFour, testComponent); + var testDrawComponentFourID = worldBuilder.SetDrawComponent(entityFour, testDrawComponentFour, -5); var world = worldBuilder.Build(); From 46d742fe49c9f63e2e998f4a4cecd34b0f01fbb7 Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Wed, 21 Aug 2019 15:14:53 -0700 Subject: [PATCH 03/13] 0.13.0-rc3 --- encompass-cs/encompass-cs.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/encompass-cs/encompass-cs.csproj b/encompass-cs/encompass-cs.csproj index c8714c9..05cddaa 100644 --- a/encompass-cs/encompass-cs.csproj +++ b/encompass-cs/encompass-cs.csproj @@ -3,7 +3,7 @@ netstandard2.0 Encompass EncompassECS.Framework - 0.13.0-rc2 + 0.13.0-rc3 Evan Hemsley true Moonside Games From 4a7f65f2f5ef6f739384da07e3b850272aa7d0b3 Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Wed, 21 Aug 2019 17:54:43 -0700 Subject: [PATCH 04/13] more write conflict checks --- TODO | 2 +- encompass-cs/WorldBuilder.cs | 46 +++++++++++++++++++++++--- test/WorldBuilderTest.cs | 62 ++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 6 deletions(-) diff --git a/TODO b/TODO index 7324834..4429109 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,4 @@ - look at test coverage - docs -- WritesPending and writes redundant? \ No newline at end of file +- make sure two different engines that Write the same component cannot have same priority \ No newline at end of file diff --git a/encompass-cs/WorldBuilder.cs b/encompass-cs/WorldBuilder.cs index 1e8de7e..74bb81d 100644 --- a/encompass-cs/WorldBuilder.cs +++ b/encompass-cs/WorldBuilder.cs @@ -186,6 +186,8 @@ namespace Encompass var writtenComponentTypesWithoutPriority = new HashSet(); var writtenComponentTypesWithPriority = new HashSet(); var duplicateWritesWithoutPriority = new List(); + var duplicateWritesWithSamePriority = new List(); + var writePriorities = new Dictionary>(); var writeMessageToEngines = new Dictionary>(); foreach (var engine in engines) @@ -196,7 +198,31 @@ namespace Encompass { var componentType = writeType.GetGenericArguments()[0]; - if (!engine.writePriorities.ContainsKey(componentType)) + if (engine.writePriorities.ContainsKey(componentType)) + { + var priority = engine.writePriorities[componentType]; + + writtenComponentTypesWithPriority.Add(componentType); + + if (!writePriorities.ContainsKey(componentType)) + { + writePriorities[componentType] = new HashSet(); + } + + if (writePriorities[componentType].Contains(priority)) + { + duplicateWritesWithSamePriority.Add(componentType); + } + else if (writtenComponentTypesWithoutPriority.Contains(componentType)) + { + duplicateWritesWithoutPriority.Add(componentType); + } + else + { + writePriorities[componentType].Add(priority); + } + } + else { if (writtenComponentTypesWithoutPriority.Contains(componentType) || writtenComponentTypesWithPriority.Contains(componentType)) { @@ -207,10 +233,6 @@ namespace Encompass writtenComponentTypesWithoutPriority.Add(componentType); } } - else - { - writtenComponentTypesWithPriority.Add(componentType); - } if (!writeMessageToEngines.ContainsKey(componentType)) { @@ -235,6 +257,20 @@ namespace Encompass throw new EngineWriteConflictException(errorString); } + if (duplicateWritesWithSamePriority.Count > 0) + { + var errorString = "Multiple Engines write the same Component with the same priority: "; + foreach (var componentType in duplicateWritesWithSamePriority) + { + errorString += "\n" + + componentType.Name + " written by: " + + string.Join(", ", writeMessageToEngines[componentType].Select(engine => engine.GetType().Name)); + } + errorString += "\nTo resolve the conflict, add priority arguments to the Writes declarations."; + + throw new EngineWriteConflictException(errorString); + } + var engineOrder = new List(); foreach (var engine in engineGraph.TopologicalSort()) { diff --git a/test/WorldBuilderTest.cs b/test/WorldBuilderTest.cs index a93b1b5..32d1207 100644 --- a/test/WorldBuilderTest.cs +++ b/test/WorldBuilderTest.cs @@ -275,6 +275,68 @@ namespace Tests } } + public class PriorityConflict + { + [Writes(typeof(MockComponent), 2)] + class AEngine : Engine + { + public override void Update(double dt) + { + + } + } + + [Writes(typeof(MockComponent), 2)] + class BEngine : Engine + { + public override void Update(double dt) + { + + } + } + + [Test] + public void PriorityConflictTest() + { + var worldBuilder = new WorldBuilder(); + worldBuilder.AddEngine(new AEngine()); + worldBuilder.AddEngine(new BEngine()); + + Assert.Throws(() => worldBuilder.Build()); + } + } + + public class EngineWriteConflict + { + [Writes(typeof(MockComponent))] + class AEngine : Engine + { + public override void Update(double dt) + { + + } + } + + [Writes(typeof(MockComponent), 2)] + class BEngine : Engine + { + public override void Update(double dt) + { + + } + } + + [Test] + public void EngineWriteConflictPriorityAndNoPriorityTest() + { + var worldBuilder = new WorldBuilder(); + worldBuilder.AddEngine(new AEngine()); + worldBuilder.AddEngine(new BEngine()); + + Assert.Throws(() => worldBuilder.Build()); + } + } + public class LegalEngines { static List order = new List(); From fa9000590b056438dcc907727256c37b2e0e0c64 Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Wed, 21 Aug 2019 17:54:58 -0700 Subject: [PATCH 05/13] check off TODO --- TODO | 2 -- 1 file changed, 2 deletions(-) diff --git a/TODO b/TODO index 4429109..4e18f97 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,2 @@ - look at test coverage - docs - -- make sure two different engines that Write the same component cannot have same priority \ No newline at end of file From dd22c000728b222608d22ae2f11a2bb425cd3404 Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Wed, 21 Aug 2019 19:21:55 -0700 Subject: [PATCH 06/13] fix crash on multiple write declarations --- encompass-cs/Engine.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/encompass-cs/Engine.cs b/encompass-cs/Engine.cs index 4579b29..b3e69e7 100644 --- a/encompass-cs/Engine.cs +++ b/encompass-cs/Engine.cs @@ -31,11 +31,11 @@ namespace Encompass sendTypes.UnionWith(activatesAttribute.writePendingTypes); } - var writesAttribute = GetType().GetCustomAttribute(false); - if (writesAttribute != null) + var writesAttributes = GetType().GetCustomAttributes(false); + foreach (var writesAttribute in writesAttributes) { sendTypes.UnionWith(writesAttribute.writeTypes); - writePriorities = writesAttribute.priorities; + writePriorities = new Dictionary[2] { writePriorities, writesAttribute.priorities }.SelectMany(dict => dict).ToDictionary(pair => pair.Key, pair => pair.Value); } var receivesAttribute = GetType().GetCustomAttribute(false); From 0a2cc2527ae9e5c3da462ed81f69e8cd81f07e0c Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Wed, 21 Aug 2019 19:22:14 -0700 Subject: [PATCH 07/13] 0.13.0-rc4 --- encompass-cs/encompass-cs.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/encompass-cs/encompass-cs.csproj b/encompass-cs/encompass-cs.csproj index 05cddaa..faf10e8 100644 --- a/encompass-cs/encompass-cs.csproj +++ b/encompass-cs/encompass-cs.csproj @@ -3,7 +3,7 @@ netstandard2.0 Encompass EncompassECS.Framework - 0.13.0-rc3 + 0.13.0-rc4 Evan Hemsley true Moonside Games From fa6958ed953d5dbbb399bb122628015840c3a294 Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Thu, 22 Aug 2019 15:20:10 -0700 Subject: [PATCH 08/13] more helpful ID lookup error messages --- encompass-cs/ComponentManager.cs | 27 ++++++++++++++++--- encompass-cs/ComponentMessageManager.cs | 27 ++++++++++++++++--- encompass-cs/EntityManager.cs | 11 +++++++- .../Exceptions/ComponentNotFoundException.cs | 12 +++++++++ .../Exceptions/EntityNotFoundException.cs | 12 +++++++++ 5 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 encompass-cs/Exceptions/ComponentNotFoundException.cs create mode 100644 encompass-cs/Exceptions/EntityNotFoundException.cs diff --git a/encompass-cs/ComponentManager.cs b/encompass-cs/ComponentManager.cs index 3f9371f..53cdbf0 100644 --- a/encompass-cs/ComponentManager.cs +++ b/encompass-cs/ComponentManager.cs @@ -167,17 +167,38 @@ namespace Encompass internal IComponent GetComponentByID(Guid componentID) { - return IDToComponent[componentID]; + if (IDToComponent.ContainsKey(componentID)) + { + return IDToComponent[componentID]; + } + else + { + throw new ComponentNotFoundException("Component with ID {0} does not exist.", componentID); + } } internal Type GetComponentTypeByID(Guid componentID) { - return componentIDToType[componentID]; + if (componentIDToType.ContainsKey(componentID)) + { + return componentIDToType[componentID]; + } + else + { + throw new ComponentNotFoundException("Component with ID {0} does not exist.", componentID); + } } internal Guid GetEntityIDByComponentID(Guid componentID) { - return componentIDToEntityID[componentID]; + if (componentIDToEntityID.ContainsKey(componentID)) + { + return componentIDToEntityID[componentID]; + } + else + { + throw new ComponentNotFoundException("Component with ID {0} does not exist.", componentID); + } } internal void MarkAllComponentsOnEntityForRemoval(Guid entityID) diff --git a/encompass-cs/ComponentMessageManager.cs b/encompass-cs/ComponentMessageManager.cs index 029d11a..d363e37 100644 --- a/encompass-cs/ComponentMessageManager.cs +++ b/encompass-cs/ComponentMessageManager.cs @@ -277,17 +277,38 @@ namespace Encompass internal IComponent GetComponentByID(Guid componentID) { - return componentIDToComponent[componentID]; + if (componentIDToComponent.ContainsKey(componentID)) + { + return componentIDToComponent[componentID]; + } + else + { + throw new ComponentNotFoundException("Component with ID {0} does not exist.", componentID); + } } internal Type GetComponentTypeByID(Guid componentID) { - return componentIDToType[componentID]; + if (componentIDToType.ContainsKey(componentID)) + { + return componentIDToType[componentID]; + } + else + { + throw new ComponentNotFoundException("Component with ID {0} does not exist.", componentID); + } } internal Guid GetEntityIDByComponentID(Guid componentID) { - return componentIDToEntityID[componentID]; + if (componentIDToEntityID.ContainsKey(componentID)) + { + return componentIDToEntityID[componentID]; + } + else + { + throw new ComponentNotFoundException("Component with ID {0} does not exist.", componentID); + } } } } diff --git a/encompass-cs/EntityManager.cs b/encompass-cs/EntityManager.cs index 107918f..6a6e525 100644 --- a/encompass-cs/EntityManager.cs +++ b/encompass-cs/EntityManager.cs @@ -1,5 +1,7 @@ +using System.Linq; using System; using System.Collections.Generic; +using Encompass.Exceptions; namespace Encompass { @@ -35,7 +37,14 @@ namespace Encompass public Entity GetEntity(Guid id) { - return IDToEntity[id]; + if (IDToEntity.ContainsKey(id)) + { + return IDToEntity[id]; + } + else + { + throw new EntityNotFoundException("Entity with ID {0} does not exist.", id); + } } public void MarkForDestroy(Guid entityID) diff --git a/encompass-cs/Exceptions/ComponentNotFoundException.cs b/encompass-cs/Exceptions/ComponentNotFoundException.cs new file mode 100644 index 0000000..5f1a988 --- /dev/null +++ b/encompass-cs/Exceptions/ComponentNotFoundException.cs @@ -0,0 +1,12 @@ +using System; + +namespace Encompass.Exceptions +{ + public class ComponentNotFoundException : Exception + { + public ComponentNotFoundException( + string format, + params object[] args + ) : base(string.Format(format, args)) { } + } +} diff --git a/encompass-cs/Exceptions/EntityNotFoundException.cs b/encompass-cs/Exceptions/EntityNotFoundException.cs new file mode 100644 index 0000000..c388a12 --- /dev/null +++ b/encompass-cs/Exceptions/EntityNotFoundException.cs @@ -0,0 +1,12 @@ +using System; + +namespace Encompass.Exceptions +{ + public class EntityNotFoundException : Exception + { + public EntityNotFoundException( + string format, + params object[] args + ) : base(string.Format(format, args)) { } + } +} From f26cd48afd1395ea3eabce77bfe1bdf67555f182 Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Thu, 22 Aug 2019 15:27:52 -0700 Subject: [PATCH 09/13] 0.13.0-rc5 --- encompass-cs/encompass-cs.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/encompass-cs/encompass-cs.csproj b/encompass-cs/encompass-cs.csproj index faf10e8..eafdc9b 100644 --- a/encompass-cs/encompass-cs.csproj +++ b/encompass-cs/encompass-cs.csproj @@ -3,7 +3,7 @@ netstandard2.0 Encompass EncompassECS.Framework - 0.13.0-rc4 + 0.13.0-rc5 Evan Hemsley true Moonside Games From 5bcde8bb969fd4d3e7c02b16fea48ec7cd95ad7f Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Thu, 22 Aug 2019 20:07:41 -0700 Subject: [PATCH 10/13] fix crash where overwriting an existing component on the same frame as an entity was being destroyed would cause the component to not be destroyed --- encompass-cs/ComponentManager.cs | 4 +-- test/EngineTest.cs | 44 ++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/encompass-cs/ComponentManager.cs b/encompass-cs/ComponentManager.cs index 53cdbf0..95d3423 100644 --- a/encompass-cs/ComponentManager.cs +++ b/encompass-cs/ComponentManager.cs @@ -105,7 +105,7 @@ namespace Encompass var (entity, type) = keyValuePair.Key; var (componentID, component) = keyValuePair.Value; - if (!entityIDToComponentTypeToComponentID.ContainsKey(entity.ID)) { continue; } + if (!componentIDsMarkedForWrite.Contains(componentID) || !entityIDToComponentTypeToComponentID.ContainsKey(entity.ID)) { continue; } if (entityIDToComponentTypeToComponentID[entity.ID].ContainsKey(type)) { @@ -218,7 +218,7 @@ namespace Encompass { foreach (var componentID in componentsMarkedForRemoval) { - if (componentIDsMarkedForWrite.Contains(componentID)) + if (componentIDsMarkedForWrite.Contains(componentID) && !IDToComponent.ContainsKey(componentID)) { componentIDsMarkedForWrite.Remove(componentID); } diff --git a/test/EngineTest.cs b/test/EngineTest.cs index 6fb829a..9f313d2 100644 --- a/test/EngineTest.cs +++ b/test/EngineTest.cs @@ -847,5 +847,49 @@ namespace Tests Assert.DoesNotThrow(() => world.Update(0.01)); } + + struct DestroyComponentMessage : IMessage { public Entity entity; } + + [Reads(typeof(MockComponent))] + [Writes(typeof(MockComponent))] + class AddComponentEngine : Engine + { + public override void Update(double dt) + { + foreach (var (componentID, component) in ReadComponents()) + { + SetComponent(componentID, new MockComponent {}); + } + } + } + + [Receives(typeof(DestroyComponentMessage))] + class DestroyEntityEngine : Engine + { + public override void Update(double dt) + { + foreach (var message in ReadMessages()) + { + Destroy(message.entity.ID); + } + } + } + + [Test] + public void EngineSetComponentAndDestroyEntitySameFrame() + { + var worldBuilder = new WorldBuilder(); + worldBuilder.AddEngine(new AddComponentEngine()); + worldBuilder.AddEngine(new DestroyEntityEngine()); + + var entity = worldBuilder.CreateEntity(); + worldBuilder.SetComponent(entity, new MockComponent {}); + worldBuilder.SendMessage(new DestroyComponentMessage { entity = entity }); + + var world = worldBuilder.Build(); + world.Update(0.01); + + Assert.DoesNotThrow(() => world.Update(0.01)); + } } } From 0873761bc7b572852528bb753683069764153149 Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Thu, 22 Aug 2019 20:08:20 -0700 Subject: [PATCH 11/13] 0.13.0-rc6 --- encompass-cs/encompass-cs.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/encompass-cs/encompass-cs.csproj b/encompass-cs/encompass-cs.csproj index eafdc9b..9570625 100644 --- a/encompass-cs/encompass-cs.csproj +++ b/encompass-cs/encompass-cs.csproj @@ -3,7 +3,7 @@ netstandard2.0 Encompass EncompassECS.Framework - 0.13.0-rc5 + 0.13.0-rc6 Evan Hemsley true Moonside Games From 44e01fbfc188cc1ad35d34c107aa5a0b4950f044 Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Thu, 22 Aug 2019 20:10:41 -0700 Subject: [PATCH 12/13] fix another related issue lol --- encompass-cs/ComponentManager.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/encompass-cs/ComponentManager.cs b/encompass-cs/ComponentManager.cs index 95d3423..4c7cc60 100644 --- a/encompass-cs/ComponentManager.cs +++ b/encompass-cs/ComponentManager.cs @@ -218,11 +218,12 @@ namespace Encompass { foreach (var componentID in componentsMarkedForRemoval) { - if (componentIDsMarkedForWrite.Contains(componentID) && !IDToComponent.ContainsKey(componentID)) + if (componentIDsMarkedForWrite.Contains(componentID)) { componentIDsMarkedForWrite.Remove(componentID); } - else + + if (IDToComponent.ContainsKey(componentID)) { Remove(componentID); } From 2ff13966644d1f3818688ca8805250d04d754f59 Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Thu, 22 Aug 2019 20:11:05 -0700 Subject: [PATCH 13/13] 0.13.0-rc7 --- encompass-cs/encompass-cs.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/encompass-cs/encompass-cs.csproj b/encompass-cs/encompass-cs.csproj index 9570625..ec781f5 100644 --- a/encompass-cs/encompass-cs.csproj +++ b/encompass-cs/encompass-cs.csproj @@ -3,7 +3,7 @@ netstandard2.0 Encompass EncompassECS.Framework - 0.13.0-rc6 + 0.13.0-rc7 Evan Hemsley true Moonside Games