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

Naive attemptsA 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.

Solution BIterating 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.

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.

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(...).

@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

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'.

Hi, appreciate more people! Σ ♥ = ¾Learn how to award medals... and work your way up the social rankings!

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).

1 2 3 4 5 6

BufferedIterator<String> iter = newBufferedIterator<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.

(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.

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'.

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])

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.

@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

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'.

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.

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.

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.

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?

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

Quote

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.

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.

Quote

2. when removing elements there is no array resizing in collection classes.

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.

Quote

3. you seem to think a 'queue' is neither a linked list nor an array backed collection. what is it?

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).

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.

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.

We can conclude that for these data-sizes, we have a 3x, 4x, 25x and 246x performance increase, which is obviously significant and shows the compact-sweep algorithm scales exceptionally well (linearly).

You're misinformed, unless you actually notice a 'stutter' of a couple of nanoseconds.

I actually notice those things because I have a dinosaur of a netbook. And also because I also code games on a 66mhz console. Ram latency is also the reason why I went with simple boxed grid spatial partitioning in lieu of an octree for a 3D renderer on the said console.

But this is java so these things might not matter (JVM stuff I have no idea about).

It's like acusing a whale of triggering the earthquake that caused the tsunami, by reasoning: 'the whale can do that, because it's bloody big' - while it's 6-7 orders of magnitude off of being relevant.

And yes, boxed grids are much, much cache-friendlier than quad-trees and oct-trees. But even a tree (with continuous cache misses while traversing nodes) won't cause stuttering, it will just be slower, consistently.

I have no experience with 66MHz hardware, but even with the crappiest 1GHz Atom cache misses won't cause stuttering.

Hi, appreciate more people! Σ ♥ = ¾Learn how to award medals... and work your way up the social rankings!

We're moving, sorting (and rendering) 150,000 constantly moving entities at 60fps, using a non-square grid of buckets, which are sorted by gnome-sort. Don't let the 'performance characteristics' of O(n^2) fool you, I found it to be the best algorithm for this specific use case (sorting on Y, then X, to implement the painters algorithm).

Hi, appreciate more people! Σ ♥ = ¾Learn how to award medals... and work your way up the social rankings!

We're moving, sorting (and rendering) 150,000 constantly moving entities at 60fps, using a non-square grid of buckets, which are sorted by gnome-sort. Don't let the 'performance characteristics' of O(n^2) fool you, I found it to be the best algorithm for this specific use case (sorting on Y, then X, to implement the painters algorithm).

Yeah but how do I actually do sorting with that collection? Or am I asking something plain stupid here? It doesn't look like this is possible with your code yet...

It's not a sortable collection; the whole point of it is that you need the order in which things are placed in it maintained. The example Riven gave there was not related to this collection; it's just an example of how misleading big-O notation is when one doesn't know the actual cost of the algorithm on its expected workload parameters.

java-gaming.org is not responsible for the content posted by its members, including references to external websites,
and other references that may or may not have a relation with our primarily
gaming and game production oriented community.
inquiries and complaints can be sent via email to the info‑account of the
company managing the website of java‑gaming.org