-
-
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
Throttling individual bindings #114
Comments
Quoting @cmeeren from elmish/elmish#189 (comment):
Here is some very not-off-the-top-of-my-head analysis ;) I have looked at all the ways that
|
What I imagine, partly from the top of my head, is this: Bindings.fs: and internal TwoWayData<'model, 'msg> = {
Get: 'model -> obj
Set: obj -> 'model -> 'msg
+ ModifyDispatch: Dispatch<'msg> -> Dispatch<'msg>
} static member twoWay
(get: 'model -> 'a,
- set: 'a -> 'model -> 'msg)
+ set: 'a -> 'model -> 'msg,
+ ?modifyDispatch: Dispatch<'msg> -> Dispatch<'msg>)
: string -> Binding<'model, 'msg> =
TwoWayData {
Get = get >> box
Set = unbox<'a> >> set
+ ModifyDispatch = defaultArg modifyDispatch id
} |> createBinding And corresponding changes in Note that there may be better names than Also note that this is not related to throttling or debouncing per se; it's a general abstraction that allows any modification of It would also be nice to have a sample demonstrating this new feature (such as a slider with 8000 steps, as you say). |
Yep, I think that works too.
Your name is reasonable. My working name has been
Indeed. And I think this is the right place to start. I like API designs that both provide simple interfaces for the common cases (throttling, debouncing, etc.) while also not limiting what can be done. Right now, we are working on ensuring that anything is possible. Sometime later, we can add the simple interfaces for the common cases.
Definitely. I can add that.
To what cases do you think we should expose the possibility to wrap
Do you still want to extend only those four even after reading my summary of my analysis? (I am fine with that...just want to double check.) |
No, we should definitely extend all bindings that can dispatch. (Note that this does not include any sub-model bindings. The wrappers for those will be in the sub-model bindings themselves.) |
Also, |
Clearly that applies to |
I can't think of any immediate use-case for this. I suggest we wait until it's actually requested by users, if ever. |
Quoting part of #118 (comment).
We are definitely on the same page. I don't want to add any more optional parameters related to wrapping dispatch. To aid in helping users discover what is possible, I was thinking we could add something to the documentation of the argument |
I tried to use Rx to throttle the slider binding, but I ran into a deadlock. Before I get into that, I would like to consider a simpler case that also had unexpected behavior. For a slider, I expect the following to be equivalent:
However, the case with Why can't the slider move in a |
I don't know for sure, but I guess it's because WPF reads the value of a two-way binding after setting it, thus resetting it to the current value, whereas in a one-way binding it doesn't. (I don't know for sure that it works that way, but it's my best guess at the moment, and there was a bug in the past that I think perhaps touched on the same, #10.) |
Excellent guess. Yes, I think it is related to that issue. When using a When using a
This situation is simpler now than it was back when #10 was created because the Elmish dispatch loop now runs on the UI thread #40. |
Ok, now for the real question. I tried to throttle the messages using Rx in 1562bcb. In particular, the Rx throttling code I wrote is
The
Oh, but since dotnet/reactive#395 (comment)
we can just call |
There is a lock in the Rx code that was causing a deadlock. (It does seem necessary for there to be a lock somewhere.) Both of the following actions require this lock:
The second action requires the UI thread, so if both are done on the UI thread, then there could be a deadlock (edited to add) if the second actions starts on a different thread. Thus, don't call
|
Glad you solved it! I don't know about the Rx internals, but if it uses a normal |
Correct. That is something that I only learned within the last few months, which is that locks in .NET are reentrant.
Also correct. They do use (at least one) additional thread. I can't find where though because the code seems a bit complicated to me. That thread calls the synchronized Elmish.WPF/src/Elmish.WPF/Program.fs Lines 27 to 28 in 0f38874
while the UI thread is waiting for the lock just after calling subject.OnNext .
|
I see. So it's not trivial to support Rx stuff. Perhaps we can have overloads taking let asDispatchWrapper
(configure: IObservable<'msg> -> IObservable<'msg>)
(dispatch: Dispatch<'msg>)
: Dispatch<'msg> =
let subject = new Subject<'msg>()
subject :> IObservable<'msg> |> configure |> Observable.add dispatch
subject.OnNext |> Async.ofFunc >> Async.Start This of course requires that the above is the right way to do it every time, and just generally useful to include in Elmish.WPF (I think it might be; there's no external dependencies, and it allows users to easily use Rx combinators. But let's not rush.) We have to make sure this doesn't leak anything, though (which might be a concern since we never unsubscribe). |
Your example depends on System.Reactive; the full name of
To what method overloads are you referring? As I envision it, Elmish.Reactive would contain functions that return
|
The ones where you recently added In any case, we should consider prior art and the rest of the Elmish ecosystem, too. How would you feel about opening a high-level discussion in elmish/elmish so that all Elmish parties can discuss whether it's sensible to have a common implementation? If the requirements turn out too different, we could just make an |
Did we ever solve the issue with the "stuck" slider? I am trying to write a tutorial (#161) section on the You mentioned something about this in #114 (comment). |
That is the correct behavior for debounce. You probably meant to throttle. Given in #114 (comment), here is a SO answer that does a great job defining the difference. If you are using Rx, recall the quote in #114 (comment) that quotes dotnet/reactive#395 (comment), which says
You can ignore that comment. It was about a small but related issue. Prior to that comment, I used
Yes, the essential work was completed in PR #118. I think we left this issue open as a reminder that we could make it simpler to wrap |
The slider is also stuck for throttle (i.e. But I realize now that it's expected, since the slider is, after all, locked to the model's value. |
After experimenting a bit: Is the slider really a problem? I set up a slider with 10,000 steps, and when I quickly dragged it from 1 to about halfway, it only dispatched for the following values, without needing any |
Yes, that is correct. My original description of the problem was that every tick in the interval gets put into a message and dispatched. I was wrong about that. Eventually I figured out that the bad behavior is caused by too many messages and one iteration of the dispatch loop executing too slowly. There are two numbers then: how long am iteration takes to execute and the throttling interval. As I recall, the best behavior is achieved when the throttling interval is slightly longer than the execution time, like maybe by 30 - 50 ms. This makes sense because if something can be denounced/throttled, then the interval time might as well be at least the execution time. Of course in practice, the execution time would not be known and it would not be a constant. To come to this conclusion, I added a call to So the slider is just one way to dispatch many somewhat redundant messages. I understand Elmish.WPF much better now than I did when I first raised this issue. I am still happy that we added dispatch wrapping, but I think the more urgent issue is the performance of the |
That is what confused me at first. I thought WPF would keep its own value for the slider that would be become inconsistent with our model if debouncing/throttling decided to hold onto a message (or in the extreme case, |
Is this issue still relevant? |
We can close this issue. The main thing we achieved (in PR #118) was adding the optional If anyone wants something like that, then they can comment here or open a new issue. Also, these functions would be independent of Elmish.WPF (and WPF), so we implement them here first for convenience and then maybe one day they could be promoted to Elmish. |
I want to throttle an individual binding. I started the discussion in elmish/elmish#189 (comment). I will briefly summarize the relevant parts.
My definition of throttling comes from this SO answer. That answer contrasts throttling with debouncing. Both of those concepts involve swallowing/dropping some amount of data. A related idea could be called limiting (as in rate limiting), which limits the rate at which data is allowed to flow by but doesn't drop any data. So although I am primarily motivated by throttling, debouncing and limiting are so similar that we can probably make all of these possible in one go.
My specific use case is a Slider with discrete interval of length 8000. As the user drags the thumb (as it is called) of the slider, each integral value encountered dispatches a message. My
update
function is trivial: it just saves the new value of the slider in the model. It is not computationally expensive. The part that is computationally expensive isupdate
being called ~4000 in a single flick of the user's wrist.@cmeeren (as stated in elmish/elmish#189 (comment)) and I like this idea. I am currenting having fun struggling with the details to make it happen :)
The text was updated successfully, but these errors were encountered: