Game Development Community

Behavior Method "Multi-Casting" Could be Awesome

by Charlie Patterson · in Torque Game Builder · 07/20/2011 (8:40 pm) · 3 replies

Either a bug fix or a programming paradigm idea:

short version


When an owner receives a method call, for example onMouseDown(), all of its behaviors with that method are called, for example behavior1.onMouseDown and behavior2.onMouseDown. (Cool!) However, once inside one of these, you can not call other methods on the owner and have it also go to every behavior that defines it, say %this.owner.takesDamage(). (See lots of examples below.)

The exception is the rare case that you are not inside a method called from the owner. Since all event callbacks are through the owner, this doesn't leave many places.

This seems to be because the calling of behavior methods from within behaviors has been purposely avoided as the code snippet below shows:

behaviorComponent.cpp: 108
   // We don't want behaviors to call a method on its owner which would recursively call it
   // again on the behavior and cause an infinite loop so we mark it when calling the behavior
   // method and trap it if it reenters and force it to call the method on this object.
   // This is a quick fix for now and I will review this before the end of the release.

I'm not sure if there is a major problem with mutual recursion here, or just a concern it may happen? In the latter case, I feel that recursion and its perils are natural and shouldn't require throwing out the baby with the bath water to avoid a potential error. I really, really want to be able to call behaviors from within other behaviors.

long version


I love behaviors, and I love to use them as "multi-cast" methods -- if an object has more than one behavior with a method, say "onMouseDown()", then all of the onMouseDown's in that object run!

This works for your own methods as well. Let's suppose you define a generic-sounding method like "takesDamage(%amount)" and use it in several behaviors:
* One behavior subtracts the pain amount from your objects hit points.
* Another behavior blinks red when the pain is over 25.
* Another behavior stops moving the object if it has taken damage in the last 3 seconds.

A behavior can signal all its "sibling behaviors" (in the same owner) by calling %this.owner.takesDamage(50);

Unfortunately, this only works if you are in a method that was not called through the owner, which is rare.

tests

To be specific about what doesn't work, check out this lengthy list of tests. :)

function Behavior1::onCollision(%this)
{
	 // [b] we ultimately wants to call %this.owner.takesDamage()[/b] so other
	 // sibling behaviors (Behavior2, Behavior3) will run their methods

   // note we are inside of an owner-derived callback, so we can't "multi-cast"!

   // all of these [i]won't[/i] work
   %this.owner.takesDamage();       // the case to begin with
   %this.helper();                  // can't trick it through another function call
   %this.schedule(0, takesDamage);  // never even tried to go through owner
   %this.owner.helper();            // WILL find Behavior1::helper unexpectedly
   %this.owner.schedule(0, helper); // the helper will be called owner-derived so no go

	 // these [i]will[/i] work but only during the next timeslice
   %this.schedule(0, helper);
   %this.owner.schedule(0, takesDamage);
}

function Behavior1::helper(%this)
{
    %this.owner.takesDamage();
}

What I've been doing, as an experiment is lots of scheduling of functions in the next time slice
%this.owner.schedule(0, takesDamage); However I've been lucky that in my game waiting until the next time slice hasn't been a problem.

#1
07/21/2011 (4:41 am)
It is really inconvenient that recursoin is prevented in such a cryptic manner, but I find this limitation to be necessary. Since behavior methods are called with the help of some engine magic, there is no way to tell if there is a recursion possibility or not just by looking at the script.

One of the proper ways to shoot yourself in the foot, besides schedule, might be to use events. Also you may want to experiment with "call" and "callMethod" methods.
#2
08/10/2011 (5:00 pm)
@Rpahut - I finally got around to playing with this some more. The idea of "call" and "callMethod" sound like they could be great hack-arounds, and looking at the source code, I thought they may actually work.

But for some reason, they didn't, unless you know better on how to use them?

I tried

%this.owner.call("mcSetActive", true);
// or...
%this.owner.callMethod("mcSetActive", true);
#3
08/11/2011 (12:34 pm)
Nope, can't say I do. I'm just scheduling stuff around and it doesn't seems to cause any trouble to my game so far.

Actually I'm wondering if one tick delay makes any difference. Scheduled calls can pretty much be applied before next object update, in that case there is no practical difference to direct calls.