Archived

This topic is now archived and is closed to further replies.

Pluggable Factories (allllmost there..)

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

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);
	
}


    

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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


Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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 easier
template
class 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 factory
template >
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

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
null_pointer:

The problem with typeid is that I need to have an instance of the type already. I normally use a virtual Clone function (or NewInstance) when I create a new instance of a type that I already have. I only use the factory when I don''t have a prototype for the object I want.

Share this post


Link to post
Share on other sites
Wilka:

Can''t you access the template parameter? You can use typeid on the class name, not just an instance of the class. Requiring an instance of the class is what you are trying to avoid with templates anyway, right?


template <typename command_type>
class command_allocator
{
public:
command_type* allocate()
{ return new command_type; }

string identifier()
{ return string(typeid(command_type).name()); }
};



I''m not sure if this would work correctly as a factory allocator (IMHO, class factories are irritating and used too often to cover bad design, but not always ).

Of course, the allocate function allocates a new object, and the identifier (or call it ID if you like) just returns the class name.

Another thing you could do is this:


#pragma warning(disable: 4786)

#include <map>
#include <string>

namespace creator
{

template <typename base_class>
class basic_allocator
{
public:
virtual base_class* allocate() = 0;
virtual std::string identifier() = 0;
};

template <typename command_class, typename base_class>
class allocator
: public basic_allocator<base_class>
{
public:
virtual base_class* allocate() { return (base_class*) new command_class; };
virtual std::string identifier() { return std::string(typeid(command_class).name()); }
};

template <typename base_class>
class factory
{
public: // make this protected later
std::map< std::string, creator::basic_allocator< base_class >* > allocators;

public:
base_class* create(const std::string& identifier)
{ return allocators[identifier].allocate(); }

void add(basic_allocator* allocator)
{ allocators.insert(std::map< std::string, creator::basic_allocator< base_class >* >::value_type(allocator->identifier(), allocator)); }
};

}; // end namespace creator

// example of how to use it
class command {};

class myclass
: public command
{
};

int main(int argc, char* argv[])
{
creator::allocator<myclass, command> the_allocator;
creator::factory<command> the_factory;

the_factory.add(&the_allocator);

// try allocating an object using the class name
delete( the_factory.allocators[typeid(myclass).name()]->allocate() );

return 0;
}



The reason for the derivation would be that the creator of the allocator object will know both the abstract base and the derived type in use, and so he will use the allocator class. However, to actually use those allocators together with objects of the same abstract base but different derived types, you need an abstract allocator class which only needs the type. So, you create a specific allocator that can generate the proper unique ID, and register it as a generic allocator. Kind of neat.

You also need some kind of registration, which could be done before startup (as per the article?) or inside the factory::creat() function and the identifier isn''t in the list of allocators. I just used a simple add() function to take care of it, for the example.

(Believe it or not, I just coded up that little set of classes in about 15 minutes!! I spent more than an hour debugging it (because of VC''s template support), but it works now in VC 6.0. I couldn''t use typedef in the factory class because of the same stupid template errors... heh heh not bad for a first try! Of course, the string/name lookup thing is kind of slow, but I guess it could be optimized...

Since you are going to access the class with a const char*, you really don''t need the string constructor being called. Then, perhaps a STL hash_map or something similar would speed it up to the level of integer IDs? I''m kind of new to those things, so don''t act surprised if I just made a joke! )




- null_pointer
Sabre Multimedia

Share this post


Link to post
Share on other sites
I though you meant use typeid when calling the NewInstace function on the factory, not inside the factory . If I''m using std::map I cant use char* for the key because there isn''t an < for char* (unless there''s a specialization of less<> for it, but I haven''t checked). If I want a faster lookup I can change the id type to an int (hence the template id type), and it''s been fine for everything I''ve used for so far. I''d probably switch to a vector instead of hash_map if it was going to slow (I don''t have a hash_map at the mo).

Share this post


Link to post
Share on other sites
Wilka:

heh heh I love templates!


gimp:

I guess this wasn''t too helpful for those errors...

However, because you are doing tons of things based on the types, and because so much redundant code is generated for each new message, templates make an ideal solution. Besides, without templates, all of the dynamic creation code spills into your message classes, which makes them harder to understand.

Think about it: someday down the road you will want to make another factory for a different set of classes. When you go to do that, you can take a templated class set like mine or Wilka''s (which seems a bit better ) and then just write your new classes how you please and they''ll work fine.

Advantages: In order to derive your own hierarchy for your new class set, you don''t have to keep copying and pasting all of the dynamic creation code into each new class. Also, you don''t have to modify the templated classes, so there is no chance of introducing new bugs.

Happy Coding!




- null_pointer
Sabre Multimedia

Share this post


Link to post
Share on other sites