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..4c7cc60 100644 --- a/encompass-cs/ComponentManager.cs +++ b/encompass-cs/ComponentManager.cs @@ -19,10 +19,10 @@ 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 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) { @@ -40,53 +40,86 @@ 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 (componentWriteData.ContainsKey((entity, typeof(TComponent)))) + { + var currentPriority = componentWritePriorities[(entity, typeof(TComponent))]; + if (priority < currentPriority) + { + componentWriteData[(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; } internal void AddComponent(Entity entity, Type type, Guid componentID, IComponent component) { - if (!entityIDToComponentTypeToComponentID.ContainsKey(entity.ID)) { return; } - - 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)) { - 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); + typeToComponentIDs.Add(type, new HashSet()); } + typeToComponentIDs[type].Add(componentID); + entityIDToComponentIDs[entity.ID].Add(componentID); } - internal void AddMarkedComponents() + internal void UpdateComponent(Guid componentID, IComponent component) { - foreach (var (entity, type, componentID, component) in componentAddData) + IDToComponent[componentID] = component; + } + + internal void WriteComponents() + { + foreach (var keyValuePair in componentWriteData) { - AddComponent(entity, type, componentID, component); + var (entity, type) = keyValuePair.Key; + var (componentID, component) = keyValuePair.Value; + + if (!componentIDsMarkedForWrite.Contains(componentID) || !entityIDToComponentTypeToComponentID.ContainsKey(entity.ID)) { continue; } + + if (entityIDToComponentTypeToComponentID[entity.ID].ContainsKey(type)) + { + UpdateComponent(componentID, component); + } + else + { + AddComponent(entity, type, componentID, component); + } } - componentAddData.Clear(); - componentIDsMarkedForAdd.Clear(); + componentWriteData.Clear(); + componentIDsMarkedForWrite.Clear(); + componentWritePriorities.Clear(); } internal IEnumerable GetComponentIDsByEntityID(Guid entityID) @@ -134,37 +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]; - } - - internal void AddUpdateComponentOperation(Guid componentID, TComponent newComponentValue) where TComponent : struct, IComponent - { - if (pendingUpdates.ContainsKey(componentID)) + if (componentIDToEntityID.ContainsKey(componentID)) { - throw new RepeatUpdateComponentException("Component {0} with ID {1} was updated multiple times this frame", typeof(TComponent).Name, componentID); + return componentIDToEntityID[componentID]; } - - pendingUpdates.Add(componentID, newComponentValue); - } - - internal void PerformComponentUpdates() - { - foreach (var idPair in pendingUpdates) + else { - IDToComponent[idPair.Key] = idPair.Value; + throw new ComponentNotFoundException("Component with ID {0} does not exist.", componentID); } - - pendingUpdates.Clear(); } internal void MarkAllComponentsOnEntityForRemoval(Guid entityID) @@ -184,11 +218,12 @@ namespace Encompass { foreach (var componentID in componentsMarkedForRemoval) { - if (componentIDsMarkedForAdd.Contains(componentID)) + if (componentIDsMarkedForWrite.Contains(componentID)) { - componentIDsMarkedForAdd.Remove(componentID); + componentIDsMarkedForWrite.Remove(componentID); } - else + + if (IDToComponent.ContainsKey(componentID)) { Remove(componentID); } diff --git a/encompass-cs/ComponentMessageManager.cs b/encompass-cs/ComponentMessageManager.cs index 0464b4e..d363e37 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); + } } } @@ -260,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/Engine.cs b/encompass-cs/Engine.cs index 0ae92a8..b3e69e7 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 writesAttributes = GetType().GetCustomAttributes(false); + foreach (var writesAttribute in writesAttributes) { - sendTypes.UnionWith(updatesAttribute.updateTypes); + sendTypes.UnionWith(writesAttribute.writeTypes); + writePriorities = new Dictionary[2] { writePriorities, writesAttribute.priorities }.SelectMany(dict => dict).ToDictionary(pair => pair.Key, pair => pair.Value); } var receivesAttribute = GetType().GetCustomAttribute(false); @@ -92,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); @@ -124,40 +142,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)); @@ -280,22 +264,57 @@ namespace Encompass } } - internal void UpdateComponentInWorld(Guid componentID, TComponent newComponent) where TComponent : struct, IComponent + protected Guid SetComponent(Entity entity, TComponent component) where TComponent : struct, IComponent { - componentManager.AddUpdateComponentOperation(componentID, newComponent); - } + var priority = writePriorities.ContainsKey(typeof(TComponent)) ? writePriorities[typeof(TComponent)] : 0; - protected void UpdateComponent(Guid componentID, TComponent newComponentValue) where TComponent : struct, IComponent - { - if (!sendTypes.Contains(typeof(ComponentUpdateMessage))) + 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; - componentUpdateMessage.componentID = componentID; - componentUpdateMessage.component = newComponentValue; - SendMessage(componentUpdateMessage); + if (sendTypes.Contains(typeof(PendingComponentMessage))) + { + PendingComponentMessage newComponentMessage; + newComponentMessage.entity = entity; + newComponentMessage.componentID = componentID; + newComponentMessage.component = component; + newComponentMessage.priority = priority; + SendPendingComponentMessage(newComponentMessage); + } + + 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); + } + + 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 deleted file mode 100644 index 2656c7e..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(ComponentUpdateMessage)); - } - - 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/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/IllegalUpdateTypeException.cs b/encompass-cs/Exceptions/ComponentNotFoundException.cs similarity index 63% rename from encompass-cs/Exceptions/IllegalUpdateTypeException.cs rename to encompass-cs/Exceptions/ComponentNotFoundException.cs index 0a63dad..5f1a988 100644 --- a/encompass-cs/Exceptions/IllegalUpdateTypeException.cs +++ b/encompass-cs/Exceptions/ComponentNotFoundException.cs @@ -2,9 +2,9 @@ using System; namespace Encompass.Exceptions { - public class IllegalUpdateTypeException : Exception + public class ComponentNotFoundException : Exception { - public IllegalUpdateTypeException( + public ComponentNotFoundException( string format, params object[] args ) : base(string.Format(format, args)) { } 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/IllegalActivateException.cs b/encompass-cs/Exceptions/EntityNotFoundException.cs similarity index 64% rename from encompass-cs/Exceptions/IllegalActivateException.cs rename to encompass-cs/Exceptions/EntityNotFoundException.cs index f13c9e8..c388a12 100644 --- a/encompass-cs/Exceptions/IllegalActivateException.cs +++ b/encompass-cs/Exceptions/EntityNotFoundException.cs @@ -2,9 +2,9 @@ using System; namespace Encompass.Exceptions { - public class IllegalActivateException : Exception + public class EntityNotFoundException : Exception { - public IllegalActivateException( + public EntityNotFoundException( 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/IllegalActivateTypeException.cs b/encompass-cs/Exceptions/IllegalWritePendingException.cs similarity index 62% rename from encompass-cs/Exceptions/IllegalActivateTypeException.cs rename to encompass-cs/Exceptions/IllegalWritePendingException.cs index f167151..3983c23 100644 --- a/encompass-cs/Exceptions/IllegalActivateTypeException.cs +++ b/encompass-cs/Exceptions/IllegalWritePendingException.cs @@ -2,9 +2,9 @@ using System; namespace Encompass.Exceptions { - public class IllegalActivateTypeException : Exception + public class IllegalWritePendingException : Exception { - public IllegalActivateTypeException( + public IllegalWritePendingException( string format, params object[] args ) : base(string.Format(format, args)) { } diff --git a/encompass-cs/Exceptions/IllegalWritePendingTypeException.cs b/encompass-cs/Exceptions/IllegalWritePendingTypeException.cs new file mode 100644 index 0000000..19554be --- /dev/null +++ b/encompass-cs/Exceptions/IllegalWritePendingTypeException.cs @@ -0,0 +1,12 @@ +using System; + +namespace Encompass.Exceptions +{ + public class IllegalWritePendingTypeException : Exception + { + public IllegalWritePendingTypeException( + string format, + params object[] args + ) : base(string.Format(format, args)) { } + } +} diff --git a/encompass-cs/Exceptions/IllegalWriteTypeException.cs b/encompass-cs/Exceptions/IllegalWriteTypeException.cs new file mode 100644 index 0000000..3c720b6 --- /dev/null +++ b/encompass-cs/Exceptions/IllegalWriteTypeException.cs @@ -0,0 +1,12 @@ +using System; + +namespace Encompass.Exceptions +{ + public class IllegalWriteTypeException : Exception + { + 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..576c51e 100644 --- a/encompass-cs/World.cs +++ b/encompass-cs/World.cs @@ -41,9 +41,8 @@ namespace Encompass componentMessageManager.ClearMessages(); entityManager.DestroyMarkedEntities(); - componentManager.PerformComponentUpdates(); componentManager.RemoveMarkedComponents(); - componentManager.AddMarkedComponents(); + componentManager.WriteComponents(); } public void Draw() diff --git a/encompass-cs/WorldBuilder.cs b/encompass-cs/WorldBuilder.cs index 6173506..5ffdec4 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.MarkComponentForAdd(entity, component); + 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.MarkDrawComponentForAdd(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(ComponentUpdateMessage<>)) - { - var componentType = sendType.GetGenericArguments().Single(); - RegisterNewComponentUpdater(componentType); - } - } - } - return engine; } @@ -201,45 +183,92 @@ 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 duplicateWritesWithSamePriority = new List(); + var writePriorities = new Dictionary>(); + 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); + 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 { - mutatedComponentTypes.Add(updateType); + if (writtenComponentTypesWithoutPriority.Contains(componentType) || writtenComponentTypesWithPriority.Contains(componentType)) + { + duplicateWritesWithoutPriority.Add(componentType); + } + else + { + writtenComponentTypesWithoutPriority.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); + } + + 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(); @@ -257,9 +286,8 @@ namespace Encompass renderManager ); - componentManager.PerformComponentUpdates(); componentManager.RemoveMarkedComponents(); - componentManager.AddMarkedComponents(); + componentManager.WriteComponents(); return world; } diff --git a/encompass-cs/encompass-cs.csproj b/encompass-cs/encompass-cs.csproj index c8714c9..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-rc2 + 0.13.0-rc7 Evan Hemsley true Moonside Games diff --git a/test/ComponentTest.cs b/test/ComponentTest.cs index 2d14aba..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,58 +83,76 @@ 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))] - class MultipleAddEngine : Engine + [WritesPending(typeof(MockComponent))] + [Writes(typeof(MockComponent))] + class OverwriteEngine : Engine { public override void Update(double dt) { foreach (var (mockComponentID, mockComponent) in ReadComponents()) { - var entity = GetEntityByComponentID(mockComponentID); + var entity = GetEntityByComponentID(mockComponentID); - AddComponent(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))] + [Writes(typeof(MockComponent))] class AddAndRemoveComponentEngine : Engine { public override void Update(double dt) { foreach (var (mockComponentID, mockComponent) in ReadComponents()) { - var entity = GetEntityByComponentID(mockComponentID); - AddComponent(entity, mockComponent); + var entity = GetEntityByComponentID(mockComponentID); + SetComponent(entity, mockComponent); RemoveComponent(mockComponentID); } } @@ -147,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(); @@ -183,15 +201,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); } } } @@ -239,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; @@ -282,7 +301,7 @@ namespace Tests mockComponent.myInt = 3; mockComponent.myString = "hello"; - worldBuilder.AddComponent(entity, mockComponent); + worldBuilder.SetComponent(entity, mockComponent); HasComponentTestMessage hasComponentTestMessage; hasComponentTestMessage.entity = entity; @@ -368,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 79ad717..9f313d2 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(); @@ -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(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(); @@ -169,7 +169,7 @@ namespace Tests component.myInt = 420; component.myString = "blaze it"; - UpdateComponent(componentID, component); + SetComponent(componentID, component); component = ReadComponent().Item2; } @@ -187,11 +187,11 @@ namespace Tests mockComponent.myInt = 0; mockComponent.myString = "hello"; - worldBuilder.AddComponent(entity, mockComponent); + worldBuilder.SetComponent(entity, mockComponent); 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")); } @@ -353,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(); @@ -391,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); @@ -436,7 +436,7 @@ namespace Tests foreach (var componentPair in ReadComponents()) { var componentID = componentPair.Item1; - var entityID = GetEntityIDByComponentID(componentID); + var entityID = GetEntityIDByComponentID(componentID); Destroy(entityID); } } @@ -469,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(); @@ -495,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); @@ -512,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(); @@ -528,7 +528,7 @@ namespace Tests public override void Update(double dt) { var componentID = ReadComponent().Item1; - entityFromComponentIDResult = GetEntityByComponentID(componentID); + entityFromComponentIDResult = GetEntityByComponentID(componentID); } } @@ -543,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); @@ -551,17 +551,18 @@ 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) { foreach (var (mockComponentID, mockComponent) in ReadComponents()) { - var entity = GetEntityByComponentID(mockComponentID); + var entity = GetEntityByComponentID(mockComponentID); RemoveComponent(mockComponentID); - AddComponent(entity, new MockComponent()); + SetComponent(entity, new MockComponent()); } } } @@ -573,7 +574,7 @@ namespace Tests { foreach (var (mockComponentID, mockComponent) in ReadComponents()) { - entityFromComponentIDResult = GetEntityByComponentID(mockComponentID); + entityFromComponentIDResult = GetEntityByComponentID(mockComponentID); } } } @@ -586,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(); @@ -613,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(); @@ -642,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); @@ -652,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) @@ -673,7 +674,7 @@ namespace Tests component.myString = "howdy"; var entity = worldBuilder.CreateEntity(); - worldBuilder.AddComponent(entity, component); + worldBuilder.SetComponent(entity, component); var world = worldBuilder.Build(); @@ -708,7 +709,7 @@ namespace Tests EntityIDComponent entityIDComponent; entityIDComponent.entityID = entityTwo.ID; - worldBuilder.AddComponent(entity, entityIDComponent); + worldBuilder.SetComponent(entity, entityIDComponent); var world = worldBuilder.Build(); @@ -727,25 +728,26 @@ namespace Tests public MockComponent mockComponent; } + [Reads(typeof(MockComponent))] [Receives(typeof(MockComponentUpdateMessage))] - [Updates(typeof(MockComponent))] - class RepeatUpdateEngine : Engine + [Writes(typeof(MockComponent))] + class UpdateByComponentIDEngine : 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); } } } [Test] - public void EngineUpdatesComponentMultipleTimes() + public void EngineUpdateByComponentID() { var worldBuilder = new WorldBuilder(); - worldBuilder.AddEngine(new RepeatUpdateEngine()); + worldBuilder.AddEngine(new UpdateByComponentIDEngine()); var entity = worldBuilder.CreateEntity(); @@ -753,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; @@ -761,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))] @@ -787,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(); @@ -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 {}); } } } @@ -844,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)); + } } } 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 079d2c7..413ad3c 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/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..32d1207 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)); } } @@ -206,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(); diff --git a/test/WorldTest.cs b/test/WorldTest.cs index 2c3712f..ae2ffa2 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();