object-oriented 2d game engine design problem =/

Started by
8 comments, last by shurcool 21 years, 8 months ago
i ran in quite a serious design problem with my object-oriented 2d game engine. i already know 2 solutions to it, but neither one is perfect (still hoping someone can suggest one that is ), and i can't figure out which one to use. this is the basic structure for my oo game engine. it's pretty self-explanatory (i hope ), just read the legend in the top right corner. edit: replaced with a gif. anyway, i will now try to explain this problem. as you can see, CRoot is the main class (a singleton), and it has access to everything. you must delete each singleton class manually if you don't want a memory leak, but CRoot does that for you. it registers a static function (CRoot::DestroySingleton(), which deletes the singleton instance) inside CRoot ctor to be called on exit, using 'atexit(DestroySingleton);'. in the dtor of CRoot i have calls to each of the manager's DestroySingleton() function. of course, each manager's dtor cleans up after itself (e.g. CLogManager closes all opened CLog's). that way all singletons are automatically deleted on exit. CRoot is constructed when you first call CRoot::GetSingleton() (which is also used to return a pointer to the singleton). i want the user to be able to control everything, so if he doesn't want a default log (all the major wwEngine events get logged there), then there shouln't be one. if he does want it, he'll call 'CRoot::GetSingleton()->GetLogManager()->CreateDefaultLog(/* ...* /);' note that CRoot::GetLogManager() returns 'CLogManager::GetSingleton()'. the problem, as you might already have noticed, that CRoot will be constructed first, and only then it will create the default log (if one doesn't exist, all attempts to log a message to it will be ignored). thus any attempt to log something in CRoot ctor (which is supposed to initialize some stuff, for example some libraries i'm using) will fail. now, on to the 2 solutions i came up with. 1. instead of doing all the init in CRoot ctor, move all the init code inside a function (called Init()), which the user should call after he creates (or not) the default log. this is bad because i'd need to somehow check if the user did not call CRoot::Init(), and by 'standards', all the init code should be in a class'es ctor. 2. since my program includes CRoot's header (#include "CRoot.h"), which includes CLogManager's header, my program can do this:
int main(int argc, char **argv)
{
	using namespace wwE;

	// i use exceptions so some try {} catch () {} stuff is also in here
	// i omitted it to make things simpler

	CLogManager::GetSingleton()->CreateDefaultLog(/* ... */);

	CRoot*	pRoot = CRoot::GetSingleton();	// this line creates an instance of CRoot,
	// and its ctor will log anything it wants to successfully,
	// because the default log is already created

	// the rest of the code goes here

	return 0;
} 
the problem with this is that it makes me wonder the whole purpose of CRoot::GetSingleton()->GetSomeManager(), if you can just call CSomeManager::GetSingleton(). so i don't know what's better. =/ if anyone can suggest another solution, which is better than the two above, or help me decide on what to do, i would really appreciate it! thank you very much. ps. sorry for a long post. ps2. singleton is a class that only has one instance of it at all times (no more). to get a pointer to it, you call CClassName::GetSingleton() (well, in my implentation anyway). ps3. ctor == constructor; dtor == destructor. ---
shurcool
wwdev
[edited by - shurcool on August 13, 2002 6:17:47 PM]
Advertisement
i knew this would happen. my post is too long, and no one wants to read it...

---
shurcool
wwdev
I read it, but I can''t understand why you want to use a 100% oop structure like that. I mean, derive EVERYTHING from croot? Thats just a bit too much (for me at least) and to be honost an exception handler and a logmanager are the last two things I would worry about; I would get the graphics and input going first then worry about the other. I just can''t imagine a 2d game being that complicated; or, I would never want to write a 2d game as complicated as your making this one.

But I''m all about doing what you want; I can see how the challenge of doing it this way would be fun, personally i''m not that advance yet.

I''ve noticed these boards aren''t getting as much attention as they used to lately... maybe thats due to the overall lack of updates and content.
I''d go with solution number 2. I agree with your assessment--I don''t see what the point of doing CRoot::GetSingleton()->GetSomeManager() as opposed to just CSomeManager::GetSingleton() is. If I were you I''d just get rid of the GetSomeManager function and simply use the GetSingleton method of whatever manager you want. The only reason coming to mind right now as to why this might be bad is if you need to explicitly control the order of creation of the singleton; for example, if someone calling CExceptionManager::GetSingleton without having called CRoot::GetSingleton would cause some sort of major problem. In this case, I think what I would do is to create some kind of buffer of log messages made before a call to CreateDefaultLog, and then when CreateDefaultLog is called dump those messages into it, or just discard them if it''s never called. Anyway, that''s my $0.02, hope it helps a little.

--Matt
NONE of my classes shown the diagram are derived from anything (so far)! CRoot is there just to provide access to all managers, which provide access to whatever they manage. for example CTextureManager would allow for loading, using, and freeing textures (when it''s destroyed, it will auto free any unclosed textures). also, CRoot is used to destroy all singleton classes, so that the user wouldn''t have to worry about that (calling each CSomeManager::DestroySingleton()).

