Skip to main content
GameDev.net gamedev.net
🔒 Locked

Void* Messaging for Classes (Now W/ Source Code)

Started by Drew_Benton Mar 27, 2005 at 8:15 PM 35 replies 7.7k views
Original Post
Drew_Benton
Drew_Benton
Click Here to go to most recent update.
(Sorry for all the source boxes - only way to read it nicely [headshake]) Ok so I've had quite a few ideas recently that just have not worked out. So I decided to start at the point at which they failed - messaging. Now I would like to make an easy way to be able to have all my objects interact with each other. Don't ask me why, I just want to see if this concept is possible. So before I begin, I know a lot of people who see this code are going to have heart attacks - I am 100% aware of safety issues, I just want you to know that. Here's the idea - I start out with having a generic messaging structure -
<blockquote>
struct IMessage
{
    std::vector<void*> ptr;
};
</blockquote> 
I then have my base class that acts as the "Root" of all other classes:

class Actor
{
private:
	typedef void (Actor::*ActorPtr)( const IMessage *data );
	ActorPtr mActorPtr;	
	std::map< std::string, Actor::ActorPtr> Functions;

public:
	Actor();
	~Actor();

	virtual void Create( const IMessage *data = 0 );
	virtual void Render( const IMessage *data = 0 );
	virtual void Destroy( const IMessage *data = 0 );
	virtual void Message( std::string message, const IMessage *data = 0);
};



Now some important things to point out. It contains a std::map of function pointers. This is part of the messaging system, as you will see shortly. Now as for function implemtations, there are two main functions that are the most important -
<blockquote>
void Actor::Message( std::string message, const IMessage *data )
{
	if( Functions.find( message ) != Functions.end() )
	{
		(this->*Functions[ message ])( data );
	}
	else
	{
		std::cout << std::endl << "Non processed message: " <<  message << std::endl;
	}
}</blockquote> 
This function is what receives a message and then calls the appropriate function with the passed data. Next -
<blockquote> 
Actor::Actor()
{
	Functions["Create"] = &Actor::Create;
	Functions["Render"] = &Actor::Render;
	Functions["Destroy"] = &Actor::Destroy;
}
</blockquote> 
The ctor, for the time being, registers what function calls are mapped to what string. Now as for use, it is very simple. The first way in messaging a class, is without data:
<blockquote> 
Actor A1;
A1.Message("Create");
A1.Message("Render");
A1.Message("Destroy");
</blockquote> 
To which Actor::Create, Actor::Render, and Actor::Destroy are called. The second way in messaging a class, is with data:
<blockquote> 
IMessage tmp;
tmp.ptr.resize( 1, 0 );
tmp.ptr[0] = (void*)("This is a test");
A1.Message("Print", &tmp );
</blockquote> 
Now as for handling the data sent in, it is up to the class to properly handle it. Once again I am aware of safety issues, but that is not my concern as of now. Now that was just the Base Actor class, other classes are derived from there and work in a similar way. The basic idea is that any class can interact with any other class without knowing about it. This means that you can send unit data to your GUI wihtout having to include in the header files for your units and GUI - thus greatly reducing any dependecies. Here is a sample Main.CPP file:

#include "Actor.h"

Actor *Actor1;
Box B1;

int main( int argc, char* argv[] )
{
	Actor1 = &B1

	Actor1->Message( "Create" );

	IMessage tmp;

	tmp.ptr.resize( 1, 0 );
	tmp.ptr[0] = (void*)("File1.bmp");
	Actor1->Message( "SetTexture", &tmp );

	Actor1->Message( "12345" );

	tmp.ptr.resize( 3, 0 );
	tmp.ptr[0] = (void*)("5");
	tmp.ptr[1] = (void*)("0");
	tmp.ptr[2] = (void*)("-8");
	Actor1->Message( "SetPosition", &tmp );

	tmp.ptr.resize( 3, 0 );
	float x = 0.25f, y = 1.0f, z = -0.75f;
	tmp.ptr[0] = (void*)(&x);
	tmp.ptr[1] = (void*)(&y);
	tmp.ptr[2] = (void*)(&z);
	Actor1->Message( "SetRotate", &tmp );

	Actor1->Message( "Render" );
	
	Actor1->Message( "Destroy" );

	return 0;
}



When ran, the output is:

Call to Box::Create

Call to Box::SetTexture
FileName: File1.bmp

Non processed message: 12345

Call to Box::SetPosition
X: 5
Y: 0
Z: -8

Call to Box::SetRotate
X: 0.25
Y: 1
Z: -0.75

Call to Box::Render

Call to Box::Destroy
Press any key to continue 



