From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Thu, 7 May 2020 19:17:36 -0400
Subject: [PATCH] Fix Light Command

This lets you run /paper fixlight <chunkRadius> (max 5) to automatically
fix all light data in the chunks.

diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java
index 005361c38b02713fb823d0be40954400d59f0c4d..3091c100eaf5a86ba270ef0d96de1852a2a0ac9e 100644
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java
@@ -10,7 +10,8 @@ import net.minecraft.server.MinecraftServer;
 import net.minecraft.server.level.ChunkHolder;
 import net.minecraft.server.level.ServerChunkCache;
 import net.minecraft.server.level.ServerLevel;
-import net.minecraft.world.entity.Entity;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.level.ThreadedLevelLightEngine;
 import net.minecraft.world.entity.EntityType;
 import net.minecraft.world.level.ChunkPos;
 import net.minecraft.resources.ResourceLocation;
@@ -25,15 +26,18 @@ import org.bukkit.command.Command;
 import org.bukkit.command.CommandSender;
 import org.bukkit.craftbukkit.CraftServer;
 import org.bukkit.craftbukkit.CraftWorld;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
 import org.bukkit.entity.Player;
 
 import java.io.File;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Deque;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
@@ -43,7 +47,7 @@ import java.util.stream.Collectors;
 
 public class PaperCommand extends Command {
     private static final String BASE_PERM = "bukkit.command.paper.";
-    private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo").build();
+    private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo", "fixlight").build();
 
     public PaperCommand(String name) {
         super(name);
@@ -158,6 +162,9 @@ public class PaperCommand extends Command {
             case "chunkinfo":
                 doChunkInfo(sender, args);
                 break;
+            case "fixlight":
+                this.doFixLight(sender, args);
+                break;
             case "ver":
                 if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set)
             case "version":
@@ -413,4 +420,74 @@ public class PaperCommand extends Command {
 
         Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Paper config reload complete.");
     }
+    private void doFixLight(CommandSender sender, String[] args) {
+        if (!(sender instanceof Player)) {
+            sender.sendMessage("Only players can use this command");
+            return;
+        }
+        int radius = 2;
+        if (args.length > 1) {
+            try {
+                radius = Math.min(5, Integer.parseInt(args[1]));
+            } catch (Exception e) {
+                sender.sendMessage("Not a number");
+                return;
+            }
+
+        }
+
+        CraftPlayer player = (CraftPlayer) sender;
+        ServerPlayer handle = player.getHandle();
+        ServerLevel world = (ServerLevel) handle.level;
+        ThreadedLevelLightEngine lightengine = world.getChunkSource().getLightEngine();
+
+        net.minecraft.core.BlockPos center = MCUtil.toBlockPosition(player.getLocation());
+        Deque<ChunkPos> queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius));
+        updateLight(sender, world, lightengine, queue);
+    }
+
+    private void updateLight(CommandSender sender, ServerLevel world, ThreadedLevelLightEngine lightengine, Deque<ChunkPos> queue) {
+        ChunkPos coord = queue.poll();
+        if (coord == null) {
+            sender.sendMessage("All Chunks Light updated");
+            return;
+        }
+        world.getChunkSource().getChunkAtAsynchronously(coord.x, coord.z, false, false).whenCompleteAsync((either, ex) -> {
+            if (ex != null) {
+                sender.sendMessage("Error loading chunk " + coord);
+                updateLight(sender, world, lightengine, queue);
+                return;
+            }
+            net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) either.left().orElse(null);
+            if (chunk == null) {
+                updateLight(sender, world, lightengine, queue);
+                return;
+            }
+            lightengine.setTaskPerBatch(world.paperConfig.lightQueueSize + 16 * 256); // ensure full chunk can fit into queue
+            sender.sendMessage("Updating Light " + coord);
+            int cx = chunk.getPos().x << 4;
+            int cz = chunk.getPos().z << 4;
+            for (int y = 0; y < world.getHeight(); y++) {
+                for (int x = 0; x < 16; x++) {
+                    for (int z = 0; z < 16; z++) {
+                        net.minecraft.core.BlockPos pos = new net.minecraft.core.BlockPos(cx + x, y, cz + z);
+                        lightengine.checkBlock(pos);
+                    }
+                }
+            }
+            lightengine.tryScheduleUpdate();
+            ChunkHolder visibleChunk = world.getChunkSource().chunkMap.getVisibleChunkIfPresent(chunk.coordinateKey);
+            if (visibleChunk != null) {
+                world.getChunkSource().chunkMap.addLightTask(visibleChunk, () -> {
+                    MinecraftServer.getServer().processQueue.add(() -> {
+                        visibleChunk.broadcast(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(chunk.getPos(), lightengine, null, null, true), false);
+                        updateLight(sender, world, lightengine, queue);
+                    });
+                });
+            } else {
+                updateLight(sender, world, lightengine, queue);
+            }
+            lightengine.setTaskPerBatch(world.paperConfig.lightQueueSize);
+        }, MinecraftServer.getServer());
+    }
 }
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 969401d1c4a506111ad212950bca42fdac59ad1f..f3f6fc973cd75a42594f1ec222c220e3894e11ee 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -132,6 +132,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
     private final ChunkTaskPriorityQueueSorter queueSorter;
     private final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> worldgenMailbox;
     public final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> mainThreadMailbox;
+    // Paper start
+    final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> mailboxLight;
+    public void addLightTask(ChunkHolder playerchunk, Runnable run) {
+        this.mailboxLight.tell(ChunkTaskPriorityQueueSorter.message(playerchunk, run));
+    }
+    // Paper end
     public final ChunkProgressListener progressListener;
     private final ChunkStatusUpdateListener chunkStatusListener;
     public final ChunkMap.ChunkDistanceManager distanceManager;
@@ -240,11 +246,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
 
         this.progressListener = worldGenerationProgressListener;
         this.chunkStatusListener = chunkStatusChangeListener;
-        ProcessorMailbox<Runnable> threadedmailbox1 = ProcessorMailbox.create(executor, "light");
+        ProcessorMailbox<Runnable> lightthreaded; ProcessorMailbox<Runnable> threadedmailbox1 = lightthreaded = ProcessorMailbox.create(executor, "light"); // Paper
 
         this.queueSorter = new ChunkTaskPriorityQueueSorter(ImmutableList.of(threadedmailbox, mailbox, threadedmailbox1), executor, Integer.MAX_VALUE);
         this.worldgenMailbox = this.queueSorter.getProcessor(threadedmailbox, false);
         this.mainThreadMailbox = this.queueSorter.getProcessor(mailbox, false);
+        this.mailboxLight = this.queueSorter.getProcessor(lightthreaded, false);// Paper
         this.lightEngine = new ThreadedLevelLightEngine(chunkProvider, this, this.level.dimensionType().hasSkyLight(), threadedmailbox1, this.queueSorter.getProcessor(threadedmailbox1, false));
         this.distanceManager = new ChunkMap.ChunkDistanceManager(executor, mainThreadExecutor);
         this.overworldDataStorage = persistentStateManagerFactory;