From a4e202676e2bdc7ac16b62159a028fa3406f586b Mon Sep 17 00:00:00 2001 From: Aikar Date: Sun, 24 Aug 2014 21:19:36 -0500 Subject: [PATCH] Implement optimized Tile Entity ticking --- ...handling-out-of-the-chest-tick-loop.patch} | 36 ++- .../Optimize-TileEntity-Ticking.patch | 283 ++++++++++++++++++ 2 files changed, 303 insertions(+), 16 deletions(-) rename Spigot-Server-Patches/{Don-t-tick-chests.patch => Move-sound-handling-out-of-the-chest-tick-loop.patch} (89%) create mode 100644 Spigot-Server-Patches/Optimize-TileEntity-Ticking.patch diff --git a/Spigot-Server-Patches/Don-t-tick-chests.patch b/Spigot-Server-Patches/Move-sound-handling-out-of-the-chest-tick-loop.patch similarity index 89% rename from Spigot-Server-Patches/Don-t-tick-chests.patch rename to Spigot-Server-Patches/Move-sound-handling-out-of-the-chest-tick-loop.patch index 03ad3f7454..f554c466f8 100644 --- a/Spigot-Server-Patches/Don-t-tick-chests.patch +++ b/Spigot-Server-Patches/Move-sound-handling-out-of-the-chest-tick-loop.patch @@ -1,22 +1,25 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Iceee -Date: Fri, 11 Jul 2014 01:31:43 -0500 -Subject: [PATCH] Don't tick chests +Date: Mon, 11 Aug 2014 23:03:47 -0500 +Subject: [PATCH] Move sound handling out of the chest tick loop +This allows us to disable ticking chests and enderchests without any +noticeable difference to players diff --git a/src/main/java/net/minecraft/server/TileEntityChest.java b/src/main/java/net/minecraft/server/TileEntityChest.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/TileEntityChest.java +++ b/src/main/java/net/minecraft/server/TileEntityChest.java @@ -0,0 +0,0 @@ public class TileEntityChest extends TileEntity implements IInventory { - } + } - public void h() { -+ // PaperSpigot start - Ixnay on the Chest tick-ay + this.n = this.m; ++ ++ // PaperSpigot start - Move chest sound handling out of the tick loop + /* - super.h(); - if (this.world == null) return; // CraftBukkit - this.i(); + f = 0.1F; + double d0; + @@ -0,0 +0,0 @@ public class TileEntityChest extends TileEntity implements IInventory { this.m = 0.0F; } @@ -30,7 +33,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 if (this.world == null) return; // CraftBukkit this.world.playBlockAction(this.x, this.y, this.z, this.q(), 1, this.o); -+ // PaperSpigot start - Move chest open sound handling down here ++ // PaperSpigot start - Move chest open sound handling down to here + this.i(); + double d0; + @@ -57,7 +60,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 if (this.world == null) return; // CraftBukkit this.world.playBlockAction(this.x, this.y, this.z, this.q(), 1, this.o); -+ // PaperSpigot start - Move chest close sound handling down here ++ // PaperSpigot start - Move chest close sound handling down to here + this.i(); + double d0; + @@ -85,14 +88,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/net/minecraft/server/TileEntityEnderChest.java +++ b/src/main/java/net/minecraft/server/TileEntityEnderChest.java @@ -0,0 +0,0 @@ public class TileEntityEnderChest extends TileEntity { - public TileEntityEnderChest() {} + } - public void h() { -+ // PaperSpigot start - Ixnay on the EnderChest tick-ay + this.i = this.a; ++ ++ // PaperSpigot start - Move chest sound handling out of the tick loop + /* - super.h(); - if (++this.k % 20 * 4 == 0) { - this.world.playBlockAction(this.x, this.y, this.z, Blocks.ENDER_CHEST, 1, this.j); + float f = 0.1F; + double d0; + @@ -0,0 +0,0 @@ public class TileEntityEnderChest extends TileEntity { this.a = 0.0F; } diff --git a/Spigot-Server-Patches/Optimize-TileEntity-Ticking.patch b/Spigot-Server-Patches/Optimize-TileEntity-Ticking.patch new file mode 100644 index 0000000000..91a250636d --- /dev/null +++ b/Spigot-Server-Patches/Optimize-TileEntity-Ticking.patch @@ -0,0 +1,283 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Sun, 24 Aug 2014 21:35:11 -0400 +Subject: [PATCH] Optimize TileEntity Ticking + +Re-organizes the servers TileEntity Tick List to be bucketed based on their tick interval. + +We now will not even store a Tile Entity 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. + +Then by reducing chests to 1 in 20 ticks, we cut out 95% of isLoaded checks and findPlayer +calls on chests, and 100% of the checks for Signs, the 2 most popular Tile Entities. + +This cuts out a massive amount of checks revolving around TileEntity ticking. +Servers with large amounts of TileEntities should see significant improvement. + +Finally, this then spreads out the ticking of reduced-rate TileEntities so that they +do not all tick on the same tick, distributing the load of some TileEntities like Chest. + +diff --git a/src/main/java/net/minecraft/server/TileEntity.java b/src/main/java/net/minecraft/server/TileEntity.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/TileEntity.java ++++ b/src/main/java/net/minecraft/server/TileEntity.java +@@ -0,0 +0,0 @@ public class TileEntity { + } + } + } ++ ++ // Optimized TileEntity Tick changes ++ private static int tileEntityCounter = 0; ++ public boolean isAdded = false; ++ public int tileId = tileEntityCounter++; ++ + // Spigot end + + public TileEntity() {} +diff --git a/src/main/java/net/minecraft/server/TileEntityBeacon.java b/src/main/java/net/minecraft/server/TileEntityBeacon.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/TileEntityBeacon.java ++++ b/src/main/java/net/minecraft/server/TileEntityBeacon.java +@@ -0,0 +0,0 @@ public class TileEntityBeacon extends TileEntity implements IInventory { + public TileEntityBeacon() {} + + public void h() { +- if (this.world.getTime() % 80L == 0L) { ++ if (true || this.world.getTime() % 80L == 0L) { // PaperSpigot - controlled by Improved Tick handling + this.y(); + this.x(); + } +diff --git a/src/main/java/net/minecraft/server/TileEntityChest.java b/src/main/java/net/minecraft/server/TileEntityChest.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/TileEntityChest.java ++++ b/src/main/java/net/minecraft/server/TileEntityChest.java +@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/TileEntityEnderChest.java ++++ b/src/main/java/net/minecraft/server/TileEntityEnderChest.java +@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/TileEntityLightDetector.java ++++ b/src/main/java/net/minecraft/server/TileEntityLightDetector.java +@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/World.java ++++ b/src/main/java/net/minecraft/server/World.java +@@ -0,0 +0,0 @@ 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.spigotmc.WorldTileEntityList(this); // CraftBukkit - ArrayList -> HashSet + private List a = new ArrayList(); + private List b = new ArrayList(); + public List players = new ArrayList(); +diff --git a/src/main/java/org/spigotmc/WorldTileEntityList.java b/src/main/java/org/spigotmc/WorldTileEntityList.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/org/spigotmc/WorldTileEntityList.java +@@ -0,0 +0,0 @@ ++package org.spigotmc; ++ ++import com.google.common.collect.ArrayListMultimap; ++import com.google.common.collect.Maps; ++import com.google.common.collect.Multimap; ++import net.minecraft.server.*; ++import net.minecraft.util.gnu.trove.map.hash.TObjectIntHashMap; ++ ++import java.util.Collection; ++import java.util.HashSet; ++import java.util.Iterator; ++import java.util.Map; ++ ++public class WorldTileEntityList extends HashSet { ++ private static final TObjectIntHashMap> tileEntityTickIntervals = ++ new TObjectIntHashMap>() {{ ++ // Use -1 for no ticking ++ // These TE's have empty tick methods, doing nothing. Never bother ticking them. ++ for (Class ignored : new Class[]{ ++ TileEntityChest.class, // PaperSpigot - Don't tick chests either ++ TileEntityEnderChest.class, // PaperSpigot - Don't tick chests either ++ TileEntityRecordPlayer.class, ++ TileEntityDispenser.class, ++ TileEntityDropper.class, ++ TileEntitySign.class, ++ TileEntityNote.class, ++ TileEntityEnderPortal.class, ++ TileEntityCommand.class, ++ TileEntitySkull.class, ++ TileEntityComparator.class, ++ TileEntityFlowerPot.class ++ }) { ++ put(ignored, -1); ++ } ++ ++ // does findPlayer lookup, so this helps performance to slow down ++ 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); ++ }}; ++ private static int getInterval(Class cls) { ++ int tickInterval = tileEntityTickIntervals.get(cls); ++ return tickInterval != 0 ? tickInterval : 1; ++ } ++ ++ private static int getBucketId(TileEntity entity, Integer interval) { ++ return entity.tileId % interval; ++ } ++ ++ private final Map> tickList = Maps.newHashMap(); ++ private final WorldServer world; ++ ++ public WorldTileEntityList(World world) { ++ this.world = (WorldServer) world; ++ } ++ ++ ++ private Multimap getBucket(int interval) { ++ Multimap intervalBucket = tickList.get(interval); ++ if (intervalBucket == null) { ++ intervalBucket = ArrayListMultimap.create(); ++ tickList.put(interval, intervalBucket); ++ } ++ return intervalBucket; ++ } ++ ++ /** ++ * Adds the TileEntity to the tick list only if it is expected to tick ++ */ ++ @Override ++ public boolean add(TileEntity entity) { ++ if (entity.isAdded) { ++ return false; ++ } ++ ++ int interval = getInterval(entity.getClass()); ++ if (interval > 0) { ++ entity.isAdded = true; ++ int bucket = getBucketId(entity, interval); ++ Multimap typeBucket = getBucket(interval); ++ return typeBucket.put(bucket, entity); ++ } ++ return false; ++ } ++ ++ @Override ++ public boolean remove(Object o) { ++ if (!(o instanceof TileEntity)) { ++ return false; ++ } ++ TileEntity entity = (TileEntity) o; ++ if (!entity.isAdded) { ++ return false; ++ } ++ entity.isAdded = false; ++ int interval = getInterval(entity.getClass()); ++ int bucket = getBucketId(entity, interval); ++ Multimap typeBucket = getBucket(interval); ++ return typeBucket.remove(bucket, entity); ++ } ++ ++ @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 { ++ private final Iterator>> intervalIterator; ++ private Map.Entry> intervalMap = null; ++ private Iterator listIterator = null; ++ ++ protected WorldTileEntityIterator() { ++ intervalIterator = tickList.entrySet().iterator(); ++ nextInterval(); ++ } ++ ++ private boolean nextInterval() { ++ listIterator = null; ++ if (intervalIterator.hasNext()) { ++ intervalMap = intervalIterator.next(); ++ ++ final Integer interval = intervalMap.getKey(); ++ final Multimap buckets = intervalMap.getValue(); ++ ++ int bucket = (int) (world.getTime() % interval); ++ ++ if (!buckets.isEmpty() && buckets.containsKey(bucket)) { ++ final Collection tileList = buckets.get(bucket); ++ ++ if (tileList != null && !tileList.isEmpty()) { ++ listIterator = tileList.iterator(); ++ return true; ++ } ++ } ++ } ++ ++ return false; ++ ++ } ++ ++ @Override ++ public boolean hasNext() { ++ do { ++ if (listIterator != null && listIterator.hasNext()) { ++ return true; ++ } ++ } while (nextInterval()); ++ return false; ++ } ++ ++ @Override ++ public TileEntity next() { ++ return listIterator.next(); ++ } ++ ++ @Override ++ public void remove() { ++ listIterator.remove(); ++ } ++ } ++} +-- \ No newline at end of file