Pluggable Factories (allllmost there..)

Started by
12 comments, last by gimp 23 years, 9 months ago
Ok, I''ve been hammering away at trying to understand the code mechanics behind pluggable factories for about 2 weeks now and it''s driving me nuts. Here is my complete peice of test code. If the code was correct it should process the dummy data and produce a message box. My only goal that''s different to the incomplete sample code at gamedev is that all my classes should be created at startup, not when dereferenced for obvious performance reasons. The following code potentially only has some small bugs in Switch section. Errors: C:\Program Files\Microsoft Visual Studio\VC98\myprojects\PluggableFactory\plug.cpp(43) : error C2275: ''CMessageSwitch::BaseMessageClass::IDRegistryMap'' : illegal use of this type as an expression C:\Program Files\Microsoft Visual Studio\VC98\myprojects\PluggableFactory\plug.cpp(43) : error C2228: left of ''.second'' must have class/struct/union type C:\Program Files\Microsoft Visual Studio\VC98\myprojects\PluggableFactory\plug.cpp(44) : error C2273: ''function-style cast'' : illegal as right side of ''->'' operator C:\Program Files\Microsoft Visual Studio\VC98\myprojects\PluggableFactory\plug.cpp(44) : error C2227: left of ''->MessageData'' must point to class/struct/union C:\Program Files\Microsoft Visual Studio\VC98\myprojects\PluggableFactory\plug.cpp(94) : error C2143: syntax error : missing '';'' before ''->'' C:\Program Files\Microsoft Visual Studio\VC98\myprojects\PluggableFactory\plug.cpp(94) : error C2143: syntax error : missing '';'' before ''->'' Source:
    
#pragma warning(disable:4786)

#include <iostream>
#include <string>
#include <map>
#include <vector>

using namespace std;

typedef vector<unsigned char> ByteArray;

enum MESSAGEID
{
	TEXT_MESSAGE,
	MESSAGE_QUIT,
	MESSAGE_MOVEMENT1
};


//Base factory class. All other classes derive from this simple so they can use the 

// IDRegistry.

class BaseMessageClass
{
public:
protected:
	void RegisterID(MESSAGEID messageid)
	{
		IDRegistry.insert(IDRegistryMap::value_type((unsigned char)messageid,this));
	}

	typedef map<unsigned char,	BaseMessageClass*> IDRegistryMap;
	static IDRegistryMap		IDRegistry;
};

//Publicly used switch to select a class based on messageID in ByteArray. Feed it the 

//ByteArray and It''ll make sure that the data is passed to the right function for 

//processing

class CMessageSwitch : public BaseMessageClass
{
public:
	void Switch(ByteArray &Data)
	{
		BaseMessageClass *TheMessageHandler = (*IDRegistryMap.find((unsigned char)Data[0])).second;
		TheMessageHandler->BaseMessageClass->MessageData(Data);
	}
};

//Standard maker class.

//Typical class for registering and making the ''processing'' class. Destroying this 

//will also destroy the class it makes. This should probably be a template but how?

class QuitMessageMaker : public BaseMessageClass
{
public:
	QuitMessageMaker()
	{
		BaseMessageClass *DestinationClass = new QuitMessage();
		RegisterID(MESSAGE_QUIT);
	}
	~QuitMessageMaker()
	{
		delete(DestinationClass);
		//Should probably remove from registry here too

	}
	BaseMessageClass *DestinationClass;
private :
	static void dummy(void);
	
};

//Actual worker class

class QuitMessage : public BaseMessageClass
{
public :
	void MessageData(ByteArray &Data)
	{
	  MessageBox(NULL, "", "Data recieved", MB_OK | MB_ICONSTOP);
	}
};





void main(void)
{
	//Make some dummy data

	ByteArray blah;
	blah[0] = MESSAGE_QUIT;
	blah[1] = 1;
	blah[2] = 2;
	blah[2] = 3;

	CMessageSwitch *MessageSwitch = new CMessageSwitch;
	CMessageSwitch->Switch(&blah);
	
}


    
Chris Brodie
Advertisement
Forgive me if my source tags do not turn out right.

Try this line

        BaseMessageClass *TheMessageHandler = *(IDRegistryMap.find((unsigned char)Data[0]).second);        


The derefrence operator has a wierd order of operations so I always put () around things that have it.

