Apple Developer Connection
Member Login Log In | Not a Member? Contact ADC

< Previous PageNext Page > Hide TOC

Timing Loops

On rare occasions, you may find the need to perform some operation on a periodic basis with greater than the one second precision offered by sleep. Although the shell does not offer any precision timers, you can closely approximate such behavior through the use of a calibrated delay loop.

The basic design for such a loop consists of two parts: a calibration routine and a delay loop. The calibration routine should execute approximately the same instructions as the delay loop for a known number of iterations.

The nature of the instructions within the delay loop are largely unimportant. They can be any instructions that your program needs to execute while waiting for the desired amount of time to elapse. However, a common technique is to perform nonblocking I/O during the delay loop and then process any characters received.

For example, Listing 7-1 shows a very simple timing loop that reads a byte and triggers some simple echo statements (depending on what key is pressed) while simultaneously echoing a statement to the screen about once per second.

Listing 7-1  A simple one-second timing loop

#!/bin/sh
 
ONE_SECOND=1000
 
function read_test()
{
    COUNT=0
    local ONE_SECOND=1000                       # ensure this never trips!
    while [ $COUNT -lt 200 ] ; do
        CHAR=`./getch`
        if [ $1 = "rot" ] ; then
                CHAR=","
        fi
        case "$CHAR" in
                ( "q" | "Q" )
                        CONT=0;
                        GAMEOVER=1
                ;;
                ( "" )
                        # Silently ignore empty input.
                ;;
                ( * )
                        echo "Unknown key $CHAR"
                ;;
        esac
        COUNT=`expr $COUNT '+' 1`
        while [ $COUNT -ge $ONE_SECOND ] ; do
                COUNT=`expr $COUNT - $ONE_SECOND`
                MODE="clear";
                draw_cur $ROT;
                VPOS=`expr $VPOS '+' 1`
                MODE="apple";
                draw_cur $ROT
        done
    done
}
 
function calibrate_timers()
{
    2>/tmp/readtesttime time $0 -readtest
    local READ_DUR=`grep real /tmp/readtesttime | sed 's/real.*//' | tr -d ' '`
    # echo "READ_DUR: $READ_DUR"
 
    local READ_SINGLE=`echo "scale=20; ($READ_DUR / 200)" | bc`
    ONE_SECOND=`echo "scale=0; 1.0  / $READ_SINGLE" | bc`
 
    # echo "READ_SINGLE: $READ_SINGLE";
    # exit
 
    echo "One second is about $ONE_SECOND cycles."
}
 
if [ "x$1" = "x-readtest" ] ; then
        read_test
        exit
fi
 
echo "Calibrating.  Please wait."
calibrate_timers
 
echo "Done calibrating.  You should see a message about once per second.  Press 'q' to quit."
stty -icanon -isig
 
GAMEOVER=0
COUNT=0
# Start the game loop.
while [ $GAMEOVER -eq 0 ] ; do
        # echo -n "Enter a character: "
        CHAR=`./getch`
        case "$CHAR" in
                ( "q" | "Q" )
                        CONT=0;
                        GAMEOVER=1
                ;;
                ( "" )
                        # Silently ignore empty input.
                ;;
                ( * )
                        echo "Unknown key $CHAR"
                ;;
        esac
        COUNT=`expr $COUNT '+' 1`
        while [ $COUNT -ge $ONE_SECOND ] ; do
                COUNT=`expr $COUNT - $ONE_SECOND`
                echo "One second elapsed (give or take)."
        done
done
 
stty sane

In a real-world timing loop, you will probably have keys that perform certain operations that take time—moving a piece on a checkerboard, for example. In that case, your calibration should also perform a series to tests to approximate the amount of time for each of those operations.

If you divide the time for the slow operation by the duration of a single read operation (READ_SINGLE), you can discern an approximate penalty for the move using iterations of the main program loop as the unit value. Then, when you perform one of those operations later, you simply add that penalty value to the main loop counter, thus ensuring that the "One second elapsed” messages will quickly catch up with (approximately) where they should be.

You can approximate this further by using larger numbers in your loop counter to achieve greater precision. For example, you might increment your loop counter by 100 instead of by 1. This will give a much more accurate approximation of the number of cycles stolen by a slow operation.

!

Warning: If you perform significant multiplication (for example, to increase game play speed on subsequent levels) to change the rate of your timer, using larger values means that you are much more likely to exceed the maximum value that shell math or expr math can handle during your interim calculations. In such cases, you may find it better to use bc, which works with floating-point quantities.



< Previous PageNext Page > Hide TOC


Last updated: 2008-04-08




Did this document help you?
Yes: Tell us what works for you.

It’s good, but: Report typos, inaccuracies, and so forth.

It wasn’t helpful: Tell us what would have helped.
Get information on Apple products.
Visit the Apple Store online or at retail locations.
1-800-MY-APPLE

Copyright © 2007 Apple Inc.
All rights reserved. | Terms of use | Privacy Notice