Now idealy, an Object Factory would be used so there would not be an explicit Box variable, but this is just a quick demo. What are your reactions to this idea? Sound promising or too much hassal (in terms of use, I'm the one programming it [wink]). Thanks for your time! Unlike my other posts, I will try not to actively defend anything brought up - I will let a few responses be made then reply to them, just so it doesn't grow to such a large size that no one want's to read it (like my last post [lol]) I am open to ALL comments and suggestions - I want to hear what you developers think. - Drew [Edited by - Drew_Benton on March 31, 2005 6:24:59 PM]
Deyja
Deyja
You may want to consider using boost's signal/slot library. It was designed for exactly this sort of thing. Also; you can tack-on some runtime type safety using a class like boost::any.
nilkn
nilkn
Yep, messaging is a very good and reliable way for communication between disparate objects. Your method will certainly work, and there is nothing wrong with it. Here's a few suggestions though:

1. Try to seperate the messaging system from the game entities (Actor's in this case). This gives more flexibility and also easier reuse of the system.

2. Maybe allow certain messages to have a "delay", in that they aren't handled immediately but wait to be executed until a timer has expired.

3. Periodic messages (or so I call them) -- messages that are executed repeatedly on a set time interval forever, until otherwise specified.

I have literally just finished writing my own messaging system, so I it could be fun to look at each others implementations. I know I did mine considerably differently. [smile] Here's the code for my system (not that it's not fully optimized yet -- I literaly just finished this):

Class.hpp
#ifndef __XRGAME_CLASS_H__#define __XRGAME_CLASS_H__#include <list>#include <string>using std::list;using std::string;struct xrRTTI;class xrClass;typedef list<xrClass*> xrClassList;struct xrRTTI{	xrRTTI() :		ClassName(""),		Parent(NULL)	{	}	xrRTTI(const string& ClassName_, const xrRTTI* Parent_) :		ClassName(ClassName_),		Parent(Parent_)	{	}	const string ClassName;	const xrRTTI* Parent;};#define CLASS_DECLARE 	private: 		static xrRTTI* m_RTTI; 	public: 		static const xrRTTI& GetRTTI(); 		static const string& GetClassName();#define CLASS_DEFINE_BASE(classname) 	xrRTTI* classname::m_RTTI = new xrRTTI(#classname, NULL); 	const xrRTTI& classname::GetRTTI() { 		return *m_RTTI; 	} 	const string& classname::GetClassName() { 		return m_RTTI->ClassName; 	}#define CLASS_DEFINE(classname, parentname) 	xrRTTI* classname::m_RTTI = new xrRTTI(#classname, &(parentname::GetRTTI())); 	const xrRTTI& classname::GetRTTI() { 		return *m_RTTI; 	}class xrClass{	CLASS_DECLARE;public:	xrClass();	virtual ~xrClass();	inline const int AddRef();	inline const int Release();	inline const int GetRefCount() const;	const bool IsExactly(xrClass& Class) const;	const bool DerivesFrom(xrClass& Class) const;	inline const int GetUID() const;	static const xrClassList& GetClassList();	static void CollectGarbage();	static void CollectRemainingObjects();	static const int CreateNewUID();private:	static xrClassList s_ClassList;	static int s_NextUID;	const bool DerivesFrom(xrRTTI& RTTI1, xrRTTI& RTTI2) const;protected:	int m_UID;	int m_RefCount;};inline const int xrClass::AddRef(){	return ++m_RefCount;}inline const int xrClass::Release(){	--m_RefCount;	int RefCountCpy = m_RefCount;	if (m_RefCount <= 0) {		s_ClassList.remove(this);		delete this;	}	return RefCountCpy;}inline const int xrClass::GetRefCount() const{	return m_RefCount;}inline const int xrClass::GetUID() const{	return m_UID;}#endif


Class.cpp
#include "Class.hpp"xrClassList xrClass::s_ClassList = xrClassList();int xrClass::s_NextUID = 0;CLASS_DEFINE_BASE(xrClass);xrClass::xrClass() :	m_RefCount(0),	m_UID(xrClass::CreateNewUID()){	s_ClassList.push_back(this);}xrClass::~xrClass(){	s_ClassList.remove(this);}int const xrClass::CreateNewUID(){	return ++s_NextUID;}void xrClass::CollectGarbage(){	xrClassList::iterator ClsIt;	for (ClsIt = s_ClassList.begin(); ClsIt != s_ClassList.end(); ++ClsIt) {		xrClass* Cls = (*ClsIt);		if (Cls->GetRefCount() <= 0) {			ClsIt = s_ClassList.erase(ClsIt);			delete Cls;		}	}}void xrClass::CollectRemainingObjects(){	xrClassList::iterator ClsIt;	for (ClsIt = s_ClassList.begin(); ClsIt != s_ClassList.end(); ++ClsIt) {		xrClass* Cls = (*ClsIt);		ClsIt = s_ClassList.erase(ClsIt);		delete Cls;	}}const bool xrClass::IsExactly(xrClass& Class) const{	return (m_RTTI == &(Class.GetRTTI()));}const bool xrClass::DerivesFrom(xrClass& Class) const{	if (IsExactly(Class))		return true;	return DerivesFrom(const_cast<xrRTTI&>(*m_RTTI->Parent), 		               const_cast<xrRTTI&>(Class.GetRTTI()));}const bool xrClass::DerivesFrom(xrRTTI& RTTI1, xrRTTI& RTTI2) const{	if (&RTTI1 == &RTTI2)		return true;	return DerivesFrom(const_cast<xrRTTI&>(*RTTI1.Parent), RTTI2);}


Ptr.hpp
#ifndef __XRGAME_PTR_H__#define __XRGAME_PTR_H__#include <assert.h>template <class Type>class xrPtr{public:	xrPtr() :	  m_Object(NULL)	{	}	xrPtr(const Type* Object) :		m_Object(const_cast<Type*>(Object))	{		if (m_Object) m_Object->AddRef();	}	xrPtr(const xrPtr<Type>& Object) :		m_Object(Object.m_Object)	{		if (m_Object != NULL) m_Object->AddRef();	}	~xrPtr()	{		if (m_Object != NULL)			m_Object->Release();	}	inline operator =(Type* Object)	{		if (m_Object) m_Object->Release();		m_Object = Object;		if (m_Object) m_Object->AddRef();		return 1;	}	inline operator =(const xrPtr<Type>& Object)	{				if (m_Object) m_Object->Release();		m_Object = Object.m_Object;		if (m_Object) m_Object->AddRef();		return 1;	}	inline operator Type*() const	{		return m_Object;	}	inline Type& operator *() const	{		assert(m_Object);		return *m_Object;	}	inline Type* operator ->() const	{		assert(m_Object);		return m_Object;	}	inline const bool Valid() const	{		return (m_Object != NULL);	}	inline const bool operator !() const	{		return (!m_Object);	}	inline const bool operator ==(Type* Object) const	{		return (m_Object == Object);	}	inline const bool operator ==(const xrPtr<Type>& Object) const	{		return (m_Object == Object.m_Object);	}	inline const bool operator !=(Type* Object) const	{		return (m_Object != Object);	}	inline const bool operator !=(const xrPtr<Type>& Object) const	{		return (m_Object != Object.m_Object);	}private:	Type* m_Object;};#endif


MessageSystem.hpp
#ifndef __XRGAME_MESSAGESYSTEM_H__#define __XRGAME_MESSAGESYSTEM_H__#include <vector>#include <map>#include <string>#include "Class.hpp"#include "Ptr.hpp"using std::vector;using std::map;using std::string;class xrCallback;struct xrMessageDef;struct xrMsgListenerReg;struct xrMessage;class xrMessagePump;typedef xrPtr<xrCallback> xrCallbackPtr;typedef xrPtr<xrMsgListenerReg> xrMsgListenerRegPtr;typedef xrPtr<xrMessageDef> xrMessageDefPtr;typedef xrPtr<xrMessage> xrMessagePtr;typedef map<int, xrMsgListenerRegPtr> xrMsgListenerRegMap;typedef map<string, xrMessageDefPtr> xrMessageDefMap;typedef vector<xrMessagePtr> xrMessageList;typedef map<xrMessagePtr, float> xrPeriodicMsgDelayMap;class xrCallback : public xrClass{	CLASS_DECLARE;public:	virtual void Function(xrMessagePtr Msg, xrClass* Parent) = 0; // must use unmanaged pointer for typecasting};struct xrMessageDef : public xrClass{	CLASS_DECLARE;public:	xrMessageDef() :		Name(""),		MsgListenerRegMap(xrMsgListenerRegMap())	{	}	string Name;	xrMsgListenerRegMap MsgListenerRegMap;};struct xrMsgListenerReg : public xrClass{	CLASS_DECLARE;public:	xrMsgListenerReg() :		Parent(NULL),		CBack(NULL)	{	}	xrPtr<xrClass> Parent;	xrCallbackPtr CBack;};struct xrMessage : public xrClass{	CLASS_DECLARE;public:	xrMessage() :		TimeCreated(0),		DestID(-1),		Priority(MESSAGE_PRIORITY_MEDIUM),		Sender(NULL),		Message(""),		Arg1(NULL),		Arg2(NULL),		Delivered(false)	{	}	enum 	{		MESSAGE_PRIORITY_AMBIENT = 0,		MESSAGE_PRIORITY_LOW,		MESSAGE_PRIORITY_MEDIUM,		MESSAGE_PRIORITY_HIGH,		MESSAGE_PRIORITY_IMMEDIATE,		MESSAGE_PRIORITY_PERIODIC,	};	float TimeCreated;	float Delay;	int DestID;	int Priority;	bool Delivered;	xrPtr<xrClass> Sender;	string Message;	xrPtr<xrClass> Arg1;	xrPtr<xrClass> Arg2;};class xrMessagePump{public:	static void Init(bool ArbitrateMessages = true);	static void Shutdown();	static void SendMessage(xrMessagePtr Message);	static void SendMessage(int DestID, xrPtr<xrClass> Sender, string Message, float Delay, int Priority, xrPtr<xrClass> Arg1);	static void RegisterMessage(string Message);	static void UnregisterMessage(string Message);	static void RegisterClassAsListener(int UID, xrPtr<xrClass> Parent, xrCallbackPtr CBack, string Message);	static bool IsClassListeningFor(int UID, string Message);	static void UnregisterClassAsListener(int UID, string Message);	static void UnregisterAllListenersFor(string Message);	static void ProcessMessages(float TimeStep);	static const bool GetArbitrateMessages();	static void EnableMessageArbitration();	static void DisableMessageArbitration();private:	static xrMessageDefMap s_MessageDefMap;	static xrMessageList s_MessageList;	static xrMessageList s_PeriodicMessageList;	static xrPeriodicMsgDelayMap s_PeriodicMsgDelayMap;	static bool s_ArbitrateMessages;	static xrMessageDefPtr ExtractMessageDef(string Message);	static xrMsgListenerRegPtr ExtractMsgListenerReg(int UID, xrMessageDefPtr MsgDef);	static void ProcessMessage(xrMessagePtr Message);	class MessagePrioritySort	{	public:		bool operator()(xrMessagePtr Msg1, xrMessagePtr Msg2) const;	};	class MessageCompare	{	public:		bool operator()(xrMessagePtr Msg1, xrMessagePtr Msg2) const;	};	class RemoveMessageIf	{	public:		bool operator()(xrMessagePtr Msg) const;	};};#endif


MessageSystem.cpp
#include <ClanLib/core.h>#include <assert.h>#include <set>#include "MessageSystem.hpp"CLASS_DEFINE_BASE(xrCallback);CLASS_DEFINE_BASE(xrMessageDef);CLASS_DEFINE_BASE(xrMsgListenerReg);CLASS_DEFINE_BASE(xrMessage);xrMessageDefMap xrMessagePump::s_MessageDefMap = xrMessageDefMap();xrMessageList xrMessagePump::s_MessageList = xrMessageList();xrMessageList xrMessagePump::s_PeriodicMessageList = xrMessageList();xrPeriodicMsgDelayMap xrMessagePump::s_PeriodicMsgDelayMap = xrPeriodicMsgDelayMap();bool xrMessagePump::s_ArbitrateMessages = true;void xrMessagePump::Init(bool ArbitrateMessages){	s_ArbitrateMessages = ArbitrateMessages;}void xrMessagePump::Shutdown(){	xrMessageDefMap::iterator MsgDefIt;	for (MsgDefIt = s_MessageDefMap.begin(); MsgDefIt != s_MessageDefMap.end(); ++MsgDefIt) {		xrMessageDef* MsgDef = (*MsgDefIt).second;		UnregisterAllListenersFor(MsgDef->Name);		MsgDefIt = s_MessageDefMap.erase(MsgDefIt);		delete MsgDef;	}	s_MessageDefMap.clear();	xrMessageList::iterator MsgIt;	for (MsgIt = s_MessageList.begin(); MsgIt != s_MessageList.end(); ++MsgIt) {		xrMessage* Msg = (*MsgIt);		MsgIt = s_MessageList.erase(MsgIt);		delete Msg;	}	s_MessageList.clear();	for (MsgIt = s_PeriodicMessageList.begin(); MsgIt != s_PeriodicMessageList.end(); ++MsgIt) {		xrMessage* Msg = (*MsgIt);		MsgIt = s_PeriodicMessageList.erase(MsgIt);		delete Msg;	}}void xrMessagePump::SendMessage(xrMessagePtr Message){	assert(&Message);	if (Message->Priority == xrMessage::MESSAGE_PRIORITY_IMMEDIATE)		ProcessMessage(Message);	else if (Message->Priority == xrMessage::MESSAGE_PRIORITY_PERIODIC) {		s_PeriodicMessageList.push_back(Message);		s_PeriodicMsgDelayMap[Message] = Message->Delay;	}	else		s_MessageList.push_back(Message);}void xrMessagePump::SendMessage(int DestID, xrPtr<xrClass> Sender, string Message, float Delay, int Priority, xrPtr<xrClass> Arg1){	xrMessagePtr Msg = new xrMessage();	Msg->DestID = DestID;	Msg->Sender = Sender;	Msg->Message = Message;	Msg->Delay = Delay;	Msg->Priority = Priority;	Msg->Arg1 = Arg1;	Msg->TimeCreated = CL_System::get_time();	SendMessage(Msg);}xrMessageDefPtr xrMessagePump::ExtractMessageDef(string Message){	xrMessageDefMap::iterator MsgDefIt = s_MessageDefMap.find(Message);	if (MsgDefIt != s_MessageDefMap.end())		return (*MsgDefIt).second;	return NULL;}xrMsgListenerRegPtr xrMessagePump::ExtractMsgListenerReg(int UID, xrMessageDefPtr MsgDef){	assert(MsgDef != NULL);	xrMsgListenerRegMap::iterator ListRegIt = MsgDef->MsgListenerRegMap.find(UID);	if (ListRegIt != MsgDef->MsgListenerRegMap.end())		return (*ListRegIt).second;	return NULL;}void xrMessagePump::RegisterMessage(string Message){	if (ExtractMessageDef(Message) == NULL) {		xrMessageDefPtr MsgDef = new xrMessageDef();		MsgDef->Name = Message;		s_MessageDefMap[Message] = MsgDef;	}}void xrMessagePump::UnregisterMessage(string Message){	xrMessageDefPtr MsgDef = ExtractMessageDef(Message);	if (MsgDef != NULL) {		s_MessageDefMap.erase(Message);		delete MsgDef;	}}void xrMessagePump::RegisterClassAsListener(int UID, xrPtr<xrClass> Parent, xrCallbackPtr CBack, string Message){	xrMessageDefPtr MsgDef = ExtractMessageDef(Message);	if (MsgDef != NULL) {		xrMsgListenerRegPtr ListReg = ExtractMsgListenerReg(UID, MsgDef);		if (ListReg == NULL) {			ListReg = new xrMsgListenerReg();			ListReg->Parent = Parent;			ListReg->CBack = CBack;			MsgDef->MsgListenerRegMap[UID] = ListReg;		}	}}bool xrMessagePump::IsClassListeningFor(int UID, string Message){	xrMessageDefPtr MsgDef = ExtractMessageDef(Message);	xrMsgListenerRegPtr ListReg = ExtractMsgListenerReg(UID, MsgDef);	return (ListReg != NULL);}void xrMessagePump::UnregisterClassAsListener(int UID, string Message){	xrMessageDefPtr MsgDef = ExtractMessageDef(Message);	if (MsgDef != NULL) {		xrMsgListenerRegPtr ListReg = ExtractMsgListenerReg(UID, MsgDef);		if (ListReg != NULL) {			MsgDef->MsgListenerRegMap.erase(UID);			delete ListReg;		}	}}void xrMessagePump::UnregisterAllListenersFor(string Message){	xrMessageDefPtr MsgDef = ExtractMessageDef(Message);	if (MsgDef != NULL) {		xrMsgListenerRegMap::iterator ListRegIt;		for (ListRegIt = MsgDef->MsgListenerRegMap.begin(); ListRegIt != MsgDef->MsgListenerRegMap.end(); ++ListRegIt) {			xrMsgListenerRegPtr ListReg = (*ListRegIt).second;			MsgDef->MsgListenerRegMap.erase(ListRegIt);			delete ListReg;		}	}}const bool xrMessagePump::GetArbitrateMessages(){		return s_ArbitrateMessages;}void xrMessagePump::EnableMessageArbitration(){	s_ArbitrateMessages = true;}void xrMessagePump::DisableMessageArbitration(){	s_ArbitrateMessages = false;}bool xrMessagePump::MessagePrioritySort::operator()(xrMessagePtr Msg1,													xrMessagePtr Msg2) const{	if (Msg1->Priority == Msg2->Priority)		return (Msg1->TimeCreated > Msg2->TimeCreated);	return (Msg1->Priority > Msg2->Priority);}bool xrMessagePump::MessageCompare::operator()(xrMessagePtr Msg1,											   xrMessagePtr Msg2) const{	return (Msg1 == Msg2);}bool xrMessagePump::RemoveMessageIf::operator()(xrMessagePtr Msg) const{	return Msg->Delivered;}void xrMessagePump::ProcessMessage(xrMessagePtr Message){	xrMessageDefPtr MsgDef = ExtractMessageDef(Message->Message);	if (MsgDef != NULL) {		xrMsgListenerRegMap::iterator ListRegIt;		for (ListRegIt = MsgDef->MsgListenerRegMap.begin(); ListRegIt != MsgDef->MsgListenerRegMap.end();++ListRegIt) {			xrMsgListenerRegPtr ListReg = (*ListRegIt).second;			if (ListReg != NULL) {				if (Message->DestID == -1 || Message->DestID == ListReg->Parent->GetUID()) {					ListReg->CBack->Function(Message, ListReg->Parent);					Message->Delivered = true;				}			}		}	}}void xrMessagePump::ProcessMessages(float TimeStep){	float TimeTaken = 0.0f;	float StartTime = CL_System::get_time();	float EndTime = 0.0f;	xrMessageList::iterator MsgIt;	if (s_PeriodicMessageList.size() <= 0)		goto NormalMessageProcessing;	for (MsgIt = s_PeriodicMessageList.begin(); MsgIt != s_PeriodicMessageList.end(); ++MsgIt) {		xrMessagePtr Msg = (*MsgIt);		if (Msg->Delay > 0.0f) {			Msg->Delay -= TimeStep;			continue;		}		float InitialMsgDelay = (s_PeriodicMsgDelayMap.find(Msg))->second;		Msg->Delay = InitialMsgDelay;		ProcessMessage(Msg);	}NormalMessageProcessing:	if (s_ArbitrateMessages)		sort(s_MessageList.begin(), s_MessageList.end(), xrMessagePump::MessagePrioritySort());	for (MsgIt = s_MessageList.begin(); MsgIt != s_MessageList.end(); ++MsgIt) {		if (s_ArbitrateMessages) {			if (TimeTaken >= TimeStep)				break;			StartTime = CL_System::get_time();		}		xrMessagePtr Msg = (*MsgIt);		if (Msg->Delay > 0.0f) {			Msg->Delay -= TimeStep;			continue;		}		ProcessMessage(Msg);		if (s_ArbitrateMessages) {			EndTime = CL_System::get_time();			TimeTaken += EndTime - StartTime;		}	}	xrMessageList::iterator End = s_MessageList.end();	xrMessageList::iterator NewEnd = remove_if(s_MessageList.begin(), s_MessageList.end(), xrMessagePump::RemoveMessageIf());	if (End != NewEnd)		s_MessageList.erase(NewEnd, End);	if (s_ArbitrateMessages && s_MessageList.size() > 0) {		xrMessageList::iterator MsgIt;		for (MsgIt = s_MessageList.begin(); MsgIt != s_MessageList.end(); ++MsgIt) {			xrMessagePtr Msg = (*MsgIt);			if (Msg->Priority != xrMessage::MESSAGE_PRIORITY_AMBIENT &&				Msg->Priority != xrMessage::MESSAGE_PRIORITY_HIGH)				++Msg->Priority;		}	}}


I apologize for the xrClass and xrPtr stuff, but the message system is so heavily based on them I felt it was necessary for the sake of comprehension. If you don't want to read through them, just know that xrClass is a class that unifies garbage collection, reference counting and custom RTTI, and that xrPtr is just a simple smart pointer for anything derived from xrClass. [smile]

Edit: Sorry for the immense size of this post. If I knew another way to do it, I would.

Edit2: I just realized I uploaded an older version of Class.hpp and Class.cpp. The RTTI functions IsExactly() and DerivedFrom() aren't any good in this version, so just ignore them!

[Edited by - nilkn on March 28, 2005 6:35:59 PM]
Zahlman
Zahlman
1) Instead of the void*s that you know are so ugly ;), how about Boost::Any?

2) I don't see your implementation for Actor::Message with no arguments, but I assume it is similar. There's some duplication here, though, since you could just as easily create an IMessage with a zero-size vector.

3) The whole process of constructing an IMessage seems rather annoying.

My recommendation would be to wrap the function name into the IMessage itself, and give it functionality for appending arguments. Something like:

class IMessage {  std::string procedure;  std::vector<Boost::any> arguments;  public:  IMessage(const std::string & procedure) : procedure(procedure), arguments() {}  // Let's use operator() to append arguments. There are several options. You  // might find operator, to "look nicest", except you will probably need extra  // pairs of parentheses...  template <typename T>  IMessage& operator() (const T& arg) {    arguments.push_back(Boost::any(arg));    return *this;  }  // We'll make the procedure name read-only, so that the dispatcher can check it  const std::string& proc() const { return procedure; }  // OTOH, it would probably be better OOP to have the Message()  // function pass the Functions map to an IMessage method like "dispatchFrom"  // ("lookUpSelfIn"?)... i.e. have the message do the lookup work, rather than  // providing a dumb accessor.  // We'll provide read-only access to the arguments  const Boost::any& operator[] (int index) const { return arguments[index]; }  // OTGH, since we pretty much have to have read access to the arguments -  // this is really a container object after all - we may as well have it for  // the procedure too... :s}// You're on your own for fixing up the Message() dispatcher etc.// BTW, shouldn't that Functions map be static? :)// later:Actor* Actor1 = new Box();Actor1->Message( IMessage("Create") );Actor1->Message( IMessage("SetTexture")("File1.bmp") );Actor1->Message( IMessage("12345") ); // will complain, no such procedureActor1->Message( IMessage("SetPosition")("5")("0")("-8") );Actor1->Message( IMessage("SetRotate")(0.25f)(1.0f)(-0.75f) );Actor1->Message( IMessage("Render") );Actor1->Message( IMessage("Destroy") );
Telastyn
Telastyn
Direct object messaging seems icky to me. I think I'd prefer the design where all messages are passed through a chokepoint [read: message handler] and divvied down to individual objects [or groups of objects].

Also, having a map like that seems also kinda icky. If there's any sort of loop where the message is constant and the data changes, you're spending a lot of time in Functions.find. Doing something like:

Function *f=Functions.find( something );// use f a lot...


just seems like unnecissary hassle.

Further you describe this as solving many dependancy issues, and I'm skeptical. I mean just because the thing compiles doesn't mean there aren't dependancies. Classes still need to know about other classes, or else they're sending bogus messages slowing your app down.

That said, I also have something sort of like this setup for some of the messages you deal with like SetPosition and the such. It doesn't work so well, so I wouldn't recommend it. I'd likely go with a messaging system in my next big project myself.

Anyways, your posts are great and thought provoking, keep em coming.

Drew_Benton
Drew_Benton
Wow thanks everyone for answering so quickly! I appologize for the way it looks, I just can't find a 'nice and clean' way that is not long - I will be in the GDNet suggestion forum soon.

@Deyja - Thanks, it seems like everyday that passes, there is more of a reason for me to use boost! I was trying to avoid it, but now I guess I might need to give in and learn it.

@nilkn - WOW I will begin looking at all of that, thanks! And it's fine about the size, I'm in the same position [smile]

@Zahlman - Oooo, now that's nice! I'd love to be able to do something like that, thanks

@Telastyn - Thanks too. I know what you are saying in terms of redudant messages and such. However, in terms of the dependicies, it's more of a the programmer knows, but not the compiler kind of thing [wink]. I will definitly work on a better demo demonstrating this - I know it sounds too good to be true, but it's possible! [smile]

Ok thanks everyone once again! I am still trying to figure out how to make a neater post [lol] - so that's why this one is a little short, I will re-address all of this when I get this fixed and start comparing posts to my class right now.

- Drew
antareus
antareus
Err if you want to do all this why not use a language like Smalltalk?

You send an object a message (e.g. call a method). If the object doesn't know how to interpret the message, it returns doesNotUnderstand or something like that. All dynamic.

Just suggesting that you choose a higher-level language if you want this sort of dynamic behavior, because its like pulling teeth to get C++ to take it.
--God has paid us the intolerable compliment of loving us, in the deepest, most tragic, most inexorable sense.- C.S. Lewis
IFooBar
IFooBar
I like this system, your code is most certainly food for thought. Anyway, the problem with it is that Actor will have to know about the functions to be called right? Why not make either a message dispatcher, or maybe make use of templates. Make the Actor a templated class and have the Actor define:

typedef void (T::*ActorPtr)( const IMessage *data );

so that class Box : Actor will allow you to define custom Box specific functions. No harm in trying it out right. Or in the case of a Dispatcher, you can make Actor a pure interface, with just a Dispatch function. Keep the map of function pointers class specific so that Actor dosent have to know about the functionality of each class.

Also, about the speed issues. You can use some type of hashing to make it extremely faster. One options is to make the map key an unsigned 32 bit integer and use an algo like 32 bit pkzip or something to turn a string into IDs.

Another option to make it faster is if you are strictly using msvc, you can turn string pooling on and use the address of each string as an ID. I've never done anything like this and don't know how stable it would be though.
[size=2]aliak.net
Drew_Benton
Drew_Benton
First, Zahlman, you are my hero! I had NO IDEA you could do what you just did. That is the most amazing thing I've ever seen! Thanks to you this is what the main.cpp looks like now ( I still want to wait on boost [wink]):
#include "Actor.h"Actor *Actor1;Box B1;int main( int argc, char* argv[] ){	Actor1 = &B1	Actor1->Message( "Create" );	Actor1->Message( "SetTexture", &IMessage("File1.bmp") );	Actor1->Message( "12345" );	Actor1->Message( "SetPosition", &IMessage("5")("0")("-8") );	float x = 0.25f, y = 1.0f, z = -0.75f;	Actor1->Message( "SetRotate", &IMessage(&x)(&y)(&z) );	Actor1->Message( "Render" );	Actor1->Message( "Destroy" );	return 0;}

And the output is the same and no crashes! Thanks! One question though -
Quote:
I don't see your implementation for Actor::Message with no arguments, but I assume it is similar

I don't have one of those defined - are you talking about an void parameter, or using the same technique as you did with the () operator?



Quote:
Original post by antareus
Err if you want to do all this why not use a language like Smalltalk?


To be honest, I am language ignorant. Right now I only know C/C++ and have about 10 mins worth of Java experience. I am trying to change that though! I started to learn a little Python and have plans to do some C# some time soon as well. I will definitly take a look at smalltalk though, I've only heard of it's name a few times, I don't know anything about it.

As to why I want to do this in C++, it's more of a 'learning' thing. I'd like to have a system that I've made so when the time comes to do it in something else, I at least know the 'hard' way to do it [wink]. I'd really like to use it for our game, but I do not know if it will be applicable or not with what we are using (OGRE & co.)

Thank you for your suggestion though! I really do need to catch up to modern day [lol] I've been in the past a bit too long.



Quote:
Original post by IFooBar
I like this system, your code is most certainly food for thought. Anyway, the problem with it is that Actor will have to know about the functions to be called right? Why not make either a message dispatcher, or maybe make use of templates. Make the Actor a templated class and have the Actor define:

typedef void (T::*ActorPtr)( const IMessage *data );

so that class Box : Actor will allow you to define custom Box specific functions. No harm in trying it out right. Or in the case of a Dispatcher, you can make Actor a pure interface, with just a Dispatch function. Keep the map of function pointers class specific so that Actor dosent have to know about the functionality of each class.


Well right now, as you can see from the new Main.cpp on top, the Actor class does not *know* about the Box class. The way I have it, through polymorphism, I just need to call the Message function, of which is part of the Actor class and thus overloaded in all derived classes, and the messages are handled in that specific class the way they need to.

When I get it right - there will be no Box class or any other class in the main.cpp, just an ObjectFactory that is ready to create any class on demand [smile] I will try to add that in tonight.

Quote:
Also, about the speed issues. You can use some type of hashing to make it extremely faster. One options is to make the map key an unsigned 32 bit integer and use an algo like 32 bit pkzip or something to turn a string into IDs.


Thank you for the speed suggestiosn as well [smile] When I start off, I always use some sort of map just so I can see how it all works without worry about optimizations, when I get ready for speed, I do switch over to hash_map for strings. That does make it O(n) run time for access, so I am happy with that. This is what I did in my OpenAL audio manager - at first I had just used map, then I learned how to use the Standard C++ Library a bit better, so I added iterators, hash_map, and typenames for the reused names

Anyways, I am stil learning [smile] Thank you everyone for your comments and suggestions! They are much appreciated! Keep 'em coming.

- Drew

[edit]Another idea I was thinking about the use of this was perhaps something along the lines of being used in scripting.[/edit]

[Edited by - Drew_Benton on March 27, 2005 10:11:35 PM]
Drew_Benton
Drew_Benton
Ok, I think I have a base code to work with that does EXACTLY what I want it too [smile] Thanks for all the help!

I see no reason not to share this with everyone, so feel free to take a look at what I have been raving about for so long [lol]. I added comments to everything so you can follow. Just know this is by no means finished for 'optimized'. This is just one part of my original idea I could get figure out the first 2 times around. Tell me what you think!

Download Here

- Drew Benton
Zahlman
Zahlman
Quote:
Original post by Drew_Benton
Quote:
I don't see your implementation for Actor::Message with no arguments, but I assume it is similar

I don't have one of those defined - are you talking about an void parameter, or using the same technique as you did with the () operator?


What I meant was, how does "Actor1->Message( "Render" );" work, given that you're not passing any IMessage to Message(), and there doesn't appear to be a default parameter or function overload to take care of it?

Quote:
To be honest, I am language ignorant. Right now I only know C/C++ and have about 10 mins worth of Java experience. I am trying to change that though! I started to learn a little Python and have plans to do some C# some time soon as well. I will definitly take a look at smalltalk though, I've only heard of it's name a few times, I don't know anything about it.


You can do it in Python too:

class Actor(object):  def Message(self, name, *args, **kwargs):    self.__class__.__dict__[name](*args, **kwargs)     # throws KeyError for bad message names


Although the language is dynamic in so many other ways that it's hard to imagine why you'd need this. :)
Drew_Benton
Drew_Benton
Ok awesome! Thanks. How/where did you learn that overloading the () operator as well as ctor trick at? That was really the most interesting thing I've ever seen.
Genjix
Genjix
i implemented this sort of thing a long time ago and i have words to say about this about you.
	/*create node messenger handler*/	World myworld;	/*create nodes and reference them	with their parents.*/	AreaNode area(&myworld);	BackgroundNode bg(&myworld);	StartUpResourceNode sur(&myworld);	/*register the nodes with	node handler*/	myworld.Register("area",area);	myworld.Register("background",bg);	myworld.Register("startup",sur);

