Async proxy data setting and fetching; add timestamp API to player data; add option for bounceBackSyncrhonization

feat/data-edit-commands
William 3 years ago
parent b7e6861f03
commit c5e0640f83

@ -51,8 +51,9 @@ public class BukkitEventListener implements Listener {
// Mark the player as awaiting data fetch
HuskSyncBukkit.bukkitCache.setAwaitingDataFetch(player.getUniqueId());
if (!HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.isMySqlPlayerDataBridgeInstalled)
if (!HuskSyncBukkit.handshakeCompleted || HuskSyncBukkit.isMySqlPlayerDataBridgeInstalled) {
return; // If the data handshake has not been completed yet (or MySqlPlayerDataBridge is installed)
}
// Send a redis message requesting the player data (if they need to)
if (HuskSyncBukkit.bukkitCache.isPlayerRequestingOnJoin(player.getUniqueId())) {
@ -64,7 +65,7 @@ public class BukkitEventListener implements Listener {
}
});
} else {
// If the player's data wasn't set after 20 ticks, ensure it will be
// If the player's data wasn't set after the synchronization timeout retry delay ticks, ensure it will be
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> {
if (player.isOnline()) {
try {

@ -330,7 +330,7 @@ public class PlayerSetter {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
// Apply the advancements to the player
Iterator<Advancement> serverAdvancements = Bukkit.getServer().advancementIterator();
final Iterator<Advancement> serverAdvancements = Bukkit.getServer().advancementIterator();
while (serverAdvancements.hasNext()) { // Iterate through all advancements
boolean correctExperienceCheck = false; // Determines whether the experience might have changed warranting an update
Advancement advancement = serverAdvancements.next();

@ -60,6 +60,8 @@ public class ConfigLoader {
Settings.hikariKeepAliveTime = config.getLong("data_storage_settings.hikari_pool_settings.keepalive_time", 0);
Settings.hikariConnectionTimeOut = config.getLong("data_storage_settings.hikari_pool_settings.connection_timeout", 5000);
Settings.bounceBackSynchronisation = config.getBoolean("bounce_back_synchronization", true);
// Read cluster data
Configuration section = config.getSection("clusters");
final String settingDatabaseName = Settings.mySQLDatabase != null ? Settings.mySQLDatabase : "HuskSync";

@ -63,37 +63,33 @@ public class BungeeRedisListener extends RedisListener {
}
switch (message.getMessageType()) {
case PLAYER_DATA_REQUEST -> {
case PLAYER_DATA_REQUEST -> ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> {
// Get the UUID of the requesting player
final UUID requestingPlayerUUID = UUID.fromString(message.getMessageData());
ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> {
try {
// Send the reply, serializing the message data
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET,
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, requestingPlayerUUID, message.getMessageTarget().targetClusterId()),
RedisMessage.serialize(getPlayerCachedData(requestingPlayerUUID, message.getMessageTarget().targetClusterId())))
.send();
try {
// Send the reply, serializing the message data
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET,
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, requestingPlayerUUID, message.getMessageTarget().targetClusterId()),
RedisMessage.serialize(getPlayerCachedData(requestingPlayerUUID, message.getMessageTarget().targetClusterId())))
.send();
// Send an update to all bukkit servers removing the player from the requester cache
new RedisMessage(RedisMessage.MessageType.REQUEST_DATA_ON_JOIN,
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()),
RedisMessage.RequestOnJoinUpdateType.REMOVE_REQUESTER.toString(), requestingPlayerUUID.toString())
.send();
// Send an update to all bukkit servers removing the player from the requester cache
new RedisMessage(RedisMessage.MessageType.REQUEST_DATA_ON_JOIN,
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()),
RedisMessage.RequestOnJoinUpdateType.REMOVE_REQUESTER.toString(), requestingPlayerUUID.toString())
.send();
// Send synchronisation complete message
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(requestingPlayerUUID);
if (player != null) {
if (player.isConnected()) {
player.sendMessage(ChatMessageType.ACTION_BAR, new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent());
}
}
} catch (IOException e) {
log(Level.SEVERE, "Failed to serialize data when replying to a data request");
e.printStackTrace();
// Send synchronisation complete message
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(requestingPlayerUUID);
if (player != null) {
player.sendMessage(ChatMessageType.ACTION_BAR, new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent());
}
});
}
case PLAYER_DATA_UPDATE -> {
} catch (IOException e) {
log(Level.SEVERE, "Failed to serialize data when replying to a data request");
e.printStackTrace();
}
});
case PLAYER_DATA_UPDATE -> ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> {
// Deserialize the PlayerData received
PlayerData playerData;
final String serializedPlayerData = message.getMessageData();
@ -114,10 +110,10 @@ public class BungeeRedisListener extends RedisListener {
}
// Reply with the player data if they are still online (switching server)
try {
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(playerData.getPlayerUUID());
if (player != null) {
if (player.isConnected()) {
if (Settings.bounceBackSynchronisation) {
try {
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(playerData.getPlayerUUID());
if (player != null) {
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET,
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, playerData.getPlayerUUID(), message.getMessageTarget().targetClusterId()),
serializedPlayerData)
@ -126,12 +122,12 @@ public class BungeeRedisListener extends RedisListener {
// Send synchronisation complete message
player.sendMessage(ChatMessageType.ACTION_BAR, new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent());
}
} catch (IOException e) {
log(Level.SEVERE, "Failed to re-serialize PlayerData when handling a player update request");
e.printStackTrace();
}
} catch (IOException e) {
log(Level.SEVERE, "Failed to re-serialize PlayerData when handling a player update request");
e.printStackTrace();
}
}
});
case CONNECTION_HANDSHAKE -> {
// Reply to a Bukkit server's connection handshake to complete the process
if (HuskSyncBungeeCord.isDisabling) return; // Return if the Proxy is disabling
@ -198,10 +194,9 @@ public class BungeeRedisListener extends RedisListener {
HuskSyncBungeeCord.dataManager));
}
}
case API_DATA_REQUEST -> {
case API_DATA_REQUEST -> ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> {
final UUID playerUUID = UUID.fromString(message.getMessageDataElements()[0]);
final UUID requestUUID = UUID.fromString(message.getMessageDataElements()[1]);
try {
final PlayerData data = getPlayerCachedData(playerUUID, message.getMessageTarget().targetClusterId());
@ -221,7 +216,7 @@ public class BungeeRedisListener extends RedisListener {
} catch (IOException e) {
plugin.getBungeeLogger().log(Level.SEVERE, "Failed to serialize PlayerData requested via the API");
}
}
});
}
}

