From ac971bee981bb4fc593be811e0421f517ab22f15 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Tue, 18 Jun 2024 15:22:14 -0700 Subject: [PATCH] Add relight command This is simply for feature parity with Paper. --- .../ThreadedLevelLightEngineMixin.java | 97 +++++++++++++++++++ .../player/RegionizedPlayerChunkLoader.java | 17 +++- .../scheduling/ChunkHolderManager.java | 1 - .../scheduling/ChunkTaskScheduler.java | 7 ++ .../util/ParallelSearchRadiusIteration.java | 2 +- .../patches/command/MoonriseCommand.java | 87 +++++++++++++++++ .../light/StarLightLightingProvider.java | 9 ++ 7 files changed, 217 insertions(+), 3 deletions(-) diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/starlight/lightengine/ThreadedLevelLightEngineMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/starlight/lightengine/ThreadedLevelLightEngineMixin.java index d5b2aac9..433faf87 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/starlight/lightengine/ThreadedLevelLightEngineMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/starlight/lightengine/ThreadedLevelLightEngineMixin.java @@ -1,12 +1,21 @@ package ca.spottedleaf.moonrise.mixin.starlight.lightengine; +import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; +import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder; import ca.spottedleaf.moonrise.patches.starlight.light.StarLightInterface; import ca.spottedleaf.moonrise.patches.starlight.light.StarLightLightingProvider; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientboundLightUpdatePacket; +import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkTaskPriorityQueueSorter; import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ThreadedLevelLightEngine; +import net.minecraft.server.network.ServerGamePacketListenerImpl; import net.minecraft.util.thread.ProcessorHandle; import net.minecraft.util.thread.ProcessorMailbox; import net.minecraft.world.level.ChunkPos; @@ -24,8 +33,17 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; +import java.util.function.IntConsumer; import java.util.function.IntSupplier; import java.util.function.Supplier; @@ -78,6 +96,85 @@ private void queueTaskForSection(final int chunkX, final int chunkY, final int c }); } + @Override + public final int starlight$serverRelightChunks(final Collection chunks0, + final Consumer chunkLightCallback, + final IntConsumer onComplete) { + final Set chunks = new LinkedHashSet<>(chunks0); + final Map ticketIds = new HashMap<>(); + final ServerLevel world = (ServerLevel)this.starlight$getLightEngine().getWorld(); + + for (final Iterator iterator = chunks.iterator(); iterator.hasNext();) { + final ChunkPos pos = iterator.next(); + + final Long id = ChunkTaskScheduler.getNextChunkRelightId(); + world.getChunkSource().addRegionTicket(ChunkTaskScheduler.CHUNK_RELIGHT, pos, 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(ChunkStatus.LIGHT)) { + // cannot relight this chunk + iterator.remove(); + ticketIds.remove(pos); + world.getChunkSource().removeRegionTicket(ChunkTaskScheduler.CHUNK_RELIGHT, pos, StarLightInterface.REGION_LIGHT_TICKET_LEVEL, id); + continue; + } + } + + ((ChunkSystemServerLevel)world).moonrise$getChunkTaskScheduler().radiusAwareScheduler.queueInfiniteRadiusTask(() -> { + ThreadedLevelLightEngineMixin.this.starlight$getLightEngine().relightChunks( + chunks, + (final ChunkPos pos) -> { + if (chunkLightCallback != null) { + chunkLightCallback.accept(pos); + } + + ((ChunkSystemServerLevel)world).moonrise$getChunkTaskScheduler().scheduleChunkTask(pos.x, pos.z, () -> { + final NewChunkHolder chunkHolder = ((ChunkSystemServerLevel)world).moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder( + pos.x, pos.z + ); + + if (chunkHolder == null) { + return; + } + + final List players = ((ChunkSystemChunkHolder)chunkHolder.vanillaChunkHolder).moonrise$getPlayers(false); + + if (players.isEmpty()) { + return; + } + + final Packet relightPacket = new ClientboundLightUpdatePacket( + pos, (ThreadedLevelLightEngine)(Object)ThreadedLevelLightEngineMixin.this, + null, null + ); + + for (final ServerPlayer player : players) { + final ServerGamePacketListenerImpl conn = player.connection; + if (conn != null) { + conn.send(relightPacket); + } + } + }); + }, + (final int relight) -> { + if (onComplete != null) { + onComplete.accept(relight); + } + + for (final Map.Entry entry : ticketIds.entrySet()) { + world.getChunkSource().removeRegionTicket( + ChunkTaskScheduler.CHUNK_RELIGHT, entry.getKey(), + StarLightInterface.REGION_LIGHT_TICKET_LEVEL, entry.getValue() + ); + } + } + ); + }); + + return chunks.size(); + } + /** * @reason Destroy old chunk system hook * @author Spottedleaf diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java index 4bc1a6f7..4f7be7ee 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java @@ -14,6 +14,7 @@ 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.HashCommon; import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; import it.unimi.dsi.fastutil.longs.LongArrayList; @@ -58,7 +59,7 @@ public final class RegionizedPlayerChunkLoader { 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); @@ -106,6 +107,10 @@ public void setSendViewDistance(final int distance) { return param.setTickViewDistance(distance); }); } + + public JsonObject toJson() { + return this.getViewDistances().toJson(); + } } public static final record ViewDistances( @@ -124,6 +129,16 @@ public ViewDistances setLoadViewDistance(final int distance) { 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) { diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java index ffd08511..6b798306 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java @@ -1282,7 +1282,6 @@ static List getCurrentTicketUpdateScheduling() { } private boolean processTicketUpdates(final boolean processFullUpdates, List scheduledTasks) { - 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"); } diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java index dbc40fa3..86707580 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkTaskScheduler.java @@ -112,6 +112,13 @@ public static Long getNextPoiLoadId() { 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); diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ParallelSearchRadiusIteration.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ParallelSearchRadiusIteration.java index 51c9ed3d..3a9a564e 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ParallelSearchRadiusIteration.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/util/ParallelSearchRadiusIteration.java @@ -9,7 +9,7 @@ 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 diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/command/MoonriseCommand.java b/src/main/java/ca/spottedleaf/moonrise/patches/command/MoonriseCommand.java index 7ea5c0c2..75fe0507 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/command/MoonriseCommand.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/command/MoonriseCommand.java @@ -1,21 +1,34 @@ package ca.spottedleaf.moonrise.patches.command; +import ca.spottedleaf.moonrise.common.util.CoordinateUtils; import ca.spottedleaf.moonrise.common.util.MoonriseCommon; +import ca.spottedleaf.moonrise.common.util.MoonriseConstants; import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; +import ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer; import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder; +import ca.spottedleaf.moonrise.patches.starlight.light.StarLightLightingProvider; import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.context.CommandContext; +import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.network.chat.MutableComponent; import net.minecraft.network.chat.contents.PlainTextContents; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Mth; +import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ImposterProtoChunk; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.ProtoChunk; +import net.minecraft.world.phys.Vec3; import java.io.File; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; public final class MoonriseCommand { @@ -38,6 +51,17 @@ public static void register(final CommandDispatcher dispatch .executes((final CommandContext ctx) -> { return MoonriseCommand.reload(ctx); }) + ).then( + Commands.literal("relight") + .executes((final CommandContext ctx) -> { + return MoonriseCommand.relight(ctx, 10); + }) + .then( + Commands.argument("radius", IntegerArgumentType.integer(0, MoonriseConstants.MAX_VIEW_DISTANCE)) + .executes((final CommandContext ctx) -> { + return MoonriseCommand.relight(ctx, IntegerArgumentType.getInteger(ctx, "radius")); + }) + ) ) ); } @@ -151,4 +175,67 @@ public static int reload(final CommandContext ctx) { return 0; } + + public static int relight(final CommandContext ctx, final int radius) { + final Vec3 center = ctx.getSource().getPosition(); + + final int centerChunkX = Mth.floor(center.x) >> 4; + final int centerChunkZ = Mth.floor(center.z) >> 4; + + final List chunks = new ArrayList<>(); + + final LongOpenHashSet seen = new LongOpenHashSet(); + final LongArrayFIFOQueue queue = new LongArrayFIFOQueue(); + + final long zero = CoordinateUtils.getChunkKey(0, 0); + + seen.add(zero); + queue.enqueue(zero); + chunks.add(new ChunkPos(centerChunkX, centerChunkZ)); + + final int[][] offsets = new int[][] { + new int[] { -1, 0 }, + new int[] { 1, 0 }, + new int[] { 0, -1 }, + new int[] { 0, 1 } + }; + + while (!queue.isEmpty()) { + final long chunk = queue.dequeueLong(); + final int chunkX = CoordinateUtils.getChunkX(chunk); + final int chunkZ = CoordinateUtils.getChunkZ(chunk); + + for (final int[] offset : offsets) { + final int neighbourX = chunkX + offset[0]; + final int neighbourZ = chunkZ + offset[1]; + final long neighbour = CoordinateUtils.getChunkKey(neighbourX, neighbourZ); + + final int dist = Math.max(Math.abs(neighbourX), Math.abs(neighbourZ)); + + if (dist > radius || !seen.add(neighbour)) { + continue; + } + + queue.enqueue(neighbour); + chunks.add(new ChunkPos(neighbourX + centerChunkX, neighbourZ + centerChunkZ)); + } + } + + + final int ret = ((StarLightLightingProvider)ctx.getSource().getLevel().getLightEngine()).starlight$serverRelightChunks( + chunks, + null, + null + ); + + ctx.getSource().sendSuccess(() -> { + return MutableComponent.create( + new PlainTextContents.LiteralContents( + "Relighting " + ret + " chunks" + ) + ); + }, true); + + return ret; + } } diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightLightingProvider.java b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightLightingProvider.java index 39ec319d..7fe59ab7 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightLightingProvider.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/starlight/light/StarLightLightingProvider.java @@ -5,6 +5,9 @@ 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 { @@ -17,4 +20,10 @@ public interface StarLightLightingProvider { 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(); + } + }