aboutsummaryrefslogtreecommitdiffstats
path: root/src/jogl/classes/com/jogamp/opengl/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/jogl/classes/com/jogamp/opengl/util')
-rw-r--r--src/jogl/classes/com/jogamp/opengl/util/Animator.java187
-rw-r--r--src/jogl/classes/com/jogamp/opengl/util/AnimatorBase.java458
-rw-r--r--src/jogl/classes/com/jogamp/opengl/util/FPSAnimator.java307
3 files changed, 708 insertions, 244 deletions
diff --git a/src/jogl/classes/com/jogamp/opengl/util/Animator.java b/src/jogl/classes/com/jogamp/opengl/util/Animator.java
index ddbc66fa3..10f43a0c1 100644
--- a/src/jogl/classes/com/jogamp/opengl/util/Animator.java
+++ b/src/jogl/classes/com/jogamp/opengl/util/Animator.java
@@ -41,7 +41,7 @@
package com.jogamp.opengl.util;
import javax.media.opengl.GLAutoDrawable;
-
+import javax.media.opengl.GLException;
/** <P> An Animator can be attached to one or more {@link
GLAutoDrawable}s to drive their display() methods in a loop. </P>
@@ -57,10 +57,7 @@ import javax.media.opengl.GLAutoDrawable;
* Call {@link #stop() } to terminate the animation and it's execution thread.
* </p>
*/
-public class Animator extends AnimatorBase {
- /** timeout in milliseconds, 15 frames @ 60Hz = 240ms, limiting {@link #finishLifecycleAction(Condition)} */
- private static final long TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION = 15*16;
-
+public class Animator extends AnimatorBase {
protected ThreadGroup threadGroup;
private Runnable runnable;
private boolean runAsFastAsPossible;
@@ -68,6 +65,9 @@ public class Animator extends AnimatorBase {
protected boolean pauseIssued;
protected volatile boolean stopIssued;
+ /**
+ * Creates a new, empty Animator.
+ */
public Animator() {
super();
if(DEBUG) {
@@ -75,25 +75,38 @@ public class Animator extends AnimatorBase {
}
}
+ /**
+ * Creates a new Animator w/ an associated ThreadGroup.
+ */
public Animator(ThreadGroup tg) {
super();
- threadGroup = tg;
-
+ setThreadGroup(tg);
if(DEBUG) {
System.err.println("Animator created, ThreadGroup: "+threadGroup);
}
}
- /** Creates a new Animator for a particular drawable. */
+ /**
+ * Creates a new Animator for a particular drawable.
+ */
public Animator(GLAutoDrawable drawable) {
super();
add(drawable);
+ if(DEBUG) {
+ System.err.println("Animator created, w/ "+drawable);
+ }
}
- /** Creates a new Animator for a particular drawable. */
+ /**
+ * Creates a new Animator w/ an associated ThreadGroup for a particular drawable.
+ */
public Animator(ThreadGroup tg, GLAutoDrawable drawable) {
- this(tg);
+ super();
+ setThreadGroup(tg);
add(drawable);
+ if(DEBUG) {
+ System.err.println("Animator created, ThreadGroup: "+threadGroup+" and "+drawable);
+ }
}
protected String getBaseName(String prefix) {
@@ -114,7 +127,7 @@ public class Animator extends AnimatorBase {
stateSync.unlock();
}
}
-
+
private final void setIsAnimatingSynced(boolean v) {
stateSync.lock();
try {
@@ -126,36 +139,39 @@ public class Animator extends AnimatorBase {
class MainLoop implements Runnable {
public String toString() {
- return "[started "+isStartedImpl()+", animating "+isAnimatingImpl()+", paused "+isPausedImpl()+", drawable "+drawables.size()+"]";
+ return "[started "+isStartedImpl()+", animating "+isAnimatingImpl()+", paused "+isPausedImpl()+", drawable "+drawables.size()+", drawablesEmpty "+drawablesEmpty+"]";
}
public void run() {
try {
- synchronized (Animator.this) {
- if(DEBUG) {
- System.err.println("Animator start:" + Thread.currentThread() + ": " + toString());
- }
- fpsCounter.resetFPSCounter();
- animThread = Thread.currentThread();
- setIsAnimatingSynced(false); // barrier
- Animator.this.notifyAll();
+ if(DEBUG) {
+ System.err.println("Animator start:" + Thread.currentThread() + ": " + toString());
}
+ fpsCounter.resetFPSCounter();
+ animThread = Thread.currentThread();
+ setIsAnimatingSynced(false); // barrier
+ // 'waitForStartedCondition' wake-up is handled below!
while (!stopIssued) {
synchronized (Animator.this) {
- // Don't consume CPU unless there is work to be done and not paused
+ // Pause; Also don't consume CPU unless there is work to be done and not paused
+ boolean ectCleared = false;
while (!stopIssued && (pauseIssued || drawablesEmpty)) {
boolean wasPaused = pauseIssued;
if (DEBUG) {
System.err.println("Animator pause:" + Thread.currentThread() + ": " + toString());
}
+ if ( exclusiveContext && !drawablesEmpty && !ectCleared ) {
+ ectCleared = true;
+ setDrawablesExclCtxState(false);
+ display(); // propagate exclusive change!
+ }
setIsAnimatingSynced(false); // barrier
Animator.this.notifyAll();
try {
Animator.this.wait();
} catch (InterruptedException e) {
}
-
if (wasPaused) {
// resume from pause -> reset counter
fpsCounter.resetFPSCounter();
@@ -165,9 +181,12 @@ public class Animator extends AnimatorBase {
}
}
if (!stopIssued && !isAnimating) {
- // resume from pause or drawablesEmpty,
+ // Wakes up 'waitForStartedCondition' sync
+ // - and -
+ // Resume from pause or drawablesEmpty,
// implies !pauseIssued and !drawablesEmpty
- setIsAnimatingSynced(true);
+ setIsAnimatingSynced(true); // barrier
+ setDrawablesExclCtxState(exclusiveContext);
Animator.this.notifyAll();
}
} // sync Animator.this
@@ -180,6 +199,10 @@ public class Animator extends AnimatorBase {
}
}
} finally {
+ if( exclusiveContext && !drawablesEmpty ) {
+ setDrawablesExclCtxState(false);
+ display(); // propagate exclusive change!
+ }
synchronized (Animator.this) {
if(DEBUG) {
System.err.println("Animator stop " + Thread.currentThread() + ": " + toString());
@@ -194,18 +217,6 @@ public class Animator extends AnimatorBase {
}
}
- private final boolean isStartedImpl() {
- return animThread != null ;
- }
- public final boolean isStarted() {
- stateSync.lock();
- try {
- return animThread != null ;
- } finally {
- stateSync.unlock();
- }
- }
-
private final boolean isAnimatingImpl() {
return animThread != null && isAnimating ;
}
@@ -230,39 +241,19 @@ public class Animator extends AnimatorBase {
}
}
- interface Condition {
- /**
- * @return true if branching (cont waiting, action), otherwise false
- */
- boolean result();
- }
-
- private synchronized void finishLifecycleAction(Condition condition) {
- // It's hard to tell whether the thread which changes the lifecycle has
- // dependencies on the Animator's internal thread. Currently we
- // use a couple of heuristics to determine whether we should do
- // the blocking wait().
- final boolean blocking = impl.blockUntilDone(animThread);
- long remaining = blocking ? TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION : 0;
- while (remaining>0 && condition.result()) {
- long td = System.currentTimeMillis();
- try {
- wait(remaining);
- } catch (InterruptedException ie) { }
- remaining -= (System.currentTimeMillis() - td) ;
- }
- if(DEBUG) {
- if(remaining<0) {
- System.err.println("finishLifecycleAction(" + condition.getClass().getName() + "): ++++++ timeout reached ++++++ " + Thread.currentThread().getName());
- }
- System.err.println("finishLifecycleAction(" + condition.getClass().getName() + "): finished "+
- "- blocking "+blocking+
- ", waited " + (blocking ? ( TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION - remaining ) : 0 ) + "/" + TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION +
- ", started: " + isStartedImpl() +", animating: " + isAnimatingImpl() +
- ", paused: " + isPausedImpl() + ", drawables " + drawables.size() + " - " + Thread.currentThread().getName());
+ /**
+ * Set a {@link ThreadGroup} for the {@link #getThread() animation thread}.
+ *
+ * @param tg the {@link ThreadGroup}
+ * @throws GLException if the animator has already been started
+ */
+ public synchronized void setThreadGroup(ThreadGroup tg) throws GLException {
+ if ( isStartedImpl() ) {
+ throw new GLException("Animator already started.");
}
+ threadGroup = tg;
}
-
+
public synchronized boolean start() {
if ( isStartedImpl() ) {
return false;
@@ -284,74 +275,48 @@ public class Animator extends AnimatorBase {
System.err.println("Animator "+ct.getName()+"[daemon "+ct.isDaemon()+"]: starting "+thread.getName()+"[daemon "+thread.isDaemon()+"]");
}
thread.start();
- finishLifecycleAction(waitForStartedCondition);
- return true;
+ return finishLifecycleAction(waitForStartedCondition, 0);
}
-
- private class WaitForStartedCondition implements Condition {
- public boolean result() {
+ private final Condition waitForStartedCondition = new Condition() {
+ public boolean eval() {
return !isStartedImpl() || (!drawablesEmpty && !isAnimating) ;
- }
- }
- Condition waitForStartedCondition = new WaitForStartedCondition();
+ } };
public synchronized boolean stop() {
if ( !isStartedImpl() ) {
return false;
}
stopIssued = true;
- notifyAll();
- finishLifecycleAction(waitForStoppedCondition);
- return true;
+ return finishLifecycleAction(waitForStoppedCondition, 0);
}
- private class WaitForStoppedCondition implements Condition {
- public boolean result() {
+ private final Condition waitForStoppedCondition = new Condition() {
+ public boolean eval() {
return isStartedImpl();
- }
- }
- Condition waitForStoppedCondition = new WaitForStoppedCondition();
+ } };
public synchronized boolean pause() {
if ( !isStartedImpl() || pauseIssued ) {
return false;
}
- stateSync.lock();
- try {
- pauseIssued = true;
- } finally {
- stateSync.unlock();
- }
- notifyAll();
- finishLifecycleAction(waitForPausedCondition);
- return true;
+ pauseIssued = true;
+ return finishLifecycleAction(waitForPausedCondition, 0);
}
- private class WaitForPausedCondition implements Condition {
- public boolean result() {
+ private final Condition waitForPausedCondition = new Condition() {
+ public boolean eval() {
// end waiting if stopped as well
return isAnimating && isStartedImpl();
- }
- }
- Condition waitForPausedCondition = new WaitForPausedCondition();
+ } };
public synchronized boolean resume() {
if ( !isStartedImpl() || !pauseIssued ) {
return false;
}
- stateSync.lock();
- try {
- pauseIssued = false;
- } finally {
- stateSync.unlock();
- }
- notifyAll();
- finishLifecycleAction(waitForResumeCondition);
- return true;
+ pauseIssued = false;
+ return finishLifecycleAction(waitForResumeCondition, 0);
}
- private class WaitForResumeCondition implements Condition {
- public boolean result() {
+ private final Condition waitForResumeCondition = new Condition() {
+ public boolean eval() {
// end waiting if stopped as well
return !drawablesEmpty && !isAnimating && isStartedImpl();
- }
- }
- Condition waitForResumeCondition = new WaitForResumeCondition();
+ } };
}
diff --git a/src/jogl/classes/com/jogamp/opengl/util/AnimatorBase.java b/src/jogl/classes/com/jogamp/opengl/util/AnimatorBase.java
index 46fc1d991..bda716e97 100644
--- a/src/jogl/classes/com/jogamp/opengl/util/AnimatorBase.java
+++ b/src/jogl/classes/com/jogamp/opengl/util/AnimatorBase.java
@@ -38,6 +38,7 @@ import java.util.ArrayList;
import javax.media.opengl.GLAnimatorControl;
import javax.media.opengl.GLAutoDrawable;
+import javax.media.opengl.GLException;
import javax.media.opengl.GLProfile;
/**
@@ -51,69 +52,174 @@ import javax.media.opengl.GLProfile;
*/
public abstract class AnimatorBase implements GLAnimatorControl {
protected static final boolean DEBUG = Debug.debug("Animator");
-
- private static int animatorCount = 0;
-
+
+ /** A 1s timeout while waiting for a native action response, limiting {@link #finishLifecycleAction(Condition, long)} */
+ protected static final long TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION = 1000;
+
+ protected static final long POLLP_WAIT_FOR_FINISH_LIFECYCLE_ACTION = 32; // 2 frames @ 60Hz
+
+ /**
+ * If present in <code>modeBits</code> field and
+ * {@link GLProfile#isAWTAvailable() AWT is available},
+ * implementation is aware of the AWT EDT, otherwise not.
+ * <p>
+ * This is the <i>default</i>.
+ * </p>
+ * @see #setModeBits(boolean, int)
+ */
+ public static final int MODE_EXPECT_AWT_RENDERING_THREAD = 1 << 0;
+
public interface AnimatorImpl {
void display(ArrayList<GLAutoDrawable> drawables, boolean ignoreExceptions, boolean printExceptions);
boolean blockUntilDone(Thread thread);
}
- protected ArrayList<GLAutoDrawable> drawables = new ArrayList<GLAutoDrawable>();
- protected boolean drawablesEmpty;
+ protected int modeBits;
protected AnimatorImpl impl;
protected String baseName;
+
+ protected ArrayList<GLAutoDrawable> drawables = new ArrayList<GLAutoDrawable>();
+ protected boolean drawablesEmpty;
protected Thread animThread;
protected boolean ignoreExceptions;
protected boolean printExceptions;
+ protected boolean exclusiveContext;
+ protected Thread userExclusiveContextThread;
protected FPSCounterImpl fpsCounter = new FPSCounterImpl();
protected RecursiveLock stateSync = LockFactory.createRecursiveLock();
-
- /** Creates a new, empty Animator. */
- public AnimatorBase() {
- if(GLProfile.isAWTAvailable()) {
+
+ private final static Class<?> awtAnimatorImplClazz;
+ static {
+ GLProfile.initSingleton();
+ if( GLProfile.isAWTAvailable() ) {
+ Class<?> clazz;
try {
- impl = (AnimatorImpl) Class.forName("com.jogamp.opengl.util.AWTAnimatorImpl").newInstance();
- baseName = "AWTAnimator";
- } catch (Exception e) { e.printStackTrace(); }
- }
- if(null==impl) {
- impl = new DefaultAnimatorImpl();
- baseName = "Animator";
- }
- synchronized (Animator.class) {
- animatorCount++;
- baseName = baseName.concat("-"+animatorCount);
- drawablesEmpty = true;
+ clazz = Class.forName("com.jogamp.opengl.util.AWTAnimatorImpl");
+ } catch (Exception e) {
+ clazz = null;
+ }
+ awtAnimatorImplClazz = clazz;
+ } else {
+ awtAnimatorImplClazz = null;
}
}
+ /**
+ * Creates a new, empty Animator instance
+ * while expecting an AWT rendering thread if AWT is available.
+ *
+ * @see GLProfile#isAWTAvailable()
+ */
+ public AnimatorBase() {
+ modeBits = MODE_EXPECT_AWT_RENDERING_THREAD; // default!
+ drawablesEmpty = true;
+ }
+
+ /**
+ * Initializes implementation details post setup,
+ * invoked at {@link #add(GLAutoDrawable)}, {@link #start()}, ..
+ * <p>
+ * Operation is a NOP if <code>force</code> is <code>false</code>
+ * and this instance is already initialized.
+ * </p>
+ *
+ * @throws GLException if Animator is {@link #isStarted()}
+ */
+ protected void initImpl(boolean force) {
+ if( force || null == impl ) {
+ if( 0 != ( MODE_EXPECT_AWT_RENDERING_THREAD & modeBits ) && null != awtAnimatorImplClazz ) {
+ try {
+ impl = (AnimatorImpl) awtAnimatorImplClazz.newInstance();
+ baseName = getBaseName("AWT");
+ } catch (Exception e) { e.printStackTrace(); }
+ }
+ if( null == impl ) {
+ impl = new DefaultAnimatorImpl();
+ baseName = getBaseName("");
+ }
+ if(DEBUG) {
+ System.err.println("Animator.initImpl: baseName "+baseName+", implClazz "+impl.getClass().getName()+" - "+toString()+" - "+Thread.currentThread().getName());
+ }
+ }
+ }
protected abstract String getBaseName(String prefix);
- public synchronized void add(GLAutoDrawable drawable) {
+ /**
+ * Enables or disables the given <code>bitValues</code>
+ * in this Animators <code>modeBits</code>.
+ * @param enable
+ * @param bitValues
+ *
+ * @throws GLException if Animator is {@link #isStarted()}
+ * @see AnimatorBase#MODE_EXPECT_AWT_RENDERING_THREAD
+ */
+ public synchronized void setModeBits(boolean enable, int bitValues) throws GLException {
+ if( isStarted() ) {
+ throw new GLException("Animator already started");
+ }
+ final int _oldModeBits = modeBits;
+ if(enable) {
+ modeBits |= bitValues;
+ } else {
+ modeBits &= ~bitValues;
+ }
+ if( _oldModeBits != modeBits ) {
+ initImpl(true);
+ }
+ }
+ public synchronized int getModeBits() { return modeBits; }
+
+
+ @Override
+ public synchronized void add(final GLAutoDrawable drawable) {
if(DEBUG) {
- System.err.println("Animator add: "+drawable.hashCode()+" - "+Thread.currentThread().getName());
+ System.err.println("Animator add: 0x"+Integer.toHexString(drawable.hashCode())+" - "+toString()+" - "+Thread.currentThread().getName());
}
+ if( drawables.contains(drawable) ) {
+ throw new IllegalArgumentException("Drawable already added to animator: "+this+", "+drawable);
+ }
+ initImpl(false);
boolean paused = pause();
+ if( isStarted() ) {
+ drawable.setExclusiveContextThread( exclusiveContext ? getExclusiveContextThread() : null ); // if already running ..
+ }
drawables.add(drawable);
drawablesEmpty = drawables.size() == 0;
drawable.setAnimator(this);
if(paused) {
resume();
}
- if(impl.blockUntilDone(animThread)) {
- while(isStarted() && !isPaused() && !isAnimating()) {
- try {
- wait();
- } catch (InterruptedException ie) { }
- }
+ final Condition waitForAnimatingAndECTCondition = new Condition() {
+ public boolean eval() {
+ final Thread dect = drawable.getExclusiveContextThread();
+ return isStarted() && !isPaused() && !isAnimating() && ( exclusiveContext && null == dect || !exclusiveContext && null != dect );
+ } };
+ final boolean res = finishLifecycleAction(waitForAnimatingAndECTCondition, 0);
+ if(DEBUG) {
+ System.err.println("Animator add: Wait for Animating/ECT OK: "+res+", "+toString()+", dect "+drawable.getExclusiveContextThread());
}
notifyAll();
}
- public synchronized void remove(GLAutoDrawable drawable) {
+ @Override
+ public synchronized void remove(final GLAutoDrawable drawable) {
if(DEBUG) {
- System.err.println("Animator remove: "+drawable.hashCode()+" - "+Thread.currentThread().getName() + ": "+toString());
+ System.err.println("Animator remove: 0x"+Integer.toHexString(drawable.hashCode())+" - "+toString()+" - "+Thread.currentThread().getName());
+ }
+ if( !drawables.contains(drawable) ) {
+ throw new IllegalArgumentException("Drawable not added to animator: "+this+", "+drawable);
+ }
+
+ if( exclusiveContext && isAnimating() ) {
+ drawable.setExclusiveContextThread( null );
+ final Condition waitForNullECTCondition = new Condition() {
+ public boolean eval() {
+ return null != drawable.getExclusiveContextThread();
+ } };
+ final boolean res = finishLifecycleAction(waitForNullECTCondition, POLLP_WAIT_FOR_FINISH_LIFECYCLE_ACTION);
+ if(DEBUG) {
+ System.err.println("Animator remove: Wait for Null-ECT OK: "+res+", "+toString()+", dect "+drawable.getExclusiveContextThread());
+ }
}
boolean paused = pause();
@@ -123,14 +229,202 @@ public abstract class AnimatorBase implements GLAnimatorControl {
if(paused) {
resume();
}
- if(impl.blockUntilDone(animThread)) {
- while(isStarted() && drawablesEmpty && isAnimating()) {
+ final boolean res = finishLifecycleAction(waitForNotAnimatingIfEmptyCondition, 0);
+ if(DEBUG) {
+ System.err.println("Animator remove: Wait for !Animating-if-empty OK: "+res+", "+toString());
+ }
+ notifyAll();
+ }
+ private final Condition waitForNotAnimatingIfEmptyCondition = new Condition() {
+ public boolean eval() {
+ return isStarted() && drawablesEmpty && isAnimating();
+ } };
+
+
+ /**
+ * Dedicate all {@link GLAutoDrawable}'s context to the given exclusive context thread.
+ * <p>
+ * The given thread will be exclusive to all {@link GLAutoDrawable}'s context while {@link #isAnimating()}.
+ * </p>
+ * <p>
+ * If already started and disabling, method waits
+ * until change is propagated to all {@link GLAutoDrawable} if not
+ * called from the animator thread or {@link #getExclusiveContextThread() exclusive context thread}.
+ * </p>
+ * <p>
+ * Note: Utilizing this feature w/ AWT could lead to an AWT-EDT deadlock, depending on the AWT implementation.
+ * Hence it is advised not to use it with native AWT GLAutoDrawable like GLCanvas.
+ * </p>
+ *
+ * @param enable
+ * @return previous value
+ * @see #setExclusiveContext(boolean)
+ * @see #getExclusiveContextThread()
+ * @see #isExclusiveContextEnabled()
+ */
+ // @Override
+ public final Thread setExclusiveContext(Thread t) {
+ final Thread old;
+ final boolean enable = null != t;
+ stateSync.lock();
+ try {
+ old = userExclusiveContextThread;
+ if( enable && t != animThread ) { // disable: will be cleared at end after propagation && filter out own animThread usae
+ userExclusiveContextThread=t;
+ }
+ } finally {
+ stateSync.unlock();
+ }
+ setExclusiveContext(enable);
+ return old;
+ }
+
+ /**
+ * Dedicate all {@link GLAutoDrawable}'s context to this animator thread.
+ * <p>
+ * The given thread will be exclusive to all {@link GLAutoDrawable}'s context while {@link #isAnimating()}.
+ * </p>
+ * <p>
+ * If already started and disabling, method waits
+ * until change is propagated to all {@link GLAutoDrawable} if not
+ * called from the animator thread or {@link #getExclusiveContextThread() exclusive context thread}.
+ * </p>
+ * <p>
+ * Note: Utilizing this feature w/ AWT could lead to an AWT-EDT deadlock, depending on the AWT implementation.
+ * Hence it is advised not to use it with native AWT GLAutoDrawable like GLCanvas.
+ * </p>
+ *
+ * @param enable
+ * @return previous value
+ * @see #setExclusiveContext(Thread)
+ * @see #getExclusiveContextThread()
+ * @see #isExclusiveContextEnabled()
+ */
+ // @Override
+ public final boolean setExclusiveContext(boolean enable) {
+ final boolean propagateState;
+ final boolean oldExclusiveContext;
+ final Thread _exclusiveContextThread;
+ synchronized (AnimatorBase.this) {
+ propagateState = isStarted() && !drawablesEmpty;
+ _exclusiveContextThread = userExclusiveContextThread;
+ oldExclusiveContext = exclusiveContext;
+ exclusiveContext = enable;
+ if(DEBUG) {
+ System.err.println("AnimatorBase.setExclusiveContextThread: "+oldExclusiveContext+" -> "+exclusiveContext+", propagateState "+propagateState+", "+this);
+ }
+ }
+ final Thread dECT = enable ? ( null != userExclusiveContextThread ? userExclusiveContextThread : animThread ) : null ;
+ if( propagateState ) {
+ setDrawablesExclCtxState(enable);
+ if( !enable ) {
+ if( Thread.currentThread() == getThread() || Thread.currentThread() == _exclusiveContextThread ) {
+ display();
+ } else {
+ final boolean resumed = isAnimating() ? false : resume();
+ int counter = 10;
+ while( 0<counter && isAnimating() && !validateDrawablesExclCtxState(dECT) ) {
+ try {
+ Thread.sleep(20);
+ } catch (InterruptedException e) { }
+ counter--;
+ }
+ if(resumed) {
+ pause();
+ }
+ }
+ stateSync.lock();
try {
- wait();
- } catch (InterruptedException ie) { }
+ userExclusiveContextThread=null;
+ } finally {
+ stateSync.unlock();
+ }
}
}
- notifyAll();
+ if(DEBUG) {
+ System.err.println("AnimatorBase.setExclusiveContextThread: all-GLAD Ok: "+validateDrawablesExclCtxState(dECT)+", "+this);
+ }
+ return oldExclusiveContext;
+ }
+
+ /**
+ * Returns <code>true</code>, if the exclusive context thread is enabled, otherwise <code>false</code>.
+ *
+ * @see #setExclusiveContext(boolean)
+ * @see #setExclusiveContext(Thread)
+ */
+ // @Override
+ public final boolean isExclusiveContextEnabled() {
+ stateSync.lock();
+ try {
+ return exclusiveContext;
+ } finally {
+ stateSync.unlock();
+ }
+ }
+
+ /**
+ * Returns the exclusive context thread if {@link #isExclusiveContextEnabled()} and {@link #isStarted()}, otherwise <code>null</code>.
+ * <p>
+ * If exclusive context is enabled via {@link #setExclusiveContext(boolean)}
+ * the {@link #getThread() animator thread} is returned if above conditions are met.
+ * </p>
+ * <p>
+ * If exclusive context is enabled via {@link #setExclusiveContext(Thread)}
+ * the user passed thread is returned if above conditions are met.
+ * </p>
+ * @see #setExclusiveContext(boolean)
+ * @see #setExclusiveContext(Thread)
+ */
+ // @Override
+ public final Thread getExclusiveContextThread() {
+ stateSync.lock();
+ try {
+ return ( isStartedImpl() && exclusiveContext ) ? ( null != userExclusiveContextThread ? userExclusiveContextThread : animThread ) : null ;
+ } finally {
+ stateSync.unlock();
+ }
+ }
+
+ /**
+ * Should be called at {@link #start()} and {@link #stop()}
+ * from within the animator thread.
+ * <p>
+ * At {@link #stop()} an additional {@link #display()} call shall be issued
+ * to allow propagation of releasing the exclusive thread.
+ * </p>
+ */
+ protected final synchronized void setDrawablesExclCtxState(boolean enable) {
+ if(DEBUG) {
+ System.err.println("AnimatorBase.setExclusiveContextImpl exlusive "+exclusiveContext+": Enable "+enable+" for "+this+" - "+Thread.currentThread());
+ // Thread.dumpStack();
+ }
+ final Thread ect = getExclusiveContextThread();
+ for (int i=0; i<drawables.size(); i++) {
+ try {
+ drawables.get(i).setExclusiveContextThread( enable ? ect : null );
+ } catch (RuntimeException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ protected final boolean validateDrawablesExclCtxState(Thread expected) {
+ for (int i=0; i<drawables.size(); i++) {
+ if( expected != drawables.get(i).getExclusiveContextThread() ) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public final Thread getThread() {
+ stateSync.lock();
+ try {
+ return animThread;
+ } finally {
+ stateSync.unlock();
+ }
}
/** Called every frame to cause redrawing of all of the
@@ -143,55 +437,56 @@ public abstract class AnimatorBase implements GLAnimatorControl {
fpsCounter.tickFPS();
}
+ @Override
public final void setUpdateFPSFrames(int frames, PrintStream out) {
fpsCounter.setUpdateFPSFrames(frames, out);
}
+ @Override
public final void resetFPSCounter() {
fpsCounter.resetFPSCounter();
}
+ @Override
public final int getUpdateFPSFrames() {
return fpsCounter.getUpdateFPSFrames();
}
+ @Override
public final long getFPSStartTime() {
return fpsCounter.getFPSStartTime();
}
+ @Override
public final long getLastFPSUpdateTime() {
return fpsCounter.getLastFPSUpdateTime();
}
+ @Override
public final long getLastFPSPeriod() {
return fpsCounter.getLastFPSPeriod();
}
+ @Override
public final float getLastFPS() {
return fpsCounter.getLastFPS();
}
+ @Override
public final int getTotalFPSFrames() {
return fpsCounter.getTotalFPSFrames();
}
+ @Override
public final long getTotalFPSDuration() {
return fpsCounter.getTotalFPSDuration();
}
+ @Override
public final float getTotalFPS() {
return fpsCounter.getTotalFPS();
}
- public final Thread getThread() {
- stateSync.lock();
- try {
- return animThread;
- } finally {
- stateSync.unlock();
- }
- }
-
/** Sets a flag causing this Animator to ignore exceptions produced
while redrawing the drawables. By default this flag is set to
false, causing any exception thrown to halt the Animator. */
@@ -207,7 +502,78 @@ public abstract class AnimatorBase implements GLAnimatorControl {
this.printExceptions = printExceptions;
}
+ protected interface Condition {
+ /**
+ * @return true if branching (continue waiting, action), otherwise false
+ */
+ boolean eval();
+ }
+
+ /**
+ * @param waitCondition method will wait until TO is reached or {@link Condition#eval() waitCondition.eval()} returns <code>false</code>.
+ * @param pollPeriod if <code>0</code>, method will wait until TO is reached or being notified.
+ * if &gt; <code>0</code>, method will wait for the given <code>pollPeriod</code> in milliseconds.
+ * @return <code>true</code> if {@link Condition#eval() waitCondition.eval()} returned <code>false</code>, otherwise <code>false</code>.
+ */
+ protected synchronized boolean finishLifecycleAction(Condition waitCondition, long pollPeriod) {
+ // It's hard to tell whether the thread which changes the lifecycle has
+ // dependencies on the Animator's internal thread. Currently we
+ // use a couple of heuristics to determine whether we should do
+ // the blocking wait().
+ initImpl(false);
+ final boolean blocking = impl.blockUntilDone(animThread);
+ long remaining = blocking ? TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION : 0;
+ if( 0 >= pollPeriod ) {
+ pollPeriod = remaining;
+ }
+ boolean nok = waitCondition.eval();
+ while ( nok && remaining>0 ) {
+ final long t1 = System.currentTimeMillis();
+ if( pollPeriod > remaining ) { pollPeriod = remaining; }
+ notifyAll();
+ try {
+ wait(pollPeriod);
+ } catch (InterruptedException ie) { }
+ remaining -= System.currentTimeMillis() - t1 ;
+ nok = waitCondition.eval();
+ }
+ if(DEBUG || nok) {
+ if( remaining<=0 && nok ) {
+ System.err.println("finishLifecycleAction(" + waitCondition.getClass().getName() + "): ++++++ timeout reached ++++++ " + Thread.currentThread().getName());
+ }
+ stateSync.lock(); // avoid too many lock/unlock ops
+ try {
+ System.err.println("finishLifecycleAction(" + waitCondition.getClass().getName() + "): OK "+(!nok)+
+ "- pollPeriod "+pollPeriod+", blocking "+blocking+
+ ", waited " + (blocking ? ( TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION - remaining ) : 0 ) + "/" + TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION +
+ " - " + Thread.currentThread().getName());
+ System.err.println(" - "+toString());
+ } finally {
+ stateSync.unlock();
+ }
+ if(nok) {
+ Thread.dumpStack();
+ }
+ }
+ return !nok;
+ }
+
+ protected final boolean isStartedImpl() {
+ return animThread != null ;
+ }
+ @Override
+ public boolean isStarted() {
+ stateSync.lock();
+ try {
+ return animThread != null ;
+ } finally {
+ stateSync.unlock();
+ }
+ }
+
public String toString() {
- return getClass().getName()+"[started "+isStarted()+", animating "+isAnimating()+", paused "+isPaused()+", drawable "+drawables.size()+", totals[dt "+getTotalFPSDuration()+", frames "+getTotalFPSFrames()+", fps "+getTotalFPS()+"]]";
+ return getClass().getName()+"[started "+isStarted()+", animating "+isAnimating()+", paused "+isPaused()+", drawable "+drawables.size()+
+ ", totals[dt "+getTotalFPSDuration()+", frames "+getTotalFPSFrames()+", fps "+getTotalFPS()+
+ "], modeBits "+modeBits+", init'ed "+(null!=impl)+", animThread "+getThread()+", exclCtxThread "+exclusiveContext+"("+getExclusiveContextThread()+")]";
}
}
diff --git a/src/jogl/classes/com/jogamp/opengl/util/FPSAnimator.java b/src/jogl/classes/com/jogamp/opengl/util/FPSAnimator.java
index 251792110..bfcab23fd 100644
--- a/src/jogl/classes/com/jogamp/opengl/util/FPSAnimator.java
+++ b/src/jogl/classes/com/jogamp/opengl/util/FPSAnimator.java
@@ -39,8 +39,11 @@
*/
package com.jogamp.opengl.util;
-import java.util.*;
-import javax.media.opengl.*;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.media.opengl.GLAutoDrawable;
+import javax.media.opengl.GLException;
/**
* An Animator subclass which attempts to achieve a target
@@ -54,10 +57,12 @@ import javax.media.opengl.*;
*/
public class FPSAnimator extends AnimatorBase {
private Timer timer = null;
- private TimerTask task = null;
+ private MainTask task = null;
private int fps;
private boolean scheduleAtFixedRate;
- private volatile boolean shouldRun;
+ private boolean isAnimating; // MainTask feedback
+ private volatile boolean shouldRun; // MainTask trigger
+ private volatile boolean shouldStop; // MainTask trigger
protected String getBaseName(String prefix) {
return "FPS" + prefix + "Animator" ;
@@ -88,6 +93,7 @@ public class FPSAnimator extends AnimatorBase {
value, an initial drawable to animate, and a flag indicating
whether to use fixed-rate scheduling. */
public FPSAnimator(GLAutoDrawable drawable, int fps, boolean scheduleAtFixedRate) {
+ super();
this.fps = fps;
if (drawable != null) {
add(drawable);
@@ -95,133 +101,260 @@ public class FPSAnimator extends AnimatorBase {
this.scheduleAtFixedRate = scheduleAtFixedRate;
}
- public final boolean isStarted() {
- stateSync.lock();
- try {
- return (timer != null);
- } finally {
- stateSync.unlock();
+ /**
+ * @param fps
+ * @throws GLException if the animator has already been started
+ */
+ public final synchronized void setFPS(int fps) throws GLException {
+ if ( isStartedImpl() ) {
+ throw new GLException("Animator already started.");
}
+ this.fps = fps;
}
+ public final int getFPS() { return fps; }
+
+ class MainTask extends TimerTask {
+ private boolean justStarted;
+ private boolean alreadyStopped;
+ private boolean alreadyPaused;
+
+ public MainTask() {
+ }
+
+ public void start(Timer timer) {
+ fpsCounter.resetFPSCounter();
+ shouldRun = true;
+ shouldStop = false;
+
+ justStarted = true;
+ alreadyStopped = false;
+ alreadyPaused = false;
+ final long period = 0 < fps ? (long) (1000.0f / (float) fps) : 1; // 0 -> 1: IllegalArgumentException: Non-positive period
+ if (scheduleAtFixedRate) {
+ timer.scheduleAtFixedRate(this, 0, period);
+ } else {
+ timer.schedule(this, 0, period);
+ }
+ }
+
+ public boolean isActive() { return !alreadyStopped && !alreadyPaused; }
+
+ public String toString() {
+ return "Task[thread "+animThread+", stopped "+alreadyStopped+", paused "+alreadyPaused+" shouldRun "+shouldRun+", shouldStop "+shouldStop+" -- started "+isStartedImpl()+", animating "+isAnimatingImpl()+", paused "+isPausedImpl()+", drawable "+drawables.size()+", drawablesEmpty "+drawablesEmpty+"]";
+ }
+
+ public void run() {
+ if( justStarted ) {
+ justStarted = false;
+ synchronized (FPSAnimator.this) {
+ animThread = Thread.currentThread();
+ if(DEBUG) {
+ System.err.println("FPSAnimator start/resume:" + Thread.currentThread() + ": " + toString());
+ }
+ isAnimating = true;
+ if( drawablesEmpty ) {
+ shouldRun = false; // isAnimating:=false @ pause below
+ } else {
+ shouldRun = true;
+ setDrawablesExclCtxState(exclusiveContext);
+ FPSAnimator.this.notifyAll();
+ }
+ System.err.println("FPSAnimator P1:" + Thread.currentThread() + ": " + toString());
+ }
+ }
+ if( shouldRun ) {
+ display();
+ } else if( shouldStop ) { // STOP
+ System.err.println("FPSAnimator P4: "+alreadyStopped+", "+ Thread.currentThread() + ": " + toString());
+ this.cancel();
+
+ if( !alreadyStopped ) {
+ alreadyStopped = true;
+ if( exclusiveContext && !drawablesEmpty ) {
+ setDrawablesExclCtxState(false);
+ display(); // propagate exclusive change!
+ }
+ synchronized (FPSAnimator.this) {
+ if(DEBUG) {
+ System.err.println("FPSAnimator stop " + Thread.currentThread() + ": " + toString());
+ }
+ animThread = null;
+ isAnimating = false;
+ FPSAnimator.this.notifyAll();
+ }
+ }
+ } else {
+ System.err.println("FPSAnimator P5: "+alreadyPaused+", "+ Thread.currentThread() + ": " + toString());
+ this.cancel();
+
+ if( !alreadyPaused ) { // PAUSE
+ alreadyPaused = true;
+ if( exclusiveContext && !drawablesEmpty ) {
+ setDrawablesExclCtxState(false);
+ display(); // propagate exclusive change!
+ }
+ synchronized (FPSAnimator.this) {
+ if(DEBUG) {
+ System.err.println("FPSAnimator pause " + Thread.currentThread() + ": " + toString());
+ }
+ isAnimating = false;
+ FPSAnimator.this.notifyAll();
+ }
+ }
+ }
+ }
+ }
+ private final boolean isAnimatingImpl() {
+ return animThread != null && isAnimating ;
+ }
public final boolean isAnimating() {
stateSync.lock();
try {
- return (timer != null) && (task != null);
+ return animThread != null && isAnimating ;
} finally {
stateSync.unlock();
}
}
+ private final boolean isPausedImpl() {
+ return animThread != null && ( !shouldRun && !shouldStop ) ;
+ }
public final boolean isPaused() {
stateSync.lock();
try {
- return (timer != null) && (task == null);
+ return animThread != null && ( !shouldRun && !shouldStop ) ;
} finally {
stateSync.unlock();
}
}
- private void startTask() {
- if(null != task) {
- return;
- }
- final long period = (long) (1000.0f / (float) fps);
- task = new TimerTask() {
- public void run() {
- if(FPSAnimator.this.shouldRun) {
- FPSAnimator.this.animThread = Thread.currentThread();
- // display impl. uses synchronized block on the animator instance
- display();
- }
- }
- };
-
- fpsCounter.resetFPSCounter();
- shouldRun = true;
-
- if (scheduleAtFixedRate) {
- timer.scheduleAtFixedRate(task, 0, period);
- } else {
- timer.schedule(task, 0, period);
- }
- }
-
- public synchronized boolean start() {
- if (timer != null) {
+ static int timerNo = 0;
+
+ public synchronized boolean start() {
+ if ( null != timer || null != task || isStartedImpl() ) {
return false;
}
- stateSync.lock();
- try {
- timer = new Timer();
- startTask();
- } finally {
- stateSync.unlock();
+ timer = new Timer( Thread.currentThread().getName()+"-"+baseName+"-Timer"+(timerNo++) );
+ task = new MainTask();
+ if(DEBUG) {
+ System.err.println("FPSAnimator.start() START: "+task+", "+ Thread.currentThread() + ": " + toString());
}
- return true;
+ task.start(timer);
+
+ final boolean res = finishLifecycleAction( drawablesEmpty ? waitForStartedEmptyCondition : waitForStartedAddedCondition,
+ POLLP_WAIT_FOR_FINISH_LIFECYCLE_ACTION);
+ if(DEBUG) {
+ System.err.println("FPSAnimator.start() END: "+task+", "+ Thread.currentThread() + ": " + toString());
+ }
+ if( drawablesEmpty ) {
+ task.cancel();
+ task = null;
+ }
+ return res;
}
+ private final Condition waitForStartedAddedCondition = new Condition() {
+ public boolean eval() {
+ return !isStartedImpl() || !isAnimating ;
+ } };
+ private final Condition waitForStartedEmptyCondition = new Condition() {
+ public boolean eval() {
+ return !isStartedImpl() || isAnimating ;
+ } };
/** Stops this FPSAnimator. Due to the implementation of the
FPSAnimator it is not guaranteed that the FPSAnimator will be
completely stopped by the time this method returns. */
public synchronized boolean stop() {
- if (timer == null) {
+ if ( null == timer || !isStartedImpl() ) {
return false;
+ }
+ if(DEBUG) {
+ System.err.println("FPSAnimator.stop() START: "+task+", "+ Thread.currentThread() + ": " + toString());
}
- stateSync.lock();
- try {
+ final boolean res;
+ if( null == task ) {
+ // start/resume case w/ drawablesEmpty
+ res = true;
+ } else {
shouldRun = false;
- if(null != task) {
- task.cancel();
- task = null;
- }
- if(null != timer) {
- timer.cancel();
- timer = null;
- }
- animThread = null;
- try {
- final long periodx2 = 2L * (long) (1000.0f / (float) fps);
- Thread.sleep(periodx2 > 20L ? periodx2 : 20L); // max(2 x timer period, ~ 1/60), since we can't ctrl stopped threads
- } catch (InterruptedException e) { }
- } finally {
- stateSync.unlock();
+ shouldStop = true;
+ res = finishLifecycleAction(waitForStoppedCondition, POLLP_WAIT_FOR_FINISH_LIFECYCLE_ACTION);
+ }
+
+ if(DEBUG) {
+ System.err.println("FPSAnimator.stop() END: "+task+", "+ Thread.currentThread() + ": " + toString());
+ }
+ if(null != task) {
+ task.cancel();
+ task = null;
}
- return true;
+ if(null != timer) {
+ timer.cancel();
+ timer = null;
+ }
+ animThread = null;
+ return res;
}
+ private final Condition waitForStoppedCondition = new Condition() {
+ public boolean eval() {
+ return isStartedImpl();
+ } };
public synchronized boolean pause() {
- if (timer == null) {
+ if ( !isStartedImpl() || ( null != task && isPausedImpl() ) ) {
return false;
}
- stateSync.lock();
- try {
+ if(DEBUG) {
+ System.err.println("FPSAnimator.pause() START: "+task+", "+ Thread.currentThread() + ": " + toString());
+ }
+ final boolean res;
+ if( null == task ) {
+ // start/resume case w/ drawablesEmpty
+ res = true;
+ } else {
shouldRun = false;
- if(null != task) {
- task.cancel();
- task = null;
- }
- animThread = null;
- try {
- final long periodx2 = 2L * (long) (1000.0f / (float) fps);
- Thread.sleep(periodx2 > 20L ? periodx2 : 20L); // max(2 x timer period, ~ 1/60), since we can't ctrl stopped threads
- } catch (InterruptedException e) { }
- } finally {
- stateSync.unlock();
+ res = finishLifecycleAction(waitForPausedCondition, POLLP_WAIT_FOR_FINISH_LIFECYCLE_ACTION);
+ }
+
+ if(DEBUG) {
+ System.err.println("FPSAnimator.pause() END: "+task+", "+ Thread.currentThread() + ": " + toString());
+ }
+ if(null != task) {
+ task.cancel();
+ task = null;
}
- return true;
+ return res;
}
+ private final Condition waitForPausedCondition = new Condition() {
+ public boolean eval() {
+ // end waiting if stopped as well
+ return isAnimating && isStartedImpl();
+ } };
public synchronized boolean resume() {
- if (timer == null) {
+ if ( null != task || !isStartedImpl() || !isPausedImpl() ) {
return false;
}
- stateSync.lock();
- try {
- startTask();
- } finally {
- stateSync.unlock();
+ if(DEBUG) {
+ System.err.println("FPSAnimator.resume() START: "+ Thread.currentThread() + ": " + toString());
+ }
+ final boolean res;
+ if( drawablesEmpty ) {
+ res = true;
+ } else {
+ task = new MainTask();
+ task.start(timer);
+ res = finishLifecycleAction(waitForResumeCondition, POLLP_WAIT_FOR_FINISH_LIFECYCLE_ACTION);
+ }
+ if(DEBUG) {
+ System.err.println("FPSAnimator.resume() END: "+task+", "+ Thread.currentThread() + ": " + toString());
}
- return true;
+ return res;
}
+ private final Condition waitForResumeCondition = new Condition() {
+ public boolean eval() {
+ // end waiting if stopped as well
+ return !drawablesEmpty && !isAnimating && isStartedImpl();
+ } };
}