Async saving players data

This commit is contained in:
Sotr
2019-03-28 15:11:12 +08:00
parent 5a4bc4b4ba
commit 61b00d4ee8
6 changed files with 116 additions and 12 deletions

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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()) {

View File

@@ -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);

View File

@@ -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

View File

@@ -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);
}