Use thread executors to speed up the database loading process

This commit is contained in:
KingRainbow44
2023-05-30 15:38:26 -04:00
parent 2009a90e66
commit 36a35c11aa
6 changed files with 184 additions and 79 deletions

View File

@@ -1,8 +1,5 @@
package emu.grasscutter; package emu.grasscutter;
import static emu.grasscutter.config.Configuration.SERVER;
import static emu.grasscutter.utils.lang.Language.translate;
import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.Logger;
import emu.grasscutter.auth.AuthenticationSystem; import emu.grasscutter.auth.AuthenticationSystem;
@@ -33,12 +30,7 @@ import emu.grasscutter.utils.JsonUtils;
import emu.grasscutter.utils.StartupArguments; import emu.grasscutter.utils.StartupArguments;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import emu.grasscutter.utils.lang.Language; import emu.grasscutter.utils.lang.Language;
import java.io.File; import io.netty.util.concurrent.FastThreadLocalThread;
import java.io.FileWriter;
import java.io.IOError;
import java.io.IOException;
import java.util.Calendar;
import javax.annotation.Nullable;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.jline.reader.EndOfFileException; import org.jline.reader.EndOfFileException;
@@ -50,6 +42,20 @@ import org.jline.terminal.TerminalBuilder;
import org.reflections.Reflections; import org.reflections.Reflections;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.File;
import java.io.FileWriter;
import java.io.IOError;
import java.io.IOException;
import java.util.Calendar;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import static emu.grasscutter.config.Configuration.SERVER;
import static emu.grasscutter.utils.lang.Language.translate;
public final class Grasscutter { public final class Grasscutter {
public static final File configFile = new File("./config.json"); public static final File configFile = new File("./config.json");
public static final Reflections reflector = new Reflections("emu.grasscutter"); public static final Reflections reflector = new Reflections("emu.grasscutter");
@@ -75,6 +81,13 @@ public final class Grasscutter {
private static LineReader consoleLineReader = null; private static LineReader consoleLineReader = null;
@Getter private static final ExecutorService threadPool = new ThreadPoolExecutor(
6, 6, 60, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(),
FastThreadLocalThread::new,
new ThreadPoolExecutor.AbortPolicy()
);
static { static {
// Declare logback configuration. // Declare logback configuration.
System.setProperty("logback.configurationFile", "src/main/resources/logback.xml"); System.setProperty("logback.configurationFile", "src/main/resources/logback.xml");

View File

@@ -1,8 +1,5 @@
package emu.grasscutter.database; package emu.grasscutter.database;
import static com.mongodb.client.model.Filters.eq;
import com.mongodb.client.result.DeleteResult;
import dev.morphia.query.FindOptions; import dev.morphia.query.FindOptions;
import dev.morphia.query.Sort; import dev.morphia.query.Sort;
import dev.morphia.query.experimental.filters.Filters; import dev.morphia.query.experimental.filters.Filters;
@@ -22,10 +19,74 @@ import emu.grasscutter.game.mail.Mail;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.GameMainQuest; import emu.grasscutter.game.quest.GameMainQuest;
import emu.grasscutter.game.world.SceneGroupInstance; import emu.grasscutter.game.world.SceneGroupInstance;
import emu.grasscutter.utils.objects.Returnable;
import io.netty.util.concurrent.FastThreadLocalThread;
import java.util.List; import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Stream; import java.util.stream.Stream;
import static com.mongodb.client.model.Filters.eq;
public final class DatabaseHelper { public final class DatabaseHelper {
private static final ExecutorService eventExecutor = new ThreadPoolExecutor(
6, 6, 60, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(),
FastThreadLocalThread::new,
new ThreadPoolExecutor.AbortPolicy()
);
/**
* Saves an object on the account datastore.
*
* @param object The object to save.
*/
public static void saveAccountAsync(Object object) {
DatabaseHelper.eventExecutor.submit(() ->
DatabaseManager.getAccountDatastore().save(object));
}
/**
* Saves an object on the game datastore.
*
* @param object The object to save.
*/
public static void saveGameAsync(Object object) {
DatabaseHelper.eventExecutor.submit(() ->
DatabaseHelper.saveGameAsync(object));
}
/**
* Runs a runnable on the event executor.
* Should be limited to database-related operations.
*
* @param runnable The runnable to run.
*/
public static void asyncOperation(Runnable runnable) {
DatabaseHelper.eventExecutor.submit(runnable);
}
/**
* Fetches an object asynchronously.
*
* @param task The task to run.
* @return The future.
*/
public static <T> CompletableFuture<T> fetchAsync(Returnable<T> task) {
var future = new CompletableFuture<T>();
// Run the task on the event executor.
DatabaseHelper.eventExecutor.submit(() -> {
try {
future.complete(task.invoke());
} catch (Exception e) {
future.completeExceptionally(e);
}
});
return future;
}
public static Account createAccount(String username) { public static Account createAccount(String username) {
return createAccountWithUid(username, 0); return createAccountWithUid(username, 0);
} }
@@ -85,7 +146,7 @@ public final class DatabaseHelper {
} }
public static void saveAccount(Account account) { public static void saveAccount(Account account) {
DatabaseManager.getAccountDatastore().save(account); DatabaseHelper.saveAccountAsync(account);
} }
public static Account getAccountByName(String username) { public static Account getAccountByName(String username) {
@@ -159,31 +220,34 @@ public final class DatabaseHelper {
if (player == null) return; if (player == null) return;
} }
int uid = player.getUid(); int uid = player.getUid();
// Delete data from collections
DatabaseManager.getGameDatabase().getCollection("achievements").deleteMany(eq("uid", uid));
DatabaseManager.getGameDatabase().getCollection("activities").deleteMany(eq("uid", uid));
DatabaseManager.getGameDatabase().getCollection("homes").deleteMany(eq("ownerUid", uid));
DatabaseManager.getGameDatabase().getCollection("mail").deleteMany(eq("ownerUid", uid));
DatabaseManager.getGameDatabase().getCollection("avatars").deleteMany(eq("ownerId", uid));
DatabaseManager.getGameDatabase().getCollection("gachas").deleteMany(eq("ownerId", uid));
DatabaseManager.getGameDatabase().getCollection("items").deleteMany(eq("ownerId", uid));
DatabaseManager.getGameDatabase().getCollection("quests").deleteMany(eq("ownerUid", uid));
DatabaseManager.getGameDatabase().getCollection("battlepass").deleteMany(eq("ownerUid", uid));
// Delete friendships. DatabaseHelper.asyncOperation(() -> {
// Here, we need to make sure to not only delete the deleted account's friendships, // Delete data from collections
// but also all friendship entries for that account's friends. DatabaseManager.getGameDatabase().getCollection("achievements").deleteMany(eq("uid", uid));
DatabaseManager.getGameDatabase().getCollection("friendships").deleteMany(eq("ownerId", uid)); DatabaseManager.getGameDatabase().getCollection("activities").deleteMany(eq("uid", uid));
DatabaseManager.getGameDatabase().getCollection("friendships").deleteMany(eq("friendId", uid)); DatabaseManager.getGameDatabase().getCollection("homes").deleteMany(eq("ownerUid", uid));
DatabaseManager.getGameDatabase().getCollection("mail").deleteMany(eq("ownerUid", uid));
DatabaseManager.getGameDatabase().getCollection("avatars").deleteMany(eq("ownerId", uid));
DatabaseManager.getGameDatabase().getCollection("gachas").deleteMany(eq("ownerId", uid));
DatabaseManager.getGameDatabase().getCollection("items").deleteMany(eq("ownerId", uid));
DatabaseManager.getGameDatabase().getCollection("quests").deleteMany(eq("ownerUid", uid));
DatabaseManager.getGameDatabase().getCollection("battlepass").deleteMany(eq("ownerUid", uid));
// Delete the player last. // Delete friendships.
DatabaseManager.getGameDatastore().find(Player.class).filter(Filters.eq("id", uid)).delete(); // Here, we need to make sure to not only delete the deleted account's friendships,
// but also all friendship entries for that account's friends.
DatabaseManager.getGameDatabase().getCollection("friendships").deleteMany(eq("ownerId", uid));
DatabaseManager.getGameDatabase().getCollection("friendships").deleteMany(eq("friendId", uid));
// Finally, delete the account itself. // Delete the player last.
DatabaseManager.getAccountDatastore() DatabaseManager.getGameDatastore().find(Player.class).filter(Filters.eq("id", uid)).delete();
// Finally, delete the account itself.
DatabaseManager.getAccountDatastore()
.find(Account.class) .find(Account.class)
.filter(Filters.eq("id", target.getId())) .filter(Filters.eq("id", target.getId()))
.delete(); .delete();
});
} }
public static <T> Stream<T> getByGameClass(Class<T> classType) { public static <T> Stream<T> getByGameClass(Class<T> classType) {
@@ -239,7 +303,7 @@ public final class DatabaseHelper {
> 0; > 0;
} }
public static synchronized Player generatePlayerUid(Player character, int reservedId) { public static synchronized void generatePlayerUid(Player character, int reservedId) {
// Check if reserved id // Check if reserved id
int id; int id;
if (reservedId > 0 && !checkIfPlayerExists(reservedId)) { if (reservedId > 0 && !checkIfPlayerExists(reservedId)) {
@@ -251,9 +315,9 @@ public final class DatabaseHelper {
} while (checkIfPlayerExists(id)); } while (checkIfPlayerExists(id));
character.setUid(id); character.setUid(id);
} }
// Save to database // Save to database
DatabaseManager.getGameDatastore().save(character); DatabaseHelper.saveGameAsync(character);
return character;
} }
public static synchronized int getNextPlayerId(int reservedId) { public static synchronized int getNextPlayerId(int reservedId) {
@@ -270,36 +334,48 @@ public final class DatabaseHelper {
} }
public static void savePlayer(Player character) { public static void savePlayer(Player character) {
DatabaseManager.getGameDatastore().save(character); DatabaseHelper.saveGameAsync(character);
} }
public static void saveAvatar(Avatar avatar) { public static void saveAvatar(Avatar avatar) {
DatabaseManager.getGameDatastore().save(avatar); DatabaseHelper.saveGameAsync(avatar);
} }
/**
* Fetches all avatars of a player.
*
* @param player The player.
* @return The list of avatars.
*/
public static List<Avatar> getAvatars(Player player) { public static List<Avatar> getAvatars(Player player) {
return DatabaseManager.getGameDatastore() return DatabaseManager.getGameDatastore()
.find(Avatar.class) .find(Avatar.class)
.filter(Filters.eq("ownerId", player.getUid())) .filter(Filters.eq("ownerId", player.getUid()))
.stream() .stream()
.toList(); .toList();
} }
public static void saveItem(GameItem item) { public static void saveItem(GameItem item) {
DatabaseManager.getGameDatastore().save(item); DatabaseHelper.saveGameAsync(item);
} }
public static boolean deleteItem(GameItem item) { public static void deleteItem(GameItem item) {
DeleteResult result = DatabaseManager.getGameDatastore().delete(item); DatabaseHelper.asyncOperation(() ->
return result.wasAcknowledged(); DatabaseManager.getGameDatastore().delete(item));
} }
/**
* Fetches all items of a player.
*
* @param player The player.
* @return The list of items.
*/
public static List<GameItem> getInventoryItems(Player player) { public static List<GameItem> getInventoryItems(Player player) {
return DatabaseManager.getGameDatastore() return DatabaseManager.getGameDatastore()
.find(GameItem.class) .find(GameItem.class)
.filter(Filters.eq("ownerId", player.getUid())) .filter(Filters.eq("ownerId", player.getUid()))
.stream() .stream()
.toList(); .toList();
} }
public static List<Friendship> getFriends(Player player) { public static List<Friendship> getFriends(Player player) {
@@ -319,11 +395,12 @@ public final class DatabaseHelper {
} }
public static void saveFriendship(Friendship friendship) { public static void saveFriendship(Friendship friendship) {
DatabaseManager.getGameDatastore().save(friendship); DatabaseHelper.saveGameAsync(friendship);
} }
public static void deleteFriendship(Friendship friendship) { public static void deleteFriendship(Friendship friendship) {
DatabaseManager.getGameDatastore().delete(friendship); DatabaseHelper.asyncOperation(() ->
DatabaseManager.getGameDatastore().delete(friendship));
} }
public static Friendship getReverseFriendship(Friendship friendship) { public static Friendship getReverseFriendship(Friendship friendship) {
@@ -367,7 +444,7 @@ public final class DatabaseHelper {
} }
public static void saveGachaRecord(GachaRecord gachaRecord) { public static void saveGachaRecord(GachaRecord gachaRecord) {
DatabaseManager.getGameDatastore().save(gachaRecord); DatabaseHelper.saveGameAsync(gachaRecord);
} }
public static List<Mail> getAllMail(Player player) { public static List<Mail> getAllMail(Player player) {
@@ -379,12 +456,12 @@ public final class DatabaseHelper {
} }
public static void saveMail(Mail mail) { public static void saveMail(Mail mail) {
DatabaseManager.getGameDatastore().save(mail); DatabaseHelper.saveGameAsync(mail);
} }
public static boolean deleteMail(Mail mail) { public static void deleteMail(Mail mail) {
DeleteResult result = DatabaseManager.getGameDatastore().delete(mail); DatabaseHelper.asyncOperation(() ->
return result.wasAcknowledged(); DatabaseManager.getGameDatastore().delete(mail));
} }
public static List<GameMainQuest> getAllQuests(Player player) { public static List<GameMainQuest> getAllQuests(Player player) {
@@ -396,11 +473,12 @@ public final class DatabaseHelper {
} }
public static void saveQuest(GameMainQuest quest) { public static void saveQuest(GameMainQuest quest) {
DatabaseManager.getGameDatastore().save(quest); DatabaseHelper.saveGameAsync(quest);
} }
public static boolean deleteQuest(GameMainQuest quest) { public static void deleteQuest(GameMainQuest quest) {
return DatabaseManager.getGameDatastore().delete(quest).wasAcknowledged(); DatabaseHelper.asyncOperation(() ->
DatabaseManager.getGameDatastore().delete(quest));
} }
public static GameHome getHomeByUid(int id) { public static GameHome getHomeByUid(int id) {
@@ -411,7 +489,7 @@ public final class DatabaseHelper {
} }
public static void saveHome(GameHome gameHome) { public static void saveHome(GameHome gameHome) {
DatabaseManager.getGameDatastore().save(gameHome); DatabaseHelper.saveGameAsync(gameHome);
} }
public static BattlePassManager loadBattlePass(Player player) { public static BattlePassManager loadBattlePass(Player player) {
@@ -430,7 +508,7 @@ public final class DatabaseHelper {
} }
public static void saveBattlePass(BattlePassManager manager) { public static void saveBattlePass(BattlePassManager manager) {
DatabaseManager.getGameDatastore().save(manager); DatabaseHelper.saveGameAsync(manager);
} }
public static PlayerActivityData getPlayerActivityData(int uid, int activityId) { public static PlayerActivityData getPlayerActivityData(int uid, int activityId) {
@@ -441,7 +519,7 @@ public final class DatabaseHelper {
} }
public static void savePlayerActivityData(PlayerActivityData playerActivityData) { public static void savePlayerActivityData(PlayerActivityData playerActivityData) {
DatabaseManager.getGameDatastore().save(playerActivityData); DatabaseHelper.saveGameAsync(playerActivityData);
} }
public static MusicGameBeatmap getMusicGameBeatmap(long musicShareId) { public static MusicGameBeatmap getMusicGameBeatmap(long musicShareId) {
@@ -452,7 +530,7 @@ public final class DatabaseHelper {
} }
public static void saveMusicGameBeatmap(MusicGameBeatmap musicGameBeatmap) { public static void saveMusicGameBeatmap(MusicGameBeatmap musicGameBeatmap) {
DatabaseManager.getGameDatastore().save(musicGameBeatmap); DatabaseHelper.saveGameAsync(musicGameBeatmap);
} }
public static Achievements getAchievementData(int uid) { public static Achievements getAchievementData(int uid) {
@@ -463,11 +541,11 @@ public final class DatabaseHelper {
} }
public static void saveAchievementData(Achievements achievements) { public static void saveAchievementData(Achievements achievements) {
DatabaseManager.getGameDatastore().save(achievements); DatabaseHelper.saveGameAsync(achievements);
} }
public static void saveGroupInstance(SceneGroupInstance instance) { public static void saveGroupInstance(SceneGroupInstance instance) {
DatabaseManager.getGameDatastore().save(instance); DatabaseHelper.saveGameAsync(instance);
} }
public static SceneGroupInstance loadGroupInstance(int groupId, Player owner) { public static SceneGroupInstance loadGroupInstance(int groupId, Player owner) {

View File

@@ -1,7 +1,5 @@
package emu.grasscutter.database; package emu.grasscutter.database;
import static emu.grasscutter.config.Configuration.DATABASE;
import com.mongodb.MongoCommandException; import com.mongodb.MongoCommandException;
import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients; import com.mongodb.client.MongoClients;
@@ -18,6 +16,8 @@ import emu.grasscutter.Grasscutter.ServerRunMode;
import emu.grasscutter.game.Account; import emu.grasscutter.game.Account;
import org.reflections.Reflections; import org.reflections.Reflections;
import static emu.grasscutter.config.Configuration.DATABASE;
public final class DatabaseManager { public final class DatabaseManager {
private static Datastore gameDatastore; private static Datastore gameDatastore;
private static Datastore dispatchDatastore; private static Datastore dispatchDatastore;
@@ -107,10 +107,11 @@ public final class DatabaseManager {
if (counter == null) { if (counter == null) {
counter = new DatabaseCounter(c.getSimpleName()); counter = new DatabaseCounter(c.getSimpleName());
} }
try { try {
return counter.getNextId(); return counter.getNextId();
} finally { } finally {
getGameDatastore().save(counter); DatabaseHelper.saveGameAsync(counter);
} }
} }

View File

@@ -1,7 +1,5 @@
package emu.grasscutter.game.inventory; package emu.grasscutter.game.inventory;
import static emu.grasscutter.config.Configuration.INVENTORY_LIMITS;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.common.ItemParamData;
@@ -22,11 +20,14 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import static emu.grasscutter.config.Configuration.INVENTORY_LIMITS;
public class Inventory extends BasePlayerManager implements Iterable<GameItem> { public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
private final Long2ObjectMap<GameItem> store; private final Long2ObjectMap<GameItem> store;
private final Int2ObjectMap<InventoryTab> inventoryTypes; private final Int2ObjectMap<InventoryTab> inventoryTypes;
@@ -572,6 +573,9 @@ public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
} }
} }
} }
// Load avatars after inventory.
this.getPlayer().getAvatars().postLoad();
} }
@Override @Override

View File

@@ -1313,16 +1313,17 @@ public class Player implements PlayerHook, FieldFetch {
} }
// Load from db // Load from db
this.achievements = Achievements.getByPlayer(this); var runner = Grasscutter.getThreadPool();
this.getAvatars().loadFromDatabase(); runner.submit(() -> this.achievements = Achievements.getByPlayer(this));
this.getInventory().loadFromDatabase();
this.getFriendsList().loadFromDatabase(); runner.submit(this.getAvatars()::loadFromDatabase);
this.getMailHandler().loadFromDatabase(); runner.submit(this.getInventory()::loadFromDatabase);
this.getQuestManager().loadFromDatabase();
this.loadBattlePassManager(); runner.submit(this.getFriendsList()::loadFromDatabase);
this.getAvatars().postLoad(); // Needs to be called after inventory is handled runner.submit(this.getMailHandler()::loadFromDatabase);
runner.submit(this.getQuestManager()::loadFromDatabase);
runner.submit(this::loadBattlePassManager);
this.getPlayerProgress().setPlayer(this); // Add reference to the player. this.getPlayerProgress().setPlayer(this); // Add reference to the player.
} }
@@ -1397,7 +1398,7 @@ public class Player implements PlayerHook, FieldFetch {
home = GameHome.getByUid(getUid()); home = GameHome.getByUid(getUid());
home.onOwnerLogin(this); home.onOwnerLogin(this);
// Activity // Activity
activityManager = new ActivityManager(this); this.activityManager = new ActivityManager(this);
session.send(new PacketPlayerEnterSceneNotify(this)); // Enter game world session.send(new PacketPlayerEnterSceneNotify(this)); // Enter game world
session.send(new PacketPlayerLevelRewardUpdateNotify(rewardedLevels)); session.send(new PacketPlayerLevelRewardUpdateNotify(rewardedLevels));

View File

@@ -0,0 +1,8 @@
package emu.grasscutter.utils.objects;
public interface Returnable<T> {
/**
* @return The value.
*/
T invoke();
}