Your other error is you are using your class name instead of the variable you just created. Use MessageSwitch instead of CMessageSwitch.


Another problem you might have is you have blah[2]= 2 and blah[2]=3. I think you want to change that.


Hope this helps...
Eck

P.S.
Pluggable factories are so freaking cool.

Edited by - Eck on July 14, 2000 12:13:01 PM

EckTech Games - Games and Unity Assets I'm working on
Still Flying - My GameDev journal
The Shilwulf Dynasty - Campaign notes for my Rogue Trader RPG

Could someone enlighten me on what pluggable factories are?
I'm reminded of the day my daughter came in, looked over my shoulder at some Perl 4 code, and said, "What is that, swearing?" - Larry Wall
Thanks Eck... I''ll have to reread my code...

Muzzafarath : Imagine your writing your console handler. You have like 150 commands planned. You start by creating a base command class the derive the 150 class from that. The base class has a lookup feature that''ll point you to the right command class. So far all good. The lookup feature is most likely a big switch statment. Everytime you add a new command you go in to the switch add a few extra lines, small effoert bu there is always a small right that you might forget the break or something, mess something up, potentially with unknown consequences. If you delete a class or rename it, again in to the switch for more maintanence

Pluggable factories work on the idea of self registering classes. The big switch''ish statment is built dynamically from a map. As each class is started they place a reference to themselves in the map along with the lookup code\string\whatever. Here is where we get tricky, apparently static class members force object creation at app start(before main is called). using this you add a dummy entry to the class and chain of inheritance causes the creation of the map etc. So, at program start we have a fully populated map of tokens to object pointers.

So this method is cool because to make a new console command you just add the code, deriving from the right class and presto the code at runtime knows to register your new command in the map.

NO STATIC LINKING... kind of magical don''t you think.

I love it(onc I get it working which I''m probably right on top of. I''ll be using it for a console interpreter, network message handler, subsystem loader (file,protocols,input,video,sound) etc.

Very cool stuff... There is an article in gamedev on it.

gimp


Chris Brodie
Actually, I think that the dereference operator is in the right place (it's just crazy stl syntax), but here are the problems that do I see:

1) In the line with compile errors, you should be using "IDRegistry.find" instead of "IDRegistryMap.find". IDRegistryMap is the type and IDRegistry is an instance of the type. You may also want to break it up into multiple lines to ensure that the "find" call actually returns something valid (ie. not equal to IDRegistry.end())

2) The following line (TheMessageHandler->BaseMessageClass->MessageData(Data)) also has some problems. First, "->BaseMessageClass" doesn't make any sense, because TheMessageHandler is of type BaseMessageClass. Second, you probably can't call the MessageData() function at all since it's not a member of the BaseMessageClass class. You probably want to add MessageData() as pure virtual public method to your BaseMessageClass class. Something like:
void MessageData(ByteArray &Data) = 0;  


3) The vector class doesn't dynamically expand when you index into it using the [] operator, so your main function also has a bug. You either want to instatiate "blah" with a non-zero size (ByteArray blah(4)) or add your data using the vector insertion methods (blah.push_back(MESSAGE_QUIT)), or both.

Hope this helps.
...Syzygy

Edited by - syzygy on July 14, 2000 10:29:43 PM
The article for pluggable factories can be found at

http://www.gamedev.net/reference/programming/features/factories/

There are some really powerful tricks that he uses. So do not be upset if you do not understand right away. There is also a bad link to another site in the article. But you can go to the main site and search for it in the archives.


Have fun,
Eck

Edited by - Eck on July 17, 2000 10:29:36 AM

EckTech Games - Games and Unity Assets I'm working on
Still Flying - My GameDev journal
The Shilwulf Dynasty - Campaign notes for my Rogue Trader RPG

Wow! Just checked out that article, however,
when I tried the first example I am getting an
unresolved external symbol on linking involving
the static map, registry.
Am I missing something?
This is pretty interesting stuff, any help
would be greatly appreciated!

Thanks in advance!

