diff --git a/leaf_notes.txt b/leaf_notes.txt index 6196c3d607..30da036b0f 100644 --- a/leaf_notes.txt +++ b/leaf_notes.txt @@ -1,4 +1,3 @@ -- Starlight fixlight command + method on light engine (note: add to mod to, after done this) - note: for paper, the chunk debug command - rebase IntervalledCounter into util patch - mcutil diff diff --git a/patches/server/Chunk-System-Starlight-from-Moonrise.patch b/patches/server/Chunk-System-Starlight-from-Moonrise.patch index 1524fc1e16..280222da0b 100644 --- a/patches/server/Chunk-System-Starlight-from-Moonrise.patch +++ b/patches/server/Chunk-System-Starlight-from-Moonrise.patch @@ -7443,6 +7443,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; +import ca.spottedleaf.moonrise.patches.chunk_system.util.ParallelSearchRadiusIteration; ++import com.google.gson.JsonObject; +import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongComparator; @@ -7482,7 +7483,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + public static final int LOADED_TICKET_LEVEL = ChunkTaskScheduler.getTicketLevel(ChunkStatus.EMPTY); + public static final int TICK_TICKET_LEVEL = ChunkHolderManager.ENTITY_TICKING_TICKET_LEVEL; + -+ public static class ViewDistanceHolder { ++ public static final class ViewDistanceHolder { + + private volatile ViewDistances viewDistances; + private static final VarHandle VIEW_DISTANCES_HANDLE = ConcurrentUtil.getVarHandle(ViewDistanceHolder.class, "viewDistances", ViewDistances.class); @@ -7530,6 +7531,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return param.setTickViewDistance(distance); + }); + } ++ ++ public JsonObject toJson() { ++ return this.getViewDistances().toJson(); ++ } + } + + public static final record ViewDistances( @@ -7548,6 +7553,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + public ViewDistances setSendViewDistance(final int distance) { + return new ViewDistances(this.tickViewDistance, this.loadViewDistance, distance); + } ++ ++ public JsonObject toJson() { ++ final JsonObject ret = new JsonObject(); ++ ++ ret.addProperty("tick-view-distance", this.tickViewDistance); ++ ret.addProperty("load-view-distance", this.loadViewDistance); ++ ret.addProperty("send-view-distance", this.sendViewDistance); ++ ++ return ret; ++ } + } + + public static int getAPITickViewDistance(final ServerPlayer player) { @@ -9949,7 +9964,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + private boolean processTicketUpdates(final boolean processFullUpdates, List scheduledTasks) { -+ io.papermc.paper.util.TickThread.ensureTickThread("Cannot process ticket levels off-main"); + if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) { + throw new IllegalStateException("Cannot update ticket level while unloading chunks or updating entity manager"); + } @@ -10203,6 +10217,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return Long.valueOf(POI_LOAD_IDS.getAndIncrement()); + } + ++ public static final TicketType CHUNK_RELIGHT = TicketType.create("starlight:chunk_relight", Long::compareTo); ++ private static final AtomicLong CHUNK_RELIGHT_IDS = new AtomicLong(); ++ ++ public static Long getNextChunkRelightId() { ++ return Long.valueOf(CHUNK_RELIGHT_IDS.getAndIncrement()); ++ } ++ + + public static int getTicketLevel(final ChunkStatus status) { + return ChunkLevel.byStatus(status); @@ -10541,12 +10562,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + this.scheduleChunkLoad(chunkX, chunkZ, ChunkStatus.EMPTY, addTicket, priority, (final ChunkAccess chunk) -> { + if (chunk == null) { -+ onComplete.accept(null); ++ if (onComplete != null) { ++ onComplete.accept(null); ++ } + } else { + if (chunk.getPersistedStatus().isOrAfter(toStatus)) { + this.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); + } else { -+ onComplete.accept(null); ++ if (onComplete != null) { ++ onComplete.accept(null); ++ } + } + } + }); @@ -17342,7 +17367,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import java.util.Arrays; +import java.util.Objects; + -+public class ParallelSearchRadiusIteration { ++public final class ParallelSearchRadiusIteration { + + // expected that this list returns for a given radius, the set of chunks ordered + // by manhattan distance @@ -21699,6 +21724,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.chunk.DataLayer; +import net.minecraft.world.level.chunk.LevelChunk; ++import java.util.Collection; ++import java.util.function.Consumer; ++import java.util.function.IntConsumer; + +public interface StarLightLightingProvider { + @@ -21711,6 +21739,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + public void starlight$clientChunkLoad(final ChunkPos pos, final LevelChunk chunk); + ++ public default int starlight$serverRelightChunks(final Collection chunks, ++ final Consumer chunkLightCallback, ++ final IntConsumer onComplete) throws UnsupportedOperationException { ++ throw new UnsupportedOperationException(); ++ } ++ +} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/util/SaveUtil.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/util/SaveUtil.java new file mode 100644 @@ -22159,6 +22193,140 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } private ChunkSystem() { +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -0,0 +0,0 @@ public final class PaperCommand extends Command { + commands.put(Set.of("dumpitem"), new DumpItemCommand()); + commands.put(Set.of("mobcaps", "playermobcaps"), new MobcapsCommand()); + commands.put(Set.of("dumplisteners"), new DumpListenersCommand()); ++ commands.put(Set.of("fixlight"), new FixLightCommand()); // Paper - rewrite chunk system + + 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..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.command.subcommands; ++ ++import ca.spottedleaf.moonrise.patches.starlight.light.StarLightLightingProvider; ++import io.papermc.paper.command.PaperSubcommand; ++import io.papermc.paper.util.MCUtil; ++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.ChunkAccess; ++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 java.text.DecimalFormat; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.BLUE; ++import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++ ++@DefaultQualifier(NonNull.class) ++public final class FixLightCommand implements PaperSubcommand { ++ ++ private static final ThreadLocal ONE_DECIMAL_PLACES = ThreadLocal.withInitial(() -> { ++ return new DecimalFormat("#,##0.0"); ++ }); ++ ++ @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 = 32; ++ 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(); ++ this.starlightFixLight(handle, world, lightengine, radius, post); ++ } ++ ++ private void starlightFixLight( ++ final ServerPlayer sender, ++ final ServerLevel world, ++ final ThreadedLevelLightEngine lightengine, ++ final int radius, ++ final @Nullable Runnable done ++ ) { ++ final long start = System.nanoTime(); ++ final java.util.LinkedHashSet chunks = new java.util.LinkedHashSet<>(MCUtil.getSpiralOutChunks(sender.blockPosition(), radius)); // getChunkCoordinates is actually just bad mappings, this function rets position as blockpos ++ ++ final int[] pending = new int[1]; ++ for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext(); ) { ++ final ChunkPos chunkPos = iterator.next(); ++ ++ final @Nullable ChunkAccess chunk = (ChunkAccess) world.getChunkSource().getChunkForLighting(chunkPos.x, chunkPos.z); ++ if (chunk == null || !chunk.isLightCorrect() || !chunk.getPersistedStatus().isOrAfter(net.minecraft.world.level.chunk.status.ChunkStatus.LIGHT)) { ++ // cannot relight this chunk ++ iterator.remove(); ++ continue; ++ } ++ ++ ++pending[0]; ++ } ++ ++ final int[] relitChunks = new int[1]; ++ ((StarLightLightingProvider)lightengine).starlight$serverRelightChunks(chunks, ++ (final ChunkPos chunkPos) -> { ++ ++relitChunks[0]; ++ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( ++ text("Relit chunk ", BLUE), text(chunkPos.toString()), ++ text(", progress: ", BLUE), text(ONE_DECIMAL_PLACES.get().format(100.0 * (double) (relitChunks[0]) / (double) pending[0]) + "%") ++ )); ++ }, ++ (final int totalRelit) -> { ++ final long end = System.nanoTime(); ++ sender.getBukkitEntity().sendMessage(text().color(DARK_AQUA).append( ++ text("Relit ", BLUE), text(totalRelit), ++ text(" chunks. Took ", BLUE), text(ONE_DECIMAL_PLACES.get().format(1.0e-6 * (end - start)) + "ms") ++ )); ++ if (done != null) { ++ done.run(); ++ } ++ } ++ ); ++ sender.getBukkitEntity().sendMessage(text().color(BLUE).append(text("Relighting "), text(pending[0], DARK_AQUA), text(" chunks"))); ++ } ++} diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java @@ -25646,6 +25814,85 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + world.getChunkSource().removeRegionTicket(ca.spottedleaf.moonrise.patches.starlight.light.StarLightInterface.CHUNK_WORK_TICKET, pos, ca.spottedleaf.moonrise.patches.starlight.light.StarLightInterface.REGION_LIGHT_TICKET_LEVEL, ticketId); + }); + } ++ ++ @Override ++ public final int starlight$serverRelightChunks(final java.util.Collection chunks0, ++ final java.util.function.Consumer chunkLightCallback, ++ final java.util.function.IntConsumer onComplete) { ++ final java.util.Set chunks = new java.util.LinkedHashSet<>(chunks0); ++ final java.util.Map ticketIds = new java.util.HashMap<>(); ++ final ServerLevel world = (ServerLevel)this.starlight$getLightEngine().getWorld(); ++ ++ for (final java.util.Iterator iterator = chunks.iterator(); iterator.hasNext();) { ++ final ChunkPos pos = iterator.next(); ++ ++ final Long id = ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.getNextChunkRelightId(); ++ world.getChunkSource().addRegionTicket(ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.CHUNK_RELIGHT, pos, ca.spottedleaf.moonrise.patches.starlight.light.StarLightInterface.REGION_LIGHT_TICKET_LEVEL, id); ++ ticketIds.put(pos, id); ++ ++ final ChunkAccess chunk = (ChunkAccess)world.getChunkSource().getChunkForLighting(pos.x, pos.z); ++ if (chunk == null || !chunk.isLightCorrect() || !chunk.getPersistedStatus().isOrAfter(net.minecraft.world.level.chunk.status.ChunkStatus.LIGHT)) { ++ // cannot relight this chunk ++ iterator.remove(); ++ ticketIds.remove(pos); ++ world.getChunkSource().removeRegionTicket(ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.CHUNK_RELIGHT, pos, ca.spottedleaf.moonrise.patches.starlight.light.StarLightInterface.REGION_LIGHT_TICKET_LEVEL, id); ++ continue; ++ } ++ } ++ ++ ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)world).moonrise$getChunkTaskScheduler().radiusAwareScheduler.queueInfiniteRadiusTask(() -> { ++ ThreadedLevelLightEngine.this.starlight$getLightEngine().relightChunks( ++ chunks, ++ (final ChunkPos pos) -> { ++ if (chunkLightCallback != null) { ++ chunkLightCallback.accept(pos); ++ } ++ ++ ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)world).moonrise$getChunkTaskScheduler().scheduleChunkTask(pos.x, pos.z, () -> { ++ final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder chunkHolder = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)world).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder( ++ pos.x, pos.z ++ ); ++ ++ if (chunkHolder == null) { ++ return; ++ } ++ ++ final java.util.List players = ((ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder)chunkHolder.vanillaChunkHolder).moonrise$getPlayers(false); ++ ++ if (players.isEmpty()) { ++ return; ++ } ++ ++ final net.minecraft.network.protocol.Packet relightPacket = new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket( ++ pos, (ThreadedLevelLightEngine)(Object)ThreadedLevelLightEngine.this, ++ null, null ++ ); ++ ++ for (final ServerPlayer player : players) { ++ final net.minecraft.server.network.ServerGamePacketListenerImpl conn = player.connection; ++ if (conn != null) { ++ conn.send(relightPacket); ++ } ++ } ++ }); ++ }, ++ (final int relight) -> { ++ if (onComplete != null) { ++ onComplete.accept(relight); ++ } ++ ++ for (final java.util.Map.Entry entry : ticketIds.entrySet()) { ++ world.getChunkSource().removeRegionTicket( ++ ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.CHUNK_RELIGHT, entry.getKey(), ++ ca.spottedleaf.moonrise.patches.starlight.light.StarLightInterface.REGION_LIGHT_TICKET_LEVEL, entry.getValue() ++ ); ++ } ++ } ++ ); ++ }); ++ ++ return chunks.size(); ++ } + // Paper end - rewrite chunk system public ThreadedLevelLightEngine(