/**
* 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 com.jogamp.common.util.locks.LockFactory;
import com.jogamp.common.util.locks.RecursiveLock;
import jogamp.opengl.Debug;
import jogamp.opengl.FPSCounterImpl;
import java.io.PrintStream;
import java.util.ArrayList;
import javax.media.opengl.GLAnimatorControl;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLException;
import javax.media.opengl.GLProfile;
/**
* 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; public interface AnimatorImpl { void display(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()}
* @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: 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);
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() {
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 synchronized void remove(final GLAutoDrawable drawable) {
if(DEBUG) {
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();
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() {
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 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. ** 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(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( 0false
.
*
* @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 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 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. ** At {@link #stop()} an additional {@link #display()} call shall be issued * to allow propagation of releasing the exclusive thread. *
*/ 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; 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
, otherwise false
.
*/
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 || blocking && nok) { // Info only if DEBUG or ( blocking && not-ok ) ; !blocking possible if AWT
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()+
"], modeBits "+modeBits+", init'ed "+(null!=impl)+", animThread "+getThread()+", exclCtxThread "+exclusiveContext+"("+getExclusiveContextThread()+")]";
}
}