Sign in to follow this  

Assign received network message to correct class instance?

This topic is 2568 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

I have a class that sends and receives data over at loopback network connection. This class can be called from various instances of other classes.

If I'm sending a message from one class instance, how can I make sure that any network response (received messages) to that message is sent back to the correct class instance each time?

Share this post


Link to post
Share on other sites
You'll have to tag the response with some sort of identifier. You could have a "response to" field in your message, which contains the ID of the message being responded to, or you can have your objects implement some sort of a message listener that registers itself with a message dispatcher for a specific event type. When a message is received, its header is parsed to get the message type ID. The message dispatcher then sends that message along to the objects that have registered to receive that type of event.

Share this post


Link to post
Share on other sites
Tagging will of course add overhead on the server side.
As smr said you can just have one class responsible for sending/receiving data and let other classes registers themselves as observers if they are interested of listening to network events. You probably have to make some architecture redesign in your client, but you skip the server overhead.

Share this post


Link to post
Share on other sites
True, it adds overhead, but some overhead cannot be avoided. Unless your message dispatcher can somehow divine the intended recipient of a message, then you'll need to include something in the message that can identify the target.

Share this post


Link to post
Share on other sites
Quote:
smr
you can have your objects implement some sort of a message listener that registers itself with a message dispatcher for a specific event type. When a message is received, its header is parsed to get the message type ID. The message dispatcher then sends that message along to the objects that have registered to receive that type of event.


This idea sounds good but I wouldn't know how to implement it. Could you post a simple example please?

I was also thinking about using a an Interface class INetworkIdentifiable which all classes that send data over the network use.

That way the message could be appended with the class instance which could then be called using a INetworkIdentifiable.Response() method?

Does that seem viable?

Share this post


Link to post
Share on other sites
I'm not sure what language you're most comfortable with, so I'll use Java. It probably won't compile because this isn't actual code I ripped from any existing project.



class Message {
public int messageTypeId;
public byte[] payload;
}

class Dispatcher {
private HashMap<Integer, HashSet<MessageListener>> listeners;

public Dispatcher() {
listeners = new HashMap<Integer, HashSet<MessageListener>>();
}

public void registerListener(Integer messageId, MessageListener listener) {
HashSet<MessageListener> messageListeners = listeners.get(messageId);
if (messageListeners == null) {
messageListeners = new HashSet<MessageListener>();
listeners.put(messageId, listener);
}
messageListeners.add(listener);
}

public void dispatchMessage(Message message) {
HashSet<MessageListener> messageListeners = listeners.get(message.messageTypeId);
if (messageListeners != null) {
Message specificMessage = Deserializer.unpack(message);
for (MessageListener listener : listeners) {
listener.acceptMessage(specificMessage);
}
}
}
}

interface MessageListener {
void acceptMessage(Message message);
}

class SomeMessageListener implements MessageListener {
public void acceptMessage(Message message) {
switch (message.messageTypeId) {
case MESSAGE_UPDATE_POSITION:
handleUpdatePositionMessage((UpdatePositionMessage) message);
break;
// continue to handle different messages.
}
}
}

... Registering the listener

Dispatcher dispatcher = new Dispatcher();
...
SomeMessageListener listener = new SomeMessageListener();
dispatcher.registerListener(listener);

... Dispatching a message
Message m = Network.getNextMessage();
dispatcher.dispatchMessage(m);



The switch statement in acceptMessage is generally considered bad form because it breaks encapsulation. The acceptMessage method has to inspect the message before it knows its actual type, then has to cast appropriately. It could be handled more elegantly using the double dispatch technique, but it usually ends up being more trouble than it's worth, in my opinion. I've done it both ways and probably will continue to use the switch method from now on.

Share this post


Link to post
Share on other sites
Here is how I do it in C++, but without the ID's. The Id's you see in the code are game object Id's.

IModelListener.h:

#ifndef ABYDOS_IMODEL_LISTENER_H
#define ABYDOS_IMODEL_LISTENER_H

#include <string>

namespace Abydos {

class IModelListener
{
public:
virtual void modelObjectCreated(std::string objId) {};
virtual void modelObjectDestroyed(std::string objId) {};
virtual void modelPropertyChanged(std::string objId,
std::string property) {};

};

}

#endif


Model.cpp (Most of the code has been removed because of readability):

void Model::registerListener(IModelListener* listener) {
listeners.push_back(listener);
}

// When an object is created, let all registered listeners know about it.
void Model::createObject(string id) {
MObject *obj = new MObject(id);
objects.insert(make_pair(id, obj));

listenersIt = listeners.begin();
while(listenersIt != listeners.end()) {
IModelListener *listener = *listenersIt;
listener->modelObjectCreated(id);
listenersIt++;
}
}

Share this post


Link to post
Share on other sites
Quote:
Original post by smr
True, it adds overhead, but some overhead cannot be avoided. Unless your message dispatcher can somehow divine the intended recipient of a message, then you'll need to include something in the message that can identify the target.


What I do is actually letting listeners receive all events when they register themselves in a component, the listener will themselves decide to react or ignore the events.

I am not saying this is generally better, just different and avoids the need of tagging events/messages.

Share this post


Link to post
Share on other sites
Quote:
Original post by flodihn

What I do is actually letting listeners receive all events when they register themselves in a component, the listener will themselves decide to react or ignore the events.
Which means event ID is the tag.

When client receives that event ID, it needs to map it to handler.

If it's a request/response style communication, each pair needs to have a token that identifies it. The token can double as event id.

For publish subscribe, event ID will typically identify the channel, which may further be partitioned by message type/payload.

For networked communication, another ID is the IP:port tuple.

Share this post


Link to post
Share on other sites
Quote:
Original post by Antheus
Quote:
Original post by flodihn

