Object factories and differing ctor signatures (continued)

Started by
4 comments, last by JimPrice 19 years, 3 months ago
I'm rewriting a lot of code in an attempt to make it more modular. One of the things I'm playing with is an object factory that manages my game entities. The basic signature is something like this:

#pragma once

#include <map>
#include <string>

#include <boost/shared_ptr.hpp>
#include <singleton.h>

class Entity;

class SingletonEntityFactory
{
public:
	typedef boost::shared_ptr<Entity> (*CreateNewEntityCallback)();

public:
	SingletonEntityFactory(void);
	~SingletonEntityFactory(void);

	bool RegisterEntityType(std::string whichEntity, CreateNewEntityCallback newEntityFn);
	boost::shared_ptr<Entity> RequestEntity(const std::string& whichEntity);

private:
	typedef std::map<std::string, CreateNewEntityCallback> theAvailableEntitiesTypedef_;
	theAvailableEntitiesTypedef_ theAvailableEntities_;
};


typedef Loki::SingletonHolder<SingletonEntityFactory> EntityFactory;


New entities register themselves with the object factory, along with a calling string. The calling string is then used to create a new version of that entity. So far, so good. The problem comes when entities have differing constructor signatures. For example, here are 3 entities derived from the entity base class: EntityPlayer : requires location (D3DXVECTOR2) EntityBuilding : requires std::vector<std::pair<D3DXVECTOR2, D3DXVECTOR2> > to represent building walls (this is a top-down simply drawn demo program) EntityHotspot : requires text (std::string) and a callback function (void (*func)() ) used when the text is clicked. The question, therefore, is as follows: How do I build these different signatures into the object factory? I have thought of some solutions, but all seem a little ugly. For example: Possible solution 1. I could change the profile of the callback function in the code above to: typedef boost::shared_ptr<Entity> (*CreateNewEntityCallback)(std::vector<boost::any>); I'd have to remember what form the ctor takes to populate the vector (although I have to know the string name for the entity already), and I suspect it may not be brilliantly efficient. Possible solution 2. Use dynamic casting when creating the entity, like so: boost::shared_ptr<Entity> thisPtr = entityManager_.PushNewEntity("Hotspot"); boost::shared_ptr<EntityHotspot> newPtr = boost::dynamic_pointer_cast<EntityHotspot>(thisPtr); newPtr->Details("This is a hotspot", PathFindCallback); This uses dyanamic casting, and promptly sticks all the compile time dependencies back in. Urgh. Possible solution 3. Use ellipses in the callback function profile: typedef boost::shared_ptr<Entity> (*CreateNewEntityCallback)(...); I'm not even sure if you can do this, and what the pros and cons are; I've never really touched va_agrs and its budies. So - any advice? Is there a better way? Is there a preferred method above? Thanks, Jim. [Edited by - JimPrice on January 12, 2005 8:21:14 PM]
Advertisement
I think the cleanest thing would be to hardcode all entity constructors - i.e. screw the registerEntityType stuff and just write all the functions, like createEntityPlayer(location).

If your project isn't HUGE, the flexibility gained by the registering stuff won't matter.
---Just trying to be helpful.Sebastian Beschkehttp://randomz.heim.at/
What about using a base class for the parameters of Entity subclasses?
I mean you add CEntityParameters class as a mother class, from which you derive CEntityPlayerPar, CEntityBuildingPar and so on.
You'll call:

CEntityPlayerPar par(location);
EntityFactory.RequestEntity("EntityPlayer",&par);

So RequestEntity has (always) a pointer to CEntityParameters as second parameter (hiding the differences among the parameters needed by Entity suclasses).
RequestEntity will search for "EntityPlayer" to find the corresponding CreateNewEntityCallback function. Then, it will extract location from par (it knows is of CEntityPlayerPar type) and will supply this info to the constructor of CEntityPlayer new object.

I hope you see what I mean.
Fil (il genio)
Thanks for the replies.

Quote:
I think the cleanest thing would be to hardcode all entity constructors - i.e. screw the registerEntityType stuff and just write all the functions, like createEntityPlayer(location).


Agree entirely; using this as a learning experience though, and once I'd encountered the issue I wanted to solve it, instead of just leaving it. Even if I never use it, it's another snippet of C++ learnt.

Quote:
What about using a base class for the parameters of Entity subclasses?
I mean you add CEntityParameters class as a mother class, from which you derive CEntityPlayerPar, CEntityBuildingPar and so on.


I like this. I'd actually thought of replacing callback functions with functors while I was in the gym this morning; this ends up with a similar solution to yours. In fact, I think MC++D mentions using functors at one point, although not in this specific context.

Thanks all the comments.
Jim.
Hi,

Playing with the Loki Functor implementation, as I attempt to solve the above problem. The readme documentation says:

If you use the small object allocator directly or indirectly (through the Functor class) you must add SmallObj.cpp to your project/makefile.

Er - how do you do this? If I just #include <smallobj.cpp>, like so:

#include <iostream>#include <Functor.h>#include <smallobj.cpp>int main(){	std::cin.get();	return 0;}


I get all sorts of messages like:

c:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\include\algorithm(1187): error C2784: 'bool std::operator <(const std::vector<_Ty,_Alloc> &,const std::vector<_Ty,_Alloc> &)' : could not deduce template argument for 'const std::vector<_Ty,_Ax> &' from 'std::allocator<_Ty>::value_type'
with
[
_Ty=Loki::FixedAllocator
]

I assume that adding something to the makefile means something a little different - but, I'll be honest - I'm not sure what, or how to do it.

Using VS.Net2003.

Thanks,
Jim.
Don't worry - found out how to do it. Obvious really!

Jim.

This topic is closed to new replies.

Advertisement