I put comments next to the places the compiler is griping..
    class Shape{   public:      int Center;};class Circle : public Shape{   public:      Circle();};class ShapeMaker{   public:      ShapeMaker( const string& ClassName );      static Shape* newShape( string className );         protected:      virtual Shape* makeShape( ) const = 0;         private:      typedef map< string, ShapeMaker*> MakerMap;      static MakerMap registry;};class CircleMaker : public ShapeMaker{   public:         private:      CircleMaker() : ShapeMaker( "Circle" ) {}      static const CircleMaker registerThis;      Shape* makeShape( ) const      {         return new Circle;      }};ShapeMaker::ShapeMaker( const string& ClassName ){   //********* RIGHT HERE, registry is unresolved   registry.insert( make_pair( ClassName, this ) );   printf( "> ShapeMaker::ShapeMaker: %s\n", ClassName );}Shape* ShapeMaker::newShape( string className ){   ShapeMaker* maker = NULL;   //********* HERE TOO, registry is unresolved   maker = (*registry.find( className)).second;   return maker->makeShape( );}    


Thanks!

Edited by - taulin on July 18, 2000 12:05:57 PM
If it's any help, here's the factory class I use for creating objects. It's a template so it'll work with any types (you'll probably have to edit this post to see the code properly).

#include #include ////////////////////////////////////////////////////////////////////////template factory class for creating TFactProduct pointers//any class that wants to created by this factory should register //an id and a creation function. Use CFactoryRegister to make registration easiertemplateclass CFactory  {	//singleton implementation	CFactory(){}	CFactory(const CFactory&){}public:	typedef TFactProduct* (*CreationFunc)(); //the creation function	//access to the only instance of this type	static CFactory& Instance()	{		static CFactory factory;		return factory;	}	//call this to register a type with the factory	void RegisterType(IDType id, CreationFunc pfn)	{		//Is the id already in the map?		assert(m_factMap.find(id) == m_factMap.end());		//add this id and function		m_factMap.insert(make_pair(id, pfn));	}	// this function finds 'id' in the map, then returns a 	// pointer to the type created by the CreationFunc registered with this id	TFactProduct* NewInstance(IDType id) const	{		//try and find the id in the map		MapIt it = m_factMap.find(id);		//if it wasn't found, we want to know about it		assert(it != m_factMap.end()); //trying to create a type that isn't registered?				//if it worked, call the creation function and return the result		if(it != m_factMap.end())		{			return it->second();		}		else		{			return NULL;		}	}private:	typedef std::map::const_iterator MapIt;	std::map m_factMap;};////////////////////////////////////////////////////////////////////////template class to make registering with the factory easier//any class that wants to register with the factory should container a //const static CFactoryRegister with the correct template parameters.//Each class should pass its id to the ctor of this class to correctly //register with the factorytemplate >class CFactoryRegister  {public:	CFactoryRegister(IDType id)	{		//register this id for type 'TRealType' with the factory. We'll use the static NewInstance		//as the creation function for the factory		TFact::Instance().RegisterType(id, CFactoryRegister::NewInstance);	}	static TBaseType* NewInstance()	{		return new TRealType;	}};  


Then to register a class with the factory, you'd do somthing like

class CShotgun : public CWeapon  {public:	CShotgun();	virtual ~CShotgun();//rest of class...private:	const static CFactoryRegister m_Register;};const CFactoryRegister CShotgun::m_Register(2)  


Obviously you'd have some sort of system for the factory id entry. Maybe the class name with std::string as the id type, then you don't have to worry about having the id in use by something else.

The to create a CRocket object, you'd do something like:

CWeapon* pWeapon = CFactory::Instance().NewInstance(2); 


or if you had used std::string with the class name:

CWeapon* pWeapon = CFactory::Instance().NewInstance("CRocket"); 


You'd probably want to make a typedef for weapon factory. I just noticed that Instance().NewInstance looks a bit odd, I should probably have called NewInstance something else but I can't be bothered to change it now

Edited by - Wilka on July 18, 2000 1:53:43 PM
The problem I am having with the code found in
all the articles, the static const of the class
the supposed to force a constructor call.

For example a small class:

    class Stupid : public Base{   private:      Stupid( ) : Base()      {         printf( "> Stupid constructor\n" );         Number++;      }      static const Stupid registerThis;};    


By having that static const in there, all the articles
say it supposed to call the constructor of Stupid.
However, it never gets called for me.

Am I missing something here?
Wilka:

This code should give you the name of the class at run-time (but only from within a member function), so you are assured of no typos:


typeid(*this).name();



(Of course, you have to enable RTTI in the project settings or a compiler flag.)



- null_pointer
Sabre Multimedia

This topic is closed to new replies.

Advertisement