Handle removing entity while ticking. Fixes BUKKIT-1331

If a plugin causes an entity to be removed from the world while the
world is ticking entities the ticking loop gets out of sync and fails due
to trying to go beyond the end of the entity array. To ensure this doesn't
happen we store the loop position as a field so we can fix it up in the
entity remove method just like the tick method does when it removes an
entity.
This commit is contained in:
Travis Watkins 2014-02-10 14:59:44 -06:00
parent 1113d54dae
commit 826643c606

View File

@ -14,7 +14,6 @@ import java.util.concurrent.Callable;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.util.CraftMagicNumbers; import org.bukkit.craftbukkit.util.CraftMagicNumbers;
import org.bukkit.craftbukkit.util.LongHashSet; import org.bukkit.craftbukkit.util.LongHashSet;
import org.bukkit.craftbukkit.util.UnsafeList;
import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator;
import org.bukkit.craftbukkit.CraftServer; import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.CraftWorld;
@ -71,6 +70,7 @@ public abstract class World implements IBlockAccess {
public long ticksPerAnimalSpawns; public long ticksPerAnimalSpawns;
public long ticksPerMonsterSpawns; public long ticksPerMonsterSpawns;
public boolean populating; public boolean populating;
private int tickPosition;
// CraftBukkit end // CraftBukkit end
private ArrayList M; private ArrayList M;
private boolean N; private boolean N;
@ -974,7 +974,14 @@ public abstract class World implements IBlockAccess {
this.getChunkAt(i, j).b(entity); this.getChunkAt(i, j).b(entity);
} }
this.entityList.remove(entity); // CraftBukkit start - Decrement loop variable field if we've already ticked this entity
int index = this.entityList.indexOf(entity);
if (index <= this.tickPosition) {
this.tickPosition--;
}
this.entityList.remove(index);
// CraftBukkit end
this.b(entity); this.b(entity);
} }
@ -1182,10 +1189,11 @@ public abstract class World implements IBlockAccess {
this.f.clear(); this.f.clear();
this.methodProfiler.c("regular"); this.methodProfiler.c("regular");
for (i = 0; i < this.entityList.size(); ++i) { // CraftBukkit start - Use field for loop variable
entity = (Entity) this.entityList.get(i); for (this.tickPosition = 0; this.tickPosition < this.entityList.size(); ++this.tickPosition) {
entity = (Entity) this.entityList.get(this.tickPosition);
// CraftBukkit start - Don't tick entities in chunks queued for unload // Don't tick entities in chunks queued for unload
ChunkProviderServer chunkProviderServer = ((WorldServer) this).chunkProviderServer; ChunkProviderServer chunkProviderServer = ((WorldServer) this).chunkProviderServer;
if (chunkProviderServer.unloadQueue.contains(MathHelper.floor(entity.locX) >> 4, MathHelper.floor(entity.locZ) >> 4)) { if (chunkProviderServer.unloadQueue.contains(MathHelper.floor(entity.locX) >> 4, MathHelper.floor(entity.locZ) >> 4)) {
continue; continue;
@ -1222,7 +1230,7 @@ public abstract class World implements IBlockAccess {
this.getChunkAt(j, k).b(entity); this.getChunkAt(j, k).b(entity);
} }
this.entityList.remove(i--); this.entityList.remove(this.tickPosition--); // CraftBukkit - Use field for loop variable
this.b(entity); this.b(entity);
} }