锁定到同一对象以进行保存和加载

时间:2019-02-22 22:19:59

标签: minecraft bukkit bungeecord

我为Bungeecord创建了自己的小平面文件库存网桥。当玩家离开时,他的数据被保存,并且当他加入时,他的数据被加载。

我遇到的问题是,当播放器切换服务器时,在保存数据之前先加载数据。因此,我增加了加载数据的延迟,并且它可以正常工作。

但这是不可靠的,所以有人说我应该使用“同步块锁定到同一对象上,以加载和保存播放器数据”。

这是我的代码:

private Player player;

public DataManager(Player player) {
    this.player = player;
}

public synchronized void saveData() throws IOException {
    ConfigManager configmanager = new ConfigManager(player.getUniqueId());
    FileConfiguration config = configmanager.getConfig();
    config.set("Contents", player.getInventory().getContents());
    config.set("Enderchest", player.getEnderChest().getContents());
    config.set("Armor", player.getInventory().getArmorContents());      
    config.set("Effects", player.getActivePotionEffects()); 
    config.set("Health", player.getHealth());
    config.set("Experience", player.getExp());
    config.set("Food", player.getFoodLevel());      
    configmanager.saveConfig();
}

@SuppressWarnings("unchecked")
public synchronized void loadData() {
    FileConfiguration config = new ConfigManager(player.getUniqueId()).getConfig();
    player.getInventory().setContents(((List<ItemStack>) config.get("Contents")).toArray(new ItemStack[0]));
    player.getEnderChest().setContents(((List<ItemStack>) config.get("Enderchest")).toArray(new ItemStack[0]));
    player.getInventory().setArmorContents(((List<ItemStack>) config.get("Armor")).toArray(new ItemStack[0]));
    player.addPotionEffects((Collection<PotionEffect>) config.get("Effects"));
    player.setHealth(config.getDouble("Health"));
    player.setExp((float) config.getDouble("Experience"));
    player.setFoodLevel(config.getInt("Food"));     
}   

玩家加入时:

@EventHandler
public void onJoin(PlayerJoinEvent event) {
    TimerTask task = new TimerTask() {          
        @Override
        public void run() {
            new DataManager(event.getPlayer()).loadData();              
        }
    };
    new Timer().schedule(task, 5000);
}

玩家退出时:

@EventHandler
public void onQuit(PlayerQuitEvent event) throws IOException {
    new DataManager(event.getPlayer()).saveData();
}

我不完全知道这个人的意思。 我该怎么办?

1 个答案:

答案 0 :(得分:0)

我真的不知道这个人是什么意思,但是我为您提供了可靠的解决方案。

首先,我将通知您artifactSourceId=xyz发生时的情况:

基本上,Java会存储new DataManager(event.getPlayer())的副本并将其存储在RAM中,然后执行您的任务。由于这在您仅将新实例用于一项任务(例如保存或加载数据)时是无用的,因此您可以创建静态函数并异步运行它们,这样它们就不会阻塞bukkit的主线程。这个想法是只要写入数据就创建一个public class DataManager文件,并且仅在删除.lock文件后读取数据。

.lock

public static void saveData(Player player) throws IOException { File locker = new File(YOUR_PATH_TO_CONFIG + player.getUniqueId() + ".lock"); locker.createNewFile(); //create locker-file Bukkit.getServer().getScheduler().runTaskAsynchronously(plugin, () -> { try { ConfigManager configmanager = new ConfigManager(player.getUniqueId()); FileConfiguration config = configmanager.getConfig(); config.set("Contents", player.getInventory().getContents()); config.set("Enderchest", player.getEnderChest().getContents()); config.set("Armor", player.getInventory().getArmorContents()); config.set("Effects", player.getActivePotionEffects()); config.set("Health", player.getHealth()); config.set("Experience", player.getExp()); config.set("Food", player.getFoodLevel()); configmanager.saveConfig(); } catch (IOException ex) { // TODO: handle exception } locker.delete(); //delete the locker-file so the new server can access the playerdata }); } public static void loadData(Player player) { Bukkit.getServer().getScheduler().runTaskAsynchronously(plugin, () -> { //run async to not block the main thread File f = new File(YOUR_PATH_TO_CONFIG + player.getUniqueId() + ".lock"); while (f.exists()) { //as long as the locker-file exists we want to wait try { Thread.sleep(50); //wait one minecraft-tick } catch (InterruptedException ignored) {} } Bukkit.getScheduler().runTask(plugin, () -> { //run sync FileConfiguration config = new ConfigManager(player.getUniqueId()).getConfig(); player.getInventory().setContents(Arrays.asList((List<ItemStack>) config.get("Contents")).toArray(new ItemStack[0])); player.getEnderChest().setContents(((List<ItemStack>) config.get("Enderchest")).toArray(new ItemStack[0])); player.getInventory().setArmorContents(((List<ItemStack>) config.get("Armor")).toArray(new ItemStack[0])); player.addPotionEffects((Collection<PotionEffect>) config.get("Effects")); player.setHealth(config.getDouble("Health")); player.setExp((float) config.getDouble("Experience")); player.setFoodLevel(config.getInt("Food")); }); }); } 替换为存储播放器配置的位置。

现在您应该将事件更改为此:

YOUR_PATH_TO_CONFIG

我将QuitEvent的优先级设置为LOWEST,以便首先调用它((服务器加入新服务器之前的事件),将JoinEvent设置为MONITOR,以便在播放器已经存在时最后被调用加入。

我没有多次对此进行全面测试,并且服务器延迟,但是到目前为止它仍然有效。如果它不起作用,您应该考虑使用Bungeecords @EventHandler(priority = EventPriority.MONITOR) public void onJoin(PlayerJoinEvent event) { DataManager.loadData(event.getPlayer()); } @EventHandler(priority = EventPriority.LOWEST) public void onQuit(PlayerQuitEvent event) throws IOException { DataManager.saveData(event.getPlayer()); } https://www.spigotmc.org/wiki/bukkit-bungee-plugin-messaging-channel/在那里,您可以通过插件消息将完整清单发送到BungeeCord,然后将其发送到播放器加入的下一台服务器。只要确保BungeeCord服务器完全收到插件消息后,播放器就可以加入新服务器。但这是真正的编程经验,如果您刚刚开始编程,我不会推荐给您,因为这会给您带来一些麻烦(只需询问stackoverflow即可)

也许对您的并发性问题更简单,更可靠的解决办法是,让播放器在5秒钟之内不让玩家加入另一台服务器,以便在保存数据期间他停留在大厅中

这些全都只是要求,我希望我在此处发布的第一个代码段能够正常工作。如果某事不起作用,请亲自问我。我会尽力为您提供帮助。