/* * 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; /**

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 { /** timeout in milliseconds, 15 frames @ 60Hz = 240ms, limiting {@link #finishLifecycleAction(Condition)} */ private static final long TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION = 15*16; protected ThreadGroup threadGroup; private Runnable runnable; private boolean runAsFastAsPossible; protected boolean isAnimating; protected boolean pauseIssued; protected volatile boolean stopIssued; public Animator() { super(); if(DEBUG) { System.err.println("Animator created"); } } public Animator(ThreadGroup tg) { super(); threadGroup = 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); } /** Creates a new Animator for a particular drawable. */ public Animator(ThreadGroup tg, GLAutoDrawable drawable) { this(tg); add(drawable); } 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 { public String toString() { return "[started "+isStartedImpl()+", animating "+isAnimatingImpl()+", paused "+isPausedImpl()+", drawable "+drawables.size()+"]"; } 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(); } while (!stopIssued) { synchronized (Animator.this) { // Don't consume CPU unless there is work to be done and not paused while (!stopIssued && (pauseIssued || drawablesEmpty)) { boolean wasPaused = pauseIssued; if (DEBUG) { System.err.println("Animator pause:" + Thread.currentThread() + ": " + toString()); } 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:" + Thread.currentThread() + ": " + toString()); } } } if (!stopIssued && !isAnimating) { // resume from pause or drawablesEmpty, // implies !pauseIssued and !drawablesEmpty setIsAnimatingSynced(true); Animator.this.notifyAll(); } } // sync Animator.this if (!stopIssued) { display(); } if (!stopIssued && !runAsFastAsPossible) { // Avoid swamping the CPU Thread.yield(); } } } finally { synchronized (Animator.this) { if(DEBUG) { System.err.println("Animator stop " + Thread.currentThread() + ": " + toString()); } stopIssued = false; pauseIssued = false; animThread = null; setIsAnimatingSynced(false); // barrier Animator.this.notifyAll(); } } } } 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 ; } public final boolean isAnimating() { stateSync.lock(); try { return animThread != null && isAnimating ; } finally { stateSync.unlock(); } } private final boolean isPausedImpl() { return animThread != null && pauseIssued ; } public final boolean isPaused() { stateSync.lock(); try { return animThread != null && pauseIssued ; } finally { stateSync.unlock(); } } 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()); } } public synchronized boolean start() { if ( isStartedImpl() ) { return false; } if (runnable == null) { runnable = new MainLoop(); } fpsCounter.resetFPSCounter(); String threadName = Thread.currentThread().getName()+"-"+baseName; Thread thread; if(null==threadGroup) { thread = new Thread(runnable, threadName); } else { thread = new Thread(threadGroup, runnable, threadName); } thread.start(); finishLifecycleAction(waitForStartedCondition); return true; } private class WaitForStartedCondition implements Condition { public boolean result() { return !isStartedImpl() || (!drawablesEmpty && !isAnimating) ; } } Condition waitForStartedCondition = new WaitForStartedCondition(); public synchronized boolean stop() { if ( !isStartedImpl() ) { return false; } stopIssued = true; notifyAll(); finishLifecycleAction(waitForStoppedCondition); return true; } private class WaitForStoppedCondition implements Condition { public boolean result() { 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; } private class WaitForPausedCondition implements Condition { public boolean result() { // 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; } private class WaitForResumeCondition implements Condition { public boolean result() { // end waiting if stopped as well return !drawablesEmpty && !isAnimating && isStartedImpl(); } } Condition waitForResumeCondition = new WaitForResumeCondition(); }