Jump to content
  • Advertisement
Sign in to follow this  
Solid_Spy

passing data between objects using messages... wat do?

This topic is 1415 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello, I am looking for a better method for passing variables (or components maybe) between objects in c++.

 

I already have a working messaging system completed, and I am using a component based architecture.

 

Surprisingly, I haven't found very much information on how to actually pass information from one object to another in the form of variables.

 

for example: Lets say I'm making a game, and mario needs to know how much time is left in a level. Lets say there is a Mario object and a Time object. The time object has a float time;, and mario needs to know about it in his update function. How would I go about retrieving that time value (or a pointer to it for that matter)?

 

I was thinking of just grabbing a pointer to the component that has the variable (with an Interface Component pointer), and then just type cast it to the right type of component to get the variable...

 

but one major downside to this method is the lack of flexibility. I would have to #include the data type of the component for every type of component I want to access... which would be daunting. Sure, I could just put them all inside a MainHeader.h and include it in every Component.h, but that would stifle my compile times, and every time I add a new #include, I would have to recompile the whole program @~@!

 

Another alternative I was considering, Is to just subscribe a pointer to the variable I want to a map of maps, kinda like a list of subscribers. An example would be map<string, map<string, void*>>; Where the first string would be the instance name of the object, and the second string would be the variable name, and the void* would be a pointer to the variable I want. I could subscribe the variable name to the list of map objects when the object is created, so that I can access it later. I could send a message to the message system requesting the variable in question, and retrieve it easily.

 

However... the downside to this method is the overhead... I would have to subscribe hundreds of variables every frame if I was creating a whole lot of objects, and I would be doing a lot of string comparisons... etc. However I cannot think of a more flexible method.

 

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 

What i'm really asking is... is there a better method to allow objects to flexibly access other objects variables?

 

Any code examples would really help me out.

 

Any help would be appreciated :3.

Edited by Solid_Spy

Share this post


Link to post
Share on other sites
Advertisement

You're right in that reducing file inclusion is important. Some kind of messaging system would allow you to reduce file inclusions in your source code.

 

It's important to realize that a function call itself is a type of messaging, albeit one of the simplest. Interfaces and virtual functions are also ways to reduce file inclusions. Perhaps instead of sending a pointer to an entire object and typecasting it, you can just send a pointer to the data that you need. I myself like to use this specifically:

struct Variable
{
    template <typename T>
    T& GetValue( )
    {
        return *((T *)data);
    }

    void* data;
};

There's not a lot of type-safety here so you as a programmer would have to make sure that you're typecasting things to their appropriate types. This kind of Variable object can also be equipped with some of kind of type-safety itself, if you were to implement it.

 

You can now have some kind of interface for your components like so:

class IComponent
{
public:
    virtual SendMessage( MessageID id, Variable* variable ) = 0;
};

The idea is that any component can have a message sent to it. You can run a switch statement (or other mechanism) on the message ID and then do whatever you wish with the variable. In your case you can just assign the variable's data pointer to point some some member variable -this acts like a member variable Gettor function, but through a more opaque SendMessage function (which means low file inclusion).

 

Hope this helps!

Edited by Randy Gaul

Share this post


Link to post
Share on other sites

You're right in that reducing file inclusion is important. Some kind of messaging system would allow you to reduce file inclusions in your source code.

 

It's important to realize that a function call itself is a type of messaging, albeit one of the simplest. Interfaces and virtual functions are also ways to reduce file inclusions. Perhaps instead of sending a pointer to an entire object and typecasting it, you can just send a pointer to the data that you need. I myself like to use this specifically:

struct Variable
{
    template <typename T>
    T& GetValue( )
    {
        return *((T *)data);
    }

    void* data;
};

There's not a lot of type-safety here so you as a programmer would have to make sure that you're typecasting things to their appropriate types. This kind of Variable object can also be equipped with some of kind of type-safety itself, if you were to implement it.

 

You can now have some kind of interface for your components like so:

class IComponent
{
public:
    virtual SendMessage( MessageID id, Variable* variable ) = 0;
};

The idea is that any component can have a message sent to it. You can run a switch statement (or other mechanism) on the message ID and then do whatever you wish with the variable. In your case you can just assign the variable's data pointer to point some some member variable -this acts like a member variable Gettor function, but through a more opaque SendMessage function (which means low file inclusion).

 

Hope this helps!

Ah, I see.

 

This seems similar to my second idea, except I shouldn't use a map of void pointers, but instead use a switch statement to get the variable based on the message type.

 

So for example:

 

-Mario wants the time variable from the Time object.

 

-Mario sends a message to Time of type MESSAGE_GET_TIME, to request a pointer to it's time variable.

 

-The Time object checks it's switch statement for a case MESSAGE_GET_TIME, and if it does, it adds the pointer to the time variable to the message.

 

-Mario recieves the message, and can now access the time.

 

Is that correct?

Share this post


Link to post
Share on other sites

Why not simply store a reference to the Time object in the Mario object when you create it?

Mario is apparently dependent on Time so I see no reason why this should be done with messages.

Share this post


Link to post
Share on other sites

Why not simply store a reference to the Time object in the Mario object when you create it?

Mario is apparently dependent on Time so I see no reason why this should be done with messages.

Because that creates a lack of cohesion, and leads rise to a rats nest of dependencies when doing the same with other objects, which in turn, increases the odds of encountering a hard to detect, circular dependency. It also helps increase compile times by having to recompile all other dependent classes when one of them is changed. I can't list all the other reasons why it is bad design, but I can provide some links:

 

http://gameprogrammingpatterns.com/component.html

http://gamedev.stackexchange.com/questions/38746/game-messaging-system-design

http://gameprogrammingpatterns.com/event-queue.html

 

It was ok back then to use that simple methodology, but only due to processor limitations. Most game engines try not to use too many dependencies.

It's also ok to use for smaller games, but for larger games, you want to avoid tight dependencies to avoid circular dependencies errors in the long run.

Edited by Solid_Spy

Share this post


Link to post
Share on other sites

Is compilation time really such a bottleneck that passing stuff through a message queue for things as simple as finding out what time it is really preferred compared to just including a likely very rarely changing 'clock' header?  I'd argue that if this is your problem, you actually have other much bigger problems.

 

While Randys solution could get the job done, it also eliminates your ability to implement your timer in such a way as to have your "time" stored internally as something other than a "float", or whatever form you'd be using it in.  You also lose the ability to to sanitize things, such as by internally assuring atomicity guarantees or other such stuff when updating your timer.  You could try to get around this by having your "time" be a function pointer that takes a void* handle object and returns a float, which would internally try recoup the relevant type info.

 

In any of these cases, you're pushing compile time type system errors and boot-up errors into goofy run-time errors that'll be harder to diagnose, and trading a recurring run-time performance hit for a nonrecurring compile-time performance gain.  Not a trade I'd make.  Not a trade I'd make even for multi-million line projects.

Edited by Oolala

Share this post


Link to post
Share on other sites

Is compilation time really such a bottleneck that passing stuff through a message queue for things as simple as finding out what time it is really preferred compared to just including a likely very rarely changing 'clock' header?  I'd argue that if this is your problem, you actually have other much bigger problems.

 

While Randys solution could get the job done, it also eliminates your ability to implement your timer in such a way as to have your "time" stored internally as something other than a "float", or whatever form you'd be using it in.  You also lose the ability to to sanitize things, such as by internally assuring atomicity guarantees or other such stuff when updating your timer.  You could try to get around this by having your "time" be a function pointer that takes a void* handle object and returns a float, which would internally try recoup the relevant type info.

 

In any of these cases, you're pushing compile time type system errors and boot-up errors into goofy run-time errors that'll be harder to diagnose, and trading a recurring run-time performance hit for a nonrecurring compile-time performance gain.  Not a trade I'd make.  Not a trade I'd make even for multi-million line projects.

You mis-understood what I said. I'm sorry, I should've been more specific. I'm talking about in the long run, if I have thousands of different objects updating variables and gathering variables. Not just one measly time variable. I would just use direct access if that was the case. And what if I wanted someone to edit a script while the game was running to access a different variable in a different type of object? Then I would have to #include everything, if not for a message system.

 

Also I'm not concerned about run-time performance in this case. Passing even a thousand different messages would have very little impact on my performance. Many game engines pass messages around to objects all the time, such as Unity, or UDK. Nobody complains about it. As for atomicity, I'm not planning on using any threads and know very little about that stuff, so that shouldn't be a problem for me.

 

I'm somewhat just doing things by the book. Game Engine Architecture, and Game Coding Complete both reccomend using a component based system, with messages, and they both give very good reasons for using one.

 

I don't want to spend weeks just trying to find out some circular dependence between one and a thousand different objects in my code, sorry but I just don't see the point.

 

EDIT: Also, as far as getting a variable from another object. This can actually be done with high performance (not high-er i'll admit, but pretty high tongue.png), You don't have to dynamiclaly allocate a message to get a variable from another object. You can just pass one by reference and use the message immediately. Store the variable in a pointer, and then store it in a (insert - type - here) weak pointer in your object for later access. Thus, very little performance loss.

Edited by Solid_Spy

Share this post


Link to post
Share on other sites

Messages are fine.  Components are fine.  What you're actually talking about is "binding", or specifically the difference between late and early/static binding.

 

Early binding, which occurs when you normally use a variable in C++, is a completely waste of a messaging system

 

Late binding is typically better introduced with a pair of different features: named variables which you likely already have with your message system, and introspection.  These together effectively allows the ability to find and interact with a variable at runtime.  Lots of more modern languages feature introspection as a first-class feature, but C++ doesn't fall in that category.  Adding introspection however is a pretty easy thing to add via macros, and doesn't result in a clunky battery of "handle message" functions with everyone passing around either function handles or pointers to internal state.

 

Not going to bother telling you to switch to a language that features introspection if what you want it introspection, there are a lot of C++ libraries out there are facilitate the introduction of introspection/reflection.

 

Named lookup + introspection = everything you're asking for.

Share this post


Link to post
Share on other sites

Messages are fine.  Components are fine.  What you're actually talking about is "binding", or specifically the difference between late and early/static binding.

 

Early binding, which occurs when you normally use a variable in C++, is a completely waste of a messaging system

 

Late binding is typically better introduced with a pair of different features: named variables which you likely already have with your message system, and introspection.  These together effectively allows the ability to find and interact with a variable at runtime.  Lots of more modern languages feature introspection as a first-class feature, but C++ doesn't fall in that category.  Adding introspection however is a pretty easy thing to add via macros, and doesn't result in a clunky battery of "handle message" functions with everyone passing around either function handles or pointers to internal state.

 

Not going to bother telling you to switch to a language that features introspection if what you want it introspection, there are a lot of C++ libraries out there are facilitate the introduction of introspection/reflection.

 

Named lookup + introspection = everything you're asking for.

Ah, ok I see now. I was just researching introspection and realized that that is what Unity uses. I was considering using Introspection, but just didn't know how to use it in c++. I wouldn't mind using it if I can find a way to make it work in c++, maybe boost has a library?

 

Yeah Introspection would definitly solve my problem. I will look into that more. Thanks for the help!

Share this post


Link to post
Share on other sites

It sounds like you are trying to implement a completely dynamic system without any explicit dependencies. While this is possible it completely circumvents c++'s type system with respect to classes.

Also it is arguably far worse to have dependencies implicitly encoded within your code rather than explicitly enforced at compile time.

 

Reality is, there will always be dependencies. No object is an island.

There exist different design patterns to deal with these dependencies. The mediator pattern is the first than comes to mind.

 

As for circular dependencies, if you write your headers the right way (inclusion guards, forward declarations) and design your game right you won't have problems here.

 

Sounds to me like a case of premature optimization.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!