<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <p>Hi Andy,</p>
    <p>Those examples seem to be just timers, it would be hard to
      construct the primitives like throttle and debounce with these, as
      they don't take into account when the value last changed, or
      whether or not is important that the value changed again (reset
      timer or not).  These timers would just run forever, while the
      functionality I propose here would have no timers running when
      things are stable. The timeout would also trigger precisily on the
      first value change.  Having a running timer is more like sampling,
      not throttling or debouncing.<br>
    </p>
    <p>The functionality I'm proposing would be more along the lines of
      #reduceSucessions in
<a class="moz-txt-link-freetext" href="https://github.com/TomasMikula/ReactFX/blob/master/reactfx/src/main/java/org/reactfx/EventStream.java">https://github.com/TomasMikula/ReactFX/blob/master/reactfx/src/main/java/org/reactfx/EventStream.java</a>
      -- except that it would never support accumulation or combining of
      values (that's something for streams, not for values).</p>
    <p>--John</p>
    <div class="moz-cite-prefix">On 03/04/2023 18:47, Andy Goryachev
      wrote:<br>
    </div>
    <blockquote type="cite"
cite="mid:DM5PR1001MB2172DF5CB8DCC167B4191DE0E5929@DM5PR1001MB2172.namprd10.prod.outlook.com">
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <meta name="Generator" content="Microsoft Word 15 (filtered
        medium)">
      <style>@font-face
        {font-family:"Cambria Math";
        panose-1:2 4 5 3 5 4 6 3 2 4;}@font-face
        {font-family:Calibri;
        panose-1:2 15 5 2 2 2 4 3 2 4;}@font-face
        {font-family:Verdana;
        panose-1:2 11 6 4 3 5 4 4 2 4;}@font-face
        {font-family:"Times New Roman \(Body CS\)";
        panose-1:2 11 6 4 2 2 2 2 2 4;}p.MsoNormal, li.MsoNormal, div.MsoNormal
        {margin:0in;
        font-size:10.0pt;
        font-family:"Calibri",sans-serif;}a:link, span.MsoHyperlink
        {mso-style-priority:99;
        color:#0563C1;
        text-decoration:underline;}span.EmailStyle18
        {mso-style-type:personal-reply;
        font-family:"Courier New";
        color:windowtext;}.MsoChpDefault
        {mso-style-type:export-only;
        font-size:10.0pt;}div.WordSection1
        {page:WordSection1;}</style>
      <div class="WordSection1">
        <p class="MsoNormal"><span
            style="font-size:11.0pt;font-family:"Courier New"">My
            two cents: I think the functionality of debouncing should
            better be solved by a separate facility, rather than added
            to observables.  An example would be a use case when
            multiple observables trigger an expensive or delayed
            computation or a UI update.<o:p></o:p></span></p>
        <p class="MsoNormal"><span
            style="font-size:11.0pt;font-family:"Courier New""><o:p> </o:p></span></p>
        <p class="MsoNormal"><span
            style="font-size:11.0pt;font-family:"Courier New"">Something
            along the lines of<o:p></o:p></span></p>
        <p class="MsoNormal"><span
            style="font-size:11.0pt;font-family:"Courier New""><a
href="https://github.com/TomasMikula/ReactFX/blob/master/reactfx/src/main/java/org/reactfx/util/FxTimer.java"
              moz-do-not-send="true" class="moz-txt-link-freetext">https://github.com/TomasMikula/ReactFX/blob/master/reactfx/src/main/java/org/reactfx/util/FxTimer.java</a><o:p></o:p></span></p>
        <p class="MsoNormal"><span
            style="font-size:11.0pt;font-family:"Courier New"">or
            <o:p>
            </o:p></span></p>
        <p class="MsoNormal"><span
            style="font-size:11.0pt;font-family:"Courier New""><a
