From 0000000000000000000000000000000000000000 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 * Don't check for Entities with Inventories if the block above us is also occluding (not just Inventoried) * Remove Streams from Item Suck In and restore restore 1.12 AABB checks which is simpler and no voxel allocations (was doing TWO Item Suck ins) diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java index a26848df994ffb0dc02d6a6051971439613dd0ba..cab503bd5c34d12b38a2f5deed6d3feb9287b370 100644 --- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java @@ -624,4 +624,13 @@ public class PaperWorldConfig { private void entitiesTargetWithFollowRange() { entitiesTargetWithFollowRange = getBoolean("entities-target-with-follow-range", entitiesTargetWithFollowRange); } + + 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")); + } } diff --git a/src/main/java/net/minecraft/server/IHopper.java b/src/main/java/net/minecraft/server/IHopper.java index e1aa272e526950e6405221e566cf4299c869a6b2..4da26365ec59ed33e10b55789e535a1c0e7b92a1 100644 --- a/src/main/java/net/minecraft/server/IHopper.java +++ b/src/main/java/net/minecraft/server/IHopper.java @@ -12,12 +12,13 @@ public interface IHopper extends IInventory { return IHopper.c; } - @Nullable + //@Nullable // Paper - it's annoying World getWorld(); + default BlockPosition getBlockPosition() { return new BlockPosition(getX(), getY(), getZ()); } // Paper - double z(); + double z();default double getX() { return z(); } // Paper - OBFHELPER - double A(); + double A();default double getY() { return A(); } // Paper - OBFHELPER - double B(); + double B();default double getZ() { return B(); } // Paper - OBFHELPER } diff --git a/src/main/java/net/minecraft/server/ItemStack.java b/src/main/java/net/minecraft/server/ItemStack.java index d953cdef14a9b62833a35a4fe94a22b5e9b19c2d..d6e43313bf0c678cf78fe77de2f8f4b6f819e3f4 100644 --- a/src/main/java/net/minecraft/server/ItemStack.java +++ b/src/main/java/net/minecraft/server/ItemStack.java @@ -482,11 +482,12 @@ public final class ItemStack { return this.getItem().a(this, entityhuman, entityliving, enumhand); } - public ItemStack cloneItemStack() { - if (this.isEmpty()) { + public ItemStack cloneItemStack() { return cloneItemStack(false); } // Paper + public ItemStack cloneItemStack(boolean origItem) { // Paper + if (!origItem && this.isEmpty()) { // Paper return ItemStack.a; } else { - ItemStack itemstack = new ItemStack(this.getItem(), this.count); + ItemStack itemstack = new ItemStack(origItem ? this.item : this.getItem(), this.count); // Paper itemstack.d(this.C()); if (this.tag != null) { diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 9450e27e5ce9295d87b95be2797fc27984ca2b0b..8c08a542a97edd76c07ef7d64834bdbd70345876 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -1222,6 +1222,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant 0; // Paper + 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 this.methodProfiler.a(() -> { return worldserver.getWorldData().getName() + " " + IRegistry.DIMENSION_TYPE.getKey(worldserver.worldProvider.getDimensionManager()); diff --git a/src/main/java/net/minecraft/server/TileEntity.java b/src/main/java/net/minecraft/server/TileEntity.java index 958279249fdadfe5c2808d2a046636f06c3bd500..a8e64dfdab1e73894144a65c10c15d22f9198d3d 100644 --- a/src/main/java/net/minecraft/server/TileEntity.java +++ b/src/main/java/net/minecraft/server/TileEntity.java @@ -62,6 +62,7 @@ public abstract class TileEntity implements KeyedObject { // Paper 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 @@ -140,6 +141,7 @@ public abstract class TileEntity implements KeyedObject { // Paper public void update() { if (this.world != null) { + if (IGNORE_TILE_UPDATES) return; // Paper this.c = this.world.getType(this.position); this.world.b(this.position, this); if (!this.c.isAir()) { diff --git a/src/main/java/net/minecraft/server/TileEntityHopper.java b/src/main/java/net/minecraft/server/TileEntityHopper.java index 907d088c8691eec5d72836ccda420a7a0703ad22..c755faed4f63884cb6a66bf951104a27dbaf887f 100644 --- a/src/main/java/net/minecraft/server/TileEntityHopper.java +++ b/src/main/java/net/minecraft/server/TileEntityHopper.java @@ -168,6 +168,160 @@ 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) { + ItemStack item = this.getItem(i); + if (!item.isEmpty()) { + foundItem = true; + ItemStack origItemStack = item; + 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); + if (!origItemStack.isEmpty()) { + 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, ItemStack origItemStack, int 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); + if (!origItemStack.isEmpty()) { + 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 j() { IInventory iinventory = this.k(); @@ -179,6 +333,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi if (this.b(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(); @@ -216,7 +371,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi } } - return false; + return false;*/ // Paper - end commenting out replaced block for Hopper Optimizations } } } @@ -225,18 +380,54 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi return iinventory instanceof IWorldInventory ? IntStream.of(((IWorldInventory) iinventory).getSlotsForFace(enumdirection)) : IntStream.range(0, iinventory.getSize()); } - private boolean b(IInventory iinventory, EnumDirection enumdirection) { - return a(iinventory, enumdirection).allMatch((i) -> { - ItemStack itemstack = iinventory.getItem(i); + private static boolean allMatch(IInventory iinventory, EnumDirection enumdirection, java.util.function.BiPredicate test) { + if (iinventory instanceof IWorldInventory) { + for (int i : ((IWorldInventory) iinventory).getSlotsForFace(enumdirection)) { + if (!test.test(iinventory.getItem(i), i)) { + return false; + } + } + } else { + int size = iinventory.getSize(); + for (int i = 0; i < size; i++) { + if (!test.test(iinventory.getItem(i), i)) { + return false; + } + } + } + return true; + } - return itemstack.getCount() >= itemstack.getMaxStackSize(); - }); + private static boolean anyMatch(IInventory iinventory, EnumDirection enumdirection, java.util.function.BiPredicate test) { + if (iinventory instanceof IWorldInventory) { + for (int i : ((IWorldInventory) iinventory).getSlotsForFace(enumdirection)) { + if (test.test(iinventory.getItem(i), i)) { + return true; + } + } + } else { + int size = iinventory.getSize(); + for (int i = 0; i < size; i++) { + if (test.test(iinventory.getItem(i), i)) { + return true; + } + } + } + return true; + } + private static final java.util.function.BiPredicate STACK_SIZE_TEST = (itemstack, i) -> itemstack.getCount() >= itemstack.getMaxStackSize(); + private static final java.util.function.BiPredicate IS_EMPTY_TEST = (itemstack, i) -> itemstack.isEmpty(); + + // Paper end + + private boolean b(IInventory iinventory, EnumDirection enumdirection) { + // Paper start - no streams + return allMatch(iinventory, enumdirection, STACK_SIZE_TEST); + // Paper end } private static boolean c(IInventory iinventory, EnumDirection enumdirection) { - return a(iinventory, enumdirection).allMatch((i) -> { - return iinventory.getItem(i).isEmpty(); - }); + return allMatch(iinventory, enumdirection, IS_EMPTY_TEST); } public static boolean a(IHopper ihopper) { @@ -245,9 +436,17 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi if (iinventory != null) { EnumDirection enumdirection = EnumDirection.DOWN; - return c(iinventory, enumdirection) ? false : a(iinventory, enumdirection).anyMatch((i) -> { - return a(ihopper, iinventory, i, enumdirection); + // Paper start - optimize hoppers and remove streams + skipPullModeEventFire = skipHopperEvents; + return !c(iinventory, enumdirection) && anyMatch(iinventory, enumdirection, (item, i) -> { + // Logic copied from below to avoid extra getItem calls + if (!item.isEmpty() && canTakeItem(iinventory, item, i, enumdirection)) { + return hopperPull(ihopper, iinventory, item, i); + } else { + return false; + } }); + // Paper end } else { Iterator iterator = c(ihopper).iterator(); @@ -268,7 +467,8 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi private static boolean a(IHopper ihopper, IInventory iinventory, int i, EnumDirection enumdirection) { ItemStack itemstack = iinventory.getItem(i); - if (!itemstack.isEmpty() && b(iinventory, itemstack, i, enumdirection)) { + if (!itemstack.isEmpty() && b(iinventory, itemstack, i, enumdirection)) { // If this logic changes, update above. this is left inused incase reflective plugins + return hopperPull(ihopper, iinventory, itemstack, 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 @@ -305,7 +505,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; @@ -314,7 +514,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; @@ -356,6 +556,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi return !iinventory.b(i, itemstack) ? false : !(iinventory instanceof IWorldInventory) || ((IWorldInventory) iinventory).canPlaceItemThroughFace(i, itemstack, enumdirection); } + private static boolean canTakeItem(IInventory iinventory, ItemStack itemstack, int i, EnumDirection enumdirection) { return b(iinventory, itemstack, i, enumdirection); } // Paper - OBFHELPER private static boolean b(IInventory iinventory, ItemStack itemstack, int i, EnumDirection enumdirection) { return !(iinventory instanceof IWorldInventory) || ((IWorldInventory) iinventory).canTakeItemThroughFace(i, itemstack, enumdirection); } @@ -368,7 +569,9 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi boolean flag1 = iinventory1.isEmpty(); 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)) { @@ -419,18 +622,24 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi } public static List c(IHopper ihopper) { - return (List) ihopper.P_().d().stream().flatMap((axisalignedbb) -> { - return ihopper.getWorld().a(EntityItem.class, axisalignedbb.d(ihopper.z() - 0.5D, ihopper.A() - 0.5D, ihopper.B() - 0.5D), IEntitySelector.a).stream(); - }).collect(Collectors.toList()); + // Paper start - Optimize item suck in. remove streams, restore 1.12 checks. Seriously checking the bowl?! + World world = ihopper.getWorld(); + double d0 = ihopper.getX(); + double d1 = ihopper.getY(); + double d2 = ihopper.getZ(); + AxisAlignedBB bb = new AxisAlignedBB(d0 - 0.5D, d1, d2 - 0.5D, d0 + 0.5D, d1 + 1.5D, d2 + 0.5D); + return world.getEntities(EntityItem.class, bb, Entity::isAlive); + // Paper end } @Nullable public static IInventory b(World world, BlockPosition blockposition) { - return a(world, (double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D); + return a(world, (double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D, true); // Paper } @Nullable - public static IInventory a(World world, double d0, double d1, double d2) { + public static IInventory a(World world, double d0, double d1, double d2) { return a(world, d0, d1, d2, false); } // Paper - overload to default false + public static IInventory a(World world, double d0, double d1, double d2, boolean optimizeEntities) { // Paper Object object = null; BlockPosition blockposition = new BlockPosition(d0, d1, d2); if ( !world.isLoaded( blockposition ) ) return null; // Spigot @@ -450,7 +659,7 @@ public class TileEntityHopper extends TileEntityLootable implements IHopper, ITi } } - if (object == null) { + if (object == null && (!optimizeEntities || !org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(block).isOccluding())) { // Paper List list = world.getEntities((Entity) null, new AxisAlignedBB(d0 - 0.5D, d1 - 0.5D, d2 - 0.5D, d0 + 0.5D, d1 + 0.5D, d2 + 0.5D), IEntitySelector.d); if (!list.isEmpty()) { diff --git a/src/main/java/net/minecraft/server/TileEntityLootable.java b/src/main/java/net/minecraft/server/TileEntityLootable.java index d4cbce3243fe1f4973c9c0ae0dbdab10e3390897..3b394c2726e0fbe595641a022e59c8967d525f82 100644 --- a/src/main/java/net/minecraft/server/TileEntityLootable.java +++ b/src/main/java/net/minecraft/server/TileEntityLootable.java @@ -72,12 +72,19 @@ public abstract class TileEntityLootable extends TileEntityContainer { @Override public boolean isEmpty() { this.d((EntityHuman) null); - return this.f().stream().allMatch(ItemStack::isEmpty); + // Paper start + for (ItemStack itemStack : this.f()) { + if (!itemStack.isEmpty()) { + return false; + } + } + // Paper end + return true; } @Override public ItemStack getItem(int i) { - this.d((EntityHuman) null); + if (i == 0) this.d((EntityHuman) null); // Paper return (ItemStack) this.f().get(i); } diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java index 045e449c7e411dec7ef415c76c808cda426db652..2beb1374b105381d4467de4989c207339cb5dca1 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -1206,8 +1206,8 @@ public abstract class World implements GeneratorAccess, AutoCloseable { return list; } - @Override - public List a(Class oclass, AxisAlignedBB axisalignedbb, @Nullable Predicate predicate) { + public List getEntities(Class oclass, AxisAlignedBB axisalignedbb, @Nullable Predicate predicate) { return a(oclass, axisalignedbb, predicate); } // Paper - OBFHELPER + @Override public List a(Class oclass, AxisAlignedBB axisalignedbb, @Nullable Predicate predicate) { this.getMethodProfiler().c("getEntities"); int i = MathHelper.floor((axisalignedbb.minX - 2.0D) / 16.0D); int j = MathHelper.f((axisalignedbb.maxX + 2.0D) / 16.0D);