The inevitable God object

Started by
21 comments, last by 100GPing100 10 years, 1 month ago


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.

Advertisement


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

Isn't this how it's works?

Beginner in Game Development?  Read here. And read here.

 

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.


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?

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.

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).

[size="1"]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!

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.


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?

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

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.

This topic is closed to new replies.

Advertisement