href="https://github.com/andy-goryachev/FxEditor/blob/master/src/goryachev/fx/FxTimer.java"
              moz-do-not-send="true" class="moz-txt-link-freetext">https://github.com/andy-goryachev/FxEditor/blob/master/src/goryachev/fx/FxTimer.java</a><o:p></o:p></span></p>
        <p class="MsoNormal"><span
            style="font-size:11.0pt;font-family:"Courier New""><o:p> </o:p></span></p>
        <p class="MsoNormal"><span
            style="font-size:11.0pt;font-family:"Courier New"">cheers,<o:p></o:p></span></p>
        <p class="MsoNormal"><span
            style="font-size:11.0pt;font-family:"Courier New"">-andy<o:p></o:p></span></p>
        <p class="MsoNormal"><span
            style="font-size:11.0pt;font-family:"Courier New""><o:p> </o:p></span></p>
        <p class="MsoNormal"><span
            style="font-size:11.0pt;font-family:"Courier New""><o:p> </o:p></span></p>
        <p class="MsoNormal"><span
            style="font-size:11.0pt;font-family:"Courier New""><o:p> </o:p></span></p>
        <div style="border:none;border-top:solid #B5C4DF
          1.0pt;padding:3.0pt 0in 0in 0in">
          <p class="MsoNormal" style="margin-bottom:12.0pt"><b><span
                style="font-size:12.0pt;color:black">From:
              </span></b><span style="font-size:12.0pt;color:black">openjfx-dev
              <a class="moz-txt-link-rfc2396E" href="mailto:openjfx-dev-retn@openjdk.org"><openjfx-dev-retn@openjdk.org></a> on behalf of Marius
              Hanl <a class="moz-txt-link-rfc2396E" href="mailto:mariushanl@web.de"><mariushanl@web.de></a><br>
              <b>Date: </b>Thursday, March 30, 2023 at 15:20<br>
              <b>To: </b>John Hendrikx <a class="moz-txt-link-rfc2396E" href="mailto:john.hendrikx@gmail.com"><john.hendrikx@gmail.com></a><br>
              <b>Cc: </b><a class="moz-txt-link-abbreviated" href="mailto:openjfx-dev@openjdk.org">openjfx-dev@openjdk.org</a>
              <a class="moz-txt-link-rfc2396E" href="mailto:openjfx-dev@openjdk.org"><openjfx-dev@openjdk.org></a><br>
              <b>Subject: </b>Aw: Gauging interest in bindings that can
              delay changing their value (debounce/throttle)<o:p></o:p></span></p>
        </div>
        <div>
          <div>
            <p class="MsoNormal"><span
                style="font-size:9.0pt;font-family:"Verdana",sans-serif">+
                1 for this. Debouncing is a common functionality for
                observables.<o:p></o:p></span></p>
          </div>
          <div>
            <p class="MsoNormal"><span
                style="font-size:9.0pt;font-family:"Verdana",sans-serif">One
                of the common scenarios is obviously something like a
                search filter functionality, where typing in characters
                triggers an expensive calculation.<o:p></o:p></span></p>
          </div>
          <div>
            <p class="MsoNormal"><span
                style="font-size:9.0pt;font-family:"Verdana",sans-serif">Debouncing
                solves the problem by doing that when nothing happened
                for some time, which is typically met when the user
                finished typing.<o:p></o:p></span></p>
          </div>
          <div>
            <p class="MsoNormal"><span
                style="font-size:9.0pt;font-family:"Verdana",sans-serif"> <o:p></o:p></span></p>
          </div>
          <div>
            <p class="MsoNormal"><span
                style="font-size:9.0pt;font-family:"Verdana",sans-serif">--
                Marius<o:p></o:p></span></p>
          </div>
          <div>
            <p class="MsoNormal"><span
                style="font-size:9.0pt;font-family:"Verdana",sans-serif"> 
                <o:p></o:p></span></p>
            <div>
              <p class="MsoNormal"><span
                  style="font-size:9.0pt;font-family:"Verdana",sans-serif"> 
                  <o:p></o:p></span></p>
              <div style="border:none;border-left:solid #C3D9E5
                1.5pt;padding:0in 0in 0in
8.0pt;margin-left:7.5pt;margin-top:7.5pt;margin-right:3.75pt;margin-bottom:3.75pt;-webkit-nbsp-mode:
                space;-webkit-line-break: after-white-space"
                name="quote">
                <div style="margin-bottom:7.5pt">
                  <p class="MsoNormal"><b><span
                        style="font-size:9.0pt;font-family:"Verdana",sans-serif">Gesendet:</span></b><span
style="font-size:9.0pt;font-family:"Verdana",sans-serif"> Donnerstag,
                      23. März 2023 um 18:09 Uhr<br>
                      <b>Von:</b> "John Hendrikx"
                      <a class="moz-txt-link-rfc2396E" href="mailto:john.hendrikx@gmail.com"><john.hendrikx@gmail.com></a><br>
                      <b>An:</b> <a class="moz-txt-link-abbreviated" href="mailto:openjfx-dev@openjdk.org">openjfx-dev@openjdk.org</a><br>
                      <b>Betreff:</b> Gauging interest in bindings that
                      can delay changing their value (debounce/throttle)<o:p></o:p></span></p>
                </div>
                <div name="quoted-content">
                  <p class="MsoNormal"><span
                      style="font-size:9.0pt;font-family:"Verdana",sans-serif">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>
                       <o:p></o:p></span></p>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </blockquote>
  </body>
</html>