Search code examples
c++visual-c++componentsgame-enginegeneric-programming

Flexible Data Messaging in a component oriented system


I'm creating a Component orientated system for a small game I'm developing. The basic structure is as follows: Every object in the game is composed of a "GameEntity"; a container holding a vector of pointers to items in the "Component" class.

Components and entities communicate with one another by calling the send method in a component's parent GameEntity class. The send method is a template which has two parameters, a Command (which is an enum which includes instructions such as STEP_TIME and the like), and a data parameter of generic type 'T'. The send function loops through the Component* vector and calls each's component's receive message, which due to the template use conveniently calls the overloaded receive method which corresponds to data type T.

Where the problem comes in however (or rather the inconvenience), is that the Component class is a pure virtual function and will always be extended. Because of the practical limitation of not allowing template functions to be virtualised, I would have to declare a virtual receive function in the header for each and every data type which could conceivably be used by a component. This is not very flexible nor extensible, and moreover at least to me seems to be a violation of the OO programming ethos of not duplicating code unnecessarily.

So my question is, how can I modify the code stubs provided below to make my component orientated object structure as flexible as possible without using a method which violates best coding practises

Here is the pertinent header stubs of each class and an example of in what ways an extended component class might be used, to provide some context for my problem:

Game Entity class:

class Component;

class GameEntity
{

public: 
GameEntity(string entityName, int entityID, int layer);

~GameEntity(void){};

//Adds a pointer to a component to the components vector.
void addComponent (Component* component);

void removeComponent(Component*);

    //A template to allow values of any type to be passed to components
template<typename T>
void send(Component::Command command,T value){
       //Iterates through the vector, calling the receive method for each component
    for(std::vector<Component*>::iterator it =components.begin();  it!=components.end();it++){
        (*it)->receive(command,value);
    }
}
private:
     vector <Component*> components;    

};

Component Class: #include "GameEntity.h" class Component

{
public:
static enum Command{STEP_TIME, TOGGLE_ANTI_ALIAS, REPLACE_SPRITE};

Component(GameEntity* parent)
    {this->compParent=parent;};

virtual ~Component (void){};    

GameEntity* parent(){
    return compParent;
}
void setParent(GameEntity* parent){
    this->compParent=parent;
}

virtual void receive(Command command,int value)=0;
virtual void receive(Command command,string value)=0;
virtual void receive(Command command,double value)=0;
virtual void receive(Command command,Sprite value)=0;
    //ETC. For each and every data type


private:
GameEntity* compParent;

};

A possible extension of the Component class:

#include "Sprite.h"
#include "Component.h"
class GraphicsComponent: Component{
    public:
          GraphicsComponent(Sprite sprite, string name, GameEntity* parent);
          virtual void receive(Command command, Sprite value){
                 switch(command){
                      case REPLACE_SPRITE: this->sprite=value; break
                 }
           }

    private:
          Spite sprite;


}

Should I use a null pointer and cast it as the appropriate type? This might be feasible as in most cases the type will be known from the command, but again is not very flexible.


