Java / JavaFX: опросите базу данных для обновления одного значения, сохраняя при этом отзывчивость в графическом интерфейсе - PullRequest
0 голосов
/ 03 апреля 2019

Вместе с друзьями я пытался создать игру на основе поворота. У нас есть некоторые проблемы, связанные с проверкой того, когда пользователь имеет свою очередь, а также с поддержкой графического интерфейса пользователя, а также закрытием потока, который мы используем сейчас, когда игра закрыта. Я хочу получить некоторую информацию о том, как это сделать, но я не уверен, связана ли проблема с JavaFX, с потоками или с обоими.

Я пытался искать как можно больше, но просто не мог найти именно то, что искал, хотя я считаю, что это довольно просто. Прямо сейчас у нас есть поток, запускающий цикл, когда вы нажимаете кнопку, чтобы проверить, настала ли ваша очередь. Когда не ваша очередь, мы хотим отключить некоторый пользовательский ввод, поэтому я не уверен, нужен ли нам поток, кроме как для того, чтобы сохранить отзывчивость.

Я также пытался реализовать класс, расширяющий Thread, но это только усугубляло проблемы: либо запускали новый поток каждый раз, когда не было хода игроков, либо замораживали графический интерфейс, если я поместил цикл за пределы нить.

public void refreshButtonPressed(){
    try{
        refreshButton.setDisable(true);
        Thread pollThread = new Thread(() -> {
            System.out.println("Thread started");  //Stop being able to start more threads
            int user_id = 0;
            String gamePin = "xxxxxx";
        while (!GameConnection.yourTurn(user_id, Context.getContext().getGamePin())){ //This method checks the database if it is your turn
            try{
                Thread.sleep(5000);  //So we don't flood the database
            }
            catch (InterruptedException e){
                System.out.println("Interrupted");
                break;
            }
             //If we close the game, stop the thread/while loop. 
            if (TurnPolling.closedGame){
                break;
            }
        }

        playerButton.setDisable(false);
        refreshButton.setDisable(false);
        refreshButton.setText("Refresh");
        System.out.println("Thread ended");
        });
        pollThread.start();
    }catch (Exception e){
        e.printStackTrace();
    }
}

И в контроллере для файла gameScreen.fxml (не главного экрана, а загруженного через экраны входа в систему и Основное расширяемое приложение).

public void initialize(URL location, ResourceBundle resources) {
    playerButton.setDisable(!GameConnection.yourTurn(user_id, gameTurn));
    myStage.setOnCloseRequest(event -> TurnPolling.closedGame = true);
}

Прямо сейчас класс TurnPolling имеет только открытую статическую логическую переменную closedGame, чтобы не сохранять это в контроллере. Последняя строка, устанавливающая значение closedGame = true, фактически дает мне исключение NullPointerException, которое может быть из-за того, что этап еще не инициализирован, когда я делаю это в методе initialize ()?

Я бы хотел включить кнопки игроков только тогда, когда наступила их очередь, а также закрывать тему (если необходимо), когда игровой экран закрывается. Прямо сейчас вы должны нажать на кнопку, чтобы проверить, настал ли ваш ход, который снова проверяется каждые пять секунд и не остановится, когда вы закроете игру.

Пожалуйста, скажите мне, если вам нужно больше кода или разъяснений, это мой первый большой проект, так что я не знаю, сколько здесь поставить. Я знаю, что это не рабочий код, но это все, что я могу сделать, не чувствуя беспорядка. Спасибо за любые ответы!

1 Ответ

3 голосов
/ 03 апреля 2019

Во-первых, важно помнить, что запрещено изменять узлы JavaFX в любом потоке, кроме потока приложения JavaFX.Итак, ваш поток должен будет переместить эти строки:

playerButton.setDisable(false);
refreshButton.setDisable(false);
refreshButton.setText("Refresh");

в Runnable, который передается в Platform.runLater :

Platform.runLater(() -> {
    playerButton.setDisable(false);
    refreshButton.setDisable(false);
    refreshButton.setText("Refresh");
});

Обратите внимание, что меняется наВаше поле TurnPolling.closedGame в одном потоке может быть невидимым в другом потоке, если оно не объявлено volatile.Начиная с Спецификации языка Java :

Например, в следующем (неработающем) фрагменте кода предположим, что this.done не является полем volatile boolean:

while (!this.done)
    Thread.sleep(1000);

Компилятор может прочитать поле this.done только один раз и повторно использовать кэшированное значение при каждом выполнении цикла.Это будет означать, что цикл никогда не завершится, даже если другой поток изменил значение this.done.

Использование задачи и службы

JavaFX обеспечивает более чистое решение для всего этого: Задача и Служба .

Служба создает задачи.Сервис имеет свойство привязываемого значения, которое всегда равно значению последней созданной Задачи.Вы можете привязать свойства своей кнопки к свойству значения Сервиса:

int user_id = 0;

Service<Boolean> turnPollService = new Service<Boolean>() {
    @Override
    protected Task<Boolean> createTask() {
        return new Task<Boolean>() {
            @Override
            protected Boolean call()
            throws InterruptedException {

                updateValue(true);

                String gamePin = Context.getContext().getGamePin();

                while (!GameConnection.yourTurn(user_id, gamePin)) {
                    Thread.sleep(5000);

                    if (TurnPolling.closedGame){
                        break;
                    }
                }

                return false;
            }
        };
    }
};

playerButton.disableProperty().bind(turnPollService.valueProperty());
refreshButton.disableProperty().bind(turnPollService.valueProperty());

refreshButton.textProperty().bind(
    Bindings.when(
        turnPollService.valueProperty().isEqualTo(true))
        .then("Waiting for your turn\u2026")
        .otherwise("Refresh"));

Когда ход игрока закончится, вы бы позвонили turnPollService.restart();.

Независимо от того, используете ли вы Сервис или просто используетеPlatform.runLater, вам все еще нужно сделать TurnPolling.closedGame потоко-безопасным, либо сделав его volatile, либо заключив все обращения к нему в synchronized блоки (или блокировку защиты).

...