What I do is actually letting listeners receive all events when they register themselves in a component, the listener will themselves decide to react or ignore the events.
Which means event ID is the tag.

When client receives that event ID, it needs to map it to handler.

If it's a request/response style communication, each pair needs to have a token that identifies it. The token can double as event id.

For publish subscribe, event ID will typically identify the channel, which may further be partitioned by message type/payload.

For networked communication, another ID is the IP:port tuple.


Not really but I see where you are going. Instead of having event IDs, I have something called properties (key, value) where the key could be regarded as event id.

For example the interface has 3 callbacks, modelObjectCreated, modelObjectDestroyed and modelObjectPropertyChanged.
When an object is created or destroyed I send the id of the game object, I need no event id since the these events are have their own handler, just like you said:
Quote:
When client receives that event ID, it needs to map it to handler.


However in the modelPropertyChange callback, the key in the property is used to determine the type of the event (just like an event id). The listener can choose to do something or just ignore the event.

If a property is set in the model like this:


void Model::setProperty(string id, string key, Property p) {
objectsIt = objects.find(id);
if(objectsIt == objects.end())
return;

MObject* obj = objectsIt->second;
obj->setProperty(key, p);

listenersIt = listeners.begin();

while(listenersIt != listeners.end()) {
IModelListener *listener = *listenersIt;
listener->modelPropertyChanged(id, key);
listenersIt++;
}

}


In the OgreView.cpp which is listening on the model I do something like this:


void OgreView::modelObjectCreated(string id) {
if(id == "gui" || id == "terrain" ||
id == "camera" || id == "skybox" ||
id == "player")
return;

createOgreObject(id);
}

void OgreView::modelPropertyChanged(string id, string key) {
Property p = mModel->getProperty(id, key);
mObjectsLoadedIt = mObjectsLoaded.find(id);
if(mObjectsLoadedIt == mObjectsLoaded.end()) {
return;
}

OgreObject* object = mObjectsLoadedIt->second;
if(key.compare("mesh") == 0) {
object->setMesh((char *) p.str.c_str());
} else if(key.compare("billboard") == 0) {
object->setBillboard(p.str);
} else if(key.compare("yaw") == 0) {
object->yaw(p.doubleVal);
} else if(key.compare("pitch") == 0) {
object->pitch(p.doubleVal);
} else if(key.compare("scale") == 0) {
//Vector scale = p->getVectorValue();
object->setScale(p.x, p.y, p.z);
} else if(key.compare("pos") == 0) {
object->setPosition(p.x, p.y, p.z);
} else if(key.compare("rotx") == 0) {
object->setRotX(p.doubleVal);
} else if(key.compare("roty") == 0) {
object->setRotY(p.doubleVal);
} else if(key.compare("rotz") == 0) {
object->setRotZ(p.doubleVal);
} else if(key.compare("dir") == 0) {
object->setDirection(p.x, p.y, p.z);
} else if(key.compare("speed") == 0) {
object->setSpeed(p.doubleVal);
} else if(key.compare("anim") == 0) {
object->setAnimation(p.str);
} else if(key.compare("stop_anim") == 0) {
object->stopAnimation(p.str);
} else if(key.compare("movement") == 0) {
string type = p.str;
if(type == "ground")
object->disableFreeMovement();
else
object->enableFreeMovement();
} else {
cout << "OgreView: Ignoring modelPropertyChanged " << key << endl;
}
}


Really how I do this internally is not so importent, the importance is that I handle these events without adding overhead data to the server.

Share this post


Link to post
Share on other sites
Hmm, can you explain a bit more because I read http://en.wikipedia.org/wiki/Function_object and can not see how this can help me.
Since I have a lot of OgreObject I can not see how function pointers could work in my case.

Share this post


Link to post
Share on other sites
Instead of all those if() statements to see which property is being updated, you could use a map<> with the name of the property as the key.
The value would be a function object that takes the object and the "p" value as arguments, and applies the value to the appropriate property.
That function object might look something like this (a sketch I'm writing straight into the forum, so it might have minor typos):

class PropertySetter
{
public:
template<typename Type, void (OgreObject::*Func)(Type t)>
PropertySetter &add(char const *name, Func f) {
setters_[name] = new SetFuncImpl<Type, Func>();
return *this;
}
void set(OgreObject *obj, std::string const &name, PropertyValue const &val) {
std::map<std::string, SetFunc *>::iterator it(setters_.find(name));
if (it == setters_.end()) return;
(*(*it).second)(obj, val);
}

private:
class SetFunc {
public:
virtual void operator()(OgreObject *obj, PropertyValue const &val) = 0;
}
template<typename Type, void (OgreObject::*Func)(Type t)>
class SetFuncImpl : public SetFuncImpl {
public:
void operator()(OgreObject *obj, PropertyValue const &val) {
(obj->*Func)(get<Type>(val));
}
template<typename T> T get(PropertyValue const &val);
template<> double get<double>(PropertyValue const &val) { return val.doubleValue; }
template<> char const * get<char const *>(PropertyValue const &val) { return val.str.c_str(); }
template<> std::string get<std::string>(PropertyValue const &val) { return val.str; }
};

std::map<std::string, SetFunc *> setters_;
};



Usage is simple. Because you know it's an OgreObject, you just need one.

PropertySetter ps = PropertySetter()
.add("model", &OgreObject::setModel)
.add("scale", &OgreObject::setScale)
....;



Now, when you have an object, a property name, and a property value, you simply call ps.set(object, name, value) and the right value will be set on the object.

You can extend this to be even simpler to set up, and you can even extend this to use for marshaling (what properties you need to replicate, etc). For more information, see my article in Game Engine Gems 2, to be released at GDC in March :-)

Share this post


Link to post
Share on other sites

This topic is 2568 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.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this