12

When loading in a new FXML and setting the center of a BorderPane there is a brief 'freeze' of the application where existing animation, whether from a Timeline, or from a gif in an image view, will stop. I'm using this code to change the centerView:

 @FXML
    public void handleChangeView(ActionEvent event) {
        Task<Parent> loadTask = new Task<>() {
            @Override
            public Parent call() throws IOException {

                String changeButtonID = ((ToggleButton) event.getSource()).getId();
                Parent newOne = getFxmls().get(changeButtonID);

                if (newOne == null) {
                    FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/" + changeButtonID + ".fxml"));
                    newOne = loader.load();
                    getFxmls().put(changeButtonID, newOne);
                }
                return newOne ;
            }
        };

        loadTask.setOnSucceeded(e -> {
                    getMainUI().setCenter(loadTask.getValue());
        });

        loadTask.setOnFailed(e -> loadTask.getException().printStackTrace());

        Thread thread = new Thread(loadTask);
        thread.start();
    }

And while this does the job in keeping the UI responsive in the load time, when the center stage displays for the first time there is a noticable lag. Looking at a CPU profile:

enter image description here

I'm not sure if the delay is from the initialize function running, or the loading of the elements. Here's a gif of the program, you can see the visible delay:

enter image description here

By loading it up in VLC and progresing frame-by-frame the delay looks to be 5 or 6 frames in a 30 fps video, meaning about a 200ms delay which implies the animation freezes for more than the 50ms or so initialize method. (Maybe the entire load method freezes the animation?)

The question is, is it possible to keep the animation smooth during this?

//********* EDIT **********//

So I went through the project and cut out as many methods and classes as possible, reducing the entire game to 6 minimal classes. I STILL have the delay in pressing the character button ('you' button). With such a minimal example I'm lost to what could be wrong. I've uploaded this to google drive for anyone to take a look.

https://drive.google.com/open?id=17A-PB2517bPJc8Dek-yp2wGXsjTyTj1d

AlwaysNeedingHelp
  • 1,851
  • 3
  • 21
  • 29
  • 2
    Since you are performing the load on a separate thread it shouldn't cause the UI thread to stutter and the profiling info for that thread aren't really relevant. OTOH, I am surprised it works, as FXML loading and node initialization mostly need to be done on the UI thread. Can you share the code of the relevant `initialize` method? – Itai Jun 17 '18 at 08:58
  • It would be quite a lot of code to share. The method does all the bindings between the model and the FXML nodes, as well as filling the inventories with nodes that have the drag/drop/mouse events to make the drag and dropping of inventories & tooltips work. It would probably be easier to share the entire project on google drive if you were interested in taking a look. I just tried it again but commented out the entire initialize method - there still is a stutter of the same length so it can't be the initalize method. Surely it must be the loading and drawing of the nodes themselves? – AlwaysNeedingHelp Jun 17 '18 at 18:28
  • @Itai If you have time, I went through and stipped the program down to 6 classes cutting out pretty much everything. I still have the same delay. I've editted the main post with a link to the project, would love it if you, or anyone reading this will have a look. I'll put a 100 bounty up on this once its eligible if not solved by then. At this point I'm thinking there's something fundemental in how I've structured the program that's causing this. – AlwaysNeedingHelp Jun 18 '18 at 10:49
  • Try to wrap part of your code in `code` Platform.runlater(->{ //your code }); – Joao Jun 21 '18 at 21:59
  • Which part though? – AlwaysNeedingHelp Jun 22 '18 at 15:46
  • 2
    The freeze is caused from `getMainUI().setCenter(loadTask.getValue());` inside the `setOnSucceeded()` and only once, i mean if you run the process again removing the center and add it again there isnt any kind of lag. And this has nothing to do about the FXML also cause if for example you skip the FXML loading and just set a TextField on the center you will notice a smaller lag again. – JKostikiadis Jun 22 '18 at 17:55
  • 2
    Also wrapping the the code getMainUI().setCenter(loadTask.getValue()); with a Platform.runLater change nothing at all. The problem appear to be more or less a rendering issue of JavaFX of the Node you set on center of the BorderPane, which cause the main UI to freeze. – JKostikiadis Jun 22 '18 at 18:07
  • Thanks for having a look @JKostikiadis. Do you know if there is anyway to prevent the freeze from happening? As you said it only happens the first time - the other times seem to be fine. If it's not the FXML could it be something about the .setCenter method that causes lag? – AlwaysNeedingHelp Jun 23 '18 at 08:21
  • 1
    I was thinking about it a lot and the problem seems to be the rendering of the node you are trying to add on center. Even if you try to make a new stage instead of setting the node to the center of your main stage the freeze will happened again. I would like to check the code more but right now I am unable to. I will give it a look tomorrow and let you know if I find anything. – JKostikiadis Jun 23 '18 at 10:45
  • Thanks I appreciate it. I'll also see if I can come up with anything! – AlwaysNeedingHelp Jun 23 '18 at 10:52
  • 1
    I had a chance to check your code again, there are a lot of things that I don't like to be honest. The fact that almost every method, field and UI element are static is something no one will recommend. In addition, you have a master controller which you share with multiple viewer which is really a bad idea/architecture but not only you define that controller through FXML, you also create and use an instance of it through Java code. Still with all the above, I can't say for sure that what is causing the lagging effect. – JKostikiadis Jun 25 '18 at 18:11
  • Thanks for having another look. I needed my controllers to all have access to the same things - the model etc - so having them inherit it from a MasterController seemed the only solution I could think of. And since it's the same model/elements always making them static seemed obvious. thanks again – AlwaysNeedingHelp Jun 25 '18 at 22:08

1 Answers1

2

There is nothing "wrong" with your code (at least in regards to this issue).

The issue you are experiencing also has nothing to do with the loading of the FXML (which is very slow and you have correctly handled off FX-Thread).

The stutter happens for These reasons:

  • relativly large hierarchy in character.fxml
  • lots of CSS (delete the main.css and you will notice; the stutter is slightly less prominent)
  • dynamically changing the scene graph (adding/removing Nodes during runtime)

Every time you replace the center of the mainView with some large Node, it causes the JavaFx runtime to completely re-layout and re-style (at least) that node. This happens on the FX-Thread, hence you notice the stutter.

One possible mitigation is a classic game dev technique: Pre-allocating as much as possible. Simply load all necessary FMXLs once during startup and put them into the scene graph. In your click handlers then, change the visibility or (Z-)position of the Nodes you want to show/hide. This is a good use case for a StackPane for example.

I adapted your code a little to demonstrate what I mean: Prototype

Check out these ressources to learn more:

Oliver Jan Krylow
  • 1,758
  • 15
  • 22
  • Thank you for the detailed explanation, the example, and the further reading. I appreciate you taking the time (and the other commentators) greatly. – AlwaysNeedingHelp Jun 26 '18 at 21:06