Use ScenePulseListener to avoid expensive recalculations?

Tomas Mikula tomas.mikula at gmail.com
Thu Nov 7 07:03:36 PST 2013


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).

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