Async saving players data
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
package io.akarin.server.core;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
public class AkarinAsyncExecutor {
|
||||
private static final Executor singleExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("Akarin Single Async Executor Thread - %1$d").build());
|
||||
private static final Executor asyncExecutor = Executors.newFixedThreadPool(4, new ThreadFactoryBuilder().setNameFormat("Akarin Async Executor Thread - %1$d").build());
|
||||
private static final ExecutorService singleExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("Akarin Single Async Executor Thread - %1$d").build());
|
||||
private static final ExecutorService asyncExecutor = Executors.newFixedThreadPool(4, new ThreadFactoryBuilder().setNameFormat("Akarin Async Executor Thread - %1$d").build());
|
||||
|
||||
/**
|
||||
* Posts a task to be executed asynchronously in a single thread
|
||||
@@ -24,4 +26,21 @@ public class AkarinAsyncExecutor {
|
||||
public static void scheduleAsyncTask(Runnable run) {
|
||||
asyncExecutor.execute(run);
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts a task to be executed asynchronously in a single thread
|
||||
* @param run
|
||||
* @return
|
||||
*/
|
||||
public static <V> Future<V> scheduleSingleAsyncTask(Callable<V> run) {
|
||||
return singleExecutor.submit(run);
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts a task to be executed asynchronously
|
||||
* @param run
|
||||
*/
|
||||
public static <V> Future<V> scheduleAsyncTask(Callable<V> run) {
|
||||
return asyncExecutor.submit(run);
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,15 @@ public class AkarinAsyncScheduler extends Thread {
|
||||
playerListTick = 0;
|
||||
}
|
||||
|
||||
// Save players data
|
||||
int playerSaveInterval = com.destroystokyo.paper.PaperConfig.playerAutoSaveRate;
|
||||
if (playerSaveInterval < 0) {
|
||||
playerSaveInterval = server.autosavePeriod;
|
||||
}
|
||||
if (playerSaveInterval > 0) {
|
||||
server.getPlayerList().savePlayers(playerSaveInterval);
|
||||
}
|
||||
|
||||
try {
|
||||
long sleepFixed = STD_TICK_TIME - (System.currentTimeMillis() - currentLoop);
|
||||
if (sleepFixed > 0) Thread.sleep(sleepFixed);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.minecraft.server;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
@@ -11,13 +12,18 @@ import com.google.gson.JsonParseException;
|
||||
import com.google.gson.internal.Streams;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.koloboke.collect.map.hash.HashObjObjMaps;
|
||||
import com.mojang.datafixers.DataFixTypes;
|
||||
import com.mojang.datafixers.Dynamic;
|
||||
import com.mojang.datafixers.types.JsonOps;
|
||||
|
||||
import io.akarin.server.core.AkarinAsyncExecutor;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -196,9 +202,31 @@ public class AdvancementDataPlayer {
|
||||
this.d();
|
||||
}
|
||||
|
||||
// Akarin start - copied from below
|
||||
public Map<MinecraftKey, AdvancementProgress> toSerializableMap() {
|
||||
return org.spigotmc.SpigotConfig.disableAdvancementSaving ?
|
||||
HashObjObjMaps.newImmutableMap(
|
||||
Collections2.transform(data.keySet(), Advancement::getName),
|
||||
data.values().stream()
|
||||
.filter(AdvancementProgress::b)
|
||||
.collect(Collectors.toSet())
|
||||
) : Collections.emptyMap();
|
||||
}
|
||||
public void save(Map<MinecraftKey, AdvancementProgress> serializableMap) {
|
||||
if (this.e.getParentFile() != null) {
|
||||
this.e.getParentFile().mkdirs();
|
||||
}
|
||||
|
||||
try {
|
||||
Files.write(AdvancementDataPlayer.b.toJson(serializableMap), this.e, StandardCharsets.UTF_8);
|
||||
} catch (IOException ioexception) {
|
||||
AdvancementDataPlayer.a.error("Couldn't save player advancements to {}", this.e, ioexception);
|
||||
}
|
||||
}
|
||||
// Akarin end
|
||||
public void c() {
|
||||
if (org.spigotmc.SpigotConfig.disableAdvancementSaving) return;
|
||||
Map<MinecraftKey, AdvancementProgress> map = Maps.newHashMap();
|
||||
Map<MinecraftKey, AdvancementProgress> map = HashObjObjMaps.newMutableMap(); // Akarin
|
||||
Iterator iterator = this.data.entrySet().iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
|
||||
@@ -216,7 +216,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
||||
this.justCreated = true;
|
||||
this.uniqueID = MathHelper.a(java.util.concurrent.ThreadLocalRandom.current()); // Paper
|
||||
this.au = this.uniqueID.toString();
|
||||
this.aJ = Sets.newHashSet();
|
||||
this.aJ = Collections.synchronizedSet(Sets.newHashSet()); // Akarin - synchronized to protect plugins
|
||||
this.aL = new double[] { 0.0D, 0.0D, 0.0D};
|
||||
this.g = entitytypes;
|
||||
this.world = world;
|
||||
@@ -257,11 +257,15 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
||||
}
|
||||
|
||||
public boolean addScoreboardTag(String s) {
|
||||
synchronized (this.aJ) { // Akarin
|
||||
return this.aJ.size() >= 1024 ? false : this.aJ.add(s);
|
||||
} // Akarin
|
||||
}
|
||||
|
||||
public boolean removeScoreboardTag(String s) {
|
||||
synchronized (this.aJ) { // Akarin
|
||||
return this.aJ.remove(s);
|
||||
} // Akarin
|
||||
}
|
||||
|
||||
public void killEntity() {
|
||||
@@ -1654,6 +1658,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
||||
NBTTagList nbttaglist;
|
||||
Iterator iterator;
|
||||
|
||||
synchronized (this.aJ) { // Akarin
|
||||
if (!this.aJ.isEmpty()) {
|
||||
nbttaglist = new NBTTagList();
|
||||
iterator = this.aJ.iterator();
|
||||
@@ -1666,6 +1671,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
||||
|
||||
nbttagcompound.set("Tags", nbttaglist);
|
||||
}
|
||||
} // Akarin
|
||||
|
||||
this.b(nbttagcompound);
|
||||
if (this.isVehicle()) {
|
||||
@@ -1773,6 +1779,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
||||
this.setNoGravity(nbttagcompound.getBoolean("NoGravity"));
|
||||
this.h(nbttagcompound.getBoolean("Glowing"));
|
||||
if (nbttagcompound.hasKeyOfType("Tags", 9)) {
|
||||
synchronized (this.aJ) { // Akarin
|
||||
this.aJ.clear();
|
||||
NBTTagList nbttaglist3 = nbttagcompound.getList("Tags", 8);
|
||||
int i = Math.min(nbttaglist3.size(), 1024);
|
||||
@@ -1780,6 +1787,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
||||
for (int j = 0; j < i; ++j) {
|
||||
this.aJ.add(nbttaglist3.getString(j));
|
||||
}
|
||||
} // Akarin
|
||||
}
|
||||
|
||||
this.a(nbttagcompound);
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.google.common.collect.Sets;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
|
||||
import io.akarin.server.core.AkarinAsyncExecutor;
|
||||
import io.akarin.server.core.AkarinAsyncScheduler;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.io.File;
|
||||
import java.net.SocketAddress;
|
||||
@@ -343,21 +344,35 @@ public abstract class PlayerList {
|
||||
return nbttagcompound1;
|
||||
}
|
||||
|
||||
// Akarin start
|
||||
protected void savePlayerFile(EntityPlayer entityplayer) {
|
||||
savePlayerFile(entityplayer, true);
|
||||
}
|
||||
// Akarin end
|
||||
protected void savePlayerFile(EntityPlayer entityplayer, boolean async) {
|
||||
if (!entityplayer.getBukkitEntity().isPersistent()) return; // CraftBukkit
|
||||
entityplayer.lastSave = MinecraftServer.currentTick; // Paper
|
||||
this.playerFileData.save(entityplayer);
|
||||
//this.playerFileData.save(entityplayer); // Akarin - moved down
|
||||
ServerStatisticManager serverstatisticmanager = (ServerStatisticManager) entityplayer.getStatisticManager(); // CraftBukkit
|
||||
|
||||
if (serverstatisticmanager != null) {
|
||||
serverstatisticmanager.a();
|
||||
//serverstatisticmanager.a(); // Akarin - moved down
|
||||
}
|
||||
|
||||
AdvancementDataPlayer advancementdataplayer = (AdvancementDataPlayer) entityplayer.getAdvancementData(); // CraftBukkit
|
||||
|
||||
Map<MinecraftKey, AdvancementProgress> advancements = advancementdataplayer.toSerializableMap(); // Akarin
|
||||
if (advancementdataplayer != null) {
|
||||
advancementdataplayer.c();
|
||||
}
|
||||
// Akarin start
|
||||
AkarinAsyncExecutor.scheduleSingleAsyncTask(() -> {
|
||||
this.playerFileData.save(entityplayer);
|
||||
if (serverstatisticmanager != null)
|
||||
serverstatisticmanager.a();
|
||||
advancementdataplayer.save(advancements);
|
||||
});
|
||||
// Akarin end
|
||||
|
||||
}
|
||||
|
||||
@@ -654,6 +669,7 @@ public abstract class PlayerList {
|
||||
entityplayer1.copyFrom(entityplayer, flag);
|
||||
entityplayer1.e(entityplayer.getId());
|
||||
entityplayer1.a(entityplayer.getMainHand());
|
||||
synchronized (entityplayer.getScoreboardTags()) { // Akarin
|
||||
Iterator iterator = entityplayer.getScoreboardTags().iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
@@ -661,6 +677,7 @@ public abstract class PlayerList {
|
||||
|
||||
entityplayer1.addScoreboardTag(s);
|
||||
}
|
||||
} // Akarin
|
||||
|
||||
// WorldServer worldserver = this.server.getWorldServer(entityplayer.dimension); // CraftBukkit - handled later
|
||||
|
||||
@@ -1267,19 +1284,19 @@ public abstract class PlayerList {
|
||||
}
|
||||
|
||||
public void savePlayers(Integer interval) {
|
||||
MCUtil.ensureMain("Save Players", () -> { // Paper - ensure main
|
||||
//MCUtil.ensureMain("Save Players", () -> { // Paper - ensure main // Akarin
|
||||
long now = MinecraftServer.currentTick;
|
||||
MinecraftTimings.savePlayers.startTimingUnsafe(); // Paper
|
||||
//MinecraftTimings.savePlayers.startTiming(); // Paper // Akarin
|
||||
int numSaved = 0; // Paper
|
||||
for (int i = 0; i < this.players.size(); ++i) {
|
||||
EntityPlayer entityplayer = this.players.get(i);
|
||||
if (interval == null || now - entityplayer.lastSave >= interval) {
|
||||
this.savePlayerFile(entityplayer);
|
||||
this.savePlayerFile(entityplayer, false);
|
||||
if (interval != null && ++numSaved <= com.destroystokyo.paper.PaperConfig.maxPlayerAutoSavePerTick) { break; } // Paper
|
||||
}
|
||||
}
|
||||
MinecraftTimings.savePlayers.stopTimingUnsafe(); // Paper
|
||||
return null; }); // Paper - ensure main
|
||||
//MinecraftTimings.savePlayers.stopTiming(); // Paper // Akarin
|
||||
//return null; }); // Paper - ensure main // Akarin
|
||||
}
|
||||
// Paper end
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@ package net.minecraft.server;
|
||||
|
||||
import com.mojang.datafixers.DataFixTypes;
|
||||
import com.mojang.datafixers.DataFixer;
|
||||
|
||||
import io.akarin.server.core.AkarinAsyncExecutor;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
@@ -29,6 +32,7 @@ public class WorldNBTStorage implements IDataManager, IPlayerFileData {
|
||||
private final DefinedStructureManager g;
|
||||
protected final DataFixer a;
|
||||
private UUID uuid = null; // CraftBukkit
|
||||
final Object playerFileLock = new Object(); // Akarin
|
||||
|
||||
public WorldNBTStorage(File file, String s, @Nullable MinecraftServer minecraftserver, DataFixer datafixer) {
|
||||
this.a = datafixer;
|
||||
@@ -192,22 +196,37 @@ public class WorldNBTStorage implements IDataManager, IPlayerFileData {
|
||||
this.saveWorldData(worlddata, (NBTTagCompound) null);
|
||||
}
|
||||
|
||||
// Akarin start
|
||||
public void save(EntityHuman entityhuman) {
|
||||
save(entityhuman, true);
|
||||
}
|
||||
// Akarin end
|
||||
public void save(EntityHuman entityhuman, boolean async) {
|
||||
if(!com.destroystokyo.paper.PaperConfig.savePlayerData) return; // Paper - Make player data saving configurable
|
||||
Runnable runnable = () -> { // Akarin
|
||||
try {
|
||||
NBTTagCompound nbttagcompound = entityhuman.save(new NBTTagCompound());
|
||||
File file = new File(this.playerDir, entityhuman.bu() + ".dat.tmp");
|
||||
File file1 = new File(this.playerDir, entityhuman.bu() + ".dat");
|
||||
|
||||
synchronized (playerFileLock) { // Akarin
|
||||
NBTCompressedStreamTools.a(nbttagcompound, (OutputStream) (new FileOutputStream(file)));
|
||||
if (file1.exists()) {
|
||||
file1.delete();
|
||||
}
|
||||
|
||||
file.renameTo(file1);
|
||||
} // Akarin
|
||||
} catch (Exception exception) {
|
||||
WorldNBTStorage.b.error("Failed to save player data for {}", entityhuman.getName(), exception); // Paper
|
||||
}
|
||||
// Akarin start
|
||||
};
|
||||
if (async)
|
||||
AkarinAsyncExecutor.scheduleSingleAsyncTask(runnable);
|
||||
else
|
||||
runnable.run();
|
||||
// Akarin end
|
||||
|
||||
}
|
||||
|
||||
@@ -219,6 +238,7 @@ public class WorldNBTStorage implements IDataManager, IPlayerFileData {
|
||||
File file = new File(this.playerDir, entityhuman.bu() + ".dat");
|
||||
// Spigot Start
|
||||
boolean usingWrongFile = false;
|
||||
synchronized (playerFileLock) { // Akarin
|
||||
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
|
||||
{
|
||||
@@ -239,6 +259,7 @@ public class WorldNBTStorage implements IDataManager, IPlayerFileData {
|
||||
{
|
||||
file.renameTo( new File( file.getPath() + ".offline-read" ) );
|
||||
}
|
||||
} // Akarin
|
||||
// Spigot End
|
||||
} catch (Exception exception) {
|
||||
WorldNBTStorage.b.warn("Failed to load player data for {}", entityhuman.getDisplayName().getString());
|
||||
@@ -266,11 +287,13 @@ public class WorldNBTStorage implements IDataManager, IPlayerFileData {
|
||||
// CraftBukkit start
|
||||
public NBTTagCompound getPlayerData(String s) {
|
||||
try {
|
||||
synchronized (playerFileLock) { // Akarin
|
||||
File file1 = new File(this.playerDir, s + ".dat");
|
||||
|
||||
if (file1.exists()) {
|
||||
return NBTCompressedStreamTools.a((InputStream) (new FileInputStream(file1)));
|
||||
}
|
||||
} // Akarin
|
||||
} catch (Exception exception) {
|
||||
b.warn("Failed to load player data for " + s);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user