mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-23 17:09:29 +00:00
1765 lines
84 KiB
Diff
1765 lines
84 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: wangxyper <wangxyper@163.com>
|
|
Date: Mon, 9 Jan 2023 12:35:11 +0800
|
|
Subject: [PATCH] Hearse: Complete half of the code and fix some problems
|
|
|
|
Original license: MIT
|
|
Original project: https://github.com/NaturalCodeClub/HearseRewrite
|
|
|
|
diff --git a/src/main/java/co/earthme/hearse/concurrent/WorkerThread.java b/src/main/java/co/earthme/hearse/concurrent/WorkerThread.java
|
|
index 2a43625d13d7aa253c15aba8092ac9361785a5f0..52f0c9dddf29a28cc360fbacb923445e5c3f82a6 100644
|
|
--- a/src/main/java/co/earthme/hearse/concurrent/WorkerThread.java
|
|
+++ b/src/main/java/co/earthme/hearse/concurrent/WorkerThread.java
|
|
@@ -4,12 +4,19 @@ import io.papermc.paper.util.TickThread;
|
|
|
|
public class WorkerThread extends TickThread {
|
|
|
|
+
|
|
public WorkerThread(String name) {
|
|
super(name);
|
|
this.setDaemon(true);
|
|
this.setPriority(Thread.NORM_PRIORITY - 2);
|
|
}
|
|
|
|
+ public WorkerThread(Runnable run, String name) {
|
|
+ super(run, name);
|
|
+ this.setDaemon(true);
|
|
+ this.setPriority(Thread.NORM_PRIORITY - 2);
|
|
+ }
|
|
+
|
|
public static boolean isWorker(){
|
|
return Thread.currentThread() instanceof WorkerThread;
|
|
}
|
|
diff --git a/src/main/java/co/earthme/hearse/concurrent/WorkerThreadPoolExecutor.java b/src/main/java/co/earthme/hearse/concurrent/WorkerThreadPoolExecutor.java
|
|
index f7ca6d650d9089b65137d61acca64c89e5b4db22..8899c02a2242b51097a03c7e3ca03b8768c60117 100644
|
|
--- a/src/main/java/co/earthme/hearse/concurrent/WorkerThreadPoolExecutor.java
|
|
+++ b/src/main/java/co/earthme/hearse/concurrent/WorkerThreadPoolExecutor.java
|
|
@@ -10,11 +10,11 @@ public class WorkerThreadPoolExecutor extends ThreadPoolExecutor {
|
|
private final Queue<TaskEntry> taskEntries = new ConcurrentLinkedQueue<>();
|
|
|
|
public WorkerThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, @NotNull TimeUnit unit, @NotNull BlockingQueue<Runnable> workQueue, @NotNull WorkerThreadFactory workerThreadFactory) {
|
|
- super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,r->workerThreadFactory.getNewThread(r));
|
|
+ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, workerThreadFactory::getNewThread);
|
|
}
|
|
|
|
public WorkerThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, @NotNull TimeUnit unit, @NotNull BlockingQueue<Runnable> workQueue, @NotNull WorkerThreadFactory workerThreadFactory, @NotNull RejectedExecutionHandler handler) {
|
|
- super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,r->workerThreadFactory.getNewThread(r), handler);
|
|
+ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, workerThreadFactory::getNewThread, handler);
|
|
}
|
|
|
|
public void executeWithSubTask(Runnable mainTask,Runnable subTask){
|
|
diff --git a/src/main/java/co/earthme/hearse/server/ServerHook.java b/src/main/java/co/earthme/hearse/server/ServerHook.java
|
|
index 22260735664d986fed6bf82e4016b647417e1932..524a55c3298a079e416c742641af55725a602a2b 100644
|
|
--- a/src/main/java/co/earthme/hearse/server/ServerHook.java
|
|
+++ b/src/main/java/co/earthme/hearse/server/ServerHook.java
|
|
@@ -5,6 +5,8 @@ import co.earthme.hearse.concurrent.WorkerThreadPoolExecutor;
|
|
import net.minecraft.server.MinecraftServer;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.world.entity.Entity;
|
|
+import org.apache.logging.log4j.LogManager;
|
|
+
|
|
import java.util.concurrent.LinkedBlockingQueue;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
@@ -19,7 +21,7 @@ public class ServerHook {
|
|
TimeUnit.MILLISECONDS,
|
|
new LinkedBlockingQueue<>(),
|
|
task -> {
|
|
- WorkerThread workerThread = new WorkerThread("Hearse-Worker-Thread # "+threadId.getAndIncrement());
|
|
+ WorkerThread workerThread = new WorkerThread(task,"Hearse-Worker-Thread # "+threadId.getAndIncrement());
|
|
return workerThread;
|
|
}
|
|
);
|
|
diff --git a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java
|
|
index f72d99bea1c78c8882793fa8ee3dc3642be1b8c7..62a74cbdb7f04b652dddac9e9c6191d5b86c3323 100644
|
|
--- a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java
|
|
+++ b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java
|
|
@@ -37,11 +37,11 @@ public final class PlayerChunkLoader {
|
|
public static final int LOADED_TICKET_LEVEL = 33;
|
|
|
|
public static int getTickViewDistance(final Player player) {
|
|
- return getTickViewDistance(((CraftPlayer)player).getHandle());
|
|
+ return getTickViewDistance(((CraftPlayer) player).getHandle());
|
|
}
|
|
|
|
public static int getTickViewDistance(final ServerPlayer player) {
|
|
- final ServerLevel level = (ServerLevel)player.level;
|
|
+ final ServerLevel level = (ServerLevel) player.level;
|
|
final PlayerLoaderData data = level.chunkSource.chunkMap.playerChunkManager.getData(player);
|
|
if (data == null) {
|
|
return level.chunkSource.chunkMap.playerChunkManager.getTargetTickViewDistance();
|
|
@@ -50,11 +50,11 @@ public final class PlayerChunkLoader {
|
|
}
|
|
|
|
public static int getLoadViewDistance(final Player player) {
|
|
- return getLoadViewDistance(((CraftPlayer)player).getHandle());
|
|
+ return getLoadViewDistance(((CraftPlayer) player).getHandle());
|
|
}
|
|
|
|
public static int getLoadViewDistance(final ServerPlayer player) {
|
|
- final ServerLevel level = (ServerLevel)player.level;
|
|
+ final ServerLevel level = (ServerLevel) player.level;
|
|
final PlayerLoaderData data = level.chunkSource.chunkMap.playerChunkManager.getData(player);
|
|
if (data == null) {
|
|
return level.chunkSource.chunkMap.playerChunkManager.getLoadDistance();
|
|
@@ -63,11 +63,11 @@ public final class PlayerChunkLoader {
|
|
}
|
|
|
|
public static int getSendViewDistance(final Player player) {
|
|
- return getSendViewDistance(((CraftPlayer)player).getHandle());
|
|
+ return getSendViewDistance(((CraftPlayer) player).getHandle());
|
|
}
|
|
|
|
public static int getSendViewDistance(final ServerPlayer player) {
|
|
- final ServerLevel level = (ServerLevel)player.level;
|
|
+ final ServerLevel level = (ServerLevel) player.level;
|
|
final PlayerLoaderData data = level.chunkSource.chunkMap.playerChunkManager.getData(player);
|
|
if (data == null) {
|
|
return level.chunkSource.chunkMap.playerChunkManager.getTargetSendDistance();
|
|
@@ -75,9 +75,6 @@ public final class PlayerChunkLoader {
|
|
return data.getTargetSendViewDistance();
|
|
}
|
|
|
|
- private final StampedLock sendLock = new StampedLock();
|
|
- private final StampedLock loadLock = new StampedLock();
|
|
-
|
|
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);
|
|
@@ -376,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() {
|
|
@@ -386,15 +383,15 @@ public final class PlayerChunkLoader {
|
|
protected int getMaxChunkLoads() {
|
|
double config = GlobalConfiguration.get().chunkLoading.playerMaxConcurrentLoads;
|
|
double max = GlobalConfiguration.get().chunkLoading.globalMaxConcurrentLoads;
|
|
- return (int)Math.ceil(Math.min(config * MinecraftServer.getServer().getPlayerCount(), max <= 1.0 ? Double.MAX_VALUE : max));
|
|
+ return (int) Math.ceil(Math.min(config * MinecraftServer.getServer().getPlayerCount(), max <= 1.0 ? Double.MAX_VALUE : max));
|
|
}
|
|
|
|
protected long getTargetSendPerPlayerAddend() {
|
|
- return GlobalConfiguration.get().chunkLoading.targetPlayerChunkSendRate <= 1.0 ? 0L : (long)Math.round(1.0e9 / GlobalConfiguration.get().chunkLoading.targetPlayerChunkSendRate);
|
|
+ return GlobalConfiguration.get().chunkLoading.targetPlayerChunkSendRate <= 1.0 ? 0L : (long) Math.round(1.0e9 / GlobalConfiguration.get().chunkLoading.targetPlayerChunkSendRate);
|
|
}
|
|
|
|
protected long getMaxSendAddend() {
|
|
- return GlobalConfiguration.get().chunkLoading.globalMaxChunkSendRate <= 1.0 ? 0L : (long)Math.round(1.0e9 / GlobalConfiguration.get().chunkLoading.globalMaxChunkSendRate);
|
|
+ return GlobalConfiguration.get().chunkLoading.globalMaxChunkSendRate <= 1.0 ? 0L : (long) Math.round(1.0e9 / GlobalConfiguration.get().chunkLoading.globalMaxChunkSendRate);
|
|
}
|
|
|
|
public void onChunkPlayerTickReady(final int chunkX, final int chunkZ) {
|
|
@@ -416,7 +413,7 @@ public final class PlayerChunkLoader {
|
|
if (!(raw instanceof ServerPlayer)) {
|
|
continue;
|
|
}
|
|
- this.onChunkSendReady((ServerPlayer)raw, chunkX, chunkZ);
|
|
+ this.onChunkSendReady((ServerPlayer) raw, chunkX, chunkZ);
|
|
}
|
|
}
|
|
|
|
@@ -485,19 +482,9 @@ public final class PlayerChunkLoader {
|
|
}
|
|
loaderData.remove();
|
|
|
|
- long id1 = this.loadLock.writeLock();
|
|
- try {
|
|
- this.chunkLoadQueue.remove(loaderData);
|
|
- }finally {
|
|
- this.loadLock.unlockWrite(id1);
|
|
- }
|
|
+ this.chunkLoadQueue.remove(loaderData);
|
|
|
|
- final long id = this.sendLock.writeLock();
|
|
- try {
|
|
- this.chunkSendQueue.remove(loaderData);
|
|
- }finally {
|
|
- this.sendLock.unlockWrite(id);
|
|
- }
|
|
+ this.chunkSendQueue.remove(loaderData);
|
|
|
|
this.chunkSendWaitQueue.remove(loaderData);
|
|
synchronized (this.sendingChunkCounts) {
|
|
@@ -534,49 +521,34 @@ 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;
|
|
}
|
|
- final long id = this.sendLock.writeLock();
|
|
- try {
|
|
- // drain entries from wait queue
|
|
- while (!this.chunkSendWaitQueue.isEmpty()) {
|
|
- final PlayerLoaderData data = this.chunkSendWaitQueue.first();
|
|
+ // drain entries from wait queue
|
|
+ while (!this.chunkSendWaitQueue.isEmpty()) {
|
|
+ final PlayerLoaderData data = this.chunkSendWaitQueue.first();
|
|
|
|
- if (data.nextChunkSendTarget > time) {
|
|
- break;
|
|
- }
|
|
+ if (data.nextChunkSendTarget > time) {
|
|
+ break;
|
|
+ }
|
|
|
|
- this.chunkSendWaitQueue.pollFirst();
|
|
+ this.chunkSendWaitQueue.pollFirst();
|
|
|
|
- this.chunkSendQueue.add(data);
|
|
- }
|
|
- }finally {
|
|
- this.sendLock.unlockWrite(id);
|
|
+ this.chunkSendQueue.add(data);
|
|
}
|
|
|
|
- long id2 = this.sendLock.readLock();
|
|
- try {
|
|
- if (this.chunkSendQueue.isEmpty()) {
|
|
- return;
|
|
- }
|
|
- }finally {
|
|
- this.sendLock.unlockRead(id2);
|
|
+ if (this.chunkSendQueue.isEmpty()) {
|
|
+ return;
|
|
}
|
|
|
|
final int maxSends = this.getMaxConcurrentChunkSends();
|
|
final long nextPlayerDeadline = this.getTargetSendPerPlayerAddend() + time;
|
|
- for (;;) {
|
|
-
|
|
- long id3 = this.sendLock.readLock();
|
|
- try {
|
|
- if (this.chunkSendQueue.isEmpty()) {
|
|
- break;
|
|
- }
|
|
- }finally {
|
|
- this.sendLock.unlockRead(id3);
|
|
+ for (; ; ) {
|
|
+ if (this.chunkSendQueue.isEmpty()) {
|
|
+ break;
|
|
}
|
|
|
|
final int currSends = concurrentChunkSends.get();
|
|
@@ -590,29 +562,17 @@ public final class PlayerChunkLoader {
|
|
|
|
// send chunk
|
|
|
|
- PlayerLoaderData data;
|
|
-
|
|
- final long id4 = this.sendLock.writeLock();
|
|
- try {
|
|
- data = this.chunkSendQueue.removeFirst();
|
|
- }finally {
|
|
- this.sendLock.unlockWrite(id4);
|
|
- }
|
|
+ PlayerLoaderData data = this.chunkSendQueue.removeFirst();
|
|
|
|
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
|
|
- final long id5 = this.sendLock.readLock();
|
|
- try {
|
|
- if (this.chunkSendQueue.isEmpty()) {
|
|
- // nothing left
|
|
- break;
|
|
- }
|
|
- continue;
|
|
- }finally {
|
|
- this.sendLock.unlockRead(id5);
|
|
+ if (this.chunkSendQueue.isEmpty()) {
|
|
+ // nothing left
|
|
+ break;
|
|
}
|
|
+ continue;
|
|
}
|
|
|
|
if (!this.isChunkPlayerLoaded(queuedSend.chunkX, queuedSend.chunkZ)) {
|
|
@@ -620,7 +580,6 @@ public final class PlayerChunkLoader {
|
|
}
|
|
|
|
data.nextChunkSendTarget = nextPlayerDeadline;
|
|
- this.sendLock.writeLock();
|
|
|
|
synchronized (this.sendingChunkCounts) {
|
|
this.sendingChunkCounts.addTo(data, 1);
|
|
@@ -652,43 +611,27 @@ public final class PlayerChunkLoader {
|
|
|
|
protected int concurrentChunkLoads;
|
|
// this interval prevents bursting a lot of chunk loads
|
|
- protected static final IntervalledCounter TICKET_ADDITION_COUNTER_SHORT = new IntervalledCounter((long)(1.0e6 * 50.0)); // 50ms
|
|
+ protected static final IntervalledCounter TICKET_ADDITION_COUNTER_SHORT = new IntervalledCounter((long) (1.0e6 * 50.0)); // 50ms
|
|
// this interval ensures the rate is kept between ticks correctly
|
|
- protected static final IntervalledCounter TICKET_ADDITION_COUNTER_LONG = new IntervalledCounter((long)(1.0e6 * 1000.0)); // 1000ms
|
|
+ protected static final IntervalledCounter TICKET_ADDITION_COUNTER_LONG = new IntervalledCounter((long) (1.0e6 * 1000.0)); // 1000ms
|
|
+
|
|
private void tryLoadChunks() {
|
|
- final long id = this.loadLock.writeLock();
|
|
- try {
|
|
- if (this.chunkLoadQueue.isEmpty()) {
|
|
- return;
|
|
- }
|
|
- }finally {
|
|
- this.loadLock.unlockWrite(id);
|
|
+ if (this.chunkLoadQueue.isEmpty()) {
|
|
+ return;
|
|
}
|
|
|
|
final int maxLoads = this.getMaxChunkLoads();
|
|
final long time = System.nanoTime();
|
|
boolean updatedCounters = false;
|
|
- for (;;) {
|
|
- PlayerLoaderData data;
|
|
-
|
|
- final long id1 = this.loadLock.writeLock();
|
|
- try {
|
|
- data = this.chunkLoadQueue.pollFirst();
|
|
- }finally {
|
|
- this.loadLock.unlock(id1);
|
|
- }
|
|
+ for (; ; ) {
|
|
+ PlayerLoaderData data = this.chunkLoadQueue.pollFirst();
|
|
|
|
data.lastChunkLoad = time;
|
|
|
|
final ChunkPriorityHolder queuedLoad = data.loadQueue.peekFirst();
|
|
if (queuedLoad == null) {
|
|
- long id2 = this.loadLock.writeLock();
|
|
- try {
|
|
- if (this.chunkLoadQueue.isEmpty()) {
|
|
- break;
|
|
- }
|
|
- }finally {
|
|
- this.loadLock.unlockWrite(id2);
|
|
+ if (this.chunkLoadQueue.isEmpty()) {
|
|
+ break;
|
|
}
|
|
continue;
|
|
}
|
|
@@ -704,12 +647,7 @@ public final class PlayerChunkLoader {
|
|
if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) {
|
|
// already loaded!
|
|
data.loadQueue.pollFirst(); // already loaded so we just skip
|
|
- final long id3 = this.loadLock.writeLock();
|
|
- try {
|
|
- this.chunkLoadQueue.add(data);
|
|
- }finally {
|
|
- this.loadLock.unlockWrite(id3);
|
|
- }
|
|
+ this.chunkLoadQueue.add(data);
|
|
// ensure the chunk is queued to send
|
|
this.onChunkSendReady(queuedLoad.chunkX, queuedLoad.chunkZ);
|
|
continue;
|
|
@@ -739,14 +677,9 @@ 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
|
|
- final long id4 = this.loadLock.writeLock();
|
|
- try {
|
|
- this.chunkLoadQueue.add(data);
|
|
- }finally {
|
|
- this.loadLock.unlockWrite(id4);
|
|
- }
|
|
+ this.chunkLoadQueue.add(data);
|
|
break;
|
|
}
|
|
}
|
|
@@ -755,12 +688,7 @@ public final class PlayerChunkLoader {
|
|
data.loadQueue.pollFirst();
|
|
|
|
// now that we've polled we can re-add to load queue
|
|
- final long id4 = this.loadLock.writeLock();
|
|
- try {
|
|
- this.chunkLoadQueue.add(data);
|
|
- }finally {
|
|
- this.loadLock.unlockWrite(id4);
|
|
- }
|
|
+ this.chunkLoadQueue.add(data);
|
|
|
|
// add necessary tickets to load chunk up to send-ready
|
|
for (int dz = -1; dz <= 1; ++dz) {
|
|
@@ -840,7 +768,7 @@ public final class PlayerChunkLoader {
|
|
protected static final double PRIORITISED_DISTANCE = 12.0 * 16.0;
|
|
|
|
// Player max sprint speed is approximately 8m/s
|
|
- protected static final double LOOK_PRIORITY_SPEED_THRESHOLD = (10.0/20.0) * (10.0/20.0);
|
|
+ protected static final double LOOK_PRIORITY_SPEED_THRESHOLD = (10.0 / 20.0) * (10.0 / 20.0);
|
|
protected static final double LOOK_PRIORITY_YAW_DELTA_RECALC_THRESHOLD = 3.0f;
|
|
|
|
protected double lastLocX = Double.NEGATIVE_INFINITY;
|
|
@@ -887,9 +815,9 @@ public final class PlayerChunkLoader {
|
|
protected long nextChunkSendTarget;
|
|
|
|
// this interval prevents bursting a lot of chunk loads
|
|
- protected final IntervalledCounter ticketAdditionCounterShort = new IntervalledCounter((long)(1.0e6 * 50.0)); // 50ms
|
|
+ protected final IntervalledCounter ticketAdditionCounterShort = new IntervalledCounter((long) (1.0e6 * 50.0)); // 50ms
|
|
// this ensures the rate is kept between ticks correctly
|
|
- protected final IntervalledCounter ticketAdditionCounterLong = new IntervalledCounter((long)(1.0e6 * 1000.0)); // 1000ms
|
|
+ protected final IntervalledCounter ticketAdditionCounterLong = new IntervalledCounter((long) (1.0e6 * 1000.0)); // 1000ms
|
|
|
|
public long lastChunkLoad;
|
|
|
|
@@ -986,14 +914,14 @@ public final class PlayerChunkLoader {
|
|
// b = ((p3z - p1z)(targetX - p3x) + (p1x - p3x)(targetZ - p3z)) / d
|
|
// c = 1.0 - a - b
|
|
|
|
- final double d = (p2z - p3z)*(p1x - p3x) + (p3x - p2x)*(p1z - p3z);
|
|
- final double a = ((p2z - p3z)*(targetX - p3x) + (p3x - p2x)*(targetZ - p3z)) / d;
|
|
+ final double d = (p2z - p3z) * (p1x - p3x) + (p3x - p2x) * (p1z - p3z);
|
|
+ final double a = ((p2z - p3z) * (targetX - p3x) + (p3x - p2x) * (targetZ - p3z)) / d;
|
|
|
|
if (a < 0.0 || a > 1.0) {
|
|
return false;
|
|
}
|
|
|
|
- final double b = ((p3z - p1z)*(targetX - p3x) + (p1x - p3x)*(targetZ - p3z)) / d;
|
|
+ final double b = ((p3z - p1z) * (targetX - p3x) + (p1x - p3x) * (targetZ - p3z)) / d;
|
|
if (b < 0.0 || b > 1.0) {
|
|
return false;
|
|
}
|
|
@@ -1040,14 +968,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
|
|
@@ -1096,15 +1024,15 @@ public final class PlayerChunkLoader {
|
|
final double p1z = posZ;
|
|
|
|
// to the left of the looking direction
|
|
- final double p2x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw + (double)(FOV / 2.0))) // calculate rotated vector
|
|
+ final double p2x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw + (double) (FOV / 2.0))) // calculate rotated vector
|
|
+ p1x; // offset vector
|
|
- final double p2z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw + (double)(FOV / 2.0))) // calculate rotated vector
|
|
+ final double p2z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw + (double) (FOV / 2.0))) // calculate rotated vector
|
|
+ p1z; // offset vector
|
|
|
|
// to the right of the looking direction
|
|
- final double p3x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw - (double)(FOV / 2.0))) // calculate rotated vector
|
|
+ final double p3x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw - (double) (FOV / 2.0))) // calculate rotated vector
|
|
+ p1x; // offset vector
|
|
- final double p3z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw - (double)(FOV / 2.0))) // calculate rotated vector
|
|
+ final double p3z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw - (double) (FOV / 2.0))) // calculate rotated vector
|
|
+ p1z; // offset vector
|
|
|
|
// now that we have all of our points, we can recalculate the load queue
|
|
@@ -1142,7 +1070,7 @@ public final class PlayerChunkLoader {
|
|
p1x, p1z, p2x, p2z, p3x, p3z,
|
|
|
|
// center of chunk
|
|
- (double)((chunkX << 4) | 8), (double)((chunkZ << 4) | 8)
|
|
+ (double) ((chunkX << 4) | 8), (double) ((chunkZ << 4) | 8)
|
|
);
|
|
|
|
final int manhattanDistance = Math.abs(dx) + Math.abs(dz);
|
|
@@ -1157,9 +1085,9 @@ public final class PlayerChunkLoader {
|
|
if (prioritised) {
|
|
// we don't prioritise these chunks above others because we also want to make sure some chunks
|
|
// will be loaded if the player changes direction
|
|
- priority = (double)manhattanDistance / 6.0;
|
|
+ priority = (double) manhattanDistance / 6.0;
|
|
} else {
|
|
- priority = (double)manhattanDistance;
|
|
+ priority = (double) manhattanDistance;
|
|
}
|
|
}
|
|
|
|
@@ -1186,23 +1114,13 @@ public final class PlayerChunkLoader {
|
|
});
|
|
|
|
// we're modifying loadQueue, must remove
|
|
- final long id3 = this.loader.loadLock.writeLock();
|
|
- try {
|
|
- this.loader.chunkLoadQueue.remove(this);
|
|
- }finally {
|
|
- this.loader.loadLock.unlockWrite(id3);
|
|
- }
|
|
+ this.loader.chunkLoadQueue.remove(this);
|
|
|
|
this.loadQueue.clear();
|
|
this.loadQueue.addAll(loadQueue);
|
|
|
|
- final long id4 = this.loader.loadLock.writeLock();
|
|
- try {
|
|
- // must re-add
|
|
- this.loader.chunkLoadQueue.add(this);
|
|
- }finally {
|
|
- this.loader.loadLock.unlockWrite(id4);
|
|
- }
|
|
+ // must re-add
|
|
+ this.loader.chunkLoadQueue.add(this);
|
|
|
|
// update the chunk center
|
|
// this must be done last so that the client does not ignore any of our unload chunk packets
|
|
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 36d3b0d0e1e716cb0e5a2e98c37a834776e26c20..26e1b4060f2a93cb659170f83e6ce64086e0eb0c 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,14 @@ 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.Int2ReferenceMap;
|
|
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMaps;
|
|
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
|
|
+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.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;
|
|
@@ -49,17 +55,16 @@ public final class EntityLookup implements LevelEntityGetter<Entity> {
|
|
|
|
private final StampedLock entityByLock = new StampedLock();
|
|
private final StampedLock stateLock = new StampedLock();
|
|
+ //Hearse -- Just for multithreaded entity ticking
|
|
|
|
- private final ReadWriteLock paraEntityLock = new ReentrantReadWriteLock(false); //Hearse -- Just for multithreaded entity ticking
|
|
-
|
|
- protected final Long2ObjectOpenHashMap<ChunkSlicesRegion> regions = new Long2ObjectOpenHashMap<>(128, 0.5f);
|
|
+ protected final Long2ObjectMap<ChunkSlicesRegion> regions = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>(128, 0.5f));
|
|
|
|
private final int minSection; // inclusive
|
|
private final int maxSection; // inclusive
|
|
private final LevelCallback<Entity> worldCallback;
|
|
|
|
- private final Int2ReferenceOpenHashMap<Entity> entityById = new Int2ReferenceOpenHashMap<>();
|
|
- private final Object2ReferenceOpenHashMap<UUID, Entity> entityByUUID = new Object2ReferenceOpenHashMap<>();
|
|
+ private final Int2ReferenceMap<Entity> entityById = Int2ReferenceMaps.synchronize(new Int2ReferenceOpenHashMap<>());
|
|
+ private final Object2ReferenceMap<UUID, Entity> entityByUUID = Object2ReferenceMaps.synchronize(new Object2ReferenceOpenHashMap<>());
|
|
private final EntityList accessibleEntities = new EntityList();
|
|
|
|
public EntityLookup(final ServerLevel world, final LevelCallback<Entity> worldCallback) {
|
|
@@ -80,61 +85,51 @@ public final class EntityLookup implements LevelEntityGetter<Entity> {
|
|
@Nullable
|
|
@Override
|
|
public Entity get(final int id) {
|
|
- this.paraEntityLock.readLock().lock();//Hearse
|
|
- try {
|
|
- final long attempt = this.entityByLock.tryOptimisticRead();
|
|
- if (attempt != 0L) {
|
|
- try {
|
|
- final Entity ret = this.entityById.get(id);
|
|
+ final long attempt = this.entityByLock.tryOptimisticRead();
|
|
+ if (attempt != 0L) {
|
|
+ try {
|
|
+ final Entity ret = this.entityById.get(id);
|
|
|
|
- if (this.entityByLock.validate(attempt)) {
|
|
- return maskNonAccessible(ret);
|
|
- }
|
|
- } catch (final Error error) {
|
|
- throw error;
|
|
- } catch (final Throwable thr) {
|
|
- // ignore
|
|
+ if (this.entityByLock.validate(attempt)) {
|
|
+ return maskNonAccessible(ret);
|
|
}
|
|
+ } catch (final Error error) {
|
|
+ throw error;
|
|
+ } catch (final Throwable thr) {
|
|
+ // ignore
|
|
}
|
|
+ }
|
|
|
|
- this.entityByLock.readLock();
|
|
- try {
|
|
- return maskNonAccessible(this.entityById.get(id));
|
|
- } finally {
|
|
- this.entityByLock.tryUnlockRead();
|
|
- }
|
|
- }finally {
|
|
- this.paraEntityLock.readLock().unlock();//Hearse
|
|
+ this.entityByLock.readLock();
|
|
+ try {
|
|
+ return maskNonAccessible(this.entityById.get(id));
|
|
+ } finally {
|
|
+ this.entityByLock.tryUnlockRead();
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public Entity get(final UUID id) {
|
|
- this.paraEntityLock.readLock().lock();//Hearse
|
|
- try {
|
|
- final long attempt = this.entityByLock.tryOptimisticRead();
|
|
- if (attempt != 0L) {
|
|
- try {
|
|
- final Entity ret = this.entityByUUID.get(id);
|
|
- if (this.entityByLock.validate(attempt)) {
|
|
- return maskNonAccessible(ret);
|
|
- }
|
|
- } catch (final Error error) {
|
|
- throw error;
|
|
- } catch (final Throwable thr) {
|
|
- // ignore
|
|
+ final long attempt = this.entityByLock.tryOptimisticRead();
|
|
+ if (attempt != 0L) {
|
|
+ try {
|
|
+ final Entity ret = this.entityByUUID.get(id);
|
|
+ if (this.entityByLock.validate(attempt)) {
|
|
+ return maskNonAccessible(ret);
|
|
}
|
|
+ } catch (final Error error) {
|
|
+ throw error;
|
|
+ } catch (final Throwable thr) {
|
|
+ // ignore
|
|
}
|
|
+ }
|
|
|
|
- this.entityByLock.readLock();
|
|
- try {
|
|
- return maskNonAccessible(this.entityByUUID.get(id));
|
|
- } finally {
|
|
- this.entityByLock.tryUnlockRead();
|
|
- }
|
|
- }finally {
|
|
- this.paraEntityLock.readLock().unlock();//Hearse
|
|
+ this.entityByLock.readLock();
|
|
+ try {
|
|
+ return maskNonAccessible(this.entityByUUID.get(id));
|
|
+ } finally {
|
|
+ this.entityByLock.tryUnlockRead();
|
|
}
|
|
}
|
|
|
|
@@ -143,12 +138,7 @@ public final class EntityLookup implements LevelEntityGetter<Entity> {
|
|
}
|
|
|
|
public String getDebugInfo() {
|
|
- this.paraEntityLock.readLock();//Hearse
|
|
- try {
|
|
- return "count_id:" + this.entityById.size() + ",count_uuid:" + this.entityByUUID.size() + ",region_count:" + this.regions.size();
|
|
- }finally {
|
|
- this.paraEntityLock.readLock().unlock();//Hearse
|
|
- }
|
|
+ return "count_id:" + this.entityById.size() + ",count_uuid:" + this.entityByUUID.size() + ",region_count:" + this.regions.size();
|
|
}
|
|
|
|
static final class ArrayIterable<T> implements Iterable<T> {
|
|
@@ -206,30 +196,20 @@ public final class EntityLookup implements LevelEntityGetter<Entity> {
|
|
|
|
@Override
|
|
public Iterable<Entity> getAll() {
|
|
- this.paraEntityLock.readLock().lock();//Hearse
|
|
- try {
|
|
- return new ArrayIterable<>(this.accessibleEntities.getRawData(), 0, this.accessibleEntities.size());
|
|
- }finally {
|
|
- this.paraEntityLock.readLock().unlock();//Hearse
|
|
- }
|
|
+ return new ArrayIterable<>(this.accessibleEntities.getRawData(), 0, this.accessibleEntities.size());
|
|
}
|
|
|
|
@Override
|
|
public <U extends Entity> void get(final EntityTypeTest<Entity, U> filter, final AbortableIterationConsumer<U> action) {
|
|
- this.paraEntityLock.readLock().lock();//Hearse
|
|
- try {
|
|
- for (final Entity entity : this.entityById.values()) {
|
|
- final Visibility visibility = EntityLookup.getEntityStatus(entity);
|
|
- if (!visibility.isAccessible()) {
|
|
- continue;
|
|
- }
|
|
- final U casted = filter.tryCast(entity);
|
|
- if (casted != null && action.accept(casted).shouldAbort()) {
|
|
- break;
|
|
- }
|
|
+ for (final Entity entity : this.entityById.values()) {
|
|
+ final Visibility visibility = EntityLookup.getEntityStatus(entity);
|
|
+ if (!visibility.isAccessible()) {
|
|
+ continue;
|
|
+ }
|
|
+ final U casted = filter.tryCast(entity);
|
|
+ if (casted != null && action.accept(casted).shouldAbort()) {
|
|
+ break;
|
|
}
|
|
- }finally {
|
|
- this.paraEntityLock.readLock().unlock();//Hearse
|
|
}
|
|
}
|
|
|
|
@@ -257,55 +237,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");
|
|
- this.paraEntityLock.writeLock().lock();//Hearse
|
|
+ final Boolean ticketBlockBefore = this.world.chunkTaskScheduler.chunkHolderManager.blockTicketUpdates();
|
|
try {
|
|
- final Boolean ticketBlockBefore = this.world.chunkTaskScheduler.chunkHolderManager.blockTicketUpdates();
|
|
- 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 (!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 (newVisibility.ordinal() > oldVisibility.ordinal()) {
|
|
+ // status upgrade
|
|
+ if (!oldVisibility.isAccessible() && newVisibility.isAccessible()) {
|
|
+ this.accessibleEntities.add(entity);
|
|
+ EntityLookup.this.worldCallback.onTrackingStart(entity);
|
|
}
|
|
|
|
- if (moved && newVisibility.isAccessible()) {
|
|
- EntityLookup.this.worldCallback.onSectionChange(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 (destroyed) {
|
|
- EntityLookup.this.worldCallback.onDestroyed(entity);
|
|
+ if (oldVisibility.isAccessible() && !newVisibility.isAccessible()) {
|
|
+ this.accessibleEntities.remove(entity);
|
|
+ EntityLookup.this.worldCallback.onTrackingEnd(entity);
|
|
}
|
|
- } finally {
|
|
- this.world.chunkTaskScheduler.chunkHolderManager.unblockTicketUpdates(ticketBlockBefore);
|
|
+ }
|
|
+
|
|
+ if (moved && newVisibility.isAccessible()) {
|
|
+ EntityLookup.this.worldCallback.onSectionChange(entity);
|
|
+ }
|
|
+
|
|
+ if (destroyed) {
|
|
+ EntityLookup.this.worldCallback.onDestroyed(entity);
|
|
}
|
|
} finally {
|
|
- this.paraEntityLock.writeLock().lock();//Hearse
|
|
+ this.world.chunkTaskScheduler.chunkHolderManager.unblockTicketUpdates(ticketBlockBefore);
|
|
}
|
|
}
|
|
|
|
@@ -350,53 +325,48 @@ public final class EntityLookup implements LevelEntityGetter<Entity> {
|
|
final int sectionZ = pos.getZ() >> 4;
|
|
TickThread.ensureTickThread(this.world, sectionX, sectionZ, "Cannot add entity off-main thread");
|
|
|
|
- this.paraEntityLock.writeLock().lock();//Hearse
|
|
- try {
|
|
+ if (entity.isRemoved()) {
|
|
+ LOGGER.warn("Refusing to add removed entity: " + entity);
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (fromDisk) {
|
|
+ ChunkSystem.onEntityPreAdd(this.world, entity);
|
|
if (entity.isRemoved()) {
|
|
- LOGGER.warn("Refusing to add removed entity: " + entity);
|
|
+ // removed from checkDupeUUID call
|
|
return false;
|
|
}
|
|
+ }
|
|
|
|
- if (fromDisk) {
|
|
- ChunkSystem.onEntityPreAdd(this.world, entity);
|
|
- if (entity.isRemoved()) {
|
|
- // removed from checkDupeUUID call
|
|
- return false;
|
|
- }
|
|
+ this.entityByLock.writeLock();
|
|
+ try {
|
|
+ if (this.entityById.containsKey(entity.getId())) {
|
|
+ LOGGER.warn("Entity id already exists: " + entity.getId() + ", mapped to " + this.entityById.get(entity.getId()) + ", can't add " + entity);
|
|
+ return false;
|
|
}
|
|
-
|
|
- this.entityByLock.writeLock();
|
|
- try {
|
|
- if (this.entityById.containsKey(entity.getId())) {
|
|
- LOGGER.warn("Entity id already exists: " + entity.getId() + ", mapped to " + this.entityById.get(entity.getId()) + ", can't add " + entity);
|
|
- return false;
|
|
- }
|
|
- if (this.entityByUUID.containsKey(entity.getUUID())) {
|
|
- LOGGER.warn("Entity uuid already exists: " + entity.getUUID() + ", mapped to " + this.entityByUUID.get(entity.getUUID()) + ", can't add " + entity);
|
|
- return false;
|
|
- }
|
|
- this.entityById.put(entity.getId(), entity);
|
|
- this.entityByUUID.put(entity.getUUID(), entity);
|
|
- } finally {
|
|
- this.entityByLock.tryUnlockWrite();
|
|
+ if (this.entityByUUID.containsKey(entity.getUUID())) {
|
|
+ LOGGER.warn("Entity uuid already exists: " + entity.getUUID() + ", mapped to " + this.entityByUUID.get(entity.getUUID()) + ", can't add " + entity);
|
|
+ return false;
|
|
}
|
|
+ this.entityById.put(entity.getId(), entity);
|
|
+ this.entityByUUID.put(entity.getUUID(), entity);
|
|
+ } finally {
|
|
+ this.entityByLock.tryUnlockWrite();
|
|
+ }
|
|
|
|
- entity.sectionX = sectionX;
|
|
- entity.sectionY = sectionY;
|
|
- entity.sectionZ = sectionZ;
|
|
- final ChunkEntitySlices slices = this.getOrCreateChunk(sectionX, sectionZ);
|
|
- if (!slices.addEntity(entity, sectionY)) {
|
|
- LOGGER.warn("Entity " + entity + " added to world '" + this.world.getWorld().getName() + "', but was already contained in entity chunk (" + sectionX + "," + sectionZ + ")");
|
|
- }
|
|
+ entity.sectionX = sectionX;
|
|
+ entity.sectionY = sectionY;
|
|
+ entity.sectionZ = sectionZ;
|
|
+ final ChunkEntitySlices slices = this.getOrCreateChunk(sectionX, sectionZ);
|
|
+ if (!slices.addEntity(entity, sectionY)) {
|
|
+ LOGGER.warn("Entity " + entity + " added to world '" + this.world.getWorld().getName() + "', but was already contained in entity chunk (" + sectionX + "," + sectionZ + ")");
|
|
+ }
|
|
|
|
- entity.setLevelCallback(new EntityCallback(entity));
|
|
+ entity.setLevelCallback(new EntityCallback(entity));
|
|
|
|
- this.entityStatusChange(entity, slices, Visibility.HIDDEN, getEntityStatus(entity), false, !fromDisk, false);
|
|
+ this.entityStatusChange(entity, slices, Visibility.HIDDEN, getEntityStatus(entity), false, !fromDisk, false);
|
|
|
|
- return true;
|
|
- }finally {
|
|
- this.paraEntityLock.writeLock().unlock();//Hearse
|
|
- }
|
|
+ return true;
|
|
}
|
|
|
|
private void removeEntity(final Entity entity) {
|
|
@@ -407,32 +377,27 @@ public final class EntityLookup implements LevelEntityGetter<Entity> {
|
|
if (!entity.isRemoved()) {
|
|
throw new IllegalStateException("Only call Entity#setRemoved to remove an entity");
|
|
}
|
|
- this.paraEntityLock.writeLock().lock();//Hearse
|
|
- try {
|
|
- final ChunkEntitySlices slices = this.getChunk(sectionX, sectionZ);
|
|
- // all entities should be in a chunk
|
|
- if (slices == null) {
|
|
- LOGGER.warn("Cannot remove entity " + entity + " from null entity slices (" + sectionX + "," + sectionZ + ")");
|
|
- } else {
|
|
- if (!slices.removeEntity(entity, sectionY)) {
|
|
- LOGGER.warn("Failed to remove entity " + entity + " from entity slices (" + sectionX + "," + sectionZ + ")");
|
|
- }
|
|
+ final ChunkEntitySlices slices = this.getChunk(sectionX, sectionZ);
|
|
+ // all entities should be in a chunk
|
|
+ if (slices == null) {
|
|
+ LOGGER.warn("Cannot remove entity " + entity + " from null entity slices (" + sectionX + "," + sectionZ + ")");
|
|
+ } else {
|
|
+ if (!slices.removeEntity(entity, sectionY)) {
|
|
+ LOGGER.warn("Failed to remove entity " + entity + " from entity slices (" + sectionX + "," + sectionZ + ")");
|
|
}
|
|
- entity.sectionX = entity.sectionY = entity.sectionZ = Integer.MIN_VALUE;
|
|
+ }
|
|
+ entity.sectionX = entity.sectionY = entity.sectionZ = Integer.MIN_VALUE;
|
|
|
|
- this.entityByLock.writeLock();
|
|
- try {
|
|
- if (!this.entityById.remove(entity.getId(), entity)) {
|
|
- LOGGER.warn("Failed to remove entity " + entity + " by id, current entity mapped: " + this.entityById.get(entity.getId()));
|
|
- }
|
|
- if (!this.entityByUUID.remove(entity.getUUID(), entity)) {
|
|
- LOGGER.warn("Failed to remove entity " + entity + " by uuid, current entity mapped: " + this.entityByUUID.get(entity.getUUID()));
|
|
- }
|
|
- } finally {
|
|
- this.entityByLock.tryUnlockWrite();
|
|
+ this.entityByLock.writeLock();
|
|
+ try {
|
|
+ if (!this.entityById.remove(entity.getId(), entity)) {
|
|
+ LOGGER.warn("Failed to remove entity " + entity + " by id, current entity mapped: " + this.entityById.get(entity.getId()));
|
|
+ }
|
|
+ if (!this.entityByUUID.remove(entity.getUUID(), entity)) {
|
|
+ LOGGER.warn("Failed to remove entity " + entity + " by uuid, current entity mapped: " + this.entityByUUID.get(entity.getUUID()));
|
|
}
|
|
- }finally {
|
|
- this.paraEntityLock.writeLock().unlock();//Hearse
|
|
+ } finally {
|
|
+ this.entityByLock.tryUnlockWrite();
|
|
}
|
|
}
|
|
|
|
@@ -455,27 +420,22 @@ 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");
|
|
|
|
- this.paraEntityLock.writeLock().lock();//Hearse
|
|
- try {
|
|
- final ChunkEntitySlices old = this.getChunk(entity.sectionX, entity.sectionZ);
|
|
- final ChunkEntitySlices slices = this.getOrCreateChunk(newSectionX, newSectionZ);
|
|
+ final ChunkEntitySlices old = this.getChunk(entity.sectionX, entity.sectionZ);
|
|
+ final ChunkEntitySlices slices = this.getOrCreateChunk(newSectionX, newSectionZ);
|
|
|
|
- if (!old.removeEntity(entity, entity.sectionY)) {
|
|
- LOGGER.warn("Could not remove entity " + entity + " from its old chunk section (" + entity.sectionX + "," + entity.sectionY + "," + entity.sectionZ + ") since it was not contained in the section");
|
|
- }
|
|
+ if (!old.removeEntity(entity, entity.sectionY)) {
|
|
+ LOGGER.warn("Could not remove entity " + entity + " from its old chunk section (" + entity.sectionX + "," + entity.sectionY + "," + entity.sectionZ + ") since it was not contained in the section");
|
|
+ }
|
|
|
|
- if (!slices.addEntity(entity, newSectionY)) {
|
|
- LOGGER.warn("Could not add entity " + entity + " to its new chunk section (" + newSectionX + "," + newSectionY + "," + newSectionZ + ") as it is already contained in the section");
|
|
- }
|
|
+ if (!slices.addEntity(entity, newSectionY)) {
|
|
+ LOGGER.warn("Could not add entity " + entity + " to its new chunk section (" + newSectionX + "," + newSectionY + "," + newSectionZ + ") as it is already contained in the section");
|
|
+ }
|
|
|
|
- entity.sectionX = newSectionX;
|
|
- entity.sectionY = newSectionY;
|
|
- entity.sectionZ = newSectionZ;
|
|
+ entity.sectionX = newSectionX;
|
|
+ entity.sectionY = newSectionY;
|
|
+ entity.sectionZ = newSectionZ;
|
|
|
|
- return slices;
|
|
- }finally {
|
|
- this.paraEntityLock.writeLock().unlock();//Hearse
|
|
- }
|
|
+ return slices;
|
|
}
|
|
|
|
public void getEntitiesWithoutDragonParts(final Entity except, final AABB box, final List<Entity> into, final Predicate<? super Entity> predicate) {
|
|
@@ -489,36 +449,31 @@ public final class EntityLookup implements LevelEntityGetter<Entity> {
|
|
final int maxRegionX = maxChunkX >> REGION_SHIFT;
|
|
final int maxRegionZ = maxChunkZ >> REGION_SHIFT;
|
|
|
|
- this.paraEntityLock.readLock().lock();//Hearse
|
|
- try {
|
|
- for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) {
|
|
- final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0;
|
|
- final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK;
|
|
+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) {
|
|
+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0;
|
|
+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK;
|
|
|
|
- for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) {
|
|
- final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ);
|
|
+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) {
|
|
+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ);
|
|
|
|
- if (region == null) {
|
|
- continue;
|
|
- }
|
|
-
|
|
- final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0;
|
|
- final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK;
|
|
+ if (region == null) {
|
|
+ continue;
|
|
+ }
|
|
|
|
- for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
|
- for (int currX = minX; currX <= maxX; ++currX) {
|
|
- final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT));
|
|
- if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
|
|
- continue;
|
|
- }
|
|
+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0;
|
|
+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK;
|
|
|
|
- chunk.getEntitiesWithoutDragonParts(except, box, into, predicate);
|
|
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
|
+ for (int currX = minX; currX <= maxX; ++currX) {
|
|
+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT));
|
|
+ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
|
|
+ continue;
|
|
}
|
|
+
|
|
+ chunk.getEntitiesWithoutDragonParts(except, box, into, predicate);
|
|
}
|
|
}
|
|
}
|
|
- }finally {
|
|
- this.paraEntityLock.readLock().unlock();//Hearse
|
|
}
|
|
}
|
|
|
|
@@ -533,36 +488,31 @@ public final class EntityLookup implements LevelEntityGetter<Entity> {
|
|
final int maxRegionX = maxChunkX >> REGION_SHIFT;
|
|
final int maxRegionZ = maxChunkZ >> REGION_SHIFT;
|
|
|
|
- this.paraEntityLock.readLock().lock();//Hearse
|
|
- try {
|
|
- for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) {
|
|
- final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0;
|
|
- final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK;
|
|
-
|
|
- for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) {
|
|
- final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ);
|
|
+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) {
|
|
+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0;
|
|
+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK;
|
|
|
|
- if (region == null) {
|
|
- continue;
|
|
- }
|
|
+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) {
|
|
+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ);
|
|
|
|
- final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0;
|
|
- final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK;
|
|
+ if (region == null) {
|
|
+ continue;
|
|
+ }
|
|
|
|
- for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
|
- for (int currX = minX; currX <= maxX; ++currX) {
|
|
- final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT));
|
|
- if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
|
|
- continue;
|
|
- }
|
|
+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0;
|
|
+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK;
|
|
|
|
- chunk.getEntities(except, box, into, predicate);
|
|
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
|
+ for (int currX = minX; currX <= maxX; ++currX) {
|
|
+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT));
|
|
+ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
|
|
+ continue;
|
|
}
|
|
+
|
|
+ chunk.getEntities(except, box, into, predicate);
|
|
}
|
|
}
|
|
}
|
|
- }finally {
|
|
- this.paraEntityLock.readLock().unlock();//Hearse
|
|
}
|
|
}
|
|
|
|
@@ -577,36 +527,31 @@ public final class EntityLookup implements LevelEntityGetter<Entity> {
|
|
final int maxRegionX = maxChunkX >> REGION_SHIFT;
|
|
final int maxRegionZ = maxChunkZ >> REGION_SHIFT;
|
|
|
|
- this.paraEntityLock.readLock().lock();//Hearse
|
|
- try {
|
|
- for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) {
|
|
- final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0;
|
|
- final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK;
|
|
-
|
|
- for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) {
|
|
- final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ);
|
|
+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) {
|
|
+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0;
|
|
+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK;
|
|
|
|
- if (region == null) {
|
|
- continue;
|
|
- }
|
|
+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) {
|
|
+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ);
|
|
|
|
- final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0;
|
|
- final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK;
|
|
+ if (region == null) {
|
|
+ continue;
|
|
+ }
|
|
|
|
- for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
|
- for (int currX = minX; currX <= maxX; ++currX) {
|
|
- final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT));
|
|
- if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
|
|
- continue;
|
|
- }
|
|
+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0;
|
|
+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK;
|
|
|
|
- chunk.getHardCollidingEntities(except, box, into, predicate);
|
|
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
|
+ for (int currX = minX; currX <= maxX; ++currX) {
|
|
+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT));
|
|
+ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
|
|
+ continue;
|
|
}
|
|
+
|
|
+ chunk.getHardCollidingEntities(except, box, into, predicate);
|
|
}
|
|
}
|
|
}
|
|
- }finally {
|
|
- this.paraEntityLock.readLock().unlock();//Hearse
|
|
}
|
|
}
|
|
|
|
@@ -622,36 +567,31 @@ public final class EntityLookup implements LevelEntityGetter<Entity> {
|
|
final int maxRegionX = maxChunkX >> REGION_SHIFT;
|
|
final int maxRegionZ = maxChunkZ >> REGION_SHIFT;
|
|
|
|
- this.paraEntityLock.readLock().lock();//Hearse
|
|
- try {
|
|
- for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) {
|
|
- final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0;
|
|
- final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK;
|
|
-
|
|
- for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) {
|
|
- final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ);
|
|
+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) {
|
|
+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0;
|
|
+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK;
|
|
|
|
- if (region == null) {
|
|
- continue;
|
|
- }
|
|
+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) {
|
|
+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ);
|
|
|
|
- final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0;
|
|
- final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK;
|
|
+ if (region == null) {
|
|
+ continue;
|
|
+ }
|
|
|
|
- for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
|
- for (int currX = minX; currX <= maxX; ++currX) {
|
|
- final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT));
|
|
- if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
|
|
- continue;
|
|
- }
|
|
+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0;
|
|
+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK;
|
|
|
|
- chunk.getEntities(type, box, (List)into, (Predicate)predicate);
|
|
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
|
+ for (int currX = minX; currX <= maxX; ++currX) {
|
|
+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT));
|
|
+ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
|
|
+ continue;
|
|
}
|
|
+
|
|
+ chunk.getEntities(type, box, (List) into, (Predicate) predicate);
|
|
}
|
|
}
|
|
}
|
|
- }finally {
|
|
- this.paraEntityLock.readLock().unlock();//Hearse
|
|
}
|
|
}
|
|
|
|
@@ -667,36 +607,31 @@ public final class EntityLookup implements LevelEntityGetter<Entity> {
|
|
final int maxRegionX = maxChunkX >> REGION_SHIFT;
|
|
final int maxRegionZ = maxChunkZ >> REGION_SHIFT;
|
|
|
|
- this.paraEntityLock.readLock().unlock();//Hearse
|
|
- try {
|
|
- for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) {
|
|
- final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0;
|
|
- final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK;
|
|
-
|
|
- for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) {
|
|
- final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ);
|
|
+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) {
|
|
+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0;
|
|
+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK;
|
|
|
|
- if (region == null) {
|
|
- continue;
|
|
- }
|
|
+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) {
|
|
+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ);
|
|
|
|
- final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0;
|
|
- final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK;
|
|
+ if (region == null) {
|
|
+ continue;
|
|
+ }
|
|
|
|
- for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
|
- for (int currX = minX; currX <= maxX; ++currX) {
|
|
- final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT));
|
|
- if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
|
|
- continue;
|
|
- }
|
|
+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0;
|
|
+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK;
|
|
|
|
- chunk.getEntities(clazz, except, box, into, predicate);
|
|
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
|
+ for (int currX = minX; currX <= maxX; ++currX) {
|
|
+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT));
|
|
+ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
|
|
+ continue;
|
|
}
|
|
+
|
|
+ chunk.getEntities(clazz, except, box, into, predicate);
|
|
}
|
|
}
|
|
}
|
|
- }finally {
|
|
- this.paraEntityLock.readLock().unlock();//Hearse
|
|
}
|
|
}
|
|
|
|
@@ -875,9 +810,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/util/maplist/IteratorSafeOrderedReferenceSet.java b/src/main/java/io/papermc/paper/util/maplist/IteratorSafeOrderedReferenceSet.java
|
|
index 0fd814f1d65c111266a2b20f86561839a4cef755..fe4d76875462ac9d408c972b968647af78f2ed14 100644
|
|
--- a/src/main/java/io/papermc/paper/util/maplist/IteratorSafeOrderedReferenceSet.java
|
|
+++ b/src/main/java/io/papermc/paper/util/maplist/IteratorSafeOrderedReferenceSet.java
|
|
@@ -94,7 +94,7 @@ public final class IteratorSafeOrderedReferenceSet<E> {
|
|
return 1.0 - ((double)this.indexMap.size() / (double)this.listSize);
|
|
}
|
|
|
|
- public int createRawIterator() {
|
|
+ public synchronized int createRawIterator() {
|
|
if (this.allowSafeIteration()) {
|
|
++this.iteratorCount;
|
|
}
|
|
@@ -105,7 +105,7 @@ public final class IteratorSafeOrderedReferenceSet<E> {
|
|
}
|
|
}
|
|
|
|
- public int advanceRawIterator(final int index) {
|
|
+ public synchronized int advanceRawIterator(final int index) {
|
|
final E[] elements = this.listElements;
|
|
int ret = index + 1;
|
|
for (int len = this.listSize; ret < len; ++ret) {
|
|
@@ -117,7 +117,7 @@ public final class IteratorSafeOrderedReferenceSet<E> {
|
|
return -1;
|
|
}
|
|
|
|
- public void finishRawIterator() {
|
|
+ public synchronized void finishRawIterator() {
|
|
if (this.allowSafeIteration() && --this.iteratorCount == 0) {
|
|
if (this.getFragFactor() >= this.maxFragFactor) {
|
|
this.defrag();
|
|
@@ -125,7 +125,7 @@ public final class IteratorSafeOrderedReferenceSet<E> {
|
|
}
|
|
}
|
|
|
|
- public boolean remove(final E element) {
|
|
+ public synchronized boolean remove(final E element) {
|
|
final int index = this.indexMap.removeInt(element);
|
|
if (index >= 0) {
|
|
if (this.firstInvalidIndex < 0 || index < this.firstInvalidIndex) {
|
|
@@ -144,11 +144,11 @@ public final class IteratorSafeOrderedReferenceSet<E> {
|
|
return false;
|
|
}
|
|
|
|
- public boolean contains(final E element) {
|
|
+ public synchronized boolean contains(final E element) {
|
|
return this.indexMap.containsKey(element);
|
|
}
|
|
|
|
- public boolean add(final E element) {
|
|
+ public synchronized boolean add(final E element) {
|
|
final int listSize = this.listSize;
|
|
|
|
final int previous = this.indexMap.putIfAbsent(element, listSize);
|
|
@@ -223,30 +223,30 @@ public final class IteratorSafeOrderedReferenceSet<E> {
|
|
//this.check();
|
|
}
|
|
|
|
- public E rawGet(final int index) {
|
|
+ public synchronized E rawGet(final int index) {
|
|
return this.listElements[index];
|
|
}
|
|
|
|
- public int size() {
|
|
+ public synchronized int size() {
|
|
// always returns the correct amount - listSize can be different
|
|
return this.indexMap.size();
|
|
}
|
|
|
|
- public IteratorSafeOrderedReferenceSet.Iterator<E> iterator() {
|
|
+ public synchronized IteratorSafeOrderedReferenceSet.Iterator<E> iterator() {
|
|
return this.iterator(0);
|
|
}
|
|
|
|
- public IteratorSafeOrderedReferenceSet.Iterator<E> iterator(final int flags) {
|
|
+ public synchronized IteratorSafeOrderedReferenceSet.Iterator<E> iterator(final int flags) {
|
|
if (this.allowSafeIteration()) {
|
|
++this.iteratorCount;
|
|
}
|
|
return new BaseIterator<>(this, true, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize);
|
|
}
|
|
|
|
- public java.util.Iterator<E> unsafeIterator() {
|
|
+ public synchronized java.util.Iterator<E> unsafeIterator() {
|
|
return this.unsafeIterator(0);
|
|
}
|
|
- public java.util.Iterator<E> unsafeIterator(final int flags) {
|
|
+ public synchronized java.util.Iterator<E> unsafeIterator(final int flags) {
|
|
return new BaseIterator<>(this, false, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize);
|
|
}
|
|
|
|
@@ -273,7 +273,7 @@ public final class IteratorSafeOrderedReferenceSet<E> {
|
|
}
|
|
|
|
@Override
|
|
- public boolean hasNext() {
|
|
+ public synchronized boolean hasNext() {
|
|
if (this.finished) {
|
|
return false;
|
|
}
|
|
@@ -297,7 +297,7 @@ public final class IteratorSafeOrderedReferenceSet<E> {
|
|
}
|
|
|
|
@Override
|
|
- public E next() {
|
|
+ public synchronized E next() {
|
|
if (!this.hasNext()) {
|
|
throw new NoSuchElementException();
|
|
}
|
|
@@ -310,7 +310,7 @@ public final class IteratorSafeOrderedReferenceSet<E> {
|
|
}
|
|
|
|
@Override
|
|
- public void remove() {
|
|
+ public synchronized void remove() {
|
|
final E lastReturned = this.lastReturned;
|
|
if (lastReturned == null) {
|
|
throw new IllegalStateException();
|
|
@@ -320,7 +320,7 @@ public final class IteratorSafeOrderedReferenceSet<E> {
|
|
}
|
|
|
|
@Override
|
|
- public void finishedIterating() {
|
|
+ public synchronized void finishedIterating() {
|
|
if (this.finished || !this.canFinish) {
|
|
throw new IllegalStateException();
|
|
}
|
|
diff --git a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java
|
|
index f6c8dccb7f7cd287f1ebdf46481365b952baa891..ae22ca9ea5fd3d78d8c5bf9f1ab96f1129fddc11 100644
|
|
--- a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java
|
|
+++ b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java
|
|
@@ -4,6 +4,7 @@ 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;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.server.level.ChunkHolder;
|
|
@@ -35,7 +36,7 @@ public final class ChunkEntitySlices {
|
|
|
|
protected final EntityCollectionBySection allEntities;
|
|
protected final EntityCollectionBySection hardCollidingEntities;
|
|
- protected final Reference2ObjectOpenHashMap<Class<? extends Entity>, EntityCollectionBySection> entitiesByClass;
|
|
+ protected final Reference2ObjectMap<Class<? extends Entity>, EntityCollectionBySection> entitiesByClass;
|
|
protected final EntityList entities = new EntityList();
|
|
|
|
public ChunkHolder.FullChunkStatus status;
|
|
@@ -62,7 +63,7 @@ public final class ChunkEntitySlices {
|
|
|
|
this.allEntities = new EntityCollectionBySection(this);
|
|
this.hardCollidingEntities = new EntityCollectionBySection(this);
|
|
- this.entitiesByClass = new Reference2ObjectOpenHashMap<>();
|
|
+ this.entitiesByClass = Reference2ObjectMaps.synchronize(new Reference2ObjectOpenHashMap<>());
|
|
|
|
this.status = status;
|
|
}
|
|
@@ -199,7 +200,7 @@ public final class ChunkEntitySlices {
|
|
}
|
|
|
|
for (final Iterator<Reference2ObjectMap.Entry<Class<? extends Entity>, EntityCollectionBySection>> iterator =
|
|
- this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
|
|
+ this.entitiesByClass.reference2ObjectEntrySet().iterator(); iterator.hasNext();) {
|
|
final Reference2ObjectMap.Entry<Class<? extends Entity>, EntityCollectionBySection> entry = iterator.next();
|
|
|
|
if (entry.getKey().isInstance(entity)) {
|
|
@@ -224,7 +225,7 @@ public final class ChunkEntitySlices {
|
|
}
|
|
|
|
for (final Iterator<Reference2ObjectMap.Entry<Class<? extends Entity>, EntityCollectionBySection>> iterator =
|
|
- this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
|
|
+ this.entitiesByClass.reference2ObjectEntrySet().iterator(); iterator.hasNext();) {
|
|
final Reference2ObjectMap.Entry<Class<? extends Entity>, EntityCollectionBySection> entry = iterator.next();
|
|
|
|
if (entry.getKey().isInstance(entity)) {
|
|
@@ -302,11 +303,11 @@ public final class ChunkEntitySlices {
|
|
this.storage = (E[])(cap <= 0 ? EMPTY : new Entity[cap]);
|
|
}
|
|
|
|
- public boolean isEmpty() {
|
|
+ public synchronized boolean isEmpty() {
|
|
return this.size == 0;
|
|
}
|
|
|
|
- public int size() {
|
|
+ public synchronized int size() {
|
|
return this.size;
|
|
}
|
|
|
|
@@ -318,7 +319,7 @@ public final class ChunkEntitySlices {
|
|
}
|
|
}
|
|
|
|
- public void add(final E entity) {
|
|
+ public synchronized void add(final E entity) {
|
|
final int idx = this.size++;
|
|
if (idx >= this.storage.length) {
|
|
this.resize();
|
|
@@ -328,7 +329,7 @@ public final class ChunkEntitySlices {
|
|
}
|
|
}
|
|
|
|
- public int indexOf(final E entity) {
|
|
+ public synchronized 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) {
|
|
@@ -340,7 +341,7 @@ public final class ChunkEntitySlices {
|
|
return -1;
|
|
}
|
|
|
|
- public boolean remove(final E entity) {
|
|
+ public synchronized boolean remove(final E entity) {
|
|
final int idx = this.indexOf(entity);
|
|
if (idx == -1) {
|
|
return false;
|
|
@@ -357,7 +358,7 @@ public final class ChunkEntitySlices {
|
|
return true;
|
|
}
|
|
|
|
- public boolean has(final E entity) {
|
|
+ public synchronized boolean has(final E entity) {
|
|
return this.indexOf(entity) != -1;
|
|
}
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
index 10c7d776ca0d959541d3110c75ceb45a340278ac..a23379120ddc3653b58bdb08f9c837c60b6b83ca 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
@@ -1,10 +1,7 @@
|
|
package net.minecraft.server.level;
|
|
|
|
-import com.google.common.collect.ImmutableList;
|
|
+import com.google.common.collect.*;
|
|
import com.google.common.collect.ImmutableList.Builder;
|
|
-import com.google.common.collect.Iterables;
|
|
-import com.google.common.collect.Lists;
|
|
-import com.google.common.collect.Queues;
|
|
import com.google.gson.JsonElement;
|
|
import com.mojang.datafixers.DataFixer;
|
|
import com.mojang.datafixers.util.Either;
|
|
@@ -103,7 +100,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
public final StructureTemplateManager structureTemplateManager; // Paper - rewrite chunk system
|
|
private final String storageName;
|
|
private final PlayerMap playerMap;
|
|
- public final Int2ObjectMap<ChunkMap.TrackedEntity> entityMap;
|
|
+ public final Map<Integer,ChunkMap.TrackedEntity> entityMap;
|
|
private final Long2ByteMap chunkTypeCache;
|
|
private final Long2LongMap chunkSaveCooldowns;
|
|
private final Queue<Runnable> unloadQueue;
|
|
@@ -251,7 +248,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
// Paper - rewrite chunk system
|
|
this.tickingGenerated = new AtomicInteger();
|
|
this.playerMap = new PlayerMap();
|
|
- this.entityMap = Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap());
|
|
+ this.entityMap = Maps.newConcurrentMap();
|
|
this.chunkTypeCache = Long2ByteMaps.synchronize(new Long2ByteOpenHashMap());
|
|
this.chunkSaveCooldowns = Long2LongMaps.synchronize(new Long2LongOpenHashMap());
|
|
this.unloadQueue = Queues.newConcurrentLinkedQueue();
|
|
@@ -1165,11 +1162,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
ServerPlayer entityplayer = (ServerPlayer) entity;
|
|
|
|
this.updatePlayerStatus(entityplayer, true);
|
|
- ObjectIterator objectiterator = this.entityMap.values().iterator();
|
|
-
|
|
- while (objectiterator.hasNext()) {
|
|
- ChunkMap.TrackedEntity playerchunkmap_entitytracker1 = (ChunkMap.TrackedEntity) objectiterator.next();
|
|
|
|
+ for (TrackedEntity playerchunkmap_entitytracker1 : this.entityMap.values()) {
|
|
if (playerchunkmap_entitytracker1.entity != entityplayer) {
|
|
playerchunkmap_entitytracker1.updatePlayer(entityplayer);
|
|
}
|
|
@@ -1187,11 +1181,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
ServerPlayer entityplayer = (ServerPlayer) entity;
|
|
|
|
this.updatePlayerStatus(entityplayer, false);
|
|
- ObjectIterator objectiterator = this.entityMap.values().iterator();
|
|
-
|
|
- while (objectiterator.hasNext()) {
|
|
- ChunkMap.TrackedEntity playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next();
|
|
|
|
+ for (TrackedEntity playerchunkmap_entitytracker : this.entityMap.values()) {
|
|
playerchunkmap_entitytracker.removePlayer(entityplayer);
|
|
}
|
|
}
|
|
@@ -1237,7 +1228,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
// Paper end - optimized tracker
|
|
List<ServerPlayer> list = Lists.newArrayList();
|
|
List<ServerPlayer> list1 = this.level.players();
|
|
- ObjectIterator objectiterator = this.entityMap.values().iterator();
|
|
+ Iterator objectiterator = this.entityMap.values().iterator();
|
|
//level.timings.tracker1.startTiming(); // Paper // Purpur
|
|
|
|
ChunkMap.TrackedEntity playerchunkmap_entitytracker;
|
|
diff --git a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
|
|
index 0c70810edace99bb5037d927388e055a514fcbde..0eadacb873658a0c7bd9ab24f191bc75eaebcaca 100644
|
|
--- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
|
|
+++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
|
|
@@ -3,9 +3,12 @@ package net.minecraft.util.thread;
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.Queues;
|
|
import com.mojang.logging.LogUtils;
|
|
+
|
|
+import java.util.Deque;
|
|
import java.util.List;
|
|
import java.util.Queue;
|
|
import java.util.concurrent.CompletableFuture;
|
|
+import java.util.concurrent.ConcurrentLinkedDeque;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.concurrent.locks.LockSupport;
|
|
import java.util.concurrent.locks.StampedLock;
|
|
@@ -20,7 +23,7 @@ import org.slf4j.Logger;
|
|
public abstract class BlockableEventLoop<R extends Runnable> implements ProfilerMeasured, ProcessorHandle<R>, Executor {
|
|
private final String name;
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
- private final Queue<R> pendingRunnables = Queues.newConcurrentLinkedQueue();
|
|
+ private final Deque<R> pendingRunnables = new ConcurrentLinkedDeque<>();
|
|
private final StampedLock lock = new StampedLock();
|
|
private int blockingCount;
|
|
|
|
@@ -44,12 +47,7 @@ public abstract class BlockableEventLoop<R extends Runnable> implements Profiler
|
|
}
|
|
|
|
public int getPendingTasksCount() {
|
|
- final long id = this.lock.readLock();
|
|
- try{
|
|
- return this.pendingRunnables.size();
|
|
- }finally {
|
|
- this.lock.unlockRead(id);
|
|
- }
|
|
+ return this.pendingRunnables.size();
|
|
}
|
|
|
|
@Override
|
|
@@ -95,12 +93,7 @@ public abstract class BlockableEventLoop<R extends Runnable> implements Profiler
|
|
|
|
@Override
|
|
public void tell(R runnable) {
|
|
- final long id = this.lock.writeLock();
|
|
- try {
|
|
- this.pendingRunnables.add(runnable);
|
|
- }finally {
|
|
- this.lock.unlockWrite(id);
|
|
- }
|
|
+ this.pendingRunnables.add(runnable);
|
|
LockSupport.unpark(this.getRunningThread());
|
|
}
|
|
|
|
@@ -119,12 +112,7 @@ public abstract class BlockableEventLoop<R extends Runnable> implements Profiler
|
|
}
|
|
|
|
protected void dropAllTasks() {
|
|
- final long id = this.lock.writeLock();
|
|
- try {
|
|
- this.pendingRunnables.clear();
|
|
- }finally {
|
|
- this.lock.unlockWrite(id);
|
|
- }
|
|
+ this.pendingRunnables.clear();
|
|
}
|
|
|
|
protected void runAllTasks() {
|
|
@@ -134,19 +122,15 @@ public abstract class BlockableEventLoop<R extends Runnable> implements Profiler
|
|
}
|
|
|
|
public boolean pollTask() {
|
|
- final long id = this.lock.writeLock();
|
|
- try {
|
|
- R runnable = this.pendingRunnables.peek();
|
|
- if (runnable == null) {
|
|
- return false;
|
|
- } else if (this.blockingCount == 0 && !this.shouldRun(runnable)) {
|
|
- return false;
|
|
- } else {
|
|
- this.doRunTask(this.pendingRunnables.remove());
|
|
- return true;
|
|
- }
|
|
- }finally {
|
|
- this.lock.unlockWrite(id);
|
|
+ R runnable = this.pendingRunnables.poll();
|
|
+ if (runnable == null) {
|
|
+ return false;
|
|
+ } else if (this.blockingCount == 0 && !this.shouldRun(runnable)) {
|
|
+ this.pendingRunnables.addFirst(runnable);
|
|
+ return false;
|
|
+ } else {
|
|
+ this.doRunTask(runnable);
|
|
+ return true;
|
|
}
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
|
|
index 00f7b58db5948f4d7c7f07736d8fcf1972009c77..dca7c7f83043452b5fef3c1d24a99f08dfaf242a 100644
|
|
--- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
|
|
+++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
|
|
@@ -1,14 +1,15 @@
|
|
package net.minecraft.world.level.entity;
|
|
|
|
-import java.util.Iterator;
|
|
-import java.util.Set;
|
|
+import java.util.*;
|
|
import java.util.function.Consumer;
|
|
+
|
|
+import com.google.common.collect.Lists;
|
|
import it.unimi.dsi.fastutil.objects.ObjectArraySet;
|
|
import it.unimi.dsi.fastutil.objects.ObjectSets;
|
|
import net.minecraft.world.entity.Entity;
|
|
|
|
public class EntityTickList {
|
|
- public final Set<Entity> entities = ObjectSets.synchronize(new ObjectArraySet<>());
|
|
+ public final List<Entity> entities = Lists.newCopyOnWriteArrayList();
|
|
|
|
public void add(Entity entity) {
|
|
io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist addition"); // Paper
|
|
diff --git a/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java b/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java
|
|
index a097e00bd62f53630568f68854d3a34300012277..799cd9d04d156ed87e9b1dfde75ae15280c9eb0d 100644
|
|
--- a/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java
|
|
+++ b/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java
|
|
@@ -22,8 +22,6 @@ public class CollectingNeighborUpdater implements NeighborUpdater {
|
|
private final List<CollectingNeighborUpdater.NeighborUpdates> addedThisLayer = new CopyOnWriteArrayList<>();
|
|
private int count = 0;
|
|
|
|
- private final StampedLock lock = new StampedLock();
|
|
-
|
|
public CollectingNeighborUpdater(Level world, int maxChainDepth) {
|
|
this.level = world;
|
|
this.maxChainedNeighborUpdates = maxChainDepth;
|
|
@@ -49,23 +47,18 @@ public class CollectingNeighborUpdater implements NeighborUpdater {
|
|
this.addAndRun(pos, new CollectingNeighborUpdater.MultiNeighborUpdate(pos.immutable(), sourceBlock, except));
|
|
}
|
|
|
|
- private void addAndRun(BlockPos pos, CollectingNeighborUpdater.NeighborUpdates entry) {
|
|
+ private synchronized void addAndRun(BlockPos pos, CollectingNeighborUpdater.NeighborUpdates entry) {
|
|
boolean bl = this.count > 0;
|
|
boolean bl2 = this.maxChainedNeighborUpdates >= 0 && this.count >= this.maxChainedNeighborUpdates;
|
|
++this.count;
|
|
- final long lockId = this.lock.writeLock();
|
|
- try {
|
|
- if (!bl2) {
|
|
- if (bl) {
|
|
- this.addedThisLayer.add(entry);
|
|
- } else {
|
|
- this.stack.push(entry);
|
|
- }
|
|
- } else if (this.count - 1 == this.maxChainedNeighborUpdates) {
|
|
- LOGGER.error("Too many chained neighbor updates. Skipping the rest. First skipped position: " + pos.toShortString());
|
|
+ if (!bl2) {
|
|
+ if (bl) {
|
|
+ this.addedThisLayer.add(entry);
|
|
+ } else {
|
|
+ this.stack.push(entry);
|
|
}
|
|
- }finally {
|
|
- this.lock.unlockWrite(lockId);
|
|
+ } else if (this.count - 1 == this.maxChainedNeighborUpdates) {
|
|
+ LOGGER.error("Too many chained neighbor updates. Skipping the rest. First skipped position: " + pos.toShortString());
|
|
}
|
|
|
|
if (!bl) {
|
|
@@ -75,30 +68,25 @@ public class CollectingNeighborUpdater implements NeighborUpdater {
|
|
}
|
|
|
|
private void runUpdates() {
|
|
- final long lockid = this.lock.writeLock();
|
|
try {
|
|
- try {
|
|
- while(!this.stack.isEmpty() || !this.addedThisLayer.isEmpty()) {
|
|
- for(int i = this.addedThisLayer.size() - 1; i >= 0; --i) {
|
|
- this.stack.push(this.addedThisLayer.get(i));
|
|
- }
|
|
- this.addedThisLayer.clear();
|
|
- CollectingNeighborUpdater.NeighborUpdates neighborUpdates = this.stack.peek();
|
|
-
|
|
- while(this.addedThisLayer.isEmpty()) {
|
|
- if (!neighborUpdates.runNext(this.level)) {
|
|
- this.stack.pop();
|
|
- break;
|
|
- }
|
|
- }
|
|
+ while (!this.stack.isEmpty() || !this.addedThisLayer.isEmpty()) {
|
|
+ for (int i = this.addedThisLayer.size() - 1; i >= 0; --i) {
|
|
+ this.stack.push(this.addedThisLayer.get(i));
|
|
}
|
|
- } finally {
|
|
- this.stack.clear();
|
|
this.addedThisLayer.clear();
|
|
- this.count = 0;
|
|
+ CollectingNeighborUpdater.NeighborUpdates neighborUpdates = this.stack.peek();
|
|
+
|
|
+ while (this.addedThisLayer.isEmpty()) {
|
|
+ if (!neighborUpdates.runNext(this.level)) {
|
|
+ this.stack.pop();
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
}
|
|
- }finally {
|
|
- this.lock.unlockWrite(lockid);
|
|
+ } finally {
|
|
+ this.stack.clear();
|
|
+ this.addedThisLayer.clear();
|
|
+ this.count = 0;
|
|
}
|
|
}
|
|
|