From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Hugo Planque <hookwood01@gmail.com>
Date: Thu, 21 Jan 2021 17:56:03 +0100
Subject: [PATCH] New nbt cache

The goal of this patch is to reduce I/O operations from the main thread while saving player data and also to avoid too many I/O operations while reading NBT Player file by using a cache (Which start to delete the oldest data when there is too much player compared to the map size)

Co-authored-by: ishland <ishlandmc@yeah.net>

diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 228fbcfca5724be741ebe863e2bff2107a73f0d3..67e27a2931acd281803f8003069fac54245d4df6 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -830,7 +830,9 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
         }
         // Spigot start
         MCUtil.asyncExecutor.shutdown(); // Paper
+        this.worldNBTStorage.executorService.shutdown(); // Yatopia
         try { MCUtil.asyncExecutor.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS); // Paper
+            this.worldNBTStorage.executorService.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS); // Yatopia - New async nbt cache
         } catch (java.lang.InterruptedException ignored) {} // Paper
         if (org.spigotmc.SpigotConfig.saveUserCacheOnStopOnly) {
             LOGGER.info("Saving usercache.json");
diff --git a/src/main/java/net/minecraft/server/WorldNBTStorage.java b/src/main/java/net/minecraft/server/WorldNBTStorage.java
index 1f77b251d7e7b0f023793cbf0876fc067caa75c1..caefa07da1501cb4707dcdae15f3fefdfa260aa0 100644
--- a/src/main/java/net/minecraft/server/WorldNBTStorage.java
+++ b/src/main/java/net/minecraft/server/WorldNBTStorage.java
@@ -17,6 +17,11 @@ public class WorldNBTStorage {
     private static final Logger LOGGER = LogManager.getLogger();
     private final File playerDir;
     protected final DataFixer a;
+    // Yatopia start - NBT Cache system
+    private final org.yatopiamc.yatopia.server.cache.NBTCache dataCache = new org.yatopiamc.yatopia.server.cache.NBTCache();
+    public final java.util.concurrent.ExecutorService executorService = java.util.concurrent.Executors.newSingleThreadExecutor(
+            new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setPriority(Thread.NORM_PRIORITY - 1).build());
+    // Yatopia end
 
     public WorldNBTStorage(Convertable.ConversionSession convertable_conversionsession, DataFixer datafixer) {
         this.a = datafixer;
@@ -30,11 +35,22 @@ public class WorldNBTStorage {
             NBTTagCompound nbttagcompound = entityhuman.save(new NBTTagCompound());
             File file = File.createTempFile(entityhuman.getUniqueIDString() + "-", ".dat", this.playerDir);
 
-            NBTCompressedStreamTools.a(nbttagcompound, file);
+            // NBTCompressedStreamTools.a(nbttagcompound, file); // Yatopia
+            // Yatopia start - NBT Cache system
+            Runnable task = () -> { try { NBTCompressedStreamTools.a(nbttagcompound, file);
             File file1 = new File(this.playerDir, entityhuman.getUniqueIDString() + ".dat");
             File file2 = new File(this.playerDir, entityhuman.getUniqueIDString() + ".dat_old");
 
             SystemUtils.a(file1, file, file2);
+            } catch (Exception exception) {
+                WorldNBTStorage.LOGGER.error("Failed to save player data for {}", entityhuman.getName(), exception); // Paper
+            }
+            };
+            synchronized (this.dataCache){
+                this.dataCache.put(file, nbttagcompound);
+            }
+            this.executorService.execute(task);
+            // Yatopia end
         } catch (Exception exception) {
             WorldNBTStorage.LOGGER.error("Failed to save player data for {}", entityhuman.getName(), exception); // Paper
         }
@@ -50,9 +66,18 @@ public class WorldNBTStorage {
             // 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
+            // Yatopia start - NBT Cache system
+            NBTTagCompound 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
             {
                 file = new File( this.playerDir, java.util.UUID.nameUUIDFromBytes( ( "OfflinePlayer:" + entityhuman.getName() ).getBytes( "UTF-8" ) ).toString() + ".dat");
+                synchronized (this.dataCache){
+                    playerData = this.dataCache.get(file);
+                }
                 if ( file.exists() )
                 {
                     usingWrongFile = true;
@@ -60,10 +85,13 @@ public class WorldNBTStorage {
                 }
             }
             // Spigot End
-
-            if (normalFile) { // Akarin - avoid double I/O operation
+            // if (normalFile) { // Akarin - avoid double I/O operation // Yatopia
+            if (playerData != null) {
+                nbttagcompound = playerData;
+            } else if (normalFile) { // Akarin - avoid double I/O operation
                 nbttagcompound = NBTCompressedStreamTools.a(file);
             }
+            // Yatopia end
             // Spigot Start
             if ( usingWrongFile )
             {
diff --git a/src/main/java/org/yatopiamc/yatopia/server/cache/NBTCache.java b/src/main/java/org/yatopiamc/yatopia/server/cache/NBTCache.java
new file mode 100644
index 0000000000000000000000000000000000000000..942163396e41795b1b5624348e42a0c0325703f8
--- /dev/null
+++ b/src/main/java/org/yatopiamc/yatopia/server/cache/NBTCache.java
@@ -0,0 +1,32 @@
+package org.yatopiamc.yatopia.server.cache;
+
+import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenCustomHashMap;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.NBTTagCompound;
+
+import java.io.File;
+
+public class NBTCache extends Object2ObjectLinkedOpenCustomHashMap<File, NBTTagCompound> {
+
+    public NBTCache() {
+        super(100, 0.75F, new Strategy<File>() {
+            @Override
+            public int hashCode(File k) {
+                return k.hashCode();
+            }
+
+            @Override
+            public boolean equals(File k, File k1) {
+                return k.equals(k1);
+            }
+        });
+    }
+
+    @Override
+    public NBTTagCompound put(File k, NBTTagCompound v) {
+        if (this.size() > MinecraftServer.getServer().getPlayerCount()) {
+            this.removeLast();
+        }
+        return super.putAndMoveToFirst(k, v);
+    }
+}
