9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-29 11:59:24 +00:00

Update changes from ver/1.21.4 branch

This commit is contained in:
Dreeam
2025-04-17 03:44:13 -04:00
parent 3f4246fe9a
commit c12312bc33
50 changed files with 1082 additions and 309 deletions

View File

@@ -5,7 +5,7 @@ Subject: [PATCH] Configurable connection message
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index c18921f4ec1d4c205fa1d6efbb60eb05dcec4908..a289c3247ef6e1b7ae76fdc86c286e7b426731b4 100644
index c18921f4ec1d4c205fa1d6efbb60eb05dcec4908..2d626735c75caa3ff6d5435882c4303aa204bd1e 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -336,7 +336,7 @@ public abstract class PlayerList {
@@ -35,7 +35,7 @@ index c18921f4ec1d4c205fa1d6efbb60eb05dcec4908..a289c3247ef6e1b7ae76fdc86c286e7b
this.cserver.getPluginManager().callEvent(playerQuitEvent);
player.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage());
@@ -1527,4 +1527,34 @@ public abstract class PlayerList {
@@ -1527,4 +1527,40 @@ public abstract class PlayerList {
public boolean isAllowCommandsForAllPlayers() {
return this.allowCommandsForAllPlayers;
}
@@ -47,7 +47,10 @@ index c18921f4ec1d4c205fa1d6efbb60eb05dcec4908..a289c3247ef6e1b7ae76fdc86c286e7b
+ return io.papermc.paper.adventure.PaperAdventure.asAdventure(defaultJoinMsg);
+ }
+
+ return net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.dreeam.leaf.config.modules.misc.ConnectionMessage.joinMessage)
+ final String joinMessage = org.dreeam.leaf.config.modules.misc.ConnectionMessage.joinMessage
+ .replace("<player_name>", craftPlayer.getName());
+
+ return net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(joinMessage)
+ .replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("<player_name>").replacement(craftPlayer.getName()).build())
+ .replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("<player_displayname>").replacement(craftPlayer.displayName()).build());
+ }
@@ -61,7 +64,10 @@ index c18921f4ec1d4c205fa1d6efbb60eb05dcec4908..a289c3247ef6e1b7ae76fdc86c286e7b
+ return defaultJoinMsg;
+ }
+
+ return net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.dreeam.leaf.config.modules.misc.ConnectionMessage.quitMessage)
+ final String quitMessage = org.dreeam.leaf.config.modules.misc.ConnectionMessage.quitMessage
+ .replace("<player_name>", craftPlayer.getName());
+
+ return net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(quitMessage)
+ .replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("<player_name>").replacement(craftPlayer.getName()).build())
+ .replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("<player_displayname>").replacement(craftPlayer.displayName()).build());
+ }

View File

