From ee59e0684b2c9e47a823773705ae801f99e6e393 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Fri, 6 Sep 2024 13:37:17 -0400 Subject: [PATCH] Add Mutltithreaded Tracker & Async playerdata aving (#109) * init Multithreaded Tracker * Rebase & Clean up * Some clean up * Some work * [ci skip] Checked some petal issues * Fix tracker * Unify thread name again * Nitori: Async playerdata Save * Rebase * Fix Citizens player type NPC tracking issue (WIP) Temporary move sendChanges to off-main only. This can fix Citizens's player type NPC visible issue. But still working on making updatePlayer async too, since it also takes big part of performance, and also need to be compat with Citizens. * [ci skip] Drop useless patch * [ci skip] Adjust comments * Optimize tracker, batch processing sendChanges tasks * Clean up and fix * Rebase * Partial update player asynchronously & Fix citizens player type NPC visual issue This made async tracker compat with CItizens, but still need to further optimize * Optimize and update config * Fix realPlayer detect condition & Made more async & Update patch comment * Add compat mode for tracker By isolating Citizens compat logic into compat mode, it can gain more performance if Citizens is not installed. * [ci skip] Update comment --- README.md | 1 + ...-Optimize-Use-thread-safe-Collection.patch | 63 -- ...-Tracking-Optimize-reduce-ArmorStand.patch | 79 --- ...imize-Skip-redundant-useless-packets.patch | 56 -- ...12-Fix-Pufferfish-and-Purpur-patches.patch | 4 +- .../server/0039-Petal-Async-Pathfinding.patch | 4 +- ...p-EntityScheduler-s-executeTick-chec.patch | 4 +- .../server/0100-Multithreaded-Tracker.patch | 537 ++++++++++++++++++ .../0101-Nitori-Async-playerdata-Save.patch | 111 ++++ 9 files changed, 655 insertions(+), 204 deletions(-) delete mode 100644 patches/removed/server/0082-Tracking-Optimize-Use-thread-safe-Collection.patch delete mode 100644 patches/removed/server/0084-Tracking-Optimize-reduce-ArmorStand.patch delete mode 100644 patches/removed/server/0085-Tracking-Optimize-Skip-redundant-useless-packets.patch create mode 100644 patches/server/0100-Multithreaded-Tracker.patch create mode 100644 patches/server/0101-Nitori-Async-playerdata-Save.patch diff --git a/README.md b/README.md index 2ecd018e4..3d225e398 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ If these excellent projects hadn't appeared, Leaf wouldn't have become great. • Polpot
Matter
Luminol
+ • Nitori

diff --git a/patches/removed/server/0082-Tracking-Optimize-Use-thread-safe-Collection.patch b/patches/removed/server/0082-Tracking-Optimize-Use-thread-safe-Collection.patch deleted file mode 100644 index 1af17d903..000000000 --- a/patches/removed/server/0082-Tracking-Optimize-Use-thread-safe-Collection.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Thu, 23 May 2024 21:28:02 +0800 -Subject: [PATCH] Tracking Optimize: Use thread-safe Collection - -Removed since 1.21, not good, useless - -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index a8821f031d85ca66f96aece7c179c40ee96fb90f..29780ed86e938ea1f9e6405e5dee84b09abe3ac0 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -244,7 +244,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - public final io.papermc.paper.util.player.NearbyPlayers nearbyPlayers; - // Paper end - // Paper start - optimise chunk tick iteration -- public final it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet needsChangeBroadcasting = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); -+ public final Set needsChangeBroadcasting = Sets.newConcurrentHashSet(); // Leaf - Use thread-safe collection - public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap = new gg.pufferfish.pufferfish.util.AsyncPlayerAreaMap(this.pooledLinkedPlayerHashSets); // Pufferfish - // Paper end - optimise chunk tick iteration - -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 31d789e6559bc72c699f1be5457230fb640be045..700b8713e41982aa9e1f1ae54eb850a31d3ace52 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -644,7 +644,7 @@ public class ServerChunkCache extends ChunkSource { - // Paper - optimise chunk tick iteration - // Paper start - optimise chunk tick iteration - if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) { -- it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet copy = this.chunkMap.needsChangeBroadcasting.clone(); -+ List copy = new java.util.ArrayList<>(this.chunkMap.needsChangeBroadcasting); // Leaf - Use thread-safe collection - this.chunkMap.needsChangeBroadcasting.clear(); - for (ChunkHolder holder : copy) { - holder.broadcastChanges(holder.getFullChunkNowUnchecked()); // LevelChunks are NEVER unloaded -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index 5a17ccd4a620f17c6434f457244ae2b790e2f34e..d57c6b9d9b37d94829c01f63977ad2caca110dfd 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -436,7 +436,7 @@ public class ServerEntity { - - if (!set.isEmpty()) { - // Leaf start - petal - sync -- final Set copy = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(set); -+ final Set copy = com.google.common.collect.Sets.newConcurrentHashSet(set); - ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() -> { - // CraftBukkit start - Send scaled max health - if (this.entity instanceof ServerPlayer) { -diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java -index 894082d9a8e2aa05f86948bfd335090f37a4ba07..9d16bcfb64885d8f23df8effd44f35bb3a0fa2e4 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java -+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java -@@ -19,8 +19,10 @@ import org.slf4j.Logger; - - public class AttributeMap { - private static final Logger LOGGER = LogUtils.getLogger(); -- private final Map, AttributeInstance> attributes = new Object2ObjectOpenHashMap<>(); -- private final Set dirtyAttributes = new ObjectOpenHashSet<>(); -+ // Leaf start - Use thread-safe collection -+ private final Map, AttributeInstance> attributes = com.google.common.collect.Maps.newConcurrentMap(); -+ private final Set dirtyAttributes = com.google.common.collect.Sets.newConcurrentHashSet(); -+ // Leaf end - Use thread-safe collection - 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 diff --git a/patches/removed/server/0084-Tracking-Optimize-reduce-ArmorStand.patch b/patches/removed/server/0084-Tracking-Optimize-reduce-ArmorStand.patch deleted file mode 100644 index 280f670e7..000000000 --- a/patches/removed/server/0084-Tracking-Optimize-reduce-ArmorStand.patch +++ /dev/null @@ -1,79 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Thu, 23 May 2024 23:03:10 +0800 -Subject: [PATCH] Tracking Optimize: reduce ArmorStand - -Removed since 1.21, not good, useless - -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index dced0a22e924838b13edd0c24a7d3fb3de9242d6..05d94bdc6f9c8baafa392fa3e8f2221df999277c 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1437,7 +1437,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - return; - }*/ // Paper - comment out EAR 2 - // Spigot end -- final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(entity); -+ boolean isActive = shouldActive(entity); // Leaf - Reduce entity tick - entity.setOldPosAndRot(); - - ++entity.tickCount; -@@ -1465,7 +1465,7 @@ public class ServerLevel extends Level implements WorldGenLevel { - if (!passenger.isRemoved() && passenger.getVehicle() == vehicle) { - if (passenger instanceof Player || this.entityTickList.contains(passenger)) { - // Paper - EAR 2 -- final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(passenger); -+ final boolean isActive = shouldActive(passenger); // Leaf - Reduce entity tick - passenger.setOldPosAndRot(); - ++passenger.tickCount; - // Paper start - EAR 2 -@@ -1492,6 +1492,12 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - } - -+ // Leaf start - Reduce entity tick -+ private boolean shouldActive(Entity entity) { -+ return entity instanceof net.minecraft.world.entity.decoration.ArmorStand ? entity.level().paperConfig().entities.armorStands.tick : org.spigotmc.ActivationRange.checkIfActive(entity); -+ } -+ // Leaf end - Reduce entity tick -+ - @Override - public boolean mayInteract(Player player, BlockPos pos) { - return !this.server.isUnderSpawnProtection(this, pos, player) && this.getWorldBorder().isWithinBounds(pos); -diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java -index 37e35606e3f47325db4da5deeff02aad9d541e87..b3fa485dbf96a5b691f423d004bbe0ff7bde4f9a 100644 ---- a/src/main/java/org/spigotmc/ActivationRange.java -+++ b/src/main/java/org/spigotmc/ActivationRange.java -@@ -147,6 +147,13 @@ public class ActivationRange - */ - public static boolean initializeEntityActivationState(Entity entity, SpigotWorldConfig config) - { -+ // Leaf start - Reduce ArmorStand tick -+ if (entity instanceof net.minecraft.world.entity.decoration.ArmorStand -+ && !entity.level().paperConfig().entities.armorStands.tick) { -+ return false; -+ } -+ // Leaf end - Reduce ArmorStand tick -+ - if ( ( entity.activationType == ActivationType.MISC && config.miscActivationRange <= 0 ) - || ( entity.activationType == ActivationType.RAIDER && config.raiderActivationRange <= 0 ) - || ( entity.activationType == ActivationType.ANIMAL && config.animalActivationRange <= 0 ) -@@ -233,12 +240,18 @@ public class ActivationRange - // Paper start - java.util.List entities = world.getEntities((Entity)null, ActivationRange.maxBB, null); - boolean tickMarkers = world.paperConfig().entities.markers.tick; // Paper - Configurable marker ticking -+ boolean tickArmorStands = world.paperConfig().entities.armorStands.tick; // Leaf - Reduce entity tick - for (Entity entity : entities) { - // Paper start - Configurable marker ticking - if (!tickMarkers && entity instanceof net.minecraft.world.entity.Marker) { - continue; - } - // Paper end - Configurable marker ticking -+ // Leaf start - Reduce entity tick -+ if (!tickArmorStands && entity instanceof net.minecraft.world.entity.decoration.ArmorStand) { -+ continue; -+ } -+ // Leaf end - Reduce entity tick - ActivationRange.activateEntity(entity); - - // Pufferfish start diff --git a/patches/removed/server/0085-Tracking-Optimize-Skip-redundant-useless-packets.patch b/patches/removed/server/0085-Tracking-Optimize-Skip-redundant-useless-packets.patch deleted file mode 100644 index 5231441f7..000000000 --- a/patches/removed/server/0085-Tracking-Optimize-Skip-redundant-useless-packets.patch +++ /dev/null @@ -1,56 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Fri, 24 May 2024 19:03:02 +0800 -Subject: [PATCH] Tracking Optimize: Skip redundant useless packets - -Removed since 1.21, not good, useless - -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index c2db18cbb1230588e6b1783dc51c69ed3ccd2e34..71a7b36f7ab1d0743591495a45d8a153cee138e8 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -418,23 +418,34 @@ public class ServerEntity { - // Paper end - } - -+ // Leaf start - Tracking Optimize - Skip redundant useless packets -+ private List> lastSendDirtyData = new ArrayList<>(); -+ private Set lastSendDirtyAttributes = com.google.common.collect.Sets.newConcurrentHashSet(); -+ // Leaf end - Tracking Optimize - Skip redundant useless packets - private void sendDirtyEntityData() { - SynchedEntityData datawatcher = this.entity.getEntityData(); - List> list = datawatcher.packDirty(); - - if (list != null) { - this.trackedDataValues = datawatcher.getNonDefaultValues(); -+ // Leaf start - Tracking Optimize - Skip redundant useless packets -+ if (!lastSendDirtyData.equals(list)) { - // Leaf start - petal - sync - ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() -> - this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list)) - ); - // Leaf end - petal -+ lastSendDirtyData = new ArrayList<>(list); -+ } -+ // Leaf end - Tracking Optimize - Skip redundant useless packets - } - - if (this.entity instanceof LivingEntity) { - Set set = ((LivingEntity) this.entity).getAttributes().getDirtyAttributes(); - - if (!set.isEmpty()) { -+ // Leaf start - Tracking Optimize - Skip redundant useless packets -+ if (!lastSendDirtyAttributes.equals(set)) { - // Leaf start - petal - sync - final Set copy = com.google.common.collect.Sets.newConcurrentHashSet(set); - ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() -> { -@@ -447,6 +458,9 @@ public class ServerEntity { - - }); - // Leaf end - petal -+ lastSendDirtyAttributes = com.google.common.collect.Sets.newConcurrentHashSet(set); -+ } -+ // Leaf end - Tracking Optimize - Skip redundant useless packets - } - - ((LivingEntity) this.entity).getAttributes().clearDirtyAttributes(); // Leaf diff --git a/patches/server/0012-Fix-Pufferfish-and-Purpur-patches.patch b/patches/server/0012-Fix-Pufferfish-and-Purpur-patches.patch index 256c302bb..d96f9dd7f 100644 --- a/patches/server/0012-Fix-Pufferfish-and-Purpur-patches.patch +++ b/patches/server/0012-Fix-Pufferfish-and-Purpur-patches.patch @@ -28,7 +28,7 @@ index 23609a71a993fc91271578ee0a541a9c6ec7354f..34f7f74b374d319d0d578e498db7663d @Override diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 9d082b3c06281b0cafe455959d6ef20b8891110e..5071828f4ec318ca457d7c29d5ffaa3de2f43e48 100644 +index 9d082b3c06281b0cafe455959d6ef20b8891110e..c842bd59cb612a98d261d31a026d4ae8ce4e455b 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -300,7 +300,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { AtomicReference atomicreference = new AtomicReference(); diff --git a/patches/server/0039-Petal-Async-Pathfinding.patch b/patches/server/0039-Petal-Async-Pathfinding.patch index bfdcad642..4eb5ac5cc 100644 --- a/patches/server/0039-Petal-Async-Pathfinding.patch +++ b/patches/server/0039-Petal-Async-Pathfinding.patch @@ -1095,7 +1095,7 @@ index 0000000000000000000000000000000000000000..2f5aff1f0e2aca0a8bfeab27c4ab027f +} diff --git a/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java b/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java new file mode 100644 -index 0000000000000000000000000000000000000000..ccee3723679d3740d34bd13c04018bafafa5da62 +index 0000000000000000000000000000000000000000..3eb86fc2e0ea28be18e23dd2c060e043f1fede93 --- /dev/null +++ b/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java @@ -0,0 +1,52 @@ @@ -1123,7 +1123,7 @@ index 0000000000000000000000000000000000000000..ccee3723679d3740d34bd13c04018baf + org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingKeepalive, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), + new ThreadFactoryBuilder() -+ .setNameFormat("petal-async-pathfinding-thread-%d") ++ .setNameFormat("Leaf Async Pathfinding Thread - %d") + .setPriority(Thread.NORM_PRIORITY - 2) + .build() + ); diff --git a/patches/server/0055-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch b/patches/server/0055-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch index 45a3da07b..3867f6d2a 100644 --- a/patches/server/0055-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch +++ b/patches/server/0055-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch @@ -75,13 +75,13 @@ index c03608fec96b51e1867f43d8f42e5aefb1520e46..15b21fa3907db1b77ed5b5d1050a37f4 throw new IllegalStateException("Ticking retired scheduler"); } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index efd29f9d2417c9483b0aa2c521109a9e20f6990c..7308c3ae41397338092ab3f7f95baca6b2a515c3 100644 +index 55466ff58d724a6254fd6e53c72adb592f2d433a..0255d5a23e2f5a1a67af3ff1588b608521e4c133 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -312,6 +312,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop entitiesWithScheduledTasks = java.util.concurrent.ConcurrentHashMap.newKeySet(); // SparklyPaper - skip EntityScheduler's executeTick checks if there isn't any tasks to be run (concurrent because plugins may schedule tasks async) public static S spin(Function serverFactory) { diff --git a/patches/server/0100-Multithreaded-Tracker.patch b/patches/server/0100-Multithreaded-Tracker.patch new file mode 100644 index 000000000..3d37253c0 --- /dev/null +++ b/patches/server/0100-Multithreaded-Tracker.patch @@ -0,0 +1,537 @@ +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 + +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/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +index 31bb5c3058233c98cbdd919e4803dd2f2266d39d..2622a82b6e34cb636eaad239d8e6e30dc8cce589 100644 +--- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +@@ -42,6 +42,12 @@ class PaperEventManager { + if (event.isAsynchronous() && this.server.isPrimaryThread()) { + throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously."); + } else if (!event.isAsynchronous() && !this.server.isPrimaryThread() && !this.server.isStopping()) { ++ // Leaf start - petal - Multithreaded tracker ++ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { ++ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(event::callEvent); ++ return; ++ } ++ // Leaf end - petal - Multithreaded tracker + throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); + } + +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 200a08d09904c6d5ea85b9e2c0228e6184f3aed1..cfd9545384c0b74605115e47c390c876a61dbdd3 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -906,6 +906,21 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity)entity).moonrise$setTrackedEntity(null); // Paper - optimise entity tracker + } + ++ // Leaf start - petal - Multithreaded tracker ++ private final java.util.concurrent.ConcurrentLinkedQueue trackerMainThreadTasks = new java.util.concurrent.ConcurrentLinkedQueue<>(); ++ private boolean tracking = false; ++ ++ public void runOnTrackerMainThread(final Runnable runnable) { ++ //final boolean isOnMain = ca.spottedleaf.moonrise.common.util.TickThread.isTickThread(); ++ //System.out.println(isOnMain); ++ if (false && this.tracking) { // TODO: check here ++ this.trackerMainThreadTasks.add(runnable); ++ } else { ++ runnable.run(); ++ } ++ } ++ // Leaf end - petal - Multithreaded tracker ++ + // Paper start - optimise entity tracker + private void newTrackerTick() { + final ca.spottedleaf.moonrise.common.misc.NearbyPlayers nearbyPlayers = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getNearbyPlayers(); +@@ -939,6 +954,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // Paper end - optimise entity tracker + + protected void tick() { ++ // Leaf start - petal - 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 - petal - Multithreaded tracker + // Paper start - optimise entity tracker + if (true) { + this.newTrackerTick(); +@@ -1088,7 +1110,9 @@ 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 ++ ? 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; +@@ -1116,6 +1140,37 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + final ServerPlayer[] playersRaw = players.getRawDataUnchecked(); + ++ // 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, len = players.size(); i < len; ++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); +@@ -1130,6 +1185,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + } + } ++ } ++ // Leaf end - Multithreaded tracker + } + + @Override +@@ -1184,14 +1241,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + public void broadcast(Packet packet) { +- Iterator iterator = this.seenBy.iterator(); +- +- while (iterator.hasNext()) { +- ServerPlayerConnection serverplayerconnection = (ServerPlayerConnection) iterator.next(); +- ++ // Leaf start - petal - Multithreaded tracker ++ for (ServerPlayerConnection serverplayerconnection : this.seenBy.toArray(new ServerPlayerConnection[0])) { + serverplayerconnection.send(packet); + } +- ++ // Leaf end - petal - Multithreaded tracker + } + + public void broadcastAndSend(Packet packet) { +@@ -1203,18 +1257,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + public void broadcastRemoved() { +- Iterator iterator = this.seenBy.iterator(); +- +- while (iterator.hasNext()) { +- ServerPlayerConnection serverplayerconnection = (ServerPlayerConnection) iterator.next(); +- ++ // Leaf start - petal - Multithreaded tracker ++ for (ServerPlayerConnection serverplayerconnection : this.seenBy.toArray(new ServerPlayerConnection[0])) { + this.serverEntity.removePairing(serverplayerconnection.getPlayer()); + } +- ++ // Leaf end - petal - Multithreaded tracker + } + + 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); + } +@@ -1222,7 +1273,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 // Leaf - petal - Multithreaded tracker - 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 4f91107f9ae42f96c060c310596db9aa869a8dbc..f9889f593ed144ee8f1f5bd380e631c659b0c2b8 100644 +--- a/src/main/java/net/minecraft/server/level/ServerBossEvent.java ++++ b/src/main/java/net/minecraft/server/level/ServerBossEvent.java +@@ -13,7 +13,9 @@ 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/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 05125144ce0cb50fa6ac769fa025cda010c93f14..3b40fc420ec1a8aca4c66a77f54cf628a39aa6f2 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -336,7 +336,11 @@ public class ServerEntity { + + public void removePairing(ServerPlayer player) { + this.entity.stopSeenByPlayer(player); +- player.connection.send(new ClientboundRemoveEntitiesPacket(new int[]{this.entity.getId()})); ++ // Leaf start - petal - Multithreaded tracker - send in main thread ++ ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() -> ++ player.connection.send(new ClientboundRemoveEntitiesPacket(this.entity.getId())) ++ ); ++ // Leaf end - petal - Multithreaded tracker - send in main thread + } + + public void addPairing(ServerPlayer player) { +@@ -344,7 +348,11 @@ public class ServerEntity { + + Objects.requireNonNull(list); + this.sendPairingData(player, list::add); +- player.connection.send(new ClientboundBundlePacket(list)); ++ // Leaf start - petal - Multithreaded tracker - send in main thread ++ ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() -> ++ player.connection.send(new ClientboundBundlePacket(list)) ++ ); ++ // Leaf end - petal - Multithreaded tracker - send in main thread + this.entity.startSeenByPlayer(player); + } + +@@ -464,19 +472,28 @@ public class ServerEntity { + + if (list != null) { + this.trackedDataValues = datawatcher.getNonDefaultValues(); +- this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list)); ++ // Leaf start - petal - Multithreaded tracker - send in main thread ++ ((ServerLevel) this.entity.level()).chunkSource.chunkMap.runOnTrackerMainThread(() -> ++ this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list)) ++ ); ++ // Leaf end - petal - Multithreaded tracker - send in main thread + } + + if (this.entity instanceof LivingEntity) { + Set set = ((LivingEntity) this.entity).getAttributes().getAttributesToSync(); + + if (!set.isEmpty()) { ++ // Leaf end - petal - Multithreaded tracker - send in main thread ++ final Set copy = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(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)); ++ }); ++ // Leaf end - petal - Multithreaded tracker - send in main thread + } + + set.clear(); +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index dab20d348d7542a985a2510b37029ff97e8be1f6..44f128f7d6741d47f9a4bbd92e147b4011447a50 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -2399,7 +2399,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf. + + @Override + public LevelEntityGetter getEntities() { +- org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot ++ //org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot // Leaf - petal - Multithreaded tracker + return this.moonrise$getEntityLookup(); // Paper - rewrite chunk system + } + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 5ee48b2347b4d588206d4c4aabd47a3918046973..2f0184997c55f35d081bcaed29c763455684b621 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1816,7 +1816,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + } + + public void internalTeleport(double d0, double d1, double d2, float f, float f1, Set set) { // Paper +- org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper ++ //org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper // Leaf - petal - Multithreaded tracker + // Paper start - Prevent teleporting dead entities + if (player.isRemoved()) { + LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); +diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java +index 14ceb3308474e76220bd64b0254df3f2925d4206..5fc03bf452082d13c577e2fcf49288c5c20c2d19 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java ++++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java +@@ -19,11 +19,20 @@ import org.slf4j.Logger; + + public class AttributeMap { + private static final Logger LOGGER = LogUtils.getLogger(); ++ // Leaf start - petal - 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 - petal - 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 +diff --git a/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java b/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java +new file mode 100644 +index 0000000000000000000000000000000000000000..16c59bdeeaa7f114c912e4b3d6409272e0f682bf +--- /dev/null ++++ b/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java +@@ -0,0 +1,162 @@ ++package org.dreeam.leaf.async.tracker; ++ ++import ca.spottedleaf.moonrise.common.list.ReferenceList; ++import ca.spottedleaf.moonrise.common.misc.NearbyPlayers; ++import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; ++import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup; ++import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity; ++import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity; ++import com.google.common.util.concurrent.ThreadFactoryBuilder; ++import net.minecraft.server.level.ChunkMap; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.entity.Entity; ++ ++import java.util.Arrays; ++import java.util.concurrent.Executor; ++import java.util.concurrent.LinkedBlockingQueue; ++import java.util.concurrent.ThreadPoolExecutor; ++import java.util.concurrent.TimeUnit; ++ ++public class MultithreadedTracker { ++ ++ private static final Executor trackerExecutor = new ThreadPoolExecutor( ++ 1, ++ org.dreeam.leaf.config.modules.async.MultithreadedTracker.asyncEntityTrackerMaxThreads, ++ org.dreeam.leaf.config.modules.async.MultithreadedTracker.asyncEntityTrackerKeepalive, TimeUnit.SECONDS, ++ new LinkedBlockingQueue<>(), ++ new ThreadFactoryBuilder() ++ .setNameFormat("Leaf Async Tracker Thread - %d") ++ .setPriority(Thread.NORM_PRIORITY - 2) ++ .build()); ++ ++ private MultithreadedTracker() { ++ } ++ ++ public static Executor getTrackerExecutor() { ++ return trackerExecutor; ++ } ++ ++ public static void tick(ChunkSystemServerLevel level) { ++ if (!org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled) { ++ tickAsync(level); ++ } else { ++ tickAsyncWithCompatMode(level); ++ } ++ } ++ ++ private static void tickAsync(ChunkSystemServerLevel level) { ++ final NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers(); ++ final ServerEntityLookup entityLookup = (ServerEntityLookup) level.moonrise$getEntityLookup(); ++ ++ final ReferenceList trackerEntities = entityLookup.trackerEntities; ++ final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked(); ++ ++ // Move tracking to off-main ++ trackerExecutor.execute(() -> { ++ for (final Entity entity : trackerEntitiesRaw) { ++ if (entity == null) continue; ++ ++ final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity(); ++ ++ if (tracker == null) continue; ++ ++ ((EntityTrackerTrackedEntity) tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); ++ tracker.serverEntity.sendChanges(); ++ } ++ }); ++ ++ // process unloads ++ final ReferenceList unloadedEntities = entityLookup.trackerUnloadedEntities; ++ final Entity[] unloadedEntitiesRaw = Arrays.copyOf(unloadedEntities.getRawDataUnchecked(), unloadedEntities.size()); ++ unloadedEntities.clear(); ++ ++ // Move player unload to off-main ++ trackerExecutor.execute(() -> { ++ for (final Entity entity : unloadedEntitiesRaw) { ++ final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity(); ++ ++ if (tracker == null) continue; ++ ++ ((EntityTrackerTrackedEntity) tracker).moonrise$clearPlayers(); ++ } ++ }); ++ } ++ ++ private static void tickAsyncWithCompatMode(ChunkSystemServerLevel level) { ++ final NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers(); ++ final ServerEntityLookup entityLookup = (ServerEntityLookup) level.moonrise$getEntityLookup(); ++ ++ final ReferenceList trackerEntities = entityLookup.trackerEntities; ++ final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked(); ++ final Runnable[] sendChangesTasks = new Runnable[trackerEntitiesRaw.length]; ++ int index = 0; ++ ++ for (final Entity entity : trackerEntitiesRaw) { ++ if (entity == null) continue; ++ ++ final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity(); ++ ++ if (tracker == null) continue; ++ ++ ((EntityTrackerTrackedEntity) tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); ++ sendChangesTasks[index++] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array ++ } ++ ++ // batch submit tasks ++ trackerExecutor.execute(() -> { ++ for (final Runnable sendChanges : sendChangesTasks) { ++ if (sendChanges == null) continue; ++ ++ sendChanges.run(); ++ } ++ }); ++ ++ // process unloads ++ final ReferenceList unloadedEntities = entityLookup.trackerUnloadedEntities; ++ final Entity[] unloadedEntitiesRaw = Arrays.copyOf(unloadedEntities.getRawDataUnchecked(), unloadedEntities.size()); ++ unloadedEntities.clear(); ++ ++ trackerExecutor.execute(() -> { ++ for (final Entity entity : unloadedEntitiesRaw) { ++ final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity(); ++ ++ if (tracker == null) continue; ++ ++ ((EntityTrackerTrackedEntity) tracker).moonrise$clearPlayers(); ++ } ++ }); ++ } ++ ++ // Original ChunkMap#newTrackerTick of Paper ++ // Just for diff usage for future update ++ private static void tickOriginal(ServerLevel level) { ++ final ca.spottedleaf.moonrise.common.misc.NearbyPlayers nearbyPlayers = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel) level).moonrise$getNearbyPlayers(); ++ final ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup entityLookup = (ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup) ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel) level).moonrise$getEntityLookup(); ++ ; ++ ++ final ca.spottedleaf.moonrise.common.list.ReferenceList trackerEntities = entityLookup.trackerEntities; ++ final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked(); ++ for (int i = 0, len = trackerEntities.size(); i < len; ++i) { ++ final Entity entity = trackerEntitiesRaw[i]; ++ final ChunkMap.TrackedEntity tracker = ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity) entity).moonrise$getTrackedEntity(); ++ if (tracker == null) { ++ continue; ++ } ++ ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity) tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); ++ tracker.serverEntity.sendChanges(); ++ } ++ ++ // process unloads ++ final ca.spottedleaf.moonrise.common.list.ReferenceList unloadedEntities = entityLookup.trackerUnloadedEntities; ++ final Entity[] unloadedEntitiesRaw = java.util.Arrays.copyOf(unloadedEntities.getRawDataUnchecked(), unloadedEntities.size()); ++ unloadedEntities.clear(); ++ ++ for (final Entity entity : unloadedEntitiesRaw) { ++ final ChunkMap.TrackedEntity tracker = ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity) entity).moonrise$getTrackedEntity(); ++ if (tracker == null) { ++ continue; ++ } ++ ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity) tracker).moonrise$clearPlayers(); ++ } ++ } ++} +diff --git a/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java b/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java +new file mode 100644 +index 0000000000000000000000000000000000000000..33da9733f0cc0e414189152afa7c757f4ffd1900 +--- /dev/null ++++ b/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java +@@ -0,0 +1,43 @@ ++package org.dreeam.leaf.config.modules.async; ++ ++import org.dreeam.leaf.config.ConfigModules; ++import org.dreeam.leaf.config.EnumConfigCategory; ++import org.dreeam.leaf.config.LeafConfig; ++ ++public class MultithreadedTracker extends ConfigModules { ++ ++ public String getBasePath() { ++ return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-entity-tracker"; ++ } ++ ++ public static boolean enabled = true; ++ public static boolean compatModeEnabled = false; ++ public static int asyncEntityTrackerMaxThreads = 0; ++ public static int asyncEntityTrackerKeepalive = 60; ++ ++ @Override ++ public void onLoaded() { ++ config.addComment(getBasePath(), """ ++ Make entity tracking saving asynchronously, can improve performance significantly, ++ especially in some massive entities in small area situations. ++ """); ++ ++ enabled = config().getBoolean(getBasePath() + ".enabled", enabled); ++ compatModeEnabled = config.getBoolean(getBasePath() + ".compat-mode", compatModeEnabled, """ ++ Enable compat mode ONLY if Citizens or NPC plugins using real entity has installed, ++ Compat mode fixed visible issue with player type NPCs of Citizens, ++ But still recommend to use packet based / virtual entity NPC plugin, e.g. ZNPC Plus, Adyeshach, Fancy NPC or else."""); ++ asyncEntityTrackerMaxThreads = config.getInt(getBasePath() + ".max-threads", asyncEntityTrackerMaxThreads); ++ asyncEntityTrackerKeepalive = config.getInt(getBasePath() + ".keepalive", asyncEntityTrackerKeepalive); ++ ++ if (asyncEntityTrackerMaxThreads < 0) ++ asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncEntityTrackerMaxThreads, 1); ++ else if (asyncEntityTrackerMaxThreads == 0) ++ asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() / 4, 1); ++ ++ if (!enabled) ++ asyncEntityTrackerMaxThreads = 0; ++ else ++ LeafConfig.LOGGER.info("Using {} threads for Async Entity Tracker", asyncEntityTrackerMaxThreads); ++ } ++} diff --git a/patches/server/0101-Nitori-Async-playerdata-Save.patch b/patches/server/0101-Nitori-Async-playerdata-Save.patch new file mode 100644 index 000000000..f6eaa542f --- /dev/null +++ b/patches/server/0101-Nitori-Async-playerdata-Save.patch @@ -0,0 +1,111 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Fri, 23 Aug 2024 22:04:20 -0400 +Subject: [PATCH] Nitori: Async playerdata Save + +Original license: GPL v3 +Original project: https://github.com/Gensokyo-Reimagined/Nitori + +diff --git a/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java b/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java +index 85ba843ce7e1f62971e736fa2cc028c47b274ce4..7d018095f9cafbe727be41655742875bee2c028b 100644 +--- a/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java ++++ b/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java +@@ -605,7 +605,11 @@ public class LevelStorageSource { + CompoundTag nbttagcompound2 = new CompoundTag(); + + nbttagcompound2.put("Data", nbttagcompound1); +- this.saveLevelData(nbttagcompound2); ++ ++ // Leaf start - Nitori - Async playerdata save ++ Runnable runnable = () -> this.saveLevelData(nbttagcompound2); ++ org.dreeam.leaf.async.AsyncPlayerDataSaving.saveAsync(runnable); ++ // Leaf end - Nitori - Async playerdata save + } + + private void saveLevelData(CompoundTag nbt) { +@@ -702,7 +706,11 @@ public class LevelStorageSource { + CompoundTag nbttagcompound = LevelStorageSource.readLevelDataTagRaw(this.levelDirectory.dataFile()); + + nbtProcessor.accept(nbttagcompound.getCompound("Data")); +- this.saveLevelData(nbttagcompound); ++ ++ // Leaf start - Nitori - Async playerdata save ++ Runnable runnable = () -> this.saveLevelData(nbttagcompound); ++ org.dreeam.leaf.async.AsyncPlayerDataSaving.saveAsync(runnable); ++ // Leaf end - Nitori - Async playerdata save + } + + public long makeWorldBackup() throws IOException { +diff --git a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java +index b148cf247acdd36f856d0495cde4cc5ad32b5a2f..e825d9e573a38531f5a3b3f9cdccc24570953015 100644 +--- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java ++++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java +@@ -36,6 +36,13 @@ public class PlayerDataStorage { + + public void save(Player player) { + if (org.spigotmc.SpigotConfig.disablePlayerDataSaving) return; // Spigot ++ ++ // Leaf start - Nitori - Async playerdata save ++ Runnable runnable = () -> save0(player); ++ org.dreeam.leaf.async.AsyncPlayerDataSaving.saveAsync(runnable); ++ } ++ private void save0(Player player) { ++ // Leaf end - Nitori - Async playerdata save + try { + CompoundTag nbttagcompound = player.saveWithoutId(new CompoundTag()); + Path path = this.playerDir.toPath(); +diff --git a/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java b/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6f74ca2f5bdae24434255976ec24f28c4980ac17 +--- /dev/null ++++ b/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java +@@ -0,0 +1,23 @@ ++package org.dreeam.leaf.async; ++ ++import net.minecraft.Util; ++import org.dreeam.leaf.config.modules.async.AsyncPlayerDataSave; ++ ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.ExecutorService; ++ ++public class AsyncPlayerDataSaving { ++ ++ private AsyncPlayerDataSaving() { ++ } ++ ++ public static void saveAsync(Runnable runnable) { ++ if (!AsyncPlayerDataSave.enabled) { ++ runnable.run(); ++ return; ++ } ++ ++ ExecutorService ioExecutor = Util.backgroundExecutor(); ++ CompletableFuture.runAsync(runnable, ioExecutor); ++ } ++} +diff --git a/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java b/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f1d715723a641dc042a7f645398169e6f483700c +--- /dev/null ++++ b/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java +@@ -0,0 +1,20 @@ ++package org.dreeam.leaf.config.modules.async; ++ ++import org.dreeam.leaf.config.ConfigModules; ++import org.dreeam.leaf.config.EnumConfigCategory; ++ ++public class AsyncPlayerDataSave extends ConfigModules { ++ ++ public String getBasePath() { ++ return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-playerdata-save"; ++ } ++ ++ public static boolean enabled = true; ++ ++ @Override ++ public void onLoaded() { ++ config.addComment(getBasePath(), "Make PlayerData saving asynchronously."); ++ ++ enabled = config().getBoolean(getBasePath() + ".enabled", enabled); ++ } ++}