using System; using System.Reflection; using System.Collections.Generic; using System.Linq; using Encompass.Exceptions; using MoonTools.FastCollections; 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 ReadImmediateTypes = new HashSet(); internal readonly HashSet SendTypes = new HashSet(); internal readonly HashSet ReceiveTypes = new HashSet(); internal readonly HashSet WriteTypes = new HashSet(); internal readonly HashSet WriteImmediateTypes = new HashSet(); internal readonly HashSet QueryWithTypes = new HashSet(); internal readonly HashSet QueryWithoutTypes = 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 TimeManager _timeManager; private TrackingManager _trackingManager; private EntitySetQuery _entityQuery; private readonly HashSet _trackedEntities = new HashSet(); protected IEnumerable TrackedEntities { get { foreach (var entityID in _trackedEntities) { yield return _entityManager.GetEntity(entityID); } } } private readonly HashSet _newlyCreatedEntities = new HashSet(); protected Engine() { _id = Guid.NewGuid(); var sendsAttribute = GetType().GetCustomAttribute(false); if (sendsAttribute != null) { SendTypes = sendsAttribute.SendTypes; } var activatesAttribute = GetType().GetCustomAttribute(false); if (activatesAttribute != null) { WriteImmediateTypes = activatesAttribute.WriteImmediateTypes; } 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 readsImmediateAttribute = GetType().GetCustomAttribute(false); if (readsImmediateAttribute != null) { ReadImmediateTypes = readsImmediateAttribute.ReadImmediateTypes; } var queryWithAttribute = GetType().GetCustomAttribute(false); if (queryWithAttribute != null) { QueryWithTypes = queryWithAttribute.QueryWithTypes; } foreach (var queryType in QueryWithTypes) { ReadTypes.Add(queryType); } var queryWithoutAttribute = GetType().GetCustomAttribute(false); if (queryWithoutAttribute != null) { QueryWithoutTypes = queryWithoutAttribute.QueryWithoutTypes; } foreach (var queryType in QueryWithoutTypes) { ReadTypes.Add(queryType); } } 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 HashCode.Combine(_id); } internal void AssignEntityManager(EntityManager entityManager) { _entityManager = entityManager; } internal void AssignComponentManager(ComponentManager componentManager) { _componentManager = componentManager; } internal void AssignMessageManager(MessageManager messageManager) { _messageManager = messageManager; } internal void AssignTimeManager(TimeManager timeManager) { _timeManager = timeManager; } internal void AssignTrackingManager(TrackingManager trackingManager) { _trackingManager = trackingManager; } internal void CheckMessageRead() 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); } } private bool EntityCreatedThisFrame(int entityID) { return _newlyCreatedEntities.Contains(entityID); } internal void ClearNewlyCreatedEntities() { _newlyCreatedEntities.Clear(); } /// /// 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() { var entity = _entityManager.CreateEntity(); _newlyCreatedEntities.Add(entity.ID); return entity; } /// /// Returns true if an Entity with the specified ID exists. /// protected bool EntityExists(in Entity entity) { return _entityManager.EntityExists(entity.ID); } /// /// Returns true if an Entity with the specified ID exists. /// protected bool EntityExists(int entityID) { return _entityManager.EntityExists(entityID); } /// /// Returns all Entities containing the specified Component type. /// protected ReadOnlySpan ReadEntities() where TComponent : struct, IComponent { var immediateRead = ReadImmediateTypes.Contains(typeof(TComponent)); var existingRead = ReadTypes.Contains(typeof(TComponent)); if (existingRead && immediateRead) { return _componentManager.GetExistingAndImmediateEntities(); } else if (existingRead) { return _componentManager.GetExistingEntities(); } else if (immediateRead) { return _componentManager.GetImmediateEntities(); } else { throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", GetType().Name, typeof(TComponent).Name); } } /// /// Returns an Entity containing the specified Component type. /// protected ref readonly Entity ReadEntity() where TComponent : struct, IComponent { var immediateRead = ReadImmediateTypes.Contains(typeof(TComponent)); var existingRead = ReadTypes.Contains(typeof(TComponent)); if (existingRead && immediateRead) { return ref _componentManager.ExistingOrImmediateSingularEntity(); } else if (existingRead) { return ref _componentManager.ExistingSingularEntity(); } else if (immediateRead) { return ref _componentManager.ImmediateSingularEntity(); } 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 ReadOnlySpan ReadComponents() where TComponent : struct, IComponent { var immediateRead = ReadImmediateTypes.Contains(typeof(TComponent)); var existingRead = ReadTypes.Contains(typeof(TComponent)); if (existingRead && immediateRead) { return _componentManager.ReadExistingAndImmediateComponentsByType(); } else if (existingRead) { return _componentManager.GetExistingComponents(); } else if (immediateRead) { return _componentManager.ReadImmediateComponentsByType(); } 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 ref readonly TComponent ReadComponent() where TComponent : struct, IComponent { var immediateRead = ReadImmediateTypes.Contains(typeof(TComponent)); var existingRead = ReadTypes.Contains(typeof(TComponent)); if (existingRead && immediateRead) { return ref _componentManager.ExistingOrImmediateSingular(); } else if (existingRead) { return ref _componentManager.ExistingSingular(); } else if (immediateRead) { return ref _componentManager.ImmediateSingular(); } 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 immediateRead = ReadImmediateTypes.Contains(typeof(TComponent)); var existingRead = ReadTypes.Contains(typeof(TComponent)); if (existingRead && immediateRead) { return _componentManager.SomeExistingOrImmediateComponent(); } else if (existingRead) { return _componentManager.SomeExistingComponent(); } else if (immediateRead) { return _componentManager.SomeImmediateComponent(); } else { throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", GetType().Name, typeof(TComponent).Name); } } private ref TComponent GetComponentHelper(int entityID) where TComponent : struct, IComponent { var immediateRead = ReadImmediateTypes.Contains(typeof(TComponent)); var existingRead = ReadTypes.Contains(typeof(TComponent)); if (existingRead && immediateRead) { return ref _componentManager.ReadImmediateOrExistingComponentByEntityAndType(entityID); } else if (existingRead) { return ref _componentManager.ReadExistingComponentByEntityAndType(entityID); } else if (immediateRead) { return ref _componentManager.ReadImmediateComponentByEntityAndType(entityID); } 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 ref readonly TComponent GetComponent(in Entity entity) where TComponent : struct, IComponent { return ref GetComponentHelper(entity.ID); } /// /// 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(in Entity entity) where TComponent : struct, IComponent { var immediateRead = ReadImmediateTypes.Contains(typeof(TComponent)); var existingRead = ReadTypes.Contains(typeof(TComponent)); if (immediateRead && existingRead) { return _componentManager.HasExistingOrImmediateComponent(entity.ID); } else if (existingRead) { return _componentManager.HasExistingComponent(entity.ID); } else if (immediateRead) { return _componentManager.HasImmediateComponent(entity.ID); } 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(in Entity entity, Type type) { var immediateRead = ReadImmediateTypes.Contains(type); var existingRead = ReadTypes.Contains(type); if (immediateRead && existingRead) { return _componentManager.HasExistingOrImmediateComponent(entity.ID, type); } else if (existingRead) { return _componentManager.HasExistingComponent(entity.ID, type); } else if (immediateRead) { return _componentManager.HasImmediateComponent(entity.ID, 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(in Entity entity, in 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 (WriteImmediateTypes.Contains(typeof(TComponent))) { written = _componentManager.AddImmediateComponent(entity.ID, component, priority); if (written) { _trackingManager.ImmediateUpdateTracking(entity.ID, typeof(TComponent)); } } else { written = _componentManager.UpdateComponent(entity.ID, component, priority); } if (!_componentManager.HasExistingComponent(entity.ID)) { _trackingManager.RegisterAddition(entity.ID, typeof(TComponent)); } if (written && component is IDrawableComponent drawableComponent) { _componentManager.RegisterDrawableComponent(entity.ID, drawableComponent.Layer); } } /// /// An alternative to SetComponent that can be used for new Entities and does not require setting write priority. /// /// /// Thrown when the Engine does not declare that it Writes the given Component Type. /// protected void AddComponent(in Entity entity, in TComponent component) where TComponent : struct, IComponent { if (!EntityCreatedThisFrame(entity.ID)) { throw new IllegalWriteException("AddComponent used on Entity that was not created in this context. Use SetComponent instead."); } if (WriteImmediateTypes.Contains(typeof(TComponent))) { _componentManager.AddImmediateComponent(entity.ID, component); _trackingManager.ImmediateUpdateTracking(entity.ID, typeof(TComponent)); } else { _componentManager.AddComponent(entity.ID, component); } _trackingManager.RegisterAddition(entity.ID, typeof(TComponent)); if (component is IDrawableComponent drawableComponent) { _componentManager.RegisterDrawableComponent(entity.ID, drawableComponent.Layer); } } /// /// Sends a Message. /// /// /// Thrown when the Engine does not declare that it Sends the Message Type. /// protected void SendMessage(in 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(in 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(in TMessage message, double time) where TMessage : struct, IMessage { _messageManager.AddMessageIgnoringTimeDilation(message, time); } /// /// Reads all messages of the specified Type. /// /// /// Thrown when the Engine does not declare that it Receives the specified Message Type. /// protected ReadOnlySpan ReadMessages() where TMessage : struct, IMessage { CheckMessageRead(); 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 ref readonly TMessage ReadMessage() where TMessage : struct, IMessage { CheckMessageRead(); return ref _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 { CheckMessageRead(); 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(in 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 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 void RemoveComponent(in Entity entity) 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 remove undeclared Component {1}. Declare with Writes attribute.", GetType().Name, typeof(TComponent).Name); } if (WriteImmediateTypes.Contains(typeof(TComponent))) { if (_componentManager.RemoveImmediate(entity.ID, priority)) { _trackingManager.ImmediateUpdateTracking(entity.ID, typeof(TComponent)); } } else { _componentManager.Remove(entity.ID, priority); } if (_componentManager.HasExistingComponent(entity.ID)) { _trackingManager.RegisterRemoval(entity.ID, typeof(TComponent)); } } /// /// 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. protected 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. protected 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. protected 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. protected void ActivateTimeDilation(double factor, double easeInTime, System.Func easeInFunction, double activeTime, double easeOutTime, System.Func easeOutFunction) { _timeManager.ActivateTimeDilation(factor, easeInTime, easeInFunction, activeTime, easeOutTime, easeOutFunction); } /// /// Efficiently reads Messages of a given type that all reference the given Entity. /// /// The Message subtype. /// The entity that all messages in the IEnumerable refer to. /// protected IEnumerable ReadMessagesWithEntity(in Entity entity) where TMessage : struct, IMessage, IHasEntity { CheckMessageRead(); return _messageManager.WithEntity(entity.ID); } /// /// Efficiently reads a single Message of a given type that references a given Entity. /// It is recommended to use this method in conjunction with SomeMessageWithEntity to prevent errors. /// protected ref readonly TMessage ReadMessageWithEntity(in Entity entity) where TMessage : struct, IMessage, IHasEntity { CheckMessageRead(); return ref _messageManager.WithEntitySingular(entity.ID); } /// /// Efficiently checks if any Message of a given type referencing a given Entity exists. /// protected bool SomeMessageWithEntity(in Entity entity) where TMessage : struct, IMessage, IHasEntity { CheckMessageRead(); return _messageManager.SomeWithEntity(entity.ID); } internal void CheckAndUpdateTracking(int entityID) { if (_trackedEntities.Contains(entityID) && !_entityQuery.CheckEntity(entityID, _componentManager.ExistingBits)) { _trackedEntities.Remove(entityID); } else if (!_trackedEntities.Contains(entityID) && _entityQuery.CheckEntity(entityID, _componentManager.ExistingBits)) { _trackedEntities.Add(entityID); } } internal void ImmediateCheckAndUpdateTracking(int entityID) { if (_trackedEntities.Contains(entityID) && !_entityQuery.ImmediateCheckEntity(entityID, _componentManager.ImmediateBits, _componentManager.ExistingBits)) { _trackedEntities.Remove(entityID); } else if (!_trackedEntities.Contains(entityID) && _entityQuery.ImmediateCheckEntity(entityID, _componentManager.ImmediateBits, _componentManager.ExistingBits)) { _trackedEntities.Add(entityID); } } /// /// Returns an empty EntitySetQuery. Can be modified and iterated over to obtain Entities that fit the given criteria. /// internal void BuildEntityQuery() { var withMask = BitSet512.Zero; foreach (var type in QueryWithTypes) { if (!_componentManager.TypeToIndex.ContainsKey(type)) { _componentManager.TypeToIndex.Add(type, _componentManager.TypeToIndex.Count); } withMask = withMask.Set(_componentManager.TypeToIndex[type]); } var withoutMask = BitSet512.Zero; foreach (var type in QueryWithoutTypes) { if (!_componentManager.TypeToIndex.ContainsKey(type)) { _componentManager.TypeToIndex.Add(type, _componentManager.TypeToIndex.Count); } withoutMask = withoutMask.Set(_componentManager.TypeToIndex[type]); } var immediateMask = BitSet512.Zero; foreach (var type in ReadImmediateTypes) { if (!_componentManager.TypeToIndex.ContainsKey(type)) { _componentManager.TypeToIndex.Add(type, _componentManager.TypeToIndex.Count); } immediateMask = immediateMask.Set(_componentManager.TypeToIndex[type]); } var existingMask = BitSet512.Zero; foreach (var type in ReadTypes) { if (!_componentManager.TypeToIndex.ContainsKey(type)) { _componentManager.TypeToIndex.Add(type, _componentManager.TypeToIndex.Count); } existingMask = existingMask.Set(_componentManager.TypeToIndex[type]); } _entityQuery = new EntitySetQuery( withMask & immediateMask, withMask & existingMask, withoutMask & immediateMask, withoutMask & existingMask, ~withMask ); } internal void RegisterDestroyedEntity(int entityID) { _trackedEntities.Remove(entityID); } } }