a575963da9
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
1908 lines
68 KiB
C#
1908 lines
68 KiB
C#
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
|
|
|
using System.Collections.Generic;
|
|
using System.Reactive.Concurrency;
|
|
using System.Reactive.Disposables;
|
|
using System.Reactive.Subjects;
|
|
|
|
namespace System.Reactive.Linq
|
|
{
|
|
#if !NO_PERF
|
|
using ObservableImpl;
|
|
#endif
|
|
|
|
internal partial class QueryLanguage
|
|
{
|
|
#region + Buffer +
|
|
|
|
#region TimeSpan only
|
|
|
|
public virtual IObservable<IList<TSource>> Buffer<TSource>(IObservable<TSource> source, TimeSpan timeSpan)
|
|
{
|
|
return Buffer_<TSource>(source, timeSpan, timeSpan, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<IList<TSource>> Buffer<TSource>(IObservable<TSource> source, TimeSpan timeSpan, IScheduler scheduler)
|
|
{
|
|
return Buffer_<TSource>(source, timeSpan, timeSpan, scheduler);
|
|
}
|
|
|
|
public virtual IObservable<IList<TSource>> Buffer<TSource>(IObservable<TSource> source, TimeSpan timeSpan, TimeSpan timeShift)
|
|
{
|
|
return Buffer_<TSource>(source, timeSpan, timeShift, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<IList<TSource>> Buffer<TSource>(IObservable<TSource> source, TimeSpan timeSpan, TimeSpan timeShift, IScheduler scheduler)
|
|
{
|
|
return Buffer_<TSource>(source, timeSpan, timeShift, scheduler);
|
|
}
|
|
|
|
private static IObservable<IList<TSource>> Buffer_<TSource>(IObservable<TSource> source, TimeSpan timeSpan, TimeSpan timeShift, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
return new Buffer<TSource>(source, timeSpan, timeShift, scheduler);
|
|
#else
|
|
return source.Window(timeSpan, timeShift, scheduler).SelectMany(Observable.ToList);
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region TimeSpan + int
|
|
|
|
public virtual IObservable<IList<TSource>> Buffer<TSource>(IObservable<TSource> source, TimeSpan timeSpan, int count)
|
|
{
|
|
return Buffer_<TSource>(source, timeSpan, count, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<IList<TSource>> Buffer<TSource>(IObservable<TSource> source, TimeSpan timeSpan, int count, IScheduler scheduler)
|
|
{
|
|
return Buffer_<TSource>(source, timeSpan, count, scheduler);
|
|
}
|
|
|
|
private static IObservable<IList<TSource>> Buffer_<TSource>(IObservable<TSource> source, TimeSpan timeSpan, int count, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
return new Buffer<TSource>(source, timeSpan, count, scheduler);
|
|
#else
|
|
return source.Window(timeSpan, count, scheduler).SelectMany(Observable.ToList);
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
#region + Delay +
|
|
|
|
#region TimeSpan
|
|
|
|
public virtual IObservable<TSource> Delay<TSource>(IObservable<TSource> source, TimeSpan dueTime)
|
|
{
|
|
return Delay_<TSource>(source, dueTime, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<TSource> Delay<TSource>(IObservable<TSource> source, TimeSpan dueTime, IScheduler scheduler)
|
|
{
|
|
return Delay_<TSource>(source, dueTime, scheduler);
|
|
}
|
|
|
|
private static IObservable<TSource> Delay_<TSource>(IObservable<TSource> source, TimeSpan dueTime, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
return new Delay<TSource>(source, dueTime, scheduler);
|
|
#else
|
|
return new AnonymousObservable<TSource>(observer =>
|
|
{
|
|
var gate = new object();
|
|
var q = new Queue<Timestamped<Notification<TSource>>>();
|
|
var active = false;
|
|
var running = false;
|
|
var cancelable = new SerialDisposable();
|
|
var exception = default(Exception);
|
|
|
|
var subscription = source.Materialize().Timestamp(scheduler).Subscribe(notification =>
|
|
{
|
|
var shouldRun = false;
|
|
|
|
lock (gate)
|
|
{
|
|
if (notification.Value.Kind == NotificationKind.OnError)
|
|
{
|
|
q.Clear();
|
|
q.Enqueue(notification);
|
|
exception = notification.Value.Exception;
|
|
shouldRun = !running;
|
|
}
|
|
else
|
|
{
|
|
q.Enqueue(new Timestamped<Notification<TSource>>(notification.Value, notification.Timestamp.Add(dueTime)));
|
|
shouldRun = !active;
|
|
active = true;
|
|
}
|
|
}
|
|
|
|
if (shouldRun)
|
|
{
|
|
if (exception != null)
|
|
observer.OnError(exception);
|
|
else
|
|
{
|
|
var d = new SingleAssignmentDisposable();
|
|
cancelable.Disposable = d;
|
|
d.Disposable = scheduler.Schedule(dueTime, self =>
|
|
{
|
|
lock (gate)
|
|
{
|
|
if (exception != null)
|
|
return;
|
|
running = true;
|
|
}
|
|
Notification<TSource> result;
|
|
|
|
do
|
|
{
|
|
result = null;
|
|
lock (gate)
|
|
{
|
|
if (q.Count > 0 && q.Peek().Timestamp.CompareTo(scheduler.Now) <= 0)
|
|
result = q.Dequeue().Value;
|
|
}
|
|
|
|
if (result != null)
|
|
result.Accept(observer);
|
|
} while (result != null);
|
|
|
|
var shouldRecurse = false;
|
|
var recurseDueTime = TimeSpan.Zero;
|
|
var e = default(Exception);
|
|
lock (gate)
|
|
{
|
|
if (q.Count > 0)
|
|
{
|
|
shouldRecurse = true;
|
|
recurseDueTime = TimeSpan.FromTicks(Math.Max(0, q.Peek().Timestamp.Subtract(scheduler.Now).Ticks));
|
|
}
|
|
else
|
|
active = false;
|
|
e = exception;
|
|
running = false;
|
|
}
|
|
if (e != null)
|
|
observer.OnError(e);
|
|
else if (shouldRecurse)
|
|
self(recurseDueTime);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
return new CompositeDisposable(subscription, cancelable);
|
|
});
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region DateTimeOffset
|
|
|
|
public virtual IObservable<TSource> Delay<TSource>(IObservable<TSource> source, DateTimeOffset dueTime)
|
|
{
|
|
return Delay_<TSource>(source, dueTime, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<TSource> Delay<TSource>(IObservable<TSource> source, DateTimeOffset dueTime, IScheduler scheduler)
|
|
{
|
|
return Delay_<TSource>(source, dueTime, scheduler);
|
|
}
|
|
|
|
private static IObservable<TSource> Delay_<TSource>(IObservable<TSource> source, DateTimeOffset dueTime, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
return new Delay<TSource>(source, dueTime, scheduler);
|
|
#else
|
|
return Observable.Defer(() =>
|
|
{
|
|
var timeSpan = dueTime.Subtract(scheduler.Now);
|
|
return Delay_<TSource>(source, timeSpan, scheduler);
|
|
});
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Duration selector
|
|
|
|
public virtual IObservable<TSource> Delay<TSource, TDelay>(IObservable<TSource> source, Func<TSource, IObservable<TDelay>> delayDurationSelector)
|
|
{
|
|
return Delay_<TSource, TDelay>(source, null, delayDurationSelector);
|
|
}
|
|
|
|
public virtual IObservable<TSource> Delay<TSource, TDelay>(IObservable<TSource> source, IObservable<TDelay> subscriptionDelay, Func<TSource, IObservable<TDelay>> delayDurationSelector)
|
|
{
|
|
return Delay_<TSource, TDelay>(source, subscriptionDelay, delayDurationSelector);
|
|
}
|
|
|
|
private static IObservable<TSource> Delay_<TSource, TDelay>(IObservable<TSource> source, IObservable<TDelay> subscriptionDelay, Func<TSource, IObservable<TDelay>> delayDurationSelector)
|
|
{
|
|
#if !NO_PERF
|
|
return new Delay<TSource, TDelay>(source, subscriptionDelay, delayDurationSelector);
|
|
#else
|
|
return new AnonymousObservable<TSource>(observer =>
|
|
{
|
|
var delays = new CompositeDisposable();
|
|
|
|
var gate = new object();
|
|
var atEnd = false;
|
|
|
|
var done = new Action(() =>
|
|
{
|
|
if (atEnd && delays.Count == 0)
|
|
{
|
|
observer.OnCompleted();
|
|
}
|
|
});
|
|
|
|
var subscription = new SerialDisposable();
|
|
|
|
var start = new Action(() =>
|
|
{
|
|
subscription.Disposable = source.Subscribe(
|
|
x =>
|
|
{
|
|
var delay = default(IObservable<TDelay>);
|
|
try
|
|
{
|
|
delay = delayDurationSelector(x);
|
|
}
|
|
catch (Exception error)
|
|
{
|
|
lock (gate)
|
|
observer.OnError(error);
|
|
|
|
return;
|
|
}
|
|
|
|
var d = new SingleAssignmentDisposable();
|
|
delays.Add(d);
|
|
d.Disposable = delay.Subscribe(
|
|
_ =>
|
|
{
|
|
lock (gate)
|
|
{
|
|
observer.OnNext(x);
|
|
|
|
delays.Remove(d);
|
|
done();
|
|
}
|
|
},
|
|
exception =>
|
|
{
|
|
lock (gate)
|
|
observer.OnError(exception);
|
|
},
|
|
() =>
|
|
{
|
|
lock (gate)
|
|
{
|
|
observer.OnNext(x);
|
|
|
|
delays.Remove(d);
|
|
done();
|
|
}
|
|
}
|
|
);
|
|
},
|
|
exception =>
|
|
{
|
|
lock (gate)
|
|
{
|
|
observer.OnError(exception);
|
|
}
|
|
},
|
|
() =>
|
|
{
|
|
lock (gate)
|
|
{
|
|
atEnd = true;
|
|
subscription.Dispose();
|
|
|
|
done();
|
|
}
|
|
}
|
|
);
|
|
});
|
|
|
|
if (subscriptionDelay == null)
|
|
{
|
|
start();
|
|
}
|
|
else
|
|
{
|
|
subscription.Disposable = subscriptionDelay.Subscribe(
|
|
_ =>
|
|
{
|
|
start();
|
|
},
|
|
observer.OnError,
|
|
start
|
|
);
|
|
}
|
|
|
|
return new CompositeDisposable(subscription, delays);
|
|
});
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
#region + DelaySubscription +
|
|
|
|
public virtual IObservable<TSource> DelaySubscription<TSource>(IObservable<TSource> source, TimeSpan dueTime)
|
|
{
|
|
return DelaySubscription_<TSource>(source, dueTime, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<TSource> DelaySubscription<TSource>(IObservable<TSource> source, TimeSpan dueTime, IScheduler scheduler)
|
|
{
|
|
return DelaySubscription_<TSource>(source, dueTime, scheduler);
|
|
}
|
|
|
|
private static IObservable<TSource> DelaySubscription_<TSource>(IObservable<TSource> source, TimeSpan dueTime, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
return new DelaySubscription<TSource>(source, dueTime, scheduler);
|
|
#else
|
|
return new AnonymousObservable<TSource>(observer =>
|
|
{
|
|
var d = new MultipleAssignmentDisposable();
|
|
|
|
var dt = Normalize(dueTime);
|
|
|
|
d.Disposable = scheduler.Schedule(dt, () =>
|
|
{
|
|
d.Disposable = source.Subscribe(observer);
|
|
});
|
|
|
|
return d;
|
|
});
|
|
#endif
|
|
}
|
|
|
|
public virtual IObservable<TSource> DelaySubscription<TSource>(IObservable<TSource> source, DateTimeOffset dueTime)
|
|
{
|
|
return DelaySubscription_<TSource>(source, dueTime, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<TSource> DelaySubscription<TSource>(IObservable<TSource> source, DateTimeOffset dueTime, IScheduler scheduler)
|
|
{
|
|
return DelaySubscription_<TSource>(source, dueTime, scheduler);
|
|
}
|
|
|
|
private static IObservable<TSource> DelaySubscription_<TSource>(IObservable<TSource> source, DateTimeOffset dueTime, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
return new DelaySubscription<TSource>(source, dueTime, scheduler);
|
|
#else
|
|
return new AnonymousObservable<TSource>(observer =>
|
|
{
|
|
var d = new MultipleAssignmentDisposable();
|
|
|
|
d.Disposable = scheduler.Schedule(dueTime, () =>
|
|
{
|
|
d.Disposable = source.Subscribe(observer);
|
|
});
|
|
|
|
return d;
|
|
});
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region + Generate +
|
|
|
|
public virtual IObservable<TResult> Generate<TState, TResult>(TState initialState, Func<TState, bool> condition, Func<TState, TState> iterate, Func<TState, TResult> resultSelector, Func<TState, TimeSpan> timeSelector)
|
|
{
|
|
return Generate_<TState, TResult>(initialState, condition, iterate, resultSelector, timeSelector, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<TResult> Generate<TState, TResult>(TState initialState, Func<TState, bool> condition, Func<TState, TState> iterate, Func<TState, TResult> resultSelector, Func<TState, TimeSpan> timeSelector, IScheduler scheduler)
|
|
{
|
|
return Generate_<TState, TResult>(initialState, condition, iterate, resultSelector, timeSelector, scheduler);
|
|
}
|
|
|
|
private static IObservable<TResult> Generate_<TState, TResult>(TState initialState, Func<TState, bool> condition, Func<TState, TState> iterate, Func<TState, TResult> resultSelector, Func<TState, TimeSpan> timeSelector, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
return new Generate<TState, TResult>(initialState, condition, iterate, resultSelector, timeSelector, scheduler);
|
|
#else
|
|
return new AnonymousObservable<TResult>(observer =>
|
|
{
|
|
var state = initialState;
|
|
var first = true;
|
|
var hasResult = false;
|
|
var result = default(TResult);
|
|
var time = default(TimeSpan);
|
|
return scheduler.Schedule(TimeSpan.Zero, self =>
|
|
{
|
|
if (hasResult)
|
|
observer.OnNext(result);
|
|
try
|
|
{
|
|
if (first)
|
|
first = false;
|
|
else
|
|
state = iterate(state);
|
|
hasResult = condition(state);
|
|
if (hasResult)
|
|
{
|
|
result = resultSelector(state);
|
|
time = timeSelector(state);
|
|
}
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
observer.OnError(exception);
|
|
return;
|
|
}
|
|
|
|
if (hasResult)
|
|
self(time);
|
|
else
|
|
observer.OnCompleted();
|
|
});
|
|
});
|
|
#endif
|
|
}
|
|
|
|
public virtual IObservable<TResult> Generate<TState, TResult>(TState initialState, Func<TState, bool> condition, Func<TState, TState> iterate, Func<TState, TResult> resultSelector, Func<TState, DateTimeOffset> timeSelector)
|
|
{
|
|
return Generate_<TState, TResult>(initialState, condition, iterate, resultSelector, timeSelector, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<TResult> Generate<TState, TResult>(TState initialState, Func<TState, bool> condition, Func<TState, TState> iterate, Func<TState, TResult> resultSelector, Func<TState, DateTimeOffset> timeSelector, IScheduler scheduler)
|
|
{
|
|
return Generate_<TState, TResult>(initialState, condition, iterate, resultSelector, timeSelector, scheduler);
|
|
}
|
|
|
|
private static IObservable<TResult> Generate_<TState, TResult>(TState initialState, Func<TState, bool> condition, Func<TState, TState> iterate, Func<TState, TResult> resultSelector, Func<TState, DateTimeOffset> timeSelector, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
return new Generate<TState, TResult>(initialState, condition, iterate, resultSelector, timeSelector, scheduler);
|
|
#else
|
|
return new AnonymousObservable<TResult>(observer =>
|
|
{
|
|
var state = initialState;
|
|
var first = true;
|
|
var hasResult = false;
|
|
var result = default(TResult);
|
|
var time = default(DateTimeOffset);
|
|
return scheduler.Schedule(scheduler.Now, self =>
|
|
{
|
|
if (hasResult)
|
|
observer.OnNext(result);
|
|
try
|
|
{
|
|
if (first)
|
|
first = false;
|
|
else
|
|
state = iterate(state);
|
|
hasResult = condition(state);
|
|
if (hasResult)
|
|
{
|
|
result = resultSelector(state);
|
|
time = timeSelector(state);
|
|
}
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
observer.OnError(exception);
|
|
return;
|
|
}
|
|
|
|
if (hasResult)
|
|
self(time);
|
|
else
|
|
observer.OnCompleted();
|
|
});
|
|
});
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region + Interval +
|
|
|
|
public virtual IObservable<long> Interval(TimeSpan period)
|
|
{
|
|
return Timer_(period, period, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<long> Interval(TimeSpan period, IScheduler scheduler)
|
|
{
|
|
return Timer_(period, period, scheduler);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region + Sample +
|
|
|
|
public virtual IObservable<TSource> Sample<TSource>(IObservable<TSource> source, TimeSpan interval)
|
|
{
|
|
return Sample_<TSource>(source, interval, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<TSource> Sample<TSource>(IObservable<TSource> source, TimeSpan interval, IScheduler scheduler)
|
|
{
|
|
return Sample_<TSource>(source, interval, scheduler);
|
|
}
|
|
|
|
private static IObservable<TSource> Sample_<TSource>(IObservable<TSource> source, TimeSpan interval, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
return new Sample<TSource>(source, interval, scheduler);
|
|
#else
|
|
var sampler = Observable.Interval(interval, scheduler);
|
|
return Sample_<TSource, long>(source, sampler);
|
|
#endif
|
|
}
|
|
|
|
public virtual IObservable<TSource> Sample<TSource, TSample>(IObservable<TSource> source, IObservable<TSample> sampler)
|
|
{
|
|
return Sample_<TSource, TSample>(source, sampler);
|
|
}
|
|
|
|
private static IObservable<TSource> Sample_<TSource, TSample>(IObservable<TSource> source, IObservable<TSample> sampler)
|
|
{
|
|
#if !NO_PERF
|
|
return new Sample<TSource, TSample>(source, sampler);
|
|
#else
|
|
return Combine(source, sampler, (IObserver<TSource> observer, IDisposable leftSubscription, IDisposable rightSubscription) =>
|
|
{
|
|
var value = default(Notification<TSource>);
|
|
var atEnd = false;
|
|
return new BinaryObserver<TSource, TSample>(
|
|
newValue =>
|
|
{
|
|
switch (newValue.Kind)
|
|
{
|
|
case NotificationKind.OnNext:
|
|
value = newValue;
|
|
break;
|
|
case NotificationKind.OnError:
|
|
newValue.Accept(observer);
|
|
break;
|
|
case NotificationKind.OnCompleted:
|
|
atEnd = true;
|
|
break;
|
|
}
|
|
},
|
|
_ =>
|
|
{
|
|
var myValue = value;
|
|
value = null;
|
|
if (myValue != null)
|
|
myValue.Accept(observer);
|
|
if (atEnd)
|
|
observer.OnCompleted();
|
|
});
|
|
});
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region + Skip +
|
|
|
|
public virtual IObservable<TSource> Skip<TSource>(IObservable<TSource> source, TimeSpan duration)
|
|
{
|
|
return Skip_<TSource>(source, duration, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<TSource> Skip<TSource>(IObservable<TSource> source, TimeSpan duration, IScheduler scheduler)
|
|
{
|
|
return Skip_<TSource>(source, duration, scheduler);
|
|
}
|
|
|
|
private static IObservable<TSource> Skip_<TSource>(IObservable<TSource> source, TimeSpan duration, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
var skip = source as Skip<TSource>;
|
|
if (skip != null && skip._scheduler == scheduler)
|
|
return skip.Omega(duration);
|
|
|
|
return new Skip<TSource>(source, duration, scheduler);
|
|
#else
|
|
return new AnonymousObservable<TSource>(observer =>
|
|
{
|
|
var open = false;
|
|
|
|
var t = scheduler.Schedule(duration, () => open = true);
|
|
|
|
var d = source.Subscribe(
|
|
x =>
|
|
{
|
|
if (open)
|
|
observer.OnNext(x);
|
|
},
|
|
observer.OnError,
|
|
observer.OnCompleted
|
|
);
|
|
|
|
return new CompositeDisposable(t, d);
|
|
});
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region + SkipLast +
|
|
|
|
public virtual IObservable<TSource> SkipLast<TSource>(IObservable<TSource> source, TimeSpan duration)
|
|
{
|
|
return SkipLast_<TSource>(source, duration, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<TSource> SkipLast<TSource>(IObservable<TSource> source, TimeSpan duration, IScheduler scheduler)
|
|
{
|
|
return SkipLast_<TSource>(source, duration, scheduler);
|
|
}
|
|
|
|
private static IObservable<TSource> SkipLast_<TSource>(IObservable<TSource> source, TimeSpan duration, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
return new SkipLast<TSource>(source, duration, scheduler);
|
|
#else
|
|
return new AnonymousObservable<TSource>(observer =>
|
|
{
|
|
var q = new Queue<System.Reactive.TimeInterval<TSource>>();
|
|
|
|
var swp = scheduler.AsStopwatchProvider();
|
|
var sw = swp != null ? swp.StartStopwatch() : new DefaultStopwatch();
|
|
|
|
return source.Subscribe(
|
|
x =>
|
|
{
|
|
var now = sw.Elapsed;
|
|
q.Enqueue(new System.Reactive.TimeInterval<TSource>(x, now));
|
|
while (q.Count > 0 && now - q.Peek().Interval >= duration)
|
|
observer.OnNext(q.Dequeue().Value);
|
|
},
|
|
observer.OnError,
|
|
() =>
|
|
{
|
|
var now = sw.Elapsed;
|
|
while (q.Count > 0 && now - q.Peek().Interval >= duration)
|
|
observer.OnNext(q.Dequeue().Value);
|
|
observer.OnCompleted();
|
|
}
|
|
);
|
|
});
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region + SkipUntil +
|
|
|
|
public virtual IObservable<TSource> SkipUntil<TSource>(IObservable<TSource> source, DateTimeOffset startTime)
|
|
{
|
|
return SkipUntil_<TSource>(source, startTime, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<TSource> SkipUntil<TSource>(IObservable<TSource> source, DateTimeOffset startTime, IScheduler scheduler)
|
|
{
|
|
return SkipUntil_<TSource>(source, startTime, scheduler);
|
|
}
|
|
|
|
private static IObservable<TSource> SkipUntil_<TSource>(IObservable<TSource> source, DateTimeOffset startTime, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
var skipUntil = source as SkipUntil<TSource>;
|
|
if (skipUntil != null && skipUntil._scheduler == scheduler)
|
|
return skipUntil.Omega(startTime);
|
|
|
|
return new SkipUntil<TSource>(source, startTime, scheduler);
|
|
#else
|
|
return new AnonymousObservable<TSource>(observer =>
|
|
{
|
|
var open = false;
|
|
|
|
var t = scheduler.Schedule(startTime, () => open = true);
|
|
|
|
var d = source.Subscribe(
|
|
x =>
|
|
{
|
|
if (open)
|
|
observer.OnNext(x);
|
|
},
|
|
observer.OnError,
|
|
observer.OnCompleted
|
|
);
|
|
|
|
return new CompositeDisposable(t, d);
|
|
});
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region + Take +
|
|
|
|
public virtual IObservable<TSource> Take<TSource>(IObservable<TSource> source, TimeSpan duration)
|
|
{
|
|
return Take_<TSource>(source, duration, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<TSource> Take<TSource>(IObservable<TSource> source, TimeSpan duration, IScheduler scheduler)
|
|
{
|
|
return Take_<TSource>(source, duration, scheduler);
|
|
}
|
|
|
|
private static IObservable<TSource> Take_<TSource>(IObservable<TSource> source, TimeSpan duration, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
var take = source as Take<TSource>;
|
|
if (take != null && take._scheduler == scheduler)
|
|
return take.Omega(duration);
|
|
|
|
return new Take<TSource>(source, duration, scheduler);
|
|
#else
|
|
return new AnonymousObservable<TSource>(observer =>
|
|
{
|
|
var gate = new object();
|
|
|
|
var t = scheduler.Schedule(duration, () =>
|
|
{
|
|
lock (gate)
|
|
{
|
|
observer.OnCompleted();
|
|
}
|
|
});
|
|
|
|
var d = source.Synchronize(gate).Subscribe(observer);
|
|
|
|
return new CompositeDisposable(t, d);
|
|
});
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region + TakeLast +
|
|
|
|
public virtual IObservable<TSource> TakeLast<TSource>(IObservable<TSource> source, TimeSpan duration)
|
|
{
|
|
return TakeLast_<TSource>(source, duration, SchedulerDefaults.TimeBasedOperations, SchedulerDefaults.Iteration);
|
|
}
|
|
|
|
public virtual IObservable<TSource> TakeLast<TSource>(IObservable<TSource> source, TimeSpan duration, IScheduler scheduler)
|
|
{
|
|
return TakeLast_<TSource>(source, duration, scheduler, SchedulerDefaults.Iteration);
|
|
}
|
|
|
|
public virtual IObservable<TSource> TakeLast<TSource>(IObservable<TSource> source, TimeSpan duration, IScheduler timerScheduler, IScheduler loopScheduler)
|
|
{
|
|
return TakeLast_<TSource>(source, duration, timerScheduler, loopScheduler);
|
|
}
|
|
|
|
private static IObservable<TSource> TakeLast_<TSource>(IObservable<TSource> source, TimeSpan duration, IScheduler timerScheduler, IScheduler loopScheduler)
|
|
{
|
|
#if !NO_PERF
|
|
return new TakeLast<TSource>(source, duration, timerScheduler, loopScheduler);
|
|
#else
|
|
return new AnonymousObservable<TSource>(observer =>
|
|
{
|
|
var q = new Queue<System.Reactive.TimeInterval<TSource>>();
|
|
|
|
var swp = timerScheduler.AsStopwatchProvider();
|
|
var sw = swp != null ? swp.StartStopwatch() : new DefaultStopwatch();
|
|
|
|
var trim = new Action<TimeSpan>(now =>
|
|
{
|
|
while (q.Count > 0 && now - q.Peek().Interval >= duration)
|
|
q.Dequeue();
|
|
});
|
|
|
|
var g = new CompositeDisposable();
|
|
|
|
g.Add(source.Subscribe(
|
|
x =>
|
|
{
|
|
var now = sw.Elapsed;
|
|
|
|
q.Enqueue(new System.Reactive.TimeInterval<TSource>(x, now));
|
|
trim(now);
|
|
},
|
|
observer.OnError,
|
|
() =>
|
|
{
|
|
var now = sw.Elapsed;
|
|
trim(now);
|
|
|
|
g.Add(loopScheduler.Schedule(rec =>
|
|
{
|
|
if (q.Count > 0)
|
|
{
|
|
observer.OnNext(q.Dequeue().Value);
|
|
rec();
|
|
}
|
|
else
|
|
{
|
|
observer.OnCompleted();
|
|
}
|
|
}));
|
|
}
|
|
));
|
|
|
|
return g;
|
|
});
|
|
#endif
|
|
}
|
|
|
|
public virtual IObservable<IList<TSource>> TakeLastBuffer<TSource>(IObservable<TSource> source, TimeSpan duration)
|
|
{
|
|
return TakeLastBuffer_<TSource>(source, duration, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<IList<TSource>> TakeLastBuffer<TSource>(IObservable<TSource> source, TimeSpan duration, IScheduler scheduler)
|
|
{
|
|
return TakeLastBuffer_<TSource>(source, duration, scheduler);
|
|
}
|
|
|
|
private static IObservable<IList<TSource>> TakeLastBuffer_<TSource>(IObservable<TSource> source, TimeSpan duration, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
return new TakeLastBuffer<TSource>(source, duration, scheduler);
|
|
#else
|
|
return new AnonymousObservable<IList<TSource>>(observer =>
|
|
{
|
|
var q = new Queue<System.Reactive.TimeInterval<TSource>>();
|
|
|
|
var swp = scheduler.AsStopwatchProvider();
|
|
var sw = swp != null ? swp.StartStopwatch() : new DefaultStopwatch();
|
|
|
|
return source.Subscribe(
|
|
x =>
|
|
{
|
|
var now = sw.Elapsed;
|
|
|
|
q.Enqueue(new System.Reactive.TimeInterval<TSource>(x, now));
|
|
while (q.Count > 0 && now - q.Peek().Interval >= duration)
|
|
q.Dequeue();
|
|
},
|
|
observer.OnError,
|
|
() =>
|
|
{
|
|
var now = sw.Elapsed;
|
|
|
|
var res = new List<TSource>();
|
|
while (q.Count > 0)
|
|
{
|
|
var next = q.Dequeue();
|
|
if (now - next.Interval <= duration)
|
|
res.Add(next.Value);
|
|
}
|
|
|
|
observer.OnNext(res);
|
|
observer.OnCompleted();
|
|
}
|
|
);
|
|
});
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region + TakeUntil +
|
|
|
|
public virtual IObservable<TSource> TakeUntil<TSource>(IObservable<TSource> source, DateTimeOffset endTime)
|
|
{
|
|
return TakeUntil_<TSource>(source, endTime, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<TSource> TakeUntil<TSource>(IObservable<TSource> source, DateTimeOffset endTime, IScheduler scheduler)
|
|
{
|
|
return TakeUntil_<TSource>(source, endTime, scheduler);
|
|
}
|
|
|
|
private static IObservable<TSource> TakeUntil_<TSource>(IObservable<TSource> source, DateTimeOffset endTime, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
var takeUntil = source as TakeUntil<TSource>;
|
|
if (takeUntil != null && takeUntil._scheduler == scheduler)
|
|
return takeUntil.Omega(endTime);
|
|
|
|
return new TakeUntil<TSource>(source, endTime, scheduler);
|
|
#else
|
|
return new AnonymousObservable<TSource>(observer =>
|
|
{
|
|
var gate = new object();
|
|
|
|
var t = scheduler.Schedule(endTime, () =>
|
|
{
|
|
lock (gate)
|
|
{
|
|
observer.OnCompleted();
|
|
}
|
|
});
|
|
|
|
var d = source.Synchronize(gate).Subscribe(observer);
|
|
|
|
return new CompositeDisposable(t, d);
|
|
});
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region + Throttle +
|
|
|
|
public virtual IObservable<TSource> Throttle<TSource>(IObservable<TSource> source, TimeSpan dueTime)
|
|
{
|
|
return Throttle_<TSource>(source, dueTime, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<TSource> Throttle<TSource>(IObservable<TSource> source, TimeSpan dueTime, IScheduler scheduler)
|
|
{
|
|
return Throttle_<TSource>(source, dueTime, scheduler);
|
|
}
|
|
|
|
private static IObservable<TSource> Throttle_<TSource>(IObservable<TSource> source, TimeSpan dueTime, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
return new Throttle<TSource>(source, dueTime, scheduler);
|
|
#else
|
|
return new AnonymousObservable<TSource>(observer =>
|
|
{
|
|
var gate = new object();
|
|
var value = default(TSource);
|
|
var hasValue = false;
|
|
var cancelable = new SerialDisposable();
|
|
var id = 0UL;
|
|
|
|
var subscription = source.Subscribe(x =>
|
|
{
|
|
ulong currentid;
|
|
lock (gate)
|
|
{
|
|
hasValue = true;
|
|
value = x;
|
|
id = unchecked(id + 1);
|
|
currentid = id;
|
|
}
|
|
var d = new SingleAssignmentDisposable();
|
|
cancelable.Disposable = d;
|
|
d.Disposable = scheduler.Schedule(dueTime, () =>
|
|
{
|
|
lock (gate)
|
|
{
|
|
if (hasValue && id == currentid)
|
|
observer.OnNext(value);
|
|
hasValue = false;
|
|
}
|
|
});
|
|
},
|
|
exception =>
|
|
{
|
|
cancelable.Dispose();
|
|
|
|
lock (gate)
|
|
{
|
|
observer.OnError(exception);
|
|
hasValue = false;
|
|
id = unchecked(id + 1);
|
|
}
|
|
},
|
|
() =>
|
|
{
|
|
cancelable.Dispose();
|
|
|
|
lock (gate)
|
|
{
|
|
if (hasValue)
|
|
observer.OnNext(value);
|
|
observer.OnCompleted();
|
|
hasValue = false;
|
|
id = unchecked(id + 1);
|
|
}
|
|
});
|
|
|
|
return new CompositeDisposable(subscription, cancelable);
|
|
});
|
|
#endif
|
|
}
|
|
|
|
public virtual IObservable<TSource> Throttle<TSource, TThrottle>(IObservable<TSource> source, Func<TSource, IObservable<TThrottle>> throttleDurationSelector)
|
|
{
|
|
#if !NO_PERF
|
|
return new Throttle<TSource, TThrottle>(source, throttleDurationSelector);
|
|
#else
|
|
return new AnonymousObservable<TSource>(observer =>
|
|
{
|
|
var gate = new object();
|
|
var value = default(TSource);
|
|
var hasValue = false;
|
|
var cancelable = new SerialDisposable();
|
|
var id = 0UL;
|
|
|
|
var subscription = source.Subscribe(
|
|
x =>
|
|
{
|
|
var throttle = default(IObservable<TThrottle>);
|
|
try
|
|
{
|
|
throttle = throttleDurationSelector(x);
|
|
}
|
|
catch (Exception error)
|
|
{
|
|
lock (gate)
|
|
observer.OnError(error);
|
|
|
|
return;
|
|
}
|
|
|
|
ulong currentid;
|
|
lock (gate)
|
|
{
|
|
hasValue = true;
|
|
value = x;
|
|
id = unchecked(id + 1);
|
|
currentid = id;
|
|
}
|
|
|
|
var d = new SingleAssignmentDisposable();
|
|
cancelable.Disposable = d;
|
|
d.Disposable = throttle.Subscribe(
|
|
_ =>
|
|
{
|
|
lock (gate)
|
|
{
|
|
if (hasValue && id == currentid)
|
|
observer.OnNext(value);
|
|
|
|
hasValue = false;
|
|
d.Dispose();
|
|
}
|
|
},
|
|
exception =>
|
|
{
|
|
lock (gate)
|
|
{
|
|
observer.OnError(exception);
|
|
}
|
|
},
|
|
() =>
|
|
{
|
|
lock (gate)
|
|
{
|
|
if (hasValue && id == currentid)
|
|
observer.OnNext(value);
|
|
|
|
hasValue = false;
|
|
d.Dispose();
|
|
}
|
|
}
|
|
);
|
|
},
|
|
exception =>
|
|
{
|
|
cancelable.Dispose();
|
|
|
|
lock (gate)
|
|
{
|
|
observer.OnError(exception);
|
|
hasValue = false;
|
|
id = unchecked(id + 1);
|
|
}
|
|
},
|
|
() =>
|
|
{
|
|
cancelable.Dispose();
|
|
|
|
lock (gate)
|
|
{
|
|
if (hasValue)
|
|
observer.OnNext(value);
|
|
observer.OnCompleted();
|
|
hasValue = false;
|
|
id = unchecked(id + 1);
|
|
}
|
|
});
|
|
|
|
return new CompositeDisposable(subscription, cancelable);
|
|
});
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region + TimeInterval +
|
|
|
|
public virtual IObservable<System.Reactive.TimeInterval<TSource>> TimeInterval<TSource>(IObservable<TSource> source)
|
|
{
|
|
return TimeInterval_<TSource>(source, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<System.Reactive.TimeInterval<TSource>> TimeInterval<TSource>(IObservable<TSource> source, IScheduler scheduler)
|
|
{
|
|
return TimeInterval_<TSource>(source, scheduler);
|
|
}
|
|
|
|
#if !NO_PERF
|
|
private static IObservable<System.Reactive.TimeInterval<TSource>> TimeInterval_<TSource>(IObservable<TSource> source, IScheduler scheduler)
|
|
{
|
|
return new TimeInterval<TSource>(source, scheduler);
|
|
}
|
|
#else
|
|
private IObservable<System.Reactive.TimeInterval<TSource>> TimeInterval_<TSource>(IObservable<TSource> source, IScheduler scheduler)
|
|
{
|
|
return Defer(() =>
|
|
{
|
|
var last = scheduler.Now;
|
|
return source.Select(x =>
|
|
{
|
|
var now = scheduler.Now;
|
|
var span = now.Subtract(last);
|
|
last = now;
|
|
return new System.Reactive.TimeInterval<TSource>(x, span);
|
|
});
|
|
});
|
|
}
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
#region + Timeout +
|
|
|
|
#region TimeSpan
|
|
|
|
public virtual IObservable<TSource> Timeout<TSource>(IObservable<TSource> source, TimeSpan dueTime)
|
|
{
|
|
return Timeout_<TSource>(source, dueTime, Observable.Throw<TSource>(new TimeoutException()), SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<TSource> Timeout<TSource>(IObservable<TSource> source, TimeSpan dueTime, IScheduler scheduler)
|
|
{
|
|
return Timeout_<TSource>(source, dueTime, Observable.Throw<TSource>(new TimeoutException()), scheduler);
|
|
}
|
|
|
|
public virtual IObservable<TSource> Timeout<TSource>(IObservable<TSource> source, TimeSpan dueTime, IObservable<TSource> other)
|
|
{
|
|
return Timeout_<TSource>(source, dueTime, other, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<TSource> Timeout<TSource>(IObservable<TSource> source, TimeSpan dueTime, IObservable<TSource> other, IScheduler scheduler)
|
|
{
|
|
return Timeout_<TSource>(source, dueTime, other, scheduler);
|
|
}
|
|
|
|
private static IObservable<TSource> Timeout_<TSource>(IObservable<TSource> source, TimeSpan dueTime, IObservable<TSource> other, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
return new Timeout<TSource>(source, dueTime, other, scheduler);
|
|
#else
|
|
return new AnonymousObservable<TSource>(observer =>
|
|
{
|
|
var subscription = new SerialDisposable();
|
|
var timer = new SerialDisposable();
|
|
var original = new SingleAssignmentDisposable();
|
|
|
|
subscription.Disposable = original;
|
|
|
|
var gate = new object();
|
|
var id = 0UL;
|
|
var switched = false;
|
|
|
|
Action createTimer = () =>
|
|
{
|
|
var myid = id;
|
|
timer.Disposable = scheduler.Schedule(dueTime, () =>
|
|
{
|
|
var timerWins = false;
|
|
|
|
lock (gate)
|
|
{
|
|
switched = (id == myid);
|
|
timerWins = switched;
|
|
}
|
|
|
|
if (timerWins)
|
|
subscription.Disposable = other.Subscribe(observer);
|
|
});
|
|
};
|
|
|
|
createTimer();
|
|
|
|
original.Disposable = source.Subscribe(
|
|
x =>
|
|
{
|
|
var onNextWins = false;
|
|
|
|
lock (gate)
|
|
{
|
|
onNextWins = !switched;
|
|
if (onNextWins)
|
|
{
|
|
id = unchecked(id + 1);
|
|
}
|
|
}
|
|
|
|
if (onNextWins)
|
|
{
|
|
observer.OnNext(x);
|
|
createTimer();
|
|
}
|
|
},
|
|
exception =>
|
|
{
|
|
var onErrorWins = false;
|
|
|
|
lock (gate)
|
|
{
|
|
onErrorWins = !switched;
|
|
if (onErrorWins)
|
|
{
|
|
id = unchecked(id + 1);
|
|
}
|
|
}
|
|
|
|
if (onErrorWins)
|
|
observer.OnError(exception);
|
|
},
|
|
() =>
|
|
{
|
|
var onCompletedWins = false;
|
|
|
|
lock (gate)
|
|
{
|
|
onCompletedWins = !switched;
|
|
if (onCompletedWins)
|
|
{
|
|
id = unchecked(id + 1);
|
|
}
|
|
}
|
|
|
|
if (onCompletedWins)
|
|
observer.OnCompleted();
|
|
});
|
|
|
|
return new CompositeDisposable(subscription, timer);
|
|
});
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region DateTimeOffset
|
|
|
|
public virtual IObservable<TSource> Timeout<TSource>(IObservable<TSource> source, DateTimeOffset dueTime)
|
|
{
|
|
return Timeout_<TSource>(source, dueTime, Observable.Throw<TSource>(new TimeoutException()), SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<TSource> Timeout<TSource>(IObservable<TSource> source, DateTimeOffset dueTime, IScheduler scheduler)
|
|
{
|
|
return Timeout_<TSource>(source, dueTime, Observable.Throw<TSource>(new TimeoutException()), scheduler);
|
|
}
|
|
|
|
public virtual IObservable<TSource> Timeout<TSource>(IObservable<TSource> source, DateTimeOffset dueTime, IObservable<TSource> other)
|
|
{
|
|
return Timeout_<TSource>(source, dueTime, other, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<TSource> Timeout<TSource>(IObservable<TSource> source, DateTimeOffset dueTime, IObservable<TSource> other, IScheduler scheduler)
|
|
{
|
|
return Timeout_<TSource>(source, dueTime, other, scheduler);
|
|
}
|
|
|
|
private static IObservable<TSource> Timeout_<TSource>(IObservable<TSource> source, DateTimeOffset dueTime, IObservable<TSource> other, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
return new Timeout<TSource>(source, dueTime, other, scheduler);
|
|
#else
|
|
return new AnonymousObservable<TSource>(observer =>
|
|
{
|
|
var subscription = new SerialDisposable();
|
|
var original = new SingleAssignmentDisposable();
|
|
|
|
subscription.Disposable = original;
|
|
|
|
var gate = new object();
|
|
var switched = false;
|
|
|
|
var timer = scheduler.Schedule(dueTime, () =>
|
|
{
|
|
var timerWins = false;
|
|
|
|
lock (gate)
|
|
{
|
|
timerWins = !switched;
|
|
switched = true;
|
|
}
|
|
|
|
if (timerWins)
|
|
subscription.Disposable = other.Subscribe(observer);
|
|
});
|
|
|
|
original.Disposable = source.Subscribe(
|
|
x =>
|
|
{
|
|
lock (gate)
|
|
{
|
|
if (!switched)
|
|
observer.OnNext(x);
|
|
}
|
|
},
|
|
exception =>
|
|
{
|
|
var onErrorWins = false;
|
|
|
|
lock (gate)
|
|
{
|
|
onErrorWins = !switched;
|
|
switched = true;
|
|
}
|
|
|
|
if (onErrorWins)
|
|
observer.OnError(exception);
|
|
},
|
|
() =>
|
|
{
|
|
var onCompletedWins = false;
|
|
|
|
lock (gate)
|
|
{
|
|
onCompletedWins = !switched;
|
|
switched = true;
|
|
}
|
|
|
|
if (onCompletedWins)
|
|
observer.OnCompleted();
|
|
});
|
|
|
|
return new CompositeDisposable(subscription, timer);
|
|
});
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Duration selector
|
|
|
|
public virtual IObservable<TSource> Timeout<TSource, TTimeout>(IObservable<TSource> source, Func<TSource, IObservable<TTimeout>> timeoutDurationSelector)
|
|
{
|
|
return Timeout_<TSource, TTimeout>(source, Observable.Never<TTimeout>(), timeoutDurationSelector, Observable.Throw<TSource>(new TimeoutException()));
|
|
}
|
|
|
|
public virtual IObservable<TSource> Timeout<TSource, TTimeout>(IObservable<TSource> source, Func<TSource, IObservable<TTimeout>> timeoutDurationSelector, IObservable<TSource> other)
|
|
{
|
|
return Timeout_<TSource, TTimeout>(source, Observable.Never<TTimeout>(), timeoutDurationSelector, other);
|
|
}
|
|
|
|
public virtual IObservable<TSource> Timeout<TSource, TTimeout>(IObservable<TSource> source, IObservable<TTimeout> firstTimeout, Func<TSource, IObservable<TTimeout>> timeoutDurationSelector)
|
|
{
|
|
return Timeout_<TSource, TTimeout>(source, firstTimeout, timeoutDurationSelector, Observable.Throw<TSource>(new TimeoutException()));
|
|
}
|
|
|
|
public virtual IObservable<TSource> Timeout<TSource, TTimeout>(IObservable<TSource> source, IObservable<TTimeout> firstTimeout, Func<TSource, IObservable<TTimeout>> timeoutDurationSelector, IObservable<TSource> other)
|
|
{
|
|
return Timeout_<TSource, TTimeout>(source, firstTimeout, timeoutDurationSelector, other);
|
|
}
|
|
|
|
private static IObservable<TSource> Timeout_<TSource, TTimeout>(IObservable<TSource> source, IObservable<TTimeout> firstTimeout, Func<TSource, IObservable<TTimeout>> timeoutDurationSelector, IObservable<TSource> other)
|
|
{
|
|
#if !NO_PERF
|
|
return new Timeout<TSource, TTimeout>(source, firstTimeout, timeoutDurationSelector, other);
|
|
#else
|
|
return new AnonymousObservable<TSource>(observer =>
|
|
{
|
|
var subscription = new SerialDisposable();
|
|
var timer = new SerialDisposable();
|
|
var original = new SingleAssignmentDisposable();
|
|
|
|
subscription.Disposable = original;
|
|
|
|
var gate = new object();
|
|
var id = 0UL;
|
|
var switched = false;
|
|
|
|
Action<IObservable<TTimeout>> setTimer = timeout =>
|
|
{
|
|
var myid = id;
|
|
|
|
Func<bool> timerWins = () =>
|
|
{
|
|
var res = false;
|
|
|
|
lock (gate)
|
|
{
|
|
switched = (id == myid);
|
|
res = switched;
|
|
}
|
|
|
|
return res;
|
|
};
|
|
|
|
var d = new SingleAssignmentDisposable();
|
|
timer.Disposable = d;
|
|
d.Disposable = timeout.Subscribe(
|
|
_ =>
|
|
{
|
|
if (timerWins())
|
|
subscription.Disposable = other.Subscribe(observer);
|
|
|
|
d.Dispose();
|
|
},
|
|
error =>
|
|
{
|
|
if (timerWins())
|
|
observer.OnError(error);
|
|
},
|
|
() =>
|
|
{
|
|
if (timerWins())
|
|
subscription.Disposable = other.Subscribe(observer);
|
|
}
|
|
);
|
|
};
|
|
|
|
setTimer(firstTimeout);
|
|
|
|
Func<bool> observerWins = () =>
|
|
{
|
|
var res = false;
|
|
|
|
lock (gate)
|
|
{
|
|
res = !switched;
|
|
if (res)
|
|
{
|
|
id = unchecked(id + 1);
|
|
}
|
|
}
|
|
|
|
return res;
|
|
};
|
|
|
|
original.Disposable = source.Subscribe(
|
|
x =>
|
|
{
|
|
if (observerWins())
|
|
{
|
|
observer.OnNext(x);
|
|
|
|
var timeout = default(IObservable<TTimeout>);
|
|
try
|
|
{
|
|
timeout = timeoutDurationSelector(x);
|
|
}
|
|
catch (Exception error)
|
|
{
|
|
observer.OnError(error);
|
|
return;
|
|
}
|
|
|
|
setTimer(timeout);
|
|
}
|
|
},
|
|
exception =>
|
|
{
|
|
if (observerWins())
|
|
observer.OnError(exception);
|
|
},
|
|
() =>
|
|
{
|
|
if (observerWins())
|
|
observer.OnCompleted();
|
|
}
|
|
);
|
|
|
|
return new CompositeDisposable(subscription, timer);
|
|
});
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
#region + Timer +
|
|
|
|
public virtual IObservable<long> Timer(TimeSpan dueTime)
|
|
{
|
|
return Timer_(dueTime, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<long> Timer(DateTimeOffset dueTime)
|
|
{
|
|
return Timer_(dueTime, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<long> Timer(TimeSpan dueTime, TimeSpan period)
|
|
{
|
|
return Timer_(dueTime, period, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<long> Timer(DateTimeOffset dueTime, TimeSpan period)
|
|
{
|
|
return Timer_(dueTime, period, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<long> Timer(TimeSpan dueTime, IScheduler scheduler)
|
|
{
|
|
return Timer_(dueTime, scheduler);
|
|
}
|
|
|
|
public virtual IObservable<long> Timer(DateTimeOffset dueTime, IScheduler scheduler)
|
|
{
|
|
return Timer_(dueTime, scheduler);
|
|
}
|
|
|
|
public virtual IObservable<long> Timer(TimeSpan dueTime, TimeSpan period, IScheduler scheduler)
|
|
{
|
|
return Timer_(dueTime, period, scheduler);
|
|
}
|
|
|
|
public virtual IObservable<long> Timer(DateTimeOffset dueTime, TimeSpan period, IScheduler scheduler)
|
|
{
|
|
return Timer_(dueTime, period, scheduler);
|
|
}
|
|
|
|
private static IObservable<long> Timer_(TimeSpan dueTime, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
return new Timer(dueTime, null, scheduler);
|
|
#else
|
|
var d = Normalize(dueTime);
|
|
|
|
return new AnonymousObservable<long>(observer =>
|
|
scheduler.Schedule(d, () =>
|
|
{
|
|
observer.OnNext(0);
|
|
observer.OnCompleted();
|
|
}));
|
|
#endif
|
|
}
|
|
|
|
#if !NO_PERF
|
|
private static IObservable<long> Timer_(TimeSpan dueTime, TimeSpan period, IScheduler scheduler)
|
|
{
|
|
return new Timer(dueTime, period, scheduler);
|
|
}
|
|
#else
|
|
private IObservable<long> Timer_(TimeSpan dueTime, TimeSpan period, IScheduler scheduler)
|
|
{
|
|
var p = Normalize(period);
|
|
|
|
return Defer(() => Timer(scheduler.Now + dueTime, p, scheduler));
|
|
}
|
|
#endif
|
|
|
|
private static IObservable<long> Timer_(DateTimeOffset dueTime, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
return new Timer(dueTime, null, scheduler);
|
|
#else
|
|
return new AnonymousObservable<long>(observer =>
|
|
scheduler.Schedule(dueTime, () =>
|
|
{
|
|
observer.OnNext(0);
|
|
observer.OnCompleted();
|
|
}));
|
|
#endif
|
|
}
|
|
|
|
private static IObservable<long> Timer_(DateTimeOffset dueTime, TimeSpan period, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
return new Timer(dueTime, period, scheduler);
|
|
#else
|
|
var p = Normalize(period);
|
|
|
|
return new AnonymousObservable<long>(observer =>
|
|
{
|
|
var d = dueTime;
|
|
var count = 0L;
|
|
|
|
return scheduler.Schedule(d, self =>
|
|
{
|
|
if (p > TimeSpan.Zero)
|
|
{
|
|
var now = scheduler.Now;
|
|
d = d + p;
|
|
if (d <= now)
|
|
d = now + p;
|
|
}
|
|
|
|
observer.OnNext(count);
|
|
count = unchecked(count + 1);
|
|
self(d);
|
|
});
|
|
});
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region + Timestamp +
|
|
|
|
public virtual IObservable<Timestamped<TSource>> Timestamp<TSource>(IObservable<TSource> source)
|
|
{
|
|
return Timestamp_<TSource>(source, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<Timestamped<TSource>> Timestamp<TSource>(IObservable<TSource> source, IScheduler scheduler)
|
|
{
|
|
return Timestamp_<TSource>(source, scheduler);
|
|
}
|
|
|
|
private static IObservable<Timestamped<TSource>> Timestamp_<TSource>(IObservable<TSource> source, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
return new Timestamp<TSource>(source, scheduler);
|
|
#else
|
|
return source.Select(x => new Timestamped<TSource>(x, scheduler.Now));
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region + Window +
|
|
|
|
#region TimeSpan only
|
|
|
|
public virtual IObservable<IObservable<TSource>> Window<TSource>(IObservable<TSource> source, TimeSpan timeSpan)
|
|
{
|
|
return Window_<TSource>(source, timeSpan, timeSpan, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<IObservable<TSource>> Window<TSource>(IObservable<TSource> source, TimeSpan timeSpan, IScheduler scheduler)
|
|
{
|
|
return Window_<TSource>(source, timeSpan, timeSpan, scheduler);
|
|
}
|
|
|
|
public virtual IObservable<IObservable<TSource>> Window<TSource>(IObservable<TSource> source, TimeSpan timeSpan, TimeSpan timeShift)
|
|
{
|
|
return Window_<TSource>(source, timeSpan, timeShift, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<IObservable<TSource>> Window<TSource>(IObservable<TSource> source, TimeSpan timeSpan, TimeSpan timeShift, IScheduler scheduler)
|
|
{
|
|
return Window_<TSource>(source, timeSpan, timeShift, scheduler);
|
|
}
|
|
|
|
private static IObservable<IObservable<TSource>> Window_<TSource>(IObservable<TSource> source, TimeSpan timeSpan, TimeSpan timeShift, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
return new Window<TSource>(source, timeSpan, timeShift, scheduler);
|
|
#else
|
|
return new AnonymousObservable<IObservable<TSource>>(observer =>
|
|
{
|
|
var totalTime = TimeSpan.Zero;
|
|
var nextShift = timeShift;
|
|
var nextSpan = timeSpan;
|
|
|
|
var gate = new object();
|
|
var q = new Queue<ISubject<TSource>>();
|
|
|
|
var timerD = new SerialDisposable();
|
|
var groupDisposable = new CompositeDisposable(2) { timerD };
|
|
var refCountDisposable = new RefCountDisposable(groupDisposable);
|
|
|
|
var createTimer = default(Action);
|
|
createTimer = () =>
|
|
{
|
|
var m = new SingleAssignmentDisposable();
|
|
timerD.Disposable = m;
|
|
|
|
var isSpan = false;
|
|
var isShift = false;
|
|
if (nextSpan == nextShift)
|
|
{
|
|
isSpan = true;
|
|
isShift = true;
|
|
}
|
|
else if (nextSpan < nextShift)
|
|
isSpan = true;
|
|
else
|
|
isShift = true;
|
|
|
|
var newTotalTime = isSpan ? nextSpan : nextShift;
|
|
var ts = newTotalTime - totalTime;
|
|
totalTime = newTotalTime;
|
|
|
|
if (isSpan)
|
|
nextSpan += timeShift;
|
|
if (isShift)
|
|
nextShift += timeShift;
|
|
|
|
m.Disposable = scheduler.Schedule(ts, () =>
|
|
{
|
|
lock (gate)
|
|
{
|
|
if (isShift)
|
|
{
|
|
var s = new Subject<TSource>();
|
|
q.Enqueue(s);
|
|
observer.OnNext(s.AddRef(refCountDisposable));
|
|
}
|
|
if (isSpan)
|
|
{
|
|
var s = q.Dequeue();
|
|
s.OnCompleted();
|
|
}
|
|
}
|
|
|
|
createTimer();
|
|
});
|
|
};
|
|
|
|
q.Enqueue(new Subject<TSource>());
|
|
observer.OnNext(q.Peek().AddRef(refCountDisposable));
|
|
createTimer();
|
|
|
|
groupDisposable.Add(source.Subscribe(
|
|
x =>
|
|
{
|
|
lock (gate)
|
|
{
|
|
foreach (var s in q)
|
|
s.OnNext(x);
|
|
}
|
|
},
|
|
exception =>
|
|
{
|
|
lock (gate)
|
|
{
|
|
foreach (var s in q)
|
|
s.OnError(exception);
|
|
observer.OnError(exception);
|
|
}
|
|
},
|
|
() =>
|
|
{
|
|
lock (gate)
|
|
{
|
|
foreach (var s in q)
|
|
s.OnCompleted();
|
|
observer.OnCompleted();
|
|
}
|
|
}
|
|
));
|
|
|
|
return refCountDisposable;
|
|
});
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region TimeSpan + int
|
|
|
|
public virtual IObservable<IObservable<TSource>> Window<TSource>(IObservable<TSource> source, TimeSpan timeSpan, int count)
|
|
{
|
|
return Window_<TSource>(source, timeSpan, count, SchedulerDefaults.TimeBasedOperations);
|
|
}
|
|
|
|
public virtual IObservable<IObservable<TSource>> Window<TSource>(IObservable<TSource> source, TimeSpan timeSpan, int count, IScheduler scheduler)
|
|
{
|
|
return Window_<TSource>(source, timeSpan, count, scheduler);
|
|
}
|
|
|
|
private static IObservable<IObservable<TSource>> Window_<TSource>(IObservable<TSource> source, TimeSpan timeSpan, int count, IScheduler scheduler)
|
|
{
|
|
#if !NO_PERF
|
|
return new Window<TSource>(source, timeSpan, count, scheduler);
|
|
#else
|
|
return new AnonymousObservable<IObservable<TSource>>(observer =>
|
|
{
|
|
var gate = new object();
|
|
var s = default(ISubject<TSource>);
|
|
var n = 0;
|
|
var windowId = 0;
|
|
|
|
var timerD = new SerialDisposable();
|
|
var groupDisposable = new CompositeDisposable(2) { timerD };
|
|
var refCountDisposable = new RefCountDisposable(groupDisposable);
|
|
|
|
var createTimer = default(Action<int>);
|
|
createTimer = id =>
|
|
{
|
|
var m = new SingleAssignmentDisposable();
|
|
timerD.Disposable = m;
|
|
|
|
m.Disposable = scheduler.Schedule(timeSpan, () =>
|
|
{
|
|
var newId = 0;
|
|
lock (gate)
|
|
{
|
|
if (id != windowId)
|
|
return;
|
|
n = 0;
|
|
newId = ++windowId;
|
|
|
|
s.OnCompleted();
|
|
s = new Subject<TSource>();
|
|
observer.OnNext(s.AddRef(refCountDisposable));
|
|
}
|
|
|
|
createTimer(newId);
|
|
});
|
|
};
|
|
|
|
s = new Subject<TSource>();
|
|
observer.OnNext(s.AddRef(refCountDisposable));
|
|
createTimer(0);
|
|
|
|
groupDisposable.Add(source.Subscribe(
|
|
x =>
|
|
{
|
|
var newWindow = false;
|
|
var newId = 0;
|
|
|
|
lock (gate)
|
|
{
|
|
s.OnNext(x);
|
|
|
|
n++;
|
|
if (n == count)
|
|
{
|
|
newWindow = true;
|
|
n = 0;
|
|
newId = ++windowId;
|
|
|
|
s.OnCompleted();
|
|
s = new Subject<TSource>();
|
|
observer.OnNext(s.AddRef(refCountDisposable));
|
|
}
|
|
}
|
|
|
|
if (newWindow)
|
|
createTimer(newId);
|
|
},
|
|
exception =>
|
|
{
|
|
lock (gate)
|
|
{
|
|
s.OnError(exception);
|
|
observer.OnError(exception);
|
|
}
|
|
},
|
|
() =>
|
|
{
|
|
lock (gate)
|
|
{
|
|
s.OnCompleted();
|
|
observer.OnCompleted();
|
|
}
|
|
}
|
|
));
|
|
|
|
return refCountDisposable;
|
|
});
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
#region |> Helpers <|
|
|
|
|
#if NO_PERF
|
|
private static TimeSpan Normalize(TimeSpan timeSpan)
|
|
{
|
|
if (timeSpan.CompareTo(TimeSpan.Zero) < 0)
|
|
return TimeSpan.Zero;
|
|
return timeSpan;
|
|
}
|
|
#endif
|
|
|
|
#endregion
|
|
}
|
|
}
|