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 { internal Guid ID; internal readonly HashSet sendTypes = new HashSet(); internal readonly HashSet receiveTypes = new HashSet(); internal readonly Dictionary writePriorities = new Dictionary(); /// /// If false, the Engine will ignore time dilation. /// internal bool usesTimeDilation = true; public bool TimeDilationActive { get => usesTimeDilation && timeManager.TimeDilationActive; } /// /// Used when activating time dilation. Lower priority overrides higher priority. /// private EntityManager entityManager; private MessageManager messageManager; private ComponentManager componentManager; private ComponentMessageManager componentMessageManager; private TimeManager timeManager; 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; } internal void AssignTimeManager(TimeManager timeManager) { this.timeManager = timeManager; } /// /// 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(Entity entity) { return entityManager.EntityExists(entity.ID); } /// /// Returns the Entity with the specified ID. /// /// /// Thrown when an Entity with the given ID does not exist. /// internal Entity GetEntity(Guid entityID) { return entityManager.GetEntity(entityID); } /// /// Returns an Entity containing the specified Component type. /// protected Entity ReadEntity() where TComponent : struct, IComponent { return ReadComponentHelper().Item1; } /// /// Returns all Entities containing the specified Component type. /// protected IEnumerable ReadEntities() where TComponent : struct, IComponent { return ReadComponentsHelper().Select(pair => pair.Item1); } // these next two are for the ComponentMessageEmitter only internal IEnumerable ReadComponentsFromWorld() where TComponent : struct, IComponent { return componentManager.GetComponentsByType(); } private IEnumerable<(Entity, TComponent)> ReadComponentsHelper() 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 all of the Components with the specified Component Type. /// protected IEnumerable ReadComponents() where TComponent : struct, IComponent { return ReadComponentsHelper().Select(tuple => tuple.Item2); } /// /// Returns all of the components of the specified type including an Entity reference for each Component. /// protected IEnumerable<(TComponent, Entity)> ReadComponentsIncludingEntity() where TComponent : struct, IComponent { return ReadComponentsHelper().Select((tuple) => (tuple.Item2, tuple.Item1)); } internal IEnumerable<(TComponent, Entity)> InternalRead() where TComponent : struct, IComponent { return componentManager.GetComponentsIncludingEntity(); } private (Entity, TComponent) ReadComponentHelper() 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 a Component with the specified Component Type. If multiples exist, an arbitrary Component is returned. /// protected TComponent ReadComponent() where TComponent : struct, IComponent { return ReadComponentHelper().Item2; } /// /// Returns a component of the specified type including its Entity reference. If multiples exist, an arbitrary Component is returned. /// protected (TComponent, Entity) ReadComponentIncludingEntity() where TComponent : struct, IComponent { var (entity, component) = ReadComponentHelper(); return (component, entity); } /// /// 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); } } private TComponent GetComponentHelper(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 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 TComponent GetComponent(Entity entity) where TComponent : struct, IComponent { return GetComponentHelper(entity); } /// /// 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 void SetComponent(Entity entity, TComponent component) where TComponent : struct, IComponent { var priority = writePriorities.ContainsKey(typeof(TComponent)) ? writePriorities[typeof(TComponent)] : 0; 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.component = component; newComponentMessage.priority = priority; SendPendingComponentMessage(newComponentMessage); } else { componentMessageManager.UpdateComponent(entity, component, priority); } if (component is IDrawableComponent drawableComponent) { componentManager.RegisterDrawableComponent(entity, component, drawableComponent.Layer); } } /// /// 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, respecting time dilation. /// /// The time in seconds that will elapse before the message is sent. protected void SendMessage(TMessage message, double time) where TMessage : struct, IMessage { messageManager.AddMessageDelayed(message, time); } /// /// Sends a message after the specified number of seconds, ignoring time dilation. /// /// The time in seconds that will elapse before the message is sent. protected void SendMessageIgnoringTimeDilation(TMessage message, double time) where TMessage : struct, IMessage { messageManager.AddMessageDelayedIgnoringTimeDilation(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 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); } /// /// 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 a Component with the specified type from the given Entity. /// Note that the Engine must Read the Component type that is being removed. /// If a Component with the specified type does not exist on the Entity, returns false and does not mutate the Entity. /// protected bool RemoveComponent(Entity entity) where TComponent : struct, IComponent { if (!HasComponent(entity)) { return false; } componentManager.Remove(entity); return true; } /// /// Activates the Encompass time dilation system. /// Engines that have the IgnoresTimeDilation property will ignore all time dilation. /// If multiple time dilations are active they will be averaged. /// /// The time dilation factor, which is multiplied by real delta time. /// The time that will elapse before time is fully dilated, in real time. /// The length of real time that time will be fully dilated. /// The time that will elapse before time is fully undilated. public void ActivateTimeDilation(double factor, double easeInTime, double activeTime, double easeOutTime) { timeManager.ActivateTimeDilation(factor, easeInTime, activeTime, easeOutTime); } /// /// Activates the Encompass time dilation system. /// Engines that have the IgnoresTimeDilation property will ignore all time dilation. /// If multiple time dilations are active they will be averaged. /// /// The time dilation factor, which is multiplied by real delta time. /// The time that will elapse before time is fully dilated, in real time. /// An easing function for the easing in of time dilation. /// The length of real time that time will be fully dilated. /// The time that will elapse before time is fully undilated. public void ActivateTimeDilation(double factor, double easeInTime, System.Func easeInFunction, double activeTime, double easeOutTime) { timeManager.ActivateTimeDilation(factor, easeInTime, easeInFunction, activeTime, easeOutTime); } /// /// Activates the Encompass time dilation system. /// Engines that have the IgnoresTimeDilation property will ignore all time dilation. /// If multiple time dilations are active they will be averaged. /// /// The time dilation factor, which is multiplied by real delta time. /// The time that will elapse before time is fully dilated, in real time. /// The length of real time that time will be fully dilated. /// The time that will elapse before time is fully undilated. /// An easing function for the easing out of time dilation. public void ActivateTimeDilation(double factor, double easeInTime, double activeTime, double easeOutTime, System.Func easeOutFunction) { timeManager.ActivateTimeDilation(factor, easeInTime, activeTime, easeOutTime, easeOutFunction); } /// /// Activates the Encompass time dilation system. /// Engines that have the IgnoresTimeDilation property will ignore all time dilation. /// If multiple time dilations are active they will be averaged. /// /// The time dilation factor, which is multiplied by real delta time. /// The time that will elapse before time is fully dilated, in real time. /// An easing function for the easing in of time dilation. /// The length of real time that time will be fully dilated. /// The time that will elapse before time is fully undilated. /// An easing function for the easing out of time dilation. public void ActivateTimeDilation(double factor, double easeInTime, System.Func easeInFunction, double activeTime, double easeOutTime, System.Func easeOutFunction) { timeManager.ActivateTimeDilation(factor, easeInTime, easeInFunction, activeTime, easeOutTime, easeOutFunction); } } }