-
-
Notifications
You must be signed in to change notification settings - Fork 71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
v4: Throttling, Debouncing, and Limiting #271
Comments
+1 user for the throttling function. I am using it for a slider. |
A slider is what original motivated me to add |
@Evangelink Could you please verify that the throttling is actually necessary? IIRC, when we have discussed this issue previously, it seemed that WPF throttles/debounces itself (e.g. for sliders), and thus it may not be needed at all in Elmish.WPF. (It may, in other words, be a premature optimization.) AFAIK, so far we haven't had any explanation/demonstration of a use-case where throttling/debouncing/etc. it is actually needed (#269 (comment)). |
I might be doing something wrong but I confirm that I had to recently introduce the throttle with the dispatchWrap as described in another ticket because of the slowness when dragging the thumb index on the slider. Actually that's not exactly a slowness but more of a thumb index acting jaggy (doing being jumps forward and backward). I didn't noticed that before but I am not sure if that's a miss, the fact that the model was a lot simpler or because the slider wasn't yet styled. We have changed the control template to make it look like youtube bar so maybe we broke something. I will try to create a small app with a simple model and my slider to see if that's related. Not sure exactly when I will be able to make this but I will try to have it done by the end of the week. |
@cmeeren I have created a little project which kind of reproduce the behavior (jaggy effect) I am having with the third-party component on my real app https://github.com/Evangelink/Elmish.Wpf.Experiments/tree/main/ElmishThrottle Launch the app, click and drag the thumb forward and/or backward and you should notice the behavior (frames jump). |
@Evangelink, can you also create and share videos or GIFs of this jaggy behavior and then the same of the improved behavior when throttling? |
IMHO both of those are poor UX. Ideally it should be debounced in the UI and the slider should not be locked to the exact value from the Elmish model, to ensure a smooth UI, though I have no idea how to make that work cleanly with MVU (perhaps my one main gripes with it). |
Can you share a snippet of code in which you use this delay? |
Sure, here you go (note that the important part is on the binding of <Slider Margin="10" IsSnapToTickEnabled="True"
IsMoveToPointEnabled="True" IsSelectionRangeEnabled="False" LargeChange="5"
Maximum="{Binding FrameMaxIndex, FallbackValue=1, TargetNullValue=1}"
Minimum="0" SmallChange="1"
Value="{Binding CurrentFrameIndex, Mode=TwoWay, Delay=100}" /> You can also see Evangelink/Elmish.Wpf.Experiments#3 |
Excellent! The BindingBase.Delay documentation says
In my understanding of the difference between throttling, debouncing, and limiting, this Here is a video of that behavior in @Evangelink's after branch after making the highlighted change so that the console logging is visible. |
Here is that beautiful demo again of throttling vs debouncing. I think each of those also has a choice of passing along an event at the beginning of the corresponding interval or at the end. Both of those do so at the end. I think of the difference between throttling and (rate) limiting as whether data is lost. I think of throttling drops data to ensure a maximum rate is not exceeded. I think of rate limiting using a queue to store all incoming events (so no data is lost) and then pass them along at a rate that does not exceed a given maximum. I get the impression that some people define rate limiting the same as I have defined throttling. |
So is dispatch wrapping no longer available at all? |
Correct. (c.f. #364 (comment)).
Ok. As I said in #364 (comment), I think we can implement throttling in a way that is compatible with I am probably oversimplifying there and missing some issues. However, I don't plan to look into this until after I have the composable binding API complete (c.f. #263). |
@TysonMN so what are we going to do with throttling? So that I can migrate Elmish.Uno to be compatible with Elmish.WPF v4 |
What is your use case for throttling? I haven't spent any time working on throttling, deboucing, and limiting because I am still working on the composable binding API, but I still think they will be possible to implement. |
Entering text to text fields and calling API with that text automatically |
Like autocomplte |
That is a very good use case. Thanks for mentioning it. |
Great news! I just added to the To see this in action, try using the slider in the @xperiandri, is this feature sufficient for your use case? (CC @cmeeren: Just wanted to bring to your attention this great improvement. No need to replay.) |
What is the main difference to v3 implementation? |
Why signature has changed? |
Sorry for the delayed response. I haven't had time to write a clear(er) explanation. And actually, I still don't. I am going to delay my response further because I have an idea that might allow me to improve this feature. This might take a week. Stay tuned! |
These answer are a bit moot now (see next comment), but here are brief answers. The signature of the ('msg -> unit) -> 'msg -> unit while the signature of the parameter demonstrated in the above code is (obj -> unit) -> obj -> unit This feature is unsound in the sense that it is possible for the user to write code involving it that complies but fails at runtime due to an I changed the But forget about this. I now have something better! |
Dispatch wrapping is back! See the branch polymorphic_dispatch_wrapping for a proof of concept. (I still need to do lots of clean up.) This time, dispatch wrapping is
It couldn't be any better!! :D So @xperiandri, you will not lose any functionality (related to dispatch wrapping) in v4. In fact, it will be slightly more expressive than it was before. I opened this issue because I thought I would have to introduce a breaking change in v4 by removing all of the optional I don't like the name Instead, of the name function name (CC @cmeeren: Just wanted to bring to your attention this even greater improvement. No need to replay.) |
👏 |
This is excellent, @TysonMN! 😁 Good work! 👍 |
I now better understand the limits of F# against which I was fighting. Below is a rather minimal reproduction. It doesn't perfectly reproduce the problem, but it is close. First, the recursion I need seems particularly difficult for F#'s type inference. To compensate, I need to make everything explicit: argument types, the return type, and even type parameters. One of the last things I figure out or tried was specifying the return type. Without specifying the return type (of the method
Notice that the type I provided and the desired type are the same but the F# compiler seems to suggest they are different. Even so, it was the suggestion at the bottom that lead me to add a type annotation for the return type on each of my recursive functions. In my reproduction below, there is a less informative warning message when the return type is not specified.
I constantly saw this message at other times during my coding with Second, in order to have explicit type parameters, I either need to use a method or a function in a module. I cannot use a function in a class. Trying to do so results in this error.
Because of this and because so many of the types involved are depend on each other recursively (and because the Here is my small (near) reproduction. module TopLevelModule
[<RequireQualifiedAccess>]
type HeterogeneousValueList<'a> =
| Empty
| HeadTail of 'a * HeterogeneousValueList<obj>
[<RequireQualifiedAccess>]
type HeterogeneousFunctionList<'a, 'b> =
| Empty
| HeadTail of ('a -> 'b) * HeterogeneousFunctionList<obj, obj>
module HeterogeneousValueList =
let rec mapAsFunctionBindingInModule<'a, 'b>
(fs: HeterogeneousFunctionList<'a, 'b>)
(aList: HeterogeneousValueList<'a>)
: HeterogeneousValueList<'b> =
match fs, aList with
| HeterogeneousFunctionList.Empty, _
| _, HeterogeneousValueList.Empty ->
HeterogeneousValueList.Empty
| HeterogeneousFunctionList.HeadTail(f, fsTail), HeterogeneousValueList.HeadTail (a, aTail) ->
let b: 'b = f a
let tail = mapAsFunctionBindingInModule fsTail aTail
HeterogeneousValueList.HeadTail(b, tail)
type HeterogeneousValueList () =
//let rec mapAsFunctionBindingInClass<'a, 'b> // FS0665 Explicit type parameters may only be used on module or member bindings
// (fs: HeterogeneousFunctionList<'a, 'b>)
// (aList: HeterogeneousValueList<'a>)
// : HeterogeneousValueList<'b> =
// match fs, aList with
// | HeterogeneousFunctionList.Empty, _
// | _, HeterogeneousValueList.Empty ->
// HeterogeneousValueList.Empty
// | HeterogeneousFunctionList.HeadTail(f, fsTail), HeterogeneousValueList.HeadTail (a, aTail) ->
// let b: 'b = f a
// let tail = mapAsFunctionBindingInClass fsTail aTail
// HeterogeneousValueList.HeadTail(b, tail)
static member mapAsMemberBindingInClass<'a, 'b>
(fs: HeterogeneousFunctionList<'a, 'b>,
aList: HeterogeneousValueList<'a>)
: HeterogeneousValueList<'b> =
match fs, aList with
| HeterogeneousFunctionList.Empty, _
| _, HeterogeneousValueList.Empty ->
HeterogeneousValueList.Empty
| HeterogeneousFunctionList.HeadTail(f, fsTail), HeterogeneousValueList.HeadTail (a, aTail) ->
let b: 'b = f a
let tail = HeterogeneousValueList.mapAsMemberBindingInClass(fsTail, aTail)
HeterogeneousValueList.HeadTail(b, tail) |
wrapDispatch
was removed fromv4
in PR #256 (because it was deemed inferior to a mutually exclusive feature). A partial migration path is to implement stream adjustment behavior like throttling, debouncing, and limiting. This partial migration path will help at least one user (c.f. #269).As a first step, I want to precisely define what I mean by throttling, debouncing, and limiting, and I want to find existing implementations of these behaviors.
The text was updated successfully, but these errors were encountered: