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:
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:
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
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:
http://gameprogrammingpatterns.com/
https://stackoverflow.com/a/26537688/1271937
https://www.javaworld.com/article/2074652/core-java/javaone-2012-javafx-graphics-tips-and-tricks.html
Related
I am creating a JavaFX application that uses sounds for example for Button hovering. I create an JavaFX AudioClip to create and play the sound. It works ok so far (meaning: I hear the sound).
When the play(); method is called, the sound is played immediatly. If I hover the button 10 times, I hear the sound 10 times.
BUT: in the background JavaFX is creating hundreds of threads when the play() method returns (several hundred for each call). I cannot even see what it actually is, because there are so many, Eclipse does not even show them properly (there is just a white area and a crazy jumping scrollbar up and down).
This causes a massive lag and I do not understand what JavaFX is doing here! It is always the same sound, so I do have cached it into a hashmap already, but the problem is not instantiating the AudioClip, it is clearly stacking up when the play() method returns.
I have been looking into this for hours, but I can't figure out a workaround to reduce the lag (other than maybe reduce the size of the soundfiles, which I did).
final AudioClip soundClip;
if (audioClipCache.get(url.toString()) != null) {
// Log.info("Playing sound from cache...");
soundClip = audioClipCache.get(url.toString());
} else {
// Log.info("Caching sound...");
soundClip = new AudioClip(url.toString());
audioClipCache.put(url.toString(), soundClip);
}
Platform.runLater(new Runnable() {
#Override
public void run() {
soundClip.play(soundVolume);
}
});
Forget the hashmap to cache the AudioClips for a moment, that does not make any difference whatsoever. So called the following code, say, 10 times in a row, leads Java to go crazy for about 10 seconds.
AudioClip soundClip = new AudioClip(url.toString());
soundClip.play(soundVolume);
That works as it should (as in 5.000.000 examples across the internet), but it produces Threads (and Lag) like crazy after the play(); method is called (several hundred threads per hover / call (as descibed)).
I think you have a problem elsewhere in your code. I've distilled your description down to this (admittedly simplistic) example:
#Override
public void start(Stage arg0) throws Exception
{
Thread.sleep(10000);
AudioClip audioClip = new AudioClip(Paths.get("src/main/resources/stackoverflow/audio/alert.wav").toUri().toString());
for (int i = 0; i < 100; i++)
{
int volume = i;
Platform.runLater(() -> audioClip.play(volume));
Thread.sleep(10);
}
arg0.show();
}
Watching Java Mission Control at the ten second mark, I see 100 threads get created all at once. However, they all immediately die (they've played the short alert sound and are done). So the "Live Thread" graph spikes up by 100 threads and then drops right back to where it was within a couple of seconds.
Is there something elsewhere in your code that is holding onto a resource or a thread reference? That's my best suggestion for trying to find out why you're multiplying.
It is bad practice to do big jobs on the UI thread as if you do, those big jobs will cause the program to hang (not accept user input or render any new data) until that job is finished.
I am looking to add a widget to our code base that will indicate to developers when they have committed this taboo. My idea, and one I've seen on a number of other applications, is to have some component that is constantly moving at a constant speed, such as a bar that is constantly twirling on the screen. With such a tool, if a developer is working and accidentally does something that is more computationally difficult than he expected on the UI thread, this spinning bar will become choppy, indicating to him, when he does functional testing, that he needs to implement mechanisms that will cause this job to be executed elsewhere.
One odd requirement on this code is that it should be completely non-existent in production builds, and only present in dev builds, since it is a widget not for users, but for developers.
I jumped into the Canvas objects and wrote up a quick component that simply spins a teal bar. The idea is that if a big job is dumped on the UI thread, the bar will stop spinning (since the FX job queue wont continue dispatching) and the bar will jump forward, rather than rotate smoothly (as it does when the program is at rest).
Below is a screen-shot of this first implementation:
(notice the teal bars, which, if you saw our application running, would be rotating slowly but steadily --hopefully anyways)
The issue here (as you might notice) is that our layout's been screwed up. This is because I'm modifying the scene graph from this:
Scene
RootComponent
Content
to
Scene
obnoxiousPane
Canvas
Spinner(s)
RootComponent
Content
Modifying the scene graph in such a way has things like preferred height, mouse events and (presumably) any number of other events getting dispatched to the spinners rather than the content components.
Of course, when we go to production, I would like to have the original scene graph in the version that we give to our users.
So my question is this: How should I go about correcting these problems?
I could go after each of them individually as they come up, writing a lot of custom code to do things like
obnoxiousPane.prefHeightProperty().bind(content.prefHeightProperty)
obnoxiousPane.prefWidthProperty()//...
spinner.setMouseTransparent(true)
spinner.setOtherEventsIProbablyCantEnumerateWithoutSeriousResearchTransparent(true)
Or I could try to go after this problem with reflection, attempting to bind every property in the content pane to the corresponding obnoxiousPane property, but this seems like a bug breeding ground.
Or... what? I'm hoping there's some LightWeight component or ImNotReallyHereProperty that I can leverage to add this development aid.
Thanks for any help!
Your approach seems fundamentally flawed. You shouldn't be stalling the JavaFX application thread.
Instead you should have a concurrent process and update the UI as appropriate as the process starts, progresses and completes.
See this java2s sample for using the JavaFX concurrency and progress indicator facilities for an example of such an alternate approach.
If you want to disable some portion of the UI for a time, nodes have a disabled property which you can set. You can use CSS to style a disabled node so the user has some indication that the thing hasn't just hung and is deliberately disabled.
I'm having a layout problem when adding a Label to a Pane which is added to another Pane.
Like in this example:
public class MyClass extends Pane {
private final Pane myPane;
public MyClass() {
this.myPane.prefWidthProperty().bind(this.widthProperty);
this.myPane.prefHeightProperty().bind(this.heightProperty);
this.getChildren().add(this.myPane);
}
#Override
layoutChildren() {
this.foo();
}
private void foo() {
this.myPane.getChildren().add(new Label("foo"));
}
}
The problem is that it just keeping calling layoutChildren infinitely. One thing that is weird is that if I add a Text instead of a Label, the "problem" doesn't occour.
I've checked every node sizes and they don't change. It seens to me that someone is expanding and for that the layout is called, but I just can't find where.
Is there something trivial that I'm missing?
layoutChildren is called constantly by the QuantumToolkit. The entire scene graph is traversed and each node has this method called during the lifetime of the application. It never ends.
From Oracle:
Threads
The system runs two or more of the following threads at any given time.
JavaFX application thread: This is the primary thread used by JavaFX application developers. Any “live” scene, which is a scene that is part of a window, must be accessed from this thread. A scene graph can be created and manipulated in a background thread, but when its root node is attached to any live object in the scene, that scene graph must be accessed from the JavaFX application thread. This enables developers to create complex scene graphs on a background thread while keeping animations on 'live' scenes smooth and fast. The JavaFX application thread is a different thread from the Swing and AWT Event Dispatch Thread (EDT), so care must be taken when embedding JavaFX code into Swing applications.
Prism render thread: This thread handles the rendering separately from the event dispatcher. It allows frame N to be rendered while frame N +1 is being processed. This ability to perform concurrent processing is a big advantage, especially on modern systems that have multiple processors. The Prism render thread may also have multiple rasterization threads that help off-load work that needs to be done in rendering.
Media thread: This thread runs in the background and synchronizes the latest frames through the scene graph by using the JavaFX application thread.
Pulse
A pulse is an event that indicates to the JavaFX scene graph that it is time to synchronize the state of the elements on the scene graph with Prism. A pulse is throttled at 60 frames per second (fps) maximum and is fired whenever animations are running on the scene graph. Even when animation is not running, a pulse is scheduled when something in the scene graph is changed. For example, if a position of a button is changed, a pulse is scheduled.
When a pulse is fired, the state of the elements on the scene graph is synchronized down to the rendering layer. A pulse enables application developers a way to handle events asynchronously. This important feature allows the system to batch and execute events on the pulse.
Layout and CSS are also tied to pulse events. Numerous changes in the scene graph could lead to multiple layout or CSS updates, which could seriously degrade performance. The system automatically performs a CSS and layout pass once per pulse to avoid performance degradation. Application developers can also manually trigger layout passes as needed to take measurements prior to a pulse.
The Glass Windowing Toolkit is responsible for executing the pulse events. It uses the high-resolution native timers to make the execution.
Layout Children is called 60 times/second on all nodes that have been altered in some way. So if a child deep in the graph is changed, all parents of that child will have layoutChildren called.
Your overriden layout method invokes foo, which in turn causes a new Label to be added. Unless I am mistaken, this will cause another layout pass to be initiated, which in turn will add a new Label, thus causing your infinite cycle.
Description:
I have a StackPane as parent container (display) and my components are extending Region and kept in a ConcurrentLinkedQueue. The display of the components are not a problem, even the animation work as I wanted in sync with a timeline (the timeline records the whole process. E.g. min 2 -> Circle pop's up and does some fading + transition, etc.). All nice until now.
Now my problem comes: first of all I want after I consume and play this component to delete it from the display and from the Queue.
Second thing, now when I run this and I have two components that they are at the same time they run together in parallel, even though they are polled from queue (that I don't understand). So I need something that delays or waits until the first component was played, after this delete this component and play the next.
Any suggestion will be awesome!
//show message
public void showMessages() {
while(!messageQueue.isEmpty()) {
playMessage(messageQueue.poll());
}
}
/**
* Searches for the source and target component, finds the connection points and prepares the message to be played
* #param mess Visual message node, that will appear on dashboard
* If no source and target component will be found this will return without doing anything
*/
public void playMessage(MessageNodeVGT mess) {
//get the source and the target of the message
CompNodeVGT source=getComponent(mess, SOURCE);
CompNodeVGT target=getComponent(mess, TARGET);
if(source==null || target == null) {
return;
}
Point2D sourcePoint=get2DPoint(source.impl_getShape(),target.impl_getShape());
Point2D targetPoint=get2DPoint(target.impl_getShape(),source.impl_getShape());
mess.setSourceCoordinates(sourcePoint);
mess.setTargetCoordinates(targetPoint);
instance.getChildren().add(mess.createPath());
instance.getChildren().add(mess);
mess.play();
}
Due to the fact that I have two timelines (one general for the whole application and one local for the animation/s) the lock or other technique didn't help. Here it's why:
1st local animation ~ 3seconds (duration)
2nd local animation ~ 3seconds (duration)
general timeline: |-----10s----20s---x-x2---..
let's say x=22s =2200 millis, this is when the 1st animation pops up if the second animation will be at x2 and if this x2 is in between 1 millis - 1000 millis (even more) I will see this as a parallel event, even this is not completely true.
What I did? I just paused the general timeline until the 1st animation finishes, then on setOnFinish I just resume the general timeline.
This is not a perfect answer, because actually with this Pause you lose some time in the case if you are trying to follow some event in Real time.
I'm open to other solutions, hope this helps others too.
My game takes around a minute to load in android till the first screen appears. Till the time its loading, jmonkey’s input manager seems to queue all inputs which results in nasty behavior(automatic button clicks) as soon as my first nifty screen loads.
Same happens when the scene loads(which again takes a while on pressing the appropriate nifty button). This happens despite the fact that I set mappings and listeners in the last App State which loads.
Is there a way to flush all previous input which I can call just before and after adding listeners to input manager?
I dont do much work in update() and initialize of my appstates but some functions (reinitialize()) which I call on nifty's OnClick(), loads all the scene and models in the scene garph so it takes a while. Here is a pseudo code of my application
In Main.java {
// Nothing in SimpleUpdate()
// This app state contains
stateManager.attach(new MainMenuAppState());
}
In MainMenuAppState.java implements ScreenController {
initialize() {
niftyDisplay = new NiftyJmeDisplay(app.getAssetManager(), app.getInputManager(), app.getAudioRenderer(), app.getGuiViewPort());
// Create a new nifty GUI object
nifty = niftyDisplay.getNifty();
// attach a couple of more app states which also has nothing significant in update loop
// do some good computation
// attach 5 new APP STATES which does not have anything significant in update()
display the appropriate screen of nifty
}
onClick() {
nifty.gotoScreen(“loadScreen”);
// appstate previously attached. they dont have anything significant in update.
// They have significant initialize methods.
app.enqueue(
rgas.reInitialize(time,cameraLoc,cameraRot);
maes.reInitialize(qId); // loads all the scene and models on screen
nifty.gotoScreen(“hudScreen”);
nifty.getScreen(“hudScreen”).findElementByName(“ConfirmModuleButton”).setFocus();
ppes.reInitialize(); // this contains input mappings
);
}
}
If there is a way to do this it will be on the InputManager so you could check out the API for that. Your problem may be though that the queue isn't really a queue in the way you are thinking. Potentially it is not a queue of input events but a queue of actions being taken in response to the events. Since events don't process until the update loop runs them then if the upload loop is stalled they will keep building up.
You could simply not add the listeners until the application has finished loading, then any events will get ignored automatically. You could also try breaking the scene loading up using a queue or similar of your own to load things a bit at a time while not completely stalling the system.
You may get a better response on this question if you try the jME3 forums. There are more monkeys active there than here including people with more detailed knowledge of the input system than me :)
I guess what Tim B said is your best bet.
However you could try calling nifty.setIgnoreMouseEvents(true) and nifty.setIgnoreKeyboardEvents(true) at some appropriate time to shut-off handling of any events that might reach Nifty and enable it later again.