[prev in list] [next in list] [prev in thread] [next in thread] 

List:       openjdk-openjfx-dev
Subject:    Aw: Gauging interest in bindings that can delay changing their value (debounce/throttle)
From:       Marius Hanl <mariushanl () web ! de>
Date:       2023-03-30 22:19:30
Message-ID: trinity-3f77e3cf-afa4-4c84-bf34-5410664659f6-1680214770794 () 3c-app-webde-bap33
[Download RAW message or body]

<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>&nbsp;</div>

<div>-- Marius</div>

<div>&nbsp;
<div>&nbsp;
<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>&nbsp;Donnerstag, 23. M&auml;rz 2023 um 18:09 Uhr<br/> \
<b>Von:</b>&nbsp;&quot;John Hendrikx&quot; &lt;john.hendrikx@gmail.com&gt;<br/> \
<b>An:</b>&nbsp;openjfx-dev@openjdk.org<br/> <b>Betreff:</b>&nbsp;Gauging interest in \
bindings that can delay changing their value (debounce/throttle)</div>

<div name="quoted-content">Hi list,<br/>
<br/>
I&#39;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&#39;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/>
&#96;ObservableValue&#96; with the signature:<br/>
<br/>
&nbsp;&nbsp;&nbsp; ObservableValue&lt;T&gt; throttle(Throttler throttler);<br/>
<br/>
The parameter &#96;Throttler&#96; can be obtained via static methods of a helper<br/>
class named &#96;FXThrottlers&#96;. These provide various pre-configured<br/>
throttlers that work correctly with JavaFX&#39;s event thread model.&nbsp; My<br/>
current proof of concept provides:<br/>
<br/>
&nbsp;&nbsp;&nbsp; public static Throttler debounce(Duration quietPeriod);<br/>
&nbsp;&nbsp;&nbsp; public static Throttler debounceTrailing(Duration \
quietPeriod);<br/> &nbsp;&nbsp;&nbsp; public static Throttler throttle(Duration \
period);<br/> &nbsp;&nbsp;&nbsp; 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.&nbsp; 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/>
&nbsp;&nbsp;&nbsp; ObjectProperty&lt;T&gt; selectedItem =<br/>
listView.getSelectionModel().selectedItemProperty();<br/>
<br/>
&nbsp;&nbsp;&nbsp; selectedItem<br/>
.throttle(FXThrottlers.debounceTrailing(Duration.ofMillis(500)))<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .addListener((obs, old, current) -&gt; \
{<br/> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if \
(current != null) {<br/> &nbsp; &nbsp;&nbsp; \
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \
updatePreviewPanel(current);<br/> \
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br/> \
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; });<br/> <br/>
Implementation details:<br/>
<br/>
ObservableValue is part of javafx.base, and as such can&#39;t use animations<br/>
or call Platform::runLater.&nbsp; 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.&nbsp; The Throttler class also simplifies testing; the test can<br/>
provide its own timing source and background scheduler.&nbsp; The Throttler<br/>
interface has the following methods:<br/>
<br/>
&nbsp;&nbsp;&nbsp; /**<br/>
&nbsp;&nbsp;&nbsp;&nbsp; * Schedules a command to run on an unspecified thread after \
the time<br/> &nbsp;&nbsp;&nbsp;&nbsp; * given by {@code nanos} elapses.<br/>
&nbsp;&nbsp;&nbsp;&nbsp; *<br/>
&nbsp;&nbsp;&nbsp;&nbsp; * @param command a command to run, cannot be {@code \
null}<br/> &nbsp;&nbsp;&nbsp;&nbsp; * @param nanos a time in nanoseconds<br/>
&nbsp;&nbsp;&nbsp;&nbsp; */<br/>
&nbsp;&nbsp;&nbsp; void schedule(Runnable command, long nanos);<br/>
<br/>
&nbsp;&nbsp;&nbsp; /**<br/>
&nbsp;&nbsp;&nbsp;&nbsp; * Provides the current time in nanoseconds.<br/>
&nbsp;&nbsp;&nbsp;&nbsp; *<br/>
&nbsp;&nbsp;&nbsp;&nbsp; * @return the current time in nanoseconds<br/>
&nbsp;&nbsp;&nbsp;&nbsp; */<br/>
&nbsp;&nbsp;&nbsp; long nanoTime();<br/>
<br/>
&nbsp;&nbsp;&nbsp; /**<br/>
&nbsp;&nbsp;&nbsp;&nbsp; * Runs the given command as soon as possible on a thread \
specified<br/> by this<br/>
&nbsp;&nbsp;&nbsp;&nbsp; * throttler for updating property values.<br/>
&nbsp;&nbsp;&nbsp;&nbsp; *<br/>
&nbsp;&nbsp;&nbsp;&nbsp; * @param command a command to run, cannot be {@code \
null}<br/> &nbsp;&nbsp;&nbsp;&nbsp; */<br/>
&nbsp;&nbsp;&nbsp; void update(Runnable command);<br/>
<br/>
&nbsp;&nbsp;&nbsp; /**<br/>
&nbsp;&nbsp;&nbsp;&nbsp; * Given the current elapsed time in the current change \
window, and the<br/> &nbsp;&nbsp;&nbsp;&nbsp; * amount of time elapsed since the last \
change was detected,<br/> determines<br/>
&nbsp;&nbsp;&nbsp;&nbsp; * if and by how much the current change window should be \
extended.<br/> &nbsp;&nbsp;&nbsp;&nbsp; *<br/>
&nbsp;&nbsp;&nbsp;&nbsp; * @param elapsed nanoseconds elapsed since the start of \
the<br/> current change window<br/>
&nbsp;&nbsp;&nbsp;&nbsp; * @param elapsedSinceLastChange nanoseconds elapsed since \
the last<br/> change<br/>
&nbsp;&nbsp;&nbsp;&nbsp; * @return nanoseconds to extend the window with<br/>
&nbsp;&nbsp;&nbsp;&nbsp; */<br/>
&nbsp;&nbsp;&nbsp; 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/>
&#96;Platform.runLater(command)&#96;.&nbsp; The schedule and nanoTime methods<br/>
delegate to an Executor and System.nanoTime respectively.&nbsp; 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/>
&nbsp;&nbsp;&nbsp; @Test<br/>
&nbsp;&nbsp;&nbsp; void testThrottleLeadingAndTrailing() {<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // create Throttler with deterministic behavior:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Throttler throttler =<br/>
create(Throttler.IntervalHandler.throttle(Duration.ofNanos(4));<br/>
<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // create throttled observable:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ObservableValue&lt;String&gt; binding = \
source.throttle(throttler);<br/> <br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; assertChanges(<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; binding,<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \
&quot;--a-b--c---d-----e-------f-g-----------f-g-----&quot;,<br/> \
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \
&quot;--a---b---c---d---e------f---g---------f---g---&quot;<br/> \
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; );<br/> <br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; assertInvalidations(<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; binding,<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \
&quot;--a-b--c---d-----e-------f-g-----------f-g-----&quot;,<br/> \
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \
&quot;--i---i---i---i---i------i---i---------i---i---&quot;<br/> \
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; );<br/> &nbsp;&nbsp;&nbsp; }<br/>
<br/>
Thanks for reading, I look forward to your feedback!<br/>
<br/>
--John<br/>
<br/>
<br/>
<br/>
&nbsp;</div>
</div>
</div>
</div></div></body></html>


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic