From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: violetc <58360096+s-yh-china@users.noreply.github.com> Date: Sun, 14 Aug 2022 00:39:45 +0800 Subject: [PATCH] Multithreaded Tracker This patch is Powered by Pufferfish(https://github.com/pufferfish-gg/Pufferfish) 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..28f853e93b149de9eb838f5aa3b6779d59a77e2b 100644 --- a/src/main/java/io/papermc/paper/util/maplist/IteratorSafeOrderedReferenceSet.java +++ b/src/main/java/io/papermc/paper/util/maplist/IteratorSafeOrderedReferenceSet.java @@ -16,6 +16,7 @@ public final class IteratorSafeOrderedReferenceSet { /* list impl */ protected E[] listElements; protected int listSize; + public int getListSize() { return this.listSize; } // Leaves - 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 f597d65d56964297eeeed6c7e77703764178fee0..a584f2d0829db0bb220b2934ae2934dab981fdd5 100644 --- a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java +++ b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java @@ -35,7 +35,7 @@ public final class ChunkEntitySlices { protected final EntityCollectionBySection allEntities; protected final EntityCollectionBySection hardCollidingEntities; protected final Reference2ObjectOpenHashMap, EntityCollectionBySection> entitiesByClass; - protected final EntityList entities = new EntityList(); + public final EntityList entities = new EntityList(); // Leaves - protected -> public 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 31f6791935b3f4cc1349716448d46da2bcea2f15..6cca26640b5c392d2d1bb3ecb6b2b235937f54bd 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -1251,8 +1251,38 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider entity.tracker = null; // Paper - We're no longer tracked } + // Leaves start - multithreaded tracker + private @Nullable top.leavesmc.leaves.tracker.MultithreadedTracker multithreadedTracker; + private final java.util.concurrent.ConcurrentLinkedQueue trackerMainThreadTasks = new java.util.concurrent.ConcurrentLinkedQueue<>(); + private boolean multithreadedTrackingInProgress; + + public void runOnTrackerMainThread(final Runnable runnable) { + if (multithreadedTrackingInProgress) { + this.trackerMainThreadTasks.add(runnable); + } else { + runnable.run(); + } + } + // Leaves end - multithreaded tracker + // Paper start - optimised tracker private final void processTrackQueue() { + // Leaves start - multithreaded tracker + if (top.leavesmc.leaves.LeavesConfig.asyncEntityTracker) { + if (this.multithreadedTracker == null) { + this.multithreadedTracker = new top.leavesmc.leaves.tracker.MultithreadedTracker(this.level.chunkSource.entityTickingChunks, this.trackerMainThreadTasks); + } + + try { + multithreadedTrackingInProgress = true; + this.multithreadedTracker.tick(); + } finally { + multithreadedTrackingInProgress = false; + } + return; + } + // Leaves end - multithreaded tracker + this.level.timings.tracker1.startTiming(); try { for (TrackedEntity tracker : this.entityMap.values()) { @@ -1509,11 +1539,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider public class TrackedEntity { - public final ServerEntity serverEntity; - final Entity entity; + public final ServerEntity serverEntity; // Leaves - package -> public + public final Entity entity; // Leaves - package -> public private final int range; SectionPos lastSectionPos; - public final Set seenBy = new ReferenceOpenHashSet<>(); // Paper - optimise map impl + public final Set seenBy = it.unimi.dsi.fastutil.objects.ReferenceSets.synchronize(new ReferenceOpenHashSet<>()); // Paper - optimise map impl // Leaves - 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 @@ -1525,7 +1555,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider // Paper start - use distance map to optimise tracker com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet lastTrackerCandidates; - final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newTrackerCandidates) { + public final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newTrackerCandidates) { // Leaves - package -> public com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet oldTrackerCandidates = this.lastTrackerCandidates; this.lastTrackerCandidates = newTrackerCandidates; @@ -1597,7 +1627,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 // Leaves - we can remove async too if (this.seenBy.remove(player.connection)) { this.serverEntity.removePairing(player); } @@ -1605,7 +1635,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 // Leaves - 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..464678e973513b5c9e0cf0910cac1b3b98419720 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 players = Sets.newHashSet(); + private final Set players = Sets.newConcurrentHashSet(); // Leaves - players can be removed in async tracking private final Set 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 209685e67ebdb7f062bb58118242dcbf884a3876..555f5075f5b61f7c0527289471ead1c56789d200 100644 --- a/src/main/java/net/minecraft/server/level/ServerEntity.java +++ b/src/main/java/net/minecraft/server/level/ServerEntity.java @@ -302,7 +302,11 @@ public class ServerEntity { public void removePairing(ServerPlayer player) { this.entity.stopSeenByPlayer(player); - player.connection.send(new ClientboundRemoveEntitiesPacket(new int[]{this.entity.getId()})); + // Leaves start - ensure main thread + ((ServerLevel) this.entity.level).chunkSource.chunkMap.runOnTrackerMainThread(() -> + player.connection.send(new ClientboundRemoveEntitiesPacket(new int[]{this.entity.getId()})) + ); + // Leaves end - ensure main thread } public void addPairing(ServerPlayer player) { @@ -425,12 +429,17 @@ public class ServerEntity { Set set = ((LivingEntity) this.entity).getAttributes().getDirtyAttributes(); if (!set.isEmpty()) { + // Leaves start - multithreaded tracker + List attributesCopy = 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(attributesCopy, false); // Leaves } // CraftBukkit end - this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), set)); + this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesCopy)); // Leaves + }); + // Leaves end - multithreaded tracker } set.clear(); diff --git a/src/main/java/top/leavesmc/leaves/LeavesConfig.java b/src/main/java/top/leavesmc/leaves/LeavesConfig.java index 76c382c839b904640cb92afa503d1aef1c29ffc6..3c13acaae250ddc1480a7c846097cc187038cde4 100644 --- a/src/main/java/top/leavesmc/leaves/LeavesConfig.java +++ b/src/main/java/top/leavesmc/leaves/LeavesConfig.java @@ -241,6 +241,20 @@ public final class LeavesConfig { dontSendUselessEntityPackets = getBoolean("settings.performance.dont-send-useless-entity-packets", dontSendUselessEntityPackets); } + public static boolean asyncEntityTracker = false; + private static boolean asyncEntityTrackerLock = false; + private static void asyncEntityTracker() { + if (!asyncEntityTrackerLock) { + asyncEntityTracker = getBoolean("settings.performance.async-entity-tracker", asyncEntityTracker); + asyncEntityTrackerLock = true; + } + + if (asyncEntityTracker) { + asyncEntityTracker = false; + LeavesLogger.LOGGER.severe("Async EntityTracker is updating, it can't work"); + } + } + public static final class WorldConfig { public final String worldName; diff --git a/src/main/java/top/leavesmc/leaves/tracker/MultithreadedTracker.java b/src/main/java/top/leavesmc/leaves/tracker/MultithreadedTracker.java new file mode 100644 index 0000000000000000000000000000000000000000..559daa0150e395921d110efc63c477be0248ad25 --- /dev/null +++ b/src/main/java/top/leavesmc/leaves/tracker/MultithreadedTracker.java @@ -0,0 +1,125 @@ +package top.leavesmc.leaves.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; + +// Powered by Pufferfish(https://github.com/pufferfish-gg/Pufferfish) +public class MultithreadedTracker { + + private static final int parallelism = Math.max(4, Runtime.getRuntime().availableProcessors()); + private static final Executor trackerExecutor = Executors.newFixedThreadPool(parallelism, new ThreadFactoryBuilder() + .setNameFormat("puff-tracker-%d") + .setPriority(Thread.NORM_PRIORITY - 2) + .build()); + + private final IteratorSafeOrderedReferenceSet entityTickingChunks; + private final AtomicInteger taskIndex = new AtomicInteger(); + + private final ConcurrentLinkedQueue mainThreadTasks; + private final AtomicInteger finishedTasks = new AtomicInteger(); + + public MultithreadedTracker(IteratorSafeOrderedReferenceSet entityTickingChunks, ConcurrentLinkedQueue mainThreadTasks) { + this.entityTickingChunks = entityTickingChunks; + this.mainThreadTasks = mainThreadTasks; + } + + public void tick() { + int iterator = this.entityTickingChunks.createRawIterator(); + + if (iterator == -1) { + return; + } + + try { + this.taskIndex.set(iterator); + this.finishedTasks.set(0); + + for (int i = 0; i < parallelism; i++) { + trackerExecutor.execute(this::run); + } + + while (this.taskIndex.get() < this.entityTickingChunks.getListSize()) { + this.runMainThreadTasks(); + this.handleTasks(5); // assist + } + + while (this.finishedTasks.get() != parallelism) { + this.runMainThreadTasks(); + } + + this.runMainThreadTasks(); // finish any remaining tasks + } 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 run() { + try { + while (handleTasks(10)); + } finally { + this.finishedTasks.incrementAndGet(); + } + } + + private boolean handleTasks(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); + } catch (Throwable throwable) { + MinecraftServer.LOGGER.warn("Ticking tracker failed", throwable); + } + } + } + return true; + } + return false; + } + + private void processChunk(LevelChunk chunk) { + /* Updating DISABLE THIS + final ChunkEntitySlices entitySlices = chunk.level.entityManager.entitySliceManager.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) { + entityTracker.updatePlayers(entityTracker.entity.getPlayersInTrackRange()); + this.mainThreadTasks.offer(entityTracker.serverEntity::sendChanges); + } + } + } + */ + } +}