Newer
Older
piccolo2d.java / extras / src / main / java / edu / umd / cs / piccolox / swt / SWTTimerQueue.java
package edu.umd.cs.piccolox.swt;

import org.eclipse.swt.widgets.Display;

/**
 * @author Lance Good
 */
public class SWTTimerQueue implements Runnable {
    static SWTTimerQueue instance;

    Display display = null;

    SWTTimer firstTimer;
    boolean running;

    /**
     * Constructor for TimerQueue.
     */
    public SWTTimerQueue(Display display) {
        super();

        this.display = display;

        // Now start the TimerQueue thread.
        start();
    }

    public static SWTTimerQueue sharedInstance(Display display) {
        if (instance == null) {
            instance = new SWTTimerQueue(display);
        }
        return instance;
    }

    synchronized void start() {
        if (running) {
            throw new RuntimeException("Can't start a TimerQueue " + "that is already running");
        }
        else {
            Display.getDefault().asyncExec(new Runnable() {
                public void run() {
                    Thread timerThread = new Thread(SWTTimerQueue.this, "TimerQueue");
                    timerThread.setDaemon(true);
                    timerThread.setPriority(Thread.NORM_PRIORITY);
                    timerThread.start();
                }
            });
            running = true;
        }
    }

    synchronized void stop() {
        running = false;
        notify();
    }

    synchronized void addTimer(SWTTimer timer, long expirationTime) {
        SWTTimer previousTimer;
        SWTTimer nextTimer;

        // If the Timer is already in the queue, then ignore the add.
        if (timer.running) {
            return;
        }

        previousTimer = null;
        nextTimer = firstTimer;

        // Insert the Timer into the linked list in the order they will
        // expire. If two timers expire at the same time, put the newer entry
        // later so they expire in the order they came in.

        while (nextTimer != null) {
            if (nextTimer.expirationTime > expirationTime)
                break;

            previousTimer = nextTimer;
            nextTimer = nextTimer.nextTimer;
        }

        if (previousTimer == null) {
            firstTimer = timer;
        }
        else {
            previousTimer.nextTimer = timer;
        }

        timer.expirationTime = expirationTime;
        timer.nextTimer = nextTimer;
        timer.running = true;
        notify();
    }

    synchronized void removeTimer(SWTTimer timer) {
        SWTTimer previousTimer;
        SWTTimer nextTimer;
        boolean found;

        if (!timer.running)
            return;

        previousTimer = null;
        nextTimer = firstTimer;
        found = false;

        while (nextTimer != null) {
            if (nextTimer == timer) {
                found = true;
                break;
            }

            previousTimer = nextTimer;
            nextTimer = nextTimer.nextTimer;
        }

        if (!found)
            return;

        if (previousTimer == null) {
            firstTimer = timer.nextTimer;
        }
        else {
            previousTimer.nextTimer = timer.nextTimer;
        }

        timer.expirationTime = 0;
        timer.nextTimer = null;
        timer.running = false;
    }

    synchronized boolean containsTimer(SWTTimer timer) {
        return timer.running;
    }

    /**
     * If there are a ton of timers, this method may never return. It loops
     * checking to see if the head of the Timer list has expired. If it has, it
     * posts the Timer and reschedules it if necessary.
     */
    synchronized long postExpiredTimers() {
        SWTTimer timer;
        long currentTime;
        long timeToWait;

        // The timeToWait we return should never be negative and only be zero
        // when we have no Timers to wait for.

        do {
            timer = firstTimer;
            if (timer == null)
                return 0;

            currentTime = System.currentTimeMillis();
            timeToWait = timer.expirationTime - currentTime;

            if (timeToWait <= 0) {
                try {
                    timer.postOverride(); // have timer post an event
                }
                catch (SecurityException e) {
                }

                // Remove the timer from the queue
                removeTimer(timer);

                // This tries to keep the interval uniform at
                // the cost of drift.
                if (timer.isRepeats()) {
                    addTimer(timer, currentTime + timer.getDelay());
                }

                // Allow other threads to call addTimer() and removeTimer()
                // even when we are posting Timers like mad. Since the wait()
                // releases the lock, be sure not to maintain any state
                // between iterations of the loop.

                try {
                    wait(1);
                }
                catch (InterruptedException e) {
                }
            }
        } while (timeToWait <= 0);

        return timeToWait;
    }

    public synchronized void run() {
        long timeToWait;

        try {
            while (running) {
                timeToWait = postExpiredTimers();
                try {
                    wait(timeToWait);
                }
                catch (InterruptedException e) {
                }
            }
        }
        catch (ThreadDeath td) {
            running = false;
            // Mark all the timers we contain as not being queued.
            SWTTimer timer = firstTimer;
            while (timer != null) {
                timer.cancelEventOverride();
                timer = timer.nextTimer;
            }
            display.asyncExec(new SWTTimerQueueRestart(display));
            throw td;
        }
    }

    public synchronized String toString() {
        StringBuffer buf;
        SWTTimer nextTimer;

        buf = new StringBuffer();
        buf.append("TimerQueue (");

        nextTimer = firstTimer;
        while (nextTimer != null) {
            buf.append(nextTimer.toString());

            nextTimer = nextTimer.nextTimer;
            if (nextTimer != null)
                buf.append(", ");
        }

        buf.append(")");
        return buf.toString();
    }

    /**
     * Runnable that will message the shared instance of the Timer Queue to
     * restart.
     */
    protected static class SWTTimerQueueRestart implements Runnable {
        boolean attemptedStart;

        Display display = null;

        public SWTTimerQueueRestart(Display display) {
            this.display = display;
        }

        public synchronized void run() {
            // Only try and restart the q once.
            if (!attemptedStart) {
                SWTTimerQueue q = SWTTimerQueue.sharedInstance(display);

                synchronized (q) {
                    if (!q.running)
                        q.start();
                }
                attemptedStart = true;
            }
        }
    }

}