The Other Worlds Shrine

Your place for discussion about RPGs, gaming, music, movies, anime, computers, sports, and any other stuff we care to talk about... 

  • Another C++ question for Kup

  • Somehow, we still tolerate each other. Eventually this will be the only forum left.
Somehow, we still tolerate each other. Eventually this will be the only forum left.
 #90381  by Nev
 Wed Jul 20, 2005 12:41 am
Well, or anyone else, but Kup is very helpful at this...

I find myself writing a lot of classes that use states (all game programmers do) and I'd wondered about writing a generic StateSwitching base class (or similar) to use with all of them. The class would just basically accept two functors for each state - one to call when the state is switched to, and one to call when it's switched from, sort of analogous to "loading" and "unloading" a state.

The StateSwitching class interface would be something like:
Code: Select all
class StateSwitching
{
protected:
  void RegisterStateFunctors(int stateID, functor loadFunctor, 
		functor unloadFunctor);
public:
  void SwitchState(int stateID);
  int GetCurrentState();
}
The problem that occurs that makes this impossible is implementing the functors. To preserve good data hiding, I would like to have the functors be implemented as or call private/protected functions in the derived classes, like:
Code: Select all

const int ATTACKING = 0;
const int DEFENDING = 1;
const int KO = 2;

class Enemy: public StateSwitching
{
private:
void ChangeStateToAttacking();
void ChangeStateToDefending();
void ChangeStateToKO();
public:
void Initialize() 
{
RegisterStateFunctors(ATTACKING, &Enemy::ChangeStateToAttacking, NULL);
RegisterStateFunctors(DEFENDING, &Enemy::ChangeStateToDefending, NULL);
RegisterStateFunctors(KO, &Enemy::ChangeStateToKO, NULL);
}  

//some other code that uses the Enemy class
{
Enemy orc;
if (/*something*)
orc.SwitchState(ATTACKING);
else
orc.SwitchState(DEFENDING);
if (/*orc dies*/)
orc.SwitchState(KO);
}
However, the strong typing on C++ pointer-to-member functions mean that I can't pass
Code: Select all
&Enemy::ChangeStateToAttacking
as a parameter to
Code: Select all
StateSwitching::RegisterStateFunctors
.

I don't know that the way I'm going about it is even the most modern way to think about it, so I guess my question is whether or not you're familiar with abstract or inheritable implementations of state-switching mechanisms and how they're implemented in C++. A way to pass member functions of derived classes into base classes wouldn't hurt, either, if you know of any. A language that supports closures seems like it would probably have better facility for this stuff, but sadly I am not working in one at present. I suppose I could define the state-switcher as a template class, but
Code: Select all
Enemy : public StateSwitching<Enemy>
seems like it would just be jumping through a lot of hoops for the final functionality I'd be getting.

I know this is kind of detailed, so if you don't have time/interest in helping it's completely understood.

 #90387  by Kupek
 Wed Jul 20, 2005 10:09 am
I'm going to run through these quickly, so I might miss some points.

First of all, I see two contradictory things going on. You use the term <i>functor</i>, which while not a C++ keyword, means to me <i>function object</i>. But you're passing pointers to member functions, not function objects. Now, regarding pointers to members (including member functions), I'm going to rip directly from Stroustrup:
Bjarne Stroustrup wrote:Just like ordinary pointers to functions, pointers to member functions are used when we need to refer to a function without having to know its name. However, a pointer to a member isn't a pointer to a piece of memory the way a pointer to a variable or a pointer to a function is. It is more like an offset into a structure or an index into an array. When a pointer to [a] member is combined with a pointer to an object of the right type, it yields something that identifies a particular member of a particular object.
Read that until all of it makes sense.

What does this mean in your case? Passing a pointer to a member function isn't sufficient to accomplish what you want, other issues aside. That pointer points to a member function for a particular <i>class</i>, not a particular <i>object</i>.

As for the privacy concerns, I think those are secondary. My main concern is that the base class requires knowledge of the derived classes. You're passing information up the inheritance hierarchy, which is generally going to be messy. I'd go for a different design. The most obvious alternative is to make <tt>StateSwitching</tt> an abstract base class, and make every derived class implement their own <tt>SwitchState()</tt> and <tt>GetCurrentState()</tt> member functions. However, this design will involve code duplication (the state switching skeleton), so it's not optimal. I don't think your final suggestion would work because there's still no way for the base class to know what the states and associated actions are supposed to be.

I'm not convinced inheritance is the way to go here. I'd consider implementing a state switcher object as a standalone class. Each class that needs to switch states contains a state switching object. On construction, it tells its state switching object "This is who I am (the <tt>this</tt> pointer), these are their states (predefined values) and their associated actions (pointers to member functions)." Does passing private member functions to a contained object violate data hiding? I don't think so, since this object is performing private actions for you, and you're hiding this object from the outside world. Basically, each <tt>Enemy</tt> object would contain its own state machine.

You can still use an abstract base class, however, to tell the world "Hey, I can switch states." So you could have a <tt>Switchable</tt> abstract base class, and a <tt>Switcher</tt> object that actually does the state changes. However, there's still some redundancy: you could just as easily inherit from <tt>Switcher</tt>, forcing it to provide both implementation and interface. So then the only real difference between what I proposed and what you proposed is the passing of the <tt>this</tt> pointer so the member functions can actually operate on an object.

Briefly, for the typing concerns, I don't think you're prevented from allowing the outside world access to a pointer to a private member function, just as you're not prevented from allowing the outside world access to a pointer to a private data member.

So, that's a bunch of thoughts on the matter, and I'm still not satisfied with any of the proposed designs.

 #90390  by Nev
 Wed Jul 20, 2005 12:25 pm
Sorry; I knew "functor" was possibly the wrong word. I should have said "abstract idea of a function and its associated data, including this pointer if necessary". I know passing in the pointer-to-member isn't exactly correct - I thought it would get the point across, but you picked up on the fact that it's going to be missing some required data. In my current actual implementation, I am actually using small function objects that contain pointers to the functions, their data, and a pointer to the referring object.

Perhaps using static member functions would be better, since I understand that unlike ordinary pointers-to-member-function, they *are* simple memory addresses. BREW has a general admonition against using too much stack data, however, I think because so many phones have really tiny stacks. Are static member functions allocated using stack space?

I can certainly use an abstract base class, and I may, but there is some code duplication I'm trying to avoid. I actually do think data hiding is paramount in general, and I think it's generally a good design principle anyway. I mean, I suppose I could just tell anyone else who has to use these classes, "Uh, that's public, but it's really not supposed to be. Don't use it." The reason the data hiding comes into play is that, to implement state-switching engines without a good OOP state-switching design mechanism, and actually to implement functors in general, I could use (and have been using, actually - try not to be too horrified) ordinary global functions of the declaration void (void*) that call public functions in the derived classes. However, I don't think I need to tell you I don't like this much.

I wonder if a language with better support for closures would be able to do something like this better.

It's just that in game design, state machines get used a lot. Many are used for agents - I don't know if there's a formal game design term here, but I regard an agent as some data in a representation of a larger "world", which uses simple AI to interact with that world's data and other agents, as well as being able to be "perceived" in the context of that world. Most enemies and neutral/friendly "creatures" in all games I usually think of as agents. But state machines get used for non-AI bits of world data as well (powerups/similar), as well as general "world" states, and the general state of the application (in-menu, in-game, in-transition, etc.) So I was trying to abstract the idea of a state and see if there was any code duplication I could avoid by doing so. It seems that a state, in an OOP context, is a subset of member data that represents the actual quality of being in that state, another subset of member data that represents qualities associated with that state (duration or conditions for change being the main one), as well as the functions that act on those particular sets of data when that state is loaded and when it is unloaded - assuming that a particular state-machine object can only be in one state at a time, of course.

I wonder if the problem is that the idea I'm trying to implement is too abstract in general.

 #90404  by SineSwiper
 Wed Jul 20, 2005 4:09 pm
Wow...
Just like ordinary pointers to functions, pointers to member functions are used when we need to refer to a function without having to know its name. However, a pointer to a member isn't a pointer to a piece of memory the way a pointer to a variable or a pointer to a function is. It is more like an offset into a structure or an index into an array. When a pointer to [a] member is combined with a pointer to an object of the right type, it yields something that identifies a particular member of a particular object.
The aircraft knows where it is at all times. It knows this because it knows where it isn't. By subtracting where it is from where it isn't, or where it isn't from where it is (whichever is the greater), it obtains a difference, or deviation.

The Inertial Guidance System uses deviations to generate error signal commands which instruct the aircraft to move from a position where it is to a position where it isn't, arriving at a position where it wasn't, or now is. Consequently, the position where it is, is now the position where it wasn't; thus, it follows logically that the position where it was is the position where it isn't.

In the event that the position where the aircraft now is, is not the position where it wasn't, the Inertial Guidance System has acquired a variation. Variations are caused by external factors, the discussions of which are beyond the scope of this report.

A variation is the difference between where the aircraft is and where the aircraft wasn't. If the variation is considered to be a factor of significant magnitude, a correction may be applied by the use of the autopilot system. However, use of this correction requires that the aircraft now knows where it was because the variation has modified some of the information which the aircraft has, so it is sure where it isn't.

Nevertheless, the aircraft is sure where it isn't (within reason) and it knows where it was. It now subtracts where it should be from where it isn't, where it ought to be from where it wasn't (or vice versa) and intergrates the difference with the product of where it shouldn't be and where it was; thus obtaining the difference between its deviation and its variation, which is variable constant called "error".

 #90408  by Kupek
 Wed Jul 20, 2005 5:01 pm
That excerpt is a succint and precise description of an aspect of the language. If you're unfamiliar with the concepts of the language, then it's not going to be transparent to you. But it's not meant to be clear to those unfamiliar with C++, it's meant to be clear to those are familiar with C++.

 #90410  by Nev
 Wed Jul 20, 2005 5:09 pm
SineSwiper wrote:Wow...
Just like ordinary pointers to functions, pointers to member functions are used when we need to refer to a function without having to know its name. However, a pointer to a member isn't a pointer to a piece of memory the way a pointer to a variable or a pointer to a function is. It is more like an offset into a structure or an index into an array. When a pointer to [a] member is combined with a pointer to an object of the right type, it yields something that identifies a particular member of a particular object.
The aircraft knows where it is at all times. It knows this because it knows where it isn't. By subtracting where it is from where it isn't, or where it isn't from where it is (whichever is the greater), it obtains a difference, or deviation.

The Inertial Guidance System uses deviations to generate error signal commands which instruct the aircraft to move from a position where it is to a position where it isn't, arriving at a position where it wasn't, or now is. Consequently, the position where it is, is now the position where it wasn't; thus, it follows logically that the position where it was is the position where it isn't.

In the event that the position where the aircraft now is, is not the position where it wasn't, the Inertial Guidance System has acquired a variation. Variations are caused by external factors, the discussions of which are beyond the scope of this report.

A variation is the difference between where the aircraft is and where the aircraft wasn't. If the variation is considered to be a factor of significant magnitude, a correction may be applied by the use of the autopilot system. However, use of this correction requires that the aircraft now knows where it was because the variation has modified some of the information which the aircraft has, so it is sure where it isn't.

Nevertheless, the aircraft is sure where it isn't (within reason) and it knows where it was. It now subtracts where it should be from where it isn't, where it ought to be from where it wasn't (or vice versa) and intergrates the difference with the product of where it shouldn't be and where it was; thus obtaining the difference between its deviation and its variation, which is variable constant called "error".
What the good glory rassumfrackin' are you talking about? Are you meaning to say it was convoluted? It really wasn't.

 #90423  by SineSwiper
 Wed Jul 20, 2005 7:08 pm
Kupek wrote:That excerpt is a succint and precise description of an aspect of the language. If you're unfamiliar with the concepts of the language, then it's not going to be transparent to you. But it's not meant to be clear to those unfamiliar with C++, it's meant to be clear to those are familiar with C++.
I know, I know. It's just that the language was similar in both quotes.

 #90426  by Kupek
 Wed Jul 20, 2005 7:30 pm
Mental wrote:Perhaps using static member functions would be better, since I understand that unlike ordinary pointers-to-member-function, they *are* simple memory addresses. BREW has a general admonition against using too much stack data, however, I think because so many phones have really tiny stacks. Are static member functions allocated using stack space?
Static member functions won't do the job. They are class-wide; they are not associated with any particular object. In your example, using static member functions would mean that all <tt>Enemy</tt> objects are always in the same state, which I don't think is your intention. As for allocation, they're still class member functions, and will be allocated along with the rest of the class' member functions.
Mental wrote:I actually do think data hiding is paramount in general, and I think it's generally a good design principle anyway.
I agree, and that's not what I'm talking about at all. If an object contains another object, and tells the contained object private things, no data hiding principles are violated.

I don't think closures would really help here, because you want this action to have a well defined name.
Mental wrote: I wonder if the problem is that the idea I'm trying to implement is too abstract in general.
Certainly not, state machines are a common abstraction and I'm sure this sort of thing has come up many times in many places before. If you search around, you might find a good solution. I'll probably give it more thought, just because now I'm curious.

 #90432  by Nev
 Wed Jul 20, 2005 9:09 pm
I did a very very very small bit of research (read: one Google search on "state machines") and found a lot of low-level hardwareish stuff, but nothing on high-level abstractions.

I probably won't look more because, well, I won't...I'd love to, but an abstract state-switcher would come more in handy in being able to provide abstract libraries to others, and right now it looks like I may be the only one working on the code until its conclusion. My global-function-wrapper-mock-delegate thing is horrid, but at least I trust myself not to fuck it up, and right now we have more pressure to get the game done than we do to implement cool high-level abstractions. I consider myself a lot better than some industry people I've met at utilizing and learning modern techniques - a few people I've worked for seem to be like, "if it works and we can do it, we ought to, we have a product to get out the door, go go go", and that always seemed to be the proverbial penny-wise but pound-foolish attitude. But right now we do have a game to finish, and I think I can go back and isolate the state-switching stuff if I find a better way to do it later. I hate to say it, but there are times when one has to go with the best that one has and forego the research time necessary for a better technique, at least for the time being. If you find something, though, let me know!