From c732ec95bbc61f40de02a3d88b1b49754ac36e2e Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Mon, 5 Jan 2026 22:24:40 +0100 Subject: [PATCH 01/24] Fix foojay-resolver-convention for Gradle 9 --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 3afab42..78de0f1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,7 +7,7 @@ pluginManagement { } plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0" + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } rootProject.name = "shreddedpaper" From ccaf8f9364b496c07f9c5fd34ae9e5b90b01010c Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Mon, 5 Jan 2026 22:24:49 +0100 Subject: [PATCH 02/24] Thread-safe scoreboards Apply scoreboard thread-safety as source patches instead of feature patch --- .../0045-Thread-safe-scoreboards.patch | 118 ------------------ .../server/ServerScoreboard.java.patch | 19 +++ .../world/scores/PlayerTeam.java.patch | 19 +++ .../world/scores/Scoreboard.java.patch | 45 +++++++ 4 files changed, 83 insertions(+), 118 deletions(-) delete mode 100644 patches_removed/0045-Thread-safe-scoreboards.patch create mode 100644 shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/ServerScoreboard.java.patch create mode 100644 shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/scores/PlayerTeam.java.patch create mode 100644 shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/scores/Scoreboard.java.patch diff --git a/patches_removed/0045-Thread-safe-scoreboards.patch b/patches_removed/0045-Thread-safe-scoreboards.patch deleted file mode 100644 index a36a8be..0000000 --- a/patches_removed/0045-Thread-safe-scoreboards.patch +++ /dev/null @@ -1,118 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Sat, 27 Jul 2024 14:11:39 +0900 -Subject: [PATCH] Thread-safe scoreboards - - -diff --git a/src/main/java/net/minecraft/server/ServerScoreboard.java b/src/main/java/net/minecraft/server/ServerScoreboard.java -index f180001493146ef0d54079a8b2b47ad7decc24ca..775666dbf839d184bd50b0cb91a79658b298522d 100644 ---- a/src/main/java/net/minecraft/server/ServerScoreboard.java -+++ b/src/main/java/net/minecraft/server/ServerScoreboard.java -@@ -7,6 +7,8 @@ import java.util.List; - import java.util.Objects; - import java.util.Optional; - import java.util.Set; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.concurrent.CopyOnWriteArrayList; - import javax.annotation.Nullable; - import net.minecraft.core.HolderLookup; - import net.minecraft.nbt.CompoundTag; -@@ -31,8 +33,8 @@ import net.minecraft.world.scores.ScoreboardSaveData; - public class ServerScoreboard extends Scoreboard { - - private final MinecraftServer server; -- private final Set trackedObjectives = Sets.newHashSet(); -- private final List dirtyListeners = Lists.newArrayList(); -+ private final Set trackedObjectives = ConcurrentHashMap.newKeySet(); // ShreddedPaper -+ private final List dirtyListeners = new CopyOnWriteArrayList<>(); // ShreddedPaper - rarely modified, thus CopyOnWriteArrayList suffices - - public ServerScoreboard(MinecraftServer server) { - this.server = server; -diff --git a/src/main/java/net/minecraft/world/scores/PlayerTeam.java b/src/main/java/net/minecraft/world/scores/PlayerTeam.java -index 9464054912e19fc78dd965b71fce20a18564b351..0304ebb48563f4e337855eb091b2c1a8f6cbd590 100644 ---- a/src/main/java/net/minecraft/world/scores/PlayerTeam.java -+++ b/src/main/java/net/minecraft/world/scores/PlayerTeam.java -@@ -3,6 +3,7 @@ package net.minecraft.world.scores; - import com.google.common.collect.Sets; - import java.util.Collection; - import java.util.Set; -+import java.util.concurrent.ConcurrentHashMap; - import javax.annotation.Nullable; - import net.minecraft.ChatFormatting; - import net.minecraft.network.chat.CommonComponents; -@@ -17,7 +18,7 @@ public class PlayerTeam extends Team { - private static final int BIT_SEE_INVISIBLES = 1; - private final Scoreboard scoreboard; - private final String name; -- private final Set players = Sets.newHashSet(); -+ private final Set players = ConcurrentHashMap.newKeySet(); // ShreddedPaper - private Component displayName; - private Component playerPrefix = CommonComponents.EMPTY; - private Component playerSuffix = CommonComponents.EMPTY; -diff --git a/src/main/java/net/minecraft/world/scores/Scoreboard.java b/src/main/java/net/minecraft/world/scores/Scoreboard.java -index 121b57ec018bffc05904e7596e0963e2bb545dc1..2af1dcad557607800e3742fd2866885481f4835c 100644 ---- a/src/main/java/net/minecraft/world/scores/Scoreboard.java -+++ b/src/main/java/net/minecraft/world/scores/Scoreboard.java -@@ -15,6 +15,7 @@ import java.util.EnumMap; - import java.util.List; - import java.util.Map; - import java.util.Objects; -+import java.util.concurrent.ConcurrentHashMap; - import java.util.function.Consumer; - import javax.annotation.Nullable; - import net.minecraft.core.HolderLookup; -@@ -31,12 +32,12 @@ import org.slf4j.Logger; - public class Scoreboard { - public static final String HIDDEN_SCORE_PREFIX = "#"; - private static final Logger LOGGER = LogUtils.getLogger(); -- private final Object2ObjectMap objectivesByName = new Object2ObjectOpenHashMap<>(16, 0.5F); -- private final Reference2ObjectMap> objectivesByCriteria = new Reference2ObjectOpenHashMap<>(); -- private final Map playerScores = new Object2ObjectOpenHashMap<>(16, 0.5F); -+ private final Map objectivesByName = new ConcurrentHashMap<>(); // ShreddedPaper -+ private final Map> objectivesByCriteria = new ConcurrentHashMap<>(); // ShreddedPaper -+ private final Map playerScores = new ConcurrentHashMap<>(); // ShreddedPaper - private final Map displayObjectives = new EnumMap<>(DisplaySlot.class); -- private final Object2ObjectMap teamsByName = new Object2ObjectOpenHashMap<>(); -- private final Object2ObjectMap teamsByPlayer = new Object2ObjectOpenHashMap<>(); -+ private final Map teamsByName = new ConcurrentHashMap<>(); // ShreddedPaper -+ private final Map teamsByPlayer = new ConcurrentHashMap<>(); // ShreddedPaper - - @Nullable - public Objective getObjective(@Nullable String name) { -@@ -88,7 +89,7 @@ public class Scoreboard { - } - - @Override -- public void set(int score) { -+ public void set(int newScoreValue) { // ShreddedPaper compile error - rename to newScoreValue - if (!bl) { - throw new IllegalStateException("Cannot modify read-only score"); - } else { -@@ -101,8 +102,8 @@ public class Scoreboard { - } - } - -- if (score != score.value()) { -- score.value(score); -+ if (newScoreValue != score.value()) { // ShreddedPaper compile error - rename to newScoreValue -+ score.value(newScoreValue); // ShreddedPaper compile error - rename to newScoreValue - bl = true; - } - -@@ -240,7 +241,7 @@ public class Scoreboard { - this.onObjectiveRemoved(objective); - } - -- public void setDisplayObjective(DisplaySlot slot, @Nullable Objective objective) { -+ public synchronized void setDisplayObjective(DisplaySlot slot, @Nullable Objective objective) { // ShreddedPaper - this.displayObjectives.put(slot, objective); - } - -@@ -251,6 +252,7 @@ public class Scoreboard { - - @Nullable - public PlayerTeam getPlayerTeam(String name) { -+ if (name == null) return null; // ShreddedPaper - return this.teamsByName.get(name); - } - diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/ServerScoreboard.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/ServerScoreboard.java.patch new file mode 100644 index 0000000..cf27886 --- /dev/null +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/ServerScoreboard.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/server/ServerScoreboard.java ++++ b/net/minecraft/server/ServerScoreboard.java +@@ -6,6 +_,7 @@ + import java.util.Objects; + import java.util.Optional; + import java.util.Set; ++import java.util.concurrent.ConcurrentHashMap; + import net.minecraft.network.protocol.Packet; + import net.minecraft.network.protocol.game.ClientboundResetScorePacket; + import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket; +@@ -26,7 +_,7 @@ + + public class ServerScoreboard extends Scoreboard { + private final MinecraftServer server; +- private final Set trackedObjectives = Sets.newHashSet(); ++ private final Set trackedObjectives = ConcurrentHashMap.newKeySet(); // ShreddedPaper - concurrent + private boolean dirty; + + public ServerScoreboard(MinecraftServer server) { diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/scores/PlayerTeam.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/scores/PlayerTeam.java.patch new file mode 100644 index 0000000..f9ad8c7 --- /dev/null +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/scores/PlayerTeam.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/world/scores/PlayerTeam.java ++++ b/net/minecraft/world/scores/PlayerTeam.java +@@ -7,6 +_,7 @@ + import java.util.List; + import java.util.Optional; + import java.util.Set; ++import java.util.concurrent.ConcurrentHashMap; + import net.minecraft.ChatFormatting; + import net.minecraft.network.chat.CommonComponents; + import net.minecraft.network.chat.Component; +@@ -22,7 +_,7 @@ + private static final int BIT_SEE_INVISIBLES = 1; + private final Scoreboard scoreboard; + private final String name; +- private final Set players = Sets.newHashSet(); ++ private final Set players = ConcurrentHashMap.newKeySet(); // ShreddedPaper - concurrent + private Component displayName; + private Component playerPrefix = CommonComponents.EMPTY; + private Component playerSuffix = CommonComponents.EMPTY; diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/scores/Scoreboard.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/scores/Scoreboard.java.patch new file mode 100644 index 0000000..5a073b9 --- /dev/null +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/scores/Scoreboard.java.patch @@ -0,0 +1,45 @@ +--- a/net/minecraft/world/scores/Scoreboard.java ++++ b/net/minecraft/world/scores/Scoreboard.java +@@ -17,6 +_,7 @@ + import java.util.List; + import java.util.Map; + import java.util.Objects; ++import java.util.concurrent.ConcurrentHashMap; + import java.util.function.Consumer; + import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.numbers.NumberFormat; +@@ -30,12 +_,12 @@ + public class Scoreboard { + public static final String HIDDEN_SCORE_PREFIX = "#"; + private static final Logger LOGGER = LogUtils.getLogger(); +- private final Object2ObjectMap objectivesByName = new Object2ObjectOpenHashMap<>(16, 0.5F); +- private final Reference2ObjectMap> objectivesByCriteria = new Reference2ObjectOpenHashMap<>(); +- private final Map playerScores = new Object2ObjectOpenHashMap<>(16, 0.5F); ++ private final Map objectivesByName = new ConcurrentHashMap<>(); // ShreddedPaper - concurrent ++ private final Map> objectivesByCriteria = new ConcurrentHashMap<>(); // ShreddedPaper - concurrent ++ private final Map playerScores = new ConcurrentHashMap<>(); // ShreddedPaper - concurrent + private final Map displayObjectives = new EnumMap<>(DisplaySlot.class); +- private final Object2ObjectMap teamsByName = new Object2ObjectOpenHashMap<>(); +- private final Object2ObjectMap teamsByPlayer = new Object2ObjectOpenHashMap<>(); ++ private final Map teamsByName = new ConcurrentHashMap<>(); // ShreddedPaper - concurrent ++ private final Map teamsByPlayer = new ConcurrentHashMap<>(); // ShreddedPaper - concurrent + + public @Nullable Objective getObjective(@Nullable String name) { + return this.objectivesByName.get(name); +@@ -236,7 +_,7 @@ + this.onObjectiveRemoved(objective); + } + +- public void setDisplayObjective(DisplaySlot slot, @Nullable Objective objective) { ++ public synchronized void setDisplayObjective(DisplaySlot slot, @Nullable Objective objective) { // ShreddedPaper - synchronized + this.displayObjectives.put(slot, objective); + } + +@@ -245,6 +_,7 @@ + } + + public @Nullable PlayerTeam getPlayerTeam(String name) { ++ if (name == null) return null; // ShreddedPaper + return this.teamsByName.get(name); + } + From 0b2f57befa66dc51218ad8a2f8c9c9533319450e Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Mon, 5 Jan 2026 22:24:55 +0100 Subject: [PATCH 03/24] Thread-safe raids Apply raid thread-safety as source patches instead of feature patch --- patches_removed/0037-Raids.patch | 80 ------------------- .../world/entity/raid/Raid.java.patch | 27 +++++++ .../world/entity/raid/Raids.java.patch | 30 +++++++ 3 files changed, 57 insertions(+), 80 deletions(-) delete mode 100644 patches_removed/0037-Raids.patch create mode 100644 shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/raid/Raid.java.patch create mode 100644 shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/raid/Raids.java.patch diff --git a/patches_removed/0037-Raids.patch b/patches_removed/0037-Raids.patch deleted file mode 100644 index 5338b70..0000000 --- a/patches_removed/0037-Raids.patch +++ /dev/null @@ -1,80 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Sun, 9 Jun 2024 09:42:44 +0900 -Subject: [PATCH] Raids - - -diff --git a/src/main/java/net/minecraft/world/entity/raid/Raid.java b/src/main/java/net/minecraft/world/entity/raid/Raid.java -index fdff9788eaf663be79214b2ca491f0f0444f6136..71a442d218b7e7e7ebb57d9a1559289bda04c4d5 100644 ---- a/src/main/java/net/minecraft/world/entity/raid/Raid.java -+++ b/src/main/java/net/minecraft/world/entity/raid/Raid.java -@@ -15,6 +15,8 @@ import java.util.UUID; - import java.util.function.Predicate; - import java.util.stream.Stream; - import javax.annotation.Nullable; -+ -+import io.multipaper.shreddedpaper.ShreddedPaper; - import net.minecraft.ChatFormatting; - import net.minecraft.advancements.CriteriaTriggers; - import net.minecraft.core.BlockPos; -@@ -425,14 +427,17 @@ public class Raid { - LivingEntity entityliving = (LivingEntity) entity; - - if (!entity.isSpectator()) { -+ ShreddedPaper.ensureSync(entity, () -> { // ShreddedPaper - run on right thread - entityliving.addEffect(new MobEffectInstance(MobEffects.HERO_OF_THE_VILLAGE, 48000, this.raidOmenLevel - 1, false, false, true)); - if (entityliving instanceof ServerPlayer) { - ServerPlayer entityplayer = (ServerPlayer) entityliving; - - entityplayer.awardStat(Stats.RAID_WIN); - CriteriaTriggers.RAID_WIN.trigger(entityplayer); -- winners.add(entityplayer.getBukkitEntity()); // CraftBukkit -+ // winners.add(entityplayer.getBukkitEntity()); // CraftBukkit // ShreddedPaper - don't run on right thread - } -+ }); // ShreddedPaper - run on right thread -+ if (entityliving instanceof ServerPlayer entityplayer) winners.add(entityplayer.getBukkitEntity()); // ShreddedPaper - this doesn't need to be run on right thread - } - } - } -diff --git a/src/main/java/net/minecraft/world/entity/raid/Raids.java b/src/main/java/net/minecraft/world/entity/raid/Raids.java -index eedce2a3d67d875d5174ee125e2679480d4d412c..998d7184cb5718e6f643e6dccc416cb872b9d519 100644 ---- a/src/main/java/net/minecraft/world/entity/raid/Raids.java -+++ b/src/main/java/net/minecraft/world/entity/raid/Raids.java -@@ -5,6 +5,8 @@ import java.util.Iterator; - import java.util.List; - import java.util.Map; - import javax.annotation.Nullable; -+ -+import io.multipaper.shreddedpaper.ShreddedPaper; - import net.minecraft.core.BlockPos; - import net.minecraft.core.Holder; - import net.minecraft.core.HolderLookup; -@@ -27,7 +29,7 @@ public class Raids extends SavedData { - - private static final String RAID_FILE_ID = "raids"; - public final Map playerCooldowns = Maps.newHashMap(); -- public final Map raidMap = Maps.newHashMap(); -+ public final Map raidMap = Maps.newConcurrentMap(); // ShreddedPaper - concurrent map - private final ServerLevel level; - private int nextAvailableID; - private int tick; -@@ -68,16 +70,18 @@ public class Raids extends SavedData { - while (iterator.hasNext()) { - Raid raid = (Raid) iterator.next(); - -+ ShreddedPaper.runSync((ServerLevel) raid.getLevel(), raid.getCenter(), () -> { // ShreddedPaper - run on right thread - if (this.level.getGameRules().getBoolean(GameRules.RULE_DISABLE_RAIDS)) { - raid.stop(); - } - - if (raid.isStopped()) { -- iterator.remove(); -+ this.raidMap.remove(raid.getId(), raid); // iterator.remove(); // ShreddedPaper - run on right thread - this.setDirty(); - } else { - raid.tick(); - } -+ }); // ShreddedPaper - run on right thread - } - - if (this.tick % 200 == 0) { diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/raid/Raid.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/raid/Raid.java.patch new file mode 100644 index 0000000..f8c71d9 --- /dev/null +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/raid/Raid.java.patch @@ -0,0 +1,27 @@ +--- a/net/minecraft/world/entity/raid/Raid.java ++++ b/net/minecraft/world/entity/raid/Raid.java +@@ -15,6 +_,7 @@ + import java.util.UUID; + import java.util.function.Predicate; + import java.util.stream.Stream; ++import io.multipaper.shreddedpaper.ShreddedPaper; + import net.minecraft.SharedConstants; + import net.minecraft.advancements.CriteriaTriggers; + import net.minecraft.core.BlockPos; +@@ -418,12 +_,15 @@ + for (UUID uuid : this.heroesOfTheVillage) { + Entity entity = level.getEntity(uuid); + if (entity instanceof LivingEntity livingEntity && !entity.isSpectator()) { ++ ShreddedPaper.ensureSync(livingEntity, () -> { // ShreddedPaper - run on right thread + livingEntity.addEffect(new MobEffectInstance(MobEffects.HERO_OF_THE_VILLAGE, 48000, this.raidOmenLevel - 1, false, false, true)); + if (livingEntity instanceof ServerPlayer serverPlayer) { + serverPlayer.awardStat(Stats.RAID_WIN); + CriteriaTriggers.RAID_WIN.trigger(serverPlayer); +- winners.add(serverPlayer.getBukkitEntity()); // CraftBukkit ++ // winners.add(serverPlayer.getBukkitEntity()); // CraftBukkit // ShreddedPaper - don't run on right thread + } ++ }); // ShreddedPaper - run on right thread ++ if (livingEntity instanceof ServerPlayer serverPlayer) winners.add(serverPlayer.getBukkitEntity()); // ShreddedPaper - this doesn't need to be run on right thread + } + } + org.bukkit.craftbukkit.event.CraftEventFactory.callRaidFinishEvent(level, this, winners); // CraftBukkit diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/raid/Raids.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/raid/Raids.java.patch new file mode 100644 index 0000000..adfc67c --- /dev/null +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/raid/Raids.java.patch @@ -0,0 +1,30 @@ +--- a/net/minecraft/world/entity/raid/Raids.java ++++ b/net/minecraft/world/entity/raid/Raids.java +@@ -2,6 +_,7 @@ + + import com.mojang.serialization.Codec; + import com.mojang.serialization.codecs.RecordCodecBuilder; ++import io.multipaper.shreddedpaper.ShreddedPaper; + import it.unimi.dsi.fastutil.ints.Int2ObjectMap; + import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; +@@ -98,16 +_,18 @@ + + while (iterator.hasNext()) { + Raid raid = iterator.next(); ++ ShreddedPaper.runSync(level, raid.getCenter(), () -> { // ShreddedPaper - run on right thread + if (!level.getGameRules().get(GameRules.RAIDS)) { + raid.stop(); + } + + if (raid.isStopped()) { +- iterator.remove(); ++ this.raidMap.remove(raid.idOrNegativeOne, raid); // iterator.remove(); // ShreddedPaper - run on right thread + this.setDirty(); + } else { + raid.tick(level); + } ++ }); // ShreddedPaper - run on right thread + } + + if (this.tick % 200 == 0) { From eeb53c78688a87151e6c5a60d91dc89bb3ec14e8 Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Mon, 5 Jan 2026 22:25:02 +0100 Subject: [PATCH 04/24] Thread-safe redstone torch burnout tracking Apply redstone torch thread-safety as source patch instead of feature patch --- ...read-safe-alternate-redstone-handler.patch | 28 --------------- .../level/block/RedstoneTorchBlock.java.patch | 34 +++++++++++++++++++ 2 files changed, 34 insertions(+), 28 deletions(-) delete mode 100644 patches_removed/0038-Thread-safe-alternate-redstone-handler.patch create mode 100644 shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch diff --git a/patches_removed/0038-Thread-safe-alternate-redstone-handler.patch b/patches_removed/0038-Thread-safe-alternate-redstone-handler.patch deleted file mode 100644 index 7dd4c05..0000000 --- a/patches_removed/0038-Thread-safe-alternate-redstone-handler.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Tue, 18 Jun 2024 08:49:24 +0900 -Subject: [PATCH] Thread-safe alternate redstone handler - - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index e07d635bffd12a71e078e824e0a7bc41784e1146..b6ffd2ac046777466657eaedbf13f2cbaa8456b3 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -229,7 +229,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - public final UUID uuid; - public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent - public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent -- private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) -+ private final ThreadLocal wireHandler = ThreadLocal.withInitial(() -> new alternate.current.wire.WireHandler(this)); // Paper - optimize redstone (Alternate Current) - public boolean hasRidableMoveEvent = false; // Purpur - - public LevelChunk getChunkIfLoaded(int x, int z) { -@@ -2767,7 +2767,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - // Paper start - optimize redstone (Alternate Current) - @Override - public alternate.current.wire.WireHandler getWireHandler() { -- return wireHandler; -+ return wireHandler.get(); - } - // Paper end - optimize redstone (Alternate Current) - diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch new file mode 100644 index 0000000..b743f3c --- /dev/null +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch @@ -0,0 +1,34 @@ +--- a/net/minecraft/world/level/block/RedstoneTorchBlock.java ++++ b/net/minecraft/world/level/block/RedstoneTorchBlock.java +@@ -2,6 +_,7 @@ + + import com.google.common.collect.Lists; + import com.mojang.serialization.MapCodec; ++import io.multipaper.shreddedpaper.region.RegionPos; + import java.util.List; + import java.util.Map; + import java.util.WeakHashMap; +@@ -73,7 +_,7 @@ + protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { + boolean hasNeighborSignal = this.hasNeighborSignal(level, pos, state); + // Paper start - Faster redstone torch rapid clock removal +- java.util.ArrayDeque redstoneUpdateInfos = level.redstoneUpdateInfos; ++ java.util.ArrayDeque redstoneUpdateInfos = level.chunkSource.tickingRegions.get(RegionPos.forBlockPos(pos)).redstoneUpdateInfos; // ShreddedPaper - move redstoneUpdateInfos to the region + if (redstoneUpdateInfos != null) { + RedstoneTorchBlock.Toggle curr; + while ((curr = redstoneUpdateInfos.peek()) != null && level.getGameTime() - curr.when > 60L) { +@@ -154,10 +_,12 @@ + + private static boolean isToggledTooFrequently(Level level, BlockPos pos, boolean logToggle) { + // Paper start - Faster redstone torch rapid clock removal +- java.util.ArrayDeque list = level.redstoneUpdateInfos; ++ // ShreddedPaper start - move redstoneUpdateInfos to the region ++ java.util.ArrayDeque list = ((ServerLevel) level).chunkSource.tickingRegions.get(RegionPos.forBlockPos(pos)).redstoneUpdateInfos; + if (list == null) { +- list = level.redstoneUpdateInfos = new java.util.ArrayDeque<>(); ++ list = ((ServerLevel) level).chunkSource.tickingRegions.get(RegionPos.forBlockPos(pos)).redstoneUpdateInfos = new java.util.ArrayDeque<>(); + } ++ // ShreddedPaper end - move redstoneUpdateInfos to the region + // Paper end - Faster redstone torch rapid clock removal + if (logToggle) { + list.add(new RedstoneTorchBlock.Toggle(pos.immutable(), level.getGameTime())); From 44adb1d34b14a06a47187d7050d6f6e5ae5fba8e Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Mon, 5 Jan 2026 22:25:16 +0100 Subject: [PATCH 05/24] Optimization: entity activation check frequency Apply entity activation optimization as source patch instead of feature patch --- .../activation/ActivationRange.java.patch | 45 +++++++------------ 1 file changed, 17 insertions(+), 28 deletions(-) rename patches_removed/0046-Optimization-entity-activation-check-frequency.patch => shreddedpaper-server/minecraft-patches/sources/io/papermc/paper/entity/activation/ActivationRange.java.patch (50%) diff --git a/patches_removed/0046-Optimization-entity-activation-check-frequency.patch b/shreddedpaper-server/minecraft-patches/sources/io/papermc/paper/entity/activation/ActivationRange.java.patch similarity index 50% rename from patches_removed/0046-Optimization-entity-activation-check-frequency.patch rename to shreddedpaper-server/minecraft-patches/sources/io/papermc/paper/entity/activation/ActivationRange.java.patch index 62c6e74..5a06629 100644 --- a/patches_removed/0046-Optimization-entity-activation-check-frequency.patch +++ b/shreddedpaper-server/minecraft-patches/sources/io/papermc/paper/entity/activation/ActivationRange.java.patch @@ -1,45 +1,34 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Wed, 31 Jul 2024 00:46:51 +0900 -Subject: [PATCH] Optimization: entity-activation-check-frequency - - -diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java -index c9a10c0b952f7590a48d0b96cacd8e012543d805..ad566654e2e09a5f47de5583353553263e448ee9 100644 ---- a/src/main/java/org/spigotmc/ActivationRange.java -+++ b/src/main/java/org/spigotmc/ActivationRange.java -@@ -1,5 +1,6 @@ - package org.spigotmc; +--- a/io/papermc/paper/entity/activation/ActivationRange.java ++++ b/io/papermc/paper/entity/activation/ActivationRange.java +@@ -1,5 +_,6 @@ + package io.papermc.paper.entity.activation; +import io.multipaper.shreddedpaper.config.ShreddedPaperConfiguration; import net.minecraft.core.BlockPos; import net.minecraft.server.MinecraftServer; - import net.minecraft.server.level.ServerChunkCache; -@@ -199,7 +200,8 @@ public class ActivationRange + import net.minecraft.world.entity.Entity; +@@ -142,7 +_,8 @@ + maxRange = Math.min((world.spigotConfig.simulationDistance << 4) - 8, maxRange); - for ( Player player : world.players() ) - { + for (final Player player : world.players()) { - player.activatedTick = MinecraftServer.currentTick; + if (ShreddedPaperConfiguration.get().optimizations.entityActivationCheckFrequency > 1 && (player.getId() + MinecraftServer.currentTick) % ShreddedPaperConfiguration.get().optimizations.entityActivationCheckFrequency != 0) continue; // ShreddedPaper - Configurable entity activation check frequency + player.activatedTick = MinecraftServer.currentTick + Math.max(0, ShreddedPaperConfiguration.get().optimizations.entityActivationCheckFrequency); // ShreddedPaper - Configurable entity activation check frequency - if ( world.spigotConfig.ignoreSpectatorActivation && player.isSpectator() ) - { + if (world.spigotConfig.ignoreSpectatorActivation && player.isSpectator()) { continue; -@@ -260,16 +262,16 @@ public class ActivationRange + } +@@ -177,13 +_,13 @@ + * @param entity */ - private static void activateEntity(Entity entity) - { -- if ( MinecraftServer.currentTick > entity.activatedTick ) -+ if ( MinecraftServer.currentTick + Math.max(0, ShreddedPaperConfiguration.get().optimizations.entityActivationCheckFrequency) > entity.activatedTick ) // ShreddedPaper - Configurable entity activation check frequency - { - if ( entity.defaultActivationState ) - { // Pufferfish - diff on change + private static void activateEntity(final Entity entity) { +- if (MinecraftServer.currentTick > entity.activatedTick) { ++ if (MinecraftServer.currentTick + Math.max(0, ShreddedPaperConfiguration.get().optimizations.entityActivationCheckFrequency) > entity.activatedTick) { // ShreddedPaper - Configurable entity activation check frequency + if (entity.defaultActivationState) { - entity.activatedTick = MinecraftServer.currentTick; + entity.activatedTick = MinecraftServer.currentTick + Math.max(0, ShreddedPaperConfiguration.get().optimizations.entityActivationCheckFrequency); // ShreddedPaper - Configurable entity activation check frequency return; } - if ( entity.activationType.boundingBox.intersects( entity.getBoundingBox() ) ) - { // Pufferfish - diff on change + if (entity.activationType.boundingBox.intersects(entity.getBoundingBox())) { - entity.activatedTick = MinecraftServer.currentTick; + entity.activatedTick = MinecraftServer.currentTick + Math.max(0, ShreddedPaperConfiguration.get().optimizations.entityActivationCheckFrequency); // ShreddedPaper - Configurable entity activation check frequency } From 2a51a6834315d48008aceebed3781ee998b9d55c Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Mon, 5 Jan 2026 22:25:23 +0100 Subject: [PATCH 06/24] Optimization: only apply item gravity if not on ground Apply item gravity optimization as source patch instead of feature patch --- ...-apply-item-gravity-if-not-on-ground.patch | 19 ------------------- .../world/entity/item/ItemEntity.java.patch | 11 +++++++++++ 2 files changed, 11 insertions(+), 19 deletions(-) delete mode 100644 patches_removed/0070-Only-apply-item-gravity-if-not-on-ground.patch create mode 100644 shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch diff --git a/patches_removed/0070-Only-apply-item-gravity-if-not-on-ground.patch b/patches_removed/0070-Only-apply-item-gravity-if-not-on-ground.patch deleted file mode 100644 index 4a04719..0000000 --- a/patches_removed/0070-Only-apply-item-gravity-if-not-on-ground.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Fri, 31 Oct 2025 03:02:25 +0900 -Subject: [PATCH] Only apply item gravity if not on ground - - -diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -index 7dae2a7c759fc3e004f806c59e083f52c3a460b2..4c7b3c900f7fceafe34e2da34e7060e31c920888 100644 ---- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -@@ -176,7 +176,7 @@ public class ItemEntity extends Entity implements TraceableEntity { - } else if (this.isInLava() && this.getFluidHeight(FluidTags.LAVA) > 0.10000000149011612D) { - this.setUnderLavaMovement(); - } else { -- this.applyGravity(); -+ if (!this.onGround()) this.applyGravity(); // ShreddedPaper - only apply gravity if not on ground - } - - if (this.level().isClientSide) { diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch new file mode 100644 index 0000000..bb593de --- /dev/null +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/entity/item/ItemEntity.java ++++ b/net/minecraft/world/entity/item/ItemEntity.java +@@ -165,7 +_,7 @@ + } else if (this.isInLava() && this.getFluidHeight(FluidTags.LAVA) > 0.1F) { + this.setUnderLavaMovement(); + } else { +- this.applyGravity(); ++ if (!this.onGround()) this.applyGravity(); // ShreddedPaper - only apply gravity if not on ground + } + + if (this.level().isClientSide()) { From 37801e8796f380b345d4c6a1cfef60d0b7cbf240 Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Mon, 5 Jan 2026 22:25:32 +0100 Subject: [PATCH 07/24] Thread-safe eye of ender signal fire search --- .../entity/projectile/EyeOfEnder.java.patch | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch new file mode 100644 index 0000000..98f683d --- /dev/null +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch @@ -0,0 +1,21 @@ +--- a/net/minecraft/world/entity/projectile/EyeOfEnder.java ++++ b/net/minecraft/world/entity/projectile/EyeOfEnder.java +@@ -1,5 +_,6 @@ + package net.minecraft.world.entity.projectile; + ++import io.multipaper.shreddedpaper.region.RegionPos; // ShreddedPaper + import net.minecraft.core.particles.ParticleTypes; + import net.minecraft.network.syncher.EntityDataAccessor; + import net.minecraft.network.syncher.EntityDataSerializers; +@@ -148,6 +_,11 @@ + d *= 0.8; + d1 *= 0.8; + } ++ ++ // ShreddedPaper start - keep velocity within a region ++ double maxDistance = RegionPos.REGION_SIZE * 16; ++ if (d > maxDistance) d = maxDistance; ++ // ShreddedPaper end - keep velocity within a region + + double d2 = pos.y - deltaMovement.y < target.y ? 1.0 : -1.0; + return vec3.scale(d / len).add(0.0, d1 + (d2 - d1) * 0.015, 0.0); From 38301d0c5e65ff9188e248ad0a2310852ced9552 Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Mon, 5 Jan 2026 22:25:41 +0100 Subject: [PATCH 08/24] Fix player task scheduler execution Execute scheduled tasks on the player's task scheduler during tick (e.g., packet handlers) --- .../shreddedpaper/threading/ShreddedPaperPlayerTicker.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/shreddedpaper-server/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperPlayerTicker.java b/shreddedpaper-server/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperPlayerTicker.java index fe715d3..b0619c9 100644 --- a/shreddedpaper-server/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperPlayerTicker.java +++ b/shreddedpaper-server/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperPlayerTicker.java @@ -2,10 +2,17 @@ import ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader; import net.minecraft.server.level.ServerPlayer; +import org.bukkit.craftbukkit.entity.CraftPlayer; public class ShreddedPaperPlayerTicker { public static void tickPlayer(ServerPlayer serverPlayer) { + // ShreddedPaper - Execute scheduled tasks on the player's task scheduler (e.g., packet handlers) + CraftPlayer craftPlayer = serverPlayer.getBukkitEntity(); + if (craftPlayer != null) { + craftPlayer.taskScheduler.executeTick(); + } + serverPlayer.connection.connection.tick(); final RegionizedPlayerChunkLoader.PlayerChunkLoaderData loader = serverPlayer.moonrise$getChunkLoader(); if (loader != null) { From 42ca57884b5c1c35117dbf50bb1ad245a28396f2 Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Mon, 5 Jan 2026 22:25:48 +0100 Subject: [PATCH 09/24] Remove debug logging from LevelChunkRegionMap --- .../multipaper/shreddedpaper/region/LevelChunkRegionMap.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/shreddedpaper-server/src/main/java/io/multipaper/shreddedpaper/region/LevelChunkRegionMap.java b/shreddedpaper-server/src/main/java/io/multipaper/shreddedpaper/region/LevelChunkRegionMap.java index 657b47f..febef32 100644 --- a/shreddedpaper-server/src/main/java/io/multipaper/shreddedpaper/region/LevelChunkRegionMap.java +++ b/shreddedpaper-server/src/main/java/io/multipaper/shreddedpaper/region/LevelChunkRegionMap.java @@ -71,9 +71,7 @@ public void forEach(Consumer consumer) { } public void addTickingEntity(Entity entity) { - entity.SHREDDEDPAPER_DEBUG.append(System.currentTimeMillis() + ": " + Thread.currentThread().getName() + ": addTickingEntity " + entity + " previousTickingChunkPosRegion=" + entity.previousTickingChunkPosRegion + "\n"); if (entity.previousTickingChunkPosRegion != null) { - LOGGER.error(entity.SHREDDEDPAPER_DEBUG.toString()); throw new IllegalStateException("Entity has already been added to a ticking list " + entity); } @@ -82,9 +80,7 @@ public void addTickingEntity(Entity entity) { } public void removeTickingEntity(Entity entity) { - entity.SHREDDEDPAPER_DEBUG.append(System.currentTimeMillis() + ": " + Thread.currentThread().getName() + ": removeTickingEntity " + entity + " previousTickingChunkPosRegion=" + entity.previousTickingChunkPosRegion + "\n"); if (entity.previousTickingChunkPosRegion == null) { - LOGGER.error(entity.SHREDDEDPAPER_DEBUG.toString()); throw new IllegalStateException("Entity has not been added to a ticking list " + entity); } @@ -93,7 +89,6 @@ public void removeTickingEntity(Entity entity) { } public void moveTickingEntity(Entity entity) { - entity.SHREDDEDPAPER_DEBUG.append(System.currentTimeMillis() + ": " + Thread.currentThread().getName() + ": moveTickingEntity " + entity + " previousTickingChunkPosRegion=" + entity.previousTickingChunkPosRegion + "\n"); if (entity.previousTickingChunkPosRegion == null) { // Not ticking, ignore return; From 7a34f3eded0fcc6d68b64ca0816e6a31c70445c1 Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Mon, 5 Jan 2026 22:26:03 +0100 Subject: [PATCH 10/24] Update patches for upstream compatibility Sync patch context and naming conventions with upstream --- .../scheduling/NewChunkHolder.java.patch | 8 ++- .../dispenser/DispenseItemBehavior.java.patch | 10 +-- .../minecraft/network/Connection.java.patch | 23 +++++++ .../server/MinecraftServer.java.patch | 9 +++ .../ClearInventoryCommands.java.patch | 2 +- .../server/commands/GiveCommand.java.patch | 7 +-- .../server/commands/KillCommand.java.patch | 16 +++-- .../commands/TeleportCommand.java.patch | 2 +- .../server/level/ChunkHolder.java.patch | 45 +++++++++++++ .../server/level/ServerLevel.java.patch | 39 +++++++----- .../level/ServerPlayerGameMode.java.patch | 31 ++++++++- .../server/players/PlayerList.java.patch | 20 ++++++ .../minecraft/world/entity/Entity.java.patch | 49 ++++++++------- .../FireworkRocketEntity.java.patch | 15 +++-- .../minecraft/world/item/ItemStack.java.patch | 32 +++++----- .../minecraft/world/level/Level.java.patch | 63 +++++++++---------- .../world/level/NaturalSpawner.java.patch | 8 +++ .../world/level/block/BedBlock.java.patch | 2 +- .../world/level/block/Block.java.patch | 4 +- .../world/level/block/SaplingBlock.java.patch | 12 ++-- .../level/block/WitherSkullBlock.java.patch | 2 +- .../world/level/chunk/LevelChunk.java.patch | 4 +- .../dimension/end/EndDragonFight.java.patch | 5 +- .../bukkit/craftbukkit/CraftWorld.java.patch | 24 ++++--- .../craftbukkit/block/CraftBlock.java.patch | 10 +-- .../craftbukkit/entity/CraftPlayer.java.patch | 27 -------- .../shreddedpaper/ShreddedPaper.java | 2 +- .../config/ShreddedPaperConfiguration.java | 1 - 28 files changed, 297 insertions(+), 175 deletions(-) diff --git a/shreddedpaper-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java.patch b/shreddedpaper-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java.patch index 72c358c..5a543da 100644 --- a/shreddedpaper-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java.patch @@ -40,7 +40,7 @@ } } } -@@ -1194,6 +_,22 @@ +@@ -1194,6 +_,24 @@ this.currentFullChunkStatus = to; } @@ -48,8 +48,10 @@ + public boolean ensureFullStatusChangeWithWriteLock() { + // Upgrading to a full loaded chunk is just a chunk load task, which does not require the write lock + // However, upgrading to block ticking or entity ticking is more than just loading a chunk, and requires us to have the write lock -+ if (!MinecraftServer.getServer().forceTicks && !ShreddedPaperChunkTicker.isCurrentlyTickingRegion(this.world, RegionPos.forChunk(this.vanillaChunkHolder.getPos()))) { -+ ShreddedPaper.runSync(world, vanillaChunkHolder.getPos(), () -> { ++ ChunkPos chunkPos = this.vanillaChunkHolder.getPos(); ++ RegionPos regionPos = RegionPos.forChunk(chunkPos); ++ if (!MinecraftServer.getServer().forceTicks && !this.world.chunkScheduler.getRegionLocker().hasWriteLock(regionPos)) { ++ ShreddedPaper.runSync(world, chunkPos, () -> { + List changedFullStatus2 = new ArrayList<>(); + handleFullStatusChange(changedFullStatus2); + this.scheduler.chunkHolderManager.addChangedStatuses(changedFullStatus2); diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch index 63ea66e..1decc51 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch @@ -5,7 +5,7 @@ } // Paper end - Call BlockDispenseEvent - level.captureTreeGeneration = true; // CraftBukkit -+ level.captureTreeGenerationThreadLocal.set(true); // CraftBukkit // ShreddedPaper - use thread local ++ level.captureTreeGeneration.set(true); // CraftBukkit // ShreddedPaper - thread local if (!BoneMealItem.growCrop(item, level, blockPos) && !BoneMealItem.growWaterPlant(item, level, blockPos, null)) { this.setSuccess(false); } else if (!level.isClientSide()) { @@ -16,15 +16,15 @@ - if (!level.capturedBlockStates.isEmpty()) { - org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType; - net.minecraft.world.level.block.SaplingBlock.treeType = null; -+ level.captureTreeGenerationThreadLocal.set(false); // ShreddedPaper - use thread local -+ if (!level.capturedBlockStatesThreadLocal.get().isEmpty()) { // ShreddedPaper - use thread local ++ level.captureTreeGeneration.set(false); // ShreddedPaper - thread local ++ if (!level.capturedBlockStates.get().isEmpty()) { // ShreddedPaper - thread local + org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeTypeThreadLocal.get(); // ShreddedPaper - use thread local + net.minecraft.world.level.block.SaplingBlock.treeTypeThreadLocal.remove(); // ShreddedPaper - use thread local org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(blockPos, level); - List states = new java.util.ArrayList<>(level.capturedBlockStates.values()); - level.capturedBlockStates.clear(); -+ List states = new java.util.ArrayList<>(level.capturedBlockStatesThreadLocal.get().values()); // ShreddedPaper - use thread local -+ level.capturedBlockStatesThreadLocal.get().clear(); // ShreddedPaper - use thread local ++ List states = new java.util.ArrayList<>(level.capturedBlockStates.get().values()); // ShreddedPaper - thread local ++ level.capturedBlockStates.get().clear(); // ShreddedPaper - thread local org.bukkit.event.world.StructureGrowEvent structureEvent = null; if (treeType != null) { structureEvent = new org.bukkit.event.world.StructureGrowEvent(location, treeType, false, null, states); diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/network/Connection.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/network/Connection.java.patch index 996eef0..6c827d5 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/network/Connection.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/network/Connection.java.patch @@ -10,6 +10,14 @@ import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelException; +@@ -21,6 +_,7 @@ + import io.netty.channel.local.LocalServerChannel; + import io.netty.handler.flow.FlowControlHandler; + import io.netty.handler.timeout.ReadTimeoutHandler; ++import io.netty.util.concurrent.AbstractEventExecutor; + import io.netty.handler.timeout.TimeoutException; + import java.net.InetSocketAddress; + import java.net.SocketAddress; @@ -45,8 +_,10 @@ import net.minecraft.network.protocol.login.LoginProtocols; import net.minecraft.network.protocol.status.ClientStatusPacketListener; @@ -38,6 +46,21 @@ if (!connected && !this.preparing) { return; } +@@ -448,6 +_,14 @@ + if (this.channel.eventLoop().inEventLoop()) { + this.doSendPacket(packet, sendListener, flush); + } else { ++ // ShreddedPaper start - Use lazyExecute if we aren't flushing ++ if (ShreddedPaperConfiguration.get().optimizations.useLazyExecuteWhenNotFlushing && !flush) { ++ ((AbstractEventExecutor) this.channel.eventLoop()).lazyExecute(() -> { ++ this.doSendPacket(packet, sendListener, flush); ++ }); ++ return; ++ } ++ // ShreddedPaper end - Use lazyExecute if we aren't flushing + this.channel.eventLoop().execute(() -> this.doSendPacket(packet, sendListener, flush)); + } + } @@ -500,7 +_,7 @@ } diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/MinecraftServer.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/MinecraftServer.java.patch index ebc980b..32ec04e 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/MinecraftServer.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/MinecraftServer.java.patch @@ -58,6 +58,15 @@ } @Override +@@ -895,6 +_,8 @@ + + // CraftBukkit start + public void prepareLevel(ServerLevel serverLevel) { ++ if (!TickThread.isTickThread()) throw new IllegalStateException("Cannot load worlds async!!"); // ShreddedPaper ++ if (ShreddedPaperTickThread.isShreddedPaperTickThread()) throw new IllegalStateException("Must load worlds from the global scheduler!"); // ShreddedPaper + this.forceTicks = true; + // CraftBukkit end + ChunkLoadCounter chunkLoadCounter = new ChunkLoadCounter(); @@ -1020,6 +_,7 @@ LOGGER.info("Stopping server"); diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/ClearInventoryCommands.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/ClearInventoryCommands.java.patch index c802744..1fa8f31 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/ClearInventoryCommands.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/ClearInventoryCommands.java.patch @@ -26,7 +26,7 @@ + serverPlayer.inventoryMenu.slotsChanged(serverPlayer.getInventory()); + future.complete(j); + }); -+ i += future.getNow(1); // ShreddedPaper - default to 1 if player in on a different thread ++ i += future.getNow(1); // ShreddedPaper - default to 1 if player is on a different thread + // ShreddedPaper end - run on correct thread } diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/GiveCommand.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/GiveCommand.java.patch index 74ab6e7..9e6a799 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/GiveCommand.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/GiveCommand.java.patch @@ -1,11 +1,10 @@ --- a/net/minecraft/server/commands/GiveCommand.java +++ b/net/minecraft/server/commands/GiveCommand.java -@@ -4,6 +_,8 @@ +@@ -4,6 +_,7 @@ import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.exceptions.CommandSyntaxException; import java.util.Collection; -+ -+import io.multipaper.shreddedpaper.ShreddedPaper; ++import io.multipaper.shreddedpaper.ShreddedPaper; // ShreddedPaper import net.minecraft.commands.CommandBuildContext; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; @@ -16,7 +15,7 @@ + ShreddedPaper.ensureSync(serverPlayer, () -> { // ShreddedPaper - run on player's thread boolean flag = serverPlayer.getInventory().add(itemStack1); - if (org.purpurmc.purpur.PurpurConfig.disableGiveCommandDrops) continue; // Purpur - add config option for toggling give command dropping -+ if (org.purpurmc.purpur.PurpurConfig.disableGiveCommandDrops) return; // Purpur - add config option for toggling give command dropping // ShreddedPaper - run on player's thread ++ if (org.purpurmc.purpur.PurpurConfig.disableGiveCommandDrops) return; // Purpur - add config option for toggling give command dropping // ShreddedPaper if (flag && itemStack1.isEmpty()) { ItemEntity itemEntity = serverPlayer.drop(itemStack, false, false, false, null); // Paper - do not fire PlayerDropItemEvent for /give command if (itemEntity != null) { diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/KillCommand.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/KillCommand.java.patch index bb02260..568e864 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/KillCommand.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/KillCommand.java.patch @@ -1,20 +1,26 @@ --- a/net/minecraft/server/commands/KillCommand.java +++ b/net/minecraft/server/commands/KillCommand.java -@@ -3,6 +_,8 @@ +@@ -3,10 +_,12 @@ import com.google.common.collect.ImmutableList; import com.mojang.brigadier.CommandDispatcher; import java.util.Collection; -+ -+import io.multipaper.shreddedpaper.ShreddedPaper; ++import io.multipaper.shreddedpaper.ShreddedPaper; // ShreddedPaper import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.commands.arguments.EntityArgument; -@@ -24,7 +_,7 @@ + import net.minecraft.network.chat.Component; ++import net.minecraft.server.level.ServerLevel; // ShreddedPaper + import net.minecraft.world.entity.Entity; + + public class KillCommand { +@@ -23,8 +_,9 @@ + } private static int kill(CommandSourceStack source, Collection targets) { ++ ServerLevel level = source.getLevel(); // ShreddedPaper for (Entity entity : targets) { - entity.kill(source.getLevel()); -+ ShreddedPaper.ensureSync(entity, () -> entity.kill(source.getLevel())); // ShreddedPaper - run on entity's thread ++ ShreddedPaper.ensureSync(entity, e -> e.kill(level)); // ShreddedPaper - run on right thread } if (targets.size() == 1) { diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/TeleportCommand.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/TeleportCommand.java.patch index c37dc38..f6ca075 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/TeleportCommand.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/TeleportCommand.java.patch @@ -21,7 +21,7 @@ float f1 = relatives.contains(Relative.X_ROT) ? xRot - target.getXRot() : xRot; float f2 = Mth.wrapDegrees(f); float f3 = Mth.wrapDegrees(f1); -+ ShreddedPaper.ensureSync(target, level, new ChunkPos(new BlockPos((int) x, (int) y, (int) z)), () -> { // ShreddedPaper - run on correct thread ++ ShreddedPaper.ensureSync(target, level, new ChunkPos(new BlockPos((int) d, (int) d1, (int) d2)), () -> { // ShreddedPaper - run on correct thread if (target.teleportTo(level, d, d1, d2, relatives, f2, f3, true, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND)) { // Paper - teleport cause if (lookAt != null) { lookAt.perform(source, target); diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ChunkHolder.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ChunkHolder.java.patch index ceff324..ae49802 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ChunkHolder.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ChunkHolder.java.patch @@ -23,6 +23,51 @@ public static final ChunkResult UNLOADED_LEVEL_CHUNK = ChunkResult.error("Unloaded level chunk"); private static final CompletableFuture> UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.completedFuture(UNLOADED_LEVEL_CHUNK); private final LevelHeightAccessor levelHeightAccessor; +@@ -65,16 +_,20 @@ + + @Override + public final void moonrise$addReceivedChunk(final ServerPlayer player) { ++ synchronized (this.playersSentChunkTo) { // ShreddedPaper - synchronize + if (!this.playersSentChunkTo.add(player)) { + throw new IllegalStateException("Already sent chunk " + this.pos + " in world '" + ca.spottedleaf.moonrise.common.util.WorldUtil.getWorldName(this.getChunkMap().level) + "' to player " + player); + } ++ } // ShreddedPaper - synchronize + } + + @Override + public final void moonrise$removeReceivedChunk(final ServerPlayer player) { ++ synchronized (this.playersSentChunkTo) { // ShreddedPaper - synchronize + if (!this.playersSentChunkTo.remove(player)) { + throw new IllegalStateException("Already sent chunk " + this.pos + " in world '" + ca.spottedleaf.moonrise.common.util.WorldUtil.getWorldName(this.getChunkMap().level) + "' to player " + player); + } ++ } // ShreddedPaper - synchronize + } + + @Override +@@ -84,12 +_,15 @@ + + @Override + public final boolean moonrise$hasChunkBeenSent(final ServerPlayer to) { ++ synchronized (this.playersSentChunkTo) { // ShreddedPaper - synchronize + return this.playersSentChunkTo.contains(to); ++ } // ShreddedPaper - synchronize + } + + @Override + public final List moonrise$getPlayers(final boolean onlyOnWatchDistanceEdge) { + final List ret = new java.util.ArrayList<>(); ++ synchronized (this.playersSentChunkTo) { // ShreddedPaper - synchronize + final ServerPlayer[] raw = this.playersSentChunkTo.getRawDataUnchecked(); + for (int i = 0, len = this.playersSentChunkTo.size(); i < len; ++i) { + final ServerPlayer player = raw[i]; +@@ -98,6 +_,7 @@ + } + ret.add(player); + } ++ } // ShreddedPaper - synchronize + + return ret; + } @@ -221,6 +_,10 @@ } diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ServerLevel.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ServerLevel.java.patch index 3e160d1..8971537 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ServerLevel.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ServerLevel.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -1,10 +_,18 @@ +@@ -1,10 +_,16 @@ package net.minecraft.server.level; +import ca.spottedleaf.moonrise.common.util.TickThread; @@ -10,12 +10,10 @@ import com.mojang.datafixers.util.Pair; import com.mojang.logging.LogUtils; +import io.multipaper.shreddedpaper.ShreddedPaper; -+import io.multipaper.shreddedpaper.config.ShreddedPaperConfiguration; +import io.multipaper.shreddedpaper.region.LevelChunkRegion; +import io.multipaper.shreddedpaper.region.LevelTicksRegionProxy; +import io.multipaper.shreddedpaper.region.RegionPos; +import io.multipaper.shreddedpaper.threading.ShreddedPaperRegionScheduler; -+import io.multipaper.shreddedpaper.util.NoOpServerWaypointManager; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.LongSet; @@ -73,6 +71,15 @@ final LevelDebugSynchronizers debugSynchronizers = new LevelDebugSynchronizers(this); // CraftBukkit start +@@ -229,7 +_,7 @@ + public final net.minecraft.server.level.progress.LevelLoadListener levelLoadListener; + public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent + public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent +- private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) ++ private final ThreadLocal wireHandler = ThreadLocal.withInitial(() -> new alternate.current.wire.WireHandler(this)); // Paper - optimize redstone (Alternate Current) // ShreddedPaper - ThreadLocal + public boolean hasRidableMoveEvent = false; // Purpur - Ridables + + @Override @@ -316,9 +_,9 @@ private long tickedBlocksOrFluids; private final ca.spottedleaf.moonrise.common.misc.NearbyPlayers nearbyPlayers = new ca.spottedleaf.moonrise.common.misc.NearbyPlayers((ServerLevel)(Object)this); @@ -261,15 +268,6 @@ // Paper end - chunk tick iteration public ServerLevel( -@@ -711,7 +_,7 @@ - this.sleepStatus = new SleepStatus(); - this.gameEventDispatcher = new GameEventDispatcher(this); - this.randomSequences = Objects.requireNonNullElseGet(randomSequences, () -> this.getDataStorage().computeIfAbsent(RandomSequences.TYPE)); -- this.waypointManager = new ServerWaypointManager(); -+ this.waypointManager = ShreddedPaperConfiguration.get().optimizations.disableLocatorBar ? new NoOpServerWaypointManager() : new ServerWaypointManager(); // ShreddedPaper - Optimise ServerWaypointManager - this.environmentAttributes = EnvironmentAttributeSystem.builder().addDefaultLayers(this).build(); - this.updateSkyBrightness(); - // Paper start - rewrite chunk system @@ -767,9 +_,9 @@ return this.environmentAttributes; } @@ -450,8 +448,8 @@ // Paper start - capture all item additions to the world - if (captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) { - captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity); -+ if (captureDropsThreadLocal.get() != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) { // ShreddedPaper - use thread local -+ captureDropsThreadLocal.get().add((net.minecraft.world.entity.item.ItemEntity) entity); // ShreddedPaper - use thread local ++ if (captureDrops.get() != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) { // ShreddedPaper - thread local ++ captureDrops.get().add((net.minecraft.world.entity.item.ItemEntity) entity); // ShreddedPaper - thread local return true; } // Paper end - capture all item additions to the world @@ -499,14 +497,14 @@ } // CraftBukkit end - if (captureBlockStates) { return; } // Paper - Cancel all physics during placement -+ if (captureBlockStatesThreadLocal.get()) { return; } // Paper - Cancel all physics during placement // ShreddedPaper - use thread local ++ if (captureBlockStates.get()) { return; } // Paper - Cancel all physics during placement // ShreddedPaper - thread local this.updateNeighborsAt(pos, block, ExperimentalRedstoneUtils.initialOrientation(this, null, null)); } @Override public void updateNeighborsAt(BlockPos pos, Block block, @Nullable Orientation orientation) { - if (captureBlockStates) { return; } // Paper - Cancel all physics during placement -+ if (captureBlockStatesThreadLocal.get()) { return; } // Paper - Cancel all physics during placement // ShreddedPaper - use thread local ++ if (captureBlockStates.get()) { return; } // Paper - Cancel all physics during placement // ShreddedPaper - thread local this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, block, null, orientation); } @@ -640,6 +638,15 @@ return this.moonrise$getEntityLookup(); // Paper - rewrite chunk system } +@@ -2816,7 +_,7 @@ + // Paper start - optimize redstone (Alternate Current) + @Override + public alternate.current.wire.WireHandler getWireHandler() { +- return wireHandler; ++ return wireHandler.get(); // ShreddedPaper - ThreadLocal + } + // Paper end - optimize redstone (Alternate Current) + @@ -2857,12 +_,12 @@ @Override public void onTickingStart(Entity entity) { diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch index 16c3252..d028e75 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch @@ -1,11 +1,36 @@ --- a/net/minecraft/server/level/ServerPlayerGameMode.java +++ b/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -1,5 +_,6 @@ + package net.minecraft.server.level; + ++import ca.spottedleaf.moonrise.common.util.TickThread; + import com.mojang.logging.LogUtils; + import java.util.List; + import java.util.Objects; +@@ -129,7 +_,7 @@ + // this.gameTicks = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit + this.gameTicks = (int) this.level.getLagCompensationTick(); // Paper - lag compensate eating + if (this.hasDelayedDestroy) { +- BlockState blockState = this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks ++ BlockState blockState = !TickThread.isTickThreadFor(this.level, this.delayedDestroyPos) ? null : this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks // ShreddedPaper - Don't allow digging into chunks outside our region + if (blockState == null || blockState.isAir()) { // Paper - Don't allow digging into unloaded chunks + this.hasDelayedDestroy = false; + } else { +@@ -141,7 +_,7 @@ + } + } else if (this.isDestroyingBlock) { + // Paper start - Don't allow digging into unloaded chunks; don't want to do same logic as above, return instead +- BlockState blockState = this.level.getBlockStateIfLoaded(this.destroyPos); ++ BlockState blockState = !TickThread.isTickThreadFor(this.level, this.destroyPos) ? null : this.level.getBlockStateIfLoaded(this.destroyPos); // ShreddedPaper - Don't allow digging into chunks outside our region + if (blockState == null) { + this.isDestroyingBlock = false; + return; @@ -384,7 +_,7 @@ } else { // CraftBukkit start org.bukkit.block.BlockState state = bblock.getState(); - this.level.captureDrops = new java.util.ArrayList<>(); -+ this.level.captureDropsThreadLocal.set(new java.util.ArrayList<>()); // ShreddedPaper - use thread local ++ this.level.captureDrops.set(new java.util.ArrayList<>()); // ShreddedPaper - thread local // CraftBukkit end BlockState blockState1 = block.playerWillDestroy(this.level, pos, blockState, this.player); boolean flag = this.level.removeBlock(pos, false); @@ -15,8 +40,8 @@ // CraftBukkit start - java.util.List itemsToDrop = this.level.captureDrops; // Paper - capture all item additions to the world - this.level.captureDrops = null; // Paper - capture all item additions to the world; Remove this earlier so that we can actually drop stuff -+ java.util.List itemsToDrop = this.level.captureDropsThreadLocal.get(); // Paper - capture all item additions to the world // ShreddedPaper - use thread local -+ this.level.captureDropsThreadLocal.remove(); // Paper - capture all item additions to the world; Remove this earlier so that we can actually drop stuff // ShreddedPaper - use thread local ++ java.util.List itemsToDrop = this.level.captureDrops.get(); // Paper - capture all item additions to the world // ShreddedPaper - thread local ++ this.level.captureDrops.set(null); // Paper - capture all item additions to the world; Remove this earlier so that we can actually drop stuff // ShreddedPaper - thread local if (event.isDropItems()) { org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, itemsToDrop); // Paper - capture all item additions to the world } diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/players/PlayerList.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/players/PlayerList.java.patch index 97d0a7f..5c640aa 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/players/PlayerList.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/players/PlayerList.java.patch @@ -20,6 +20,26 @@ import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; +@@ -227,6 +_,9 @@ + // Paper start - Fire PlayerJoinEvent when Player is actually ready; correctly register player BEFORE PlayerJoinEvent, so the entity is valid and doesn't require tick delay hacks + player.suppressTrackerForLogin = true; + this.sendLevelInfo(player, serverLevel); ++ // ShreddedPaper start - ensure chunk is fully loaded before adding player ++ serverLevel.getWorld().getChunkAtAsync(player.getBlockX() >> 4, player.getBlockZ() >> 4, true, true).join(); ++ // ShreddedPaper end - ensure chunk is fully loaded before adding player + serverLevel.addNewPlayer(player); + this.server.getCustomBossEvents().onPlayerConnect(player); // see commented out section below serverLevel.addPlayerJoin(player); + // Paper end - Fire PlayerJoinEvent when Player is actually ready +@@ -515,8 +_,7 @@ + // CraftBukkit start + // this.broadcastAll(new ClientboundPlayerInfoRemovePacket(List.of(player.getUUID()))); + ClientboundPlayerInfoRemovePacket packet = new ClientboundPlayerInfoRemovePacket(List.of(player.getUUID())); +- for (int i = 0; i < this.players.size(); i++) { +- ServerPlayer otherPlayer = this.players.get(i); ++ for (ServerPlayer otherPlayer : this.players) { // ShreddedPaper - Fix concurrent modification + + if (otherPlayer.getBukkitEntity().canSee(player.getBukkitEntity())) { + otherPlayer.connection.send(packet); @@ -595,12 +_,13 @@ } diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/Entity.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/Entity.java.patch index b0d4e34..0831439 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/Entity.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/Entity.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java -@@ -1,11 +_,14 @@ +@@ -1,11 +_,13 @@ package net.minecraft.world.entity; +import ca.spottedleaf.moonrise.common.util.TickThread; @@ -11,7 +11,6 @@ import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; +import io.multipaper.shreddedpaper.ShreddedPaper; -+import io.multipaper.shreddedpaper.region.RegionPos; import it.unimi.dsi.fastutil.floats.FloatArraySet; import it.unimi.dsi.fastutil.floats.FloatArrays; import it.unimi.dsi.fastutil.floats.FloatSet; @@ -40,14 +39,6 @@ public float maxUpStep; // Purpur - Add option to set armorstand step height public boolean noPhysics; public final RandomSource random; // Paper - Share random for entities to make them more random // Add toggle for RNG manipulation -@@ -376,6 +_,7 @@ - public boolean isTemporarilyActive; - public long activatedImmunityTick = Integer.MIN_VALUE; - public @Nullable Boolean immuneToFire = null; // Purpur - Fire immune API -+ public StringBuilder SHREDDEDPAPER_DEBUG = new StringBuilder(); // ShreddedPaper - TODO REMOVE - - public void inactiveTick() { - } @@ -708,7 +_,7 @@ public void resendPossiblyDesyncedEntityData(net.minecraft.server.level.ServerPlayer player) { if (player.getBukkitEntity().canSee(this.getBukkitEntity())) { @@ -72,14 +63,16 @@ this.setPosRaw(x, y, z, true); // Paper - Block invalid positions and bounding box; force update // this.setBoundingBox(this.makeBoundingBox()); // Paper - Block invalid positions and bounding box; move into setPosRaw } -@@ -1147,7 +_,9 @@ +@@ -1147,7 +_,11 @@ public void move(MoverType type, Vec3 movement) { final Vec3 originalMovement = movement; // Paper - Expose pre-collision velocity // Paper start - detailed watchdog information - ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread("Cannot move an entity off-main"); + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot move an entity off-main"); // ShreddedPaper -+ BlockPos newPos = new BlockPos((int) (this.getX() + movement.x), (int) (this.getY() + movement.y), (int) (this.getZ() + movement.z)); // ShreddedPaper - check destination region -+ TickThread.ensureTickThread((ServerLevel) this.level, newPos, "Cannot move an entity into off-main"); // ShreddedPaper - check destination region ++ // ShreddedPaper start - check destination region ++ net.minecraft.core.BlockPos newPos = new net.minecraft.core.BlockPos((int) (this.getX() + movement.x), (int) (this.getY() + movement.y), (int) (this.getZ() + movement.z)); ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((net.minecraft.server.level.ServerLevel) this.level, newPos, "Cannot move an entity into off-main"); ++ // ShreddedPaper end - check destination region synchronized (this.posLock) { this.moveStartX = this.getX(); this.moveStartY = this.getY(); @@ -92,6 +85,21 @@ BlockPos blockPos = this.mainSupportingBlockPos.get(); if (!(yOffset > 1.0E-5F)) { return blockPos; +@@ -2376,7 +_,13 @@ + } + delta = event.getKnockback(); + } +- this.setDeltaMovement(this.getDeltaMovement().add(delta.getX(), delta.getY(), delta.getZ())); ++ // ShreddedPaper start - limit push velocity ++ Vec3 newDelta = this.getDeltaMovement().add(delta.getX(), delta.getY(), delta.getZ()); ++ if (newDelta.lengthSqr() > io.multipaper.shreddedpaper.region.RegionPos.MAX_DISTANCE_SQR) { ++ newDelta = newDelta.normalize().scale(io.multipaper.shreddedpaper.region.RegionPos.REGION_SIZE * 16); ++ } ++ this.setDeltaMovement(newDelta); ++ // ShreddedPaper end - limit push velocity + // Paper end - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent + this.needsSync = true; + } @@ -3546,7 +_,9 @@ } } @@ -135,16 +143,15 @@ // Paper start - gateway-specific teleport event final org.bukkit.event.entity.EntityTeleportEvent teleEvent; if (this.portalProcess != null && this.portalProcess.isSamePortal(((net.minecraft.world.level.block.EndGatewayBlock) Blocks.END_GATEWAY)) && this.level.getBlockEntity(this.portalProcess.getEntryPosition()) instanceof net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity theEndGatewayBlockEntity) { -@@ -4996,6 +_,12 @@ +@@ -4995,6 +_,11 @@ + } public void setDeltaMovement(Vec3 deltaMovement) { ++ // ShreddedPaper start - why is something setting the entity velocity to larger than one region... ++ if (deltaMovement.horizontalDistanceSqr() > io.multipaper.shreddedpaper.region.RegionPos.MAX_DISTANCE_SQR + 1 && deltaMovement.horizontalDistanceSqr() > this.deltaMovement.horizontalDistanceSqr()) { ++ LOGGER.warn("Velocity is being set larger than the ShreddedPaper region size: {} for entity {}", deltaMovement, this, new Exception("Velocity larger than region size")); ++ } ++ // ShreddedPaper end - why is something setting the entity velocity to larger than one region... if (deltaMovement.isFinite()) { -+ // ShreddedPaper start - why is something setting the entity velocity to larger than one region... -+ if (deltaMovement.horizontalDistanceSqr() > RegionPos.MAX_DISTANCE_SQR + 1 && deltaMovement.horizontalDistanceSqr() > this.deltaMovement.horizontalDistanceSqr()) { -+ LOGGER.warn("Velocity is being set larger than the ShreddedPaper region size: {} for entity {}", deltaMovement, this, new Exception("Velocity larger than region size, limiting velocity to region size")); -+ deltaMovement = deltaMovement.normalize().scale(RegionPos.MAX_DISTANCE_SQR); -+ } -+ // ShreddedPaper end - why is something setting the entity velocity to larger than one region... synchronized (this.posLock) { // Paper - detailed watchdog information this.deltaMovement = deltaMovement; - } // Paper - detailed watchdog information diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch index 1fd76dc..ac66dee 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch @@ -7,17 +7,16 @@ import it.unimi.dsi.fastutil.doubles.DoubleDoubleImmutablePair; import java.util.List; import java.util.OptionalInt; -@@ -137,6 +_,13 @@ - } +@@ -156,6 +_,12 @@ + handHoldingItemAngle = Vec3.ZERO; + } - if (this.attachedToEntity != null) { + // ShreddedPaper start - remove firework rocket if entity teleported away -+ if (!TickThread.isTickThreadFor(this.attachedToEntity)) { ++ if (!TickThread.isTickThreadFor((ServerLevel) this.level(), new Vec3(this.attachedToEntity.getX() + handHoldingItemAngle.x, this.attachedToEntity.getY() + handHoldingItemAngle.y, this.attachedToEntity.getZ() + handHoldingItemAngle.z))) { + this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); // ShreddedPaper - add Bukkit remove cause + return; + } + // ShreddedPaper end - remove firework rocket if entity teleported away -+ - Vec3 handHoldingItemAngle; - if (this.attachedToEntity.isFallFlying()) { - Vec3 lookAngle = this.attachedToEntity.getLookAngle(); + this.setPos( + this.attachedToEntity.getX() + handHoldingItemAngle.x, + this.attachedToEntity.getY() + handHoldingItemAngle.y, diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/item/ItemStack.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/item/ItemStack.java.patch index c3a6109..38e26ae 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/item/ItemStack.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/item/ItemStack.java.patch @@ -5,21 +5,21 @@ if (!(item instanceof BucketItem/* || item instanceof SolidBucketItem*/)) { // if not bucket // Paper - Fix cancelled powdered snow bucket placement - serverLevel.captureBlockStates = true; -+ serverLevel.captureBlockStatesThreadLocal.set(true); // ShreddedPaper - use thread local ++ serverLevel.captureBlockStates.set(true); // ShreddedPaper - thread local // special case bonemeal if (item == Items.BONE_MEAL) { - serverLevel.captureTreeGeneration = true; -+ serverLevel.captureTreeGenerationThreadLocal.set(false); // ShreddedPaper - use thread local ++ serverLevel.captureTreeGeneration.set(true); // ShreddedPaper - thread local } } InteractionResult interactionResult; try { -+ serverLevel.capturedTileEntitiesThreadLocal.get().clear(); // ShreddedPaper - use thread local - clear beforehand -+ serverLevel.capturedBlockStatesThreadLocal.get().clear(); // ShreddedPaper - use thread local - clear beforehand ++ serverLevel.capturedTileEntities.get().clear(); // ShreddedPaper - clear beforehand ++ serverLevel.capturedBlockStates.get().clear(); // ShreddedPaper - clear beforehand interactionResult = item.useOn(context); } finally { - serverLevel.captureBlockStates = false; -+ serverLevel.captureBlockStatesThreadLocal.set(false); // ShreddedPaper - use thread local ++ serverLevel.captureBlockStates.set(false); // ShreddedPaper - thread local } DataComponentPatch newPatch = this.components.asPatch(); int newCount = this.getCount(); @@ -27,8 +27,8 @@ this.restorePatch(previousPatch); - if (interactionResult.consumesAction() && serverLevel.captureTreeGeneration && !serverLevel.capturedBlockStates.isEmpty()) { - serverLevel.captureTreeGeneration = false; -+ if (interactionResult.consumesAction() && serverLevel.captureTreeGenerationThreadLocal.get() && !serverLevel.capturedBlockStatesThreadLocal.get().isEmpty()) { // ShreddedPaper - use thread local -+ serverLevel.captureTreeGenerationThreadLocal.set(false); // ShreddedPaper - use thread local ++ if (interactionResult.consumesAction() && serverLevel.captureTreeGeneration.get() && !serverLevel.capturedBlockStates.get().isEmpty()) { // ShreddedPaper - thread local ++ serverLevel.captureTreeGeneration.set(false); // ShreddedPaper - thread local org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(clickedPos, serverLevel); - org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType; - net.minecraft.world.level.block.SaplingBlock.treeType = null; @@ -36,8 +36,8 @@ - serverLevel.capturedBlockStates.clear(); + org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeTypeThreadLocal.get(); // ShreddedPaper - use thread local + net.minecraft.world.level.block.SaplingBlock.treeTypeThreadLocal.remove(); // ShreddedPaper - use thread local -+ List blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStatesThreadLocal.get().values()); // ShreddedPaper - use thread local -+ serverLevel.capturedBlockStatesThreadLocal.get().clear(); // ShreddedPaper - use thread local ++ List blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.get().values()); // ShreddedPaper - thread local ++ serverLevel.capturedBlockStates.get().clear(); // ShreddedPaper - thread local org.bukkit.event.world.StructureGrowEvent structureEvent = null; if (treeType != null) { boolean isBonemeal = this.getItem() == Items.BONE_MEAL; @@ -50,14 +50,14 @@ return interactionResult; } - serverLevel.captureTreeGeneration = false; -+ serverLevel.captureTreeGenerationThreadLocal.set(false); // ShreddedPaper - use thread local ++ serverLevel.captureTreeGeneration.set(false); // ShreddedPaper - thread local if (player != null && interactionResult instanceof InteractionResult.Success success && success.wasItemInteraction()) { InteractionHand hand = context.getHand(); org.bukkit.event.block.BlockPlaceEvent placeEvent = null; - List blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.values()); - serverLevel.capturedBlockStates.clear(); -+ List blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStatesThreadLocal.get().values()); // ShreddedPaper - use thread local -+ serverLevel.capturedBlockStatesThreadLocal.get().clear(); // ShreddedPaper - use thread local ++ List blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.get().values()); // ShreddedPaper - thread local ++ serverLevel.capturedBlockStates.get().clear(); // ShreddedPaper - thread local if (blocks.size() > 1) { placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(serverLevel, player, hand, blocks, clickedPos); } else if (blocks.size() == 1 && item != Items.POWDER_SNOW_BUCKET) { // Paper - Fix cancelled powdered snow bucket placement @@ -66,7 +66,7 @@ // PAIL: Remove this when MC-99075 fixed player.containerMenu.forceHeldSlot(hand); - serverLevel.capturedTileEntities.clear(); // Paper - Allow chests to be placed with NBT data; clear out block entities as chests and such will pop loot -+ serverLevel.capturedTileEntitiesThreadLocal.get().clear(); // Paper - Allow chests to be placed with NBT data; clear out block entities as chests and such will pop loot // ShreddedPaper - use thread local ++ serverLevel.capturedTileEntities.get().clear(); // Paper - Allow chests to be placed with NBT data; clear out block entities as chests and such will pop loot // ShreddedPaper - thread local // revert back all captured blocks for (org.bukkit.block.BlockState blockstate : blocks) { ((org.bukkit.craftbukkit.block.CraftBlockState) blockstate).revertPlace(); @@ -83,7 +83,7 @@ } - for (java.util.Map.Entry e : serverLevel.capturedTileEntities.entrySet()) { -+ for (java.util.Map.Entry e : serverLevel.capturedTileEntitiesThreadLocal.get().entrySet()) { // ShreddedPaper - use thread local ++ for (java.util.Map.Entry e : serverLevel.capturedTileEntities.get().entrySet()) { // ShreddedPaper - thread local serverLevel.setBlockEntity(e.getValue()); } @@ -113,8 +113,8 @@ } - serverLevel.capturedTileEntities.clear(); - serverLevel.capturedBlockStates.clear(); -+ serverLevel.capturedTileEntitiesThreadLocal.get().clear(); // ShreddedPaper - use thread local -+ serverLevel.capturedBlockStatesThreadLocal.get().clear(); // ShreddedPaper - use thread local ++ serverLevel.capturedTileEntities.get().clear(); // ShreddedPaper - thread local ++ serverLevel.capturedBlockStates.get().clear(); // ShreddedPaper - thread local // CraftBukkit end return interactionResult; diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch index eafdf81..24e77af 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch @@ -1,13 +1,5 @@ --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java -@@ -3,6 +_,7 @@ - import com.google.common.collect.Lists; - import com.mojang.serialization.Codec; - import java.io.IOException; -+import java.util.ArrayList; - import java.util.Collection; - import java.util.Iterator; - import java.util.List; @@ -10,6 +_,10 @@ import java.util.function.Consumer; import java.util.function.Predicate; @@ -40,7 +32,7 @@ private boolean tickingBlockEntities; public final Thread thread; private final boolean isDebug; -@@ -144,12 +_,11 @@ +@@ -144,12 +_,13 @@ public net.kyori.adventure.util.TriState pvpMode = net.kyori.adventure.util.TriState.NOT_SET; public org.bukkit.generator.@Nullable ChunkGenerator generator; @@ -50,11 +42,13 @@ - public Map capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper - Retain block place order when capturing blockstates - @Nullable - public List captureDrops; -+ public ThreadLocal captureBlockStatesThreadLocal = ThreadLocal.withInitial(() -> false); // ShreddedPaper - use thread local -+ public ThreadLocal captureTreeGenerationThreadLocal = ThreadLocal.withInitial(() -> false); // ShreddedPaper - use thread local -+ public ThreadLocal> capturedBlockStatesThreadLocal = ThreadLocal.withInitial(() -> new java.util.LinkedHashMap<>()); // Paper // ShreddedPaper - use thread local -+ public ThreadLocal> capturedTileEntitiesThreadLocal = ThreadLocal.withInitial(() -> new java.util.LinkedHashMap<>()); // Paper - Retain block place order when capturing blockstates // ShreddedPaper - use thread local -+ public ThreadLocal<@Nullable List> captureDropsThreadLocal = new ThreadLocal<>(); // ShreddedPaper - use thread local ++ // ShreddedPaper start - threading ++ public ThreadLocal captureBlockStates = ThreadLocal.withInitial(() -> false); ++ public ThreadLocal captureTreeGeneration = ThreadLocal.withInitial(() -> false); ++ public ThreadLocal> capturedBlockStates = ThreadLocal.withInitial(java.util.LinkedHashMap::new); // Paper ++ public ThreadLocal> capturedTileEntities = ThreadLocal.withInitial(java.util.LinkedHashMap::new); // Paper - Retain block place order when capturing blockstates ++ public ThreadLocal<@Nullable List> captureDrops = new ThreadLocal<>(); ++ // ShreddedPaper end - threading public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>(); // Paper start - EAR 2 public int wakeupInactiveRemainingAnimals; @@ -67,15 +61,18 @@ public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot // Paper start - add paper world config private final io.papermc.paper.configuration.WorldConfiguration paperConfig; -@@ -168,7 +_,7 @@ +@@ -168,9 +_,9 @@ public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files public static @Nullable BlockPos lastPhysicsProblem; // Spigot - private int tileTickPosition; + // private int tileTickPosition; // ShreddedPaper - removed tileTickPosition public final Map explosionDensityCache = new java.util.HashMap<>(); // Paper - Optimize explosions - public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here +- public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here ++ // public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here // ShreddedPaper - move to LevelChunkRegion + // Purpur start - Add adjustable breeding cooldown to config + private com.google.common.cache.Cache playerBreedingCooldowns; @@ -908,7 +_,7 @@ this.thread = Thread.currentThread(); this.biomeManager = new BiomeManager(this, biomeZoomSeed); @@ -91,8 +88,8 @@ // CraftBukkit start - tree generation - if (this.captureTreeGeneration) { - CraftBlockState previous = this.capturedBlockStates.get(pos); -+ if (this.captureTreeGenerationThreadLocal.get()) { // ShreddedPaper - use thread local -+ CraftBlockState previous = this.capturedBlockStatesThreadLocal.get().get(pos); // ShreddedPaper - use thread local ++ if (this.captureTreeGeneration.get()) { // ShreddedPaper - thread local ++ CraftBlockState previous = this.capturedBlockStates.get().get(pos); // ShreddedPaper - thread local if (previous != null) { return previous.getHandle(); } @@ -101,17 +98,17 @@ public boolean setBlock(BlockPos pos, BlockState state, @Block.UpdateFlags int flags, int recursionLeft) { // CraftBukkit start - tree generation - if (this.captureTreeGeneration) { -+ if (this.captureTreeGenerationThreadLocal.get()) { // ShreddedPaper - use thread local ++ if (this.captureTreeGeneration.get()) { // ShreddedPaper - thread local // Paper start - Protect Bedrock and End Portal/Frames from being destroyed BlockState type = getBlockState(pos); if (!type.isDestroyable()) return false; // Paper end - Protect Bedrock and End Portal/Frames from being destroyed - CraftBlockState blockstate = this.capturedBlockStates.get(pos); -+ CraftBlockState blockstate = this.capturedBlockStatesThreadLocal.get().get(pos); // ShreddedPaper - use thread local ++ CraftBlockState blockstate = this.capturedBlockStates.get().get(pos); // ShreddedPaper - thread local if (blockstate == null) { blockstate = org.bukkit.craftbukkit.block.CapturedBlockState.getTreeBlockState(this, pos, flags); - this.capturedBlockStates.put(pos.immutable(), blockstate); -+ this.capturedBlockStatesThreadLocal.get().put(pos.immutable(), blockstate); // ShreddedPaper - use thread local ++ this.capturedBlockStates.get().put(pos.immutable(), blockstate); // ShreddedPaper - thread local } blockstate.setData(state); blockstate.setFlags(flags); @@ -120,17 +117,17 @@ // CraftBukkit start - capture blockstates boolean captured = false; - if (this.captureBlockStates) { -+ if (this.captureBlockStatesThreadLocal.get()) { // ShreddedPaper - use thread local ++ if (this.captureBlockStates.get()) { // ShreddedPaper - thread local final CraftBlockState snapshot; - if (!this.capturedBlockStates.containsKey(pos)) { -+ if (!this.capturedBlockStatesThreadLocal.get().containsKey(pos)) { // ShreddedPaper - use thread local ++ if (!this.capturedBlockStates.get().containsKey(pos)) { // ShreddedPaper - thread local snapshot = (CraftBlockState) org.bukkit.craftbukkit.block.CraftBlock.at(this, pos).getState(); // Paper - use CB getState to get a suitable snapshot - this.capturedBlockStates.put(pos.immutable(), snapshot); -+ this.capturedBlockStatesThreadLocal.get().put(pos.immutable(), snapshot); // ShreddedPaper - use thread local ++ this.capturedBlockStates.get().put(pos.immutable(), snapshot); // ShreddedPaper - thread local captured = true; } else { - snapshot = this.capturedBlockStates.get(pos); -+ snapshot = this.capturedBlockStatesThreadLocal.get().get(pos); // ShreddedPaper - use thread local ++ snapshot = this.capturedBlockStates.get().get(pos); // ShreddedPaper - thread local } snapshot.setFlags(flags); // Paper - always set the flag of the most recent call to mitigate issues with multiple update at the same pos with different flags } @@ -140,8 +137,8 @@ // CraftBukkit start - remove blockstate if failed (or the same) - if (this.captureBlockStates && captured) { - this.capturedBlockStates.remove(pos); -+ if (this.captureBlockStatesThreadLocal.get() && captured) { // ShreddedPaper - use thread local -+ this.capturedBlockStatesThreadLocal.get().remove(pos); // ShreddedPaper - use thread local ++ if (this.captureBlockStates.get() && captured) { // ShreddedPaper - thread local ++ this.capturedBlockStates.get().remove(pos); // ShreddedPaper - thread local } // CraftBukkit end return false; @@ -150,7 +147,7 @@ // CraftBukkit start - if (!this.captureBlockStates) { // Don't notify clients or update physics while capturing blockstates -+ if (!this.captureBlockStatesThreadLocal.get()) { // Don't notify clients or update physics while capturing blockstates // ShreddedPaper - use thread local ++ if (!this.captureBlockStates.get()) { // Don't notify clients or update physics while capturing blockstates // ShreddedPaper - thread local // Modularize client and physic updates // Spigot start try { @@ -160,8 +157,8 @@ // CraftBukkit start - tree generation - if (this.captureTreeGeneration) { - CraftBlockState previous = this.capturedBlockStates.get(pos); // Paper -+ if (this.captureTreeGenerationThreadLocal.get()) { // ShreddedPaper - use thread local -+ CraftBlockState previous = this.capturedBlockStatesThreadLocal.get().get(pos); // Paper // ShreddedPaper - use thread local ++ if (this.captureTreeGeneration.get()) { // ShreddedPaper - thread local ++ CraftBlockState previous = this.capturedBlockStates.get().get(pos); // Paper // ShreddedPaper - thread local if (previous != null) { return previous.getHandle(); } @@ -230,7 +227,7 @@ // Paper start - Perf: Optimize capturedTileEntities lookup net.minecraft.world.level.block.entity.BlockEntity blockEntity; - if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(pos)) != null) { -+ if (!this.capturedTileEntitiesThreadLocal.get().isEmpty() && (blockEntity = this.capturedTileEntitiesThreadLocal.get().get(pos)) != null) { // ShreddedPaper - use thread local ++ if (!this.capturedTileEntities.get().isEmpty() && (blockEntity = this.capturedTileEntities.get().get(pos)) != null) { // ShreddedPaper - thread local return blockEntity; } // Paper end - Perf: Optimize capturedTileEntities lookup @@ -240,8 +237,8 @@ // CraftBukkit start - if (this.captureBlockStates) { - this.capturedTileEntities.put(blockPos.immutable(), blockEntity); -+ if (this.captureBlockStatesThreadLocal.get()) { // ShreddedPaper - use thread local -+ this.capturedTileEntitiesThreadLocal.get().put(blockPos.immutable(), blockEntity); // ShreddedPaper - use thread local ++ if (this.captureBlockStates.get()) { // ShreddedPaper - thread local ++ this.capturedTileEntities.get().put(blockPos.immutable(), blockEntity); // ShreddedPaper - thread local return; } // CraftBukkit end diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch index 98d73f3..1df8426 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/NaturalSpawner.java.patch @@ -1,5 +1,13 @@ --- a/net/minecraft/world/level/NaturalSpawner.java +++ b/net/minecraft/world/level/NaturalSpawner.java +@@ -81,6 +_,7 @@ + Object2IntOpenHashMap map = new Object2IntOpenHashMap<>(); + + for (Entity entity : entities) { ++ if (entity == null) continue; // ShreddedPaper - concurrent modification + if (!(entity instanceof Mob mob && (mob.isPersistenceRequired() || mob.requiresCustomPersistence()))) { + MobCategory category = entity.getType().getCategory(); + if (category != MobCategory.MISC) { @@ -177,7 +_,9 @@ if (inRange != null) { final net.minecraft.server.level.ServerPlayer[] backingSet = inRange.getRawDataUnchecked(); diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/BedBlock.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/BedBlock.java.patch index ec4186a..3d83d6e 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/BedBlock.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/BedBlock.java.patch @@ -5,7 +5,7 @@ level.setBlock(blockPos, state.setValue(PART, BedPart.HEAD), Block.UPDATE_ALL); // CraftBukkit start - SPIGOT-7315: Don't updated if we capture block states - if (level.captureBlockStates) { -+ if (level.captureBlockStatesThreadLocal.get()) { // ShreddedPaper - use thread local ++ if (level.captureBlockStates.get()) { // ShreddedPaper - thread local return; } // CraftBukkit end diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/Block.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/Block.java.patch index ab6e008..81de94e 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/Block.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/Block.java.patch @@ -6,8 +6,8 @@ // CraftBukkit start - if (level.captureDrops != null) { - level.captureDrops.add(itemEntity); -+ if (level.captureDropsThreadLocal.get() != null) { // ShreddedPaper - use thread local -+ level.captureDropsThreadLocal.get().add(itemEntity); // ShreddedPaper - use thread local ++ if (level.captureDrops.get() != null) { // ShreddedPaper - thread local ++ level.captureDrops.get().add(itemEntity); // ShreddedPaper - thread local } else { level.addFreshEntity(itemEntity); } diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/SaplingBlock.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/SaplingBlock.java.patch index 55612d4..90b707c 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/SaplingBlock.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/SaplingBlock.java.patch @@ -22,25 +22,25 @@ } else { // CraftBukkit start - if (level.captureTreeGeneration) { -+ if (level.captureTreeGenerationThreadLocal.get()) { // ShreddedPaper - use thread local ++ if (level.captureTreeGeneration.get()) { // ShreddedPaper - thread local this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random); } else { - level.captureTreeGeneration = true; -+ level.captureTreeGenerationThreadLocal.set(true); // ShreddedPaper - use thread local ++ level.captureTreeGeneration.set(true); // ShreddedPaper - thread local this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random); - level.captureTreeGeneration = false; - if (!level.capturedBlockStates.isEmpty()) { - org.bukkit.TreeType treeType = SaplingBlock.treeType; - SaplingBlock.treeType = null; -+ level.captureTreeGenerationThreadLocal.set(false); // ShreddedPaper - use thread local -+ if (!level.capturedBlockStatesThreadLocal.get().isEmpty()) { // ShreddedPaper - use thread local ++ level.captureTreeGeneration.set(false); // ShreddedPaper - thread local ++ if (!level.capturedBlockStates.get().isEmpty()) { // ShreddedPaper - thread local + org.bukkit.TreeType treeType = SaplingBlock.treeTypeThreadLocal.get(); // ShreddedPaper - use thread local + SaplingBlock.treeTypeThreadLocal.remove(); // ShreddedPaper - use thread local org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level); - java.util.List blocks = new java.util.ArrayList<>(level.capturedBlockStates.values()); - level.capturedBlockStates.clear(); -+ java.util.List blocks = new java.util.ArrayList<>(level.capturedBlockStatesThreadLocal.get().values()); // ShreddedPaper - use thread local -+ level.capturedBlockStatesThreadLocal.get().clear(); // ShreddedPaper - use thread local ++ java.util.List blocks = new java.util.ArrayList<>(level.capturedBlockStates.get().values()); // ShreddedPaper - thread local ++ level.capturedBlockStates.get().clear(); // ShreddedPaper - thread local org.bukkit.event.world.StructureGrowEvent event = null; if (treeType != null) { event = new org.bukkit.event.world.StructureGrowEvent(location, treeType, false, null, blocks); diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/WitherSkullBlock.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/WitherSkullBlock.java.patch index 47efd26..6fee49f 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/WitherSkullBlock.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/WitherSkullBlock.java.patch @@ -5,7 +5,7 @@ public static void checkSpawn(Level level, BlockPos pos, SkullBlockEntity blockEntity) { - if (level.captureBlockStates) return; // CraftBukkit -+ if (level.captureBlockStatesThreadLocal.get()) return; // CraftBukkit // ShreddedPaper - use thread local ++ if (level.captureBlockStates.get()) return; // CraftBukkit // ShreddedPaper - thread local if (!level.isClientSide()) { BlockState blockState = blockEntity.getBlockState(); boolean flag = blockState.is(Blocks.WITHER_SKELETON_SKULL) || blockState.is(Blocks.WITHER_SKELETON_WALL_SKULL); diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch index 58ef12e..e16f64e 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/chunk/LevelChunk.java.patch @@ -40,7 +40,7 @@ return null; } else { - if (!this.level.isClientSide() && (flags & Block.UPDATE_SKIP_ON_PLACE) == 0 && (!this.level.captureBlockStates || block instanceof net.minecraft.world.level.block.BaseEntityBlock)) { // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled. -+ if (!this.level.isClientSide() && (flags & Block.UPDATE_SKIP_ON_PLACE) == 0 && (!this.level.captureBlockStatesThreadLocal.get() || block instanceof net.minecraft.world.level.block.BaseEntityBlock)) { // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled. // ShreddedPaper - use thread local ++ if (!this.level.isClientSide() && (flags & Block.UPDATE_SKIP_ON_PLACE) == 0 && (!this.level.captureBlockStates.get() || block instanceof net.minecraft.world.level.block.BaseEntityBlock)) { // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled. // ShreddedPaper - thread local state.onPlace(this.level, pos, blockState, flag1); } @@ -49,7 +49,7 @@ public @Nullable BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType) { // CraftBukkit start - BlockEntity blockEntity = this.level.capturedTileEntities.get(pos); -+ BlockEntity blockEntity = this.level.capturedTileEntitiesThreadLocal.get().get(pos); // ShreddedPaper - use thread local ++ BlockEntity blockEntity = this.level.capturedTileEntities.get().get(pos); // ShreddedPaper - thread local if (blockEntity == null) { blockEntity = this.blockEntities.get(pos); } diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch index d796b20..ec84ca3 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch @@ -16,15 +16,14 @@ this.level.getChunkSource().addTicketWithRadius(TicketType.DRAGON, new ChunkPos(0, 0), 9); boolean isArenaLoaded = this.isArenaLoaded(); if (this.needsStateScanning && isArenaLoaded) { -@@ -184,6 +_,13 @@ +@@ -184,6 +_,12 @@ this.ticksSinceCrystalsScanned = 0; } } + // ShreddedPaper start - run on end island thread + }; + while (!this.level.chunkScheduler.getRegionLocker().tryLockNow(RegionPos.forChunk(0, 0), r)) { -+ long startBlockingTime = System.nanoTime(); -+ this.level.chunkSource.mainThreadProcessor.managedBlock(() -> System.nanoTime() - startBlockingTime < 1_000_000); // Wait for 1ms ++ Thread.yield(); // ShreddedPaper - yield instead of managedBlock to avoid deadlock + } + // ShreddedPaper end - run on end island thread } else { diff --git a/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftWorld.java.patch b/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftWorld.java.patch index e683ce8..c188d7a 100644 --- a/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftWorld.java.patch +++ b/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftWorld.java.patch @@ -47,24 +47,28 @@ } ChunkAccess chunk = world.getChunkSource().getChunkAtImmediately(x, z); if (chunk != null) { -@@ -751,13 +_,15 @@ +@@ -751,13 +_,19 @@ @Override public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) { - this.world.captureTreeGeneration = true; - this.world.captureBlockStates = true; -+ this.world.captureTreeGenerationThreadLocal.set(true); // ShreddedPaper - use thread local -+ this.world.captureBlockStatesThreadLocal.set(true); // ShreddedPaper - use thread local -+ this.world.capturedTileEntitiesThreadLocal.get().clear(); // ShreddedPaper - use thread local - clear beforehand -+ this.world.capturedBlockStatesThreadLocal.get().clear(); // ShreddedPaper - use thread local - clear beforehand ++ // ShreddedPaper start - thread local ++ this.world.captureTreeGeneration.set(true); ++ this.world.captureBlockStates.set(true); ++ this.world.capturedTileEntities.get().clear(); ++ this.world.capturedBlockStates.get().clear(); ++ // ShreddedPaper end - thread local boolean grownTree = this.generateTree(loc, type); - this.world.captureBlockStates = false; - this.world.captureTreeGeneration = false; -+ this.world.captureBlockStatesThreadLocal.set(false); // ShreddedPaper - use thread local -+ this.world.captureTreeGenerationThreadLocal.set(false); // ShreddedPaper - use thread local ++ // ShreddedPaper start - thread local ++ this.world.captureBlockStates.set(false); ++ this.world.captureTreeGeneration.set(false); ++ // ShreddedPaper end - thread local if (grownTree) { // Copy block data to delegate - for (BlockState blockstate : this.world.capturedBlockStates.values()) { -+ for (BlockState blockstate : this.world.capturedBlockStatesThreadLocal.get().values()) { // ShreddedPaper - use thread local ++ for (BlockState blockstate : this.world.capturedBlockStates.get().values()) { // ShreddedPaper - thread local BlockPos position = ((CraftBlockState) blockstate).getPosition(); net.minecraft.world.level.block.state.BlockState oldBlock = this.world.getBlockState(position); int flags = ((CraftBlockState) blockstate).getFlags(); @@ -73,11 +77,11 @@ this.world.notifyAndUpdatePhysics(position, null, oldBlock, newBlock, newBlock, flags, net.minecraft.world.level.block.Block.UPDATE_LIMIT); } - this.world.capturedBlockStates.clear(); -+ this.world.capturedBlockStatesThreadLocal.get().clear(); // ShreddedPaper - use thread local ++ this.world.capturedBlockStates.get().clear(); // ShreddedPaper - thread local return true; } else { - this.world.capturedBlockStates.clear(); -+ this.world.capturedBlockStatesThreadLocal.get().clear(); // ShreddedPaper - use thread local ++ this.world.capturedBlockStates.get().clear(); // ShreddedPaper - thread local return false; } } diff --git a/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java.patch b/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java.patch index 22f6129..a318adf 100644 --- a/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java.patch +++ b/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java.patch @@ -5,21 +5,21 @@ // SPIGOT-6895: Call StructureGrowEvent and BlockFertilizeEvent - world.captureTreeGeneration = true; -+ world.captureTreeGenerationThreadLocal.set(true); // ShreddedPaper - use thread local ++ world.captureTreeGeneration.set(true); // ShreddedPaper - thread local InteractionResult result = BoneMealItem.applyBonemeal(context); - world.captureTreeGeneration = false; -+ world.captureTreeGenerationThreadLocal.set(false); // ShreddedPaper - use thread local ++ world.captureTreeGeneration.set(false); // ShreddedPaper - thread local - if (!world.capturedBlockStates.isEmpty()) { - TreeType treeType = SaplingBlock.treeType; - SaplingBlock.treeType = null; - List states = new ArrayList<>(world.capturedBlockStates.values()); - world.capturedBlockStates.clear(); -+ if (!world.capturedBlockStatesThreadLocal.get().isEmpty()) { // ShreddedPaper - use thread local ++ if (!world.capturedBlockStates.get().isEmpty()) { // ShreddedPaper - thread local + TreeType treeType = SaplingBlock.treeTypeThreadLocal.get(); // ShreddedPaper - use thread local + SaplingBlock.treeTypeThreadLocal.remove(); // ShreddedPaper - use thread local -+ List states = new ArrayList<>(world.capturedBlockStatesThreadLocal.get().values()); // ShreddedPaper - use thread local -+ world.capturedBlockStatesThreadLocal.get().clear(); // ShreddedPaper - use thread local ++ List states = new ArrayList<>(world.capturedBlockStates.get().values()); // ShreddedPaper - thread local ++ world.capturedBlockStates.get().clear(); // ShreddedPaper - thread local StructureGrowEvent structureEvent = null; if (treeType != null) { diff --git a/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java.patch b/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java.patch index 901a8b8..a962aa6 100644 --- a/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java.patch +++ b/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java.patch @@ -7,14 +7,6 @@ import com.destroystokyo.paper.event.player.PlayerSetSpawnEvent; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; -@@ -8,6 +_,7 @@ - import com.mojang.authlib.GameProfile; - import com.mojang.datafixers.util.Pair; - import com.mojang.logging.LogUtils; -+import io.multipaper.shreddedpaper.config.ShreddedPaperConfiguration; - import io.papermc.paper.FeatureHooks; - import io.papermc.paper.connection.PlayerGameConnection; - import io.papermc.paper.connection.PluginMessageBridgeImpl; @@ -1461,9 +_,12 @@ return CraftLocation.toBukkit(respawnData.pos(), world, respawnData.yaw(), respawnData.pitch()); } @@ -49,25 +41,6 @@ if (entry != null && !entry.seenBy.contains(this.getHandle().connection)) { entry.updatePlayer(this.getHandle()); } -@@ -2137,15 +_,18 @@ - - @Override - public boolean canSee(Player player) { -+ if (ShreddedPaperConfiguration.get().optimizations.disableVanishApi) return true; // ShreddedPaper - optimize canSee - return this.canSee((org.bukkit.entity.Entity) player); - } - - @Override - public boolean canSee(org.bukkit.entity.Entity entity) { -+ if (ShreddedPaperConfiguration.get().optimizations.disableVanishApi) return true; // ShreddedPaper - optimize canSee - return this.equals(entity) || entity.isVisibleByDefault() ^ this.invertedVisibilityEntities.containsKey(entity.getUniqueId()); // SPIGOT-7312: Can always see self - } - - public boolean canSeePlayer(UUID uuid) { -+ if (ShreddedPaperConfiguration.get().optimizations.disableVanishApi) return true; // ShreddedPaper - optimize canSee - org.bukkit.entity.Entity entity = this.getServer().getPlayer(uuid); - - return (entity != null) ? this.canSee(entity) : false; // If we can't find it, we can't see it @@ -3136,8 +_,9 @@ @Override public void respawn() { diff --git a/shreddedpaper-server/src/main/java/io/multipaper/shreddedpaper/ShreddedPaper.java b/shreddedpaper-server/src/main/java/io/multipaper/shreddedpaper/ShreddedPaper.java index 99dfb63..5e44be3 100644 --- a/shreddedpaper-server/src/main/java/io/multipaper/shreddedpaper/ShreddedPaper.java +++ b/shreddedpaper-server/src/main/java/io/multipaper/shreddedpaper/ShreddedPaper.java @@ -26,7 +26,7 @@ public static void runSync(ServerLevel serverLevel, BlockPos blockPos, Runnable } public static void runSync(ServerLevel serverLevel, ChunkPos chunkPos, Runnable runnable) { - serverLevel.getChunkSource().tickingRegions.scheduleTask(RegionPos.forChunk(chunkPos), runnable); + serverLevel.chunkScheduler.schedule(RegionPos.forChunk(chunkPos), runnable); } public static void runSync(ServerLevel serverLevel1, ChunkPos chunkPos1, ServerLevel serverLevel2, ChunkPos chunkPos2, Runnable runnable) { diff --git a/shreddedpaper-server/src/main/java/io/multipaper/shreddedpaper/config/ShreddedPaperConfiguration.java b/shreddedpaper-server/src/main/java/io/multipaper/shreddedpaper/config/ShreddedPaperConfiguration.java index 65f35de..d0dd169 100644 --- a/shreddedpaper-server/src/main/java/io/multipaper/shreddedpaper/config/ShreddedPaperConfiguration.java +++ b/shreddedpaper-server/src/main/java/io/multipaper/shreddedpaper/config/ShreddedPaperConfiguration.java @@ -42,7 +42,6 @@ public class Optimizations extends ConfigurationPart { public int entityActivationCheckFrequency = 20; public boolean disableVanishApi = false; - public boolean disableLocatorBar = true; public boolean useLazyExecuteWhenNotFlushing = true; public boolean processTrackQueueInParallel = true; public boolean flushQueueInParallel = true; From b258f48818bc4a7c27541ef194be596a64621403 Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Mon, 5 Jan 2026 23:24:42 +0100 Subject: [PATCH 11/24] Remove already-applied patches from patches_removed These 12 patches have been verified as already integrated into the 1.21.11 codebase: - 0026-Run-unsupported-plugins-in-sync (fully integrated) - 0027-Thread-safe-AreaMap (superseded by Moonrise architecture) - 0028-Threaded-chunk-changes-broadcasting (fully integrated) - 0039-Thread-safe-redstoneUpdateInfos (fully integrated) - 0040-Thread-safe-chunksBeingWorkedOn (superseded by Moonrise) - 0048-Synchronize-playersSentChunkTo (fully integrated) - 0056-Don-t-allow-actions-outside-of-our-region (fully integrated) - 0061-Optimization-Use-lazyExecute-if-we-aren-t-flushing (fully integrated) - 0069-Optimization-Load-chunk-before-joining-player (fully integrated) - 0071-Fix-Concurrent-modification-when-removing-player (fully integrated) - X0016-EntityLookup-accessibleEntities (fully integrated) - X0055-Moving-into-another-region (fully integrated) --- ...0026-Run-unsupported-plugins-in-sync.patch | 434 ------------------ .../0027-Thread-safe-AreaMap.patch | 170 ------- ...-Threaded-chunk-changes-broadcasting.patch | 218 --------- ...0039-Thread-safe-redstoneUpdateInfos.patch | 77 ---- ...0040-Thread-safe-chunksBeingWorkedOn.patch | 75 --- .../0048-Synchronize-playersSentChunkTo.patch | 56 --- ...-allow-actions-outside-of-our-region.patch | 36 -- ...se-lazyExecute-if-we-aren-t-flushing.patch | 41 -- ...ion-Load-chunk-before-joining-player.patch | 19 - ...nt-modification-when-removing-player.patch | 20 - ...0016-EntityLookup-accessibleEntities.patch | 73 --- .../X0055-Moving-into-another-region.patch | 89 ---- 12 files changed, 1308 deletions(-) delete mode 100644 patches_removed/0026-Run-unsupported-plugins-in-sync.patch delete mode 100644 patches_removed/0027-Thread-safe-AreaMap.patch delete mode 100644 patches_removed/0028-Threaded-chunk-changes-broadcasting.patch delete mode 100644 patches_removed/0039-Thread-safe-redstoneUpdateInfos.patch delete mode 100644 patches_removed/0040-Thread-safe-chunksBeingWorkedOn.patch delete mode 100644 patches_removed/0048-Synchronize-playersSentChunkTo.patch delete mode 100644 patches_removed/0056-Don-t-allow-actions-outside-of-our-region.patch delete mode 100644 patches_removed/0061-Optimization-Use-lazyExecute-if-we-aren-t-flushing.patch delete mode 100644 patches_removed/0069-Optimization-Load-chunk-before-joining-player.patch delete mode 100644 patches_removed/0071-Fix-Concurrent-modification-when-removing-player.patch delete mode 100644 patches_removed/X0016-EntityLookup-accessibleEntities.patch delete mode 100644 patches_removed/X0055-Moving-into-another-region.patch diff --git a/patches_removed/0026-Run-unsupported-plugins-in-sync.patch b/patches_removed/0026-Run-unsupported-plugins-in-sync.patch deleted file mode 100644 index 815f50f..0000000 --- a/patches_removed/0026-Run-unsupported-plugins-in-sync.patch +++ /dev/null @@ -1,434 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Sat, 25 May 2024 19:27:42 +0900 -Subject: [PATCH] Run unsupported plugins in sync - - -diff --git a/src/main/java/io/multipaper/shreddedpaper/threading/SynchronousPluginExecution.java b/src/main/java/io/multipaper/shreddedpaper/threading/SynchronousPluginExecution.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3625ee53f17766aae695c0d9e35d755e331e6c56 ---- /dev/null -+++ b/src/main/java/io/multipaper/shreddedpaper/threading/SynchronousPluginExecution.java -@@ -0,0 +1,180 @@ -+package io.multipaper.shreddedpaper.threading; -+ -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.util.TickThread; -+import org.bukkit.plugin.Plugin; -+import org.slf4j.Logger; -+import io.multipaper.shreddedpaper.config.ShreddedPaperConfiguration; -+ -+import java.lang.ref.WeakReference; -+import java.util.ArrayList; -+import java.util.Collections; -+import java.util.Comparator; -+import java.util.List; -+import java.util.Map; -+import java.util.TreeSet; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.concurrent.locks.ReentrantLock; -+ -+public class SynchronousPluginExecution { -+ -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ -+ private static final Map> cachedDependencyLists = new ConcurrentHashMap<>(); // Does not support dynamic plugin reloading, but oh well -+ private static final Map locks = new ConcurrentHashMap<>(); -+ -+ private static final ThreadLocal> currentPlugin = new ThreadLocal<>(); -+ private static final ThreadLocal> heldPluginLocks = ThreadLocal.withInitial(ArrayList::new); // Use a list as the same plugin may be locked multiple times in a single thread due to recursion -+ -+ public static Plugin getCurrentPlugin() { -+ WeakReference pluginRef = currentPlugin.get(); -+ return pluginRef == null ? null : pluginRef.get(); -+ } -+ -+ public static void executeNoException(Plugin plugin, RunnableWithException runnable) { -+ try { -+ execute(plugin, runnable); -+ } catch (RuntimeException e) { -+ throw e; // passthrough, no need to wrap it again -+ } catch (Exception e) { -+ throw new RuntimeException(e); -+ } -+ } -+ -+ public static void execute(Plugin plugin, RunnableWithException runnable) throws Exception { -+ if (plugin == null || !ShreddedPaperConfiguration.get().multithreading.runUnsupportedPluginsInSync || plugin.getDescription().isFoliaSupported() || TickThread.isShutdownThread()) { -+ // Multi-thread safe plugin, run it straight away -+ runnable.run(); -+ return; -+ } -+ -+ // Lock the plugins in a predictable order to prevent deadlocks -+ List pluginsToLock = cachedDependencyLists.get(plugin.getName()); -+ -+ if (pluginsToLock == null) { -+ // computeIfAbsent requires an expensive synchronized call even if the value is already present, so check with a get first -+ pluginsToLock = cachedDependencyLists.computeIfAbsent(plugin.getName(), (name) -> { -+ TreeSet dependencyList = new TreeSet<>(Comparator.naturalOrder()); -+ fillPluginsToLock(plugin, dependencyList); -+ return new ArrayList<>(dependencyList); -+ }); -+ } -+ -+ lock(pluginsToLock); -+ -+ WeakReference parentPlugin = currentPlugin.get(); -+ try { -+ currentPlugin.set(new WeakReference<>(plugin)); -+ runnable.run(); -+ } finally { -+ currentPlugin.set(parentPlugin); -+ for (String pluginToLock : pluginsToLock) { -+ getLock(pluginToLock).unlock(); -+ heldPluginLocks.get().remove(pluginToLock); -+ } -+ } -+ } -+ -+ private static ReentrantLock getLock(String plugin) { -+ ReentrantLock lock = locks.get(plugin); -+ -+ if (lock == null) { -+ // computeIfAbsent requires an expensive synchronized call even if the value is already present, so check with a get first -+ lock = locks.computeIfAbsent(plugin, (name) -> new ReentrantLock()); -+ } -+ -+ return lock; -+ } -+ -+ private static void lock(List pluginsToLock) { -+ List locksToAcquire = new ArrayList<>(pluginsToLock); // Must be a list as we can hold the same lock multiple times due to recursion -+ -+ if (!heldPluginLocks.get().isEmpty()) { -+ // We already have some locks, take care to avoid a deadlock with another thread -+ if (tryLockNow(locksToAcquire)) { -+ heldPluginLocks.get().addAll(locksToAcquire); -+ return; -+ } else { -+ // We failed to instantly acquire the lock, back off from all locks and try again to avoid a deadlock -+ for (String plugin : heldPluginLocks.get()) { -+ getLock(plugin).unlock(); -+ locksToAcquire.add(plugin); -+ } -+ heldPluginLocks.get().clear(); -+ Collections.sort(locksToAcquire); // Ensure we lock in a predictable order to avoid deadlocks -+ } -+ } -+ -+ for (String plugin : locksToAcquire) { -+ getLock(plugin).lock(); -+ heldPluginLocks.get().add(plugin); -+ } -+ } -+ -+ private static boolean tryLockNow(List locksToReacquire) { -+ boolean success = true; -+ -+ List locksAquired = new ArrayList<>(); -+ -+ for (String plugin : locksToReacquire) { -+ ReentrantLock lock = getLock(plugin); -+ if (lock.tryLock()) { -+ locksAquired.add(lock); -+ } else { -+ success = false; -+ break; -+ } -+ } -+ -+ if (!success) { -+ for (ReentrantLock lock : locksAquired) { -+ lock.unlock(); -+ } -+ } -+ -+ return success; -+ } -+ -+ private static boolean fillPluginsToLock(Plugin plugin, TreeSet pluginsToLock) { -+ if (pluginsToLock.contains(plugin.getName())) { -+ // Cyclic graphhhh -+ return true; -+ } -+ -+ if (plugin.getDescription().isFoliaSupported()) { -+ // Multi-thread safe plugin, we don't need to lock it -+ return false; -+ } -+ -+ boolean hasDependency = false; -+ -+ for (String depend : plugin.getDescription().getDepend()) { -+ Plugin dependPlugin = plugin.getServer().getPluginManager().getPlugin(depend); -+ if (dependPlugin != null) { -+ hasDependency |= fillPluginsToLock(dependPlugin, pluginsToLock); -+ } else { -+ LOGGER.warn("Could not find dependency " + depend + " for plugin " + plugin.getName() + " even though it is a required dependency - this code shouldn't've been able to be run!"); -+ } -+ } -+ -+ for (String softDepend : plugin.getDescription().getSoftDepend()) { -+ Plugin softDependPlugin = plugin.getServer().getPluginManager().getPlugin(softDepend); -+ if (softDependPlugin != null) { -+ hasDependency |= fillPluginsToLock(softDependPlugin, pluginsToLock); -+ } -+ } -+ -+ if (!hasDependency) { -+ // Only add our own plugin if we have no dependencies to lock -+ // If we have a dependency, there's no point in locking this plugin by itself as we'll always be locking the dependency anyway -+ pluginsToLock.add(plugin.getName()); -+ } -+ -+ return true; -+ } -+ -+ public interface RunnableWithException { -+ void run() throws Exception; -+ } -+ -+} -diff --git a/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java -index e94224ed280247ee69dfdff8dc960f2b8729be33..9025b44a3ef50e95dcbb4351763f550c0d49e193 100644 ---- a/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java -+++ b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java -@@ -3,6 +3,7 @@ package io.papermc.paper.command; - import com.google.common.collect.Lists; - import io.leangen.geantyref.GenericTypeReflector; - import io.leangen.geantyref.TypeToken; -+import io.multipaper.shreddedpaper.config.ShreddedPaperConfiguration; - import io.papermc.paper.plugin.configuration.PluginMeta; - import io.papermc.paper.plugin.entrypoint.Entrypoint; - import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler; -@@ -61,8 +62,18 @@ public class PaperPluginsCommand extends BukkitCommand { - performance issues. - """)); - -+ private static final Component NON_FOLIA_PLUGIN_INFO = Component.text("ℹ What is a non-supported plugin?", INFO_COLOR) -+ .append(asPlainComponents(""" -+ A non-supported plugin is a plugin that was not made -+ to run in a multi-threaded environment. -+ -+ It is encouraged that you replace this plugin, -+ as they might not work correctly and may cause -+ performance issues. -+ """)); -+ - private static final Component LEGACY_PLUGIN_STAR = Component.text('*', TextColor.color(255, 212, 42)).hoverEvent(LEGACY_PLUGIN_INFO); -- private static final Component INFO_ICON_START = Component.text("ℹ ", INFO_COLOR); -+ private static final Component NON_FOLIA_PLUGIN_STAR = Component.text('⁺', TextColor.color(255, 212, 42)).hoverEvent(NON_FOLIA_PLUGIN_INFO); // ShreddedPaper - private static final Component PAPER_HEADER = Component.text("Paper Plugins:", TextColor.color(2, 136, 209)); - private static final Component BUKKIT_HEADER = Component.text("Bukkit Plugins:", TextColor.color(237, 129, 6)); - private static final Component PLUGIN_TICK = Component.text("- ", NamedTextColor.DARK_GRAY); -@@ -115,6 +126,12 @@ public class PaperPluginsCommand extends BukkitCommand { - builder.append(LEGACY_PLUGIN_STAR); - } - -+ // ShreddedPaper start -+ if (provider instanceof SpigotPluginProvider spigotPluginProvider && ShreddedPaperConfiguration.get().multithreading.threadCount != 1 && !spigotPluginProvider.getMeta().isFoliaSupported()) { -+ builder.append(NON_FOLIA_PLUGIN_STAR); -+ } -+ // ShreddedPaper end -+ - String name = provider.getMeta().getName(); - Component pluginName = Component.text(name, fromStatus(provider)) - // Purpur start -diff --git a/src/main/java/io/papermc/paper/command/brigadier/bukkit/BukkitCommandNode.java b/src/main/java/io/papermc/paper/command/brigadier/bukkit/BukkitCommandNode.java -index 24121a43aeb5e9bce013f30c92dddd15f99736c6..c234deec92e155336078cbbce1f726d87c35b753 100644 ---- a/src/main/java/io/papermc/paper/command/brigadier/bukkit/BukkitCommandNode.java -+++ b/src/main/java/io/papermc/paper/command/brigadier/bukkit/BukkitCommandNode.java -@@ -19,6 +19,9 @@ import org.bukkit.Location; - import org.bukkit.command.Command; - import org.bukkit.command.CommandException; - import org.bukkit.command.CommandSender; -+import org.bukkit.command.PluginCommand; -+import io.multipaper.shreddedpaper.threading.SynchronousPluginExecution; -+import io.multipaper.shreddedpaper.util.ObjectHolder; - - import java.util.Arrays; - import java.util.List; -@@ -88,7 +91,14 @@ public class BukkitCommandNode extends LiteralCommandNode { - - //try (Timing ignored = this.command.timings.startTiming()) { // Purpur - // Note: we don't return the result of target.execute as thats success / failure, we return handled (true) or not handled (false) -- this.command.execute(sender, this.literal, Arrays.copyOfRange(args, 1, args.length)); -+ // ShreddedPaper start - run unsupported plugins in sync -+ Runnable runnable = () -> this.command.execute(sender, this.literal, Arrays.copyOfRange(args, 1, args.length)); -+ if (this.command instanceof PluginCommand pluginCommand) { -+ SynchronousPluginExecution.executeNoException(pluginCommand.getPlugin(), runnable::run); -+ } else { -+ runnable.run(); -+ } -+ // ShreddedPaper end - //} // Purpur - - // return true as command was handled -@@ -112,32 +122,45 @@ public class BukkitCommandNode extends LiteralCommandNode { - org.bukkit.command.CommandSender sender = context.getSource().getSender(); - String[] args = builder.getRemaining().split(" ", -1); // We need the command included -- Set limit to -1, allow for trailing spaces - -- List results = null; -+ ObjectHolder> results = new ObjectHolder<>(); // ShreddedPaper - Location pos = context.getSource().getLocation(); - try { -- results = this.command.tabComplete(sender, this.literal, args, pos.clone()); -+ // ShreddedPaper start - run unsupported plugins in sync -+ try { -+ SynchronousPluginExecution.RunnableWithException runnable = () -> results.value(this.command.tabComplete(sender, this.literal, args, pos.clone())); -+ if (this.command instanceof PluginCommand pluginCommand) { -+ SynchronousPluginExecution.execute(pluginCommand.getPlugin(), runnable); -+ } else { -+ runnable.run(); -+ } -+ } catch (Error | RuntimeException e) { -+ throw e; -+ } catch (Exception e) { -+ throw new RuntimeException(e); -+ } -+ // ShreddedPaper end - } catch (CommandException ex) { - sender.sendMessage(ChatColor.RED + "An internal error occurred while attempting to tab-complete this command"); - Bukkit.getServer().getLogger().log(Level.SEVERE, "Exception when " + sender.getName() + " attempted to tab complete " + builder.getRemaining(), ex); - } - - if (sender instanceof final Player player) { -- TabCompleteEvent tabEvent = new org.bukkit.event.server.TabCompleteEvent(player, builder.getInput(), results != null ? results : new ArrayList<>(), true, pos); // Paper - AsyncTabCompleteEvent -+ TabCompleteEvent tabEvent = new org.bukkit.event.server.TabCompleteEvent(player, builder.getInput(), results.value() != null ? results.value() : new ArrayList<>(), true, pos); // Paper - AsyncTabCompleteEvent // ShreddedPaper - if (!tabEvent.callEvent()) { -- results = null; -+ results.value(null); // ShreddedPaper - } else { -- results = tabEvent.getCompletions(); -+ results.value(tabEvent.getCompletions()); // ShreddedPaper - } - } - // Paper end -- if (results == null) { -+ if (results.value() == null) { // ShreddedPaper - return builder.buildFuture(); - } - - // Defaults to sub nodes, but we have just one giant args node, so offset accordingly - builder = builder.createOffset(builder.getInput().lastIndexOf(' ') + 1); - -- for (String s : results) { -+ for (String s : results.value()) { // ShreddedPaper - builder.suggest(s); - } - -diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java -index 2c82a48867ab347f21822576baa0368273242f82..143638f994a842472e827cb9b4efc160a7235aa4 100644 ---- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java -+++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java -@@ -19,6 +19,8 @@ import org.bukkit.plugin.IllegalPluginAccessException; - import org.bukkit.plugin.Plugin; - import org.bukkit.plugin.RegisteredListener; - import org.jetbrains.annotations.NotNull; -+import io.multipaper.shreddedpaper.config.ShreddedPaperConfiguration; -+import io.multipaper.shreddedpaper.threading.SynchronousPluginExecution; - - import java.lang.reflect.Method; - import java.util.Arrays; -@@ -53,7 +55,15 @@ class PaperEventManager { - } - - try { -- registration.callEvent(event); -+ // ShreddedPaper start - run unsupported plugins in sync -+ if (event.isAsynchronous()) { -+ registration.callEvent(event); -+ } else { -+ SynchronousPluginExecution.execute(registration.getPlugin(), () -> { -+ registration.callEvent(event); -+ }); -+ } -+ // ShreddedPaper end - run unsupported plugins in sync - } catch (AuthorNagException ex) { - Plugin plugin = registration.getPlugin(); - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 91a7816968b3eb7ec2bc471a328d4b75eec09770..0ab40a285b72ab548a575edc3855422805e3b994 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -7,6 +7,7 @@ import com.google.common.collect.ImmutableMap; - import com.mojang.datafixers.util.Pair; - import io.multipaper.shreddedpaper.ShreddedPaper; - import io.multipaper.shreddedpaper.region.RegionPos; -+import io.multipaper.shreddedpaper.threading.SynchronousPluginExecution; - import io.papermc.paper.util.TickThread; - import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; - import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -@@ -2583,12 +2584,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { - } - - java.util.concurrent.CompletableFuture ret = new java.util.concurrent.CompletableFuture<>(); -+ Plugin plugin = SynchronousPluginExecution.getCurrentPlugin(); // ShreddedPaper - synchronous plugin execution - - io.papermc.paper.chunk.system.ChunkSystem.scheduleChunkLoad(this.getHandle(), x, z, gen, ChunkStatus.FULL, true, priority, (c) -> { - if (c != null) this.addTicket(x, z); // Paper // ShreddedPaper - add the ticket before scheduling on correct thread - ShreddedPaper.ensureSync(this.getHandle(), c.getPos(), () -> { // ShreddedPaper - ensure on correct thread - net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)c; -- ret.complete(chunk == null ? null : new CraftChunk(chunk)); -+ SynchronousPluginExecution.executeNoException(plugin, () -> ret.complete(chunk == null ? null : new CraftChunk(chunk))); // ShreddedPaper - synchronous plugin execution - }); - }); - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 8bdb226269e931d31d3876a10f24f4e7810d4aae..99fad810a700952b15f71ca89d984ccd64e6a697 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -10,6 +10,7 @@ import java.util.Set; - import java.util.UUID; - - import io.multipaper.shreddedpaper.ShreddedPaper; -+import io.multipaper.shreddedpaper.threading.SynchronousPluginExecution; - import net.minecraft.nbt.CompoundTag; - import net.minecraft.nbt.Tag; - import net.minecraft.network.chat.Component; -@@ -1068,6 +1069,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - - net.minecraft.server.level.ServerLevel world = ((CraftWorld)locationClone.getWorld()).getHandle(); - java.util.concurrent.CompletableFuture ret = new java.util.concurrent.CompletableFuture<>(); -+ Plugin plugin = SynchronousPluginExecution.getCurrentPlugin(); // ShreddedPaper - synchronous plugin execution - - // ShreddedPaper start - run sync if possible - ChunkPos fromChunkPos = this.getHandle().chunkPosition(); -@@ -1100,13 +1102,13 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - world, RegionPos.forLocation(locationClone), - () -> { // ShreddedPaper - Run teleports on the correct threads - try { -- ret.complete(CraftEntity.this.teleport(locationClone, cause, teleportFlags) ? Boolean.TRUE : Boolean.FALSE); -+ SynchronousPluginExecution.executeNoException(plugin, () -> ret.complete(CraftEntity.this.teleport(locationClone, cause, teleportFlags) ? Boolean.TRUE : Boolean.FALSE)); // ShreddedPaper - synchronous plugin execution - } catch (Throwable throwable) { - if (throwable instanceof ThreadDeath) { - throw (ThreadDeath)throwable; - } - net.minecraft.server.MinecraftServer.LOGGER.error("Failed to teleport entity " + CraftEntity.this, throwable); -- ret.completeExceptionally(throwable); -+ SynchronousPluginExecution.executeNoException(plugin, () -> ret.completeExceptionally(throwable)); // ShreddedPaper - synchronous plugin execution - } - }); - }); -diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -index 57f0342513205e4fc536f31d47623b6564efc8b7..e8437d039af0457fc6fc877def12f973ab263735 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java -@@ -24,6 +24,7 @@ import org.bukkit.scheduler.BukkitRunnable; - import org.bukkit.scheduler.BukkitScheduler; - import org.bukkit.scheduler.BukkitTask; - import org.bukkit.scheduler.BukkitWorker; -+import io.multipaper.shreddedpaper.threading.SynchronousPluginExecution; - - /** - * The fundamental concepts for this implementation: -@@ -479,7 +480,7 @@ public class CraftScheduler implements BukkitScheduler { - if (task.isSync()) { - this.currentTask = task; - try { -- task.run(); -+ SynchronousPluginExecution.execute(task.getOwner(), task::run); // ShreddedPaper - run unsupported plugins in sync - } catch (final Throwable throwable) { - // Paper start - String msg = String.format( diff --git a/patches_removed/0027-Thread-safe-AreaMap.patch b/patches_removed/0027-Thread-safe-AreaMap.patch deleted file mode 100644 index fe543e9..0000000 --- a/patches_removed/0027-Thread-safe-AreaMap.patch +++ /dev/null @@ -1,170 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Thu, 30 May 2024 17:43:14 +0900 -Subject: [PATCH] Thread-safe AreaMap - - -diff --git a/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java -index 091b1ae822e1c0517e59572e7a9bda11e998c0ee..e6a46b9902a9c9b546bfb62de37d0c6cfdab4b82 100644 ---- a/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java -+++ b/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java -@@ -1,5 +1,6 @@ - package com.destroystokyo.paper.util.misc; - -+import io.multipaper.shreddedpaper.util.SimpleStampedLock; - import io.papermc.paper.util.IntegerUtil; - import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; - import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -@@ -33,6 +34,8 @@ public abstract class AreaMap { - protected final ChangeCallback removeCallback; - protected final ChangeSourceCallback changeSourceCallback; - -+ protected final SimpleStampedLock lock = new SimpleStampedLock(); // ShreddedPaper - Multi-threaded access -+ - public AreaMap() { - this(new PooledLinkedHashSets<>()); - } -@@ -54,37 +57,48 @@ public abstract class AreaMap { - - @Nullable - public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getObjectsInRange(final long key) { -- return this.areaMap.get(key); -+ return this.lock.optimisticRead(() -> this.areaMap.get(key)); // ShreddedPaper - Multi-threaded access - } - - @Nullable - public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getObjectsInRange(final ChunkPos chunkPos) { -- return this.areaMap.get(MCUtil.getCoordinateKey(chunkPos)); -+ return this.lock.optimisticRead(() -> this.areaMap.get(MCUtil.getCoordinateKey(chunkPos))); // ShreddedPaper - Multi-threaded access - } - - @Nullable - public final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getObjectsInRange(final int chunkX, final int chunkZ) { -- return this.areaMap.get(MCUtil.getCoordinateKey(chunkX, chunkZ)); -+ return this.lock.optimisticRead(() -> this.areaMap.get(MCUtil.getCoordinateKey(chunkX, chunkZ))); // ShreddedPaper - Multi-threaded access - } - - // Long.MIN_VALUE indicates the object is not mapped - public final long getLastCoordinate(final E object) { -- return this.objectToLastCoordinate.getOrDefault(object, Long.MIN_VALUE); -+ return this.lock.optimisticRead(() -> this.objectToLastCoordinate.getOrDefault(object, Long.MIN_VALUE)); // ShreddedPaper - Multi-threaded access - } - - // -1 indicates the object is not mapped - public final int getLastViewDistance(final E object) { -- return this.objectToViewDistance.getOrDefault(object, -1); -+ return this.lock.optimisticRead(() -> this.objectToViewDistance.getOrDefault(object, -1)); // ShreddedPaper - Multi-threaded access - } - - // returns the total number of mapped chunks - public final int size() { -- return this.areaMap.size(); -+ return this.lock.optimisticRead(this.areaMap::size); // ShreddedPaper - Multi-threaded access - } - - public final void addOrUpdate(final E object, final int chunkX, final int chunkZ, final int viewDistance) { -- final int oldViewDistance = this.objectToViewDistance.put(object, viewDistance); -+ // ShreddedPaper start - Don't recalculate if not needed - final long newPos = MCUtil.getCoordinateKey(chunkX, chunkZ); -+ try { -+ final int oldViewDistance = this.objectToViewDistance.getInt(object); -+ final long oldPos = this.objectToLastCoordinate.getLong(object); -+ if (oldViewDistance == viewDistance && oldPos == newPos) { -+ return; // no change, don't recalculate -+ } -+ } catch (Exception ignored) { /* Probably just a concurrent modification error */ } -+ // ShreddedPaper end - Don't recalculate if not needed -+ this.lock.write(() -> { // ShreddedPaper - Multi-threaded access -+ final int oldViewDistance = this.objectToViewDistance.put(object, viewDistance); -+ // final long newPos = MCUtil.getCoordinateKey(chunkX, chunkZ); // ShreddedPaper - moved up - final long oldPos = this.objectToLastCoordinate.put(object, newPos); - - if (oldViewDistance == -1) { -@@ -95,20 +109,33 @@ public abstract class AreaMap { - this.updateObjectCallback(object, oldPos, newPos, oldViewDistance, viewDistance); - } - //this.validate(object, viewDistance); -+ }); // ShreddedPaper - Multi-threaded access - } - - public final boolean update(final E object, final int chunkX, final int chunkZ, final int viewDistance) { -+ // ShreddedPaper start - Don't recalculate if not needed -+ final long newPos = MCUtil.getCoordinateKey(chunkX, chunkZ); -+ try { -+ final int oldViewDistance = this.objectToViewDistance.getInt(object); -+ final long oldPos = this.objectToLastCoordinate.getLong(object); -+ if (oldViewDistance == viewDistance && oldPos == newPos) { -+ return false; // no change, don't recalculate -+ } -+ } catch (Exception ignored) { /* Probably just a concurrent modification error */ } -+ // ShreddedPaper end - Don't recalculate if not needed -+ return this.lock.write(() -> { // ShreddedPaper - Multi-threaded access - final int oldViewDistance = this.objectToViewDistance.replace(object, viewDistance); - if (oldViewDistance == -1) { - return false; - } else { -- final long newPos = MCUtil.getCoordinateKey(chunkX, chunkZ); -+ // final long newPos = MCUtil.getCoordinateKey(chunkX, chunkZ); // ShreddedPaper - moved up - final long oldPos = this.objectToLastCoordinate.put(object, newPos); - this.updateObject(object, oldPos, newPos, oldViewDistance, viewDistance); - this.updateObjectCallback(object, oldPos, newPos, oldViewDistance, viewDistance); - } - //this.validate(object, viewDistance); - return true; -+ }); // ShreddedPaper - Multi-threaded access - } - - // called after the distance map updates -@@ -119,6 +146,7 @@ public abstract class AreaMap { - } - - public final boolean add(final E object, final int chunkX, final int chunkZ, final int viewDistance) { -+ return this.lock.write(() -> { // ShreddedPaper - Multi-threaded access - final int oldViewDistance = this.objectToViewDistance.putIfAbsent(object, viewDistance); - if (oldViewDistance != -1) { - return false; -@@ -132,12 +160,14 @@ public abstract class AreaMap { - //this.validate(object, viewDistance); - - return true; -+ }); // ShreddedPaper - Multi-threaded access - } - - // called after the distance map updates - protected void addObjectCallback(final E object, final int chunkX, final int chunkZ, final int viewDistance) {} - - public final boolean remove(final E object) { -+ return this.lock.write(() -> { // ShreddedPaper - Multi-threaded access - final long position = this.objectToLastCoordinate.removeLong(object); - final int viewDistance = this.objectToViewDistance.removeInt(object); - -@@ -152,6 +182,7 @@ public abstract class AreaMap { - this.removeObjectCallback(object, currentX, currentZ, viewDistance); - //this.validate(object, -1); - return true; -+ }); // ShreddedPaper - Multi-threaded access - } - - // called after the distance map updates -diff --git a/src/main/java/com/destroystokyo/paper/util/misc/PooledLinkedHashSets.java b/src/main/java/com/destroystokyo/paper/util/misc/PooledLinkedHashSets.java -index e51104e65a07b6ea7bbbcbb6afb066ef6401cc5b..ba1e9413cee8f9b12023d21567384aa9420b7fc4 100644 ---- a/src/main/java/com/destroystokyo/paper/util/misc/PooledLinkedHashSets.java -+++ b/src/main/java/com/destroystokyo/paper/util/misc/PooledLinkedHashSets.java -@@ -24,7 +24,7 @@ public class PooledLinkedHashSets { - return; - } - -- public PooledObjectLinkedOpenHashSet findMapWith(final PooledObjectLinkedOpenHashSet current, final E object) { -+ public synchronized PooledObjectLinkedOpenHashSet findMapWith(final PooledObjectLinkedOpenHashSet current, final E object) { // ShreddedPaper - Multi-threaded access - final PooledObjectLinkedOpenHashSet cached = current.getAddCache(object); - - if (cached != null) { -@@ -76,7 +76,7 @@ public class PooledLinkedHashSets { - } - - // rets null if current.size() == 1 -- public PooledObjectLinkedOpenHashSet findMapWithout(final PooledObjectLinkedOpenHashSet current, final E object) { -+ public synchronized PooledObjectLinkedOpenHashSet findMapWithout(final PooledObjectLinkedOpenHashSet current, final E object) { // ShreddedPaper - Multi-threaded access - if (current.set.size() == 1) { - decrementReferenceCount(current); - return null; diff --git a/patches_removed/0028-Threaded-chunk-changes-broadcasting.patch b/patches_removed/0028-Threaded-chunk-changes-broadcasting.patch deleted file mode 100644 index 5922e50..0000000 --- a/patches_removed/0028-Threaded-chunk-changes-broadcasting.patch +++ /dev/null @@ -1,218 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Thu, 30 May 2024 20:37:51 +0900 -Subject: [PATCH] Threaded chunk changes broadcasting - - -diff --git a/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChangesBroadcaster.java b/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChangesBroadcaster.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ad7960c9a11cffc4b89a08dac45dd0056d12d668 ---- /dev/null -+++ b/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChangesBroadcaster.java -@@ -0,0 +1,53 @@ -+package io.multipaper.shreddedpaper.threading; -+ -+import io.papermc.paper.util.TickThread; -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -+import net.minecraft.server.level.ChunkHolder; -+ -+public class ShreddedPaperChangesBroadcaster { -+ -+ private static final ThreadLocal> needsChangeBroadcastingThreadLocal = new ThreadLocal<>(); -+ -+ public static void setAsWorkerThread() { -+ if (needsChangeBroadcastingThreadLocal.get() == null) { -+ needsChangeBroadcastingThreadLocal.set(new ReferenceOpenHashSet<>()); -+ } -+ } -+ -+ public static void add(ChunkHolder chunkHolder) { -+ ReferenceOpenHashSet needsChangeBroadcasting = needsChangeBroadcastingThreadLocal.get(); -+ if (needsChangeBroadcasting != null) { -+ needsChangeBroadcasting.add(chunkHolder); -+ } -+ } -+ -+ public static void remove(ChunkHolder chunkHolder) { -+ ReferenceOpenHashSet needsChangeBroadcasting = needsChangeBroadcastingThreadLocal.get(); -+ if (needsChangeBroadcasting != null) { -+ needsChangeBroadcasting.remove(chunkHolder); -+ } -+ } -+ -+ public static void broadcastChanges() { -+ broadcastChanges(needsChangeBroadcastingThreadLocal.get()); -+ } -+ -+ public static void broadcastChanges(ReferenceOpenHashSet needsChangeBroadcasting) { -+ if (!needsChangeBroadcasting.isEmpty()) { -+ ReferenceOpenHashSet copy = needsChangeBroadcasting.clone(); -+ needsChangeBroadcasting.clear(); -+ for (ChunkHolder holder : copy) { -+ if (!TickThread.isTickThreadFor(holder.newChunkHolder.world, holder.pos)) { -+ // The changes will get picked up by the correct thread when it is ticked -+ continue; -+ } -+ -+ holder.broadcastChanges(holder.getFullChunkNowUnchecked()); // LevelChunks are NEVER unloaded -+ if (holder.needsBroadcastChanges()) { -+ // I DON'T want to KNOW what DUMB plugins might be doing. -+ needsChangeBroadcasting.add(holder); -+ } -+ } -+ } -+ } -+} -diff --git a/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java b/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java -index c6a2e84fd3a6b88fa4136dbffda04ffebd27f4bd..f014a81b107ed43e80431ca715e6c12a99c70269 100644 ---- a/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java -+++ b/src/main/java/io/multipaper/shreddedpaper/threading/ShreddedPaperChunkTicker.java -@@ -62,6 +62,8 @@ public class ShreddedPaperChunkTicker { - throw new IllegalStateException("Ticking region " + level.convertable.getLevelId() + " " + region.getRegionPos() + " outside of ShreddedPaperTickThread!"); - } - -+ ShreddedPaperChangesBroadcaster.setAsWorkerThread(); -+ - while (region.getInternalTaskQueue().executeTask()) ; - - level.chunkTaskScheduler.chunkHolderManager.processUnloads(region); -@@ -92,6 +94,8 @@ public class ShreddedPaperChunkTicker { - - while (region.getInternalTaskQueue().executeTask()) ; - -+ ShreddedPaperChangesBroadcaster.broadcastChanges(); -+ - if (region.isEmpty()) { - level.chunkSource.tickingRegions.remove(region.getRegionPos()); - } -@@ -101,6 +105,8 @@ public class ShreddedPaperChunkTicker { - } - - private void _tickChunk(ServerLevel level, LevelChunk chunk1, NaturalSpawner.SpawnState spawnercreature_d) { -+ if (chunk1.getChunkHolder().vanillaChunkHolder.needsBroadcastChanges()) ShreddedPaperChangesBroadcaster.add(chunk1.getChunkHolder().vanillaChunkHolder); // ShreddedPaper -+ - // Start - Import the same variables as the original chunk ticking method to make copying new changes easier - int j = 1; // Inhabited time increment in ticks - boolean flag = level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !level.players().isEmpty(); // Should run mob spawning code -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index 0e0c3a010d5cfaa0c15bcb38c4e3b1a8d7ad39a1..8da6278f841e0ac032ae74ed75b7689d43e2cdfb 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -2,6 +2,7 @@ package net.minecraft.server.level; - - import com.mojang.datafixers.util.Pair; - import io.multipaper.shreddedpaper.region.RegionPos; -+import io.multipaper.shreddedpaper.threading.ShreddedPaperChangesBroadcaster; - import io.papermc.paper.util.TickThread; - import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; - import it.unimi.dsi.fastutil.shorts.ShortSet; -@@ -81,7 +82,7 @@ public class ChunkHolder { - public void onChunkAdd() { - // Paper start - optimise chunk tick iteration - if (this.needsBroadcastChanges()) { -- this.chunkMap.needsChangeBroadcasting.add(this); -+ ShreddedPaperChangesBroadcaster.add(this); // this.chunkMap.needsChangeBroadcasting.add(this); // ShreddedPaper - handled by the regions - } - // Paper end - optimise chunk tick iteration - } -@@ -89,7 +90,7 @@ public class ChunkHolder { - public void onChunkRemove() { - // Paper start - optimise chunk tick iteration - if (this.needsBroadcastChanges()) { -- this.chunkMap.needsChangeBroadcasting.remove(this); -+ ShreddedPaperChangesBroadcaster.remove(this); // this.chunkMap.needsChangeBroadcasting.remove(this); // ShreddedPaper - handled by the regions - } - // Paper end - optimise chunk tick iteration - } -@@ -275,7 +276,7 @@ public class ChunkHolder { - - private void addToBroadcastMap() { - io.papermc.paper.util.TickThread.ensureTickThread(this.chunkMap.level, this.pos, "Asynchronous ChunkHolder update is not allowed"); -- this.chunkMap.needsChangeBroadcasting.add(this); -+ ShreddedPaperChangesBroadcaster.add(this); // this.chunkMap.needsChangeBroadcasting.add(this); // ShreddedPaper - handled by the regions - } - // Paper end - optimise chunk tick iteration - -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 8992460e60b9fc72b020e91edf59dcaf3ec7f7ce..a45a92b4cada4c23769cad17a7dbe10f59641834 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -15,6 +15,7 @@ import java.util.function.Supplier; - import javax.annotation.Nullable; - - import io.multipaper.shreddedpaper.region.RegionPos; -+import io.multipaper.shreddedpaper.threading.ShreddedPaperChangesBroadcaster; - import io.papermc.paper.util.TickThread; - import net.minecraft.Util; - import net.minecraft.core.BlockPos; -@@ -692,19 +693,21 @@ public class ServerChunkCache extends ChunkSource { - // Paper - optimise chunk tick iteration - //this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing // Purpur - // Paper start - optimise chunk tick iteration -- future = future.thenRun(() -> { // ShreddedPaper - run async -- if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) { -- it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet copy = this.chunkMap.needsChangeBroadcasting.clone(); -- this.chunkMap.needsChangeBroadcasting.clear(); -- for (ChunkHolder holder : copy) { -- holder.broadcastChanges(holder.getFullChunkNowUnchecked()); // LevelChunks are NEVER unloaded -- if (holder.needsBroadcastChanges()) { -- // I DON'T want to KNOW what DUMB plugins might be doing. -- this.chunkMap.needsChangeBroadcasting.add(holder); -- } -- } -- } -- }); // ShreddedPaper - run async -+ // ShreddedPaper start - // ShreddedPaper - handled in the region -+// future = future.thenRun(() -> { // ShreddedPaper - run async -+// if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) { -+// it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet copy = this.chunkMap.needsChangeBroadcasting.clone(); -+// this.chunkMap.needsChangeBroadcasting.clear(); -+// for (ChunkHolder holder : copy) { -+// holder.broadcastChanges(holder.getFullChunkNowUnchecked()); // LevelChunks are NEVER unloaded -+// if (holder.needsBroadcastChanges()) { -+// // I DON'T want to KNOW what DUMB plugins might be doing. -+// this.chunkMap.needsChangeBroadcasting.add(holder); -+// } -+// } -+// } -+// }); // ShreddedPaper - run async -+ // ShreddedPaper end - handled in the region - // Paper end - optimise chunk tick iteration - //this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing // Purpur - // Paper - optimise chunk tick iteration -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 7424246750d6ceca1acd5d9ebfd48f0d66504c5d..0a1e3784a26c5b0058b80455e0b0a357d4f25e91 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -3,6 +3,9 @@ package net.minecraft.server.level; - import com.mojang.logging.LogUtils; - import java.util.Objects; - import javax.annotation.Nullable; -+ -+import io.multipaper.shreddedpaper.region.RegionPos; -+import io.multipaper.shreddedpaper.threading.ShreddedPaperChunkTicker; - import net.minecraft.advancements.CriteriaTriggers; - import net.minecraft.core.BlockPos; - import net.minecraft.core.Direction; -@@ -26,6 +29,7 @@ import net.minecraft.world.level.block.TrapDoorBlock; - import net.minecraft.world.level.block.entity.BlockEntity; - import net.minecraft.world.level.block.state.BlockState; - import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; -+import net.minecraft.world.level.chunk.LevelChunk; - import net.minecraft.world.phys.BlockHitResult; - import org.slf4j.Logger; - -@@ -467,6 +471,13 @@ public class ServerPlayerGameMode { - } - // Paper end - Trigger bee_nest_destroyed trigger in the correct place - -+ // ShreddedPaper start - broadcast block changes immediately if on the wrong thread -+ LevelChunk levelChunk = this.level.getChunkIfLoaded(pos); -+ if (levelChunk != null) { -+ levelChunk.getChunkHolder().vanillaChunkHolder.broadcastChanges(levelChunk); -+ } -+ // ShreddedPaper end - broadcast block changes immediately if on the wrong thread -+ - return true; - // CraftBukkit end - } diff --git a/patches_removed/0039-Thread-safe-redstoneUpdateInfos.patch b/patches_removed/0039-Thread-safe-redstoneUpdateInfos.patch deleted file mode 100644 index f1ee034..0000000 --- a/patches_removed/0039-Thread-safe-redstoneUpdateInfos.patch +++ /dev/null @@ -1,77 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Tue, 18 Jun 2024 17:01:01 +0900 -Subject: [PATCH] Thread-safe redstoneUpdateInfos - - -diff --git a/src/main/java/io/multipaper/shreddedpaper/region/LevelChunkRegion.java b/src/main/java/io/multipaper/shreddedpaper/region/LevelChunkRegion.java -index 99680a983da4c64b709cae624348992cdc83e7f3..ee00dd2ea0d2143cbc8282a34ad25a08b0bdd1b4 100644 ---- a/src/main/java/io/multipaper/shreddedpaper/region/LevelChunkRegion.java -+++ b/src/main/java/io/multipaper/shreddedpaper/region/LevelChunkRegion.java -@@ -12,9 +12,11 @@ import net.minecraft.server.level.ServerPlayer; - import net.minecraft.world.entity.Entity; - import net.minecraft.world.entity.Mob; - import net.minecraft.world.level.BlockEventData; -+import net.minecraft.world.level.block.RedstoneTorchBlock; - import net.minecraft.world.level.block.entity.TickingBlockEntity; - import net.minecraft.world.level.chunk.LevelChunk; - -+import java.util.ArrayDeque; - import java.util.ArrayList; - import java.util.Collection; - import java.util.List; -@@ -39,6 +41,7 @@ public class LevelChunkRegion { - public final List pendingBlockEntityTickers = new ReferenceArrayList<>(); - private final ObjectOpenHashSet navigatingMobs = new ObjectOpenHashSet<>(); - private final ObjectLinkedOpenHashSet blockEvents = new ObjectLinkedOpenHashSet<>(); -+ public ArrayDeque redstoneUpdateInfos; - - public LevelChunkRegion(ServerLevel level, RegionPos regionPos) { - this.level = level; -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 484ba0a90bd6f13fd2116279e422b1c9855d25ea..0af343050a13371b86379a794caeb8dfeeb8550b 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -186,7 +186,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - private org.spigotmc.TickLimiter tileLimiter; - // private int tileTickPosition; // ShreddedPaper - removed tileTickPosition - public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions -- public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here -+ // public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here // ShreddedPaper - move to LevelChunkRegion - - // Purpur start - private com.google.common.cache.Cache playerBreedingCooldowns; -diff --git a/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java b/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java -index ceba9617748a8b4f3a9bd459475952c9c6c9ed7c..554377cfee5a7b8df3ddd67a043e8b6a20982a35 100644 ---- a/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/RedstoneTorchBlock.java -@@ -6,6 +6,8 @@ import java.util.Iterator; - import java.util.List; - import java.util.Map; - import java.util.WeakHashMap; -+ -+import io.multipaper.shreddedpaper.region.RegionPos; - import net.minecraft.core.BlockPos; - import net.minecraft.core.Direction; - import net.minecraft.core.particles.DustParticleOptions; -@@ -81,7 +83,7 @@ public class RedstoneTorchBlock extends BaseTorchBlock { - protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { - boolean flag = this.hasNeighborSignal(world, pos, state); - // Paper start - Faster redstone torch rapid clock removal -- java.util.ArrayDeque redstoneUpdateInfos = world.redstoneUpdateInfos; -+ java.util.ArrayDeque redstoneUpdateInfos = world.chunkSource.tickingRegions.get(RegionPos.forBlockPos(pos)).redstoneUpdateInfos; // ShreddedPaper - move redstoneUpdateInfos to the region - if (redstoneUpdateInfos != null) { - RedstoneTorchBlock.Toggle curr; - while ((curr = redstoneUpdateInfos.peek()) != null && world.getGameTime() - curr.when > 60L) { -@@ -165,9 +167,9 @@ public class RedstoneTorchBlock extends BaseTorchBlock { - - private static boolean isToggledTooFrequently(Level world, BlockPos pos, boolean addNew) { - // Paper start - Faster redstone torch rapid clock removal -- java.util.ArrayDeque list = world.redstoneUpdateInfos; -+ java.util.ArrayDeque list = ((ServerLevel) world).chunkSource.tickingRegions.get(RegionPos.forBlockPos(pos)).redstoneUpdateInfos; // ShreddedPaper - move redstoneUpdateInfos to the region - if (list == null) { -- list = world.redstoneUpdateInfos = new java.util.ArrayDeque<>(); -+ list = ((ServerLevel) world).chunkSource.tickingRegions.get(RegionPos.forBlockPos(pos)).redstoneUpdateInfos = new java.util.ArrayDeque<>(); // ShreddedPaper - move redstoneUpdateInfos to the region - } - // Paper end - Faster redstone torch rapid clock removal - diff --git a/patches_removed/0040-Thread-safe-chunksBeingWorkedOn.patch b/patches_removed/0040-Thread-safe-chunksBeingWorkedOn.patch deleted file mode 100644 index b1b5857..0000000 --- a/patches_removed/0040-Thread-safe-chunksBeingWorkedOn.patch +++ /dev/null @@ -1,75 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Thu, 20 Jun 2024 14:56:49 +0900 -Subject: [PATCH] Thread-safe chunksBeingWorkedOn - - -diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -index 8ef22f8f0d6da49247a90152e5cfa9ffc7f596a4..161b847fe36613a6bc76ed7399ff118a356c05aa 100644 ---- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -+++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -@@ -5,7 +5,10 @@ import com.mojang.logging.LogUtils; - import it.unimi.dsi.fastutil.objects.ObjectArrayList; - import it.unimi.dsi.fastutil.objects.ObjectList; - import it.unimi.dsi.fastutil.objects.ObjectListIterator; -+ -+import java.util.Map; - import java.util.concurrent.CompletableFuture; -+import java.util.concurrent.ConcurrentHashMap; - import java.util.concurrent.atomic.AtomicBoolean; - import java.util.function.IntSupplier; - import javax.annotation.Nullable; -@@ -114,7 +117,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - return totalChunks; - } - -- private final Long2IntOpenHashMap chunksBeingWorkedOn = new Long2IntOpenHashMap(); -+ private final Map chunksBeingWorkedOn = new ConcurrentHashMap<>(); // ShreddedPaper - thread-safe chunksBeingWorkedOn - - private void queueTaskForSection(final int chunkX, final int chunkY, final int chunkZ, - final Supplier runnable) { // Paper - rewrite chunk system -@@ -157,21 +160,31 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - } - updateFuture.isTicketAdded = true; - -- final int references = this.chunksBeingWorkedOn.addTo(key, 1); -- if (references == 0) { -- final ChunkPos pos = new ChunkPos(chunkX, chunkZ); -- world.getChunkSource().addRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); -- } -- -- updateFuture.onComplete.thenAcceptAsync((final Void ignore) -> { -- final int newReferences = this.chunksBeingWorkedOn.get(key); -- if (newReferences == 1) { -- this.chunksBeingWorkedOn.remove(key); -+ // ShreddedPaper start - thread-safe chunksBeingWorkedOn -+ this.chunksBeingWorkedOn.compute(key, (k, references) -> { -+ if (references == null || references == 0) { - final ChunkPos pos = new ChunkPos(chunkX, chunkZ); -- world.getChunkSource().removeRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); -- } else { -- this.chunksBeingWorkedOn.put(key, newReferences - 1); -+ world.getChunkSource().addRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); -+ return 1; - } -+ return references + 1; -+ }); -+ // ShreddedPaper end - thread-safe chunksBeingWorkedOn -+ -+ updateFuture.onComplete.thenAcceptAsync((final Void ignore) -> { -+ // ShreddedPaper start - thread-safe chunksBeingWorkedOn -+ this.chunksBeingWorkedOn.compute(key, (k, newReferences) -> { -+ if (newReferences == null) { -+ throw new NullPointerException("newReferences should not be null here! Should be at least 1 or larger"); -+ } -+ if (newReferences == 1) { -+ final ChunkPos pos = new ChunkPos(chunkX, chunkZ); -+ world.getChunkSource().removeRegionTicket(ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); -+ return null; // Removes from chunksBeingWorkedOn -+ } -+ return newReferences - 1; -+ }); -+ // ShreddedPaper end - thread-safe chunksBeingWorkedOn - }, world.getChunkSource().chunkMap.mainThreadExecutor).whenComplete((final Void ignore, final Throwable thr) -> { - if (thr != null) { - LOGGER.error("Failed to remove ticket level for post chunk task " + new ChunkPos(chunkX, chunkZ), thr); diff --git a/patches_removed/0048-Synchronize-playersSentChunkTo.patch b/patches_removed/0048-Synchronize-playersSentChunkTo.patch deleted file mode 100644 index b9a46c2..0000000 --- a/patches_removed/0048-Synchronize-playersSentChunkTo.patch +++ /dev/null @@ -1,56 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Sun, 4 Aug 2024 00:46:29 +0900 -Subject: [PATCH] Synchronize playersSentChunkTo - - -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index 8da6278f841e0ac032ae74ed75b7689d43e2cdfb..d1d0bc714ddb0ba148c215b69f62d0d195459b37 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -102,15 +102,19 @@ public class ChunkHolder { - private final com.destroystokyo.paper.util.maplist.ReferenceList playersSentChunkTo = new com.destroystokyo.paper.util.maplist.ReferenceList<>(); - - public void addPlayer(ServerPlayer player) { -+ synchronized (playersSentChunkTo) { // ShreddedPaper - if (!this.playersSentChunkTo.add(player)) { - throw new IllegalStateException("Already sent chunk " + this.pos + " in world '" + this.chunkMap.level.getWorld().getName() + "' to player " + player); - } -+ } // ShreddedPaper - } - - public void removePlayer(ServerPlayer player) { -+ synchronized (playersSentChunkTo) { // ShreddedPaper - if (!this.playersSentChunkTo.remove(player)) { - throw new IllegalStateException("Have not sent chunk " + this.pos + " in world '" + this.chunkMap.level.getWorld().getName() + "' to player " + player); - } -+ } // ShreddedPaper - } - - public boolean hasChunkBeenSent() { -@@ -118,7 +122,9 @@ public class ChunkHolder { - } - - public boolean hasBeenSent(ServerPlayer to) { -+ synchronized (playersSentChunkTo) { // ShreddedPaper - return this.playersSentChunkTo.contains(to); -+ } // ShreddedPaper - } - // Paper end - replace player chunk loader - public ChunkHolder(ChunkPos pos, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.PlayerProvider playersWatchingChunkProvider, io.papermc.paper.chunk.system.scheduling.NewChunkHolder newChunkHolder) { // Paper - rewrite chunk system -@@ -360,6 +366,7 @@ public class ChunkHolder { - public List getPlayers(boolean onlyOnWatchDistanceEdge) { - List ret = new java.util.ArrayList<>(); - -+ synchronized (playersSentChunkTo) { // ShreddedPaper - for (int i = 0, len = this.playersSentChunkTo.size(); i < len; ++i) { - ServerPlayer player = this.playersSentChunkTo.getUnchecked(i); - if (onlyOnWatchDistanceEdge && !this.chunkMap.level.playerChunkLoader.isChunkSent(player, this.pos.x, this.pos.z, onlyOnWatchDistanceEdge)) { -@@ -367,6 +374,7 @@ public class ChunkHolder { - } - ret.add(player); - } -+ } // ShreddedPaper - - return ret; - } diff --git a/patches_removed/0056-Don-t-allow-actions-outside-of-our-region.patch b/patches_removed/0056-Don-t-allow-actions-outside-of-our-region.patch deleted file mode 100644 index 5fe690f..0000000 --- a/patches_removed/0056-Don-t-allow-actions-outside-of-our-region.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Wed, 28 Aug 2024 13:47:10 +0900 -Subject: [PATCH] Don't allow actions outside of our region - - -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 510653da484f93666158553ffdc1200976481322..eccd1b92bddf6322ad3b95d4ba00934fe873ac20 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -6,6 +6,7 @@ import javax.annotation.Nullable; - - import io.multipaper.shreddedpaper.region.RegionPos; - import io.multipaper.shreddedpaper.threading.ShreddedPaperChunkTicker; -+import io.papermc.paper.util.TickThread; - import net.minecraft.advancements.CriteriaTriggers; - import net.minecraft.core.BlockPos; - import net.minecraft.core.Direction; -@@ -135,7 +136,7 @@ public class ServerPlayerGameMode { - BlockState iblockdata; - - if (this.hasDelayedDestroy) { -- iblockdata = this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks -+ iblockdata = !TickThread.isTickThreadFor(this.level, this.delayedDestroyPos) ? null : this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks // ShreddedPaper - Don't allow digging into chunks outside our region - if (iblockdata == null || iblockdata.isAir()) { // Paper - Don't allow digging into unloaded chunks - this.hasDelayedDestroy = false; - } else { -@@ -148,7 +149,7 @@ public class ServerPlayerGameMode { - } - } else if (this.isDestroyingBlock) { - // Paper start - Don't allow digging into unloaded chunks; don't want to do same logic as above, return instead -- iblockdata = this.level.getBlockStateIfLoaded(this.destroyPos); -+ iblockdata = !TickThread.isTickThreadFor(this.level, this.destroyPos) ? null : this.level.getBlockStateIfLoaded(this.destroyPos); // ShreddedPaper - Don't allow digging into chunks outside our region - if (iblockdata == null) { - this.isDestroyingBlock = false; - return; diff --git a/patches_removed/0061-Optimization-Use-lazyExecute-if-we-aren-t-flushing.patch b/patches_removed/0061-Optimization-Use-lazyExecute-if-we-aren-t-flushing.patch deleted file mode 100644 index 03196d3..0000000 --- a/patches_removed/0061-Optimization-Use-lazyExecute-if-we-aren-t-flushing.patch +++ /dev/null @@ -1,41 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Wed, 16 Jul 2025 18:42:23 +0900 -Subject: [PATCH] Optimization: Use lazyExecute if we aren't flushing - - -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index d2494f38d1208e2e21daf7c45ccbfe07f3e02f7c..bb28c13015a2aa7622d2f762dc797eb0344400db 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -4,6 +4,7 @@ import com.google.common.base.Suppliers; - import com.google.common.collect.Queues; - import com.google.common.util.concurrent.ThreadFactoryBuilder; - import com.mojang.logging.LogUtils; -+import io.multipaper.shreddedpaper.config.ShreddedPaperConfiguration; - import io.netty.bootstrap.Bootstrap; - import io.netty.channel.Channel; - import io.netty.channel.ChannelException; -@@ -43,6 +44,7 @@ import java.util.function.Supplier; - import javax.annotation.Nullable; - import javax.crypto.Cipher; - -+import io.netty.util.concurrent.AbstractEventExecutor; - import io.papermc.paper.util.TickThread; - import net.minecraft.SharedConstants; - import net.minecraft.Util; -@@ -491,6 +493,14 @@ public class Connection extends SimpleChannelInboundHandler> { - if (this.channel.eventLoop().inEventLoop()) { - this.doSendPacket(packet, callbacks, flush); - } else { -+ // ShreddedPaper start - Use lazyExecute if we aren't flushing -+ if (ShreddedPaperConfiguration.get().optimizations.useLazyExecuteWhenNotFlushing && !flush) { -+ ((AbstractEventExecutor) this.channel.eventLoop()).lazyExecute(() -> { -+ this.doSendPacket(packet, callbacks, flush); -+ }); -+ return; -+ } -+ // ShreddedPaper end - Use lazyExecute if we aren't flushing - this.channel.eventLoop().execute(() -> { - this.doSendPacket(packet, callbacks, flush); - }); diff --git a/patches_removed/0069-Optimization-Load-chunk-before-joining-player.patch b/patches_removed/0069-Optimization-Load-chunk-before-joining-player.patch deleted file mode 100644 index bde7ac9..0000000 --- a/patches_removed/0069-Optimization-Load-chunk-before-joining-player.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Sat, 25 Oct 2025 19:24:51 +0900 -Subject: [PATCH] Optimization: Load chunk before joining player - - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index dc99d088d6d915de789a1215df810a4a86212e42..973110b208a4026fd296663f58cd60960d9c6a1c 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -292,7 +292,7 @@ public abstract class PlayerList { - - Location loc = ev.getSpawnLocation(); - // ShreddedPaper start - join the region's thread -- ShreddedPaper.ensureSync(loc, () -> placeNewPlayer3(connection, player, clientData, worldserver1, loc, optional, s, s1)); -+ worldserver1.getWorld().getChunkAtAsync(loc).thenRun(() -> placeNewPlayer3(connection, player, clientData, worldserver1, loc, optional, s, s1)); // ShreddedPaper - ensure spawn chunk is loaded - } - - public void placeNewPlayer3(Connection connection, ServerPlayer player, CommonListenerCookie clientData, ServerLevel worldserver1, Location loc, Optional optional, String s, String s1) { diff --git a/patches_removed/0071-Fix-Concurrent-modification-when-removing-player.patch b/patches_removed/0071-Fix-Concurrent-modification-when-removing-player.patch deleted file mode 100644 index 1ef71a1..0000000 --- a/patches_removed/0071-Fix-Concurrent-modification-when-removing-player.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Thu, 20 Nov 2025 15:38:21 +0900 -Subject: [PATCH] Fix: Concurrent modification when removing player - - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 973110b208a4026fd296663f58cd60960d9c6a1c..66cb6ad79c1910dd86f61a68adaf1ea00e95ce49 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -702,8 +702,7 @@ public abstract class PlayerList { - // CraftBukkit start - // this.broadcastAll(new ClientboundPlayerInfoRemovePacket(List.of(entityplayer.getUUID()))); - ClientboundPlayerInfoRemovePacket packet = new ClientboundPlayerInfoRemovePacket(List.of(entityplayer.getUUID())); -- for (int i = 0; i < this.players.size(); i++) { -- ServerPlayer entityplayer2 = (ServerPlayer) this.players.get(i); -+ for (ServerPlayer entityplayer2 : this.players) { // ShreddedPaper - Fix concurrent modification - - if (entityplayer2.getBukkitEntity().canSee(entityplayer.getBukkitEntity())) { - entityplayer2.connection.send(packet); diff --git a/patches_removed/X0016-EntityLookup-accessibleEntities.patch b/patches_removed/X0016-EntityLookup-accessibleEntities.patch deleted file mode 100644 index 68f7fa5..0000000 --- a/patches_removed/X0016-EntityLookup-accessibleEntities.patch +++ /dev/null @@ -1,73 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Fri, 24 May 2024 00:03:50 +0900 -Subject: [PATCH] EntityLookup accessibleEntities - -Use a synchronized block as data is only read rarely for debug reports - -diff --git a/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java b/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java -index 15ee41452992714108efe53b708b5a4e1da7c1ff..e22012b5854c9e7725780cf8f3173949202c1472 100644 ---- a/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java -+++ b/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java -@@ -191,11 +191,15 @@ public final class EntityLookup implements LevelEntityGetter { - - @Override - public Iterable getAll() { -+ synchronized (this.accessibleEntities) { // ShreddedPaper - return new ArrayIterable<>(this.accessibleEntities.getRawData(), 0, this.accessibleEntities.size()); -+ } // ShreddedPaper - } - - public Entity[] getAllCopy() { -+ synchronized (this.accessibleEntities) { // ShreddedPaper - return Arrays.copyOf(this.accessibleEntities.getRawData(), this.accessibleEntities.size(), Entity[].class); -+ } // ShreddedPaper - } - - @Override -@@ -277,7 +281,9 @@ public final class EntityLookup implements LevelEntityGetter { - if (newVisibility.ordinal() > oldVisibility.ordinal()) { - // status upgrade - if (!oldVisibility.isAccessible() && newVisibility.isAccessible()) { -+ synchronized (this.accessibleEntities) { // ShreddedPaper - this.accessibleEntities.add(entity); -+ } // ShreddedPaper - EntityLookup.this.worldCallback.onTrackingStart(entity); - } - -@@ -291,7 +297,9 @@ public final class EntityLookup implements LevelEntityGetter { - } - - if (oldVisibility.isAccessible() && !newVisibility.isAccessible()) { -+ synchronized (this.accessibleEntities) { // ShreddedPaper - this.accessibleEntities.remove(entity); -+ } // ShreddedPaper - EntityLookup.this.worldCallback.onTrackingEnd(entity); - } - } -diff --git a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java -index 777b789fdcdf297309cfb36fc7f77e3fdb6327ca..b35cd0b4434c8f0cf078fc1cdf7c683517dc6b6b 100644 ---- a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java -+++ b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java -@@ -103,6 +103,8 @@ public final class EntityCommand implements PaperSubcommand { - Map nonEntityTicking = Maps.newHashMap(); - ServerChunkCache chunkProviderServer = world.getChunkSource(); - world.getAllEntities().forEach(e -> { -+ if (e == null) return; // ShreddedPaper - Concurrent modification -+ - ResourceLocation key = EntityType.getKey(e.getType()); - - MutablePair> info = list.computeIfAbsent(key, k -> MutablePair.of(0, Maps.newHashMap())); -diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index 0f90a6803851eba51e164772c984b1cd1193d882..d483c94b8afd624af4c0c165a809d3a5853ad02e 100644 ---- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java -+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -@@ -80,6 +80,8 @@ public final class NaturalSpawner { - while (iterator.hasNext()) { - Entity entity = (Entity) iterator.next(); - -+ if (entity == null) continue; // ShreddedPaper - concurrent modification -+ - if (entity instanceof Mob entityinsentient) { - if (entityinsentient.isPersistenceRequired() || entityinsentient.requiresCustomPersistence()) { - continue; diff --git a/patches_removed/X0055-Moving-into-another-region.patch b/patches_removed/X0055-Moving-into-another-region.patch deleted file mode 100644 index 057250c..0000000 --- a/patches_removed/X0055-Moving-into-another-region.patch +++ /dev/null @@ -1,89 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Fri, 23 Aug 2024 01:03:30 +0900 -Subject: [PATCH] Moving into another region - - -diff --git a/src/main/java/io/multipaper/shreddedpaper/region/RegionPos.java b/src/main/java/io/multipaper/shreddedpaper/region/RegionPos.java -index c8caf9240e1aa5e434a47cbdf4b7bf68e4025586..c5fc229de6f086a0fe66d05380b612d2468d6079 100644 ---- a/src/main/java/io/multipaper/shreddedpaper/region/RegionPos.java -+++ b/src/main/java/io/multipaper/shreddedpaper/region/RegionPos.java -@@ -16,6 +16,7 @@ public class RegionPos { - public static final int REGION_SIZE; // eg 8 (for an 8x8 region) - public static final int REGION_SHIFT; // eg 3 (1 << 3 == 8) - public static final int REGION_SIZE_MASK; // eg 7 (9 % 8 == 9 & 7 == 1) -+ public static final int MAX_DISTANCE_SQR; - - static { - // desiredRegionSize = 7 -> shift = 3, size = 8, mask = 7 -@@ -43,6 +44,8 @@ public class RegionPos { - } - - LOGGER.info("Using region size: {}, shift={}, mask={}", REGION_SIZE, REGION_SHIFT, REGION_SIZE_MASK); -+ -+ MAX_DISTANCE_SQR = RegionPos.REGION_SIZE * 16 * RegionPos.REGION_SIZE * 16; - } - - public final int x; -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 6b2c2e2f782630a3b7e25532260cc61e02c8049f..cbed509e5ea85396279b5c410413a7fa70c9089d 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -1176,7 +1176,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - public void move(MoverType movementType, Vec3 movement) { - final Vec3 originalMovement = movement; // Paper - Expose pre-collision velocity - // Paper start - detailed watchdog information -- io.papermc.paper.util.TickThread.ensureTickThread("Cannot move an entity off-main"); -+ io.papermc.paper.util.TickThread.ensureTickThread(this, "Cannot move an entity off-main"); // ShreddedPaper - check source region -+ BlockPos newPos = new BlockPos((int) (this.getX() + movement.x), (int) (this.getY() + movement.y), (int) (this.getZ() + movement.z)); // ShreddedPaper - check destination -+ io.papermc.paper.util.TickThread.ensureTickThread((ServerLevel) this.level, newPos, "Cannot move an entity into off-main"); // ShreddedPaper - check destination region - synchronized (this.posLock) { - this.moveStartX = this.getX(); - this.moveStartY = this.getY(); -@@ -2271,7 +2273,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - delta = event.getKnockback(); - } -- this.setDeltaMovement(this.getDeltaMovement().add(delta.getX(), delta.getY(), delta.getZ())); -+ // ShreddedPaper start - limit push velocity -+ Vec3 newDelta = this.getDeltaMovement().add(delta.getX(), delta.getY(), delta.getZ()); -+ if (newDelta.lengthSqr() > RegionPos.MAX_DISTANCE_SQR) { -+ newDelta = newDelta.normalize().scale(RegionPos.REGION_SIZE * 16); -+ } -+ this.setDeltaMovement(newDelta); -+ // ShreddedPaper end - limit push velocity - // Paper end - Add EntityKnockbackByEntityEvent and EntityPushedByEntityAttackEvent - this.hasImpulse = true; - } -@@ -4780,6 +4788,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - public void setDeltaMovement(Vec3 velocity) { -+ // ShreddedPaper start - why is something setting the entity velocity to larger than one region... -+ if (velocity.horizontalDistanceSqr() > RegionPos.MAX_DISTANCE_SQR + 1 && velocity.horizontalDistanceSqr() > this.deltaMovement.horizontalDistanceSqr()) { -+ LOGGER.warn("Velocity is being set larger than the ShreddedPaper region size: {} for entity {}", velocity, this, new Exception("Velocity larger than region size")); -+ } -+ // ShreddedPaper end - why is something setting the entity velocity to larger than one region... - synchronized (this.posLock) { // Paper - this.deltaMovement = velocity; - } // Paper -diff --git a/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java b/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java -index fca3786d0a3f99a3e61e7a4b2251361276eff9d7..74f0577397c8665c9bea3f79775dc26c15543e62 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java -@@ -1,5 +1,6 @@ - package net.minecraft.world.entity.projectile; - -+import io.multipaper.shreddedpaper.region.RegionPos; - import net.minecraft.core.BlockPos; - import net.minecraft.core.particles.ParticleTypes; - import net.minecraft.nbt.CompoundTag; -@@ -137,6 +138,8 @@ public class EyeOfEnder extends Entity implements ItemSupplier { - - int i = this.getY() < this.ty ? 1 : -1; - -+ if (d6 > RegionPos.MAX_DISTANCE_SQR) d6 = RegionPos.MAX_DISTANCE_SQR; // ShreddedPaper - keep within a region -+ - vec3d = new Vec3(Math.cos((double) f1) * d6, d7 + ((double) i - d7) * 0.014999999664723873D, Math.sin((double) f1) * d6); - this.setDeltaMovement(vec3d); - } From a6a3bd66ae7cce2d6d382f6765f41dde27badab9 Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Mon, 5 Jan 2026 23:25:16 +0100 Subject: [PATCH 12/24] Remove obsolete/not-applicable patches from patches_removed These patches cannot be applied to 1.21.11: - X0017-World-gen: Marked as obsolete, functionality refactored - X0019-Multithreaded-WeakSeqLock: Target class (WeakSeqLock) doesn't exist in 1.21.11 - X0036-Entity-retirement-debug-log: Debug/experimental patch, no longer needed - 0058-Fix-EntityRemoveEventTest: Depends on Pufferfish optimization that doesn't exist --- ...EventTest-by-adding-missing-Bukkit-r.patch | 20 --------- patches_removed/X0017-World-gen.patch | 34 --------------- .../X0019-Multithreaded-WeakSeqLock.patch | 33 --------------- .../X0036-Entity-retirement-debug-log.patch | 42 ------------------- 4 files changed, 129 deletions(-) delete mode 100644 patches_removed/0058-Fix-EntityRemoveEventTest-by-adding-missing-Bukkit-r.patch delete mode 100644 patches_removed/X0017-World-gen.patch delete mode 100644 patches_removed/X0019-Multithreaded-WeakSeqLock.patch delete mode 100644 patches_removed/X0036-Entity-retirement-debug-log.patch diff --git a/patches_removed/0058-Fix-EntityRemoveEventTest-by-adding-missing-Bukkit-r.patch b/patches_removed/0058-Fix-EntityRemoveEventTest-by-adding-missing-Bukkit-r.patch deleted file mode 100644 index c10152e..0000000 --- a/patches_removed/0058-Fix-EntityRemoveEventTest-by-adding-missing-Bukkit-r.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: UnlimitedBytes -Date: Mon, 9 Jun 2025 01:04:56 +0200 -Subject: [PATCH] Fix EntityRemoveEventTest by adding missing Bukkit remove - cause to Projectile discard - - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -index a69cd350b6c1e66fc0e81917e23bccc1bc0ab75d..2b2c6cb02342b18d61596d194e024f10e8d1f54b 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -@@ -65,7 +65,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { - if (!isLoaded) { - if (Projectile.loadedThisTick > gg.pufferfish.pufferfish.PufferfishConfig.maxProjectileLoadsPerTick) { - if (++this.loadedLifetime > gg.pufferfish.pufferfish.PufferfishConfig.maxProjectileLoadsPerProjectile) { -- this.discard(); -+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // ShreddedPaper - add Bukkit remove cause - } - return; - } diff --git a/patches_removed/X0017-World-gen.patch b/patches_removed/X0017-World-gen.patch deleted file mode 100644 index f8665ed..0000000 --- a/patches_removed/X0017-World-gen.patch +++ /dev/null @@ -1,34 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Fri, 24 May 2024 16:44:47 +0900 -Subject: [PATCH] World gen - - -diff --git a/src/main/java/net/minecraft/server/level/PlayerRespawnLogic.java b/src/main/java/net/minecraft/server/level/PlayerRespawnLogic.java -index 5bfdd772f2e8933acca69ed62c04755a5655fa94..e1ee330b6fdd43114a1330677b1597bf437c1ed5 100644 ---- a/src/main/java/net/minecraft/server/level/PlayerRespawnLogic.java -+++ b/src/main/java/net/minecraft/server/level/PlayerRespawnLogic.java -@@ -10,6 +10,7 @@ import net.minecraft.world.level.block.Block; - import net.minecraft.world.level.block.state.BlockState; - import net.minecraft.world.level.chunk.LevelChunk; - import net.minecraft.world.level.levelgen.Heightmap; -+import io.multipaper.shreddedpaper.region.RegionPos; - - public class PlayerRespawnLogic { - @Nullable -@@ -48,6 +49,7 @@ public class PlayerRespawnLogic { - if (SharedConstants.debugVoidTerrain(chunkPos)) { - return null; - } else { -+ return world.chunkScheduler.getRegionLocker().lockRegion(RegionPos.forChunk(chunkPos), () -> { // ShreddedPaper - lock region - for (int i = chunkPos.getMinBlockX(); i <= chunkPos.getMaxBlockX(); i++) { - for (int j = chunkPos.getMinBlockZ(); j <= chunkPos.getMaxBlockZ(); j++) { - BlockPos blockPos = getOverworldRespawnPos(world, i, j); -@@ -58,6 +60,7 @@ public class PlayerRespawnLogic { - } - - return null; -+ }); // ShreddedPaper - } - } - } diff --git a/patches_removed/X0019-Multithreaded-WeakSeqLock.patch b/patches_removed/X0019-Multithreaded-WeakSeqLock.patch deleted file mode 100644 index 482ba1c..0000000 --- a/patches_removed/X0019-Multithreaded-WeakSeqLock.patch +++ /dev/null @@ -1,33 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Sat, 25 May 2024 02:33:24 +0900 -Subject: [PATCH] Multithreaded WeakSeqLock - - -diff --git a/src/main/java/com/destroystokyo/paper/util/concurrent/WeakSeqLock.java b/src/main/java/com/destroystokyo/paper/util/concurrent/WeakSeqLock.java -index 4029dc68cf35d63aa70c4a76c35bf65a7fc6358f..7d0cd938c9ce83845657a21a120d4a2a74376fc4 100644 ---- a/src/main/java/com/destroystokyo/paper/util/concurrent/WeakSeqLock.java -+++ b/src/main/java/com/destroystokyo/paper/util/concurrent/WeakSeqLock.java -@@ -16,8 +16,21 @@ public final class WeakSeqLock { - } - - public void acquireWrite() { -+ // ShreddedPaper start - Only one thread can hold the write lock at a time -+ int failures = 0; -+ long curr; -+ -+ for (curr = this.lock.get(); !this.canRead(curr) || !lock.compareAndSet(curr, curr + 1); curr = this.lock.get()) { -+ -+ if (++failures > 5_000) { -+ Thread.yield(); -+ } -+ -+ } -+ - // must be release-type write -- this.lock.lazySet(this.lock.get() + 1); -+ // this.lock.lazySet(this.lock.get() + 1); -+ // ShreddedPaper end - Only one thread can hold the write lock at a time - } - - public boolean canRead(final long read) { diff --git a/patches_removed/X0036-Entity-retirement-debug-log.patch b/patches_removed/X0036-Entity-retirement-debug-log.patch deleted file mode 100644 index d1d1c6a..0000000 --- a/patches_removed/X0036-Entity-retirement-debug-log.patch +++ /dev/null @@ -1,42 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Sat, 8 Jun 2024 05:14:47 +0900 -Subject: [PATCH] Entity retirement debug log - - -diff --git a/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java -index 62484ebf4550b05182f693a3180bbac5d5fd906d..9fb58f07997e0c4c336b246e518d7e97eb766ba5 100644 ---- a/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java -+++ b/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java -@@ -1,6 +1,7 @@ - package io.papermc.paper.threadedregions; - - import ca.spottedleaf.concurrentutil.util.Validate; -+import com.mojang.logging.LogUtils; - import io.papermc.paper.util.TickThread; - import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; - import net.minecraft.world.entity.Entity; -@@ -40,6 +41,7 @@ public final class EntityScheduler { - private static final record ScheduledTask(Consumer run, Consumer retired) {} - - private long tickCount = 0L; -+ private Exception retiredReason; // ShreddedPaper - private static final long RETIRED_TICK_COUNT = -1L; - private final Object stateLock = new Object(); - private final Long2ObjectOpenHashMap> oneTimeDelayed = new Long2ObjectOpenHashMap<>(); -@@ -66,6 +68,7 @@ public final class EntityScheduler { - throw new IllegalStateException("Already retired"); - } - this.tickCount = RETIRED_TICK_COUNT; -+ this.retiredReason = new Exception("Retired"); // ShreddedPaper - } - - final Entity thisEntity = this.entity.getHandleRaw(); -@@ -144,6 +147,7 @@ public final class EntityScheduler { - final List toRun; - synchronized (this.stateLock) { - if (this.tickCount == RETIRED_TICK_COUNT) { -+ LogUtils.getClassLogger().error("Tried to execute tick on entity, but was retired here: {}", this.entity.getHandle(), retiredReason); // ShreddedPaper - throw new IllegalStateException("Ticking retired scheduler"); - } - ++this.tickCount; From 42307c88a7fc1f73d263665b4ea82ee349e2ceab Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Mon, 5 Jan 2026 23:34:55 +0100 Subject: [PATCH 13/24] Port 0043: Ensure worlds are loaded on the global scheduler Adds thread safety checks to CraftServer.createWorld() to ensure worlds are only loaded from the global scheduler tick thread, not from async or region tick threads. --- ...s-are-loaded-on-the-global-scheduler.patch | 32 ------------------- .../bukkit/craftbukkit/CraftServer.java.patch | 9 ++++++ 2 files changed, 9 insertions(+), 32 deletions(-) delete mode 100644 patches_removed/0043-Ensure-worlds-are-loaded-on-the-global-scheduler.patch diff --git a/patches_removed/0043-Ensure-worlds-are-loaded-on-the-global-scheduler.patch b/patches_removed/0043-Ensure-worlds-are-loaded-on-the-global-scheduler.patch deleted file mode 100644 index 608edce..0000000 --- a/patches_removed/0043-Ensure-worlds-are-loaded-on-the-global-scheduler.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Wed, 3 Jul 2024 19:10:59 +0900 -Subject: [PATCH] Ensure worlds are loaded on the global scheduler - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 8179f9a24fb9926f67bc9b181082d2cae10b500b..7103b2bd575202c8d943f89eec82e7d8fffdc333 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -808,6 +808,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop Date: Mon, 5 Jan 2026 23:43:34 +0100 Subject: [PATCH 14/24] Port 0051: Bukkit API thread checks Adds thread safety checks to CraftChunk, CraftWorld, and CraftBlock to ensure chunk and block operations are only performed from the correct region tick thread: - CraftChunk.getHandle(): Check region ownership - CraftWorld.getChunkAt(): Check region ownership - CraftBlock: getNMS(), getData(), getType(), setBlockState() ensure tick thread for block position --- .../0051-Bukkit-API-thread-checks.patch | 93 ------------------- .../bukkit/craftbukkit/CraftChunk.java.patch | 19 ++++ .../bukkit/craftbukkit/CraftWorld.java.patch | 11 ++- .../craftbukkit/block/CraftBlock.java.patch | 39 ++++++++ 4 files changed, 68 insertions(+), 94 deletions(-) delete mode 100644 patches_removed/0051-Bukkit-API-thread-checks.patch create mode 100644 shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftChunk.java.patch diff --git a/patches_removed/0051-Bukkit-API-thread-checks.patch b/patches_removed/0051-Bukkit-API-thread-checks.patch deleted file mode 100644 index 8784708..0000000 --- a/patches_removed/0051-Bukkit-API-thread-checks.patch +++ /dev/null @@ -1,93 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Sat, 10 Aug 2024 21:21:57 +0900 -Subject: [PATCH] Bukkit API thread checks - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -index 92f1ea81b5e90529905d9c508aca18c31443ff6a..b3ebd6a29895777166174b40006177eaa5d751ee 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -@@ -9,6 +9,9 @@ import java.util.Objects; - import java.util.concurrent.locks.LockSupport; - import java.util.function.BooleanSupplier; - import java.util.function.Predicate; -+ -+import io.multipaper.shreddedpaper.threading.ShreddedPaperTickThread; -+import io.papermc.paper.util.TickThread; - import net.minecraft.core.BlockPos; - import net.minecraft.core.Holder; - import net.minecraft.core.Registry; -@@ -82,6 +85,7 @@ public class CraftChunk implements Chunk { - } - - public ChunkAccess getHandle(ChunkStatus chunkStatus) { -+ if (ShreddedPaperTickThread.isShreddedPaperTickThread() && !TickThread.isTickThreadFor(this.worldServer, this.x, this.z)) TickThread.failedTickThreadCheck("Cannot get chunk from a region that is not ours!", "world=" + this.worldServer.convertable.getLevelId() + ", chunkpos=[" + this.x + "," + this.z + "]"); // ShreddedPaper - regions - ChunkAccess chunkAccess = this.worldServer.getChunk(this.x, this.z, chunkStatus); - - // SPIGOT-7332: Get unwrapped extension -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 3723dd028800c81f3f392657f1b209884864c5ba..12091e6b2696f7adf5275988ecc4cdc51e6760be 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -7,6 +7,7 @@ import com.google.common.collect.ImmutableMap; - import com.mojang.datafixers.util.Pair; - import io.multipaper.shreddedpaper.ShreddedPaper; - import io.multipaper.shreddedpaper.region.RegionPos; -+import io.multipaper.shreddedpaper.threading.ShreddedPaperTickThread; - import io.multipaper.shreddedpaper.threading.SynchronousPluginExecution; - import io.papermc.paper.util.TickThread; - import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; -@@ -355,6 +356,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public Chunk getChunkAt(int x, int z) { -+ if (ShreddedPaperTickThread.isShreddedPaperTickThread() && !TickThread.isTickThreadFor(this.world, x, z)) TickThread.failedTickThreadCheck("Cannot get chunk from a region that is not ours!", "world=" + this.world.convertable.getLevelId() + ", chunkpos=[" + x + "," + z + "]"); // ShreddedPaper - regions - warnUnsafeChunk("getting a faraway chunk", x, z); // Paper - // Paper start - add ticket to hold chunk for a little while longer if plugin accesses it - net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index ed30feb916daecc1d9b9aaa854ac5b832aa59757..59f9d77239c03a56eb2dfad4599f32ec77894830 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -6,6 +6,8 @@ import java.util.Collection; - import java.util.Collections; - import java.util.List; - import java.util.stream.Collectors; -+ -+import io.papermc.paper.util.TickThread; - import net.minecraft.core.BlockPos; - import net.minecraft.core.Direction; - import net.minecraft.server.level.ServerLevel; -@@ -75,6 +77,7 @@ public class CraftBlock implements Block { - } - - public net.minecraft.world.level.block.state.BlockState getNMS() { -+ if (this.world instanceof ServerLevel serverLevel) TickThread.ensureTickThread(serverLevel, this.position, "Cannot get block asynchronously!"); - return this.world.getBlockState(this.position); - } - -@@ -162,6 +165,7 @@ public class CraftBlock implements Block { - - @Override - public byte getData() { -+ if (this.world instanceof ServerLevel serverLevel) TickThread.ensureTickThread(serverLevel, this.position, "Cannot get block asynchronously!"); - net.minecraft.world.level.block.state.BlockState blockData = this.world.getBlockState(this.position); - return CraftMagicNumbers.toLegacyData(blockData); - } -@@ -194,6 +198,7 @@ public class CraftBlock implements Block { - } - - boolean setTypeAndData(final net.minecraft.world.level.block.state.BlockState blockData, final boolean applyPhysics) { -+ if (this.world instanceof ServerLevel serverLevel) TickThread.ensureTickThread(serverLevel, this.position, "Cannot set block asynchronously!"); - return CraftBlock.setTypeAndData(this.world, this.position, this.getNMS(), blockData, applyPhysics); - } - -@@ -226,6 +231,7 @@ public class CraftBlock implements Block { - - @Override - public Material getType() { -+ if (this.world instanceof ServerLevel serverLevel) TickThread.ensureTickThread(serverLevel, this.position, "Cannot get block asynchronously!"); - return this.world.getBlockState(this.position).getBukkitMaterial(); // Paper - optimise getType calls - } - diff --git a/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftChunk.java.patch b/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftChunk.java.patch new file mode 100644 index 0000000..95a253d --- /dev/null +++ b/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftChunk.java.patch @@ -0,0 +1,19 @@ +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -3,6 +_,8 @@ + import com.google.common.base.Preconditions; + import com.google.common.base.Predicates; + import com.mojang.serialization.Codec; ++import ca.spottedleaf.moonrise.common.util.TickThread; ++import io.multipaper.shreddedpaper.threading.ShreddedPaperTickThread; + import io.papermc.paper.FeatureHooks; + import java.util.ArrayList; + import java.util.Arrays; +@@ -74,6 +_,7 @@ + } + + public ChunkAccess getHandle(ChunkStatus chunkStatus) { ++ if (ShreddedPaperTickThread.isShreddedPaperTickThread() && !TickThread.isTickThreadFor(this.level, this.x, this.z)) throw new IllegalStateException("Cannot get chunk from a region that is not ours! world=" + this.level.serverLevelData.getLevelName() + ", chunkpos=[" + this.x + "," + this.z + "]"); // ShreddedPaper - regions + // Paper start - chunk system + net.minecraft.world.level.chunk.LevelChunk full = this.level.getChunkIfLoaded(this.x, this.z); + if (full != null) { diff --git a/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftWorld.java.patch b/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftWorld.java.patch index c188d7a..d37303d 100644 --- a/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftWorld.java.patch +++ b/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftWorld.java.patch @@ -1,6 +1,6 @@ --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -2,10 +_,13 @@ +@@ -2,10 +_,14 @@ import ca.spottedleaf.moonrise.common.list.ReferenceList; import ca.spottedleaf.moonrise.common.util.CoordinateUtils; @@ -10,6 +10,7 @@ import com.google.common.collect.Lists; import com.mojang.datafixers.util.Pair; +import io.multipaper.shreddedpaper.region.RegionPos; ++import io.multipaper.shreddedpaper.threading.ShreddedPaperTickThread; +import io.multipaper.shreddedpaper.threading.SynchronousPluginExecution; import io.papermc.paper.FeatureHooks; import io.papermc.paper.raytracing.BlockCollisionMode; @@ -31,6 +32,14 @@ } @Override +@@ -383,6 +_,7 @@ + + @Override + public Chunk getChunkAt(int x, int z) { ++ if (ShreddedPaperTickThread.isShreddedPaperTickThread() && !TickThread.isTickThreadFor(this.world, x, z)) throw new IllegalStateException("Cannot get chunk from a region that is not ours! world=" + this.world.serverLevelData.getLevelName() + ", chunkpos=[" + x + "," + z + "]"); // ShreddedPaper - regions + warnUnsafeChunk("getting a faraway chunk", x, z); // Paper + net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) this.world.getChunk(x, z, ChunkStatus.FULL, true); + return new CraftChunk(chunk); @@ -413,10 +_,12 @@ @Override public boolean isChunkGenerated(int x, int z) { diff --git a/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java.patch b/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java.patch index a318adf..f107631 100644 --- a/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java.patch +++ b/shreddedpaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java.patch @@ -1,5 +1,44 @@ --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -1,5 +_,6 @@ + package org.bukkit.craftbukkit.block; + ++import ca.spottedleaf.moonrise.common.util.TickThread; + import com.google.common.base.Preconditions; + import java.util.ArrayList; + import java.util.Collection; +@@ -75,6 +_,7 @@ + } + + public net.minecraft.world.level.block.state.BlockState getNMS() { ++ if (this.world instanceof ServerLevel serverLevel) TickThread.ensureTickThread(serverLevel, this.position, "Cannot get block asynchronously!"); + return this.world.getBlockState(this.position); + } + +@@ -160,6 +_,7 @@ + + @Override + public byte getData() { ++ if (this.world instanceof ServerLevel serverLevel) TickThread.ensureTickThread(serverLevel, this.position, "Cannot get block asynchronously!"); + net.minecraft.world.level.block.state.BlockState state = this.world.getBlockState(this.position); + return CraftMagicNumbers.toLegacyData(state); + } +@@ -192,6 +_,7 @@ + } + + boolean setBlockState(final net.minecraft.world.level.block.state.BlockState state, final boolean applyPhysics) { ++ if (this.world instanceof ServerLevel serverLevel) TickThread.ensureTickThread(serverLevel, this.position, "Cannot set block asynchronously!"); + return CraftBlock.setBlockState(this.world, this.position, this.getNMS(), state, applyPhysics); + } + +@@ -227,6 +_,7 @@ + + @Override + public Material getType() { ++ if (this.world instanceof ServerLevel serverLevel) TickThread.ensureTickThread(serverLevel, this.position, "Cannot get block asynchronously!"); + return this.world.getBlockState(this.position).getBukkitMaterial(); // Paper - optimise getType calls + } + @@ -554,15 +_,15 @@ UseOnContext context = new UseOnContext(world, null, InteractionHand.MAIN_HAND, Items.BONE_MEAL.getDefaultInstance(), new BlockHitResult(Vec3.ZERO, direction, this.getPosition(), false)); From 004986356d6ffb7cb336085a735987ff8f5bac62 Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Mon, 5 Jan 2026 23:46:13 +0100 Subject: [PATCH 15/24] Port 0057: Add null safety check for server in PaperEventManager Most changes from the original patch were already applied. This ports the remaining null safety check for this.server in PaperEventManager callEvent() to prevent NPE during early initialization. --- ...-failures-and-add-null-safety-checks.patch | 64 ------------------- .../manager/PaperEventManager.java.patch | 9 +++ 2 files changed, 9 insertions(+), 64 deletions(-) delete mode 100644 patches_removed/0057-Fix-test-failures-and-add-null-safety-checks.patch diff --git a/patches_removed/0057-Fix-test-failures-and-add-null-safety-checks.patch b/patches_removed/0057-Fix-test-failures-and-add-null-safety-checks.patch deleted file mode 100644 index 22fc76a..0000000 --- a/patches_removed/0057-Fix-test-failures-and-add-null-safety-checks.patch +++ /dev/null @@ -1,64 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: UnlimitedBytes -Date: Mon, 9 Jun 2025 00:56:34 +0200 -Subject: [PATCH] Fix test failures and add null safety checks - -Add null safety check for ShreddedPaperConfiguration in SynchronousPluginExecution -Add null safety check for server in PaperEventManager.callEvent -Add missing EntityRemoveEvent.Cause to entity discard calls in Entity.tick and FireworkRocketEntity.tick - -Fixes SyntheticEventTest and EntityRemoveEventTest failures. - -diff --git a/src/main/java/io/multipaper/shreddedpaper/threading/SynchronousPluginExecution.java b/src/main/java/io/multipaper/shreddedpaper/threading/SynchronousPluginExecution.java -index 1740789c109533b952c4407175eb733f7edc0290..d17f397c8c523063cf25b4ea410af5d8e67557ab 100644 ---- a/src/main/java/io/multipaper/shreddedpaper/threading/SynchronousPluginExecution.java -+++ b/src/main/java/io/multipaper/shreddedpaper/threading/SynchronousPluginExecution.java -@@ -42,7 +42,8 @@ public class SynchronousPluginExecution { - } - - public static void execute(Plugin plugin, RunnableWithException runnable) throws Exception { -- if (plugin == null || !ShreddedPaperConfiguration.get().multithreading.runUnsupportedPluginsInSync || plugin.getDescription().isFoliaSupported() || TickThread.isShutdownThread()) { -+ ShreddedPaperConfiguration config = ShreddedPaperConfiguration.get(); -+ if (plugin == null || config == null || !config.multithreading.runUnsupportedPluginsInSync || plugin.getDescription().isFoliaSupported() || TickThread.isShutdownThread()) { - // Multi-thread safe plugin, run it straight away - runnable.run(); - return; -diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java -index 143638f994a842472e827cb9b4efc160a7235aa4..17d1e11fbb68ddf30d0ccf09216ffb506e532b28 100644 ---- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java -+++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java -@@ -42,7 +42,7 @@ class PaperEventManager { - public void callEvent(@NotNull Event event) { - if (event.isAsynchronous() && (Thread.currentThread() instanceof TickThread || ShreddedPaperTickThread.isShreddedPaperTickThread())) { - throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously."); -- } else if (!event.isAsynchronous() && !this.server.isPrimaryThread() && !this.server.isStopping()) { -+ } else if (!event.isAsynchronous() && this.server != null && !this.server.isPrimaryThread() && !this.server.isStopping()) { - throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); - } - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index f45ae96bdb13e8d697fc48a50f3e405a3b681a73..e13e62e2cb33c3bb4a02912e34b4c011d08d6d51 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -902,7 +902,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - public void tick() { - // Pufferfish start - entity TTL - if (type != EntityType.PLAYER && type.ttl >= 0 && this.tickCount >= type.ttl) { -- discard(); -+ discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // ShreddedPaper - add Bukkit remove cause - return; - } - // Pufferfish end - entity TTL -diff --git a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java -index 4671f34ba2796c1284af5bd9b2d2edfe37869ad6..15f44a51cbaf66b39ba211eadb1567e346ab4ac0 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java -@@ -153,7 +153,7 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier { - - // ShreddedPaper start - remove firework rocket if entity teleported away - if (!TickThread.isTickThreadFor((ServerLevel) this.level(), new Vec3(this.attachedToEntity.getX() + vec3d.x, this.attachedToEntity.getY() + vec3d.y, this.attachedToEntity.getZ() + vec3d.z))) { -- this.discard(); -+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); // ShreddedPaper - add Bukkit remove cause - return; - } - // ShreddedPaper end - remove firework rocket if entity teleported away diff --git a/shreddedpaper-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java.patch b/shreddedpaper-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java.patch index cf11fee..358dc0b 100644 --- a/shreddedpaper-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java.patch +++ b/shreddedpaper-server/paper-patches/files/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java.patch @@ -8,6 +8,15 @@ import org.bukkit.Server; import org.bukkit.Warning; import org.bukkit.event.Event; +@@ -38,7 +_,7 @@ + public void callEvent(@NotNull Event event) { + if (event.isAsynchronous() && this.server.isPrimaryThread()) { + throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously."); +- } else if (!event.isAsynchronous() && !this.server.isPrimaryThread() && !this.server.isStopping()) { ++ } else if (!event.isAsynchronous() && this.server != null && !this.server.isPrimaryThread() && !this.server.isStopping()) { + throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); + } + @@ -51,7 +_,15 @@ } From debf02541702d538521b5ac307d2b0bcafd82ae1 Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Mon, 5 Jan 2026 23:49:50 +0100 Subject: [PATCH 16/24] Port 0059: seenBy thread safety Adds ReentrantLock-based thread safety to TrackedEntity's seenBy set operations in ChunkMap to prevent ConcurrentModificationException when multiple threads access entity tracking data. --- .../0059-seenBy-thread-safety.patch | 98 ---------------- .../server/level/ChunkMap.java.patch | 106 +++++++++++++++++- 2 files changed, 104 insertions(+), 100 deletions(-) delete mode 100644 patches_removed/0059-seenBy-thread-safety.patch diff --git a/patches_removed/0059-seenBy-thread-safety.patch b/patches_removed/0059-seenBy-thread-safety.patch deleted file mode 100644 index 9d4b27b..0000000 --- a/patches_removed/0059-seenBy-thread-safety.patch +++ /dev/null @@ -1,98 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Fri, 19 Sep 2025 00:22:11 +0900 -Subject: [PATCH] seenBy thread safety - - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 9178bb86c11140b1f8813e2c05efdb384d9ae5c3..192f0fcfe9415b4ccdf2d48cfe7ac73cc524de54 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -46,6 +46,7 @@ import java.util.concurrent.CompletionException; - import java.util.concurrent.ConcurrentHashMap; - import java.util.concurrent.Executor; - import java.util.concurrent.atomic.AtomicInteger; -+import java.util.concurrent.locks.ReentrantLock; - import java.util.function.BooleanSupplier; - import java.util.function.Consumer; - import java.util.function.IntFunction; -@@ -1333,6 +1334,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - final Entity entity; - private final int range; - SectionPos lastSectionPos; -+ private final ReentrantLock seenByLock = new ReentrantLock(); // ShreededPaper - seenBy thread safety - public final Set seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl - - public TrackedEntity(final Entity entity, final int i, final int j, final boolean flag) { -@@ -1370,11 +1372,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // stuff could have been removed, so we need to check the trackedPlayers set - // for players that were removed - -+ seenByLock.lock(); try { // ShreededPaper - seenBy thread safety - for (ServerPlayerConnection conn : this.seenBy.toArray(new ServerPlayerConnection[0])) { // avoid CME - if (newTrackerCandidates == null || !newTrackerCandidates.contains(conn.getPlayer())) { - this.updatePlayer(conn.getPlayer()); - } - } -+ } finally { seenByLock.unlock(); } // ShreededPaper - seenBy thread safety - } - // Paper end - use distance map to optimise tracker - -@@ -1387,6 +1391,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - public void broadcast(Packet packet) { -+ seenByLock.lock(); try { // ShreededPaper - seenBy thread safety - Iterator iterator = this.seenBy.iterator(); - - while (iterator.hasNext()) { -@@ -1395,6 +1400,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - serverplayerconnection.send(packet); - } - -+ } finally { seenByLock.unlock(); } // ShreededPaper - seenBy thread safety - } - - public void broadcastAndSend(Packet packet) { -@@ -1406,6 +1412,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - public void broadcastRemoved() { -+ seenByLock.lock(); try { // ShreededPaper - seenBy thread safety - Iterator iterator = this.seenBy.iterator(); - - while (iterator.hasNext()) { -@@ -1414,14 +1421,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.serverEntity.removePairing(serverplayerconnection.getPlayer()); - } - -+ } finally { seenByLock.unlock(); } // ShreededPaper - seenBy thread safety - } - - public void removePlayer(ServerPlayer player) { -+ seenByLock.lock(); try { // ShreededPaper - seenBy thread safety - org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot - if (this.seenBy.remove(player.connection)) { - this.serverEntity.removePairing(player); - } - -+ } finally { seenByLock.unlock(); } // ShreededPaper - seenBy thread safety - } - - public void updatePlayer(ServerPlayer player) { -@@ -1452,6 +1462,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - flag = false; - } - // CraftBukkit end -+ seenByLock.lock(); try { // ShreededPaper - seenBy thread safety - if (flag) { - if (this.seenBy.add(player.connection)) { - // Paper start - entity tracking events -@@ -1464,6 +1475,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } else if (this.seenBy.remove(player.connection)) { - this.serverEntity.removePairing(player); - } -+ } finally { seenByLock.unlock(); } // ShreededPaper - seenBy thread safety - - } - } diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch index b117bf5..820619a 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch @@ -8,14 +8,16 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ByteMap; -@@ -36,6 +_,7 @@ +@@ -36,7 +_,9 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; ++import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BooleanSupplier; + import java.util.function.Consumer; @@ -139,7 +_,8 @@ public final ChunkMap.DistanceManager distanceManager; private final String storageName; @@ -311,7 +313,15 @@ if (trackedEntity.seenBy.contains(player.connection)) { action.accept(trackedEntity.entity); } -@@ -1241,8 +_,10 @@ +@@ -1213,6 +_,7 @@ + private final int range; + SectionPos lastSectionPos; + public final Set seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl ++ public final ReentrantLock seenByLock = new ReentrantLock(); // ShreddedPaper - thread safety for seenBy + + // Paper start - optimise entity tracker + private long lastChunkUpdate = -1L; +@@ -1241,18 +_,25 @@ final ServerPlayer[] playersRaw = players.getRawDataUnchecked(); for (int i = 0, len = players.size(); i < len; ++i) { @@ -322,3 +332,95 @@ } if (lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk) { + // need to purge any players possible not in the chunk list ++ // ShreddedPaper start - thread safety for seenBy ++ this.seenByLock.lock(); ++ try { + for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { + final ServerPlayer player = conn.getPlayer(); + if (!players.contains(player)) { + this.removePlayer(player); + } + } ++ } finally { this.seenByLock.unlock(); } ++ // ShreddedPaper end - thread safety for seenBy + } + } + +@@ -1316,9 +_,14 @@ + + @Override + public void sendToTrackingPlayers(Packet packet) { ++ // ShreddedPaper start - thread safety for seenBy ++ this.seenByLock.lock(); ++ try { + for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { + serverPlayerConnection.send(packet); + } ++ } finally { this.seenByLock.unlock(); } ++ // ShreddedPaper end - thread safety for seenBy + } + + @Override +@@ -1331,27 +_,42 @@ + + @Override + public void sendToTrackingPlayersFiltered(Packet packet, Predicate filter) { ++ // ShreddedPaper start - thread safety for seenBy ++ this.seenByLock.lock(); ++ try { + for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { + if (filter.test(serverPlayerConnection.getPlayer())) { + serverPlayerConnection.send(packet); + } + } ++ } finally { this.seenByLock.unlock(); } ++ // ShreddedPaper end - thread safety for seenBy + } + + public void broadcastRemoved() { ++ // ShreddedPaper start - thread safety for seenBy ++ this.seenByLock.lock(); ++ try { + for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { + this.serverEntity.removePairing(serverPlayerConnection.getPlayer()); + } ++ } finally { this.seenByLock.unlock(); } ++ // ShreddedPaper end - thread safety for seenBy + } + + public void removePlayer(ServerPlayer player) { + org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot ++ // ShreddedPaper start - thread safety for seenBy ++ this.seenByLock.lock(); ++ try { + if (this.seenBy.remove(player.connection)) { + this.serverEntity.removePairing(player); + if (this.seenBy.isEmpty()) { + ChunkMap.this.level.debugSynchronizers().dropEntity(this.entity); + } + } ++ } finally { this.seenByLock.unlock(); } ++ // ShreddedPaper end - thread safety for seenBy + } + + public void updatePlayer(ServerPlayer player) { +@@ -1383,6 +_,9 @@ + } + // CraftBukkit end + if (flag) { ++ // ShreddedPaper start - thread safety for seenBy ++ this.seenByLock.lock(); ++ try { + if (this.seenBy.add(player.connection)) { + // Paper start - entity tracking events + if (io.papermc.paper.event.player.PlayerTrackEntityEvent.getHandlerList().getRegisteredListeners().length == 0 || new io.papermc.paper.event.player.PlayerTrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent()) { +@@ -1396,6 +_,8 @@ + // Paper end - entity tracking events + this.serverEntity.onPlayerAdd(); // Paper - fix desync when a player is added to the tracker + } ++ } finally { this.seenByLock.unlock(); } ++ // ShreddedPaper end - thread safety for seenBy + } else { + this.removePlayer(player); + } From d79f468a81050aacf744363e94903086deea9d07 Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Tue, 6 Jan 2026 00:05:07 +0100 Subject: [PATCH 17/24] Port 0064: Maximum trackers per entity optimization Adds tracker update frequency optimization and maximum trackers per entity limit. --- ...mization-maximum-trackers-per-entity.patch | 200 ------------------ .../server/level/ChunkMap.java.patch | 138 +++++++++++- .../server/level/ServerPlayer.java.patch | 8 + 3 files changed, 141 insertions(+), 205 deletions(-) delete mode 100644 patches_removed/0064-Optimization-maximum-trackers-per-entity.patch diff --git a/patches_removed/0064-Optimization-maximum-trackers-per-entity.patch b/patches_removed/0064-Optimization-maximum-trackers-per-entity.patch deleted file mode 100644 index 2089a62..0000000 --- a/patches_removed/0064-Optimization-maximum-trackers-per-entity.patch +++ /dev/null @@ -1,200 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Fri, 18 Jul 2025 14:24:19 +0900 -Subject: [PATCH] Optimization: maximum trackers per entity - - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 192f0fcfe9415b4ccdf2d48cfe7ac73cc524de54..456b8450c0c55f1213701ed53c5aa9f8fbc56518 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -13,6 +13,8 @@ import com.mojang.datafixers.DataFixer; - import com.mojang.logging.LogUtils; - import com.mojang.serialization.DataResult; - import com.mojang.serialization.JsonOps; -+import io.multipaper.shreddedpaper.config.ShreddedPaperConfiguration; -+import io.multipaper.shreddedpaper.threading.ShreddedPaperTickThread; - import io.multipaper.shreddedpaper.util.Int2ObjectMapWrapper; - import it.unimi.dsi.fastutil.ints.Int2ObjectMap; - import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -@@ -25,12 +27,15 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry; - import it.unimi.dsi.fastutil.longs.LongIterator; - import it.unimi.dsi.fastutil.longs.LongOpenHashSet; - import it.unimi.dsi.fastutil.longs.LongSet; -+import it.unimi.dsi.fastutil.objects.ObjectArrayList; - import it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator; - import it.unimi.dsi.fastutil.objects.ObjectIterator; - import java.io.IOException; - import java.io.Writer; - import java.nio.file.Path; - import java.util.ArrayList; -+import java.util.Collection; -+import java.util.ConcurrentModificationException; - import java.util.HashMap; - import java.util.Iterator; - import java.util.List; -@@ -40,6 +45,7 @@ import java.util.Objects; - import java.util.Optional; - import java.util.Queue; - import java.util.Set; -+import java.util.TreeSet; - import java.util.concurrent.CancellationException; - import java.util.concurrent.CompletableFuture; - import java.util.concurrent.CompletionException; -@@ -53,6 +59,8 @@ import java.util.function.IntFunction; - import java.util.function.IntSupplier; - import java.util.function.Supplier; - import javax.annotation.Nullable; -+ -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; - import net.minecraft.CrashReport; - import net.minecraft.CrashReportCategory; - import net.minecraft.ReportedException; -@@ -107,6 +115,7 @@ import net.minecraft.world.level.storage.DimensionDataStorage; - import net.minecraft.world.level.storage.LevelStorageSource; - import net.minecraft.world.phys.Vec3; - import org.apache.commons.lang3.mutable.MutableBoolean; -+import org.jetbrains.annotations.NotNull; - import org.slf4j.Logger; - - // CraftBukkit start -@@ -1126,6 +1135,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - ServerPlayer entityplayer = (ServerPlayer) entity; - - this.updatePlayerStatus(entityplayer, true); -+ if (true) return; // ShreddedPaper - handled elsewhere - Iterator objectiterator = this.entityConcurrentMap.values().iterator(); // ShreddedPaper - use entityConcurrentMap - - while (objectiterator.hasNext()) { -@@ -1351,6 +1361,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet oldTrackerCandidates = this.lastTrackerCandidates; - this.lastTrackerCandidates = newTrackerCandidates; - -+ boolean fullTrackerUpdate = (level.getGameTime() ^ (long) this.entity.getId()) % ShreddedPaperConfiguration.get().optimizations.trackerFullUpdateFrequency == 0L; // ShreddedPaper - trackerFullUpdateFrequency -+ if (fullTrackerUpdate && this.entity instanceof ServerPlayer player) player.hasMaximumTrackerBypassPermission = player.getBukkitEntity().hasPermission("shreddedpaper.maximumtrackerbypass"); // ShreddedPaper -+ if (this.lastTrackerCandidates != null && this.lastTrackerCandidates.size() > ShreddedPaperConfiguration.get().optimizations.maximumTrackersPerEntity) { updatePlayersLimitedAndOrdered(oldTrackerCandidates != newTrackerCandidates && (oldTrackerCandidates == null || newTrackerCandidates == null || oldTrackerCandidates.size() != newTrackerCandidates.size()), fullTrackerUpdate); return; } // ShreddedPaper - maximum trackers per entity -+ - if (newTrackerCandidates != null) { - Object[] rawData = newTrackerCandidates.getBackingSet(); - for (int i = 0, len = rawData.length; i < len; ++i) { -@@ -1359,10 +1373,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - continue; - } - ServerPlayer player = (ServerPlayer)raw; -- this.updatePlayer(player); -+ if (fullTrackerUpdate || !this.seenBy.contains(player.connection)) this.updatePlayer(player); // ShreddedPaper - trackerFullUpdateFrequency - } - } - -+ if (!fullTrackerUpdate) return; // ShreddedPaper - trackerFullUpdateFrequency -+ - if (oldTrackerCandidates == newTrackerCandidates) { - // this is likely the case. - // means there has been no range changes, so we can just use the above for tracking. -@@ -1382,6 +1398,94 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - // Paper end - use distance map to optimise tracker - -+ // ShreddedPaper start - maximum trackers per entity -+ private record DistancedServerPlayer(ServerPlayer serverPlayer, double distanceSqr, boolean hasBypassPerm) implements Comparable { -+ DistancedServerPlayer(ServerPlayer serverPlayer, Entity entity) { -+ this(serverPlayer, distanceBetween(serverPlayer, entity), serverPlayer.hasMaximumTrackerBypassPermission); -+ } -+ -+ private static double distanceBetween(ServerPlayer serverPlayer, Entity entity) { -+ double vec3d_dx = serverPlayer.getX() - entity.getX(); -+ double vec3d_dz = serverPlayer.getZ() - entity.getZ(); -+ return vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; -+ } -+ -+ @Override -+ public int compareTo(@NotNull ChunkMap.TrackedEntity.DistancedServerPlayer o) { -+ int compareResult = Double.compare(hasBypassPerm ? 0 : this.distanceSqr, o.hasBypassPerm ? 0 : o.distanceSqr); -+ return compareResult == 0 ? Integer.compare(this.serverPlayer.getId(), o.serverPlayer.getId()) : compareResult; -+ } -+ } -+ -+ public void updatePlayersLimitedAndOrdered(boolean hasTrackerCandidatesChanged, boolean fullTrackerUpdate) { -+ if (!hasTrackerCandidatesChanged && !fullTrackerUpdate) return; -+ -+ TreeSet playerSet = new TreeSet<>(); -+ Object[] rawData = this.lastTrackerCandidates.getBackingSet(); -+ -+ for (int index = 0, len = rawData.length; index < len; ++index) { -+ Object raw = rawData[index]; -+ if (!(raw instanceof ServerPlayer player)) { -+ continue; -+ } -+ if (player == this.entity) continue; -+ DistancedServerPlayer distancedPlayer = new DistancedServerPlayer(player, this.entity); -+ int i = ChunkMap.this.getPlayerViewDistance(player); -+ double d0 = (double) Math.min(this.getEffectiveRange(), i * 16); -+ double d2 = d0 * d0; -+ boolean flag = distancedPlayer.distanceSqr <= d2 && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); -+ // Paper start - Configurable entity tracking range by Y -+ if (flag && level.paperConfig().entities.trackingRangeY.enabled) { -+ double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1); -+ if (rangeY != -1) { -+ double vec3d_dy = player.getY() - this.entity.getY(); -+ flag = vec3d_dy * vec3d_dy <= rangeY * rangeY; -+ } -+ } -+ // Paper end - Configurable entity tracking range by Y -+ -+ // CraftBukkit start - respect vanish API -+ if (flag && !player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper - only consider hits -+ flag = false; -+ } -+ -+ if (flag) { -+ playerSet.add(distancedPlayer); -+ } -+ } -+ -+ seenByLock.lock(); -+ try { -+ Set canBeSeenBy = new ReferenceOpenHashSet<>(Math.min(ShreddedPaperConfiguration.get().optimizations.maximumTrackersPerEntity, playerSet.size())); -+ Iterator playerSetIterator = playerSet.iterator(); -+ int count = 0; -+ while (playerSetIterator.hasNext() && count++ < ShreddedPaperConfiguration.get().optimizations.maximumTrackersPerEntity) { // ShreddedPaper - maximum trackers per entity -+ DistancedServerPlayer distancedPlayer = playerSetIterator.next(); -+ ServerPlayer player = distancedPlayer.serverPlayer(); -+ canBeSeenBy.add(player); -+ if (this.seenBy.add(player.connection)) { -+ // Paper start - entity tracking events -+ if (io.papermc.paper.event.player.PlayerTrackEntityEvent.getHandlerList().getRegisteredListeners().length == 0 || new io.papermc.paper.event.player.PlayerTrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent()) { -+ this.serverEntity.addPairing(player); -+ } -+ // Paper end - entity tracking events -+ this.serverEntity.onPlayerAdd(); // Paper - fix desync when a player is added to the tracker -+ } -+ } -+ -+ if (this.seenBy.size() != canBeSeenBy.size()) { -+ for (ServerPlayerConnection conn : this.seenBy.toArray(new ServerPlayerConnection[0])) { // avoid CME -+ if (!canBeSeenBy.contains(conn.getPlayer()) && this.seenBy.remove(conn)) { -+ this.serverEntity.removePairing(conn.getPlayer()); -+ } -+ } -+ } -+ } finally { -+ seenByLock.unlock(); -+ } -+ } -+ // ShreddedPaper end - maximum trackers per entity -+ - public boolean equals(Object object) { - return object instanceof ChunkMap.TrackedEntity ? ((ChunkMap.TrackedEntity) object).entity.getId() == this.entity.getId() : false; - } -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index de42949f9754b73ca03873c7a97ccdaba0455a5a..26af494184eb07f71852e8e3fa6f7fcef1ce1c5a 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -257,6 +257,7 @@ public class ServerPlayer extends Player { - private BlockPos respawnPosition; - private boolean respawnForced; - private float respawnAngle; -+ public boolean hasMaximumTrackerBypassPermission; // ShreddedPaper - private final TextFilter textFilter; - private boolean textFilteringEnabled; - private boolean allowsListing; diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch index 820619a..4ce494c 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch @@ -1,14 +1,28 @@ --- a/net/minecraft/server/level/ChunkMap.java +++ b/net/minecraft/server/level/ChunkMap.java -@@ -8,6 +_,7 @@ +@@ -8,6 +_,8 @@ import com.mojang.datafixers.DataFixer; import com.mojang.logging.LogUtils; import com.mojang.serialization.MapCodec; ++import io.multipaper.shreddedpaper.config.ShreddedPaperConfiguration; +import io.multipaper.shreddedpaper.util.Int2ObjectMapWrapper; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ByteMap; -@@ -36,7 +_,9 @@ +@@ -20,6 +_,7 @@ + import it.unimi.dsi.fastutil.longs.LongOpenHashSet; + import it.unimi.dsi.fastutil.longs.LongSet; + import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry; ++import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; + import java.io.IOException; + import java.io.Writer; + import java.nio.file.Path; +@@ -32,11 +_,14 @@ + import java.util.Optional; + import java.util.Queue; + import java.util.Set; ++import java.util.TreeSet; + import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; @@ -18,6 +32,14 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BooleanSupplier; import java.util.function.Consumer; +@@ -105,6 +_,7 @@ + import net.minecraft.world.level.storage.LevelStorageSource; + import net.minecraft.world.phys.Vec3; + import org.apache.commons.lang3.mutable.MutableBoolean; ++import org.jetbrains.annotations.NotNull; + import org.jspecify.annotations.Nullable; + import org.slf4j.Logger; + @@ -139,7 +_,8 @@ public final ChunkMap.DistanceManager distanceManager; private final String storageName; @@ -191,9 +213,11 @@ // Paper start - optimise entity tracker if (((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity)entity).moonrise$getTrackedEntity() != null) { throw new IllegalStateException("Entity is already tracked"); -@@ -1013,7 +_,7 @@ +@@ -1012,8 +_,9 @@ + trackedEntity.updatePlayers(this.level.players()); if (entity instanceof ServerPlayer serverPlayer) { this.updatePlayerStatus(serverPlayer, true); ++ if (true) return; // ShreddedPaper - handled elsewhere - for (ChunkMap.TrackedEntity trackedEntity1 : this.entityMap.values()) { + for (ChunkMap.TrackedEntity trackedEntity1 : this.entityConcurrentMap.values()) { // ShreddedPaper - use entityConcurrentMap @@ -321,15 +345,27 @@ // Paper start - optimise entity tracker private long lastChunkUpdate = -1L; -@@ -1241,18 +_,25 @@ +@@ -1238,21 +_,36 @@ + this.lastChunkUpdate = currChunkUpdate; + this.lastTrackedChunk = chunk; + ++ // ShreddedPaper start - trackerFullUpdateFrequency ++ boolean fullTrackerUpdate = (level.getGameTime() ^ (long) this.entity.getId()) % ShreddedPaperConfiguration.get().optimizations.trackerFullUpdateFrequency == 0L; ++ if (fullTrackerUpdate && this.entity instanceof ServerPlayer player) player.hasMaximumTrackerBypassPermission = player.getBukkitEntity().hasPermission("shreddedpaper.maximumtrackerbypass"); ++ if (players.size() > ShreddedPaperConfiguration.get().optimizations.maximumTrackersPerEntity) { updatePlayersLimitedAndOrdered(players, lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk, fullTrackerUpdate); return; } ++ // ShreddedPaper end - trackerFullUpdateFrequency ++ final ServerPlayer[] playersRaw = players.getRawDataUnchecked(); for (int i = 0, len = players.size(); i < len; ++i) { + try { // ShreddedPaper - concurrent modification final ServerPlayer player = playersRaw[i]; - this.updatePlayer(player); +- this.updatePlayer(player); ++ if (fullTrackerUpdate || !this.seenBy.contains(player.connection)) this.updatePlayer(player); // ShreddedPaper - trackerFullUpdateFrequency + } catch (IndexOutOfBoundsException | NullPointerException ignored) {} // ShreddedPaper - concurrent modification } ++ ++ if (!fullTrackerUpdate) return; // ShreddedPaper - trackerFullUpdateFrequency if (lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk) { // need to purge any players possible not in the chunk list @@ -347,6 +383,98 @@ } } +@@ -1304,6 +_,91 @@ + this.lastSectionPos = SectionPos.of(entity); + } + ++ // ShreddedPaper start - maximum trackers per entity ++ private record DistancedServerPlayer(ServerPlayer serverPlayer, double distanceSqr, boolean hasBypassPerm) implements Comparable { ++ DistancedServerPlayer(ServerPlayer serverPlayer, Entity entity) { ++ this(serverPlayer, distanceBetween(serverPlayer, entity), serverPlayer.hasMaximumTrackerBypassPermission); ++ } ++ ++ private static double distanceBetween(ServerPlayer serverPlayer, Entity entity) { ++ double vec3d_dx = serverPlayer.getX() - entity.getX(); ++ double vec3d_dz = serverPlayer.getZ() - entity.getZ(); ++ return vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; ++ } ++ ++ @Override ++ public int compareTo(@NotNull ChunkMap.TrackedEntity.DistancedServerPlayer o) { ++ int compareResult = Double.compare(hasBypassPerm ? 0 : this.distanceSqr, o.hasBypassPerm ? 0 : o.distanceSqr); ++ return compareResult == 0 ? Integer.compare(this.serverPlayer.getId(), o.serverPlayer.getId()) : compareResult; ++ } ++ } ++ ++ public void updatePlayersLimitedAndOrdered(ca.spottedleaf.moonrise.common.list.ReferenceList players, boolean hasTrackerCandidatesChanged, boolean fullTrackerUpdate) { ++ if (!hasTrackerCandidatesChanged && !fullTrackerUpdate) return; ++ ++ TreeSet playerSet = new TreeSet<>(); ++ final ServerPlayer[] playersRaw = players.getRawDataUnchecked(); ++ ++ for (int index = 0, len = players.size(); index < len; ++index) { ++ ServerPlayer player = playersRaw[index]; ++ if (player == null || player == this.entity) continue; ++ DistancedServerPlayer distancedPlayer = new DistancedServerPlayer(player, this.entity); ++ int i = ChunkMap.this.getPlayerViewDistance(player); ++ double d0 = (double) Math.min(this.getEffectiveRange(), i * 16); ++ double d2 = d0 * d0; ++ boolean flag = distancedPlayer.distanceSqr <= d2 && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); ++ // Paper start - Configurable entity tracking range by Y ++ if (flag && level.paperConfig().entities.trackingRangeY.enabled) { ++ double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1); ++ if (rangeY != -1) { ++ double vec3d_dy = player.getY() - this.entity.getY(); ++ flag = vec3d_dy * vec3d_dy <= rangeY * rangeY; ++ } ++ } ++ // Paper end - Configurable entity tracking range by Y ++ ++ // CraftBukkit start - respect vanish API ++ if (flag && !player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper - only consider hits ++ flag = false; ++ } ++ ++ if (flag) { ++ playerSet.add(distancedPlayer); ++ } ++ } ++ ++ seenByLock.lock(); ++ try { ++ Set canBeSeenBy = new ReferenceOpenHashSet<>(Math.min(ShreddedPaperConfiguration.get().optimizations.maximumTrackersPerEntity, playerSet.size())); ++ java.util.Iterator playerSetIterator = playerSet.iterator(); ++ int count = 0; ++ while (playerSetIterator.hasNext() && count++ < ShreddedPaperConfiguration.get().optimizations.maximumTrackersPerEntity) { // ShreddedPaper - maximum trackers per entity ++ DistancedServerPlayer distancedPlayer = playerSetIterator.next(); ++ ServerPlayer player = distancedPlayer.serverPlayer(); ++ canBeSeenBy.add(player); ++ if (this.seenBy.add(player.connection)) { ++ // Paper start - entity tracking events ++ if (io.papermc.paper.event.player.PlayerTrackEntityEvent.getHandlerList().getRegisteredListeners().length == 0 || new io.papermc.paper.event.player.PlayerTrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent()) { ++ this.serverEntity.addPairing(player); ++ } ++ // Paper end - entity tracking events ++ this.serverEntity.onPlayerAdd(); // Paper - fix desync when a player is added to the tracker ++ } ++ } ++ ++ if (this.seenBy.size() != canBeSeenBy.size()) { ++ for (ServerPlayerConnection conn : this.seenBy.toArray(new ServerPlayerConnection[0])) { // avoid CME ++ if (!canBeSeenBy.contains(conn.getPlayer()) && this.seenBy.remove(conn)) { ++ this.serverEntity.removePairing(conn.getPlayer()); ++ } ++ } ++ } ++ } finally { ++ seenByLock.unlock(); ++ } ++ } ++ // ShreddedPaper end - maximum trackers per entity ++ + @Override + public boolean equals(Object other) { + return other instanceof ChunkMap.TrackedEntity && ((ChunkMap.TrackedEntity)other).entity.getId() == this.entity.getId(); @@ -1316,9 +_,14 @@ @Override diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch index d27ec5b..bfebf2f 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ServerPlayer.java.patch @@ -18,6 +18,14 @@ import net.minecraft.network.protocol.game.ClientboundOpenSignEditorPacket; import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket; import net.minecraft.network.protocol.game.ClientboundPlayerCombatEndPacket; +@@ -247,6 +_,7 @@ + private @Nullable Entity camera; + public boolean isChangingDimension; + public boolean seenCredits = false; ++ public boolean hasMaximumTrackerBypassPermission; // ShreddedPaper + private final ServerRecipeBook recipeBook; + private @Nullable Vec3 levitationStartPos; + private int levitationStartTime; @@ -259,6 +_,7 @@ private @Nullable Vec3 enteredLavaOnVehiclePosition; private SectionPos lastSectionPos = SectionPos.of(0, 0, 0); From 280a036bfeeb4588a752a4c7d15a219cf30f0f10 Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Tue, 6 Jan 2026 00:10:23 +0100 Subject: [PATCH 18/24] Port 0066: Command blocks synchronization Adds thread synchronization for command block output updates. --- patches_removed/0066-Command-blocks.patch | 40 ------------------- .../minecart/MinecartCommandBlock.java.patch | 13 ++++++ .../world/level/BaseCommandBlock.java.patch | 37 +++++++++++++++++ .../entity/CommandBlockEntity.java.patch | 14 +++++++ 4 files changed, 64 insertions(+), 40 deletions(-) delete mode 100644 patches_removed/0066-Command-blocks.patch create mode 100644 shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/vehicle/minecart/MinecartCommandBlock.java.patch create mode 100644 shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch create mode 100644 shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch diff --git a/patches_removed/0066-Command-blocks.patch b/patches_removed/0066-Command-blocks.patch deleted file mode 100644 index 7eb0c11..0000000 --- a/patches_removed/0066-Command-blocks.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Sun, 3 Aug 2025 15:24:04 +0900 -Subject: [PATCH] Command blocks - - -diff --git a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -index e6bfcc50cdf728216084bc00a5bb8b6b3b8f72e4..14fc959b1ec12a0edb67b4fc528fc2580056ed7c 100644 ---- a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -+++ b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -@@ -3,11 +3,14 @@ package net.minecraft.world.level; - import java.text.SimpleDateFormat; - import java.util.Date; - import javax.annotation.Nullable; -+ -+import io.multipaper.shreddedpaper.ShreddedPaper; - import net.minecraft.CrashReport; - import net.minecraft.CrashReportCategory; - import net.minecraft.ReportedException; - import net.minecraft.commands.CommandSource; - import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.core.BlockPos; - import net.minecraft.core.HolderLookup; - import net.minecraft.nbt.CompoundTag; - import net.minecraft.network.chat.CommonComponents; -@@ -178,12 +181,14 @@ public abstract class BaseCommandBlock implements CommandSource { - @Override - public void sendSystemMessage(Component message) { - if (this.trackOutput) { -+ ShreddedPaper.ensureSync(this.getLevel(), new BlockPos((int) this.getPosition().x(), (int) this.getPosition().y(), (int) this.getPosition().z()), () -> { // ShreddedPaper - run sync - org.spigotmc.AsyncCatcher.catchOp("sendSystemMessage to a command block"); // Paper - Don't broadcast messages to command blocks - SimpleDateFormat simpledateformat = BaseCommandBlock.TIME_FORMAT; - Date date = new Date(); - - this.lastOutput = Component.literal("[" + simpledateformat.format(date) + "] ").append(message); - this.onUpdated(); -+ }); // ShreddedPaper - run sync - } - - } diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/vehicle/minecart/MinecartCommandBlock.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/vehicle/minecart/MinecartCommandBlock.java.patch new file mode 100644 index 0000000..cc3f412 --- /dev/null +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/entity/vehicle/minecart/MinecartCommandBlock.java.patch @@ -0,0 +1,13 @@ +--- a/net/minecraft/world/entity/vehicle/minecart/MinecartCommandBlock.java ++++ b/net/minecraft/world/entity/vehicle/minecart/MinecartCommandBlock.java +@@ -146,5 +_,10 @@ + return (net.minecraft.server.level.ServerLevel) MinecartCommandBlock.this.level(); + } + // CraftBukkit end ++ ++ @Override ++ public net.minecraft.core.BlockPos getBlockPos() { ++ return MinecartCommandBlock.this.blockPosition(); ++ } // ShreddedPaper + } + } diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch new file mode 100644 index 0000000..217a99f --- /dev/null +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch @@ -0,0 +1,37 @@ +--- a/net/minecraft/world/level/BaseCommandBlock.java ++++ b/net/minecraft/world/level/BaseCommandBlock.java +@@ -1,5 +_,6 @@ + package net.minecraft.world.level; + ++import io.multipaper.shreddedpaper.ShreddedPaper; + import java.time.ZonedDateTime; + import java.time.format.DateTimeFormatter; + import java.util.Locale; +@@ -9,6 +_,7 @@ + import net.minecraft.ReportedException; + import net.minecraft.commands.CommandSource; + import net.minecraft.commands.CommandSourceStack; ++import net.minecraft.core.BlockPos; + import net.minecraft.network.chat.CommonComponents; + import net.minecraft.network.chat.Component; + import net.minecraft.network.chat.ComponentSerialization; +@@ -34,6 +_,7 @@ + protected abstract org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper); + public abstract ServerLevel getLevel(); + // CraftBukkit end ++ public abstract BlockPos getBlockPos(); // ShreddedPaper + + public int getSuccessCount() { + return this.successCount; +@@ -206,9 +_,11 @@ + @Override + public void sendSystemMessage(Component message) { + if (this.trackOutput && !this.closed) { // Paper - add back source when output disabled ++ ShreddedPaper.ensureSync(this.level, BaseCommandBlock.this.getBlockPos(), () -> { // ShreddedPaper - run sync + org.spigotmc.AsyncCatcher.catchOp("sendSystemMessage to a command block"); // Paper - Don't broadcast messages to command blocks + BaseCommandBlock.this.lastOutput = Component.literal("[" + TIME_FORMAT.format(ZonedDateTime.now()) + "] ").append(message); + BaseCommandBlock.this.onUpdated(this.level); ++ }); // ShreddedPaper - run sync + } + } + diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch new file mode 100644 index 0000000..6454d82 --- /dev/null +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch @@ -0,0 +1,14 @@ +--- a/net/minecraft/world/level/block/entity/CommandBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/CommandBlockEntity.java +@@ -40,6 +_,11 @@ + // CraftBukkit end + + @Override ++ public BlockPos getBlockPos() { ++ return CommandBlockEntity.this.worldPosition; ++ } // ShreddedPaper ++ ++ @Override + public void setCommand(String command) { + super.setCommand(command); + CommandBlockEntity.this.setChanged(); From 9f5d0e43a4fffa0371aa3ff4cb26fa678ad636d2 Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Tue, 6 Jan 2026 00:15:56 +0100 Subject: [PATCH 19/24] Port 0067: BroadcastPacketEvent integration Adds event calls for packet broadcasting in ChunkMap, ServerLevel, and PlayerList. --- .../0067-BroadcastPacketEvent.patch | 143 ------------------ .../server/level/ChunkMap.java.patch | 4 +- .../server/level/ServerLevel.java.patch | 20 +++ .../server/players/PlayerList.java.patch | 8 + 4 files changed, 31 insertions(+), 144 deletions(-) delete mode 100644 patches_removed/0067-BroadcastPacketEvent.patch diff --git a/patches_removed/0067-BroadcastPacketEvent.patch b/patches_removed/0067-BroadcastPacketEvent.patch deleted file mode 100644 index 4783b7d..0000000 --- a/patches_removed/0067-BroadcastPacketEvent.patch +++ /dev/null @@ -1,143 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Sun, 10 Aug 2025 18:50:42 +0900 -Subject: [PATCH] BroadcastPacketEvent - - -diff --git a/src/main/java/io/multipaper/shreddedpaper/event/BroadcastPacketEvent.java b/src/main/java/io/multipaper/shreddedpaper/event/BroadcastPacketEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..33b427802ec5bdcbf6c8fed236fe718d6fee60e2 ---- /dev/null -+++ b/src/main/java/io/multipaper/shreddedpaper/event/BroadcastPacketEvent.java -@@ -0,0 +1,67 @@ -+package io.multipaper.shreddedpaper.event; -+ -+import net.minecraft.network.protocol.Packet; -+import org.bukkit.World; -+import org.bukkit.entity.Entity; -+import org.bukkit.event.Event; -+import org.bukkit.event.HandlerList; -+import org.jetbrains.annotations.ApiStatus; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+/** -+ * Called whenever a packet is broadcasted to players, but will not be sent to -+ * the player that initiates the packet. Will be called even if no players will -+ * receive the packet. -+ */ -+@ApiStatus.Experimental -+public class BroadcastPacketEvent extends Event { -+ -+ private static final HandlerList HANDLER_LIST = new HandlerList(); -+ -+ private final World world; -+ private final Packet packet; -+ private final Entity source; -+ -+ public BroadcastPacketEvent(@NotNull World world, @NotNull Packet packet, @Nullable Entity source) { -+ this.world = world; -+ this.packet = packet; -+ this.source = source; -+ } -+ -+ /** -+ * The world the packet is being broadcasted in. -+ */ -+ @NotNull -+ public World getWorld() { -+ return world; -+ } -+ -+ /** -+ * The packet being broadcasted. -+ */ -+ @NotNull -+ public Packet getPacket() { -+ return packet; -+ } -+ -+ /** -+ * Gets the entity that triggered this packet to be broadcasted. May be null -+ * if the packet was not triggered by an entity. -+ */ -+ @Nullable -+ public Entity getSource() { -+ return source; -+ } -+ -+ @NotNull -+ @Override -+ public HandlerList getHandlers() { -+ return HANDLER_LIST; -+ } -+ -+ @NotNull -+ public static HandlerList getHandlerList() { -+ return HANDLER_LIST; -+ } -+} -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 456b8450c0c55f1213701ed53c5aa9f8fbc56518..3026b340d162e5ad75488e35e25eb10f9d6d8d79 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -1505,6 +1505,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - } finally { seenByLock.unlock(); } // ShreededPaper - seenBy thread safety -+ -+ new io.multipaper.shreddedpaper.event.BroadcastPacketEvent(ChunkMap.this.level.getWorld(), packet, this.entity.getBukkitEntity()).callEvent(); // ShreddedPaper - BroadcastPacketEvent - } - - public void broadcastAndSend(Packet packet) { -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 5f99874eb7d8e33686c2fb662e1220d05883fd0a..0ecc3fa3d70a1180709a5f3b5b6f9a5b1b1c5322 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1845,7 +1845,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - - @Override - public void destroyBlockProgress(int entityId, BlockPos pos, int progress) { -- Iterator iterator = this.server.getPlayerList().getPlayers().iterator(); -+ Iterator iterator = this.players.iterator(); // ShreddedPaper - BroadcastPacketEvent - optimisation: use the level's players instead of all players - - // CraftBukkit start - Player entityhuman = null; -@@ -1864,6 +1864,8 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - // Paper end - Add BlockBreakProgressUpdateEvent - -+ ClientboundBlockDestructionPacket packet = new ClientboundBlockDestructionPacket(entityId, pos, progress); // ShreddedPaper - BroadcastPacketEvent - move packet creation out of loop -+ new io.multipaper.shreddedpaper.event.BroadcastPacketEvent(this.getWorld(), packet, entity == null ? null : entity.getBukkitEntity()).callEvent(); // ShreddedPaper - BroadcastPacketEvent - while (iterator.hasNext()) { - ServerPlayer entityplayer = (ServerPlayer) iterator.next(); - -@@ -1879,7 +1881,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - // CraftBukkit end - - if (d0 * d0 + d1 * d1 + d2 * d2 < 1024.0D) { -- entityplayer.connection.send(new ClientboundBlockDestructionPacket(entityId, pos, progress)); -+ entityplayer.connection.send(packet); // ShreddedPaper - BroadcastPacketEvent - move packet creation out of loop - } - } - } -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index ab96dcb873758f9d92eecb62b473b84dc9e295a2..dc99d088d6d915de789a1215df810a4a86212e42 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -76,6 +76,7 @@ import net.minecraft.server.MinecraftServer; - import net.minecraft.server.PlayerAdvancements; - import net.minecraft.server.RegistryLayer; - import net.minecraft.server.ServerScoreboard; -+import net.minecraft.server.level.ChunkMap; - import net.minecraft.server.level.ClientInformation; - import net.minecraft.server.level.ServerLevel; - import net.minecraft.server.level.ServerPlayer; -@@ -1333,6 +1334,7 @@ public abstract class PlayerList { - } - - public void broadcast(@Nullable net.minecraft.world.entity.player.Player player, double x, double y, double z, double distance, ResourceKey worldKey, Packet packet) { -+ if (player != null) new io.multipaper.shreddedpaper.event.BroadcastPacketEvent(this.getServer().getLevel(worldKey).getWorld(), packet, player.getBukkitEntity()).callEvent(); // ShreddedPaper - BroadcastPacketEvent - for (int i = 0; i < this.players.size(); ++i) { - ServerPlayer entityplayer = (ServerPlayer) this.players.get(i); - diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch index 4ce494c..8898600 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ChunkMap.java.patch @@ -475,7 +475,7 @@ @Override public boolean equals(Object other) { return other instanceof ChunkMap.TrackedEntity && ((ChunkMap.TrackedEntity)other).entity.getId() == this.entity.getId(); -@@ -1316,9 +_,14 @@ +@@ -1316,9 +_,16 @@ @Override public void sendToTrackingPlayers(Packet packet) { @@ -487,6 +487,8 @@ } + } finally { this.seenByLock.unlock(); } + // ShreddedPaper end - thread safety for seenBy ++ ++ new io.multipaper.shreddedpaper.event.BroadcastPacketEvent(ChunkMap.this.level.getWorld(), packet, this.entity.getBukkitEntity()).callEvent(); // ShreddedPaper - BroadcastPacketEvent } @Override diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ServerLevel.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ServerLevel.java.patch index 8971537..ccd73df 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ServerLevel.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/level/ServerLevel.java.patch @@ -453,6 +453,26 @@ return true; } // Paper end - capture all item additions to the world +@@ -1797,7 +_,9 @@ + .callEvent(); + } + // Paper end - Add BlockBreakProgressUpdateEvent +- for (ServerPlayer serverPlayer : this.server.getPlayerList().getPlayers()) { ++ ClientboundBlockDestructionPacket packet = new ClientboundBlockDestructionPacket(breakerId, pos, progress); // ShreddedPaper - BroadcastPacketEvent - move packet creation out of loop ++ new io.multipaper.shreddedpaper.event.BroadcastPacketEvent(this.getWorld(), packet, entity == null ? null : entity.getBukkitEntity()).callEvent(); // ShreddedPaper - BroadcastPacketEvent ++ for (ServerPlayer serverPlayer : this.players) { // ShreddedPaper - BroadcastPacketEvent - optimization: use the level's players instead of all players + if (serverPlayer.level() == this && serverPlayer.getId() != breakerId) { + double d = pos.getX() - serverPlayer.getX(); + double d1 = pos.getY() - serverPlayer.getY(); +@@ -1808,7 +_,7 @@ + } + // CraftBukkit end + if (d * d + d1 * d1 + d2 * d2 < 1024.0) { +- serverPlayer.connection.send(new ClientboundBlockDestructionPacket(breakerId, pos, progress)); ++ serverPlayer.connection.send(packet); // ShreddedPaper - BroadcastPacketEvent - move packet creation out of loop + } + } + } @@ -1903,7 +_,7 @@ @Override diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/players/PlayerList.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/players/PlayerList.java.patch index 5c640aa..c62f241 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/players/PlayerList.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/players/PlayerList.java.patch @@ -66,3 +66,11 @@ } public void sendActivePlayerEffects(ServerPlayer player) { +@@ -961,6 +_,7 @@ + } + + public void broadcast(@Nullable Player except, double x, double y, double z, double radius, ResourceKey dimension, Packet packet) { ++ if (except != null) new io.multipaper.shreddedpaper.event.BroadcastPacketEvent(this.getServer().getLevel(dimension).getWorld(), packet, except.getBukkitEntity()).callEvent(); // ShreddedPaper - BroadcastPacketEvent + for (int i = 0; i < this.players.size(); i++) { + ServerPlayer serverPlayer = this.players.get(i); + // CraftBukkit start - Test if player receiving packet can see the source of the packet From 0dd642871ee2678eb53d072453173b6bed246912 Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Tue, 6 Jan 2026 00:18:27 +0100 Subject: [PATCH 20/24] Port 0068: Write player saves async Adds asynchronous writing for player save data to improve performance. --- ...ptimization-Write-player-saves-async.patch | 55 ------------------- .../storage/PlayerDataStorage.java.patch | 53 ++++++++++++++++++ 2 files changed, 53 insertions(+), 55 deletions(-) delete mode 100644 patches_removed/0068-Optimization-Write-player-saves-async.patch create mode 100644 shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/storage/PlayerDataStorage.java.patch diff --git a/patches_removed/0068-Optimization-Write-player-saves-async.patch b/patches_removed/0068-Optimization-Write-player-saves-async.patch deleted file mode 100644 index 74f73e1..0000000 --- a/patches_removed/0068-Optimization-Write-player-saves-async.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: PureGero -Date: Fri, 19 Sep 2025 08:05:06 +0900 -Subject: [PATCH] Optimization: Write player saves async - - -diff --git a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -index 8ab7ca373a885fbe658013c9c6a2e38d32d77bb2..f6d88899f97d46584aabdb2b4719b714bd2d9662 100644 ---- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -+++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -@@ -3,6 +3,7 @@ package net.minecraft.world.level.storage; - import com.mojang.datafixers.DataFixer; - import com.mojang.logging.LogUtils; - import java.io.File; -+import java.io.IOException; - import java.nio.file.Files; - import java.nio.file.LinkOption; - import java.nio.file.Path; -@@ -10,6 +11,10 @@ import java.nio.file.StandardCopyOption; - import java.time.LocalDateTime; - import java.time.format.DateTimeFormatter; - import java.util.Optional; -+import java.util.concurrent.Executor; -+ -+import io.multipaper.shreddedpaper.config.ShreddedPaperConfiguration; -+import io.papermc.paper.util.TickThread; - import net.minecraft.Util; - import net.minecraft.nbt.CompoundTag; - import net.minecraft.nbt.NbtAccounter; -@@ -28,6 +33,8 @@ public class PlayerDataStorage { - protected final DataFixer fixerUpper; - private static final DateTimeFormatter FORMATTER = FileNameDateFormatter.create(); - -+ private static final Executor IO_WORKER = Util.ioPool(); // ShreddedPaper -+ - public PlayerDataStorage(LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer) { - this.fixerUpper = dataFixer; - this.playerDir = session.getLevelPath(LevelResource.PLAYER_DATA_DIR).toFile(); -@@ -38,6 +45,7 @@ public class PlayerDataStorage { - if (org.spigotmc.SpigotConfig.disablePlayerDataSaving) return; // Spigot - try { - CompoundTag nbttagcompound = player.saveWithoutId(new CompoundTag()); -+ Runnable r = () -> { try { // ShreddedPaper - write async - Path path = this.playerDir.toPath(); - Path path1 = Files.createTempFile(path, player.getStringUUID() + "-", ".dat"); - -@@ -46,6 +54,8 @@ public class PlayerDataStorage { - Path path3 = path.resolve(player.getStringUUID() + ".dat_old"); - - Util.safeReplaceFile(path2, path1, path3); -+ } catch (IOException e) { throw new RuntimeException(e); }}; // ShreddedPaper - write async -+ if (ShreddedPaperConfiguration.get().optimizations.writePlayerSavesAsync && !TickThread.isShutdownThread()) IO_WORKER.execute(r); else r.run(); // ShreddedPaper - write async - } catch (Exception exception) { - PlayerDataStorage.LOGGER.warn("Failed to save player data for {}", player.getScoreboardName(), exception); // Paper - Print exception - } diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/storage/PlayerDataStorage.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/storage/PlayerDataStorage.java.patch new file mode 100644 index 0000000..7df98b0 --- /dev/null +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/world/level/storage/PlayerDataStorage.java.patch @@ -0,0 +1,53 @@ +--- a/net/minecraft/world/level/storage/PlayerDataStorage.java ++++ b/net/minecraft/world/level/storage/PlayerDataStorage.java +@@ -2,12 +_,16 @@ + + import com.mojang.datafixers.DataFixer; + import com.mojang.logging.LogUtils; ++import ca.spottedleaf.moonrise.common.util.TickThread; ++import io.multipaper.shreddedpaper.config.ShreddedPaperConfiguration; + import java.io.File; ++import java.io.IOException; + import java.nio.file.Files; + import java.nio.file.Path; + import java.nio.file.StandardCopyOption; + import java.time.ZonedDateTime; + import java.util.Optional; ++import java.util.concurrent.Executor; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.nbt.NbtAccounter; + import net.minecraft.nbt.NbtIo; +@@ -23,6 +_,7 @@ + private static final Logger LOGGER = LogUtils.getLogger(); + private final File playerDir; + protected final DataFixer fixerUpper; ++ private static final Executor IO_WORKER = Util.ioPool(); // ShreddedPaper + + public PlayerDataStorage(LevelStorageSource.LevelStorageAccess levelStorageAccess, DataFixer fixerUpper) { + this.fixerUpper = fixerUpper; +@@ -35,13 +_,19 @@ + try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(player.problemPath(), LOGGER)) { + TagValueOutput tagValueOutput = TagValueOutput.createWithContext(scopedCollector, player.registryAccess()); + player.saveWithoutId(tagValueOutput); +- Path path = this.playerDir.toPath(); +- Path path1 = Files.createTempFile(path, player.getStringUUID() + "-", ".dat"); + CompoundTag compoundTag = tagValueOutput.buildResult(); +- NbtIo.writeCompressed(compoundTag, path1); +- Path path2 = path.resolve(player.getStringUUID() + ".dat"); +- Path path3 = path.resolve(player.getStringUUID() + ".dat_old"); +- Util.safeReplaceFile(path2, path1, path3); ++ // ShreddedPaper start - write async ++ Path path = this.playerDir.toPath(); ++ String playerUUID = player.getStringUUID(); ++ Runnable r = () -> { try { ++ Path path1 = Files.createTempFile(path, playerUUID + "-", ".dat"); ++ NbtIo.writeCompressed(compoundTag, path1); ++ Path path2 = path.resolve(playerUUID + ".dat"); ++ Path path3 = path.resolve(playerUUID + ".dat_old"); ++ Util.safeReplaceFile(path2, path1, path3); ++ } catch (IOException e) { throw new RuntimeException(e); }}; ++ if (ShreddedPaperConfiguration.get().optimizations.writePlayerSavesAsync && !TickThread.isShutdownThread()) IO_WORKER.execute(r); else r.run(); ++ // ShreddedPaper end - write async + } catch (Exception var11) { + LOGGER.warn("Failed to save player data for {}", player.getPlainTextName(), var11); // Paper - Print exception + } From d7faab1d27ca3def2eb79d076326a6a90c85d132 Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Tue, 6 Jan 2026 01:50:17 +0100 Subject: [PATCH 21/24] Fix SetBlockCommand cross-region block placement Wraps block-modifying operations in ShreddedPaper.ensureSync() to ensure they run on the correct region thread when a command block executes /setblock targeting coordinates in a different region. --- .../commands/SetBlockCommand.java.patch | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/SetBlockCommand.java.patch diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/SetBlockCommand.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/SetBlockCommand.java.patch new file mode 100644 index 0000000..49635b5 --- /dev/null +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/SetBlockCommand.java.patch @@ -0,0 +1,39 @@ +--- a/net/minecraft/server/commands/SetBlockCommand.java ++++ b/net/minecraft/server/commands/SetBlockCommand.java +@@ -3,6 +_,7 @@ + import com.mojang.brigadier.CommandDispatcher; + import com.mojang.brigadier.exceptions.CommandSyntaxException; + import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; ++import io.multipaper.shreddedpaper.ShreddedPaper; + import java.util.function.Predicate; + import net.minecraft.commands.CommandBuildContext; + import net.minecraft.commands.CommandSourceStack; +@@ -106,6 +_,8 @@ + } else if (filter != null && !filter.test(new BlockInWorld(level, pos, true))) { + throw ERROR_FAILED.create(); + } else { ++ // ShreddedPaper start - run on correct region thread ++ ShreddedPaper.ensureSync(level, pos, () -> { + boolean flag; + if (mode == SetBlockCommand.Mode.DESTROY) { + level.destroyBlock(pos, true); +@@ -117,15 +_,17 @@ + BlockState blockState = level.getBlockState(pos); + if (flag + && !block.place(level, pos, Block.UPDATE_CLIENTS | (strict ? Block.UPDATE_SKIP_ALL_SIDEEFFECTS : Block.UPDATE_SKIP_BLOCK_ENTITY_SIDEEFFECTS))) { +- throw ERROR_FAILED.create(); ++ source.sendFailure(Component.translatable("commands.setblock.failed")); // ShreddedPaper - send failure message instead of throwing + } else { + if (!strict) { + level.updateNeighboursOnBlockSet(pos, blockState); + } + + source.sendSuccess(() -> Component.translatable("commands.setblock.success", pos.getX(), pos.getY(), pos.getZ()), true); +- return 1; + } ++ }); ++ return 1; ++ // ShreddedPaper end - run on correct region thread + } + } + From 8f343ac1c4226acd0fa07ca87d695c50a3fa9771 Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Tue, 6 Jan 2026 02:18:44 +0100 Subject: [PATCH 22/24] Fix FillCommand cross-region block placement Wraps block-modifying operations in ShreddedPaper.ensureSync() to ensure they run on the correct region thread when a command block executes /fill targeting coordinates in a different region. --- .../server/commands/FillCommand.java.patch | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/FillCommand.java.patch diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/FillCommand.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/FillCommand.java.patch new file mode 100644 index 0000000..cf7e890 --- /dev/null +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/FillCommand.java.patch @@ -0,0 +1,78 @@ +--- a/net/minecraft/server/commands/FillCommand.java ++++ b/net/minecraft/server/commands/FillCommand.java +@@ -7,6 +_,7 @@ + import com.mojang.brigadier.exceptions.CommandSyntaxException; + import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType; + import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; ++import io.multipaper.shreddedpaper.ShreddedPaper; + import java.util.Collections; + import java.util.List; + import java.util.function.Predicate; +@@ -180,15 +_,17 @@ + if (i > i1) { + throw ERROR_AREA_TOO_LARGE.create(i1, i); + } else { +- record UpdatedPosition(BlockPos pos, BlockState oldState) { +- } +- +- List list = Lists.newArrayList(); + ServerLevel level = source.getLevel(); + if (level.isDebug()) { + throw ERROR_FAILED.create(); + } else { +- int i2 = 0; ++ // ShreddedPaper start - run on correct region thread ++ ShreddedPaper.ensureSync(level, new BlockPos(box.minX(), box.minY(), box.minZ()), () -> { ++ record UpdatedPosition(BlockPos pos, BlockState oldState) { ++ } ++ ++ List list = Lists.newArrayList(); ++ int[] counter = {0}; // ShreddedPaper - use array to allow modification in lambda + + for (BlockPos blockPos : BlockPos.betweenClosed(box.minX(), box.minY(), box.minZ(), box.maxX(), box.maxY(), box.maxZ())) { + if (filter == null || filter.test(new BlockInWorld(level, blockPos, true))) { +@@ -201,20 +_,20 @@ + BlockInput blockInput = mode.filter.filter(box, blockPos, block, level); + if (blockInput == null) { + if (flag) { +- i2++; ++ counter[0]++; + } + } else if (!blockInput.place( + level, blockPos, Block.UPDATE_CLIENTS | (strict ? Block.UPDATE_SKIP_ALL_SIDEEFFECTS : Block.UPDATE_SKIP_BLOCK_ENTITY_SIDEEFFECTS) + )) { + if (flag) { +- i2++; ++ counter[0]++; + } + } else { + if (!strict) { + list.add(new UpdatedPosition(blockPos.immutable(), blockState)); + } + +- i2++; ++ counter[0]++; + } + } + } +@@ -223,13 +_,15 @@ + level.updateNeighboursOnBlockSet(updatedPosition.pos, updatedPosition.oldState); + } + +- if (i2 == 0) { +- throw ERROR_FAILED.create(); ++ if (counter[0] == 0) { ++ source.sendFailure(Component.translatable("commands.fill.failed")); // ShreddedPaper - send failure message instead of throwing + } else { +- int i3 = i2; +- source.sendSuccess(() -> Component.translatable("commands.fill.success", i3), true); +- return i2; ++ int finalCount = counter[0]; ++ source.sendSuccess(() -> Component.translatable("commands.fill.success", finalCount), true); + } ++ }); ++ return 1; ++ // ShreddedPaper end - run on correct region thread + } + } + } From d45930aae15caf4d8cd74999c75bfacd88fee3d9 Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Tue, 6 Jan 2026 02:20:25 +0100 Subject: [PATCH 23/24] Fix CloneCommands cross-region block placement Wraps block-modifying operations in ShreddedPaper.ensureSync() to ensure they run on the correct region thread when a command block executes /clone targeting coordinates in a different region. --- .../server/commands/CloneCommands.java.patch | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/CloneCommands.java.patch diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/CloneCommands.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/CloneCommands.java.patch new file mode 100644 index 0000000..898256a --- /dev/null +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/CloneCommands.java.patch @@ -0,0 +1,60 @@ +--- a/net/minecraft/server/commands/CloneCommands.java ++++ b/net/minecraft/server/commands/CloneCommands.java +@@ -8,6 +_,7 @@ + import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType; + import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; + import com.mojang.logging.LogUtils; ++import io.multipaper.shreddedpaper.ShreddedPaper; + import java.util.Deque; + import java.util.List; + import java.util.function.Predicate; +@@ -233,11 +_,13 @@ + } else if (serverLevel1.isDebug()) { + throw ERROR_FAILED.create(); + } else { ++ // ShreddedPaper start - run on correct region thread ++ ShreddedPaper.ensureSync(serverLevel1, blockPos2, () -> { + List list = Lists.newArrayList(); + List list1 = Lists.newArrayList(); + List list2 = Lists.newArrayList(); + Deque list3 = Lists.newLinkedList(); +- int i2 = 0; ++ int[] counter = {0}; // ShreddedPaper - use array to allow modification in lambda + ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(LOGGER); + + try { +@@ -303,7 +_,7 @@ + + for (CloneCommands.CloneBlockInfo cloneBlockInfo : list4) { + if (serverLevel1.setBlock(cloneBlockInfo.pos, cloneBlockInfo.state, z)) { +- i2++; ++ counter[0]++; + } + } + +@@ -336,17 +_,19 @@ + var35.addSuppressed(var34); + } + +- throw var35; ++ throw new RuntimeException(var35); // ShreddedPaper - wrap in RuntimeException for lambda + } + + scopedCollector.close(); +- if (i2 == 0) { +- throw ERROR_FAILED.create(); ++ if (counter[0] == 0) { ++ source.sendFailure(Component.translatable("commands.clone.failed")); // ShreddedPaper - send failure message instead of throwing + } else { +- int i3 = i2; +- source.sendSuccess(() -> Component.translatable("commands.clone.success", i3), true); +- return i2; ++ int finalCount = counter[0]; ++ source.sendSuccess(() -> Component.translatable("commands.clone.success", finalCount), true); + } ++ }); ++ return 1; ++ // ShreddedPaper end - run on correct region thread + } + } + } From 68de0ede72829de7a18320100daf53de01041846 Mon Sep 17 00:00:00 2001 From: UnlimitedBytes Date: Tue, 6 Jan 2026 02:27:20 +0100 Subject: [PATCH 24/24] Clear cached chunk packets after block commands Fixes invisible blocks issue when SetBlockCommand, FillCommand, or CloneCommands modify chunks that aren't being watched by any player. The cached chunk packet is now invalidated so players receive fresh chunk data when they teleport to the affected area. --- .../server/commands/CloneCommands.java.patch | 16 ++++++++++++++++ .../server/commands/FillCommand.java.patch | 11 ++++++++++- .../server/commands/SetBlockCommand.java.patch | 7 ++++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/CloneCommands.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/CloneCommands.java.patch index 898256a..6a1f7a2 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/CloneCommands.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/CloneCommands.java.patch @@ -32,6 +32,22 @@ } } +@@ -329,6 +_,15 @@ + } + + serverLevel1.getBlockTicks().copyAreaFrom(serverLevel.getBlockTicks(), boundingBox, blockPos4); ++ ++ // ShreddedPaper start - clear cached chunk packets for destination chunks ++ for (int chunkX = boundingBox1.minX() >> 4; chunkX <= boundingBox1.maxX() >> 4; chunkX++) { ++ for (int chunkZ = boundingBox1.minZ() >> 4; chunkZ <= boundingBox1.maxZ() >> 4; chunkZ++) { ++ net.minecraft.world.level.chunk.LevelChunk chunk = serverLevel1.getChunkIfLoaded(chunkX, chunkZ); ++ if (chunk != null) chunk.cachedChunkPacket = null; ++ } ++ } ++ // ShreddedPaper end - clear cached chunk packets for destination chunks + } catch (Throwable var35) { + try { + scopedCollector.close(); @@ -336,17 +_,19 @@ var35.addSuppressed(var34); } diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/FillCommand.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/FillCommand.java.patch index cf7e890..cd1f70b 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/FillCommand.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/FillCommand.java.patch @@ -55,12 +55,21 @@ } } } -@@ -223,13 +_,15 @@ +@@ -223,13 +_,24 @@ level.updateNeighboursOnBlockSet(updatedPosition.pos, updatedPosition.oldState); } - if (i2 == 0) { - throw ERROR_FAILED.create(); ++ // ShreddedPaper start - clear cached chunk packets for affected chunks ++ for (int chunkX = box.minX() >> 4; chunkX <= box.maxX() >> 4; chunkX++) { ++ for (int chunkZ = box.minZ() >> 4; chunkZ <= box.maxZ() >> 4; chunkZ++) { ++ net.minecraft.world.level.chunk.LevelChunk chunk = level.getChunkIfLoaded(chunkX, chunkZ); ++ if (chunk != null) chunk.cachedChunkPacket = null; ++ } ++ } ++ // ShreddedPaper end - clear cached chunk packets for affected chunks ++ + if (counter[0] == 0) { + source.sendFailure(Component.translatable("commands.fill.failed")); // ShreddedPaper - send failure message instead of throwing } else { diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/SetBlockCommand.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/SetBlockCommand.java.patch index 49635b5..77d24b0 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/SetBlockCommand.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/commands/SetBlockCommand.java.patch @@ -17,7 +17,7 @@ boolean flag; if (mode == SetBlockCommand.Mode.DESTROY) { level.destroyBlock(pos, true); -@@ -117,15 +_,17 @@ +@@ -117,15 +_,22 @@ BlockState blockState = level.getBlockState(pos); if (flag && !block.place(level, pos, Block.UPDATE_CLIENTS | (strict ? Block.UPDATE_SKIP_ALL_SIDEEFFECTS : Block.UPDATE_SKIP_BLOCK_ENTITY_SIDEEFFECTS))) { @@ -28,6 +28,11 @@ level.updateNeighboursOnBlockSet(pos, blockState); } ++ // ShreddedPaper start - clear cached chunk packet ++ net.minecraft.world.level.chunk.LevelChunk chunk = level.getChunkIfLoaded(pos.getX() >> 4, pos.getZ() >> 4); ++ if (chunk != null) chunk.cachedChunkPacket = null; ++ // ShreddedPaper end - clear cached chunk packet ++ source.sendSuccess(() -> Component.translatable("commands.setblock.success", pos.getX(), pos.getY(), pos.getZ()), true); - return 1; }