hopefully you can see what i'm doing here (the names i've chosen for this example are just random).
Now lets say the "startup" node wants to message "background" with the screen width and height.
void StartUpResourceNode::StartScreen(){	int w = 800 , h = 600 , bpp = 16;	Screen::Init(w,h,bpp);	/*now we send background node, the screen dimensions*/	Signal dim("screen width and height settings");	dim("screenw" , w)	   ("screenh" , h);	SendSignal("background" , "set screen" , dim);}

notice my Signal object can be declared as follows
Signal sig("this is the debug message in case of a problem");sig("name" , (const char*)"aaat")   ("age"  , 10)   ("pi"   , 3.141592654);  // from memory :)   ("chr"  , 'a')   ("obj"  , obj)   ("ptr"  , &obj);

now the sendsignal above would call its parent pointer and call from it SendSignal.
#ifndef WORLD_H#define WORLD_H#include "node.h"/**Application's Communication Server SystemThis is the main server, whereby each section of theprogram has to register with this class, to enablesignalling with the other 'nodes'.*/class World{    public:	/***/	World();	/***/	~World();	/**Add a node to our server*/	void Register(const string &name , Node &newnode);	/**Send signal and identifier to all nodes		An example could be Broadcast("KeyPress",buttons_pressed)*/	void Broadcast(const string &id , const Signal &sig);	/**Same as broadcast, except a specific node can be signalled*/	void SendSignal(const string &name , const string &id , const Signal &sig);    private:	/**Nodes are stored in a hash (named array)*/	map<string , Node*> nodes;};#endif

