diff --git a/README.md b/README.md index 2ecd018e..3d225e39 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 1af17d90..00000000 --- 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 280f670e..00000000 --- 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 5231441f..00000000 --- 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 256c302b..d96f9dd7 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 bfdcad64..4eb5ac5c 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 45a3da07..3867f6d2 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 00000000..3d37253c --- /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 00000000..f6eaa542 --- /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); ++ } ++}