2019-08-05 16:35:40 +00:00
From 57e4e704f0bd0d50d54eb66b0050a85a0e7ca8a6 Mon Sep 17 00:00:00 2001
2018-08-18 22:15:17 +00:00
From: Aikar <aikar@aikar.co>
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
2019-07-24 03:20:14 +00:00
index 5832c3e868..ede558d029 100644
2018-08-18 22:15:17 +00:00
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
2019-07-03 20:44:07 +00:00
@@ -355,6 +355,15 @@ public class PaperWorldConfig {
2018-08-18 22:15:17 +00:00
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/ItemStack.java b/src/main/java/net/minecraft/server/ItemStack.java
2019-07-24 03:20:14 +00:00
index c69a067ef1..1e23d77e72 100644
2018-08-18 22:15:17 +00:00
--- a/src/main/java/net/minecraft/server/ItemStack.java
+++ b/src/main/java/net/minecraft/server/ItemStack.java
2019-05-05 02:23:25 +00:00
@@ -482,8 +482,9 @@ public final class ItemStack {
2018-08-18 22:15:17 +00:00
return this.getItem().a(this, entityhuman, entityliving, enumhand);
}
- public ItemStack cloneItemStack() {
- ItemStack itemstack = new ItemStack(this.getItem(), this.count);
+ public ItemStack cloneItemStack() { return cloneItemStack(false); } // Paper
+ public ItemStack cloneItemStack(boolean origItem) { // Paper
+ ItemStack itemstack = new ItemStack(origItem ? this.item : this.getItem(), this.count); // Paper
2019-05-05 02:23:25 +00:00
itemstack.d(this.C());
2018-08-18 22:15:17 +00:00
if (this.tag != null) {
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
2019-08-05 16:35:40 +00:00
index 2acb71fa8a..0d690fc8e1 100644
2018-08-18 22:15:17 +00:00
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
2019-08-05 16:35:40 +00:00
@@ -1143,6 +1143,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
2019-05-05 02:23:25 +00:00
WorldServer worldserver = (WorldServer) iterator.next();
2019-06-25 01:47:58 +00:00
2019-02-26 01:38:55 +00:00
worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper
2018-08-26 18:11:49 +00:00
+ TileEntityHopper.skipHopperEvents = worldserver.paperConfig.disableHopperMoveEvents || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper
if (true || worldserver.worldProvider.getDimensionManager() == DimensionManager.OVERWORLD || this.getAllowNether()) { // CraftBukkit
2018-08-18 22:15:17 +00:00
this.methodProfiler.a(() -> {
2019-06-25 01:47:58 +00:00
return worldserver.getWorldData().getName() + " " + IRegistry.DIMENSION_TYPE.getKey(worldserver.worldProvider.getDimensionManager());
2018-08-18 22:15:17 +00:00
diff --git a/src/main/java/net/minecraft/server/TileEntity.java b/src/main/java/net/minecraft/server/TileEntity.java
2019-07-24 03:20:14 +00:00
index c72b013868..b8ddb99fa3 100644
2018-08-18 22:15:17 +00:00
--- a/src/main/java/net/minecraft/server/TileEntity.java
+++ b/src/main/java/net/minecraft/server/TileEntity.java
2019-05-14 02:20:58 +00:00
@@ -62,6 +62,7 @@ public abstract class TileEntity implements KeyedObject { // Paper
2018-08-18 22:15:17 +00:00
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
2019-07-05 02:13:38 +00:00
@@ -139,6 +140,7 @@ public abstract class TileEntity implements KeyedObject { // Paper
2018-08-18 22:15:17 +00:00
public void update() {
if (this.world != null) {
+ if (IGNORE_TILE_UPDATES) return; // Paper
2019-05-05 02:23:25 +00:00
this.c = this.world.getType(this.position);
2018-08-18 22:15:17 +00:00
this.world.b(this.position, this);
2019-05-05 02:23:25 +00:00
if (!this.c.isAir()) {
2018-08-18 22:15:17 +00:00
diff --git a/src/main/java/net/minecraft/server/TileEntityHopper.java b/src/main/java/net/minecraft/server/TileEntityHopper.java
2019-07-24 03:20:14 +00:00
index 1ba98bf736..6f6519f6c5 100644
2018-08-18 22:15:17 +00:00
--- a/src/main/java/net/minecraft/server/TileEntityHopper.java
+++ b/src/main/java/net/minecraft/server/TileEntityHopper.java
2019-05-27 23:01:45 +00:00
@@ -189,6 +189,154 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi
2018-08-18 22:15:17 +00:00
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(true);
+ 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(true);
+ 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(),
2019-05-14 02:20:58 +00:00
+ CraftItemStack.asCraftMirror(itemstack), destinationInventory, true);
2018-08-18 22:15:17 +00:00
+ 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,
2019-05-14 02:20:58 +00:00
+ // Mirror is safe as we no plugins ever use this item
+ CraftItemStack.asCraftMirror(itemstack), destination, false);
2018-08-18 22:15:17 +00:00
+ 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);
+ }
+ }
2019-05-27 23:01:45 +00:00
+ // Paper end
2018-08-18 22:15:17 +00:00
+
2019-05-27 23:01:45 +00:00
private boolean t() {
IInventory iinventory = this.u();
2018-08-18 22:15:17 +00:00
2019-05-27 23:01:45 +00:00
@@ -200,6 +348,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi
2019-05-05 02:23:25 +00:00
if (this.b(iinventory, enumdirection)) {
2018-08-18 22:15:17 +00:00
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();
2019-05-27 23:01:45 +00:00
@@ -237,7 +386,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi
2018-08-18 22:15:17 +00:00
}
}
- return false;
+ return false;*/ // Paper - end commenting out replaced block for Hopper Optimizations
}
}
}
2019-05-27 23:01:45 +00:00
@@ -267,6 +416,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi
2019-05-05 02:23:25 +00:00
EnumDirection enumdirection = EnumDirection.DOWN;
2018-08-18 22:15:17 +00:00
2019-05-05 02:23:25 +00:00
return c(iinventory, enumdirection) ? false : a(iinventory, enumdirection).anyMatch((i) -> {
+ skipPullModeEventFire = skipHopperEvents; // Paper
return a(ihopper, iinventory, i, enumdirection);
});
} else {
2019-05-27 23:01:45 +00:00
@@ -290,6 +440,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi
2018-08-18 22:15:17 +00:00
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
2019-05-27 23:01:45 +00:00
@@ -326,7 +477,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi
2018-08-18 22:15:17 +00:00
}
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;
2019-05-27 23:01:45 +00:00
@@ -335,7 +486,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi
2018-08-18 22:15:17 +00:00
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;
2019-05-27 23:01:45 +00:00
@@ -389,7 +540,9 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi
2019-05-05 02:23:25 +00:00
boolean flag1 = iinventory1.isNotEmpty();
2018-08-18 22:15:17 +00:00
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)) {
--
2019-06-25 01:47:58 +00:00
2.22.0
2018-08-18 22:15:17 +00:00