The bug
In many cases when entities are moved into another chunk they are not added to the new chunks entity lists rightaway.
Instead they get added when the entity receives the next update tick. Until that happens the entity will not be found by some searches that use the chunk-wise lists.
In some cases (like in lazy chunks) it can even happen that the entity is not ticked and doesn't get added to the new list at all. Upon unloading the world the entity will not get saved in the correct chunk and be deleted upon the next reload.
Two cases where this bug can happen are teleport commands and pistons. It's likely that there are more cases though.
How to reproduce
Create a superflat world.
/setworldspawn 0 0 0
/tp 1000 100 0
Reload the world to make sure only the right chunks are loaded.
/summon minecraft:armor_stand 0 100 0 {NoGravity:1,Tags:["MC-108469"]}
/tp @e[tag=MC-108469,x=0,y=100,z=0,distance=..10] 200 100 0
Notice that the target position is still a loaded chunk, just not one in which entities get ticked.
If you now unload and reload the world the armour stand will be gone only leaving the following messages in the launcher:
[INFO] 01:58:46.893 Preparing spawn area: 0% [WARN] 01:58:46.919 Wrong location! (12, 0) should be (0, 0), avm['Armor Stand'/22, l='19w46b', x=200.50, y=100.00, z=0.50] [INFO] 01:58:46.960 Changing view distance to 31, from 10
Alternatively to reloading the world, the error can be shown using these commands. They are not able to find the entity.
/say @e[tag=MC-108469]
/say @e[tag=MC-108469,x=200,y=100,z=0,distance=..100]
In contrast this one does find the entity:
/say @e[tag=MC-108469,x=200,y=100,z=0,distance=..1000]
Given the position of the armorstand all of the above commands should be able to find it, however, since it is not constantly updated across different entity lists, only the third one will find it (as off 1.15pre1).
Also note that the behaviour of these commands is highly dependant on performance decisions of entity selectors and might not behave the same under all conditions.
This issue affects survival (pistons) as well as creative/map making (teleport commands).
Another way this issue shows is when trying to teleport an entity twice in the same tick through several chunks, using selectors that make use of the chunk entity lists. The second selector will then not be able to find the entity.
Code analysis
Code analysis and suggested fix by @unknown can be found here and there. Additional minor optimization here.
Linked issues
is duplicated by 3
relates to 2
Comments 32
The occasional disappearance of moving entities after chunk unloading probably also is related to that. Also sometimes moving entities get duplicated after chunks get unloaded/loaded
Relates to MC-89667
This bug is related to the way the entity's are processed. The entity code in World.java on line 1957: "updateEntityWithOptionalForce" causes this issue.
This is because entity's that are suddenly moved into a new chunk that is not loaded never get added to the unloaded chunk they have entered. They are removed from the list of entity's that are added to chunks and they stay in memory until the chunks around the entity are loaded to make it entity process. Then the entity is added to the chunk it is in. Because all data is stored per chunk basis it means that if entity's do not belong to any chunk they simply get unloaded along with the world without being saved to disk. This can happen to entity's that are moving faster then 32 blocks per game tick and other rare instances when pistons push entity's.
Detailed code explanation.
In World.java line 1959 in updateEntityWithOptionalForce this if check can be found
if (!(entityIn instanceof EntityPlayer))
{
int i = MathHelper.floor(entityIn.posX);
int j = MathHelper.floor(entityIn.posZ);
int k = 32;
if (forceUpdate && !this.isAreaLoaded(i - 32, 0, j - 32, i + 32, 0, j + 32, true))
{
return;
}
}
This code allows for entity's to be skipped if there is a chunk within a 5x5 area around that is not loaded. The term entity processing is used to describe this.
Later in the same function at line 1987 the entity gets updated at the line "entityIn.onUpdate();" and it might have been moved at this point.
if (forceUpdate && entityIn.addedToChunk)
{
++entityIn.ticksExisted;
if (entityIn.isRiding())
{
entityIn.updateRidden();
}
else
{
entityIn.onUpdate();
}
}
If the entity moves into a new chunk the entity gets removed from the old chunk and added to the next chunk it enters. This happens at line 2022.
if (!entityIn.addedToChunk || entityIn.chunkCoordX != l || entityIn.chunkCoordY != i1 || entityIn.chunkCoordZ != j1)
{
if (entityIn.addedToChunk && this.isChunkLoaded(entityIn.chunkCoordX, entityIn.chunkCoordZ, true))
{
this.getChunkFromChunkCoords(entityIn.chunkCoordX, entityIn.chunkCoordZ).removeEntityAtIndex(entityIn, entityIn.chunkCoordY);
}
if (!entityIn.setPositionNonDirty() && !this.isChunkLoaded(l, j1, true))
{
entityIn.addedToChunk = false;
}
else
{
this.getChunkFromChunkCoords(l, j1).addEntity(entityIn);
}
}
This is where the bug shows up. at line 2039 an if check is done on the entity to make sure that the chunk the entity have moved into is loaded. If the entity have moved into a unloaded chunk it simply gets a boolean check "addedToChunk" as false without being added to the chunk it has entered and at the same time a few lines above it gets removed from the old chunk it was in.
At this point the entity gets stuck in memory as it dosen't have a 5x5 loaded chunk area around itself to become processing while it is not saved to any chunk and not able to get saved to disk. The exception to this is when the entity is teleported as in goes through portals or teleported by other means. This rare instance the "setPositionNonDirty" is set to true and the entity is added to the chunk it has entered. But this only happens when entity's are moved via teleportation.
This is probably not as intended because the entity can only be saved to disk and unloaded from memory if the player loads the area where the entity is in again and makes the entity become entity processing. Only then does the entity notice that it doesn't belong to any chunk and adds itself to the chunk it is in. The entity's that are moved into unloaded chunks can cause corruption and other issues when the world is shut down without being properly saved to disk. Mobs can be lost because of this. If entity's are not allowed to get saved to disk they also cause lag as they clog the memory by being added to list of loaded entity's without being able to get removed.
Suggested fix
As entity's can only be saved to disk and removed from memory when they are added to a particular chunk. It is suggested to simply add all entity's to the chunk they enter by removing the if check and explicitly add them to said chunk they enter. The only reason this check must have been done would have been to save some additional memory space prior to the "save-all chunks" every 900 game ticks. This must have been here to prevent chunks from becoming loaded prematurely. With the added 900 game tick "save-all chunks" added to the game makes this particular issue a non issue. Entity's can simply be added to chunks they enter and temporarily load the chunk to get saved to it. Later they can get unloaded and removed from memory while being saved to disk.
This code suggestion can simply be done to fix this bug.
if (!entityIn.addedToChunk || entityIn.chunkCoordX != l || entityIn.chunkCoordY != i1 || entityIn.chunkCoordZ != j1)
{
if (entityIn.addedToChunk && this.isChunkLoaded(entityIn.chunkCoordX, entityIn.chunkCoordZ, true))
{
this.getChunkFromChunkCoords(entityIn.chunkCoordX, entityIn.chunkCoordZ).removeEntityAtIndex(entityIn, entityIn.chunkCoordY);
}
// if (!entityIn.setPositionNonDirty() && !this.isChunkLoaded(l, j1, true))
// {
// entityIn.addedToChunk = false;
// }
// else
// {
this.getChunkFromChunkCoords(l, j1).addEntity(entityIn);
// }
}
It is not harmful to the game to add an entity to an unloaded chunk. What will happen is that the chunk it is temporarily loaded and later unloaded by the 900 game tick "save-all chunk unloading" cycle. This suggested fix however dosesn't fix the issue that piston causes to entity's that are moved into the next chunk. The fix to make sure entity's get properly saved to disk when pushed by pistons needs a more complex fix that haven't been looked into. Most likely will need a better entity movement check done by pistons.
I also can't reproduce it in 20w27a. The armour stand is not loaded anymore after step 6, there is no warning in the log and none of the given commands find it, "/say @e" doesn't, either. When you teleport to it, it's still there. This also does not break the common commands trick to modify "Pos" of an entity and teleport a player to it.
Seems good to me!
We did changes to improve situation, but it's probably not enough to fix that issue.
In 1.16 we've extended chunk handling to also work for entities in "border" chunks (as opposed to just "entity ticking"), since that's part of bug could be reproduced without commands (for example, with piston in block ticking border pushing entity over chunk boundary). However, I have no idea if that fixes issues with longer teleports to areas completely outside of loaded chunks.
Would that mean that this bug cannot occur without (command) teleports or NBT changes anymore? That would at least check this off my "critical issues" list, because you can't just randomly lose entities in your Survival world anymore.
To be considered/tested: Portals, respawning, ender pearls, chorus fruit (incl. foxes), anti-cheat, endermen, shulkers, pets, minecarts (snapping to rails) and dragon eggs all have/cause some kind of instantaneous motion that might be considered "teleporting" in the context of this bug or might not.
This bug could not be triggered by move that happened inside entity tick, which covers most of the categories (even things like being pushed by fluid). Only "external" moves (like pistons, because it happens during block tick or commands) caused that.
Since I am playing a long term world with version 1.16.5 my question is if this bug still affects this world in pure survival mode? Reading the history here, it looks like this is no longer the case since 1.16. Are there still constellations in pure survival mode where the bug still has an effect in 1.16.5? Unfortunately, the last comment from 07.07.2020 (Bartosz Bok) did not answer this very clearly for me.
Does MC-92916 describe your issue?