Callbacks and Con::executef
by Daniel Buckmaster · in Torque Game Engine · 06/09/2007 (3:03 am) · 11 replies
I'm trying to make a custom class of mine use functions defined in script - for example, for ShapeBase objects, you can write your own onCollision in script, and it gets executed when onCollision is called in the engine.
Here's the console function I'm using:
And then in a .cs file, I have:
When I call '[object number].primaryTrigger(1);' in the console, I get no response.
I think the error is somewhere in my Con::executef syntax, and what I could find with a search of the site wasn't too helpful. Could someone explain how to use Con::executef to execute a simple scripted function?
Here's the console function I'm using:
void Pickup::onPrimaryTrigger(bool val)
{
Con::executef(this,3,"onPrimaryTrigger",0,scriptThis(),val);
}
ConsoleMethod(Pickup,primaryTrigger,void,3,3,"The item's primary action")
{
object->onPrimaryTrigger(dAtob(argv[2]));
}I know I could probably condense that into just the console function, but I want to sort this problem out first.And then in a .cs file, I have:
function Pickup::onPrimaryTrigger(%val)
{
echo("Pickup::onPrimaryTrigger" SPC %val);
}When I call '[object number].primaryTrigger(1);' in the console, I get no response.
I think the error is somewhere in my Con::executef syntax, and what I could find with a search of the site wasn't too helpful. Could someone explain how to use Con::executef to execute a simple scripted function?
About the author
Studying mechatronic engineering and computer science at the University of Sydney. Game development is probably my most time-consuming hobby!
#2
06/12/2007 (1:15 pm)
Thanks! I'll try that. The function I was using to study Con::executef was onCollision, which is rather more complicated :P. Made it hard to see the wood through the trees...
#3
06/14/2007 (7:23 am)
Hmm, still doesn't work. I'm going to see if I can figure out how to use the debugger and step into what Con::executef is doing.
#4
I want a console function primaryTrigger(bool val) to cal a script function on the object's datablock - function Datablock::onPrimaryTrigger(%this,%obj,%val), where %this is the datablock, %obj is the object the method was called on, and %val is the boolean primaryTrigger was passed.
I know it'd look something like
06/14/2007 (10:54 am)
Okay, a lot of wierdness is happening. Can anyone show me a way to get this working?I want a console function primaryTrigger(bool val) to cal a script function on the object's datablock - function Datablock::onPrimaryTrigger(%this,%obj,%val), where %this is the datablock, %obj is the object the method was called on, and %val is the boolean primaryTrigger was passed.
I know it'd look something like
Con::executef(mDataBlock,3,"onPrimaryTrigger",scriptThis(),val)But that doesn't seem to work.
#5
Console Method -- code written in c++ on a class namespace intended so that TorqueScript can execute pre-written functionality provided by the class designer for a specific object.
Console Function -- code written in c++ in a global namespace intended so that TorqueScript can execute pre-written functionality that is not part of a specific class, and therefore is called with global scope.
Script Method -- TorqueScript code that is written (either by the class designer, but in script, or by the user of the class) to handle calls made in TorqueScript (by a standard TS method call on an object), or c++ (by a callback) on a specific object (or in some cases on the datablock of a specific object).
Script Function -- as Script Method, but global namespace, and therefore not on a specific object.
First, an observation on use: in general, it doesn't make sense to call a Console Method that is only designed to turn around and issue a callback--just perform the call to the Script Method that you have handling the callback directly, from script. Now of course, if this is just a first stage and you plan on having the Console Method actually do something else besides just call right back to script again, your setup is fine, but if that's all you are going to do, it's redundant to call c++ via a CM only to turn right around again and call TS via a callback.
Now, I think the core problem with your setup is how you are tracking your namespaces. Summary of how I see things according to what is in the post:
"Pickup" -- probably a datablock namespace--specifically, the datablock of objects in your scene. I make this assumption because there isn't a "Pickup" c++ class (unless that is what you are writing).
[object number] -- obviously an object ID, but it's namespace is important. How was the object instantiated (and therefore, what are it's namespaces?). If it is, for example, created with the following code:
Then this is fundamentally different in how you would handle it than
The reason it is fundamentally different is due to how the TS parser is going to look for your functions. Given your calling use ([object number].PrimaryTrigger(1)), the execution flow will be:
--search the namespaces for the object
----the namespace of the object will contain the object's c++ class, and all parent c++ classes
--assuming it finds the ConsoleMethod code (throw in a Con::printf("Hey, found the PrimaryTrigger Console Method" to prove that to yourself), it then will call the associated c++ code
--now your code uses the datablock of your object as it's primary namespace, and looks for the code ::onPrimaryTrigger() to execute. Your syntax and parameters look ok from here for that to happen, but we don't know what the actual datablock is, so we can't (yet) confirm from here if it will call the method you wrote.
Are you seeing any console.log messages when you execute the code? They can be very helpful to know what's happening, because we currently don't know if it's failing to find the Console Method, the Callback Handler, or none of the above.
06/14/2007 (11:30 am)
Taking a half step back here to define some terms and general design principles:Console Method -- code written in c++ on a class namespace intended so that TorqueScript can execute pre-written functionality provided by the class designer for a specific object.
Console Function -- code written in c++ in a global namespace intended so that TorqueScript can execute pre-written functionality that is not part of a specific class, and therefore is called with global scope.
Script Method -- TorqueScript code that is written (either by the class designer, but in script, or by the user of the class) to handle calls made in TorqueScript (by a standard TS method call on an object), or c++ (by a callback) on a specific object (or in some cases on the datablock of a specific object).
Script Function -- as Script Method, but global namespace, and therefore not on a specific object.
First, an observation on use: in general, it doesn't make sense to call a Console Method that is only designed to turn around and issue a callback--just perform the call to the Script Method that you have handling the callback directly, from script. Now of course, if this is just a first stage and you plan on having the Console Method actually do something else besides just call right back to script again, your setup is fine, but if that's all you are going to do, it's redundant to call c++ via a CM only to turn right around again and call TS via a callback.
Now, I think the core problem with your setup is how you are tracking your namespaces. Summary of how I see things according to what is in the post:
"Pickup" -- probably a datablock namespace--specifically, the datablock of objects in your scene. I make this assumption because there isn't a "Pickup" c++ class (unless that is what you are writing).
[object number] -- obviously an object ID, but it's namespace is important. How was the object instantiated (and therefore, what are it's namespaces?). If it is, for example, created with the following code:
%myObject = new Item() {
datablock = Pickup;
};Then this is fundamentally different in how you would handle it than
%myObject = new Pickup() {
datablock = SomeTypeOfPickup;
};The reason it is fundamentally different is due to how the TS parser is going to look for your functions. Given your calling use ([object number].PrimaryTrigger(1)), the execution flow will be:
--search the namespaces for the object
----the namespace of the object will contain the object's c++ class, and all parent c++ classes
--assuming it finds the ConsoleMethod code (throw in a Con::printf("Hey, found the PrimaryTrigger Console Method" to prove that to yourself), it then will call the associated c++ code
--now your code uses the datablock of your object as it's primary namespace, and looks for the code ::onPrimaryTrigger() to execute. Your syntax and parameters look ok from here for that to happen, but we don't know what the actual datablock is, so we can't (yet) confirm from here if it will call the method you wrote.
Are you seeing any console.log messages when you execute the code? They can be very helpful to know what's happening, because we currently don't know if it's failing to find the Console Method, the Callback Handler, or none of the above.
#6
The final thing I want to happen is this:
-Player calls primaryTrigger, a console method, on a Pickup object when he wants it to do its thing.
-Pickup object calls the onPrimaryTrigger script method on its datablock.
-onPrimaryTrigger, defined for each datablock, will do whatever the type of Picku[p i's being used for should do.
So this way, I can define a script method titled DatablockName::onPrimaryTrigger(%this,%obj,%val) for each instance of PickupData. That way Pickups using different datablocks behave differently when they are used.
06/15/2007 (1:32 pm)
Pickup is a custom class I've written based off Item - sorry, should have made that more explicit.The final thing I want to happen is this:
-Player calls primaryTrigger, a console method, on a Pickup object when he wants it to do its thing.
-Pickup object calls the onPrimaryTrigger script method on its datablock.
-onPrimaryTrigger, defined for each datablock, will do whatever the type of Picku[p i's being used for should do.
So this way, I can define a script method titled DatablockName::onPrimaryTrigger(%this,%obj,%val) for each instance of PickupData. That way Pickups using different datablocks behave differently when they are used.
#7
Do realize that you can call a script method from another script method, even if it's on an object's datablock namespace:
In general the rule of thumb for using ConsoleMethods is:
--create a ConsoleMethod when you want c++ code to execute on the "command" of script. As I've explained above, if all your c++ code is going to do is to turn around and execute script (via a callback), then just call that script directly from script.
Given those confirmations, there shouldn't be anything syntactically incorrect with what I see above. We just need to see the assignment of the datablock in the object's creation, as well as the callback handler on the object itself--you probably have a typo or some other mis-match that is causing the code to not be executed, or possibly just a simple syntax error in your .cs file that is causing your handler to not be loaded...so check the console.log and see if you are getting anything that helps, or use Torsion to trace your calls :)
06/15/2007 (2:59 pm)
Ok, I can see that making sense (in a way) with what you've got set up.Do realize that you can call a script method from another script method, even if it's on an object's datablock namespace:
%myObject.getDataBlock().activateTriggerEffect();
In general the rule of thumb for using ConsoleMethods is:
--create a ConsoleMethod when you want c++ code to execute on the "command" of script. As I've explained above, if all your c++ code is going to do is to turn around and execute script (via a callback), then just call that script directly from script.
Given those confirmations, there shouldn't be anything syntactically incorrect with what I see above. We just need to see the assignment of the datablock in the object's creation, as well as the callback handler on the object itself--you probably have a typo or some other mis-match that is causing the code to not be executed, or possibly just a simple syntax error in your .cs file that is causing your handler to not be loaded...so check the console.log and see if you are getting anything that helps, or use Torsion to trace your calls :)
#8
pickup.cs:
Typing [object number].primaryTrigger(1); in the console crashes the engine with no message but the windows 'this program needs to close'. Here's the C++:
06/15/2007 (11:24 pm)
Quote:Do realize that you can call a script method from another script method, even if it's on an object's datablock namespace:Yeah, I guess that'd be the smart way. I was partly just looking for experience with coding, but meh. In other classes based on Pickup (I'm planning classes for munitions, ranged weapons and melee weapons), there may be some C++ work to do in the primaryTrigger method, so I thought I'd set it up like this first.
%myObject.getDataBlock().activateTriggerEffect();
pickup.cs:
datablock PickupData(TestPickup)
{
category = "Pickups";
shapeFile = "~/data/shapes/items/healthKit.dts";
mass = 1;
friction = 1;
elasticity = 0.3;
emap = true;
};
function PickupData::create(%data)
{
%obj = new Pickup() {
dataBlock = %data;
};
return %obj;
}
function TestPickup::onPrimaryTrigger(%this,%obj,%val)
{
echo("Pickup onPrimaryTrigger" SPC %this SPC %obj SPC %val);
}Typing [object number].primaryTrigger(1); in the console crashes the engine with no message but the windows 'this program needs to close'. Here's the C++:
void Pickup::primaryTrigger(bool val)
{
Con::executef(mDataBlock,3,"onPrimaryTrigger",scriptThis(),val);
}
ConsoleMethod(Pickup,"primaryTrigger",void,3,3,"Desc")
{
object->primaryTrigger(dAtob(argv[2]));
}
#9
This is very unusual--you don't normally create an object within a datablock's namespace like this (there isn't nothing actually wrong with it, but it makes me suspicious of the calling context.
How do you actually call the above code?
Also, in your console method, have you actually tested the value of argv[2]? Given the type of crash, I'm betting this is the problem. Have you used Visual Studio with breakpoints and/or inspecting the call stack after the crash?
06/15/2007 (11:33 pm)
As long as you understand the reasons when it's good to call a ConsoleMethod, and when it's better to go right from script to script, learning is awesome :) I just wanted to make sure that both you, and others that follow this discussion understand the methodology well.function PickupData::create(%data)
{
%obj = new Pickup() {
dataBlock = %data;
};
return %obj;
}This is very unusual--you don't normally create an object within a datablock's namespace like this (there isn't nothing actually wrong with it, but it makes me suspicious of the calling context.
How do you actually call the above code?
Also, in your console method, have you actually tested the value of argv[2]? Given the type of crash, I'm betting this is the problem. Have you used Visual Studio with breakpoints and/or inspecting the call stack after the crash?
#10
06/15/2007 (11:55 pm)
Con::executef doesn't like being passed parameters that aren't strings of some kind. To fix the most obvious problem in your code, you want to do something like:void Pickup::primaryTrigger(const bool& _value)
{
// Execute a callback to: Pickup::onPrimaryTrigger(%this, %object, %trigger)
Con::executef(mDataBlock, 3, "onPrimaryTrigger", scriptThis(), (_value ? "1" : "0"));
}
ConsoleMethod(Pickup, "primaryTrigger", void, 3, 3, "")
{
object->primaryTrigger(dAtob(argv[2]));
}
#11
Stephen - I was under the impression that the create method was needed to tie in to the mission editor. As for argv[2], I just used the number from another 1-parameter console method. I'll check with a breakpoint... argv[2] contains '0x0148bb05"1"', and the type is 'const char'. (That was from calling primaryTrigger(1);)
I can try stepping into Con::executef, but I have a feeling I won't understand any of what's going on (have taken a glance at the code...).
For now I'll make Daniel's suggested change.
EDIT: I stepped into Con::executef just for fun, anjd I actually discovered something. I think argv is somehow getting confused - in the argv[128] array, there are the arguments "onPrimaryTrigger","onPrimaryTrigger",and "1545" (ID of the obejct I called it on). So somehow bool val is getting lost.
*Now* I'm going to make Daniel's change.
EDIT: That did it! Now the onPrimaryTrigger is returning the correct values!
Thank you both very much for the help :). I wish there was a hugs and kisses smiley. Sort of.
06/16/2007 (12:17 am)
Daniel - thanks for the tip!Stephen - I was under the impression that the create method was needed to tie in to the mission editor. As for argv[2], I just used the number from another 1-parameter console method. I'll check with a breakpoint... argv[2] contains '0x0148bb05"1"', and the type is 'const char'. (That was from calling primaryTrigger(1);)
I can try stepping into Con::executef, but I have a feeling I won't understand any of what's going on (have taken a glance at the code...).
For now I'll make Daniel's suggested change.
EDIT: I stepped into Con::executef just for fun, anjd I actually discovered something. I think argv is somehow getting confused - in the argv[128] array, there are the arguments "onPrimaryTrigger","onPrimaryTrigger",and "1545" (ID of the obejct I called it on). So somehow bool val is getting lost.
*Now* I'm going to make Daniel's change.
EDIT: That did it! Now the onPrimaryTrigger is returning the correct values!
Thank you both very much for the help :). I wish there was a hugs and kisses smiley. Sort of.
Torque 3D Owner Maxime Jouin
Default Studio Name
For the short explanation for con::executef when executing a console method you need to first pass a pointer to a SimObject, then the number of the arguments the method takes (+& for the name of the method), then the name of the method, and finally all the arguments the method takes.
To execute a console function you just drop the first parameter which is the pointer to a SimObject.
Remember, always pass the number of arguments your function/method takes +1 to take into account the name of the method.