<html><head></head><body><div style="font-family: Verdana;font-size: 12.0px;"><div>+ 1 for this. Debouncing is a common functionality for observables.</div>
<div>One of the common scenarios is obviously something like a search filter functionality, where typing in characters triggers an expensive calculation.</div>
<div>Debouncing solves the problem by doing that when nothing happened for some time, which is typically met when the user finished typing.</div>
<div> </div>
<div>-- Marius</div>
<div>
<div>
<div name="quote" style="margin:10px 5px 5px 10px; padding: 10px 0 10px 10px; border-left:2px solid #C3D9E5; word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;">
<div style="margin:0 0 10px 0;"><b>Gesendet:</b> Donnerstag, 23. März 2023 um 18:09 Uhr<br/>
<b>Von:</b> "John Hendrikx" <john.hendrikx@gmail.com><br/>
<b>An:</b> openjfx-dev@openjdk.org<br/>
<b>Betreff:</b> Gauging interest in bindings that can delay changing their value (debounce/throttle)</div>
<div name="quoted-content">Hi list,<br/>
<br/>
I've been working on a potential new API (and proof of concept<br/>
implementation) for adding a new type of fluent binding which can delay<br/>
changing their values, and I'm wondering how much interest there is in<br/>
such a thing.<br/>
<br/>
The main purpose of such an API is to prevent being flooded with changes<br/>
when properties change often, or to simply delay certain actions until<br/>
the user has settled on a selection or has stopped typing.<br/>
<br/>
For this purpose I would like to introduce a default method on<br/>
`ObservableValue` with the signature:<br/>
<br/>
ObservableValue<T> throttle(Throttler throttler);<br/>
<br/>
The parameter `Throttler` can be obtained via static methods of a helper<br/>
class named `FXThrottlers`. These provide various pre-configured<br/>
throttlers that work correctly with JavaFX's event thread model. My<br/>
current proof of concept provides:<br/>
<br/>
public static Throttler debounce(Duration quietPeriod);<br/>
public static Throttler debounceTrailing(Duration quietPeriod);<br/>
public static Throttler throttle(Duration period);<br/>
public static Throttler throttleTrailing(Duration period);<br/>
<br/>
These are variations of similar concepts, and vary mostly in when<br/>
exactly they will allow value changes; debouncers will wait for a period<br/>
without any changes, while throttlers will periodically allow changes.<br/>
The trailing variants will not immediately emit the first change but<br/>
will wait for the period to elapse first; all variants will eventually<br/>
take on the value of the source observable. Debouncing is typically<br/>
used when you wish for an input to settle before taking action (like<br/>
typing in a search bar), while throttling is used to give regular<br/>
feedback but avoid doing so too often (like feedback during window<br/>
resizing).<br/>
<br/>
Usage example which updates a preview panel when the user has finished<br/>
(cursor) scrolling through a list view:<br/>
<br/>
ObjectProperty<T> selectedItem =<br/>
listView.getSelectionModel().selectedItemProperty();<br/>
<br/>
selectedItem<br/>
.throttle(FXThrottlers.debounceTrailing(Duration.ofMillis(500)))<br/>
.addListener((obs, old, current) -> {<br/>
if (current != null) {<br/>
updatePreviewPanel(current);<br/>
}<br/>
});<br/>
<br/>
Implementation details:<br/>
<br/>
ObservableValue is part of javafx.base, and as such can't use animations<br/>
or call Platform::runLater. The ThrottledBinding implementation has<br/>
abstracted all of these out into the Throttler class, and FXThrottlers<br/>
(which would live in javafx.graphics) therefore provides the necessary<br/>
call backs to integrate property changes correctly back onto the JavaFX<br/>
event thread. The Throttler class also simplifies testing; the test can<br/>
provide its own timing source and background scheduler. The Throttler<br/>
interface has the following methods:<br/>
<br/>
/**<br/>
* Schedules a command to run on an unspecified thread after the time<br/>
* given by {@code nanos} elapses.<br/>
*<br/>
* @param command a command to run, cannot be {@code null}<br/>
* @param nanos a time in nanoseconds<br/>
*/<br/>
void schedule(Runnable command, long nanos);<br/>
<br/>
/**<br/>
* Provides the current time in nanoseconds.<br/>
*<br/>
* @return the current time in nanoseconds<br/>
*/<br/>
long nanoTime();<br/>
<br/>
/**<br/>
* Runs the given command as soon as possible on a thread specified<br/>
by this<br/>
* throttler for updating property values.<br/>
*<br/>
* @param command a command to run, cannot be {@code null}<br/>
*/<br/>
void update(Runnable command);<br/>
<br/>
/**<br/>
* Given the current elapsed time in the current change window, and the<br/>
* amount of time elapsed since the last change was detected,<br/>
determines<br/>
* if and by how much the current change window should be extended.<br/>
*<br/>
* @param elapsed nanoseconds elapsed since the start of the<br/>
current change window<br/>
* @param elapsedSinceLastChange nanoseconds elapsed since the last<br/>
change<br/>
* @return nanoseconds to extend the window with<br/>
*/<br/>
long determineInterval(long elapsed, long elapsedSinceLastChange);<br/>
<br/>
For testing purposes, the schedule and nanoTime can be provided such<br/>
that the throttle function can be tested deterministically. For<br/>
integrating with JavaFX, update is implemented as<br/>
`Platform.runLater(command)`. The schedule and nanoTime methods<br/>
delegate to an Executor and System.nanoTime respectively. When using<br/>
properties without JavaFX, Throttler implementations can be provided<br/>
which run property updates on a scheduler thread (just calling<br/>
Runnable::run on the current thread) or via some user provided executor.<br/>
<br/>
A sample test case looks like this (read with a mono space font :-)):<br/>
<br/>
@Test<br/>
void testThrottleLeadingAndTrailing() {<br/>
// create Throttler with deterministic behavior:<br/>
Throttler throttler =<br/>
create(Throttler.IntervalHandler.throttle(Duration.ofNanos(4));<br/>
<br/>
// create throttled observable:<br/>
ObservableValue<String> binding = source.throttle(throttler);<br/>
<br/>
assertChanges(<br/>
binding,<br/>
"--a-b--c---d-----e-------f-g-----------f-g-----",<br/>
"--a---b---c---d---e------f---g---------f---g---"<br/>
);<br/>
<br/>
assertInvalidations(<br/>
binding,<br/>
"--a-b--c---d-----e-------f-g-----------f-g-----",<br/>
"--i---i---i---i---i------i---i---------i---i---"<br/>
);<br/>
}<br/>
<br/>
Thanks for reading, I look forward to your feedback!<br/>
<br/>
--John<br/>
<br/>
<br/>
<br/>
</div>
</div>
</div>
</div></div></body></html>