mojira.dev
MC-145813

Chunks past a certain point (multiplayer cache?) fail to load when client render distance is less than server view distance.

The only way to work around this is to set the client render distance greater than server view distance, and reconnect. F3+A or just changing the render distance after the fact do not help.
There are no errors on the server command line.
There are lots of errors like this on the client game output screen (19w11a):

02:05:15 dgb Client thread warn Ignoring chunk since it's not in the view range: 16, -21

There are lots of errors like this on the client command line (19w11b):

[0316/133139:INFO:GameCallbacks.cpp(198)] game/dga (Client thread) warn Ignoring chunk since it's not in the view range: -9, 6

Steps to reproduce (tested to work with 19w12b also):

  1. Start a new world on vanilla 19w12b server with unmodified configuration. (start any vanilla 19w12b server with view distance greater than 2)

  2. Join with a vanilla 19w12b client with render distance set to 2.

  3. Walk ~100 blocks in any direction. (Walk at least (MultiplayerChunkCache ^ 0.5 + 1) * 8 blocks along either X or Z axis)

You will seemingly stand on nothing now. Logging out and reconnecting allows you to load the same amount of chunks from this point to any direction.

Logs for both client and server are included. Note that all of the "fell out of the world" messages are results of the kill command on server command line, not falling down.

I can confirm that the server sends chunks too far away when client render distance is less than the server's, only for the client to discard them, and it won't re-send them when the client would actually need them.

Full example:

  • both server and client are vanilla 19w11b.

  • client render distance: 2

  • server view distance: 10

Connected to the server standing in chunk [0, 0]
The client ignores chunks:

  • [-11, 1] through [-11, 11]

  • [-10, 11] through [-6, 11]

  • [-10, -10] through [10, 10], except for a smaller square in it, [-4, -4] through [4, 4].

The smaller square not discarded, [-4, -4] through [4, 4], is the same size as the cache displayed on the f3 overlay:
MultiplayerChunkCache: 81, 81

When walking over to chunk [1, 0], the client ignores chunks [11, -10] through [11, 10] and discards [-4, -4] through [-4, 4] leaving MultiplayerChunkCache 81, 72

Same with walking (along X) to chunk (2, 3, 4, 5); ignoring X (12, 13, 14, 15); discarding X (-3, -2, -1, 0); chunks remaining in cache (63, 54, 45, 36).

Blocks in chunk [5, 0] are not rendered (entities are).
When stepping over the edge to the seemingly nonexistent chunk [5, 0], the client ignores chunks [15, -10] through [15, 10] and discards [0, -4] through [0, 4] leaving MultiplayerChunkCache 81, 36.
When "air walking" back over to chunk [4, 0], the client (again) ignores chunks ( [-7, 1] through [-7, 11] ) and ( [-6, -10] through [-6, 0] )
At this point chunks [-3, -4] through [0, 4] have been unloaded from the client and there is no way to load them either without reconnecting to the server (this includes the chunk I connected standing in, [0, 0]). F3 overlay shows MultiplayerChunkCache: 81, 36 now.

Related issues

Attachments

Comments

migrated
[media][media][media][media][media][media][media][media][media]
migrated

My original wording "New chunks..." made it seem like this had something to do with the chunks being just generated on the server. This is not the case as this seems to happen with every configuration and every world, given that the client has a lower render distance than the server.

PhiPro

Copied my code analysis from MC-138114:

Here's a code analysis on this bug:

To take away the punch line: The issue is completely client-side. Chunks are loaded properly on the server and sent to the client. The client, however, discards them when they fall outside its view range which leads to multiple problems.

There are two codepaths on the client that throw away chunks when they fall outside the clients view distance.

(1) A filter that discards chunks right away when the chunk packet arrives.

ClientChunkManager.java

