14.4. Timer-Queue Sensors

Timer-queue sensors, like data sensors, can be used to invoke user-specified callbacks. Instead of attaching a timer-queue sensor to a node or path in the scene graph, however, you simply schedule it, so that its callback is invoked at a specific time. (Timer-queue sensors are sorted within the timer queue by time rather than by priority.) Inventor includes two types of timer-queue sensors: SoAlarmSensor SoAlarmSensor SoAlarmSensor and SoTimerSensor SoTimerSensor SoTimerSensor .

The following sequence describes the necessary steps for setting up timer- queue sensors:

  1. Construct the sensor.

  2. Set the callback function (see the section called “Callback Function”).

  3. Set the timing parameters for the sensor.

  4. Schedule the sensor using the schedule() method.

  5. When you are finished with the sensor, delete it.

Timing parameters (when and how often the sensor is triggered) should not be changed while a sensor is scheduled. Use the unschedule() method to remove a sensor from the queue, change the parameter(s), and then schedule the sensor again.

An SoAlarmSensor SoAlarmSensor SoAlarmSensor , like an alarm clock, is set to go off at a specified time. When that time is reached or passed, the sensor's callback function is invoked. A calendar program might use an SoAlarmSensor SoAlarmSensor SoAlarmSensor , for example, to put a flag on the screen to indicate that it's time for your scheduled 2 o'clock meeting.

Use one of the following methods to set the time for this sensor:

The time is specified using the SbTime SbTime class, which provides several different formats for time. Use the getTime() method of SoAlarmSensor SoAlarmSensor SoAlarmSensor to obtain the scheduled time for an alarm sensor.

Example 14.3, “ Using an Alarm Sensor shows using an SoAlarmSensor SoAlarmSensor SoAlarmSensor to raise a flag on the screen when one minute has passed.

Example 14.3.  Using an Alarm Sensor

static void
raiseFlagCallback(void *data, SoSensor *)
{
  // We know data is really a SoTransform node:
  SoTransform *flagAngleXform = (SoTransform *)data;
  
  // Rotate flag by 90 degrees about the z axis:
  flagAngleXform->rotation.setValue(SbVec3f(0,0,1), M_PI/2);
}

{
  ...
  
  SoTransform *flagXform = new SoTransform;
  
  // Create an alarm that will call the flag-raising callback:
  SoAlarmSensor *myAlarm = new SoAlarmSensor(raiseFlagCallback, flagXform);
  myAlarm->setTimeFromNow(60.0);
  myAlarm->schedule();
  
  ...
}
  
void RaiseFlagCallback(SoSensor sensor)
{
  // Rotate flag by 90 degrees about the Z axis:
  flagXform.rotation.SetValue(new SbVec3f(0.0f, 0.0f, 1.0f), (float)(Math.PI / 2.0f));
}

{
  ...
  
  SoTransform flagXform = new SoTransform();

  // Create an alarm that will call the flag-raising callback:
  myAlarm = new SoAlarmSensor();
  myAlarm.Action = new SoSensor.SensorCB(RaiseFlagCallback);
  myAlarm.SetTimeFromNow(new SbTime(5.0f));
  myAlarm.Schedule();

  ...
}
class RaiseFlagTask implements Runnable
{
  SoTransform flagAngleXform;
  
  RaiseFlagTask(SoTransform flagAngleXform)
  {
    this.flagAngleXform = flagAngleXform;
  }
  public void run()
  {
    // Rotate flag by 90 degrees about the Z axis:
    flagAngleXform.rotation.setValue(new SbVec3f(0,0,1), (float)Math.PI/2F) ;
  }
}

{
  ...
  
  SoTransform flagXform = new SoTransform() ;
  
  // Create an alarm that will call the flag-raising task:
  SoAlarmSensor myAlarm = new SoAlarmSensor(new RaiseFlagTask(flagXform)) ;
  myAlarm.setDelay(2000); // 2 seconds
  myAlarm.schedule() ;

  ...
}

An SoTimerSensor SoTimerSensor SoTimerSensor is similar to an SoAlarmSensor SoAlarmSensor SoAlarmSensor , except that it is set to go off at regular intervals—like the snooze button on your alarm clock. You might use an SoTimerSensor SoTimerSensor SoTimerSensor for certain types of animation, for example, to move the second hand of an animated clock on the screen. You can set the interval and the base time for an SoTimerSensor SoTimerSensor SoTimerSensor using these methods:

