From 0cc2503b88343c4d10d9e6ecf7592d56762b4cae Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Sat, 5 Feb 2022 20:47:11 +0100 Subject: [PATCH] Implement World#regenerateChunk (#7425) Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com> --- .../api/0365-Implement-regenerateChunk.patch | 21 ++++ ...-data-to-disk-if-it-serializes-witho.patch | 55 ++++++++++- .../0862-Implement-regenerateChunk.patch | 97 +++++++++++++++++++ 3 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 patches/api/0365-Implement-regenerateChunk.patch create mode 100644 patches/server/0862-Implement-regenerateChunk.patch diff --git a/patches/api/0365-Implement-regenerateChunk.patch b/patches/api/0365-Implement-regenerateChunk.patch new file mode 100644 index 0000000000..b3aa50528c --- /dev/null +++ b/patches/api/0365-Implement-regenerateChunk.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Sat, 5 Feb 2022 20:25:28 +0100 +Subject: [PATCH] Implement regenerateChunk + + +diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java +index c808cc8dc96e325c543391048414880ed18a3ed3..7d2873a924ee81411a2a00bace0f58403fec43ea 100644 +--- a/src/main/java/org/bukkit/World.java ++++ b/src/main/java/org/bukkit/World.java +@@ -505,8 +505,8 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient + * @return Whether the chunk was actually regenerated + * + * @deprecated regenerating a single chunk is not likely to produce the same +- * chunk as before as terrain decoration may be spread across chunks. Use of +- * this method should be avoided as it is known to produce buggy results. ++ * chunk as before as terrain decoration may be spread across chunks. It may ++ * or may not change blocks in the adjacent chunks as well. + */ + @Deprecated + public boolean regenerateChunk(int x, int z); diff --git a/patches/server/0825-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch b/patches/server/0825-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch index f6365f90b4..cf58798438 100644 --- a/patches/server/0825-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch +++ b/patches/server/0825-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch @@ -7,15 +7,50 @@ Subject: [PATCH] Only write chunk data to disk if it serializes without This ensures at least a valid version of the chunk exists on disk, even if outdated +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +index 950efcc80455f73ec8ca4e991fcf9a5b2b7fa22e..012909055f4def2330e2fa839a98ff8bb41ea604 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +@@ -1020,6 +1020,24 @@ public class RegionFile implements AutoCloseable { + this.pos = chunkcoordintpair; + } + ++ // Paper start - don't write garbage data to disk if writing serialization fails ++ @Override ++ public void write(final int b) { ++ if (this.count > 500_000_000) { ++ throw new RegionFileStorage.RegionFileSizeException("Region file too large: " + this.count); ++ } ++ super.write(b); ++ } ++ ++ @Override ++ public void write(final byte[] b, final int off, final int len) { ++ if (this.count + len > 500_000_000) { ++ throw new RegionFileStorage.RegionFileSizeException("Region file too large: " + (this.count + len)); ++ } ++ super.write(b, off, len); ++ } ++ // Paper end ++ + public void close() throws IOException { + ByteBuffer bytebuffer = ByteBuffer.wrap(this.buf, 0, this.count); + diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -index 2dde10324e515bd58fc6ba7e93156ae783492cc2..c7216cf3317cbd49b032c44feb370c50928dd21e 100644 +index 9f6c1de59ca011bd1203499f325fdfa305e215ce..4502362ff3c43eac489125deee59c66d76204e98 100644 --- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -@@ -298,10 +298,11 @@ public class RegionFileStorage implements AutoCloseable { +@@ -298,10 +298,17 @@ public class RegionFileStorage implements AutoCloseable { NbtIo.write(nbt, (DataOutput) dataoutputstream); regionfile.setStatus(pos.x, pos.z, ChunkSerializer.getStatus(nbt)); // Paper - cache status on disk regionfile.setOversized(pos.x, pos.z, false); // Paper - We don't do this anymore, mojang stores differently, but clear old meta flag if it exists to get rid of our own meta file once last oversized is gone + dataoutputstream.close(); // Paper - only write if successful ++ // Paper start - don't write garbage data to disk if writing serialization fails ++ } catch (RegionFileSizeException e) { ++ attempts = 5; // Don't retry ++ regionfile.clear(pos); ++ throw e; ++ // Paper end - don't write garbage data to disk if writing serialization fails } catch (Throwable throwable) { if (dataoutputstream != null) { try { @@ -24,7 +59,7 @@ index 2dde10324e515bd58fc6ba7e93156ae783492cc2..c7216cf3317cbd49b032c44feb370c50 } catch (Throwable throwable1) { throwable.addSuppressed(throwable1); } -@@ -309,10 +310,7 @@ public class RegionFileStorage implements AutoCloseable { +@@ -309,10 +316,7 @@ public class RegionFileStorage implements AutoCloseable { throw throwable; } @@ -36,3 +71,17 @@ index 2dde10324e515bd58fc6ba7e93156ae783492cc2..c7216cf3317cbd49b032c44feb370c50 } // Paper start +@@ -359,4 +363,13 @@ public class RegionFileStorage implements AutoCloseable { + } + + } ++ ++ // Paper start ++ public static final class RegionFileSizeException extends RuntimeException { ++ ++ public RegionFileSizeException(String message) { ++ super(message); ++ } ++ } ++ // Paper end + } diff --git a/patches/server/0862-Implement-regenerateChunk.patch b/patches/server/0862-Implement-regenerateChunk.patch new file mode 100644 index 0000000000..ecd6547d58 --- /dev/null +++ b/patches/server/0862-Implement-regenerateChunk.patch @@ -0,0 +1,97 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Mon, 31 Jan 2022 11:21:50 +0100 +Subject: [PATCH] Implement regenerateChunk + +Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com> + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 5fb475b3ccaa98861e2c817b37cd1740e5bfed8d..f2deb875992ad2d5c9dbcfe9ee7071a773fed684 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -121,6 +121,7 @@ import org.bukkit.util.Vector; + + public class CraftWorld extends CraftRegionAccessor implements World { + public static final int CUSTOM_DIMENSION_OFFSET = 10; ++ private static final ChunkStatus[] REGEN_CHUNK_STATUSES = {ChunkStatus.BIOMES, ChunkStatus.NOISE, ChunkStatus.SURFACE, ChunkStatus.CARVERS, ChunkStatus.LIQUID_CARVERS, ChunkStatus.FEATURES}; // Paper - implement regenerate chunk method + + private final ServerLevel world; + private WorldBorder worldBorder; +@@ -435,27 +436,61 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + public boolean regenerateChunk(int x, int z) { + org.spigotmc.AsyncCatcher.catchOp("chunk regenerate"); // Spigot +- throw new UnsupportedOperationException("Not supported in this Minecraft version! Unless you can fix it, this is not a bug :)"); +- /* +- if (!unloadChunk0(x, z, false)) { +- return false; +- } +- +- final long chunkKey = ChunkCoordIntPair.pair(x, z); +- world.getChunkProvider().unloadQueue.remove(chunkKey); ++ // Paper start - implement regenerateChunk method ++ final ServerLevel serverLevel = this.world; ++ final net.minecraft.server.level.ServerChunkCache serverChunkCache = serverLevel.getChunkSource(); ++ final ChunkPos chunkPos = new ChunkPos(x, z); ++ final net.minecraft.world.level.chunk.LevelChunk levelChunk = serverChunkCache.getChunk(chunkPos.x, chunkPos.z, true); ++ for (final BlockPos blockPos : BlockPos.betweenClosed(chunkPos.getMinBlockX(), serverLevel.getMinBuildHeight(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), serverLevel.getMaxBuildHeight() - 1, chunkPos.getMaxBlockZ())) { ++ levelChunk.removeBlockEntity(blockPos); ++ serverLevel.setBlock(blockPos, Blocks.AIR.defaultBlockState(), 16); ++ } ++ ++ for (final ChunkStatus chunkStatus : REGEN_CHUNK_STATUSES) { ++ final List list = new ArrayList<>(); ++ final int range = Math.max(1, chunkStatus.getRange()); ++ for (int chunkX = chunkPos.z - range; chunkX <= chunkPos.z + range; chunkX++) { ++ for (int chunkZ = chunkPos.x - range; chunkZ <= chunkPos.x + range; chunkZ++) { ++ ChunkAccess chunkAccess = serverChunkCache.getChunk(chunkZ, chunkX, chunkStatus.getParent(), true); ++ if (chunkAccess instanceof ImposterProtoChunk accessProtoChunk) { ++ chunkAccess = new ImposterProtoChunk(accessProtoChunk.getWrapped(), true); ++ } else if (chunkAccess instanceof net.minecraft.world.level.chunk.LevelChunk accessLevelChunk) { ++ chunkAccess = new ImposterProtoChunk(accessLevelChunk, true); ++ } ++ list.add(chunkAccess); ++ } ++ } + +- net.minecraft.server.Chunk chunk = world.getChunkProvider().generateChunk(x, z); +- PlayerChunk playerChunk = world.getPlayerChunkMap().getChunk(x, z); +- if (playerChunk != null) { +- playerChunk.chunk = chunk; ++ final com.mojang.datafixers.util.Either either = chunkStatus.generate( ++ Runnable::run, ++ serverLevel, ++ serverChunkCache.getGenerator(), ++ serverLevel.getStructureManager(), ++ serverChunkCache.getLightEngine(), ++ chunk -> { ++ throw new UnsupportedOperationException("Not creating full chunks here"); ++ }, ++ list, ++ true ++ ).join(); ++ if (chunkStatus == ChunkStatus.NOISE) { ++ either.left().ifPresent(chunk -> net.minecraft.world.level.levelgen.Heightmap.primeHeightmaps(chunk, ChunkStatus.POST_FEATURES)); ++ } + } + +- if (chunk != null) { +- refreshChunk(x, z); ++ for (final BlockPos blockPos : BlockPos.betweenClosed(chunkPos.getMinBlockX(), serverLevel.getMinBuildHeight(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), serverLevel.getMaxBuildHeight() - 1, chunkPos.getMaxBlockZ())) { ++ serverChunkCache.blockChanged(blockPos); + } + +- return chunk != null; +- */ ++ final Set chunksToRelight = new HashSet<>(9); ++ for (int chunkX = chunkPos.x - 1; chunkX <= chunkPos.x + 1 ; chunkX++) { ++ for (int chunkZ = chunkPos.z - 1; chunkZ <= chunkPos.z + 1 ; chunkZ++) { ++ chunksToRelight.add(new ChunkPos(chunkX, chunkZ)); ++ } ++ } ++ serverChunkCache.getLightEngine().relight(chunksToRelight, pos -> {}, relit -> {}); ++ return true; ++ // Paper end + } + + @Override