1
\$\begingroup\$

I asked this question in stackoverflow, but I didn't get any responses. I think it's better suited in this exchange.

I'm building a game engine of sorts (purely educational) and am not sure the best way to keep a large system decoupled when using objects the are shared by systems.

My engine is making use of an entity-component design where components contain only state. Systems are the driving force of making these components run so I need to be able to register components with their respective system. I am just unsure on how to pass the components through the hierarchy which looks like this:

Current Heirarchy

I can think of a few ways of dealing with this.

Option 1: Singleton. If I make my Engine object a singleton I can use static methods directly in my input component to bind to the input system.

class InputComponent: public Component
{
    InputComponent()
    {
        Engine::getEngine()->getInputSystem()->bindInputComponent( this );
    }

    virtual ~InputComponent()
    {
        Engine::getEngine()->getInputSystem()->unbindInputComponent( this );
    }
}

This has the benefit of components automatically binding and unbinding themselves to their system which is convenient. It has the downfall of being a singleton (not necessarily bad, but it can possibly make my engine inflexible in the future) and my InputComponent "knows" about the InputSystem which might be bad design.

Option 2: Work down from scene system. I can make my scene system responsible for binding and unbinding components.

class SceneSystem: public System
{
    void setActiveScene( Scene* scene )
    {
        // Deactivate (and unset components) for the current scene.
        unsetActiveScene();

        for( auto i : scene->getEntities() )
        {
            for( auto j : i->getAllComponents<InputComponent>() )
            {
                // Send message through mediator to bind the component
                DataMessage<InputComponent*> bindMessage( MESSAGE_BIND_INPUT_COMPONENT, j );
                Message response;
                sendMessage( &bindMessage, &response, SYSTEM_INPUT );
            }
        }
    }
}

In this way the input component isn't worried about looking up the hierarchy. But now my scene system has to "know" about input components (and all other components that need to be registered). This is coupling my system.

Also if I need to add/delete components I'll need to do it through the scene system.

Option 3: Working up from input component. The last thing I can imagine is to instead work my way up the hierarchy from the input component. I think in this scenario I would also add a general bindComponent to the scene system so not too much complexity about the mediator messaging creeps into my components.

class SceneSystem: public System
{
    template <typename T>
    void bindComponent( T* component, int system )
    {
        DataMessage<T*> bindMessage( MESSAGE_BIND_COMPONENT, component );
        Message response;
        sendMessage( &bindMessage, &response, system);
    }
}

class InputComponent: public Component
{
public:
    InputComponent( Entity* parentEntity ) : m_parentEntity( parentEntity )
    {
        m_parentEntity->getParentScene()->getSceneSystem()->bindComponent<InputComponent>( this, SYSTEM_INPUT );
    }

    virtual ~InputComponent()
    {
        m_parentEntity->getParentScene()->getSceneSystem()->unbindComponent<InputComponent>( this, SYSTEM_INPUT );
    }

private:
    Entity* m_parentEntity;
}

This has the effect of my hierarchy being bidirectional (not sure if this is a bad thing)

Of these options, which is better for keeping such a system flexible and reasonably decoupled. Also point out other options especially if there are flaws with my current design and/or understanding of the problem.

\$\endgroup\$
1
\$\begingroup\$

This has the benefit of components automatically binding and unbinding themselves to their system which is convenient. It has the downfall of being a singleton (not necessarily bad, but it can possibly make my engine inflexible in the future) and my InputComponent "knows" about the InputSystem which might be bad design.

Overall, there isn't a real problem with the actual base engine being a singleton as there is often objects and memory allocation you want to do once, regardless how many things internally you want running concurrently.

The flexibility concerns you speak can be easily resolved by a good design between the engine instance itself and its internal components. One approach to this is to use some context like object that is meant to represent the engine's global state.

InputComponent(Context* context) 
{
    context->GetSystem<InputSystem>()->BindComponent( this );
}

In this way the input component isn't worried about looking up the hierarchy. But now my scene system has to "know" about input components (and all other components that need to be registered). This is coupling my system.

There will inevitability be some coupling, but I agree I am not fond of this approach. I think I'd instead rather consider some callback mechanism if you want to work from the scene perspective.

  1. Create an entity attached to a given scene.
  2. Create component data classes and attach them to a given entity instance.
  3. Activate entity, triggering a series of callbacks and system registrations.

The basis for the concept here is that an entity is basically non-existent from a systems perspective until it has been activated. Once an entity is activated, you would generally not be able to change its component list unless you deactivate it, modify, and reactivate it.

void Entity::Activate() 
{
  for( System* system : GetScene()->GetSystems() ) 
    system->OnEntityActivated( this );
}

The idea is that the systems query the entity and determine based on the components that exist if the entity should be registered with that system. If not, the system does nothing. If so, the system adds that entity to some internal data structure for update-loop iteration.

Overall your design seems fine to me.

If it were me however, I tend to use my latter suggestion because it avoids tight coupling and allows for a flexible design with a minimal contract between the scene and systems.

\$\endgroup\$
  • \$\begingroup\$ Thanks for your input. I especially like your latter idea and I can easily see how to broadcast to every system in my current design. I was trying to do something similar, but where the SceneSystem is responsible for filtering entities and sending them to the correct system (other systems would alert the scene system about what they wanted). That way systems only get alerted for the components they want. The problem was that the only way I could think to accomplish it was with using RTTI. I think I'm going to go with your latter suggestion \$\endgroup\$ – Jacob Hull Aug 11 '17 at 16:10
  • \$\begingroup\$ You could apply the filter check in the loop by invoking another system method but I prefer that check be done in the callback. It allows the system to encapsulate whether it should filter or not rather than it being a generalized contract between Entity::Activate and System. \$\endgroup\$ – Naros Aug 14 '17 at 13:22
-2
\$\begingroup\$

I watched a tutorial a while back on a Game engine design around SFML on youtube that has since became my design cornerstone: https://www.youtube.com/watch?v=tgXEws1Ef8s&list=PLRtjMdoYXLf4md1jeCkdPJYpDgPkjT9Di

In it, they use a state machine to express their hierarchy. There is one struct object that gets passed around between states as they are each in turn loaded. States being treated like their own game individually... the splash screen might have a state, the main menu being one, gameplay being one, pause menu being one, etc. Each state either being paused or destroyed when another one is added. The way this works between states is that each state is responsible for calling another and in turn can pass along data as well.

\$\endgroup\$
  • 1
    \$\begingroup\$ I don't think this really has much to do with my question, but I haven't had a chance to look at the whole video yet. I'm familiar with how games progress through a finite state machine and with similar ideas such as "rooms" or "scenes". I'm currently working at a lower level where each state is composed of entities and each entity is composed of components. When an entity wants to create a component it needs to tell a low-level system about the component so that system knows to update that component and thus the entity. \$\endgroup\$ – Jacob Hull Sep 15 '17 at 20:18
  • \$\begingroup\$ -1 (I see you're new enough to only have 11 rep, I'll remove it if you can adjust your answer) I shouldn't have to follow your link and watch a video to determine how your answer is related to @jacobHull 's question. You should explicitly say how it's related pay more attention to the tags that the original question asker has put. \$\endgroup\$ – Joshua Hedges Oct 21 '17 at 3:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.