package iiuf.util; import java.util.Random; import java.io.Serializable; /** The timer class. This class implements timer tasks that can be run at a specified time. This class stores tasks in an efficient tree representation which keeps the insert and remove costs almost constant even for a large (> 10000) number of tasks. (c) 1999, 2000, 2001, IIUF, DIUF<p> @author $Author: ohitz $ @version $Revision: 1.1 $ */ public class Timer implements Runnable, Serializable { /** @serial The initial delay. */ public long milliseconds; /** @serial The absolute time when this timer will be triggerd. */ public long absolute = Long.MAX_VALUE; /** The link to the next timer. */ transient protected Timer next; /** Set to <code>true</code> as soon as the <code>run</code> method starts executing. reset by <code>reschedule</code> or <code>schedule</code>. */ transient protected boolean execute; /** @serial If <code>true</code>, the timer's <code>run</code> method will be run in a separate thread. */ protected boolean thread; /** The task to run by this timer, or <code>null</code> if no task. */ transient private Runnable timer_task; /** If <code>true</code>, the timer's <code>run</code> will not be called. This variable is set to false by <code>TimerTree.insert()</code> and set by <code>TimerTree.remove()</code>. */ transient boolean dontRun; protected Timer() {this(false);} protected Timer(boolean thread_) { thread = thread_; } /** Creates a new timer task. @param timer_task_ The timer task to run. @param thread Run the task as a thread if true, run by timer thread if false. */ public Timer(Runnable timer_task_, boolean thread) { this(thread); timer_task = timer_task_; } /** Schedules this objects <code>run</code> method to be executed in <code>milliseconds_</code> from now. @param milliseconds_ Milliseconds from now until the the <code>run</code> method is executed. @return This timer. */ public Timer schedule(long milliseconds_) { execute = false; TimerTree.remove(this); milliseconds = milliseconds_; absolute = System.currentTimeMillis() + milliseconds; TimerTree.insert(this); return this; } /** Cancels this timer. Cancel only ensures that the <code>run</code> method is not executed after <code>cancel</code> returns. But the <code>run</code> method may be excuted during the execution of <code>cancel</code>. @return This timer. */ public Timer cancel() { TimerTree.remove(this); return this; } /** Re-schedules this objects <code>run</code> method to be executed in <code>milliseconds_</code> after the <code>schedule</code> call from the last execution of the <code>run</code> method. Use this call for fixed frequency tasks instead of using <code>schedule</code> in order to avoid drift. @return This timer. */ public Timer reschedule() { TimerTree.remove(this); absolute += milliseconds; execute = false; TimerTree.insert(this); return this; } public String toString() { return "(ms:" + milliseconds + ":abs:" + absolute + ":exe:" + execute + (next == null ? "" : ":next:" + next) + ")"; } public void run() { if(timer_task != null) timer_task.run(); } // // T E S T S T U F F // private static Random random = new Random(); private static int rnd(int range) { int result = random.nextInt(); result = result < 0 ? -result : result; return result % range; } private static void countdown(int sec) { for(int i = sec; sec >= 0; sec--) { System.out.print("\b\b\b\b\b\b\b\b\b" + sec + " "); System.out.flush(); try{Thread.sleep(1000);} catch(Exception e) {} } } /** Test program. */ public static void main(String[] argv) { int test = Integer.parseInt(argv[0]); switch(test) { case 0: System.out.println("Single task, in 5 seconds."); new TestTimer("Hello!", false).schedule(5000); countdown(5); break; case 1: System.out.println("Single task, every 5 seconds."); new TestTimer("Hello!", true).schedule(5000); break; case 2: System.out.println("Two task, in 5 seconds."); TestTimer t0 = new TestTimer("Hello 0!", false); TestTimer t1 = new TestTimer("Hello 1!", false); t0.schedule(5000); t1.milliseconds = t0.milliseconds; t1.absolute = t0.absolute; TimerTree.insert(t1); System.out.println(TimerTree.root); countdown(5); break; case 3: { System.out.println("Random timers"); Timer[] t = new Timer[10000]; for(;;) { int idx = rnd(t.length); int time = rnd(5000) + 100; boolean reschedule = rnd(2) == 1; if(t[idx] != null) t[idx].cancel(); t[idx] = new TestTimer("[" + idx + "]Hello(" + time + ", " + reschedule + ")", reschedule); System.out.println("Scheduling new task..."); t[idx].schedule(time); try{ Thread.sleep(250); if(System.in.available() > 0) { System.out.println(TimerTree.root); System.exit(0); } } catch(Exception e) {} } } case 4: { System.out.println("Random timers fill"); Timer[] t = new Timer[Integer.parseInt(argv[1])]; for(int i = 0; i < t.length; i++) { int time = rnd(5000); t[i] = new TestTimer("[" + i + "]Hello(" + time + ")", false); t[i].schedule(time); } countdown(15); System.out.println(); long now = System.currentTimeMillis(); for(int i = 0; i < t.length; i++) System.out.println("[" + i + "]" + t[i] + (now - t[i].absolute)); System.out.println(TimerTree.root); System.exit(0); break; } } } } class TestTimer extends Timer { static long min = Long.MAX_VALUE; static long max = Long.MIN_VALUE; static long sum; static long count; transient String message; transient boolean reschedule; TestTimer(String message_, boolean reschedule_) { message = message_; reschedule = reschedule_; } public void run() { long error = System.currentTimeMillis() - absolute; if(error < min) min = error; if(error > max) max = error; sum += error; count++; System.out.println(message + "[" + min + "," + error + "," + max + "," + (sum / count) + "]"); if(reschedule) reschedule(); } } final class TimerTree { static final Object LOCK = TimerTree.class; static final boolean DEBUG = false; static final int CHILDS = 256; static final int ILLEGAL = 1000; static long start = System.currentTimeMillis(); static TimerTree root; static TimerThread timer_thread = new TimerThread(); static int baseshift; int shift; int min; int max; TimerTree[] childs; Timer[] timers; static Timer min() { synchronized(LOCK) { return root._min(); } } static void insert(Timer timer) { if(DEBUG) System.out.println("insert(" + timer + ")"); timer.dontRun = false; long ltime = timer.absolute - start; long stime = ltime >> baseshift; synchronized(LOCK) { while(stime > 0) { baseshift += 8; stime = ltime >> baseshift; if(root != null) root = new TimerTree(baseshift, root); } if(root == null) root = new TimerTree(baseshift); root.insert(ltime, timer); } timer_thread.check(); if(DEBUG) System.out.println(root); } static void remove(Timer timer) { if(DEBUG) System.out.println("remove(" + timer + ")"); synchronized(LOCK) { if(root != null) { root.remove(timer.absolute - start, timer); timer.next = null; } } timer.dontRun = true; if(DEBUG) System.out.println(root); } private Timer _min() { if(min == ILLEGAL) return null; return shift > 0 ? childs[min]._min() : timers[min]; } private TimerTree(int shift_) { shift = shift_ - 8; min = ILLEGAL; max = -1; if(shift > 0) childs = new TimerTree[CHILDS]; else timers = new Timer[CHILDS]; } private TimerTree(int shift_, TimerTree old) { shift = shift_ - 8; childs = new TimerTree[CHILDS]; if(old.min != ILLEGAL) { childs[0] = old; min = max = 0; } else { min = ILLEGAL; max = -1; } } private TimerTree(int shift_, long time, Timer timer) { shift = shift_ - 8; min = max = (int)((time >> shift) & 0x0FF); if(shift > 0) { childs = new TimerTree[CHILDS]; childs[min] = new TimerTree(shift, time, timer); } else { timers = new Timer[CHILDS]; timers[min] = timer; } } private void insert(long time, Timer timer) { int idx = (int)(( time >> shift) & 0x0FF); if(idx < min) min = idx; if(idx > max) max = idx; if(shift > 0) { if(childs[idx] == null) childs[idx] = new TimerTree(shift, time, timer); else childs[idx].insert(time, timer); } else { timer.next = timers[idx]; timers[idx] = timer; } } private boolean remove(long time, Timer timer) { int idx = (int)((time >> shift) & 0x0FF); if(shift > 0) { if(childs[idx] != null && childs[idx].remove(time, timer)) { childs[idx] = null; for(int i = min; i <= max; i++) if(childs[i] != null) { min = i; return false; } min = ILLEGAL; max = -1; return true; } return false; } else { Timer last = null; for(Timer t = timers[idx]; t != null; t = t.next) { if(t == timer) if(last == null) timers[idx] = t.next; else last.next = t.next; last = t; } for(int i = min; i <= max; i++) if(timers[i] != null) { min = i; return false; } min = ILLEGAL; max = -1; return true; } } private String prefix() { String result = ""; for(int i = 0; i < (64 - shift) / 8; i++) result += " "; return result; } public String toString() { String result = prefix() + "shift:" + shift + ":min:" + min + ":max:" + max + "\n"; if(childs != null) for(int i = 0; i < childs.length; i++) if(childs[i] != null) result += prefix() + "childs[" + i + "]\n" + childs[i]; if(timers != null) for(int i = 0; i < timers.length; i++) if(timers[i] != null) result += prefix() + "timers[" + i + "]:" + timers[i] + "\n"; return result; } } final class TimerThread extends Thread { private boolean notified; TimerThread() { setName("TimerThread"); setPriority(Thread.MAX_PRIORITY); start(); } synchronized void check() { notified = true; notify(); } public void run() { synchronized(this) { while(TimerTree.root == null) { try{wait();} catch(InterruptedException e) { Util.printStackTrace(e); } } } for(;;) { long now = System.currentTimeMillis(); Timer t = TimerTree.min(); if(t != null) { if(now >= t.absolute) { if(t.dontRun) { TimerTree.remove(t); continue; } t.execute = true; TimerTree.remove(t); if(t.thread) new Thread(t).start(); else t.run(); continue; } else { long delta = t.absolute - now; if(delta > 10) { synchronized(this) { if(notified) { notified = false; continue; } try{wait(delta);} catch(InterruptedException e) { Util.printStackTrace(e); } } } } } else { synchronized(this) { if(notified) { notified = false; continue; } try{wait();} catch(InterruptedException e) { Util.printStackTrace(e); } } } } } } /* $Log: Timer.java,v $ Revision 1.1 2002/07/11 12:00:11 ohitz Initial checkin Revision 1.8 2001/04/30 07:33:17 schubige added webcom to cvstree Revision 1.7 2001/04/11 14:17:03 schubige adapted tinja stuff for semantic checks Revision 1.6 2001/02/23 17:23:11 schubige Added loop source to soundium and fxed some bugs along Revision 1.5 2001/02/02 18:04:35 schubige rpc client working for udp & tcp Revision 1.4 2001/01/04 16:28:42 schubige Header update for 2001 and DIUF Revision 1.3 2000/08/17 16:22:15 schubige Swing cleanup & TreeView added Revision 1.2 1999/09/03 15:50:08 schubige Changed to new header & log conventions. */