Solution

  • This is a perfect case for type erasure!

    When template based generic programming and object oriented programming collide, you are left with a simple, but hard to solve problem: how do I store, in a safe way, a variable where I don't care about the type but instead care about how I can use it? Generic programming tends to lead to an explosion of type information, where as object oriented programming depends on very specific types. What is a programmer to do?

    In this case, the simplest solution is some sort of container which has a fixed size, can store any variable, and SAFELY retrieve it / query it's type. Luckily, boost has such a type: boost::any.

    Now you only need one virtual function:

    virtual void receive(Command command,boost::any val)=0;
    

    Each component "knows" what it was sent, and can thus pull out the value, like so:

    virtual void receive(Command command, boost::any val)
    {
    // I take an int!
        int foo = any_cast<int>(val);
    }
    

    This will either successfully convert the int, or throw an exception. Don't like exceptions? Do a test first:

    virtual void receive(Command command, boost::any val)
    {
    // Am I an int?
        if( val.type() == typeid(int) )
        {
            int foo = any_cast<int>(val);
        }
    }
    

    "But oh!" you might say, eager to dislike this solution, "I want to send more than one parameter!"

    virtual void receive(Command command, boost::any val)
    {
        if( val.type() == typeid(std::tuple<double, char, std::string>) )
        {
            auto foo = any_cast< std::tuple<double, char, std::string> >(val);
        }
    }
    

    "Well", you might ponder, "How do I allow arbitrary types to be passed, like if I want float one time and int another?" And to that, sir, you would be beaten, because that is a Bad Idea. Instead, bundle two entry points to the same internal object:

    // Inside Object A
    virtual void receive(Command command, boost::any val)
    {
        if( val.type() == typeid(std::tuple<double, char, std::string>) )
        {
            auto foo = any_cast< std::tuple<double, char, std::string> >(val);
            this->internalObject->CallWithDoubleCharString(foo);
        }
    }
    
    // Inside Object B
    virtual void receive(Command command, boost::any val)
    {
        if( val.type() == typeid(std::tuple<float, customtype, std::string>) )
        {
            auto foo = any_cast< std::tuple<float, customtype, std::string> >(val);
            this->internalObject->CallWithFloatAndStuff(foo);
        }
    }
    

    And there you have it. By removing the pesky "interesting" part of the type using boost::any, we can now pass arguments safely and securely.

    For more information on type erasure, and on the benefits to erasing the parts of the type on objects you don't need so they mesh better with generic programming, see this article

    Another idea, if you love string manipulations, is this:

    // Inside Object A
    virtual void receive(Command command, unsigned int argc, std::string argv)
    {
       // Use [boost::program_options][2] or similar to break argv into argc arguments
       //    Left as exercise for the reader
    }
    

    This has a curious elegance to it; programs parse their parameters in the same way, so you could conceptualize the data messaging as running "sub-programs", which then opens up a whole host of metaphors and such that might lead to interesting optimizations, such as threading off parts of the data messaging, etc etc.

    However, the cost is high: string operations can be quite expensive compare to a simple cast. Also note that boost::any does not come at zero cost; each any_cast requires RTTI lookups, compared to the zero lookups needed for just passing fixed amounts of parameters. Flexibility and indirection require costs; in this case, it is more than worth it however.

    If you wish to avoid any such costs at all, there IS one possibility that gets the necessary flexibility as well as no dependencies, and perhaps even a more palatable syntax. But while it is a standard feature, it can be quite unsafe:

    // Inside Object A
    virtual void receive(Command command, unsigned int argc, ...)
    {
       va_list args;
       va_start ( args, argc );
    
       your_type var = va_arg ( args, your_type );
       // etc
    
       va_end( args );
    }
    

    The variable argument feature, used in printf for example, allows you to pass arbitrary many arguments; obviously, you will need to tell the callee function how many arguments passed, so that's provided via argc. Keep in mind, however, that the callee function has no way to tell if the correct parameters were passed; it will happily take whatever you give it and interpret it as if it were correct. So, if you accidentally pass the wrong information, there will be no compile time support to help you figure out what goes wrong. Garbage in, Garbage out.

    Also, there area host of things to remember regarding va_list, such as all floats are upconverted to double, structs are passed by pointers (I think), but if your code is correct and precise, there will be no problems and you will have efficiency, lack of dependencies, and ease of use. I would recommend, for most uses, to wrap the va_list and such into a macro:

    #define GET_DATAMESSAGE_ONE(ret, type) \
        do { va_list args; va_start(args,argc); ret = va_args(args,type); } \
        while(0)
    

    And then a version for two args, then one for three. Sadly, a template or inline solution can't be used here, but most data packets will not have more than 1-5 parameters, and most of them will be primitives (almost certainly, though your use case may be different), so designing a few ugly macros to help your users out will deal largely with the unsafety aspect.

    I do not recommend this tactic, but it may very well be the fastest and easiest tactic on some platforms, such as ones that do not allow even compile time dependencies or embedded systems, where virtual calls may be unallowed.