using System; using System.Reflection; using System.Collections.Generic; using System.Linq; using Encompass.Exceptions; namespace Encompass { /// /// Engines are the Encompass notion of an ECS System. /// They are responsible for reading the World state, reading messages, emitting messages, and creating or mutating Entities and Components. /// Engines run once per World Update. /// public abstract class Engine : IEquatable { public Guid ID; internal readonly HashSet sendTypes = new HashSet(); internal readonly HashSet receiveTypes = new HashSet(); internal readonly Dictionary writePriorities = new Dictionary(); private EntityManager entityManager; private MessageManager messageManager; private ComponentManager componentManager; private ComponentMessageManager componentMessageManager; protected Engine() { ID = Guid.NewGuid(); var sendsAttribute = GetType().GetCustomAttribute(false); if (sendsAttribute != null) { sendTypes = sendsAttribute.sendTypes; } var activatesAttribute = GetType().GetCustomAttribute(false); if (activatesAttribute != null) { sendTypes.UnionWith(activatesAttribute.writePendingTypes); } var writesAttributes = GetType().GetCustomAttributes(false); foreach (var writesAttribute in writesAttributes) { 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); if (receivesAttribute != null) { receiveTypes = receivesAttribute.receiveTypes; } var readsAttribute = GetType().GetCustomAttribute(false); if (readsAttribute != null) { receiveTypes.UnionWith(readsAttribute.readTypes); } var readsPendingAttribute = GetType().GetCustomAttribute(false); if (readsPendingAttribute != null) { receiveTypes.UnionWith(readsPendingAttribute.readPendingTypes); } } public override bool Equals(object obj) { if (obj is Engine) { return this.Equals((Engine)obj); } return false; } public bool Equals(Engine other) { return other.ID == ID; } public override int GetHashCode() { return ID.GetHashCode(); } internal void AssignEntityManager(EntityManager entityManager) { this.entityManager = entityManager; } internal void AssignComponentManager(ComponentManager componentManager) { this.componentManager = componentManager; } internal void AssignMessageManager(MessageManager messageManager) { this.messageManager = messageManager; } internal void AssignComponentMessageManager(ComponentMessageManager componentMessageManager) { this.componentMessageManager = componentMessageManager; } /// /// Runs once per World update with the calculated delta-time. /// /// The time in seconds that has elapsed since the previous frame. public abstract void Update(double dt); /// /// Creates and returns a new empty Entity. /// protected Entity CreateEntity() { return entityManager.CreateEntity(); } /// /// Returns true if an Entity with the specified ID exists. /// protected bool EntityExists(Guid entityID) { return entityManager.EntityExists(entityID); } /// /// Returns the Entity with the specified ID. /// /// /// Thrown when an Entity with the given ID does not exist. /// protected Entity GetEntity(Guid entityID) { return entityManager.GetEntity(entityID); } /// /// Returns the Entity ID associated with the specified Component Type and ID. /// 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); } /// /// Returns the Entity associated with the specified Component Type and ID. /// protected Entity GetEntityByComponentID(Guid componentID) where TComponent : struct, IComponent { return GetEntity(GetEntityIDByComponentID(componentID)); } /// /// Returns an Entity containing the specified Component type. /// protected Entity ReadEntity() where TComponent : struct, IComponent { var (id, component) = ReadComponent(); return GetEntityByComponentID(id); } /// /// Returns all Entities containing the specified Component type. /// protected IEnumerable ReadEntities() where TComponent : struct, IComponent { foreach (var (id, component) in ReadComponents()) { yield return GetEntityByComponentID(id); } } /// /// Returns the Component struct with the specified Component Type and ID. /// 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); } return (TComponent)componentMessageManager.GetComponentByID(componentID); } // these next two are for the ComponentMessageEmitter only internal IEnumerable<(Guid, TComponent)> ReadComponentsFromWorld() where TComponent : struct, IComponent { return componentManager.GetComponentsByType(); } internal Entity ReadEntityFromWorld(Guid componentID) { return GetEntity(componentManager.GetEntityIDByComponentID(componentID)); } /// /// Returns all of the Components with the specified Component Type. /// protected IEnumerable<(Guid, TComponent)> ReadComponents() where TComponent : struct, IComponent { var pendingRead = receiveTypes.Contains(typeof(PendingComponentMessage)); var existingRead = receiveTypes.Contains(typeof(ComponentMessage)); if (existingRead && pendingRead) { return componentMessageManager.ReadExistingAndPendingComponentsByType(); } else if (existingRead) { return componentMessageManager.ReadExistingComponentsByType(); } else if (pendingRead) { return componentMessageManager.ReadPendingComponentsByType(); } else { throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", GetType().Name, typeof(TComponent).Name); } } /// /// Returns a Component with the specified Component Type and ID. If multiples exist, an arbitrary Component is returned. /// protected (Guid, TComponent) ReadComponent() where TComponent : struct, IComponent { var pendingRead = receiveTypes.Contains(typeof(PendingComponentMessage)); var existingRead = receiveTypes.Contains(typeof(ComponentMessage)); if (existingRead && pendingRead) { return componentMessageManager.ReadFirstExistingOrPendingComponentByType(); } else if (existingRead) { return componentMessageManager.ReadFirstExistingComponentByType(); } else if (pendingRead) { return componentMessageManager.ReadFirstPendingComponentByType(); } else { throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", GetType().Name, typeof(TComponent).Name); } } /// /// Returns true if any Component with the specified Component Type exists. /// protected bool SomeComponent() where TComponent : struct, IComponent { var pendingRead = receiveTypes.Contains(typeof(PendingComponentMessage)); var existingRead = receiveTypes.Contains(typeof(ComponentMessage)); if (existingRead && pendingRead) { return componentMessageManager.SomeExistingOrPendingComponent(); } else if (existingRead) { return componentMessageManager.SomeExistingComponent(); } else if (pendingRead) { return componentMessageManager.SomePendingComponent(); } else { throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", GetType().Name, typeof(TComponent).Name); } } /// /// Returns a Component with the specified Type that exists on the Entity. /// /// /// Thrown when the Entity does not have a Component of the specified Type /// /// /// Thrown when the Engine does not declare that it reads the given Component Type. /// protected (Guid, TComponent) GetComponent(Entity entity) where TComponent : struct, IComponent { var pendingRead = receiveTypes.Contains(typeof(PendingComponentMessage)); var existingRead = receiveTypes.Contains(typeof(ComponentMessage)); if (existingRead && pendingRead) { if (componentMessageManager.HasPendingComponent(entity)) { return componentMessageManager.ReadPendingComponentByEntityAndType(entity); } else if (componentMessageManager.HasExistingComponent(entity)) { return componentMessageManager.ReadExistingComponentByEntityAndType(entity); } else { throw new NoComponentOfTypeOnEntityException("No Component of type {0} exists on Entity {1}", typeof(TComponent).Name, entity.ID); } } else if (existingRead) { return componentMessageManager.ReadExistingComponentByEntityAndType(entity); } else if (pendingRead) { return componentMessageManager.ReadPendingComponentByEntityAndType(entity); } else { throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", GetType().Name, typeof(TComponent).Name); } } /// /// Returns true if the Entity has a Component of the given Type. /// /// /// Thrown when the Engine does not declare that is Reads the given Component Type. /// protected bool HasComponent(Entity entity) where TComponent : struct, IComponent { var pendingRead = receiveTypes.Contains(typeof(PendingComponentMessage)); var existingRead = receiveTypes.Contains(typeof(ComponentMessage)); if (pendingRead && existingRead) { return componentMessageManager.HasExistingOrPendingComponent(entity); } else if (existingRead) { return componentMessageManager.HasExistingComponent(entity); } else if (pendingRead) { return componentMessageManager.HasPendingComponent(entity); } else { throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", GetType().Name, typeof(TComponent).Name); } } /// /// Returns true if the Entity has a Component of the given Type. /// /// /// Thrown when the Engine does not declare that is Reads the given Component Type. /// protected bool HasComponent(Entity entity, Type type) { var pending = typeof(PendingComponentMessage<>).MakeGenericType(type); var existing = typeof(ComponentMessage<>).MakeGenericType(type); var pendingRead = receiveTypes.Contains(pending); var existingRead = receiveTypes.Contains(existing); if (pendingRead && existingRead) { return componentMessageManager.HasExistingOrPendingComponent(entity, type); } else if (existingRead) { return componentMessageManager.HasExistingComponent(entity, type); } else if (pendingRead) { return componentMessageManager.HasPendingComponent(entity, type); } else { throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", GetType().Name, type.Name); } } /// /// Sets Component data for the specified Component Type on the specified Entity. If Component data for this Type already existed on the Entity, the component data is overwritten. /// /// /// Thrown when the Engine does not declare that it Writes the given Component Type. /// protected Guid SetComponent(Entity entity, TComponent component) where TComponent : struct, IComponent { var priority = writePriorities.ContainsKey(typeof(TComponent)) ? writePriorities[typeof(TComponent)] : 0; var componentID = componentManager.MarkComponentForWrite(entity, component, priority); if (!sendTypes.Contains(typeof(ComponentWriteMessage))) { throw new IllegalWriteException("Engine {0} tried to update 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; } /// /// Overwrites Component struct data associated with the specified Component ID. /// protected Guid SetComponent(Guid componentID, TComponent component) where TComponent : struct, IComponent { return SetComponent(GetEntityByComponentID(componentID), component); } /// /// Sets Draw Component data for the specified Component Type on the specified Entity. /// This method must be used for the Draw Component to be readable by an OrderedRenderer. /// If Component data for this Type already existed on the Entity, the component data is overwritten. /// /// /// Thrown when the Engine does not declare that it Writes the given Component Type. /// protected Guid SetDrawComponent(Entity entity, TComponent component, int layer = 0) where TComponent : struct, IComponent, IDrawComponent { 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; } /// /// Sends a Message. /// /// /// Thrown when the Engine does not declare that it Sends the Message Type. /// protected void SendMessage(TMessage message) where TMessage : struct, IMessage { if (!sendTypes.Contains(typeof(TMessage))) { throw new IllegalSendException("Engine {0} tried to send undeclared Message {1}", GetType().Name, typeof(TMessage).Name); } messageManager.AddMessage(message); } /// /// Sends a message after the specified number of seconds. /// /// The time in seconds that will elapse before the message is sent. protected void SendMessageDelayed(TMessage message, double time) where TMessage : struct, IMessage { messageManager.AddMessageDelayed(message, time); } // unparameterized version to enable dynamic dispatch protected void SendMessage(IMessage message) { var type = message.GetType(); if (!sendTypes.Contains(type) || !type.IsValueType) { throw new IllegalSendException("Engine {0} tried to send undeclared Message {1}", GetType().Name, type.Name); } messageManager.AddMessage(message); } internal void SendExistingComponentMessage(ComponentMessage message) where TComponent : struct, IComponent { componentMessageManager.AddExistingComponentMessage(message); } internal void SendPendingComponentMessage(PendingComponentMessage message) where TComponent : struct, IComponent { componentMessageManager.AddPendingComponentMessage(message); } /// /// Reads all messages of the specified Type. /// /// /// Thrown when the Engine does not declare that it Receives the specified Message Type. /// protected IEnumerable ReadMessages() where TMessage : struct, IMessage { if (!receiveTypes.Contains(typeof(TMessage))) { throw new IllegalReadException("Engine {0} tried to read undeclared Message {1}", this.GetType().Name, typeof(TMessage).Name); } return messageManager.GetMessagesByType(); } /// /// Reads an arbitrary message of the specified Type. /// /// /// Thrown when the Engine does not declare that it Receives the specified Message Type. /// protected TMessage ReadMessage() where TMessage : struct, IMessage { return ReadMessages().First(); } /// /// Returns true if a Message of the specified Type has been sent this frame. /// /// /// Thrown when the Engine does not declare that it Receives the specified Message Type. /// protected bool SomeMessage() where TMessage : struct, IMessage { if (!receiveTypes.Contains(typeof(TMessage))) { throw new IllegalReadException("Engine {0} tried to read undeclared Message {1}", GetType().Name, typeof(TMessage).Name); } return ReadMessages().Any(); } /// /// Destroys the Entity with the specified ID. This also removes all of the Components associated with the Entity. /// Entity destruction takes place after all the Engines have been processed by World Update. /// protected void Destroy(Guid entityID) { entityManager.MarkForDestroy(entityID); } /// /// Destroys the specified Entity. This also removes all of the Components associated with the Entity. /// Entity destruction takes place after all the Engines have been processed by World Update. /// protected void Destroy(Entity entity) { entityManager.MarkForDestroy(entity.ID); } /// /// Destroys an arbitrary Entity containing a Component of the specified Type. /// Entity destruction takes place after all the Engines have been processed by World Update. /// protected void DestroyWith() where TComponent : struct, IComponent { Destroy(ReadEntity()); } /// /// Destroys all Entities containing a Component of the specified Type. /// Entity destruction takes place after all the Engines have been processed by World Update. /// protected void DestroyAllWith() where TComponent : struct, IComponent { foreach (var entity in ReadEntities()) { Destroy(entity); } } /// /// Removes the Component with the specified ID from its Entity. /// protected void RemoveComponent(Guid componentID) { componentManager.MarkForRemoval(componentID); } } }