Bug Open

`World unloaded` thrown repeatedly by culling for dummy entities

#10142 opened by chikage_ 2 days ago

chikage_reported this bug
  • Version: ModelEngine R4.2.0, Leaf 1.21.4-525
  • Period covered: Oct 13 to Nov 4, 2026
  • Occurrences: 374,711, with 355,139 on Oct 13 and 18,110 on Oct 14, then up to about 200 per day
  • Location: BasicCulling.updateCulledPlayer:66, called by DummyEntityData.cullUpdate:92

Checked against ModelEngine-R4.2.0.jar. The line numbers in the stack traces match the bytecode of this jar.

Logs

[16:33:28] [Craft Scheduler Thread - 935 - ModelEngine/WARN]: java.lang.IllegalArgumentException: World unloaded
[16:33:28] [Craft Scheduler Thread - 935 - ModelEngine/WARN]: 	at com.google.common.base.Preconditions.checkArgument(Preconditions.java:143)
[16:33:28] [Craft Scheduler Thread - 935 - ModelEngine/WARN]: 	at org.bukkit.Location.getWorld(Location.java:110)
[16:33:28] [Craft Scheduler Thread - 935 - ModelEngine/WARN]: 	at ModelEngine.jar//com.ticxo.modelengine.api.entity.cull.BasicCulling.updateCulledPlayer(BasicCulling.java:66)
[16:33:28] [Craft Scheduler Thread - 935 - ModelEngine/WARN]: 	at ModelEngine.jar//com.ticxo.modelengine.api.entity.data.DummyEntityData.cullUpdate(DummyEntityData.java:92)
[16:33:28] [Craft Scheduler Thread - 935 - ModelEngine/WARN]: 	at ModelEngine.jar//com.ticxo.modelengine.api.entity.EntityDataTrackers$Tracker.asyncFetchEntityData(EntityDataTrackers.java:132)
[16:33:28] [Craft Scheduler Thread - 935 - ModelEngine/WARN]: 	at ModelEngine.jar//com.ticxo.modelengine.api.entity.EntityDataTrackers.lambda$setup$1(EntityDataTrackers.java:62)

Problem

A dummy has no link to its world's lifetime. DummyEntityData.isDataValid() only checks whether the dummy is marked as removed. WorldListener handles WorldUnloadEvent through getWorld().getEntities(), which only covers real entities. A dummy can therefore stay tracked after its world is unloaded.

Culling assumes the world is still there. BasicCulling.updateCulledPlayer calls Location.getWorld() for each tracked player. That call throws once the location's world reference is gone.

One bad dummy interrupts the whole culling pass. Tracker.asyncFetchEntityData loops over all its entries with no guard around each one. The exception ends the loop, so the entries after the bad dummy are skipped for that pass.

The error repeats until restart or removal. PseudoThread.tick catches the exception, prints it and keeps the repeating task. The dummy stays tracked, so the next pass fails the same way.

Calling ModeledEntity.destroy() is not enough to release a dummy. This call does not remove the model from uuidLookup or mark the dummy as removed, so the tracker keeps the dummy data. An owner has to mark the dummy as removed as well, which is easy to miss.

Solution

  1. Treat a dummy whose world is unloaded as invalid, and remove it from the trackers and model registry.
  2. Check that the world is still available at the start of updateCulledPlayer.
  3. Guard each entry in asyncFetchEntityData, so one failure cannot skip culling for other models.
  4. Log this failure once per dummy.
  5. Document that dummy owners must mark the dummy as removed, or make ModeledEntity.destroy() release it.
Sign in to join the discussion.