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();
}
}
}