0
\$\begingroup\$

I have coded a simple card game that updates the GUI each second and contains many nodes in game which are used to display game events and animation.

People who have tried the game said that their system slows down and memory and CPU usage goes up (I was shocked when I saw from one of my friend's screen that it used around 1.4 GB RAM) is this supposed to happen?

I have checked some questions related to JavaFX memory usage problems but could not relate to my matter. In one article, I read that using many nodes in a JavaFX game might result in high memory usage.

In a short runtime:

minimum memory usage is around 100-250 MB

maximum memory usage is around 800-1200 MB

average memory usage is around 500-700 MB

IMPORTANT:

THERE ARE 100 BUTTONS IN A 10X10 GRIDPANE WHICH DO NOT HAVE FX IDS

In gameButtonClicked matches are found buttons.indexOf(event.getSource())

UML DIAGRAM

Main.java

import javafx.application.Application;  
import javafx.application.Platform;  
import javafx.fxml.FXMLLoader;  
import javafx.scene.Parent;  
import javafx.scene.Scene;  
import javafx.scene.media.Media;  
import javafx.scene.media.MediaPlayer;  
import javafx.stage.Stage;  
import javafx.util.Duration;  
import java.net.URL;  
import java.util.ArrayList;  
import java.util.Collections;  
import java.util.HashMap;  
import java.util.Random;  
import java.util.concurrent.atomic.AtomicLong;


public class Main extends Application {
    static boolean isMuted;
    static Stage window;
    static MediaPlayer mediaPlayerBGM;
    static MediaPlayer mediaPlayerSFX;
    private static HashMap<String, Media> sounds;
    private static ArrayList<Long> usage;
    private final String[] SOUND_LIST = {"bgm_credits.mp3", "bgm_game.mp3", "bgm_game_1.mp3", "bgm_game_2.mp3", "bgm_game_3.mp3",
            "bgm_how_to.mp3", "bgm_menu.mp3", "bgm_victory.mp3", "sfx_button_clicked.wav",
            "sfx_card_unfold.wav", "sfx_toggle.wav"
    };

    public static void main(String[] args) {
        launch(args);
        AtomicLong sum = new AtomicLong();
        usage.forEach(sum::addAndGet);
        long average = sum.get() / usage.size();
        System.out.printf("minimum usage: %d, maximum usage: %d, average usage: %d",
                Collections.min(usage), Collections.max(usage), average);
    }

    static void playBGM(String key) {
        mediaPlayerBGM.stop();
        mediaPlayerBGM.setStartTime(Duration.ZERO);
        if (key.equals("bgm_game")) {
            String[] suffix = {"", "_1", "_2", "_3"};
            Random random = new Random();
            mediaPlayerBGM = new MediaPlayer(sounds.get(key + suffix[random.nextInt(4)]));
        } else {
            mediaPlayerBGM = new MediaPlayer(sounds.get(key));
        }
        mediaPlayerBGM.setCycleCount(MediaPlayer.INDEFINITE);
        if (isMuted) {
            mediaPlayerBGM.setVolume(0.0);
        }
        mediaPlayerBGM.play();
    }

    static void playSFX(String key) {
        if (mediaPlayerSFX != null) {
            mediaPlayerSFX.stop();
        }
        mediaPlayerSFX = new MediaPlayer(sounds.get(key));
        if (isMuted) {
            mediaPlayerSFX.setVolume(0.0);
        }
        mediaPlayerSFX.play();
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        sounds = new HashMap<>();
        usage = new ArrayList<>();
        isMuted = false;
        for (String soundName :
                SOUND_LIST) {
            URL resource = getClass().getResource("/" + soundName);
            System.out.println(soundName);
            System.out.println(resource.toString());
            sounds.put(soundName.substring(0, soundName.lastIndexOf('.')), new Media(resource.toString()));
        }
        mediaPlayerBGM = new MediaPlayer(sounds.get("bgm_menu"));
        mediaPlayerBGM.setCycleCount(MediaPlayer.INDEFINITE);
        mediaPlayerBGM.play();
        window = primaryStage;
        Parent root = FXMLLoader.load(getClass().getResource("menu.fxml"));
        // long running operation runs on different thread
        Thread thread = new Thread(() -> {
            Runnable updater = () -> {
                if (!Game.isGameIsOver() && Game.getScore() != 0 && window.getTitle().equals("The Main Pick") &&
                        Game.firstClickHappened()) {
                    Game.scoreCalculator();
                }

            };

            while (true) {
                try {
                    usage.add(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
                    System.out.printf("Used memory: %d\n", Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                    System.out.println("Interrupted");
                }

                // UI update is run on the Application thread
                Platform.runLater(updater);
            }
        });
        // don't let thread prevent JVM shutdown
        thread.setDaemon(true);
        thread.start();
        window.setTitle("Main Menu");
        window.setScene(new Scene(root, 600, 600));
        window.setResizable(false);
        window.show();
    }
}

Controller.java

package com.sample;

import javafx.animation.FadeTransition;  
import javafx.collections.ObservableList;   
import javafx.event.ActionEvent;  
import javafx.fxml.FXML;  
import javafx.fxml.FXMLLoader; 
import javafx.scene.Node;  
import javafx.scene.Parent;   
import javafx.scene.Scene;  
import javafx.scene.control.Button;  
import javafx.scene.control.Label;  
import javafx.scene.control.RadioButton;  
import javafx.scene.layout.GridPane;  
import javafx.util.Duration;  
import java.io.IOException;

public class Controller
{
    @FXML
    public RadioButton musicOnOff;
    @FXML
    public Button newGame;
    @FXML
    public Button howTo;
    @FXML
    public Button credits;
    @FXML
    public Button exit;
    @FXML
    public Button menu;
    @FXML
    public GridPane pane;
    @FXML
    public Label score;
    @FXML
    public Label time;
    @FXML
    public Label tries;
    private boolean animating;
    public void initialize() {
        if (score != null && time != null && tries != null) {
            score.textProperty().bind(Game.scoreProperty);
            time.textProperty().bind(Game.timeProperty);
            tries.textProperty().bind(Game.triesProperty);
            animating=false;
        }
        if (musicOnOff!=null){
            if(Main.isMuted){
                musicOnOff.setSelected(true);
            }
        }
    }

    public void newGameButtonClicked() {
        try {
            Main.playSFX("sfx_button_clicked");
            new Game();
            Main.window.hide();
            Main.window.setScene(getScene("game"));
            Main.window.setTitle("The Main Pick");
            Main.window.setMaximized(true);
            Main.playBGM("bgm_game");
            Main.window.show();
        } catch (IOException e) {
            System.out.println("could not change the scene to: game");
        }
    }

    public void menuButtonClicked() {
        try {
            Main.playSFX("sfx_button_clicked");
            Main.playBGM("bgm_menu");
            if (Main.window.getTitle().equals("The Main Pick")) {
                Main.window.hide();
                Game.setGameOver();
                Main.window.setScene(getScene("menu"));
                Main.window.setMaximized(false);
                Main.window.show();
            } else {
                Main.window.setScene(getScene("menu"));
            }
            Main.window.setTitle("Main Menu");
        } catch (IOException e) {
            System.out.println("could not change the scene to: game");
        }
    }

    public void gameButtonClicked(ActionEvent event) {
        ObservableList<Node> buttons = pane.getChildren();
        int index = buttons.indexOf(event.getSource());
        int column = index % 10;
        int row = (index - index % 10) / 10;
        if (!((Button) event.getSource()).getStyleClass().toString().equals("button button-treasure") &&
                !((Button) event.getSource()).getStyleClass().toString().equals("button button-uncovered"))
        {

            FadeTransition transition = new FadeTransition();
            transition.setNode((Button) event.getSource());
            transition.setDuration(new Duration(500));
            transition.setFromValue(1.0);
            transition.setToValue(0.0);
            transition.setCycleCount(1);
            transition.setOnFinished(actionEvent -> {
                if (((Button) event.getSource()).getStyleClass().toString().equals("button button-treasure")) {
                    loadVictoryScene();

                }
                if (!((Button) event.getSource()).getStyleClass().toString().equals("button button-treasure") &&
                        !((Button) event.getSource()).getStyleClass().toString().equals("button button-uncovered"))
                {

                    transition.setFromValue(0.0);
                    transition.setToValue(1.0);
                    System.out.println(((Button) event.getSource()).getStyleClass().toString());
                    ((Button) event.getSource()).getStyleClass().remove("button-covered");
                    ((Button) event.getSource()).getStyleClass().add(Game.click(row, column));
                    transition.play();
                    transition.setOnFinished(ActionEvent->animating=false);
                }
            });
            System.out.println(animating);
            if(!animating){
            animating=true;
            transition.play();
            Main.playSFX("sfx_card_unfold");}
        }

        System.out.printf("button index:%d,row:%d,column:%d\n", index, row, column);
    }

    public void howToPlayButtonClicked() {
        try {
            Main.playSFX("sfx_button_clicked");
            Main.playBGM("bgm_how_to");
            Main.window.setScene(getScene("howTo"));
            Main.window.setTitle("How to Play");
        } catch (IOException e) {
            System.out.println("could not change the scene to: how to play");
        }
    }

    public void creditsButtonClicked() {
        try {
            Main.playSFX("sfx_button_clicked");
            Main.playBGM("bgm_credits");
            Main.window.setScene(getScene("credits"));
            Main.window.setTitle("Credits");
        } catch (IOException e) {
            System.out.println("could not change the scene to: credits");
        }
    }

    public void exitButtonClicked() {
        Main.playSFX("sfx_button_clicked");
        Main.window.close();
    }

    public void musicOnOffRadioButtonChecked() {
        Main.playSFX("sfx_toggle");
        if (musicOnOff.isSelected()) {
            Main.isMuted=true;
            Main.mediaPlayerBGM.setVolume(0.0);
            Main.mediaPlayerSFX.setVolume(0.0);
            System.out.println("now selected");
        } else {
            Main.isMuted=false;
            Main.mediaPlayerBGM.setVolume(1.0);
            Main.mediaPlayerSFX.setVolume(1.0);
            System.out.println("unselected");
        }
    }

    private void loadVictoryScene() {
        try {
            Main.window.hide();
            Main.playBGM("bgm_victory");
            Main.window.setScene(getScene("victory"));
            Main.window.setTitle("Victory");
            Main.window.setMaximized(false);
            Main.window.show();
        } catch (IOException e) {
            System.out.println("could not change the scene to: victory");
        }
    }

    private Scene getScene(String name) throws IOException {
        Parent root = FXMLLoader.load(getClass().getResource(name + ".fxml"));
        if (name.equals("game")) {
            return new Scene(root, 800, 800);
        }
        return new Scene(root, 600, 600);
    }
}

Game.java

package com.sample;

import javafx.beans.property.SimpleStringProperty;  
import javafx.beans.property.StringProperty;  
import java.util.Random;

public class Game {
    static StringProperty scoreProperty;
    static StringProperty timeProperty;
    static StringProperty triesProperty;
    private static int time;
    private static int score;
    private static int tries;
    private static int[][] tiles;
    private static boolean gameIsOver;
    private static boolean firstClick;

    public Game() {
        System.out.println("Game created...");
        tries = 0;
        score = 100000;
        time = 0;
        gameIsOver = false;
        firstClick = false;
        scoreProperty = new SimpleStringProperty("" + score);
        timeProperty = new SimpleStringProperty("0");
        triesProperty = new SimpleStringProperty("0");
        tiles = new int[10][10];
        Random random = new Random();
        int treasureColumn = random.nextInt(10);
        int treasureRow = random.nextInt(10);
        tiles[treasureRow][treasureColumn] = 1;
    }

    static boolean firstClickHappened() {
        return firstClick;
    }

    static void setGameOver() {
        gameIsOver = true;
    }

    static boolean isGameIsOver() {
        return gameIsOver;
    }

    static int getScore() {
        return score;
    }

    static void scoreCalculator() {
        time++;
        if (time < 10) {
            score = score - 100;
        } else if (time < 20) {
            score = score - 200;
        } else if (time < 30) {
            score = score - 300;
        } else if (time < 50) {
            score = score - 500;
        } else {
            score = score - 1000;
        }
        if (score < 0) {
            score = 0;
        }
        scoreProperty.setValue("" + score);
        timeProperty.setValue("" + time);
        triesProperty.setValue("" + tries);
        System.out.printf("Score:%s,Time:%s,Tries%s\n", scoreProperty.getValue(), timeProperty.getValue(), triesProperty.getValue());
    }


    static String click(int row, int column) {
        if (!firstClickHappened()) firstClick = true;
        System.out.println(row + "," + column);
        int clickValue = tiles[row][column];
        System.out.println(clickValue);
        if (clickValue == 0) {
            tries++;
            score -= 1000;
        } else {
            setGameOver();
        }
        return (clickValue == 1) ? "button-treasure" : "button-uncovered";
    }
}
\$\endgroup\$
  • \$\begingroup\$ What is your definition of "adequate resources"? \$\endgroup\$ – Mast Jan 28 at 19:02
  • 1
    \$\begingroup\$ @Mast I am a second year cs student so I do not know many things about performance stuff, by adequate I mean for example calculating the sum of positive integers 5-10 can be easily done with a loop and will require less time and processing(?) but doing it recursively will require more time and processing; so maybe "optimum values"? \$\endgroup\$ – Altuğ Ceylan Jan 28 at 19:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.