Skip to main content
gamedev.net
🔒 Locked

[C++] OOP instance reachability

Started by Vanderry 16 replies 1.6k views
Original Post
Vanderry
Vanderry
Hey guys !

I'm working on this game engine of increasing complexity and before I really start messing up the connectivity between different objects, I wanted to ask you for some professional opinions on the subject.

Consider the following ownership hierarchy, I hope the class names kind of explain their purpose (a "doodad" is any static game object, such as a tree or rock):

CApplication (1)	CGraphics (1)	CInput (1)	CTimer (1)	CScene (1)		CPlayer (1)		CMonster (N)		CDoodad (N)


In many cases I want to pass information from the graphics or timer object into a monster or doodad object, previously I've done so by passing them as pointer arguments in their respective methods:

CMonster::Update( CGraphics* pGraphics, CTimer* pTimer );

When an object is updated or rendered it calls the same method for every other object that it owns, and this means that if I pass objects by reference then I have to either store the pointer internally or pass it as a function argument all the way from the top of the hierarchy (I personally dislike the idea of having to store the direct pointer, just in case I have to recreate some object instance).

I'm starting to realize that my application is becoming cluttered and a bit obscure. The best solution I could think of myself is to pass the root CApplication instance as a reference into every newly created object and store it internally (this is one class instance that should never get recreated during the running of the application).

So my real question is, is this a good idea or is there a better solution?

Many thanks in advance !

- Dave
Gage64
Gage64
Quote:
Original post by Vanderry
In many cases I want to pass information from the graphics or timer object into a monster or doodad object


Can you give a few examples? What information and how will the information be used?

Quote:
When an object is updated or rendered it calls the same method for every other object that it owns


What does it mean for an object to "own" another object?
Steve132
Steve132
My answer would be that it seems that you aren't exactly using OOP correctly. For example, what (specifically) object is modeled by the CInput class? What object is modeled by the CGraphics class? There is no concrete noun that can be acually described by the statement "What are you?" "I'm a Graphics!"

Instead, it looks like you actually have a series of subsystems that are loosely connected to each-other in order to facilitate grouping of related code. That is not really OOP, it is more of a module or packaging approach to making a structured program.

That's not necessarily a bad thing. Although you aren't being fully object oriented, modules and packages are really successful. In my opinion, you can approach this three ways:

1) Refactor your subsystem classes with ownership into namespaces. This is easy to use and to read, but it has the problem that namespaces have no memory space, and have no real instances. All your member variables become de-facto global although at namespace scope.

2) Refactor your subsystem classes to use some kind of singleton pattern. That way "Graphics" doesn't represent an object or literal noun, but it is a "module" in the environment. There is only one of them, and you can access it with some kind of Graphics::getInstance() method globally.

3) Some people don't like singleton, but like other similar patterns such as Factory that do something similar. In my opinion, you want a singleton subsystem class, but there is debate as to whether or not using singleton is universally bad, so they usually replace it with a factory or something else.
Vanderry
Vanderry
Quote:
Original post by Gage64Can you give a few examples? What information and how will the information be used?


Due to the hierarchic structure of the application, I always assume that the more basic object types "at the top" are updated before anything below them. This guarantees that I always can get fresh information about the graphics device to be used when updating other things. For example when I update the animation for a monster I need to know how much time has elapsed since the last program loop, this is obtained from float CTimer::GetTimeElapsed().

Quote:
Original post by Gage64What does it mean for an object to "own" another object?


I apologize for my lacking jargon. What I mean by "owning" is that I intend one class to specifically be the one kind that contains the instance pointer of another class. For instance the CApplication is the only class that will ever hold a CScene instance. This is the general idea, mainly for organization. When I collapse the scene (destroy it), the objects that it "owns" are destroyed, and their objects etc.
Gage64
Gage64
Quote:
Original post by Vanderry
when I update the animation for a monster I need to know how much time has elapsed since the last program loop, this is obtained from float CTimer::GetTimeElapsed().


It might be better to pass the elapsed time directly (as a float, for example). That way, the receiving class will be decoupled from the CTimer class.

But I still don't understand what kind of clutter and obscurity you're talking about...
Vanderry
Vanderry
Admittedly, I'm not intimately familiar with the principles or naming convention associated with OOP, I might even have used the wrong word. By personal preference I often try to "visualize" stuff through these ideas of ownership, but I want to take things to a more "professional" level before really stitching together this application.

I am using a singleton for error handling, but I have to agree with a lot of people about the ugliness of this method. I assume that objects that will exist in a multitude can't be handled in this namespace manner, it feels really weird to think otherwise. Is this "modular" way of thinking that I've adapted admitted in the "professional" world?
Vanderry
Vanderry
Quote:
Original post by Gage64
Quote:
Original post by Vanderry
when I update the animation for a monster I need to know how much time has elapsed since the last program loop, this is obtained from float CTimer::GetTimeElapsed().


It might be better to pass the elapsed time directly (as a float, for example). That way, the receiving class will be decoupled from the CTimer class.

But I still don't understand what kind of clutter and obscurity you're talking about...


It doesn't really show in my examples, I thought it would be a nice thing not to paste a lot of monotonous code. There was a time when I passed these values as simple PODs, but as the argument lists grew I started to pass references to those basic class instances instead, so that each object would dig out the important data individually.

Again, this hierarchical update-call required that references to CTimer, CGraphics, CInput (and other objects) were passed down so that those on top took into concideration what things the objects lower in the hierarchy would need. This is why I've started to think that it might be a good idea to pass the root class instance instead, so that everything is reachable from there. A monster could obtain timer information through m_pApplication->GetTimer()->GetTimeElapsed(). I can understand if this seems like a bad solution due to the many get-calls that might be required.

If the internal pointer storage sounds like a bad idea in itself, then I'm definitely open to the idea of making the root class a singleton, or perhaps even just a global variable.
Dancin_Fool
Dancin_Fool
Quote:
Original post by Vanderry

Again, this hierarchical update-call required that references to CTimer, CGraphics, CInput (and other objects) were passed down so that those on top took into concideration what things the objects lower in the hierarchy would need. This is why I've started to think that it might be a good idea to pass the root class instance instead, so that everything is reachable from there. A monster could obtain timer information through m_pApplication->GetTimer()->GetTimeElapsed(). I can understand if this seems like a bad solution due to the many get-calls that might be required.

If the internal pointer storage sounds like a bad idea in itself, then I'm definitely open to the idea of making the root class a singleton, or perhaps even just a global variable.


I would avoid passing the root application object just as it becomes a bit of an eyesore when things get complex and you're calling application->GetSomething()->GetSomethingElse()->GetSomethingElseAgain(). I'd suggest splitting up your update function what about

void Update( UpdateContext* context )void Render( RenderContext* context )


then you can store anything necessary to the update in the UpdateContext, and anything necessary to the render in the RenderContext. It also makes it easy to add additional parameters as you don't have to change any function definitions.
rip-off
rip-off
It gets less messy if you split it up a little. You shouldn't be drawing in your update methods, this couples the physics framerate to the rendering framerate. Instead, have dedicated update() and draw() functions. You could go all the way and do a full MVC architecture if you want, it depends on the size of the game whether that is necessary.

Your "input" subsystem can have its control flow inverted. Dependencies between it and your other classes can be broken by introducing a signal/slot mechanism.

Passing the top-level "application" class would be a poor decision, IMO. However, grouping a number of useful sub-systems into a simple "context" object, and passing that around isn't quite so bad.
Decrius
Decrius
I second rip-off's thoughts. Decouple update and draw, and use a signal/slot system. Those were one of the best things that happened to my code.

Be aware that your draw method SHOULD be const. It's drawing, not changing itself (beside the caching data that then should be made mutable). This also makes drawing a read-only operation, allowing update and draw to be run in parallel.

Personally, the object-oriented design of such a system is, in some cases I encountered, theoretically elegant but complex / clumsy. I currently am implementing a data-oriented design: I only have 2D quads, so I store an interleaved array (position, colour, texture coordinates...) for all objects. When you over-come some of the design problems that introduces, it really simplifies your code, and speeds things up.
[size="2"]SignatureShuffle: [size="2"]Random signature images on fora
Vanderry
Vanderry
I'm definitely separating the update and rendering procedures (although I may not have been very clear about it). The update method calls the update method of other "owned" objects and the rendering method works the same way. The general class structure is:

class CSomething{public:	CSomething();	~CSomething() { Release(); }	BOOL Initialize(  ... );	void Release();	void Update( ... );	void Render( ... );		// Get & Set methods	...private:	...};

And the main loop is basically just calling Update() and Render() for the CApplication instance.

I guess it might be sufficient to pass pointers to these basic objects if concentrating them into one container class doesn't make things cleaner. The different basic objects that I think will need to be passed at this point are for the Update() procedure:

CTimer - Timing properties.
CInput - Information about pressed keys, mouse buttons, cursor position etc.
CSound - Fmod or similar functionality.
CData - Access to game data that can be loaded during runtime.
CPhysics - If I decide to use Bullet or any other physics platform.

...and for the Render() procedure:

CGraphics - In my case, DirectX-related properties.

This may not look horribly "cluttersome", but on top of these I sometimes need to pass more specific arguments as well. Perhaps this is too much of an aesthetic concern.

[Edited by - Vanderry on September 18, 2010 1:38:41 PM]
_the_phantom_
_the_phantom_
That has nothing todo with the 'logical const' for a class an instance; 'const' says "I will not change the classe's instance's internal state" which, depending on how you set it up could be true for a render function.

I find myself wondering if a 'render' function really needs to exist anyway; a high level object might well have a 'renderable' associated with it which can be enabled or disabled in the update loop, however ultimately the thing which renders it should be some kind of 'scene graph' which contains all renderable objects and will be the thing which sorts draw calls and excutes them in some manner.

So, for example, your 'player' class would have a "renderable" which it owns and the 'scene graph' has reference to. On 'update' the 'player' might well adjust some states but those don't come into effect until such time as the scene graph performs an update or maybe directly at render time. The 'player' class however wouldn't have a render function itself as it isn't directly renderable.

The key point to all this is that not everything needs access to everything; by decoupling data and having the correct subsystem handling the correct actions.
Vanderry
Vanderry
The way Direct3D9 is organized is that it uses "devices" (it's probably familiar to a lot of people but given that this is the general forum I won't take that for granted). Upon initialization, I create a rendering device which contains both properties and methods for rendering. Because of this, I need to reach this device any time I want to adjust the rendering settings or draw something. This may have been what got me thinking on these lines. I personally find it pretty comfortable, but then I don't really know of any other good alternatives.

Stripping the rendering method off all physical game objects sounds to me like their superior object (in my case CScene or CApplication) would loop through all objects in the scene, get their properties and states and render thereby. If this is the correct understanding, then it sounds to me like it has only been moved from one class to another. There are somewhat different rendering procedures to different objects.

I feel like I'm starting to dilute the main topic here and I want to thank everyone who shared their thoughts. Only carry on with the discussion if you have time and interest :)
Decrius
Decrius
Quote:
Original post by Gage64
Quote:
Original post by Decrius
Be aware that your draw method SHOULD be const. It's drawing, not changing itself


Drawing can involve changing render state, for example setting a new texture. Or maybe you meant something else?


I don't think this should ever occur. When you want to draw the _current_ state of an object, you don't want to change it. Never. I strictly make my draw function const.

When going through the hierarchy with the update(), you change the object states according to events that happened or due to time being passed. After that, you go through the full hierarchy again, but then draw() the object's state.

You need to traverse the hierarchy twice, since updating one object MAY update another object BACK up the hierarchy, which we had already gone past updating. So if we had already drawn it, we would've drawn too early.
[size="2"]SignatureShuffle: [size="2"]Random signature images on fora
_the_phantom_
_the_phantom_
Quote:
Original post by Vanderry
Because of this, I need to reach this device any time I want to adjust the rendering settings or draw something. This may have been what got me thinking on these lines. I personally find it pretty comfortable, but then I don't really know of any other good alternatives.


This is why you limit what can do the changing and thus what needs access to the device so that you don't need it "everywhere".

A 'renderable' would be part of the logical code set which has access to the device in some manner (be it via another class or directly via a stored 'device' pointer) and would take care of that when drawing was required.

Your 'game' classes which need rendering would then have a renderable (likely constructed from a factory function somewhere) and would use this to setup states for the rendering it requires without requiring direct interaction with any rendering setup.

Quote:

Stripping the rendering method off all physical game objects sounds to me like their superior object (in my case CScene or CApplication) would loop through all objects in the scene, get their properties and states and render thereby. If this is the correct understanding, then it sounds to me like it has only been moved from one class to another. There are somewhat different rendering procedures to different objects.


Yes and no.
Your 'renderable' objects would be part of a scene structure, this scene structure would be passed over and a 'draw list' constructed from the 'renderables' which are active.

This centralises the drawing logic AND allows for concrete types in the render loop.

Rendering, if you centralise, isn't really that different between objects;
- set material (shader, textures, other parameters)
- set per instance data
- set vertex stream
- set index stream
- draw

Quote:

I feel like I'm starting to dilute the main topic here and I want to thank everyone who shared their thoughts. Only carry on with the discussion if you have time and interest :)


Its still pretty centeral to the original post as this is still about object relationship and interaction between them.

The trick is to think/design beyond the 'simple' solution you have, which is a very common starting point and one which many people use and is certainly workable.
Vanderry
Vanderry
Thank you phantom, that is an interesting perspective that I haven't considered. I assume there still must be some connectivity between these 'renderables' and their corresponding game objects, in order to keep track of changes. Should this be a two-way connection so that when a monster for example is destroyed, I can be sure that its 'renderable' is destroyed as well?
_the_phantom_
_the_phantom_
The 'monster' would have a reference of some sort to the 'renderable' (probably a smart pointer of some type in C++, or some other handle type structure) so that it can do things such disable it or change some state related data.

When the 'monster' is killed it would ask the scene graph system to remove it from the list of things to be rendered and then dispose of it in some manner (delete, remove functionality from a factory (which might cache the memory for reuse), simple RAII out of scope clean up).

The 'renderable' doesn't need to know who owns it or who draws it, it simply exists and can be acted upon.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.