@@ -199,7 +199,7 @@ index 209a2b6a30d334fc4f6d0b1c02682db7f0b5e435..1489ecc2754901c6f30ec1b5ff0f324b
attributesToSync.clear();
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 66177932ec65b7c4c2df0c021eeefbdb51f71eea..fc1430cb711a86281cfc7b7c94221e7ef867da9e 100644
index 5943b18f172fb1d77ef1fe768daa8e8f43c3c8c1..7b85a9ebdbe3e8bee0a8fc100ede8a3f07eee5ce 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -2503,7 +2503,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -211,6 +211,15 @@ index 66177932ec65b7c4c2df0c021eeefbdb51f71eea..fc1430cb711a86281cfc7b7c94221e7e
return this.moonrise$getEntityLookup(); // Paper - rewrite chunk system
}
@@ -2739,7 +2739,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
}
map.carriedByPlayers.remove(player);
- if (map.carriedBy.removeIf(holdingPlayer -> holdingPlayer.player == player)) {
+ if (map.carriedBy.removeIf(holdingPlayer -> holdingPlayer != null && holdingPlayer.player == player)) { // Leaf - Multithreaded tracker
map.decorations.remove(player.getName().getString());
}
}
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 27ca0be25253b35ebfe54b725f9ba28a120f4ea0..94dd2887398cbebb60200e9a5c61c01b2fda0a7f 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -264,3 +273,15 @@ index 701025715e0aca3c1f920a66f9b3d03ec08eaf02..2b8b335cf5779d1b6eb639935d1b92d8
private final AttributeSupplier supplier;
private final java.util.function.Function<Holder<Attribute>, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations
private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables
diff --git a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
index 3ae69b17fec1cdb2bee2b5a795026a875f197c30..df471cd42f4084facb895b229c261b685054c3ae 100644
--- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
+++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
@@ -211,6 +211,7 @@ public class MapItemSavedData extends SavedData {
for (int i = 0; i < this.carriedBy.size(); i++) {
MapItemSavedData.HoldingPlayer holdingPlayer1 = this.carriedBy.get(i);
+ if (holdingPlayer1 == null) continue; // Leaf - Multithreaded tracker
Player player1 = holdingPlayer1.player;
String string = player1.getName().getString();
if (!player1.isRemoved() && (player1.getInventory().contains(predicate) || mapStack.isFramed())) {

View File

@@ -1,7 +1,7 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com>
Date: Fri, 23 Aug 2024 22:04:20 -0400
Subject: [PATCH] Nitori: Async playerdata Save
Subject: [PATCH] Nitori: Async playerdata saving
Original license: GPL v3
Original project: https://github.com/Gensokyo-Reimagined/Nitori
@@ -24,7 +24,7 @@ index 17d3a8a2cc3c86ed6aae9c20ed9f281dc9715cf5..354823def23167feb1e7b35cd9db5ef1
public String getLocalIp() {
diff --git a/net/minecraft/world/level/storage/LevelStorageSource.java b/net/minecraft/world/level/storage/LevelStorageSource.java
index 8104f71c30c1fa46c83acdf0b2e58483df9d89cc..aec5646db69d972e8c9119e62dad0cece4432ad1 100644
index 8104f71c30c1fa46c83acdf0b2e58483df9d89cc..169b4544ab3d0e8515a2d7020a23ae0e2e0c952d 100644
--- a/net/minecraft/world/level/storage/LevelStorageSource.java
+++ b/net/minecraft/world/level/storage/LevelStorageSource.java
@@ -521,15 +521,26 @@ public class LevelStorageSource {
@@ -45,7 +45,7 @@ index 8104f71c30c1fa46c83acdf0b2e58483df9d89cc..aec5646db69d972e8c9119e62dad0cec
- LevelStorageSource.LOGGER.error("Failed to save level {}", path, var6);
+ LevelStorageSource.LOGGER.error("Failed to encode level {}", path, var6);
}
+ org.dreeam.leaf.async.AsyncPlayerDataSaving.save(() -> {
+ org.dreeam.leaf.async.AsyncPlayerDataSaving.submit(() -> {
+ try {
+ Path path1 = Files.createTempFile(path, "level", ".dat");
+ org.apache.commons.io.FileUtils.writeByteArrayToFile(path1.toFile(), nbtBytes.array, 0, nbtBytes.length, false);
@@ -61,25 +61,25 @@ index 8104f71c30c1fa46c83acdf0b2e58483df9d89cc..aec5646db69d972e8c9119e62dad0cec
public Optional<Path> getIconFile() {
diff --git a/net/minecraft/world/level/storage/PlayerDataStorage.java b/net/minecraft/world/level/storage/PlayerDataStorage.java
index ab9282c04c1996b037567d07f95e2b150bcfcd38..fa33d4b56eec6e00912e8027195c6f63c440bc59 100644
index ab9282c04c1996b037567d07f95e2b150bcfcd38..7a39ea109dee258e7fb83982572ee18731b5446f 100644
--- a/net/minecraft/world/level/storage/PlayerDataStorage.java
+++ b/net/minecraft/world/level/storage/PlayerDataStorage.java
@@ -23,6 +23,7 @@ public class PlayerDataStorage {
private final File playerDir;
protected final DataFixer fixerUpper;
private static final DateTimeFormatter FORMATTER = FileNameDateFormatter.create();
+ public final it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<java.util.UUID> savingQueue = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(); // Leaf - Async playerdata saving
+ private final java.util.Map<java.util.UUID, java.util.concurrent.Future<?>> savingLocks = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(); // Leaf - Async playerdata saving
public PlayerDataStorage(LevelStorageSource.LevelStorageAccess levelStorageAccess, DataFixer fixerUpper) {
this.fixerUpper = fixerUpper;
@@ -32,17 +33,43 @@ public class PlayerDataStorage {
@@ -32,19 +33,82 @@ public class PlayerDataStorage {
public void save(Player player) {
if (org.spigotmc.SpigotConfig.disablePlayerDataSaving) return; // Spigot
+ // Leaf start - Async playerdata saving
+ var nbtBytes = new it.unimi.dsi.fastutil.io.FastByteArrayOutputStream(65536);
+ CompoundTag compoundTag;
try {
CompoundTag compoundTag = player.saveWithoutId(new CompoundTag());
- CompoundTag compoundTag = player.saveWithoutId(new CompoundTag());
- Path path = this.playerDir.toPath();
- Path path1 = Files.createTempFile(path, player.getStringUUID() + "-", ".dat");
- NbtIo.writeCompressed(compoundTag, path1);
@@ -88,43 +88,83 @@ index ab9282c04c1996b037567d07f95e2b150bcfcd38..fa33d4b56eec6e00912e8027195c6f63
- Util.safeReplaceFile(path2, path1, path3);
- } catch (Exception var7) {
- LOGGER.warn("Failed to save player data for {}", player.getScoreboardName(), var7); // Paper - Print exception
+ NbtIo.writeCompressed(compoundTag, nbtBytes);
+ compoundTag = player.saveWithoutId(new CompoundTag());
+ } catch (Exception exception) {
+ LOGGER.warn("Failed to encode player data for {}", player.getScoreboardName(), exception);
+ return;
}
+ String playerName = player.getScoreboardName();
+ String stringUuid = player.getStringUUID();
+ java.util.UUID playerUuid = player.getUUID();
+ synchronized (PlayerDataStorage.this) {
+ while (savingQueue.contains(playerUuid)) {
+ try {
+ Thread.sleep(1L);
+ } catch (InterruptedException ignored) {
+ }
+ }
+ savingQueue.add(playerUuid);
+ }
+ org.dreeam.leaf.async.AsyncPlayerDataSaving.save(() -> {
+ try {
+ Path path = this.playerDir.toPath();
+ Path path1 = Files.createTempFile(path, stringUuid + "-", ".dat");
+ org.apache.commons.io.FileUtils.writeByteArrayToFile(path1.toFile(), nbtBytes.array, 0, nbtBytes.length, false);
+ Path path2 = path.resolve(stringUuid + ".dat");
+ Path path3 = path.resolve(stringUuid + ".dat_old");
+ Util.safeReplaceFile(path2, path1, path3);
+ } catch (Exception var7) {
+ LOGGER.warn("Failed to save player data for {}", playerName, var7); // Paper - Print exception
+ } finally {
+ synchronized (PlayerDataStorage.this) {
+ savingQueue.remove(playerUuid);
+ }
+ }
+ });
+ save(player.getScoreboardName(), player.getUUID(), player.getStringUUID(), compoundTag);
+ // Leaf end - Async playerdata saving
}
+ // Leaf start - Async playerdata saving
+ public void save(String playerName, java.util.UUID uniqueId, String stringId, CompoundTag compoundTag) {
+ var nbtBytes = new it.unimi.dsi.fastutil.io.FastByteArrayOutputStream(65536);
+ try {
+ NbtIo.writeCompressed(compoundTag, nbtBytes);
+ } catch (Exception exception) {
+ LOGGER.warn("Failed to encode player data for {}", stringId, exception);
+ }
+ lockFor(uniqueId, playerName);
+ synchronized (PlayerDataStorage.this) {
+ org.dreeam.leaf.async.AsyncPlayerDataSaving.submit(() -> {
+ try {
+ Path path = this.playerDir.toPath();
+ Path path1 = Files.createTempFile(path, stringId + "-", ".dat");
+ org.apache.commons.io.FileUtils.writeByteArrayToFile(path1.toFile(), nbtBytes.array, 0, nbtBytes.length, false);
+ Path path2 = path.resolve(stringId + ".dat");
+ Path path3 = path.resolve(stringId + ".dat_old");
+ Util.safeReplaceFile(path2, path1, path3);
+ } catch (Exception var7) {
+ LOGGER.warn("Failed to save player data for {}", playerName, var7);
+ } finally {
+ synchronized (PlayerDataStorage.this) {
+ savingLocks.remove(uniqueId);
+ }
+ }
+ }).ifPresent(future -> savingLocks.put(uniqueId, future));
+ }
+ }
+
+ private void lockFor(java.util.UUID uniqueId, String playerName) {
+ java.util.concurrent.Future<?> fut;
+ synchronized (this) {
+ fut = savingLocks.get(uniqueId);
+ }
+ if (fut == null) {
+ return;
+ }
+ while (true) {
+ try {
+ fut.get(10_000L, java.util.concurrent.TimeUnit.MILLISECONDS);
+ break;
+ } catch (InterruptedException ignored) {
+ } catch (java.util.concurrent.ExecutionException
+ | java.util.concurrent.TimeoutException exception) {
+ LOGGER.warn("Failed to save player data for {}", playerName, exception);
+
+ String threadDump = "";
+ var threadMXBean = java.lang.management.ManagementFactory.getThreadMXBean();
+ for (var threadInfo : threadMXBean.dumpAllThreads(true, true)) {
+ if (threadInfo.getThreadName().equals("Leaf IO Thread")) {
+ threadDump = threadInfo.toString();
+ break;
+ }
+ }
+ LOGGER.warn(threadDump);
+ fut.cancel(true);
+ break;
+ } finally {
+ savingLocks.remove(uniqueId);
+ }
+ }
+ }
+ // Leaf end - Async playerdata saving
+
private void backup(String name, String stringUuid, String suffix) { // CraftBukkit
@@ -58,7 +85,20 @@ public class PlayerDataStorage {
Path path = this.playerDir.toPath();
Path path1 = path.resolve(stringUuid + suffix); // CraftBukkit
@@ -58,7 +122,13 @@ public class PlayerDataStorage {
}
}
@@ -134,19 +174,12 @@ index ab9282c04c1996b037567d07f95e2b150bcfcd38..fa33d4b56eec6e00912e8027195c6f63
+ return load(name, stringUuid, suffix, java.util.UUID.fromString(stringUuid));
+ }
+ private Optional<CompoundTag> load(String name, String stringUuid, String suffix, java.util.UUID playerUuid) { // CraftBukkit
+ synchronized (PlayerDataStorage.this) {
+ while (savingQueue.contains(playerUuid)) {
+ try {
+ Thread.sleep(1L);
+ } catch (InterruptedException ignored) {
+ }
+ }
+ }
+ lockFor(playerUuid, name);
+ // Leaf end - Async playerdata saving
File file = new File(this.playerDir, stringUuid + suffix); // CraftBukkit
// Spigot start
boolean usingWrongFile = false;
@@ -89,7 +129,7 @@ public class PlayerDataStorage {
@@ -89,7 +159,7 @@ public class PlayerDataStorage {
public Optional<CompoundTag> load(Player player) {
// CraftBukkit start
@@ -155,7 +188,7 @@ index ab9282c04c1996b037567d07f95e2b150bcfcd38..fa33d4b56eec6e00912e8027195c6f63
if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) {
org.bukkit.craftbukkit.entity.CraftPlayer craftPlayer = serverPlayer.getBukkitEntity();
// Only update first played if it is older than the one we have
@@ -104,20 +144,25 @@ public class PlayerDataStorage {
@@ -104,20 +174,25 @@ public class PlayerDataStorage {
});
}

View File

@@ -1,19 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Author: PureGero" <puregero@gmail.com>
Date: Thu, 1 Aug 2024 00:43:05 +0900
Subject: [PATCH] ShreddedPaper: Don't block main thread in
Connection#syncAfterConfigurationChange
diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
index 00a82873d226f113278632a53c0faca420dd67d4..5b46036868b6c9d082e35591e58735e16adaae62 100644
--- a/net/minecraft/network/Connection.java
+++ b/net/minecraft/network/Connection.java
@@ -325,6 +325,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
private static void syncAfterConfigurationChange(ChannelFuture future) {
try {
+ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThread()) net.minecraft.server.MinecraftServer.getServer().managedBlock(future::isDone); // ShreddedPaper - Don't block main thread in Connection#syncAfterConfigurationChange
future.syncUninterruptibly();
} catch (Exception var2) {
if (var2 instanceof ClosedChannelException) {

View File

@@ -159,7 +159,7 @@ index 4ca68a903e67606fc4ef0bfa9862a73797121c8b..bed3a64388bb43e47c2ba4e67f7dde5b
public static final class SaveState {
diff --git a/net/minecraft/world/level/chunk/LevelChunkSection.java b/net/minecraft/world/level/chunk/LevelChunkSection.java
index 963c51d14f87d2557a3d686fb8fe3ec9cba367b3..492618b13ecc7ba541339fea2f4ea4daddfa143f 100644
index 36c033b0ee63dfc273d721fb4b614733e8fdef19..dc9f1bc6dd8b1057da3416e24f15f2329658f996 100644
--- a/net/minecraft/world/level/chunk/LevelChunkSection.java
+++ b/net/minecraft/world/level/chunk/LevelChunkSection.java
@@ -24,6 +24,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_

View File

@@ -9,27 +9,37 @@ Leaf: ~48ms (-36%)
This should help drastically on the farms that use actively changing fluids.
diff --git a/net/minecraft/world/level/material/FlowingFluid.java b/net/minecraft/world/level/material/FlowingFluid.java
index 4625bd55e1cb01dfb9921dcd033f05b4a8f9ad74..8646ccf77f738d7b66efd0a7ecf2519d8a8d6bdf 100644
index 4625bd55e1cb01dfb9921dcd033f05b4a8f9ad74..c0f78556d112e59333ace60f1522e7fd1efe71c3 100644
--- a/net/minecraft/world/level/material/FlowingFluid.java
+++ b/net/minecraft/world/level/material/FlowingFluid.java
@@ -8,6 +8,8 @@ import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Queue;
+
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
@@ -342,31 +344,72 @@ public abstract class FlowingFluid extends Fluid {
@@ -342,32 +342,81 @@ 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 = new it.unimi.dsi.fastutil.longs.LongOpenHashSet(512); // Pre-allocate capacity
+ java.util.Queue<FlowingFluid.SlopeDistanceNode> queue = new java.util.ArrayDeque<>(64); // Optimized initial capacity
+ it.unimi.dsi.fastutil.longs.LongSet visited = new it.unimi.dsi.fastutil.longs.LongOpenHashSet(512);
+ java.util.Queue<FlowingFluid.SlopeDistanceNode> queue = new java.util.ArrayDeque<>(256);
+
+ for (Direction dir : Direction.Plane.HORIZONTAL) {
+ if (dir == excludedDirection) continue;
+
+ BlockPos neighborPos = startPos.relative(dir);
+ 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) {
@@ -41,8 +51,8 @@ index 4625bd55e1cb01dfb9921dcd033f05b4a8f9ad74..8646ccf77f738d7b66efd0a7ecf2519d
- if (spreadContext.isHole(blockPos)) {
- return depth;
- }
+ for (Direction dir : Direction.Plane.HORIZONTAL) {
+ if (dir == excludedDirection) continue;
+ 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);
@@ -50,27 +60,15 @@ index 4625bd55e1cb01dfb9921dcd033f05b4a8f9ad74..8646ccf77f738d7b66efd0a7ecf2519d
- i = slopeDistance;
- }
- }
+ BlockPos neighborPos = startPos.relative(dir);
+ BlockState neighborState = spreadContext.getBlockStateIfLoaded(neighborPos);
+ if (neighborState == null) continue;
+ long visitKey = encodeSlopeNode(neighborPos, dir.getOpposite());
+ if (visited.add(visitKey)) {
+ queue.add(new FlowingFluid.SlopeDistanceNode(neighborPos, initialDepth + 1, dir.getOpposite(), neighborState));
+ }
+ }
+
+ int slopeFindDistance = this.getSlopeFindDistance(level);
+ int minDistance = 1000;
+
+ // Process the queue
+ while (!queue.isEmpty()) {
+ FlowingFluid.SlopeDistanceNode current = queue.poll();
+ if (current.depth >= slopeFindDistance) continue;
+
+ if (spreadContext.isHole(current.pos)) {
+ return current.depth;
+ }
+
+ if (current.depth >= slopeFindDistance) continue;
+
+ for (Direction dir : Direction.Plane.HORIZONTAL) {
+ if (dir == current.excludedDir) continue;
+
@@ -91,7 +89,7 @@ index 4625bd55e1cb01dfb9921dcd033f05b4a8f9ad74..8646ccf77f738d7b66efd0a7ecf2519d
}
- return i;
+ return minDistance; // Return fallback value
+ return minDistance;
+ }
+
+ private static long encodeSlopeNode(BlockPos pos, Direction excludedDir) {
@@ -111,5 +109,7 @@ index 4625bd55e1cb01dfb9921dcd033f05b4a8f9ad74..8646ccf77f738d7b66efd0a7ecf2519d
+ this.state = 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)

View File

@@ -5,7 +5,7 @@ Subject: [PATCH] Micro optimizations for random tick
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index d8390cb3901a40b97e99990d9f71f12c74f96607..4d7cf866452db7388ab90f9be284e859b39d8e61 100644
index 7ca4fd418599cdb1bb1de44f4c3c57f1770a4038..461b620b703c9fca2691f724a9b865ad54aa90a4 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -903,7 +903,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe

View File

@@ -0,0 +1,183 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
Date: Sun, 23 Mar 2025 11:51:44 +0100
Subject: [PATCH] Async Block Finding
diff --git a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..007da9cb39ff76285c52ce0abdff60997acdff0f 100644
--- a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
+++ b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
@@ -20,6 +20,18 @@ public abstract class MoveToBlockGoal extends Goal {
private final int verticalSearchRange;
protected int verticalSearchStart;
+ // Leaf start - Async Block Finding
+ private static final java.util.concurrent.ExecutorService BLOCK_FINDER_EXECUTOR =
+ java.util.concurrent.Executors.newSingleThreadExecutor(
+ new com.google.common.util.concurrent.ThreadFactoryBuilder()
+ .setNameFormat("Leaf Block Finding Thread - %d")
+ .setDaemon(true)
+ .build());
+
+ private final java.util.concurrent.ConcurrentLinkedQueue<BlockPos> candidateBlocks = new java.util.concurrent.ConcurrentLinkedQueue<>();
+ private boolean asyncSearchInProgress = false;
+ // Leaf end - Async Block Finding
+
public MoveToBlockGoal(PathfinderMob mob, double speedModifier, int searchRange) {
this(mob, speedModifier, searchRange, 1);
}
@@ -29,6 +41,10 @@ public abstract class MoveToBlockGoal extends Goal {
super.stop();
this.blockPos = BlockPos.ZERO;
this.mob.movingTarget = null;
+ // Leaf start - Async Block Finding - Reset async state on goal stop
+ this.candidateBlocks.clear();
+ this.asyncSearchInProgress = false;
+ // Leaf end - Async Block Finding - Reset async state on goal stop
}
// Paper end
@@ -53,23 +69,23 @@ public abstract class MoveToBlockGoal extends Goal {
}
protected int nextStartTick(PathfinderMob creature) {
- return reducedTickDelay(200 + creature.getRandom().nextInt(200));
+ return Goal.reducedTickDelay(200 + creature.getRandom().nextInt(200)); // Leaf - Async Block Finding - Use the static method from the Goal class directly
}
@Override
public boolean canContinueToUse() {
- return this.tryTicks >= -this.maxStayTicks && this.tryTicks <= 1200 && this.isValidTarget(this.mob.level(), this.blockPos);
+ return this.tryTicks >= -this.maxStayTicks && this.tryTicks <= 1200 && this.blockPos != BlockPos.ZERO && this.isValidTarget(this.mob.level(), this.blockPos); // Leaf - Async Block Finding
}
@Override
public void start() {
- this.moveMobToBlock();
+ if (this.blockPos != BlockPos.ZERO) this.moveMobToBlock(); // Leaf - Async Block Finding
this.tryTicks = 0;
this.maxStayTicks = this.mob.getRandom().nextInt(this.mob.getRandom().nextInt(1200) + 1200) + 1200;
}
protected void moveMobToBlock() {
- this.mob.getNavigation().moveTo(this.blockPos.getX() + 0.5, this.blockPos.getY() + 1, this.blockPos.getZ() + 0.5, this.speedModifier);
+ if (this.blockPos != BlockPos.ZERO) this.mob.getNavigation().moveTo(this.blockPos.getX() + 0.5, this.blockPos.getY() + 1, this.blockPos.getZ() + 0.5, this.speedModifier); // Leaf - Async Block Finding
}
public double acceptedDistance() {
@@ -77,7 +93,7 @@ public abstract class MoveToBlockGoal extends Goal {
}
protected BlockPos getMoveToTarget() {
- return this.blockPos.above();
+ return this.blockPos != BlockPos.ZERO ? this.blockPos.above() : BlockPos.ZERO; // Leaf - Async Block Finding
}
@Override
@@ -87,7 +103,10 @@ public abstract class MoveToBlockGoal extends Goal {
@Override
public void tick() {
+ if (this.blockPos == BlockPos.ZERO) return; // Leaf - Async Block Finding
BlockPos moveToTarget = this.getMoveToTarget();
+ if (moveToTarget == BlockPos.ZERO) return; // Leaf - Async Block Finding
+
if (!moveToTarget.closerToCenterThan(this.mob.position(), this.acceptedDistance())) {
this.reachedTarget = false;
this.tryTicks++;
@@ -109,20 +128,90 @@ public abstract class MoveToBlockGoal extends Goal {
}
protected boolean findNearestBlock() {
+ // Leaf start - Async Block Finding
+ if (!org.dreeam.leaf.config.modules.async.AsyncBlockFinding.enabled) {
+ return findNearestBlockSync();
+ }
+
+ while (!candidateBlocks.isEmpty()) {
+ BlockPos pos = candidateBlocks.poll();
+ if (pos != null && this.mob.level().hasChunkAt(pos) &&
+ this.mob.isWithinRestriction(pos) &&
+ this.isValidTarget(this.mob.level(), pos)) {
+
+ this.blockPos = pos;
+ this.mob.movingTarget = pos == BlockPos.ZERO ? null : pos;
+ return true;
+ }
+ }
+
+ if (asyncSearchInProgress) {
+ return false;
+ }
+
+ // Check again before starting, avoids tiny race condition if canUse is called rapidly
+ if (!asyncSearchInProgress) {
+ asyncSearchInProgress = true;
+ final BlockPos centerPos = this.mob.blockPosition().immutable();
+ final int searchRange = this.searchRange;
+ final int verticalRange = this.verticalSearchRange;
+ final int verticalStart = this.verticalSearchStart;
+
+ BLOCK_FINDER_EXECUTOR.execute(() -> {
+ try {
+ generateCandidateBlocks(centerPos, searchRange, verticalRange, verticalStart);
+ } catch (Exception e) {
+ e.printStackTrace(); // Keep basic error logging
+ } finally {
+ asyncSearchInProgress = false;
+ }
+ });
+ }
+
+ return false;
+ }
+
+ private void generateCandidateBlocks(BlockPos center, int searchRange, int verticalRange, int verticalStart) {
+ java.util.List<BlockPos> positions = new java.util.ArrayList<>();
+
+ for (int i2 = verticalStart; i2 <= verticalRange; i2 = i2 > 0 ? -i2 : 1 - i2) {
+ for (int i3 = 0; i3 < searchRange; i3++) {
+ for (int i4 = 0; i4 <= i3; i4 = i4 > 0 ? -i4 : 1 - i4) {
+ for (int i5 = i4 < i3 && i4 > -i3 ? i3 : 0; i5 <= i3; i5 = i5 > 0 ? -i5 : 1 - i5) {
+ BlockPos pos = center.offset(i4, i2 - 1, i5);
+ positions.add(pos.immutable());
+ }
+ }
+ }
+ }
+
+ positions.sort((p1, p2) -> {
+ double d1 = p1.distSqr(center);
+ double d2 = p2.distSqr(center);
+ return Double.compare(d1, d2);
+ });
+
+ for (BlockPos pos : positions) {
+ candidateBlocks.add(pos);
+ }
+ }
+
+ protected boolean findNearestBlockSync() {
+ // Leaf end - Async Block Finding
int i = this.searchRange;
int i1 = this.verticalSearchRange;
- BlockPos blockPos = this.mob.blockPosition();
+ BlockPos blockPosOrigin = this.mob.blockPosition(); // Leaf - Async Block Finding
BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
for (int i2 = this.verticalSearchStart; i2 <= i1; i2 = i2 > 0 ? -i2 : 1 - i2) {
for (int i3 = 0; i3 < i; i3++) {
for (int i4 = 0; i4 <= i3; i4 = i4 > 0 ? -i4 : 1 - i4) {
for (int i5 = i4 < i3 && i4 > -i3 ? i3 : 0; i5 <= i3; i5 = i5 > 0 ? -i5 : 1 - i5) {
- mutableBlockPos.setWithOffset(blockPos, i4, i2 - 1, i5);
+ mutableBlockPos.setWithOffset(blockPosOrigin, i4, i2 - 1, i5); // Leaf - Async Block Finding
if (!this.mob.level().hasChunkAt(mutableBlockPos)) continue; // Gale - Airplane - block goal does not load chunks - if this block isn't loaded, continue
if (this.mob.isWithinRestriction(mutableBlockPos) && this.isValidTarget(this.mob.level(), mutableBlockPos)) {
- this.blockPos = mutableBlockPos;
- this.mob.movingTarget = mutableBlockPos == BlockPos.ZERO ? null : mutableBlockPos.immutable(); // Paper
+ this.blockPos = mutableBlockPos.immutable(); // Leaf - Async Block Finding
+ this.mob.movingTarget = this.blockPos == BlockPos.ZERO ? null : this.blockPos; // Paper // Leaf - Async Block Finding
return true;
}
}

View File

@@ -1,126 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
Date: Sun, 23 Mar 2025 11:51:44 +0100
Subject: [PATCH] Async Block Finding
diff --git a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..2bc0f19b86067491f33f647d2e387acd83492844 100644
--- a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
+++ b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java
@@ -20,6 +20,19 @@ public abstract class MoveToBlockGoal extends Goal {
private final int verticalSearchRange;
protected int verticalSearchStart;
+ // Leaf start - Async Block Finding
+ private static final java.util.concurrent.ExecutorService BLOCK_FINDER_EXECUTOR =
+ java.util.concurrent.Executors.newSingleThreadExecutor(
+ new com.google.common.util.concurrent.ThreadFactoryBuilder()
+ .setNameFormat("Leaf Block Finding Thread - %d")
+ .setDaemon(true)
+ .build());
+
+ private final java.util.concurrent.ConcurrentLinkedQueue<BlockPos> candidateBlocks = new java.util.concurrent.ConcurrentLinkedQueue<>();
+ private boolean asyncSearchInProgress = false;
+ private boolean searchComplete = false;
+ // Leaf end - Async Block Finding
+
public MoveToBlockGoal(PathfinderMob mob, double speedModifier, int searchRange) {
this(mob, speedModifier, searchRange, 1);
}
@@ -109,6 +122,95 @@ public abstract class MoveToBlockGoal extends Goal {
}
protected boolean findNearestBlock() {
+ // Leaf start - Async Block Finding
+ // Check if async is enabled, if not use the original implementation
+ if (!org.dreeam.leaf.config.modules.async.AsyncBlockFinding.enabled) {
+ return findNearestBlockSync();
+ }
+
+ // Check if we're done with the current search
+ if (searchComplete) {
+ searchComplete = false;
+ asyncSearchInProgress = false;
+ candidateBlocks.clear();
+ return false;
+ }
+
+ while (!candidateBlocks.isEmpty()) {
+ BlockPos pos = candidateBlocks.poll();
+
+ if (pos != null && this.mob.level().hasChunkAt(pos) &&
+ this.mob.isWithinRestriction(pos) &&
+ this.isValidTarget(this.mob.level(), pos)) {
+
+ this.blockPos = pos;
+ this.mob.movingTarget = pos == BlockPos.ZERO ? null : pos;
+ searchComplete = true;
+ return true;
+ }
+ }
+
+ // If no candidates are left and async search is done
+ if (candidateBlocks.isEmpty() && !asyncSearchInProgress) {
+ searchComplete = true;
+ return false;
+ }
+
+ // Start async search if needed
+ if (!asyncSearchInProgress && candidateBlocks.isEmpty()) {
+ asyncSearchInProgress = true;
+
+ // Get necessary data from main thread
+ final BlockPos centerPos = this.mob.blockPosition().immutable();
+ final int searchRange = this.searchRange;
+ final int verticalRange = this.verticalSearchRange;
+ final int verticalStart = this.verticalSearchStart;
+ BLOCK_FINDER_EXECUTOR.execute(() -> {
+ try {
+ generateCandidateBlocks(centerPos, searchRange, verticalRange, verticalStart);
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ asyncSearchInProgress = false;
+ }
+ });
+ }
+
+ return false;
+ }
+
+ // Generate candidate blocks in a spiral pattern
+ private void generateCandidateBlocks(BlockPos center, int searchRange, int verticalRange, int verticalStart) {
+ // Pre-calculate a prioritized list of positions
+ java.util.List<BlockPos> positions = new java.util.ArrayList<>();
+
+ for (int i2 = verticalStart; i2 <= verticalRange; i2 = i2 > 0 ? -i2 : 1 - i2) {
+ for (int i3 = 0; i3 < searchRange; i3++) {
+ for (int i4 = 0; i4 <= i3; i4 = i4 > 0 ? -i4 : 1 - i4) {
+ for (int i5 = i4 < i3 && i4 > -i3 ? i3 : 0; i5 <= i3; i5 = i5 > 0 ? -i5 : 1 - i5) {
+ BlockPos pos = center.offset(i4, i2 - 1, i5);
+ positions.add(pos.immutable());
+ }
+ }
+ }
+ }
+
+ // Sort by distance to center (closest first)
+ positions.sort((p1, p2) -> {
+ double d1 = p1.distSqr(center);
+ double d2 = p2.distSqr(center);
+ return Double.compare(d1, d2);
+ });
+
+ // Add to candidate queue
+ for (BlockPos pos : positions) {
+ candidateBlocks.add(pos);
+ }
+ }
+
+ // The original method renamed
+ protected boolean findNearestBlockSync() {
+ // Leaf end - Async Block Finding
int i = this.searchRange;
int i1 = this.verticalSearchRange;
BlockPos blockPos = this.mob.blockPosition();

View File

@@ -26,7 +26,7 @@ Locally this patch drops the entity tracker tick by a full 1.5x.
Co-authored-by: Quang Tran <3d7777456@gmail.com>
diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
index 5b46036868b6c9d082e35591e58735e16adaae62..9563165c945757996da11f55e2221e620dd93327 100644
index 00a82873d226f113278632a53c0faca420dd67d4..f3e9de8716f5e1a72ec465ee897c8f0413f7b1c3 100644
--- a/net/minecraft/network/Connection.java
+++ b/net/minecraft/network/Connection.java
@@ -147,6 +147,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
@@ -45,7 +45,7 @@ index 5b46036868b6c9d082e35591e58735e16adaae62..9563165c945757996da11f55e2221e62
this.address = this.channel.remoteAddress();
this.preparing = false; // Spigot
if (this.delayedDisconnect != null) {
@@ -477,6 +479,11 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
@@ -476,6 +478,11 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
if (this.channel.eventLoop().inEventLoop()) {
this.doSendPacket(packet, sendListener, flush);
} else {

View File

@@ -0,0 +1,319 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
Date: Sat, 29 Mar 2025 13:40:46 +0100
Subject: [PATCH] Async Target Finding
diff --git a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java
index 024792900c8ab716e91ef512d2da22548075044d..7f256793232cfa9666728223cb9964e49ff8b6ba 100644
--- a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java
+++ b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java
@@ -16,9 +16,37 @@ public class NearestAttackableTargetGoal<T extends LivingEntity> extends TargetG
protected final Class<T> targetType;
protected final int randomInterval;
@Nullable
- protected LivingEntity target;
+ protected volatile LivingEntity target; // Leaf - Async Target Finding
protected TargetingConditions targetConditions;
+ // Leaf start - Async Target Finding
+ // Single thread executor to prevent overwhelming the server
+ private static final java.util.concurrent.ExecutorService TARGET_FINDER_EXECUTOR = java.util.concurrent.Executors.newSingleThreadExecutor(r -> {
+ Thread thread = new Thread(r, "Leaf - Target-Finder-Thread");
+ thread.setDaemon(true);
+ thread.setPriority(Thread.MIN_PRIORITY); // Lower priority to avoid competing with main thread
+ return thread;
+ });
+
+ // Flag to track if a search is in progress
+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false);
+ private final java.util.concurrent.atomic.AtomicReference<LivingEntity> pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null);
+ static {
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ try {
+ TARGET_FINDER_EXECUTOR.shutdown();
+ TARGET_FINDER_EXECUTOR.awaitTermination(2, java.util.concurrent.TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ } finally {
+ if (!TARGET_FINDER_EXECUTOR.isTerminated()) {
+ TARGET_FINDER_EXECUTOR.shutdownNow();
+ }
+ }
+ }));
+ }
+ // Leaf end - Async Target Finding
+
public NearestAttackableTargetGoal(Mob mob, Class<T> targetType, boolean mustSee) {
this(mob, targetType, 10, mustSee, false, null);
}
@@ -46,8 +74,14 @@ public class NearestAttackableTargetGoal<T extends LivingEntity> extends TargetG
if (this.randomInterval > 0 && this.mob.getRandom().nextInt(this.randomInterval) != 0) {
return false;
} else {
- this.findTarget();
- return this.target != null;
+ // Leaf start - Async Target Finding
+ findTarget();
+ LivingEntity pending = pendingTarget.getAndSet(null);
+ if (pending != null && !pending.isRemoved() && pending.isAlive()) {
+ this.target = pending;
+ }
+ return this.target != null && this.target.isAlive() && !this.target.isRemoved();
+ // Leaf end - Async Target Finding
}
}
@@ -55,25 +89,239 @@ public class NearestAttackableTargetGoal<T extends LivingEntity> extends TargetG
return this.mob.getBoundingBox().inflate(targetDistance, targetDistance, targetDistance);
}
+ // Leaf start - Async Target Finding
+ // Async find target implementation with safer entity handling
protected void findTarget() {
- ServerLevel serverLevel = getServerLevel(this.mob);
- if (this.targetType != Player.class && this.targetType != ServerPlayer.class) {
- this.target = serverLevel.getNearestEntity(
- this.mob.level().getEntitiesOfClass(this.targetType, this.getTargetSearchArea(this.getFollowDistance()), entity -> true),
- this.getTargetConditions(),
- this.mob,
- this.mob.getX(),
- this.mob.getEyeY(),
- this.mob.getZ()
- );
- } else {
- this.target = serverLevel.getNearestPlayer(this.getTargetConditions(), this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ());
+ // If async is disabled or we're already searching, use sync method
+ if (!org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled || !isSearching.compareAndSet(false, true)) {
+ findTargetSync();
+ return;
}
+
+ // Capture mutable state to avoid race conditions
+ final Mob mob = this.mob;
+
+ // Safety check
+ if (mob == null || mob.isRemoved() || !mob.isAlive()) {
+ isSearching.set(false);
+ return;
+ }
+
+ final double x = mob.getX();
+ final double y = mob.getEyeY();
+ final double z = mob.getZ();
+ final double followDistance = this.getFollowDistance();
+ final TargetingConditions targetConditions = this.getTargetConditions();
+ final Class<T> targetType = this.targetType;
+
+ // Start async search with immutable captured state - using submit instead of runAsync
+ java.util.concurrent.CompletableFuture.supplyAsync(() -> {
+ try {
+ ServerLevel serverLevel = getServerLevel(mob);
+ if (serverLevel == null) {
+ return null;
+ }
+ if (mob.isRemoved() || !mob.isAlive()) {
+ return null;
+ }
+
+ try {
+ if (targetType != Player.class && targetType != ServerPlayer.class) {
+ AABB searchArea = new AABB(
+ x - followDistance, y - followDistance, z - followDistance,
+ x + followDistance, y + followDistance, z + followDistance
+ );
+
+ java.util.List<T> entities = null;
+ try {
+ entities = mob.level().getEntitiesOfClass(targetType, searchArea, entity -> true);
+ } catch (Exception e) {
+ System.err.println("Error getting entities: " + e.getMessage());
+ return null;
+ }
+
+ if (entities != null && !entities.isEmpty()) {
+ return findNearestEntitySafely(entities, targetConditions, mob, x, y, z, serverLevel);
+ }
+ } else {
+ return findNearestPlayerSafely(targetConditions, mob, x, y, z, serverLevel);
+ }
+ } catch (Exception e) {
+ System.err.println("Error finding entities in async target finder: " + e.getMessage());
+ }
+
+ return null;
+ } catch (Exception e) {
+ System.err.println("Error during async target finding: " + e.getMessage());
+ return null;
+ } finally {
+ isSearching.set(false);
+ }
+ }, TARGET_FINDER_EXECUTOR).thenAccept(result -> {
+ if (result != null && result.isAlive() && !result.isRemoved()) {
+ pendingTarget.set(result);
+ }
+ });
}
+ @Nullable
+ private LivingEntity findNearestEntitySafely(
+ java.util.List<? extends LivingEntity> entities,
+ TargetingConditions conditions,
+ Mob source,
+ double x,
+ double y,
+ double z,
+ ServerLevel level) {
+
+ if (entities == null || entities.isEmpty() || level == null) {
+ return null;
+ }
+
+ try {
+ double closestDistSq = -1.0;
+ LivingEntity closest = null;
+
+ for (int i = 0; i < entities.size(); i++) {
+ try {
+ LivingEntity entity = entities.get(i);
+ if (entity == null || entity.isRemoved() || !entity.isAlive()) {
+ continue;
+ }
+
+ if (conditions.test(level, source, entity)) {
+ double dx = entity.getX() - x;
+ double dy = entity.getY() - y;
+ double dz = entity.getZ() - z;
+ double distSq = dx * dx + dy * dy + dz * dz;
+
+ if (closestDistSq == -1.0 || distSq < closestDistSq) {
+ closestDistSq = distSq;
+ closest = entity;
+ }
+ }
+ } catch (IndexOutOfBoundsException e) {
+ break;
+ } catch (Exception e) {
+ System.err.println("Error processing entity in findNearestEntitySafely: " + e.getMessage());
+ continue;
+ }
+ }
+
+ return closest;
+ } catch (Exception e) {
+ System.err.println("Error in findNearestEntitySafely: " + e.getMessage());
+ return null;
+ }
+ }
+
+ @Nullable
+ private Player findNearestPlayerSafely(
+ TargetingConditions conditions,
+ Mob source,
+ double x,
+ double y,
+ double z,
+ ServerLevel level) {
+
+ if (level == null) {
+ return null;
+ }
+
+ try {
+ java.util.List<? extends Player> players = level.players();
+ if (players == null || players.isEmpty()) {
+ return null;
+ }
+
+ double closestDistSq = -1.0;
+ Player closest = null;
+
+ for (int i = 0; i < players.size(); i++) {
+ try {
+ Player player = players.get(i);
+ if (player == null || player.isRemoved() || !player.isAlive()) {
+ continue;
+ }
+
+ if (conditions.test(level, source, player)) {
+ double dx = player.getX() - x;
+ double dy = player.getY() - y;
+ double dz = player.getZ() - z;
+ double distSq = dx * dx + dy * dy + dz * dz;
+
+ if (closestDistSq == -1.0 || distSq < closestDistSq) {
+ closestDistSq = distSq;
+ closest = player;
+ }
+ }
+ } catch (IndexOutOfBoundsException e) {
+ break;
+ } catch (Exception e) {
+ System.err.println("Error processing player in findNearestPlayerSafely: " + e.getMessage());
+ continue;
+ }
+ }
+
+ return closest;
+ } catch (Exception e) {
+ System.err.println("Error in findNearestPlayerSafely: " + e.getMessage());
+ return null;
+ }
+ }
+
+ // Synchronous fallback method
+ private void findTargetSync() {
+ try {
+ ServerLevel serverLevel = getServerLevel(this.mob);
+ if (serverLevel == null) {
+ return;
+ }
+
+ if (this.targetType != Player.class && this.targetType != ServerPlayer.class) {
+ try {
+ this.target = serverLevel.getNearestEntity(
+ this.mob.level().getEntitiesOfClass(this.targetType, this.getTargetSearchArea(this.getFollowDistance()), entity -> true),
+ this.getTargetConditions(),
+ this.mob,
+ this.mob.getX(),
+ this.mob.getEyeY(),
+ this.mob.getZ()
+ );
+ } catch (Exception e) {
+ System.err.println("Error in sync entity finding: " + e.getMessage());
+ this.target = null;
+ }
+ } else {
+ try {
+ this.target = serverLevel.getNearestPlayer(this.getTargetConditions(), this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ());
+ } catch (Exception e) {
+ System.err.println("Error in sync player finding: " + e.getMessage());
+ this.target = null;
+ }
+ }
+ } catch (Exception e) {
+ System.err.println("Error in findTargetSync: " + e.getMessage());
+ this.target = null;
+ }
+ }
+ // Leaf end - Async Target Finding
+
@Override
public void start() {
- this.mob.setTarget(this.target, this.target instanceof ServerPlayer ? org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER : org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY); // CraftBukkit - reason
+ // Leaf start - Async Target Finding
+ LivingEntity targetEntity = this.target;
+ if (targetEntity != null && !targetEntity.isRemoved() && targetEntity.isAlive()) {
+ try {
+ this.mob.setTarget(targetEntity, targetEntity instanceof ServerPlayer ?
+ org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER :
+ org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY);
+ } catch (Exception e) {
+ System.err.println("Error in setTarget: " + e.getMessage());
+ this.target = null;
+ }
+ }
+ // Leaf end - Async Target Finding
super.start();
}

View File

@@ -0,0 +1,26 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
Date: Wed, 2 Apr 2025 23:03:22 +0200
Subject: [PATCH] Null handling on MultifaceSpreader
WHYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
diff --git a/net/minecraft/world/level/block/MultifaceSpreader.java b/net/minecraft/world/level/block/MultifaceSpreader.java
index 5eb291396a83b8d98294c5f53d2e1f4915a0d84e..b5a78dc342bd2d2eecaaa3ab7cc89e391a91b2da 100644
--- a/net/minecraft/world/level/block/MultifaceSpreader.java
+++ b/net/minecraft/world/level/block/MultifaceSpreader.java
@@ -148,6 +148,14 @@ public class MultifaceSpreader {
}
default boolean placeBlock(LevelAccessor level, MultifaceSpreader.SpreadPos pos, BlockState state, boolean markForPostprocessing) {
+ // Leaf start - Null handling on MultifaceSpreader
+ // Check for null
+ if (pos.source() == null || pos.pos() == null) {
+ org.dreeam.leaf.config.LeafConfig.LOGGER.warn("Invalid SpreadPos with null source or position: {}", pos);
+ return false;
+ }
+ // Leaf end - Null handling on MultifaceSpreader
+
BlockState stateForPlacement = this.getStateForPlacement(state, level, pos.pos(), pos.face());
if (stateForPlacement != null) {
if (markForPostprocessing) {

View File

@@ -0,0 +1,112 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
Date: Sun, 6 Apr 2025 11:22:35 +0200
Subject: [PATCH] More virtual threads
diff --git a/net/minecraft/Util.java b/net/minecraft/Util.java
index 9918572306e983281d05c6d28c8a5d843348ad2d..1d4ad1370ca041753ce765b1a2feddae59f1f8ca 100644
--- a/net/minecraft/Util.java
+++ b/net/minecraft/Util.java
@@ -98,7 +98,8 @@ public class Util {
public static final TracingExecutor DIMENSION_DATA_IO_POOL = makeExtraIoExecutor("Dimension-Data-IO-Worker-"); // Paper - Separate dimension data IO pool
private static final TracingExecutor DOWNLOAD_POOL = makeIoExecutor("Download-", true);
// Paper start - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread
- public static final ExecutorService PROFILE_EXECUTOR = Executors.newFixedThreadPool(2, new java.util.concurrent.ThreadFactory() {
+ // Leaf start - More virtual threads
+ public static final ExecutorService PROFILE_EXECUTOR = createProfileExecutor(); /* new Executors.newFixedThreadPool(2, new java.util.concurrent.ThreadFactory() {
private final AtomicInteger count = new AtomicInteger();
@@ -111,7 +112,30 @@ public class Util {
});
return ret;
}
- });
+ }); */
+
+ private static ExecutorService createProfileExecutor() {
+ final java.util.concurrent.ThreadFactory factory;
+ if (org.dreeam.leaf.config.modules.opt.VT4ProfileExecutor.enabled && org.galemc.gale.virtualthread.VirtualThreadService.isSupported()) {
+ factory = org.galemc.gale.virtualthread.VirtualThreadService.get().createFactory();
+ } else {
+ factory = new java.util.concurrent.ThreadFactory() {
+ private final AtomicInteger count = new AtomicInteger();
+
+ @Override
+ public Thread newThread(Runnable run) {
+ Thread ret = new Thread(run);
+ ret.setName("Profile Lookup Executor #" + this.count.getAndIncrement());
+ ret.setUncaughtExceptionHandler((Thread thread, Throwable throwable) -> {
+ LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable);
+ });
+ return ret;
+ }
+ };
+ }
+ return Executors.newFixedThreadPool(2, factory);
+ }
+ // Leaf end - More virtual threads
// Paper end - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread
private static final DateTimeFormatter FILENAME_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss", Locale.ROOT);
public static final int LINEAR_LOOKUP_THRESHOLD = 8;
@@ -255,16 +279,31 @@ public class Util {
}
private static TracingExecutor makeIoExecutor(String name, boolean daemon) {
- AtomicInteger atomicInteger = new AtomicInteger(1);
- return new TracingExecutor(Executors.newCachedThreadPool(task -> {
- Thread thread = new Thread(task);
- String string = name + atomicInteger.getAndIncrement();
- TracyClient.setThreadName(string, name.hashCode());
- thread.setName(string);
- thread.setDaemon(daemon);
- thread.setUncaughtExceptionHandler(Util::onThreadException);
- return thread;
- }));
+ // Leaf start - More virtual threads
+ final java.util.concurrent.ThreadFactory factory;
+ final boolean useVirtualThreads; // Gale - virtual thread support
+ if (name.startsWith("Download-")) { // Gale - virtual thread support
+ useVirtualThreads = org.dreeam.leaf.config.modules.opt.VT4DownloadPool.enabled && org.galemc.gale.virtualthread.VirtualThreadService.isSupported(); // Gale - virtual thread support
+ } else {
+ useVirtualThreads = false;
+ }
+
+ if (useVirtualThreads) {
+ factory = org.galemc.gale.virtualthread.VirtualThreadService.get().createFactory();
+ } else {
+ AtomicInteger atomicInteger = new AtomicInteger(1);
+ factory = task -> {
+ Thread thread = new Thread(task);
+ String string = name + atomicInteger.getAndIncrement();
+ TracyClient.setThreadName(string, name.hashCode());
+ thread.setName(string);
+ thread.setDaemon(daemon);
+ thread.setUncaughtExceptionHandler(Util::onThreadException);
+ return thread;
+ };
+ }
+ return new TracingExecutor(Executors.newCachedThreadPool(factory));
+ // Leaf end - More virtual threads
}
// Paper start - Separate dimension data IO pool
@@ -1109,7 +1148,7 @@ public class Util {
}
public static <T> Typed<T> readTypedOrThrow(Type<T> type, Dynamic<?> data, boolean partial) {
- DataResult<Typed<T>> dataResult = type.readTyped(data).map(Pair::getFirst);
+ DataResult<Typed<T>> dataResult = type.readTyped(data).map(Pair::getFirst); // Paper - Fix generics issue // Gale - Fix generics issue
try {
return partial ? dataResult.getPartialOrThrow(IllegalStateException::new) : dataResult.getOrThrow(IllegalStateException::new);
@@ -1158,7 +1197,7 @@ public class Util {
}
public void openUri(URI uri) {
- throw new IllegalStateException("This method is not useful on dedicated servers."); // Paper - Fix warnings on build by removing client-only code
+ // throw new IllegalStateException("This method is not useful on dedicated servers."); // Paper - Fix warnings on build by removing client-only code // Leaf - More virtual threads - This method is not useful on dedicated servers
}
public void openFile(File file) {

View File

@@ -5,17 +5,18 @@ Subject: [PATCH] PlayerInventoryOverflowEvent
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
index 19180c08f41db939c1a9f0caeb62e5beb1117f69..59ab5bd3582cdae351d579719244c4ad28878a00 100644
index 19180c08f41db939c1a9f0caeb62e5beb1117f69..c765118189cbf8db7291c1d50214a7f31acc3a49 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
@@ -340,6 +340,15 @@ public class CraftInventory implements Inventory {
@@ -340,6 +340,16 @@ public class CraftInventory implements Inventory {
}
}
}
+
+ // Leaf start - PlayerInventoryOverflowEvent
+ if (org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent.getHandlerList().getRegisteredListeners().length > 0 && !leftover.isEmpty() && this.getHolder() instanceof org.bukkit.craftbukkit.entity.CraftPlayer craftPlayer) {
+ new org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent(craftPlayer, leftover).callEvent();
+ if (org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent.getHandlerList().getRegisteredListeners().length > 0
+ && !leftover.isEmpty() && this.inventory instanceof net.minecraft.world.entity.player.Inventory && this.inventory.getOwner() instanceof org.bukkit.entity.Player player) {
+ new org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent(player, leftover).callEvent();
+
+ leftover = new HashMap<>();
+ }

View File

@@ -5,7 +5,7 @@ Subject: [PATCH] Async playerdata saving
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java
index 1456f2d1a92c8315177fb03d0c7ec943d5f5b097..e70692272aae39ea01fb6860ec4cb703ea531781 100644
index 1456f2d1a92c8315177fb03d0c7ec943d5f5b097..aadc92b9e82bfe3d65ea8f47ac28ba2d70eb3a7f 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java
@@ -199,7 +199,7 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa
@@ -17,34 +17,21 @@ index 1456f2d1a92c8315177fb03d0c7ec943d5f5b097..e70692272aae39ea01fb6860ec4cb703
}
private CompoundTag getBukkitData() {
@@ -744,6 +744,17 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa
@@ -744,16 +744,7 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa
* @param compoundTag
*/
private void save(CompoundTag compoundTag) {
+ // Leaf start - Async playerdata saving
+ synchronized (server.console.playerDataStorage) {
+ while (server.console.playerDataStorage.savingQueue.contains(getUniqueId())) {
+ try {
+ Thread.sleep(1L);
+ } catch (InterruptedException ignored) {
+ }
+ }
+ server.console.playerDataStorage.savingQueue.add(getUniqueId());
+ }
+ // Leaf end - Async playerdata saving
File playerDir = server.console.playerDataStorage.getPlayerDir();
try {
File tempFile = File.createTempFile(this.getUniqueId()+"-", ".dat", playerDir);
@@ -753,6 +764,12 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa
net.minecraft.Util.safeReplaceFile(playerDataFile.toPath(), tempFile.toPath(), playerDataFileOld.toPath());
} catch (java.io.IOException e) {
e.printStackTrace();
+ // Leaf start - Async playerdata saving
+ } finally {
+ synchronized (server.console.playerDataStorage) {
+ server.console.playerDataStorage.savingQueue.remove(getUniqueId());
+ }
+ // Leaf end - Async playerdata saving
}
- File playerDir = server.console.playerDataStorage.getPlayerDir();
- try {
- File tempFile = File.createTempFile(this.getUniqueId()+"-", ".dat", playerDir);
- net.minecraft.nbt.NbtIo.writeCompressed(compoundTag, tempFile.toPath());
- File playerDataFile = new File(playerDir, this.getUniqueId()+".dat");
- File playerDataFileOld = new File(playerDir, this.getUniqueId()+".dat_old");
- net.minecraft.Util.safeReplaceFile(playerDataFile.toPath(), tempFile.toPath(), playerDataFileOld.toPath());
- } catch (java.io.IOException e) {
- e.printStackTrace();
- }
+ server.console.playerDataStorage.save(this.getName(), this.getUniqueId(), this.getUniqueId().toString(), compoundTag); // Leaf - Async playerdata saving
}
// Purpur end - OfflinePlayer API
}

View File

@@ -2,7 +2,9 @@ package org.dreeam.leaf.async;
import org.dreeam.leaf.config.modules.async.AsyncPlayerDataSave;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -10,7 +12,7 @@ import java.util.concurrent.TimeUnit;
public class AsyncPlayerDataSaving {
public static final ExecutorService IO_POOL = new ThreadPoolExecutor(
1, 1, 0, TimeUnit.MILLISECONDS,
1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
new com.google.common.util.concurrent.ThreadFactoryBuilder()
.setPriority(Thread.NORM_PRIORITY - 2)
@@ -23,11 +25,12 @@ public class AsyncPlayerDataSaving {
private AsyncPlayerDataSaving() {
}
public static void save(Runnable runnable) {
public static Optional<Future<?>> submit(Runnable runnable) {
if (!AsyncPlayerDataSave.enabled) {
runnable.run();
return Optional.empty();
} else {
IO_POOL.execute(runnable);
return Optional.of(IO_POOL.submit(runnable));
}
}
}

View File

@@ -2,7 +2,6 @@ package org.dreeam.leaf.config.modules.async;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
import org.dreeam.leaf.config.annotations.Experimental;
public class AsyncBlockFinding extends ConfigModules {
@@ -10,14 +9,12 @@ public class AsyncBlockFinding extends ConfigModules {
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-block-finding";
}
@Experimental
public static boolean enabled = false;
public static boolean asyncBlockFindingInitialized;
@Override
public void onLoaded() {
config.addCommentRegionBased(getBasePath(), """
**Experimental feature**
This moves the expensive search calculations to a background thread while
keeping the actual block validation on the main thread.""",
"""

View File

@@ -2,7 +2,6 @@ package org.dreeam.leaf.config.modules.async;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
import org.dreeam.leaf.config.annotations.Experimental;
public class AsyncChunkSend extends ConfigModules {

View File

@@ -0,0 +1,32 @@
package org.dreeam.leaf.config.modules.async;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
import org.dreeam.leaf.config.annotations.Experimental;
public class AsyncTargetFinding extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-target-finding";
}
@Experimental
public static boolean enabled = false;
public static boolean asyncTargetFindingInitialized;
@Override
public void onLoaded() {
config.addCommentRegionBased(getBasePath(), """
**Experimental feature**
This moves the expensive entity target search calculations to a background thread while
keeping the actual entity validation on the main thread.""",
"""
这会将昂贵的实体目标搜索计算移至后台线程, 同时在主线程上保持实际的实体验证.""");
if (!asyncTargetFindingInitialized) {
asyncTargetFindingInitialized = true;
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
}
}
}

View File

@@ -0,0 +1,21 @@
package org.dreeam.leaf.config.modules.opt;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class VT4DownloadPool extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName();
}
public static boolean enabled = true;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".use-virtual-thread-for-download-pool", enabled,
config.pickStringRegionBased(
"Use the new Virtual Thread introduced in JDK 21 for download worker pool.",
"是否为下载工作线程池使用虚拟线程(如果可用)。"));
}
}

View File

@@ -0,0 +1,21 @@
package org.dreeam.leaf.config.modules.opt;
import org.dreeam.leaf.config.ConfigModules;
import org.dreeam.leaf.config.EnumConfigCategory;
public class VT4ProfileExecutor extends ConfigModules {
public String getBasePath() {
return EnumConfigCategory.PERF.getBaseKeyName();
}
public static boolean enabled = true;
@Override
public void onLoaded() {
enabled = config.getBoolean(getBasePath() + ".use-virtual-thread-for-profile-executor", enabled,
config.pickStringRegionBased(
"Use the new Virtual Thread introduced in JDK 21 for profile lookup executor.",
"是否为档案查询执行器使用虚拟线程(如果可用)。"));
}
}

View File

@@ -10,7 +10,7 @@ import java.util.function.Function;
public class StringCanonizingOpenHashMap<T> extends Object2ObjectOpenHashMap<String, T> {
private static final Interner<String> KEY_INTERNER = Interners.newWeakInterner();
private static final Interner<String> KEY_INTERNER = Interners.newBuilder().weak().concurrencyLevel(16).<String>build();
private static String intern(String key) {
return key != null ? KEY_INTERNER.intern(key) : null;
@@ -36,9 +36,10 @@ public class StringCanonizingOpenHashMap<T> extends Object2ObjectOpenHashMap<Str
@Override
public void putAll(Map<? extends String, ? extends T> m) {
if (m.isEmpty()) return;
Map<String, T> tmp = new Object2ObjectOpenHashMap<>(m.size());
m.forEach((k, v) -> tmp.put(intern(k), v));
super.putAll(tmp);
ensureCapacity(size() + m.size());
for (Map.Entry<? extends String, ? extends T> entry : m.entrySet()) {
super.put(intern(entry.getKey()), entry.getValue());
}
}
private void putWithoutInterning(String key, T value) {
@@ -46,7 +47,7 @@ public class StringCanonizingOpenHashMap<T> extends Object2ObjectOpenHashMap<Str
}
public static <T> StringCanonizingOpenHashMap<T> deepCopy(StringCanonizingOpenHashMap<T> incomingMap, Function<T, T> deepCopier) {
StringCanonizingOpenHashMap<T> newMap = new StringCanonizingOpenHashMap<>(incomingMap.size(), 0.8f);
StringCanonizingOpenHashMap<T> newMap = new StringCanonizingOpenHashMap<>(incomingMap.size(), incomingMap.f);
ObjectIterator<Entry<String, T>> iterator = incomingMap.object2ObjectEntrySet().fastIterator();
while (iterator.hasNext()) {