/*
* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
* Copyright (c) 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:
*
* - Redistribution of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistribution 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.
*
* Neither the name of Sun Microsystems, Inc. or the names of
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
* INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
* MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
* ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
* DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
* ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
* DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
* DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
* ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
* SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed or intended for use
* in the design, construction, operation or maintenance of any nuclear
* facility.
*
* Sun gratefully acknowledges that this software was originally authored
* and developed by Kenneth Bradley Russell and Christopher John Kline.
*/
package com.jogamp.opengl.util;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLException;
/**
An Animator can be attached to one or more {@link
GLAutoDrawable}s to drive their display() methods in a loop.
The Animator class creates a background thread in which the
calls to display()
are performed. After each drawable
has been redrawn, a brief pause is performed to avoid swamping the
CPU, unless {@link #setRunAsFastAsPossible} has been called.
*
* The Animator execution thread does not run as a daemon thread,
* so it is able to keep an application from terminating.
* Call {@link #stop() } to terminate the animation and it's execution thread.
*
*/
public class Animator extends AnimatorBase {
protected ThreadGroup threadGroup;
private Runnable runnable;
private boolean runAsFastAsPossible;
protected boolean isAnimating;
protected boolean pauseIssued;
protected volatile boolean stopIssued;
/**
* Creates a new, empty Animator.
*/
public Animator() {
super();
if(DEBUG) {
System.err.println("Animator created");
}
}
/**
* Creates a new Animator w/ an associated ThreadGroup.
*/
public Animator(ThreadGroup tg) {
super();
setThreadGroup(tg);
if(DEBUG) {
System.err.println("Animator created, ThreadGroup: "+threadGroup);
}
}
/**
* 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 w/ an associated ThreadGroup for a particular drawable.
*/
public Animator(ThreadGroup tg, GLAutoDrawable drawable) {
super();
setThreadGroup(tg);
add(drawable);
if(DEBUG) {
System.err.println("Animator created, ThreadGroup: "+threadGroup+" and "+drawable);
}
}
@Override
protected String getBaseName(String prefix) {
return prefix + "Animator" ;
}
/**
* Sets a flag in this Animator indicating that it is to run as
* fast as possible. By default there is a brief pause in the
* animation loop which prevents the CPU from getting swamped.
* This method may not have an effect on subclasses.
*/
public final void setRunAsFastAsPossible(boolean runFast) {
stateSync.lock();
try {
runAsFastAsPossible = runFast;
} finally {
stateSync.unlock();
}
}
private final void setIsAnimatingSynced(boolean v) {
stateSync.lock();
try {
isAnimating = v;
} finally {
stateSync.unlock();
}
}
class MainLoop implements Runnable {
@Override
public String toString() {
return "[started "+isStartedImpl()+", animating "+isAnimatingImpl()+", paused "+isPausedImpl()+", drawable "+drawables.size()+", drawablesEmpty "+drawablesEmpty+"]";
}
@Override
public void run() {
try {
if(DEBUG) {
System.err.println("Animator start on " + getThreadName() + ": " + toString());
}
fpsCounter.resetFPSCounter();
animThread = Thread.currentThread();
setIsAnimatingSynced(false); // barrier
// 'waitForStartedCondition' wake-up is handled below!
while (!stopIssued) {
synchronized (Animator.this) {
// Pause; Also don't consume CPU unless there is work to be done and not paused
boolean ectCleared = false;
while (!stopIssued && (pauseIssued || drawablesEmpty)) {
if( drawablesEmpty ) {
pauseIssued = true;
}
boolean wasPaused = pauseIssued;
if (DEBUG) {
System.err.println("Animator pause on " + animThread.getName() + ": " + 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();
if (DEBUG) {
System.err.println("Animator resume on " + animThread.getName() + ": " + toString());
}
}
}
if (!stopIssued && !isAnimating) {
// Wakes up 'waitForStartedCondition' sync
// - and -
// Resume from pause or drawablesEmpty,
// implies !pauseIssued and !drawablesEmpty
setIsAnimatingSynced(true); // barrier
setDrawablesExclCtxState(exclusiveContext);
Animator.this.notifyAll();
}
} // sync Animator.this
if (!stopIssued) {
display();
}
if (!stopIssued && !runAsFastAsPossible) {
// Avoid swamping the CPU
Thread.yield();
}
}
} catch( ThreadDeath td) {
if(DEBUG) {
System.err.println("Animator Catched: "+td.getClass().getName()+": "+td.getMessage());
td.printStackTrace();
}
} finally {
if( exclusiveContext && !drawablesEmpty ) {
setDrawablesExclCtxState(false);
display(); // propagate exclusive change!
}
synchronized (Animator.this) {
if(DEBUG) {
System.err.println("Animator stop on " + animThread.getName() + ": " + toString());
}
stopIssued = false;
pauseIssued = false;
animThread = null;
setIsAnimatingSynced(false); // barrier
Animator.this.notifyAll();
}
}
}
}
private final boolean isAnimatingImpl() {
return animThread != null && isAnimating ;
}
@Override
public final boolean isAnimating() {
stateSync.lock();
try {
return animThread != null && isAnimating ;
} finally {
stateSync.unlock();
}
}
private final boolean isPausedImpl() {
return animThread != null && pauseIssued ;
}
@Override
public final boolean isPaused() {
stateSync.lock();
try {
return animThread != null && pauseIssued ;
} finally {
stateSync.unlock();
}
}
/**
* 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;
}
@Override
public synchronized boolean start() {
if ( isStartedImpl() ) {
return false;
}
if (runnable == null) {
runnable = new MainLoop();
}
fpsCounter.resetFPSCounter();
String threadName = getThreadName()+"-"+baseName;
Thread thread;
if(null==threadGroup) {
thread = new Thread(runnable, threadName);
} else {
thread = new Thread(threadGroup, runnable, threadName);
}
thread.setDaemon(false); // force to be non daemon, regardless of parent thread
if(DEBUG) {
final Thread ct = Thread.currentThread();
System.err.println("Animator "+ct.getName()+"[daemon "+ct.isDaemon()+"]: starting "+thread.getName()+"[daemon "+thread.isDaemon()+"]");
}
thread.start();
return finishLifecycleAction(waitForStartedCondition, 0);
}
private final Condition waitForStartedCondition = new Condition() {
@Override
public boolean eval() {
return !isStartedImpl() || (!drawablesEmpty && !isAnimating) ;
} };
@Override
public synchronized boolean stop() {
if ( !isStartedImpl() ) {
return false;
}
stopIssued = true;
return finishLifecycleAction(waitForStoppedCondition, 0);
}
private final Condition waitForStoppedCondition = new Condition() {
@Override
public boolean eval() {
return isStartedImpl();
} };
@Override
public synchronized boolean pause() {
if ( !isStartedImpl() || pauseIssued ) {
return false;
}
pauseIssued = true;
return finishLifecycleAction(waitForPausedCondition, 0);
}
private final Condition waitForPausedCondition = new Condition() {
@Override
public boolean eval() {
// end waiting if stopped as well
return isStartedImpl() && isAnimating;
} };
@Override
public synchronized boolean resume() {
if ( !isStartedImpl() || !pauseIssued ) {
return false;
}
pauseIssued = false;
return finishLifecycleAction(waitForResumeCondition, 0);
}
private final Condition waitForResumeCondition = new Condition() {
@Override
public boolean eval() {
// end waiting if stopped as well
return isStartedImpl() && ( !drawablesEmpty && !isAnimating || drawablesEmpty && !pauseIssued ) ;
} };
}