On Tue, 4 Jan 2022 03:28:57 GMT, Nir Lisker <nlis...@openjdk.org> wrote:

> Unrelated to the review, will it makes sense in the future to make all 
> bindings lazily register listeners like `LazyObjectBinding`, perhaps when we 
> introduce `Subscription`?

That would need to be very well tested. There are some noticeable differences 
in behavior vs the standard bindings:

1) Standard bindings can more easily be GC'd (accidentally usually, but it 
could be intentional), take for example:


     textProperty.addListener((obs, old, current) -> 
System.out.println(current));
     textProperty.concat("A").addListener((obs, old, current) -> 
System.out.println(current));


These behave very different.  The first listener keeps working as you'd expect, 
while the second one can stop working as soon the GC runs. This is because 
`concat` results in an `ObservableValue` that is weakly bound.  Compare this to:

     textProperty.map(x -> x + "A").addListener((obs, old, current) -> 
System.out.println(current));

This keeps working and will not be GC'd by accident.

2) Standard bindings will always cache values. This means that when `getValue` 
is called, it will just return the cached value instead of calling 
`computeValue`.  If `computeValue` is really expensive (unwise since this 
happens on the FX thread) then this cost is paid each time for Lazy bindings, 
at least when they're not bound to anything else (unobserved) and you are just 
using `getValue`. Furthermore, for a chain of Lazy bindings that is unobserved, 
this would propagate through the entire chain.  As soon as they're observed 
though, they become non-lazy and values are cached.

In effect, Lazy bindings behave the same as standard bindings when they're 
observed but their behavior changes when not observed: they never become valid 
and they stop caching values

This has pros and cons:

Pro: Lazy bindings can be garbage collected when not referenced and not 
actively being used without the use of weak listeners.  See the first example 
where the binding keeps working when used by `println` lambda.  This is in 
contrast to traditional bindings which can be garbage collected when 
unreferenced by user code even if actively used(!!).  This is a huge gotcha and 
one of the biggest reasons to use the lazy model.

Pro: Lazy bindings don't register unnecessary listeners to be aware of changed 
or invalidated values that are not used by anyone. A good example is the 
problems we saw about a year ago where `Node` had created an `isShowing` 
property which bounds to its `Scene` and `Window`.  This looks harmless, until 
you realize that a listener is created on these properties for each `Node` in 
existence.  If a `Scene` has tens of thousands of `Node`s then that means that 
the `Scene#windowProperty` will have a listener list containing tens of 
thousands of entries.  This list is not only expensive to change (when a node 
is added or removed) but also expensive to act on (when the scene, window or 
its showing state changes).  And for what?  Almost nobody was actually using 
this property, yet listeners had to be added for each and every `Node`.  In 
effect with lazy bindings, it is much cheaper now to create properties that 
create convenient calculated values which will only register listeners or 
compute
  their values when in actual use. 

Con: lazy bindings never become valid when not observed.  This means that 
`getValue` will always have to recompute the value as the value is not cached.  
However, if you register an invalidation listener the binding becomes observed 
and it will start behaving like a traditional binding sending invalidation 
events and caching the current value.  A small "pro" here could be that this 
means that intermediate values in a long binding chain don't take up memory 
(and are prevented from GC), however that goes away as soon as the binding is 
observed.

In summary: I think lazy bindings are far superior in the experience that they 
offer, but it does come at a cost that values may need to be recomputed every 
time when the bindings are unobserved. However, doing substantial work in 
`computeValue` is probably unwise anyway as it blocks the FX thread so making 
lazy binding the default behavior in well behaving code could be of only minor 
impact.

-------------

PR: https://git.openjdk.java.net/jfx/pull/675

Reply via email to