SendSignal would do a lookup first of all for "background", then send it the id command and the signal.
so in background it would do something like this in its RecieveSignal
void BackgroundNode::RecieveSignal(const string &id , Signal &sig){	if(id == "set screen")		SetScreen(sig);}void BackgroundNode::SetScreen(Signal &sig){	try	{		screenw = sig.Load<int>("screenw");		screenh = sig.Load<int>("screenh");	}	catch(const bad_any_cast&)	{		cerr << "error : bad cast... unable to function\n"			<< sig << Exit(-1);	}}

as you can see, all objects i registered with 'World' have access to each other, via signalling methods... but there are problem's here
a) its not type safe (it will compile if you missspell a message id command)
b) rising complexity - reimplementation of function calls, but with no type safety and higher speed let off.
c) this is no better than making those messenger classes global, and having them explicitly call each other.

the last one you may argue with, if your messaging system is encapsulated in a very deep part of your program, but then I'd ask why you would do something like this for such a minor part of your program.
snk_kid
snk_kid
Quote:
Original post by Deyja
You may want to consider using boost's signal/slot library.


Signals & slots are not exactly message based.

Personally I’m not very fond of message based systems, there to indirect for an internal/core why do you need such loose coupling and high level of indirection? What I mean is that it seems like going over board. I prefer event systems based on some form of observer pattern or signals & slots framework.

[Edited by - snk_kid on March 28, 2005 4:31:42 AM]
Programmer16
Programmer16
Hey Drew, I think your system looks awesome, but when I download the code and try to run it, it tells me that there aren't enough actual parameters for MACRO_REPEAT_15.

I'm using Microsoft Visual C++ 6.0
Houdini
Houdini
Quote:
Original post by Programmer16
Hey Drew, I think your system looks awesome, but when I download the code and try to run it, it tells me that there aren't enough actual parameters for MACRO_REPEAT_15.

I'm using Microsoft Visual C++ 6.0

Programmer16, I can answer this one. Drew is using the Object Factory class from an article here on GameDev. The problem is that VC++ 6.0 doesn't support 'template specialization' or 'explicit template argument specification for member functions', which the Object Factory class uses.

There's a VC++ 6.0 friendly version of the Object Factory Drew could use, but the syntax is a bit strange because of the hacks required to get VC++ 6.0 to work. IE, instead of writing this:

int BoxRegister = Actor::Factory.Register<Box>("Box");

You'd need to write this
int BoxRegister = Actor::Factory.Register("Box", Type2Type<Box>());



- Houdini
Houdini
Drew_Benton
Drew_Benton
Quote:
Original post by Houdini
There's a VC++ 6.0 friendly version of the Object Factory Drew could use, but the syntax is a bit strange because of the hacks required to get VC++ 6.0 to work. IE, instead of writing this:


