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);
        }
    }
}
2
  • 2
    Please search for error messages first (the problem has nothing to do with a timer because the timer callbacks are not run on different threads) Commented May 4, 2015 at 17:10
  • I dont understand where the concurrent modification is happening, thats the main problem Commented May 4, 2015 at 18:32

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

9 Comments

This one is correct. I'd use for (Iterator<Tile> iter = activeTiles.iterator(); iter.hasNext; ) ... instead, to limit the scope of iter to the loop, but it isn't strictly necessary.
I dont understand where the concurrent modification is happening, thats the main problem
onTouchEvent>>spawner.activeTiles.remove(tile); and removeFailedTiles() >>activeTiles.remove(tile);
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
May be your second problem lies in Thread.sleep(10); statement. Refer this for more info.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.