/* Copyright (C) 1993, 1992 Nathan Sidwell */ /* RCS $Id: timer.c,v 4.7 1994/01/31 17:40:36 nathan Stable $ */ /*{{{ the problem with real time under unix*/ /* * Getting accurate, stable timing is difficult. An auto repeating * interrupt cannot be used, because that has rude behaviour, if your * slightly too slow. So we must fire off the next IRQ ourselves, * preferably inside the interrupt routine. But, there will be some * slippage, due to the time for the kernel to deliver the interrupt, and * us to set off the next one. * A clever way of getting accurate average behaviour is to keep a * record of how long the previous IRQ actually took, and use that to * generate a correction factor, to be used for the next IRQ request. * But, that doesn't converge, because it's fighting the granularity of the * kernel's preemptive scheduler. For instance, if we want a rate of * 37mS, but the scheduling is in 10mS chunks, we'll be (at least) * 3mS too slow, we adjust the delta to be -3 (34mS), and next time we're * also 3mS too slow, so we adjust it again to -6 (31mS), and the same * happens, so its now a delta of -9 (28mS). This time we're * 7mS too fast so the delta changes again to -2 (35mS). As you can * see, the delta keeps changing. On average the timing should be ok, but * some frames will be too fast and some too slow, which gives * jerky animation. * Another thing which exacerbates this is that the IRQ is set in uS, but * the time can only be go to mS accuracy. * So you have a choice, smooth animation, but not quite accurate, or * jerky animation, but accurate on average. I've elected for smooth * animation. * * Now to complicate things further. The elapsed game time is now * kept, on a per screen basis. Because the game can be paused and there * are the history screens, the clock cannot be simply used. I could count * animation frames and scale by the interrupt time, but this will always * underestimate (due to the slipage above), and may be completely wrong * because the interrupts are being missed (but see below). * Most systems will have a gettimeofday which gives usec precision of the * clock (but not usec accuracy wrt real time, but should be good enough * relative to itself). I use this, stopping and starting at appropriate * points, to get the elapsed time. * * In order to get meaningful score comparisons between different * hardware, I measure the percentage of missed frames. If this gets too * large, I use the error factor to increase the frame time, and * scale the scores downwards appropriately. The actual frame time * should converge asymtotically to the minimum that the hardware * can support, if the hardware is not fast enough for the game. * * And now to complicate things further still, some OS's round the * alarm time downwards, resulting in the signal arriving earlier than * expected. To cope with these, I have added the busywait code. * During the score scaling code, the mean frame time is examined, and * if too short, the busy wait code is activated. This code can be * forced by using the busywait flag. */ /* * On AmigaOS, we don't really have a problem with timers. In order to * simplify the port, I have elected to use a timer interrupt, which * makes the code pretty similar to the Unix version. I have not added * the busywait code --- timer.device is "perfectly" accurate, esp. * when compared to the horror story about Unix timers above. */ /*}}}*/ #include "xmris.h" #ifndef AMIGAOS #ifdef TRANSPUTER #include #else #include #include #endif #define BUSYWAIT /*{{{ signal_hold, signal_release & signal_pause*/ /* signal_hold blocks the supplied signal and returns a value to be used by * signal_pause and signal_release * signal_pause uses the value supplied to return to the signal mask inuse * before the preceeding signal_hold, waits for a signal, then * reblocks the signal * signal_release returns the block mask to what it was before signal_hold */ #ifndef TRANSPUTER #ifdef POSIX # define MASK sigset_t # define signal_hold(signal, maskp) \ {\ MASK temp; \ sigemptyset(&temp); \ sigaddset(&temp, signal); \ sigprocmask(SIG_BLOCK, &temp, (maskp)); \ } # define signal_release(maskp) sigprocmask(SIG_SETMASK, maskp, (MASK *)NULL) # define signal_pause(maskp) sigpause(*(maskp)) #else # ifdef __hpux /* hpux is a weird mixture of BSD & SYSV */ /* don't know if this is right */ # define MASK int # define signal_hold(signal, maskp) \ (*(maskp) = sigblock(sigmask(signal))) # define signal_release(maskp) sigsetmask(*(maskp)) # define signal_pause(maskp) sigpause(*(maskp)) # else # ifdef SYSV /* the signals are already masks, use the supplied signal number * for the returned mask, to toggle the blocked mask. * sigpause does not automatically block the signal again, so that must be * done. */ # define MASK int # define signal_hold(signal, maskp) \ (*(maskp) = ((void)sighold(signal), signal)) # define signal_release(maskp) sigrelse(*(maskp)) # define signal_pause(maskp) (sigpause(*(maskp)), sighold(*(maskp))) # define USESIGSET # else /* signals are bit numbers, so we must construct the bit mask. * hold returns the previous mask, so we can use that for pause and release * sigpause reblocks the signal after being signalled, so no need to * reblock it */ # define MASK int # define signal_hold(signal, maskp) \ (*(maskp) = sigblock(sigmask(signal))) # define signal_release(maskp) sigsetmask(*(maskp)) # define signal_pause(maskp) sigpause(*(maskp)) # endif /* SYSV */ # endif /* __hpux */ #endif /* POSIX */ #endif /* TRANSPUTER */ /*}}}*/ /*{{{ get current time*/ /* TICKTIME specifies how many microseconds in each timer tick * tick_t is the typedef of the timer return value * BUSYWAIT is set if the timer is precise enough for busywait code * to work */ #ifdef TRANSPUTER # define gettick(ptr) (*(ptr) = ProcTime()) # define TICKTIME (unsigned long)64 # define tickdelta(later, earlier) ProcTimeMinus(later, earlier) # define tickafter(later, earlier) ProcTimerAfter(later, earlier) # define tickadd(time, interval) ProcTimePlus(time, interval) typedef int tick_t; #else # ifdef USETIME # define gettick(ptr) (*(ptr) = time((time_t *)NULL)) # define TICKTIME (unsigned long)1000000 # define tickdelta(later, earlier) (unsigned long)((later) - (earlier)) # define tickadd(time, interval) ((time) + (interval)) # define tickafter(later, earlier) \ (tickdelta(later, earlier) < ~(~(unsigned long)0 >> 1)) typedef time_t tick_t; # undef BUSYWAIT # else /* say whether gettimeofday needs a timezone argument * as far as I know only SYSV doesn't -- this needs checking */ #ifdef POSIX # define TIMEZONE struct timezone #else # ifdef __hpux # define TIMEZONE struct timezone # else # ifdef SYSV # define TIMEZONE VOID # else # define TIMEZONE struct timezone # endif /* SYSV */ # endif /* __hpux */ #endif /* POSIX */ # define TICKTIME (unsigned long)1000 # define gettick(ptr) \ {\ struct timeval timeofday; \ gettimeofday(&timeofday, (TIMEZONE *)NULL); \ *(ptr) = (unsigned long)timeofday.tv_sec * (1000000 / TICKTIME) + \ (unsigned long)timeofday.tv_usec / (1000000 / TICKTIME); \ } # define tickdelta(later, earlier) ((later) - (earlier)) # define tickadd(time, interval) ((time) + (interval)) # define tickafter(later, earlier) \ (tickdelta(later, earlier) < ~(~(unsigned long)0 >> 1)) typedef unsigned long tick_t; # endif /* USETIME */ #endif /* TRANSPUTER */ /*}}}*/ #else #ifndef EXEC_ALERTS_H #include #endif #include #include "StaticSaveds.h" #define TimerBase (TimerRequest.tr_node.io_Device) typedef struct timeval tick_t; #define gettick(ptr) GetSysTime(ptr) STATIC_SAVEDS_PROTO(void,timer_alarm); static struct Task *Task; static struct Interrupt TimerInterrupt= { {NULL,NULL,NT_INTERRUPT,0,NULL}, NULL, timer_alarm }; static struct MsgPort TimerPort= { {NULL,NULL,NT_MSGPORT,0,NULL}, PA_SOFTINT, 0, &TimerInterrupt, {(struct Node *)&TimerPort.mp_MsgList.lh_Tail,NULL,(struct Node *)&TimerPort.mp_MsgList.lh_Head,NT_MESSAGE,0} }; static struct timerequest TimerRequest; static UBYTE TimerSignal; #endif /* AMIGAOS */ /*{{{ timer*/ static struct { #ifndef AMIGAOS VOIDFUNC (*handler) PROTOARG((int)); /* original handler */ #endif /* AMIGAOS */ unsigned long usec; /* interval time in usec */ #ifdef TRANSPUTER tick_t delay; /* tickdelay waiting */ tick_t timeout; /* when the next one should timeout */ #else #ifndef AMIGAOS struct itimerval interval; /* internal interval time */ #else struct timeval interval; /* internal interval time */ #endif unsigned VOLATILE elapsed; /* timer elapsed */ unsigned VOLATILE waiting; /* waiting for the interrupt */ #ifdef BUSYWAIT unsigned busywait; /* busywaiting is turned on */ unsigned restarted; /* restarted in signal handler */ tick_t timeout; /* timeout */ tick_t delay; /* interval delay */ #endif /* BUSYWAIT */ #endif /* TRANSPUTER */ unsigned state; /* timing state */ tick_t game; /* start of game tick */ tick_t start; /* timing start */ tick_t stop; /* timing stop */ unsigned count; /* frame count */ unsigned missed; /* missed count */ } timer; /*}}}*/ /*{{{ prototypes*/ #ifndef TRANSPUTER #ifndef AMIGAOS static VOIDFUNC timer_alarm PROTOARG((int)); #endif /* AMIGAOS */ #endif /* TRANSPUTER */ /*}}}*/ #ifdef TRANSPUTER /*{{{ void sleep(delay)*/ extern VOIDFUNC sleep FUNCARG((delay), unsigned delay ) { ProcWait((int)(delay * (1000000 / TICKTIME))); return; } /*}}}*/ #endif /* TRANSPUTER */ #ifndef TRANSPUTER /*{{{ void timer_alarm(sig)*/ #ifndef AMIGAOS static VOIDFUNC timer_alarm /* ARGSUSED */ FUNCARG((sig), int sig ) #else STATIC_SAVEDS(void,timer_alarm) #endif /* AMIGAOS */ { /* * Most calls are undefined in a signal handler * (only signal, exit, longjump & abort are guaranteed to work) * This should work, except on _really_ weird library implementations, * because timer.waiting is only true when the main thread is stuck * in a wait() call. */ #ifndef AMIGAOS timer.elapsed = 1; #ifndef USESIGSET signal(SIGALRM, timer_alarm); #endif if(timer.waiting) { #ifdef BUSYWAIT if(timer.busywait) { tick_t now; gettick(&now); if(tickafter(now, timer.timeout)) { setitimer(ITIMER_REAL, &timer.interval, (struct itimerval *)NULL); timer.restarted = 1; timer.timeout = tickadd(timer.timeout, timer.delay); } } else { timer.restarted = 1; #else { #endif setitimer(ITIMER_REAL, &timer.interval, (struct itimerval *)NULL); } timer.waiting = 0; } #else /* AMIGAOS */ if (timer.waiting) { timer.waiting=FALSE; Signal(Task,1< xnew) { int t; t = xold; xold = xnew; xnew = t; } /*}}}*/ XDrawLine(display.display, display.window, GCN(GC_LOAD), xold, PIXELY(CELLS_DOWN, CELL_HEIGHT), xnew, PIXELY(CELLS_DOWN, CELL_HEIGHT)); global.dilation = dilation; timer.start = timer.stop; timer.missed = 0; timer.count = 1; /*{{{ set score scale*/ { unsigned long scale; unsigned last; scale = (unsigned long)SCORE_SCALE * SCORE_SCALE * FRAME_SCALE / global.dilation; dilation = SCORE_SCALE; do { last = dilation; dilation = (unsigned)(((unsigned long)dilation * dilation + scale) / dilation / 2); } while(dilation != last); global.scale = dilation; } /*}}}*/ } timer.count++; /*{{{ plot load point?*/ if(point < 0) { if(global.missed) { XDrawPoint(display.display, display.window, GCN(GC_LOAD), (int)(WINDOW_WIDTH - global.missed), PIXELY(CELLS_DOWN, CELL_HEIGHT)); global.missed--; } } else if(point > 0) { if(global.missed < WINDOW_WIDTH) { global.missed++; XDrawPoint(display.display, display.window, GCN(GC_LOAD), (int)(WINDOW_WIDTH - global.missed), PIXELY(CELLS_DOWN, CELL_HEIGHT)); } } /*}}}*/ return; } /*}}}*/