ReadWriteCollection for entities

The problem
We often see people asking question about entity management, involving removing items from a list, during iteration, or adding new items to them.

Naive attempts
A naive attempt usually results in a rather confusing ConcurrentModificationException, thrown by some of Java’s provided collections. When getting rid of this exception, more often than not, there is an hard to discover off-by-one bug lingering in there, leading to silent errors.


// attempt 1
for(Entity current: entities) {
   if(current.isDead()) {
      entities.remove(current); // exception
   }
   else if(current.isShooting()) {
      entities.add(new BulletEntity()); // exception
   }
}

// attempt 2
for(int i=0; i<entities.size(); i++) {
   Entity current = entities.get(i);
   if(current.isDead()) {
      entities.remove(i); // silent error, skips the next element!
   }
   else if(current.isShooting()) {
      entities.add(new BulletEntity()); // OK
   }
}

Solution A
Normally helpful people are suggesting to ‘correct i’, after a removal of an element and all is well, except performance.


for(int i=0; i<list.size(); i++) {
   Entity current = list.get(i);
   if(current.isDead()) {
      list.remove(i); // extremely slow
      i--; // correct i
   }
   else if(current.isShooting()) {
      list.add(new BulletEntity());
   }
}

Solution B
Iterating the collection in the opposite order. This has a few downsides: first, you force yourself to visit entities in opposite order, which might alter logic. Second, it prevents you from visiting elements you add to the collection while iterating over it. Last but not least, when removing an element from a List, you trigger an array-shift, which is time consuming, especially as it is performed for every removed element.


for(int i=list.size()-1; i>=0; i--) {
   Entity current = list.get(i);
   if(current.isDead()) {
      list.remove(i); // extremely slow
   }
   else if(current.isShooting()) {
      list.add(new BulletEntity()); // will not be visited this iteration
   }
}

Solution C (fast and correct, but deemed a hassle)
A fast solution is to create two lists, where you iterate over the first, while also adding new entities to the first, while copying enities that ‘survive’ to the second list. When all entities are visited, you clear the list that was just iterated, and swap both lists. This allows you to remove while iterating without any overhead, and adding items as you go, while processing those immediately during iteration. It however brings some bookkeeping, and requires twice the amount of memory. Clearing the list that was just iterated over is also less than optimal, as all array elements have to be nulled behind the scenes.


for(int i=0; i<curr.size(); i++) {
   Entity current = curr.get(i);
   if(!current.isDead()) {
      next.add(current);
   }
   else if(current.isShooting()) {
      curr.add(new BulletEntity()); // will be visited this iteration
   }
}
curr.clear();
// swap lists
temp = curr;
curr = next;
next = temp;

Proposed Solution (fast and convenient)
We can use the strategy of Solution C and merge the two lists into one. We keep the support for the feature of iterating over new entities in the current loop, and removing entities without the overhead that is imposed by the typical List implementation. Then we use the Iterator/Iterable interfaces to support convenient syntax, while also retaining support for methods like remove() and add(...).


ReadWriteCollection<Entity> entities = ...;
for(Entity entity: entities) {
   if(current.isDead()) {
      entities.remove(); // very fast
   }
   else if(current.isShooting()) {
      entities.add(new BulletEntity()); // will be visited this iteration
   }
}

You can see that the ‘entities’ instance is stateful, so that while iterating, ‘entities.remove()’ knows which element to discard.

The following demo code will show how to use the class:


ReadWriteCollection<String> data = new ReadWriteCollection<>();

data.add("a");
data.add("b");
data.add("c");
data.add("d");
data.add("e");
data.add("f");
data.add("g");

for (String str : data) {
	System.out.println("found: " + str);
}

System.out.println(data); // "[a, b, c, d, e, f, g]"

for (String str : data) {
	// print the element
	System.out.println("found: " + str);

	if (str.equals("e")) {
		// remove while iterating
		data.remove();

		// add while iterating
		data.add("ee");
	} else if (str.equals("ee")) {
		System.out.println("encountered added item!");
	}
}

System.out.println(data); // "[a, b, c, d, f, g, ee]"

