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 readTypes = new HashSet(); internal readonly HashSet readPendingTypes = new HashSet(); internal readonly HashSet sendTypes = new HashSet(); internal readonly HashSet receiveTypes = new HashSet(); internal readonly HashSet writeTypes = new HashSet(); internal readonly HashSet writePendingTypes = new HashSet(); internal readonly Dictionary writePriorities = new Dictionary(); internal readonly int defaultWritePriority = 0; /// /// If false, the Engine will ignore time dilation. /// internal bool usesTimeDilation = true; public bool TimeDilationActive { get => usesTimeDilation && timeManager.TimeDilationActive; } private EntityManager entityManager; private MessageManager messageManager; private ComponentManager componentManager; private ComponentUpdateManager componentUpdateManager; 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) { writePendingTypes = activatesAttribute.writePendingTypes; } var defaultWritePriorityAttribute = GetType().GetCustomAttribute(false); if (defaultWritePriorityAttribute != null) { defaultWritePriority = defaultWritePriorityAttribute.writePriority; } foreach (var writesAttribute in GetType().GetCustomAttributes(false)) { writeTypes.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) { readTypes = readsAttribute.readTypes; } var readsPendingAttribute = GetType().GetCustomAttribute(false); if (readsPendingAttribute != null) { readPendingTypes = readsPendingAttribute.readPendingTypes; } } public override bool Equals(object obj) { if (obj is Engine engine) { return Equals(engine); } 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 AssignComponentUpdateManager(ComponentUpdateManager componentUpdateManager) { this.componentUpdateManager = componentUpdateManager; } 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 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 = readPendingTypes.Contains(typeof(TComponent)); var existingRead = readTypes.Contains(typeof(TComponent)); if (existingRead && pendingRead) { return componentUpdateManager.ReadExistingAndPendingComponentsByType(); } else if (existingRead) { return componentUpdateManager.ReadExistingComponentsByType(); } else if (pendingRead) { return componentUpdateManager.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 = readPendingTypes.Contains(typeof(TComponent)); var existingRead = readTypes.Contains(typeof(TComponent)); if (existingRead && pendingRead) { return componentUpdateManager.ReadFirstExistingOrPendingComponentByType(); } else if (existingRead) { return componentUpdateManager.ReadFirstExistingComponentByType(); } else if (pendingRead) { return componentUpdateManager.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 = readPendingTypes.Contains(typeof(TComponent)); var existingRead = readTypes.Contains(typeof(TComponent)); if (existingRead && pendingRead) { return componentUpdateManager.SomeExistingOrPendingComponent(); } else if (existingRead) { return componentUpdateManager.SomeExistingComponent(); } else if (pendingRead) { return componentUpdateManager.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 = readPendingTypes.Contains(typeof(TComponent)); var existingRead = readTypes.Contains(typeof(TComponent)); if (existingRead && pendingRead) { if (componentUpdateManager.HasPendingComponent(entity)) { return componentUpdateManager.ReadPendingComponentByEntityAndType(entity); } else if (componentUpdateManager.HasExistingComponent(entity)) { return componentUpdateManager.ReadExistingComponentByEntityAndType(entity); } else { throw new NoComponentOfTypeOnEntityException("No Component of type {0} exists on Entity {1}", typeof(TComponent).Name, entity.ID); } } else if (existingRead) { return componentUpdateManager.ReadExistingComponentByEntityAndType(entity); } else if (pendingRead) { return componentUpdateManager.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 = readPendingTypes.Contains(typeof(TComponent)); var existingRead = readTypes.Contains(typeof(TComponent)); if (pendingRead && existingRead) { return componentUpdateManager.HasExistingOrPendingComponent(entity); } else if (existingRead) { return componentUpdateManager.HasExistingComponent(entity); } else if (pendingRead) { return componentUpdateManager.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 pendingRead = readPendingTypes.Contains(type); var existingRead = readTypes.Contains(type); if (pendingRead && existingRead) { return componentUpdateManager.HasExistingOrPendingComponent(entity, type); } else if (existingRead) { return componentUpdateManager.HasExistingComponent(entity, type); } else if (pendingRead) { return componentUpdateManager.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)] : defaultWritePriority; if (!writeTypes.Contains(typeof(TComponent))) { throw new IllegalWriteException("Engine {0} tried to update undeclared Component {1}", GetType().Name, typeof(TComponent).Name); } bool written; if (writePendingTypes.Contains(typeof(TComponent))) { written = AddPendingComponent(entity, component, priority); } else { written = componentUpdateManager.UpdateComponent(entity, component, priority); } if (written && 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.AddMessage(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.AddMessageIgnoringTimeDilation(message, time); } internal void AddExistingComponent(Entity entity, TComponent component) where TComponent : struct, IComponent { componentUpdateManager.AddExistingComponent(entity, component); } internal bool AddPendingComponent(Entity entity, TComponent component, int priority) where TComponent : struct, IComponent { return componentUpdateManager.AddPendingComponent(entity, component, priority); } /// /// 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 messageManager.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 messageManager.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); } } }