Use ScenePulseListener to avoid expensive recalculations?

Martin Sladecek martin.sladecek at oracle.com
Thu Nov 7 00:10:09 PST 2013


It depends on what you need to recompute. If you invalidate some 
properties and the internal layout depends on these properties, then 
also your pref/min size depends on these properties.

The layout (simplified) usually works in 3 steps: 1)compute the layout 
using min/pref/max size of children (here the compute* methods of child 
will be called)
2) children are resized to their width/height depending on the layout
3) layouChildren of a child is called, where these 3 steps are done for 
the child, using the newly provided size from step 2

This has several consequences: As compute* methods are called before 
layoutChildren, you need to do recomputation there. The recomputed 
values can be cached and reused in layoutChildren. The other thing is 
that you can't rely on getting exactly pref width/height, so you can 
recompute only things that don't depend on actual size of the component. 
The exact size will be known in layoutChildren() call.

It would be great to have some invalidate/recompute API that would be 
called automatically before compute* and layoutChildren method. I've 
filed a JIRA issue for that: https://javafx-jira.kenai.com/browse/RT-34118

-Martin

On 11/06/2013 08:43 AM, Yennick Trevels wrote:
> Isn't it somewhat counter-intuitive to perform such changes in one of the
> computePref*() methods? You're adding children in a method which is
> supposed to compute the preferred width/height.
>
> There should be a better way to do this. In Apache Flex for example,
> there's a commitProperties() and updateDisplayList() method in which you
> can do these things, which are called somewhat similar to a pulse in JavaFx
> (except that you have to invalidate them yourself).
>
>
> On Tue, Nov 5, 2013 at 8:10 PM, Jonathan Giles <jonathan.giles at oracle.com>wrote:
>
>> You're right in that controls don't tend to use ScenePulseListener. This
>> may be due to an oversight on our part, or just that our requirements
>> differ (I don't honestly know).
>>
>> You're also right that it is important to batch up property changes and
>> do them once per pulse, rather than as they come in. If this is not done
>> you do indeed get a severe performance hit (I remember back in the day
>> before I optimised many of the controls in this way, the performance
>> impact of not doing this was staggering).
>>
>> The way I do it is simple: in places where I receive events from
>> properties / bindings / listeners, where I know that there is
>> potentially a lot of changes coming through, I create a boolean (e.g.
>> 'refreshView' or somesuch). I then set this boolean to true, and in the
>> appropriate place in the code (most commonly layoutChildren(), but
>> sometimes in the computePref*() methods), I start the method with code
>> like this:
>>
>> if (refreshView) {
>>      doExpensiveOperationThatShouldHappenOnlyOncePerPulse();
>>      refreshView = false;
>> }
>>
>> The reason why I sometimes use layoutChildren() and sometimes the
>> computePref*() methods comes down to the (new) rule in JavaFX 8.0 that
>> you can not change the children list of a node within the
>> layoutChildren() method. Because of this, if I need to modify the
>> children list, I put the above code in the computePref*() method.
>>
>> I hope that helps.
>>
>> -- Jonathan
>>
>> On 6/11/2013 3:58 a.m., John Hendrikx wrote:
>>> Hi List,
>>>
>>> I'm considering using a ScenePulseListener to avoid expensive
>>> recalculations when multiple bindings trigger an action.
>>>
>>> My problem is this:
>>>
>>> I like to build Views (Controls) that are dumb and expose properties
>>> that can be manipulated by whatever is using the View.  The View
>>> listens to its own Properties and adjusts its state accordingly.  Some
>>> of these adjustments are related (like width + height) and can be
>>> expensive when calculated immediately.  So I would like to mark the
>>> View as "invalid" and recalculate its state (if invalid) on the next
>>> Pulse.
>>>
>>> My current use case I'm looking at is a View that wraps (amongst
>>> others) a TreeView.  The View exposes an ObservableList and a
>>> BooleanProperty that decides whether the first level of the Tree
>>> should be displayed as Tabs or as Nodes (which has an impact on what
>>> Nodes actually are added to the TreeView, and which are added as
>>> Tabs).  User code will thus often set a new list of nodes + change the
>>> boolean to show tabs or nodes.  The View currently naively has
>>> InvalidationListeners on both of these properties which cause
>>> TreeNodes to be created after the first change... then discarded and
>>> recreated after the second change by the user code, ie:
>>>
>>>    view.nodesProperty().setAll(nodes);   // Recreates all
>>> Tabs/TreeNodes with the current value of expand top level, as we donot
>>> know another change might be incoming...
>>>    view.expandTopLevelProperty().set(false);   // Recreates all
>>> Tabs/TreeNodes again if expand top level was changed...
>>>
>>> This specific problem might be done in a better way, but the point
>>> remains, how do I avoid expensive calculations when multiple
>>> properties get changed one after the other by the user code?  I'm
>>> assuming that JavaFX controls already avoid these kinds of things, and
>>> I'd like to know whether using a ScenePulseListener is the way to go,
>>> or that it can/should be done in a different way -- examining the code
>>> for TreeView (and its superclasses), I couldn't find uses of
>>> ScenePulseListener...
>>>
>>> --John
>>>
>>>
>>>
>>>
>>



More information about the openjfx-dev mailing list