// fast clearing of the collection while iterating over it
for (String str : data) {
	data.remove();
}
// or use the clear method: both versions are O ( n )
data.clear();

System.out.println(data); // "[]"

Implementation

import java.util.*;

public class ReadWriteCollection<E> implements Iterator<E>, Iterable<E> {
	private int iterateIndex, retainIndex, endIndex;
	private E[] items;
	private boolean doRetain;

	@SuppressWarnings("unchecked")
	public ReadWriteCollection() {
		items = (E[]) new Object[4];
	}

	public void add(E item) {
		if (endIndex == items.length) {
			items = Arrays.copyOf(items, Math.max(4, items.length << 1));
		}
		items[endIndex++] = item;
	}

	@Override
	public Iterator<E> iterator() {
		this.prepareForAccess();
		return this;
	}

	@Override
	public boolean hasNext() {
		if (doRetain) {
			items[retainIndex++] = items[iterateIndex - 1];
			doRetain = false;
		}

		if (iterateIndex == endIndex) {
			flip();
			return false;
		}

		return endIndex > 0;
	}

	@Override
	public E next() {
		if (iterateIndex == endIndex) {
			throw new NoSuchElementException();
		}
		doRetain = true;
		return items[iterateIndex++];
	}

	@Override
	public void remove() {
		if (iterateIndex == 0) {
			throw new NoSuchElementException();
		}
		if (!doRetain) {
			throw new IllegalStateException("already removed");
		}
		doRetain = false;
	}

	public void clear() {
		retainIndex = 0;
		flip();
	}

	public void trimToSize() {
		this.prepareForAccess();
		this.items = Arrays.copyOf(items, endIndex);
	}

	private void prepareForAccess() {
		if ((iterateIndex | retainIndex) == 0) {
			// no need to init
			return;
		}

		// process any unprocessed value from the previous iteration
		int off = iterateIndex + (doRetain ? 0 : 1);
		int len = endIndex - off;
		if (off != retainIndex) {
			System.arraycopy(items, off, items, retainIndex, len);
		}
		retainIndex += len;

		this.flip();
	}

	private void flip() {
		Arrays.fill(items, retainIndex, endIndex, null);
		endIndex = retainIndex;
		retainIndex = 0;
		iterateIndex = 0;
		doRetain = false;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder("[");
		for (int i = 0; i < retainIndex; i++) {
			sb.append(items[i]).append(",");
		}
		for (int i = iterateIndex - (doRetain ? 1 : 0); i < endIndex; i++) {
			sb.append(items[i]).append(",");
		}
		if (sb.length() > 1) {
			sb.setLength(sb.length() - 1);
		}
		return sb.append("]").toString();
	}
}

Good stuff! I use the same copy-back-during-iteration method in my entity collections as well.

@ClickerMonkey: thanks for the feedback. You mean you used ‘solution c’, or something similar to my proposed solution? If so, please show your code, so we can compare :slight_smile:

The core of the argorithm is that collection.remove() is lazy, as the algorithm is basically deciding whether to retain elements. Therefore removing is just setting a flag, and upon the next read access, that flag is used to decide whether to add the entity to the ‘second list’, which happens to use the same array as the ‘first list’.

Interesting. I have been working on a similar data structure that has a couple of differences. The first is that when elements are added, they aren’t visible during iteration until they are committed. This is to prevent cases where iterating through the list may produce more elements added to the list (which in turn may produce even more elements, etc).


BufferedIterator<String> iter = new BufferedIterator<String>();

iter.add("a");
iter.add("b");

iter.commitBuffer();  // Now "a" and "b" are available for iteration.

Think of an Event system where you loop through all of the Events and pass them to their receiving Entity to process. In handling that Event that Entity may generate more Events, which in turn may generate more.

The second is difference is that I also have an autoRemove(E element) method where the programmer can extend the data structure to automatically remove elements as it iterates.


