From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: peaches94 Date: Sat, 2 Jul 2022 00:35:56 -0500 Subject: [PATCH] Multithreaded Tracker Original license: GPL v3 Original project: https://github.com/Bloom-host/Petal Original license: GPL v3 Original project: https://github.com/TECHNOVE/Airplane-Experimental Co-authored-by: Paul Sauve Co-authored-by: Kevin Raneri Co-authored-by: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> This patch refactored from original multithreaded tracker (Petal version), and is derived from the Airplane fork by Paul Sauve, the tree is like: Airplane -> Pufferfish? -> Petal -> Leaf We made much of tracking logic asynchronously, and fixed visible issue for the case of some NPC plugins which using real entity type, e.g. Citizens. But it is still recommending to use those packet based, virtual entity based NPC plugins, e.g. ZNPC Plus, Adyeshach, Fancy NPC, etc. diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java index 02a9ef1694c796584c29430d27f0a09047368835..32608df3da169159c070f37cb55407f4f6187744 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java @@ -340,7 +340,7 @@ public final class RegionizedPlayerChunkLoader { private boolean canGenerateChunks = true; private final ArrayDeque> delayedTicketOps = new ArrayDeque<>(); - private final LongOpenHashSet sentChunks = new LongOpenHashSet(); + private final LongOpenHashSet sentChunks = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled && !org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled ? new org.dreeam.leaf.util.map.ConcurrentLongHashSet() : new LongOpenHashSet(); // Leaf - Multithreaded tracker private static final byte CHUNK_TICKET_STAGE_NONE = 0; private static final byte CHUNK_TICKET_STAGE_LOADING = 1; diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java index c60b9e4076450de2157c1a3cf4f98cc2c19e4e6a..560a857f3b61679bbf2ee93ac6da393052a1f320 100644 --- a/net/minecraft/server/level/ChunkMap.java +++ b/net/minecraft/server/level/ChunkMap.java @@ -255,6 +255,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } final ServerPlayer[] backingSet = inRange.getRawDataUnchecked(); + // Leaf start - Multithreaded tracker + if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) + for (int i = 0, len = inRange.size(); i < len; i++) { + final ServerPlayer player = backingSet[i]; + if (player == null) continue; + ++(player.mobCounts[index]); + } + else + // Leaf end - Multithreaded tracker for (int i = 0, len = inRange.size(); i < len; i++) { ++(backingSet[i].mobCounts[index]); } @@ -1013,6 +1022,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider // Paper end - optimise entity tracker protected void tick() { + // Leaf start - Multithreaded tracker + if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { + final ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel level = this.level; + org.dreeam.leaf.async.tracker.MultithreadedTracker.tick(level); + return; + } + // Leaf end - Multithreaded tracker // Paper start - optimise entity tracker if (true) { this.newTrackerTick(); @@ -1135,7 +1151,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider final Entity entity; private final int range; SectionPos lastSectionPos; - public final Set seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl + public final Set seenBy = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled ? com.google.common.collect.Sets.newConcurrentHashSet() : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl // Leaf - petal - Multithreaded tracker // Paper start - optimise entity tracker private long lastChunkUpdate = -1L; @@ -1162,7 +1178,39 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.lastTrackedChunk = chunk; final ServerPlayer[] playersRaw = players.getRawDataUnchecked(); + final int playersLen = players.size(); // Ensure length won't change in the future tasks + + // Leaf start - Multithreaded tracker + if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled && org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled) { + final boolean isServerPlayer = this.entity instanceof ServerPlayer; + final boolean isRealPlayer = isServerPlayer && ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer) this.entity).moonrise$isRealPlayer(); + Runnable updatePlayerTasks = () -> { + for (int i = 0; i < playersLen; ++i) { + final ServerPlayer player = playersRaw[i]; + this.updatePlayer(player); + } + if (lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk) { + // need to purge any players possible not in the chunk list + for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { + final ServerPlayer player = conn.getPlayer(); + if (!players.contains(player)) { + this.removePlayer(player); + } + } + } + }; + + // Only update asynchronously for real player, and sync update for fake players + // This can fix compatibility issue with NPC plugins using real entity type, like Citizens + // To prevent visible issue with player type NPCs + // btw, still recommend to use packet based NPC plugins, like ZNPC Plus, Adyeshach, Fancy NPC, etc. + if (isRealPlayer || !isServerPlayer) { + org.dreeam.leaf.async.tracker.MultithreadedTracker.getTrackerExecutor().execute(updatePlayerTasks); + } else { + updatePlayerTasks.run(); + } + } else { for (int i = 0, len = players.size(); i < len; ++i) { final ServerPlayer player = playersRaw[i]; this.updatePlayer(player); @@ -1177,6 +1225,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } } } + } + // Leaf end - Multithreaded tracker } @Override @@ -1238,7 +1288,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public void broadcast(Packet packet) { - for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { + for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) {// Leaf - petal - Multithreaded tracker serverPlayerConnection.send(packet); } } @@ -1259,21 +1309,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public void broadcastRemoved() { - for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { + for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) {// Leaf - petal - Multithreaded tracker this.serverEntity.removePairing(serverPlayerConnection.getPlayer()); } } public void removePlayer(ServerPlayer player) { - org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot + //org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot // Leaf - petal - Multithreaded tracker - We can remove async too if (this.seenBy.remove(player.connection)) { this.serverEntity.removePairing(player); } } public void updatePlayer(ServerPlayer player) { - org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot + //org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot // Leaf - petal - Multithreaded tracker - We can update async if (player != this.entity) { + if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled && player == null) return; // Leaf - Multithreaded tracker // Paper start - remove allocation of Vec3D here // Vec3 vec3 = player.position().subtract(this.entity.position()); double vec3_dx = player.getX() - this.entity.getX(); diff --git a/net/minecraft/server/level/ServerBossEvent.java b/net/minecraft/server/level/ServerBossEvent.java index f106373ef3ac4a8685c2939c9e8361688a285913..51ae390c68e7a3aa193329cc3bc47ca675930ff2 100644 --- a/net/minecraft/server/level/ServerBossEvent.java +++ b/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 = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled ? Sets.newConcurrentHashSet() : Sets.newHashSet(); // Leaf - petal - Multithreaded tracker - players can be removed in async tracking private final Set unmodifiablePlayers = Collections.unmodifiableSet(this.players); public boolean visible = true; diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java index 44d87997e1ce9b846ebed541634a4478334c920c..6b421f84e2d4e286ad4b7624670c90a76e86c8fe 100644 --- a/net/minecraft/server/level/ServerEntity.java +++ b/net/minecraft/server/level/ServerEntity.java @@ -460,12 +460,15 @@ public class ServerEntity { if (this.entity instanceof LivingEntity) { Set attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync(); if (!attributesToSync.isEmpty()) { + // Leaf start - petal - Multithreaded tracker - send in main thread + final Set copy = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(attributesToSync); // CraftBukkit start - Send scaled max health if (this.entity instanceof ServerPlayer serverPlayer) { - serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false); + serverPlayer.getBukkitEntity().injectScaledMaxHealth(copy, false); } // CraftBukkit end - this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); + this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), copy)); + // Leaf end - petal - Multithreaded tracker - send in main thread } attributesToSync.clear(); diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java index 5943b18f172fb1d77ef1fe768daa8e8f43c3c8c1..7b85a9ebdbe3e8bee0a8fc100ede8a3f07eee5ce 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -2503,7 +2503,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @Override public LevelEntityGetter getEntities() { - org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot + //org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot // Leaf - Multithreaded tracker return this.moonrise$getEntityLookup(); // Paper - rewrite chunk system } @@ -2739,7 +2739,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } map.carriedByPlayers.remove(player); - if (map.carriedBy.removeIf(holdingPlayer -> holdingPlayer.player == player)) { + if (map.carriedBy.removeIf(holdingPlayer -> holdingPlayer != null && holdingPlayer.player == player)) { // Leaf - Multithreaded tracker map.decorations.remove(player.getName().getString()); } } diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java index c34cf83f79314198b0f7a747e4ae68b88d09d2cd..c8590517efe4124c2b1db2b927d131b804736400 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -1876,7 +1876,7 @@ public class ServerGamePacketListenerImpl } public void internalTeleport(PositionMoveRotation posMoveRotation, Set relatives) { - org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper + //org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper // Leaf - Multithreaded tracker // Paper start - Prevent teleporting dead entities if (this.player.isRemoved()) { LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java index 3ac9f36eae87369354e992a1d9b5c5b2d87d17cb..d99bbf299af2b2d3a61761c5c3c33c4d371d1b9b 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java @@ -26,8 +26,11 @@ public class AttributeInstance { private final Map> modifiersByOperation = Maps.newEnumMap( AttributeModifier.Operation.class ); - private final Map modifierById = new Object2ObjectArrayMap<>(); - private final Map permanentModifiers = new Object2ObjectArrayMap<>(); + // Leaf start - Multithreaded tracker + private final boolean multiThreadedTrackingEnabled = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled; + private final Map modifierById = multiThreadedTrackingEnabled ? new java.util.concurrent.ConcurrentHashMap<>() : new Object2ObjectArrayMap<>(); + private final Map permanentModifiers = multiThreadedTrackingEnabled ? new java.util.concurrent.ConcurrentHashMap<>() : new Object2ObjectArrayMap<>(); + // Leaf end - Multithreaded tracker private double baseValue; private boolean dirty = true; private double cachedValue; diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java index 701025715e0aca3c1f920a66f9b3d03ec08eaf02..2b8b335cf5779d1b6eb639935d1b92d82aa85d7f 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java +++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java @@ -14,11 +14,14 @@ import net.minecraft.nbt.ListTag; import net.minecraft.resources.ResourceLocation; public class AttributeMap { + // Leaf start - Multithreaded tracker + private final boolean multiThreadedTrackingEnabled = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled; // Gale start - Lithium - replace AI attributes with optimized collections - private final Map, AttributeInstance> attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0); - private final Set attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); - private final Set attributesToUpdate = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); + private final Map, AttributeInstance> attributes = multiThreadedTrackingEnabled ? new java.util.concurrent.ConcurrentHashMap<>() : new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0); + private final Set attributesToSync = multiThreadedTrackingEnabled ? com.google.common.collect.Sets.newConcurrentHashSet() : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); + private final Set attributesToUpdate = multiThreadedTrackingEnabled ? com.google.common.collect.Sets.newConcurrentHashSet() : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); // Gale end - Lithium - replace AI attributes with optimized collections + // Leaf end - Multithreaded tracker private final AttributeSupplier supplier; private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables diff --git a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java index 7bbeed6c998c91e68376d3f17a510d68e3cd0b27..d62ff9ebd4b55e1a9a0b51e84be868d844e5a954 100644 --- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java @@ -211,6 +211,7 @@ public class MapItemSavedData extends SavedData { for (int i = 0; i < this.carriedBy.size(); i++) { MapItemSavedData.HoldingPlayer holdingPlayer1 = this.carriedBy.get(i); + if (holdingPlayer1 == null) continue; // Leaf - Multithreaded tracker Player player1 = holdingPlayer1.player; String string = player1.getName().getString(); if (!player1.isRemoved() && (player1.getInventory().contains(predicate) || mapStack.isFramed())) {