mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-25 09:59:15 +00:00
Petal: Add multithreaded tracker
This commit is contained in:
360
patches/server/0070-Petal-multithreaded-tracker.patch
Normal file
360
patches/server/0070-Petal-multithreaded-tracker.patch
Normal file
@@ -0,0 +1,360 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: peaches94 <peachescu94@gmail.com>
|
||||
Date: Sat, 2 Jul 2022 00:35:56 -0500
|
||||
Subject: [PATCH] Petal: multithreaded tracker
|
||||
|
||||
Original license: GPL v3
|
||||
Original project: https://github.com/Bloom-host/Petal
|
||||
|
||||
Co-authored-by: Paul Sauve <paul@technove.co>
|
||||
Co-authored-by: Kevin Raneri <kevin.raneri@gmail.com>
|
||||
|
||||
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..d27b7224ed2bcc63386dc46c33bfb8b272d91f92
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/host/bloom/tracker/MultithreadedTracker.java
|
||||
@@ -0,0 +1,154 @@
|
||||
+package host.bloom.tracker;
|
||||
+
|
||||
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
+import io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet;
|
||||
+import io.papermc.paper.world.ChunkEntitySlices;
|
||||
+import net.minecraft.server.MinecraftServer;
|
||||
+import net.minecraft.server.level.ChunkMap;
|
||||
+import net.minecraft.world.entity.Entity;
|
||||
+import net.minecraft.world.level.chunk.LevelChunk;
|
||||
+
|
||||
+import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
+import java.util.concurrent.Executor;
|
||||
+import java.util.concurrent.Executors;
|
||||
+import java.util.concurrent.atomic.AtomicInteger;
|
||||
+
|
||||
+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")
|
||||
+ .setPriority(Thread.NORM_PRIORITY - 2)
|
||||
+ .build());
|
||||
+
|
||||
+ private final IteratorSafeOrderedReferenceSet<LevelChunk> entityTickingChunks;
|
||||
+ private final AtomicInteger taskIndex = new AtomicInteger();
|
||||
+
|
||||
+ private final ConcurrentLinkedQueue<Runnable> mainThreadTasks;
|
||||
+ private final AtomicInteger finishedTasks = new AtomicInteger();
|
||||
+
|
||||
+ public MultithreadedTracker(IteratorSafeOrderedReferenceSet<LevelChunk> entityTickingChunks, ConcurrentLinkedQueue<Runnable> mainThreadTasks) {
|
||||
+ this.entityTickingChunks = entityTickingChunks;
|
||||
+ this.mainThreadTasks = mainThreadTasks;
|
||||
+ }
|
||||
+
|
||||
+ public void tick() {
|
||||
+ int iterator = this.entityTickingChunks.createRawIterator();
|
||||
+
|
||||
+ if (iterator == -1) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ // start with updating players
|
||||
+ try {
|
||||
+ this.taskIndex.set(iterator);
|
||||
+ this.finishedTasks.set(0);
|
||||
+
|
||||
+ for (int i = 0; i < parallelism; i++) {
|
||||
+ trackerExecutor.execute(this::runUpdatePlayers);
|
||||
+ }
|
||||
+
|
||||
+ while (this.taskIndex.get() < this.entityTickingChunks.getListSize()) {
|
||||
+ this.runMainThreadTasks();
|
||||
+ this.handleChunkUpdates(5); // assist
|
||||
+ }
|
||||
+
|
||||
+ while (this.finishedTasks.get() != parallelism) {
|
||||
+ this.runMainThreadTasks();
|
||||
+ }
|
||||
+
|
||||
+ this.runMainThreadTasks(); // finish any remaining tasks
|
||||
+ } 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() {
|
||||
+ try {
|
||||
+ Runnable task;
|
||||
+ while ((task = this.mainThreadTasks.poll()) != null) {
|
||||
+ task.run();
|
||||
+ }
|
||||
+ } catch (Throwable throwable) {
|
||||
+ MinecraftServer.LOGGER.warn("Tasks failed while ticking track queue", throwable);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private void runUpdatePlayers() {
|
||||
+ try {
|
||||
+ while (handleChunkUpdates(10));
|
||||
+ } finally {
|
||||
+ this.finishedTasks.incrementAndGet();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ 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.updateChunkEntities(chunk, TrackerStage.UPDATE_PLAYERS);
|
||||
+ } catch (Throwable throwable) {
|
||||
+ MinecraftServer.LOGGER.warn("Ticking tracker failed", throwable);
|
||||
+ }
|
||||
+
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ private void updateChunkEntities(LevelChunk chunk, TrackerStage trackerStage) {
|
||||
+ final ChunkEntitySlices entitySlices = chunk.level.getEntityLookup().getChunk(chunk.locX, chunk.locZ);
|
||||
+ if (entitySlices == null) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ final Entity[] rawEntities = entitySlices.entities.getRawData();
|
||||
+ final ChunkMap chunkMap = chunk.level.chunkSource.chunkMap;
|
||||
+
|
||||
+ for (int i = 0; i < rawEntities.length; i++) {
|
||||
+ Entity entity = rawEntities[i];
|
||||
+ if (entity != null) {
|
||||
+ ChunkMap.TrackedEntity entityTracker = chunkMap.entityMap.get(entity.getId());
|
||||
+ if (entityTracker != null) {
|
||||
+ if (trackerStage == TrackerStage.SEND_CHANGES) {
|
||||
+ entityTracker.serverEntity.sendChanges();
|
||||
+ } else if (trackerStage == TrackerStage.UPDATE_PLAYERS) {
|
||||
+ entityTracker.updatePlayers(entityTracker.entity.getPlayersInTrackRange());
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+}
|
||||
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 fe4d76875462ac9d408c972b968647af78f2ed14..bba131e5febb23134f347de185c1cf11412102e7 100644
|
||||
--- a/src/main/java/io/papermc/paper/util/maplist/IteratorSafeOrderedReferenceSet.java
|
||||
+++ b/src/main/java/io/papermc/paper/util/maplist/IteratorSafeOrderedReferenceSet.java
|
||||
@@ -15,7 +15,7 @@ public final class IteratorSafeOrderedReferenceSet<E> {
|
||||
|
||||
/* list impl */
|
||||
protected E[] listElements;
|
||||
- protected int listSize;
|
||||
+ protected int listSize; public int getListSize() { return this.listSize; } // petal - expose listSize
|
||||
|
||||
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 b2eb6feffb2191c450175547c1371623ce5185eb..71d6a399898402139550830605181b94a60f028c 100644
|
||||
--- a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java
|
||||
+++ b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java
|
||||
@@ -37,7 +37,7 @@ public final class ChunkEntitySlices {
|
||||
protected final EntityCollectionBySection allEntities;
|
||||
protected final EntityCollectionBySection hardCollidingEntities;
|
||||
protected final Reference2ObjectMap<Class<? extends Entity>, EntityCollectionBySection> entitiesByClass;
|
||||
- protected final EntityList entities = new EntityList();
|
||||
+ public final EntityList entities = new EntityList();
|
||||
|
||||
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 dfb747eba6bf7088af0ff400da169de00a076365..9560d81270779494e89a072825c44405c3b910b8 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
||||
@@ -1194,8 +1194,37 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
entity.tracker = null; // Paper - We're no longer tracked
|
||||
}
|
||||
|
||||
+ // petal start - multithreaded tracker
|
||||
+ private @Nullable host.bloom.tracker.MultithreadedTracker multithreadedTracker;
|
||||
+ private final java.util.concurrent.ConcurrentLinkedQueue<Runnable> trackerMainThreadTasks = new java.util.concurrent.ConcurrentLinkedQueue<>();
|
||||
+ private boolean tracking = false;
|
||||
+
|
||||
+ public void runOnTrackerMainThread(final Runnable runnable) {
|
||||
+ if (this.tracking) {
|
||||
+ this.trackerMainThreadTasks.add(runnable);
|
||||
+ } else {
|
||||
+ runnable.run();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
// Paper start - optimised tracker
|
||||
private final void processTrackQueue() {
|
||||
+ if (true) {
|
||||
+ if (this.multithreadedTracker == null) {
|
||||
+ this.multithreadedTracker = new host.bloom.tracker.MultithreadedTracker(this.level.chunkSource.entityTickingChunks, this.trackerMainThreadTasks);
|
||||
+ }
|
||||
+
|
||||
+ this.tracking = true;
|
||||
+ try {
|
||||
+ this.multithreadedTracker.tick();
|
||||
+ } finally {
|
||||
+ this.tracking = false;
|
||||
+ }
|
||||
+ return;
|
||||
+ }
|
||||
+ // petal end
|
||||
+
|
||||
+ this.level.timings.tracker1.startTiming();
|
||||
//this.level.timings.tracker1.startTiming(); // Purpur
|
||||
try {
|
||||
for (TrackedEntity tracker : this.entityMap.values()) {
|
||||
@@ -1443,10 +1472,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
public class TrackedEntity {
|
||||
|
||||
public final ServerEntity serverEntity;
|
||||
- final Entity entity;
|
||||
+ public final Entity entity; // petal -> public
|
||||
private final int range;
|
||||
SectionPos lastSectionPos;
|
||||
- public final Set<ServerPlayerConnection> seenBy = new ReferenceOpenHashSet<>(); // Paper - optimise map impl
|
||||
+ public final Set<ServerPlayerConnection> seenBy = it.unimi.dsi.fastutil.objects.ReferenceSets.synchronize(new ReferenceOpenHashSet<>()); // Paper - optimise map impl // petal - sync
|
||||
|
||||
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
|
||||
@@ -1458,7 +1487,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;
|
||||
|
||||
- final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newTrackerCandidates) {
|
||||
+ public final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newTrackerCandidates) { // petal -> public
|
||||
com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> oldTrackerCandidates = this.lastTrackerCandidates;
|
||||
this.lastTrackerCandidates = newTrackerCandidates;
|
||||
|
||||
@@ -1530,7 +1559,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
}
|
||||
|
||||
public void removePlayer(ServerPlayer player) {
|
||||
- org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot
|
||||
+ //org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot // petal - we can remove async too
|
||||
if (this.seenBy.remove(player.connection)) {
|
||||
this.serverEntity.removePairing(player);
|
||||
}
|
||||
@@ -1538,7 +1567,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
}
|
||||
|
||||
public void updatePlayer(ServerPlayer player) {
|
||||
- org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot
|
||||
+ //org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot // petal - we can update async
|
||||
if (player != this.entity) {
|
||||
// Paper start - remove allocation of Vec3D here
|
||||
// Vec3 vec3d = player.position().subtract(this.entity.position());
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ServerBossEvent.java b/src/main/java/net/minecraft/server/level/ServerBossEvent.java
|
||||
index ca42c2642a729b90d22b968af7258f3aee72e14b..40261b80d947a6be43465013fae5532197cfe721 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ServerBossEvent.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ServerBossEvent.java
|
||||
@@ -13,7 +13,7 @@ import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.BossEvent;
|
||||
|
||||
public class ServerBossEvent extends BossEvent {
|
||||
- private final Set<ServerPlayer> players = Sets.newHashSet();
|
||||
+ private final Set<ServerPlayer> players = Sets.newConcurrentHashSet(); // petal - players can be removed in async tracking
|
||||
private final Set<ServerPlayer> unmodifiablePlayers = Collections.unmodifiableSet(this.players);
|
||||
public boolean visible = true;
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
|
||||
index 50cf4d200bc2892f2140c9929193b4b20ad2bd17..4ea55536a33f462648972b96c39305fdbfb73b03 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
|
||||
@@ -254,14 +254,18 @@ public class ServerEntity {
|
||||
|
||||
public void removePairing(ServerPlayer player) {
|
||||
this.entity.stopSeenByPlayer(player);
|
||||
- player.connection.send(new ClientboundRemoveEntitiesPacket(new int[]{this.entity.getId()}));
|
||||
+ // petal start - ensure main thread
|
||||
+ ((ServerLevel) this.entity.level).chunkSource.chunkMap.runOnTrackerMainThread(() ->
|
||||
+ player.connection.send(new ClientboundRemoveEntitiesPacket(new int[]{this.entity.getId()}))
|
||||
+ );
|
||||
+ // petal end
|
||||
}
|
||||
|
||||
public void addPairing(ServerPlayer player) {
|
||||
ServerGamePacketListenerImpl playerconnection = player.connection;
|
||||
|
||||
Objects.requireNonNull(player.connection);
|
||||
- this.sendPairingData(playerconnection::send, player); // CraftBukkit - add player
|
||||
+ ((ServerLevel) this.entity.level).chunkSource.chunkMap.runOnTrackerMainThread(() -> this.sendPairingData(playerconnection::send, player)); // CraftBukkit - add player // petal - main thread
|
||||
this.entity.startSeenByPlayer(player);
|
||||
}
|
||||
|
||||
@@ -369,19 +373,30 @@ public class ServerEntity {
|
||||
|
||||
if (list != null) {
|
||||
this.trackedDataValues = datawatcher.getNonDefaultValues();
|
||||
- this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list));
|
||||
+ // Petal start - sync
|
||||
+ ((ServerLevel) this.entity.level).chunkSource.chunkMap.runOnTrackerMainThread(() ->
|
||||
+ this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list))
|
||||
+ );
|
||||
+ // Petal end
|
||||
}
|
||||
|
||||
if (this.entity instanceof LivingEntity) {
|
||||
Set<AttributeInstance> set = ((LivingEntity) this.entity).getAttributes().getDirtyAttributes();
|
||||
|
||||
if (!set.isEmpty()) {
|
||||
+ // Petal start - sync
|
||||
+ final var copy = Lists.newArrayList(set);
|
||||
+ ((ServerLevel) this.entity.level).chunkSource.chunkMap.runOnTrackerMainThread(() -> {
|
||||
+
|
||||
// CraftBukkit start - Send scaled max health
|
||||
if (this.entity instanceof ServerPlayer) {
|
||||
- ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(set, false);
|
||||
+ ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(copy, false);
|
||||
}
|
||||
// CraftBukkit end
|
||||
- this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), set));
|
||||
+ this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), copy));
|
||||
+
|
||||
+ });
|
||||
+ // Petal end
|
||||
}
|
||||
|
||||
set.clear();
|
||||
Reference in New Issue
Block a user