From 676302c9a0d699e996fbd2999cc174c3c394ad3a Mon Sep 17 00:00:00 2001 From: WatchAndyTW Date: Sun, 21 Apr 2024 11:41:02 +0800 Subject: [PATCH] Database code optimization --- .gitignore | 1 + .../proto/GetPlayerTokenReqOuterClass.java | 72 ---------------- .../java/emu/grasscutter/Grasscutter.java | 3 + .../emu/grasscutter/database/Database.java | 86 +++++++++++++++++++ .../game/activity/PlayerActivityData.java | 23 ++++- .../emu/grasscutter/game/avatar/Avatar.java | 22 ++++- .../game/avatar/AvatarStorage.java | 4 +- .../grasscutter/game/inventory/Inventory.java | 35 ++++---- .../game/managers/energy/EnergyManager.java | 2 - .../emu/grasscutter/game/player/Player.java | 22 ++++- .../grasscutter/game/player/TeamManager.java | 10 ++- .../utils/objects/DatabaseObject.java | 42 +++++++++ 12 files changed, 220 insertions(+), 102 deletions(-) create mode 100644 src/main/java/emu/grasscutter/database/Database.java create mode 100644 src/main/java/emu/grasscutter/utils/objects/DatabaseObject.java diff --git a/.gitignore b/.gitignore index 7db9aad9b1a..c70e387bade 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,7 @@ tmp/ /languages /proto /lua +lib/kcp-1.5.1.jar /*.jar /*.sh diff --git a/src/generated/main/java/emu/grasscutter/net/proto/GetPlayerTokenReqOuterClass.java b/src/generated/main/java/emu/grasscutter/net/proto/GetPlayerTokenReqOuterClass.java index c9adb74d1c3..2967e462223 100644 --- a/src/generated/main/java/emu/grasscutter/net/proto/GetPlayerTokenReqOuterClass.java +++ b/src/generated/main/java/emu/grasscutter/net/proto/GetPlayerTokenReqOuterClass.java @@ -19,19 +19,11 @@ public interface GetPlayerTokenReqOrBuilder extends com.google.protobuf.MessageOrBuilder { /** - *
-     * 4.5.0
-     * 
- * * string client_rand_key = 514; * @return The clientRandKey. */ java.lang.String getClientRandKey(); /** - *
-     * 4.5.0
-     * 
- * * string client_rand_key = 514; * @return The bytes for clientRandKey. */ @@ -39,19 +31,11 @@ public interface GetPlayerTokenReqOrBuilder extends getClientRandKeyBytes(); /** - *
-     * 4.5.0
-     * 
- * * string account_uid = 14; * @return The accountUid. */ java.lang.String getAccountUid(); /** - *
-     * 4.5.0
-     * 
- * * string account_uid = 14; * @return The bytes for accountUid. */ @@ -433,10 +417,6 @@ private GetPlayerTokenReq( public static final int CLIENT_RAND_KEY_FIELD_NUMBER = 514; private volatile java.lang.Object clientRandKey_; /** - *
-     * 4.5.0
-     * 
- * * string client_rand_key = 514; * @return The clientRandKey. */ @@ -454,10 +434,6 @@ public java.lang.String getClientRandKey() { } } /** - *
-     * 4.5.0
-     * 
- * * string client_rand_key = 514; * @return The bytes for clientRandKey. */ @@ -479,10 +455,6 @@ public java.lang.String getClientRandKey() { public static final int ACCOUNT_UID_FIELD_NUMBER = 14; private volatile java.lang.Object accountUid_; /** - *
-     * 4.5.0
-     * 
- * * string account_uid = 14; * @return The accountUid. */ @@ -500,10 +472,6 @@ public java.lang.String getAccountUid() { } } /** - *
-     * 4.5.0
-     * 
- * * string account_uid = 14; * @return The bytes for accountUid. */ @@ -1619,10 +1587,6 @@ public Builder mergeFrom( private java.lang.Object clientRandKey_ = ""; /** - *
-       * 4.5.0
-       * 
- * * string client_rand_key = 514; * @return The clientRandKey. */ @@ -1639,10 +1603,6 @@ public java.lang.String getClientRandKey() { } } /** - *
-       * 4.5.0
-       * 
- * * string client_rand_key = 514; * @return The bytes for clientRandKey. */ @@ -1660,10 +1620,6 @@ public java.lang.String getClientRandKey() { } } /** - *
-       * 4.5.0
-       * 
- * * string client_rand_key = 514; * @param value The clientRandKey to set. * @return This builder for chaining. @@ -1679,10 +1635,6 @@ public Builder setClientRandKey( return this; } /** - *
-       * 4.5.0
-       * 
- * * string client_rand_key = 514; * @return This builder for chaining. */ @@ -1693,10 +1645,6 @@ public Builder clearClientRandKey() { return this; } /** - *
-       * 4.5.0
-       * 
- * * string client_rand_key = 514; * @param value The bytes for clientRandKey to set. * @return This builder for chaining. @@ -1715,10 +1663,6 @@ public Builder setClientRandKeyBytes( private java.lang.Object accountUid_ = ""; /** - *
-       * 4.5.0
-       * 
- * * string account_uid = 14; * @return The accountUid. */ @@ -1735,10 +1679,6 @@ public java.lang.String getAccountUid() { } } /** - *
-       * 4.5.0
-       * 
- * * string account_uid = 14; * @return The bytes for accountUid. */ @@ -1756,10 +1696,6 @@ public java.lang.String getAccountUid() { } } /** - *
-       * 4.5.0
-       * 
- * * string account_uid = 14; * @param value The accountUid to set. * @return This builder for chaining. @@ -1775,10 +1711,6 @@ public Builder setAccountUid( return this; } /** - *
-       * 4.5.0
-       * 
- * * string account_uid = 14; * @return This builder for chaining. */ @@ -1789,10 +1721,6 @@ public Builder clearAccountUid() { return this; } /** - *
-       * 4.5.0
-       * 
- * * string account_uid = 14; * @param value The bytes for accountUid to set. * @return This builder for chaining. diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index 8c3248dd7c2..6f4377917d4 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -189,6 +189,9 @@ public static void main(String[] args) throws Exception { /** Server shutdown event. */ private static void onShutdown() { + // Save all data. + Database.saveAll(); + // Disable all plugins. if (pluginManager != null) pluginManager.disablePlugins(); // Shutdown the game server. diff --git a/src/main/java/emu/grasscutter/database/Database.java b/src/main/java/emu/grasscutter/database/Database.java new file mode 100644 index 00000000000..32a60124167 --- /dev/null +++ b/src/main/java/emu/grasscutter/database/Database.java @@ -0,0 +1,86 @@ +package emu.grasscutter.database; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.avatar.Avatar; +import emu.grasscutter.utils.objects.DatabaseObject; +import org.slf4j.*; + +import java.util.*; +import java.util.concurrent.*; + +/** + * Complicated manager of the MongoDB database. + * Handles caching, data operations, and more. + */ +public interface Database { + Logger logger = LoggerFactory.getLogger("Database"); + List> objects = new CopyOnWriteArrayList<>(); + + /** + * Queues an object to be saved. + * + * @param object The object to save. + */ + static void save(DatabaseObject object) { + if (object.saveImmediately()) { + object.save(); + } else { + objects.add(object); + } + } + + /** + * Performs a bulk save of all deferred objects. + */ + static void saveAll() { + var size = objects.size(); + Database.saveAll(objects); + + logger.debug("Performed auto save on {} objects.", size); + } + + /** + * Performs a bulk save of all deferred objects. + * + * @param objects The objects to save. + */ + static void saveAll(List> objects) { + // Sort all objects into their respective databases. + var gameObjects = objects.stream() + .filter(DatabaseObject::isGameObject) + .toList(); + var accountObjects = objects.stream() + .filter(o -> !o.isGameObject()) + .toList(); + + // Clear the collective list. + objects.clear(); + + // Save all objects. + var executor = DatabaseHelper.getEventExecutor(); + if (Grasscutter.getRunMode() != Grasscutter.ServerRunMode.DISPATCH_ONLY) { + executor.submit(() -> { + DatabaseManager.getGameDatastore().save(gameObjects); + }); + } + if (Grasscutter.getRunMode() != Grasscutter.ServerRunMode.GAME_ONLY) { + executor.submit(() -> { + DatabaseManager.getAccountDatastore().save(accountObjects); + }); + } + } + + /** + * Starts the auto-save thread. + * Runs every 10s 1000为1秒. + */ + static void startSaveThread() { + var timer = new Timer(); + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + Database.saveAll(); + } + }, 0, 1000 * 10); + } +} diff --git a/src/main/java/emu/grasscutter/game/activity/PlayerActivityData.java b/src/main/java/emu/grasscutter/game/activity/PlayerActivityData.java index 61ecf9d7cc8..5c7f2be19e3 100644 --- a/src/main/java/emu/grasscutter/game/activity/PlayerActivityData.java +++ b/src/main/java/emu/grasscutter/game/activity/PlayerActivityData.java @@ -12,6 +12,8 @@ import emu.grasscutter.server.packet.send.PacketActivityUpdateWatcherNotify; import emu.grasscutter.utils.JsonUtils; import java.util.*; + +import emu.grasscutter.utils.objects.DatabaseObject; import lombok.*; import lombok.experimental.FieldDefaults; @@ -19,7 +21,7 @@ @Data @FieldDefaults(level = AccessLevel.PRIVATE) @Builder(builderMethodName = "of") -public class PlayerActivityData { +public class PlayerActivityData implements DatabaseObject { @Id String id; int uid; int activityId; @@ -34,8 +36,25 @@ public static PlayerActivityData getByPlayer(Player player, int activityId) { return DatabaseHelper.getPlayerActivityData(player.getUid(), activityId); } + /** + * Saves this object to the database. + * As of Grasscutter 1.7.1, this is by default a {@link DatabaseObject#deferSave()} call. + */ public void save() { - DatabaseHelper.savePlayerActivityData(this); + this.deferSave(); + } + + /** + * Saves this object to the database. + * + * @param immediate If true, this will be a {@link DatabaseObject#save()} call instead of a {@link DatabaseObject#deferSave()} call. + */ + public void save(boolean immediate) { + if (immediate) { + DatabaseObject.super.save(); + } else { + this.save(); + } } public synchronized void addWatcherProgress(int watcherId) { diff --git a/src/main/java/emu/grasscutter/game/avatar/Avatar.java b/src/main/java/emu/grasscutter/game/avatar/Avatar.java index be5774ed06d..f0f559f4fbb 100644 --- a/src/main/java/emu/grasscutter/game/avatar/Avatar.java +++ b/src/main/java/emu/grasscutter/game/avatar/Avatar.java @@ -31,6 +31,7 @@ import emu.grasscutter.net.proto.TrialAvatarInfoOuterClass.TrialAvatarInfo; import emu.grasscutter.server.packet.send.*; import emu.grasscutter.utils.helpers.ProtoHelper; +import emu.grasscutter.utils.objects.DatabaseObject; import it.unimi.dsi.fastutil.ints.*; import java.util.*; import java.util.stream.Stream; @@ -39,7 +40,7 @@ import org.bson.types.ObjectId; @Entity(value = "avatars", useDiscriminator = false) -public class Avatar { +public class Avatar implements DatabaseObject { @Transient @Getter private final Int2ObjectMap equips; @Transient @Getter private final Int2FloatOpenHashMap fightProperties; @Transient @Getter private final Int2FloatOpenHashMap fightPropOverrides; @@ -989,8 +990,25 @@ public int getEntityId() { return entity != null ? entity.getId() : 0; } + /** + * Saves this object to the database. + * As of Grasscutter 1.7.1, this is by default a {@link DatabaseObject#deferSave()} call. + */ public void save() { - DatabaseHelper.saveAvatar(this); + this.deferSave(); + } + + /** + * Saves this object to the database. + * + * @param immediate If true, this will be a {@link DatabaseObject#save()} call instead of a {@link DatabaseObject#deferSave()} call. + */ + public void save(boolean immediate) { + if (immediate) { + DatabaseObject.super.save(); + } else { + this.save(); + } } public AvatarInfo toProto() { diff --git a/src/main/java/emu/grasscutter/game/avatar/AvatarStorage.java b/src/main/java/emu/grasscutter/game/avatar/AvatarStorage.java index 1d2002e7bef..af4617179d5 100644 --- a/src/main/java/emu/grasscutter/game/avatar/AvatarStorage.java +++ b/src/main/java/emu/grasscutter/game/avatar/AvatarStorage.java @@ -60,7 +60,7 @@ public boolean addAvatar(Avatar avatar) { this.avatars.put(avatar.getAvatarId(), avatar); this.avatarsGuid.put(avatar.getGuid(), avatar); - avatar.save(); + avatar.save(true); return true; } @@ -165,7 +165,7 @@ public void loadFromDatabase() { if ((avatar.getAvatarId() == 10000007) || (avatar.getAvatarId() == 10000005)) { avatar.setSkillDepot(skillDepot); avatar.setSkillDepotData(skillDepot); - avatar.save(); + avatar.save(true); } } diff --git a/src/main/java/emu/grasscutter/game/inventory/Inventory.java b/src/main/java/emu/grasscutter/game/inventory/Inventory.java index 7967617c136..1082642001f 100644 --- a/src/main/java/emu/grasscutter/game/inventory/Inventory.java +++ b/src/main/java/emu/grasscutter/game/inventory/Inventory.java @@ -178,7 +178,7 @@ public void addItems(Collection items, ActionReason reason) { changedItems.add(result); } } - if (changedItems.size() == 0) { + if (changedItems.isEmpty()) { return; } if (reason != null) { @@ -298,8 +298,7 @@ private synchronized GameItem putItem(GameItem item) { // Add switch (type) { - case ITEM_WEAPON: - case ITEM_RELIQUARY: + case ITEM_WEAPON, ITEM_RELIQUARY -> { if (tab.getSize() >= tab.getMaxCapacity()) { return null; } @@ -310,23 +309,23 @@ private synchronized GameItem putItem(GameItem item) { // Set ownership and save to db item.save(); return item; - case ITEM_VIRTUAL: + } + case ITEM_VIRTUAL -> { // Handle this.addVirtualItem(item.getItemId(), item.getCount()); return item; - default: + } + default -> { switch (item.getItemData().getMaterialType()) { - case MATERIAL_AVATAR: - case MATERIAL_FLYCLOAK: - case MATERIAL_COSTUME: - case MATERIAL_NAMECARD: + case MATERIAL_AVATAR, MATERIAL_FLYCLOAK, MATERIAL_COSTUME, MATERIAL_NAMECARD -> { Grasscutter.getLogger() - .warn( - "Attempted to add a " - + item.getItemData().getMaterialType().name() - + " to inventory, but item definition lacks isUseOnGain. This indicates a Resources error."); + .warn( + "Attempted to add a " + + item.getItemData().getMaterialType().name() + + " to inventory, but item definition lacks isUseOnGain. This indicates a Resources error."); return null; - default: + } + default -> { if (tab == null) { return null; } @@ -344,13 +343,15 @@ private synchronized GameItem putItem(GameItem item) { } else { // Add count existingItem.setCount( - Math.min( - existingItem.getCount() + item.getCount(), - item.getItemData().getStackLimit())); + Math.min( + existingItem.getCount() + item.getCount(), + item.getItemData().getStackLimit())); existingItem.save(); return existingItem; } + } } + } } } diff --git a/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java b/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java index 86c7ddb866e..710935221ca 100644 --- a/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java +++ b/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java @@ -360,8 +360,6 @@ private int getCurrentAvatarBallId() { private void generateElemBall(int ballId, Position position, int count) { // Generate a particle/orb with the specified parameters. ItemData itemData = GameData.getItemDataMap().get(ballId); - System.out.println("BallId: " + ballId); - System.out.println("ItemData: " + itemData); if (itemData == null) { return; } diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 571da1f917e..f3a77410de9 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -55,6 +55,7 @@ import emu.grasscutter.server.packet.send.*; import emu.grasscutter.utils.*; import emu.grasscutter.utils.helpers.DateHelper; +import emu.grasscutter.utils.objects.DatabaseObject; import emu.grasscutter.utils.objects.FieldFetch; import it.unimi.dsi.fastutil.ints.*; import lombok.*; @@ -66,7 +67,7 @@ import static emu.grasscutter.config.Configuration.GAME_OPTIONS; @Entity(value = "players", useDiscriminator = false) -public class Player implements PlayerHook, FieldFetch { +public class Player implements DatabaseObject, PlayerHook, FieldFetch { @Id private int id; @Indexed(options = @IndexOptions(unique = true)) @Getter private String accountId; @@ -1341,10 +1342,27 @@ private void onLoad() { this.getTeamManager().setPlayer(this); } + /** + * Saves this object to the database. + * As of Grasscutter 1.7.1, this is by default a {@link DatabaseObject#deferSave()} call. + */ public void save() { DatabaseHelper.savePlayer(this); } + /** + * Saves this object to the database. + * + * @param immediate If true, this will be a {@link DatabaseObject#save()} call instead of a {@link DatabaseObject#deferSave()} call. + */ + public void save(boolean immediate) { + if (immediate) { + DatabaseObject.super.save(); + } else { + this.save(); + } + } + // Called from tokenrsp public void loadFromDatabase() { // Make sure these exist @@ -1509,7 +1527,7 @@ public void onLogout() { this.getEnterHomeRequests().clear(); // Save to db - this.save(); + this.save(true); this.getTeamManager().saveAvatars(); this.getFriendsList().save(); diff --git a/src/main/java/emu/grasscutter/game/player/TeamManager.java b/src/main/java/emu/grasscutter/game/player/TeamManager.java index 86d7a0d4540..b1d18b6ef19 100644 --- a/src/main/java/emu/grasscutter/game/player/TeamManager.java +++ b/src/main/java/emu/grasscutter/game/player/TeamManager.java @@ -6,6 +6,7 @@ import emu.grasscutter.*; import emu.grasscutter.data.GameData; import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData; +import emu.grasscutter.database.Database; import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.entity.*; import emu.grasscutter.game.props.*; @@ -990,11 +991,14 @@ public Position getRespawnPosition() { return respawnPoint.get().getPointData().getTranPos(); } + /** + * Performs a bulk save operation on all avatars. + */ public void saveAvatars() { // Save all avatars from active team - for (EntityAvatar entity : this.getActiveTeam()) { - entity.getAvatar().save(); - } + Database.saveAll(this.getActiveTeam().stream() + .map(EntityAvatar::getAvatar) + .toList()); } public void onPlayerLogin() { // Hack for now to fix resonances on login diff --git a/src/main/java/emu/grasscutter/utils/objects/DatabaseObject.java b/src/main/java/emu/grasscutter/utils/objects/DatabaseObject.java new file mode 100644 index 00000000000..8478a53708a --- /dev/null +++ b/src/main/java/emu/grasscutter/utils/objects/DatabaseObject.java @@ -0,0 +1,42 @@ +package emu.grasscutter.utils.objects; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.Grasscutter.ServerRunMode; +import emu.grasscutter.database.*; + +public interface DatabaseObject { + /** + * @return Does this object belong in the game database? + */ + default boolean isGameObject() { + return true; + } + + /** + * @return Should this object be saved immediately? + */ + default boolean saveImmediately() { + return false; + } + + /** + * Performs a deferred save. + * This object will save as a group with other objects. + */ + default void deferSave() { + Database.save(this); + } + + /** + * Attempts to save this object to the database. + */ + default void save() { + if (this.isGameObject()) { + DatabaseManager.getGameDatastore().save(this); + } else if (Grasscutter.getRunMode() != ServerRunMode.GAME_ONLY) { + DatabaseManager.getAccountDatastore().save(this); + } else { + throw new UnsupportedOperationException("Unable to store an account object while in game-only mode."); + } + } +}