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