mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-26 18:39:23 +00:00
721 lines
35 KiB
Diff
721 lines
35 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: wangxyper <wangxyper@163.com>
|
|
Date: Sun, 15 Jan 2023 09:52:27 +0800
|
|
Subject: [PATCH] Hearse: Paper chunk system changes
|
|
|
|
Original license: MIT
|
|
Original project: https://github.com/Era4FunMC/Hearse
|
|
|
|
diff --git a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java
|
|
index b2e4fb69fd6564484e0ebd120ba87431c5c158e4..4945be5eac77c208d9aae317141414c0d154c4b1 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() {
|
|
@@ -523,17 +523,14 @@ public final class PlayerChunkLoader {
|
|
if (nextChunkSend - time > 0) {
|
|
return;
|
|
}
|
|
+ PlayerLoaderData data1;
|
|
// drain entries from wait queue
|
|
- while (!this.chunkSendWaitQueue.isEmpty()) {
|
|
- final PlayerLoaderData data = this.chunkSendWaitQueue.first();
|
|
-
|
|
- if (data.nextChunkSendTarget - time > 0) {
|
|
+ while ((data1 = this.chunkSendWaitQueue.pollFirst()) != null) {
|
|
+ if (data1.nextChunkSendTarget - time > 0) {
|
|
+ this.chunkSendWaitQueue.add(data1);
|
|
break;
|
|
}
|
|
-
|
|
- this.chunkSendWaitQueue.pollFirst();
|
|
-
|
|
- this.chunkSendQueue.add(data);
|
|
+ this.chunkSendQueue.add(data1);
|
|
}
|
|
|
|
if (this.chunkSendQueue.isEmpty()) {
|
|
@@ -542,10 +539,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 +550,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 +570,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 +608,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 +659,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 +722,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 +773,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 +951,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 61c170555c8854b102c640b0b6a615f9f732edbf..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,8 +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.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.Object2ReferenceOpenHashMap;
|
|
import net.minecraft.core.BlockPos;
|
|
import io.papermc.paper.chunk.system.ChunkSystem;
|
|
@@ -31,6 +38,8 @@ 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;
|
|
@@ -45,16 +54,16 @@ public final class EntityLookup implements LevelEntityGetter<Entity> {
|
|
|
|
public final ServerLevel world;
|
|
|
|
- private final StampedLock stateLock = new StampedLock();
|
|
- protected final Long2ObjectOpenHashMap<ChunkSlicesRegion> regions = 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 Int2ReferenceOpenHashMap<Entity> entityById = new Int2ReferenceOpenHashMap<>();
|
|
- private final Object2ReferenceOpenHashMap<UUID, Entity> entityByUUID = 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) {
|
|
@@ -105,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);
|
|
}
|
|
@@ -166,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();
|
|
}
|
|
@@ -208,8 +216,8 @@ public final class EntityLookup implements LevelEntityGetter<Entity> {
|
|
public void get(final AABB box, final Consumer<Entity> action) {
|
|
List<Entity> entities = new ArrayList<>();
|
|
this.getEntitiesWithoutDragonParts(null, box, entities, null);
|
|
- for (int i = 0, len = entities.size(); i < len; ++i) {
|
|
- action.accept(entities.get(i));
|
|
+ for (Entity entity : entities) {
|
|
+ action.accept(entity);
|
|
}
|
|
}
|
|
|
|
@@ -217,8 +225,8 @@ public final class EntityLookup implements LevelEntityGetter<Entity> {
|
|
public <U extends Entity> void get(final EntityTypeTest<Entity, U> filter, final AABB box, final AbortableIterationConsumer<U> action) {
|
|
List<Entity> entities = new ArrayList<>();
|
|
this.getEntitiesWithoutDragonParts(null, box, entities, null);
|
|
- for (int i = 0, len = entities.size(); i < len; ++i) {
|
|
- final U casted = filter.tryCast(entities.get(i));
|
|
+ for (Entity entity : entities) {
|
|
+ final U casted = filter.tryCast(entity);
|
|
if (casted != null && action.accept(casted).shouldAbort()) {
|
|
break;
|
|
}
|
|
@@ -228,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.error("Cannot recursively update entity chunk status for entity " + entity, new Throwable());
|
|
- return;
|
|
- }
|
|
-
|
|
- final boolean entityStatusUpdateBefore = slices == null ? false : slices.startPreventingStatusUpdates();
|
|
-
|
|
- if (entityStatusUpdateBefore) {
|
|
- LOGGER.error("Cannot update chunk status for entity " + entity + " since entity chunk (" + slices.chunkX + "," + slices.chunkZ + ") is receiving update", new Throwable());
|
|
- 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);
|
|
}
|
|
}
|
|
|
|
@@ -346,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", new Throwable());
|
|
- return false;
|
|
- }
|
|
-
|
|
if (fromDisk) {
|
|
ChunkSystem.onEntityPreAdd(this.world, entity);
|
|
if (entity.isRemoved()) {
|
|
@@ -398,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 + ")");
|
|
@@ -441,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)) {
|
|
@@ -609,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);
|
|
}
|
|
}
|
|
}
|
|
@@ -660,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);
|
|
@@ -699,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) {
|
|
@@ -730,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);
|
|
}
|
|
}
|
|
|
|
@@ -749,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);
|
|
}
|
|
}
|
|
|
|
@@ -831,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 22720de9b94163b84dd6bf6ddf8b3e57eeb9389c..80e26792e6e29a9896295eac6c5d4ed98653fa07 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;
|
|
@@ -41,13 +37,8 @@ import org.galemc.gale.executor.lock.YieldingLock;
|
|
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;
|
|
@@ -69,16 +60,16 @@ public final class ChunkHolderManager {
|
|
final YieldingLock ticketLock = new MultipleWaitingBaseThreadsYieldingLock(new ReentrantLock()); // Gale - base thread pool - yielding ChunkHolderManager
|
|
|
|
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;
|
|
}
|
|
@@ -97,7 +88,7 @@ public final class ChunkHolderManager {
|
|
}
|
|
|
|
return Long.compare(coord1, coord2);
|
|
- });
|
|
+ }));
|
|
|
|
public ChunkHolderManager(final ServerLevel world, final ChunkTaskScheduler taskScheduler) {
|
|
this.world = world;
|
|
@@ -313,7 +304,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();
|
|
}
|
|
@@ -786,7 +777,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));
|
|
}
|
|
@@ -794,7 +785,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));
|
|
}
|
|
@@ -1041,7 +1032,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;
|
|
|