Before changing either the interval or the base time, you must first unschedule the sensor, as shown in Example 14.4, “ Using a Timer Sensor ”.

Example 14.4, “ Using a Timer Sensor ” creates two timer sensors. The first sensor rotates an object. The second sensor goes off every 5 seconds and changes the interval of the rotating sensor. The rotating sensor alternates between once per second and ten times per second. (This example is provided mainly for illustration purposes. It would be better (and easier) to use two engines to do the same thing (see Chapter 15, Engines).

Example 14.4.  Using a Timer Sensor

// This function is called either 10 times/second or once every
// second; the scheduling changes every 5 seconds (see below):
static void
rotatingSensorCallback(void *data, SoSensor *)
{
   // Rotate an object...
   SoRotation *myRotation = (SoRotation *)data;
   SbRotation currentRotation = myRotation->rotation.getValue();
   currentRotation = SbRotation(SbVec3f(0,0,1), M_PI/90.0) *
            currentRotation;
   myRotation->rotation.setValue(currentRotation);
}

// This function is called once every 5 seconds, and
// reschedules the other sensor.
static void
schedulingSensorCallback(void *data, SoSensor *)
{
   SoTimerSensor *rotatingSensor = (SoTimerSensor *)data;
   rotatingSensor->unschedule();
   if (rotatingSensor->getInterval() == 1.0)
      rotatingSensor->setInterval(1.0/10.0);
   else 
      rotatingSensor->setInterval(1.0);
      rotatingSensor->schedule();
}

{
   ...

   SoRotation *myRotation = new SoRotation;
   root->addChild(myRotation);

   SoTimerSensor *rotatingSensor =
      new SoTimerSensor(rotatingSensorCallback, myRotation);
   rotatingSensor->setInterval(1.0); //scheduled once per second
   rotatingSensor->schedule();

   SoTimerSensor *schedulingSensor =
   new SoTimerSensor(schedulingSensorCallback, rotatingSensor);
   schedulingSensor->setInterval(5.0); // once per 5 seconds
   schedulingSensor->schedule();
}
  
using System;
using System.Windows.Forms;

using OIV.Inventor.Nodes;
using OIV.Inventor.Win.Viewers;
using OIV.Inventor;
using OIV.Inventor.Sensors;

namespace _12_4_TimerSensor
{
  public partial class MainForm : Form
  {
    SoWinExaminerViewer myViewer;
    SoTimerSensor rotatingSensor;
    SoTimerSensor schedulingSensor;
    SoRotation myRotation;

    public MainForm()
    {
      InitializeComponent();
      CreateSample();
    }

    // This function is called either 10 times/second or once every
    // second; the scheduling changes every 5 seconds (see below):
    void rotatingSensorCallback(SoSensor sensor)
    {
      // Rotate an object...
      SbRotation currentRotation = myRotation.rotation.Value;
      currentRotation = new SbRotation(new SbVec3f(0.0f, 0.0f, 1.0f), (float)(Math.PI / 90.0f)) * currentRotation;
      myRotation.rotation.Value = currentRotation;
    }

    // This function is called once every 5 seconds, and
    // reschedules the other sensor.
    void schedulingSensorCallback(SoSensor sensor)
    {
      rotatingSensor.Unschedule();
      if (rotatingSensor.GetInterval().GetValue() == 1.0f)
        rotatingSensor.SetInterval(new SbTime(1.0f / 10.0f));
      else rotatingSensor.SetInterval(new SbTime(1.0f));
      rotatingSensor.Schedule();
    }

    public void CreateSample()
    {
      SoSeparator root = new SoSeparator();

      myRotation = new SoRotation();
      root.AddChild(myRotation);

      rotatingSensor = new SoTimerSensor();
      rotatingSensor.Action = new SoSensor.SensorCB(rotatingSensorCallback);
      rotatingSensor.SetInterval(new SbTime(1.0f)); // scheduled once per second
      rotatingSensor.Schedule();

      schedulingSensor = new SoTimerSensor();
      schedulingSensor.Action = new SoSensor.SensorCB(schedulingSensorCallback);
      schedulingSensor.SetInterval(new SbTime(5.0f)); // once per 5 seconds
      schedulingSensor.Schedule();

      String defaultFileName = "../../../../../data/jackInTheBox.iv";

      String fileName = defaultFileName;
      SoInput inputFile = new SoInput();
      if (inputFile.OpenFile(fileName) == false)
      {
        Console.WriteLine("Could not open file " + fileName);

      }
      SoWWWInline.SetReadAsSoFile(true);

      root.AddChild(SoDB.ReadAll(inputFile));
      myViewer = new SoWinExaminerViewer(this, "", true,
          SoWinFullViewer.BuildFlags.BUILD_ALL, SoWinViewer.Types.BROWSER);
      myViewer.SetSceneGraph(root);
      myViewer.SetTitle("Two Timers");
    }
  }
}
  


// inner classes to declare tasks
class RotatingSensorTask implements Runnable
{
  private SoRotation myRotation;
  public RotatingSensorTask(SoRotation rot)
  {
      myRotation = rot;
  }

  public void run()
  {
    // Rotate an object...
    SbRotation currentRotation = myRotation.rotation.getValue() ;
    currentRotation.multiply(new SbRotation(new SbVec3f(0,0,1), (float) Math.PI/90F)) ;
    myRotation.rotation.setValue(currentRotation) ;
  }
}

class SchedulingSensorTask implements Runnable
{
  public void run()
  {
    // This function is called once every 5 seconds, and
    // reschedules the other sensor.
    rotatingSensor.unschedule() ;
    if (rotatingSensor.getMsecInterval() == 1000)
      rotatingSensor.setMsecInterval(100);
    else
      rotatingSensor.setMsecInterval(1000);
    rotatingSensor.schedule() ;
  }
}
// end of inner classes declaration

{
...

  SoRotation myRotation = new SoRotation();
  activeRoot.addChild(myRotation);
  
  rotatingSensor = new SoTimerSensor(new RotatingSensorTask(myRotation));
  rotatingSensor.setMsecInterval(1000); // scheduled once per second
  rotatingSensor.schedule();
  
  SoTimerSensor schedulingSensor = new SoTimerSensor(new SchedulingSensorTask());
  schedulingSensor.setMsecInterval(5000); // once per 5 seconds
  schedulingSensor.schedule();

...
}
  

The following descriptions apply only to applications using the Inventor Component Library with the Xt toolkit. Other window system toolkits may have a different relationship between processing of the different queues. If you aren't interested in the details of how timers are scheduled, you can skip this section.

The general order of processing is event queue, timer queue, delay queue. A slight deviation from this order arises because the delay queue is also processed at regular intervals, whether or not there are timers or events pending. The sequence can be described as follows:

SoXt main loop calls XtAppMainLoop:

BEGIN:
If there's an event waiting:
Process all pending timers.
Process the delay queue if the delay queue time-out is
         reached.
Process the event.
Go back to BEGIN.
else (no event waiting)
if there are timers,
   Process timers.
   Go back to BEGIN.
else (no timers or events pending)
Process delay queue.
Go back to BEGIN.

When the timer queue is processed, the following conditions are guaranteed:

For example, in Figure 14.2, “ Triggering and Rescheduling Timers, at time A after the redraw, the timer queue is processed. Three timers have been scheduled in the queue (timers 0, 1, and 2). Timers 0 and 1 are ready to go off (their trigger time has already passed). Timer 2 is set to go off sometime in the future. The sequence is as follows:

  1. Timer 0 is triggered.

  2. Timer 1 is triggered.

  3. The scheduler checks the timer queue (the time is now B) and notices that timer 2's time has passed as well , so it triggers timer 2.

  4. Timers 0, 1, and 2 are rescheduled at time C.

  5. The scheduler returns to the main event loop to check for pending events.


The delay queue is processed when there are no events or timer sensors pending or when the delay queue's time-out interval elapses. By default, the delay queue times out 30 times a second. You can change this interval with the SoDB::setDelaySensorTimeout() method. Idle sensors are ignored when the delay sensor causes processing of the delay queue (because the CPU is not really idle under this condition).

When the delay queue is processed, the following conditions are guaranteed: