Cancelling a function

Author Topic: Cancelling a function  (Read 2453 times)

Practically got me tearing my hair out, gonna post and then go to bed.

So.  This package
Code: [Select]
package KeiCarRacing_EngineSounds
{
function armor::onMount(%this,%obj,%col)
{
Parent::onMount(%this,%obj,%col);
if (!isObject(%col) || %obj.getMountNode() != 0)
return;

echo(%col.getDataBlock().vehicleSounds);
if (%col.getDataBlock().vehicleSounds) {
%col.playAudio(0, KeiCarRacing_StartupSound);

schedule(1800, 0, KeiCarRacingSpeedCheck, %this, %col);
}
}
function armor::onUnMount(%this,%obj,%col)
{
Parent::onUnMount(%this,%obj,%col);

if (%obj.getMountNode() != 0)
return;

if (%col.getDataBlock().vehicleSounds) {
cancel(1);
%col.playAudio(0, KeiCarRacing_ShutOffSound);
}
}
};
ActivatePackage(KeiCarRacing_EngineSounds);

Does not work how I want it to (and I'm not sure why).  Specifically, the cancel in the second function.  This cancel is called on a schedule in the KeiCarRacingSpeedCheck function to cancel out the schedule at the end:
Code: [Select]
function KeiCarRacingSpeedCheck(%this, %obj)
{
if(!isObject(%obj))
return;

%vehicle = %obj;
%slot = 0; //Play the sound in the driver's spot
%speed = vectorLen(%obj.getVelocity());

if(%speed < 4)
%vehicle.playAudio(%slot, KeiCarRacing_IdleSound);
else// if (%speed < 20)
%vehicle.playAudio(%slot, KeiCarRacing_ThrottleSound);

schedule(500, 1, KeiCarRacingSpeedCheck, %this, %obj);

}

Whats happening is as follows:

I enter the car, and the audio for the startup sound is played.   The KeiCarRacingSpeedCheck function then kicks in and plays the current sound corresponding with the speed.  However, with the cancel, that schedule is apparently ended right away.  The speed doesn't update if I'm going as fast as possible.
I also know that it cancels it because if I comment it out, once I get in the car, it makes noises until I break the brick.

Can someone point out my mistake?
Should I (can I?) just be using states for this?


Also as a total side note, for whatever reason, if I change the "armor" name(space?) of the two functions to something else, they won't execute.  The framework for this code was ripped from Filipe's SuperKart mod and subsequently has the same name as one of the functions in Support_HandsOnVehicle.  Activating my KeiCarRacing_EngineSounds also causes the player to stand inside the vehicle instead of sitting.  If anyone has an answer as to why they won't execute and the player stands I would love that as well.

Honestly I am incredibly new to TS so if there is something glaringly horrible that I'm doing please let me know!  Thanks for whatever help I can get!

Tags: Cancelling, function, car sounds, sounds
« Last Edit: January 09, 2017, 01:02:30 PM by DaBlocko »

schedule returns an id that you can throw into cancel to cancel that event. Putting in 1 will therefore cancel some random event that got id of 1.

Try save the output of schedule into a variable and put that variable into cancel. Preferably would be to clear the variable out as well to avoid undefined results.

Preferably would be to clear the variable out as well to avoid undefined results.
the number that gets thrown out is the sequence id which is just an ever growing count of the number of schedules that have occurred, so there will in theory be no collisions, if you somehow overflow the unsigned 32 bit int value of the sequence count (4,294,967,295) you probably are going to have bigger issues than managing to wrap all the way back around to that same id and cancel it



also it should not cause any issue but you should keep the second argument on a 'global' schedule as 0, as its a backward ass way of setting a schedule on an object, and if that number doesnt refer to an existing object (in the case of 1 that you have) the schedule might not end up occurring
schedule(time,0,function,args);

to call a schedule on an object you should use this format %obj.schedule(time,function,args);

Stop spreading this goddamn misconception. You're right that it should be 0 if you don't want to use it, and you're right about the correct way to schedule a method, but the 'global' schedule function's second parameter is not meant as an alternative to the method version %object.schedule(). Cancelling the schedule when the specified object is nonexistent is exactly the purpose of that parameter. It is not in any way used to call a method on an object and I've never encountered any Torque documentation that says otherwise.

The point of setting that argument is to set a dependency for the schedule. For example, if you wanted to call a server command on the server side in a schedule, you could use schedule(5000, %client, serverCmdLight, %client); which would automatically cancel the schedule if the client disconnected. You could also make it dependant on another object like %client.player, which would cancel it if they died (and the corpse despawned).

In this case, with schedule(1800, 0, KeiCarRacingSpeedCheck, %this, %col);, the function in question is dependant on its %obj parameter. While there's currently no issue if the schedule goes off after that object is deleted due to the if(!isObject(%obj)) return; part at the top of the function, it wouldn't be an inherently bad idea to include the dependency parameter set to the appropriate object: schedule(1800, %col, KeiCarRacingSpeedCheck, %this, %col);. In this manner you'd avoid errors in the case that the scheduled function is written to assume its parameters already exist, so I'd say it's good practice to do anyway.

I've looked this up ages ago, debated it with several coders here, thoroughly tested it on multiple versions of the engine, and used it in practice. I'm 99% sure I'm right about this, and that last 1% is only due to several respectable coders around here continually saying otherwise despite showing me nothing that would give any reason to believe that.

In short:
Quote
schedule(1000, 0, func, args) -> wait 1000ms then call func(args)
obj.schedule(1000, func, args) -> wait 1000ms then if obj exists call obj.func(args)
schedule(1000, obj, func, args) -> wait 1000ms then if obj exists call obj.func(args) except it doesn't work lol so I guess that parameter is useless WRONG
schedule(1000, obj, func, args) -> wait 1000ms then if obj exists call func(args) RIGHT


as mctwist suggested
Try save the output of schedule into a variable and put that variable into cancel.
you would do something like %obj.engineLoopSched = schedule(delay,0,KeiCarRacingSpeedCheck,%this,%obj);



also worth noting there is no point in passing in the %this variable (the mounting players datablock) into the function your function would be better suited as just
function KeiCarRacingSpeedCheck(%obj)
« Last Edit: January 09, 2017, 01:58:24 PM by Swollow »

the number that gets thrown out is the sequence id which is just an ever growing count of the number of schedules that have occurred, so there will in theory be no collisions, if you somehow overflow the unsigned 32 bit int value of the sequence count (4,294,967,295) you probably are going to have bigger issues than managing to wrap all the way back around to that same id and cancel it

I am well aware of that it's incrementing and having the same value come up twice is close to none, but not impossible. I just made a quick test to see how it could handle large integers in that range, but it would of course take some time to try to overflow the id(Over 5 days for unsigned(I do assume the value is signed as all other integers are signed)) to be feasible.

Still, I'm derailing my own train, so back to why you should clear your variables: Uses less memory and ensures correct state between usage. If your server is somehow up for 30 days, there's a tiny chance that you may use enough schedules to wrap the id and somehow stumble upon an id stored 29 days ago and then break functionality. Resetting the state in this case may be unnecessary, but in all cases you should still do it as it ensures your program wont interfere with other states.

I am well aware of that it's incrementing and having the same value come up twice is close to none, but not impossible. I just made a quick test to see how it could handle large integers in that range, but it would of course take some time to try to overflow the id(Over 5 days for unsigned(I do assume the value is signed as all other integers are signed)) to be feasible.

Still, I'm derailing my own train, so back to why you should clear your variables: Uses less memory and ensures correct state between usage. If your server is somehow up for 30 days, there's a tiny chance that you may use enough schedules to wrap the id and somehow stumble upon an id stored 29 days ago and then break functionality. Resetting the state in this case may be unnecessary, but in all cases you should still do it as it ensures your program wont interfere with other states.
oh right, its initialized as an unsigned int in the torque source but when its spit out into torque script it will probably be converted to a signed int, on top of that, it doesnt matter if you loop back to the same id unless that schedule is active, and having a schedule set for longer than even several minutes is not something you should do let alone 30 days

Update! It works!
Code: [Select]
package KeiCarRacing_EngineSounds
{
function armor::onMount(%this,%obj,%col, %slot)
{
Parent::onMount(%this,%obj,%col, %slot);
if (!isObject(%col) || %obj.getMountNode() != 0)
return;

if (%col.getDataBlock().vehicleSounds) {
%col.playAudio(0, KeiCarRacing_StartupSound);
schedule(1800, 0, KeiCarRacingSpeedCheck, %col);
}
}
function armor::onUnMount(%this,%obj,%col, %slot)
{
Parent::onUnMount(%this,%obj,%col, %slot);

if (%obj.getMountNode() != 0)
return;

if (%col.getDataBlock().vehicleSounds) {
cancel(%col.engineLoopSched);
%col.playAudio(0, KeiCarRacing_ShutOffSound);
}
}
};
ActivatePackage(KeiCarRacing_EngineSounds);


function KeiCarRacingSpeedCheck(%obj)
{
if(!isObject(%obj))
return;

%vehicle = %obj;
%slot = 0; //Play the sound in the driver's spot
%speed = vectorLen(%obj.getVelocity());

//We use else if's so that it will find a true statement then skip the rest
if(%speed < 4)
%vehicle.playAudio(%slot, KeiCarRacing_IdleSound);
else// if (%speed < 20)
%vehicle.playAudio(%slot, KeiCarRacing_ThrottleSound);

%obj.engineLoopSched = schedule(500, 0, KeiCarRacingSpeedCheck, %obj);
}

Thanks for the help on this!

Preferably would be to clear the variable out as well to avoid undefined results.
Whats the syntax to do this?

Edit: Also figured out why I was standing when mounting instead of sitting.  Forgot to include %slot in armor::onMount and onUnMount (and subsequently the Parent functions).

Also could someone explain why changing "armor" to something else doesn't call the function?  Is there something inherent about "armor" that is tied to mounting in TS?

Thanks for all the help!
« Last Edit: January 09, 2017, 01:33:12 PM by DaBlocko »

also it should not cause any issue but you should keep the second argument on a 'global' schedule as 0, as its a backward ass way of setting a schedule on an object, and if that number doesnt refer to an existing object (in the case of 1 that you have) the schedule might not end up occurring
schedule(time,0,function,args);

to call a schedule on an object you should use this format %obj.schedule(time,function,args);
Stop spreading this goddamn misconception. You're right that it should be 0 if you don't want to use it, and you're right about the correct way to schedule a method, but the 'global' schedule function's second parameter is not meant as an alternative to the method version %object.schedule(). Cancelling the schedule when the specified object is nonexistent is exactly the purpose of that parameter. It is not in any way used to call a method on an object and I've never encountered any Torque documentation that says otherwise.

The point of setting that argument is to set a dependency for the schedule. For example, if you wanted to call a server command on the server side in a schedule, you could use schedule(5000, %client, serverCmdLight, %client); which would automatically cancel the schedule if the client disconnected. You could also make it dependant on another object like %client.player, which would cancel it if they died (and the corpse despawned).

In this case, with schedule(1800, 0, KeiCarRacingSpeedCheck, %this, %col);, the function in question is dependant on its %obj parameter. While there's currently no issue if the schedule goes off after that object is deleted due to the if(!isObject(%obj)) return; part at the top of the function, it wouldn't be an inherently bad idea to include the dependency parameter set to the appropriate object: schedule(1800, %col, KeiCarRacingSpeedCheck, %this, %col);. In this manner you'd avoid errors in the case that the scheduled function is written to assume its parameters already exist, so I'd say it's good practice to do anyway.

I've looked this up ages ago, debated it with several coders here, thoroughly tested it on multiple versions of the engine, and used it in practice. I'm 99% sure I'm right about this, and that last 1% is only due to several respectable coders around here continually saying otherwise despite showing me nothing that would give any reason to believe that.

In short:
Quote
schedule(1000, 0, func, args) -> wait 1000ms then call func(args)
obj.schedule(1000, func, args) -> wait 1000ms then if obj exists call obj.func(args)
schedule(1000, obj, func, args) -> wait 1000ms then if obj exists call obj.func(args) except it doesn't work lol so I guess that parameter is useless WRONG
schedule(1000, obj, func, args) -> wait 1000ms then if obj exists call func(args) RIGHT

I learned something new today


Oh wow, so I have been using schedule(time, obj, func, args) correctly :D



Also could someone explain why changing "armor" to something else doesn't call the function?  Is there something inherent about "armor" that is tied to mounting in TS?
Armor is the player's datablock, calling anything else does not work because the player is not using it for the most part.

I don't really know why it's called Armor and not PlayerData. Could anyone explain that? It makes sense but for creating datablocks it's the same classname for the most part, so I wonder why this one is different than the others.
« Last Edit: January 09, 2017, 09:31:55 PM by Kyuande »

Oh wow, so I have been using schedule(time, obj, func, args) correctly :D


Armor is the player's datablock, calling anything else does not work because the player is not using it for the most part.

I don't really know why it's called Armor and not PlayerData. Could anyone explain that? It makes sense but for creating datablocks it's the same classname for the most part, so I wonder why this one is different than the others.
Armor is the className of the datablock.

But, why is it called that and not PlayerData?

But, why is it called that and not PlayerData?
PlayerData is the type of datablock. I'm pretty sure it is possible to declare methods as PlayerData::doAThing(... without any issue, but Armor has been a convention for player datablock class names since the early days. I believe it comes from Tribes 2, where player datablocks were things like LightMaleHumanArmor, or HeavyBiodermArmor, and Armor was the class name for all player datablocks. Presumably this allowed for players datablocks to exist that didn't inherit the usual behaviors, though I'm not sure that functionality was utilized. It definitely wasn't when Blockland was built on top of all that, and like many other aspects of Torque in Blockland, it's been neglected to the point where its original benefits have been lost and yet changing it would be a nightmare to do.

oh right, its initialized as an unsigned int in the torque source but when its spit out into torque script it will probably be converted to a signed int, on top of that, it doesnt matter if you loop back to the same id unless that schedule is active, and having a schedule set for longer than even several minutes is not something you should do let alone 30 days

I checked to set the schedule for one hour, at it actually worked. I thought that before there was a limit for 10 minutes, but apparently you can set it really high without having any bigger issues. I do think that having too many events may raise other issues. But of course, the time span we're using this shouldn't make that big of a difference anyway, but it is still a good thing to reset variables. It's also good for debugging, as there's less logs too look through.

Whats the syntax to do this?

Empty string. Setting it to 0 is also valid, but will only reset the variable state, not remove it.

Code: [Select]
%col.engineLoopSched = "";
-Wall of information-

I think I've heard about it years ago. But still, I thought it was like that before someone told me. It makes more sense that way.

I also checked how schedule works with objects and namespaces by trial'n'error: All the tests I did and the only one that really worked correctly was:

Code: [Select]
%obj.schedule(%time, "test", %value);
That is, calling a namespace method through a schedule is only possible by calling schedule through the object method and include only the method name and the first argument will the the object used. You cannot call namespace methods through the global schedule and you cannot specify what namespace to call either.

As you said, the second argument in global schedule is only used to make sure the object exists when the schedule is calling the function. Calling schedule through an object is applying that functionality automatically.

Empty string. Setting it to 0 is also valid, but will only reset the variable state, not remove it.
Sweet, thanks.

And one final question (I hope).
Whenever I leave the vehicle, I get this warning in the console:
Code: [Select]
Add-Ons/Vehicle_KeiCarRacing/KeiCarRacing_Sounds.cs (54): Unable to find object: '' attempting to call function 'getDataBlock'
BackTrace: ->Armor::doDismount->[HandsOnVehicleSupport]Armor::onUnMount->[KeiCarRacing_EngineSounds]Armor::onUnMount

Whats the reason for this?  When you unMount it doesn't know what object you just unMounted from so it has to backtrace?  Is there an easy way to fix it?

Edit: Oh another one yay.
Due to scheduling time (I believe), if you enter and exit the vehicle quickly the vehicle sound continues to play and wont stop unless its respawned.  I added
 
Code: [Select]
%obj.getMountedObject(0) == -1to the If statement at the beginning of the SpeedCheck to attempt to fix this (thus, when there is no player, it won't continue playing the sound).
Code: [Select]
if(!isObject(%obj) || %obj.getMountedObject(0) == -1) {
return;
}
Apparently thats not the right function to use as now it only plays the sound that corresponds with the speed of the vehicle when you entered (i.e. doesn't change sound based on speed).
Edit: should've been getMountedObject (still doesn't work).


Fixed!  Used getMountedObjectNode instead.
« Last Edit: January 10, 2017, 02:25:54 PM by DaBlocko »