Move save operation handling to its own class

This commit is contained in:
nossr50 2021-03-19 13:31:18 -07:00
parent d33c214266
commit 9b856f456c
11 changed files with 137 additions and 26 deletions

View File

@ -1,6 +1,5 @@
package com.gmail.nossr50.commands.experience; package com.gmail.nossr50.commands.experience;
import com.gmail.nossr50.datatypes.experience.XPGainReason;
import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.datatypes.player.McMMOPlayer;
import com.gmail.nossr50.datatypes.player.PlayerProfile; import com.gmail.nossr50.datatypes.player.PlayerProfile;
import com.gmail.nossr50.datatypes.skills.PrimarySkillType; import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
@ -28,7 +27,7 @@ public class AddlevelsCommand extends ExperienceCommand {
profile.addLevels(skill, value); profile.addLevels(skill, value);
if (player == null) { if (player == null) {
profile.scheduleAsyncSave(); UserManager.getPlayerSaveHandler().scheduleAsyncSave(profile.getPlayerData());
return; return;
} }

View File

@ -32,7 +32,7 @@ public class AddxpCommand extends ExperienceCommand {
} }
else { else {
profile.addXp(skill, value); profile.addXp(skill, value);
profile.scheduleAsyncSave(); UserManager.getPlayerSaveHandler().scheduleAsyncSave(profile.getPlayerData());
} }
} }

View File

@ -30,7 +30,7 @@ public class MmoeditCommand extends ExperienceCommand {
profile.modifySkill(skill, value); profile.modifySkill(skill, value);
if (player == null) { if (player == null) {
profile.scheduleAsyncSave(); UserManager.getPlayerSaveHandler().scheduleAsyncSave(profile.getPlayerData());
return; return;
} }

View File

@ -129,7 +129,7 @@ public class SkillresetCommand implements TabExecutor {
profile.modifySkill(skill, 0); profile.modifySkill(skill, 0);
if (player == null) { if (player == null) {
profile.scheduleAsyncSave(); UserManager.getPlayerSaveHandler().scheduleAsyncSave(profile.getPlayerData());
return; return;
} }

View File

@ -4,6 +4,7 @@ import com.gmail.nossr50.api.exceptions.InvalidSkillException;
import com.gmail.nossr50.config.Config; import com.gmail.nossr50.config.Config;
import com.gmail.nossr50.datatypes.database.DatabaseType; import com.gmail.nossr50.datatypes.database.DatabaseType;
import com.gmail.nossr50.datatypes.database.PlayerStat; import com.gmail.nossr50.datatypes.database.PlayerStat;
import com.gmail.nossr50.datatypes.player.MMODataSnapshot;
import com.gmail.nossr50.datatypes.player.PlayerData; import com.gmail.nossr50.datatypes.player.PlayerData;
import com.gmail.nossr50.datatypes.player.PlayerProfile; import com.gmail.nossr50.datatypes.player.PlayerProfile;
import com.gmail.nossr50.datatypes.skills.PrimarySkillType; import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
@ -55,6 +56,13 @@ public interface DatabaseManager {
*/ */
boolean saveUser(@NotNull PlayerData playerData); boolean saveUser(@NotNull PlayerData playerData);
/**
*
* @param dataSnapshot target data snapshot
* @return true if successful, false on failure
*/
boolean saveUser(@NotNull MMODataSnapshot dataSnapshot);
/** /**
* Retrieve leaderboard info. * Retrieve leaderboard info.
* Will never be null but it may be empty * Will never be null but it may be empty

View File

@ -258,9 +258,12 @@ public final class FlatFileDatabaseManager implements DatabaseManager {
//Not used in FlatFile //Not used in FlatFile
} }
public boolean saveUser(@NotNull PlayerData playerData) { public boolean saveUser(@NotNull PlayerData playerData) {
MMODataSnapshot mmoDataSnapshot = new MMODataSnapshot(playerData); //Clone data into Immutable data MMODataSnapshot mmoDataSnapshot = new MMODataSnapshot(playerData);
return saveUser(mmoDataSnapshot); //Clone data into Immutable data
}
public boolean saveUser(@NotNull MMODataSnapshot mmoDataSnapshot) {
String playerName = mmoDataSnapshot.getPlayerName(); String playerName = mmoDataSnapshot.getPlayerName();
UUID uuid = mmoDataSnapshot.getPlayerUUID(); UUID uuid = mmoDataSnapshot.getPlayerUUID();

View File

@ -10,6 +10,9 @@ import org.jetbrains.annotations.NotNull;
import java.util.UUID; import java.util.UUID;
public class MMODataSnapshot { public class MMODataSnapshot {
/* Save Attempts */
private int saveAttempts = 0;
/* Player Stuff */ /* Player Stuff */
private final @NotNull String playerName; private final @NotNull String playerName;
private final @NotNull UUID playerUUID; private final @NotNull UUID playerUUID;
@ -109,4 +112,17 @@ public class MMODataSnapshot {
public boolean isLeaderBoardExcluded() { public boolean isLeaderBoardExcluded() {
return leaderBoardExclusion; return leaderBoardExclusion;
} }
public int getSaveAttempts() {
return saveAttempts;
}
public void setSaveAttempts(int saveAttempts) {
this.saveAttempts = saveAttempts;
}
public void incrementSaveAttempts() {
this.saveAttempts += 1;
}
} }

View File

@ -1,6 +1,8 @@
package com.gmail.nossr50.runnables; package com.gmail.nossr50.runnables;
import com.gmail.nossr50.datatypes.player.McMMOPlayer;
import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.util.player.UserManager;
import com.neetgames.mcmmo.player.OnlineMMOPlayer; import com.neetgames.mcmmo.player.OnlineMMOPlayer;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
@ -12,8 +14,8 @@ public class SaveTimerTask extends BukkitRunnable {
int count = 1; int count = 1;
//TODO: write a more efficient bulk save //TODO: write a more efficient bulk save
for (OnlineMMOPlayer mmoPlayer : UserManager.getPlayers()) { for (McMMOPlayer mmoPlayer : UserManager.getPlayers()) {
UserManager.saveUserWithDelay(mmoPlayer.getPersistentPlayerData(), false, count); UserManager.getPlayerSaveHandler().scheduleAsyncSaveDelay(mmoPlayer.getPlayerData());
count++; count++;
} }

View File

@ -41,9 +41,8 @@ public class FormulaConversionTask extends BukkitRunnable {
editValues(profile); editValues(profile);
// Since this is a temporary profile, we save it here. // Since this is a temporary profile, we save it here.
profile.scheduleAsyncSave(); UserManager.getPlayerSaveHandler().scheduleAsyncSave(profile.getPlayerData());
} } else {
else {
profile = mcMMOPlayer.getProfile(); profile = mcMMOPlayer.getProfile();
editValues(profile); editValues(profile);
} }

View File

@ -0,0 +1,81 @@
package com.gmail.nossr50.util.player;
import com.gmail.nossr50.datatypes.player.MMODataSnapshot;
import com.gmail.nossr50.datatypes.player.PlayerData;
import com.gmail.nossr50.mcMMO;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
//TODO: Low priority - Track pending Async saves to avoid data loss during server shutdown
//TODO: T&C Javadocs
public class PlayerSaveHandler {
private void save(@NotNull MMODataSnapshot mmoDataSnapshot, boolean useSync) {
boolean saveSuccessful = mcMMO.getDatabaseManager().saveUser(mmoDataSnapshot);
//Check for failure to save
if (!saveSuccessful) {
String playerName = mmoDataSnapshot.getPlayerName();
String uuidStr = mmoDataSnapshot.getPlayerUUID().toString();
mcMMO.p.getLogger().severe("PlayerProfile saving failed for player name: " + playerName + " UUID: " + uuidStr);
if(mmoDataSnapshot.getSaveAttempts() > 0) {
mcMMO.p.getLogger().severe("Attempted to save profile for player "+playerName
+ " resulted in failure. " + mmoDataSnapshot.getSaveAttempts() + " have been made so far.");
}
if(mmoDataSnapshot.getSaveAttempts() < 10) {
mmoDataSnapshot.incrementSaveAttempts();
//Back out of async saving if we detect a server shutdown, this is not always going to be caught
if(mcMMO.isServerShutdownExecuted() || useSync)
scheduleSyncSave(mmoDataSnapshot); //Execute sync saves immediately
else
scheduleAsyncSave(mmoDataSnapshot);
} else {
mcMMO.p.getLogger().severe("mcMMO has failed to save the profile for "
+ playerName + " numerous times." +
" mcMMO will now stop attempting to save this profile." +
" Check your console for errors and inspect your DB for issues.");
}
}
}
public void save(@NotNull PlayerData playerData, boolean useSync) {
//TODO: We no longer check if a profile is loaded or not as it should never be unloaded if a save operation is being called, need to double check this to be true
if(!playerData.isDirtyProfile()) {
return; //Don't save data that hasn't changed
}
MMODataSnapshot mmoDataSnapshot = new MMODataSnapshot(playerData);
save(mmoDataSnapshot, useSync);
}
public void scheduleAsyncSave(@NotNull PlayerData playerData) {
MMODataSnapshot mmoDataSnapshot = new MMODataSnapshot(playerData);
scheduleAsyncSave(mmoDataSnapshot);
}
public void scheduleAsyncSaveDelay(@NotNull PlayerData playerData) {
MMODataSnapshot mmoDataSnapshot = new MMODataSnapshot(playerData);
scheduleAsyncSaveDelay(mmoDataSnapshot);
}
public void scheduleSyncSave(@NotNull PlayerData playerData) {
MMODataSnapshot mmoDataSnapshot = new MMODataSnapshot(playerData);
scheduleSyncSave(mmoDataSnapshot);
}
private void scheduleAsyncSave(@NotNull MMODataSnapshot mmoDataSnapshot) {
Bukkit.getScheduler().runTaskAsynchronously(mcMMO.p, () -> save(mmoDataSnapshot, false));
}
private void scheduleAsyncSaveDelay(@NotNull MMODataSnapshot mmoDataSnapshot) {
Bukkit.getScheduler().runTaskLaterAsynchronously(mcMMO.p, () -> save(mmoDataSnapshot, false), 20L);
}
private void scheduleSyncSave(@NotNull MMODataSnapshot mmoDataSnapshot) {
Bukkit.getScheduler().runTask(mcMMO.p, () -> save(mmoDataSnapshot, true));
}
}

View File

@ -19,6 +19,7 @@ import java.util.HashSet;
public final class UserManager { public final class UserManager {
private static HashSet<McMMOPlayer> playerDataSet; //Used to track players for sync saves on shutdown private static HashSet<McMMOPlayer> playerDataSet; //Used to track players for sync saves on shutdown
private @NotNull static final PlayerSaveHandler playerSaveHandler = new PlayerSaveHandler();
private UserManager() {} private UserManager() {}
@ -69,7 +70,7 @@ public final class UserManager {
} }
/** /**
* Save all users ON THIS THREAD. * Save all users on main thread
*/ */
public static void saveAll() { public static void saveAll() {
if(playerDataSet == null) if(playerDataSet == null)
@ -79,15 +80,12 @@ public final class UserManager {
mcMMO.p.getLogger().info("Saving mcMMOPlayers... (" + trackedSyncData.size() + ")"); mcMMO.p.getLogger().info("Saving mcMMOPlayers... (" + trackedSyncData.size() + ")");
for (McMMOPlayer playerData : trackedSyncData) { for (McMMOPlayer mmoPlayer : trackedSyncData) {
try try {
{ mcMMO.p.getLogger().info("Saving data for player: "+mmoPlayer.getPlayerName());
mcMMO.p.getLogger().info("Saving data for player: "+playerData.getPlayerName()); getPlayerSaveHandler().save(mmoPlayer.getPlayerData(), true);
playerData.getProfile().save(true); } catch (Exception e) {
} mcMMO.p.getLogger().severe("Could not save mcMMO player data for player: " + mmoPlayer.getPlayerName());
catch (Exception e)
{
mcMMO.p.getLogger().warning("Could not save mcMMO player data for player: " + playerData.getPlayerName());
} }
} }
@ -120,9 +118,9 @@ public final class UserManager {
mmoPlayer.getTamingManager().cleanupAllSummons(); mmoPlayer.getTamingManager().cleanupAllSummons();
if (syncSave) { if (syncSave) {
getProfile().save(true); //TODO: T&C Wire this up, see master branch com.gmail.nossr50.datatypes.player.PlayerProfile#save getPlayerSaveHandler().save(mmoPlayer.getPlayerData(), true); //TODO: T&C Wire this up, see master branch com.gmail.nossr50.datatypes.player.PlayerProfile#save
} else { } else {
getProfile().scheduleAsyncSave(); //TODO: T&C Wire this up, see master branch com.gmail.nossr50.datatypes.player.PlayerProfile#scheduleAsyncSave getPlayerSaveHandler().scheduleAsyncSave(mmoPlayer.getPlayerData()); //TODO: T&C Wire this up, see master branch com.gmail.nossr50.datatypes.player.PlayerProfile#scheduleAsyncSave
} }
UserManager.remove(targetPlayer); UserManager.remove(targetPlayer);
@ -190,4 +188,9 @@ public final class UserManager {
public static boolean hasPlayerDataKey(Entity entity) { public static boolean hasPlayerDataKey(Entity entity) {
return entity != null && entity.hasMetadata(mcMMO.playerDataKey); return entity != null && entity.hasMetadata(mcMMO.playerDataKey);
} }
public static @NotNull PlayerSaveHandler getPlayerSaveHandler() {
return playerSaveHandler;
}
} }