diff src/samer/tools/RThread.java @ 0:bf79fb79ee13

Initial Mercurial check in.
author samer
date Tue, 17 Jan 2012 17:50:20 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/samer/tools/RThread.java	Tue Jan 17 17:50:20 2012 +0000
@@ -0,0 +1,336 @@
+/*
+ *	Copyright (c) 2000, Samer Abdallah, King's College London.
+ *	All rights reserved.
+ *
+ *	This software is provided AS iS and WITHOUT ANY WARRANTY;
+ *	without even the implied warranty of MERCHANTABILITY or
+ *	FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package samer.tools;
+
+import samer.core.*;
+import samer.core.util.*;
+import samer.core.types.*;
+import java.util.*;
+
+/** frame rate regulator */
+
+public class RThread extends Viewable implements Agent, Runnable
+{
+	Task		task, termhook;
+	Thread	thread;
+	int			prio;
+	boolean	running;
+	int			numloops=0;
+
+	int			N=10, count;
+	long		t0, elapsed, iterations;
+	boolean	regulated=true;
+	VDouble	target;			// target frame rate
+	double		actual;			// actual frame rate
+	long		period, wakeTime,nanos;
+
+	private volatile boolean paused=false;
+	private volatile boolean kill=false;
+
+
+	// .....Constructors................................
+
+	public RThread() { this(new NullTask()); }
+	public RThread(Task r)
+	{
+		super("regulator");
+
+		Shell.push(node);
+		target = new VDouble("target",50,0);
+		regulated = Shell.getBoolean("regulated",regulated);
+		setPriority(Shell.getDouble("thread.priority",-0.25));
+		Shell.pop();
+
+		setN(actual=target.value);
+		setPeriod();
+		setAgent(this);
+
+		task=r; termhook=null;
+		thread=new Thread(this);
+		thread.setPriority(prio);
+		running=false;
+
+		Shell.registerAgent(this);
+		Shell.registerViewable(this);
+	}
+
+	public Task onFinish(Task t) { Task old=termhook; termhook=t; return old; }
+	public Viewer	getViewer() { return new UI(); }
+
+	public void dispose()
+	{
+		Shell.deregisterAgent(this);
+		Shell.deregisterViewable(this);
+		target.dispose();
+		super.dispose();
+	}
+
+	public Task getTask() { return task; }
+	public void setTask(Task t) { task=t; }
+	
+	public class Kill extends Exception {}
+
+	public void interrupt() { thread.interrupt(); }
+	public void join() {
+		try { thread.join(); }
+		catch (InterruptedException ex) {}
+	}
+
+	public void start() { start(0); }
+	public void start(int n)
+	{
+		paused=false; kill=false;
+		numloops=n;
+		reset();
+		thread=new Thread(this);
+		thread.setPriority(prio);
+		thread.start();
+	}
+
+	/**	<b>Important:</b> this method BLOCKS until the
+	  *	thread co-operatively terminates, either naturally
+	  *	or by not handling the InterruptedException that
+	  *	will be thrown by the <code>checkpoint()</code> method
+	  *	the next time it is called.
+	  */
+
+	public void kill()
+	{
+		if (!thread.isAlive()) return;
+
+		synchronized (this) {
+			kill=true;
+			paused=true;
+			thread.interrupt(); // this will interrupt sleep, wait etc
+		}
+		try { thread.join(5000); }
+		catch (Exception ex) {
+			Shell.trace("RThread.kill failed: "+ex);
+		}
+	}
+
+
+	public void setPriority(double p)
+	{
+		Shell.trace("thread priority is "+p);
+		if (p==0) { prio=Thread.NORM_PRIORITY; }
+		else if (p>0) {
+			prio=Thread.NORM_PRIORITY+(int)(p*(Thread.MAX_PRIORITY-Thread.NORM_PRIORITY));
+		} else {
+			prio=Thread.NORM_PRIORITY-(int)(p*(Thread.MIN_PRIORITY-Thread.NORM_PRIORITY));
+		}
+	}
+
+	public void pause() { paused=true; }
+	public void unpause() {
+		if (paused) synchronized (this) {
+			paused=false; notifyAll();
+		}
+	}
+
+	/**	This method blocks if the thread has been paused
+	  *	and throws an InterruptedException if the thread
+	  *	has been killed.
+	  */
+
+	public final void checkpoint() throws Kill
+	{
+		if (paused) synchronized (this) {
+			Shell.trace("pausing");
+			running=false; task.stopping();
+			if (kill) throw new Kill();
+			try { wait(); } catch (Exception ex) {}
+			if (kill) throw new Kill();
+			running=true; task.starting(); reset();
+			Shell.trace("unpausing");
+		}
+	}
+
+	public void run()
+	{
+		try {
+			Shell.trace("Regulator: init");
+			task.starting();
+			running=true;
+
+			try {
+				if (numloops==0) {
+					for (;;) {
+						task.run();
+						if (regulated) tick();
+						//if (regulated) synchronized (this) { wait(); }
+						if (++count>=N) { tock(); changed(); }
+						checkpoint();
+					}
+				} else {
+					int n=numloops;
+					numloops=0;
+					for (int i=0;i<n;i++) {
+						task.run();
+						if (regulated) tick();
+						//if (regulated) synchronized (this) { wait(); }
+						if (++count>=N) { tock(); changed(); }
+						checkpoint();
+					}
+					task.stopping();
+					tock(); changed();
+				}
+			}
+			catch (Kill ex) { throw ex; }
+			catch (Exception ex) { task.stopping(); throw ex; }
+			finally {
+				running=false;
+				Shell.trace("Regulator: term");
+				if (termhook!=null) {
+					Shell.trace("running termination hook");
+					try { termhook.run(); }
+					catch (Exception ex) { ex.printStackTrace(); }
+				}
+			}
+		}
+		catch (Kill ex) {
+			System.out.println("Regulator: thread killed");
+		}
+		catch (Exception ex) {
+			System.out.println("Regulator: "+ex);
+			ex.printStackTrace();
+		}
+	}
+
+	private final void tick() throws Exception {
+		long now=System.currentTimeMillis();
+		if (now<wakeTime) {
+			Thread.sleep(wakeTime-now);
+			wakeTime+=period;
+		} else wakeTime=now+period;
+	}
+
+	public double fps()				{ return actual; }
+	public double elapsedTime() { return elapsed/1000.0; }
+	public long iterations() 			{ return iterations; }
+	public void setRate( double r) { target.value=r; target.changed(); }
+	public void regulate() { regulated=true; setPeriod(); }
+	public void deregulate() { regulated=false; }
+
+	private void setPeriod() {
+		period=(long)(1000.0/target.value);
+		// double T=(1000.0/target.value);
+		// period = (long)T;
+		// nanos = (long)(1000000.0*(T-period));
+		// Shell.trace("period = "+period+"ms + "+nanos+"ns");
+	}
+
+	public void reset()
+	{
+		setPeriod();
+		count=0; setN(actual);
+		t0=System.currentTimeMillis();
+		wakeTime=t0+period;
+	}
+
+
+	public void tock()
+	{
+		long	t = System.currentTimeMillis();
+		long	dt = t-t0;
+
+		elapsed+=dt;
+		iterations+=count;
+		actual = (1000.0*count)/dt;
+		count = 0;	t0 = t;
+		setN(actual); // try to count for 2 seconds
+		setPeriod();
+	}
+
+	private void setN(double rate)
+	{
+		// set the number of iterations to time
+		// given a certain rate
+		N=(int)(2*rate); 	// try to count for 2 seconds
+		if (N==0) N=1;	// at least 1!
+	}
+
+	public void getCommands(Agent.Registry r) {
+		r.add("start").add("pause").add("kill").add("step");
+		r.add("zero"); // do we want this one here?
+	}
+
+	public void execute(String c, Environment env) throws Exception
+	{
+		if      (c.equals("start") || c.equals("resume")) { if (thread.isAlive()) unpause(); else start(); }
+		else if (c.equals("stop") || c.equals("pause"))  pause();
+		else if (c.equals("step")) {
+			if (!running) {
+				task.starting();
+				task.run();
+				task.stopping();
+			} else {
+				Shell.print("Cannot step while thread is running.");
+			}
+		} else if (c.equals("regulated")) {
+			regulated = X._bool(env.datum(),!regulated);
+			reset(); changed();
+		} else if (c.equals("zero")) {
+			tock();  elapsed=iterations=0; changed();
+		} else if (c.equals("tock")) { tock(); changed(); }
+		else if (c.equals("exit") || c.equals("kill")) {
+			Shell.trace("killing thread...");
+			kill();
+			Shell.trace("thread killed.");
+		}
+	}
+
+	class UI extends BaseViewer
+	{
+		NumberViewer	actualRate=Shell.createNumberViewer("Actual rate",NumberViewer.TEXT);
+		NumberViewer	elapsedTime=Shell.createNumberViewer("Elapsed time",NumberViewer.TEXT);
+		NumberViewer	iterationCount=Shell.createNumberViewer("Iterations",NumberViewer.TEXT);
+		Viewer			targetViewer;
+		VBoolean		reg;
+
+		UI()
+		{
+			super(RThread.this);
+			setLayout( new StackLayout(2));
+			panel().setName(getLabel());
+
+			targetViewer = target.getViewer();
+			reg = new VBoolean("regulated",regulated,0);
+			reg.addObserver(this);
+
+			add( targetViewer.getComponent());
+			add( actualRate.getComponent());
+			add( elapsedTime.getComponent());
+			add( iterationCount.getComponent());
+			add( reg.getViewer().getComponent());
+
+			panel().add(Shell.createButtonsFor(RThread.this));
+		}
+
+		public void attach() { targetViewer.attach(); super.attach(); }
+		public void detach() {	targetViewer.detach(); super.detach(); }
+
+		public void update(Observable o, Object src)
+		{
+			if (reg==o && src!=this) {
+				regulated = reg.value;	setPeriod();
+			}
+			if (reg.value!=regulated) {
+				reg.value=regulated; reg.changed(this);
+			}
+
+			actualRate.set((int)actual);
+			elapsedTime.set((double)(elapsed/1000.0));
+			iterationCount.set((int)(iterations));
+			super.update(o,src);
+		}
+
+	}
+}