Reputation: 10975
I have a Sprite class:
public class Sprite {
public static ArrayList<Sprite> sprites = null;
private Texture texture = null;
public Sprite(Texture texture) {
sprites.add(this);
this.texture = texture;
}
public void render(SpriteBatch batch) {
batch.draw(texture);
}
public void dispose() {
sprites.remove(this);
}
}
Before I create any sprites, I make sure that the Sprite class has a reference to the pool of sprites that are going to be rendered:
public class Main extends ApplicationAdapter {
private static ArrayList<Sprite> sprites = new ArrayList<Sprite>();
public Main() {
Sprite.sprites = sprites;
new Sprite("texture.png");
}
@Override
public void render(SpriteBatch batch) {
for (int i = 0; i < sprites.size(); i++)
sprites.get(i).render(batch);
}
}
So now when I create a new sprite, it will automatically render without me having to manually add it into the ArrayList.
The problem is that sprites can be disposed during the time that for loop is running, and thus some sprites won't be rendered for that specific frame.
I do not wish to loop through the items backwards such as:
for (int i = sprites.size() - 1; i >= 0; i--)
sprites.get(i).render(batch);
Because this will render my sprites out of order (sprites that should be on top will be covered).
My only solution so far has been to have another ArrayList that keeps track of which objects to dispose, as such:
public class Sprite {
public static ArrayList<Sprite> sprites = null, garbage = null;
//everything else the same
public void dispose() {
garbage.add(this);
}
}
And then during render, I first remove all the garbage from sprites, then render sprites:
for (int i = 0; i < garbage.size(); i++)
sprites.remove(garbage.get(i));
garbage.clear();
for (int i = 0; i < sprites.size(); i++)
sprites.get(i).render(batch);
This method works, but I feel it is inefficient. (Having two arraylists? Then doing two for loops?)
Is there any way I can loop through Sprites without it skipping a Sprite when a Sprite is disposed? I don't want to loop through Sprites backwards though because it will mess up the order.
I've now tested this with a synchronized list, but I am unable to get this working properly.
Testable code, (with java.util.ConcurrentModificationException
)
import java.util.*;
class Ideone {
public static class Sprite {
public static List<Sprite> sprites = null;
public int age = 0;
public Sprite() {
synchronized(sprites) {
sprites.add(this);
}
}
public void render() {
age++;
if (age > 30)
dispose();
}
public void dispose() {
synchronized(sprites) {
sprites.remove(this);
}
}
}
private static List<Sprite> sprites = Collections.synchronizedList(new ArrayList<Sprite>());
public static void main(String[] args) {
Sprite.sprites = sprites;
new Sprite();
new Sprite();
for (int i = 0; i < 60; i++)
render();
}
public static void render() {
synchronized(sprites) {
Iterator<Sprite> iterator = sprites.iterator();
while (iterator.hasNext())
iterator.next().render();
}
}
}
Upvotes: 1
Views: 345
Reputation: 10975
Here's what worked for me clicky:
import java.util.*;
class Ideone {
public static class Sprite {
public static ArrayList<Sprite> sprites = null;
public int age = 0;
private boolean removed = false;
public Sprite() {
sprites.add(this);
}
public boolean render() {
if (removed)
return false;
age++;
if (age > 30)
dispose();
return true;
}
public void dispose() {
removed = true;
}
}
private static ArrayList<Sprite> sprites = new ArrayList<Sprite>();
public static void main(String[] args) {
Sprite.sprites = sprites;
new Sprite();
new Sprite();
for (int i = 0; i < 60; i++)
render();
}
public static void render() {
Iterator<Sprite> iterator = sprites.iterator();
while (iterator.hasNext())
if (!iterator.next().render())
iterator.remove();
}
}
So, when a sprite should be removed, I set a quick bool that tells the main render loop to remove it with the iterator. I'll have to adapt this to use a sync'd list (so that adding items doesn't throw an exception).
Upvotes: 0
Reputation: 9559
First of all if sprites
list is accessed from multiple threads you need to synchronize access to it or use thread safe data structure (e.g. CopyOnWriteArrayList or CopyOnWriteArraySet).
Sprite.dispose
can directly remove itself from sprites
in this case.
Upvotes: 2
Reputation: 3456
If you just have to take care of remove
, then use Iterator
like
Iterator<Sprite> itr = sprites.iterator();
while (itr.hasNext()) {
// do something with itr.next()
// call itr.remove() to remove the current element in the loop
}
Iterator
provide remove
method to remove any item from the list but make sure you don't add any new item to the list otherwise it will throw ConcurrentModificationException
.
Upvotes: 2