From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Aikar Date: Thu, 7 May 2020 19:17:36 -0400 Subject: [PATCH] Fix Light Command This lets you run /paper fixlight (max 5) to automatically fix all light data in the chunks. diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java index 395c43f6440c1e0e47919eef096ea8a8d552ccec..f44ab1d71210e84328661c0feb662989a5635b6d 100644 --- a/src/main/java/io/papermc/paper/command/PaperCommand.java +++ b/src/main/java/io/papermc/paper/command/PaperCommand.java @@ -2,6 +2,7 @@ package io.papermc.paper.command; import io.papermc.paper.command.subcommands.ChunkDebugCommand; import io.papermc.paper.command.subcommands.EntityCommand; +import io.papermc.paper.command.subcommands.FixLightCommand; import io.papermc.paper.command.subcommands.HeapDumpCommand; import io.papermc.paper.command.subcommands.ReloadCommand; import io.papermc.paper.command.subcommands.VersionCommand; @@ -42,6 +43,7 @@ public final class PaperCommand extends Command { commands.put(Set.of("reload"), new ReloadCommand()); commands.put(Set.of("version"), new VersionCommand()); commands.put(Set.of("debug", "chunkinfo"), new ChunkDebugCommand()); + commands.put(Set.of("fixlight"), new FixLightCommand()); return commands.entrySet().stream() .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) diff --git a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..190df802cb24aa360f6cf4d291e38b4b3fe4a2ac --- /dev/null +++ b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java @@ -0,0 +1,121 @@ +package io.papermc.paper.command.subcommands; + +import io.papermc.paper.command.PaperSubcommand; +import java.util.ArrayDeque; +import java.util.Deque; +import net.minecraft.server.MCUtil; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.ThreadedLevelLightEngine; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.LevelChunk; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.DefaultQualifier; + +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.format.NamedTextColor.GREEN; +import static net.kyori.adventure.text.format.NamedTextColor.RED; + +@DefaultQualifier(NonNull.class) +public final class FixLightCommand implements PaperSubcommand { + @Override + public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { + this.doFixLight(sender, args); + return true; + } + + private void doFixLight(final CommandSender sender, final String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage(text("Only players can use this command", RED)); + return; + } + @Nullable Runnable post = null; + int radius = 2; + if (args.length > 0) { + try { + final int parsed = Integer.parseInt(args[0]); + if (parsed < 0) { + sender.sendMessage(text("Radius cannot be negative!", RED)); + return; + } + final int maxRadius = 5; + radius = Math.min(maxRadius, parsed); + if (radius != parsed) { + post = () -> sender.sendMessage(text("Radius '" + parsed + "' was not in the required range [0, " + maxRadius + "], it was lowered to the maximum (" + maxRadius + " chunks).", RED)); + } + } catch (final Exception e) { + sender.sendMessage(text("'" + args[0] + "' is not a valid number.", RED)); + 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 queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius)); + updateLight(sender, world, lightengine, queue, post); + } + + private void updateLight( + final CommandSender sender, + final ServerLevel world, + final ThreadedLevelLightEngine lightengine, + final Deque queue, + final @Nullable Runnable done + ) { + @Nullable ChunkPos coord = queue.poll(); + if (coord == null) { + sender.sendMessage(text("All Chunks Light updated", GREEN)); + if (done != null) { + done.run(); + } + return; + } + world.getChunkSource().getChunkAtAsynchronously(coord.x, coord.z, false, false).whenCompleteAsync((either, ex) -> { + if (ex != null) { + sender.sendMessage(text("Error loading chunk " + coord, RED)); + updateLight(sender, world, lightengine, queue, done); + return; + } + @Nullable LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) either.left().orElse(null); + if (chunk == null) { + updateLight(sender, world, lightengine, queue, done); + return; + } + lightengine.setTaskPerBatch(world.paperConfig().misc.lightQueueSize + 16 * 256); // ensure full chunk can fit into queue + sender.sendMessage(text("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(); + @Nullable 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, done); + }); + }); + } else { + updateLight(sender, world, lightengine, queue, done); + } + lightengine.setTaskPerBatch(world.paperConfig().misc.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 375f66bf1617e255b3465b736076a65e080eb36e..d95db45e21861eb9f1623c44dd797429ae158760 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -141,6 +141,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider private final ChunkTaskPriorityQueueSorter queueSorter; private final ProcessorHandle> worldgenMailbox; public final ProcessorHandle> mainThreadMailbox; + // Paper start + final ProcessorHandle> 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; @@ -264,11 +270,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.progressListener = worldGenerationProgressListener; this.chunkStatusListener = chunkStatusChangeListener; - ProcessorMailbox threadedmailbox1 = ProcessorMailbox.create(executor, "light"); + ProcessorMailbox lightthreaded; ProcessorMailbox 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;