9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-23 17:09:29 +00:00
Files
Leaf/patches/server/0043-MikuServer-Concurrent-problems-fixes.patch
2023-01-30 03:26:45 -05:00

1292 lines
58 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: wangxyper <wangxyper@163.com>
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<ServerPlayer, PlayerLoaderData> playerMap = new Reference2ObjectLinkedOpenHashMap<>(512, 0.7f);
- protected final ReferenceLinkedOpenHashSet<PlayerLoaderData> chunkSendQueue = new ReferenceLinkedOpenHashSet<>(512, 0.7f);
+ protected final Reference2ObjectMap<ServerPlayer, PlayerLoaderData> playerMap = Reference2ObjectMaps.synchronize(new Reference2ObjectLinkedOpenHashMap<>(512, 0.7f));
+ protected final Deque<PlayerLoaderData> chunkSendQueue = new ConcurrentLinkedDeque<>();
- protected final TreeSet<PlayerLoaderData> chunkLoadQueue = new TreeSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> {
+ protected final NavigableSet<PlayerLoaderData> 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<PlayerLoaderData> chunkSendWaitQueue = new TreeSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> {
+ protected final NavigableSet<PlayerLoaderData> 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<ServerPlayer> 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<PlayerLoaderData> 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<PlayerLoaderData> 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<ChunkPriorityHolder> loadQueue = new ArrayDeque<>();
- protected final LongOpenHashSet sentChunks = new LongOpenHashSet();
- protected final LongOpenHashSet chunksToBeSent = new LongOpenHashSet();
+ protected final Deque<ChunkPriorityHolder> loadQueue = new ConcurrentLinkedDeque<>();
+ protected final LongSet sentChunks = LongSets.synchronize(new LongOpenHashSet());
+ protected final LongSet chunksToBeSent = LongSets.synchronize(new LongOpenHashSet());
- protected final TreeSet<ChunkPriorityHolder> sendQueue = new TreeSet<>((final ChunkPriorityHolder p1, final ChunkPriorityHolder p2) -> {
+ protected final NavigableSet<ChunkPriorityHolder> 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<Entity> {
public final ServerLevel world;
- private final StampedLock stateLock = new StampedLock();
- protected final Long2ObjectMap<ChunkSlicesRegion> regions = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>(128, 0.5f));
+ private final StampedLock entityByLock = new StampedLock();
+ private final Lock regionLoadLock = new ReentrantLock(true);
+
+ protected final Long2ObjectMap<ChunkSlicesRegion> regions = Long2ObjectMaps.synchronize(new Long2ObjectArrayMap<>());
private final int minSection; // inclusive
private final int maxSection; // inclusive
private final LevelCallback<Entity> worldCallback;
-
- private final StampedLock entityByLock = new StampedLock();
- private final Map<Integer,Entity> entityById = Int2ReferenceMaps.synchronize(new Int2ReferenceOpenHashMap<>());
- private final Object2ReferenceMap<UUID, Entity> entityByUUID = Object2ReferenceMaps.synchronize(new Object2ReferenceOpenHashMap<>());
+ private final Int2ReferenceMap<Entity> entityById = new Int2ReferenceArrayMap<>();
+ private final Object2ReferenceMap<UUID, Entity> entityByUUID = new Object2ReferenceArrayMap<>();
private final EntityList accessibleEntities = new EntityList();
public EntityLookup(final ServerLevel world, final LevelCallback<Entity> worldCallback) {
@@ -108,7 +114,6 @@ public final class EntityLookup implements LevelEntityGetter<Entity> {
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<Entity> {
}
@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<Entity> {
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<Entity> {
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<Entity> {
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<Entity> {
// 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<Entity> {
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<Entity> {
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<Entity> {
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<Entity> {
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<Entity> {
} 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<Entity> {
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<NewChunkHolder> chunkHolders = new SWMRLong2ObjectHashTable<>(16384, 0.25f);
- private final Long2ObjectOpenHashMap<SortedArraySet<Ticket<?>>> tickets = new Long2ObjectOpenHashMap<>(8192, 0.25f);
+ private final Long2ObjectMap<SortedArraySet<Ticket<?>>> 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<Long2IntOpenHashMap> removeTickToChunkExpireTicketCount = new Long2ObjectOpenHashMap<>();
+ private final Long2ObjectMap<Long2IntOpenHashMap> removeTickToChunkExpireTicketCount = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>());
private final ServerLevel world;
private final ChunkTaskScheduler taskScheduler;
private long currentTick;
- private final ArrayDeque<NewChunkHolder> pendingFullLoadUpdate = new ArrayDeque<>();
- private final ObjectRBTreeSet<NewChunkHolder> autoSaveQueue = new ObjectRBTreeSet<>((final NewChunkHolder c1, final NewChunkHolder c2) -> {
+ private final Deque<NewChunkHolder> pendingFullLoadUpdate = new ConcurrentLinkedDeque<>();
+ private final ObjectSortedSet<NewChunkHolder> 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<SortedArraySet<Ticket<?>>> 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<NewChunkHolder> pendingFullLoadUpdate = ChunkHolderManager.this.pendingFullLoadUpdate;
+ final Deque<NewChunkHolder> 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<NewChunkHolder> pendingFullLoadUpdate = this.pendingFullLoadUpdate;
+ final Deque<NewChunkHolder> 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<NewChunkHolder> pendingFullLoadUpdate = this.pendingFullLoadUpdate;
+ final Deque<NewChunkHolder> 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<Class<? extends Entity>, 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<org.bukkit.entity.Entity> 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<Entity> 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<Entity> 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<Reference2ObjectMap.Entry<Class<? extends Entity>, EntityCollectionBySection>> iterator =
- this.entitiesByClass.reference2ObjectEntrySet().iterator(); iterator.hasNext();) {
- final Reference2ObjectMap.Entry<Class<? extends Entity>, 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<Class<? extends Entity>, 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<Reference2ObjectMap.Entry<Class<? extends Entity>, EntityCollectionBySection>> iterator =
- this.entitiesByClass.reference2ObjectEntrySet().iterator(); iterator.hasNext();) {
- final Reference2ObjectMap.Entry<Class<? extends Entity>, 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<Class<? extends Entity>, 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<Entity> into, final Predicate<? super Entity> 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<Entity> into, final Predicate<? super Entity> 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<Entity> into, final Predicate<? super Entity> 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 <T extends Entity> void getEntities(final EntityType<?> type, final AABB box, final List<? super T> into,
final Predicate<? super T> 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<? extends Entity> clazz) {
@@ -288,12 +411,28 @@ public final class ChunkEntitySlices {
public <T extends Entity> void getEntities(final Class<? extends T> clazz, final Entity except, final AABB box, final List<? super T> into,
final Predicate<? super T> 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<Entity> 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<Entity> 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<Entity> into, final Predicate<? super Entity> predicate) {
+ public void getEntities(final Entity except, final AABB box, final List<Entity> into, final Predicate<? super Entity> 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<Entity> into,
+ public void getEntitiesWithEnderDragonParts(final Entity except, final AABB box, final List<Entity> into,
final Predicate<? super Entity> 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<Entity> into,
+ public void getEntitiesWithEnderDragonParts(final Entity except, final Class<?> clazz, final AABB box, final List<Entity> into,
final Predicate<? super Entity> 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 <T extends Entity> void getEntities(final EntityType<?> type, final AABB box, final List<? super T> into,
+ public <T extends Entity> void getEntities(final EntityType<?> type, final AABB box, final List<? super T> into,
final Predicate<? super T> 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);
}
}
}