diff --git a/patches/unapplied/server/Add-ray-tracing-methods-to-LivingEntity.patch b/patches/server/Add-ray-tracing-methods-to-LivingEntity.patch similarity index 100% rename from patches/unapplied/server/Add-ray-tracing-methods-to-LivingEntity.patch rename to patches/server/Add-ray-tracing-methods-to-LivingEntity.patch diff --git a/patches/unapplied/server/Allow-chests-to-be-placed-with-NBT-data.patch b/patches/server/Allow-chests-to-be-placed-with-NBT-data.patch similarity index 100% rename from patches/unapplied/server/Allow-chests-to-be-placed-with-NBT-data.patch rename to patches/server/Allow-chests-to-be-placed-with-NBT-data.patch diff --git a/patches/unapplied/server/Asynchronous-chunk-IO-and-loading.patch b/patches/server/Asynchronous-chunk-IO-and-loading.patch similarity index 94% rename from patches/unapplied/server/Asynchronous-chunk-IO-and-loading.patch rename to patches/server/Asynchronous-chunk-IO-and-loading.patch index e667db7d9c..04251d5c5a 100644 --- a/patches/unapplied/server/Asynchronous-chunk-IO-and-loading.patch +++ b/patches/server/Asynchronous-chunk-IO-and-loading.patch @@ -846,16 +846,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + write = this.inProgressWrite; + } + -+ // check if another process is writing -+ /*try { TODO: Can we restore this? -+ ((WorldServer)this.world).checkSession(); -+ } catch (final Exception ex) { -+ LOGGER.fatal("Couldn't save chunk; already in use by another instance of Minecraft?", ex); -+ // we don't need to set the write counter to -1 as we know at this stage there's no point in re-scheduling -+ // writes since they'll fail anyways. -+ return; -+ } -+*/ + for (;;) { + final long writeCounter; + final CompoundTag data; @@ -1487,13 +1477,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import com.destroystokyo.paper.io.IOUtil; +import java.util.ArrayDeque; +import java.util.function.Consumer; ++import com.mojang.logging.LogUtils; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.storage.ChunkSerializer; ++import org.slf4j.Logger; + +public final class ChunkLoadTask extends ChunkTask { + ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ + public boolean cancelled; + + Consumer onComplete; @@ -1519,7 +1513,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + try { + this.executeTask(); + } catch (final Throwable ex) { -+ PaperFileIOThread.LOGGER.error("Failed to execute chunk load task: " + this.toString(), ex); ++ LOGGER.error("Failed to execute chunk load task: " + this.toString(), ex); + if (!this.hasCompleted) { + this.complete(ChunkLoadTask.createEmptyHolder()); + } @@ -1552,7 +1546,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + final PaperFileIOThread.ChunkData chunkData = this.chunkData; + + if (chunkData.poiData == PaperFileIOThread.FAILURE_VALUE || chunkData.chunkData == PaperFileIOThread.FAILURE_VALUE) { -+ PaperFileIOThread.LOGGER.error("Could not load chunk for task: " + this.toString() + ", file IO thread has dumped the relevant exception above"); ++ LOGGER.error("Could not load chunk for task: " + this.toString() + ", file IO thread has dumped the relevant exception above"); + this.complete(ChunkLoadTask.createEmptyHolder()); + return; + } @@ -1563,6 +1557,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return; + } + ++ if (!ChunkMap.isChunkDataValid(chunkData.chunkData)) { ++ LOGGER.error("Chunk file at {} is missing level data, skipping", new ChunkPos(this.chunkX, this.chunkZ)); ++ this.complete(ChunkLoadTask.createEmptyHolder()); ++ return; ++ } ++ + final ChunkPos chunkPos = new ChunkPos(this.chunkX, this.chunkZ); + + final ChunkMap chunkManager = this.world.getChunkSource().chunkMap; @@ -1576,7 +1576,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + chunkData.chunkData = chunkManager.upgradeChunkTag(this.world.getTypeKey(), + chunkManager.overworldDataStorage, chunkData.chunkData, chunkManager.generator.getTypeNameForDataFixer(), chunkPos, this.world); // clone data for safety, file IO thread does not clone + } catch (final Throwable ex) { -+ PaperFileIOThread.LOGGER.error("Could not apply datafixers for chunk task: " + this.toString(), ex); ++ LOGGER.error("Could not apply datafixers for chunk task: " + this.toString(), ex); + this.complete(ChunkLoadTask.createEmptyHolder()); + return; + } @@ -1589,7 +1589,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + chunkHolder = ChunkSerializer.loadChunk(this.world, chunkManager.getPoiManager(), chunkPos, + chunkData.chunkData, true); + } catch (final Throwable ex) { -+ PaperFileIOThread.LOGGER.error("Could not de-serialize chunk data for task: " + this.toString(), ex); ++ LOGGER.error("Could not de-serialize chunk data for task: " + this.toString(), ex); + this.complete(ChunkLoadTask.createEmptyHolder()); + return; + } @@ -1612,7 +1612,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + try { + ChunkLoadTask.this.onComplete.accept(holder); + } catch (final Throwable thr) { -+ PaperFileIOThread.LOGGER.error("Failed to complete chunk data for task: " + this.toString(), thr); ++ LOGGER.error("Failed to complete chunk data for task: " + this.toString(), thr); + } + return null; + }); @@ -2315,7 +2315,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 */ + Class.forName(net.minecraft.world.entity.npc.VillagerTrades.class.getName());// Paper - load this sync so it won't fail later async final DedicatedServer dedicatedserver = (DedicatedServer) MinecraftServer.spin((thread) -> { - DedicatedServer dedicatedserver1 = new DedicatedServer(optionset, config.get(), ops.get(), thread, convertable_conversionsession, resourcepackrepository, worldstem, dedicatedserversettings, DataFixers.getDataFixer(), minecraftsessionservice, gameprofilerepository, usercache, LoggerChunkProgressListener::new); + DedicatedServer dedicatedserver1 = new DedicatedServer(optionset, config.get(), ops.get(), thread, convertable_conversionsession, resourcepackrepository, worldstem, dedicatedserversettings, DataFixers.getDataFixer(), services, LoggerChunkProgressListener::new); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 @@ -2330,19 +2330,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } public String getLocalIp() { -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -0,0 +0,0 @@ public class ChunkHolder { - ChunkStatus chunkstatus = ChunkHolder.getStatus(this.oldTicketLevel); - ChunkStatus chunkstatus1 = ChunkHolder.getStatus(this.ticketLevel); - boolean flag = this.oldTicketLevel <= ChunkMap.MAX_CHUNK_DISTANCE; -- boolean flag1 = this.ticketLevel <= ChunkMap.MAX_CHUNK_DISTANCE; -+ boolean flag1 = this.ticketLevel <= ChunkMap.MAX_CHUNK_DISTANCE; // Paper - diff on change: (flag1 = new ticket level is in loadable range) - ChunkHolder.FullChunkStatus playerchunk_state = ChunkHolder.getFullChunkStatus(this.oldTicketLevel); - ChunkHolder.FullChunkStatus playerchunk_state1 = ChunkHolder.getFullChunkStatus(this.ticketLevel); - // CraftBukkit start diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -2403,41 +2390,32 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } private CompletableFuture> scheduleChunkLoad(ChunkPos pos) { -- return CompletableFuture.supplyAsync(() -> { +- return this.readChunk(pos).thenApply((optional) -> { +- return optional.filter((nbttagcompound) -> { +- boolean flag = ChunkMap.isChunkDataValid(nbttagcompound); + // Paper start - Async chunk io + final java.util.function.BiFunction> syncLoadComplete = (chunkHolder, ioThrowable) -> { - try (Timing ignored = this.level.timings.chunkLoad.startTimingIfSync()) { // Paper - this.level.getProfiler().incrementCounter("chunkLoad"); -- CompoundTag nbttagcompound; // Paper -- try (Timing ignored2 = this.level.timings.chunkIO.startTimingIfSync()) { // Paper start - timings -- nbttagcompound = this.readChunk(pos); -- } // Paper end -- -- if (nbttagcompound != null) {try (Timing ignored2 = this.level.timings.chunkLoadLevelTimer.startTimingIfSync()) { // Paper start - timings -- boolean flag = nbttagcompound.contains("Status", 8); -- -- if (flag) { -- ProtoChunk protochunk = ChunkSerializer.read(this.level, this.poiManager, pos, nbttagcompound); -+ // Paper start ++ try (Timing ignored = this.level.timings.chunkLoad.startTimingIfSync()) { // Paper ++ this.level.getProfiler().incrementCounter("chunkLoad"); + if (ioThrowable != null) { -+ com.destroystokyo.paper.util.SneakyThrow.sneaky(ioThrowable); ++ return this.handleChunkLoadFailure(ioThrowable, pos); + } + this.poiManager.loadInData(pos, chunkHolder.poiData); + chunkHolder.tasks.forEach(Runnable::run); -+ // Paper end -+ if (chunkHolder.protoChunk != null) {try (Timing ignored2 = this.level.timings.chunkLoadLevelTimer.startTimingIfSync()) { // Paper start - timings // Paper - chunk is created async -+ if (true) { -+ ProtoChunk protochunk = chunkHolder.protoChunk; - this.markPosition(pos, protochunk.getStatus().getChunkType()); - return Either.left(protochunk); - } -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +- if (!flag) { +- ChunkMap.LOGGER.error("Chunk file at {} is missing level data, skipping", pos); ++ if (chunkHolder.protoChunk != null) { ++ ProtoChunk protochunk = chunkHolder.protoChunk; ++ this.markPosition(pos, protochunk.getStatus().getChunkType()); ++ return Either.left(protochunk); + } ++ } catch (Exception ex) { ++ return this.handleChunkLoadFailure(ex, pos); ++ } - this.markPositionReplaceable(pos); - return Either.left(new ProtoChunk(pos, UpgradeData.EMPTY, this.level, this.level.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), (BlendingData) null)); -- }, this.mainThreadExecutor); -+ // Paper start - Async chunk io +- return flag; ++ return Either.left(this.createEmptyChunk(pos)); + }; + CompletableFuture> ret = new CompletableFuture<>(); + @@ -2449,9 +2427,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } catch (Exception e) { + ret.completeExceptionally(e); + } -+ }); + }); +- }).thenApplyAsync((optional) -> { +- this.level.getProfiler().incrementCounter("chunkLoad"); +- if (optional.isPresent()) { +- ProtoChunk protochunk = ChunkSerializer.read(this.level, this.poiManager, pos, (CompoundTag) optional.get()); + }; -+ + +- this.markPosition(pos, protochunk.getStatus().getChunkType()); +- return Either.left(protochunk); // CraftBukkit - decompile error +- } else { +- return Either.left(this.createEmptyChunk(pos)); // CraftBukkit - decompile error +- } +- }, this.mainThreadExecutor).exceptionallyAsync((throwable) -> { +- return this.handleChunkLoadFailure(throwable, pos); +- }, this.mainThreadExecutor); + CompletableFuture chunkSaveFuture = this.level.asyncChunkTaskManager.getChunkSaveFuture(pos.x, pos.z); + if (chunkSaveFuture != null) { + this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z, @@ -2462,10 +2452,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY, chunkHolderConsumer, false); + } + return ret; -+ // Paper end ++ // Paper end - Async chunk io + } + +- private static boolean isChunkDataValid(CompoundTag nbt) { ++ public static boolean isChunkDataValid(CompoundTag nbt) { // Paper - async chunk loading + return nbt.contains("Status", 8); } - private void markPositionReplaceable(ChunkPos pos) { @@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } } @@ -2557,7 +2551,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // Paper start - Asynchronous chunk io + @Nullable + @Override -+ public CompoundTag read(ChunkPos chunkcoordintpair) throws IOException { ++ public CompoundTag readSync(ChunkPos chunkcoordintpair) throws IOException { + if (Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) { + CompoundTag ret = com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE + .loadChunkDataAsyncFuture(this.level, chunkcoordintpair.x, chunkcoordintpair.z, com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread(), @@ -2568,7 +2562,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + return ret; + } -+ return super.read(chunkcoordintpair); ++ return super.readSync(chunkcoordintpair); + } + + @Override @@ -2583,9 +2577,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + // Paper end + - @Nullable - public CompoundTag readChunk(ChunkPos pos) throws IOException { - CompoundTag nbttagcompound = this.read(pos); + private CompletableFuture> readChunk(ChunkPos chunkPos) { + return this.read(chunkPos).thenApplyAsync((optional) -> { + return optional.map((nbttagcompound) -> this.upgradeChunkTag(nbttagcompound, chunkPos)); // CraftBukkit diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/level/DistanceManager.java @@ -2814,7 +2808,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + @Override + public net.minecraft.nbt.CompoundTag readData(int x, int z) throws java.io.IOException { -+ return ServerLevel.this.getChunkSource().chunkMap.read(new ChunkPos(x, z)); ++ return ServerLevel.this.getChunkSource().chunkMap.readSync(new ChunkPos(x, z)); + } + + @Override @@ -2843,7 +2837,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + public final com.destroystokyo.paper.io.chunk.ChunkTaskManager asyncChunkTaskManager; // Paper end - // Add env and gen to constructor, WorldData -> WorldDataServer + // Add env and gen to constructor, IWorldDataServer -> WorldDataServer @@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel { this.sleepStatus = new SleepStatus(); @@ -2870,13 +2864,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser - server.scheduleOnMain(() -> this.disconnect(new TranslatableComponent("disconnect.spam", new Object[0]))); // Paper + server.scheduleOnMain(() -> this.disconnect(Component.translatable("disconnect.spam", new Object[0]))); // Paper return; } + // Paper start + String str = packet.getCommand(); int index = -1; + if (str.length() > 64 && ((index = str.indexOf(' ')) == -1 || index >= 64)) { -+ server.scheduleOnMain(() -> this.disconnect(new TranslatableComponent("disconnect.spam", new Object[0]))); // Paper ++ server.scheduleOnMain(() -> this.disconnect(Component.translatable("disconnect.spam", new Object[0]))); // Paper + return; + } + // Paper end @@ -2893,8 +2887,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 private final LongSet loadedChunks = new LongOpenHashSet(); + private final net.minecraft.server.level.ServerLevel world; // Paper - public PoiManager(Path path, DataFixer dataFixer, boolean dsync, LevelHeightAccessor world) { - super(path, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, world); + public PoiManager(Path path, DataFixer dataFixer, boolean dsync, RegistryAccess registryManager, LevelHeightAccessor world) { + super(path, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, registryManager, world); + this.world = (net.minecraft.server.level.ServerLevel)world; // Paper this.distanceTracker = new PoiManager.DistanceTracker(); } @@ -2992,17 +2986,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 if (!Objects.equals(chunkPos, chunkcoordintpair1)) { @@ -0,0 +0,0 @@ public class ChunkSerializer { - LevelLightEngine lightengine = chunkproviderserver.getLightEngine(); - - if (flag) { -+ tasksToExecuteOnMain.add(() -> { // Paper - delay this task since we're executing off-main - lightengine.retainData(chunkPos, true); -+ }); // Paper - delay this task since we're executing off-main - } - - Registry iregistry = world.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY); -@@ -0,0 +0,0 @@ public class ChunkSerializer { - LevelChunkSection chunksection = new LevelChunkSection(b0, datapaletteblock, datapaletteblock1); + LevelChunkSection chunksection = new LevelChunkSection(b0, datapaletteblock, (PalettedContainer) object); // CraftBukkit - read/write achunksection[k] = chunksection; + tasksToExecuteOnMain.add(() -> { // Paper - delay this task since we're executing off-main @@ -3010,23 +2994,33 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + }); // Paper - delay this task since we're executing off-main } - if (flag) { - if (nbttagcompound1.contains("BlockLight", 7)) { + boolean flag3 = nbttagcompound1.contains("BlockLight", 7); +@@ -0,0 +0,0 @@ public class ChunkSerializer { + + if (flag3 || flag4) { + if (!flag2) { ++ tasksToExecuteOnMain.add(() -> { // Paper - delay this task since we're executing off-main + lightengine.retainData(chunkPos, true); ++ }); // Paper - delay this task since we're executing off-main + flag2 = true; + } + + if (flag3) { - lightengine.queueSectionData(LightLayer.BLOCK, SectionPos.of(chunkPos, b0), new DataLayer(nbttagcompound1.getByteArray("BlockLight")), true); + // Paper start - delay this task since we're executing off-main -+ DataLayer blockLight = new DataLayer(nbttagcompound1.getByteArray("BlockLight")); ++ DataLayer blockLight = new DataLayer(nbttagcompound1.getByteArray("BlockLight").clone()); + tasksToExecuteOnMain.add(() -> { -+ lightengine.queueSectionData(LightLayer.BLOCK, SectionPos.of(chunkcoordintpair1, b0), blockLight, true); ++ lightengine.queueSectionData(LightLayer.BLOCK, SectionPos.of(chunkPos, b0), blockLight, true); + }); + // Paper end - delay this task since we're executing off-main } - if (flag1 && nbttagcompound1.contains("SkyLight", 7)) { + if (flag4) { - lightengine.queueSectionData(LightLayer.SKY, SectionPos.of(chunkPos, b0), new DataLayer(nbttagcompound1.getByteArray("SkyLight")), true); + // Paper start - delay this task since we're executing off-main -+ DataLayer skyLight = new DataLayer(nbttagcompound1.getByteArray("SkyLight")); ++ DataLayer skyLight = new DataLayer(nbttagcompound1.getByteArray("SkyLight").clone()); + tasksToExecuteOnMain.add(() -> { -+ lightengine.queueSectionData(LightLayer.SKY, SectionPos.of(chunkcoordintpair1, b0), skyLight, true); ++ lightengine.queueSectionData(LightLayer.SKY, SectionPos.of(chunkPos, b0), skyLight, true); + }); + // Paper end - delay this task since we're executing off-mai } @@ -3036,13 +3030,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } if (chunkstatus_type == ChunkStatus.ChunkType.LEVELCHUNK) { -- return new ImposterProtoChunk((LevelChunk) object, false); -+ return new InProgressChunkHolder(new ImposterProtoChunk((LevelChunk) object, false), tasksToExecuteOnMain); // Paper - Async chunk loading +- return new ImposterProtoChunk((LevelChunk) object1, false); ++ return new InProgressChunkHolder(new ImposterProtoChunk((LevelChunk) object1, false), tasksToExecuteOnMain); // Paper - Async chunk loading } else { - ProtoChunk protochunk1 = (ProtoChunk) object; + ProtoChunk protochunk1 = (ProtoChunk) object1; @@ -0,0 +0,0 @@ public class ChunkSerializer { - protochunk1.setCarvingMask(worldgenstage_features, new CarvingMask(nbttagcompound4.getLongArray(s1), ((ChunkAccess) object).getMinBuildHeight())); + protochunk1.setCarvingMask(worldgenstage_features, new CarvingMask(nbttagcompound4.getLongArray(s1), ((ChunkAccess) object1).getMinBuildHeight())); } - return protochunk1; @@ -3111,7 +3105,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 private static void logErrors(ChunkPos chunkPos, int y, String message) { ChunkSerializer.LOGGER.error("Recoverable errors when loading section [" + chunkPos.x + ", " + y + ", " + chunkPos.z + "]: " + message); @@ -0,0 +0,0 @@ public class ChunkSerializer { - } + // CraftBukkit end public static CompoundTag write(ServerLevel world, ChunkAccess chunk) { + // Paper start @@ -3198,9 +3192,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // Paper - nuke IO worker protected final DataFixer fixerUpper; @Nullable -- private LegacyStructureDataHandler legacyStructureHandler; + private volatile LegacyStructureDataHandler legacyStructureHandler; + // Paper start - async chunk loading -+ private volatile LegacyStructureDataHandler legacyStructureHandler; + private final Object persistentDataLock = new Object(); // Paper + public final RegionFileStorage regionFileCache; + // Paper end - async chunk loading @@ -3214,8 +3207,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // Paper end - async chunk io } + public boolean isOldChunkAround(ChunkPos chunkPos, int checkRadius) { +- return this.worker.isOldChunkAround(chunkPos, checkRadius); ++ return true; // Paper - (for now, old unoptimised behavior) TODO implement later? the chunk status that blender uses SHOULD already have this radius loaded, no need to go back for it... + } + // CraftBukkit start - private boolean check(ServerChunkCache cps, int x, int z) throws IOException { + private boolean check(ServerChunkCache cps, int x, int z) { ChunkPos pos = new ChunkPos(x, z); if (cps != null) { - com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); @@ -3225,19 +3223,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 return true; } } - -- CompoundTag nbt = this.read(pos); -+ // Paper start - prioritize -+ CompoundTag nbt = cps == null ? read(pos) : -+ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.loadChunkData((ServerLevel)cps.getLevel(), x, z, -+ com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHER_PRIORITY, false, true).chunkData; -+ // Paper end - if (nbt != null) { - CompoundTag level = nbt.getCompound("Level"); - if (level.getBoolean("TerrainPopulated")) { @@ -0,0 +0,0 @@ public class ChunkStorage implements AutoCloseable { - public CompoundTag upgradeChunkTag(ResourceKey resourcekey, Supplier supplier, CompoundTag nbttagcompound, Optional>> optional, ChunkPos pos, @Nullable LevelAccessor generatoraccess) throws IOException { + public CompoundTag upgradeChunkTag(ResourceKey resourcekey, Supplier supplier, CompoundTag nbttagcompound, Optional>> optional, ChunkPos pos, @Nullable LevelAccessor generatoraccess) { // CraftBukkit end + nbttagcompound = nbttagcompound.copy(); // Paper - defensive copy, another thread might modify this int i = ChunkStorage.getVersion(nbttagcompound); @@ -3248,23 +3236,40 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 nbttagcompound = NbtUtils.update(this.fixerUpper, DataFixTypes.CHUNK, nbttagcompound, i, 1493); if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) { + synchronized (this.persistentDataLock) { // Paper - Async chunk loading - if (this.legacyStructureHandler == null) { - this.legacyStructureHandler = LegacyStructureDataHandler.getLegacyStructureHandler(resourcekey, (DimensionDataStorage) supplier.get()); - } + LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(resourcekey, supplier); - nbttagcompound = this.legacyStructureHandler.updateFromLegacy(nbttagcompound); + nbttagcompound = persistentstructurelegacy.updateFromLegacy(nbttagcompound); + } // Paper - Async chunk loading } } @@ -0,0 +0,0 @@ public class ChunkStorage implements AutoCloseable { + LegacyStructureDataHandler persistentstructurelegacy = this.legacyStructureHandler; - @Nullable - public CompoundTag read(ChunkPos chunkPos) throws IOException { -- return this.worker.load(chunkPos); -+ return this.regionFileCache.read(chunkPos); // Paper - async chunk io + if (persistentstructurelegacy == null) { +- synchronized (this) { ++ synchronized (this.persistentDataLock) { // Paper - async chunk loading + persistentstructurelegacy = this.legacyStructureHandler; + if (persistentstructurelegacy == null) { + this.legacyStructureHandler = persistentstructurelegacy = LegacyStructureDataHandler.getLegacyStructureHandler(resourcekey, (DimensionDataStorage) supplier.get()); +@@ -0,0 +0,0 @@ public class ChunkStorage implements AutoCloseable { } + public CompletableFuture> read(ChunkPos chunkPos) { +- return this.worker.loadAsync(chunkPos); ++ // Paper start - async chunk io ++ try { ++ return CompletableFuture.completedFuture(Optional.ofNullable(this.readSync(chunkPos))); ++ } catch (Throwable thr) { ++ return CompletableFuture.failedFuture(thr); ++ } ++ } ++ @Nullable ++ public CompoundTag readSync(ChunkPos chunkPos) throws IOException { ++ return this.regionFileCache.read(chunkPos); + } ++ // Paper end - async chunk io + - public void write(ChunkPos chunkPos, CompoundTag nbt) { - this.worker.store(chunkPos, nbt); + // Paper start - async chunk io @@ -3475,12 +3480,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ public class SectionStorage implements AutoCloseable { protected final LevelHeightAccessor levelHeightAccessor; - public SectionStorage(Path path, Function> codecFactory, Function factory, DataFixer dataFixer, DataFixTypes dataFixTypes, boolean dsync, LevelHeightAccessor world) { -+ super(path, dsync); + public SectionStorage(Path path, Function> codecFactory, Function factory, DataFixer dataFixer, DataFixTypes dataFixTypes, boolean dsync, RegistryAccess dynamicRegistryManager, LevelHeightAccessor world) { ++ super(path, dsync); // Paper - remove mojang I/O thread this.codec = codecFactory; this.factory = factory; this.fixerUpper = dataFixer; this.type = dataFixTypes; + this.registryAccess = dynamicRegistryManager; this.levelHeightAccessor = world; - this.worker = new IOWorker(path, dsync, path.getFileName().toString()); + // Paper - remove mojang I/O thread @@ -3490,34 +3496,43 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ public class SectionStorage implements AutoCloseable { } - private void readColumn(ChunkPos chunkPos) { -- this.readColumn(chunkPos, NbtOps.INSTANCE, this.tryRead(chunkPos)); -+ // Paper start - expose function to load in data -+ this.loadInData(chunkPos, this.tryRead(chunkPos)); + private CompletableFuture> tryRead(ChunkPos pos) { +- return this.worker.loadAsync(pos).exceptionally((throwable) -> { +- if (throwable instanceof IOException iOException) { +- LOGGER.error("Error reading chunk {} data from disk", pos, iOException); +- return Optional.empty(); +- } else { +- throw new CompletionException(throwable); +- } +- }); ++ // Paper start - async chunk io ++ try { ++ return CompletableFuture.completedFuture(Optional.ofNullable(this.read(pos))); ++ } catch (Throwable thr) { ++ return CompletableFuture.failedFuture(thr); ++ } ++ // Paper end - async chunk io + } ++ ++ // Paper start - async chunk io + public void loadInData(ChunkPos chunkPos, CompoundTag compound) { + this.readColumn(chunkPos, NbtOps.INSTANCE, compound); -+ // Paper end - expose function to load in data } ++ // Paper end - aync chnnk i - @Nullable - private CompoundTag tryRead(ChunkPos pos) { - try { -- return this.worker.load(pos); -+ return this.read(pos); // Paper - nuke IOWorker - } catch (IOException var3) { - LOGGER.error("Error reading chunk {} data from disk", pos, var3); - return null; + private void readColumn(ChunkPos pos, DynamicOps ops, @Nullable T data) { + if (data == null) { @@ -0,0 +0,0 @@ public class SectionStorage implements AutoCloseable { - Dynamic dynamic = this.writeColumn(chunkPos, NbtOps.INSTANCE); + Dynamic dynamic = this.writeColumn(pos, registryOps); Tag tag = dynamic.getValue(); if (tag instanceof CompoundTag) { -- this.worker.store(chunkPos, (CompoundTag)tag); -+ try { this.write(chunkPos, (CompoundTag)tag); } catch (IOException ioexception) { SectionStorage.LOGGER.error("Error writing data to disk", ioexception); } // Paper - nuke IOWorker +- this.worker.store(pos, (CompoundTag)tag); ++ try { this.write(pos, (CompoundTag)tag); } catch (IOException ioexception) { SectionStorage.LOGGER.error("Error writing data to disk", ioexception); } // Paper - nuke IOWorker } else { LOGGER.error("Expected compound tag, got {}", (Object)tag); } - +@@ -0,0 +0,0 @@ public class SectionStorage implements AutoCloseable { + return new Dynamic<>(ops, ops.createMap(ImmutableMap.of(ops.createString("Sections"), ops.createMap(map), ops.createString("DataVersion"), ops.createInt(SharedConstants.getCurrentVersion().getWorldVersion())))); } + // Paper start - internal get data function, copied from above @@ -3533,9 +3548,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return null; + } + // Paper end - private Dynamic writeColumn(ChunkPos chunkPos, DynamicOps dynamicOps) { - Map map = Maps.newHashMap(); - ++ + private static long getKey(ChunkPos chunkPos, int y) { + return SectionPos.asLong(chunkPos.x, y, chunkPos.z); + } @@ -0,0 +0,0 @@ public class SectionStorage implements AutoCloseable { @Override @@ -3604,14 +3620,6 @@ diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/ index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -0,0 +0,0 @@ import net.minecraft.network.chat.Component; - import net.minecraft.server.level.ChunkMap; - import net.minecraft.server.level.ServerLevel; - import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.level.TicketType; - import net.minecraft.world.damagesource.DamageSource; - import net.minecraft.world.entity.AreaEffectCloud; - import net.minecraft.world.entity.Entity; @@ -0,0 +0,0 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { this.entity.setYHeadRot(yaw); } diff --git a/patches/unapplied/server/Catch-JsonParseException-in-Entity-and-TE-names.patch b/patches/server/Catch-JsonParseException-in-Entity-and-TE-names.patch similarity index 100% rename from patches/unapplied/server/Catch-JsonParseException-in-Entity-and-TE-names.patch rename to patches/server/Catch-JsonParseException-in-Entity-and-TE-names.patch diff --git a/patches/unapplied/server/Expose-attack-cooldown-methods-for-Player.patch b/patches/server/Expose-attack-cooldown-methods-for-Player.patch similarity index 100% rename from patches/unapplied/server/Expose-attack-cooldown-methods-for-Player.patch rename to patches/server/Expose-attack-cooldown-methods-for-Player.patch diff --git a/patches/unapplied/server/Honor-EntityAgeable.ageLock.patch b/patches/server/Honor-EntityAgeable.ageLock.patch similarity index 100% rename from patches/unapplied/server/Honor-EntityAgeable.ageLock.patch rename to patches/server/Honor-EntityAgeable.ageLock.patch diff --git a/patches/unapplied/server/Implement-an-API-for-CanPlaceOn-and-CanDestroy-NBT-v.patch b/patches/server/Implement-an-API-for-CanPlaceOn-and-CanDestroy-NBT-v.patch similarity index 100% rename from patches/unapplied/server/Implement-an-API-for-CanPlaceOn-and-CanDestroy-NBT-v.patch rename to patches/server/Implement-an-API-for-CanPlaceOn-and-CanDestroy-NBT-v.patch diff --git a/patches/unapplied/server/Implement-furnace-cook-speed-multiplier-API.patch b/patches/server/Implement-furnace-cook-speed-multiplier-API.patch similarity index 76% rename from patches/unapplied/server/Implement-furnace-cook-speed-multiplier-API.patch rename to patches/server/Implement-furnace-cook-speed-multiplier-API.patch index 151542217c..95c1900f44 100644 --- a/patches/unapplied/server/Implement-furnace-cook-speed-multiplier-API.patch +++ b/patches/server/Implement-furnace-cook-speed-multiplier-API.patch @@ -22,6 +22,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 public int cookingProgress; public int cookingTotalTime; protected final ContainerData dataAccess; + public final Object2IntOpenHashMap recipesUsed; + private final RecipeManager.CachedCheck quickCheck; ++ public final RecipeType recipeType; // Paper + + protected AbstractFurnaceBlockEntity(BlockEntityType blockEntityType, BlockPos pos, BlockState state, RecipeType recipeType) { + super(blockEntityType, pos, state); +@@ -0,0 +0,0 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit + }; + this.recipesUsed = new Object2IntOpenHashMap(); + this.quickCheck = RecipeManager.createCheck((RecipeType) recipeType); // CraftBukkit - decompile error // Eclipse fail ++ this.recipeType = recipeType; // Paper + } + + public static Map getFuel() { @@ -0,0 +0,0 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit this.recipesUsed.put(new ResourceLocation(s), nbttagcompound1.getInt(s)); } @@ -58,8 +72,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - if (blockEntity.cookingProgress == blockEntity.cookingTotalTime) { + if (blockEntity.cookingProgress >= blockEntity.cookingTotalTime) { // Paper - cook speed multiplier API blockEntity.cookingProgress = 0; -- blockEntity.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(world, blockEntity.recipeType, blockEntity); -+ blockEntity.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(world, blockEntity.recipeType, blockEntity, blockEntity.cookSpeedMultiplier); +- blockEntity.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(world, blockEntity); ++ blockEntity.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(world, blockEntity.recipeType, blockEntity, blockEntity.cookSpeedMultiplier); // Paper if (AbstractFurnaceBlockEntity.burn(blockEntity.level, blockEntity.worldPosition, irecipe, blockEntity.items, i)) { // CraftBukkit blockEntity.setRecipeUsed(irecipe); } @@ -67,12 +81,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } } -- private static int getTotalCookTime(Level world, RecipeType recipeType, Container inventory) { -- return (world != null) ? (Integer) world.getRecipeManager().getRecipeFor((RecipeType) recipeType, inventory, world).map(AbstractCookingRecipe::getCookingTime).orElse(200) : 200; // CraftBukkit - SPIGOT-4302 // Eclipse fail -+ // Paper begin - Expose this function so CraftFurnace can correctly scale the total cooking time to a new multiplier -+ public static int getTotalCookTime(@Nullable Level world, RecipeType recipeType, Container inventory, final double cookSpeedMultiplier) { +- private static int getTotalCookTime(Level world, AbstractFurnaceBlockEntity furnace) { +- return (world != null) ? (Integer) furnace.quickCheck.getRecipeFor(furnace, world).map(AbstractCookingRecipe::getCookingTime).orElse(200) : 200; // CraftBukkit - SPIGOT-4302 ++ // Paper start ++ public static int getTotalCookTime(Level world, RecipeType recipeType, AbstractFurnaceBlockEntity furnace, double cookSpeedMultiplier) { + /* Scale the recipe's cooking time to the current cookSpeedMultiplier */ -+ int cookTime = (world != null ? world.getRecipeManager() : net.minecraft.server.MinecraftServer.getServer().getRecipeManager()).getRecipeFor(recipeType, inventory, world /* passing a null level here is safe. world is only used for map extending recipes which won't happen here */).map(AbstractCookingRecipe::getCookingTime).orElse(200); // CraftBukkit - SPIGOT-4302 // Eclipse fail ++ int cookTime = world == null ? furnace.quickCheck.getRecipeFor(furnace, world).map(AbstractCookingRecipe::getCookingTime).orElse(200) : (net.minecraft.server.MinecraftServer.getServer().getRecipeManager().getRecipeFor(recipeType, furnace, world /* passing a null level here is safe. world is only used for map extending recipes which won't happen here */).map(AbstractCookingRecipe::getCookingTime).orElse(200)); + return (int) Math.ceil (cookTime / cookSpeedMultiplier); } + // Paper end @@ -83,8 +97,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } if (slot == 0 && !flag) { -- this.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(this.level, this.recipeType, this); -+ this.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(this.level, this.recipeType, this, this.cookSpeedMultiplier); +- this.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(this.level, this); ++ this.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(this.level, this.recipeType, this, this.cookSpeedMultiplier); // Paper this.cookingProgress = 0; this.setChanged(); } diff --git a/patches/unapplied/server/Improve-death-events.patch b/patches/server/Improve-death-events.patch similarity index 96% rename from patches/unapplied/server/Improve-death-events.patch rename to patches/server/Improve-death-events.patch index 9cbcb772af..e892632551 100644 --- a/patches/unapplied/server/Improve-death-events.patch +++ b/patches/server/Improve-death-events.patch @@ -101,12 +101,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 this.playHurtSound(source); @@ -0,0 +0,0 @@ public abstract class LivingEntity extends Entity { if (!this.isRemoved() && !this.dead) { - Entity entity = source.getEntity(); + Entity entity = damageSource.getEntity(); LivingEntity entityliving = this.getKillCredit(); - + /* // Paper - move down to make death event cancellable - this is the awardKillScore below if (this.deathScore >= 0 && entityliving != null) { - entityliving.awardKillScore(this, this.deathScore, source); + entityliving.awardKillScore(this, this.deathScore, damageSource); } @@ -0,0 +0,0 @@ public abstract class LivingEntity extends Entity { if (!this.level.isClientSide && this.hasCustomName()) { @@ -118,17 +118,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - this.getCombatTracker().recheckStatus(); + // Paper - moved into if below if (this.level instanceof ServerLevel) { - if (entity != null) { -- entity.killed((ServerLevel) this.level, this); -+ // Paper - move below into if for onKill - } - -- this.dropAllDeathLoot(source); +- if (entity == null || entity.wasKilled((ServerLevel) this.level, this)) { ++ // Paper - move below into if for onKill ++ + // Paper start -+ org.bukkit.event.entity.EntityDeathEvent deathEvent = this.dropAllDeathLoot(source); ++ org.bukkit.event.entity.EntityDeathEvent deathEvent = this.dropAllDeathLoot(damageSource); + if (deathEvent == null || !deathEvent.isCancelled()) { + if (this.deathScore >= 0 && entityliving != null) { -+ entityliving.awardKillScore(this, this.deathScore, source); ++ entityliving.awardKillScore(this, this.deathScore, damageSource); + } + // Paper start - clear equipment if event is not cancelled + if (this instanceof Mob) { @@ -149,18 +146,23 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + this.getCombatTracker().recheckStatus(); + if (entity != null) { -+ entity.killed((ServerLevel) this.level, this); ++ entity.wasKilled((ServerLevel) this.level, this); + } + this.gameEvent(GameEvent.ENTITY_DIE); +- this.dropAllDeathLoot(damageSource); +- this.createWitherRose(entityliving); + } else { + this.dead = false; + this.setHealth((float) deathEvent.getReviveHealth()); -+ } + } +- +- this.level.broadcastEntityEvent(this, (byte) 3); + // Paper end - this.createWitherRose(entityliving); ++ this.createWitherRose(entityliving); } + if (this.dead) { // Paper - this.level.broadcastEntityEvent(this, (byte) 3); ++ this.level.broadcastEntityEvent(this, (byte) 3); this.setPose(Pose.DYING); + } // Paper } @@ -305,8 +307,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + org.bukkit.event.entity.EntityDeathEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, drops); // CraftBukkit - call event // Paper - make cancellable + if (event.isCancelled()) return; // Paper - make cancellable this.remove(Entity.RemovalReason.KILLED); + this.gameEvent(GameEvent.ENTITY_DIE); } - diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java diff --git a/patches/unapplied/server/Mob-Pathfinding-API.patch b/patches/server/Mob-Pathfinding-API.patch similarity index 100% rename from patches/unapplied/server/Mob-Pathfinding-API.patch rename to patches/server/Mob-Pathfinding-API.patch diff --git a/patches/unapplied/server/Prevent-Mob-AI-Rules-from-Loading-Chunks.patch b/patches/server/Prevent-Mob-AI-Rules-from-Loading-Chunks.patch similarity index 100% rename from patches/unapplied/server/Prevent-Mob-AI-Rules-from-Loading-Chunks.patch rename to patches/server/Prevent-Mob-AI-Rules-from-Loading-Chunks.patch diff --git a/patches/unapplied/server/Prevent-chunk-loading-from-Fluid-Flowing.patch b/patches/server/Prevent-chunk-loading-from-Fluid-Flowing.patch similarity index 100% rename from patches/unapplied/server/Prevent-chunk-loading-from-Fluid-Flowing.patch rename to patches/server/Prevent-chunk-loading-from-Fluid-Flowing.patch diff --git a/patches/unapplied/server/Prevent-mob-spawning-from-loading-generating-chunks.patch b/patches/server/Prevent-mob-spawning-from-loading-generating-chunks.patch similarity index 96% rename from patches/unapplied/server/Prevent-mob-spawning-from-loading-generating-chunks.patch rename to patches/server/Prevent-mob-spawning-from-loading-generating-chunks.patch index bd415097ad..107ed17173 100644 --- a/patches/unapplied/server/Prevent-mob-spawning-from-loading-generating-chunks.patch +++ b/patches/server/Prevent-mob-spawning-from-loading-generating-chunks.patch @@ -10,7 +10,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java +++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java @@ -0,0 +0,0 @@ public final class NaturalSpawner { - StructureFeatureManager structuremanager = world.structureFeatureManager(); + StructureManager structuremanager = world.structureManager(); ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator(); int i = pos.getY(); - BlockState iblockdata = chunk.getBlockState(pos);