Блокировка на тот же объект для сохранения и загрузки - PullRequest
0 голосов
/ 23 февраля 2019

Я создал свой собственный небольшой инвентарный мост для плоских файлов для 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 голосов
/ 10 мая 2019

Я действительно не знаю, что имел в виду этот парень, но у меня есть надежное решение для вас.

Прежде всего я сообщу вам о том, что происходит, когда вы не можете new DataManager(event.getPlayer()):

В основном Java копирует копию вашего public class DataManager и сохраняет ее в оперативной памяти, а затем выполняет ваши задачи.Поскольку это бесполезно, когда вы просто используете новый экземпляр для одной задачи (например, сохраняете или загружаете данные), вы можете просто создавать статические функции и запускать их асинхронно, чтобы они не блокировали основной поток Bukkit.Идея состоит в том, чтобы создать файл .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 местом, в которое вы записываете данные.сохраните настройки проигрывателя.

Теперь вы должны изменить ваши события на это:

@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());
}

Я установил приоритет QuitEvent на LOWEST, чтобы он вызывался первым (событиедо того, как сервер присоединится к новому серверу) и JoinEvent to MONITOR, чтобы он наконец вызывался, когда игрок уже присоединился.

Я не полностью тестировал это несколько раз и с задержками на сервере, но до сих пор это работало.Если это не сработает, вам стоит подумать об использовании bungeecords Plugin Messaging Channel.https://www.spigotmc.org/wiki/bukkit-bungee-plugin-messaging-channel/ Там вы можете отправить полный инвентарь через плагин-сообщение на BungeeCord, а затем отправить его на следующий сервер, к которому присоединяется игрок.Просто убедитесь, что игрок может присоединиться к новому серверу, только когда сообщение о плагине было полностью получено вашим сервером BungeeCord.Но это действительно опытное программирование, и я не рекомендовал бы его вам, если вы только начали программировать, поскольку это доставит вам некоторую головную боль (просто спросите stackoverflow)

Возможно, более простое и надежное исправление вашего проблема параллелизма состоит в том, чтобы не позволить игроку присоединиться к другому серверу в течение примерно 5 секунд, чтобы он оставался в лобби во время сохранения данных.

Все это были только рекомендации, и я надеюсь, чтоПервый фрагмент кода, который я разместил здесь, работает.Если что-то не работает, просто спросите меня лично.Я постараюсь помочь вам в дальнейшем.

...