diff options
Diffstat (limited to 'src/jogl/classes/com/jogamp/opengl/util')
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 > <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(); + } }; } |