Archived

This topic is now archived and is closed to further replies.

A lack of C++ encapulation makes me feel guilty

This topic is 5584 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi, I''m writing 3D asteroids. I''ve got a class managing the asteroids and a class managing the energy pulses projected from the ship. Obviously, when the pulses strike the asteroids there is a collision. In order to check for a collision, there needs to be a comparison of the data in one object with the data in the other. The book I''ve been reading suggests that you have each object own a pointer to the data in the other object. This seems quite sensible. The thought of using accessor functions to get hold of a copy of some data which comprises a list of asteroids seems ridiculously inefficient. However, given that two distinct objects have direct access to some of each other''s data, surely this is a big contradiction of encapsulation? How is one supposed to write good C++ given this situation? Thanks, BD.

Share this post


Link to post
Share on other sites
Accessor methods are very necessary for encapsilation. It might seem inefficient now, but when you start writing much more complex applications, your going to kick yourself if you don't use them.

You should ahve a "world" amanger class that keeps track of all the positions of all the objects. It should do the collision checking by using the accessors of the coordinates and sizes of all the objects. Just do a brute force comparison of every object to every object for now, you can optimize it later.

Ponters between objects like this is not good. Better to have an integer reference to each object. Like asteroid "1", "2", "3", "4", "5", etc. Then pulse "1", "2", "3", "4", "5". The "world" manager can then use store the pointers in an array and index them using the int reference.

Ok, did that make sense? I think I lost myself.

[edited by - smanches on August 28, 2002 3:44:31 PM]

Share this post


Link to post
Share on other sites
How about have the energy-pulse-controller simply ask the astroid-controller to do the collision detection and pass it the information about the location/size/orientation of the pulse. The astroid-controller then goes through its own list and if it finds a collision it takes care of splitting/destroying the affected astroid. It then returns to the energy-pulse-controller weather or not a collision occurred.

The key here is decoupling the objects. Instead of the energy-pulse-controller having to sift through all the astroids - and thus be aware of how the astroids are stored - it relies on the astroid-controller. Now the implementaion of the astroid-controller can change without affecting the energy-pulse-controller class.

** timeout expired **

[edited by - wayfarerx on August 28, 2002 3:45:14 PM]

Share this post


Link to post
Share on other sites
Wow! Wasn''t expecting that response.

WayfarerX. You seem to be suggesting that in order to compare the data, one manager object should pass its data, one element at a time, to the other within the parameters of a function.

I think this is, in fact, sort of what the book I''m reading is doing. One object still has access to the other''s elements but it only uses them to call a collision check function on each of them and sends the necessary data in the parameter list.

What you are saying seems better than this because in your version, the calling object would only need a pointer to the other manager and not its data. The called manager is obviously capable of iterating through its own list of objects.

Alas, invoking countless function calls and copying data to the stack seems to be the way to do things!

Smanches. Are you saying that all the data should be kept in the world manager?

Cheers,

BD.




Share this post


Link to post
Share on other sites
What if the data one manager requires from the other is quite a substantial object in its self?

Surely, copying by value larger and larger objects will sooner or later become too inefficient?

If giving one object access to another''s data is so bad, why do so many of the DirectX interface function parameter lists take pointers to structures like D3DXVECTOR3s?

BD.

Share this post


Link to post
Share on other sites
You should pass pointers to methods. Then use the pointers to access the accessor methods of the object. Just remember, whatever class created the pointer is the only class that should destroy it.

I was just sugesting you use a single manager to manager all the objects. Have one list of Asteroids and one list of Pulses. The way WayfarerX describes would work also. The only difference is that the pulse manager would own the collision detection. Is that the correct place for it in the long run?

If your application grows and you soon have other objects that need collision detection, the pulse manager would start doing a lot of stuff that really isn''t it''s job. It''s the pulse manager, not the collision detector.

Just trying to explain the bigger picture. You can do what WayfarerX says without any problems.

Share this post


Link to post
Share on other sites
quote:
Original post by Barn Door
Alas, invoking countless function calls and copying data to the stack seems to be the way to do things!


Don't focus on optimization up-front. Passing simple geometric constructs consisting of only a couple of numbers is practically free when you're only doing it once per frame. You can also pass arguements as const refrences. Works the same as a pointer but safer.

quote:
Original post by smanches
I was just sugesting you use a single manager to manager all the objects. Have one list of Asteroids and one list of Pulses. The way WayfarerX describes would work also. The only difference is that the pulse manager would own the collision detection. Is that the correct place for it in the long run?


Actually, I would pull the actual collision detection code out into another object that could be used by any of the managers, or anyone else for that matter. Using a global object manger would work, but I'm generally against applying global solutions to simple concepts like Astroids. The ship, the energy pulses, and the astroids have very little in common when it comes to how they act and interact within the simulation. Splitting them up allows you to focus just on the cases where they interact.

[edited by - wayfarerx on August 28, 2002 6:11:03 PM]

Share this post


Link to post
Share on other sites
quote:
Original post by Barn Door
What if the data one manager requires from the other is quite a substantial object in its self?


Beware of any one class trying to do too much.
quote:

Surely, copying by value larger and larger objects will sooner or later become too inefficient?


Whoa, hold those horsies! Why is anything being copied by value? Objects can be passed by reference through the manager for collision detection--no need for object copies at all.

Whenever you have interdependent functionality, like you do here between pulses and asteroids, you should look at doing an escalation: make a superclass that has the functions that will do this functionality. This is straight out of Lakos'' "Large Scale C++ Design", except his example uses Windows and Rectangles:

  
class CollisionUtil
{
// returns true if collision happened

static bool hasCollided (const Pulse &p, const Asteroid &a);
};

class Game
{
std::list<Pulse> m_pulseList;
std::list<Asteroid> m_astList;
void loop ()
{
// ...

// Collision detection loop

for ( /* all pulses p */ )
{
for ( /* all asteroids a */
)
{
if (hasCollided (p, a))
{
doBigAsteroidExplosion (p, a); // or whatever

}
}
}
// ...

}
};

That''s the general idea. Note that when you use references, the compiler interally passes object pointers--not object copies. It''s very fast.

BTW, this presumes that a collision can be detected purely via the public members of pulse & asteroid.

It''s also important to note that this design allows Pulse to not know anything about Asteroid, and vice versa. This is part of the reusability theme. If you write a kick-butt Asteroid class, and you want to use it in a future game, you don''t want to forced to have to also use Pulse as well, do you? This design will free you of that dependency.

Share this post


Link to post
Share on other sites
Thanks guys.

This thread seems to have split into two. On one hand we have..

Given two sets of independent data which need to be compared, where does the comparison take place?

and ...

How does one object gain access to another object's internal data?


I like the idea of a generic collision detecter. I could overload a 'hasCollided' type function to handle any combination of collisions required.

quote:

Objects can be passed by reference through the manager for collision detection



This is still confusing me. If an object has a pointer or a reference to another object's internal data then doesn't that break encapsulation even if they're a const pointer or reference?
Surely for strict encapulation all values would have to be copied by value?

Once again, why is it acceptable for DirectX interfaces to ask for non-const pointers to your program's data in their parameter lists?

Cheers,

BD.

[edited by - Barn Door on August 28, 2002 9:08:18 PM]

[edited by - Barn Door on August 28, 2002 9:10:25 PM]

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
dude, lay off the religion. Really, you have heard about the benefits of a certain style and guess what? Your mind is locked into it. Now there are benefits, don''t get me wrong, but there are downsides too. You have to mix and match styles to get a good program. Again, adherance to a religious style of programming isn''t helping you. Afterall, without your religion wouldn''t you have solved this problem by now? Go finish your program now. Then afterwards you can reflect on what went wrong and look around for new techniques.

Share this post


Link to post
Share on other sites
Pointers and references, in this example, do not fall under encapsulation because they are objects themselves. Encapsulation means hiding the internal data objects. Since you would be passing an entire asteroid object into a pulse object, or however you decide to do it, it's not breaking encapsulation.

If you were to access the coordinates of the asteroid directly, ie make them public, then you break encapsulation.

(don't rant on me if I spelled encapsulation wrong. :-)

[edited by - smanches on August 28, 2002 10:30:15 PM]

Share this post


Link to post
Share on other sites
Stoffel> Why this?

class CollisionUtil{
// returns true if collision happened
static bool hasCollided (const Pulse &p, const Asteroid &a);
};


When you could just write a global function? That seems a little too religously OO for me. I'd just make an overloaded global function to perform that task for any pair of objects in my game.

bool checkCollision(const Pulse &p, const Asteroid &a);
bool checkCollision(const Asteroid &a, const Pulse &p);
bool checkCollision(const Asteroid &a, const Asteroid &a2);
//etc etc


Or better yet, I would make a class CActor or something, which would have a pure virtual accessor to get the position of an object:

  
class CActor
{
virtual CBoundingBox getBoundingBox();
};
//and then...

bool checkCollision(const CActor &a1, const CActor &a2)
{
boundingBox1 = a1.getBoundingBox();
boundingBox2 = a2.getBoundingBox();
//check collision...

}



[edited by - Neosmyle on August 28, 2002 10:39:07 PM]

Share this post


Link to post
Share on other sites
quote:

dude, lay off the religion.


quote:

Your mind is locked into it.


Well, in my defence, if I was totally locked into it then I wouldn't have started this thread in the first place.

quote:

Afterall, without your religion wouldn't you have solved this problem by now?


You can say that again!

I've actually come from working in Flash. In this environment you have your code spread amongst objects called movie clips. However, there aren't any private members. You can refer to any variable in any object from anywhere. Worked OK for me. So coming to C++ and its principles is a little confusing especially when satisfying them appears to be impractical.

Hence,

quote:

That seems a little too religously OO for me.



BD.





[edited by - Barn Door on August 29, 2002 11:48:45 AM]

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
you could use class inheritence and have a base object class.
derive the pulse and asteroid object from the base class and it wont be a problem accessing the information required for a colission.

Share this post


Link to post
Share on other sites
You are in the classing situation of the double-dispatch pattern.In fact, many books give collsions in an asteroids game as an example ( Effective C++, Modern C++ Design). Problem is, C++ does noy have an inherent double-dispatch mechanism. At this point i''ve read two different approaches to this problem without using type list templates, or some other compiler hack:

1) The Manager: You would have a manager that knows it all about collisions, and is aware of all the different kind of shapes you have in your design. This approach is fine if you don''t have many kinds of shapes. Beware, though, because if the manager has a method "HasCollided", and it takes a base class reference ( for example:MyGameObject*), you will have to implement a mechanism so the objects identify themselves ( like int GetType()), or use RTTI; because the manager must know the dynamic class of the objects!
2) The base class: this works if all of your objects have the same basic shape. If you can do with a circle, or a bounding box, then it is enough. If you have a circle AND a bounding box, you can still use this approach, but your objects will have behaviour similar to the manager.

Personally, i would take the first approach. After all, it seems intuitive that an object in the game does not need to know how to collide to other specific objects...physical rules are the ones that decide it out. But...it is personal, no necessarily correct, or the best in your case. It depends on the requirements of your game.

Good Luck!
Daniel Benmergui.

Share this post


Link to post
Share on other sites
I''ve recently tried experimenting with a different approach (than my usual) to solve this problem. Normally I''d use a "manager" class that handled collisions but this was very sloppy as objects couldn''t directly "see" their environment.

I''m going to simplify this because my solution takes on a lot of other issues, so this is actually different than what I did, untested and may have errors. So, feel free to correct me.

Start off with a class called CPresence. This class stores the actual position and dimensions (presence) of the game object. It can be anything. It could be a player, asteroid, laser fire, etc.

Next, create a manager class and we''ll call it CEntity. This contains an array of CPresence. This array is static and private. The methods of CEntity allow access to the array.

So, here''s the trick: each object is an instantiation of CEntity. If you want to create and asteroid, you would derive CAsteroid from CEntity. Laser fire, CLaserFire, would be derived from CEntity as well. Because the array of CPresence is static, all instantiations have access to it but because it is private, it is only accessible through the CEntity methods. This allows you to maintain encapsulation.

One more property of CEntity is that it has a pointer (not static) to its own data in the array of CPresence. So, the entity can access its own data directly through the ptr but accesses the limited data of others via its own methods.

Initializing a CEntity derivative distributes a CPresence from the array to itself. You can do this with a static property of CEntity that keeps track of what presences are available.

Now, CEntity also contains basic methods of movement and setting position. When the position of the entity is set, it first looks at the new position in relation to the old and checks through the array of CPresence to spot a collision, in which case it adjusts the new position.

Is this clear or should I post code?

- Jay

"I have head-explody!!!" - NNY

Get Tranced!

Share this post


Link to post
Share on other sites
Two things:

- code classes as though you don''t know what''s going on inside the others

- one class one responsibility

coderx75''s example sounds clever but very inflexible. what if you have different kinds of collision algorithms etc? the entity class has to manage everything, memomry, movement, collision detection, what about culling for the renderer too?

i feel uneasy and am just expressing that without any good suggestions

Share this post


Link to post
Share on other sites
quote:
Original post by Daniel Benmergui
You are in the classing situation of the double-dispatch pattern.


I agree.

quote:

Beware, though, because if the manager has a method "HasCollided", and it takes a base class reference ( for example:MyGameObject*), you will have to implement a mechanism so the objects identify themselves ( like int GetType()), or use RTTI; because the manager must know the dynamic class of the objects!


Not true! IIR, asteroids pass through asteroid, and pulses pass through pulses, so you only need to compare pulses with asteroids. Avoid RTTI like the plague.

quote:

2) The base class: this works if all of your objects have the same basic shape. If you can do with a circle, or a bounding box, then it is enough. If you have a circle AND a bounding box, you can still use this approach, but your objects will have behaviour similar to the manager.


Or, if you wanted to be more general, objects to be checked for collision could be derived from a base class that provides a "const BoundingRgn &getBoundingRgn () const" method that would provide the bounding region of the object. The manager would then only need to compare the two bounding regions, oblivious to the underlying object types. I think this is approaching overkill, IMHO.

Share this post


Link to post
Share on other sites
quote:
Original post by Neosmyle
Stoffel> Why this?


Two reasons:
1) It was taken directly from Lakos' "Large-Scale C++ Design".
2) It lets me say CollisionUtil::checkCollision, instead of just checkCollision. I've worked on several large-scale (thousands of source file) projects, and know from experience that global functions are horrific because you have to grep for every little thing. With the leading CollisionUtil::, I know exactly where to look. (note that a global function in a CollisionUtil namespace would also accomplish the same goal--I don't often use namespaces but I think that's the preferred method).

Global functions also have a high chance of name collision, which is why encapsulating them in a class or namespace is preferred.

quote:

When you could just write a global function? That seems a little too religously OO for me.


No such thing as too-OO. You can have bad OO, but if it's good OO, it's never enough.

[edited by - Stoffel on August 29, 2002 2:28:31 PM]

Share this post


Link to post
Share on other sites
quote:
Original post by petewood
Two things:

- code classes as though you don''t know what''s going on inside the others

- one class one responsibility

coderx75''s example sounds clever but very inflexible. what if you have different kinds of collision algorithms etc? the entity class has to manage everything, memomry, movement, collision detection, what about culling for the renderer too?

i feel uneasy and am just expressing that without any good suggestions


Actually, it allows me a lot of flexibility. The purpose of this is that I don''t need more than one collision algorithm. In this system, everything is an entity and viewed the same by the CEntity class. The way I''ve set it up is that I have a collision system that exerts force onto the colliding entities. This force not only cause the objects to react (bounce) but also inflicts damage. Therefore a bullet is only an entity with light mass and high velocity. When it hits something, the damage is distributed to the bullet and the target. The bullet, having few "hit points" is destroyed.

The CEntity class doesn''t actually know what''s happening in other instances. It can request that information through the methods of other instances... hmmm, I''ll post some code...

- Jay

"I have head-explody!!!" - NNY

Get Tranced!

Share this post


Link to post
Share on other sites
This is from memory so excuse any errors (I''m at work and my code is at home).


  
class CPresence {
public:
//place dimensions and position of entity here...

};

class CEntity:public CObject {
private:
static CPresence presence[MAX_ENTITIES];
static short counter;

protected:
CPresence *self;

void Init ();

public:
short SetPosition (long x, long y, long z);
void SetDimensions (...);
long GetX ();
long GetY ();
long GetZ ();

virtual short Move (long speed);
};

class CAsteroid:public CEntity {
//place asteroid specific interface here...

public:
CAsteroid (); //calls CEntity::Init()

short Move (...);
void Update (...);
};


Okay, this ain''t pretty... I can post the actual header file tomorrow morning if you''re interested. (Notice I use integers for everything... I''m old-school =b) I''ll explain all this in better detail:

CPresence just holds whatever data your game objects require. CEntity is the parent class of all your game objects and holds an array of CPresence objects. This is very important: the array is both static and private. This means that all of your objects can view the data in the array ONLY if the methods allow it but no CEntity derivative can access it directly. CEntity has no idea what other "entities" are doing unless those entities allow that information to be passed. It is in fact very controlled.

The "counter" property simply counts how many of our CPresence objects have been used so we know what the next available presence is.

The "self" pointer points to its own presence in the array. This allows direct access only to its own data. Notice that this property is protected. This means that CAsteroid and other derivatives of CEntity can access their own data but only theirs and no other object can access it directly.

The Init() method (also protected) assigns a presence to the object. In reality, it only assigns presence[counter++] to *self.

SetPosition() does as its name implies but not without first checking for collisions. It returns true if there is a collision.

Move() accepts a parameter for speed in units per second. This is adjusted to the current framerate and the new position is calculated. That position is sent to SetPosition() where the collision is checked.

The other methods simply allow the outside world to "set" or "get" properties of the CPresence object that *self points to.

The CAsteroid object contains all asteroid specific properties and methods. Notice the constructor. This calls the CEntity::Init() method to snatch up a presence.

This very off-the-cuff. My actual header file includes a LOT more than this and would probably be more confusing. The presence array is actually organized into a two-dimensional array of linked lists (the b!tch can handle 8,000 entities at 66 fps! WOOHOO! ...on a P$ 1.4GHz, btw). Anywayz... is this clear?

btw, I think the CEntity constructor assigns the presence... not sure =/

- Jay

"I have head-explody!!!" - NNY

Get Tranced!

Share this post


Link to post
Share on other sites
Hi coderx75. Cheers for posting your code.

I''m interested to know how you would go about iterating through all the elements in the CPresence array from start to finish.

BD.

Share this post


Link to post
Share on other sites
quote:
Original post by Barn Door
Hi coderx75. Cheers for posting your code.

I''m interested to know how you would go about iterating through all the elements in the CPresence array from start to finish.

BD.


My guess is...a for loop? =)

Even though, I think an std::vector would work better instead of an array. Variable size, the ability to quickly remove an element from the middle of the vector, and ease of use are all pluses.

Share this post


Link to post
Share on other sites
quote:

My guess is...a for loop? =)


What for loop?

The CPresence is ..
quote:
only accessible through the CEntity methods.



.. but the CEntity methods are only accessible through an instance of a derived class such as an asteroid.

But the asteroid or entity..
quote:

can access its own data directly through the ptr but accesses the limited data of others via its own methods.



Key word being ''limited''.

So in order to iterate through all the objects in the array you''d have to put all your elements in another array and get each object, in turn, to pull out its data from the pool at the bottom.

Surely one of the arrays is redundant?

What am I missing?

BD.

Share this post


Link to post
Share on other sites