0

Basically I am running a java emulator at 60 FPS and each time the screen refreshes it draws pixels from an array and paints it to the screen. I am getting a java.util.ConcurrentModificationException i.e. the contents of the pixel array are getting modified sometime during the paint() method execution. Is there a way I can synchronize the paint method or the generatePalette method to avoid this? Or is there another/better solution?

Code:

public class LCD extends Canvas implements Runnable {
    
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private static final int FRAME_RATE = 1 / 60 * 1000;
    private BufferedImage canvas;
    private List<Pixel> pixels = new ArrayList<>();
    private int tilemapStart, tilemapEnd;
    private GBCpuBootRom cpu;
    private GBBus bus;
    
    public void init() {
        setTileMap(bus.cpuRUnsigned8(0xFF40));
        generatePalette(bus, bus.cpuRUnsigned8(0xFF40));
    }
    
    public LCD(int len, int hei, GBCpuBootRom cpu, GBBus bus) {
        canvas = new BufferedImage(len,hei,BufferedImage.TYPE_INT_ARGB);
        this.cpu =  cpu;
        this.bus = bus;
        Thread t = new Thread(this);
        t.start();
    }
    
    public void setTileMapExplicit(int start, int end) {
        tilemapStart = start;
        tilemapEnd = end;
    }
    
    public void setTileMap(int mFF40) {
        /*
         * Bit 3 (bit 5)
         * 0: 9800 to 9BFF
         * 1: 9C00 to 9FFF
         * 
         * Bit 4 (bit 4)
         * 0: 8800 to 97FF
         * 1: 8000 to 8FFF
         */
        
        if ( ((mFF40 & 0b00001000) >> 3) == 1) {
            tilemapStart = 0x9C00;
            tilemapEnd = 0xA000;
        }
        else {
            tilemapStart = 0x9800;
            tilemapEnd = 0x9C00;
        }
    }
    
    public void paint(Graphics g) {
        for(Pixel p:pixels) 
            try {
                canvas.setRGB(p.getX(), p.getY(), p.getColor().getRGB());
            }
            catch(Exception e) {
                /*
                 * If a pixel cannot be drawn
                 * skip it and move on to next.
                 */
                System.out.println("Cannot draw pixel: " + p.getX() + ", " +
                    p.getY());
            }
        draw(g);
    }
    
    private void draw(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;
        g2.drawImage(canvas, null, null);
    }
    
    public void generatePalette(GBBus bus, int mFF40)  {
        int hor = 0;
        int ver = 0;
        int x = 0;
        int y = 0;
        int inc = 8;
        int pixelCount = 0;
        
        /*
         * maxRowPixels
         * Each tile has 64 pixels
         * if 256 x 256 
         *      maxRowPixels = 2048 (32 * 64)
         * else if 160 x 144
         *      maxRowPixels = 1280 (20 * 64)
         */
        int maxRowPixels = canvas.getWidth() == 256 ? 2048 : 1280;
        Pixel[] palettes;
        Palette palette = new Palette();
        
        for(int i = tilemapStart; i < tilemapEnd; i++) {
            int dataAddr = spriteAddress((mFF40 & 0b00010000) >> 4, bus.cpuRUnsigned8(i));
            /*
             * Read range 0 to F
             * e.g.
             * 08000: 08000 to 0x800F
             * 
             * 08000, 08002, 08004, 08006,
             * 08008, 0800A, 0800C, 0800D,
             */
            for(int k = 0; k < 16; k = k + 2) {
                int hByte = bus.cpuRUnsigned8(dataAddr + k);
                int lByte = bus.cpuRUnsigned8(dataAddr + (k + 1));
                palette.setHB(hByte);
                palette.setLB(lByte);
                palette.setXY(x, y);
                palette.init();
                palettes = palette.getPixels();
                for(Pixel pix: palettes)
                    pixels.add(pix);
                
                x += inc;
                pixelCount += inc;
                
                if(x % 8 == 0) {
                    x = hor;
                    y++;
                }
                
                /*
                 * 1 tile completed
                 * 8 x 8 = 64p
                 * p means pixels   
                 */
                if(pixelCount % 64 == 0) {
                    hor = hor + inc;
                    x = hor;
                    y = ver;
                }
                
                if(pixelCount % maxRowPixels == 0) {
                    x = 0;
                    hor = 0;
                    ver = ver + inc;
                    y = ver;
                }
            }
        }
    }

    @Override
    public void run() {
        Thread.currentThread();
        while(true) {
            cpu.compute();
            pixels.clear();
            init();
            try {
                Thread.sleep(1000);
            }
            catch(Exception e) {
                e.printStackTrace();
            }
            repaint();
        }
    }
        

}

This is the exception I get at line 66 (for loop in paint() method above)

Exception in thread "AWT-EventQueue-0" java.util.ConcurrentModificationException
    at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1009)
    at java.base/java.util.ArrayList$Itr.next(ArrayList.java:963)
    at gameboyconcept.logo.LCD.paint(LCD.java:66)
Faiz
  • 133
  • 1
  • 6
  • *is there another/better solution?* - for a Swing application you would do your custom painting on a JPanel and then you would use a `SwingWorker` or "Swing Timer` to avoid updating the pixels of the array at the same time you try to paint them. See: https://stackoverflow.com/questions/64196198/bubble-sort-animation for solutions using both of these approaches. – camickr Nov 07 '20 at 01:12
  • thx! Will try this out. – Faiz Nov 07 '20 at 02:15

0 Answers0