Use ScenePulseListener to avoid expensive recalculations?

Tomas Mikula tomas.mikula at gmail.com
Thu Nov 7 16:05:27 PST 2013


Sorry, you are right, I realized that on my way out. So a
ChangeListener is necessary for my example to be right.

Anyway, the problem of multiple invalidations exists and I hope I
managed to demonstrate that the solution with ScenePulseListener
doesn't scale.

Regards,
Tomas

On Thu, Nov 7, 2013 at 8:55 PM, Martin Sladecek
<martin.sladecek at oracle.com> wrote:
> On 11/07/2013 04:03 PM, Tomas Mikula wrote:
>>
>> On Thu, Nov 7, 2013 at 3:34 PM, Martin Sladecek
>> <martin.sladecek at oracle.com> wrote:
>>>
>>> On 11/07/2013 03:18 PM, Tomas Mikula wrote:
>>>>
>>>> Hi Martin,
>>>>
>>>> On Thu, Nov 7, 2013 at 2:32 PM, Martin Sladecek
>>>> <martin.sladecek at oracle.com> wrote:
>>>>>
>>>>> This is something different. When properties depends on each other
>>>>> (using
>>>>> bindings), the binding computation is deferred to the first query
>>>>> (get()
>>>>> call) of the dependant.  That means, if q depends on p, you can call
>>>>> p.set()
>>>>> as many times you want, but the recomputation will be triggered just
>>>>> before
>>>>> first q.get() call.
>>>>
>>>> The point of my first example was inconsistency.
>>>> Suppose this scenario (remember, the invalidation listeners of p are
>>>> deferred until the next pulse):
>>>>
>>>> [pulse]
>>>> ...
>>>> p.set()
>>>> ...
>>>> q.get() // returned value not consistent with the value of p
>>>> ...
>>>> [pulse]
>>>
>>> It is consistent, providing that you use binding API for the computation.
>>
>> I probably forgot to state it explicitly that I'm considering an
>> implementation of properties/bindings that don't trigger the listeners
>> (invalidation/change) right away, but defer them until the next pulse.
>> Then again, the state of that hypothetical object above is not
>> consistent (until the next pulse).
>>
>>>> I like my objects to appear consistent to the outside world at all
>>>> times, so that the client code doesn't have to deal with the
>>>> inconsistencies.
>>>>
>>>>> Also in your second example, the second invalidation of
>>>>> s would be practically a no-op.
>>>>
>>>> It is not if you have an invalidation (or change) listener registered on
>>>> s.
>>>
>>> Only in case of change listener. Invalidation listener would not
>>> validate/recompute s.
>>
>> The invalidation listener will be executed twice. That itself is bad
>> enough. The invalidation listener itself can trigger an expensive
>> computation. Whether or not it (indirectly) causes the recomputation
>> of s. Consider, for example, N such [p <- s] black boxes, where a
>> single invalidation of p causes two invalidations of s, and chain them
>> sequentially. Then a single invalidation of p_1 causes 2^N
>> invalidations of s_N. That's bad enough, even though no property value
>> is being recomputed. This is, in fact, the behavior of the bindings
>> API you mentioned. What I'm saying is that deferring invalidation
>> listeners until the next pulse doesn't help either (except for simple
>> cases).
>
>
> No, the invalidation listener will be executed only once. The property needs
> to be validated before the invalidation listener can be called again.
>
> -Martin
>
>>
>> Cheers,
>> Tomas
>>
>>> -Martin
>>>
>>>> Tomas
>>>>
>>>>> Of course, this doesn't work if you have a ChangeListener on q, because
>>>>> the
>>>>> ChangeListener basically need to do get() in order to compute the new
>>>>> property value (which is passed as an argument to it's method).
>>>>>
>>>>> When it's not a property you want to recompute, but an internal state
>>>>> (you
>>>>> use to setup the children), layoutChildren() should be the method for
>>>>> you.
>>>>>
>>>>> -Martin
>>>>>
>>>>>
>>>>> On 11/07/2013 02:08 PM, Tomas Mikula wrote:
>>>>>>
>>>>>> On Thu, Nov 7, 2013 at 11:58 AM, John Hendrikx <hjohn at xs4all.nl>
>>>>>> wrote:
>>>>>>>
>>>>>>> Hm, I found it googling, and since it showed up here:
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> http://docs.oracle.com/javafx/2/api/javafx/scene/Scene.ScenePulseListener.html
>>>>>>>
>>>>>>> I figured it was public, but I just noticed the class is defined
>>>>>>> package
>>>>>>> private.
>>>>>>
>>>>>> Although not part of the public API, you can use
>>>>>>
>>>>>>        import com.sun.javafx.tk.TKPulseListener;
>>>>>>        import com.sun.javafx.tk.Toolkit;
>>>>>>
>>>>>>        Toolkit.getToolkit().addSceneTkPulseListener(new
>>>>>> TKPulseListener(){...})
>>>>>>
>>>>>>
>>>>>> Anyway, I don't think deferring property invalidation until the next
>>>>>> pulse is very useful in general, for the following reasons:
>>>>>>
>>>>>> 1) It can lead to inconsistent observable state of your objects.
>>>>>> Consider an object with properties p, q, where the value of q depends
>>>>>> on the value of p, and consider changing the value of p. Now, right
>>>>>> after
>>>>>>        p.set(x);
>>>>>> returns, the state of the object is inconsistent until the next pulse,
>>>>>> and this inconsistency is observable to the outside world.
>>>>>>
>>>>>> 2) It doesn't (in general) avoid recalculations. Consider properties
>>>>>> p, q, r, s, whose invalidation listeners are deferred until the next
>>>>>> pulse, with bindings
>>>>>>        p <- q <- r <- s
>>>>>>        p <- s
>>>>>> and consider changing the value of p in pulse 0. The invalidation
>>>>>> listeners will fire as follows:
>>>>>> pulse 1: p
>>>>>> pulse 2: q, s
>>>>>> pulse 3: r
>>>>>> pulse 4: s
>>>>>> As you see, the listeners of s are called twice, causing potentially
>>>>>> expensive recalculation.
>>>>>>
>>>>>> For these reasons, I'm in favor of the approach suggested by Yennick,
>>>>>> where you "commit" the properties yourself.
>>>>>>
>>>>>> Regards,
>>>>>> Tomas
>>>>>
>>>>>
>


More information about the openjfx-dev mailing list