From 2f8915ce2bc91f547ac6452f6e50a9a1f70ceee2 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Fri, 28 Mar 2025 16:22:53 -0400 Subject: [PATCH] cleanup --- .../0086-Nitori-Async-playerdata-Save.patch | 84 ++++++++++++----- .../features/0095-TT20-Lag-compensation.patch | 4 +- ...-SparklyPaper-Parallel-world-ticking.patch | 8 +- ...0-SparklyPaper-Track-each-world-MSPT.patch | 4 +- ...152-Remove-streams-on-PlayerDetector.patch | 34 ++++--- .../features/0153-Async-Block-Finding.patch | 42 ++++----- ...Use-direct-iteration-on-Sensing.tick.patch | 13 +-- ...5-Optimise-non-flush-packet-sending.patch} | 20 +++-- ...-SparklyPaper-Parallel-world-ticking.patch | 89 ++----------------- .../0033-Async-playerdata-saving.patch | 25 ++++-- .../leaf/async/AsyncPlayerDataSaving.java | 1 + .../modules/async/AsyncBlockFinding.java | 1 - .../OptimizeNonFlushPacketSending.java | 14 +-- 13 files changed, 158 insertions(+), 181 deletions(-) rename leaf-server/minecraft-patches/features/{0155-Optimize-NonFlush-PacketSending.patch => 0155-Optimise-non-flush-packet-sending.patch} (78%) diff --git a/leaf-server/minecraft-patches/features/0086-Nitori-Async-playerdata-Save.patch b/leaf-server/minecraft-patches/features/0086-Nitori-Async-playerdata-Save.patch index 07f3135b..2f13c47e 100644 --- a/leaf-server/minecraft-patches/features/0086-Nitori-Async-playerdata-Save.patch +++ b/leaf-server/minecraft-patches/features/0086-Nitori-Async-playerdata-Save.patch @@ -7,31 +7,32 @@ Original license: GPL v3 Original project: https://github.com/Gensokyo-Reimagined/Nitori diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 5a0d30b8ff5e377224de67c9f464bd1c694a4397..b316c174963cd395186d45f3fb9ca78470fa5c27 100644 +index 5a0d30b8ff5e377224de67c9f464bd1c694a4397..1cb60107d95296fc9e2c106d70838c057564abeb 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -1069,6 +1069,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop getIconFile() { diff --git a/net/minecraft/world/level/storage/PlayerDataStorage.java b/net/minecraft/world/level/storage/PlayerDataStorage.java -index c44110b123ba5912af18faf0065e9ded780da9b7..0c3ba0b8ea8b1b5b66943c40b0f00bf998cf5f18 100644 +index c44110b123ba5912af18faf0065e9ded780da9b7..541c440d3211c6625974ac1e0055d62655256846 100644 --- a/net/minecraft/world/level/storage/PlayerDataStorage.java +++ b/net/minecraft/world/level/storage/PlayerDataStorage.java @@ -25,6 +25,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 saving = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(); // Leaf ++ public final it.unimi.dsi.fastutil.objects.ObjectOpenHashSet savingQueue = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(); // Leaf - Async playerdata saving public PlayerDataStorage(LevelStorageSource.LevelStorageAccess levelStorageAccess, DataFixer fixerUpper) { this.fixerUpper = fixerUpper; -@@ -34,17 +35,42 @@ public class PlayerDataStorage { +@@ -34,17 +35,43 @@ public class PlayerDataStorage { public void save(Player player) { if (org.spigotmc.SpigotConfig.disablePlayerDataSaving) return; // Spigot -+ // Leaf start - Async IO ++ // Leaf start - Async playerdata saving + var nbtBytes = new it.unimi.dsi.fastutil.io.FastByteArrayOutputStream(65536); try { CompoundTag compoundTag = player.saveWithoutId(new CompoundTag()); @@ -93,14 +94,15 @@ index c44110b123ba5912af18faf0065e9ded780da9b7..0c3ba0b8ea8b1b5b66943c40b0f00bf9 } + String playerName = player.getScoreboardName(); + String stringUuid = player.getStringUUID(); ++ java.util.UUID playerUuid = player.getUUID(); + synchronized (PlayerDataStorage.this) { -+ while (saving.contains(stringUuid)) { ++ while (savingQueue.contains(playerUuid)) { + try { + Thread.sleep(1L); + } catch (InterruptedException ignored) { + } + } -+ saving.add(stringUuid); ++ savingQueue.add(playerUuid); + } + org.dreeam.leaf.async.AsyncPlayerDataSaving.save(() -> { + try { @@ -114,28 +116,70 @@ index c44110b123ba5912af18faf0065e9ded780da9b7..0c3ba0b8ea8b1b5b66943c40b0f00bf9 + LOGGER.warn("Failed to save player data for {}", playerName, var7); // Paper - Print exception + } finally { + synchronized (PlayerDataStorage.this) { -+ saving.remove(stringUuid); ++ savingQueue.remove(playerUuid); + } + } + }); -+ // Leaf end ++ // Leaf end - Async playerdata saving } private void backup(String name, String stringUuid, String suffix) { // CraftBukkit -@@ -61,6 +87,16 @@ public class PlayerDataStorage { +@@ -60,7 +87,20 @@ public class PlayerDataStorage { + } } - private Optional load(String name, String stringUuid, String suffix) { // CraftBukkit -+ // Leaf start +- private Optional load(String name, String stringUuid, String suffix) { // CraftBukkit ++ // Leaf start - Async playerdata saving ++ private Optional load(String name, String stringUuid, String suffix) { ++ return load(name, stringUuid, suffix, java.util.UUID.fromString(stringUuid)); ++ } ++ private Optional load(String name, String stringUuid, String suffix, java.util.UUID playerUuid) { // CraftBukkit + synchronized (PlayerDataStorage.this) { -+ while (saving.contains(stringUuid)) { ++ while (savingQueue.contains(playerUuid)) { + try { + Thread.sleep(1L); + } catch (InterruptedException ignored) { + } + } + } -+ // Leaf end ++ // Leaf end - Async playerdata saving File file = new File(this.playerDir, stringUuid + suffix); // CraftBukkit // Spigot start boolean usingWrongFile = false; +@@ -91,7 +131,7 @@ public class PlayerDataStorage { + + public Optional load(Player player) { + // CraftBukkit start +- return this.load(player.getName().getString(), player.getStringUUID()).map((tag) -> { ++ return this.load(player.getName().getString(), player.getStringUUID(), player.getUUID()).map((tag) -> { // Leaf - Async playerdata saving + if (player instanceof ServerPlayer serverPlayer) { + CraftPlayer craftPlayer = serverPlayer.getBukkitEntity(); + // Only update first played if it is older than the one we have +@@ -106,20 +146,25 @@ public class PlayerDataStorage { + }); + } + ++ // Leaf start - Async playerdata saving + public Optional load(String name, String uuid) { ++ return this.load(name, uuid, java.util.UUID.fromString(uuid)); ++ } ++ public Optional load(String name, String uuid, java.util.UUID playerUuid) { + // CraftBukkit end +- Optional optional = this.load(name, uuid, ".dat"); // CraftBukkit ++ Optional optional = this.load(name, uuid, ".dat", playerUuid); // CraftBukkit + if (optional.isEmpty()) { + this.backup(name, uuid, ".dat"); // CraftBukkit + } + +- return optional.or(() -> this.load(name, uuid, ".dat_old")).map(compoundTag -> { // CraftBukkit ++ return optional.or(() -> this.load(name, uuid, ".dat_old", playerUuid)).map(compoundTag -> { // CraftBukkit + int dataVersion = NbtUtils.getDataVersion(compoundTag, -1); + compoundTag = ca.spottedleaf.dataconverter.minecraft.MCDataConverter.convertTag(ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.PLAYER, compoundTag, dataVersion, net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion()); // Paper - rewrite data conversion system + // player.load(compoundTag); // CraftBukkit - handled above + return compoundTag; + }); + } ++ // Leaf end - Async playerdata saving + + // CraftBukkit start + public File getPlayerDir() { diff --git a/leaf-server/minecraft-patches/features/0095-TT20-Lag-compensation.patch b/leaf-server/minecraft-patches/features/0095-TT20-Lag-compensation.patch index ae714502..732d462e 100644 --- a/leaf-server/minecraft-patches/features/0095-TT20-Lag-compensation.patch +++ b/leaf-server/minecraft-patches/features/0095-TT20-Lag-compensation.patch @@ -7,10 +7,10 @@ Original license: AGPL-3.0 Original project: https://github.com/snackbag/TT20 diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index cc2bf337da1c240b82dc722970d6bbaf57331328..3597f7dd2d71fe136604518985e3d14461a6aad4 100644 +index 1cb60107d95296fc9e2c106d70838c057564abeb..c50a301a0c2365c2052aefc6a23fcf6fa82e1b9d 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -1550,6 +1550,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop serverPlayer1.connection.suspendFlushing()); this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit -@@ -1737,28 +1762,50 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop, ServerLevel> oldLevels = this.levels; Map, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels); newLevels.remove(level.dimension()); diff --git a/leaf-server/minecraft-patches/features/0140-SparklyPaper-Track-each-world-MSPT.patch b/leaf-server/minecraft-patches/features/0140-SparklyPaper-Track-each-world-MSPT.patch index 011966d7..2fb7a7cb 100644 --- a/leaf-server/minecraft-patches/features/0140-SparklyPaper-Track-each-world-MSPT.patch +++ b/leaf-server/minecraft-patches/features/0140-SparklyPaper-Track-each-world-MSPT.patch @@ -6,10 +6,10 @@ Subject: [PATCH] SparklyPaper: Track each world MSPT Original project: https://github.com/SparklyPower/SparklyPaper diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index f2d1025a13649c35991a662c267a7653e75d19a2..7739b4955dcb489c6bba9c9db65ba87025f7c669 100644 +index ac751d460ae0c8dbb858c4047c459a11b57ae175..24926aa7ed5c78b235659daf18b224b14beb744c 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -1687,7 +1687,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop Date: Sat, 22 Mar 2025 12:51:28 +0100 Subject: [PATCH] Remove streams on PlayerDetector +Dreeam TODO: Merge to single loop diff --git a/net/minecraft/world/level/block/entity/trialspawner/PlayerDetector.java b/net/minecraft/world/level/block/entity/trialspawner/PlayerDetector.java -index 774d4028f0be7c388abb47f8eb97011341f50f59..1597af2092e993efb1c02f92ed14d8c5a500403a 100644 +index 774d4028f0be7c388abb47f8eb97011341f50f59..81d2dfdd8bd2a8b205e4b617911c277a366f0369 100644 --- a/net/minecraft/world/level/block/entity/trialspawner/PlayerDetector.java +++ b/net/minecraft/world/level/block/entity/trialspawner/PlayerDetector.java -@@ -21,27 +21,42 @@ import net.minecraft.world.phys.Vec3; +@@ -21,28 +21,45 @@ import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.CollisionContext; public interface PlayerDetector { - PlayerDetector NO_CREATIVE_PLAYERS = (level, entitySelector, pos, maxDistance, requireLineOfSight) -> entitySelector.getPlayers( -+ PlayerDetector NO_CREATIVE_PLAYERS = (level, entitySelector, pos, maxDistance, requireLineOfSight) -> { -+ List players = entitySelector.getPlayers( - level, player -> player.blockPosition().closerThan(pos, maxDistance) && !player.isCreative() && !player.isSpectator() +- level, player -> player.blockPosition().closerThan(pos, maxDistance) && !player.isCreative() && !player.isSpectator() - ) - .stream() - .filter(player -> !requireLineOfSight || inLineOfSight(level, pos.getCenter(), player.getEyePosition())) - .map(Entity::getUUID) - .toList(); - PlayerDetector INCLUDING_CREATIVE_PLAYERS = (level, entitySelector, pos, maxDistance, requireLineOfSight) -> entitySelector.getPlayers( +- level, player -> player.blockPosition().closerThan(pos, maxDistance) && !player.isSpectator() +- ) +- .stream() +- .filter(player -> !requireLineOfSight || inLineOfSight(level, pos.getCenter(), player.getEyePosition())) +- .map(Entity::getUUID) +- .toList(); ++ // Leaf start - Remove streams on PlayerDetector ++ PlayerDetector NO_CREATIVE_PLAYERS = (level, entitySelector, pos, maxDistance, requireLineOfSight) -> { ++ List players = entitySelector.getPlayers( ++ level, player -> player.blockPosition().closerThan(pos, maxDistance) && !player.isCreative() && !player.isSpectator() + ); + List result = new java.util.ArrayList<>(); + for (Player player : players) { @@ -34,12 +43,7 @@ index 774d4028f0be7c388abb47f8eb97011341f50f59..1597af2092e993efb1c02f92ed14d8c5 + + PlayerDetector INCLUDING_CREATIVE_PLAYERS = (level, entitySelector, pos, maxDistance, requireLineOfSight) -> { + List players = entitySelector.getPlayers( - level, player -> player.blockPosition().closerThan(pos, maxDistance) && !player.isSpectator() -- ) -- .stream() -- .filter(player -> !requireLineOfSight || inLineOfSight(level, pos.getCenter(), player.getEyePosition())) -- .map(Entity::getUUID) -- .toList(); ++ level, player -> player.blockPosition().closerThan(pos, maxDistance) && !player.isSpectator() + ); + List result = new java.util.ArrayList<>(); + for (Player player : players) { @@ -66,13 +70,16 @@ index 774d4028f0be7c388abb47f8eb97011341f50f59..1597af2092e993efb1c02f92ed14d8c5 + } + return result; }; ++ // Leaf end - Remove streams on PlayerDetector List detect(ServerLevel level, PlayerDetector.EntitySelector entitySelector, BlockPos pos, double maxDistance, boolean flag); -@@ -78,14 +93,27 @@ public interface PlayerDetector { + +@@ -78,14 +95,31 @@ public interface PlayerDetector { return new PlayerDetector.EntitySelector() { @Override public List getPlayers(ServerLevel level, Predicate predicate) { - return players.stream().filter(predicate).toList(); ++ // Leaf start - Remove streams on PlayerDetector + List result = new java.util.ArrayList<>(); + for (Player player : players) { + if (predicate.test(player)) { @@ -80,6 +87,7 @@ index 774d4028f0be7c388abb47f8eb97011341f50f59..1597af2092e993efb1c02f92ed14d8c5 + } + } + return result; ++ // Leaf end - Remove streams on PlayerDetector } @Override @@ -87,6 +95,7 @@ index 774d4028f0be7c388abb47f8eb97011341f50f59..1597af2092e993efb1c02f92ed14d8c5 ServerLevel level, EntityTypeTest typeTest, AABB boundingBox, Predicate predicate ) { - return players.stream().map(typeTest::tryCast).filter(Objects::nonNull).filter(predicate).toList(); ++ // Leaf start - Remove streams on PlayerDetector + List result = new java.util.ArrayList<>(); + for (Player player : players) { + T entity = typeTest.tryCast(player); @@ -95,6 +104,7 @@ index 774d4028f0be7c388abb47f8eb97011341f50f59..1597af2092e993efb1c02f92ed14d8c5 + } + } + return result; ++ // Leaf end - Remove streams on PlayerDetector } }; } diff --git a/leaf-server/minecraft-patches/features/0153-Async-Block-Finding.patch b/leaf-server/minecraft-patches/features/0153-Async-Block-Finding.patch index c908fdd7..1c9e14d9 100644 --- a/leaf-server/minecraft-patches/features/0153-Async-Block-Finding.patch +++ b/leaf-server/minecraft-patches/features/0153-Async-Block-Finding.patch @@ -5,50 +5,36 @@ 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..51a949dbb88d7fff076379962000b70acecacce3 100644 +index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..2bc0f19b86067491f33f647d2e387acd83492844 100644 --- a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +++ b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -@@ -1,9 +1,16 @@ - package net.minecraft.world.entity.ai.goal; - - import java.util.EnumSet; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.concurrent.ConcurrentLinkedQueue; -+import java.util.concurrent.ExecutorService; -+import java.util.concurrent.Executors; -+import com.google.common.util.concurrent.ThreadFactoryBuilder; - import net.minecraft.core.BlockPos; - import net.minecraft.world.entity.PathfinderMob; - import net.minecraft.world.level.LevelReader; -+import org.dreeam.leaf.config.modules.async.AsyncBlockFinding; - - public abstract class MoveToBlockGoal extends Goal { - private static final int GIVE_UP_TICKS = 1200; -@@ -20,6 +27,17 @@ public abstract class MoveToBlockGoal extends Goal { +@@ -20,6 +20,19 @@ public abstract class MoveToBlockGoal extends Goal { private final int verticalSearchRange; protected int verticalSearchStart; -+ private static final ExecutorService BLOCK_FINDER_EXECUTOR = -+ Executors.newSingleThreadExecutor( -+ new ThreadFactoryBuilder() -+ .setNameFormat("block-finder-%d") ++ // 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 ConcurrentLinkedQueue candidateBlocks = new ConcurrentLinkedQueue<>(); ++ private final java.util.concurrent.ConcurrentLinkedQueue 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 +127,92 @@ public abstract class MoveToBlockGoal extends Goal { +@@ -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 (!AsyncBlockFinding.enabled) { ++ if (!org.dreeam.leaf.config.modules.async.AsyncBlockFinding.enabled) { + return findNearestBlockSync(); + } + @@ -59,6 +45,7 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..51a949dbb88d7fff076379962000b70a + candidateBlocks.clear(); + return false; + } ++ + while (!candidateBlocks.isEmpty()) { + BlockPos pos = candidateBlocks.poll(); + @@ -105,7 +92,7 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..51a949dbb88d7fff076379962000b70a + // 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 -+ List positions = new ArrayList<>(); ++ java.util.List positions = new java.util.ArrayList<>(); + + for (int i2 = verticalStart; i2 <= verticalRange; i2 = i2 > 0 ? -i2 : 1 - i2) { + for (int i3 = 0; i3 < searchRange; i3++) { @@ -133,6 +120,7 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..51a949dbb88d7fff076379962000b70a + + // 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(); diff --git a/leaf-server/minecraft-patches/features/0154-Use-direct-iteration-on-Sensing.tick.patch b/leaf-server/minecraft-patches/features/0154-Use-direct-iteration-on-Sensing.tick.patch index 1ab5fb6b..d707d920 100644 --- a/leaf-server/minecraft-patches/features/0154-Use-direct-iteration-on-Sensing.tick.patch +++ b/leaf-server/minecraft-patches/features/0154-Use-direct-iteration-on-Sensing.tick.patch @@ -5,25 +5,20 @@ Subject: [PATCH] Use direct iteration on Sensing.tick diff --git a/net/minecraft/world/entity/ai/sensing/Sensing.java b/net/minecraft/world/entity/ai/sensing/Sensing.java -index cc25f5838aec5ed9fca2fb8b0322fafad9397a46..2ea5095a4290f5236caad7ea30a59d9b3eb32aaa 100644 +index cc25f5838aec5ed9fca2fb8b0322fafad9397a46..002d3c0d8b1107a275020d5c582c37e9a5c536ee 100644 --- a/net/minecraft/world/entity/ai/sensing/Sensing.java +++ b/net/minecraft/world/entity/ai/sensing/Sensing.java -@@ -34,12 +34,16 @@ public class Sensing { - - public void tick() { - if (this.expiring == null) { // Gale - Petal - reduce line of sight updates -- this.seen.clear(); -+ this.seen.clear(); - // Gale start - Petal - reduce line of sight updates +@@ -39,7 +39,12 @@ public class Sensing { } else { var expiringNow = this.expiring[this.nextToExpireIndex]; - expiringNow.forEach(this.seen::remove); -+ // Leaf - use direct iteration ++ // Leaf start - Use direct iteration on Sensing.tick + var iterator = expiringNow.iterator(); + while (iterator.hasNext()) { + this.seen.remove(iterator.nextInt()); + } ++ // Leaf end - Use direct iteration on Sensing.tick expiringNow.clear(); this.currentCacheAddIndex++; diff --git a/leaf-server/minecraft-patches/features/0155-Optimize-NonFlush-PacketSending.patch b/leaf-server/minecraft-patches/features/0155-Optimise-non-flush-packet-sending.patch similarity index 78% rename from leaf-server/minecraft-patches/features/0155-Optimize-NonFlush-PacketSending.patch rename to leaf-server/minecraft-patches/features/0155-Optimise-non-flush-packet-sending.patch index e1f98674..f15568e7 100644 --- a/leaf-server/minecraft-patches/features/0155-Optimize-NonFlush-PacketSending.patch +++ b/leaf-server/minecraft-patches/features/0155-Optimise-non-flush-packet-sending.patch @@ -1,7 +1,11 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Taiyou06 -Date: Wed, 26 Mar 2025 17:33:30 +0100 -Subject: [PATCH] Optimize NonFlush PacketSending +From: Spottedleaf +Date: Tue, 22 Sep 2020 01:49:19 -0700 +Subject: [PATCH] Optimise non-flush packet sending + +Original license: GPLv3 +Original project: https://github.com/PaperMC/Paper +Paper pull request: https://github.com/PaperMC/Paper/pull/10172 Places like entity tracking make heavy use of packet sending, and internally netty will use some very expensive thread wakeup @@ -22,14 +26,14 @@ 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..208674673169b7258d9411d818870a640df08b24 100644 +index 5b46036868b6c9d082e35591e58735e16adaae62..9563165c945757996da11f55e2221e620dd93327 100644 --- a/net/minecraft/network/Connection.java +++ b/net/minecraft/network/Connection.java @@ -147,6 +147,7 @@ public class Connection extends SimpleChannelInboundHandler> { @Nullable public SocketAddress haProxyAddress; // Paper - Add API to get player's proxy address // Paper start - Optimize network public boolean isPending = true; -+ private io.netty.channel.SingleThreadEventLoop eventLoop; // Leaf - optimise packets that are not flushed ++ private io.netty.channel.SingleThreadEventLoop eventLoop; // Paper - optimise packets that are not flushed public boolean queueImmunity; // Paper end - Optimize network @@ -37,7 +41,7 @@ index 5b46036868b6c9d082e35591e58735e16adaae62..208674673169b7258d9411d818870a64 public void channelActive(ChannelHandlerContext context) throws Exception { super.channelActive(context); this.channel = context.channel(); -+ this.eventLoop = (io.netty.channel.SingleThreadEventLoop) this.channel.eventLoop(); // Leaf - optimise packets that are not flushed ++ this.eventLoop = (io.netty.channel.SingleThreadEventLoop) this.channel.eventLoop(); // Paper - optimise packets that are not flushed this.address = this.channel.remoteAddress(); this.preparing = false; // Spigot if (this.delayedDisconnect != null) { @@ -45,11 +49,11 @@ index 5b46036868b6c9d082e35591e58735e16adaae62..208674673169b7258d9411d818870a64 if (this.channel.eventLoop().inEventLoop()) { this.doSendPacket(packet, sendListener, flush); } else { -+ // Leaf start - optimise packets that are not flushed ++ // Paper start - optimise packets that are not flushed + if (!flush && org.dreeam.leaf.config.modules.network.OptimizeNonFlushPacketSending.enabled) { + this.eventLoop.lazyExecute(() -> this.doSendPacket(packet, sendListener, flush)); + } else -+ // Leaf end - optimise packets that are not flushed ++ // Paper end - optimise packets that are not flushed this.channel.eventLoop().execute(() -> this.doSendPacket(packet, sendListener, flush)); } } diff --git a/leaf-server/paper-patches/features/0031-SparklyPaper-Parallel-world-ticking.patch b/leaf-server/paper-patches/features/0031-SparklyPaper-Parallel-world-ticking.patch index 91a05681..6b552bea 100644 --- a/leaf-server/paper-patches/features/0031-SparklyPaper-Parallel-world-ticking.patch +++ b/leaf-server/paper-patches/features/0031-SparklyPaper-Parallel-world-ticking.patch @@ -612,7 +612,7 @@ index 55572e799b5c8a74a546ac8febc14f80d5731c52..08a06c23c831a4de45b3e537228b8379 // Paper end } diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index c2552c3706831f7012b5b449fa43c7d5990056a4..2f9b7627f1801239ee6a89f334a266629b98c989 100644 +index c2552c3706831f7012b5b449fa43c7d5990056a4..4e8a1d01a6c0afef92ae56cc4909af06d63e5268 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -961,6 +961,28 @@ public class CraftEventFactory { @@ -652,7 +652,7 @@ index c2552c3706831f7012b5b449fa43c7d5990056a4..2f9b7627f1801239ee6a89f334a26662 + // Leaf start - SparklyPaper parallel world ticking mod (collapse original behavior) + final BlockPos sourceBlockOverrideRTSnap = getSourceBlockOverrideRT(); + BlockSpreadEvent event = new BlockSpreadEvent(state.getBlock(), CraftBlock.at(world, sourceBlockOverrideRTSnap != null ? sourceBlockOverrideRTSnap : source), state); // SparklyPaper - parallel world ticking -+ // Leaf end ++ // Leaf end - SparklyPaper parallel world ticking mod (collapse original behavior) Bukkit.getPluginManager().callEvent(event); if (!event.isCancelled()) { @@ -666,23 +666,26 @@ index c2552c3706831f7012b5b449fa43c7d5990056a4..2f9b7627f1801239ee6a89f334a26662 return itemStack; } diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java -index e4e2e42d0ca25df7fe9f2dd4275610e45fcb2c84..c4de7346703cbe457dbffed7716a5816314996a1 100644 +index e4e2e42d0ca25df7fe9f2dd4275610e45fcb2c84..e7c6b2ab5f2c68f3319ccd52785c8d3488a2eef7 100644 --- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java -@@ -19,11 +19,35 @@ class CraftAsyncTask extends CraftTask { +@@ -19,11 +19,39 @@ class CraftAsyncTask extends CraftTask { @Override public boolean isSync() { ++ // Leaf start - Parallel world ticking + // Return true if we should run this task synchronously when parallel world ticking is enabled and runAsyncTasksSync is true + if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && + org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.runAsyncTasksSync) { + return true; + } ++ // Leaf end - Parallel world ticking return false; } @Override public void run() { ++ // Leaf start - Parallel world ticking + // If parallel world ticking is enabled and we're configured to run async tasks sync, + // execute the task as if it were a sync task (directly on the main thread) + if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && @@ -700,85 +703,9 @@ index e4e2e42d0ca25df7fe9f2dd4275610e45fcb2c84..c4de7346703cbe457dbffed7716a5816 + } + return; + } ++ // Leaf end - Parallel world ticking + + // Original async implementation final Thread thread = Thread.currentThread(); // Paper start - name threads according to running plugin final String nameBefore = thread.getName(); -@@ -52,49 +76,49 @@ class CraftAsyncTask extends CraftTask { - } - }); - } -- Throwable thrown = null; -- try { -- super.run(); -- } catch (final Throwable t) { -- thrown = t; -- this.getOwner().getLogger().log( -+ Throwable thrown = null; -+ try { -+ super.run(); -+ } catch (final Throwable t) { -+ thrown = t; -+ this.getOwner().getLogger().log( - Level.WARNING, - String.format( - "Plugin %s generated an exception while executing task %s", - this.getOwner().getDescription().getFullName(), - this.getTaskId()), - thrown); -- } finally { -- // Cleanup is important for any async task, otherwise ghost tasks are everywhere -- synchronized (this.workers) { -- try { -- final Iterator workers = this.workers.iterator(); -- boolean removed = false; -- while (workers.hasNext()) { -- if (workers.next().getThread() == thread) { -- workers.remove(); -- removed = true; // Don't throw exception -- break; -+ } finally { -+ // Cleanup is important for any async task, otherwise ghost tasks are everywhere -+ synchronized (this.workers) { -+ try { -+ final Iterator workers = this.workers.iterator(); -+ boolean removed = false; -+ while (workers.hasNext()) { -+ if (workers.next().getThread() == thread) { -+ workers.remove(); -+ removed = true; // Don't throw exception -+ break; -+ } - } -- } -- if (!removed) { -- throw new IllegalStateException( -+ if (!removed) { -+ throw new IllegalStateException( - String.format( - "Unable to remove worker %s on task %s for %s", - thread.getName(), - this.getTaskId(), - this.getOwner().getDescription().getFullName()), - thrown); // We don't want to lose the original exception, if any -- } -- } finally { -- if (this.getPeriod() < 0 && this.workers.isEmpty()) { -- // At this spot, we know we are the final async task being executed! -- // Because we have the lock, nothing else is running or will run because delay < 0 -- this.runners.remove(this.getTaskId()); -+ } -+ } finally { -+ if (this.getPeriod() < 0 && this.workers.isEmpty()) { -+ // At this spot, we know we are the final async task being executed! -+ // Because we have the lock, nothing else is running or will run because delay < 0 -+ this.runners.remove(this.getTaskId()); -+ } - } - } - } -- } - } finally { thread.setName(nameBefore); } // Paper - name threads according to running plugin - } - diff --git a/leaf-server/paper-patches/features/0033-Async-playerdata-saving.patch b/leaf-server/paper-patches/features/0033-Async-playerdata-saving.patch index 86719c39..e35e19d3 100644 --- a/leaf-server/paper-patches/features/0033-Async-playerdata-saving.patch +++ b/leaf-server/paper-patches/features/0033-Async-playerdata-saving.patch @@ -5,24 +5,33 @@ 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 f2d87c12dd19210ce7e2147fada5c10191008632..f64844fb39cd02d8645491a8b482b21643334500 100644 +index f2d87c12dd19210ce7e2147fada5c10191008632..0cc6b6c597ca8c955b246764de637be4c25bfb40 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +@@ -207,7 +207,7 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa + } + + private CompoundTag getData() { +- return this.storage.load(this.profile.getName(), this.profile.getId().toString()).orElse(null); ++ return this.storage.load(this.profile.getName(), this.profile.getId().toString(), this.profile.getId()).orElse(null); // Leaf - Async playerdata saving + } + + private CompoundTag getBukkitData() { @@ -813,6 +813,17 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa * @param compoundTag */ private void save(CompoundTag compoundTag) { -+ // Leaf start ++ // Leaf start - Async playerdata saving + synchronized (server.console.playerDataStorage) { -+ while (server.console.playerDataStorage.saving.contains(getUniqueId().toString())) { ++ while (server.console.playerDataStorage.savingQueue.contains(getUniqueId())) { + try { + Thread.sleep(1L); + } catch (InterruptedException ignored) { + } + } -+ server.console.playerDataStorage.saving.add(getUniqueId().toString()); ++ server.console.playerDataStorage.savingQueue.add(getUniqueId()); + } -+ // Leaf end ++ // Leaf end - Async playerdata saving File playerDir = server.console.playerDataStorage.getPlayerDir(); try { File tempFile = File.createTempFile(this.getUniqueId()+"-", ".dat", playerDir); @@ -30,12 +39,12 @@ index f2d87c12dd19210ce7e2147fada5c10191008632..f64844fb39cd02d8645491a8b482b216 net.minecraft.Util.safeReplaceFile(playerDataFile.toPath(), tempFile.toPath(), playerDataFileOld.toPath()); } catch (java.io.IOException e) { e.printStackTrace(); -+ // Leaf start ++ // Leaf start - Async playerdata saving + } finally { + synchronized (server.console.playerDataStorage) { -+ server.console.playerDataStorage.saving.remove(getUniqueId().toString()); ++ server.console.playerDataStorage.savingQueue.remove(getUniqueId()); + } -+ // Leaf end ++ // Leaf end - Async playerdata saving } } // Purpur end - OfflinePlayer API diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java b/leaf-server/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java index b2c88455..bb363235 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java @@ -8,6 +8,7 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class AsyncPlayerDataSaving { + public static final ExecutorService IO_POOL = new ThreadPoolExecutor( 1, 1, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncBlockFinding.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncBlockFinding.java index 063a0b90..2bd57a18 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncBlockFinding.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncBlockFinding.java @@ -12,7 +12,6 @@ public class AsyncBlockFinding extends ConfigModules { @Experimental public static boolean enabled = false; - public static boolean asyncBlockFindingInitialized; @Override diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/OptimizeNonFlushPacketSending.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/OptimizeNonFlushPacketSending.java index 5c38c0db..1bf43512 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/OptimizeNonFlushPacketSending.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/OptimizeNonFlushPacketSending.java @@ -14,22 +14,22 @@ public class OptimizeNonFlushPacketSending extends ConfigModules { @Override public void onLoaded() { enabled = config.getBoolean(getBasePath() + ".OptimizeNonFlushPacketSending", enabled, config.pickStringRegionBased(""" - WARNING: This option is NOT compatible with ProtocolLib and may cause + WARNING: This option is NOT compatible with ProtocolLib and may cause issues with other plugins that modify packet handling. - Optimizes non-flush packet sending by using Netty's lazyExecute method to avoid + Optimizes non-flush packet sending by using Netty's lazyExecute method to avoid expensive thread wakeup calls when scheduling packet operations. Requires server restart to take effect. """, """ - 警告:此选项与 ProtocolLib 不兼容,并可能导致与其他修改数据包 - 处理的插件出现问题。 + 警告: 此选项与 ProtocolLib 不兼容, 并可能导致与其他修改数据包 + 处理的插件出现问题. - 通过使用 Netty 的 lazyExecute 方法来优化非刷新数据包的发送, - 避免在调度数据包操作时进行昂贵的线程唤醒调用。 + 通过使用 Netty 的 lazyExecute 方法来优化非刷新数据包的发送, + 避免在调度数据包操作时进行昂贵的线程唤醒调用. - 需要重启服务器才能生效。 + 需要重启服务器才能生效. """)); } }