3
\$\begingroup\$

I am done with my game's framework and I am trying to implement a life-management system. My framework so far has:

  • Systems that hold pointers to the Entities they are interested in.
  • An Engine class that owns the different systems.
  • An EntityManager that owns the Entities.

Now every time you add/remove a Component to/from an Entity, the Entity notifies the engine class to run a(n) (un)registration routine that passes the Entity to the Systems which in turn decide whether they want to (un)register the Entity or not.

Say that I have a DestroyerSystem that remembers all Entities with a HealthComponent and is responsible for deleting the ones with no life:

void DestroyerSystem::update(float dt = 0) 
{                                                             
        for (auto& entity : registered_entities)
        {
                if (entity.getComponent<HealthComponent>().hp == 0)
                {
                        EntityManager.destroy(entity);
                }
        }

}

The problem is that the Systems are supposed to keep valid pointers to the Entities and I am not sure how I can achieve that cleanly.

The only solution that I have so far is to make the Systems have an unregister() function that takes the pointer to the deleted element and erases it from the registered_entities container. The Engine class would also have an unregister() function that would call the unregister() functions of the Systems. This works good for the other systems but creates problems when applied to the DestroyerSystem because it breaks iteration. To tackle this I copy the container and iterate on that one instead:

void DestroyerSystem::update(float dt = 0) 
{       
        auto entities = registered_entities;            
        for (auto& entity : entities)
        {
                if (entity.getComponent<HealthComponent>().hp == 0)
                {
                        entityManager.destroy(entity);
                        engine.unregister(entity);
                }
        }

}

Does anyone have any better proposals for this?

\$\endgroup\$

1 Answer 1

6
\$\begingroup\$

I had the same problem in my implementation of the Entity Component System but I solved it with the use of an Event Manager.

Briefly, you can make an EventManager class that allow Systems to register some of their methods to a particular event. Then you can send an event and all the registered methods will be called.

Here are the prototype of the two methods :

void addHandler(const std::string &type, ISystem *obj, void (ISystem::*e)(IEvent *));
void sendEvent(IEvent *event); // IEvent::getType() is called to match with the type of EventManager::addHandler

With this, instead of instantly deleting the entity in your DestroyerSystem and thus breaking the iteration, you can send an event through to say "This entity should be deleted".

Another system, let's call it EntityDeleterSystem, can register for this event and store all the entities that must be deleted in a list. This System should be processed last and during the process, it will iterate through his list and delete all the entities that must be deleted.


Here is an example for better understanding :

LifeSystem.cpp

void LifeSystem::update()
{
    auto entities = _registered_entities;
    for (auto& entity : _registered_entities)
        if (e->getComponent<LifeComponent>()->getLife() <= 0)
            this->_world->sendEvent(new EntityDeletedEvent(e));
}

EntityDeleterSystem.hh

class EntityDeleterSystem : public ISystem
{
    private:
        std::vector<Entity *> _toDelete; // Used to store entities that WILL BE deleted.
};

Of course I've hidden all the unrelated code like constructor and other methods as you only need the implementations below.

EntityDeleterSystem.cpp

void EntityDeleterSystem::init()
{
    world.addEventHandler("EntityDeletedEvent", this, &EntityDeleterSystem::addEntityToDelete);
}

void EntityDeleterSystem::addEntityToDelete(IEvent *event)
{
    EntityDeletedEvent *entityDeletedEvent = dynamic_cast<EntityDeletedEvent *>(event);

    if (!entityDeletedEvent)
        return ;

    if (!(Entity *entity = entityDeletedEvent->getEntity()))
        return ;
    std::vector<Entity *>::iterator it = std::find(this->_toDelete.begin(), this->_toDelete.end(), entity);
    if (it == this->_toDelete.end())
        this->_toDelete.push_back(entity);
}

void EntityDeleterSystem::update()
{
    for_each(
        this->_toDelete.begin(),
        this->_toDelete.end(),
        [this] (Entity *entity) {
            this->_world->removeEntity(entity);
    }
    );
    this->_toDelete.clear();
}

The downside of this solution is that entities are not instantly deleted and thus could still be used in the next systems after your DestroyerSystem, so you have to make sure the systems are well ordered.

\$\endgroup\$
1
  • \$\begingroup\$ Although this is similar to my current implementation, it seperates the actual destruction of the entities from checking for deletion. This may become more valuable after I start putting more destruction conditions but it's definitelly a better way to go about things and it allows for better communication between the systems while keeping them decoupled. \$\endgroup\$
    – Veritas
    May 20, 2014 at 16:27

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .