On Tue, 4 Jan 2022 03:28:57 GMT, Nir Lisker <[email protected]> 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