Game Development Community

Exposing singletons to script

by Michael Perry · in Torque Game Engine · 01/10/2007 (8:02 am) · 9 replies

Hello all. I've written a singleton class in the engine, which I need access to in the script.

I read through this thread: Creating a SimObject(singleton)

While the approach mentioned in the thread (making the constructor public) might work, it does break a critical rule for singleton design.


Singleton Factory Engine Code:
//////////////////////////
// Factory.h file
class CFactory: public SimObject
{
	typedef SimObject    Parent;

private:
	static CFactory* _instance;
	
	CFactory();
	CFactory(const CFactory& obj);
	CFactory& operator=(const CFactory& obj);
	~CFactory();

public:
	
	static CFactory* Instance();
	void Destroy();

	DECLARE_CONOBJECT(CFactory);
};


//////////////////////////
// Factory.cpp file
#include "console/consoleTypes.h"
#include "game/Factory.h"
CFactory* CFactory::_instance = 0;

IMPLEMENT_CONOBJECT(CFactory);

// Singleton accessor
CFactory* CFactory::Instance()
{
	if(_instance == 0)
		_instance = new CFactory;

	return _instance;
}

// Singleton destroy
void CFactory::Destroy()
{
	if(_instance)
	{
		delete _instance;
		_instance = 0;
	}
}
// Construction/Destruction
CFactory::CFactory() 
{

}
CFactory::~CFactory()
{
	Destroy();
}

Here is my end-goal for script:
function foo()
{
      $Factory = GetFactoryInstance();
      $Factory.pauseFactory();

       //----  OR ----

      %factory = CFactory::Instance();
      %factory.pauseFactory();
}

The above code generates an error in the engine, because the constructor is a private member. So, anyone have any suggestions? Thanks in advance.

#1
01/10/2007 (8:05 am)
You haven't shown all of the code, so I can't see if you've made any mistakes in there. You need your ConsoleMethod to access a public function inside the class, which then gets access to the private member.
#2
01/10/2007 (8:12 am)
Thanks for the quick reply Stefan. Check the code again, because it's all there. I'll post the error to clarify:

Error	1	error C2248: 'CFactory::CFactory' : cannot access private member declared in class 'CFactory'

This is referring to the following two commands:
DECLARE_CONOBJECT(PDUProcessor);
IMPLEMENT_CONOBJECT(PDUProcessor);

These two require that the constructor for CFactory to be public, which breaks a singleton design rule

*EDIT* - Or does it? The solution found in the link in my first post does correct the compiler error, but does it maintain a true singleton? Any software architecture gurus out there?

*EDIT2* - I'm also aware that I can choose to not expose the actual class as a console object, and just manipulate it directly through ConsoleFunctions calling static member functions. This would maintain a C++ true singleton, and allow manipulation of the class. However, it doesn't expose the actual class as a console object. Just thought that was worthy of noting.
#3
01/10/2007 (8:30 am)
Sorry, I still can't see any of your ConsoleMethods. Doesn't matter though, I completely misunderstood what you were doing with the singletons. I have no clue what the defination of that is, so good luck. :)
#4
01/10/2007 (8:38 am)
I'll rephrase: all the code that pertains to this matter is posted. ConsoleMethods representing the script examples are not shown, because they do not exist. We can ignore that part.

Quote:
The whole idea of the singleton is to only allow an instance of it to be created through a call to one of its static member functions. This static member function also checks to see if an instance of the object already exists, and if it does it returns that instance without creating a new one.

-- Todd Scott Original Post

Singleton Design Pattern
#5
01/10/2007 (8:43 am)
Well, it would certainly not be a textbook singleton, but the only true rule for a singleton to be a singleton, is that any object that access the singleton accesses the same instance of the class. You could have a public constructor for the class, and as long as all code that access it access it through the getInstance, or similar, you still have a singleton.

The point of making the constructor private, is to simply not allow you to create an instance by accident, or because you didn't know the class was a singleton...

Now the only issue I can see that you may run into, is if the IMPLEMENT_CONOBJECT macro is creating a separate instance... My guess, would be that you would simply find that the first time you call getInstance, there is already one there, created via the macro.

I think if you make the constructor public, and then simply ensure you only every access the object from the getInstance, you should be fine, and you should have a singleton class, even if a CS professor would take points off the implementation...

In the end, this is game programming, and sometimes you have to throw a little mud on the ivory tower to get things working correctly.
#6
01/10/2007 (8:49 am)
Ah, so it was only an example. Sorry.

I don't know why you explicitly want a singleton object, I would just go with a global pointer and be done with it, much like netInterface/lightManager does, but I guess if you really want to make sure only one of these classes can be instantiated, then it makes sense.
#7
01/10/2007 (9:05 am)
I may not be following this thread correctly,
but i was able to successfully expose a singleton to script by calling registerObject() when the object is instanciated, and unregisterObject() right before it's destroyed.
my object is created and destroyed in a similar fashion to yours: with a static global pointer initialized to NULL.

so, something like this should result in a script-exposed object "TheCFactory":
// Singleton accessor
CFactory* CFactory::Instance()
{
   if(_instance == 0)
   {
      _instance = new CFactory;
      _instance->registerObject("TheCFactory");
   }
   
   return _instance;
}

// Singleton destroy
void CFactory::Destroy()
{
   if(_instance)
   {
      _instance->unregisterObject();
      delete _instance;
      _instance = 0;
   }
}
#8
01/10/2007 (9:14 am)
That's interesting Orion, I'll have to give this a shot.

I've figured out how to maintain the "true" singleton pattern for my usage, and still have script functionality (just not script instantiation of CFactory). I think this approach will work better for this particular class in the long run, anyway. With that being said, this has the potential to become an interesting topic of discussion.

Valid points Eric. I've already solved my problem of implementation, so now that leaves the rest of this thread up for discussion on the design pattern of the singleton. The best thing you said is:

Quote:
In the end, this is game programming, and sometimes you have to throw a little mud on the ivory tower to get things working correctly.


Textbooks are re-written to accommodate changes to a lesson, right? Well, can you spot the major changes in technology between Doom and Doom 3? My favorite change is that Doom 3 takes advantage of a scripting system. Any game or engine worth its weight, should have a script engine/system. A lot of script languages resemble C++, but follow different sets of rules, making a universal design pattern nearly impossible to develop.

At this point, should software architecture and design patterns become more lenient in terms of rules? Take the solution Todd came up with in the original post. Singleton functionality exists, and that is undeniable. The only change is that instead of throwing a compiler error at a C++ programmer, we are slamming him with a fatal assertion that more or less says "Hey stupid. This class follows the singleton design pattern, so don't instantiate me, Instance() me!"
#9
01/29/2007 (8:07 am)
Found this article recently while searching for something completely unrelated:
How to build a singleton SimObject

I haven't implemented it yet, but it looks like good code and reading material.