Legacy Documentclose button

Important: The information in this document is obsolete and should not be used for new development.

Previous Book Contents Book Index Next

Inside Macintosh: Processes
Chapter 4 - Vertical Retrace Manager / Using the Vertical Retrace Manager


Spinning the Cursor

Some VBL tasks need access only to global variables that they do not share with the main program. For example, you might wish to design a VBL task that animates the beachball or watch cursor to indicate that the user must wait while the computer finishes some lengthy processing. The main application might use the application-defined procedures StartSpinning and StopSpinning to install and remove the VBL task, but the application might not need to know, for example, which beachball or watch cursor the VBL task is displaying at any given time. The VBL task itself would need to know this information, because it must know which cursor to display when it is time to change the cursor.

One way to implement such a VBL task is to use application global variables and set up the A5 register properly, as described in the previous section, "Accessing Application Global Variables in a VBL Task." An alternate method, however, is simply to store the information that the VBL task needs directly after the task record in memory, just as you can store information about the program's A5 value there. Then, because the VBL task has access to all of the information it needs, it does not need to set up and restore the A5 register.

The listings that follow use that strategy to implement cursor spinning. This cursor spinning task implements simple animation of any number of cursor frames stored in contiguous resources in the program's resource fork.

Listing 4-11 provides a type definition for a cursor information record. This record holds the task record and information specific to cursor spinning. Listing 4-11 also defines several constants and a global variable to hold a cursor information record.

Listing 4-11 Defining a cursor information record

CONST
   kInterval = 4;                   {frequency in interrupts}
   kNumberOfCursors = 4;            {total number of frames}
   kInitialResID = 128;             {ID of first cursor resource}
TYPE
   CursorsList = ARRAY[1..kNumberOfCursors] OF CursHandle;
   CursorTask = 
      RECORD
         myVBLTask:  VBLTask;       {the actual VBLTask}
         myCursors:  CursorsList;   {handles to the cursors}
         myFrame:    Integer;       {cursor frame to display next}
      END;
   CursorTaskPtr = ^CursorTask;
VAR
   gMyCursTask:   CursorTask;       {global cursor info. record}
Listing 4-12 shows the VBL task itself. The task changes the cursor and resets the task record's vblCount field so that the Vertical Retrace Manager executes the task again.

Listing 4-12 Changing the cursor within a VBL task

PROCEDURE ChangeCursor;
TYPE
   BooleanPtr = ^Boolean;                    {to check a low-memory global}
VAR
   recPtr:  CursorTaskPtr;
BEGIN
   recPtr := CursorTaskPtr(GetVBLRec);       {get cursor information}
   {If the cursor is busy, we should not change it.}
   IF NOT BooleanPtr(CrsrBusy)^ THEN
      WITH recPtr^ DO                        {update cursor information}
      BEGIN
         SetCursor(myCursors[myFrame]^^);    {display the next cursor}
         myFrame := myFrame + 1;             {advance to next cursor frame}
         IF myFrame > kNumberOfCursors THEN
            myFrame := 1;                    {wrap around to first frame}
      END;     
   recPtr^.myVBLTask.vblCount := kInterval;  {set task to run again}
END;
The ChangeCursor procedure retrieves the address of the VBL task record. If the cursor isn't already being changed, then ChangeCursor changes the cursor to the next one in sequence and resets the index of the next cursor to display. Finally, ChangeCursor sets itself to run again after the appropriate number of interrupts have occurred.

Note
It is permissible to call SetCursor at interrupt time, provided that the cursor handle is locked and that some other routine is not currently modifying the cursor. The system global variable CrsrBusy has the value TRUE if the cursor is busy; in that case, you should not call SetCursor. Listing 4-12 illustrates the proper way to change the cursor at interrupt time. ·
Listing 4-13 defines the procedure StartSpinning, which you can call before beginning some lengthy operation. Because VBL tasks cannot depend on the validity of unlocked handles, the StartSpinning procedure must lock the cursor handles in memory before SetCursor is called in the ChangeCursor procedure.

Listing 4-13 Installing the cursor-spinning task into a vertical retrace queue

PROCEDURE StartSpinning;
CONST
   kInitialDelay = 120;          {initial delay before starting to spin}
VAR
   myErr:   OSErr;
   count:   Integer;
BEGIN
   {Initialize cursor information.}
   FOR count := 1 TO kNumberOfCursors DO
   BEGIN
      {Load cursor into memory.}
      gMyCursTask.myCursors[count] := GetCursor(kInitialResID + count - 1);
      {Lock cursor so that we can call SetCursor at interrupt time.}
      HLockHi(Handle(gMyCursTask.myCursors[count]));
   END;
   gMyCursTask.myFrame := 1;     {display cursor with kInitialResID first}

   WITH gMyCursTask.myVBLTask DO {initialize the VBL task record}
   BEGIN
      qType := ORD(vType);       {set queue type}
      vblAddr := @ChangeCursor;  {get address of VBL task}
      vblCount := kInitialDelay; {set task frequency}
      vblPhase := 0;             {no phase}
   END;

   myErr := VInstall(@gMyCursTask.myVBLTask);
END;
Notice that the initial delay (specified by the kInitialDelay constant in the vblCount field) is much larger than the number of interrupts between subsequent cursor changes (specified by the kInterval constant). This prevents the cursor from starting to spin until a reasonable time (about 2 seconds) has elapsed.

Listing 4-14 shows how to remove the cursor-spinning task from the vertical retrace queue.

Listing 4-14 Removing the cursor-spinning task from its vertical retrace queue

PROCEDURE StopSpinning;
VAR
   myErr:   OSErr;
   count:   Integer;
BEGIN
   {Remove the task record from its queue.}
   myErr := VRemove(@gMyCursTask.myVBLTask);

   {Free memory occupied by the cursors.}
   FOR count := 1 TO kNumberOfCursors DO
      ReleaseResource(Handle(gMyCursTask.myCursors[count]));

   InitCursor;                         {restore the arrow cursor}
END;
Depending on the needs of your application, you might want to load the cursors into memory at application-launch time and release them when your application quits. If so, you need to modify the StartSpinning and StopSpinning procedures accordingly.


Previous Book Contents Book Index Next

© Apple Computer, Inc.
17 JUN 1996