[SPIGOT-4752] Chunks not unloaded, but not loaded either according to World#isChunkLoaded Created: 25/Apr/19 Updated: 25/May/19 Resolved: 25/May/19 |
|
| Status: | Resolved |
| Project: | Spigot |
| Component/s: | None |
| Affects Version/s: | None |
| Fix Version/s: | None |
| Type: | Bug | Priority: | Minor |
| Reporter: | blablubbabc | Assignee: | Unassigned |
| Resolution: | Fixed | Votes: | 0 |
| Labels: | 1.14, ChunkUnloadEvent, entity, persistence | ||
| Environment: |
Fresh 1.14 Spigot server with newly generated world. No plugins except the testing plugin mentioned below. |
||
| Attachments: |
|
||||||||
| Issue Links: |
|
||||||||
| Version: | This server is running CraftBukkit version git-Spigot-0c02b0c-e879c44 (MC: 1.14) (Implementing API version 1.14-R0.1-SNAPSHOT) | ||||||||
| Guidelines Read: | Yes | ||||||||
| Description |
|
While trying to update one of my plugins (Shopkeepers) from 1.13 to 1.14 I noticed the following issues: The plugin is spawning mobs, sets them as NoAI and non-persistent, and reacts to chunk unload events to perform cleanup and chunk load events to respawn them. Additionally it periodically checks if the spawned (and tracked) entities are still valid (Entity#isValid) and if the chunk they are in is still loaded. In Spigot 1.14, the plugin would find that the chunks for these entities would get silently unloaded (without a corresponding ChunkUnloadEvent), and the entities themselves would still be marked as 'valid' indefinitely. Also the entities would not get removed when leaving the area and coming back, even though they are marked as non-persistent.
To try to further pin-point the issue(s) for reproduction I created a small testing plugin (see attachement, source code included). This plugin basically spawns a few different entity variants (NoAI+non-persistent, NoAI+persistent, AI+non-persistent,AI+persistent), checks for chunk loads and unloads, and periodically checks if these entities are still valid and if their chunk(s) are still loaded (according to World#isChunkLoaded). I started the server (freshly generated world), the plugin would spawn the mobs and outputs any chunk events and periodic entity and chunk states. Then I joined the server, teleported to these entities, flew away until out of range, flew back to these entities, and logged out again. The server log of this can be found in the attachment. One can observe the following things:
Also unexpected: When teleporting to the mobs and after leaving and coming back, all mobs were present, regardless of their persistence flag that got originally set. |
| Comments |
| Comment by blablubbabc [ 03/May/19 ] |
|
Possible implementation (needs review): https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/pull-requests/549/ |
| Comment by md_5 [ 30/Apr/19 ] |
|
I believe theres three types of chunks for exactly this reason - theres the regular chunks, then there's the non ticking chunks, and then further out still there's the border chunks. The API should only expose the first two. Events will only be initiated by the first one, potentially implicating entities the second. I don't think the true border chunks even have their entities added to the server.
Whatever the case may be I'd rather reduce the incidence of such calls to begin with even if they're not completely eliminated in obscure cases like you mention |
| Comment by blablubbabc [ 29/Apr/19 ] |
|
I wonder if there are cases in which minecraft will access those border chunk entities, (eg. AI (targeting, etc.) of an entity in a ticking chunk searching for nearby entities further than 1 chunk away) and how craftbukkit would deal with this then regarding event calls. |
| Comment by md_5 [ 29/Apr/19 ] |
|
I wouldn’t mind completely squashing all traces of border chunks from the API - I can’t think of a reason they need to be exposed, they’re really a technical detail. No events involving them should be being called, so this is basically option #1.
only difference re your last para is we should not mess with handling vanilla entities, simply just filter them in the Bukkit call. |
| Comment by blablubbabc [ 29/Apr/19 ] |
|
After looking into this a bit more I agree that accessing these border chunks and treating them as regular loaded chunks (like before) is probably not safe for plugins. 2 possible solutions could be:
Personally I would probably prefer the first solution currently. That would mean calling the chunk load/unload events in place of the chunk activate/deactivate events. Left to do would then be un/registering of entities for deactivated chunks, and checking how the ChunkUnloadedEvent's is/setSaveChunk can be fitted for this (my current assumption is that minecraft will save chunks only if they have been activated before, is this correct? In that case the ChunkUnloadEvent could set the is-goin-to-get-saved-flag for the chunk). |
| Comment by md_5 [ 27/Apr/19 ] |
|
>What exactly are the limitations of these border chunks that plugins might want to be aware of?
Accessing a border chunk will promote it to a real chunk and then load more border chunks. This can cause lots of lag, eg https://www.spigotmc.org/threads/is-this-an-intensive-method.370339/
I really think we need to tighten the API so this can't happen. |
| Comment by blablubbabc [ 26/Apr/19 ] |
|
Here is one first attempt: https://hub.spigotmc.org/stash/users/blablubbabc/repos/craftbukkit/commits/d9cefcf517db4c01c611f6199b6011aaff0860ec This seems to restore the behavior of isChunkLoaded quite well to how it has previously been (returns true until the chunk has actually been unloaded). However, This is probably not safe yet:
|
| Comment by md_5 [ 26/Apr/19 ] |
|
As you can see on line 325 of CraftWorld, isChunkLoaded is checking simply if getChunk is not null without loading it.
You’re probably gonna be digging deep into NMS to change that |
| Comment by blablubbabc [ 26/Apr/19 ] |
|
I moved the entity valid flag issue into a separate ticket: https://hub.spigotmc.org/jira/browse/SPIGOT-4774
Regarding World#isChunkLoaded: I tested removing the chunk.loaded check in CraftWorld, which has no effect. This flags seems to only get set to false once the chunk is actually getting unloaded. So I assume the chunk returned by the ChunkProviderServer is null. I have yet to check in which state the chunk is in this situation (not unloaded but not loaded according to World#isChunkLoaded).
Edit: I just checked and the chunk is indeed in the 'border' state. Would it be possible to revert the behavior of World#isChunkLoaded to be consistent again with the ChunkUn/LoadEvents? Previously it would report that the chunk is loaded until after the chunk got actually unloaded. Now it reports false if the chunk is in this border state, and also if the chunk is currently getting unloaded (PlayerChunkMap#g), which can get aborted though. Previously one could check the isChunkLoaded to avoid unnecesarry chunk loads, but still be sure to get a ChunkLoadEvent once the chunk gets eventually loaded and then perform any plugin setup necessary for the chunk (ie. loading, placing and activating objects in this chunk). I assume we don't want to add ChunkDe/ActivateEvents. Since a plugin can get loaded after the chunk has been loaded (but deactivated), a plugin would currently need to periodically poll to check whether a chunk has become 'active.
What exactly are the limitations of these border chunks that plugins might want to be aware of? Currently I can see that they might skip ticking, but (entity) ticking has always been dynamic with Spigot's activation range anyways. If there is a benefit in this, it might make sense to expose a 'isChunkActive' to plugins (for example to avoid unnecessary chunk activations by API calls that require an active chunk (I don't have a concrete example for this currently), or shutdown plugin tasks for objects in these inactive tasks (instead of shutting these down on chunk unload)).
Offtopic: While looking though the code I noticed that the the mapping for nms.World#isInsideWorld(BlockPosition) should probably be 'isOutsideWorld' instead. |
| Comment by gizmo [ 26/Apr/19 ] |
|
sorry. |
| Comment by md_5 [ 26/Apr/19 ] |
|
ChunkUnloadEvent calling has been fixed in https://hub.spigotmc.org/jira/browse/SPIGOT-4768
|
| Comment by md_5 [ 26/Apr/19 ] |
|
One report per issue please |
| Comment by gizmo [ 26/Apr/19 ] |
|
I ran into a similar issue, afaik ChunkUnloadEvents are never created, only ChunkLoadEvents are. Here is a test jar. |
| Comment by md_5 [ 26/Apr/19 ] |
|
Remove the checks to chunk.loaded in CraftWorld. Does that solve your issues?
The problem with exposing edge chunks is that they do not behave the same as normal chunks when manipulated. |
| Comment by blablubbabc [ 26/Apr/19 ] |
|
I tried different coordinates and now I get a chunk unload event. My current guess would be that I might have been to close to the spawn area that the chunk in question was kept loaded. It seems that depending on the distance to the spawn, some chunks near the spawn are kept loaded and active, and some are kept loaded but in the inactive state (no chunk unloads take place but World#isChunkLoaded reports 'false'). The only issues remaining would then be:
|
| Comment by blablubbabc [ 25/Apr/19 ] |
|
Yes, I tested the above scenario again and tried moving further away, teleporting to far places and leaving the server. So far I did not find the chunk-unloaded-notice in the server log for the chunk in question. I am currently looking at the code for PlayerChunkMap and trying to figure out:
|
| Comment by md_5 [ 25/Apr/19 ] |
|
Did you actually verify my theory? |
| Comment by blablubbabc [ 25/Apr/19 ] |
|
Okay. Is there a way to query whether a chunk is actually unloaded? The behavior of world#isChunkLoaded seemed to have changed, since it now indicates whether the chunk is 'active', not whether it has been unloaded.
Edit: Also, I assume that chunks still only get saved as part of the unload. Is that still correct? If not, an alternative to the ChunkUnloadEvent for indicating the chunk save might be useful. Or treating this no-ticking zone like chunk unloading to have similar behavior as before for plugins.
Edit: Okay I just tested this again, and it seems like the persistency flag does indeed work, because the non-persistent mobs are gone after restarting the server. I was however not yet able to observe the chunk unload for this particular chunk.
Edit: It might make sense to either treat this chunk-inactivity as unloading/loading for plugins, or add a new set of API/events for this chunk inactivity. Because plugins that previously used the chunk loading/unloading for performing cleanup (such as stopping running tasks for things located in/near that chunk) can then make use of this and perform cleanup whenever a chunk becomes 'inactive'.
|
| Comment by md_5 [ 25/Apr/19 ] |
|
Probably the chunks are not unloading, they are just being moved to the non ticking zone and therefore not exposed to bukkit.
If you move even farther away you will probably see them unload. |
| Comment by blablubbabc [ 25/Apr/19 ] |
|
| Comment by md_5 [ 25/Apr/19 ] |
|
Can you try and explain what exactly the issue is more concisely |