import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.*;
import javax.swing.*;
/**
* Demonstrates a method to do fast and smooth drawing for animation.
*
* The component drawing is done on the Event Dispatch Thread by wrapping the
* call to JComponent#paintImmediately in EventQueue#invokeAndWait. This
* technique is very smooth but not nearly as fast as using a VolatileImage
* or a BufferStrategy to do the drawing with.
* <p>
* Try commenting out the EventQueue#invokeAndWait and replacing it with the
* Component#repaint method. Component#repaint schedules repainting on the
* Event Dispatch Thread but will also allow write combining. At some point
* as the display rate is increased the drawing will start to jerk or shake as
* the Component#repaint method discards some of the earlier drawing calls for
* the most recent one.
* <p>
* There are two timing gate methods, smoothGate and lowResourceGate.
* Both are used to control the drawing rate by adding a delay to the
* animation thread. Try running this example with and without a timing gate.
* <p>
* The smoothGate method loops as fast as the processor will allow until the
* time delay for the desired frame rate has elapsed. On a modern computer
* with many processors and cores there is still adequate processor power
* available to run other programs even if one of the cores is saturated.
* <p>
* The lowResourceGate method loops around a Thread#sleep call and as a
* result uses considerably less processor power than the smoothGate method.
* The trade off is that the lowResourceGate method is not a consistent as
* the smoothGate method and requires some tweaking to get the frame rates
* accurate. This is caused by the unpredictability of Thread#sleep.
*
* @author Knute Johnson
*
* @see DrawVolatile
* @see DrawStrategy
*/
public class DrawFast extends JComponent implements Runnable {
/** Display increment angle in radians */
private static final double ANGLE_INCREMENT = 0.0005;
/** Animation thread */
private final Thread thread;
/**
* Animation thread control flag.
*
* This variable is volatile because it is written to by one thread and
* read by another.
*/
private volatile boolean runFlag;
/** Timer for rate display */
private final java.util.Timer timer;
/** TimerTask for rate display */
private final java.util.TimerTask timerTask;
/**
* Current drawing angle in radians
*
* This variable is thread constrained to the animation thread.
*/
private double radians;
/**
* Frame count for rate display.
*
* This variable is volatile because it is written to by one thread and
* read by another.
*/
private volatile long frameCount;
/**
* Frame rate for rate display.
*
* This variable is volatile because it is written to by one thread and
* read by another.
*/
private volatile double frameRate;
/**
* Constructs the component, sets its preferred size, creates the animation
* thread, creates a Timer and TimerTask to update the frame rate display.
*/
public DrawFast() {
// give the component a starting size
setPreferredSize(new Dimension(400,300));
// create the animation thread
thread = new Thread(this,"Animation Thread");
// create the animation timer as daemon so we don't have to explicitly
// stop it when the program ends
timer = new java.util.Timer("Rate Display Timer",true);
// updates the variable frameRate for the rate display
timerTask = new java.util.TimerTask() {
// time of last frame rate update
long then = 0;
// frame count of last frame rate update
long lastCount = 0;
@Override public void run() {
long now = System.nanoTime();
long frames = frameCount - lastCount;
double duration = (now - then) / 1000000000.0;
then = now;
lastCount = frameCount;
frameRate = frames / duration;
}
};
}
/**
* Starts the animation thread and the rate display timer
*/
public void start() {
runFlag = true;
thread.start();
timer.scheduleAtFixedRate(timerTask,0,1000);
}
/**
* The animation thread adjusts animation values, and calls
* JComponent#paintImmediately to draw the updated image.
*/
public void run() {
// the last time the gate was opened
long then = 0;
while (runFlag) {
// adjust display values
radians += ANGLE_INCREMENT;
++frameCount;
// delay get to control frame rate
// then = smoothGate(then,200);
// then = lowResourceGate(then,200);
// move the contained code to the Event Dispatch Thread and wait
// for it to complete
try {
EventQueue.invokeAndWait(new Runnable() {
public void run() {
// draw the component now
paintImmediately(getBounds());
}
});
} catch (InterruptedException ie) {
ie.printStackTrace();
} catch (InvocationTargetException ite) {
ite.printStackTrace();
}
// try commenting out the EventQueue#invokeAndWait code above and
// replacing it with Component#repaint. See the class notes above
// for a discussion of this topic.
// repaint();
}
}
/**
* Stops the animation thread
*/
public void stop() {
runFlag = false;
}
/**
* All drawing is done here.
*
* @param g2D the Graphics object
*/
@Override public void paintComponent(Graphics g2D) {
int w = getWidth();
int h = getHeight();
// the passed in Graphics is actually a Graphics2D
Graphics2D g = (Graphics2D)g2D;
// turn on anti-aliasiang to get smooth drawing edges and text
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// fill the background with white
g.setColor(Color.WHITE);
g.fillRect(0,0,w,h);
// draw the frame rate in the upper left corner of component
g.setColor(Color.RED);
g.drawString(String.format("%.0f fps",frameRate),5,15);
// draw the rotating blue square
g.setColor(Color.BLUE);
g.rotate(radians,w/2,h/2);
g.fillRect(w/2 - 100,h/2 - 100,200,200);
}
/**
* Delay method to control the frame rate of the display.
*
* This method uses large amounts of processor resources but is very
* smooth and will allow very fast frame rates to be used.
*
* @param then the time the gate was last entered, from the return
* value of the previous call to this method
* @param fps the desired frame rate in frames per second
*
* @return the time the gate was exited, used as input to the next
* call to this method
*/
public long smoothGate(long then, long fps) {
long now;
while ((now = System.nanoTime()) - then < (1000000000L / fps))
;
return now;
}
/**
* Delay method to control the frame rate of the display.
*
* This method uses very little processor resources but is not very smooth
* or very accurate in its timing.
*
* @param then the time the gate was last entered, from the return
* value of the previous call to this method
* @param fps the desired frame rate in frames per second
*
* @return the time the gate was exited, used as input to the next
* call to this method
*/
public long lowResourceGate(long then, long fps) {
long now;
while ((now = System.nanoTime()) - then < (1000000000L / fps))
try {
Thread.sleep(0L,10000);
} catch (InterruptedException ie) { }
// tuned for 200 fps
return now - 600000;
}
/**
* Create the GUI and use a WindowListener to start and stop the animation
* thread.
*
* @param args command line arguments (not used)
*/
public static void main(String... args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
final DrawFast dF = new DrawFast();
final JFrame frame = new JFrame("DrawFast");
frame.addWindowListener(new WindowAdapter() {
// start the animation once the GUI is created
public void windowOpened(WindowEvent we) {
dF.start();
}
// stop the animation and dispose the GUI window
public void windowClosing(WindowEvent we) {
dF.stop();
frame.dispose();
}
});
frame.add(dF,BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
}
});
}
}