/**
* Copyright 2010 JogAmp Community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of JogAmp Community.
*/
package com.jogamp.opengl.util;
import jogamp.opengl.Debug;
import jogamp.opengl.FPSCounterImpl;
import java.io.PrintStream;
import java.util.ArrayList;
import com.jogamp.opengl.GLAnimatorControl;
import com.jogamp.opengl.GLAutoDrawable;
import com.jogamp.opengl.GLException;
import com.jogamp.opengl.GLProfile;
import com.jogamp.common.ExceptionUtils;
import com.jogamp.common.util.InterruptedRuntimeException;
/**
* Base implementation of GLAnimatorControl
*
* The change synchronization is done via synchronized blocks on the AnimatorBase instance.
* Status get / set activity is synced with a RecursiveLock, used as a memory barrier.
* This is suitable, since all change requests are allowed to be expensive
* as they are not expected to be called at every frame.
*
modeBits
field and
* {@link GLProfile#isAWTAvailable() AWT is available},
* implementation is aware of the AWT EDT, otherwise not.
* * This is the default. *
* @see #setModeBits(boolean, int) */ public static final int MODE_EXPECT_AWT_RENDERING_THREAD = 1 << 0; @SuppressWarnings("serial") public static class UncaughtAnimatorException extends RuntimeException { final GLAutoDrawable drawable; public UncaughtAnimatorException(final GLAutoDrawable drawable, final Throwable cause) { super(cause); this.drawable = drawable; } public final GLAutoDrawable getGLAutoDrawable() { return drawable; } } public static interface AnimatorImpl { /** * @param drawables * @param ignoreExceptions * @param printExceptions * @throws UncaughtAnimatorException as caused by {@link GLAutoDrawable#display()} */ void display(final ArrayList
* Operation is a NOP if force
is false
* and this instance is already initialized.
*
bitValues
* in this Animators modeBits
.
* @param enable
* @param bitValues
*
* @throws GLException if Animator is {@link #isStarted()} and {@link #MODE_EXPECT_AWT_RENDERING_THREAD} about to change
* @see AnimatorBase#MODE_EXPECT_AWT_RENDERING_THREAD
*/
public final synchronized void setModeBits(final boolean enable, final int bitValues) throws GLException {
final int _oldModeBits = modeBits;
if(enable) {
modeBits |= bitValues;
} else {
modeBits &= ~bitValues;
}
if( useAWTAnimatorImpl( _oldModeBits ) != useAWTAnimatorImpl( modeBits ) ) {
if( isStarted() ) {
throw new GLException("Animator already started");
}
initImpl(true);
}
}
public synchronized int getModeBits() { return modeBits; }
@Override
public final synchronized void add(final GLAutoDrawable drawable) {
if(DEBUG) {
System.err.println("Animator add: 0x"+Integer.toHexString(drawable.hashCode())+" - "+toString()+" - "+getThreadName());
}
if( drawables.contains(drawable) ) {
throw new IllegalArgumentException("Drawable already added to animator: "+this+", "+drawable);
}
initImpl(false);
pause();
if( isStarted() ) {
drawable.setExclusiveContextThread( exclusiveContext ? getExclusiveContextThread() : null ); // if already running ..
}
drawables.add(drawable);
drawablesEmpty = drawables.size() == 0;
drawable.setAnimator(this);
if( isPaused() ) { // either paused by pause() above, or if previously drawablesEmpty==true
resume();
}
final Condition waitForAnimatingAndECTCondition = new Condition() {
@Override
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();
}
@Override
public final synchronized void remove(final GLAutoDrawable drawable) {
if(DEBUG) {
System.err.println("Animator remove: 0x"+Integer.toHexString(drawable.hashCode())+" - "+toString()+" - "+getThreadName());
}
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() {
@Override
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());
}
}
final boolean paused = pause();
drawables.remove(drawable);
drawablesEmpty = drawables.size() == 0;
drawable.setAnimator(null);
if(paused) {
resume();
}
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() {
@Override
public boolean eval() {
return isStarted() && drawablesEmpty && isAnimating();
} };
/**
* Dedicate all {@link GLAutoDrawable}'s context to the given exclusive context thread.
* * The given thread will be exclusive to all {@link GLAutoDrawable}'s context while {@link #isAnimating()}. *
** 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}. *
** 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. *
* * @param enable * @return previous value * @see #setExclusiveContext(boolean) * @see #getExclusiveContextThread() * @see #isExclusiveContextEnabled() */ // @Override public final synchronized Thread setExclusiveContext(final Thread t) { final boolean enable = null != t; final Thread old = userExclusiveContextThread; if( enable && t != animThread ) { // disable: will be cleared at end after propagation && filter out own animThread usae userExclusiveContextThread=t; } setExclusiveContext(enable); return old; } /** * Dedicate all {@link GLAutoDrawable}'s context to this animator thread. ** The given thread will be exclusive to all {@link GLAutoDrawable}'s context while {@link #isAnimating()}. *
** 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}. *
** 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. *
* * @param enable * @return previous value * @see #setExclusiveContext(Thread) * @see #getExclusiveContextThread() * @see #isExclusiveContextEnabled() */ // @Override public final boolean setExclusiveContext(final 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 != _exclusiveContextThread ? _exclusiveContextThread : animThread ) : null ; UncaughtAnimatorException displayCaught = null; if( propagateState ) { setDrawablesExclCtxState(enable); if( !enable ) { if( Thread.currentThread() == getThread() || Thread.currentThread() == _exclusiveContextThread ) { try { display(); // propagate exclusive context -> off! } catch (final UncaughtAnimatorException dre) { displayCaught = dre; } } else { final boolean resumed = isAnimating() ? false : resume(); int counter = 10; while( 0false
.
*
* @see #setExclusiveContext(boolean)
* @see #setExclusiveContext(Thread)
*/
// @Override
public final synchronized boolean isExclusiveContextEnabled() {
return exclusiveContext;
}
/**
* Returns the exclusive context thread if {@link #isExclusiveContextEnabled()} and {@link #isStarted()}, otherwise null
.
* * If exclusive context is enabled via {@link #setExclusiveContext(boolean)} * the {@link #getThread() animator thread} is returned if above conditions are met. *
** If exclusive context is enabled via {@link #setExclusiveContext(Thread)} * the user passed thread is returned if above conditions are met. *
* @see #setExclusiveContext(boolean) * @see #setExclusiveContext(Thread) */ // @Override public final synchronized Thread getExclusiveContextThread() { return ( isStarted() && exclusiveContext ) ? ( null != userExclusiveContextThread ? userExclusiveContextThread : animThread ) : null ; } /** * Should be called at {@link #start()} and {@link #stop()} * from within the animator thread. ** At {@link #stop()} an additional {@link #display()} call shall be issued * to allow propagation of releasing the exclusive thread. *
*/ protected final synchronized void setDrawablesExclCtxState(final 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; i0
, method will wait until TO is reached or being notified.
* if > 0
, method will wait for the given pollPeriod
in milliseconds.
* @return true
if {@link Condition#eval() waitCondition.eval()} returned false
* or if {@link AnimatorImpl#blockUntilDone(Thread) non-blocking}. Otherwise returns false
.
*/
protected final synchronized boolean finishLifecycleAction(final 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;
long remaining;
boolean nok;
if( impl.blockUntilDone(animThread) ) {
blocking = true;
remaining = TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION;
if( 0 >= pollPeriod ) {
pollPeriod = remaining;
}
nok = waitCondition.eval();
while ( nok && remaining>0 ) {
final long t1 = System.currentTimeMillis();
if( pollPeriod > remaining ) { pollPeriod = remaining; }
notifyAll();
try {
wait(pollPeriod);
} catch (final InterruptedException ie) {
throw new InterruptedRuntimeException(ie);
}
remaining -= System.currentTimeMillis() - t1 ;
nok = waitCondition.eval();
}
} else {
/**
* Even though we are not able to block until operation is completed at this point,
* best effort shall be made to preserve functionality.
* Here: Issue notifyAll() if waitCondition still holds and test again.
*
* Non blocking reason could be utilizing AWT Animator while operation is performed on AWT-EDT.
*/
blocking = false;
remaining = 0;
nok = waitCondition.eval();
if( nok ) {
notifyAll();
nok = waitCondition.eval();
}
}
final boolean res = !nok || !blocking;
if(DEBUG || blocking && nok) { // Info only if DEBUG or ( blocking && not-ok ) ; !blocking possible if AWT
if( blocking && remaining<=0 && nok ) {
System.err.println("finishLifecycleAction(" + waitCondition.getClass().getName() + "): ++++++ timeout reached ++++++ " + getThreadName());
}
System.err.println("finishLifecycleAction(" + waitCondition.getClass().getName() + "): OK "+(!nok)+
"- pollPeriod "+pollPeriod+", blocking "+blocking+" -> res "+res+
", waited " + (blocking ? ( TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION - remaining ) : 0 ) + "/" + TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION +
" - " + getThreadName());
System.err.println(" - "+toString());
if(nok) {
ExceptionUtils.dumpStack(System.err);
}
}
return res;
}
@Override
public synchronized boolean isStarted() {
return animThread != null ;
}
protected static String getThreadName() { return Thread.currentThread().getName(); }
@Override
public String toString() {
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()+")]";
}
}