Jump to content

  • Log In with Google      Sign In   
  • Create Account

We're offering banner ads on our site from just $5!

1. Details HERE. 2. GDNet+ Subscriptions HERE. 3. Ad upload HERE.


The inevitable God object


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
22 replies to this topic

#1 Tispe   Members   -  Reputation: 1039

Like
0Likes
Like

Posted 03 March 2014 - 12:53 PM

Hello

 

Anyone else experiencing that most of your code end up in a God Object?

 

Over some time I have toyed with several different programming ways. From pure procedural to strict OOP. As I program my game I encounter problems such as how to move messages between objects. Even messages across a network. These messages has to be properly identified, dispatched and call the correct methods. All messages are derived from a base object such that they can all be stored in the same STL container.

 

We can take advantage of double dispatch to avoid down casting these message objects. However now a message has to be accompanied by all the possible objects it may interact with. So in order for this message to have that acces, we need an interface that holds everything, the God object, or GameManager or whatever.

 

C++ has so many quirks that most of my code are based on solving these quirks rather then focusing on the game itself.

 

Am I alone, is this a noob thing, or what?



Sponsor:

#2 ApochPiQ   Moderators   -  Reputation: 16079

Like
9Likes
Like

Posted 03 March 2014 - 01:30 PM

This seems to mostly happen when you haven't seen enough design tricks to know how to break out of existing bad habits. Sometimes a single tweak can clean things up substantially; other times you might be looking at massive refactorings based on half a dozen techniques. In your case, it unfortunately seems more like the latter.



#3 SimonForsman   Crossbones+   -  Reputation: 6190

Like
8Likes
Like

Posted 03 March 2014 - 02:10 PM

Hello

 

Anyone else experiencing that most of your code end up in a God Object?

 

Over some time I have toyed with several different programming ways. From pure procedural to strict OOP. As I program my game I encounter problems such as how to move messages between objects. Even messages across a network. These messages has to be properly identified, dispatched and call the correct methods. All messages are derived from a base object such that they can all be stored in the same STL container.

 

We can take advantage of double dispatch to avoid down casting these message objects. However now a message has to be accompanied by all the possible objects it may interact with. So in order for this message to have that acces, we need an interface that holds everything, the God object, or GameManager or whatever.

 

C++ has so many quirks that most of my code are based on solving these quirks rather then focusing on the game itself.

 

Am I alone, is this a noob thing, or what?

 

Why does a message need to be derived from anything ? , a message should preferably just be data so you should be able to use the same class/struct for all messages (no need for inheritance), if your messages have methods then they're either improperly named or are doing far too much.

 

Personally i would have a message dispatcher object, senders can then send any message they want to the dispatcher and interested objects can implement a listener interface (or inherit from a pure virtual listener class in languages such as C++) and then just subscribe to messages based on type, source, etc.

Then the dispatcher just have to call a callback method in the listener interface/class when a matching message arrives and leave the exact details of how to deal with the message to the recieving object. (messages can then be a simple struct with a type identifier, basic information about the sender and some data)


I don't suffer from insanity, I'm enjoying every minute of it.
The voices in my head may not be real, but they have some good ideas!

#4 frob   Moderators   -  Reputation: 22293

Like
8Likes
Like

Posted 03 March 2014 - 02:29 PM

I've noticed that some classes can become large and full of utility functions, but as ApochPiQ mentioned, that is mostly due to design choices and habits.

 

 

Large games are not so much about hacking some code together until it is a game, instead they are rather intense software architecture and design systems.

 

In complex games simply adding a few bytes to an object can completely destabilize everything. 

 

Example:

 

The ubiquitous GameObject base class tends to become a megalithic object. Basically everything derives from it, so when a new feature is added many programmers find it quite tempting for addition. ... After all, the junior programmer may reason, adding a few virtual functions and some data members to GameObject won't hurt anything.

 

Unbeknownst to the junior programmers, the senior programmers have set email triggers on systems like the massive game object, and also on texture objects, mesh objects, particle systems, and all the other high-instance, high-memory, and high-performance source files. They do it for exactly this reason -- these systems are tempting targets for less disciplined programmers. They are sometimes tempting for the senior developers as well, but they know they'll need to defend their decision so they are more careful about it.

 

To the junior programmer's surprise, within seconds of submitting the code they receive some emails from two different senior programmers, they are told the change was reverted and a few minutes later there is a meeting scheduled in a available conference room. Why? Assuming your game engine supports several million game objects in it, some junior-level programmer comes along adding 3 floats to it (also in the wrong place so they add 16 bytes instead of 12) they just increased the memory requirements by a whopping 64 megabytes. The senior developers all have a watch flag in source control so ANY change to GameObject.h triggers a batch of emails.

 

End example.

 

 

There are many good solutions even though the solutions come with a cost. It is a tradeoff you must make.

 

A common tradeoff is to isolate features by abstract base classes. Yes, you need to pay a few cycles to check if the object is the correct cast -- usually by simply casting to the desired type and checking for null if it fails -- but that small cost is typically less than the far greater cost of adding those bytes to the GameObject class and requiring every object everywhere pay the penalty.


Edited by frob, 03 March 2014 - 02:34 PM.

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.


#5 ferrous   Members   -  Reputation: 2075

Like
1Likes
Like

Posted 03 March 2014 - 03:32 PM

I've noticed that some classes can become large and full of utility functions, but as ApochPiQ mentioned, that is mostly due to design choices and habits.

 

 

Large games are not so much about hacking some code together until it is a game, instead they are rather intense software architecture and design systems.

 

In complex games simply adding a few bytes to an object can completely destabilize everything. 

 

Example:

 

The ubiquitous GameObject base class tends to become a megalithic object. Basically everything derives from it, so when a new feature is added many programmers find it quite tempting for addition. ... After all, the junior programmer may reason, adding a few virtual functions and some data members to GameObject won't hurt anything.

 

Unbeknownst to the junior programmers, the senior programmers have set email triggers on systems like the massive game object, and also on texture objects, mesh objects, particle systems, and all the other high-instance, high-memory, and high-performance source files. They do it for exactly this reason -- these systems are tempting targets for less disciplined programmers. They are sometimes tempting for the senior developers as well, but they know they'll need to defend their decision so they are more careful about it.

 

To the junior programmer's surprise, within seconds of submitting the code they receive some emails from two different senior programmers, they are told the change was reverted and a few minutes later there is a meeting scheduled in a available conference room. Why? Assuming your game engine supports several million game objects in it, some junior-level programmer comes along adding 3 floats to it (also in the wrong place so they add 16 bytes instead of 12) they just increased the memory requirements by a whopping 64 megabytes. The senior developers all have a watch flag in source control so ANY change to GameObject.h triggers a batch of emails.

 

End example.

 

 

There are many good solutions even though the solutions come with a cost. It is a tradeoff you must make.

 

A common tradeoff is to isolate features by abstract base classes. Yes, you need to pay a few cycles to check if the object is the correct cast -- usually by simply casting to the desired type and checking for null if it fails -- but that small cost is typically less than the far greater cost of adding those bytes to the GameObject class and requiring every object everywhere pay the penalty.

 

Only thing I don't like about your example, is that code should be code reviewed before check-in. =)



#6 spinningcube   Members   -  Reputation: 90

Like
0Likes
Like

Posted 03 March 2014 - 08:49 PM

 

Hello

 

Anyone else experiencing that most of your code end up in a God Object?

 

Over some time I have toyed with several different programming ways. From pure procedural to strict OOP. As I program my game I encounter problems such as how to move messages between objects. Even messages across a network. These messages has to be properly identified, dispatched and call the correct methods. All messages are derived from a base object such that they can all be stored in the same STL container.

 

We can take advantage of double dispatch to avoid down casting these message objects. However now a message has to be accompanied by all the possible objects it may interact with. So in order for this message to have that acces, we need an interface that holds everything, the God object, or GameManager or whatever.

 

C++ has so many quirks that most of my code are based on solving these quirks rather then focusing on the game itself.

 

Am I alone, is this a noob thing, or what?

 

Why does a message need to be derived from anything ? , a message should preferably just be data so you should be able to use the same class/struct for all messages (no need for inheritance), if your messages have methods then they're either improperly named or are doing far too much.

 

Personally i would have a message dispatcher object, senders can then send any message they want to the dispatcher and interested objects can implement a listener interface (or inherit from a pure virtual listener class in languages such as C++) and then just subscribe to messages based on type, source, etc.

Then the dispatcher just have to call a callback method in the listener interface/class when a matching message arrives and leave the exact details of how to deal with the message to the recieving object. (messages can then be a simple struct with a type identifier, basic information about the sender and some data)

 

 

I agree with you and something that the Google ProcotolBuffers overdo in their design for example.

 

I too am all for the simple struct or list of arguments messages rather than the overly designed way of deriving them. Seems more like a futile exercise in OOP.


Edited by jbadams, 15 April 2014 - 02:17 AM.
Restored post contents from history.


#7 Servant of the Lord   Crossbones+   -  Reputation: 20384

Like
1Likes
Like

Posted 03 March 2014 - 10:32 PM

I also have problems with this - though usually I find it's my inexperience with code architecture engineering that's the problem.

 

By favoring use of composition over inheritance for many things, and keeping the God object as small as possible, it helps me reduce the problem.

 

Messaging is still a difficult problem for me - but less so when I realized that games are complex software, and that I shouldn't try to cram my entire game project into a single paradigm - different parts of the game code are better suited to different paradigms of architecture and different messaging systems suit different architectures better than others.

 

For example, I was writing messy code when I was trying to force my entire game project to use signals-and-slots. Then I was writing messy code when I switched over to forcing the entire project to use an event-based system. Now I realize that one paradigm does not fit all, architecturally-speaking, and that for parent-child hierarchies signals and slots work very well, but for other parts of the game architecture, it doesn't work very well and event systems or callbacks or plain ol' function calls work better (where better here means 'cleaner design').

 

I still write messy code, but just slightly less messy once I understood that.

Good architecture is something I'm still struggling to learn, so don't take my statement as an experienced view. smile.png


It's perfectly fine to abbreviate my username to 'Servant' rather than copy+pasting it all the time.
All glory be to the Man at the right hand... On David's throne the King will reign, and the Government will rest upon His shoulders. All the earth will see the salvation of God.
Of Stranger Flames - [indie turn-based rpg set in a para-historical French colony] | Indie RPG development journal

[Fly with me on Twitter] [Google+] [My broken website]

[Need web hosting? I personally like A Small Orange]


#8 Álvaro   Crossbones+   -  Reputation: 13692

Like
0Likes
Like

Posted 04 March 2014 - 08:48 AM


For example, I was writing messy code when I was trying to force my entire game project to use signals-and-slots. Then I was writing messy code when I switched over to forcing the entire project to use an event-based system. Now I realize that one paradigm does not fit all, architecturally-speaking, and that for parent-child hierarchies signals and slots work very well, but for other parts of the game architecture, it doesn't work very well and event systems or callbacks or plain ol' function calls work better (where better here means 'cleaner design').

 

What's an example of a situation where callbacks work better than signals-and-slots for you? My simple-minded understanding of signals is that they are collections of callbacks...



#9 Juliean   GDNet+   -  Reputation: 2696

Like
0Likes
Like

Posted 04 March 2014 - 09:32 AM


Why does a message need to be derived from anything ? , a message should preferably just be data so you should be able to use the same class/struct for all messages (no need for inheritance), if your messages have methods then they're either improperly named or are doing far too much.

 

Deriving messages can offer some advantages. In my Entity/Component-Implementation, this allows me to do neat stuff like:

struct MyMessage : public BaseMessage<MyMessage> // implementation generates an unique ID for this message class
{
    int data;
    float data2;
}

// a system using the message:

void System::Register(MessageManager& messages)
{
    messages.Subscribe<MyMessage>(*this);
}

void System::Receive(BasMessage& message)
{
    // eigther by using a convenience static-casting method
    if(auto pMyMessage = message.Convert<MyMessage>();
    {
        int i = pMyMessage->data;
    }
    // or potentially faster, though currently not working completely typesafe in MSVC due to lack of constexpr:
    switch(message.GetClassID())
    {
    case MessageType::MY_MESSAGE: // should really be MyMessage::ID();
        pMyMessage->data;
    }
}
    
// in game code:

m_pMessages->Send<MyMessage>(1, 2.0f);

I can't think of a way how this would work that easily without deriving (and template magic, too).

 

Oh, since we are on the topic, entity/component is a great architecture preventing god objects in gameplay/overall game design code.


Edited by Juliean, 04 March 2014 - 09:35 AM.


#10 Servant of the Lord   Crossbones+   -  Reputation: 20384

Like
1Likes
Like

Posted 04 March 2014 - 01:57 PM

What's an example of a situation where callbacks work better than signals-and-slots for you? My simple-minded understanding of signals is that they are collections of callbacks...

 

I kinda view signals as a more generalized and powerful form of callbacks, but sometimes overkill.

 

If I just want to run an function on every element of a container, passing in a callback (or functor) works perfectly. With signals and slots (in C++ specifically), both the container and the caller would have to support signals and slots, I'd have to attach a slot "DoSomethingToElement()" to the container's signal - which would be a weird signal, not really even an event - maybe called "OnElementAccessed" or something oddly forced sounding like that - and then call the container's "Crawl()" function so the signals get sent. It'd make much more sense to just pass a callback into the Crawl() function directly (and to rename the Crawl() function to something better suited like ForEvery()).

 

Callbacks are useful because they can be temporary or permanent (like signals and slots) but if they are temporary they don't require as much setup, and they don't require the caller to support slots.

 

Callback:

object.doSomething(onCallback);

Signals and slots: (when used for a temporary purpose)

object.attach(this, onCallback);
object.doSomethingThatSignals();
object.dettach(this, onCallback);

Signals and slots are more powerful and more flexible, but aren't always the best choice for me.

One of their pros is having multiple slots from multiple objects attached to the same signal, and another is the ease of use when an object has many signals that it emits.

 

It depends on the needs of the code. My logging system just takes a callback pointer that it permanently holds onto, to process log messages when they arrive. However, it might be good to expand it to take any number of functor objects, so I can have the logs outputted to multiple locations. That's a step inbetween callbacks and signals and slots.

 

I think my game objects will end up using alot of signals and slots (but that's still unknown to me), because they have alot of different types of interactions between each other and are in a parent-child hierarchy.

 

And my GUI stuff (since I'm using Qt) uses loads of signals and slots to great benefit - with a variety of different types of interactions and data being communicated. The signals going up to the parents and siblings, and regular function calls going down to the children.

 

Both the GUI (currently) and my game objects (in the future) also use regular event message queues - these are messages from the overall system rather than communication between two specific objects.

 

</inexperienced> </still-discovering-what-works>


Edited by Servant of the Lord, 04 March 2014 - 02:03 PM.

It's perfectly fine to abbreviate my username to 'Servant' rather than copy+pasting it all the time.
All glory be to the Man at the right hand... On David's throne the King will reign, and the Government will rest upon His shoulders. All the earth will see the salvation of God.
Of Stranger Flames - [indie turn-based rpg set in a para-historical French colony] | Indie RPG development journal

[Fly with me on Twitter] [Google+] [My broken website]

[Need web hosting? I personally like A Small Orange]


#11 Tispe   Members   -  Reputation: 1039

Like
0Likes
Like

Posted 04 March 2014 - 02:23 PM


Oh, since we are on the topic, entity/component is a great architecture preventing god objects in gameplay/overall game design code.

 

I don't see how you prevent the god object? You still need a way for a message to reach every object it may interact with. Either you need a god object that holds all other objects, like a phone book. Or you need coupling between objects, like a network. In the latter case, every object needs to store a pointer to every object it may want to interact with, which I think is a big mess.



#12 Alpha_ProgDes   Crossbones+   -  Reputation: 4692

Like
0Likes
Like

Posted 04 March 2014 - 02:56 PM

 


Oh, since we are on the topic, entity/component is a great architecture preventing god objects in gameplay/overall game design code.

 

I don't see how you prevent the god object? You still need a way for a message to reach every object it may interact with. Either you need a god object that holds all other objects, like a phone book. Or you need coupling between objects, like a network. In the latter case, every object needs to store a pointer to every object it may want to interact with, which I think is a big mess.

 

 

Disclaimer: I've never done what you're trying to do.

 

Instead of your Uber-Message-Object holding every object it needs to communicate with, wouldn't you just have the object hold a reference to the Uber-Object?

 

  1. Z = Uber-Message-Object
  2.  
  3. Object A subscribes to Z (ie. A has a reference to Z)
  4. Object B subscribes to Z
  5. Object C subscribes to Z.
  6. A sends a collision message to B.
  7. A sends: FromID (A), ToID (B), Data (id: 206, collide: true) to Z.
  8. Z passes messages to everyone subscribed (ie. puts message in queue)
  9. B checks message.
  10. B find ToID matches its ID.
  11. B reads Data.
  12. B processed Data.
  13. C checks message.
  14. C does not find match to ID.
  15. C ignores message.

 

Isn't this how it's works?


Beginner in Game Development? Read here.
 
Super Mario Bros clone tutorial written in XNA 4.0 [MonoGame, ANX, and MonoXNA] by Scott Haley
 
If you have found any of the posts helpful, please show your appreciation by clicking the up arrow on those posts Posted Image
 
Spoiler

#13 frob   Moderators   -  Reputation: 22293

Like
6Likes
Like

Posted 04 March 2014 - 03:16 PM

There is a pattern for that, often called the message bus but it also goes by many other names as well.

Basically it is accomplished with a central hub object, anyone who wants to listen on the message bus will register the event IDs they are interested in, a callback, and sometimes additional details like the number of times they want to get the notice (once, counted_n, forever) or whatever else fits the design. When events happen the relevant details are sent to the hub object. The hub figures out who is listening and forwards all the events.

The pattern is useful for many different types of designs. It also can be useful for debugging, simply attach a debug event generator as a registered listener. The drawback is that sometimes the individual events are too frequent or too rare, and it just ends up consuming a lot of memory. It is something to watch for on limited-memory devices, but generally works rather well for inter-object communication.

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.


#14 Juliean   GDNet+   -  Reputation: 2696

Like
0Likes
Like

Posted 05 March 2014 - 05:07 AM


I don't see how you prevent the god object? You still need a way for a message to reach every object it may interact with. Either you need a god object that holds all other objects, like a phone book. Or you need coupling between objects, like a network. In the latter case, every object needs to store a pointer to every object it may want to interact with, which I think is a big mess.

 

Not at all, I quess it depends on the implementation, but basically my implementation works as follows. You have one "manager" that holds a list of all the entities:

class EntityManager
{

// methods for accessing entities based on their components, for adding and removing entities, ...

private:

std::vector<Entitiy*> m_vEntities;
}

This is not a god class. All it is, is basically a better std::vector with some convenience methods for entities creation, removal and access. The entity class itself stores a list of components:

class Entity
{
// methods for querying, adding and removing components:
private:

std::vector<Component*> m_vComponents;
}

Again, this is merely like a more intelligent vector, and not a god class at all. A component is derived from a base class, and represents plain, stupid data:

class Position : BaseComponent<Position>
{
math::Vector3 vPosition;
}

And then there is systems, implementing actual behaviour:

class TransformSystem final : System<TransformSystem>
{
// implements the logic for creating & updating the transform matrices of a bunch of entities
void Update(double dt) override;

void Register(MessageManager& messages) override;
void Receive(const BaseMessage& message) override;
}

And for each new feature, you just create two of the above, adding the system to a SystemManager (which is once again merely a more intelligent vector). Messages are passed via those systems, too, like in my original post. Each system has all the tools/classes available for dealing with the correct messages and manipulate its components based on it.

 

So there you go. Instead of a god-game-class that has all kind of methods (update-transform, update-physics, render), and a game-object god class with an equivalent to those, you now have single systems that work on their data and their data only. Do you see how this can break up the god-object now?



#15 Tispe   Members   -  Reputation: 1039

Like
0Likes
Like

Posted 06 March 2014 - 01:39 AM

This will probably not be well recieved; but I think just packing everything into a God Object which is globally accessible is just much simpler then all this hassle.

 

From anywhere in the code just pass down the God Object or have it be a Global variable with all its members/managers public, then from anywhere add/remove/update players/state.



#16 SimonForsman   Crossbones+   -  Reputation: 6190

Like
1Likes
Like

Posted 06 March 2014 - 02:21 AM

This will probably not be well recieved; but I think just packing everything into a God Object which is globally accessible is just much simpler then all this hassle.

 

From anywhere in the code just pass down the God Object or have it be a Global variable with all its members/managers public, then from anywhere add/remove/update players/state.

 

The problem with such an approach is that you'll very easily end up adding/removing/updating players/state from anywhere which makes your code difficult to maintain(and it tends to get worse as your codebase grows), difficult to test(very little will be testable in isolation) and a pain in the arse to version control (the more spread out your changes are the more likely you are to run into conflicts that need to be resolved manually when merging commits).


I don't suffer from insanity, I'm enjoying every minute of it.
The voices in my head may not be real, but they have some good ideas!

#17 Aardvajk   Crossbones+   -  Reputation: 6073

Like
0Likes
Like

Posted 06 March 2014 - 03:18 AM

This will probably not be well recieved; but I think just packing everything into a God Object which is globally accessible is just much simpler then all this hassle.

 

From anywhere in the code just pass down the God Object or have it be a Global variable with all its members/managers public, then from anywhere add/remove/update players/state.

 

Stop kidding yourself you are using OO and just use globals then. Even easier. You are suffering from *almost* all of the same problems with this approach so saves you typing out parameter lists.



#18 Juliean   GDNet+   -  Reputation: 2696

Like
0Likes
Like

Posted 06 March 2014 - 04:57 AM


This will probably not be well recieved; but I think just packing everything into a God Object which is globally accessible is just much simpler then all this hassle.

 

Well there you have your answer why all your code ends of in a single god object. If you go by what looks simplest at the very moment and avoid everything that looks like a little bit of more work, why else would you end up with this. In the long run, a god object gets much more complicated than anything an ECS can put you up to, since ECS scales lineary, while a god object let along increases the time required for lookup of code, let alone the coupling & low cohesion that comes with this.

 

Out of personal interest, what do you even consider a "hassle" with such an entity/component code I posted? Declaring a component & system in lone isolation vs adding a member variable & method to some already huge god class?



#19 TheChubu   Crossbones+   -  Reputation: 4588

Like
2Likes
Like

Posted 06 March 2014 - 05:44 AM

Ohh but I like when a semi-god class appears (you don't have to allow it to become a god class).

 

That's the moment when I sit to really think what is happening, how the interactions work, what is done, what it needs to be done.

 

"Does this class really needs to track this thing? Can't I make a Tracker object that does it?"

"Does this class really needs to do this? Can't that responsibility fall into a new class?

"Does all these classes really need these distinct methods? Can't that behavior be dealt with a single generic class for all of them?

"Does this method really needs to have this object as parameter if its only using a little portion of it?"

 

When refactoring those kind of cases, you start to ask the right questions all over it, question everything! Give it some time to think through it. At least to me its fun to come up with solutions for those type of issues.


"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

 

My journals: dustArtemis ECS framework and Making a Terrain Generator


#20 Servant of the Lord   Crossbones+   -  Reputation: 20384

Like
0Likes
Like

Posted 06 March 2014 - 10:32 AM

This will probably not be well recieved; but I think just packing everything into a God Object which is globally accessible is just much simpler then all this hassle.

 

From anywhere in the code just pass down the God Object or have it be a Global variable with all its members/managers public, then from anywhere add/remove/update players/state.

 

For small projects, this is workable. The larger the project the better the program architecture needs to be. This is why programmers come up with all these patterns - not to be unnecessarily clever, but to simplify increasingly complex code projects.


It's perfectly fine to abbreviate my username to 'Servant' rather than copy+pasting it all the time.
All glory be to the Man at the right hand... On David's throne the King will reign, and the Government will rest upon His shoulders. All the earth will see the salvation of God.
Of Stranger Flames - [indie turn-based rpg set in a para-historical French colony] | Indie RPG development journal

[Fly with me on Twitter] [Google+] [My broken website]

[Need web hosting? I personally like A Small Orange]





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