using System; using Microsoft.Xna.Framework; using MonoGame.Extended.Collections; using MonoGame.Extended.ECS.Systems; namespace MonoGame.Extended.ECS { /// /// Represents an Entity Component System (ECS) world that manages entities, components, and system.s /// /// /// The World is the central container for all ECS operations. It manages the lifecycle of entities, coordinates /// component storage, and executes registered systems during update and draw cycles. /// public class World : SimpleDrawableGameComponent { private readonly Bag _updateSystems; private readonly Bag _drawSystems; internal EntityManager EntityManager { get; } internal ComponentManager ComponentManager { get; } /// /// Occurs when an entity is added to the world during the update cycle. /// /// /// This event is raised after the entity has been fully initialized with it initial components. /// The entity ID is valid and can be used to retrieve the entity or query its components. /// Subscribers can use this even to perform initialization logic such as adding the entity to external /// collections, registering it with other systems, or creating associated resources. /// public event Action EntityAdded; /// /// Occurs when an entity is removed from the world during the update cycle. /// /// /// This event is raised before the entity is returned to the pool and before its components are destroyed, /// allowing subscribers to perform cleanup operations such as removing the entity from external collections, /// unregistering it from other systems, or releasing associated resources. /// After this event completes, the entity ID becomes invalid and should not be used. /// public event Action EntityRemoved; /// /// Occurs when an entity's component composition changes during the update cycle. /// /// /// This event is raised whenever components are attached to or detached from an entity. /// Subscribers can use this event to respond to composition changes, such as updating cached component /// references, recalculating entity classifications, or refreshing system queries. /// The event is not raised for component value modifications, only structural changes to which components are /// present on the entity. /// public event Action EntityChanged; /// /// Gets the number of active entities in the world. /// /// /// This count reflects entities that have been created and not yet destroyed. /// Entities queued for destruction are still counted until the next update cycle completes. /// public int EntityCount => EntityManager.ActiveCount; internal World() { _updateSystems = new Bag(); _drawSystems = new Bag(); RegisterSystem(ComponentManager = new ComponentManager()); RegisterSystem(EntityManager = new EntityManager(ComponentManager)); EntityManager.EntityAdded += OnEntityAdded; EntityManager.EntityRemoved += OnEntityRemoved; EntityManager.EntityChanged += OnEntityChanged; } private void OnEntityAdded(int entityId) { if (EntityAdded != null) { EntityAdded.Invoke(entityId); } } private void OnEntityRemoved(int entityId) { if (EntityRemoved != null) { EntityRemoved.Invoke(entityId); } } private void OnEntityChanged(int entityId) { if (EntityChanged != null) { EntityChanged.Invoke(entityId); } } internal void RegisterSystem(ISystem system) { // ReSharper disable once ConvertIfStatementToSwitchStatement if (system is IUpdateSystem updateSystem) { _updateSystems.Add(updateSystem); } if (system is IDrawSystem drawSystem) { _drawSystems.Add(drawSystem); } system.Initialize(this); } /// /// Retrieves an entity by its unique identifier. /// /// The unique identifier of the entity to retrieve. /// /// The entity with the specified identifier, or null if the entity has been destroyed or the ID is /// invalid. /// public Entity GetEntity(int entityId) { return EntityManager.Get(entityId); } /// /// Creates a new entity in the world. /// /// /// The entity is created immediately, but the event is not raised until /// the next cycle. The returned entity can be used immediately to /// attach components. /// Entity IDs are reused from a pool after entities are destroyed. /// /// A new entity with a unique identifier. public Entity CreateEntity() { return EntityManager.Create(); } /// /// Destroys an entity in the world. /// /// /// The entity is queued for destruction and the event is raised during /// the next cycle, before the entity and its components are removed from /// memory. /// Calling this method multiple times with the same entity ID has no additional effect. /// After destruction completes, the entity ID may be reused for new entities. /// /// The unique identifier of the entity to destroy. public void DestroyEntity(int entityId) { EntityManager.Destroy(entityId); } /// /// Destroys an entity in the world. /// /// /// The entity is queued for destruction and the event is raised during /// the next cycle, before the entity and its components are removed from /// memory. /// Calling this method multiple times with the same entity ID has no additional effect. /// After destruction completes, the entity ID may be reused for new entities. /// /// The entity to destroy. public void DestroyEntity(Entity entity) { EntityManager.Destroy(entity); } /// /// Updates all registered systems in the world. /// /// /// This method invokes on all registered update systems in /// registration order. /// Entity lifecycle events (, , /// ) are raised during this update cycle as entities are processed by the /// . /// /// A snapshot of the timing values for the current cycle. public override void Update(GameTime gameTime) { foreach (var system in _updateSystems) { system.Update(gameTime); } } /// /// Draws all registered systems in the world. /// /// /// This method invokes on all registered draw systems in /// registration order. /// /// A snapshot of the timing values for the current cycle. public override void Draw(GameTime gameTime) { foreach (var system in _drawSystems) { system.Draw(gameTime); } } /// /// Releases all resources used by the /// public override void Dispose() { EntityManager.EntityAdded -= OnEntityAdded; EntityManager.EntityRemoved -= OnEntityRemoved; EntityManager.EntityChanged -= OnEntityChanged; foreach (var updateSystem in _updateSystems) { updateSystem.Dispose(); } foreach (var drawSystem in _drawSystems) { drawSystem.Dispose(); } _updateSystems.Clear(); _drawSystems.Clear(); base.Dispose(); } } }