home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Mac Game Programming Gurus / TricksOfTheMacGameProgrammingGurus.iso / More Source / Libraries / VideoToolbox 95.04.18 / VideoToolboxSources / Timer.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-01-05  |  21.9 KB  |  577 lines  |  [TEXT/MMCC]

  1. /*
  2. Timer.c
  3.  
  4. An interval timer based on Apple's Time Manager. It returns the time, in
  5. seconds, that elapsed between calling StartTimer() and PeekTimerSecs() or
  6. StopTimerSecs(). The timing of a single interval (from StartTimer to PeekTimer,
  7. PeekTimerSecs, StopTimer, or StopTimerSecs) is always very accurate (better than
  8. 1 part in 1000 relative to a good time standard) and a precision of better than
  9. 200 µs. Under System 7, which has the Extended Time Manager, (except on the
  10. Radius Rocket) this accuracy applies to all intervals; measuring splits by
  11. peeking has no effect on the accuracy of subsequent times. Under System 6 (or on
  12. the Radius Rocket even under System 7) each peek will lose a variable amount of
  13. time, compromising accuracy of subsequent times from that timer, due to
  14. documented deficiencies of Apple's old Revised Time Manager. The even older
  15. "Standard" Time Manager is not supported here, and will result in an error
  16. message. Note that you are allowed an unlimited number of timers that each
  17. operate independently. Peeking one timer has no effect on the rest of your
  18. timers.
  19.  
  20.     Timer *NewTimer(void);
  21.     void DisposeTimer(Timer *t);
  22.     void StartTimer(Timer *t);
  23.     double PeekTimerSecs(Timer *t);    // s, with µs precision and no time limit
  24.     double StopTimerSecs(Timer *t);    // s, with µs precision and no time limit
  25.     long PeekTimer(Timer *t);    // µs, up to 36 minutes
  26.     long StopTimer(Timer *t);    // µs, up to 36 minutes
  27.  
  28.     Timer *timer;
  29.     long t;
  30.     
  31.     timer=NewTimer();
  32.     StartTimer(timer);
  33.     do(i=0;i<100;i++);
  34.     t=StopTimer(timer);
  35.     DisposeTimer(timer);
  36.     printf("One hundred iterations takes %ld µs\n",t);
  37.  
  38. The timing result comes in two flavors. StopTimerSecs() and PeekTimerSecs() both
  39. return the time in secs as a double, and can time an interval of essentially
  40. unlimited duration with 200 µs precision. StopTimer() and PeekTimer() return the
  41. time in microseconds (with same 200 µs precision) as a long, which can hold a
  42. time up to 2,147,483,647 µs, which is nearly 36 minutes. I generally prefer
  43. PeekTimerSecs, because it's foolproof (won't overflow), even though it's a bit
  44. slower than PeekTimer. But if you don't have a floating point unit, then
  45. PeekTimerSecs is MUCH slower than PeekTimer.
  46.  
  47. You can have an unlimited number of Timers running at once. The only restriction
  48. is that you must create each Timer, by calling NewTimer() before you use it,
  49. and, obviously, should not use it after calling DisposeTimer().
  50.  
  51. The Time Manager seems to use interrupts at a higher rate than the 10 s interval
  52. I requested here (which would never expire in typical use). I infer this from
  53. the fact that disabling interrupts by SetPriority(7) greatly reduces the timing
  54. value returned by StopTimer(). So don't disable interrupts while you're timing.
  55.  
  56. StopTimer() adds a small offset (about 62 µs on a Mac IIci) to the raw time in
  57. order to return an unbiased estimate of the time from when StartTime was called
  58. to when StopTime() was called. This is the most useful time measure for
  59. measuring the interval between two events (e.g. video frames). Alternatively, if
  60. you wish to measure processing time, then it is more useful to compute the time
  61. from when StartTimer() returns to when StopTimer() is called. For this you
  62. simply subtract the fixed duration of StartTimer(), call-to-return, which is
  63. provided in µs in your Timer structure in the long structure member
  64. "timeToStartTimer".
  65.  
  66.     betweenTime = StopTimer(t) - t->timeToStartTimer;
  67.     
  68. When you use StopTimerSecs() you'd have to divide timeToStartTimer by a
  69. million to convert µs to s. For some purposes this offset is negligibly small.
  70. On my Mac IIci the timeToStartTimer is 240 µs. This offset, and the one
  71. mentioned above are measured automatically the first time you call NewTimer().
  72. Alternatively, instead of having to remember the name of the structure member,
  73. just measure the timeToStartTimer yourself and subtract it from all subsequent
  74. measures:
  75.  
  76.     StartTimer(t);
  77.     s0=StopTimerSec(t);
  78.  
  79. The Timer structures are kept in a linked list so that KillEveryTimer(), which
  80. is placed in the _atexit() queue, will find and kill them when the application
  81. exits.
  82.  
  83. TEST PROGRAM:
  84.  
  85. Measure the same interval in three different ways. All three answers agree, at
  86. least under System 7. Note that timer2 is repeatedly peeked at, while "timer" is
  87. uninterrupted. This checks for any side effects of peeking. (There will be a
  88. substantial loss of time due to peeking if you run without the Extended Time Mgr,
  89. i.e. under System 6 or on Radius Rocket.)
  90.  
  91. void main(void)
  92. {
  93.     double s,s2;
  94.     long ticks,m,m2;
  95.     int i,n=1000;
  96.     Timer *timer,*timer2;
  97.     
  98.     printf("We'll simultaneously measure a single time interval in 3 ways:\n"
  99.         "1. by just starting and stopping the timer\n"
  100.         "2. by starting the timer and repeatedly peeking at it %d times\n"
  101.         "3. by using TickCount(), which runs at 60.15 Hz.\n...\n",n);
  102.     timer=NewTimer();
  103.     timer2=NewTimer();
  104.     Delay(1,&ticks);    // synchronize ourselves to the tick counter for better accuracy.
  105.     StartTimer(timer);
  106.     StartTimer(timer2);
  107.     for(i=0;i<n;i++)s2=PeekTimerSecs(timer2);    // time PeekTimerSecs()
  108.     ticks=TickCount()-ticks;
  109.     s=StopTimerSecs(timer);
  110.     s2=StopTimerSecs(timer2);
  111.     printf("The interval was:\n1. %4.0f ms\n2. %4.0f ms\n3. %4.0f±%.0f ms\n\n"
  112.         ,s*1e3,s2*1e3,1e3*(ticks+0.5)/60.15,1e3*0.5/60.15);
  113.     printf("Each call to PeekTimerSecs() takes %4.0f µs, and loses %4.0f µs.\n"
  114.         ,s*1e6/n,(s-s2)*1e6/n);
  115.     StartTimer(timer);
  116.     StartTimer(timer2);
  117.     for(i=0;i<n;i++)m2=PeekTimer(timer2);    // time PeekTimer()
  118.     m=StopTimer(timer);
  119.     m2=StopTimer(timer2);
  120.     printf("Each call to PeekTimer()     takes %4ld µs, and loses %4ld µs.\n"
  121.         ,m/n,(m-m2)/n);
  122.     DisposeTimer(timer);
  123.     DisposeTimer(timer2);
  124. }
  125.  
  126. ACKNOWLEDGMENT:
  127. David Brainard wrote the first draft of PeekTimer().
  128.  
  129. APPLE DOCUMENTION ERRORS:
  130. There are two errors in the Time Manager chapter of "New Inside
  131. Macintosh: Processes". One of the errors is particularly relevant to the
  132. PowerPC. Both errors are on page 3-8.
  133.  
  134. 1. The manual says, "You should set tmWakeUp and tmReserved to 0 when you first
  135. install an extended Time Manager task." That's correct, but it should go on to
  136. say that "You should set tmReserved to 0 before each and every call to
  137. InsXTime."
  138.  
  139. It took me a long time to discover this. Some computers, e.g. my PowerBook 170,
  140. don't care. But, e.g. the PowerMac 6100, does care. It increments tmReserved
  141. each time a task is installed by InsXTime. If tmReserved reaches 128 then the
  142. Time Manager goes haywire. Conversely, everything runs fine on every Mac I've
  143. tested if tmReserved is zeroed before each call to InsXTime.
  144.  
  145. 2. The manual says "The tmWakeUp field contains the time at which the Time
  146. Manager task specified by tmAddr was last executed (or 0 if it has not yet been
  147. executed)." THIS IS WRONG AND MISLEADING! It should say, instead, "The tmWakeUp
  148. field contains the time at which the Time Manager task specified by tmAddr is
  149. scheduled to be executed (or 0 if it has not yet been scheduled)."
  150.  
  151. RADIUS ROCKET BUG:
  152. This applies to my Radius Rocket in my Mac II using RocketWare 1.5 running under
  153. System 7.1 with System Update.
  154.  
  155. Apparently Radius decided not to implement the Extended Time Manager and only
  156. went so far as diverting the InsXTime trap to InsTime. A minimal fix would be to
  157. at least give honest information by making Gestalt correctly report the Time
  158. Manager version as gestaltRevisedTimeMgr instead of falsely claiming it's
  159. gestaltExtendedTimeMgr. My programs check for this, and I spent a whole day
  160. debugging crashes on my Radius due to this misleading information from Gestalt.
  161.  
  162. According to Gestalt, the Extended Time Manager is present, but in fact the
  163. InsXTime trap is only providing the service expected from the InsTime trap of
  164. the Revised Time Manager. Specifically, in the program below, when we call
  165. InsXTime the second time, with a nonzero tmWakeUp, we are supposed to resume the
  166. previous interval, not start a new one. Since the original interval was 100 s,
  167. there should still be lots of time left in it, and when we call RmvTime for the
  168. last time tmCount should be nonzero. This program fails only on my Radius
  169. Rocket. It runs fine on my Mac II, IIci, IIfx, LC475, Quadra 840av, 
  170. PowerBook 170, and PowerMac.
  171.  
  172. UNDERSTANDING THE CODE OF TIMER.C:
  173. There are a few subtleties to understanding the code. Firstly, you should read
  174. the manual, i.e. the Time Manager chapter of New Inside Mac: Processes. If the
  175. Extended Time Manager is available, PeekTimer takes advantage of its ability to
  176. resume the interrupted interval, so the splits (from PeekTimer and
  177. PeekTimerSecs) won't have any effect on the accuracy of subsequent times.
  178. However, there is a tricky case that requires special handling. If we are very
  179. close to the end of the current interval then the interval might expire before
  180. we have a chance to call PrimeTime(). As explained in the manual on page 3-8
  181. this would amount to an impossible request for a task execution time in the
  182. past. When that happens the Time Mgr changes the the requested time to the
  183. present, which would destroy our synchrony. Therefore PeekTimer checks that
  184. there is at least 10 ms left in the interrupted interval, and, if not, then
  185. advances to the next interval, incrementing the counters appropriately as though
  186. our timer task had been called. This transparently preserves accuracy.
  187.  
  188. HISTORY:
  189. 8/19/92 dgp    based on Time Manager chapter in Inside Mac VI and TimeIt.c, 
  190.     which is now obsolete. I also benefited from examining code by Jonothan Kolodny
  191.     forwarded to me by Thomas Busey.
  192. 8/27/92    dgp    Rewrote everything. Made the interrupt service routine reentrant 
  193.     by eliminating all use of global variables, using only the structure pointed
  194.     to by A1. There can now be an unlimited number of timers active at once.
  195.     Added NewTimer() and DisposeTimer() to manage them. 
  196. 9/10/92    dgp    added calls to VM to HoldMemory() and UnHoldMemory(). According to Apple's
  197.     Memory book this isn't strictly necessary, since Time Manager tasks will be
  198.     called only when it's safe.
  199. 1/11/93 dgp StopTimerSecs() now returns NAN if called with a NULL pointer.
  200. 7/9/93    dgp    Test MATLAB in if() instead of #if. 
  201. 5/28/94 dgp Made compatible with Apple's Universal Headers. Thanks to Bob Dougherty 
  202. (wolfgang@cats.ucsc.edu) for reporting the incompatibility.
  203. 9/7/94    dgp    Made declaration of TimerTask--how it receives the argument ptr--conditional on PowerPC.
  204. 10/22/94 dgp Added PeekTimer, written by David Brainard, and enhanced it to used the Extended
  205. Time Mgr's facility of resuming an interrupted interval. Modularized all the code somewhat,
  206. eliminating duplications.
  207. 10/24/94 dgp Made declaration of GetA0() explicitly indicate that answer is returned in D0, 
  208. for better compatibility with Metrowerks CodeWarrior C.
  209. 10/27/94 dgp It is very annoying for the machine to crash anytime you try to quit your application
  210. by escaping via MacsBugs escape to shell. The problem is that CodeWarrior 4.5 doesn't attach
  211. the atexit() tasks to _EscapeToShell. So I do it here. 
  212. 11/3/94 dgp In response to a bug report by David Brainard that after 128 calls to PeekTimer things
  213. got screwy I replicated the problem and discovered that I could cure it by clearing tmReserved
  214. in ResumeTimer below, though I have no idea why this is necessary.
  215. 11/4/94 dgp After discovering that the Radius Rocket lies, via Gestalt, claiming that
  216. the Extended Time Manager is present when it really isn't, I added code to check for
  217. the services of the Extended Time Manager, and adjust t->timeManagerVersion accordingly.
  218. This eliminates the crashes I was getting when running PeekTimer on the Radius Rocket.
  219. 11/5/94 dgp tested and debugged the code when running without the Extended Time Manager.
  220. 1/5/95    dgp made compatible with Universal Headers 2.
  221. */
  222. #include "VideoToolbox.h"
  223. #include <math.h>    // NAN
  224. #if __powerc
  225.     static pascal void TimerTask(Timer *t);
  226. #else
  227.     static pascal void TimerTask(void);
  228. #endif
  229. void KillEveryTimer(void);
  230. static void PatchExitToShell(void);
  231.  
  232. /* This is a copy for reference. Original is in VideoToolbox.h.
  233. struct Timer{
  234.     TMTask time;
  235.     long ourA5;
  236.     long interval;                    // interval length (in negative µs)
  237.     long elapsed;                    // extra time, (in -µs), beyond elapsedIntervals*interval
  238.     long elapsedIntervals;            // positive count
  239.     long timeToStartTimer;            // minimum time in µs
  240.     long stopDelay;                    // µs from call to stop, re from call to start
  241.     long timeManagerVersion;
  242.     struct Timer *next,*previous;    // doubly linked list of Timers
  243. };
  244. */
  245.  
  246. static Timer defaultTimer,qTimer;
  247. static long vmPresent=0,pageSize=0;
  248. #define TASK_SIZE 1024    // Generous guess for size of routine
  249. static double TimerSecs(Timer *t);
  250. static void ResumeTimer(Timer *t);
  251. static long TimerMicroSecs(Timer *t);
  252. #if !defined(NewTimerProc)
  253.     #define NewTimerProc(proc)     (TimerProcPtr)proc
  254. #endif
  255.  
  256. Timer *NewTimer(void)
  257. {
  258.     static short firstTime=1;
  259.     Timer *t,*tt;
  260.     long j;
  261.     
  262.     if(firstTime){
  263.         firstTime=0;
  264.         qTimer.next=qTimer.previous=NULL;
  265.         if(!MATLAB){
  266.             #if __MWERKS__
  267.                 // In CW4.5 _atexit() only gets called if you quit via abort() or exit().
  268.                 PatchExitToShell();
  269.             #else
  270.                 _atexit(KillEveryTimer);
  271.             #endif
  272.         }
  273.         Gestalt(gestaltVMAttr,&vmPresent);
  274.         vmPresent &= gestaltVMPresent;
  275.         if(vmPresent)Gestalt(gestaltLogicalPageSize,&pageSize);
  276.         t=&defaultTimer;
  277.         t->ourA5 = SetCurrentA5();
  278.         t->time.tmAddr = NewTimerProc(TimerTask);
  279.         t->time.tmCount=t->time.tmWakeUp=t->time.tmReserved=0;
  280.         t->elapsedIntervals=t->elapsed=0;                            
  281.         t->timeManagerVersion=0;
  282.         Gestalt(gestaltTimeMgrVersion,&t->timeManagerVersion);
  283.         if(t->timeManagerVersion>=gestaltExtendedTimeMgr){
  284.             // Let's make sure the Extended Time Manager is really here.
  285.             // Under System 7.1 the Radius Rocket claims it's present, but it's not.
  286.             TMTask time;
  287.  
  288.             time.tmAddr = NULL;
  289.             time.tmCount=time.tmWakeUp=time.tmReserved=0;
  290.             InsXTime((QElemPtr)&time);
  291.             PrimeTime((QElemPtr)&time,-100000000L);    // interval of 100 s
  292.             RmvTime((QElemPtr) &time);
  293.             time.tmReserved=0;                        // undocumented, but necessary on some Macs
  294.             InsXTime((QElemPtr)&time);
  295.             PrimeTime((QElemPtr)&time,0);            // resume interrupted interval
  296.             RmvTime((QElemPtr) &time);
  297.             // a tmCount of zero couldn't happen if the Extended Time Manager were present.
  298.             if(time.tmCount==0)t->timeManagerVersion=gestaltRevisedTimeMgr;
  299.         }
  300.         switch(t->timeManagerVersion){
  301.         case 0:
  302.         case gestaltStandardTimeMgr:
  303.             printf("NewTimer: old System lacks the Revised Time Manager. %s\n",__FILE__);
  304.             return NULL;
  305.         case gestaltRevisedTimeMgr:
  306.         case gestaltExtendedTimeMgr:
  307.         default:
  308.             // The programs in Timer.c assume that t->interval<0
  309.             t->interval = -10000000;        // Set the timer interval to 10 s
  310.         }
  311.         t->next=NULL;
  312.         t->previous=&qTimer;
  313.         t->timeToStartTimer=t->stopDelay=0;
  314.         
  315.         // Measure timeToStartTimer and stopDelay offsets.
  316.         t=NewTimer();
  317.         StartTimer(t);
  318.         t->timeToStartTimer=StopTimer(t);
  319.         tt=NewTimer();
  320.         StartTimer(t);
  321.         StartTimer(tt);
  322.         j=StopTimer(t);
  323.         DisposeTimer(tt);
  324.         t->stopDelay=2*t->timeToStartTimer-j;
  325.         // The computed "cycle" interval will have stopDelay removed.
  326.         // The user wishing to compute the "between" interval will be
  327.         // subtracting the timeToStartTimer, so we should subtract 
  328.         // the stopDelay from that so the stopDelay cancels out when 
  329.         // the "between" time is computed.
  330.         t->timeToStartTimer-=t->stopDelay;
  331.         defaultTimer.timeToStartTimer=t->timeToStartTimer;
  332.         defaultTimer.stopDelay=t->stopDelay;
  333.         return t;
  334.     }
  335.     t=(Timer *)NewPtr(sizeof(Timer));
  336.     if(t!=NULL){
  337.         *t=defaultTimer;
  338.         t->next=qTimer.next;
  339.         if(t->next!=NULL)t->next->previous=t;
  340.         qTimer.next=t;
  341.         if(vmPresent){
  342.             HoldMemory(t,sizeof(*t));
  343.             if(t->time.tmAddr!=NULL)HoldMemory(t->time.tmAddr,TASK_SIZE);
  344.         }
  345.         if(t->timeManagerVersion>=gestaltExtendedTimeMgr)InsXTime((QElemPtr)t);
  346.         else InsTime((QElemPtr)t);
  347.     }
  348.     return t;
  349. }
  350.  
  351. void DisposeTimer(Timer *t)
  352. {
  353.     if(t==NULL)return;
  354.     RmvTime((QElemPtr)t);
  355.     if(vmPresent){
  356.         //UnholdMemory(t,sizeof(*t));
  357.         if(qTimer.next==t && t->next==NULL && t->time.tmAddr!=NULL)
  358.             UnholdMemory(t->time.tmAddr,TASK_SIZE);
  359.     }
  360.     t->previous->next=t->next;
  361.     if(t->next!=NULL)t->next->previous=t->previous;
  362.     DisposPtr((Ptr)t);
  363. }
  364.  
  365. void StartTimer(Timer *t)
  366. {
  367.     if(t==NULL)return;
  368.     if(t->time.qType<0)StopTimer(t);    // whoops, it's already active.        
  369.     PrimeTime((QElemPtr)t,t->interval);
  370. }
  371.  
  372. static void ResumeTimer(Timer *t)
  373. {
  374.     // I don't understand why, but it is important to clear tmReserved.
  375.     // The Time Manager documentation only says that tmReserved should be zeroed 
  376.     // when you FIRST install your task, not EACH TIME you install it.
  377.     // At least some computers (e.g. PowerMac) increment tmReserved each time PeekTimer is called, 
  378.     // and things get screwy if tmReserved>=128. 
  379.     t->time.tmReserved=0;
  380.     if(t->timeManagerVersion>=gestaltExtendedTimeMgr){
  381.         // The Extended Time Mgr (System 7) allows us to resume exactly where we left off
  382.         // provided we don't clear t->time.tmWakeUp
  383.         if(t->time.tmCount==0 || t->time.tmCount<0 && t->time.tmCount>-10000){
  384.             // we're dangerously close to the end of this interval: skip to next interval
  385.             t->elapsedIntervals++;
  386.             InsXTime((QElemPtr)t);
  387.             PrimeTime((QElemPtr)t,t->interval);        // resume, skipping current interval's wakeup
  388.         }else{
  389.             InsXTime((QElemPtr)t);
  390.             PrimeTime((QElemPtr)t,0);                // resume interrupted interval
  391.         }
  392.     }else{
  393.         // Before System 7 we must start a new interval
  394.         t->elapsed+=t->interval;
  395.         if(t->time.tmCount<0)t->elapsed-=t->time.tmCount;
  396.         else t->elapsed+=t->time.tmCount*1000;
  397.         if(t->elapsed<t->interval){
  398.             // avoid overflow of t->elapsed
  399.             t->elapsed-=t->interval;
  400.             t->elapsedIntervals++;
  401.         }
  402.         t->time.qType=t->time.tmReserved=t->time.tmWakeUp=t->time.tmCount=0;
  403.         InsTime((QElemPtr)t);
  404.         PrimeTime((QElemPtr)t,t->interval);        // resume timing
  405.     }
  406. }
  407.  
  408. static double TimerSecs(register Timer *t)
  409. {
  410.     register double s;
  411.  
  412.     // add up the elapsed time plus the interval we're in, minus the time left in it
  413.     s=-((double)(t->elapsedIntervals+1)*t->interval + t->elapsed);// µs
  414.     if(t->time.tmCount>0) s-=t->time.tmCount*1000;        // -µs until end of interval
  415.     else s+=t->time.tmCount;                            // -µs until end of interval
  416.     s-=t->stopDelay;                                    // compute "cycle" time
  417.     s*=0.000001;                                        // µs to s
  418.     return s;
  419. }
  420.  
  421. static long TimerMicroSecs(register Timer *t)
  422. {
  423.     register long microSecs;
  424.  
  425.     // add up the elapsed time plus the interval we're in, minus the time left in it
  426.     microSecs=-((t->elapsedIntervals+1)*t->interval + t->elapsed);// µs
  427.     if(t->time.tmCount>0) microSecs-=t->time.tmCount*1000;        // -µs until end of interval
  428.     else microSecs+=t->time.tmCount;                            // -µs until end of interval
  429.     microSecs-=t->stopDelay;                                    // compute "cycle" time
  430.     return microSecs;
  431. }
  432.  
  433. double PeekTimerSecs(Timer *t)
  434. {
  435.     Timer tCopy,*tt=&tCopy;
  436.  
  437.     if(t==NULL)return NAN;
  438.     RmvTime((QElemPtr) t);
  439.     *tt=*t;    // get the info and resume timing as quickly as possible
  440.     ResumeTimer(t);
  441.     return TimerSecs(tt);
  442. }
  443.  
  444. long PeekTimer(Timer *t)
  445. {
  446.     Timer tCopy,*tt=&tCopy;
  447.  
  448.     if(t==NULL)return 0;
  449.     RmvTime((QElemPtr) t);
  450.     *tt=*t;    // get the info and resume timing as quickly as possible
  451.     ResumeTimer(t);
  452.     return TimerMicroSecs(tt);
  453. }
  454.  
  455. long StopTimer(Timer *t)                            // Returns µs
  456. {
  457.     long microSecs;
  458.  
  459.     if(t==NULL)return LONG_MIN;
  460.     RmvTime((QElemPtr)t);
  461.     microSecs=TimerMicroSecs(t);
  462.     
  463.     // Reinstall the Timer, to be ready for for another call to StartTimer()
  464.     t->time.qType=t->time.tmCount=t->time.tmWakeUp=t->time.tmReserved=0;
  465.     t->elapsed=t->elapsedIntervals=0;                            
  466.     if(t->timeManagerVersion>=gestaltExtendedTimeMgr)InsXTime((QElemPtr)t);
  467.     else InsTime((QElemPtr)t);
  468.     
  469.     return microSecs;
  470. }
  471.  
  472. double StopTimerSecs(Timer *t)                        // Returns secs
  473. {
  474.     double s;
  475.  
  476.     if(t==NULL)return NAN;
  477.     RmvTime((QElemPtr)t);
  478.     s=TimerSecs(t);
  479.     
  480.     // Reinstall the Timer, to be ready for for another call to StartTimer()
  481.     t->time.qType=t->time.tmCount=t->time.tmWakeUp=t->time.tmReserved=0;
  482.     t->elapsed=t->elapsedIntervals=0;                            
  483.     if(t->timeManagerVersion>=gestaltExtendedTimeMgr)InsXTime((QElemPtr)t);
  484.     else InsTime((QElemPtr)t);
  485.     
  486.     return s;
  487. }
  488.  
  489. // KillEveryTimer turns off all our Timers before we quit. There is no need to
  490. // free the space since the system will do that automatically when the application terminates.
  491.  
  492. void KillEveryTimer(void)
  493. {
  494.     Timer *t;
  495.     extern Timer qTimer;
  496.  
  497.     t=qTimer.next;
  498.     while(t!=NULL){
  499.         RmvTime((QElemPtr)t);
  500.         if(vmPresent){
  501.             UnholdMemory(t,sizeof(*t));
  502.             if(t->time.tmAddr!=NULL)UnholdMemory(t->time.tmAddr,TASK_SIZE);
  503.         }
  504.         t=t->next;
  505.     }
  506. }
  507.  
  508. // On 68k machines the Revised & Extended Time managers set A1=&task.time before calling TimerTask
  509. // The code allowing access to globals is commented out because it is not needed here.
  510.  
  511. #if (THINK_C || THINK_CPLUS)
  512.     #pragma options(!profile)    // it would be dangerous to call the profiler from here
  513. #endif
  514. #if !__powerc
  515.     #pragma parameter __D0 GetA1
  516.     long GetA1(void)=0x2009;        // MOVE.L A1,D0
  517. #endif
  518.  
  519. #if __powerc
  520.     static pascal void TimerTask(Timer *t)
  521. #else
  522.     static pascal void TimerTask(void)            // Called at interrupt time
  523. #endif
  524.     {
  525.     #if !__powerc
  526.         //    long oldA5;
  527.         Timer *t;
  528.         t=(Timer *)GetA1();
  529.         //    oldA5 = SetA5(t->ourA5);            // Reestablish A5 for global variables
  530.     #endif
  531.     PrimeTime((QElemPtr)t,t->interval);            // Repeat the interval
  532.     t->elapsedIntervals++;
  533.     #if !__powerc
  534.         //    SetA5(oldA5);                         // Restore A5
  535.     #endif
  536. }
  537.  
  538. /*
  539. The following code, which seems to work on 68k and ppc,
  540. is based on examples that appeared in UseNet csmp-digest-v3-046
  541. by Kevin Bell (kbell@cs.utexas.edu) and Bill Hofmann (wdh@netcom.com)
  542. in response to a query by Steve Coy (stevec@jolt.mpx.com.au)
  543. */
  544.  
  545. #if UNIVERSAL_HEADERS
  546.     #if UNIVERSAL_HEADERS>1
  547.         extern pascal void ExitToShell(void) ONEWORDINLINE(0xA9F4);
  548.     #endif
  549.     typedef UniversalProcPtr TrapAddressType;
  550. #else
  551.     #define UniversalProcPtr ProcPtr
  552.     #define NewRoutineDescriptor(a,b,c) a
  553.     typedef long TrapAddressType;
  554. #endif
  555. static UniversalProcPtr oldExitToShellTrapAddress=NULL;
  556. static void PatchExitToShell(void);
  557. static pascal void MyExitToShell(void);
  558. #include <Traps.h>    // _ExitToShell
  559.  
  560. static void PatchExitToShell(void)
  561. {
  562.     UniversalProcPtr myExitToShellUPP;
  563.     if(oldExitToShellTrapAddress==NULL){
  564.         myExitToShellUPP=NewRoutineDescriptor((ProcPtr)MyExitToShell,kPascalStackBased,GetCurrentISA());
  565.         oldExitToShellTrapAddress=(UniversalProcPtr)GetToolTrapAddress(_ExitToShell);
  566.         SetToolTrapAddress((TrapAddressType)myExitToShellUPP,_ExitToShell);
  567.     }
  568. }
  569.  
  570. static pascal void MyExitToShell(void)
  571. {
  572.     SetCurrentA5();
  573.     SetToolTrapAddress((TrapAddressType)oldExitToShellTrapAddress,_ExitToShell);
  574.     KillEveryTimer();
  575.     ExitToShell();
  576. }
  577.