papermc/Spigot-Server-Patches/0071-Optimize-Entity-and-Tile-Entity-Removal.patch

233 lines
9.3 KiB
Diff

From 397ba6e9dffea1e1a2541596adb6b037088b645f Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Fri, 16 Oct 2015 22:14:48 -0500
Subject: [PATCH] Optimize Entity and Tile Entity Removal
Java's implementation of List.removeAll is extremely slow. This is
currently causing lots of TPS loss when lots of chunk unload activity
occurs, as the process iterates the removal list for every entry in the
source list, resulting in O(n^2) performance.
This change will switch the process to instead iterate over the
removal list, and marking a boolean that its removed.
Then, we then iterate the source list and use a compaction technique
that skips any object marked for removal.
After all live objects are compacted down, we do a range
removal to clear out any removed objects at the end of the current list.
This gives us O(n) performance and a much cheaper overall operation.
Compaction technique was originally used by CyberTiger in a different
implementation.
Finally, we remove MOST single .remove() calls, and run a 2nd compaction
after ticking in order to remove the singles.
This also fixes a bug with Tick Position in the Tick limiter, where
previously .removeAll would shift entity index order but the tick
position was never moved to its new location.
diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java
index 68126c4..3b995c5 100644
--- a/src/main/java/net/minecraft/server/Entity.java
+++ b/src/main/java/net/minecraft/server/Entity.java
@@ -32,7 +32,12 @@ import org.bukkit.event.entity.EntityPortalEvent;
import org.bukkit.plugin.PluginManager;
// CraftBukkit end
-public abstract class Entity implements ICommandListener {
+// PaperSpigot start - Optimized TE/E Removal
+public abstract class Entity implements ICommandListener, org.github.paperspigot.OptimizedRemoveAllArrayList.Marker {
+ private boolean needsRemoved = false;
+ public boolean isToBeRemoved() { return needsRemoved; }
+ public void setRemovalState(boolean removalState) { needsRemoved = removalState; }
+ // PaperSpigot End
// CraftBukkit start
private static final int CURRENT_LEVEL = 2;
diff --git a/src/main/java/net/minecraft/server/TileEntity.java b/src/main/java/net/minecraft/server/TileEntity.java
index 3fc6450..6d13a55 100644
--- a/src/main/java/net/minecraft/server/TileEntity.java
+++ b/src/main/java/net/minecraft/server/TileEntity.java
@@ -9,7 +9,12 @@ import org.apache.logging.log4j.Logger;
import org.spigotmc.CustomTimingsHandler; // Spigot
import org.bukkit.inventory.InventoryHolder; // CraftBukkit
-public abstract class TileEntity {
+// PaperSpigot start - Optimized TE/E Removal
+public abstract class TileEntity implements org.github.paperspigot.OptimizedRemoveAllArrayList.Marker {
+ private boolean needsRemoved = false;
+ public boolean isToBeRemoved() { return needsRemoved; }
+ public void setRemovalState(boolean removalState) { needsRemoved = removalState; }
+ // PaperSpigot end
public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getTileEntityTimings(this); // Spigot
private static final Logger a = LogManager.getLogger();
@@ -126,7 +131,7 @@ public abstract class TileEntity {
}
public boolean x() {
- return this.d;
+ return this.d && !isToBeRemoved(); // PaperSpigot - Optimize TE/E Removal
}
public void y() {
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index 13ab789..0c1ed6c 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -32,7 +32,7 @@ public abstract class World implements IBlockAccess {
private int a = 63;
protected boolean e;
// Spigot start - guard entity list from removals
- public final List<Entity> entityList = new java.util.ArrayList<Entity>()
+ public final List<Entity> entityList = new org.github.paperspigot.OptimizedRemoveAllArrayList<Entity>() // PaperSpigot - Optimized TE/E Removal
{
@Override
public Entity remove(int index)
@@ -59,7 +59,7 @@ public abstract class World implements IBlockAccess {
// Spigot end
protected final List<Entity> g = Lists.newArrayList();
//public final List<TileEntity> h = Lists.newArrayList(); // PaperSpigot - Remove unused list
- public final List<TileEntity> tileEntityList = Lists.newArrayList();
+ public final List<TileEntity> tileEntityList = new org.github.paperspigot.OptimizedRemoveAllArrayList<TileEntity>(); // PaperSpigot - Optimized TE/E Removal
private final List<TileEntity> b = Lists.newArrayList();
private final List<TileEntity> c = Lists.newArrayList();
public final List<EntityHuman> players = Lists.newArrayList();
@@ -1462,13 +1462,14 @@ public abstract class World implements IBlockAccess {
}
guardEntityList = false; // Spigot
- this.entityList.remove(this.tickPosition--); // CraftBukkit - Use field for loop variable
+ this.entityList.remove(entity); // CraftBukkit - Use field for loop variable // PaperSpigot - Optimized TE/E Removal
guardEntityList = true; // Spigot
this.b(entity);
}
this.methodProfiler.b();
}
+ this.entityList.removeAll(null); // PaperSpigot - Optimize TE/E Removal
guardEntityList = false; // Spigot
timings.entityTick.stopTiming(); // Spigot
@@ -1524,13 +1525,14 @@ public abstract class World implements IBlockAccess {
if (tileentity.x()) {
tilesThisCycle--;
- this.tileEntityList.remove(tileTickPosition--);
+ this.tileEntityList.remove(tileentity); // PaperSpigot - Optimized TE/E Removal
//this.h.remove(tileentity); // PaperSpigot - Remove unused list
if (this.isLoaded(tileentity.getPosition())) {
this.getChunkAtWorldCoords(tileentity.getPosition()).e(tileentity.getPosition());
}
}
}
+ this.tileEntityList.removeAll(null); // PaperSpigot - Optimized TE/E Removal
timings.tileEntityTick.stopTiming(); // Spigot
timings.tileEntityPending.startTiming(); // Spigot
@@ -2619,6 +2621,7 @@ public abstract class World implements IBlockAccess {
while (iterator.hasNext()) {
Entity entity = (Entity) iterator.next();
+ if (entity.isToBeRemoved()) { continue; } // PaperSpigot - Optimized TE/E Removal
if (oclass.isAssignableFrom(entity.getClass()) && predicate.apply((T) entity)) { // CraftBukkit - fix decompile error
arraylist.add(entity);
@@ -2703,6 +2706,7 @@ public abstract class World implements IBlockAccess {
while (iterator.hasNext()) {
Entity entity = (Entity) iterator.next();
+ if (entity.isToBeRemoved()) { continue; } // PaperSpigot - Optimized TE/E Removal
// CraftBukkit start - Split out persistent check, don't apply it to special persistent mobs
if (entity instanceof EntityInsentient) {
EntityInsentient entityinsentient = (EntityInsentient) entity;
diff --git a/src/main/java/org/github/paperspigot/OptimizedRemoveAllArrayList.java b/src/main/java/org/github/paperspigot/OptimizedRemoveAllArrayList.java
new file mode 100644
index 0000000..6d4388a
--- /dev/null
+++ b/src/main/java/org/github/paperspigot/OptimizedRemoveAllArrayList.java
@@ -0,0 +1,78 @@
+package org.github.paperspigot;
+
+import org.github.paperspigot.OptimizedRemoveAllArrayList.Marker;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Improved algorithim for bulk removing entries from a list
+ *
+ * WARNING: This system only works on Identity Based lists,
+ * unlike traditional .removeAll() that operates on object equality.
+ *
+ */
+public class OptimizedRemoveAllArrayList<T extends Marker> extends ArrayList<T> {
+ public OptimizedRemoveAllArrayList(int initialCapacity) {
+ super(initialCapacity);
+ }
+
+ public OptimizedRemoveAllArrayList() {
+ }
+
+ public OptimizedRemoveAllArrayList(Collection<? extends T> c) {
+ super(c);
+ }
+
+ public OptimizedRemoveAllArrayList<T> clone() {
+ return new OptimizedRemoveAllArrayList<T>(this);
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends T> c) {
+ for (T t : c) {
+ t.setRemovalState(false);
+ }
+ return super.addAll(c);
+ }
+
+ @Override
+ public boolean add(T t) {
+ t.setRemovalState(false);
+ return super.add(t);
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ ((Marker) o).setRemovalState(true);
+ return true;
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> c) {
+ if (c != null) {
+ for (Object o : c) {
+ ((Marker) o).setRemovalState(true);
+ }
+ }
+
+ int size = size();
+ int insertAt = 0;
+
+ for (int i = 0; i < size; i++) {
+ T el = get(i);
+
+ if (el != null && !el.isToBeRemoved()) {
+ set(insertAt++, el);
+ }
+ }
+ subList(insertAt, size).clear();
+
+ return size() != size;
+ }
+
+ public interface Marker {
+ boolean isToBeRemoved();
+ void setRemovalState(boolean removalState);
+ }
+}
--
2.6.2