0

I am messing around with timers to get the feel for a new game I have in mind. At the moment, I have squares that fade in and fade out using timers, and when they have faded out completely, they are removed from the list of "active tiles". I have another timer going, while the fade timers may also be running aswell, which spawns squares at random positions, each with a fade in and out. This all seems to work beautifully, but on the back end something is going wrong.

The program is throwing a ConcurrentModificationException. My knowledge of these is very limited, but I know it can happen when iterating over objects and changing objects at the same time.

This may be happening in my program, it may be when two ticks meet up simultaneously, that it throws this exception, but I have no idea.

In my head this seems like the only way to do this, as I really only know the one standard approach to timers, and do not have much experience with this. I would like to know if there is a better approach that would not cause this concurrent exception, and/or an explaination to why this is happening.

Thanks!

Below is some related code: (I am using android graphics libraries but it doesnt really apply to this)

Game.java:

public Game(Context context) {
    super(context);

    metrics = context.getResources().getDisplayMetrics();
    SCREEN_WIDTH = metrics.widthPixels;
    SCREEN_HEIGHT = metrics.heightPixels;

    spawner = new Spawner();
}


@Override
protected void onDraw(Canvas canvas) {

    for(Tile tile : spawner.activeTiles) {
        //System.out.println("Tile Color: " + tile.getColor());
        canvas.drawRect(tile, tile.getColor());
    }

    try { Thread.sleep(10); }
    catch (InterruptedException e) {}
    invalidate();
}

@Override
public boolean onTouchEvent(MotionEvent e) {

    System.out.println(spawner.activeTiles);

    float x = e.getX();
    float y = e.getY();

    for(Tile tile : spawner.activeTiles) {
        if(tile.contains(x, y) && tile.equals(spawner.activeTiles.get(0))) {
            tile.setBroken(true);
            spawner.activeTiles.remove(tile);
        }
    }

    return true;    
}

Tile.java:

    public Tile(int lifetime) {
    super();
    this.lifetime = lifetime;
    TILE_WIDTH = (int) Game.SCREEN_WIDTH / 10;
    TILE_HEIGHT = (int) Game.SCREEN_HEIGHT / 15;

    tileColor = new Paint();
    tileColor.setColor(Color.argb(0, 255, 0, 0));
    placeTile();
}

private void placeTile() {
    float screenWidth = Game.SCREEN_WIDTH;
    float screenHeight = Game.SCREEN_HEIGHT;

    Random random = new Random();
    top = random.nextInt((int) screenHeight - (TILE_HEIGHT * 2)) + 10;
    left = random.nextInt((int) screenWidth - (TILE_WIDTH * 2)) + 10;
    bottom = top + TILE_HEIGHT;
    right = left + TILE_WIDTH;

    fadeIn(5);  
}

private void fadeIn(final int speed) {
    if(tileColor != null) {
        final Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {

            @Override
            public void run() {
                int alpha = tileColor.getAlpha();

                if(alpha < 255) {
                    if(alpha + 5 > 255) tileColor.setAlpha(255);
                    else tileColor.setAlpha(alpha + 5);
                } else {
                    timer.cancel();
                    fadeOut(speed);
                }
            }   

        }, 50, speed);                  
    }
}
private void fadeOut(int speed) {
    if(tileColor != null) {
        final Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {

            @Override
            public void run() {
                int alpha = tileColor.getAlpha();

                if(alpha > 0 && !broken) {
                    if(alpha - 5 < 0) tileColor.setAlpha(0);
                    else tileColor.setAlpha(alpha - 5);
                } else {
                    failed = true;
                    timer.cancel();
                }
            }   

        }, lifetime, speed);    
    }
}

Spawner.java:

public List<Tile> activeTiles = new ArrayList<Tile>();

public long tileDelay = 500;

public Spawner() {
    start();
}

private void start() {

    Timer timer = new Timer();  
    timer.schedule(new TimerTask() {

        @Override
        public void run() {
            removeFailedTiles();

            activeTiles.add(new Tile(1000));    
        }

    }, 0, tileDelay);
}

private void removeFailedTiles() {
    for(Tile tile : activeTiles) {
        if(tile.isFailed()) {
            activeTiles.remove(tile);
        }
    }
}
Kyle Jensen
  • 419
  • 9
  • 27
  • 2
    Please [search for error messages first](http://stackoverflow.com/search?q=java+concurrent+modification+exception) (the problem has nothing to do with a timer because the timer callbacks are *not* run on different threads) – user2864740 May 04 '15 at 17:10
  • I dont understand where the concurrent modification is happening, thats the main problem – Kyle Jensen May 04 '15 at 18:32

1 Answers1

3

Use an Iterator and call remove():

Iterator<Tile > iter = activeTiles.iterator();

while (iter.hasNext()) {
    Tile tile= iter.next();

    if(tile.isFailed()) 
        iter.remove();

}

This link will help you to understand Concurrent Modification Exception.

Dhaval Patel
  • 10,119
  • 5
  • 43
  • 46
  • 1
    This one is correct. I'd use `for (Iterator iter = activeTiles.iterator(); iter.hasNext; ) ...` instead, to limit the scope of `iter` to the loop, but it isn't strictly necessary. – Sergei Tachenov May 04 '15 at 17:40
  • I dont understand where the concurrent modification is happening, thats the main problem – Kyle Jensen May 04 '15 at 18:32
  • 1
    onTouchEvent>>spawner.activeTiles.remove(tile); and removeFailedTiles() >>activeTiles.remove(tile); – Dhaval Patel May 04 '15 at 18:42
  • I realized this shortly after, but what was confusing is that it was happening when none of those events were being triggered. Seems to be working now though – Kyle Jensen May 04 '15 at 18:45
  • May be your second problem lies in Thread.sleep(10); statement. Refer [this](http://stackoverflow.com/questions/14728807/how-to-prevent-thread-sleep-in-ondraw-affecting-other-activities) for more info. – Dhaval Patel May 04 '15 at 18:59
  • Im not sure how this applies to my problem, the problem happens in the onDraw method when iterating through the elements. I removed the Thread.sleep(10); and it still happened – Kyle Jensen May 04 '15 at 19:19
  • can you debug on which line you are getting Concurrent Modification Exception? – Dhaval Patel May 04 '15 at 19:21
  • In Your Game class constructor, new Spawner() will start Spawner Thread. which call removeFailedTiles() after TimerTask Execute Run Method; So i think you are getting Concurrent Modification Exception in removeFailedTiles() method. – Dhaval Patel May 04 '15 at 19:25
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/76936/discussion-between-kyle-jensen-and-dhaval-patel). – Kyle Jensen May 04 '15 at 21:29