Ahh yes! Thank you very much for that. I should have mentioned that [embarrass]. Credit to this GameDev article for that object factoy: Creating a Generic Object Factory [smile]


Ok Genjix, your bring up some very good points, but I must ask you, in your opinion, what would a better solution to this? I can see how a system like this could get to that point where it just gets to a point where it could get kind of unmanagable, to which large projects is not something I am that experienced with.


snk_kid - You know you are always right [smile] This could be a little too overboard for somemore complex, so saying that and going with what Genjix has said, perhaps instead of worrying about objects messaging each other, do as you like, and make this more of an "event system framework"? I will work with that idea definitly for it could make this a little better, tight now it's just all experimentation.

Also I have not ever heard of signals & slots, so I guess it's time I do some reading on that topic. [wink] Thanks everyone again! I will see if I can make a Dev-CPP version of this as well, so for people that do not have VS7 can run it withouhg moddifying it, thanks for asking about that Programmer16.

- Drew
Genjix
Genjix
well, bear with me im about to mention the umentionables and programmers will be at my neck.
the advantage to this system is no globals... but as i said, its really no different. you may as well group those 'nodes' into a global namespace then have a declaration in the header file (using a macro like NODE_DECLARATION(Background background) ), and then calling the methods explicitly (such as Node::background.SetScreen(w,h) ). the type safety, and loss of code (not to mention loss of info is bad, since variables lose their type) far outweighs the risks of globals.
#ifndef BACKGROUND_H#define BACKGROUND_H#include <boost/multi_array.hpp>#include "scene/image.h"#include "types/xml/xml.h"/***/class Background{    public:	/***/	Background();	/***/	~Background();	/**set screen variables*/	void SetScreen(int w , int h);	/**parse backgrounds in	scene xml file*/	void Parse(Xml::Element bgroot);	/**draw screen, when offsetted	by (offx,offy).	draws backgrounds that intersect	(offx,offy)->(screenw,screenh)*/	void Draw(int offx , int offy);    private:		/**2 dimensional array of background	images.*/	boost::multi_array<int , 2> background_images;	int screenw;		///< screens height	int screenh;		///< screens width};NODE_DECLARATION(Background background);#endif

but this of course applies to mapping based design structure versus something like a tree for example.
I don't think globals are quite the black hole everyone makes of them, as long as they're used properly (and its not worth all the extra code, required to make them work). more Encapsulation being better does not imply all globals are bad. Using a lot of globals can wreck and make a mess of your design - but thats when they're misused.
Drew_Benton
Drew_Benton
Ahh I see. Very interesting. I will keep that in mind during development. I mean I really don't know how this will turn out on larger scales. I mean right now I say 'message' all objects, but perhaps what I am doing is what you have done, with more of an events - simply because of how I process the messages in each class. I will see how it goes. I am not sure what I will do next today with it, I think I should evaluate some before I do anything too irrational.[lol] Thanks for our suggestions and time! Oh and I wanted to ask, did you revamp your system or just abadon it for your project? I'd like to see where it went wrong in hopes of avoiding that for this one, that is if it's not already on that path [wink]

- Drew
evolutional
evolutional
This is similar to the system I'm using as a basis for my scripting environment. Instead of void *, however, I'm keeping track of type information allowing variable typing on your messages. This is necessary in my case as you never kow what's coming along in a loose-typed system, but I think it's probably unnecessary for the system Drew's describing. Interesting though as Metaclassing is something I'm interested in. All you need to do now is implement propertysets and you're in Metaclass heaven....

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.