diff --git a/Changelog.txt b/Changelog.txt
index c7c898baf..ece84ec19 100644
--- a/Changelog.txt
+++ b/Changelog.txt
@@ -1,3 +1,6 @@
+Version 2.2.028
+ Fix stack overflow during ChunkUnloadEvent
+
Version 2.2.027
Added Tridents / Crossbows to salvage.vanilla.yml config (see notes)
Fixed an issue where Folia could have all of its threads lock up effectively killing the server
diff --git a/pom.xml b/pom.xml
index b06f0ce9b..284d337fa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
4.0.0
com.gmail.nossr50.mcMMO
mcMMO
- 2.2.027
+ 2.2.028-SNAPSHOT
mcMMO
https://github.com/mcMMO-Dev/mcMMO
diff --git a/src/main/java/com/gmail/nossr50/listeners/ChunkListener.java b/src/main/java/com/gmail/nossr50/listeners/ChunkListener.java
index f972af3c0..1a6a146d7 100644
--- a/src/main/java/com/gmail/nossr50/listeners/ChunkListener.java
+++ b/src/main/java/com/gmail/nossr50/listeners/ChunkListener.java
@@ -1,21 +1,23 @@
package com.gmail.nossr50.listeners;
import com.gmail.nossr50.mcMMO;
+import org.bukkit.Chunk;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.ChunkUnloadEvent;
+import java.util.Arrays;
import java.util.List;
public class ChunkListener implements Listener {
@EventHandler(ignoreCancelled = true)
public void onChunkUnload(ChunkUnloadEvent event) {
- List matchingEntities
- = mcMMO.getTransientEntityTracker().getAllTransientEntitiesInChunk(event.getChunk());
- for(LivingEntity livingEntity : matchingEntities) {
- mcMMO.getTransientEntityTracker().killSummonAndCleanMobFlags(livingEntity, null, false);
- }
+ final Chunk unloadingChunk = event.getChunk();
+ Arrays.stream(unloadingChunk.getEntities())
+ .filter(entity -> entity instanceof LivingEntity)
+ .map(entity -> (LivingEntity) entity)
+ .forEach(livingEntity -> mcMMO.getTransientEntityTracker().removeTrackedEntity(livingEntity));
}
}
diff --git a/src/main/java/com/gmail/nossr50/util/TransientEntityTracker.java b/src/main/java/com/gmail/nossr50/util/TransientEntityTracker.java
index f45ca6140..ed3b1c16b 100644
--- a/src/main/java/com/gmail/nossr50/util/TransientEntityTracker.java
+++ b/src/main/java/com/gmail/nossr50/util/TransientEntityTracker.java
@@ -5,7 +5,6 @@ import com.gmail.nossr50.skills.taming.TrackedTamingEntity;
import com.gmail.nossr50.util.player.NotificationManager;
import com.gmail.nossr50.util.skills.ParticleEffectUtils;
import com.gmail.nossr50.util.text.StringUtils;
-import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.entity.LivingEntity;
@@ -19,10 +18,13 @@ import java.util.concurrent.ConcurrentHashMap;
import static java.util.stream.Collectors.toSet;
public class TransientEntityTracker {
- private final @NotNull Map> playerSummonedEntityTracker;
+ final @NotNull Map> playerSummonedEntityTracker;
+ // used for fast lookups during chunk unload events
+ final @NotNull Set entityLookupCache;
public TransientEntityTracker() {
- playerSummonedEntityTracker = new ConcurrentHashMap<>();
+ this.playerSummonedEntityTracker = new ConcurrentHashMap<>();
+ this.entityLookupCache = ConcurrentHashMap.newKeySet();
}
public void initPlayer(@NotNull Player player) {
@@ -33,14 +35,6 @@ public class TransientEntityTracker {
cleanPlayer(player, player.getUniqueId());
}
- public @NotNull List getAllTransientEntitiesInChunk(@NotNull Chunk chunk) {
- return playerSummonedEntityTracker.values().stream()
- .flatMap(Collection::stream)
- .map(TrackedTamingEntity::getLivingEntity)
- .filter(livingEntity -> livingEntity.getLocation().getChunk() == chunk)
- .toList();
- }
-
public int summonCountForPlayerOfType(@NotNull UUID playerUUID, @NotNull CallOfTheWildType callOfTheWildType) {
return getTrackedEntities(playerUUID, callOfTheWildType).size();
}
@@ -48,6 +42,7 @@ public class TransientEntityTracker {
public void addSummon(@NotNull UUID playerUUID, @NotNull TrackedTamingEntity trackedTamingEntity) {
playerSummonedEntityTracker.computeIfAbsent(playerUUID, __ -> ConcurrentHashMap.newKeySet())
.add(trackedTamingEntity);
+ entityLookupCache.add(trackedTamingEntity.getLivingEntity());
}
public void killSummonAndCleanMobFlags(@NotNull LivingEntity livingEntity, @Nullable Player player,
@@ -77,9 +72,7 @@ public class TransientEntityTracker {
}
public boolean isTransient(@NotNull LivingEntity livingEntity) {
- return playerSummonedEntityTracker.values().stream().anyMatch(
- trackedEntities -> trackedEntities.stream()
- .anyMatch(trackedTamingEntity -> trackedTamingEntity.getLivingEntity().equals(livingEntity)));
+ return entityLookupCache.contains(livingEntity);
}
private @NotNull Set getTrackedEntities(@NotNull UUID playerUUID,
@@ -117,7 +110,29 @@ public class TransientEntityTracker {
}
public void removeSummonFromTracker(@NotNull UUID playerUUID, @NotNull TrackedTamingEntity trackedTamingEntity) {
- playerSummonedEntityTracker.computeIfAbsent(playerUUID, __ -> ConcurrentHashMap.newKeySet())
- .remove(trackedTamingEntity);
+ if (playerSummonedEntityTracker.containsKey(playerUUID)) {
+ playerSummonedEntityTracker.get(playerUUID).remove(trackedTamingEntity);
+ entityLookupCache.remove(trackedTamingEntity.getLivingEntity());
+ }
}
+
+ public void removeTrackedEntity(@NotNull LivingEntity livingEntity) {
+ // Fail fast if the entity isn't being tracked
+ if (!entityLookupCache.contains(livingEntity)) {
+ return;
+ }
+
+ final List matchingEntities = new ArrayList<>();
+
+ // Collect matching entities without copying each set
+ playerSummonedEntityTracker.values().forEach(trackedEntitiesPerPlayer ->
+ trackedEntitiesPerPlayer.stream()
+ .filter(trackedTamingEntity -> trackedTamingEntity.getLivingEntity() == livingEntity)
+ .forEach(matchingEntities::add)
+ );
+
+ // Iterate over the collected list to handle removal and cleanup
+ matchingEntities.forEach(TrackedTamingEntity::run);
+ }
+
}