@Nullable
public WorldChunk loadChunkFromPacket(World class_1937_1, int int_1, int int_2, PacketByteBuf class_2540_1, CompoundTag class_2487_1, int int_3, boolean boolean_1) {
  this.updateChunkList();
  if (!this.chunks.hasChunk(int_1, int_2)) {
     LOGGER.warn("Ignoring chunk since it's not in the view range: {}, {}", int_1, int_2);
     return null;
  } else {
      ...

(2) A clean-up routine that unloads all chunks outside the clients view radius once per tick (or whenever a new chunk packet arrives).

private void updateChunkList() {
   int int_1 = this.chunks.loadDistance;
  int int_2 = Math.max(2, this.client.options.viewDistance + -2) + 2;
  
  ...

  int int_5 = MathHelper.floor(this.client.player.x) >> 4;
  int int_6 = MathHelper.floor(this.client.player.z) >> 4;
  if (this.playerChunkX != int_5 || this.playerChunkZ != int_6) {
     for(int int_7 = this.playerChunkZ - int_2; int_7 <= this.playerChunkZ + int_2; ++int_7) {
        for(int int_8 = this.playerChunkX - int_2; int_8 <= this.playerChunkX + int_2; ++int_8) {
           if (!isWithinDistance(int_8, int_7, int_5, int_6, int_2)) {
              this.chunks.unload(this.chunks.index(int_8, int_7), (WorldChunk)null);
           }
        }
     }

     this.playerChunkX = int_5;
     this.playerChunkZ = int_6;
  }
}

This causes (at least) three slightly different appearances of the bug.

Appearance 1: ALL chunks outside the login area are missing

When joining a multiplayer server and clientViewDistance < serverViewDistance, no chunks outside the login area are visible. This is caused by the filter codepath. The server will send chunks as soon as the player is less than serverViewDistance chunks away. However, the client will directly discard them if they fall outside clientViewDistance. So, when moving continously (ie. not teleporting) all new chunks will be discarded by the client because its view distance is stricly smaller than that of the server.

The server doesn't know about this and assumes the client knows all chunks it sent. As a result, all of those chunks will be permanently missing on the client until relogging (or moving away far enough, so the server sends them again).

This mismatch of view distances between client and server is the most servere of the three appearances.

Workaround:

As others have noticed, increasing the client view distance helps to counteract the problem. From the analysis, it is clear that you need a view distance of at least serverViewDistance.

Appearance 2: Spawn chunks and chunks loaded by other players are missing

This is a slight variation of Appearance 1, where only spawn chunks and chunks loaded by other players are missing.
One detail that I didn't mention in Appearance 1, is that the server view distance isn't actually what you configure, but it is 1 larger.

The server view distance is basically set as follows:

DedicatedServer.java

public DedicatedPlayerManager(MinecraftDedicatedServer class_3176_1) {
    super(class_3176_1, class_3176_1.getProperties().maxPlayers);
    ServerPropertiesHandler class_3806_1 = class_3176_1.getProperties();
    this.setViewDistance(class_3806_1.viewDistance, class_3806_1.viewDistance - 2);
  ...

... omitting some call levels ....

ThreadedAnvilChunkStorage.java

protected void applyViewDistance(int int_1, int int_2) {
  int int_3 = MathHelper.clamp(int_1 + 1, 3, 33);
  if (int_3 != this.viewDistance) {
     int int_4 = this.viewDistance;
     this.viewDistance= int_3;

Note the additional + 1 in MathHelper.clamp(int_1 + 1, 3, 33). This does not happen for the clientViewDistance.
Hence we should distinguish the chunkTrackingDistance = serverViewDistance + 1 where the latter is what you configure in server.properties.
Because of this, we have Appearance 1 again in disguise. It does not just happen when clientViewDistance < serverViewDistance but actually when clientViewDistance < chunkTrackingDistance.

So, why does clientViewDistance == serverViewDistance help to solve Appearance 1 in some cases?

There's another variable controlling the chunkLoadingDistance. This one is now actually == serverViewDistance (I haven't read that codepath completely to determine the exact value. But from Appearance 1 I conclude that it should be this). Because of this, the effective view distance for not yet loaded chunks is capped by the chunkLoadingDistance, ie. serverViewDistance, and so setting the client view distance to this solves Appearance 1 for all not yet loaded chunks.

However, as soon as you come near a chunk already loaded on the server, e.g. spawn chunks, the effective view distance increases to chunkTrackingDistance == serverViewDistance + 1, because you don't need to load the chunk first, so chunkLoadingDistance is irrelevant. This triggers Appearance 1 again.

Workaround:

As for Appearance 1, increase the clientViewDistance. It now becomes clear that we need to set it to at least serverViewDistance + 1.

Appearance 3: Chunks are missing when "moving too quickly"

This issue is now caused by the clean-up unloading codepath. It can be reliably produced as follows:

  • move sufficiently fast until the server teleports you back because you were "moving too quickly", e.g. when moving on a laggy server

  • turn around and move back the way you came for some distance

  • observe that there are missing chunks

Because of the clean-up unloading path, the client unloads the chunks behind you as soon as they get out of the client's view range. When the player is then teleported back by the server, those chunks will be missing. As before, the server doesn't know the client unloaded them and hence won't send them again.

From the explanation, it gets clear why this only happens for chunks behind you but not for chunks in front of you.

Workaround:

Again, increase the client view distance. As it gets larger, the probability of encountering this issue gets smaller, as you need to be teleported back at least chunkTrackingDistance - clientViewDistance chunks. Note that setting the client view distance higher than serverViewDistance + 1 shouldn't cause any harm, because the server won't send more chunks anyway. Hence I recommend setting it to 32.

Proposed Solution

The client should tell the server its view distance. And the server should take that into account for determining which chunks to send to the client. Whenever the server determines that the client should unload a chunk, it should tell it to do so (this mechanism already exists). The client should never automatically unload any chunks. (As mentioned above, allowing the client to automatically unload chunks results in the server thinking the client has a chunk that it does not have, so the server will never send that chunk again, and this is why some chunks appear to never load.)

The suggestion to have unload controlled by the server is optimal. In particular, this will reduce the bandwidth usage compared to the current algorithm, which still sends chunks that the client just discards. And it should be fairly easy to implement as most of the logic already exists. Basically, one only needs to remove the client side filter and automatic clean-up logic and instead execute unload requests sent by the server.

If you wish to allow the client to automatically unload chunks, things get more complicated. Either the client should tell the server whenever it unloads a chunk, and/or the client should be able to request to receive a chunk even if the server thinks the client already has it. Unfortunately, in this scenario, network lag may still cause client/server desync, similar to Appearance 3. For the most seamless experience, with least bandwidth, chunk unloading desired by client and server should not occur asynchronously to each other.

I really don't see any benefit in the client making unloading decisions, especially since that complicates things alot. So, I strongly recommend to just get rid of this and allow the server to decide which chunks the client should unload, based on the minimum of server view distance and client render distance.

Hope this is info is helpful πŸ™‚

Thanks to @unknown for helping me with testing and writing the report.

migrated

Why is this reopened when the snapshot isn’t even out yet?

boq

Because I realized it's not fully fixed yet.

migrated

boq

Confirmed

(Unassigned)

multiplayer, rendering, server

Minecraft 19w09a, Minecraft 19w11a, Minecraft 19w11b, Minecraft 19w12a, Minecraft 19w12b

Minecraft 19w13b

Retrieved