Game Development Community

Calling behavior method on owner in a behavior

by And Yet It Moves · in Torque Game Builder · 07/17/2007 (3:23 pm) · 10 replies

The following situation doesnt work anymore:
just some simple behavior TestBehavior1 and an sceneObject "$subject" which owns it.

$subject.pleaseBehave();

TestBehavior1::pleaseBehave(%this) {
   %this.owner.comeOn();
}

TestBehavior1::comeOn(%this) {
   echo ("no!!!");
}

this will result in "unknown command comeOn" at %this.owner.comeOn();
instead i have to use %this.comeOn(); then it works.
so the problem is: i still can call any behavior-method on an object directly as long as im not inside a behavior.
from inside behavior i only can call the method on the behavior but not on the object.

this would be no problem as long as im staying within the same behavior. but in our situation we have a lot of different behaviors working together and situations like these:

$subject owns TestBehavior1, TestBehavior2 and TestBehavior3;

$subject.pleaseBehave();

TestBehavior1::pleaseBehave(%this) {
   %this.owner.comeOn();
}

TestBehavior2::comeOn(%this) {
   echo ("no!!!");
}

TestBehavior3::comeOn(%this) {
   echo ("#$%&=?!");
}

this wont work either, the call to comeOn would have to look like this:

TestBehavior1::pleaseBehave(%this) {
   %this.owner.getBehavior("TestBehavior2").comeOn();
   %this.owner.getBehavior("TestBehavior3").comeOn();
}

so this is starting to get confusing, and its i guess hard to work around if it changes which behaviors the object owns.

So is this a bug? and if not why was this changed? it heavily corrupted our code...

thanks,
ayim team

#1
07/17/2007 (4:08 pm)
This has been reported, it's definitely a functionality that should be included. Thanks for bringing this to our attention--hardcoding the getBehavior() calls in there is not very flexible but is an alternative while we wait for the 1.5 post-release.
#2
07/18/2007 (2:00 am)
There is an alternative: TorqueScript has function handle capability.

Haven't tried yet, but potentially you could just make
%this.owner.PleaseBehave = %this.PleaseBehave;

This will naturally not allow multiple behaviors to get executed but thats something I actually suspect to happen as a function can and must only be implemented at a unique place, otherwise the language / OO is massively broken.

If I need several behaviors called, I would create a call redirector behavior, that assigns it self to a specific method (setable in the behavior dialog) and call all other behaviors on its owner with that method :)
#3
07/18/2007 (4:39 am)
Thanks for the tip, but i hope it wont be neccesary as this worked fine in the beta versions, and should work again in the post-release(?)
#4
07/18/2007 (8:30 am)
Well it may or may not make it into the soonest release but it *is* being worked on. :)
#5
09/19/2007 (11:10 am)
Hi again,

after a while of working with this behavior behavior described above, i found this "bug" quite useful: being forced to address a behavior directly to call its methods lead to a better structured code and less confusion. it gives you much more control if you are able to call only selected behaviors if they have the same methods, and its in fact quite easy to write helperfunctions which call a method on all behaviors of an object.

so i would suggest not to solve this "bug", but to disable the possibility to call behaviormethods on an owner directly at all (as described its now still possible if you are not calling from within a behavior). what do you think about this?
#6
09/19/2007 (1:25 pm)
Well personally, I'd like to see it solved - being able to call behaviors via the object makes writing modular behaviors cleaner and is consistent with the way the built-in functions are handled (onMouseOver, for instance, gets sent via the object).

And even when the functionality is restored, there's nothing that prevents continuing to call behavior functions explicitly via the behavior. No need for it to be either-or.
#7
08/27/2008 (3:01 pm)
Wondering what's the current condition of this bug - calling a behavior function via the owner sometimes works (always works from the console, but often not from scripts), but the fact that it's inconsistent makes behaviors a lot less useful. 1.7.4 seems to have made this somewhat worse than in 1.7.2, actually.

As it stands currently, behaviors are less versatile than assigning a class; if this were fixed, they'd be significantly more useful.
#8
08/27/2008 (5:04 pm)
Update: Looking through the relevant engine code, I found the cause of this, and a solution.

The cause is a flag designed to prevent an infinite loop, if the programmer does something stupid like this:
function testBehavior::screwUp(%this)
{
  echo("Screwing Up!");
  %this.owner.screwUp();  // Causes an infinite loop
}
Ok, it probably wouldn't be this obvious in real code, but you shouldn't do it in any case. To prevent this happening, the flag prevents calling a behavior method on an object while in the process of calling a different behavior method on that object.


But it has the side effect of preventing legitimate things like this:
function testBehavior::printMessage(%this, %msg)
{
  echo("Message: " @ %msg);
}

function testBehavior::printDefaultMessage(%this)
{
  %this.owner.printMessage("Default");
}
Again, just a simplified example - the main situations where this comes up are objects with multiple behaviors on them that call each-other indirectly - such as a GUI system, or a creature.


The fix is quite simple - just ignore the flag.
In component/behaviors/behaviorComponent.cpp (line 105), comment out these two lines:
if( mInBehaviorCallback ) 
      return Parent::_callMethod( argc, argv, true );
And then change line 187 from this:
if( !mInBehaviorCallback )
To this:
if ( true )

Problem solved. Just don't put any infinite loops in your behaviors.
#9
09/16/2009 (6:23 pm)
Good catch! This fixed a problem I was having where I could sometimes call a method on an object that was behavior/component controlled, but couldn't call it at other times as it said the method did not exist.

I believe that the functionality should be for methods to always work. Then, have the engine code changed so that in the case of an infinite loop, to an AssertFatal (could possibly do a loop count, and if it hits a high number in a short period of time - like 500 times in a few seconds - we can assume it's in an infinite loop and AssertFatal). We shouldn't be doing anything in script anyway that happens 500 times in a few seconds - all that should be reserved for engine code.
#10
09/18/2009 (10:39 am)
For those who do not have access to the sources there is small workaround that might be helpful. Try to schedule(0, 0, exec, %this.owner@".yourMethod(arg1, arg2);")

It's nasty way, can work only just after behavior call, can not return any data from method call and has a lot of other issues - but without fixing binary it's the only way i know to workaround this "security solution for dummies" (while(true) is still possible).