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);
}
}
}