From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Samsuik Date: Mon, 12 Aug 2024 15:35:57 +0100 Subject: [PATCH] Optimise hopper ticking diff --git a/net/minecraft/world/CompoundContainer.java b/net/minecraft/world/CompoundContainer.java index 0e9beb7f74e527a95bff064631e6d591f5775ce5..f89a8a3a1f3c87432c8816c3273c7b25b324055b 100644 --- a/net/minecraft/world/CompoundContainer.java +++ b/net/minecraft/world/CompoundContainer.java @@ -53,6 +53,15 @@ public class CompoundContainer implements Container { return this.container1.getLocation(); // TODO: right? } // CraftBukkit end + // Sakura start - optimise hopper ticking + @Override + public final boolean addListener(net.minecraft.world.level.block.entity.BlockEntity.BlockEntityChangeListener listener) { + boolean result = false; + result |= this.container1.addListener(listener); + result |= this.container2.addListener(listener); + return result; + } + // Sakura end - optimise hopper ticking public CompoundContainer(Container container1, Container container2) { this.container1 = container1; diff --git a/net/minecraft/world/Container.java b/net/minecraft/world/Container.java index b382665cc125b8b5c0938e5e55984e4bf91d37ff..0c4358053076416fc2cd3f5c9b7f6d2f630cab10 100644 --- a/net/minecraft/world/Container.java +++ b/net/minecraft/world/Container.java @@ -14,6 +14,12 @@ import net.minecraft.world.level.block.entity.BlockEntity; public interface Container extends Clearable, Iterable { float DEFAULT_DISTANCE_BUFFER = 4.0F; + // Sakura start - optimise hopper ticking + default boolean addListener(BlockEntity.BlockEntityChangeListener container) { + return false; + } + // Sakura end - optimise hopper ticking + int getContainerSize(); boolean isEmpty(); diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java index 769887ee6d798d0b11de72e9ffe80c9d358daf17..701a48ba3b0f4515a791684d63b3454dbed317f9 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -1483,7 +1483,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl // Spigot end if (tickingBlockEntity.isRemoved()) { toRemove.add(tickingBlockEntity); // Paper - Fix MC-117075; use removeAll - } else if (runsNormally && this.shouldTickBlocksAt(tickingBlockEntity.getPos())) { + } else if (runsNormally && tickingBlockEntity.isBlockEntityActive() && this.shouldTickBlocksAt(tickingBlockEntity.getPos())) { // Sakura - optimise hopper ticking tickingBlockEntity.tick(); // Paper start - rewrite chunk system if ((++tickedEntities & 7) == 0) { diff --git a/net/minecraft/world/level/block/HopperBlock.java b/net/minecraft/world/level/block/HopperBlock.java index 46a27f60ba407dacdac190b5e292ab3f1db5a078..ec05bb86803d878867b46e437cc73a39f155b9fc 100644 --- a/net/minecraft/world/level/block/HopperBlock.java +++ b/net/minecraft/world/level/block/HopperBlock.java @@ -121,6 +121,12 @@ public class HopperBlock extends BaseEntityBlock { private void checkPoweredState(Level level, BlockPos pos, BlockState state) { boolean flag = !level.hasNeighborSignal(pos); if (flag != state.getValue(ENABLED)) { + // Sakura start - optimise hopper ticking + final BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof HopperBlockEntity hbe && level.sakuraConfig().technical.optimiseIdleHopperTicking) { + hbe.setBlockEntityTicking(flag); + } + // Sakura end - optimise hopper ticking level.setBlock(pos, state.setValue(ENABLED, flag), 2); } } diff --git a/net/minecraft/world/level/block/entity/BlockEntity.java b/net/minecraft/world/level/block/entity/BlockEntity.java index a1075c26d55cc01219acd94d0138f81aa9d34c48..6aa0624eb99223dc13478a24f74a49654019fba5 100644 --- a/net/minecraft/world/level/block/entity/BlockEntity.java +++ b/net/minecraft/world/level/block/entity/BlockEntity.java @@ -48,6 +48,60 @@ public abstract class BlockEntity { private BlockState blockState; private DataComponentMap components = DataComponentMap.EMPTY; + // Sakura start - optimise hopper ticking + private final Set listeners = new it.unimi.dsi.fastutil.objects.ReferenceArraySet<>(0); + private final java.util.List listeningBlocks = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(0); + private boolean blockEntityTicking = true; + private int tickCount = 0; + + public final int getIdleTickCount() { + return this.tickCount; + } + + public final boolean isBlockEntityActive() { + // For some reason the update method is not always called. + // Activate the block entity every minute in case there's any issues. + if (!this.blockEntityTicking && this.tickCount > 1200) { + this.setBlockEntityTicking(true); + } + this.tickCount++; + return this.blockEntityTicking; + } + + public final void setBlockEntityTicking(boolean blockEntityTicking) { + this.tickCount = 0; + this.blockEntityTicking = blockEntityTicking; + } + + public final boolean addListener(BlockEntityChangeListener listener) { + if (this.listeners.add(listener)) { + ((BlockEntity) listener).listeningBlocks.add(this); + } + return true; + } + + public final void updateListeners(boolean onRemove) { + for (BlockEntityChangeListener listener : this.listeners) { + if (onRemove) { + listener.neighborRemoved(); + } else { + listener.neighborChange(); + } + } + if (onRemove) { + this.listeningBlocks.forEach(blockEntity -> blockEntity.listeners.clear()); + this.listeningBlocks.clear(); + this.listeners.clear(); + } + } + + public interface BlockEntityChangeListener { + void neighborChange(); + + void neighborRemoved(); + } + // Sakura end - optimise hopper ticking + public BlockEntity(BlockEntityType type, BlockPos pos, BlockState blockState) { this.type = type; this.worldPosition = pos.immutable(); @@ -204,11 +258,22 @@ public abstract class BlockEntity { public void setChanged() { if (this.level != null) { if (ignoreBlockEntityUpdates) return; // Paper - Perf: Optimize Hoppers - setChanged(this.level, this.worldPosition, this.blockState); + setChanged(this.level, this.worldPosition, this.blockState, this); // Sakura - optimise hopper ticking } } protected static void setChanged(Level level, BlockPos pos, BlockState state) { + // Sakura start - optimise hopper ticking + net.minecraft.world.level.chunk.LevelChunk chunk = level.getChunkIfLoaded(pos); + BlockEntity blockEntity = chunk != null ? chunk.getBlockEntity(pos) : null; + setChanged(level, pos, state, blockEntity); + } + + protected static void setChanged(Level level, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity) { + if (blockEntity != null) { + blockEntity.updateListeners(false); + } + // Sakura end - optimise hopper ticking level.blockEntityChanged(pos); if (!state.isAir()) { level.updateNeighbourForOutputSignal(pos, state.getBlock()); @@ -238,6 +303,7 @@ public abstract class BlockEntity { public void setRemoved() { this.remove = true; + this.updateListeners(true); // Sakura - optimise hopper ticking } public void clearRemoved() { diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java index 15d4f60942c0cc612c1468b4c0fda886867a67cb..98856a692b3ae5ac46cb67678642709b4f931ee7 100644 --- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java +++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java @@ -28,7 +28,7 @@ import net.minecraft.world.level.block.HopperBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.AABB; -public class HopperBlockEntity extends RandomizableContainerBlockEntity implements Hopper { +public class HopperBlockEntity extends RandomizableContainerBlockEntity implements Hopper, BlockEntity.BlockEntityChangeListener { // Sakura - optimise hopper ticking public static final int MOVE_ITEM_SPEED = 8; public static final int HOPPER_CONTAINER_SIZE = 5; private static final int[][] CACHED_SLOTS = new int[54][]; @@ -71,6 +71,58 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen this.maxStack = size; } // CraftBukkit end + // Sakura start - optimise hopper ticking + private static final int SOURCE_CONTAINER = 1 << 0; + private static final int ATTACHED_CONTAINER = 1 << 1; + private int connectedContainers = 0; + + @Override + public final void neighborChange() { + this.startTicking(); + } + + @Override + public final void neighborRemoved() { + this.connectedContainers = 0; + this.startTicking(); + } + + private void startTicking() { + this.cooldownTime -= this.getIdleTickCount(); + this.setBlockEntityTicking(true); + } + + private void waitForChange(int fullState) { + if ((fullState == HOPPER_IS_FULL || (this.connectedContainers & SOURCE_CONTAINER) != 0) && (this.connectedContainers & ATTACHED_CONTAINER) != 0) { + this.addListener(this); + this.setBlockEntityTicking(false); + } + } + + private static @Nullable Container sakura_getSourceContainer(Level level, Hopper hopper, BlockPos pos, BlockState state) { + Container container = getSourceContainer(level, hopper, pos, state); + if (hopper instanceof HopperBlockEntity hbe && org.bukkit.event.inventory.HopperInventorySearchEvent.getHandlerList().getRegisteredListeners().length == 0) { + hbe.listenForContainerChanges(container, SOURCE_CONTAINER); + } + return container; + } + + private static @Nullable Container sakura_getAttachedContainer(Level level, BlockPos pos, HopperBlockEntity hbe) { + Container container = getAttachedContainer(level, pos, hbe); + if (org.bukkit.event.inventory.HopperInventorySearchEvent.getHandlerList().getRegisteredListeners().length == 0) { + hbe.listenForContainerChanges(container, ATTACHED_CONTAINER); + } + return container; + } + + private void listenForContainerChanges(@Nullable Container container, int type) { + if (container != null && container.addListener(this)) { + this.connectedContainers |= type; // set + } else if ((this.connectedContainers & type) != 0) { + this.connectedContainers ^= type; // unset + } + } + // Sakura end - optimise hopper ticking public HopperBlockEntity(BlockPos pos, BlockState blockState) { super(BlockEntityType.HOPPER, pos, blockState); @@ -200,6 +252,12 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen setChanged(level, pos, state); return true; } + + // Sakura start - optimise hopper ticking + if (level.sakuraConfig().technical.optimiseIdleHopperTicking) { + blockEntity.waitForChange(fullState); + } + // Sakura end - optimise hopper ticking } return false; @@ -417,7 +475,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen // Paper end - Perf: Optimize Hoppers private static boolean ejectItems(Level level, BlockPos pos, HopperBlockEntity blockEntity) { - Container attachedContainer = getAttachedContainer(level, pos, blockEntity); + Container attachedContainer = sakura_getAttachedContainer(level, pos, blockEntity); // Sakura - optimise hopper ticking if (attachedContainer == null) { return false; } else { @@ -530,7 +588,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen public static boolean suckInItems(Level level, Hopper hopper) { BlockPos blockPos = BlockPos.containing(hopper.getLevelX(), hopper.getLevelY() + 1.0, hopper.getLevelZ()); BlockState blockState = level.getBlockState(blockPos); - Container sourceContainer = getSourceContainer(level, hopper, blockPos, blockState); + Container sourceContainer = sakura_getSourceContainer(level, hopper, blockPos, blockState); // Sakura - optimise hopper ticking if (sourceContainer != null) { Direction direction = Direction.DOWN; skipPullModeEventFire = skipHopperEvents; // Paper - Perf: Optimize Hoppers diff --git a/net/minecraft/world/level/block/entity/TickingBlockEntity.java b/net/minecraft/world/level/block/entity/TickingBlockEntity.java index 28e3b73507b988f7234cbf29c4024c88180d0aef..a0d247aa883553708c4b92158232425593d50534 100644 --- a/net/minecraft/world/level/block/entity/TickingBlockEntity.java +++ b/net/minecraft/world/level/block/entity/TickingBlockEntity.java @@ -10,4 +10,10 @@ public interface TickingBlockEntity { BlockPos getPos(); String getType(); + + // Sakura start - optimise hopper ticking + default boolean isBlockEntityActive() { + return true; + } + // Sakura end - optimise hopper ticking } diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java index cfc125d43cf3702d46c221f4e7b0d66a15c5d690..9f1a944b82051ea957a0fe725ca95b8f26491f1d 100644 --- a/net/minecraft/world/level/chunk/LevelChunk.java +++ b/net/minecraft/world/level/chunk/LevelChunk.java @@ -980,6 +980,13 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p return BlockEntityType.getKey(this.blockEntity.getType()).toString(); } + // Sakura start - optimise hopper ticking + @Override + public boolean isBlockEntityActive() { + return this.blockEntity.isBlockEntityActive(); + } + // Sakura end - optimise hopper ticking + @Override public String toString() { return "Level ticker for " + this.getType() + "@" + this.getPos(); @@ -1028,6 +1035,13 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p return this.ticker.getType(); } + // Sakura start - optimise hopper ticking + @Override + public boolean isBlockEntityActive() { + return this.ticker.isBlockEntityActive(); + } + // Sakura end - optimise hopper ticking + @Override public String toString() { return this.ticker + " ";