aboutsummaryrefslogtreecommitdiffhomepage
path: root/Spigot-Server-Patches/0496-Workaround-for-Client-Lag-Spikes-MC-162253.patch
blob: 36d635ac90b3e59cd642bedc450789104a4e5bc6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MeFisto94 <MeFisto94@users.noreply.github.com>
Date: Tue, 12 May 2020 23:02:43 +0200
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).

diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
index 7f508b9ef616071b1adeef7c00da7f4565ef4ddd..84dc89d961bde16f96dba5cf7f2ce4b85564215a 100644
--- a/src/main/java/net/minecraft/server/Chunk.java
+++ b/src/main/java/net/minecraft/server/Chunk.java
@@ -278,7 +278,7 @@ public class Chunk implements IChunkAccess {
 
                     // broadcast
                     Object[] backingSet = inRange.getBackingSet();
-                    Packet[] chunkPackets = new Packet[2];
+                    Packet[] chunkPackets = new Packet[10];
                     for (int index = 0, len = backingSet.length; index < len; ++index) {
                         Object temp = backingSet[index];
                         if (!(temp instanceof EntityPlayer)) {
diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java
index 860dc98ab4f84c470b27726314943936d23fcb79..8d45588ecfa33b8c7335df3db58ed6865b8c956c 100644
--- a/src/main/java/net/minecraft/server/ChunkSection.java
+++ b/src/main/java/net/minecraft/server/ChunkSection.java
@@ -100,6 +100,7 @@ public class ChunkSection {
         return this.nonEmptyBlockCount == 0;
     }
 
+    public static boolean isEmpty(@Nullable ChunkSection chunksection) { return a(chunksection) ; } // Paper - OBFHELPER
     public static boolean a(@Nullable ChunkSection chunksection) {
         return chunksection == Chunk.a || chunksection.c();
     }
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
index 3c758c74f24443031d905dad4c0d7460762b89fb..df315700fb28d682f406f8122e7cc187dbc13273 100644
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
@@ -1956,12 +1956,112 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
 
     }
 
+    // Paper start
+    private static int getLightMask(final Chunk chunk) {
+        final ChunkSection[] chunkSections = chunk.getSections();
+        int mask = 0;
+
+        for (int i = 0; i < chunkSections.length; ++i) {
+            /*
+
+
+Lightmasks have 18 bits, from the -1 (void) section until the 17th (air) section.
+Sections go from 0..16. Now whenever a section is not empty, it can potentially change lighting for the section itself, the section below and the section above, hence the bitmask 111b, which is 7d.
+
+             */
+            mask |= (ChunkSection.isEmpty(chunkSections[i]) ? 0 : 7) << i;
+        }
+
+        return mask;
+    }
+
+    private static int getCeilingLightMask(final Chunk chunk) {
+        int mask = getLightMask(chunk);
+
+        /*
+         It is similar to get highest bit, it would turn an 001010 into an 001111 so basically the highest bit and all below.
+         We then invert this, so we'd have 110000 and compare that to the "main" chunk.
+         This is because the bug only appears when the current chunks lightmaps are higher than those of the neighbors, thus we can omit sending neighbors which are lower than the current chunks lights.
+
+         so TLDR is that getCeilingLightMask returns a light mask with all bits set below the highest affected section. We could also count the number of leading zeros and invert them, somehow.
+         @TODO: Implement Leafs suggestion
+         either use Integer#numberOfLeadingZeros or document what this bithack is supposed to be doing then
+         */
+        mask |= mask >> 1;
+        mask |= mask >> 2;
+        mask |= mask >> 4;
+        mask |= mask >> 8;
+        mask |= mask >> 16;
+
+        return mask;
+    }
+    // Paper end
+
     final void sendChunk(EntityPlayer entityplayer, Packet<?>[] apacket, Chunk chunk) { this.a(entityplayer, apacket, chunk); } // Paper - OBFHELPER
     private void a(EntityPlayer entityplayer, Packet<?>[] apacket, Chunk chunk) {
         if (apacket[0] == null) {
+            // Paper start - add 8 for light fix workaround
+            if (apacket.length != 10) { // in case Plugins call sendChunk, resize
+                apacket = new Packet[10];
+            }
+            // Paper end
             apacket[0] = new PacketPlayOutMapChunk(chunk, 65535, true);
             apacket[1] = new PacketPlayOutLightUpdate(chunk.getPos(), this.lightEngine, true);
+
+            // Paper start - Fix MC-162253
+            final int lightMask = getLightMask(chunk);
+            int i = 1;
+            for (int x = -1; x <= 1; x++) {
+                for (int z = -1; z <= 1; z++) {
+                    if (x == 0 && z == 0) {
+                        continue;
+                    }
+
+                    ++i;
+
+                    if (!chunk.isNeighbourLoaded(x, z)) {
+                        continue;
+                    }
+
+                    final Chunk neighbor = chunk.getRelativeNeighbourIfLoaded(x, z);
+                    final int updateLightMask = lightMask & ~getCeilingLightMask(neighbor);
+
+                    if (updateLightMask == 0) {
+                        continue;
+                    }
+
+                    apacket[i] = new PacketPlayOutLightUpdate(new ChunkCoordIntPair(chunk.getPos().x + x, chunk.getPos().z + z), lightEngine, updateLightMask, 0, true);
+                }
+            }
+        }
+
+        final int viewDistance = playerViewDistanceBroadcastMap.getLastViewDistance(entityplayer);
+        final long lastPosition = playerViewDistanceBroadcastMap.getLastCoordinate(entityplayer);
+
+        int j = 1;
+        for (int x = -1; x <= 1; x++) {
+            for (int z = -1; z <= 1; z++) {
+                if (x == 0 && z == 0) {
+                    continue;
+                }
+
+                ++j;
+
+                Packet<?> packet = apacket[j];
+                if (packet == null) {
+                    continue;
+                }
+
+                final int distX = Math.abs(MCUtil.getCoordinateX(lastPosition) - (chunk.getPos().x + x));
+                final int distZ = Math.abs(MCUtil.getCoordinateZ(lastPosition) - (chunk.getPos().z + z));
+
+                if (Math.max(distX, distZ) > viewDistance) {
+                    continue;
+                }
+                entityplayer.playerConnection.sendPacket(packet);
+            }
         }
+        // Paper end - Fix MC-162253
 
         entityplayer.a(chunk.getPos(), apacket[0], apacket[1]);
         PacketDebug.a(this.world, chunk.getPos());