Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsWhite Papers
Discussion GroupsFirst AidDatabasesJavaBeansGUIJava 3DVirtual MachineCORBASecurityToolsGeneral
Java DirectoryOpen Source ProjectsSample Book ChaptersUser GroupsWeb Resources
Related Topics
Databases.NETMore Topics ...

Java Forum / GUI / March 2007

Tip: Looking for answers? Try searching our database.

Requesting tips, comments for an EDT thread-safe game architecture

Thread view: 
Oliver Wong - 21 Mar 2007 17:40 GMT
I recently saw a thread about the Swing EDT in the CLJP, and it made
me wonder whether my general game architecture was thread safe or not.
EDT, and threading in general, are one of my weaker points in Java. Is
this general design okay? Are there things which I've put into the EDT
which I shouldn't have? Are there things which are outside of the EDT that
should be in it?

<SSCCE>
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.geom.AffineTransform;
import java.lang.reflect.InvocationTargetException;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class Test {

private static final int DEFAULT_RENDERING_WIDTH = 640;
private static final int DEFAULT_RENDERING_HEIGHT = 480;
private static JFrame mainWindow;
/*
 * the mainPanel is where most of the drawing happens. I draw onto a
panel
 * instead of directly onto the JFrame to avoid messing around with
 * getInsets().
 */
private static JPanel mainPanel;
/*
 * Returns true if the game is in the process of shutting down and
quitting.
 * If it's in the middle of a game-loop iteration, it'll finish that
 * iteration (potentially drawing 1 more frame of animation, and then
quit.
 */
private static boolean timeToQuit = false;
/*
 * True if the player pressed the "action" key since the last iteration
 * through the game loop, false otherwise. In a real game, I'd probably
have
 * many of these booleans (e.g. an "up" key, a "down" key, etc., and move
it
 * into a seperate class for organization purposes.
 */
private static boolean pressedActionKey = false;

public static void main(String[] args) throws InterruptedException,
  InvocationTargetException {
 /*
  * This first invokeAndWait initializes all the GUI components, and
  * registers the appropriate listeners to hook into the main game
  * engine.
  */
 EventQueue.invokeAndWait(new Runnable() {
  @Override
  public void run() {
   mainWindow = new JFrame("My game");
   mainWindow.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
   mainWindow.addWindowListener(new WindowListener() {
    @Override
    public void windowActivated(WindowEvent e) {
     // Does nothing.
    }

    @Override
    public void windowClosed(WindowEvent e) {
     // Does nothing
    }

    @Override
    public void windowClosing(WindowEvent e) {
     timeToQuit = true;
     EventQueue.invokeLater(new Runnable() {
      @Override
      public void run() {
       synchronized (mainWindow) {
        mainWindow.setVisible(false);
        mainWindow.dispose();
       }
      }
     });
    }

    @Override
    public void windowDeactivated(WindowEvent e) {
     // Does nothing.
    }

    @Override
    public void windowDeiconified(WindowEvent e) {
     // Does nothing.
    }

    @Override
    public void windowIconified(WindowEvent e) {
     // Does nothing.
    }

    @Override
    public void windowOpened(WindowEvent e) {
     // Does nothing.
    }
   });
   mainWindow.addKeyListener(new KeyListener() {
    @Override
    public void keyPressed(KeyEvent e) {
     if (e.getKeyCode() == KeyEvent.VK_SPACE) {
      pressedActionKey = true;
     }
    }

    @Override
    public void keyReleased(KeyEvent e) {
     // does nothing.
    }

    @Override
    public void keyTyped(KeyEvent e) {
     // does nothing.
    }

   });
   mainPanel = new JPanel();
   mainPanel.setPreferredSize(new Dimension(
     DEFAULT_RENDERING_WIDTH, DEFAULT_RENDERING_HEIGHT));
   mainWindow.add(mainPanel);
   mainWindow.pack();
   mainWindow.setVisible(true);

  }
 });
 /*
  * This while loop is the main game loop. It basically iterates through
  * 3 stages forever: getting the player input, reacting to it, and
  * drawing the results on screen.
  */
 while (!timeToQuit) {
  getPlayerInput();
  processGameLogic();
  EventQueue.invokeAndWait(new Runnable() {
   @Override
   public void run() {
    synchronized (mainWindow) {
     Graphics2D g = (Graphics2D) mainPanel.getGraphics();
     g.setTransform(AffineTransform.getScaleInstance(
       (double) mainPanel.getWidth()
         / (double) DEFAULT_RENDERING_WIDTH,
       (double) mainPanel.getHeight()
         / (double) DEFAULT_RENDERING_HEIGHT));
     updateScreen(g);
    }
   }
  });
  Thread.sleep(1);
 }
}

public static void processGameLogic() {
 /*
  * Check if pacman touched a ghost, stuff like that. Doesn't touch any
  * Swing components.
  */
}

public static void getPlayerInput() {
 /*
  * Doesn't touch any Swing components.
  */
 if (pressedActionKey) {
  /* make mario jump */
  System.out.println("Mario jumps.");
  pressedActionKey = false;
 }
}

public static void updateScreen(Graphics2D g) {
 assert SwingUtilities.isEventDispatchThread() : "Don't call updateScreen
from outside the EDT.";
 g.setColor(Color.BLACK);
 g.fillRect(0, 0, 640, 480);
 /*
  * TODO: Draw all sorts of amazing eye-candy.
  */
}
}
</SSCCE>

   - Oliver
Knute Johnson - 22 Mar 2007 02:14 GMT
>     I recently saw a thread about the Swing EDT in the CLJP, and it made
> me wonder whether my general game architecture was thread safe or not.
[quoted text clipped - 16 lines]
>       });
>      }

Oliver:

Window events are already processed on the EDT you don't need to
specifically call them with EventQueue.invokeLater().

I'm not sure why you have the calls to set then main window visible and
dispose it synchronized.  They are already being called on the EDT and
if you don't make any Swing method calls except on the EDT they will
already be synchronized by default.

Having played a lot with one form of animation, I would use a completely
different tack.  Use a Window or JWindow and do active rendering.  This
avoids the EDT altogether for any drawing.  You still may have to
synchronize some parts of your code but the more you can avoid that the
better.  Synchronizing can have a rather significant performance hit.

Signature

Knute Johnson
email s/nospam/knute/

Daniel Pitts - 22 Mar 2007 03:29 GMT
On Mar 21, 6:14 pm, Knute Johnson <nos...@rabbitbrush.frazmtn.com>
wrote:
> >     I recently saw a thread about the Swing EDT in the CLJP, and it made
> > me wonder whether my general game architecture was thread safe or not.
[quoted text clipped - 12 lines]
> Knute Johnson
> email s/nospam/knute/

And incorrectly avoid Synchronization can have an extreme correctness
"hit".

Generally, synchronization isn't the "bad thing" that people have
stigmatized it to be.  Used incorrectly, it can lead to problems,
yes... But those generally come from a lack of understanding.  If you
tell people "Avoid, Avoid, Avoid", they'll possible never learn when/
where they MUST use it.

I suggest Java Concurrency in Practice <http://jcip.net/> for anyone
who wants to know the correct way to deal with multithreaded
applications.  I already understood SOME of it, but that book
clarified a lot of concepts, and solidified my understanding of multi-
threaded programming.

On Mar 21, 9:41 am, "Oliver Wong" <o...@castortech.com> wrote:
> <SSCCE>
[snip]
>   while (!timeToQuit) {
>    getPlayerInput();
[quoted text clipped - 16 lines]
>   }
>  }
[snip]

> </SSCCE>
>
>     - Oliver

Oliver:
I personally think its a "Bad Thing" to have a busy wait -- a while
loop with a Thread.sleep().  Swing is design around an Event model,
you can probably refactor your code to avoid running on the main
thread, and use a javax.swing.Timer instead.

That way you don't need to have a "getPlayerInput()",
"processGameLogic()", and the strange call to invokeAndWait().  It can
all be event driven.

BTW, you should avoid "synchronize" in the EDT all together.  If you
have a thread that synchronizes on an object, and then calls
"invokeAndWait" with a runnable that syncs on the same object, you
have a deadlock.  And this is only the first order example.

Hope this helps,
Daniel.

P.S. Again, I suggest Java Concurrency in Practice <http://jcip.net/>
Oliver Wong - 22 Mar 2007 15:37 GMT
> On Mar 21, 6:14 pm, Knute Johnson <nos...@rabbitbrush.frazmtn.com>
> wrote:
[quoted text clipped - 4 lines]
>> synchronize some parts of your code but the more you can avoid that the
>> better.  Synchronizing can have a rather significant performance hit.

   I thought I *was* using active rendering. I thought active rendering
was simply having your own rendering loop instead of waiting for AWT to
sent paint events (which is how my code is structured). As a quick check,
I skimmed through
http://java.sun.com/docs/books/tutorial/extra/fullscreen/rendering.html
and now I'm a bit confused. The page mentions setIgnoreRepaint, which I've
forgotten to do. Okay, fine. But the page also implies (but does not
explicitly state) that active rendering is only possible in full screen
mode.

   So:

(1) What's the difference between what I'm doing, and active rendering?
Are you thinking mainly of BufferStrategies? (I'll address that later on
in this post)
(2) I assume active rendering possible in windowed-mode. Is this
assumption correct?
(3) And finally, why do you recommend JWindow over JFrame (or do you)?

[...]

> I suggest Java Concurrency in Practice <http://jcip.net/> for anyone
> who wants to know the correct way to deal with multithreaded
> applications.  I already understood SOME of it, but that book
> clarified a lot of concepts, and solidified my understanding of multi-
> threaded programming.

   Thanks, I'll take a look at that book.

> On Mar 21, 9:41 am, "Oliver Wong" <o...@castortech.com> wrote:
>> <SSCCE>
[quoted text clipped - 30 lines]
> you can probably refactor your code to avoid running on the main
> thread, and use a javax.swing.Timer instead.

   Perhaps for a "normal" application, but I'm pretty sure the common
wisdom for games is to have your own rendering loop. Sun themselves
recommends a rendering loop in their active rendering tutorial:
http://java.sun.com/docs/books/tutorial/extra/fullscreen/rendering.html

<quote>
public void myRenderingLoop() {
 while (!done) {
   Graphics myGraphics = getPaintGraphics();
   // Draw as appropriate using myGraphics
   myGraphics.dispose();
 }
}
</quote>

> I'm not sure why you have the calls to set then main window visible and
> dispose it synchronized.  They are already being called on the EDT and
> if you don't make any Swing method calls except on the EDT they will
> already be synchronized by default.

[...]

> BTW, you should avoid "synchronize" in the EDT all together.  If you
> have a thread that synchronizes on an object, and then calls
> "invokeAndWait" with a runnable that syncs on the same object, you
> have a deadlock.  And this is only the first order example.

   I was getting some NPEs from mainPanel.getGraphics() returning null,
and I had figured it was due to the window getting disposed while the
rendering loop was still occurring or something along those lines. But
after having read a bit more about the event model (e.g. that it is
implemented as a queue and is single-threaded) I see now that my
synchronizations didn't really make sense.

   Anyway, here's my new code, taken into account your advices. The two
main changes are:

(1) Used BufferStrategy to do hardware accelerated page flipping/double
buffering. I normally do this in my games, but I thought it wasn't really
relevant to the threading questions, so I left it out for this example.
Now that I've put it in, I can't really use the JPanel (since you can't
get a strategy on the JPanel), so this also means there's a bit of extra
code to fiddle with insets on the Window.

(2) Replaced the synchronize() keywords with an if statement checking that
mainWindow is not null.

<SSCCE>
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferStrategy;
import java.lang.reflect.InvocationTargetException;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class Test {

private static final int DEFAULT_RENDERING_WIDTH = 640;
private static final int DEFAULT_RENDERING_HEIGHT = 480;
private static JFrame mainWindow;
/**
 * Returns true if the game is in the process of shutting down and
quitting.
 * If it's in the middle of a game-loop iteration, it'll finish that
 * iteration (potentially drawing 1 more frame of animation, and then
quit.
 */
private static boolean timeToQuit = false;
/**
 * True if the player pressed the "action" key since the last iteration
 * through the game loop, false otherwise. In a real game, I'd probably
have
 * many of these booleans (e.g. an "up" key, a "down" key, etc., and move
it
 * into a seperate class for organization purposes.
 */
private static boolean pressedActionKey = false;

public static void main(String[] args) throws InterruptedException,
  InvocationTargetException {
 /*
  * This first invokeAndWait initializes all the GUI components, and
  * registers the appropriate listeners to hook into the main game
  * engine.
  */
 EventQueue.invokeAndWait(new Runnable() {
  @Override
  public void run() {
   mainWindow = new JFrame("My game");
   mainWindow.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
   mainWindow.setIgnoreRepaint(true);
   mainWindow.addWindowListener(new WindowListener() {
    @Override
    public void windowActivated(WindowEvent e) {
     // Does nothing.
    }

    @Override
    public void windowClosed(WindowEvent e) {
     // Does nothing
    }

    @Override
    public void windowClosing(WindowEvent e) {
     timeToQuit = true;
     EventQueue.invokeLater(new Runnable() {
      @Override
      public void run() {
       mainWindow.dispose();
       mainWindow = null;
      }
     });
    }

    @Override
    public void windowDeactivated(WindowEvent e) {
     // Does nothing.
    }

    @Override
    public void windowDeiconified(WindowEvent e) {
     // Does nothing.
    }

    @Override
    public void windowIconified(WindowEvent e) {
     // Does nothing.
    }

    @Override
    public void windowOpened(WindowEvent e) {
     // Does nothing.
    }
   });
   mainWindow.addKeyListener(new KeyListener() {
    @Override
    public void keyPressed(KeyEvent e) {
     if (e.getKeyCode() == KeyEvent.VK_SPACE) {
      pressedActionKey = true;
     }
    }

    @Override
    public void keyReleased(KeyEvent e) {
     // does nothing.
    }

    @Override
    public void keyTyped(KeyEvent e) {
     // does nothing.
    }

   });
   mainWindow.setPreferredSize(new Dimension(
     DEFAULT_RENDERING_WIDTH, DEFAULT_RENDERING_HEIGHT));
   mainWindow.pack();
   mainWindow.setVisible(true);
   mainWindow.createBufferStrategy(2);
  }
 });
 /*
  * This while loop is the main game loop. It basically iterates through
  * 3 stages forever: getting the player input, reacting to it, and
  * drawing the results on screen.
  */
 while (!timeToQuit) {
  getPlayerInput();
  processGameLogic();
  EventQueue.invokeAndWait(new Runnable() {
   @Override
   public void run() {
    if (mainWindow != null) {
     BufferStrategy strategy = mainWindow
       .getBufferStrategy();
     Graphics2D g = (Graphics2D) strategy.getDrawGraphics();
     Insets insets = mainWindow.getInsets();
     g.translate(insets.left, insets.top);
     g.setTransform(AffineTransform
         .getScaleInstance(
           (double) (mainWindow.getWidth() - insets.right)
             / (double) DEFAULT_RENDERING_WIDTH,
           (double) (mainWindow
             .getHeight() - insets.bottom)
             / (double) DEFAULT_RENDERING_HEIGHT));
     updateScreen(g);
     strategy.show();
    }
   }
  });
  Thread.sleep(1);
 }
}

public static void processGameLogic() {
 /*
  * Check if pacman touched a ghost, stuff like that. Doesn't touch any
  * Swing components.
  */
}

public static void getPlayerInput() {
 /*
  * Doesn't touch any Swing components.
  */
 if (pressedActionKey) {
  /* make mario jump */
  System.out.println("Mario jumps.");
  pressedActionKey = false;
 }
}

public static void updateScreen(Graphics2D g) {
 assert SwingUtilities.isEventDispatchThread() : "Don't call updateScreen
from outside the EDT.";
 g.setColor(Color.BLACK);
 g.fillRect(0, 0, 640, 480);
 /*
  * TODO: Draw all sorts of amazing eye-candy.
  */
}
}
</SSCCE>

   - Oliver
Oliver Wong - 22 Mar 2007 17:03 GMT
[most of the code snipped]

>  /*
>   * This while loop is the main game loop. It basically iterates through
[quoted text clipped - 21 lines]
>              / (double) DEFAULT_RENDERING_HEIGHT));
>      updateScreen(g);

Oops, I should probably call g.dispose() here.

>      strategy.show();
>     }
>    }
>   });
>   Thread.sleep(1);
>  }

   - Oliver
Knute Johnson - 22 Mar 2007 19:40 GMT
> [most of the code snipped]
>>  /*
[quoted text clipped - 33 lines]
>
>     - Oliver

Oliver:

For active rendering I would take your rendering out of the EDT
altogether.  The problem that you are going to run into is that
eventually too much will be happening on the EDT and you won't be able
to do any user input processing or that it will delay your rendering.
You only need to create the BufferStrategy once not every time in the
rendering loop.  I like to use java.util.Timer to drive the loop but you
can use Thread.sleep().  As Daniel said I would do my user input event
driven.  You can use a Frame or JFrame but you have to deal with the
insets and title bar your self.  If you use an undecorated frame you
might as well use a window.

Below is how I would do it.  See if you like any of it.

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.util.*;
import javax.swing.*;

public class test4 extends JWindow {
    java.util.Timer timer;
    BufferStrategy bs;
    int x,y,deltaX=3,deltaY=3;

    public test4() {
        // you want to do all drawing
        setIgnoreRepaint(true);

        addWindowListener(new WindowAdapter() {
            public void windowOpened(WindowEvent we) {
                // don't create the buffer strategy
                // until window is visible
                test4.this.createBufferStrategy(2);
                bs = getBufferStrategy();
                // schedule animation
                timer.schedule(new AnimationTask(),0,30);
            }
        });

        timer = new java.util.Timer();

        setSize(400,300);
        setVisible(true);
    }

    class AnimationTask extends TimerTask {
        public void run() {
            x += deltaX;
            y += deltaY;
            if (x > getWidth() - 30 || x < 0)
                deltaX = -deltaX;
            if (y > getHeight() - 30 || y < 0)
                deltaY = -deltaY;
            render();
        }
    }

    void render() {
        do {
            do {
                // get new draw graphics every time
                Graphics g = bs.getDrawGraphics();
                // draw
                g.setColor(Color.WHITE);
                g.fillRect(0,0,getWidth(),getHeight());
                g.setColor(Color.BLUE);
                g.fillOval(x,y,30,30);
                // dispose of the draw graphics
                g.dispose();
            } while (bs.contentsRestored()) ;
            // blit/flip buffers
            bs.show();
        } while (bs.contentsLost()) ;
    }

    public static void main(String[] args) {
        Runnable r = new Runnable() {
            public void run() {
                test4 t4 = new test4();
                t4.addMouseListener(new MouseAdapter() {
                    public void mousePressed(MouseEvent me) {
                        System.exit(0);
                    }
                });
            }
        };
        EventQueue.invokeLater(r);
    }
}

Signature

Knute Johnson
email s/nospam/knute/

Oliver Wong - 22 Mar 2007 23:01 GMT
> For active rendering I would take your rendering out of the EDT
> altogether.  The problem that you are going to run into is that
> eventually too much will be happening on the EDT and you won't be able
> to do any user input processing or that it will delay your rendering.

   This key piece of information is pretty central to my
(mis?)understanding. I thought it's written that whenever you touch Swing
components, you should do so from within the EDT. If that's the case,
shouldn't you perform painting of JFrames and other widgets within the
EDT?

> You only need to create the BufferStrategy once not every time in the
> rendering loop.

   Right: in the code I posted, I actually create the BufferStrategy
once, but call getBufferStrategy() repeatedly from within the loop (as
opposed to, in your code, calling getBufferStrategy once and storing its
return value somewhere). From peering into the source code, it looks like
getBufferStrategy() is just a plain getter anyway.

>  I like to use java.util.Timer to drive the loop but you can use
> Thread.sleep().

   The Timer class seems to have its own internal "task queue". From the
javadocs:
http://java.sun.com/javase/6/docs/api/java/util/Timer.html
<quote>
Timer tasks should complete quickly. If a timer task takes excessive time
to complete, it "hogs" the timer's task execution thread. This can, in
turn, delay the execution of subsequent tasks, which may "bunch up" and
execute in rapid succession when (and if) the offending task finally
completes.
</quote>

   I haven't spent too much time analyzing the impact of this (presumably
there will only be one if I use Timer.schedule() at multiple locations in
my code), but it sounds like I can avoid worrying about it entirely with a
tight loop and Thread.sleep(1).

   I realize that the javadocs themselves claim that your design is
appropriate for animations (It says "Fixed-delay execution is appropriate
for recurring activities that require 'smoothness.' In other words, it is
appropriate for activities where it is more important to keep the
frequency accurate in the short run than in the long run. This includes
most animation tasks"), but this fixed-delay behaviour sounds like it's
incompatible with the way *I* want rendering to work in my engine.

   It's not shown in my code, but in a "real" engine, I usually calculate
the amount of time that has passed between each frame of animation (I've
got this library which tries to use native C code on platforms it
recognizes, and falls back on System.currentTimeMillis() on those it
doesn't), and then I use that elapsed time to calculate whether to skip
ahead a couple of frames (on computers that are a bit slow for this game)
or to interpolate between two frames of animations (on computers that are
a bit fast for this game).

   For example, I might have a scripting language which says "It should
take the character 2 seconds to walk from point A to point B", and the
engine will smoothly animate a sprite moving from point A to point B at
the highest frame rate that the computer can handle.

   Long story short, the "Fixed-delay execution" system seems like it'll
work counter to my goal there.

>  As Daniel said I would do my user input event driven.

   Really? I find a polling-based input system much more natural to
program for, in the context of making a game. For example, if the player
presses the "Right" cursor key, I want Sonic the Hedgehog to start running
towards the right. And if in the next frame, he's still holding down the
"right" cursor I want Sonic to keep running (otherwise, he should start to
brake, and slow down). And the longer the button is held down, the more
Sonic accelerates (up to a certain terminal velocity).

   With a polling system, the logic would look something like:

{
 if (rightButtonIsDown()) {
   if (sonic.xVel < MAX_XVEL) {
     sonic.xVel += 1.0;
   }
 } else if (leftButtonIsDown()) {
   if (sonic.xVel > -MAX_XVEL) {
     sonic.xVel -= 1.0;
   }
 } else {
   sonic.xVel /= 2; /*Quickly approaches zero.*/
 }
}

   The only way I could think of implementing this with Swing's event
model would be to set a flag when a keypress occurs, and then unset a flag
when the key is released... which is pretty much emulating a polling
system ontop of Swing's event system... which is pretty much what the code
I posted does.

>  You can use a Frame or JFrame but you have to deal with the insets and
> title bar your self.  If you use an undecorated frame you might as well
> use a window.

   Right, the insets are a bit of a pain, and I sometimes wish JFrame had
a setSizeWithoutInsets() method in addition to its standard setSize()
method. But I *do* want a decorate frame (at least when the game is not
running in full screen mode), so that the player can move the window
around and resize it. Of course, if the player switches the game to
fullscreen mode, then at that point, the window should be undecorated.

> Below is how I would do it.  See if you like any of it.
[informative code snipped]

   Thanks. You introduced me to the WindowAdapter class, and it looks
like I'll have to re-read the javadocs for BufferStrategy (since it's not
clear to me what you're trying to do with your calls to contentsLost() and
contentsRestored()) It looks like it has something to do with losing image
data due to the use of VolatileImage, but I'm wondering whether it's
necessary for my game: In most of my games, so much is going on screen at
any one point that I typically assume that the whole image is always lost
and redraw the whole frame from scratch. The game is implemented as a
state machine (not illustrated in the simplified version of the code I've
been posting), and any state which needs access to previous frame contents
(e.g. for a motion-blurring effect) needs to implement storing previous
image data itself.

   Here's yet another iteration of my design, taking into account your
advice of pulling the active-drawing code out of the EDT. I've had to
reintroduce the synchronize() statements, because otherwise it's possible
that the user might close the window (and thus invalidate the graphics
object) while I'm still using the graphics object to render the next
frame. I've added a Thread.sleep(1000) statement to increase the
likelihood of this race condition occurring for illustrative purposes.

   I've also used your WindowAdapter (and KeyAdapter) trick.

<SSCCE>
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferStrategy;
import java.lang.reflect.InvocationTargetException;

import javax.swing.JFrame;

public class Test {

private static final int DEFAULT_RENDERING_WIDTH = 640;
private static final int DEFAULT_RENDERING_HEIGHT = 480;
private static JFrame mainWindow;
/**
 * Returns true if the game is in the process of shutting down and
quitting.
 * If it's in the middle of a game-loop iteration, it'll finish that
 * iteration (potentially drawing 1 more frame of animation, and then
quit.
 */
private static boolean timeToQuit = false;
/**
 * True if the player pressed the "action" key since the last iteration
 * through the game loop, false otherwise. In a real game, I'd probably
have
 * many of these booleans (e.g. an "up" key, a "down" key, etc., and move
it
 * into a seperate class for organization purposes.
 */
private static boolean pressedActionKey = false;

public static void main(String[] args) throws InterruptedException,
  InvocationTargetException {
 /*
  * This first invokeAndWait initializes all the GUI components, and
  * registers the appropriate listeners to hook into the main game
  * engine.
  */
 EventQueue.invokeAndWait(new Runnable() {
  @Override
  public void run() {
   mainWindow = new JFrame("My game");
   mainWindow.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
   mainWindow.setIgnoreRepaint(true);
   mainWindow.addWindowListener(new WindowAdapter() {
    @Override
    public void windowClosing(WindowEvent e) {
     timeToQuit = true;
     EventQueue.invokeLater(new Runnable() {
      @Override
      public void run() {
       synchronized (mainWindow) {
        mainWindow.dispose();
        mainWindow = null;
       }
      }
     });
    }
   });
   mainWindow.addKeyListener(new KeyAdapter() {
    @Override
    public void keyPressed(KeyEvent e) {
     if (e.getKeyCode() == KeyEvent.VK_SPACE) {
      pressedActionKey = true;
     }
    }
   });
   mainWindow.setPreferredSize(new Dimension(
     DEFAULT_RENDERING_WIDTH, DEFAULT_RENDERING_HEIGHT));
   mainWindow.pack();
   mainWindow.setVisible(true);
   mainWindow.createBufferStrategy(2);
  }
 });
 /*
  * This while loop is the main game loop. It basically iterates through
  * 3 stages forever: getting the player input, reacting to it, and
  * drawing the results on screen.
  */
 while (!timeToQuit) {
  getPlayerInput();
  processGameLogic();
  synchronized (mainWindow) {
   if (mainWindow != null) {
    Thread.sleep(1000);
    BufferStrategy strategy = mainWindow.getBufferStrategy();
    Graphics2D g = (Graphics2D) strategy.getDrawGraphics();
    Insets insets = mainWindow.getInsets();
    g.translate(insets.left, insets.top);
    g.setTransform(AffineTransform.getScaleInstance(
      (double) (mainWindow.getWidth() - insets.right)
        / (double) DEFAULT_RENDERING_WIDTH,
      (double) (mainWindow.getHeight() - insets.bottom)
        / (double) DEFAULT_RENDERING_HEIGHT));
    updateScreen(g);
    g.dispose();
    strategy.show();
   }
  }
  Thread.sleep(1);
 }
}

public static void processGameLogic() {
 /*
  * Check if pacman touched a ghost, stuff like that. Doesn't touch any
  * Swing components.
  */
}

public static void getPlayerInput() {
 /*
  * Doesn't touch any Swing components.
  */
 if (pressedActionKey) {
  /* make mario jump */
  System.out.println("Mario jumps.");
  pressedActionKey = false;
 }
}

public static void updateScreen(Graphics2D g) {
 g.setColor(Color.BLACK);
 g.fillRect(0, 0, 640, 480);
 /*
  * TODO: Draw all sorts of amazing eye-candy.
  */
}
}
</SSCCE>

   - Oliver
Knute Johnson - 23 Mar 2007 02:54 GMT
>> For active rendering I would take your rendering out of the EDT
>> altogether.  The problem that you are going to run into is that
[quoted text clipped - 6 lines]
> shouldn't you perform painting of JFrames and other widgets within the
> EDT?

Unless you are trying to do high performance active rendering.  Then you
 do your rendering in another thread.  The normal Swing component
drawing is disabled and you won't interfere with it at all.

>> You only need to create the BufferStrategy once not every time in the
>> rendering loop.
[quoted text clipped - 4 lines]
> return value somewhere). From peering into the source code, it looks like
> getBufferStrategy() is just a plain getter anyway.

Yes you are correct.  In your case it is just one less method call in
the render loop to do it the way I did.

>>  I like to use java.util.Timer to drive the loop but you can use
>> Thread.sleep().
[quoted text clipped - 9 lines]
> completes.
> </quote>

Each Timer object creates a thread.  If you only schedule one task it
won't run into problems with queue delays.  I did test this with
something else I was working on and it is better to create a new Timer
for each task.

>     I haven't spent too much time analyzing the impact of this (presumably
> there will only be one if I use Timer.schedule() at multiple locations in
> my code), but it sounds like I can avoid worrying about it entirely with a
> tight loop and Thread.sleep(1).

>     I realize that the javadocs themselves claim that your design is
> appropriate for animations (It says "Fixed-delay execution is appropriate
[quoted text clipped - 3 lines]
> most animation tasks"), but this fixed-delay behaviour sounds like it's
> incompatible with the way *I* want rendering to work in my engine.

I know but I can tell you after a lot of experimentation with adjusting
timers and fixed rate Timers that it looks better with fixed delay.

>     It's not shown in my code, but in a "real" engine, I usually calculate
> the amount of time that has passed between each frame of animation (I've
[quoted text clipped - 4 lines]
> or to interpolate between two frames of animations (on computers that are
> a bit fast for this game).

I don't know how real game developers handle timing.  I do know that
with a fair amount of testing I have gone back to Timer.schedule().  It
does not produce that accurate a result on Windows XP computers that I
have tested it on.  It is just better than the alternatives I have tried.

I don't have any real experience with XP timing but I would be very
curious to know if it is possible to make a JNI method that could sleep
very accurately.  I would like to find something that could be accurate
to 250 microseconds +- to try in my timing loop.  The accuracy of
Timer.schedule() is no where near that close.  If you ran the test code
I posted you can see the jitter caused by the timing errors.

>     For example, I might have a scripting language which says "It should
> take the character 2 seconds to walk from point A to point B", and the
> engine will smoothly animate a sprite moving from point A to point B at
> the highest frame rate that the computer can handle.

I think what you will find with that method is that your frame rate is
constantly changing as thread timing and system load changes.  But I
will be interested in your results.

>     Long story short, the "Fixed-delay execution" system seems like it'll
> work counter to my goal there.

I'm not trying to talk you out of it just trying to give you what little
I know and have tried.

>>  As Daniel said I would do my user input event driven.
>
[quoted text clipped - 27 lines]
> system ontop of Swing's event system... which is pretty much what the code
> I posted does.

I used the old event model for the Pong game I wrote.  That works well
and for that sort of input will probably be better.  I was thinking
JTextFields and Buttons before.  Pong game and source is at
www.knutejohnson.com.  It does not use active rendering.

>>  You can use a Frame or JFrame but you have to deal with the insets and
>> title bar your self.  If you use an undecorated frame you might as well
[quoted text clipped - 6 lines]
> around and resize it. Of course, if the player switches the game to
> fullscreen mode, then at that point, the window should be undecorated.

I haven't used full screen exclusive mode much because most of what I
have been working on uses multiple monitors and still requires user
input.  That won't work with FSEM.  I don't know if it is possible to
remove the decoration on a window once it has been created or not.

>> Below is how I would do it.  See if you like any of it.
> [informative code snipped]
[quoted text clipped - 11 lines]
> (e.g. for a motion-blurring effect) needs to implement storing previous
> image data itself.

Even a blit buffer strategy can use a VolatileImage as the back buffer.
 If you read the docs for VolatileImage you will see what I'm up to
there.  That code is directly out of the docs.

>     Here's yet another iteration of my design, taking into account your
> advice of pulling the active-drawing code out of the EDT. I've had to
[quoted text clipped - 62 lines]
>      public void windowClosing(WindowEvent e) {
>       timeToQuit = true;

You are already in the EDT here, invokeLater is not needed.

>       EventQueue.invokeLater(new Runnable() {
>        @Override
[quoted text clipped - 80 lines]
>
>     - Oliver

Try it and see how it works.  Post something that has some action!

If you are interested in writing some C timing code for me, let me know.

Signature

Knute Johnson
email s/nospam/knute/

Oliver Wong - 23 Mar 2007 15:43 GMT
>>> For active rendering I would take your rendering out of the EDT
>>> altogether.  The problem that you are going to run into is that
[quoted text clipped - 10 lines]
> do your rendering in another thread.  The normal Swing component drawing
> is disabled and you won't interfere with it at all.

   Okay, thanks.

[...]

> I don't have any real experience with XP timing but I would be very
> curious to know if it is possible to make a JNI method that could sleep
> very accurately.  I would like to find something that could be accurate
> to 250 microseconds +- to try in my timing loop.  The accuracy of
> Timer.schedule() is no where near that close.  If you ran the test code
> I posted you can see the jitter caused by the timing errors.

   The library I use returns number of millisecond since the epoch, so
you can't get sub-millisecond precision with it. Probably there are
libraries out there that *do* provide sub-millisecond precision (and
hopefully a corresponding accuracy), but I don't know of any.

>>     For example, I might have a scripting language which says "It
>> should take the character 2 seconds to walk from point A to point B",
[quoted text clipped - 4 lines]
> constantly changing as thread timing and system load changes.  But I
> will be interested in your results.

   Yes, that's the case. The experience is "optimized" for the high end
machines. With a fixed-delay animation, you have some frame rate cap (e.g.
30fps), which all high end machines will be able to consistently produce,
and there's a certain cut-off point after which slower machines will start
to play the animation "too slowly", but will render every single frame. So
for example, things like syncing the animation to music or sound track may
become problematic.

   With my variable delay system, you have a frame rate bottom (e.g.
24fps). If a machine is unable to consistently meet that 24fps rate, then
every frame of the "minimal" 10 fps animation will play, but at a slower
than intended speed (and thus have syncing issues once again). However,
any machine which can easily produce the 10fps animation with cycles to
spare will instead display an animation with the highest fps it can
muster, correctly timed (e.g. perhaps at 60fps).

[...]
> I haven't used full screen exclusive mode much because most of what I
> have been working on uses multiple monitors and still requires user
> input.  That won't work with FSEM.  I don't know if it is possible to
> remove the decoration on a window once it has been created or not.

   I just tested it, and you cannot remove the decoration once you've
added it. I haven't used FSEM much either, because it's easier to debug
your game if you can still access the Eclipse workbench. Looks like I'll
have to rethink that part of the engine.

[...]
>>     mainWindow.addWindowListener(new WindowAdapter() {
>>      @Override
[quoted text clipped - 14 lines]
>>      }
>>     });

Thanks, I forgot about that.

[...]

> Try it and see how it works.  Post something that has some action!

   Okay, here's a version which animates a blue ball sort of like the
code you posted:

<SSCCE>
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;
import java.lang.reflect.InvocationTargetException;

import javax.swing.JFrame;

public class Test {

private static final int DEFAULT_RENDERING_WIDTH = 640;
private static final int DEFAULT_RENDERING_HEIGHT = 480;
private static JFrame mainWindow;
/**
 * Returns true if the game is in the process of shutting down and
quitting.
 * If it's in the middle of a game-loop iteration, it'll finish that
 * iteration (potentially drawing 1 more frame of animation, and then
quit.
 */
private static boolean timeToQuit = false;
/**
 * True if the player pressed the "action" key since the last iteration
 * through the game loop, false otherwise. In a real game, I'd probably
have
 * many of these booleans (e.g. an "up" key, a "down" key, etc., and move
it
 * into a seperate class for organization purposes.
 */
private static boolean pressedActionKey = false;
static double x;
static double y;
static double deltaX = 3;
static double deltaY = 3;

public static void main(String[] args) throws InterruptedException,
  InvocationTargetException {
 /*
  * This first invokeAndWait initializes all the GUI components, and
  * registers the appropriate listeners to hook into the main game
  * engine.
  */
 EventQueue.invokeAndWait(new Runnable() {
  @Override
  public void run() {
   mainWindow = new JFrame("My game");
   mainWindow.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
   mainWindow.setIgnoreRepaint(true);
   mainWindow.addWindowListener(new WindowAdapter() {
    @Override
    public void windowClosing(WindowEvent e) {
     timeToQuit = true;
     synchronized (mainWindow) {
      mainWindow.dispose();
      mainWindow = null;
     }
    }
   });
   mainWindow.addKeyListener(new KeyAdapter() {
    @Override
    public void keyPressed(KeyEvent e) {
     if (e.getKeyCode() == KeyEvent.VK_SPACE) {
      pressedActionKey = true;
     }
    }
   });
   mainWindow.setPreferredSize(new Dimension(
     DEFAULT_RENDERING_WIDTH, DEFAULT_RENDERING_HEIGHT));
   mainWindow.pack();
   mainWindow.setVisible(true);
   mainWindow.createBufferStrategy(2);
  }
 });
 /*
  * This while loop is the main game loop. It basically iterates through
  * 3 stages forever: getting the player input, reacting to it, and
  * drawing the results on screen.
  */
 long lastIteration = System.currentTimeMillis();
 while (!timeToQuit) {
  getPlayerInput();
  {
   long now = System.currentTimeMillis();
   long elapsed = now - lastIteration;
   if (elapsed > 1000 / 24) {
    /* Don't do frame skipping when you drop below 24fps. */
    elapsed = 1000 / 24;
   }
   processGameLogic(elapsed);
   lastIteration = now;
  }
  synchronized (mainWindow) {
   if (mainWindow != null) {
    BufferStrategy strategy = mainWindow.getBufferStrategy();
    Graphics2D g = (Graphics2D) strategy.getDrawGraphics();
    Insets insets = mainWindow.getInsets();
    g.translate(insets.left - 1, insets.top - 1);
    g
      .translate((double) (mainWindow.getWidth()
        - insets.right - insets.left)
        / (double) DEFAULT_RENDERING_WIDTH,
        (double) (mainWindow.getHeight()
          - insets.bottom - insets.top)
          / (double) DEFAULT_RENDERING_HEIGHT);
    updateScreen(g);
    g.dispose();
    strategy.show();
   }
  }
  Thread.sleep(1);
 }
}

public static void processGameLogic(long timeElapsed) {
 x += deltaX * timeElapsed / 10.0;
 y += deltaY * timeElapsed / 10.0;
 if (x > DEFAULT_RENDERING_WIDTH - 30) {
  deltaX = -Math.abs(deltaX);
 }
 if (x < 0) {
  deltaX = Math.abs(deltaX);
 }
 if (y > DEFAULT_RENDERING_HEIGHT - 30) {
  deltaY = -Math.abs(deltaY);
 }
 if (y < 0) {
  deltaY = Math.abs(deltaY);
 }
}

public static void getPlayerInput() {
 /*
  * Doesn't touch any Swing components.
  */
 if (pressedActionKey) {
  /* make mario jump */
  System.out.println("Mario jumps.");
  pressedActionKey = false;
 }
}

public static void updateScreen(Graphics2D g) {
 g.setColor(Color.BLACK);
 g.fillRect(0, 0, 640, 480);
 g.setColor(Color.BLUE);
 g.fillOval((int) x, (int) y, 30, 30);
}
}
</SSCCE>

   Unfortunately, to keep it as a simple SSCCE, I'm not able to include
the JNI library I'm using, so the timer resolution is pretty bad, which
means the animation isn't as smooth as it could be. I also realized I had
made a bug in the way I implemented insets and Graphics2D translation,
which is fixed in this latest iteration, though the blue ball mysteriously
seems to over-estimate the position of the bottom border, which I can't
really explain...

> If you are interested in writing some C timing code for me, let me know.

   I'm not. I'm not a C programmer. The library I use I downloaded off of
the internet:
http://www.javaworld.com/javaqa/2003-01/01-qa-0110-timing_p.html

   - Oliver
Knute Johnson - 23 Mar 2007 21:32 GMT
Oliver:

It looks pretty good.  The one thing is the yo-yo effect you see as the
ball moves.  That is caused by the unstable timing.  There are really
two things that you see when you do animation, the yo-yo effect is slow
about 2 or 3 times per second and the jitter which is the fast cycle
jerkiness.  The yo-yo effect can be eliminated by using the Timer.  The
jitter is more problematic.  If you come up with any good solutions for
the jitter, please post them.

Signature

Knute Johnson
email s/nospam/knute/



Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.