i really hope that clears it up. if there''s anything you don''t really understand (about my original post, or this), please post about it here, and i''ll try to explain it better (in less words too ).

ps. nonnus29, thanks for reading my long post.

---
shurcool
wwdev
Your complications arise from the subtleties of singletons.
Either don''t use singletons, and set-up a well-defined order of construction & destruction for the global resources you use; or get a heavy weight singleton implementation (Loki has one, and Boost may have something as well).

My recommendation is not to use singletons. They are far more complicated than simple global holes, and do essentially the same thing.

If you really want to use singletons, Loki was recently ported to MSVC7 - it also compiles correctly on gcc 3.1.x & Comdeau (all other compilers have issues, but a BCB6 one is in progress).

You could also google for phoenix singleton, I believe that type does what you want.


...
nonnus29, those are collaboration lines, not IsA relationships.


Magmai Kai Holmlor

"Oh, like you''ve never written buggy code" - Lee

[Look for information | GDNet Start Here | GDNet Search Tool | GDNet FAQ | MSDN RTF[L] | SGI STL Docs | STFW | Asking Smart Questions ]

[Free C++ Libraries | Boost | ACE | Loki | MTL | Blitz++ | wxWindows| Spirit(xBNF)]
- The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara
quote:Original post by pistonmstr
In this case, I think what I would do is to create some kind of buffer of log messages made before a call to CreateDefaultLog, and then when CreateDefaultLog is called dump those messages into it, or just discard them if it''s never called.


that''s quite an intresting idea, but i see a big problem with it. what if CreateDefaultLog is never called, then too much memory will be wasted on buffering all the logged messages, which will be ignored in the end anyway. perhaps a MAX_BUFFERED_LOG_MESSAGES? or why not just call CLogManager::GetSingleton()->CreateDefaultLog() at the very top of my program? works well too!

quote:Anyway, that''s my $0.02, hope it helps a little.


i think that order doesn''t really matter to me. well, so far, anyway. i''ll see how that works out, and possibily will do as you suggested. thanks for your reply.

---
shurcool
wwdev
quote:Original post by shurcool
CRoot is there just to provide access to all managers, which provide access to whatever they manage.

But why? Why can't you just access a manager without having to go via CRoot?
quote:
CRoot is used to destroy all singleton classes

Again, why? What is the real reason for CRoot's existence. What is the significance of the name "CRoot"?
quote:
so that the user wouldn't have to worry about that

The user doesn't have to worry about cleaning up singleton classes. For example, try this code:

    #include <iostream>#include <assert.h>class Singleton{public:	static Singleton& instance()	{		static Singleton theInstance;		return theInstance;	}	~Singleton()	{		std::cout << "~Singleton()" << std::endl;	}private:	Singleton()	{		std::cout << "Singleton()" << std::endl;	}};int main(){	Singleton& ref1 = Singleton::instance();	Singleton& ref2 = Singleton::instance();	assert(&ref1 == &ref2);}  

This will delete itself at program shut-down.

[edited by - SabreMan on August 14, 2002 12:42:04 PM]
quote:Original post by Magmai Kai Holmlor
Your complications arise from the subtleties of singletons.
Either don''t use singletons, and set-up a well-defined order of construction & destruction for the global resources you use; or get a heavy weight singleton implementation (Loki has one, and Boost may have something as well).


i think you may be right. there''s no real need for all managers to be singletons, i could just create single instance of each one, in any order i want (meaning i have control over order of creation), and GetSomeManager() will return m_pSomeManager. that seems to limit my ''original problem resolution'' to only one way - CRoot::GetSingleton()->GetSomeManager() would be the only way to get a manager. but i can''t log things such as ''CTextureManager instance created sucessfully'' or ''glfwInit() returned success'' (a library i''m using needs that function to be called before any other).

what are your thoughts on that?

meanwhile, i will take a look at these heavy weight singleton implementation of yours. thanks.

---
shurcool
wwdev
quote:Original post by SabreMan
But why? Why can''t you just access a manager without having to go via CRoot?


like i suggested in my ''solution #2'', yes, i can access a manager without CRoot.

quote:Again, why? What is the real reason for CRoot''s existence. What is the significance of the name "CRoot"?


i just though it would work well that way, a class which would init any sub components (such as any libraries that would need an Init() function to be called before any other, and Terminate() to clean up) on its init and destroy them on exit.

quote:The user doesn''t have to worry about cleaning up singleton classes. For example, try this code: ...


i understand that. trust me, i''ve read a lot about singletons, tried many many different implentations, and decided that the ''CRoot -> everything else'' structure would give me most control over everything (especially order of creation/destruction). i''ve tried (as in wrote actual code, tested it, and thought how good the implentation is for the engine) lots of different things before i came to this conclusion (but now i begin to doubt it again ).

thanks for all the input guys!

---
shurcool
wwdev

This topic is closed to new replies.

Advertisement