diff --git a/patches/server/0026-Use-LinkedBlockingDeque-in-IAsyncTaskHandler.patch b/patches/server/0026-Use-LinkedBlockingDeque-in-IAsyncTaskHandler.patch new file mode 100644 index 0000000..dc5433f --- /dev/null +++ b/patches/server/0026-Use-LinkedBlockingDeque-in-IAsyncTaskHandler.patch @@ -0,0 +1,98 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Etil <81570777+etil2jz@users.noreply.github.com> +Date: Thu, 9 Dec 2021 17:16:16 +0100 +Subject: [PATCH] Use LinkedBlockingDeque in IAsyncTaskHandler + +Original code by Titaniumtown, licensed under GNU General Public License v3.0 +You can find the original code on https://gitlab.com/Titaniumtown/JettPack + +diff --git a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +index 540aa5e12bb6b44d510c701f2867f541b07ebcc4..62b5f9daff2d45433fc294aec38489a44d03278c 100644 +--- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java ++++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java +@@ -1,12 +1,9 @@ + package net.minecraft.util.thread; + + import com.google.common.collect.ImmutableList; +-import com.google.common.collect.Queues; + import java.util.List; +-import java.util.Queue; + import java.util.concurrent.CompletableFuture; + import java.util.concurrent.Executor; +-import java.util.concurrent.locks.LockSupport; + import java.util.function.BooleanSupplier; + import java.util.function.Supplier; + import net.minecraft.util.profiling.metrics.MetricCategory; +@@ -15,12 +12,15 @@ import net.minecraft.util.profiling.metrics.MetricsRegistry; + import net.minecraft.util.profiling.metrics.ProfilerMeasured; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import java.util.concurrent.LinkedBlockingDeque; // Jettpack ++import java.util.concurrent.TimeUnit; // Jettpack + + public abstract class BlockableEventLoop implements ProfilerMeasured, ProcessorHandle, Executor { + private final String name; + private static final Logger LOGGER = LogManager.getLogger(); +- private final Queue pendingRunnables = Queues.newConcurrentLinkedQueue(); ++ private final LinkedBlockingDeque pendingRunnables = new LinkedBlockingDeque(); // Jettpack + private int blockingCount; ++ private R next = null; // Jettpack + + protected BlockableEventLoop(String name) { + this.name = name; +@@ -89,7 +89,7 @@ public abstract class BlockableEventLoop implements Profiler + @Override + public void tell(R runnable) { + this.pendingRunnables.add(runnable); +- LockSupport.unpark(this.getRunningThread()); ++ //LockSupport.unpark(this.getRunningThread()); // Jettpack + } + + @Override +@@ -113,15 +113,20 @@ public abstract class BlockableEventLoop implements Profiler + } + + public boolean pollTask() { +- R runnable = this.pendingRunnables.peek(); +- if (runnable == null) { +- return false; +- } else if (this.blockingCount == 0 && !true/*this.shouldRun(runnable)*/) { // Patina ++ // Jettpack start ++ if (this.next == null && !this.pendingRunnables.isEmpty()) { ++ this.waitForTasks(); ++ } ++ ++ if (this.next == null) { + return false; + } else { +- this.doRunTask(this.pendingRunnables.remove()); ++ R r2 = this.next; ++ this.next = null; ++ this.doRunTask(r2); + return true; + } ++ // Jettpack end + } + + public void managedBlock(BooleanSupplier stopCondition) { +@@ -140,8 +145,18 @@ public abstract class BlockableEventLoop implements Profiler + } + + protected void waitForTasks() { +- Thread.yield(); +- LockSupport.parkNanos("waiting for tasks", 100000L); ++ // Jettpack start ++ if (this.next != null) { ++ throw new IllegalStateException("next != null"); ++ } ++ try { ++ this.next = this.pendingRunnables.poll(100L, TimeUnit.MICROSECONDS); ++ return; ++ } ++ catch (InterruptedException interruptedException) { ++ return; ++ } ++ // Jettpack end + } + + protected void doRunTask(R task) { diff --git a/patches/server/0027-Better-handling-of-async-tasks.patch b/patches/server/0027-Better-handling-of-async-tasks.patch new file mode 100644 index 0000000..1bed69e --- /dev/null +++ b/patches/server/0027-Better-handling-of-async-tasks.patch @@ -0,0 +1,199 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Etil <81570777+etil2jz@users.noreply.github.com> +Date: Thu, 9 Dec 2021 17:23:06 +0100 +Subject: [PATCH] Better handling of async tasks + +Original code by Titaniumtown, licensed under GNU General Public License v3.0 +You can find the original code on https://gitlab.com/Titaniumtown/JettPack + +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index 74003e8887624736b154807e07835c315e836cf3..26dc229664be7437d4795f932757667260c901ce 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -53,11 +53,14 @@ import java.util.concurrent.atomic.AtomicBoolean; + import java.util.function.BiConsumer; + import java.util.function.Consumer; + import java.util.function.Supplier; ++import java.util.concurrent.SynchronousQueue; // Jettpack ++import java.util.concurrent.ConcurrentLinkedQueue; // Jettpack + + public final class MCUtil { ++ public static final ConcurrentLinkedQueue smallAsyncTasks = new ConcurrentLinkedQueue(); // Jettpack + public static final ThreadPoolExecutor asyncExecutor = new ThreadPoolExecutor( +- 0, 2, 60L, TimeUnit.SECONDS, +- new LinkedBlockingQueue(), ++ 4, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, // Jettpack ++ new SynchronousQueue(), // Jettpack + new ThreadFactoryBuilder().setNameFormat("Paper Async Task Handler Thread - %1$d").build() + ); + public static final ThreadPoolExecutor cleanerExecutor = new ThreadPoolExecutor( +@@ -68,6 +71,30 @@ public final class MCUtil { + + public static final long INVALID_CHUNK_KEY = getCoordinateKey(Integer.MAX_VALUE, Integer.MAX_VALUE); + ++ // Jettpack start ++ public static void flushAsyncTasks() { ++ if (!smallAsyncTasks.isEmpty()) { ++ asyncExecutor.submit(() -> { ++ Runnable runnable; ++ while((runnable = (Runnable)smallAsyncTasks.poll()) != null) { ++ runnable.run(); ++ } ++ }); ++ } ++ } ++ ++ public static void flushAsyncTasksMidTick() { ++ if (smallAsyncTasks.size() <= 16) { ++ asyncExecutor.submit(() -> { ++ Runnable runnable; ++ while((runnable = (Runnable)smallAsyncTasks.poll()) != null) { ++ runnable.run(); ++ } ++ ++ }); ++ } ++ } ++ // Jettpack end + + public static Runnable once(Runnable run) { + AtomicBoolean ran = new AtomicBoolean(false); +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index a16c569cf0c8dba0b2bc61dedf5394a941696d56..bde44438a2af1f7faaa655191c45ed2b4a6b8da0 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -372,6 +372,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { ++ MCUtil.asyncExecutor.execute(() -> { // Jettpack + try { + new LoginHandler().fireEvents(); + } catch (Exception ex) { +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java +index 3c1992e212a6d6f1db4d5b807b38d71913619fc0..5cce24b67d721d4dddc12596e1cece34949bed6b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncScheduler.java +@@ -32,23 +32,30 @@ import java.util.Iterator; + import java.util.List; + import java.util.concurrent.Executor; + import java.util.concurrent.Executors; ++/* + import java.util.concurrent.SynchronousQueue; + import java.util.concurrent.ThreadPoolExecutor; + import java.util.concurrent.TimeUnit; ++*/ ++import net.minecraft.server.MCUtil; + + public class CraftAsyncScheduler extends CraftScheduler { + ++ /* + private final ThreadPoolExecutor executor = new ThreadPoolExecutor( + 4, Integer.MAX_VALUE,30L, TimeUnit.SECONDS, new SynchronousQueue<>(), + new ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %1$d").build()); ++ */ + private final Executor management = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder() + .setNameFormat("Craft Async Scheduler Management Thread").build()); + private final List temp = new ArrayList<>(); + + CraftAsyncScheduler() { + super(true); ++ /* + executor.allowCoreThreadTimeOut(true); + executor.prestartAllCoreThreads(); ++ */ + } + + @Override +@@ -93,7 +100,7 @@ public class CraftAsyncScheduler extends CraftScheduler { + private boolean executeTask(CraftTask task) { + if (isValid(task)) { + this.runners.put(task.getTaskId(), task); +- this.executor.execute(new ServerSchedulerReportingWrapper(task)); ++ MCUtil.asyncExecutor.execute(new ServerSchedulerReportingWrapper(task)); // Jettpack + return true; + } + return false; +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index bee38307494188800886a1622fed229b88dbd8f1..a45c2c569e0ab7205b8c180c3e150f3d421f648a 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -141,6 +141,7 @@ public class WatchdogThread extends Thread + { + while ( !this.stopping ) + { ++ net.minecraft.server.MCUtil.flushAsyncTasks(); // Jettpack + // + // Paper start + Logger log = Bukkit.getServer().getLogger(); diff --git a/patches/server/0028-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR-in.patch b/patches/server/0028-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR-in.patch new file mode 100644 index 0000000..93b3115 --- /dev/null +++ b/patches/server/0028-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR-in.patch @@ -0,0 +1,100 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Etil <81570777+etil2jz@users.noreply.github.com> +Date: Thu, 9 Dec 2021 17:27:28 +0100 +Subject: [PATCH] Use MCUtil.asyncExecutor for MAIN_WORKER_EXECUTOR in + SystemUtils + +Original code by Titaniumtown, licensed under GNU General Public License v3.0 +You can find the original code on https://gitlab.com/Titaniumtown/JettPack + +diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java +index 13085533dba61cf526cf77bac44e32358b592c7e..d7a50c55d58d8b0b284cb2d9ee7a7d84f38996d8 100644 +--- a/src/main/java/net/minecraft/Util.java ++++ b/src/main/java/net/minecraft/Util.java +@@ -61,6 +61,10 @@ import net.minecraft.world.level.block.state.properties.Property; + import org.apache.commons.io.IOUtils; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import java.util.concurrent.AbstractExecutorService; ++import wtf.etil.mirai.server.util.ServerWorkerWrapper; ++import net.minecraft.server.MCUtil; ++import java.util.Collections; + + public class Util { + static final Logger LOGGER = LogManager.getLogger(); +@@ -142,7 +146,43 @@ public class Util { + if (i <= 0) { + executorService = MoreExecutors.newDirectExecutorService(); + } else { +- executorService = new java.util.concurrent.ThreadPoolExecutor(i, i,0L, TimeUnit.MILLISECONDS, new java.util.concurrent.LinkedBlockingQueue(), target -> new net.minecraft.server.ServerWorkerThread(target, s, priorityModifier)); ++ executorService = Integer.getInteger("Paper.WorkerThreadCount", i) <= 0 ? MoreExecutors.newDirectExecutorService() : new AbstractExecutorService(){ ++ private volatile boolean shutdown = false; ++ ++ @Override ++ public final List shutdownNow() { ++ this.shutdown = true; ++ return Collections.emptyList(); ++ } ++ ++ @Override ++ public final void shutdown() { ++ this.shutdown = true; ++ } ++ ++ @Override ++ public final boolean isShutdown() { ++ return this.shutdown; ++ } ++ ++ @Override ++ public final boolean isTerminated() { ++ return this.shutdown; ++ } ++ ++ @Override ++ public final boolean awaitTermination(long l2, TimeUnit timeUnit) throws InterruptedException { ++ if (!this.shutdown) { ++ throw new UnsupportedOperationException(); ++ } ++ return true; ++ } ++ ++ @Override ++ public final void execute(Runnable runnable) { ++ MCUtil.asyncExecutor.execute(new ServerWorkerWrapper(runnable)); ++ } ++ }; + } + /* + @Override +diff --git a/src/main/java/wtf/etil/mirai/server/util/ServerWorkerWrapper.java b/src/main/java/wtf/etil/mirai/server/util/ServerWorkerWrapper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4633764f3d806c7e29cbda6e85db5a3432ad0c29 +--- /dev/null ++++ b/src/main/java/wtf/etil/mirai/server/util/ServerWorkerWrapper.java +@@ -0,0 +1,24 @@ ++package wtf.etil.mirai.server.util; ++ ++import com.google.common.base.Preconditions; ++import net.minecraft.Util; ++ ++public final class ServerWorkerWrapper implements Runnable { ++ private final Runnable internalRunnable; ++ ++ public ServerWorkerWrapper(Runnable runnable) { ++ this.internalRunnable = Preconditions.checkNotNull(runnable, "internalRunnable"); ++ } ++ ++ @Override ++ public final void run() { ++ try { ++ this.internalRunnable.run(); ++ return; ++ } ++ catch (Throwable throwable) { ++ Util.onThreadException(Thread.currentThread(), throwable); ++ return; ++ } ++ } ++} +\ No newline at end of file diff --git a/patches/server/0029-Remove-sync-chunk-writes-in-server.properties.patch b/patches/server/0029-Remove-sync-chunk-writes-in-server.properties.patch new file mode 100644 index 0000000..72bf45b --- /dev/null +++ b/patches/server/0029-Remove-sync-chunk-writes-in-server.properties.patch @@ -0,0 +1,34 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Etil <81570777+etil2jz@users.noreply.github.com> +Date: Thu, 9 Dec 2021 17:32:39 +0100 +Subject: [PATCH] Remove 'sync-chunk-writes' in server.properties + +Original code by Titaniumtown, licensed under GNU General Public License v3.0 +You can find the original code on https://gitlab.com/Titaniumtown/JettPack + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 0178460888a2273e4a7f75b1330ce853dafbcce0..8e7f7b8ae88236e39fdcc9ee91af349bcc098af9 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -436,7 +436,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + BufferedWriter bufferedwriter = Files.newBufferedWriter(file); + + try { +- bufferedwriter.write(String.format("sync-chunk-writes=%s%n", dedicatedserverproperties.syncChunkWrites)); ++ //bufferedwriter.write(String.format("sync-chunk-writes=%s%n", dedicatedserverproperties.syncChunkWrites)); // JettPack - remove 'sync-chunk-writes' in server.properties + bufferedwriter.write(String.format("gamemode=%s%n", dedicatedserverproperties.gamemode)); + bufferedwriter.write(String.format("spawn-monsters=%s%n", dedicatedserverproperties.spawnMonsters)); + bufferedwriter.write(String.format("entity-broadcast-range-percentage=%d%n", dedicatedserverproperties.entityBroadcastRangePercentage)); +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +index f944e6beafc7876ed9c6923a22f58d82967b77cb..0d71d5c4af27f96e5a642bdb4fdb71e3f1b81b99 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +@@ -107,7 +107,7 @@ public class DedicatedServerProperties extends Settings { + return Mth.clamp(integer, (int) 1, 29999984); + }, 29999984); +- this.syncChunkWrites = this.get("sync-chunk-writes", true) && Boolean.getBoolean("Paper.enable-sync-chunk-writes"); // Paper - hide behind flag ++ this.syncChunkWrites = Boolean.getBoolean("Paper.enable-sync-chunk-writes"); // Paper - hide behind flag // JettPack - remove 'sync-chunk-writes' in server.properties + this.enableJmxMonitoring = this.get("enable-jmx-monitoring", false); + this.enableStatus = this.get("enable-status", true); + this.hideOnlinePlayers = this.get("hide-online-players", false); diff --git a/patches/server/0030-Remove-Spigot-tick-limiter.patch b/patches/server/0030-Remove-Spigot-tick-limiter.patch new file mode 100644 index 0000000..312348d --- /dev/null +++ b/patches/server/0030-Remove-Spigot-tick-limiter.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Etil <81570777+etil2jz@users.noreply.github.com> +Date: Thu, 9 Dec 2021 17:38:52 +0100 +Subject: [PATCH] Remove Spigot tick limiter + +Original code by Titaniumtown, licensed under GNU General Public License v3.0 +You can find the original code on https://gitlab.com/Titaniumtown/JettPack + +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 30ed4649ba827b27784cdc6278e6760e2b5acef3..426b006ba49bf244b048a3a0f3a3b5b577041cd3 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -174,8 +174,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + + public final co.aikar.timings.WorldTimingsHandler timings; // Paper + public static BlockPos lastPhysicsProblem; // Spigot +- private org.spigotmc.TickLimiter entityLimiter; +- private org.spigotmc.TickLimiter tileLimiter; ++ //private org.spigotmc.TickLimiter entityLimiter; // JettPack - remove tick limiter ++ //private org.spigotmc.TickLimiter tileLimiter; // JettPack - remove tick limiter + private int tileTickPosition; + public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions + public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Move from Map in BlockRedstoneTorch to here +@@ -401,8 +401,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + // CraftBukkit end + timings = new co.aikar.timings.WorldTimingsHandler(this); // Paper - code below can generate new world and access timings + this.keepSpawnInMemory = this.paperConfig.keepSpawnInMemory; // Paper +- this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime); +- this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime); ++ //this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime); // JettPack - remove tick limiter ++ //this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime); // JettPack - remove tick limiter + this.chunkPacketBlockController = this.paperConfig.antiXray ? new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray + this.entitySliceManager = new io.papermc.paper.world.EntitySliceManager((ServerLevel)this); // Paper + } +diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java +index f144ca888518f1bdb84ee811410937cba994245c..1ea91d49e29fa44296cca2dc97ed89233934ad01 100644 +--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java ++++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java +@@ -428,6 +428,7 @@ public class SpigotWorldConfig + this.hangingTickFrequency = this.getInt( "hanging-tick-frequency", 100 ); + } + ++ /* // JettPack start - remove tick limiter + public int tileMaxTickTime; + public int entityMaxTickTime; + private void maxTickTimes() +@@ -436,6 +437,8 @@ public class SpigotWorldConfig + this.entityMaxTickTime = this.getInt("max-tick-time.entity", 50); + this.log("Tile Max Tick Time: " + this.tileMaxTickTime + "ms Entity max Tick Time: " + this.entityMaxTickTime + "ms"); + } ++ */ ++ // JettPack end + + public int thunderChance; + private void thunderChance() +diff --git a/src/main/java/org/spigotmc/TickLimiter.java b/src/main/java/org/spigotmc/TickLimiter.java +deleted file mode 100644 +index 4074538ea6090bf99d8ab04b1e98c2832a0e9a98..0000000000000000000000000000000000000000 +--- a/src/main/java/org/spigotmc/TickLimiter.java ++++ /dev/null +@@ -1,20 +0,0 @@ +-package org.spigotmc; +- +-public class TickLimiter { +- +- private final int maxTime; +- private long startTime; +- +- public TickLimiter(int maxtime) { +- this.maxTime = maxtime; +- } +- +- public void initTick() { +- this.startTime = System.currentTimeMillis(); +- } +- +- public boolean shouldContinue() { +- long remaining = System.currentTimeMillis() - this.startTime; +- return remaining < this.maxTime; +- } +-} diff --git a/patches/server/0031-New-nbt-cache.patch b/patches/server/0031-New-nbt-cache.patch new file mode 100644 index 0000000..1690a20 --- /dev/null +++ b/patches/server/0031-New-nbt-cache.patch @@ -0,0 +1,141 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Etil <81570777+etil2jz@users.noreply.github.com> +Date: Thu, 9 Dec 2021 17:46:23 +0100 +Subject: [PATCH] New nbt cache + +Original code by YatopiaMC, licensed under MIT +You can find the original code on https://github.com/YatopiaMC/Yatopia + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index bde44438a2af1f7faaa655191c45ed2b4a6b8da0..aa1d8f84772aa3b5d329f899900ecd4a13ca24a9 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1088,7 +1088,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { ++ try { ++ NbtIo.writeCompressed(nbttagcompound, file); ++ File file1 = new File(this.playerDir, player.getStringUUID() + ".dat"); ++ File file2 = new File(this.playerDir, player.getStringUUID() + ".dat_old"); ++ ++ Util.safeReplaceFile(file1, file, file2); ++ } catch (Exception exception) { ++ PlayerDataStorage.LOGGER.error("Failed to save player data for {}", player.getScoreboardName(), exception); // Paper ++ } ++ }; ++ synchronized (this.dataCache){ ++ this.dataCache.put(file, nbttagcompound); ++ } ++ this.executorService.execute(task); ++ // Yatopia end + +- Util.safeReplaceFile(file1, file, file2); + } catch (Exception exception) { + PlayerDataStorage.LOGGER.warn("Failed to save player data for {}", player.getScoreboardName(), exception); // Paper + } +@@ -57,9 +75,18 @@ public class PlayerDataStorage { + // Spigot Start + boolean usingWrongFile = false; + boolean normalFile = file.exists() && file.isFile(); // Akarin - ensures normal file +- if ( org.bukkit.Bukkit.getOnlineMode() && !normalFile ) // Paper - Check online mode first // Akarin - ensures normal file ++ //if ( org.bukkit.Bukkit.getOnlineMode() && !normalFile ) // Paper - Check online mode first // Akarin - ensures normal file ++ // Yatopia start - NBT Cache system ++ CompoundTag playerData; ++ synchronized (this.dataCache){ ++ playerData = this.dataCache.get(file); ++ } ++ if (playerData == null && org.bukkit.Bukkit.getOnlineMode() && !normalFile ) // Paper - Check online mode first // Akarin - ensures normal file // Yatopia + { + file = new File( this.playerDir, java.util.UUID.nameUUIDFromBytes( ( "OfflinePlayer:" + player.getScoreboardName() ).getBytes( "UTF-8" ) ).toString() + ".dat"); ++ synchronized (this.dataCache){ ++ playerData = this.dataCache.get(file); ++ } + if ( file.exists() ) + { + usingWrongFile = true; +@@ -68,9 +95,13 @@ public class PlayerDataStorage { + } + // Spigot End + +- if (normalFile) { // Akarin - avoid double I/O operation ++ //if (normalFile) { // Akarin - avoid double I/O operation ++ if (playerData != null) { ++ nbttagcompound = playerData; ++ } else if (normalFile) { // Akarin - avoid double I/O operation + nbttagcompound = NbtIo.readCompressed(file); + } ++ // Yatopia end + // Spigot Start + if ( usingWrongFile ) + { +diff --git a/src/main/java/wtf/etil/mirai/server/cache/NBTCache.java b/src/main/java/wtf/etil/mirai/server/cache/NBTCache.java +new file mode 100644 +index 0000000000000000000000000000000000000000..10be297ed6e7aaa963d5bdcd40a589e877884cf7 +--- /dev/null ++++ b/src/main/java/wtf/etil/mirai/server/cache/NBTCache.java +@@ -0,0 +1,32 @@ ++package wtf.etil.mirai.server.cache; ++ ++import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenCustomHashMap; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.nbt.CompoundTag; ++ ++import java.io.File; ++ ++public class NBTCache extends Object2ObjectLinkedOpenCustomHashMap { ++ ++ public NBTCache() { ++ super(100, 0.75F, new Strategy() { ++ @Override ++ public int hashCode(File k) { ++ return k.hashCode(); ++ } ++ ++ @Override ++ public boolean equals(File k, File k1) { ++ return k.equals(k1); ++ } ++ }); ++ } ++ ++ @Override ++ public CompoundTag put(File k, CompoundTag v) { ++ if (this.size() > MinecraftServer.getServer().getPlayerCount()) { ++ this.removeLast(); ++ } ++ return super.putAndMoveToFirst(k, v); ++ } ++} +\ No newline at end of file