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)); } }