Jump to content

  • Log In with Google      Sign In   
  • Create Account

FREE SOFTWARE GIVEAWAY

We have 4 x Pro Licences (valued at $59 each) for 2d modular animation software Spriter to give away in this Thursday's GDNet Direct email newsletter.


Read more in this forum topic or make sure you're signed up (from the right-hand sidebar on the homepage) and read Thursday's newsletter to get in the running!


Structure of classes in good Game Engine?


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
46 replies to this topic

#1 Konrad Jablonski   Members   -  Reputation: 483

Like
0Likes
Like

Posted 18 June 2012 - 01:46 PM

Hello everyone :)

Well I'm making a game with a friend, and we're having small problem with organizing our classes.
What we're doing is having main engine class, which creates all classes we use in it, then we pass the
classes into constructor that we want to use.. eg:
[source lang="cpp"]gameWorld = new ChunkManager(LogSys, gfxSystem);gameCamera = new Camera(ScreenWidth, ScreenHeight, 0.0f, 0.0f, 0.0f, SDL_Key, events,joystick1, LogSys);Time = new GlobalTime(LogSys);Menu = new SDL_Menu(this,gameCamera,events,Time,LogSys);[/source]
The thing is, we pretty much pass in LogSys class to EVERYTHING!
Is there any easier way to distribute it to pretty much everything?
I was thinking of inheriting it to everything, but i heard inheritance is bad in some cases to game engines..?


Well thanks for help in advance, cant wait to see the answers! :)

Sponsor:

#2 MajorTom   Members   -  Reputation: 715

Like
6Likes
Like

Posted 18 June 2012 - 02:17 PM

If it's used everywhere, make it global. Some people fight to the death to ensure there are no globals in the code... but sometimes, globals are necessary, and it keeps the code interface nice and tidy. (Don't use globals everywhere though, since they can be a pain to track when debugging)

Using inheritance isn't "bad" all of the time, only when it's abused, like any other tool.
At the moment, try focusing on implementing the game, and think less about the engine. You'll learn a lot about what you really need the engine to do from making a game.

Hope this helps :)

Saving the world, one semi-colon at a time.


#3 Konrad Jablonski   Members   -  Reputation: 483

Like
0Likes
Like

Posted 18 June 2012 - 02:27 PM

I'm one of those people who fight to death not to have any globals in code :) is there any other way? :)

#4 Juliano Schroeder   Members   -  Reputation: 121

Like
1Likes
Like

Posted 18 June 2012 - 02:35 PM

Inheritance is not bad. It can be bad if you do it wrong or too much. Inheritance is very good when you want substitutable behavior. Take a look at the C++ Faq on inheritance:

http://www.parashift.com/c++-faq-lite/proper-inheritance.html

On your LogSys case, I would go for a global as MajorTom suggested. Take a look at your sentence "

Is there any easier way to distribute it to pretty much everything?" well, globals are for eveything everywhere.


http://www.creationguts.com - The Guts of Creation
drawing, programming and game design.

#5 ApochPiQ   Moderators   -  Reputation: 16412

Like
9Likes
Like

Posted 18 June 2012 - 02:51 PM

Keep in mind that your logging system probably doesn't need to be a class at all. Consider using a simple free function instead, possibly in a namespace.

#6 DvDmanDT   GDNet+   -  Reputation: 996

Like
1Likes
Like

Posted 18 June 2012 - 03:14 PM

Debug utilities (ie logging) is one of the very few things which I consider suitable for global access (ie global variable, global function, singleton, ..). If you have other stuff you need everywhere, that might indicate a code design/architecture problem.

#7 frob   Moderators   -  Reputation: 22770

Like
1Likes
Like

Posted 18 June 2012 - 03:22 PM

Personally I wouldn't bother with either.

There are already a bajillion existing logger systems out there, most are far more comprehensive than an individual would ever use. The better systems integrate with your debugger, allow multiple simultaneous output methods including logging across networks, provide graphical visualizations when comparing bulk logs, and do much more than simply output individual text blobs.

Why re-invent the wheel?

Check out my book, Game Development with Unity, aimed at beginners who want to build fun games fast.

Also check out my personal website at bryanwagstaff.com, where I write about assorted stuff.


#8 alnite   Crossbones+   -  Reputation: 2133

Like
1Likes
Like

Posted 18 June 2012 - 03:28 PM

Keep in mind that your logging system probably doesn't need to be a class at all. Consider using a simple free function instead, possibly in a namespace.


