9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-26 10:29:13 +00:00
Files
Leaf/leaf-server/minecraft-patches/features/0214-Use-BFS-on-getSlopeDistance.patch
Dreeam 9a4efaa230 Drop patch that causes performance regression
Originally vanilla logic is to use stream, and Mojang switched it to Guava's Collections2
since 1.21.4. It is much faster than using stream or manually adding to a new ArrayList.
Manually adding to a new ArrayList requires allocating a new object array. However, the Collections2
lazy handles filter condition on iteration, so much better.
2025-08-04 19:25:56 +08:00

174 lines
8.0 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
Date: Wed, 19 Mar 2025 18:41:56 +0100
Subject: [PATCH] Use BFS on getSlopeDistance
Uses Breadth First Search (BFS) to optimize getSlopeDistance
Paper: ~75ms
Leaf: ~48ms (-36%)
This should help drastically on the farms that use actively changing fluids.
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index a4c7fd6214ff37d4eb52e42b021c24a13d6a15c9..9453d65fbe08911674cf9090d8729264429c8d8a 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -1443,6 +1443,10 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
this.emptyTime = 0;
}
+ // Leaf start - Use BFS on getSlopeDistance
+ public it.unimi.dsi.fastutil.longs.LongSet slopeDistanceCacheVisited = new it.unimi.dsi.fastutil.longs.LongOpenHashSet(512);
+ public net.minecraft.world.level.material.FlowingFluid.SlopeDistanceNodeDeque slopeDistanceCacheQueue = new net.minecraft.world.level.material.FlowingFluid.SlopeDistanceNodeDeque();
+ // Leaf end - Use BFS on getSlopeDistance
private void tickFluid(BlockPos pos, Fluid fluid) {
BlockState blockState = this.getBlockState(pos);
FluidState fluidState = blockState.getFluidState();
diff --git a/net/minecraft/world/level/material/FlowingFluid.java b/net/minecraft/world/level/material/FlowingFluid.java
index 4fe1b3fc6304a2a404fd0f62f52fc792bcd5dfaf..a00c38b0d6b6d32af4ab3e926705b0f2594157dd 100644
--- a/net/minecraft/world/level/material/FlowingFluid.java
+++ b/net/minecraft/world/level/material/FlowingFluid.java
@@ -342,32 +342,124 @@ public abstract class FlowingFluid extends Fluid {
protected abstract void beforeDestroyingBlock(LevelAccessor level, BlockPos pos, BlockState state);
- protected int getSlopeDistance(LevelReader level, BlockPos pos, int depth, Direction direction, BlockState state, FlowingFluid.SpreadContext spreadContext) {
- int i = 1000;
+ // Leaf start - Use BFS on getSlopeDistance
+ protected int getSlopeDistance(LevelReader level, BlockPos startPos, int initialDepth, Direction excludedDirection, BlockState startState, FlowingFluid.SpreadContext spreadContext) {
+ it.unimi.dsi.fastutil.longs.LongSet visited = ((ServerLevel) level).slopeDistanceCacheVisited;
+ SlopeDistanceNodeDeque queue = ((ServerLevel) level).slopeDistanceCacheQueue;
+ visited.clear();
+ queue.clear();
+
+ for (Direction dir : Direction.Plane.HORIZONTAL) {
+ if (dir == excludedDirection) continue;
+
+ BlockPos neighborPos = startPos.relative(dir); // immutable
+ BlockState neighborState = spreadContext.getBlockStateIfLoaded(neighborPos);
+ if (neighborState == null) continue;
+
+ // Check if the fluid can actually pass through to this first neighbor before adding
+ FluidState neighborFluidState = neighborState.getFluidState();
+ if (!this.canPassThrough(level, this.getFlowing(), startPos, startState, dir, neighborPos, neighborState, neighborFluidState)) {
+ continue;
+ }
+ long visitKey = encodeSlopeNode(neighborPos, dir.getOpposite());
+ if (visited.add(visitKey)) {
+ queue.add(new FlowingFluid.SlopeDistanceNode(neighborPos, initialDepth, dir.getOpposite(), neighborState));
+ }
+ }
- for (Direction direction1 : Direction.Plane.HORIZONTAL) {
- if (direction1 != direction) {
- BlockPos blockPos = pos.relative(direction1);
- BlockState blockState = spreadContext.getBlockStateIfLoaded(blockPos); // Paper - Prevent chunk loading from fluid flowing
- if (blockState == null) continue; // Paper - Prevent chunk loading from fluid flowing
- FluidState fluidState = blockState.getFluidState();
- if (this.canPassThrough(level, this.getFlowing(), pos, state, direction1, blockPos, blockState, fluidState)) {
- if (spreadContext.isHole(blockPos)) {
- return depth;
- }
+ int slopeFindDistance = this.getSlopeFindDistance(level);
+ int minDistance = 1000;
- if (depth < this.getSlopeFindDistance(level)) {
- int slopeDistance = this.getSlopeDistance(level, blockPos, depth + 1, direction1.getOpposite(), blockState, spreadContext);
- if (slopeDistance < i) {
- i = slopeDistance;
- }
- }
+ // Process the queue
+ while (!queue.isEmpty()) {
+ FlowingFluid.SlopeDistanceNode current = queue.poll();
+ if (spreadContext.isHole(current.pos)) {
+ return current.depth;
+ }
+
+ if (current.depth >= slopeFindDistance) continue;
+
+ for (Direction dir : Direction.Plane.HORIZONTAL) {
+ if (dir == current.excludedDir) continue;
+
+ BlockPos nextPos = current.pos.relative(dir); // immutable
+ BlockState nextState = spreadContext.getBlockStateIfLoaded(nextPos);
+ if (nextState == null) continue;
+
+ FluidState nextFluidState = nextState.getFluidState();
+ if (!this.canPassThrough(level, this.getFlowing(), current.pos, current.state, dir, nextPos, nextState, nextFluidState)) {
+ continue;
+ }
+
+ long visitKey = encodeSlopeNode(nextPos, dir.getOpposite());
+ if (visited.add(visitKey)) {
+ queue.add(new FlowingFluid.SlopeDistanceNode(nextPos, current.depth + 1, dir.getOpposite(), nextState));
}
}
}
- return i;
+ return minDistance;
+ }
+
+ private static long encodeSlopeNode(BlockPos pos, Direction excludedDir) {
+ return ((long) pos.getX() & 0xFFFFFFFFL) << 32 | ((long) pos.getZ() & 0xFFFFFFFFL) << 4 | (excludedDir.ordinal() & 0x0F);
+ }
+
+ public static class SlopeDistanceNodeDeque {
+ private SlopeDistanceNode[] array;
+ private int length;
+ private int start;
+ private int end;
+
+ public SlopeDistanceNodeDeque() {
+ array = new SlopeDistanceNode[256];
+ length = array.length;
+ }
+
+ /*
+ private int size() {
+ int apparent = end - start;
+ return apparent >= 0 ? apparent : length + apparent;
+ }
+ */
+
+ private void clear() {
+ start = 0;
+ end = 0;
+ }
+
+ private boolean isEmpty() {
+ return end == start || (end <= start && length == start - end);
+ }
+
+ private SlopeDistanceNode poll() {
+ final SlopeDistanceNode t = array[start];
+ if (++start == length) start = 0;
+ return t;
+ }
+
+ private void add(final SlopeDistanceNode node) {
+ array[end++] = node;
+ if (end == length) end = 0;
+ if (end == start) resize(length, 2 * length);
+ }
+
+ private void resize(final int size, final int newLength) {
+ final SlopeDistanceNode[] newArray = new SlopeDistanceNode[newLength];
+ if (size != 0) {
+ System.arraycopy(array, start, newArray, 0, length - start);
+ System.arraycopy(array, 0, newArray, length - start, end);
+ }
+ start = 0;
+ end = size;
+ array = newArray;
+ length = newLength;
+ }
+ }
+
+ private record SlopeDistanceNode(BlockPos pos, int depth, Direction excludedDir, BlockState state) {
}
+ // Leaf end - Use BFS on getSlopeDistance
boolean isWaterHole(BlockGetter level, BlockPos pos, BlockState state, BlockPos belowPos, BlockState belowState) {
return canPassThroughWall(Direction.DOWN, level, pos, state, belowPos, belowState)