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

Started by
35 comments, last by Drew_Benton 19 years ago
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 -

struct IMessage
{
    std::vector<void*> ptr;
};
 
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 -

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;
	}
} 
This function is what receives a message and then calls the appropriate function with the passed data. Next -
 
Actor::Actor()
{
	Functions["Create"] = &Actor::Create;
	Functions["Render"] = &Actor::Render;
	Functions["Destroy"] = &Actor::Destroy;
}
 
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:
 
Actor A1;
A1.Message("Create");
A1.Message("Render");
A1.Message("Destroy");
 
To which Actor::Create, Actor::Render, and Actor::Destroy are called. The second way in messaging a class, is with data:
 
IMessage tmp;
tmp.ptr.resize( 1, 0 );
tmp.ptr[0] = (void*)("This is a test");
A1.Message("Print", &tmp );
 
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]
Advertisement
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.
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]
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") );
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.

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
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
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<Box> 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
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<Box> 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]
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

This topic is closed to new replies.

Advertisement