Game Development Community

dev|Pro Game Development Curriculum

Plastic Gem #32: Schedules

by Anthony Rosenbaum · 07/24/2008 (6:40 am) · 2 comments

Download Code File


i936.photobucket.com/albums/ad202/vincismurf/banner.jpg


Schedules, they are a core aspect to Torque scripting. Today's gem will deal with some simple scheduling issues to make your work less buggy.

What is a schedule? A schedule allows you to execute some code after an amount of time has elapsed. For instance you might have a cut scene system in which you have the program go to the next part of the cut scene automatically. However what if you allow a person to click thru the cut scene at a faster pace, you must also account for the fact a schedule was initiated and prevent it from executing once the time has elapsed.

First off, we need to know the syntax to use for schedules and a support function for schedules. There are two options for scheduling, a global schedule in which you can call any function you wish or a local schedule in which you call a method on an object.

%id = schedule(%milliseconds, %referanceObject, "functionName", [%Arg1, %Arg2]);
%id = %referanceObject.schedule(%milliseconds, "functionName", [%Arg1, %Arg2]);

You should notice that the two function calls are essentially the same except one is called on an object while the other can call a global function. It is presumed that if you passed in a %referanceObject into the global function it would call that object's method, instead of a global function. In most cases, though if you are using the global function approach the reference object is set to zero. You might note that I have provided only two parameters in the example, but in actuality there is no limit to how many parameters you pass in to the specified function. Finally you will also notice that the schedule call will return a scheduleId. This is very useful for monitoring the status of the schedule.

Here is a quick example of both uses.

Global
function showSimTime(){
echo("ShowSimTime:"SPC getSimTime());
}
%time = 1500; // 1.5 seconds
echo("currentSimTime:" SPC getSimeTime());
schedule(%time, 0, "showSimTime");

Local
function AGui::GuiTime(%this, %oldTime){
	echo("old Time was:" SPC %oldTime);
	echo("new time is:" SPC  getSimTime());
}
%time = 2500; //2.5 seconds
AGui.schedule(%time, "guiTime", getSimeTime());

Both of these code blocks show the simTime when the function was scheduled and when it is executed.

How would you prevent a schedule from executing once it is initiated? That is what the scheduleId is for. Our next example will take a GUI and move it from position to position automatically, but we will also allow the a click to force it to the next position. We will accomplish this with the isEventPending and cancel global functions.

%bool = IsEventPending($scheduleId);
cancel($scheduleId);

IsEventPending will tell us if the Id passed in is waiting to be executed, if so it will return true. If we don't want that to happen we can pass the id into the cancel function to stop it from being executed. Note, if the event has already been executed then IsEvenPending will return false.

$index =0;
$local[0] = "10 10";
$local[1] = "10 20";
$local[2] = "20 20";
$local[3] = "20 10";

function autoMove(%gui){
%gui.position = $local[$index];
$index++;
if($index >= 4)
   $index = 0;
%time = 2000;//execute every 2 seconds
$scheduledEvent = schedule(%time, 0, "autoMove", %gui);
}

function gui::onClick(%this){
	if(isEventPending($scheduledEvent))
		cancel($scheduledEvent);

	autoMove(%this);
}

The same code can be written to be encapsulated as such

function gui::onwake(%this){
%this.index = 0;
%this.local[0] = "10 10";
%this.local[1] = "10 20";
%this.local[2] = "20 20";
%this.local[3] = "20 10";
}

function gui::autoMove(%this){
if(isEventPending(%this.scheduledEvent))
return;
%gui.position = %this.local[%this.index];
%this.index++;
if(%this.index >= 4)
   %this.index = 0;
%time = 2000;//execute every 2 seconds
%this.scheduledEvent = %this.schedule(%time, "autoMove");
}

function gui::onClick(%this){
	if(isEventPending(%this.scheduledEvent))
		cancel(%this.scheduledEvent);
	%this.autoMove();
}

In my opinion encapsulation makes the code much cleaner.

I would like to conclude this resource by revisiting Gem 4: Animated Guis

Now you might have noticed that the animation function take parameters for a delay and duration.
%ctrl.animateCenter(%x, %y, %delay, %duration, %ease);

These values are NOT part of scheduling; you might think this could be used to schedules moves like the earlier examples, but it cannot. Here is something you do not want to do.

BAD
$index =0;
$local[0] = "10 10";
$local[1] = "10 20";
$local[2] = "20 20";
$local[3] = "20 10";

%delay[0] = 0; //%delaybetween moves
%delay[2] = 2000;
%delay[3] = 4000;
%delay[4] = 6000;

%dur = 2000;//duration of each move
%ctrl.animateCenter(getWord($local[0], 0), getWord($local[0], 1), %delay[0], %dur);
%ctrl.animateCenter(getWord($local[1], 0), getWord($local[1], 1), %delay[1], %dur);
%ctrl.animateCenter(getWord($local[2], 0), getWord($local[2], 1), %delay[2], %dur);
%ctrl.animateCenter(getWord($local[3], 0), getWord($local[3], 1), %delay[3], %dur);

The result of this is BAD.This example will move the %ctrl once, the move will be delayed six seconds before it animated to position "20 10". By calling the animate function repetitively you have overwritten the values used in animation system.

The correct way would be to write it like this

Good
function gui::onwake(%this){
%this.index = 0;
%this.local[0] = "10 10";
%this.local[1] = "10 20";
%this.local[2] = "20 20";
%this.local[3] = "20 10";
%this.dur = 2000;
}

function gui::autoMove(%this){
if(%this.isAnimating())
%this.cancelAllAnimations();
%this.animateCenter( getWord(%this.local[%this.index], 0), getWord(%this.local[%this.index], 1), 0 %this.dur);
%this.index++;
if(%this.index >= 4)
   %this.index = 0;
%time = 2000;//execute every 2 seconds
%this.scheduledEvent = %this.schedule(%time, "autoMove");
}

function gui::onClick(%this){
	if(isEventPending(%this.scheduledEvent))
		cancel(%this.scheduledEvent);
	%this.autoMove();
}

Notice how I also used the animation methods for checking to see if a GUI is already animating and if so I cancel the animation, so we can do the new animation.

I hope this article has helped you become more aware of Torque Scheduling power, as long as you remember to hold on to schedule Ids and check to see if they are pending before scheduling something new you should find scheduling is a great asset for the Torque Scripter.

#1
10/14/2008 (11:50 pm)
good : )
#2
02/19/2011 (12:13 am)
great how to on clean scheduling