mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-27 19:09:22 +00:00
If a chunk is already in the sentChunks set, we simply return early instead of throwing an exception. Made moonrise$addReceivedChunk call to gracefully handle the case where a chunk is already marked as received. If the chunk was already received, we remove the chunk from our sent list and return early.
135 lines
7.3 KiB
Diff
135 lines
7.3 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Taiyou06 <kaandindar21@gmail.com>
|
|
Date: Sun, 2 Mar 2025 21:23:20 +0100
|
|
Subject: [PATCH] Async ChunkSend
|
|
|
|
|
|
diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
|
|
index a35e9fae8f8da0c42f0616c4f78dc396492673aa..4818e6d8f2fd098a63e14f15644d2eb25227b6d4 100644
|
|
--- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
|
|
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
|
|
@@ -22,15 +22,13 @@ import it.unimi.dsi.fastutil.longs.LongComparator;
|
|
import it.unimi.dsi.fastutil.longs.LongHeapPriorityQueue;
|
|
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
|
import net.minecraft.network.protocol.Packet;
|
|
-import net.minecraft.network.protocol.game.ClientboundForgetLevelChunkPacket;
|
|
-import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket;
|
|
-import net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket;
|
|
-import net.minecraft.network.protocol.game.ClientboundSetSimulationDistancePacket;
|
|
+import net.minecraft.network.protocol.game.*;
|
|
import net.minecraft.server.level.ChunkTrackingView;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.server.level.ServerPlayer;
|
|
import net.minecraft.server.level.TicketType;
|
|
import net.minecraft.server.network.PlayerChunkSender;
|
|
+import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
|
import net.minecraft.world.level.ChunkPos;
|
|
import net.minecraft.world.level.GameRules;
|
|
import net.minecraft.world.level.chunk.ChunkAccess;
|
|
@@ -43,6 +41,8 @@ import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicLong;
|
|
import java.util.function.Function;
|
|
|
|
+import static org.dreeam.leaf.config.LeafConfig.LOGGER;
|
|
+
|
|
public final class RegionizedPlayerChunkLoader {
|
|
|
|
public static final TicketType<Long> PLAYER_TICKET = TicketType.create("chunk_system:player_ticket", Long::compareTo);
|
|
@@ -411,18 +411,90 @@ public final class RegionizedPlayerChunkLoader {
|
|
this.delayedTicketOps.addLast(op);
|
|
}
|
|
|
|
+ /**
|
|
+ * Sends a chunk to the player.
|
|
+ * If async chunk sending is enabled, this will prepare and send the chunk packet asynchronously.
|
|
+ * Otherwise, it will use the synchronous chunk sending implementation.
|
|
+ */
|
|
private void sendChunk(final int chunkX, final int chunkZ) {
|
|
- if (this.sentChunks.add(CoordinateUtils.getChunkKey(chunkX, chunkZ))) {
|
|
- ((ChunkSystemChunkHolder)((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager
|
|
- .getChunkHolder(chunkX, chunkZ).vanillaChunkHolder).moonrise$addReceivedChunk(this.player);
|
|
+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ);
|
|
+
|
|
+ if (!this.sentChunks.add(chunkKey)) {
|
|
+ // Already in our sent list - silently return instead of throwing an exception
|
|
+ return;
|
|
+ }
|
|
|
|
- final LevelChunk chunk = ((ChunkSystemLevel)this.world).moonrise$getFullChunkIfLoaded(chunkX, chunkZ);
|
|
+ // Get the chunk now, as we need it for both sync and async paths
|
|
+ final LevelChunk chunk = ((ChunkSystemLevel)this.world).moonrise$getFullChunkIfLoaded(chunkX, chunkZ);
|
|
+ if (chunk == null) {
|
|
+ // Handle case where chunk is no longer loaded
|
|
+ this.sentChunks.remove(chunkKey);
|
|
+ return;
|
|
+ }
|
|
|
|
+ // Try to mark the chunk as received by this player
|
|
+ try {
|
|
+ // This part needs to remain on the main thread as it affects shared state
|
|
+ ((ChunkSystemChunkHolder)((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager
|
|
+ .getChunkHolder(chunkX, chunkZ).vanillaChunkHolder).moonrise$addReceivedChunk(this.player);
|
|
+
|
|
+ // Call onChunkWatch on the main thread as it might affect server state
|
|
PlatformHooks.get().onChunkWatch(this.world, chunk, this.player);
|
|
- PlayerChunkSender.sendChunk(this.player.connection, this.world, chunk);
|
|
+ } catch (IllegalStateException e) {
|
|
+ // This happens if the chunk was already marked as received by this player
|
|
+ // Just remove it from our sent list and return
|
|
+ this.sentChunks.remove(chunkKey);
|
|
return;
|
|
}
|
|
- throw new IllegalStateException();
|
|
+
|
|
+ // Check if async chunk sending is enabled
|
|
+ if (org.dreeam.leaf.config.modules.async.AsyncChunkSend.enabled) {
|
|
+ // Async implementation
|
|
+ net.minecraft.Util.backgroundExecutor().execute(() -> {
|
|
+ try {
|
|
+ // Create and send the chunk packet asynchronously
|
|
+ final ServerGamePacketListenerImpl connection = this.player.connection;
|
|
+ final ServerLevel serverLevel = this.world;
|
|
+
|
|
+ // Create the packet
|
|
+ ClientboundLevelChunkWithLightPacket packet = new ClientboundLevelChunkWithLightPacket(chunk, serverLevel.getLightEngine(), null, null, serverLevel.chunkPacketBlockController.shouldModify(this.player, chunk));
|
|
+ // The packet is immediately ready
|
|
+ packet.setReady(true);
|
|
+
|
|
+ // Schedule sending on the main thread
|
|
+ serverLevel.getServer().execute(() -> {
|
|
+ if (this.removed || !this.sentChunks.contains(chunkKey)) {
|
|
+ // Player was removed or chunk was unloaded while we were preparing
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // Send the packet
|
|
+ connection.send(packet);
|
|
+
|
|
+ // Fire the load event
|
|
+ if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) {
|
|
+ new io.papermc.paper.event.packet.PlayerChunkLoadEvent(
|
|
+ new org.bukkit.craftbukkit.CraftChunk(chunk),
|
|
+ this.player.getBukkitEntity()
|
|
+ ).callEvent();
|
|
+ }
|
|
+
|
|
+ // Send POI packets if needed
|
|
+ ChunkPos pos = chunk.getPos();
|
|
+ DebugPackets.sendPoiPacketsForChunk(serverLevel, pos);
|
|
+ });
|
|
+ } catch (Exception e) {
|
|
+ // Log the exception
|
|
+ LOGGER.error("Failed to send chunk asynchronously", e);
|
|
+ if (!this.removed) {
|
|
+ this.sentChunks.remove(chunkKey);
|
|
+ }
|
|
+ }
|
|
+ });
|
|
+ } else {
|
|
+ // Original synchronous implementation
|
|
+ PlayerChunkSender.sendChunk(this.player.connection, this.world, chunk);
|
|
+ }
|
|
}
|
|
|
|
private void sendUnloadChunk(final int chunkX, final int chunkZ) {
|