PaperMC/Spigot-Server-Patches/0033-Optimize-TileEntity-ticking.patch

386 lines
15 KiB
Diff

From fa5a4c6926b45e62616bef1069f593d5b88d2166 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Mon, 11 Aug 2014 21:46:33 -0500
Subject: [PATCH] Optimize TileEntity ticking
Re-organizes the servers TileEntity Tick List to be bucketed by type.
This allows the server to skip buckets of Tile Entities that is known to
not have any tick function (half of them), skipping time spent iterating
them and checking if they are valid and in a loaded chunk. In other words,
a lot of "meta" time wasted on tile entities that would never do anything anyways.
This change also adds control into the interval of every TileEntity, giving
the server owner control on how fast a TileEntity ticks, slowing it down if they must
(Such as chest), to improve performance.
diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
index 0423ee9..09aa073 100644
--- a/src/main/java/net/minecraft/server/Chunk.java
+++ b/src/main/java/net/minecraft/server/Chunk.java
@@ -28,6 +28,7 @@ public class Chunk {
public final int locX;
public final int locZ;
private boolean w;
+ public org.github.paperspigot.ChunkTileEntityList tileEntityTickList; // PaperSpigot
public Map tileEntities;
public List[] entitySlices;
public boolean done;
@@ -76,6 +77,7 @@ public class Chunk {
this.b = new int[256];
this.c = new boolean[256];
this.tileEntities = new HashMap();
+ tileEntityTickList = new org.github.paperspigot.ChunkTileEntityList(world); // PaperSpigot
this.x = 4096;
this.entitySlices = new List[16];
this.world = world;
@@ -813,7 +815,7 @@ public class Chunk {
}
// Spigot End
- this.world.a(tileentity);
+ // this.world.a(tileentity); // Handled by Improved Tick List (Only loaded chunks iterate)
}
for (int i = 0; i < this.entitySlices.length; ++i) {
diff --git a/src/main/java/net/minecraft/server/TileEntity.java b/src/main/java/net/minecraft/server/TileEntity.java
index 3c5ec6f..50130ca 100644
--- a/src/main/java/net/minecraft/server/TileEntity.java
+++ b/src/main/java/net/minecraft/server/TileEntity.java
@@ -14,6 +14,7 @@ public class TileEntity {
public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getTileEntityTimings(this); // Spigot
private static final Logger a = LogManager.getLogger();
+ public boolean isAdded = false; // PaperSpigot - optimize contains checks
private static Map i = new HashMap();
private static Map j = new HashMap();
protected World world;
diff --git a/src/main/java/net/minecraft/server/TileEntityBeacon.java b/src/main/java/net/minecraft/server/TileEntityBeacon.java
index 09313ea..86142f9 100644
--- a/src/main/java/net/minecraft/server/TileEntityBeacon.java
+++ b/src/main/java/net/minecraft/server/TileEntityBeacon.java
@@ -45,10 +45,10 @@ public class TileEntityBeacon extends TileEntity implements IInventory {
public TileEntityBeacon() {}
public void h() {
- if (this.world.getTime() % 80L == 0L) {
+ // if (this.world.getTime() % 80L == 0L) { // PaperSpigot - controlled by Improved Tick handling
this.y();
this.x();
- }
+ // } // PaperSpigot
}
private void x() {
diff --git a/src/main/java/net/minecraft/server/TileEntityChest.java b/src/main/java/net/minecraft/server/TileEntityChest.java
index c900caf..e27716b 100644
--- a/src/main/java/net/minecraft/server/TileEntityChest.java
+++ b/src/main/java/net/minecraft/server/TileEntityChest.java
@@ -255,7 +255,7 @@ public class TileEntityChest extends TileEntity implements IInventory {
++this.ticks;
float f;
- if (!this.world.isStatic && this.o != 0 && (this.ticks + this.x + this.y + this.z) % 200 == 0) {
+ if (!this.world.isStatic && this.o != 0 && (this.ticks + this.x + this.y + this.z) % 10 == 0) { // PaperSpigot Reduced 200 -> 10 interval due to reduced tick rate from Improved Tick Handling
this.o = 0;
f = 5.0F;
List list = this.world.a(EntityHuman.class, AxisAlignedBB.a((double) ((float) this.x - f), (double) ((float) this.y - f), (double) ((float) this.z - f), (double) ((float) (this.x + 1) + f), (double) ((float) (this.y + 1) + f), (double) ((float) (this.z + 1) + f)));
diff --git a/src/main/java/net/minecraft/server/TileEntityEnderChest.java b/src/main/java/net/minecraft/server/TileEntityEnderChest.java
index b205ab7..d76a2b2 100644
--- a/src/main/java/net/minecraft/server/TileEntityEnderChest.java
+++ b/src/main/java/net/minecraft/server/TileEntityEnderChest.java
@@ -11,7 +11,7 @@ public class TileEntityEnderChest extends TileEntity {
public void h() {
super.h();
- if (++this.k % 20 * 4 == 0) {
+ if (++this.k % 4 == 0) { // PaperSpigot Reduced (20 * 4) -> 4 interval due to reduced tick rate from Improved Tick Handling
this.world.playBlockAction(this.x, this.y, this.z, Blocks.ENDER_CHEST, 1, this.j);
}
diff --git a/src/main/java/net/minecraft/server/TileEntityLightDetector.java b/src/main/java/net/minecraft/server/TileEntityLightDetector.java
index 143cffb..de33df0 100644
--- a/src/main/java/net/minecraft/server/TileEntityLightDetector.java
+++ b/src/main/java/net/minecraft/server/TileEntityLightDetector.java
@@ -5,7 +5,7 @@ public class TileEntityLightDetector extends TileEntity {
public TileEntityLightDetector() {}
public void h() {
- if (this.world != null && !this.world.isStatic && this.world.getTime() % 20L == 0L) {
+ if (this.world != null && !this.world.isStatic /*&& this.world.getTime() % 20L == 0L*/) { // PaperSpigot - interval controlled by Improved Tick Handling
this.h = this.q();
if (this.h instanceof BlockDaylightDetector) {
((BlockDaylightDetector) this.h).e(this.world, this.x, this.y, this.z);
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index 33c228b..940e1c8 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -58,7 +58,7 @@ public abstract class World implements IBlockAccess {
};
// Spigot end
protected List f = new ArrayList();
- public Set tileEntityList = new HashSet(); // CraftBukkit - ArrayList -> HashSet
+ public Set tileEntityList = new org.github.paperspigot.WorldTileEntityList(this); // CraftBukkit - ArrayList -> HashSet // PaperSpigot
private List a = new ArrayList();
private List b = new ArrayList();
public List players = new ArrayList();
@@ -1529,13 +1529,13 @@ public abstract class World implements IBlockAccess {
if (tileentity.r()) {
iterator.remove();
- if (this.isChunkLoaded(tileentity.x >> 4, tileentity.z >> 4)) {
+ // if (this.isChunkLoaded(tileentity.x >> 4, tileentity.z >> 4)) { // PaperSpigot - will always be loaded
Chunk chunk = this.getChunkAt(tileentity.x >> 4, tileentity.z >> 4);
if (chunk != null) {
chunk.f(tileentity.x & 15, tileentity.y, tileentity.z & 15);
}
- }
+ // } // PaperSpigot
}
}
diff --git a/src/main/java/org/github/paperspigot/ChunkTileEntityList.java b/src/main/java/org/github/paperspigot/ChunkTileEntityList.java
new file mode 100644
index 0000000..e673ba9
--- /dev/null
+++ b/src/main/java/org/github/paperspigot/ChunkTileEntityList.java
@@ -0,0 +1,121 @@
+package org.github.paperspigot;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import net.minecraft.server.*;
+import net.minecraft.util.gnu.trove.map.hash.TObjectIntHashMap;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+
+public class ChunkTileEntityList {
+ final ListMultimap<Class, TileEntity> tickList = ArrayListMultimap.create();
+
+ private static final TObjectIntHashMap<Class> tileEntityTickIntervals = new TObjectIntHashMap<Class>() {{
+ // Use 0 for no ticking
+ // These TE's have empty tick methods, doing nothing. Never bother ticking them.
+ for (Class<? extends TileEntity> ignored : new Class[]{
+ TileEntityRecordPlayer.class,
+ TileEntityDispenser.class,
+ TileEntityDropper.class,
+ TileEntitySign.class,
+ TileEntityNote.class,
+ TileEntityEnderPortal.class,
+ TileEntityCommand.class,
+ TileEntitySkull.class,
+ TileEntityComparator.class,
+ TileEntityFlowerPot.class
+ }) {
+ put(ignored, 0);
+ }
+
+ // does findPlayer lookup, so this helps performance to slow down
+ put(TileEntityChest.class, 20);
+ put(TileEntityEnderChest.class, 20);
+ put(TileEntityEnchantTable.class, 20);
+
+ // Slow things down that players won't notice due to craftbukkit "wall time" patches.
+ put(TileEntityFurnace.class, 10);
+ put(TileEntityBrewingStand.class, 10);
+
+ // Vanilla controlled values - These are checks already done in vanilla, so don't tick on ticks we know
+ // won't do anything anyways
+ put(TileEntityBeacon.class, 80);
+ put(TileEntityLightDetector.class, 20);
+ }};
+
+ public static Integer getInterval(Class cls) {
+ Integer tickInterval = tileEntityTickIntervals.get(cls);
+ return tickInterval != null ? tickInterval : 1;
+ }
+
+ private final World world;
+
+ public ChunkTileEntityList(World world) {
+ this.world = world;
+ }
+
+ public Iterator iterator() {
+ return new ChunkTileEntityIterator();
+ }
+
+
+ public boolean add(TileEntity entity) {
+ entity.isAdded = true;
+ return tickList.put(entity.getClass(), entity);
+ }
+
+ public boolean remove(TileEntity entity) {
+ entity.isAdded = false;
+ return tickList.remove(entity.getClass(), entity);
+ }
+
+ private class ChunkTileEntityIterator implements Iterator {
+ Iterator<Map.Entry<Class, Collection<TileEntity>>> typeIterator;
+ Map.Entry<Class, Collection<TileEntity>> curType = null;
+ Iterator<TileEntity> listIterator = null;
+
+ {
+ typeIterator = tickList.asMap().entrySet().iterator();
+ nextType();
+ }
+
+ private boolean nextType() {
+ if (typeIterator.hasNext()) {
+ curType = typeIterator.next();
+ final Integer interval = ChunkTileEntityList.getInterval(curType.getKey());
+ if (world.getTime() % interval != 0) {
+ listIterator = curType.getValue().iterator();
+ } else {
+ listIterator = null;
+ }
+ return true;
+ } else {
+ curType = null;
+ listIterator = null;
+ return false;
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ do {
+ if (listIterator != null && listIterator.hasNext()) {
+ return true;
+ }
+ } while (nextType());
+ return false;
+ }
+
+ @Override
+ public Object next() {
+ return listIterator.next();
+ }
+
+ @Override
+ public void remove() {
+ listIterator.remove();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/github/paperspigot/WorldTileEntityList.java b/src/main/java/org/github/paperspigot/WorldTileEntityList.java
new file mode 100644
index 0000000..d783741
--- /dev/null
+++ b/src/main/java/org/github/paperspigot/WorldTileEntityList.java
@@ -0,0 +1,106 @@
+package org.github.paperspigot;
+
+import com.google.common.collect.Sets;
+import net.minecraft.server.Chunk;
+import net.minecraft.server.TileEntity;
+import net.minecraft.server.World;
+import net.minecraft.server.WorldServer;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+public class WorldTileEntityList extends HashSet {
+ final Set<ChunkTileEntityList> tickList = Sets.newHashSet();
+ private final WorldServer world;
+
+ public WorldTileEntityList(World world) {
+ this.world = (WorldServer) world;
+ }
+
+ /**
+ * Adds the TileEntity to the tick list only if it is expected to tick
+ */
+ @Override
+ public boolean add(Object o) {
+ return o instanceof TileEntity && ChunkTileEntityList.getInterval(o.getClass()) > 0 && add((TileEntity) o);
+ }
+
+ private boolean add(TileEntity entity) {
+ if (entity.isAdded) {
+ return false;
+ }
+
+ Chunk chunk = world.getChunkIfLoaded(entity.x >> 4, entity.z >> 4);
+ return chunk != null && chunk.tileEntityTickList.add(entity);
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ if (!(o instanceof TileEntity)) {
+ return false;
+ }
+ TileEntity entity = (TileEntity) o;
+ Chunk chunk = world.getChunkIfLoaded(entity.x >> 4, entity.z >> 4);
+ if (chunk != null) {
+ return chunk.tileEntityTickList.remove(entity);
+ }
+ return true;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new WorldTileEntityIterator();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return o instanceof TileEntity && ((TileEntity) o).isAdded;
+ }
+
+ private class WorldTileEntityIterator implements Iterator {
+ Iterator<Chunk> chunkIterator;
+ Iterator<ChunkTileEntityList> chunkTileEntityListIterator;
+
+ {
+ chunkIterator = world.chunkProviderServer.chunks.values().iterator();
+ nextChunk();
+ }
+
+ private boolean nextChunk() {
+ if (chunkIterator.hasNext()) {
+ final Chunk chunk = chunkIterator.next();
+ // Skip chunks queued for unload, and save isLoaded checks
+ if (!world.chunkProviderServer.unloadQueue.contains(chunk.locX, chunk.locZ)) {
+ chunkTileEntityListIterator = chunk.tileEntityTickList.iterator();
+ } else {
+ chunkTileEntityListIterator = null;
+ }
+ return true;
+ } else {
+ chunkTileEntityListIterator = null;
+ return false;
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ do {
+ if (chunkTileEntityListIterator != null && chunkTileEntityListIterator.hasNext()) {
+ return true;
+ }
+ } while (nextChunk());
+ return false;
+ }
+
+ @Override
+ public Object next() {
+ return chunkTileEntityListIterator.next();
+ }
+
+ @Override
+ public void remove() {
+ chunkTileEntityListIterator.remove();
+ }
+ }
+}
\ No newline at end of file
--
1.9.1