1

I'm making a proof-of-concept game in Java and decided (for practice but mostly for fun) to make my own simple component with thread-safe double buffering. However, I am not very experienced when it comes to concurrency (specially regarding Swing) and I'm wondering if there are any problems with my implementation that I'm missing.

Implementation as follows:

import java.awt.Graphics;
import java.awt.image.BufferedImage;

import javax.swing.JPanel;

public class GamePanel extends JPanel
{

    private static final long serialVersionUID = 1L;

    private BufferedImage mBackImage = null;
    private BufferedImage mFrontImage = null;

    public BufferedImage getBackImage ()
    {
        return mBackImage;
    }

    public void swap ()
    {
        BufferedImage new_back;
        //
        synchronized (this)
        {
            new_back = mFrontImage;
            mFrontImage = mBackImage;
        }
        //
        int width = getWidth (), height = getHeight ();
        if (width > 0 && height > 0)
        {
            if (new_back == null || new_back.getWidth () != width
                    || new_back.getHeight () != height)
                new_back = new BufferedImage (width, height,
                        BufferedImage.TYPE_INT_ARGB);
            //
            mBackImage = new_back;
        }
        else
            mBackImage = null;
    }

    @Override
    public void paintComponent (Graphics g)
    {
        synchronized (this)
        {
            if (mFrontImage == null)
                super.paintComponent (g);
            else
                g.drawImage (mFrontImage, 0, 0, null);
        }
    }

}

I'm assuming that getBackImage() and swap() will only be called by a single thread (the game loop thread). The paints are triggered via a Swing timer and so are in the EDT. I believe that the simple synchronized-blocks around the use of mFrontImage should be enough to protect against unwanted behavior, allowing the game loop thread to render to the back image and call swap without worrying about the Swing redraws. Am I missing something?

Anton
  • 1,422
  • 1
  • 11
  • 15

2 Answers2

1
  • Swing is pure single threaded and all event must be done on EDT, then I can't see reason for synchronized (this)

  • use Icon in the JLabel for dispaying Images in the Swing GUI

  • for any of swap's types, you have look at CardLayout

Community
  • 1
  • 1
mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • 1
    Possible scenario without sync?: 1) paint invoked by EDT, `mFrontImage != null` 2) game loop thread calls `swap` when `mBackImage == null`, `mFrontImage` is now null 3) EDT calls `g.drawImage` with `mFrontImage == null` – Anton Feb 21 '12 at 22:05
1

In game thread:

BufferedImage image = gamePanel.getBackPanel();
gamePanel.swap();

Your program would now be in a state where the Event Dispatch Queue and the game thread can access the same image. Meaning funny things could be drawn to the panel if you were drawing to the image whilst the EDT was drawing the image to screen.

For the purposes of reading and writing to the frontImage field on its own then you're thread safe as all access is done inside synchronized blocks. However, the paintComponent method could be better written to reduce the amount of time spent in a synchronized block. That is:

@Override
public void paintComponent (Graphics g)
{
    BufferedImage localFrontImage;
    synchronized (this)
    {
        localFrontImage = mFrontImage;
    }
    if (localFrontImage == null)
        super.paintComponent (g);
    else
        g.drawImage (localFrontImage, 0, 0, null);

}
Dunes
  • 37,291
  • 7
  • 81
  • 97
  • Should have thought of that. Thank you. I suppose the easy fix to that is to have only one image and lock both reads and writes to it, but then it's not very concurrent any more... – Anton Feb 21 '12 at 22:11
  • But... as long as all drawing is done between calls to `getBackImage` and `swap` (and only to the returned image) then everything should be fine, yes? Maybe experimenting with concurrency at midnight was a bad idea ;) – Anton Feb 21 '12 at 22:33