On Mon, 3 Mar 2025 20:44:08 GMT, Andy Goryachev <[email protected]> wrote:
>> I haven't been tracking these fixes for allowing initialization in
>> background threads, but it seems to me that basically anything should be
>> allowed as long as you're not part of a visible scene graph -- and I think
>> there's also no expectation that all functionality of a control "works" as
>> long as it is not yet part of such a graph (ie. the listeners are only
>> needed once it is part of a scene graph).
>>
>> If you make listeners conditional on being part of a scene graph, then I
>> think you can handle these with a single code path. Such a condition would
>> be:
>>
>> ObservableValue<Boolean> partOfSceneGraph = node.sceneProperty()
>> .flatMap(Scene::windowProperty)
>> .flatMap(Window::showingProperty)
>> .orElse(false);
>>
>> // The node here is "this", the chart, because as soon as it is part
>> of the scene
>> // graph, you want the listeners to function.
>>
>> You can then safely install any listeners directly, regardless if they're on
>> a background thread or not:
>>
>> someProperty().when(partOfSceneGraph).subscribe( ... );
>>
>> Or:
>>
>> someProperty().when(partOfSceneGraph).addListener( ... );
>>
>> Or with any mappings added, you can even pick where you put the `when`:
>>
>> someProperty().flatMap(xyz).when(partOfSceneGraph).addListener( ... );
>> someProperty().when(partOfSceneGraph).flatMap(xyz).addListener( ... );
>>
>>
>> On a background thread, `partOfSceneGraph` will be `false`, and no listener
>> gets installed (yet). As soon as it becomes `true` all listeners with that
>> same condition become active. It becomes `true` automatically when the node
>> has a scene belonging to a window that is visible. Vice versa, if it ever
>> becomes `false` again, (which it may when the Node is removed or replaced)
>> all listeners using this condition are removed again.
>
> Thank you!
>
> This is not what I asked though: my question was about using `Subscription`
> in one-off case, and more specifically, about whether it is even possible to
> unsubscribe from within the lambda.
>
> What you are proposing is a slightly more involved change: for example,
> registering and unregistering listeners each time might introduce other
> issues (especially since we have no way to prioritize listeners/handlers).
> It also complicates the management of skin (as skins can be added/removed),
> something I would rather avoid.
As for this current code:
ObservableValue<Window> winProp =
sceneProperty().flatMap(Scene::windowProperty);
accessibilityActive = winProp; // keep the reference so it
won't get gc
Subscription sub = winProp.subscribe((win) -> {
if (win != null) {
if (accessibilityActive == winProp) {
accessibilityActive = null;
}
if (isAccessibilityActive()) {
handleAccessibilityActive(true);
}
//winProp.removeListener(this);
sub.unsubscribe(); <-- COMPILE ERROR
}
});
What you want there is not possible in this way as you can't use `this` in a
lambda, nor refer to it via a local. You can achieve it only if you store the
subscription in a field, not in a local. Assigning the Lambda to a
ChangeListener local, and using a ChangeListener may be simpler (unless you go
for the `when` solution that may eliminate the need for 2 different code paths).
Note that there is no need to store `winProp`; `flatMap` does not use weak
listeners (when in active use), and so as long as `sceneProperty()` exists, the
derived property via `flatMap` will also exist, and also its listener. The way
the fluent mapping system works is that listeners are only present when
something is actually listening:
ObservableValue<Window> winProp =
sceneProperty().flatMap(Scene::windowProperty);
// ^^ nobody is listening, so no listeners installed and no hard
references; it could GC but
// it is currently in a local, so it won't
ObservableValue<Window> winProp =
sceneProperty().flatMap(Scene::windowProperty);
winProp.subscribe(v -> { .. });
// ^^ somebody is listening; all listeners get installed automatically, and
now there are
// hard references. No risk of GC.
You can then write it as a single statement which won't be GC'd as long as the
subscription is active:
subscriptionFIeld = sceneProperty()
.flatMap(Scene::windowProperty);
.subscribe(v -> {
// use subscription field here if you want to unregister the lambda
// Note: when you do, there will again be no listeners and thus no
hard references and
// thus the intermediate property created by flatMap can now be
GC'd :)
});
-------------
PR Review Comment: https://git.openjdk.org/jfx/pull/1697#discussion_r1978173310