diff --git a/Spigot-Server-Patches/0306-Optimize-Hoppers.patch b/Spigot-Server-Patches/0306-Optimize-Hoppers.patch new file mode 100644 index 000000000..f3a368631 --- /dev/null +++ b/Spigot-Server-Patches/0306-Optimize-Hoppers.patch @@ -0,0 +1,287 @@ +From c48ed623914dfeed35ef536b48b527415a70eabd Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Wed, 27 Apr 2016 22:09:52 -0400 +Subject: [PATCH] Optimize Hoppers + +* Removes unnecessary extra calls to .update() that are very expensive +* Lots of itemstack cloning removed. Only clone if the item is actually moved +* Return true when a plugin cancels inventory move item event instead of false, as false causes pulls to cycle through all items. + However, pushes do not exhibit the same behavior, so this is not something plugins could of been relying on. +* Add option (Default on) to cooldown hoppers when they fail to move an item due to full inventory +* Skip subsequent InventoryMoveItemEvents if a plugin does not use the item after first event fire for an iteration + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index b9f5f4905..a8470e6e7 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -399,6 +399,15 @@ public class PaperWorldConfig { + squidMaxSpawnHeight = getDouble("squid-spawn-height.maximum", 0.0D); + } + ++ public boolean cooldownHopperWhenFull = true; ++ public boolean disableHopperMoveEvents = false; ++ private void hopperOptimizations() { ++ cooldownHopperWhenFull = getBoolean("hopper.cooldown-when-full", cooldownHopperWhenFull); ++ log("Cooldown Hoppers when Full: " + (cooldownHopperWhenFull ? "enabled" : "disabled")); ++ disableHopperMoveEvents = getBoolean("hopper.disable-move-event", disableHopperMoveEvents); ++ log("Hopper Move Item Events: " + (disableHopperMoveEvents ? "disabled" : "enabled")); ++ } ++ + public boolean disableSprintInterruptionOnAttack; + private void disableSprintInterruptionOnAttack() { + disableSprintInterruptionOnAttack = getBoolean("game-mechanics.disable-sprint-interruption-on-attack", false); +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index ebc0709eb..d989b065d 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1017,6 +1017,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati + + if (true || i == 0 || this.getAllowNether()) { // CraftBukkit + WorldServer worldserver = this.worlds.get(i); ++ TileEntityHopper.skipHopperEvents = worldserver.paperConfig.disableHopperMoveEvents || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper + + this.methodProfiler.a(() -> { + return worldserver.getWorldData().getName(); +diff --git a/src/main/java/net/minecraft/server/TileEntity.java b/src/main/java/net/minecraft/server/TileEntity.java +index 3e9b357c8..db78274a8 100644 +--- a/src/main/java/net/minecraft/server/TileEntity.java ++++ b/src/main/java/net/minecraft/server/TileEntity.java +@@ -49,6 +49,7 @@ public abstract class TileEntity implements KeyedObject { + public void setCurrentChunk(Chunk chunk) { + this.currentChunk = chunk != null ? new java.lang.ref.WeakReference<>(chunk) : null; + } ++ static boolean IGNORE_TILE_UPDATES = false; + // Paper end + + @Nullable +@@ -121,6 +122,7 @@ public abstract class TileEntity implements KeyedObject { + + public void update() { + if (this.world != null) { ++ if (IGNORE_TILE_UPDATES) return; // Paper + this.f = this.world.getType(this.position); + this.world.b(this.position, this); + if (!this.f.isAir()) { +diff --git a/src/main/java/net/minecraft/server/TileEntityHopper.java b/src/main/java/net/minecraft/server/TileEntityHopper.java +index bb41d4780..9e7a91fe4 100644 +--- a/src/main/java/net/minecraft/server/TileEntityHopper.java ++++ b/src/main/java/net/minecraft/server/TileEntityHopper.java +@@ -190,6 +190,154 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + return false; + } + ++ // Paper start - Optimize Hoppers ++ private static boolean skipPullModeEventFire = false; ++ private static boolean skipPushModeEventFire = false; ++ static boolean skipHopperEvents = false; ++ ++ private boolean hopperPush(IInventory iinventory, EnumDirection enumdirection) { ++ skipPushModeEventFire = skipHopperEvents; ++ boolean foundItem = false; ++ for (int i = 0; i < this.getSize(); ++i) { ++ if (!this.getItem(i).isEmpty()) { ++ foundItem = true; ++ ItemStack origItemStack = this.getItem(i); ++ ItemStack itemstack = origItemStack; ++ ++ final int origCount = origItemStack.getCount(); ++ final int moved = Math.min(world.spigotConfig.hopperAmount, origCount); ++ origItemStack.setCount(moved); ++ ++ // We only need to fire the event once to give protection plugins a chance to cancel this event ++ // Because nothing uses getItem, every event call should end up the same result. ++ if (!skipPushModeEventFire) { ++ itemstack = callPushMoveEvent(iinventory, itemstack); ++ if (itemstack == null) { // cancelled ++ origItemStack.setCount(origCount); ++ return false; ++ } ++ } ++ final ItemStack itemstack2 = addItem(this, iinventory, itemstack, enumdirection); ++ final int remaining = itemstack2.getCount(); ++ if (remaining != moved) { ++ origItemStack = origItemStack.cloneItemStack(); ++ origItemStack.setCount(origCount - moved + remaining); ++ this.setItem(i, origItemStack); ++ iinventory.update(); ++ return true; ++ } ++ origItemStack.setCount(origCount); ++ } ++ } ++ if (foundItem && world.paperConfig.cooldownHopperWhenFull) { // Inventory was full - cooldown ++ this.setCooldown(world.spigotConfig.hopperTransfer); ++ } ++ return false; ++ } ++ ++ private static boolean hopperPull(IHopper ihopper, IInventory iinventory, int i) { ++ ItemStack origItemStack = iinventory.getItem(i); ++ ItemStack itemstack = origItemStack; ++ final int origCount = origItemStack.getCount(); ++ final World world = ihopper.getWorld(); ++ final int moved = Math.min(world.spigotConfig.hopperAmount, origCount); ++ itemstack.setCount(moved); ++ ++ if (!skipPullModeEventFire) { ++ itemstack = callPullMoveEvent(ihopper, iinventory, itemstack); ++ if (itemstack == null) { // cancelled ++ origItemStack.setCount(origCount); ++ // Drastically improve performance by returning true. ++ // No plugin could of relied on the behavior of false as the other call ++ // site for IMIE did not exhibit the same behavior ++ return true; ++ } ++ } ++ ++ final ItemStack itemstack2 = addItem(iinventory, ihopper, itemstack, null); ++ final int remaining = itemstack2.getCount(); ++ if (remaining != moved) { ++ origItemStack = origItemStack.cloneItemStack(); ++ origItemStack.setCount(origCount - moved + remaining); ++ IGNORE_TILE_UPDATES = true; ++ iinventory.setItem(i, origItemStack); ++ IGNORE_TILE_UPDATES = false; ++ iinventory.update(); ++ return true; ++ } ++ origItemStack.setCount(origCount); ++ ++ if (world.paperConfig.cooldownHopperWhenFull) { ++ cooldownHopper(ihopper); ++ } ++ ++ return false; ++ } ++ ++ private ItemStack callPushMoveEvent(IInventory iinventory, ItemStack itemstack) { ++ Inventory destinationInventory = getInventory(iinventory); ++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(this.getOwner(false).getInventory(), ++ CraftItemStack.asCraftMirror(itemstack), destinationInventory, true); ++ boolean result = event.callEvent(); ++ if (!event.calledGetItem && !event.calledSetItem) { ++ skipPushModeEventFire = true; ++ } ++ if (!result) { ++ cooldownHopper(this); ++ return null; ++ } ++ ++ if (event.calledSetItem) { ++ return CraftItemStack.asNMSCopy(event.getItem()); ++ } else { ++ return itemstack; ++ } ++ } ++ ++ private static ItemStack callPullMoveEvent(IHopper hopper, IInventory iinventory, ItemStack itemstack) { ++ Inventory sourceInventory = getInventory(iinventory); ++ Inventory destination = getInventory(hopper); ++ ++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, ++ // Mirror is safe as we no plugins ever use this item ++ CraftItemStack.asCraftMirror(itemstack), destination, false); ++ boolean result = event.callEvent(); ++ if (!event.calledGetItem && !event.calledSetItem) { ++ skipPullModeEventFire = true; ++ } ++ if (!result) { ++ cooldownHopper(hopper); ++ return null; ++ } ++ ++ if (event.calledSetItem) { ++ return CraftItemStack.asNMSCopy(event.getItem()); ++ } else { ++ return itemstack; ++ } ++ } ++ ++ private static Inventory getInventory(IInventory iinventory) { ++ Inventory sourceInventory;// Have to special case large chests as they work oddly ++ if (iinventory instanceof InventoryLargeChest) { ++ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((InventoryLargeChest) iinventory); ++ } else if (iinventory instanceof TileEntity) { ++ sourceInventory = ((TileEntity) iinventory).getOwner(false).getInventory(); ++ } else { ++ sourceInventory = iinventory.getOwner().getInventory(); ++ } ++ return sourceInventory; ++ } ++ ++ private static void cooldownHopper(IHopper hopper) { ++ if (hopper instanceof TileEntityHopper) { ++ ((TileEntityHopper) hopper).setCooldown(hopper.getWorld().spigotConfig.hopperTransfer); ++ } else if (hopper instanceof EntityMinecartHopper) { ++ ((EntityMinecartHopper) hopper).setCooldown(hopper.getWorld().spigotConfig.hopperTransfer / 2); ++ } ++ } ++ ++ // Paper end + private boolean s() { + IInventory iinventory = this.D(); + +@@ -201,6 +349,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + if (this.a(iinventory, enumdirection)) { + return false; + } else { ++ return hopperPush(iinventory, enumdirection); /* // Paper - disable rest + for (int i = 0; i < this.getSize(); ++i) { + if (!this.getItem(i).isEmpty()) { + ItemStack itemstack = this.getItem(i).cloneItemStack(); +@@ -242,7 +391,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + } + } + +- return false; ++ return false;*/ // Paper - end commenting out replaced block for Hopper Optimizations + } + } + } +@@ -313,6 +462,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + if (b(iinventory, enumdirection)) { + return false; + } ++ skipPullModeEventFire = skipHopperEvents; // Paper + + if (iinventory instanceof IWorldInventory) { + IWorldInventory iworldinventory = (IWorldInventory) iinventory; +@@ -355,6 +505,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + ItemStack itemstack = iinventory.getItem(i); + + if (!itemstack.isEmpty() && b(iinventory, itemstack, i, enumdirection)) { ++ return hopperPull(ihopper, iinventory, i); /* // Paper - disable rest + ItemStack itemstack1 = itemstack.cloneItemStack(); + // ItemStack itemstack2 = addItem(iinventory, ihopper, iinventory.splitStack(i, 1), (EnumDirection) null); + // CraftBukkit start - Call event on collection of items from inventories into the hopper +@@ -395,7 +546,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + } + + itemstack1.subtract(origCount - itemstack2.getCount()); // Spigot +- iinventory.setItem(i, itemstack1); ++ iinventory.setItem(i, itemstack1);*/ // Paper - end commenting out replaced block for Hopper Optimizations + } + + return false; +@@ -404,7 +555,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + public static boolean a(IInventory iinventory, EntityItem entityitem) { + boolean flag = false; + // CraftBukkit start +- InventoryPickupItemEvent event = new InventoryPickupItemEvent(iinventory.getOwner().getInventory(), (org.bukkit.entity.Item) entityitem.getBukkitEntity()); ++ InventoryPickupItemEvent event = new InventoryPickupItemEvent(getInventory(iinventory), (org.bukkit.entity.Item) entityitem.getBukkitEntity()); // Paper - use getInventory() to avoid snapshot creation + entityitem.world.getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return false; +@@ -458,7 +609,9 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi + boolean flag1 = iinventory1.P_(); + + if (itemstack1.isEmpty()) { ++ IGNORE_TILE_UPDATES = true; // Paper + iinventory1.setItem(i, itemstack); ++ IGNORE_TILE_UPDATES = false; // Paper + itemstack = ItemStack.a; + flag = true; + } else if (a(itemstack1, itemstack)) { +-- +2.18.0 + diff --git a/removed/server/0259-Configurable-Max-Chunk-Gens-per-Tick.patch b/removed/server/0259-Configurable-Max-Chunk-Gens-per-Tick.patch deleted file mode 100644 index 2032691ad..000000000 --- a/removed/server/0259-Configurable-Max-Chunk-Gens-per-Tick.patch +++ /dev/null @@ -1,112 +0,0 @@ -From 9b92490838fd8805b3e867cc7235a0bcf0854f7f Mon Sep 17 00:00:00 2001 -From: Aikar -Date: Mon, 1 Jan 2018 16:10:24 -0500 -Subject: [PATCH] Configurable Max Chunk Gens per Tick - -Limit the number of generations that can occur in a single tick, forcing them -to be spread out more. - -Defaulting to 10 as an average generation is going to be 3-6ms, which means 10 will -likely cause the server to lose TPS, but constrain how much. - -This should result in no noticeable speed reduction in generation for servers not -lagging, and let larger servers reduce this value according to their own desires. - -diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java -index e3ff78180..0ddf826f9 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java -+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java -@@ -472,4 +472,16 @@ public class PaperWorldConfig { - } - log("Max Chunk Sends Per Tick: " + maxChunkSendsPerTick); - } -+ -+ public int maxChunkGensPerTick = 10; -+ private void maxChunkGensPerTick() { -+ maxChunkGensPerTick = getInt("max-chunk-gens-per-tick", maxChunkGensPerTick); -+ if (maxChunkGensPerTick <= 0) { -+ maxChunkGensPerTick = Integer.MAX_VALUE; -+ log("Max Chunk Gens Per Tick: Unlimited (NOT RECOMMENDED)"); -+ } else { -+ log("Max Chunk Gens Per Tick: " + maxChunkGensPerTick); -+ } -+ } -+ - } -diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java -index 395386f29..baf72404e 100644 ---- a/src/main/java/net/minecraft/server/PlayerChunk.java -+++ b/src/main/java/net/minecraft/server/PlayerChunk.java -@@ -27,6 +27,7 @@ public class PlayerChunk { - private boolean done; - - // CraftBukkit start - add fields -+ boolean chunkExists; // Paper - private boolean loadInProgress = false; - private Runnable loadedRunnable = new Runnable() { - public void run() { -@@ -50,6 +51,7 @@ public class PlayerChunk { - // CraftBukkit start - loadInProgress = true; - this.chunk = playerchunkmap.getWorld().getChunkProviderServer().getChunkAt(i, j, loadedRunnable, false); -+ this.chunkExists = this.chunk != null || ChunkIOExecutor.hasQueuedChunkLoad(playerChunkMap.getWorld(), i, j); // Paper - markChunkUsed(); // Paper - delay chunk unloads - // CraftBukkit end - } -diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java -index 6ee9f6cfb..99652ae3e 100644 ---- a/src/main/java/net/minecraft/server/PlayerChunkMap.java -+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java -@@ -166,6 +166,7 @@ public class PlayerChunkMap { - // Spigot start - org.spigotmc.SlackActivityAccountant activityAccountant = this.world.getMinecraftServer().slackActivityAccountant; - activityAccountant.startActivity(0.5); -+ int chunkGensAllowed = world.paperConfig.maxChunkGensPerTick; // Paper - // Spigot end - - Iterator iterator1 = this.h.iterator(); -@@ -175,6 +176,11 @@ public class PlayerChunkMap { - - if (playerchunk1.f() == null) { - boolean flag = playerchunk1.a(PlayerChunkMap.b); -+ // Paper start -+ if (flag && !playerchunk1.chunkExists && chunkGensAllowed-- <= 0) { -+ continue; -+ } -+ // Paper end - - if (playerchunk1.a(flag)) { - iterator1.remove(); -diff --git a/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java b/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java -index 9aaca21a7..f50d55c8e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java -+++ b/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java -@@ -35,4 +35,10 @@ public class ChunkIOExecutor { - public static void tick() { - instance.finishActive(); - } -+ -+ // Paper start -+ public static boolean hasQueuedChunkLoad(World world, int x, int z) { -+ return instance.hasTask(new QueuedChunk(x, z, null, world, null)); -+ } -+ // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/AsynchronousExecutor.java b/src/main/java/org/bukkit/craftbukkit/util/AsynchronousExecutor.java -index 193c3621c..cf1258c55 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/AsynchronousExecutor.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/AsynchronousExecutor.java -@@ -351,4 +351,10 @@ public final class AsynchronousExecutor { - public void setActiveThreads(final int coreSize) { - pool.setCorePoolSize(coreSize); - } -+ -+ // Paper start -+ public boolean hasTask(P parameter) throws IllegalStateException { -+ return tasks.get(parameter) != null; -+ } -+ // Paper end - } --- -2.18.0 -