This.

If you still decide it needs to be a class, ask yourself: does it need to be instantiated? What benefit do I get for having two or more instances of logger class? If there's none, then you don't need a class.

#9 bglanzer   Members   -  Reputation: 459

Like
-2Likes
Like

Posted 18 June 2012 - 03:43 PM

You can do something like this

class Log {
public:
static Log* Get() {
	 if( !m_pInstance )
		  m_pInstance = this;
	  return m_pInstance;
}
protected:
	 Log* m_pInstance;
};

Then use the log like this within your classes
Log* pLog = Log::Get();
pLog->Write( "Me and the Cap't make it happen!" );

Brendon Glanzer


#10 ApochPiQ   Moderators   -  Reputation: 16412

Like
4Likes
Like

Posted 18 June 2012 - 04:36 PM

You can do something like this

class Log {
public:
static Log* Get() {
	 if( !m_pInstance )
		  m_pInstance = this;
	  return m_pInstance;
}
protected:
	 Log* m_pInstance;
};

Then use the log like this within your classes
Log* pLog = Log::Get();
pLog->Write( "Me and the Cap't make it happen!" );



This is the "singleton pattern" or as I like to refer to it "the greatest sin perpetuated by programmers with good intentions."

The singleton thing has been debated to death, so if you're interested, I invite you to go search around for any of the dozens of threads on the subject. For now, though, I will leave it at this: please, don't use that code. Ever.

#11 bglanzer   Members   -  Reputation: 459

Like
0Likes
Like

Posted 18 June 2012 - 05:22 PM

I often times see Sington's used within references. For loggers particularly. I think its used that way in the Hieroglyph Engine from the book Practical Rendering & Computation. They have thier place but like ApochPiQ said they really shouldn't be used. They have the same issues as global variables so you have to take the same precautions.

The other way I've seen loggers done is passing a pointer to your logger on creation just like you are currently doing. I think that Method is used in 3d Game Engine Architecture. This is probably the method I would recommend.

Brendon Glanzer


#12 ApochPiQ   Moderators   -  Reputation: 16412

Like
0Likes
Like

Posted 18 June 2012 - 06:32 PM

That still raises the obvious question: why is your logger an object/class to begin with?

#13 metsfan   Members   -  Reputation: 654

Like
2Likes
Like

Posted 18 June 2012 - 07:02 PM

I don't want to address the logger, but I would like to address your hate for globals. I admittedly don't have a ton of experience with games in particular, but I do have a lot of application development experience, and I have worked on some very performance sensitive applications. The one thing I will say is, having a global "Application" instance is not a bad thing. Most programs need this sort of thing. This "Application" class will typically hold references to various subsystems of your application. This class HAS to be a singleton. It is an error for there to be more than one. If you are familiar with iOS development, it is the "AppDelegate" class. Obviously global variables all over your code are a terrible idea because it increases coupling and makes debugging a mess. But having a single class that represents your application (which can be represented as a singleton or a global variable declared in your main class. I'm fairly certain the compiler doesn't care, and will treat them both the same, though I could be wrong on that) is not a bad thing, and will save you a lot of headaches especially in a large application like a game.

On that note, your logger (if you choose to keep it as a class) could be a class member of your global Application class, so that you don't feel all dirty making it a global, and it still can remain at your disposal anywhere in the code.

I will say this though as parting words: if you find yourself passing an instance of a class to every single constructor or function call, the class is already tightly coupled with your code. There's no point in trying to fight it. It is meant to be a global.

As a note to future posters, like I said, I'm not pro on the subject of game dev, I'm just learning it myself, so I welcome criticism of my response from that perspective.

#14 bglanzer   Members   -  Reputation: 459

Like
0Likes
Like

Posted 18 June 2012 - 07:04 PM

Wouldn't a free function require opening and closing of the log file? Would that cause any type of slowdown compared to a class which opens the file on creation and closes before destruction?

It would make it alot easier to not have to pass the Log to each class that requires it.

Brendon Glanzer


#15 metsfan   Members   -  Reputation: 654

Like
-1Likes
Like

Posted 18 June 2012 - 07:09 PM


You can do something like this

class Log {
public:
static Log* Get() {
	 if( !m_pInstance )
		  m_pInstance = this;
	  return m_pInstance;
}
protected:
	 Log* m_pInstance;
};

Then use the log like this within your classes
Log* pLog = Log::Get();
pLog->Write( "Me and the Cap't make it happen!" );



This is the "singleton pattern" or as I like to refer to it "the greatest sin perpetuated by programmers with good intentions."

The singleton thing has been debated to death, so if you're interested, I invite you to go search around for any of the dozens of threads on the subject. For now, though, I will leave it at this: please, don't use that code. Ever.


Singletons used incorrectly are a bad idea, but Singletons do have their place. There are times when having two instances of a class is an error and may/will cause your program to malfunction. I am well aware of the downsides (increased strong coupling, problems with unit testing, debugging headaches), but there are times when having just one instance of a class is needed. I will say though, that a logger class is not one of those cases, having two logger classes shouldn't break your application. There are situations though where a singleton pattern makes sense, and not only can be used, but should.

However, it goes without saying that code is crap =) I don't think the poster was intending to provide copy/paste code (at least I hope not!)

Edited by metsfan, 18 June 2012 - 07:10 PM.


#16 Hodgman   Moderators   -  Reputation: 31912

Like
4Likes
Like

Posted 18 June 2012 - 08:25 PM

That still raises the obvious question: why is your logger an object/class to begin with?

Because it has state. An overly generic logger will likely have at least a file-pointer as state, perhaps also a "warning level" to ignore, a verbose flag, etc... You might even have one part of your app plugged into a network logger and another part plugged into a local file logger.

Though I know this isn't what you're getting at Posted Image You're getting at the point that a game's logger doesn't need to have state.

In my new engine core (here and here), I follow ApochPiQ's advice and only provide logging via a global/free function. Despite that, I can still filter out different engine sub-systems, and later I can still add features like network/disk logging, browser-display/formatting, etc...
eiInfoGroup( FooBar, true );//declare and enable log filter "FooBar"
eiInfo(FooBar, "couldn't frobnicate %d", foo );//Print to log, or don't depending on filters.

Singletons used incorrectly are a bad idea, but Singletons do have their place. There are times when having two instances of a class is an error and may/will cause your program to malfunction.

Can you name one? I've been trying for a while to find a justification, but my current engine still has zero. I know that in theory, singletons or monostates are the solution to the problem of "2 instances are always an error", but I'm not sure that this problem actually exists in the real world...

Wouldn't a free function require opening and closing of the log file? Would that cause any type of slowdown compared to a class which opens the file on creation and closes before destruction?

Not saying you should, but free-functions can still have hidden state. e.g. in the CPP that implements the function, you can have a file-static variable that holds the file pointer. From the outside, it acts like a free function, but internally, it's basically a singleton with it's instantiation policy hidden.
N.B. I've seen some loggers continuously open/close the file on purpose, because it ensures that if your program crashes, the log is always written out correctly (normally, the latest log data would be in a buffer, and maybe not yet on disk).

However, for the last few years, no game engine that I've worked on has logged straight to disk like this. The last ~3 engines I've used, all have a "companion" developer application that you run alongside the game -- this usually just sits down in your system tray, and communicates with the game over a socket. When the game needs to perform any dev duties, like logging, the game sends the data over the socket to this companion app. The game doesn't need to know how to rebuild assets or write logs, it just needs to know how to ask the companion/dev app how to do these things.

The thing is, we pretty much pass in LogSys class to EVERYTHING!

As above, I think logging should be implemented a a simple global function -- but if this was anything other that logging, then you're doing it correctly.
For emphasis, 99.9% of the time, I don't allow people to use new/malloc in my engine -- they've got to be passed an allocator object if they want to allocate memory. Imagine all the classes which I've got to pass around allocators between! Yet, I still believe this approach is more correct, as it makes your code slightly more functional and have no hidden dependencies/side-effects.

Edited by Hodgman, 18 June 2012 - 08:45 PM.


#17 krippy2k8   Members   -  Reputation: 646

Like
2Likes
Like

Posted 18 June 2012 - 09:12 PM

However, for the last few years, no game engine that I've worked on has logged straight to disk like this. The last ~3 engines I've used, all have a "companion" developer application that you run alongside the game -- this usually just sits down in your system tray, and communicates with the game over a socket. When the game needs to perform any dev duties, like logging, the game sends the data over the socket to this companion app. The game doesn't need to know how to rebuild assets or write logs, it just needs to know how to ask the companion/dev app how to do these things.


So those game engines are incapable of enabling any logging in production code? They likely still need state in any case, unless they're opening and closing a socket or pipe with every log request.

I would really just stick to using a global for the logger. Overuse of globals is a bad thing, but bending over backwards for ideological purity and compromising the complexity of your code for the sake of not using any globals is much worse in my opinion. The goal of development is to produce a product, not a human rights treaty.

As long as you follow a good practice of explicitly initializing and cleaning up the state of your globals in discrete locations (i.e. in your main()) instead of relying on constructors and destructors that may be called before or after it is safe to do so, there is no problem with using globals, or singletons either, really.

You can use a free function that will access the global logger object to do the actual logging if you want, to avoid littering your code with calls to global variables. Personally I like to use macros to call my logging and debugging constructs. That way I can implicitly pass things like __FILE__ and __LINE__ to the logger, and I can easily replace whatever logging/debugging implementation I want to use later if necessary.

#18 krippy2k8   Members   -  Reputation: 646

Like
3Likes
Like

Posted 18 June 2012 - 09:32 PM

Can you name one? I've been trying for a while to find a justification, but my current engine still has zero. I know that in theory, singletons or monostates are the solution to the problem of "2 instances are always an error", but I'm not sure that this problem actually exists in the real world...


In the case of PC game engines I doubt there are very many cases where a "2 instances are always an error" situation will exist, but there are many cases in the real world. Particularly when you have classes that instance third-party libraries and/or drivers that can't handle multiple interfaces in the same process. i.e. a while back I was working on an application that interfaces to control modules on vehicles, and I had to use a vendor-supplied DLL to communicate with the control modules through another piece of vendor-supplied hardware. Any attempt to instantiate more than one interface to the vendor-supplied hardware from the same process could cause problems, up to and including damaging the control module and rendering the vehicle useless until the control module was replaced or repaired. While 2 instances of a class that can interface to the vendor's DLL isn't necessarily an error, it is a huge financial risk, which is even better ;)

#19 metsfan   Members   -  Reputation: 654

Like
0Likes
Like

Posted 18 June 2012 - 09:33 PM

Can you name one? I've been trying for a while to find a justification, but my current engine still has zero. I know that in theory, singletons or monostates are the solution to the problem of "2 instances are always an error", but I'm not sure that this problem actually exists in the real world...


The best example I can think of is the one I mentioned, which is having a singleton to represent the application itself. An example would be the UIApplication class in iOS. UIApplication is implemented as a singleton because the class represents the application itself. Since there is only one application, using a singleton class to represent our application makes sense. I'm not saying this is the only way to represent an application, but it is a very intuitive and well established way of doing so.

#20 Hodgman   Moderators   -  Reputation: 31912

Like
0Likes
Like

Posted 18 June 2012 - 10:47 PM

So those game engines are incapable of enabling any logging in production code? They likely still need state in any case, unless they're opening and closing a socket or pipe with every log request.

They have to use a different logger implementation for retail builds. It could just swallow/ignore log entries (and probably would ignore/disable a lot of logging channels), or pipe them to the game's console (maybe colouring channels), or a text file inside %appdata%, etc... You don't need to inherit an interface or anything to do this, a simple ifdef to select different implementations is often good enough.

The best example I can think of is the one I mentioned, which is having a singleton to represent the application itself. Since there is only one application, using a singleton class to represent our application makes sense

It makes it convenient, but doesn't necessarily make sense. What is the one application that's running? Isn't this just a grouping of global variables into another (global) structure called 'application'? You could just make a few more globals instead and get rid of the grouping, or not even make them global ;) What if I want to launch a new process in the background, or send a message to another app -- in those cases there is more than one application in the problem domain..?

An example would be the UIApplication class in iOS. UIApplication is implemented as a singleton because the class represents the application itself. I'm not saying this is the only way to represent an application, but it is a very intuitive and well established way of doing so.

I'm not familiar with iOS, but from scanning the UIApplication docs with, it seems to be written in the kind of OOP style where you dump a whole bunch of useful functions into a class and then inherit from it. This style also often uses singletons to make it easy to get at those functions. That's fine, each style to it's own, but this is still an arbitrary choice by the API designer to use a singleton as a restriction on the end-user developers usage. You could replace the UIApplication singleton by, inside main, making local variables for a window, an event queue, a URL fetcher, etc... and then having the choice weather to make them global variables (e.g. via a singleton) or not, ourselves (i.e. the end-user of the API).

Edited by Hodgman, 18 June 2012 - 10:52 PM.





Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS