com.hdcookbook.grin.animator
Class AnimationEngine

java.lang.Object
  extended by com.hdcookbook.grin.animator.AnimationEngine
All Implemented Interfaces:
java.lang.Runnable
Direct Known Subclasses:
ClockBasedEngine, SFAAEngine

public abstract class AnimationEngine
extends java.lang.Object
implements java.lang.Runnable

Abstract base class for an animation engine.

An animation engine runs the main animation loop, and manages display of a set of animation clients to a component.


Field Summary
protected  int modelTimeSkipped
           
protected  com.hdcookbook.grin.animator.RenderContextBase renderContext
          The list of erase and drawing areas that were updated in the current frame of animation, and records of what happened in the last.
protected  java.awt.Rectangle repaintBounds
          The area that has been damaged and need to be repainted in the next cycle.
protected  boolean targetsCanOverlap
           
static java.awt.Color transparent
          A useful constant that's used extensively by engines
 
Constructor Summary
protected AnimationEngine()
          Initialize a new AnimationEngine.
 
Method Summary
protected  void advanceModel()
           
protected abstract  void callPaintTargets()
          Paint the current frame into the right graphics buffer.
 void checkDestroy()
          Throw an InterruptedException if we should bail out.
protected  void checkNewClients()
          Check to see if we have new animation clients.
protected abstract  void clearArea(int x, int y, int width, int height)
          Called from showFrame() to cause an area to be cleared in the current frame.
 void destroy()
          Destroy this animation manager, by terminating the animation thread.
 boolean destroyRequested()
          Tells us if there has been a request to destroy this animation manager.
protected abstract  void finishedFrame()
          Called when the engine is finished drawing the current frame.
 AnimationClient[] getAnimationClients()
          Return a copy of the list of animation clients that were last set for this animation engine.
abstract  java.awt.Component getComponent()
          Get the component that this animation engine renders into.
protected  java.awt.Rectangle[] getDrawTargets()
           
protected  java.awt.Rectangle[] getEraseTargets()
           
abstract  int getHeight()
          Get the height of the area we display over
 int getModelTimeSkipped()
          Return the number of ms skipped before the next model update.
protected  int getNumDrawTargets()
           
protected  int getNumEraseTargets()
           
abstract  int getWidth()
          Get the width of the area we display over
 void initClients(AnimationClient[] clients)
          Initialize this engine with the animation clients it will be managing.
abstract  void initContainer(java.awt.Container container, java.awt.Rectangle bounds)
          Initialize this engine with its parent container and the position and size of the engine within the container.
 void initialize(AnimationContext context)
          Initialize this animation manager.
protected abstract  boolean needsFullRedrawInAnimationLoop()
          Tell us whether or not this style of animation requires a full redraw of everything on the screen in each pass through the animation loop.
protected  void paintFrame(java.awt.Graphics2D g)
          Paint the current frame into the given graphics object.
 void paintNextFrameFully()
          Sets this engine so that the next frame will be painted in its entirety, with no redraw optimization.
protected  void paintTargets(java.awt.Graphics2D g)
          Paint the current set of target areas in our RenderContext for the current frame into the given graphics object.
abstract  void pause()
          Pause the currently running animations.
 void repaintFrame(java.awt.Graphics2D g)
          Repaint the current frame.
 void resetAnimationClients(AnimationClient[] clients)
          Reset the set of clients being rendered by a running animation engine.
 void run()
          This is the main loop for animation.
protected abstract  void runAnimationLoop()
          The inner loop for the animation thread.
 void setAllowOverlappingTargets(boolean v)
          Sets whether or not overlapping draw targets are allowed.
 void setDrawTargetCollapseThreshold(int t)
          Sets the threshold of the number of pixels of increased drawing the engine is willing to tolerate in order to collapse two draw targets into one.
 void setThreadPriority(int priority)
          Set the priority of the thread that runs the animation loop.
protected  void showFrame()
          Tell the model we're caught up, and show the current frame.
abstract  void start()
          Start the current animations.
protected abstract  void terminatingEraseScreen()
          Erase the screen because the AnimationManager is terminating.
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Field Detail

transparent

public static final java.awt.Color transparent
A useful constant that's used extensively by engines


renderContext

protected com.hdcookbook.grin.animator.RenderContextBase renderContext
The list of erase and drawing areas that were updated in the current frame of animation, and records of what happened in the last.


repaintBounds

protected java.awt.Rectangle repaintBounds
The area that has been damaged and need to be repainted in the next cycle. This can be used by the AnimationEngine subclass that has knowledge about the damaged area, and would rather not repaint the entire Frame via needsFullPaint variable.


modelTimeSkipped

protected int modelTimeSkipped

targetsCanOverlap

protected boolean targetsCanOverlap
Constructor Detail

AnimationEngine

protected AnimationEngine()
Initialize a new AnimationEngine.

Method Detail

initClients

public void initClients(AnimationClient[] clients)
Initialize this engine with the animation clients it will be managing. This should be called exactly once, before AnimationContext.animationInitialize() completes. It's fine to call it before starting the animation thread, too.

Note that the algorithm used to collapse render area targets is cubic with the number of targets, so this number should be kept low. Between one and three would be reasonable, and five is perhaps a workable maximum.

Parameters:
clients - The animation clients we'll support
See Also:
AnimationContext.animationInitialize()

setDrawTargetCollapseThreshold

public void setDrawTargetCollapseThreshold(int t)
Sets the threshold of the number of pixels of increased drawing the engine is willing to tolerate in order to collapse two draw targets into one. It's more efficient to draw one slightly bigger area than two smaller areas, but at some threshold it's better to leave them divided. By default, this threshold is set to about 150,000 pixels (e.g. a 385x385 area), but this is a guess.

This value may be set to 0, or even a negative number. This might be valuable to disable the collapse optimization, for clients that want a predictable and consistent render time.


setAllowOverlappingTargets

public void setAllowOverlappingTargets(boolean v)
Sets whether or not overlapping draw targets are allowed. By default they are not, that is, any draw targets that overlap will be combined into one. When overlapping targets are allowed, then erasing is not done as a separate step. Rather, it is done for each draw target just before painting. For this reason, gurantee_fill has no effect when this mode is set true.

This method must only be called during the model update.

The default value of this parameter is false.


resetAnimationClients

public void resetAnimationClients(AnimationClient[] clients)
Reset the set of clients being rendered by a running animation engine. This method simply notes the request to reset the list of clients and returns. After the current frame of animation is finished, the engine will asynchronously initialize any new clients (clients on the new list but not the old), then destroy any old clients (clients on the old list but not the new). It will re-calculate the render targets for all clients at this time, and it will cause a full screen repaint for the first frame of animation.

The framework takes ownership of the array that's passed in, so the caller should not modify that array after calling this method.


getAnimationClients

public AnimationClient[] getAnimationClients()
Return a copy of the list of animation clients that were last set for this animation engine. These clients might not be active yet, if the engine is finishing up a frame of animation right after the list of clients was reset.

Note that the old animation clients are destroyed, and not put into a quiescent state. In the case of a GRIN show, this means that if you want to later add the show back, you'll need to re-read it from the .grin file.


setThreadPriority

public void setThreadPriority(int priority)
Set the priority of the thread that runs the animation loop. The default values is 4, that is, Thread.NORM_PRIORITY-1. The animation loop should run at less than NORM_PRIORITY, to make sure that higher-priority activities, like remote control keypresses, take precidence.


initContainer

public abstract void initContainer(java.awt.Container container,
                                   java.awt.Rectangle bounds)
Initialize this engine with its parent container and the position and size of the engine within the container. This should be called exactly once, before start() is called. A good time to call this would be in the animationInitialize() call within the context code passed to initialize().

The Component will be set to the specified bounds, and an internal double buffer may be created to these bounds. The container is expected to have a null layout; if it doesn't, this might change the widget's size to be different than the bounds passed in (and therefore the double buffer). The container must be visible when this is called.

See Also:
initialize(com.hdcookbook.grin.animator.AnimationContext), AnimationContext.animationInitialize()

getWidth

public abstract int getWidth()
Get the width of the area we display over

Returns:
the width

getHeight

public abstract int getHeight()
Get the height of the area we display over

Returns:
the height

getNumEraseTargets

protected int getNumEraseTargets()
Returns:
The number of erase areas active for this frame

getEraseTargets

protected java.awt.Rectangle[] getEraseTargets()
Returns:
the total pool of erase areas. Iterate this from 0..n-1, n=getNumEraseTargets(). Some might be empty, which is indicated by a width <= 0, which makes Rectangle.isEmpty() return true.

getNumDrawTargets

protected int getNumDrawTargets()
Returns:
The number of draw areas active for this frame

getDrawTargets

protected java.awt.Rectangle[] getDrawTargets()
Returns:
the total pool of draw areas. Iterate this from 0..n-1, n=getNumDrawTargets()

destroyRequested

public boolean destroyRequested()
Tells us if there has been a request to destroy this animation manager. Any activity in the animation thread that takes more than about a tenth of a second should poll this method, and bail out if needed.


checkDestroy

public void checkDestroy()
                  throws java.lang.InterruptedException
Throw an InterruptedException if we should bail out. This will happen if destroyRequested(), or if Thread.interrupted(). This is a convenience method. It's good to call this regularly during time-consuming operations in the animatino thread.

Throws:
java.lang.InterruptedException

getComponent

public abstract java.awt.Component getComponent()
Get the component that this animation engine renders into. This is added by the engine to the container.

Returns:
the component, or null if initContainer hasn't been called yet.
See Also:
initialize(com.hdcookbook.grin.animator.AnimationContext), AnimationContext.animationInitialize()

paintNextFrameFully

public void paintNextFrameFully()
Sets this engine so that the next frame will be painted in its entirety, with no redraw optimization. This can be needed if the contents of the framebuffer was damaged somehow.


initialize

public void initialize(AnimationContext context)
Initialize this animation manager. The animation loop worker thread will be started, and it will proceed in the following order:
    context.animationInitialize();  
             // a good time to set up the HScene and Player
    for each client c
        c.initialize()
    context.animationFinishInitialization();  // perhaps set UI state
    for (frame = 0 to infinity) {
        wait until it's time for next frame
        for each client
            call nextFrame
         if animation isn't behind
             for each client
                 call setCaughtUp
                 call addDisplayAreas
             for each client
                 call paintFrame as many times as needed
                   for each client
                        call paintDone
    }
 

This method returns immediately after starting the thread, so that an Xlet can quickly return from the xlet lifecycle method Xlet.initXlet(). Any code that depends on animation clients being initialized should be put in the body of the AnimationContext.animationFinishInitialization() method. For example, if you're using a GRIN show, animationFinishInitialization() is where you should put the call to Show.activateSegment() on the show's initial segument.

Parameters:
context - The context that the animation manager is running in. This can be used for initialization that the client wants to do in the animation thread. The context might use this to create an HScene, load a GRIN show, synchronously start video playing, or do other time-consuming operations.
See Also:
AnimationContext.animationFinishInitialization(), Show.activateSegment(com.hdcookbook.grin.Segment)

start

public abstract void start()
Start the current animations. The animation will start animating sometime after this method is called. This can be called while initialization is still underway in the animation thread.

By default, the manager will assume that the contents of the framebuffer wasn't changed while the manager was paused. If it might have been, it's advisable to call paintNextFrameFully().

The animation manager starts in the paused state, so this method will normally need to be called at least once.

See Also:
paintNextFrameFully()

pause

public abstract void pause()
Pause the currently running animations. The animation thread will go into a wait state, until start() or destroy() are called.

This method will return immediately, even if the animation hasn't reached the paused state yet.

When the animation framework is paused, no effort is made to output one last frame with the current state of the model.


destroy

public void destroy()
Destroy this animation manager, by terminating the animation thread. It's OK to call this more than once. It should be called while the animation manager's component is still visible, and a child of a visible HScene.

This method will not return until the animation thread has terminated.


clearArea

protected abstract void clearArea(int x,
                                  int y,
                                  int width,
                                  int height)
Called from showFrame() to cause an area to be cleared in the current frame.

See Also:
showFrame()

needsFullRedrawInAnimationLoop

protected abstract boolean needsFullRedrawInAnimationLoop()
Tell us whether or not this style of animation requires a full redraw of everything on the screen in each pass through the animation loop. This is called from the animation loop.


callPaintTargets

protected abstract void callPaintTargets()
                                  throws java.lang.InterruptedException
Paint the current frame into the right graphics buffer. The subclass implementation of this method should call paintTargets(Graphics2D).

Throws:
java.lang.InterruptedException
See Also:
paintFrame(java.awt.Graphics2D)

finishedFrame

protected abstract void finishedFrame()
Called when the engine is finished drawing the current frame. This should flush the results to the screen, if needed. The framework guarantees that each call to callPaintTargets() will be followed by a call to finishedFrame(), even if the thread is interrupted or there's a RuntimeException that terminates the thread.


terminatingEraseScreen

protected abstract void terminatingEraseScreen()
Erase the screen because the AnimationManager is terminating. This is called in the animation thread as a result of a call to destroy(), just before the animation thread terminates.


run

public void run()
This is the main loop for animation. It's for internal use only; users of this framework should not call this method directly.

Specified by:
run in interface java.lang.Runnable

runAnimationLoop

protected abstract void runAnimationLoop()
                                  throws java.lang.InterruptedException
The inner loop for the animation thread. This implementation uses System.currentTimeMillis() and wait() to keep a steady frame rate. This method can be overridden for animation styles that have a different synch mechanism, like SFAA. If this is done, be sure to look inside this method, and obey the same semantic contract.

This method must call checkNewClients() outside of any synchronized block at least once per frame. This should be done at the beginning of the frame before any model updates or animation work is done.

This method must check destroyRequested() at least once per frame, and if it's true, bail out of the loop. To advance the model by one frame, it should call advanceModel(). When it wants to show a frame (and not skip it), it should call showFrame(), which will cause callPaintTargets() to be called.

Of course, the animation loop should also check Thread.interrupted() regularly.

Throws:
java.lang.InterruptedException
See Also:
destroyRequested(), advanceModel(), showFrame(), callPaintTargets()

checkNewClients

protected final void checkNewClients()
                              throws java.lang.InterruptedException
Check to see if we have new animation clients. This method must be called from runAnimationLoop once per frame, at the beginning. It should be called outside of any synchronized block, because it potentially makes calls out to client code to initialize and destroy animation clients.

Throws:
java.lang.InterruptedException

getModelTimeSkipped

public final int getModelTimeSkipped()
Return the number of ms skipped before the next model update. Normally, this will return 0, but if animation falls behind and model updates are found to be taking a significant amount of the time available per frame, then the framework will skip a few ms of time, so that the framework can be considered caught up. This method indicates how much time was skipped between the last frame and the current frame.

Typically, model updates should be very much faster than the time between frames, so this method should almost always return 0, unless the animation clients are doing some atypically complex calculations.

This can be queried from within AnimationClient.nextFrame() to see how much time to add to the normal frame time, if code wishes to keep time-based animations synchronized in time, even in the face of CPU-intensive model updates.

Returns:
The time skipped, in ms. Typically 0.
See Also:
AnimationClient.nextFrame()

advanceModel

protected final void advanceModel()
                           throws java.lang.InterruptedException
Throws:
java.lang.InterruptedException

showFrame

protected final void showFrame()
                        throws java.lang.InterruptedException
Tell the model we're caught up, and show the current frame. This calls callPaintTargets.

Throws:
java.lang.InterruptedException
See Also:
callPaintTargets()

paintTargets

protected final void paintTargets(java.awt.Graphics2D g)
                           throws java.lang.InterruptedException
Paint the current set of target areas in our RenderContext for the current frame into the given graphics object. This should be called by the subclass within callPaintTargets().

The Graphics2D instance must have a clip area that is set to the extent of the area being managed by this animation manager. The instance will have an unmodified clip area after this method is called, but other attributes, like the drawing mode and the foreground color, might be changed. However, the Graphics2D instance will be suitable for re-use in a subsequent paintFrame operation.

Parameters:
g - A graphics context with a clip area covering the full extent of the screen area under the control of this animation manager.
Throws:
java.lang.InterruptedException
See Also:
callPaintTargets()

paintFrame

protected final void paintFrame(java.awt.Graphics2D g)
                         throws java.lang.InterruptedException
Paint the current frame into the given graphics object. This variant is for use when the full clip area of g should be painted to, rather than the set of render area gargets.

Parameters:
g - A graphics context with a clip area covering the area that needs to be painted to
Throws:
java.lang.InterruptedException
See Also:
paintTargets(java.awt.Graphics2D)

repaintFrame

public void repaintFrame(java.awt.Graphics2D g)
                  throws java.lang.InterruptedException
Repaint the current frame. This can be called if the current frame needs to be painted to a graphics context outside of the animation loop. This should be extremely rare in practice, but in theory it can at least happen with repaint animation that uses platform-supplied double buffering, and it might be possible with SFAA when the UI is repainted.

This can also be used for things like capturing a screenshot to a buffered image.

Throws:
java.lang.InterruptedException