BufferedIterator<Event> eventList = new BufferedIterator<Event>() {
    
    @Override public boolean autoRemove(Event event) {
       return event.isExpired();
    }

(You can also remove via a remove() method like a regular iterator). I have mostly finished implementations of both an insertion ordered and no-order-guaranteed versions but I have niggling little bugs I haven’t worked out yet.

I’ll have to clean the code up, get it working, and share it to see what folks think.

continuations, lazyness. I see you moving in the functional direction. What languages did you try out in the last couple of months? If you didn’t already, try out guile scheme and haskell probably.

Usually, for games, I find option A very efficient unless you are concurrently adding and deleting elements.

In that tiny 4K game I made, all the entities are in fact in one straight array. My solution for dealing with concurrency is just splitting the process into two parts. (Which is essentially what you did with Option C & D[Yours])


//creation sequence
for(int i = 0; i < array.length; i++){
        //creation of objects here            	
}  

//deletion sequence
for(int i = 0; i < array.length; i++){
        //deletion of objects here
        i--;//for each deletion
}

I was unsuccessful in finding a way to do this in one iteration. It gets very close, but there is always lingering bugs when you try to do additions and deletions in the same loop. I’ve always used just one array to handle entities.

Usually, for particles, I like to have a max particle pool and stop rendering when particles get too much on the screen (in which human eyes won’t tell the difference anyway.) For entities that keep expanding, then I just split the array into two parts.

Works fast and efficiently for me. I’ve been using this method since I’ve been coding in C++… so it just stuck around.

I’m talking about your proposed solution, your remove() algorithm marks the current item to be copied over, so when hasNext is called it is “back-copied” as I like to say it (you overwrite previous entries overtop of dead ones). This is exactly what I do. I also like that you made the class implement Iterable and Iterator, I do this as well. It makes using for-each statements in games acceptable.

This is a trap. These are not functional notions.

Thanks a lot for sharing this code, it was precisely what I was looking for and more elegant than anything I could have concocted myself…

Unless I’m missing the point you can remove entries from any collection while iterating using standard iterators:

final Iterator itr = list.iterator();
while( itr.hasNext() ) {
final T t = itr.next();
final boolean dead = …
if( dead ) {
itr.remove();
}
}

Obviously not as concise as the for-each syntactic sugar but simple nevertheless.

Removing entries from any array-based collection (e.g. ArrayList) is going to perform poorly whatever removal strategy you use (due to the array resizing under the hood) unless the collection is a queue or linked list.

If order is not important, than you can simply replace the element you want to delete with the last element in the list and then null the last element. Array backed bags take this approach.

How about reading the first post? It clearly explains all concepts that are discussed and how standard element removal is horribly slow.

  1. if you paid any attention to the code and the text, you’d have seen that my code solved the performance problem of removing many elements from an array backed collection.
  2. when removing elements there is no array resizing in collection classes.
  3. you seem to think a ‘queue’ is neither a linked list nor an array backed collection. what is it?

This collection (and text) explicitly states it solves the problem for cases where order is important.

I’ve been using method C, and didn’t find it too much of a hassle. But I’ll try out this collection, because it gives better looking code. (And doesn’t use double the amount of memory.)

Keep in mind this class is basically a wrapper over this simple algorithm:


int entityRetainCount=0;
for(int i=0; i<entityCount; i++) {
   Entity entity = entities[i];
   if(entity.isDead()) {
      continue;
   }

@@   entities[entityRetainCount++] = entity; // compact
   if(entity.isShooting()) {
      entities[entityCount++].add(new BulletEntity()); // append
   }
}
Arrays.fill(entities, entityRetainCount, entityCount, null); // cleanup freed slots
entityCount = entityRetainCount;

I just wondered: does using break when iterating over this collection cause issues? I don’t see a way of handling this case nicely (other than not calling break of course).

Just like the algorithm it wraps, it can’t be interrupted halfway.

Sorry if I came across as challenging, I was actually attempting to be constructive lol.

Running the test you posted (and a couple of others to compare add() and clear() ) to compare an ArrayList using Iterator.remove() against ReadWriteCollection shows no difference on the three machines/JVMs I tried it on, execution times were roughly the same irrespective of the size of the data, number of iterations, random or specific data, etc. In fact ArrayList was slightly faster overall.

OK hardly a scientific analysis but what I was trying (unsuccessfully) to point out is I couldn’t see how the ReadWriteCollection implementation solved the performance problem - see below.

Of course the main point of the ReadWriteCollection was the advantage of being able to concurrently add and remove whilst iterating rather than performance per se.

Resizing was a poor choice of word, I was referring to array-copies: using the simple ArrayList as an example, most remove() operations will result in the right-hand part of the underlying array being copied one step to the left (unless one happens to remove the last element). An analysis tool such as JProbe highlights the array copy as the highest-cost part of the test. So the remove() operation is linear in both implementations, but the actual bottle-neck is the same. That was what I was trying to point out.

Again poor terminology, forget queues, what I meant was linked lists (which by definition are not backed by an array) do not have the array-copy issue.

One question: ReadWriteCollection isn’t in fact a Java Collection (realised that when I tried to write a parameterized test!) - any reason for that?

The trouble with “linked lists” is two fold. The first is what do you mean? If you have an external container ‘node’, then they’re virtually useless for large scale problems (if performance is a concern). Random memory reads skyrocket. Bad. Linked lists where the chain is embedded is better (and can be a good to best choice in certain situations), but you’re still adding memory bloat which needs to be offset by how you’re walking data (again assuming performance is one of the large concerns).

I agree. Ram latency makes games stutter. That’s why I use linked/hashed arrays for constant time insertion and deletion O(2). Maintains 2 arrays (1 size of object and 1 size of integer for the hash-like ID system). But considering that a linked list would need pointers for next and prev, the memory it needs is quite smaller.

The caveat is that you can’t resize on the fly without a nasty overhead but with games, we usually know the maximum amount of entities we need beforehand.


/**
 *
 * @author Richard Eric M. Lope (Relminator)
 * @version 1.00 2013/3/04
 */

import java.awt.Graphics2D;


public class BulletManager 
{
	private Bullet[] bullets;
	private int size;
	
	private int numFreeBullets = 0;
	private int numActiveBullets = 0;
	
	private int[] freeBulletIndex;	
	
	
	public BulletManager( int size )
	{
		this.size = size;
		bullets = new Bullet[size];
		for( int i = 0; i < size; i++ )
		{
			bullets[i] = new Bullet();
		}
		
		numActiveBullets = 0;
		numFreeBullets = size;
		freeBulletIndex = new int[size];
		
		for( int i = 0; i < size; i++ )
		{
			freeBulletIndex[i] = i;
		}
		
	}
	
	public int getSize()
	{
		return size;
	}
	
	public int getActiveBullets()
	{
		return numActiveBullets;
	}
	
	public void addBullet( Bullet bullet )
	{
		
		if( numFreeBullets == 0 )
		{
			return;
		}
		
		numFreeBullets--;
		
		int index = freeBulletIndex[numFreeBullets];
		bullets[index] = bullet;
		
		
		numActiveBullets++;
		
	}
	
	void removeBullet( int index )
	{
		
		freeBulletIndex[numFreeBullets] = index;
   		numFreeBullets++;
		numActiveBullets--;
   		
	}
	
	public void updateBullets()
	{
		for( int i = 0; i < size; i++ )
		{
			if( bullets[i].isActive() )
			{
				bullets[i].update();
				if( (bullets[i].position.x < 0) || (bullets[i].position.x > Globals.SCREEN_WIDTH) ||
					(bullets[i].position.y < 0) || (bullets[i].position.y > Globals.SCREEN_HEIGHT) )
				{
					bullets[i].setActive(false) ;
					removeBullet(i);
				}
			}
		}
		
	}
	
	public void renderBullets( Screen screen, Graphics2D g, ImageAtlas imageAtlas )
	{
		
		for( int i = 0; i < size; i++ )
		{
			if( bullets[i].isActive() )
			{
				int frame = bullets[i].getFrame();
				g.drawImage( imageAtlas.getSprite(frame), 
							 (int)bullets[i].position.x - imageAtlas.getSprite(frame).getWidth()/2,
							 (int)bullets[i].position.y - imageAtlas.getSprite(frame).getHeight()/2,
							 screen );
			}
		}
		
	}
	
}


That’s what you think…

Cas :slight_smile: