The code in ChunkSelection.java
that reports if a specific part of a chunk needs to be delivered to a client or not is faulty:
I'm not sure what the Class is named in the obfuscation. In the partially deobfuscated code it's ChunkSelection.java
and the method's name is public boolean a()
.
I'm guessing it's public boolean hasBlockData()
in the original code
ChunkSelection.java
public boolean a(){
return this.nonEmptyBlockCount == 0;
}
This method is called when a Chunk is placed in the OutgoingQueue to a client. It checks if a ChunkSelection should be delivered or not.
The code does not consider the fact that - even if there is no block in a chunkselection - there may still be light-information for that selection.
This results in light-Bugs on the client side if a Chunk hast light-information in a chunkselection that does not contain any blocks.
World Seed for reproduction: -9208998853395889608 ( 1.8.x WorldGenerator. Specifically 1.8.6 singleplayer in the video)
This Video presents the bug: https://www.youtube.com/watch?v=sfikoyETsJU ( 63 Seconds )
Video content:
Connecting to a Singleplayer World (same in Multiplayer)
Two chunks have obvious light-Information missing
Teleporting does not fix the problem
Placing a Block inside that specific chunkselection - thus working around the bug - does not fix the bug initially as the information only get updated on ChunkSending.
Teleporting post the workaround does fix the bug
Removing the Block - recreating the empty chunkselection and recreating the bug does work post teleport
F3
shows we are exactly at the edge of a chunkSelection (63/64)The client-side light-updates in the area around a player does only fix the problem temporarily
This can be fixed by changing the code:
ChunkSelection.java
public boolean a(){
// return this.nonEmptyBlockCount == 0;
return false; //kade fix, bukkit/mc reports wrongly if a part of a chunk is empty or not.
}
Performance and Gameplay implications of this bugfix
Performance
The performance impact is negligible. The problem occurs in <5% of chunks. Specifically: It occurs in every Chunk that has Blocks at the top of one ChunkSelection and light-information in the one above that but no blocks.
Bandwidth
The Bandwidth increases a small amount for the <5% Chunks. But as all that is being sent is the light-map we are talking about a compressed data of only some bytes more.
Gameplay
There are absolutely no side-effects of this fix. I have been running this code-change on my servers for at least 1 1/2 years now (concurrent playercount > 300).
Conclusion
In essence the Idea behind the "nonEmptyBlockCount == 0"-Check is good. But its neglecting the fact that there might be light-information present. As ChunkSelections without any lightinformation are null anyways there is no need for that check.
Linked issues
is duplicated by 45
relates to 2
Attachments
Comments 23
Affects 1.9-pre2 also! Just tried. Same thing, place a torch in a chunk and it doesn't go across the boundary
Using MCP Names: ChunkSelection
is ExtendedBlockStorage
, and a
is isEmpty
. In 1.10 ExtendedBlockStorage
is obfuscated as asw
(you can find this in any version by searching for a String found in Chunk
, and then finding the storage array member - the string I used was "Could not set level chunk heightmap, array length is "
).
MCP names are way better for dealing with the base code than NMS, since it's got a lot more stuff renamed (and renamed sanely, and in separate packages... It's actually designed to be used by developers, unlike NMS where it's explicitly stated that things may be poorly named and direct use should be avoided. Also, even the non-renamed stuff is named uniquely, so there's only ever one non-renamed method named something (rather than there being two a
methods, you'll see (for example) func_184161_a
and func_184159_a
)). You can also get the obfuscated names from the deobfuscated ones by looking at methods.csv or fields.csv and then using the SRG name found there in the joined.srg file (for class names you only need joined.srg). You can get MCP here.
That said: there is a performance cost to your fix, though it isn't as severe when network compression is factored in. You're still sending a chunk section of air, which is (due to the way the the chunk section is formatted) always going to use at least 4 bits per block (and would have used 16 in 1.8). That's 2048 bytes per section, uncompressed, though all the data is the same and it'll probably be pretty small when compressed.
Also, there are two types of empty chunk sections that can be found. The first is when isEmpty
returns true, and the second is when the section itself is null (see Chunk.NULL_BLOCK_STORAGE
). Your solution only covers the case where isEmpty
is true, but NULL_BLOCK_STORAGE
may also apply (though it looks like there may not be any cases where a section should be lit but is null, since they're created when lighting hits them; it's still a possible concern).
I would recommend instead that a protocol change is made to allow using 0 bits per block to indicate a section that has no blocks, but still has light. Those sections (and their light data) would still be sent, but no block data would need to be sent. (And the bits per block field already exists and currently won't ever be 0, so it wouldn't require adding any new data to the packet). Depending on whether NULL_BLOCK_STORAGE
turns out to be an issue, it may make sense to remove the primary bit mask from the chunk data packet, but it may not be necessary if it can be reasonably confirmed that there is no light data in those sections. The 0 bits per block thing would be implementable by another IBlockStatePalette
, which only ever has a length of 0 and always returns air. It would require client and server code changes, so a serverside mod couldn't do that without client modification, but overall it would be an improvement.
The isEmpty
solution does work for servers in the meantime, but it's not ideal.
Here's what's happening in 1.13.2. The torch one is happening at a chunk boundary.
[media][media][media][media]The issue was fixed in 18w43a (the first snapshot for 1.14), but is still present in 1.13.2 (it hadn't been fixed then, but since 18w43a was released 2 days after 1.13.2 it was never added). I've added 1.13.2 to the affected versions for clarity.
Thanks for the info. I'm rather clumsy with snapshot names and didn't realize it was fixed for 1.14.
I really don't understand where I'm supposed to change that code or if im supposed to, having this issue in 1.14 prerelease 2.
Still present in 16w07b.