Implement World#regenerateChunk (#7425)
Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
This commit is contained in:
		
					parent
					
						
							
								b6b252c57d
							
						
					
				
			
			
				commit
				
					
						ea776989ad
					
				
			
		
					 3 changed files with 167 additions and 0 deletions
				
			
		
							
								
								
									
										21
									
								
								patches/api/Implement-regenerateChunk.patch
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								patches/api/Implement-regenerateChunk.patch
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 | 
				
			||||||
 | 
					From: Nassim Jahnke <nassim@njahnke.dev>
 | 
				
			||||||
 | 
					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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
				
			||||||
 | 
					--- a/src/main/java/org/bukkit/World.java
 | 
				
			||||||
 | 
					+++ b/src/main/java/org/bukkit/World.java
 | 
				
			||||||
 | 
					@@ -0,0 +0,0 @@ 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);
 | 
				
			||||||
							
								
								
									
										97
									
								
								patches/server/Implement-regenerateChunk.patch
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								patches/server/Implement-regenerateChunk.patch
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,97 @@
 | 
				
			||||||
 | 
					From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 | 
				
			||||||
 | 
					From: Nassim Jahnke <nassim@njahnke.dev>
 | 
				
			||||||
 | 
					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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
				
			||||||
 | 
					--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
 | 
				
			||||||
 | 
					+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
 | 
				
			||||||
 | 
					@@ -0,0 +0,0 @@ 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;
 | 
				
			||||||
 | 
					@@ -0,0 +0,0 @@ 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<ChunkAccess> 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<ChunkAccess, ChunkHolder.ChunkLoadingFailure> 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<ChunkPos> 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
 | 
				
			||||||
| 
						 | 
					@ -7,6 +7,35 @@ Subject: [PATCH] Only write chunk data to disk if it serializes without
 | 
				
			||||||
This ensures at least a valid version of the chunk exists
 | 
					This ensures at least a valid version of the chunk exists
 | 
				
			||||||
on disk, even if outdated
 | 
					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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 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
 | 
				
			||||||
 | 
					@@ -0,0 +0,0 @@ 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
 | 
					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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
					index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 | 
				
			||||||
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
 | 
					--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
 | 
				
			||||||
| 
						 | 
					@ -16,6 +45,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 | 
				
			||||||
                 regionfile.setStatus(pos.x, pos.z, ChunkSerializer.getStatus(nbt)); // Paper - cache status on disk
 | 
					                 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
 | 
					                 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
 | 
					+                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) {
 | 
					             } catch (Throwable throwable) {
 | 
				
			||||||
                 if (dataoutputstream != null) {
 | 
					                 if (dataoutputstream != null) {
 | 
				
			||||||
                     try {
 | 
					                     try {
 | 
				
			||||||
| 
						 | 
					@ -36,3 +71,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 | 
				
			||||||
         }
 | 
					         }
 | 
				
			||||||
 
 | 
					 
 | 
				
			||||||
             // Paper start
 | 
					             // Paper start
 | 
				
			||||||
 | 
					@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable {
 | 
				
			||||||
 | 
					         }
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					+
 | 
				
			||||||
 | 
					+    // Paper start
 | 
				
			||||||
 | 
					+    public static final class RegionFileSizeException extends RuntimeException {
 | 
				
			||||||
 | 
					+
 | 
				
			||||||
 | 
					+        public RegionFileSizeException(String message) {
 | 
				
			||||||
 | 
					+            super(message);
 | 
				
			||||||
 | 
					+        }
 | 
				
			||||||
 | 
					+    }
 | 
				
			||||||
 | 
					+    // Paper end
 | 
				
			||||||
 | 
					 }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue