Reputation: 897
I currently have a feature that users Timer()
to fire an observable immediately and then every x millseconds after.
HoldPayloads = Observable.Merge(
EnumeratedSymbolKeys.Select(
o => Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => o.PreviewMouseLeftButtonDown += h,
h => o.PreviewMouseLeftButtonDown -= h)
.Select(_ => Observable.Timer(DateTimeOffset.Now, TimeSpan.FromMilliseconds(o.Frequency))
.TakeUntil(Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => o.PreviewMouseLeftButtonUp += h,
h => o.PreviewMouseLeftButtonUp -= h)))
.Switch()
.Select(_ => o.Payload)));
What I'd like to have is The observable fire immediately when a button is clicked, then after initial longer delay start to repeat at either a faster interval or a decreasing interval down to a limit, something like this:
--x------x--x--x--x--x--x-->
or
--x------x----x---x--x-x-x->
I attempted to use Delay()
combined with Scan()
to generate exponentially lower values to delay by but couldn't get it to work. Was I on the right track? Any better ways to do something like this?
Revised code using the answer from Shlomo:
HoldPayloads = Observable.Merge(
EnumeratedSymbolKeys.Select(
o => Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => o.PreviewMouseLeftButtonDown += h,
h => o.PreviewMouseLeftButtonDown -= h)
.Select(_ => Observable.Generate(
1,
q => true,
i => i+1,
i => i,
i => i==1
? TimeSpan.FromMilliseconds(0)
: i > 10
? TimeSpan.FromMilliseconds(50)
: TimeSpan.FromMilliseconds(500/i))
.TakeUntil(Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => o.PreviewMouseLeftButtonUp += h,
h => o.PreviewMouseLeftButtonUp -= h)))
.Switch()
.Select(_ => o.Payload)));
Ended up modifying the conditional so that the first item was immediate.
Upvotes: 2
Views: 930
Reputation: 117029
I just thought I'd just add an answer to help to make the final query more readable. I haven't changed the answer in any way and I'm not looking to have this answer accepted or even up-voted. I just wanted to help add some clarity in how to understand the query.
I've just done a bit of substitution refactoring and have changed the query to be:
HoldPayloads =
EnumeratedSymbolKeys
.Select(o =>
MouseDowns(o)
.Select(_ => Generate().TakeUntil(MouseUps(o)))
.Switch()
.Select(_ => o.Payload))
.Merge();
To me this is much easier to read and the intent of the query is very clear.
It just leaves the definition of MouseDowns
, MouseUps
, and Generate
. These are:
IObservable<Unit> MouseDowns(EnumeratedSymbolKey o) =>
Observable
.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => o.PreviewMouseLeftButtonDown += h, h => o.PreviewMouseLeftButtonDown -= h)
.Select(EncodingProvider => Unit.Default);
IObservable<Unit> MouseUps(EnumeratedSymbolKey o) =>
Observable
.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => o.PreviewMouseLeftButtonUp += h, h => o.PreviewMouseLeftButtonUp -= h)
.Select(EncodingProvider => Unit.Default);
IObservable<int> Generate() =>
Observable
.Generate(1, i => true, i => i + 1, i => i,
i => i == 1
? TimeSpan.FromMilliseconds(0)
: (i > 10 ? TimeSpan.FromMilliseconds(50) : TimeSpan.FromMilliseconds(500 / i)));
Now that these are separated out it's easier to confirm that each are correct and hopefully that the whole code is correct.
Upvotes: 1
Reputation: 14350
Observable.Generate
is your friend (discussed well on this page).
For increasingly frequent values, replace .Select(_ => Observable.Timer(DateTimeOffset.Now, TimeSpan.FromMilliseconds(o.Frequency))
with something like this:
Observable.Generate(
1,
_ => true,
i => i + 1,
i => i,
i => i > 10
? TimeSpan.FromMilliseconds(50)
: TimeSpan.FromMilliseconds(500 / i)
)
Whatever your pattern is, fit it into that last parameter. Think of Generate
as like a reactive for loop with all the dials available to you.
EDIT:
With the code above, the first item from Generate
will be delayed. If you want the first item immediately, then the second item delayed, you can do as follows:
Observable.Generate(
1,
_ => true,
i => i + 1,
i => i,
i => i > 10
? TimeSpan.FromMilliseconds(50)
: TimeSpan.FromMilliseconds(500 / i)
)
.StartWith(0)
So the original .Select(_ => Observable.Timer(DateTimeOffset.Now, TimeSpan.FromMilliseconds(o.Frequency)
is now .Select(_ => Observable.Generate(...).StartWith(0))
Upvotes: 3