2022-08-25 12:59:08 +02:00
|
|
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
|
|
From: Brokkonaut <hannos17@gmx.de>
|
|
|
|
Date: Sat, 18 Dec 2021 19:43:36 +0100
|
|
|
|
Subject: [PATCH] Workaround for client lag spikes (MC-162253)
|
|
|
|
|
|
|
|
When crossing certain chunk boundaries, the client needlessly
|
|
|
|
calculates light maps for chunk neighbours. In some specific map
|
|
|
|
configurations, these calculations cause a 500ms+ freeze on the Client.
|
|
|
|
|
|
|
|
This patch basically serves as a workaround by sending light maps
|
|
|
|
to the client, so that it doesn't attempt to calculate them.
|
|
|
|
This mitigates the frametime impact to a minimum (but it's still there).
|
|
|
|
|
|
|
|
Original patch by: MeFisto94 <MeFisto94@users.noreply.github.com>
|
|
|
|
Co-authored-by: =?UTF-8?q?Dani=C3=ABl=20Goossens?= <daniel@goossens.ch>
|
|
|
|
Co-authored-by: Nassim Jahnke <nassim@njahnke.dev>
|
|
|
|
|
2023-06-08 19:25:32 +02:00
|
|
|
Fixed in 1.20 with new light engine
|
|
|
|
|
2022-08-25 12:59:08 +02:00
|
|
|
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
2023-03-14 22:10:53 +01:00
|
|
|
index 95c50a36dc1e03ae8ab8ca89a96d1ea56da8d94c..fbe209a66c77c47935ad026dd3e45e682af91fd8 100644
|
2022-08-25 12:59:08 +02:00
|
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
2023-03-14 22:10:53 +01:00
|
|
|
@@ -1376,6 +1376,46 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
|
|
});
|
2022-08-25 12:59:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
+ // Paper start - Fix MC-162253
|
|
|
|
+ /**
|
|
|
|
+ * Returns the light mask for the given chunk consisting of all non-empty sections that may need sending.
|
|
|
|
+ */
|
|
|
|
+ private BitSet lightMask(final LevelChunk chunk) {
|
|
|
|
+ final net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunk.getSections();
|
|
|
|
+ final BitSet mask = new BitSet(this.lightEngine.getLightSectionCount());
|
|
|
|
+
|
|
|
|
+ // There are 2 more light sections than chunk sections so when iterating over
|
|
|
|
+ // sections we have to increment the index by 1
|
|
|
|
+ for (int i = 0; i < sections.length; i++) {
|
|
|
|
+ if (!sections[i].hasOnlyAir()) {
|
|
|
|
+ // Whenever a section is not empty, it can change lighting for the section itself (i + 1), the section below, and the section above
|
|
|
|
+ mask.set(i);
|
|
|
|
+ mask.set(i + 1);
|
|
|
|
+ mask.set(i + 2);
|
|
|
|
+ i++; // We can skip the already set upper section
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return mask;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Returns the ceiling light mask of all sections that are equal or lower to the highest non-empty section.
|
|
|
|
+ */
|
|
|
|
+ private BitSet ceilingLightMask(final LevelChunk chunk) {
|
|
|
|
+ final net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunk.getSections();
|
|
|
|
+ for (int i = sections.length - 1; i >= 0; i--) {
|
|
|
|
+ if (!sections[i].hasOnlyAir()) {
|
|
|
|
+ // Add one to get the light section, one because blocks in the section above may change, and another because BitSet's toIndex is exclusive
|
|
|
|
+ final int highest = i + 3;
|
|
|
|
+ final BitSet mask = new BitSet(highest);
|
|
|
|
+ mask.set(0, highest);
|
|
|
|
+ return mask;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return new BitSet();
|
|
|
|
+ }
|
|
|
|
+ // Paper end - Fix MC-162253
|
|
|
|
+
|
|
|
|
// Paper start - Anti-Xray - Bypass
|
|
|
|
private void playerLoadedChunk(ServerPlayer player, MutableObject<java.util.Map<Object, ClientboundLevelChunkWithLightPacket>> cachedDataPackets, LevelChunk chunk) {
|
|
|
|
if (cachedDataPackets.getValue() == null) {
|
2023-03-14 22:10:53 +01:00
|
|
|
@@ -1384,6 +1424,45 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
2022-08-25 12:59:08 +02:00
|
|
|
|
|
|
|
Boolean shouldModify = chunk.getLevel().chunkPacketBlockController.shouldModify(player, chunk);
|
|
|
|
player.trackChunk(chunk.getPos(), (Packet) cachedDataPackets.getValue().computeIfAbsent(shouldModify, (s) -> {
|
|
|
|
+ // Paper start - Fix MC-162253
|
|
|
|
+ final int viewDistance = getEffectiveViewDistance();
|
|
|
|
+ final int playerChunkX = player.getBlockX() >> 4;
|
|
|
|
+ final int playerChunkZ = player.getBlockZ() >> 4;
|
|
|
|
+
|
|
|
|
+ // For all loaded neighbours, send sky light for empty sections above highest non-empty section (+1) of the center chunk
|
|
|
|
+ // otherwise the client will try to calculate lighting there on its own
|
|
|
|
+ final BitSet lightMask = lightMask(chunk);
|
|
|
|
+ if (!lightMask.isEmpty()) {
|
|
|
|
+ for (int x = -1; x <= 1; x++) {
|
|
|
|
+ for (int z = -1; z <= 1; z++) {
|
|
|
|
+ if (x == 0 && z == 0) {
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!chunk.isNeighbourLoaded(x, z)) {
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ final int neighborChunkX = chunk.getPos().x + x;
|
|
|
|
+ final int neighborChunkZ = chunk.getPos().z + z;
|
|
|
|
+ final int distX = Math.abs(playerChunkX - neighborChunkX);
|
|
|
|
+ final int distZ = Math.abs(playerChunkZ - neighborChunkZ);
|
|
|
|
+ if (Math.max(distX, distZ) > viewDistance) {
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ final LevelChunk neighbor = chunk.getRelativeNeighbourIfLoaded(x, z);
|
|
|
|
+ final BitSet updateLightMask = (BitSet) lightMask.clone();
|
|
|
|
+ updateLightMask.andNot(ceilingLightMask(neighbor));
|
|
|
|
+ if (updateLightMask.isEmpty()) {
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ player.connection.send(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(new ChunkPos(neighborChunkX, neighborChunkZ), this.lightEngine, updateLightMask, null, true));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ // Paper end - Fix MC-162253
|
|
|
|
return new ClientboundLevelChunkWithLightPacket(chunk, this.lightEngine, (BitSet) null, (BitSet) null, true, (Boolean) s);
|
|
|
|
}));
|
|
|
|
// Paper end
|