Game Development Community

Configurable Callbacks

by Michael Woerister · in Torque Game Builder · 01/23/2006 (7:42 am) · 24 replies

Hi,

it seems to me it's the case very often that the various types of sceneobjects need to do something different in there callback. E.g. you have a player, an enemy, and another enemy, all of which are t2dAnimatedSprite, but all of which want to do something different in the onTimer-callback. Right now you have to do something like this:
function t2dAnimatedSprite::onTimer(%this)
{
      switch(%this.spriteType)
     {
           case $TYPE_PLAYER: ... break;
           case $TYPE_ENEMY1: ... break;
           case $TYPE_ENEMY2: ... break;
     };
}

This can be very annoying, I think, as you have to modify this function every time you add another type of object that needs its own callback, and it gets slower every time as the function has more an more cases to evaluate.

My solution to this in script looks like this:
function t2dAnimatedSprite::onTimer(%this)
{
    if( %this.onTimerCallback )
       call( %this.onTimerCallback, %this);
}

where %this.onTimerCallback was some function name you specified yourself.
However, as callbacks tend to get called very often it would be good, to do this in C++, with no overhead. If any t2dSceneObject had a member mOnTimerCallback that defaults to "onTimer" and just gets called instead of the "onTimer" literal string, this would mean absolutly no overhead performance-wise. (Though you had to store on additional string table pointer for every callback you want to make configurable).
This way would be perfectly backwards compatible and fairly easy to implement.

Have a nice day!

- Michael
Page «Previous 1 2
#1
01/23/2006 (5:08 pm)
Have you looked into the OOP addon for Torquescript? The OOP addon handles this scenario through a form of polymorphism similar to what you would expect from C++. You can even create subclasses and derive from t2dAnimateSprite. This method works fairly well for callbacks. I even used it to create an entire t2d based GUI... The OOP addon is available in the resources area. Just thought I'd point it out in case you missed it :)
#2
01/24/2006 (10:20 am)
Yeah, I have seen that. Thank you. Maybe I'll will use that. Why isn't this standard for TorqueScript? Performance I guess. But it would be very useful.
#3
01/25/2006 (10:57 pm)
@michael, I think your solution in script looks great!

I thought I read on one of Melv's plans that there was going to be a new kind of per-object callbacks in 1.1 (possibly having to do with GUI events) but I cannot find that post now maybe I dreamed it :-)
#4
03/04/2006 (5:24 pm)
I've seen in the source code that there are unused data members in t2dSceneObject:
class t2dSceneObject : public SimObject
{
private:
    typedef SimObject           Parent;

   // Namespace linkage.
   StringTableEntry mClassName;
   StringTableEntry mSuperClassName;

public:

This looks quite like t2dSceneObject are going to get the same OOP functionality as ScriptObjects. If that's the case that would be very cool! Really, incredibly useful.
#5
03/04/2006 (5:25 pm)
I mentioned this I think in a different post, but yes, namespace linking and availability is being given another run through, as it's not as intuitive as it should be. I honestly don't know what the final result will be, but it should hopefully be much more useful!
#6
03/04/2006 (5:28 pm)
Wow, that was a fast answer! :)

And I think this is really some good place to invest some work in. The callbacks are a very useful part of TGB.
#7
03/05/2006 (6:43 am)
I agree completely!

Ironically, the ease of use combined with the lack of using datablocks as extensively as they are used in TGE has put us in this situation...commonly in TGE, callbacks are executed on the datablock of a particular object, giving the developers the ability to right callback handlers in the datablock Namespaces and activate them simply by changing the datablock of an object.

Since for the most part TGB objects don't use datablocks in this way, it leaves us with a much more limited Namespace, simply the class of the object....and since every object in a scene is in some way a t2dSceneObject, it doesn't give us much flexibility at all.
#8
03/10/2006 (2:16 am)
Btw, is this something that will happen soon? Or is it still a better idea to lay out your object hierarchies in c++ to get proper polymorphism?
#9
03/10/2006 (10:22 am)
They current namespace situation basically has given all t2d objects the ScriptObject functionality (has a class and a superclass that you can refer to), and works like a charm once you figure it out. Tutorials and TDN will have examples to demonstrate how it will work for the next build.

During the boot camp this week we used a build of the engine that had this fix, and we were able to do things like:
(hypothetical examples here, not taken right from the code)

WheelPowerUp::installPowerUp(...)
{

}

PowerUpBaseClass::installPowerUp(...)
{
...
}

t2dStaticSprite::installPowerUp(...)
{
...
}

And it would track through the various namespaces and find the "last" (meaning in the inheritence chain, not time implemented/loaded) appropriate method to execute.
#10
03/10/2006 (10:25 am)
Well that news makes me happy :D Thanks!
#11
03/10/2006 (10:40 am)
Quote:
They current namespace situation basically has given all t2d objects the ScriptObject functionality (has a class and a superclass that you can refer to), and works like a charm once you figure it out. Tutorials and TDN will have examples to demonstrate how it will work for the next build.

Does this mean only 3 levels of inheritance? (which should be enough in most cases).

An example to check if I understand this right. I set up my object and functions this way:
$x = new t2dStaticSprite()
{
   class = "myClass";
   superClass = "mySuperClass";
};

If I now call e.g. $x.someMethod() on this object it would try to call myClass::someMethod(). If that does not exist it would try to call mySuperClass::someMethod(), then t2dStaticSprite::someMethod(), t2dSceneObject::someMethod() and so on, right? That would be all I need.

IMPORTANT EDIT:
Will this new build be released soon to the public? Or should I just copy the onAdd() code from script object into t2dSceneObject::onAdd() ?
#12
03/10/2006 (12:26 pm)
This is great news! I have been trying to tackle this very issue for nearly 2 weeks now (off and on). Is there any timeline on when this might be released? Is it in the near/distant future? Just trying to gauge where I should devote my efforts!
#13
03/11/2006 (7:53 am)
Maybe someone nice wants to post the code diff?
#14
03/11/2006 (8:20 am)
It's slightly more complicated than just 3 inheritences, because you have to keep track of the (automatic) namespace for the object's unique name if present, as well as a parallel namespace for the object's instantiation class and it's inheritence as well, but yes, basically 2 user defined namespaces plus the current.

It can be just a touch involved to set this up properly, so we're still stepping through various iterations and make sure they stay consistent.

The code change itself is very easy (as Michael mentioned, it's a combination of copying the ::onAdd stuff in script object into the various t2dXXXObject's, as well as defining class and superclass as persistent fields).

We won't be releasing an official "patch" as far as I am aware because the final implementation isn't confirmed yet, but release of the next build is quite near--not "once a week alpha builds" near, but it's not going to be 3 months either--sorry I can't be more clear on the exact date.
#15
03/11/2006 (9:14 am)
Ok, so I am the nice one ;)
Here is the code diff to enable ScriptObject-like namespace behavior for all t2dSceneObject-derivates. Be aware though: this is a hack. I put this together for myself so I can continue to work on my game. As Stephen said, the official version that comes with the next release might look different in it's functionality.


Add this to t2dSceneObject::onAdd in t2dSceneObject.cc at approx. line 5900 (just before Con::executef(this, 1, "onAdd"); :
// superClassName -> t2dxxx
   StringTableEntry parent = getClassRep()->getNameSpace()->mName;
   if(mSuperClassName[0])
   {
      if(Con::linkNamespaces(parent, mSuperClassName))
         parent = mSuperClassName;
   }

   // className -> superClassName
   if (mClassName[0])
   {
      if(Con::linkNamespaces(parent, mClassName))
         parent = mClassName;
   }

   // objectName -> className
   StringTableEntry objectName = getName();
   if (objectName && objectName[0])
   {
      if(Con::linkNamespaces(parent, objectName))
         parent = objectName;
   }

   // Store our namespace
   mNameSpace = Con::lookupNamespace(parent);

and this to t2dSceneObject::initPersistFields() at about line 6060:
addField("class",             TypeString,        Offset(mClassName, t2dSceneObject));
    addField("superClass",        TypeString,        Offset(mSuperClassName, t2dSceneObject));

If you want this to be specify-able through datablocks (which I have not tested yet), you additionally have to modify t2dSceneObject::transferDatablock() at line 5715. Add these:
if (mConfigDataBlock->isSpecified("defaultScriptClass") )
         mClassName = mConfigDataBlock->mDefaultScriptClass;

      if (mConfigDataBlock->isSpecified("defaultScriptSuperClass") )
         mSuperClassName = mConfigDataBlock->mDefaultScriptSuperclass;

This will only work for TGB 1.1 Beta 1.1.
Again, this is not official.

-Michael
#16
03/11/2006 (1:44 pm)
It's not official as he said...but it is just about exactly what I had in the build I used to teach with last week! (Nice job Michael!).

There is an outside possibility that a new namespace manager may make it to the next build, which is why I was being dodgy about it, but quite honestly I'd expect to see what you see above, or very nearly for the short term "official" solution.
#17
03/12/2006 (4:03 am)
Quote:
(Nice job Michael!)

Thanks! I am very skilled in both, the copy and the paste ;)
#18
03/13/2006 (2:44 am)
Just wanted to note that I am working with this new functionality and I must say: It is a pleasure!
Not being able to do that probably was one of the main reason why I prefered C++ over TS. But now I can really use TS for prototyping (and later map the prototype to C++ if I need to).
#19
03/13/2006 (9:22 am)
Ah, this is a god send, thank you very much for sharing!
#20
03/13/2006 (1:47 pm)
Yes, this was a workflow issue that never was apparent in TGE because of the use of datablocks (pretty much -everything- had a datablock, and you used the datablock's namespace for grouping behaviours) but since TGB doesn't use datablocks nearly as much for this purpose, we needed a different way.

It really does make things so much easier for organizing callbacks and grouping behaviours!
Page «Previous 1 2