From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: wangxyper Date: Fri, 27 Jan 2023 19:52:49 +0800 Subject: [PATCH] MikuServer: Concurrent problems fixes Original license: MIT Original project: https://github.com/MikuMC/MikuServer diff --git a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java index 0b060183429f4c72ec767075538477b4302bbf0d..23c32a06dce8f0c45647c3619c98ba95290cfa7d 100644 --- a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java +++ b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java @@ -7,9 +7,9 @@ import io.papermc.paper.util.CoordinateUtils; import io.papermc.paper.util.IntervalledCounter; import io.papermc.paper.util.TickThread; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; -import it.unimi.dsi.fastutil.objects.Reference2ObjectLinkedOpenHashMap; -import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.longs.LongSets; +import it.unimi.dsi.fastutil.objects.*; import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket; import net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket; import net.minecraft.network.protocol.game.ClientboundSetSimulationDistancePacket; @@ -22,10 +22,10 @@ import net.minecraft.world.level.chunk.LevelChunk; import org.apache.commons.lang3.mutable.MutableObject; import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.List; -import java.util.TreeSet; + +import java.util.*; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.atomic.AtomicInteger; public final class PlayerChunkLoader { @@ -76,10 +76,10 @@ public final class PlayerChunkLoader { } protected final ChunkMap chunkMap; - protected final Reference2ObjectLinkedOpenHashMap playerMap = new Reference2ObjectLinkedOpenHashMap<>(512, 0.7f); - protected final ReferenceLinkedOpenHashSet chunkSendQueue = new ReferenceLinkedOpenHashSet<>(512, 0.7f); + protected final Reference2ObjectMap playerMap = Reference2ObjectMaps.synchronize(new Reference2ObjectLinkedOpenHashMap<>(512, 0.7f)); + protected final Deque chunkSendQueue = new ConcurrentLinkedDeque<>(); - protected final TreeSet chunkLoadQueue = new TreeSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> { + protected final NavigableSet chunkLoadQueue = new ConcurrentSkipListSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> { if (p1 == p2) { return 0; } @@ -109,7 +109,7 @@ public final class PlayerChunkLoader { return Integer.compare(System.identityHashCode(p1), System.identityHashCode(p2)); }); - protected final TreeSet chunkSendWaitQueue = new TreeSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> { + protected final NavigableSet chunkSendWaitQueue = new ConcurrentSkipListSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> { if (p1 == p2) { return 0; } @@ -308,8 +308,8 @@ public final class PlayerChunkLoader { }); } - protected final LongOpenHashSet isTargetedForPlayerLoad = new LongOpenHashSet(); - protected final LongOpenHashSet chunkTicketTracker = new LongOpenHashSet(); + protected final LongSet isTargetedForPlayerLoad = LongSets.synchronize(new LongOpenHashSet()); + protected final LongSet chunkTicketTracker = LongSets.synchronize(new LongOpenHashSet()); public boolean isChunkNearPlayers(final int chunkX, final int chunkZ) { final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInSendRange = this.broadcastMap.getObjectsInRange(chunkX, chunkZ); @@ -373,7 +373,7 @@ public final class PlayerChunkLoader { } return !(data.hasSentChunk(chunkX - 1, chunkZ) && data.hasSentChunk(chunkX + 1, chunkZ) && - data.hasSentChunk(chunkX, chunkZ - 1) && data.hasSentChunk(chunkX, chunkZ + 1)); + data.hasSentChunk(chunkX, chunkZ - 1) && data.hasSentChunk(chunkX, chunkZ + 1)); } protected int getMaxConcurrentChunkSends() { @@ -518,22 +518,20 @@ public final class PlayerChunkLoader { protected static final AtomicInteger concurrentChunkSends = new AtomicInteger(); protected final Reference2IntOpenHashMap sendingChunkCounts = new Reference2IntOpenHashMap<>(); private static long nextChunkSend; + private void trySendChunks() { final long time = System.nanoTime(); if (time < nextChunkSend) { return; } + PlayerLoaderData data1; // drain entries from wait queue - while (!this.chunkSendWaitQueue.isEmpty()) { - final PlayerLoaderData data = this.chunkSendWaitQueue.first(); - - if (data.nextChunkSendTarget > time) { + while ((data1 = this.chunkSendWaitQueue.pollFirst())!=null) { + if (data1.nextChunkSendTarget > time) { + this.chunkSendWaitQueue.add(data1); break; } - - this.chunkSendWaitQueue.pollFirst(); - - this.chunkSendQueue.add(data); + this.chunkSendQueue.add(data1); } if (this.chunkSendQueue.isEmpty()) { @@ -542,10 +540,9 @@ public final class PlayerChunkLoader { final int maxSends = this.getMaxConcurrentChunkSends(); final long nextPlayerDeadline = this.getTargetSendPerPlayerAddend() + time; - for (;;) { - if (this.chunkSendQueue.isEmpty()) { - break; - } + final Deque tempCopy = new ArrayDeque<>(this.chunkSendQueue); + PlayerLoaderData data; + while ((data = tempCopy.pollFirst())!=null) { final int currSends = concurrentChunkSends.get(); if (currSends >= maxSends) { break; @@ -554,24 +551,17 @@ public final class PlayerChunkLoader { if (!concurrentChunkSends.compareAndSet(currSends, currSends + 1)) { continue; } - // send chunk - - final PlayerLoaderData data = this.chunkSendQueue.removeFirst(); - + this.chunkSendQueue.remove(data); final ChunkPriorityHolder queuedSend = data.sendQueue.pollFirst(); if (queuedSend == null) { concurrentChunkSends.getAndDecrement(); // we never sent, so decrease // stop iterating over players who have nothing to send - if (this.chunkSendQueue.isEmpty()) { - // nothing left - break; - } continue; } if (!this.isChunkPlayerLoaded(queuedSend.chunkX, queuedSend.chunkZ)) { - throw new IllegalStateException(); + continue; } data.nextChunkSendTarget = nextPlayerDeadline; @@ -581,17 +571,18 @@ public final class PlayerChunkLoader { this.sendingChunkCounts.addTo(data, 1); } + final PlayerLoaderData finalData = data; data.sendChunk(queuedSend.chunkX, queuedSend.chunkZ, () -> { synchronized (this.sendingChunkCounts) { - final int count = this.sendingChunkCounts.getInt(data); + final int count = this.sendingChunkCounts.getInt(finalData); if (count == 0) { // disconnected, so we don't need to decrement: it will be decremented for us return; } if (count == 1) { - this.sendingChunkCounts.removeInt(data); + this.sendingChunkCounts.removeInt(finalData); } else { - this.sendingChunkCounts.put(data, count - 1); + this.sendingChunkCounts.put(finalData, count - 1); } } @@ -618,16 +609,12 @@ public final class PlayerChunkLoader { final int maxLoads = this.getMaxChunkLoads(); final long time = System.nanoTime(); boolean updatedCounters = false; - for (;;) { - final PlayerLoaderData data = this.chunkLoadQueue.pollFirst(); - + PlayerLoaderData data; + while ((data = this.chunkLoadQueue.pollFirst())!=null) { data.lastChunkLoad = time; final ChunkPriorityHolder queuedLoad = data.loadQueue.peekFirst(); if (queuedLoad == null) { - if (this.chunkLoadQueue.isEmpty()) { - break; - } continue; } @@ -673,7 +660,7 @@ public final class PlayerChunkLoader { final int currentChunkLoads = this.concurrentChunkLoads; if (currentChunkLoads >= maxLoads || (GlobalConfiguration.get().chunkLoading.globalMaxChunkLoadRate > 0 && (TICKET_ADDITION_COUNTER_SHORT.getRate() >= GlobalConfiguration.get().chunkLoading.globalMaxChunkLoadRate || TICKET_ADDITION_COUNTER_LONG.getRate() >= GlobalConfiguration.get().chunkLoading.globalMaxChunkLoadRate)) - || (GlobalConfiguration.get().chunkLoading.playerMaxChunkLoadRate > 0.0 && (data.ticketAdditionCounterShort.getRate() >= GlobalConfiguration.get().chunkLoading.playerMaxChunkLoadRate || data.ticketAdditionCounterLong.getRate() >= GlobalConfiguration.get().chunkLoading.playerMaxChunkLoadRate))) { + || (GlobalConfiguration.get().chunkLoading.playerMaxChunkLoadRate > 0.0 && (data.ticketAdditionCounterShort.getRate() >= GlobalConfiguration.get().chunkLoading.playerMaxChunkLoadRate || data.ticketAdditionCounterLong.getRate() >= GlobalConfiguration.get().chunkLoading.playerMaxChunkLoadRate))) { // don't poll, we didn't load it this.chunkLoadQueue.add(data); break; @@ -736,12 +723,13 @@ public final class PlayerChunkLoader { } } + public void tickMidTick() { - // try to send more chunks - this.trySendChunks(); + // try to send more chunks + this.trySendChunks(); - // try to queue more chunks to load - this.tryLoadChunks(); + // try to queue more chunks to load + this.tryLoadChunks(); } static final class ChunkPriorityHolder { @@ -786,11 +774,11 @@ public final class PlayerChunkLoader { // warning: modifications of this field must be aware that the loadQueue inside PlayerChunkLoader uses this field // in a comparator! - protected final ArrayDeque loadQueue = new ArrayDeque<>(); - protected final LongOpenHashSet sentChunks = new LongOpenHashSet(); - protected final LongOpenHashSet chunksToBeSent = new LongOpenHashSet(); + protected final Deque loadQueue = new ConcurrentLinkedDeque<>(); + protected final LongSet sentChunks = LongSets.synchronize(new LongOpenHashSet()); + protected final LongSet chunksToBeSent = LongSets.synchronize(new LongOpenHashSet()); - protected final TreeSet sendQueue = new TreeSet<>((final ChunkPriorityHolder p1, final ChunkPriorityHolder p2) -> { + protected final NavigableSet sendQueue = new ConcurrentSkipListSet<>((final ChunkPriorityHolder p1, final ChunkPriorityHolder p2) -> { final int distanceCompare = Integer.compare(p1.manhattanDistanceToPlayer, p2.manhattanDistanceToPlayer); if (distanceCompare != 0) { return distanceCompare; @@ -964,14 +952,14 @@ public final class PlayerChunkLoader { && tickViewDistance == this.lastTickDistance && (this.usingLookingPriority ? ( - // has our block stayed the same (this also accounts for chunk change)? - Mth.floor(this.lastLocX) == Mth.floor(posX) + // has our block stayed the same (this also accounts for chunk change)? + Mth.floor(this.lastLocX) == Mth.floor(posX) && Mth.floor(this.lastLocZ) == Mth.floor(posZ) - ) : ( - // has our chunk stayed the same - (Mth.floor(this.lastLocX) >> 4) == (Mth.floor(posX) >> 4) + ) : ( + // has our chunk stayed the same + (Mth.floor(this.lastLocX) >> 4) == (Mth.floor(posX) >> 4) && (Mth.floor(this.lastLocZ) >> 4) == (Mth.floor(posZ) >> 4) - )) + )) // has our decision about look priority changed? && this.usingLookingPriority == useLookPriority diff --git a/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java b/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java index ec90ff5c2581706180498b74dbbf960d52d47209..387d07868301877dd7fca5d8dfd21e1331f4793e 100644 --- a/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java +++ b/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java @@ -6,14 +6,15 @@ import io.papermc.paper.util.CoordinateUtils; import io.papermc.paper.util.TickThread; import io.papermc.paper.util.WorldUtil; import io.papermc.paper.world.ChunkEntitySlices; +import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; -import it.unimi.dsi.fastutil.ints.Int2ReferenceMaps; import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectArrayMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ReferenceArrayMap; import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; -import it.unimi.dsi.fastutil.objects.Object2ReferenceMaps; import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; import net.minecraft.core.BlockPos; import io.papermc.paper.chunk.system.ChunkSystem; @@ -32,8 +33,13 @@ import net.minecraft.world.phys.AABB; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; - -import java.util.*; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.StampedLock; import java.util.function.Consumer; import java.util.function.Predicate; @@ -48,16 +54,16 @@ public final class EntityLookup implements LevelEntityGetter { public final ServerLevel world; - private final StampedLock stateLock = new StampedLock(); - protected final Long2ObjectMap regions = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>(128, 0.5f)); + private final StampedLock entityByLock = new StampedLock(); + private final Lock regionLoadLock = new ReentrantLock(true); + + protected final Long2ObjectMap regions = Long2ObjectMaps.synchronize(new Long2ObjectArrayMap<>()); private final int minSection; // inclusive private final int maxSection; // inclusive private final LevelCallback worldCallback; - - private final StampedLock entityByLock = new StampedLock(); - private final Map entityById = Int2ReferenceMaps.synchronize(new Int2ReferenceOpenHashMap<>()); - private final Object2ReferenceMap entityByUUID = Object2ReferenceMaps.synchronize(new Object2ReferenceOpenHashMap<>()); + private final Int2ReferenceMap entityById = new Int2ReferenceArrayMap<>(); + private final Object2ReferenceMap entityByUUID = new Object2ReferenceArrayMap<>(); private final EntityList accessibleEntities = new EntityList(); public EntityLookup(final ServerLevel world, final LevelCallback worldCallback) { @@ -108,7 +114,6 @@ public final class EntityLookup implements LevelEntityGetter { if (attempt != 0L) { try { final Entity ret = this.entityByUUID.get(id); - if (this.entityByLock.validate(attempt)) { return maskNonAccessible(ret); } @@ -169,12 +174,12 @@ public final class EntityLookup implements LevelEntityGetter { } @Override - public boolean hasNext() { + public synchronized boolean hasNext() { return this.off < this.length; } @Override - public T next() { + public synchronized T next() { if (this.off >= this.length) { throw new NoSuchElementException(); } @@ -231,75 +236,50 @@ public final class EntityLookup implements LevelEntityGetter { public void entityStatusChange(final Entity entity, final ChunkEntitySlices slices, final Visibility oldVisibility, final Visibility newVisibility, final boolean moved, final boolean created, final boolean destroyed) { TickThread.ensureTickThread(entity, "Entity status change must only happen on the main thread"); - - if (entity.updatingSectionStatus) { - // recursive status update - LOGGER.warn("Cannot recursively update entity chunk status for entity " + entity); - return; - } - - final boolean entityStatusUpdateBefore = slices == null ? false : slices.startPreventingStatusUpdates(); - - if (entityStatusUpdateBefore) { - LOGGER.warn("Cannot update chunk status for entity " + entity + " since entity chunk (" + slices.chunkX + "," + slices.chunkZ + ") is receiving update"); - return; - } - + final Boolean ticketBlockBefore = this.world.chunkTaskScheduler.chunkHolderManager.blockTicketUpdates(); try { - final Boolean ticketBlockBefore = this.world.chunkTaskScheduler.chunkHolderManager.blockTicketUpdates(); - try { - entity.updatingSectionStatus = true; - try { - if (created) { - EntityLookup.this.worldCallback.onCreated(entity); - } + if (created) { + EntityLookup.this.worldCallback.onCreated(entity); + } - if (oldVisibility == newVisibility) { - if (moved && newVisibility.isAccessible()) { - EntityLookup.this.worldCallback.onSectionChange(entity); - } - return; - } + if (oldVisibility == newVisibility) { + if (moved && newVisibility.isAccessible()) { + EntityLookup.this.worldCallback.onSectionChange(entity); + } + return; + } - if (newVisibility.ordinal() > oldVisibility.ordinal()) { - // status upgrade - if (!oldVisibility.isAccessible() && newVisibility.isAccessible()) { - this.accessibleEntities.add(entity); - EntityLookup.this.worldCallback.onTrackingStart(entity); - } + if (newVisibility.ordinal() > oldVisibility.ordinal()) { + // status upgrade + if (!oldVisibility.isAccessible() && newVisibility.isAccessible()) { + this.accessibleEntities.add(entity); + EntityLookup.this.worldCallback.onTrackingStart(entity); + } - if (!oldVisibility.isTicking() && newVisibility.isTicking()) { - EntityLookup.this.worldCallback.onTickingStart(entity); - } - } else { - // status downgrade - if (oldVisibility.isTicking() && !newVisibility.isTicking()) { - EntityLookup.this.worldCallback.onTickingEnd(entity); - } + if (!oldVisibility.isTicking() && newVisibility.isTicking()) { + EntityLookup.this.worldCallback.onTickingStart(entity); + } + } else { + // status downgrade + if (oldVisibility.isTicking() && !newVisibility.isTicking()) { + EntityLookup.this.worldCallback.onTickingEnd(entity); + } - if (oldVisibility.isAccessible() && !newVisibility.isAccessible()) { - this.accessibleEntities.remove(entity); - EntityLookup.this.worldCallback.onTrackingEnd(entity); - } - } + if (oldVisibility.isAccessible() && !newVisibility.isAccessible()) { + this.accessibleEntities.remove(entity); + EntityLookup.this.worldCallback.onTrackingEnd(entity); + } + } - if (moved && newVisibility.isAccessible()) { - EntityLookup.this.worldCallback.onSectionChange(entity); - } + if (moved && newVisibility.isAccessible()) { + EntityLookup.this.worldCallback.onSectionChange(entity); + } - if (destroyed) { - EntityLookup.this.worldCallback.onDestroyed(entity); - } - } finally { - entity.updatingSectionStatus = false; - } - } finally { - this.world.chunkTaskScheduler.chunkHolderManager.unblockTicketUpdates(ticketBlockBefore); + if (destroyed) { + EntityLookup.this.worldCallback.onDestroyed(entity); } } finally { - if (slices != null) { - slices.stopPreventingStatusUpdates(false); - } + this.world.chunkTaskScheduler.chunkHolderManager.unblockTicketUpdates(ticketBlockBefore); } } @@ -349,11 +329,6 @@ public final class EntityLookup implements LevelEntityGetter { return false; } - if (entity.updatingSectionStatus) { - LOGGER.warn("Entity " + entity + " is currently prevented from being added/removed to world since it is processing section status updates"); - return false; - } - if (fromDisk) { ChunkSystem.onEntityPreAdd(this.world, entity); if (entity.isRemoved()) { @@ -401,7 +376,14 @@ public final class EntityLookup implements LevelEntityGetter { if (!entity.isRemoved()) { throw new IllegalStateException("Only call Entity#setRemoved to remove an entity"); } - final ChunkEntitySlices slices = this.getChunk(sectionX, sectionZ); + ChunkEntitySlices slices; + this.regionLoadLock.lock(); + try { + slices = this.getChunk(sectionX, sectionZ); + }finally { + this.regionLoadLock.unlock(); + } + // all entities should be in a chunk if (slices == null) { LOGGER.warn("Cannot remove entity " + entity + " from null entity slices (" + sectionX + "," + sectionZ + ")"); @@ -444,7 +426,15 @@ public final class EntityLookup implements LevelEntityGetter { // ensure the old section is owned by this tick thread TickThread.ensureTickThread(this.world, entity.sectionX, entity.sectionZ, "Cannot move entity off-main"); - final ChunkEntitySlices old = this.getChunk(entity.sectionX, entity.sectionZ); + ChunkEntitySlices old; + + this.regionLoadLock.lock(); + try { + old = this.getChunk(entity.sectionX, entity.sectionZ); + }finally { + this.regionLoadLock.unlock(); + } + final ChunkEntitySlices slices = this.getOrCreateChunk(newSectionX, newSectionZ); if (!old.removeEntity(entity, entity.sectionY)) { @@ -612,7 +602,7 @@ public final class EntityLookup implements LevelEntityGetter { continue; } - chunk.getEntities(type, box, (List)into, (Predicate)predicate); + chunk.getEntities(type, box, (List) into, (Predicate) predicate); } } } @@ -663,18 +653,22 @@ public final class EntityLookup implements LevelEntityGetter { TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Cannot load in entity section off-main"); synchronized (this) { final ChunkEntitySlices curr = this.getChunk(chunkX, chunkZ); - if (curr != null) { - this.removeChunk(chunkX, chunkZ); - - curr.mergeInto(slices); - - this.addChunk(chunkX, chunkZ, slices); - } else { - this.addChunk(chunkX, chunkZ, slices); + this.regionLoadLock.lock(); + try { + if (curr != null) { + this.removeChunk(chunkX, chunkZ); + curr.mergeInto(slices); + this.addChunk(chunkX, chunkZ, slices); + } else { + this.addChunk(chunkX, chunkZ, slices); + } + } finally { + this.regionLoadLock.unlock(); } } } + public void entitySectionUnload(final int chunkX, final int chunkZ) { TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Cannot unload entity section off-main"); this.removeChunk(chunkX, chunkZ); @@ -702,27 +696,7 @@ public final class EntityLookup implements LevelEntityGetter { public ChunkSlicesRegion getRegion(final int regionX, final int regionZ) { final long key = CoordinateUtils.getChunkKey(regionX, regionZ); - final long attempt = this.stateLock.tryOptimisticRead(); - if (attempt != 0L) { - try { - final ChunkSlicesRegion ret = this.regions.get(key); - - if (this.stateLock.validate(attempt)) { - return ret; - } - } catch (final Error error) { - throw error; - } catch (final Throwable thr) { - // ignore - } - } - - this.stateLock.readLock(); - try { - return this.regions.get(key); - } finally { - this.stateLock.tryUnlockRead(); - } + return this.regions.get(key); } private synchronized void removeChunk(final int chunkX, final int chunkZ) { @@ -733,12 +707,7 @@ public final class EntityLookup implements LevelEntityGetter { final int remaining = region.remove(relIndex); if (remaining == 0) { - this.stateLock.writeLock(); - try { - this.regions.remove(key); - } finally { - this.stateLock.tryUnlockWrite(); - } + this.regions.remove(key); } } @@ -752,12 +721,7 @@ public final class EntityLookup implements LevelEntityGetter { } else { region = new ChunkSlicesRegion(); region.add(relIndex, slices); - this.stateLock.writeLock(); - try { - this.regions.put(key, region); - } finally { - this.stateLock.tryUnlockWrite(); - } + this.regions.put(key, region); } } @@ -834,9 +798,11 @@ public final class EntityLookup implements LevelEntityGetter { public static final NoOpCallback INSTANCE = new NoOpCallback(); @Override - public void onMove() {} + public void onMove() { + } @Override - public void onRemove(final Entity.RemovalReason reason) {} + public void onRemove(final Entity.RemovalReason reason) { + } } } diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java index 830d863cd9665d58875bfa5ca2bcd22f89ab2d49..15eeea40bb7a44470f6f3f0e2473cb451812eec1 100644 --- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java +++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java @@ -13,14 +13,10 @@ import io.papermc.paper.util.CoordinateUtils; import io.papermc.paper.util.TickThread; import io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D; import io.papermc.paper.world.ChunkEntitySlices; -import it.unimi.dsi.fastutil.longs.Long2IntLinkedOpenHashMap; -import it.unimi.dsi.fastutil.longs.Long2IntMap; -import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.LongArrayList; -import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.*; import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; +import it.unimi.dsi.fastutil.objects.ObjectSortedSet; +import it.unimi.dsi.fastutil.objects.ObjectSortedSets; import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; import net.minecraft.nbt.CompoundTag; import io.papermc.paper.chunk.system.ChunkSystem; @@ -39,13 +35,8 @@ import org.bukkit.plugin.Plugin; import org.slf4j.Logger; import java.io.IOException; import java.text.DecimalFormat; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Objects; +import java.util.*; +import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -67,16 +58,16 @@ public final class ChunkHolderManager { final ReentrantLock ticketLock = new ReentrantLock(); private final SWMRLong2ObjectHashTable chunkHolders = new SWMRLong2ObjectHashTable<>(16384, 0.25f); - private final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap<>(8192, 0.25f); + private final Long2ObjectMap>> tickets = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>(8192, 0.25f)); // what a disaster of a name // this is a map of removal tick to a map of chunks and the number of tickets a chunk has that are to expire that tick - private final Long2ObjectOpenHashMap removeTickToChunkExpireTicketCount = new Long2ObjectOpenHashMap<>(); + private final Long2ObjectMap removeTickToChunkExpireTicketCount = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); private final ServerLevel world; private final ChunkTaskScheduler taskScheduler; private long currentTick; - private final ArrayDeque pendingFullLoadUpdate = new ArrayDeque<>(); - private final ObjectRBTreeSet autoSaveQueue = new ObjectRBTreeSet<>((final NewChunkHolder c1, final NewChunkHolder c2) -> { + private final Deque pendingFullLoadUpdate = new ConcurrentLinkedDeque<>(); + private final ObjectSortedSet autoSaveQueue = ObjectSortedSets.synchronize(new ObjectRBTreeSet<>((final NewChunkHolder c1, final NewChunkHolder c2) -> { if (c1 == c2) { return 0; } @@ -95,7 +86,7 @@ public final class ChunkHolderManager { } return Long.compare(coord1, coord2); - }); + })); public ChunkHolderManager(final ServerLevel world, final ChunkTaskScheduler taskScheduler) { this.world = world; @@ -311,7 +302,7 @@ public final class ChunkHolderManager { public Long2ObjectOpenHashMap>> getTicketsCopy() { this.ticketLock.lock(); try { - return this.tickets.clone(); + return new Long2ObjectOpenHashMap<>(this.tickets); } finally { this.ticketLock.unlock(); } @@ -784,7 +775,7 @@ public final class ChunkHolderManager { } if (!TickThread.isTickThread()) { this.taskScheduler.scheduleChunkTask(() -> { - final ArrayDeque pendingFullLoadUpdate = ChunkHolderManager.this.pendingFullLoadUpdate; + final Deque pendingFullLoadUpdate = ChunkHolderManager.this.pendingFullLoadUpdate; for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { pendingFullLoadUpdate.add(changedFullStatus.get(i)); } @@ -792,7 +783,7 @@ public final class ChunkHolderManager { ChunkHolderManager.this.processPendingFullUpdate(); }, PrioritisedExecutor.Priority.HIGHEST); } else { - final ArrayDeque pendingFullLoadUpdate = this.pendingFullLoadUpdate; + final Deque pendingFullLoadUpdate = this.pendingFullLoadUpdate; for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { pendingFullLoadUpdate.add(changedFullStatus.get(i)); } @@ -1039,7 +1030,7 @@ public final class ChunkHolderManager { // only call on tick thread protected final boolean processPendingFullUpdate() { - final ArrayDeque pendingFullLoadUpdate = this.pendingFullLoadUpdate; + final Deque pendingFullLoadUpdate = this.pendingFullLoadUpdate; boolean ret = false; diff --git a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java index b3bcafc8bafe1e4a1a2b690499b91e5316a604f1..b12c02962e9dad92ae79d762887c65db10765488 100644 --- a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java +++ b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java @@ -2,7 +2,6 @@ package io.papermc.paper.world; import com.destroystokyo.paper.util.maplist.EntityList; import io.papermc.paper.chunk.system.entity.EntityLookup; -import io.papermc.paper.util.TickThread; import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; import it.unimi.dsi.fastutil.objects.Reference2ObjectMaps; import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; @@ -21,8 +20,8 @@ import net.minecraft.world.phys.AABB; import org.bukkit.craftbukkit.event.CraftEventFactory; import java.util.ArrayList; import java.util.Arrays; -import java.util.Iterator; import java.util.List; +import java.util.concurrent.locks.StampedLock; import java.util.function.Predicate; public final class ChunkEntitySlices { @@ -32,15 +31,15 @@ public final class ChunkEntitySlices { public final int chunkX; public final int chunkZ; protected final ServerLevel world; - + protected final StampedLock accessLock = new StampedLock(); //Hearse -- fix some entity can't be removed protected final EntityCollectionBySection allEntities; protected final EntityCollectionBySection hardCollidingEntities; protected final Reference2ObjectMap, EntityCollectionBySection> entitiesByClass; protected final EntityList entities = new EntityList(); - public ChunkHolder.FullChunkStatus status; + public volatile ChunkHolder.FullChunkStatus status; - protected boolean isTransient; + protected volatile boolean isTransient; public boolean isTransient() { return this.isTransient; @@ -67,8 +66,7 @@ public final class ChunkEntitySlices { this.status = status; } - // Paper start - optimise CraftChunk#getEntities - public org.bukkit.entity.Entity[] getChunkEntities() { + private org.bukkit.entity.Entity[] getChunkEntitiesUnsafe(){ List ret = new java.util.ArrayList<>(); final Entity[] entities = this.entities.getRawData(); for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) { @@ -81,11 +79,25 @@ public final class ChunkEntitySlices { ret.add(bukkit); } } - return ret.toArray(new org.bukkit.entity.Entity[0]); } - public CompoundTag save() { + // Paper start - optimise CraftChunk#getEntities + public org.bukkit.entity.Entity[] getChunkEntities() { + long id = this.accessLock.tryOptimisticRead(); + if (this.accessLock.validate(id)) { + return this.getChunkEntitiesUnsafe(); + } + + id = this.accessLock.readLock(); + try { + return this.getChunkEntitiesUnsafe(); + } finally { + this.accessLock.unlockRead(id); + } + } + + private CompoundTag saveUnsafe(){ final int len = this.entities.size(); if (len == 0) { return null; @@ -107,11 +119,36 @@ public final class ChunkEntitySlices { return EntityStorage.saveEntityChunk(collectedEntities, new ChunkPos(this.chunkX, this.chunkZ), this.world); } + public CompoundTag save() { + long id = this.accessLock.tryOptimisticRead(); + if (this.accessLock.validate(id)){ + return this.saveUnsafe(); + } + id = this.accessLock.readLock(); + try { + return this.saveUnsafe(); + } finally { + this.accessLock.unlockRead(id); + } + } + // returns true if this chunk has transient entities remaining public boolean unload() { - final int len = this.entities.size(); - final Entity[] collectedEntities = Arrays.copyOf(this.entities.getRawData(), len); - + Entity[] collectedEntities; + int len; + long id = this.accessLock.tryOptimisticRead(); + if (this.accessLock.validate(id)){ + len = this.entities.size(); + collectedEntities = Arrays.copyOf(this.entities.getRawData(), len); + }else { + id = this.accessLock.readLock(); + try { + len = this.entities.size(); + collectedEntities = Arrays.copyOf(this.entities.getRawData(), len); + } finally { + this.accessLock.unlockRead(id); + } + } for (int i = 0; i < len; ++i) { final Entity entity = collectedEntities[i]; if (entity.isRemoved()) { @@ -129,7 +166,6 @@ public final class ChunkEntitySlices { } } } - return this.entities.size() != 0; } @@ -141,53 +177,98 @@ public final class ChunkEntitySlices { final Entity[] rawData = this.entities.getRawData(); final List collectedEntities = new ArrayList<>(len); - for (int i = 0; i < len; ++i) { - collectedEntities.add(rawData[i]); - } + collectedEntities.addAll(Arrays.asList(rawData).subList(0, len)); return collectedEntities; } public void callEntitiesLoadEvent() { - CraftEventFactory.callEntitiesLoadEvent(this.world, new ChunkPos(this.chunkX, this.chunkZ), this.getAllEntities()); + long id = this.accessLock.tryOptimisticRead(); + if (this.accessLock.validate(id)){ + CraftEventFactory.callEntitiesLoadEvent(this.world, new ChunkPos(this.chunkX, this.chunkZ), this.getAllEntities()); + return; + } + id = this.accessLock.readLock(); + try { + CraftEventFactory.callEntitiesLoadEvent(this.world, new ChunkPos(this.chunkX, this.chunkZ), this.getAllEntities()); + } finally { + this.accessLock.unlockRead(id); + } } public void callEntitiesUnloadEvent() { - CraftEventFactory.callEntitiesUnloadEvent(this.world, new ChunkPos(this.chunkX, this.chunkZ), this.getAllEntities()); + long id = this.accessLock.tryOptimisticRead(); + if (this.accessLock.validate(id)){ + CraftEventFactory.callEntitiesUnloadEvent(this.world, new ChunkPos(this.chunkX, this.chunkZ), this.getAllEntities()); + return; + } + id = this.accessLock.readLock(); + try { + CraftEventFactory.callEntitiesUnloadEvent(this.world, new ChunkPos(this.chunkX, this.chunkZ), this.getAllEntities()); + } finally { + this.accessLock.unlockRead(id); + } } // Paper end - optimise CraftChunk#getEntities public boolean isEmpty() { - return this.entities.size() == 0; + long id = this.accessLock.tryOptimisticRead(); + if (this.accessLock.validate(id)){ + return this.entities.size() == 0; + } + id = this.accessLock.readLock(); + try { + return this.entities.size() == 0; + } finally { + this.accessLock.unlockRead(id); + } } public void mergeInto(final ChunkEntitySlices slices) { - final Entity[] entities = this.entities.getRawData(); - for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) { - final Entity entity = entities[i]; + final List cop = new ArrayList<>(); + long id = this.accessLock.tryOptimisticRead(); + if (this.accessLock.validate(id)){ + final Entity[] entities = this.entities.getRawData(); + for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) { + final Entity entity = entities[i]; + cop.add(entity); + } + }else { + id = this.accessLock.readLock(); + try { + final Entity[] entities = this.entities.getRawData(); + for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) { + final Entity entity = entities[i]; + cop.add(entity); + } + } finally { + this.accessLock.unlockRead(id); + } + } + for (Entity entity : cop){ slices.addEntity(entity, entity.sectionY); } } - private boolean preventStatusUpdates; - public boolean startPreventingStatusUpdates() { - final boolean ret = this.preventStatusUpdates; - this.preventStatusUpdates = true; - return ret; - } - - public void stopPreventingStatusUpdates(final boolean prev) { - this.preventStatusUpdates = prev; - } - public void updateStatus(final ChunkHolder.FullChunkStatus status, final EntityLookup lookup) { this.status = status; - final Entity[] entities = this.entities.getRawData(); + Entity[] entities; + + long id = this.accessLock.tryOptimisticRead(); + if (this.accessLock.validate(id)){ + entities = Arrays.copyOf(this.entities.getRawData(), this.entities.getRawData().length); + }else { + id = this.accessLock.readLock(); + try { + entities = Arrays.copyOf(this.entities.getRawData(), this.entities.getRawData().length); + } finally { + this.accessLock.unlockRead(id); + } + } - for (int i = 0, size = this.entities.size(); i < size; ++i) { - final Entity entity = entities[i]; + for (final Entity entity : entities) { final Visibility oldVisibility = EntityLookup.getEntityStatus(entity); entity.chunkStatus = status; final Visibility newVisibility = EntityLookup.getEntityStatus(entity); @@ -197,70 +278,112 @@ public final class ChunkEntitySlices { } public boolean addEntity(final Entity entity, final int chunkSection) { - if (!this.entities.add(entity)) { - return false; - } - entity.chunkStatus = this.status; - final int sectionIndex = chunkSection - this.minSection; - - this.allEntities.addEntity(entity, sectionIndex); + long id = this.accessLock.writeLock(); + try { + if (!this.entities.add(entity)) { + return false; + } + entity.chunkStatus = this.status; + final int sectionIndex = chunkSection - this.minSection; - if (entity.hardCollides()) { - this.hardCollidingEntities.addEntity(entity, sectionIndex); - } + this.allEntities.addEntity(entity, sectionIndex); - for (final Iterator, EntityCollectionBySection>> iterator = - this.entitiesByClass.reference2ObjectEntrySet().iterator(); iterator.hasNext();) { - final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); + if (entity.hardCollides()) { + this.hardCollidingEntities.addEntity(entity, sectionIndex); + } - if (entry.getKey().isInstance(entity)) { - entry.getValue().addEntity(entity, sectionIndex); + for (final Reference2ObjectMap.Entry, EntityCollectionBySection> entry : this.entitiesByClass.reference2ObjectEntrySet()) { + if (entry.getKey().isInstance(entity)) { + entry.getValue().addEntity(entity, sectionIndex); + } } + } finally { + this.accessLock.unlockWrite(id); } - return true; } public boolean removeEntity(final Entity entity, final int chunkSection) { - if (!this.entities.remove(entity)) { - return false; - } - entity.chunkStatus = null; - final int sectionIndex = chunkSection - this.minSection; - - this.allEntities.removeEntity(entity, sectionIndex); + long id = this.accessLock.writeLock(); + try { + if (!this.entities.remove(entity)) { + return false; + } + entity.chunkStatus = null; + final int sectionIndex = chunkSection - this.minSection; - if (entity.hardCollides()) { - this.hardCollidingEntities.removeEntity(entity, sectionIndex); - } + this.allEntities.removeEntity(entity, sectionIndex); - for (final Iterator, EntityCollectionBySection>> iterator = - this.entitiesByClass.reference2ObjectEntrySet().iterator(); iterator.hasNext();) { - final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); + if (entity.hardCollides()) { + this.hardCollidingEntities.removeEntity(entity, sectionIndex); + } - if (entry.getKey().isInstance(entity)) { - entry.getValue().removeEntity(entity, sectionIndex); + for (final Reference2ObjectMap.Entry, EntityCollectionBySection> entry : this.entitiesByClass.reference2ObjectEntrySet()) { + if (entry.getKey().isInstance(entity)) { + entry.getValue().removeEntity(entity, sectionIndex); + } } + } finally { + this.accessLock.unlockWrite(id); } - return true; } public void getHardCollidingEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { - this.hardCollidingEntities.getEntities(except, box, into, predicate); + long id = this.accessLock.tryOptimisticRead(); + if (this.accessLock.validate(id)){ + this.hardCollidingEntities.getEntities(except, box, into, predicate); + return; + } + id = this.accessLock.readLock(); + try { + this.hardCollidingEntities.getEntities(except, box, into, predicate); + } finally { + this.accessLock.unlockRead(id); + } } public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { - this.allEntities.getEntitiesWithEnderDragonParts(except, box, into, predicate); + long id = this.accessLock.tryOptimisticRead(); + if (this.accessLock.validate(id)){ + this.allEntities.getEntitiesWithEnderDragonParts(except, box, into, predicate); + return; + } + id = this.accessLock.readLock(); + try { + this.allEntities.getEntitiesWithEnderDragonParts(except, box, into, predicate); + } finally { + this.accessLock.unlockRead(id); + } } public void getEntitiesWithoutDragonParts(final Entity except, final AABB box, final List into, final Predicate predicate) { - this.allEntities.getEntities(except, box, into, predicate); + long id = this.accessLock.tryOptimisticRead(); + if (this.accessLock.validate(id)){ + this.allEntities.getEntities(except, box, into, predicate); + return; + } + id = this.accessLock.readLock(); + try { + this.allEntities.getEntities(except, box, into, predicate); + } finally { + this.accessLock.unlockRead(id); + } } public void getEntities(final EntityType type, final AABB box, final List into, final Predicate predicate) { - this.allEntities.getEntities(type, box, (List)into, (Predicate)predicate); + long id = this.accessLock.tryOptimisticRead(); + if (this.accessLock.validate(id)){ + this.allEntities.getEntities(type, box, (List) into, (Predicate) predicate); + return; + } + id = this.accessLock.readLock(); + try { + this.allEntities.getEntities(type, box, (List) into, (Predicate) predicate); + } finally { + this.accessLock.unlockRead(id); + } } protected EntityCollectionBySection initClass(final Class clazz) { @@ -288,12 +411,28 @@ public final class ChunkEntitySlices { public void getEntities(final Class clazz, final Entity except, final AABB box, final List into, final Predicate predicate) { - EntityCollectionBySection collection = this.entitiesByClass.get(clazz); - if (collection != null) { - collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List)into, (Predicate)predicate); - } else { - this.entitiesByClass.putIfAbsent(clazz, collection = this.initClass(clazz)); - collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List)into, (Predicate)predicate); + long id = this.accessLock.tryOptimisticRead(); + if (this.accessLock.validate(id)){ + EntityCollectionBySection collection = this.entitiesByClass.get(clazz); + if (collection != null) { + collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List) into, (Predicate) predicate); + } else { + this.entitiesByClass.putIfAbsent(clazz, collection = this.initClass(clazz)); + collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List) into, (Predicate) predicate); + } + return; + } + id = this.accessLock.readLock(); + try { + EntityCollectionBySection collection = this.entitiesByClass.get(clazz); + if (collection != null) { + collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List) into, (Predicate) predicate); + } else { + this.entitiesByClass.putIfAbsent(clazz, collection = this.initClass(clazz)); + collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List) into, (Predicate) predicate); + } + } finally { + this.accessLock.unlockRead(id); } } @@ -310,26 +449,26 @@ public final class ChunkEntitySlices { } public BasicEntityList(final int cap) { - this.storage = (E[])(cap <= 0 ? EMPTY : new Entity[cap]); + this.storage = (E[]) (cap <= 0 ? EMPTY : new Entity[cap]); } - public synchronized boolean isEmpty() { + public boolean isEmpty() { return this.size == 0; } - public synchronized int size() { + public int size() { return this.size; } private void resize() { if (this.storage == EMPTY) { - this.storage = (E[])new Entity[DEFAULT_CAPACITY]; + this.storage = (E[]) new Entity[DEFAULT_CAPACITY]; } else { this.storage = Arrays.copyOf(this.storage, this.storage.length * 2); } } - public synchronized void add(final E entity) { + public void add(final E entity) { final int idx = this.size++; if (idx >= this.storage.length) { this.resize(); @@ -339,7 +478,7 @@ public final class ChunkEntitySlices { } } - public synchronized int indexOf(final E entity) { + public int indexOf(final E entity) { final E[] storage = this.storage; for (int i = 0, len = Math.min(this.storage.length, this.size); i < len; ++i) { @@ -351,7 +490,7 @@ public final class ChunkEntitySlices { return -1; } - public synchronized boolean remove(final E entity) { + public boolean remove(final E entity) { final int idx = this.indexOf(entity); if (idx == -1) { return false; @@ -368,7 +507,7 @@ public final class ChunkEntitySlices { return true; } - public synchronized boolean has(final E entity) { + public boolean has(final E entity) { return this.indexOf(entity) != -1; } } @@ -389,7 +528,7 @@ public final class ChunkEntitySlices { this.entitiesBySection = new BasicEntityList[sectionCount]; } - public synchronized void addEntity(final Entity entity, final int sectionIndex) { + public void addEntity(final Entity entity, final int sectionIndex) { BasicEntityList list = this.entitiesBySection[sectionIndex]; if (list != null && list.has(entity)) { @@ -405,7 +544,7 @@ public final class ChunkEntitySlices { ++this.count; } - public synchronized void removeEntity(final Entity entity, final int sectionIndex) { + public void removeEntity(final Entity entity, final int sectionIndex) { final BasicEntityList list = this.entitiesBySection[sectionIndex]; if (list == null || !list.remove(entity)) { @@ -420,7 +559,7 @@ public final class ChunkEntitySlices { } } - public synchronized void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { + public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { if (this.count == 0) { return; } @@ -458,7 +597,7 @@ public final class ChunkEntitySlices { } } - public synchronized void getEntitiesWithEnderDragonParts(final Entity except, final AABB box, final List into, + public void getEntitiesWithEnderDragonParts(final Entity except, final AABB box, final List into, final Predicate predicate) { if (this.count == 0) { return; @@ -493,7 +632,7 @@ public final class ChunkEntitySlices { } // else: continue to test the ender dragon parts if (entity instanceof EnderDragon) { - for (final EnderDragonPart part : ((EnderDragon)entity).subEntities) { + for (final EnderDragonPart part : ((EnderDragon) entity).subEntities) { if (part == except || !part.getBoundingBox().intersects(box)) { continue; } @@ -509,7 +648,7 @@ public final class ChunkEntitySlices { } } - public synchronized void getEntitiesWithEnderDragonParts(final Entity except, final Class clazz, final AABB box, final List into, + public void getEntitiesWithEnderDragonParts(final Entity except, final Class clazz, final AABB box, final List into, final Predicate predicate) { if (this.count == 0) { return; @@ -544,7 +683,7 @@ public final class ChunkEntitySlices { } // else: continue to test the ender dragon parts if (entity instanceof EnderDragon) { - for (final EnderDragonPart part : ((EnderDragon)entity).subEntities) { + for (final EnderDragonPart part : ((EnderDragon) entity).subEntities) { if (part == except || !part.getBoundingBox().intersects(box) || !clazz.isInstance(part)) { continue; } @@ -560,7 +699,7 @@ public final class ChunkEntitySlices { } } - public synchronized void getEntities(final EntityType type, final AABB box, final List into, + public void getEntities(final EntityType type, final AABB box, final List into, final Predicate predicate) { if (this.count == 0) { return; @@ -590,11 +729,11 @@ public final class ChunkEntitySlices { continue; } - if (predicate != null && !predicate.test((T)entity)) { + if (predicate != null && !predicate.test((T) entity)) { continue; } - into.add((T)entity); + into.add((T) entity); } } }