This repository has been archived by the owner on May 1, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
thank you kev626 from pufferfish core for outlining the tracker fix!!
- Loading branch information
Showing
5 changed files
with
84 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,19 +4,23 @@ Date: Sat, 2 Jul 2022 00:35:56 -0500 | |
Subject: [PATCH] feat: multithreaded tracker | ||
|
||
Co-authored-by: Paul Sauve <[email protected]> | ||
Co-authored-by: Kevin Raneri <[email protected]> | ||
|
||
based off the airplane multithreaded tracker this patch properly handles | ||
concurrent accesses everywhere, as well as being much simpler to maintain | ||
|
||
some things are too unsafe to run off the main thread so we don't attempt to do | ||
that. this multithreaded tracker remains accurate, non-breaking and fast | ||
|
||
we also learned from pufferfish core that changes have to be sent ordered | ||
now we do that in an optimized way | ||
|
||
diff --git a/src/main/java/host/bloom/tracker/MultithreadedTracker.java b/src/main/java/host/bloom/tracker/MultithreadedTracker.java | ||
new file mode 100644 | ||
index 0000000000000000000000000000000000000000..31a9a1dbb68cdbc5dc8a83542ddc7e0467ee8fd3 | ||
index 0000000000000000000000000000000000000000..d27b7224ed2bcc63386dc46c33bfb8b272d91f92 | ||
--- /dev/null | ||
+++ b/src/main/java/host/bloom/tracker/MultithreadedTracker.java | ||
@@ -0,0 +1,128 @@ | ||
@@ -0,0 +1,154 @@ | ||
+package host.bloom.tracker; | ||
+ | ||
+import com.google.common.util.concurrent.ThreadFactoryBuilder; | ||
|
@@ -34,6 +38,11 @@ index 0000000000000000000000000000000000000000..31a9a1dbb68cdbc5dc8a83542ddc7e04 | |
+ | ||
+public class MultithreadedTracker { | ||
+ | ||
+ private enum TrackerStage { | ||
+ UPDATE_PLAYERS, | ||
+ SEND_CHANGES | ||
+ } | ||
+ | ||
+ private static final int parallelism = Math.max(4, Runtime.getRuntime().availableProcessors()); | ||
+ private static final Executor trackerExecutor = Executors.newFixedThreadPool(parallelism, new ThreadFactoryBuilder() | ||
+ .setNameFormat("petal-tracker-%d") | ||
|
@@ -58,17 +67,18 @@ index 0000000000000000000000000000000000000000..31a9a1dbb68cdbc5dc8a83542ddc7e04 | |
+ return; | ||
+ } | ||
+ | ||
+ // start with updating players | ||
+ try { | ||
+ this.taskIndex.set(iterator); | ||
+ this.finishedTasks.set(0); | ||
+ | ||
+ for (int i = 0; i < parallelism; i++) { | ||
+ trackerExecutor.execute(this::run); | ||
+ trackerExecutor.execute(this::runUpdatePlayers); | ||
+ } | ||
+ | ||
+ while (this.taskIndex.get() < this.entityTickingChunks.getListSize()) { | ||
+ this.runMainThreadTasks(); | ||
+ this.handleTasks(5); // assist | ||
+ this.handleChunkUpdates(5); // assist | ||
+ } | ||
+ | ||
+ while (this.finishedTasks.get() != parallelism) { | ||
|
@@ -79,6 +89,25 @@ index 0000000000000000000000000000000000000000..31a9a1dbb68cdbc5dc8a83542ddc7e04 | |
+ } finally { | ||
+ this.entityTickingChunks.finishRawIterator(); | ||
+ } | ||
+ | ||
+ // then send changes | ||
+ iterator = this.entityTickingChunks.createRawIterator(); | ||
+ | ||
+ if (iterator == -1) { | ||
+ return; | ||
+ } | ||
+ | ||
+ try { | ||
+ do { | ||
+ LevelChunk chunk = this.entityTickingChunks.rawGet(iterator); | ||
+ | ||
+ if (chunk != null) { | ||
+ this.updateChunkEntities(chunk, TrackerStage.SEND_CHANGES); | ||
+ } | ||
+ } while (++iterator < this.entityTickingChunks.getListSize()); | ||
+ } finally { | ||
+ this.entityTickingChunks.finishRawIterator(); | ||
+ } | ||
+ } | ||
+ | ||
+ private void runMainThreadTasks() { | ||
|
@@ -92,22 +121,22 @@ index 0000000000000000000000000000000000000000..31a9a1dbb68cdbc5dc8a83542ddc7e04 | |
+ } | ||
+ } | ||
+ | ||
+ private void run() { | ||
+ private void runUpdatePlayers() { | ||
+ try { | ||
+ while (handleTasks(10)); | ||
+ while (handleChunkUpdates(10)); | ||
+ } finally { | ||
+ this.finishedTasks.incrementAndGet(); | ||
+ } | ||
+ } | ||
+ | ||
+ private boolean handleTasks(int tasks) { | ||
+ private boolean handleChunkUpdates(int tasks) { | ||
+ int index; | ||
+ while ((index = this.taskIndex.getAndAdd(tasks)) < this.entityTickingChunks.getListSize()) { | ||
+ for (int i = index; i < index + tasks && i < this.entityTickingChunks.getListSize(); i++) { | ||
+ LevelChunk chunk = this.entityTickingChunks.rawGet(i); | ||
+ if (chunk != null) { | ||
+ try { | ||
+ this.processChunk(chunk); | ||
+ this.updateChunkEntities(chunk, TrackerStage.UPDATE_PLAYERS); | ||
+ } catch (Throwable throwable) { | ||
+ MinecraftServer.LOGGER.warn("Ticking tracker failed", throwable); | ||
+ } | ||
|
@@ -121,8 +150,8 @@ index 0000000000000000000000000000000000000000..31a9a1dbb68cdbc5dc8a83542ddc7e04 | |
+ return false; | ||
+ } | ||
+ | ||
+ private void processChunk(LevelChunk chunk) { | ||
+ final ChunkEntitySlices entitySlices = chunk.level.entityManager.entitySliceManager.getChunk(chunk.locX, chunk.locZ); | ||
+ private void updateChunkEntities(LevelChunk chunk, TrackerStage trackerStage) { | ||
+ final ChunkEntitySlices entitySlices = chunk.level.getEntityLookup().getChunk(chunk.locX, chunk.locZ); | ||
+ if (entitySlices == null) { | ||
+ return; | ||
+ } | ||
|
@@ -135,10 +164,11 @@ index 0000000000000000000000000000000000000000..31a9a1dbb68cdbc5dc8a83542ddc7e04 | |
+ if (entity != null) { | ||
+ ChunkMap.TrackedEntity entityTracker = chunkMap.entityMap.get(entity.getId()); | ||
+ if (entityTracker != null) { | ||
+ entityTracker.updatePlayers(entityTracker.entity.getPlayersInTrackRange()); | ||
+ | ||
+ // run this on the main thread but queue it up here so we can run it while processing tracking at the same time | ||
+ this.mainThreadTasks.offer(entityTracker.serverEntity::sendChanges); | ||
+ if (trackerStage == TrackerStage.SEND_CHANGES) { | ||
+ entityTracker.serverEntity.sendChanges(); | ||
+ } else if (trackerStage == TrackerStage.UPDATE_PLAYERS) { | ||
+ entityTracker.updatePlayers(entityTracker.entity.getPlayersInTrackRange()); | ||
+ } | ||
+ } | ||
+ } | ||
+ } | ||
|
@@ -159,10 +189,10 @@ index 0fd814f1d65c111266a2b20f86561839a4cef755..169ac3ad1b1e8e3e1874ada2471e4782 | |
protected final double maxFragFactor; | ||
|
||
diff --git a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java | ||
index 47b5f75d9f27cf3ab947fd1f69cbd609fb9f2749..85882eeb86d7b74db0219aa65783946d8083885d 100644 | ||
index f597d65d56964297eeeed6c7e77703764178fee0..665c377e2d0d342f4dcc89c4cbdfcc9e4b96e95c 100644 | ||
--- a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java | ||
+++ b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java | ||
@@ -27,7 +27,7 @@ public final class ChunkEntitySlices { | ||
@@ -35,7 +35,7 @@ public final class ChunkEntitySlices { | ||
protected final EntityCollectionBySection allEntities; | ||
protected final EntityCollectionBySection hardCollidingEntities; | ||
protected final Reference2ObjectOpenHashMap<Class<? extends Entity>, EntityCollectionBySection> entitiesByClass; | ||
|
@@ -172,10 +202,10 @@ index 47b5f75d9f27cf3ab947fd1f69cbd609fb9f2749..85882eeb86d7b74db0219aa65783946d | |
public ChunkHolder.FullChunkStatus status; | ||
|
||
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java | ||
index 4fa383ff6ef3a9cc59b73ea4f52ae02e90140d2a..5293b7afb82b8250e07bc1d99cd837570e68c731 100644 | ||
index 3203b953709ca7cb9172f5912a922131ad7ec9eb..b39a01b903eafa15a2fc758bc74fa9b0f9b5fd5b 100644 | ||
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java | ||
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java | ||
@@ -2077,8 +2077,27 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider | ||
@@ -1237,8 +1237,27 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider | ||
entity.tracker = null; // Paper - We're no longer tracked | ||
} | ||
|
||
|
@@ -203,7 +233,7 @@ index 4fa383ff6ef3a9cc59b73ea4f52ae02e90140d2a..5293b7afb82b8250e07bc1d99cd83757 | |
//this.level.timings.tracker1.startTiming(); // Purpur | ||
try { | ||
for (TrackedEntity tracker : this.entityMap.values()) { | ||
@@ -2349,11 +2368,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider | ||
@@ -1462,11 +1481,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider | ||
|
||
public class TrackedEntity { | ||
|
||
|
@@ -218,7 +248,7 @@ index 4fa383ff6ef3a9cc59b73ea4f52ae02e90140d2a..5293b7afb82b8250e07bc1d99cd83757 | |
|
||
public TrackedEntity(Entity entity, int i, int j, boolean flag) { | ||
this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit | ||
@@ -2365,7 +2384,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider | ||
@@ -1478,7 +1497,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider | ||
// Paper start - use distance map to optimise tracker | ||
com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> lastTrackerCandidates; | ||
|
||
|
@@ -227,7 +257,7 @@ index 4fa383ff6ef3a9cc59b73ea4f52ae02e90140d2a..5293b7afb82b8250e07bc1d99cd83757 | |
com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> oldTrackerCandidates = this.lastTrackerCandidates; | ||
this.lastTrackerCandidates = newTrackerCandidates; | ||
|
||
@@ -2437,7 +2456,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider | ||
@@ -1550,7 +1569,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider | ||
} | ||
|
||
public void removePlayer(ServerPlayer player) { | ||
|
@@ -236,7 +266,7 @@ index 4fa383ff6ef3a9cc59b73ea4f52ae02e90140d2a..5293b7afb82b8250e07bc1d99cd83757 | |
if (this.seenBy.remove(player.connection)) { | ||
this.serverEntity.removePairing(player); | ||
} | ||
@@ -2445,7 +2464,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider | ||
@@ -1558,7 +1577,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider | ||
} | ||
|
||
public void updatePlayer(ServerPlayer player) { | ||
|
Oops, something went wrong.