@ -1,6 +1,7 @@
package me.william278.husksync;
import java.io.*;
import java.time.Instant;
import java.util.UUID;
/**
@ -18,6 +19,11 @@ public class PlayerData implements Serializable {
*/
private final UUID dataVersionUUID;
/**
* Epoch time identifying when the data was last updated or created
*/
private long timestamp;
/**
* A special flag that will be {@code true} if the player is new to the network and should not have their data set when joining the Bukkit
*/
@ -70,6 +76,7 @@ public class PlayerData implements Serializable {
String serializedStatusEffects, int totalExperience, int expLevel, float expProgress, String gameMode,
String serializedStatistics, boolean isFlying, String serializedAdvancements, String serializedLocation) {
this.dataVersionUUID = UUID.randomUUID();
this.timestamp = Instant.now().getEpochSecond();
this.playerUUID = playerUUID;
this.serializedInventory = serializedInventory;
this.serializedEnderChest = serializedEnderChest;
@ -109,16 +116,17 @@ public class PlayerData implements Serializable {
* @param totalExperience Their total experience points ("Score")
* @param expLevel Their exp level
* @param expProgress Their exp progress to the next level
* @param gameMode Their game mode ({@code SURVIVAL}, {@code CREATIVE}, etc)
* @param gameMode Their game mode ({@code SURVIVAL}, {@code CREATIVE}, etc.)
* @param serializedStatistics Their serialized statistics data (Displayed in Statistics menu in ESC menu)
*/
public PlayerData(UUID playerUUID, UUID dataVersionUUID, String serializedInventory, String serializedEnderChest,
public PlayerData(UUID playerUUID, UUID dataVersionUUID, long timestamp, String serializedInventory, String serializedEnderChest,
double health, double maxHealth, double healthScale, int hunger, float saturation, float saturationExhaustion,
int selectedSlot, String serializedStatusEffects, int totalExperience, int expLevel, float expProgress,
String gameMode, String serializedStatistics, boolean isFlying, String serializedAdvancements,
String serializedLocation) {
this.playerUUID = playerUUID;
this.dataVersionUUID = dataVersionUUID;
this.timestamp = timestamp;
this.serializedInventory = serializedInventory;
this.serializedEnderChest = serializedEnderChest;
this.health = health;
@ -172,6 +180,15 @@ public class PlayerData implements Serializable {
return dataVersionUUID;
}
/**
* Get the timestamp when this data was created or last updated
*
* @return time since epoch of last data update or creation
*/
public long getDataTimestamp() {
return timestamp;
}
/**
* Returns the serialized player {@code ItemStack[]} inventory
*
@ -341,6 +358,7 @@ public class PlayerData implements Serializable {
*/
public void setSerializedInventory(String serializedInventory) {
this.serializedInventory = serializedInventory;
this.timestamp = Instant.now().getEpochSecond();
}
/**
@ -350,6 +368,7 @@ public class PlayerData implements Serializable {
*/
public void setSerializedEnderChest(String serializedEnderChest) {
this.serializedEnderChest = serializedEnderChest;
this.timestamp = Instant.now().getEpochSecond();
}
/**
@ -359,6 +378,7 @@ public class PlayerData implements Serializable {
*/
public void setHealth(double health) {
this.health = health;
this.timestamp = Instant.now().getEpochSecond();
}
/**
@ -368,6 +388,7 @@ public class PlayerData implements Serializable {
*/
public void setMaxHealth(double maxHealth) {
this.maxHealth = maxHealth;
this.timestamp = Instant.now().getEpochSecond();
}
/**
@ -377,6 +398,7 @@ public class PlayerData implements Serializable {
*/
public void setHealthScale(double healthScale) {
this.healthScale = healthScale;
this.timestamp = Instant.now().getEpochSecond();
}
/**
@ -386,6 +408,7 @@ public class PlayerData implements Serializable {
*/
public void setHunger(int hunger) {
this.hunger = hunger;
this.timestamp = Instant.now().getEpochSecond();
}
/**
@ -395,6 +418,7 @@ public class PlayerData implements Serializable {
*/
public void setSaturation(float saturation) {
this.saturation = saturation;
this.timestamp = Instant.now().getEpochSecond();
}
/**
@ -404,6 +428,7 @@ public class PlayerData implements Serializable {
*/
public void setSaturationExhaustion(float saturationExhaustion) {
this.saturationExhaustion = saturationExhaustion;
this.timestamp = Instant.now().getEpochSecond();
}
/**
@ -413,6 +438,7 @@ public class PlayerData implements Serializable {
*/
public void setSelectedSlot(int selectedSlot) {
this.selectedSlot = selectedSlot;
this.timestamp = Instant.now().getEpochSecond();
}
/**
@ -422,6 +448,7 @@ public class PlayerData implements Serializable {
*/
public void setSerializedEffectData(String serializedEffectData) {
this.serializedEffectData = serializedEffectData;
this.timestamp = Instant.now().getEpochSecond();
}
/**
@ -431,6 +458,7 @@ public class PlayerData implements Serializable {
*/
public void setTotalExperience(int totalExperience) {
this.totalExperience = totalExperience;
this.timestamp = Instant.now().getEpochSecond();
}
/**
@ -440,6 +468,7 @@ public class PlayerData implements Serializable {
*/
public void setExpLevel(int expLevel) {
this.expLevel = expLevel;
this.timestamp = Instant.now().getEpochSecond();
}
/**
@ -449,6 +478,7 @@ public class PlayerData implements Serializable {
*/
public void setExpProgress(float expProgress) {
this.expProgress = expProgress;
this.timestamp = Instant.now().getEpochSecond();
}
/**
@ -458,6 +488,7 @@ public class PlayerData implements Serializable {
*/
public void setGameMode(String gameMode) {
this.gameMode = gameMode;
this.timestamp = Instant.now().getEpochSecond();
}
/**
@ -467,6 +498,7 @@ public class PlayerData implements Serializable {
*/
public void setSerializedStatistics(String serializedStatistics) {
this.serializedStatistics = serializedStatistics;
this.timestamp = Instant.now().getEpochSecond();
}
/**
@ -476,6 +508,7 @@ public class PlayerData implements Serializable {
*/
public void setFlying(boolean flying) {
isFlying = flying;
this.timestamp = Instant.now().getEpochSecond();
}
/**
@ -485,6 +518,7 @@ public class PlayerData implements Serializable {
*/
public void setSerializedAdvancements(String serializedAdvancements) {
this.serializedAdvancements = serializedAdvancements;
this.timestamp = Instant.now().getEpochSecond();
}
/**
@ -494,5 +528,6 @@ public class PlayerData implements Serializable {
*/
public void setSerializedLocation(String serializedLocation) {
this.serializedLocation = serializedLocation;
this.timestamp = Instant.now().getEpochSecond();
}
}

@ -36,6 +36,9 @@ public class Settings {
// SQL settings
public static DataStorageType dataStorageType;
// Bounce-back synchronisation (default)
public static boolean bounceBackSynchronisation;
// MySQL specific settings
public static String mySQLHost;
public static String mySQLDatabase;

@ -184,7 +184,7 @@ public class DataManager {
ResultSet resultSet = statement.executeQuery();
if (resultSet.next()) {
final UUID dataVersionUUID = UUID.fromString(resultSet.getString("version_uuid"));
//final Timestamp dataSaveTimestamp = resultSet.getTimestamp("timestamp");
final Timestamp dataSaveTimestamp = resultSet.getTimestamp("timestamp");
final String serializedInventory = resultSet.getString("inventory");
final String serializedEnderChest = resultSet.getString("ender_chest");
final double health = resultSet.getDouble("health");
@ -204,10 +204,10 @@ public class DataManager {
final String serializedLocationData = resultSet.getString("location");
final String serializedStatisticData = resultSet.getString("statistics");
data.put(cluster, new PlayerData(playerUUID, dataVersionUUID, serializedInventory, serializedEnderChest,
health, maxHealth, healthScale, hunger, saturation, saturationExhaustion, selectedSlot, serializedStatusEffects,
totalExperience, expLevel, expProgress, gameMode, serializedStatisticData, isFlying,
serializedAdvancementData, serializedLocationData));
data.put(cluster, new PlayerData(playerUUID, dataVersionUUID, dataSaveTimestamp.toInstant().getEpochSecond(),
serializedInventory, serializedEnderChest, health, maxHealth, healthScale, hunger, saturation,
saturationExhaustion, selectedSlot, serializedStatusEffects, totalExperience, expLevel, expProgress,
gameMode, serializedStatisticData, isFlying, serializedAdvancementData, serializedLocationData));
} else {
data.put(cluster, PlayerData.DEFAULT_PLAYER_DATA(playerUUID));
}

@ -19,6 +19,7 @@ data_storage_settings:
maximum_lifetime: 1800000
keepalive_time: 0
connection_timeout: 5000
bounce_back_synchronization: true
clusters:
main:
player_table: 'husksync_players'

@ -75,6 +75,8 @@ public class ConfigLoader {
Settings.hikariKeepAliveTime = getConfigLong(config, 0, "data_storage_settings", "hikari_pool_settings", "keepalive_time");
Settings.hikariConnectionTimeOut = getConfigLong(config, 5000, "data_storage_settings", "hikari_pool_settings", "connection_timeout");
Settings.bounceBackSynchronisation = getConfigBoolean(config, true,"bounce_back_synchronization");
// Read cluster data
ConfigurationNode clusterSection = config.getNode("clusters");
final String settingDatabaseName = Settings.mySQLDatabase != null ? Settings.mySQLDatabase : "HuskSync";

@ -62,33 +62,31 @@ public class VelocityRedisListener extends RedisListener {
}
switch (message.getMessageType()) {
case PLAYER_DATA_REQUEST -> {
case PLAYER_DATA_REQUEST -> plugin.getProxyServer().getScheduler().buildTask(plugin, () -> {
// Get the UUID of the requesting player
final UUID requestingPlayerUUID = UUID.fromString(message.getMessageData());
plugin.getProxyServer().getScheduler().buildTask(plugin, () -> {
try {
// Send the reply, serializing the message data
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET,
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, requestingPlayerUUID, message.getMessageTarget().targetClusterId()),
RedisMessage.serialize(getPlayerCachedData(requestingPlayerUUID, message.getMessageTarget().targetClusterId())))
.send();
try {
// Send the reply, serializing the message data
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET,
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, requestingPlayerUUID, message.getMessageTarget().targetClusterId()),
RedisMessage.serialize(getPlayerCachedData(requestingPlayerUUID, message.getMessageTarget().targetClusterId())))
.send();
// Send an update to all bukkit servers removing the player from the requester cache
new RedisMessage(RedisMessage.MessageType.REQUEST_DATA_ON_JOIN,
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()),
RedisMessage.RequestOnJoinUpdateType.REMOVE_REQUESTER.toString(), requestingPlayerUUID.toString())
.send();
// Send an update to all bukkit servers removing the player from the requester cache
new RedisMessage(RedisMessage.MessageType.REQUEST_DATA_ON_JOIN,
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, null, message.getMessageTarget().targetClusterId()),
RedisMessage.RequestOnJoinUpdateType.REMOVE_REQUESTER.toString(), requestingPlayerUUID.toString())
.send();
// Send synchronisation complete message
Optional<Player> player = plugin.getProxyServer().getPlayer(requestingPlayerUUID);
player.ifPresent(value -> value.sendActionBar(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent()));
} catch (IOException e) {
log(Level.SEVERE, "Failed to serialize data when replying to a data request");
e.printStackTrace();
}
}).schedule();
}
case PLAYER_DATA_UPDATE -> {
// Send synchronisation complete message
Optional<Player> player = plugin.getProxyServer().getPlayer(requestingPlayerUUID);
player.ifPresent(value -> value.sendActionBar(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent()));
} catch (IOException e) {
log(Level.SEVERE, "Failed to serialize data when replying to a data request");
e.printStackTrace();
}
}).schedule();
case PLAYER_DATA_UPDATE -> plugin.getProxyServer().getScheduler().buildTask(plugin, () -> {
// Deserialize the PlayerData received
PlayerData playerData;
final String serializedPlayerData = message.getMessageData();
@ -109,23 +107,24 @@ public class VelocityRedisListener extends RedisListener {
}
// Reply with the player data if they are still online (switching server)
Optional<Player> updatingPlayer = plugin.getProxyServer().getPlayer(playerData.getPlayerUUID());
updatingPlayer.ifPresent(player -> {
try {
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET,
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, playerData.getPlayerUUID(), message.getMessageTarget().targetClusterId()),
RedisMessage.serialize(playerData))
.send();
// Send synchronisation complete message
player.sendActionBar(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent());
} catch (IOException e) {
log(Level.SEVERE, "Failed to re-serialize PlayerData when handling a player update request");
e.printStackTrace();
}
});
}
if (Settings.bounceBackSynchronisation) {
Optional<Player> updatingPlayer = plugin.getProxyServer().getPlayer(playerData.getPlayerUUID());
updatingPlayer.ifPresent(player -> {
try {
new RedisMessage(RedisMessage.MessageType.PLAYER_DATA_SET,
new RedisMessage.MessageTarget(Settings.ServerType.BUKKIT, playerData.getPlayerUUID(), message.getMessageTarget().targetClusterId()),
RedisMessage.serialize(playerData))
.send();
// Send synchronisation complete message
player.sendActionBar(new MineDown(MessageManager.getMessage("synchronisation_complete")).toComponent());
} catch (IOException e) {
log(Level.SEVERE, "Failed to re-serialize PlayerData when handling a player update request");
e.printStackTrace();
}
});
}
}).schedule();
case CONNECTION_HANDSHAKE -> {
// Reply to a Bukkit server's connection handshake to complete the process
if (HuskSyncVelocity.isDisabling) return; // Return if the Proxy is disabling
@ -191,7 +190,7 @@ public class VelocityRedisListener extends RedisListener {
migrator.loadIncomingData(migrator.incomingPlayerData, HuskSyncVelocity.dataManager);
}
}
case API_DATA_REQUEST -> {
case API_DATA_REQUEST -> plugin.getProxyServer().getScheduler().buildTask(plugin, () -> {
final UUID playerUUID = UUID.fromString(message.getMessageDataElements()[0]);
final UUID requestUUID = UUID.fromString(message.getMessageDataElements()[1]);
@ -214,7 +213,7 @@ public class VelocityRedisListener extends RedisListener {
} catch (IOException e) {
plugin.getVelocityLogger().log(Level.SEVERE, "Failed to serialize PlayerData requested via the API");
}
}
}).schedule();
}
}

Loading…
Cancel
Save