// 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> Buffer(IObservable source, TimeSpan timeSpan) { return Buffer_(source, timeSpan, timeSpan, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable> Buffer(IObservable source, TimeSpan timeSpan, IScheduler scheduler) { return Buffer_(source, timeSpan, timeSpan, scheduler); } public virtual IObservable> Buffer(IObservable source, TimeSpan timeSpan, TimeSpan timeShift) { return Buffer_(source, timeSpan, timeShift, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable> Buffer(IObservable source, TimeSpan timeSpan, TimeSpan timeShift, IScheduler scheduler) { return Buffer_(source, timeSpan, timeShift, scheduler); } private static IObservable> Buffer_(IObservable source, TimeSpan timeSpan, TimeSpan timeShift, IScheduler scheduler) { #if !NO_PERF return new Buffer(source, timeSpan, timeShift, scheduler); #else return source.Window(timeSpan, timeShift, scheduler).SelectMany(Observable.ToList); #endif } #endregion #region TimeSpan + int public virtual IObservable> Buffer(IObservable source, TimeSpan timeSpan, int count) { return Buffer_(source, timeSpan, count, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable> Buffer(IObservable source, TimeSpan timeSpan, int count, IScheduler scheduler) { return Buffer_(source, timeSpan, count, scheduler); } private static IObservable> Buffer_(IObservable source, TimeSpan timeSpan, int count, IScheduler scheduler) { #if !NO_PERF return new Buffer(source, timeSpan, count, scheduler); #else return source.Window(timeSpan, count, scheduler).SelectMany(Observable.ToList); #endif } #endregion #endregion #region + Delay + #region TimeSpan public virtual IObservable Delay(IObservable source, TimeSpan dueTime) { return Delay_(source, dueTime, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable Delay(IObservable source, TimeSpan dueTime, IScheduler scheduler) { return Delay_(source, dueTime, scheduler); } private static IObservable Delay_(IObservable source, TimeSpan dueTime, IScheduler scheduler) { #if !NO_PERF return new Delay(source, dueTime, scheduler); #else return new AnonymousObservable(observer => { var gate = new object(); var q = new Queue>>(); 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.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 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 Delay(IObservable source, DateTimeOffset dueTime) { return Delay_(source, dueTime, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable Delay(IObservable source, DateTimeOffset dueTime, IScheduler scheduler) { return Delay_(source, dueTime, scheduler); } private static IObservable Delay_(IObservable source, DateTimeOffset dueTime, IScheduler scheduler) { #if !NO_PERF return new Delay(source, dueTime, scheduler); #else return Observable.Defer(() => { var timeSpan = dueTime.Subtract(scheduler.Now); return Delay_(source, timeSpan, scheduler); }); #endif } #endregion #region Duration selector public virtual IObservable Delay(IObservable source, Func> delayDurationSelector) { return Delay_(source, null, delayDurationSelector); } public virtual IObservable Delay(IObservable source, IObservable subscriptionDelay, Func> delayDurationSelector) { return Delay_(source, subscriptionDelay, delayDurationSelector); } private static IObservable Delay_(IObservable source, IObservable subscriptionDelay, Func> delayDurationSelector) { #if !NO_PERF return new Delay(source, subscriptionDelay, delayDurationSelector); #else return new AnonymousObservable(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); 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 DelaySubscription(IObservable source, TimeSpan dueTime) { return DelaySubscription_(source, dueTime, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable DelaySubscription(IObservable source, TimeSpan dueTime, IScheduler scheduler) { return DelaySubscription_(source, dueTime, scheduler); } private static IObservable DelaySubscription_(IObservable source, TimeSpan dueTime, IScheduler scheduler) { #if !NO_PERF return new DelaySubscription(source, dueTime, scheduler); #else return new AnonymousObservable(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 DelaySubscription(IObservable source, DateTimeOffset dueTime) { return DelaySubscription_(source, dueTime, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable DelaySubscription(IObservable source, DateTimeOffset dueTime, IScheduler scheduler) { return DelaySubscription_(source, dueTime, scheduler); } private static IObservable DelaySubscription_(IObservable source, DateTimeOffset dueTime, IScheduler scheduler) { #if !NO_PERF return new DelaySubscription(source, dueTime, scheduler); #else return new AnonymousObservable(observer => { var d = new MultipleAssignmentDisposable(); d.Disposable = scheduler.Schedule(dueTime, () => { d.Disposable = source.Subscribe(observer); }); return d; }); #endif } #endregion #region + Generate + public virtual IObservable Generate(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector) { return Generate_(initialState, condition, iterate, resultSelector, timeSelector, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable Generate(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector, IScheduler scheduler) { return Generate_(initialState, condition, iterate, resultSelector, timeSelector, scheduler); } private static IObservable Generate_(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector, IScheduler scheduler) { #if !NO_PERF return new Generate(initialState, condition, iterate, resultSelector, timeSelector, scheduler); #else return new AnonymousObservable(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 Generate(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector) { return Generate_(initialState, condition, iterate, resultSelector, timeSelector, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable Generate(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector, IScheduler scheduler) { return Generate_(initialState, condition, iterate, resultSelector, timeSelector, scheduler); } private static IObservable Generate_(TState initialState, Func condition, Func iterate, Func resultSelector, Func timeSelector, IScheduler scheduler) { #if !NO_PERF return new Generate(initialState, condition, iterate, resultSelector, timeSelector, scheduler); #else return new AnonymousObservable(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 Interval(TimeSpan period) { return Timer_(period, period, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable Interval(TimeSpan period, IScheduler scheduler) { return Timer_(period, period, scheduler); } #endregion #region + Sample + public virtual IObservable Sample(IObservable source, TimeSpan interval) { return Sample_(source, interval, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable Sample(IObservable source, TimeSpan interval, IScheduler scheduler) { return Sample_(source, interval, scheduler); } private static IObservable Sample_(IObservable source, TimeSpan interval, IScheduler scheduler) { #if !NO_PERF return new Sample(source, interval, scheduler); #else var sampler = Observable.Interval(interval, scheduler); return Sample_(source, sampler); #endif } public virtual IObservable Sample(IObservable source, IObservable sampler) { return Sample_(source, sampler); } private static IObservable Sample_(IObservable source, IObservable sampler) { #if !NO_PERF return new Sample(source, sampler); #else return Combine(source, sampler, (IObserver observer, IDisposable leftSubscription, IDisposable rightSubscription) => { var value = default(Notification); var atEnd = false; return new BinaryObserver( 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 Skip(IObservable source, TimeSpan duration) { return Skip_(source, duration, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable Skip(IObservable source, TimeSpan duration, IScheduler scheduler) { return Skip_(source, duration, scheduler); } private static IObservable Skip_(IObservable source, TimeSpan duration, IScheduler scheduler) { #if !NO_PERF var skip = source as Skip; if (skip != null && skip._scheduler == scheduler) return skip.Omega(duration); return new Skip(source, duration, scheduler); #else return new AnonymousObservable(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 SkipLast(IObservable source, TimeSpan duration) { return SkipLast_(source, duration, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable SkipLast(IObservable source, TimeSpan duration, IScheduler scheduler) { return SkipLast_(source, duration, scheduler); } private static IObservable SkipLast_(IObservable source, TimeSpan duration, IScheduler scheduler) { #if !NO_PERF return new SkipLast(source, duration, scheduler); #else return new AnonymousObservable(observer => { var q = new Queue>(); 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(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 SkipUntil(IObservable source, DateTimeOffset startTime) { return SkipUntil_(source, startTime, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable SkipUntil(IObservable source, DateTimeOffset startTime, IScheduler scheduler) { return SkipUntil_(source, startTime, scheduler); } private static IObservable SkipUntil_(IObservable source, DateTimeOffset startTime, IScheduler scheduler) { #if !NO_PERF var skipUntil = source as SkipUntil; if (skipUntil != null && skipUntil._scheduler == scheduler) return skipUntil.Omega(startTime); return new SkipUntil(source, startTime, scheduler); #else return new AnonymousObservable(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 Take(IObservable source, TimeSpan duration) { return Take_(source, duration, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable Take(IObservable source, TimeSpan duration, IScheduler scheduler) { return Take_(source, duration, scheduler); } private static IObservable Take_(IObservable source, TimeSpan duration, IScheduler scheduler) { #if !NO_PERF var take = source as Take; if (take != null && take._scheduler == scheduler) return take.Omega(duration); return new Take(source, duration, scheduler); #else return new AnonymousObservable(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 TakeLast(IObservable source, TimeSpan duration) { return TakeLast_(source, duration, SchedulerDefaults.TimeBasedOperations, SchedulerDefaults.Iteration); } public virtual IObservable TakeLast(IObservable source, TimeSpan duration, IScheduler scheduler) { return TakeLast_(source, duration, scheduler, SchedulerDefaults.Iteration); } public virtual IObservable TakeLast(IObservable source, TimeSpan duration, IScheduler timerScheduler, IScheduler loopScheduler) { return TakeLast_(source, duration, timerScheduler, loopScheduler); } private static IObservable TakeLast_(IObservable source, TimeSpan duration, IScheduler timerScheduler, IScheduler loopScheduler) { #if !NO_PERF return new TakeLast(source, duration, timerScheduler, loopScheduler); #else return new AnonymousObservable(observer => { var q = new Queue>(); var swp = timerScheduler.AsStopwatchProvider(); var sw = swp != null ? swp.StartStopwatch() : new DefaultStopwatch(); var trim = new Action(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(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> TakeLastBuffer(IObservable source, TimeSpan duration) { return TakeLastBuffer_(source, duration, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable> TakeLastBuffer(IObservable source, TimeSpan duration, IScheduler scheduler) { return TakeLastBuffer_(source, duration, scheduler); } private static IObservable> TakeLastBuffer_(IObservable source, TimeSpan duration, IScheduler scheduler) { #if !NO_PERF return new TakeLastBuffer(source, duration, scheduler); #else return new AnonymousObservable>(observer => { var q = new Queue>(); 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(x, now)); while (q.Count > 0 && now - q.Peek().Interval >= duration) q.Dequeue(); }, observer.OnError, () => { var now = sw.Elapsed; var res = new List(); 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 TakeUntil(IObservable source, DateTimeOffset endTime) { return TakeUntil_(source, endTime, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable TakeUntil(IObservable source, DateTimeOffset endTime, IScheduler scheduler) { return TakeUntil_(source, endTime, scheduler); } private static IObservable TakeUntil_(IObservable source, DateTimeOffset endTime, IScheduler scheduler) { #if !NO_PERF var takeUntil = source as TakeUntil; if (takeUntil != null && takeUntil._scheduler == scheduler) return takeUntil.Omega(endTime); return new TakeUntil(source, endTime, scheduler); #else return new AnonymousObservable(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 Throttle(IObservable source, TimeSpan dueTime) { return Throttle_(source, dueTime, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable Throttle(IObservable source, TimeSpan dueTime, IScheduler scheduler) { return Throttle_(source, dueTime, scheduler); } private static IObservable Throttle_(IObservable source, TimeSpan dueTime, IScheduler scheduler) { #if !NO_PERF return new Throttle(source, dueTime, scheduler); #else return new AnonymousObservable(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 Throttle(IObservable source, Func> throttleDurationSelector) { #if !NO_PERF return new Throttle(source, throttleDurationSelector); #else return new AnonymousObservable(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); 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> TimeInterval(IObservable source) { return TimeInterval_(source, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable> TimeInterval(IObservable source, IScheduler scheduler) { return TimeInterval_(source, scheduler); } #if !NO_PERF private static IObservable> TimeInterval_(IObservable source, IScheduler scheduler) { return new TimeInterval(source, scheduler); } #else private IObservable> TimeInterval_(IObservable 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(x, span); }); }); } #endif #endregion #region + Timeout + #region TimeSpan public virtual IObservable Timeout(IObservable source, TimeSpan dueTime) { return Timeout_(source, dueTime, Observable.Throw(new TimeoutException()), SchedulerDefaults.TimeBasedOperations); } public virtual IObservable Timeout(IObservable source, TimeSpan dueTime, IScheduler scheduler) { return Timeout_(source, dueTime, Observable.Throw(new TimeoutException()), scheduler); } public virtual IObservable Timeout(IObservable source, TimeSpan dueTime, IObservable other) { return Timeout_(source, dueTime, other, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable Timeout(IObservable source, TimeSpan dueTime, IObservable other, IScheduler scheduler) { return Timeout_(source, dueTime, other, scheduler); } private static IObservable Timeout_(IObservable source, TimeSpan dueTime, IObservable other, IScheduler scheduler) { #if !NO_PERF return new Timeout(source, dueTime, other, scheduler); #else return new AnonymousObservable(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 Timeout(IObservable source, DateTimeOffset dueTime) { return Timeout_(source, dueTime, Observable.Throw(new TimeoutException()), SchedulerDefaults.TimeBasedOperations); } public virtual IObservable Timeout(IObservable source, DateTimeOffset dueTime, IScheduler scheduler) { return Timeout_(source, dueTime, Observable.Throw(new TimeoutException()), scheduler); } public virtual IObservable Timeout(IObservable source, DateTimeOffset dueTime, IObservable other) { return Timeout_(source, dueTime, other, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable Timeout(IObservable source, DateTimeOffset dueTime, IObservable other, IScheduler scheduler) { return Timeout_(source, dueTime, other, scheduler); } private static IObservable Timeout_(IObservable source, DateTimeOffset dueTime, IObservable other, IScheduler scheduler) { #if !NO_PERF return new Timeout(source, dueTime, other, scheduler); #else return new AnonymousObservable(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 Timeout(IObservable source, Func> timeoutDurationSelector) { return Timeout_(source, Observable.Never(), timeoutDurationSelector, Observable.Throw(new TimeoutException())); } public virtual IObservable Timeout(IObservable source, Func> timeoutDurationSelector, IObservable other) { return Timeout_(source, Observable.Never(), timeoutDurationSelector, other); } public virtual IObservable Timeout(IObservable source, IObservable firstTimeout, Func> timeoutDurationSelector) { return Timeout_(source, firstTimeout, timeoutDurationSelector, Observable.Throw(new TimeoutException())); } public virtual IObservable Timeout(IObservable source, IObservable firstTimeout, Func> timeoutDurationSelector, IObservable other) { return Timeout_(source, firstTimeout, timeoutDurationSelector, other); } private static IObservable Timeout_(IObservable source, IObservable firstTimeout, Func> timeoutDurationSelector, IObservable other) { #if !NO_PERF return new Timeout(source, firstTimeout, timeoutDurationSelector, other); #else return new AnonymousObservable(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> setTimer = timeout => { var myid = id; Func 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 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); 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 Timer(TimeSpan dueTime) { return Timer_(dueTime, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable Timer(DateTimeOffset dueTime) { return Timer_(dueTime, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable Timer(TimeSpan dueTime, TimeSpan period) { return Timer_(dueTime, period, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable Timer(DateTimeOffset dueTime, TimeSpan period) { return Timer_(dueTime, period, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable Timer(TimeSpan dueTime, IScheduler scheduler) { return Timer_(dueTime, scheduler); } public virtual IObservable Timer(DateTimeOffset dueTime, IScheduler scheduler) { return Timer_(dueTime, scheduler); } public virtual IObservable Timer(TimeSpan dueTime, TimeSpan period, IScheduler scheduler) { return Timer_(dueTime, period, scheduler); } public virtual IObservable Timer(DateTimeOffset dueTime, TimeSpan period, IScheduler scheduler) { return Timer_(dueTime, period, scheduler); } private static IObservable Timer_(TimeSpan dueTime, IScheduler scheduler) { #if !NO_PERF return new Timer(dueTime, null, scheduler); #else var d = Normalize(dueTime); return new AnonymousObservable(observer => scheduler.Schedule(d, () => { observer.OnNext(0); observer.OnCompleted(); })); #endif } #if !NO_PERF private static IObservable Timer_(TimeSpan dueTime, TimeSpan period, IScheduler scheduler) { return new Timer(dueTime, period, scheduler); } #else private IObservable Timer_(TimeSpan dueTime, TimeSpan period, IScheduler scheduler) { var p = Normalize(period); return Defer(() => Timer(scheduler.Now + dueTime, p, scheduler)); } #endif private static IObservable Timer_(DateTimeOffset dueTime, IScheduler scheduler) { #if !NO_PERF return new Timer(dueTime, null, scheduler); #else return new AnonymousObservable(observer => scheduler.Schedule(dueTime, () => { observer.OnNext(0); observer.OnCompleted(); })); #endif } private static IObservable Timer_(DateTimeOffset dueTime, TimeSpan period, IScheduler scheduler) { #if !NO_PERF return new Timer(dueTime, period, scheduler); #else var p = Normalize(period); return new AnonymousObservable(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> Timestamp(IObservable source) { return Timestamp_(source, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable> Timestamp(IObservable source, IScheduler scheduler) { return Timestamp_(source, scheduler); } private static IObservable> Timestamp_(IObservable source, IScheduler scheduler) { #if !NO_PERF return new Timestamp(source, scheduler); #else return source.Select(x => new Timestamped(x, scheduler.Now)); #endif } #endregion #region + Window + #region TimeSpan only public virtual IObservable> Window(IObservable source, TimeSpan timeSpan) { return Window_(source, timeSpan, timeSpan, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable> Window(IObservable source, TimeSpan timeSpan, IScheduler scheduler) { return Window_(source, timeSpan, timeSpan, scheduler); } public virtual IObservable> Window(IObservable source, TimeSpan timeSpan, TimeSpan timeShift) { return Window_(source, timeSpan, timeShift, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable> Window(IObservable source, TimeSpan timeSpan, TimeSpan timeShift, IScheduler scheduler) { return Window_(source, timeSpan, timeShift, scheduler); } private static IObservable> Window_(IObservable source, TimeSpan timeSpan, TimeSpan timeShift, IScheduler scheduler) { #if !NO_PERF return new Window(source, timeSpan, timeShift, scheduler); #else return new AnonymousObservable>(observer => { var totalTime = TimeSpan.Zero; var nextShift = timeShift; var nextSpan = timeSpan; var gate = new object(); var q = new Queue>(); 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(); q.Enqueue(s); observer.OnNext(s.AddRef(refCountDisposable)); } if (isSpan) { var s = q.Dequeue(); s.OnCompleted(); } } createTimer(); }); }; q.Enqueue(new Subject()); 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> Window(IObservable source, TimeSpan timeSpan, int count) { return Window_(source, timeSpan, count, SchedulerDefaults.TimeBasedOperations); } public virtual IObservable> Window(IObservable source, TimeSpan timeSpan, int count, IScheduler scheduler) { return Window_(source, timeSpan, count, scheduler); } private static IObservable> Window_(IObservable source, TimeSpan timeSpan, int count, IScheduler scheduler) { #if !NO_PERF return new Window(source, timeSpan, count, scheduler); #else return new AnonymousObservable>(observer => { var gate = new object(); var s = default(ISubject); 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); 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(); observer.OnNext(s.AddRef(refCountDisposable)); } createTimer(newId); }); }; s = new Subject(); 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(); 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 } }