public static IObservable <IList <TSource> > CombineLatest <TSource>(params IObservable <TSource>[] sources) { // this code is borrwed from RxOfficial(rx.codeplex.com) return(Observable.Create <IList <TSource> >(observer => { var srcs = sources.ToArray(); var N = srcs.Length; var hasValue = new bool[N]; var hasValueAll = false; var values = new List <TSource>(N); for (int i = 0; i < N; i++) { values.Add(default(TSource)); } var isDone = new bool[N]; var next = new Action <int>(i => { hasValue[i] = true; if (hasValueAll || (hasValueAll = hasValue.All(x => x))) { var res = values.ToList(); observer.OnNext(res); } else if (isDone.Where((x, j) => j != i).All(x => x)) { observer.OnCompleted(); return; } }); var done = new Action <int>(i => { isDone[i] = true; if (isDone.All(x => x)) { observer.OnCompleted(); return; } }); var subscriptions = new SingleAssignmentDisposable[N]; var gate = new object(); for (int i = 0; i < N; i++) { var j = i; subscriptions[j] = new SingleAssignmentDisposable { Disposable = srcs[j].Synchronize(gate).Subscribe( x => { values[j] = x; next(j); }, observer.OnError, () => { done(j); } ) }; } return new CompositeDisposable(subscriptions); })); }
public static IObservable <T> Switch <T>(this IObservable <IObservable <T> > sources) { // this code is borrwed from RxOfficial(rx.codeplex.com) return(Observable.Create <T>(observer => { var gate = new object(); var innerSubscription = new SerialDisposable(); var isStopped = false; var latest = 0UL; var hasLatest = false; var subscription = sources.Subscribe( innerSource => { var id = default(ulong); lock (gate) { id = unchecked (++latest); hasLatest = true; } var d = new SingleAssignmentDisposable(); innerSubscription.Disposable = d; d.Disposable = innerSource.Subscribe( x => { lock (gate) { if (latest == id) { observer.OnNext(x); } } }, exception => { lock (gate) { if (latest == id) { observer.OnError(exception); } } }, () => { lock (gate) { if (latest == id) { hasLatest = false; if (isStopped) { observer.OnCompleted(); } } } }); }, exception => { lock (gate) observer.OnError(exception); }, () => { lock (gate) { isStopped = true; if (!hasLatest) { observer.OnCompleted(); } } }); return new CompositeDisposable(subscription, innerSubscription); })); }
public static IObservable <TResult> CombineLatest <TLeft, TRight, TResult>(this IObservable <TLeft> left, IObservable <TRight> right, Func <TLeft, TRight, TResult> selector) { return(Observable.Create <TResult>(observer => { var gate = new object(); var leftValue = default(TLeft); var leftStarted = false; bool leftCompleted = false; var rightValue = default(TRight); var rightStarted = false; var rightCompleted = false; Action run = () => { if ((leftCompleted && !leftStarted) || (rightCompleted && !rightStarted)) { observer.OnCompleted(); return; } else if (!(leftStarted && rightStarted)) { return; } TResult v; try { v = selector(leftValue, rightValue); } catch (Exception ex) { observer.OnError(ex); return; } observer.OnNext(v); }; var lsubscription = left.Synchronize(gate).Subscribe(x => { leftStarted = true; leftValue = x; run(); }, observer.OnError, () => { leftCompleted = true; if (rightCompleted) { observer.OnCompleted(); } }); var rsubscription = right.Synchronize(gate).Subscribe(x => { rightStarted = true; rightValue = x; run(); }, observer.OnError, () => { rightCompleted = true; if (leftCompleted) { observer.OnCompleted(); } }); return new CompositeDisposable { lsubscription, rsubscription }; })); }
static IObservable <T> ConcatCore <T>(IEnumerable <IObservable <T> > sources) { return(Observable.Create <T>(observer => { var isDisposed = false; var e = sources.AsSafeEnumerable().GetEnumerator(); var subscription = new SerialDisposable(); var gate = new object(); var schedule = Scheduler.DefaultSchedulers.TailRecursion.Schedule(self => { lock (gate) { if (isDisposed) { return; } var current = default(IObservable <T>); var hasNext = false; var ex = default(Exception); try { hasNext = e.MoveNext(); if (hasNext) { current = e.Current; if (current == null) { throw new InvalidOperationException("sequence is null."); } } else { e.Dispose(); } } catch (Exception exception) { ex = exception; e.Dispose(); } if (ex != null) { observer.OnError(ex); return; } if (!hasNext) { observer.OnCompleted(); return; } var source = e.Current; var d = new SingleAssignmentDisposable(); subscription.Disposable = d; d.Disposable = source.Subscribe(observer.OnNext, observer.OnError, self); // OnCompleted, run self } }); return new CompositeDisposable(schedule, subscription, Disposable.Create(() => { lock (gate) { isDisposed = true; e.Dispose(); } })); })); }
public static IObservable <TSource> Catch <TSource>(this IEnumerable <IObservable <TSource> > sources) { // this code is borrowed from RxOfficial(rx.codeplex.com) and modified return(Observable.Create <TSource>(observer => { var gate = new object(); var isDisposed = false; var e = sources.AsSafeEnumerable().GetEnumerator(); var subscription = new SerialDisposable(); var lastException = default(Exception); var cancelable = Scheduler.DefaultSchedulers.TailRecursion.Schedule(self => { lock (gate) { var current = default(IObservable <TSource>); var hasNext = false; var ex = default(Exception); if (!isDisposed) { try { hasNext = e.MoveNext(); if (hasNext) { current = e.Current; } else { e.Dispose(); } } catch (Exception exception) { ex = exception; e.Dispose(); } } else { return; } if (ex != null) { observer.OnError(ex); return; } if (!hasNext) { if (lastException != null) { observer.OnError(lastException); } else { observer.OnCompleted(); } return; } var d = new SingleAssignmentDisposable(); subscription.Disposable = d; d.Disposable = current.Subscribe(observer.OnNext, exception => { lastException = exception; self(); }, observer.OnCompleted); } }); return new CompositeDisposable(subscription, cancelable, Disposable.Create(() => { lock (gate) { e.Dispose(); isDisposed = true; } })); })); }
public static IObservable <IList <T> > Zip <T>(params IObservable <T>[] sources) { return(Observable.Create <IList <T> >(observer => { var gate = new object(); var length = sources.Length; var queues = new Queue <T> [length]; for (int i = 0; i < length; i++) { queues[i] = new Queue <T>(); } var isDone = new bool[length]; Action <int> dequeue = index => { lock (gate) { if (queues.All(x => x.Count > 0)) { var result = queues.Select(x => x.Dequeue()).ToList(); observer.OnNext(result); return; } if (isDone.Where((x, i) => i != index).All(x => x)) { observer.OnCompleted(); return; } } }; var subscriptions = sources .Select((source, index) => { var d = new SingleAssignmentDisposable(); d.Disposable = source.Subscribe(x => { lock (gate) { queues[index].Enqueue(x); dequeue(index); } }, ex => { lock (gate) { observer.OnError(ex); } }, () => { lock (gate) { isDone[index] = true; if (isDone.All(x => x)) { observer.OnCompleted(); } else { d.Dispose(); } } }); return d; }) .ToArray(); return new CompositeDisposable(subscriptions) { Disposable.Create(() => { lock (gate) { foreach (var item in queues) { item.Clear(); } } }) }; })); }
public static IObservable <TSource> Delay <TSource>(this IObservable <TSource> source, TimeSpan dueTime, IScheduler scheduler) { // This code is borrowed from Rx(rx.codeplex.com) return(Observable.Create <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); })); }
public static IObservable <TResult> SelectMany <TSource, TCollection, TResult>(this IObservable <TSource> source, Func <TSource, int, IEnumerable <TCollection> > collectionSelector, Func <TSource, int, TCollection, int, TResult> resultSelector) { return(Observable.Create <TResult>(observer => { var index = 0; return source.Subscribe( x => { var xs = default(IEnumerable <TCollection>); try { xs = collectionSelector(x, checked (index++)); } catch (Exception exception) { observer.OnError(exception); return; } var e = xs.AsSafeEnumerable().GetEnumerator(); try { var eIndex = 0; var hasNext = true; while (hasNext) { hasNext = false; var current = default(TResult); try { hasNext = e.MoveNext(); if (hasNext) { current = resultSelector(x, index, e.Current, checked (eIndex++)); } } catch (Exception exception) { observer.OnError(exception); return; } if (hasNext) { observer.OnNext(current); } } } finally { if (e != null) { e.Dispose(); } } }, observer.OnError, observer.OnCompleted ); })); }
public static IObservable <TSource> Throttle <TSource>(this IObservable <TSource> source, TimeSpan dueTime, IScheduler scheduler) { // this code is borrowed from Rx Official(rx.codeplex.com) return(Observable.Create <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); })); }
public static IObservable <T> Timeout <T>(this IObservable <T> source, TimeSpan dueTime, IScheduler scheduler) { return(Observable.Create <T>(observer => { object gate = new object(); var objectId = 0ul; var isTimeout = false; Func <ulong, IDisposable> runTimer = (timerId) => { return scheduler.Schedule(dueTime, () => { lock (gate) { if (objectId == timerId) { isTimeout = true; } } if (isTimeout) { observer.OnError(new TimeoutException()); } }); }; var timerDisposable = new SerialDisposable(); timerDisposable.Disposable = runTimer(objectId); var sourceSubscription = new SingleAssignmentDisposable(); sourceSubscription.Disposable = source.Subscribe(x => { bool timeout; lock (gate) { timeout = isTimeout; objectId++; } if (timeout) { return; } timerDisposable.Disposable = Disposable.Empty; // cancel old timer observer.OnNext(x); timerDisposable.Disposable = runTimer(objectId); }, ex => { bool timeout; lock (gate) { timeout = isTimeout; objectId++; } if (timeout) { return; } timerDisposable.Dispose(); observer.OnError(ex); }, () => { bool timeout; lock (gate) { timeout = isTimeout; objectId++; } if (timeout) { return; } timerDisposable.Dispose(); observer.OnCompleted(); }); return new CompositeDisposable { timerDisposable, sourceSubscription }; })); }
public static IObservable <IList <TSource> > Buffer <TSource, TWindowBoundary>(this IObservable <TSource> source, IObservable <TWindowBoundary> windowBoundaries) { return(Observable.Create <IList <TSource> >(observer => { var list = new List <TSource>(); var gate = new object(); var d = new CompositeDisposable(2); d.Add(source.Subscribe(Observer.Create <TSource>( x => { lock (gate) { list.Add(x); } }, ex => { lock (gate) { observer.OnError(ex); } }, () => { lock (gate) { var currentList = list; list = new List <TSource>(); // safe observer.OnNext(currentList); observer.OnCompleted(); } } ))); d.Add(windowBoundaries.Subscribe(Observer.Create <TWindowBoundary>( w => { List <TSource> currentList; lock (gate) { currentList = list; if (currentList.Count != 0) { list = new List <TSource>(); } } if (currentList.Count != 0) { observer.OnNext(currentList); } }, ex => { lock (gate) { observer.OnError(ex); } }, () => { lock (gate) { var currentList = list; list = new List <TSource>(); // safe observer.OnNext(currentList); observer.OnCompleted(); } } ))); return d; })); }
public static IObservable <IList <T> > Buffer <T>(this IObservable <T> source, TimeSpan timeSpan, TimeSpan timeShift, IScheduler scheduler) { if (source == null) { throw new ArgumentNullException("source"); } return(Observable.Create <IList <T> >(observer => { var totalTime = TimeSpan.Zero; var nextShift = timeShift; var nextSpan = timeSpan; var gate = new object(); var q = new Queue <IList <T> >(); var timerD = new SerialDisposable(); 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 List <T>(); q.Enqueue(s); } if (isSpan) { var s = q.Dequeue(); observer.OnNext(s); } } createTimer(); }); }; q.Enqueue(new List <T>()); createTimer(); return source.Subscribe( x => { lock (gate) { foreach (var s in q) { s.Add(x); } } }, observer.OnError, () => { lock (gate) { foreach (var list in q) { observer.OnNext(list); } observer.OnCompleted(); } } ); })); }
public static IObservable <IList <T> > Buffer <T>(this IObservable <T> source, TimeSpan timeSpan, int count, IScheduler scheduler) { if (source == null) { throw new ArgumentNullException("source"); } if (count <= 0) { throw new ArgumentOutOfRangeException("count <= 0"); } return(Observable.Create <IList <T> >(observer => { var list = new List <T>(); var gate = new object(); var timerId = 0L; var d = new CompositeDisposable(2); var timerD = new SerialDisposable(); // timer d.Add(timerD); Action createTimer = () => { var currentTimerId = timerId; var timerS = new SingleAssignmentDisposable(); timerD.Disposable = timerS; // restart timer(dispose before) timerS.Disposable = scheduler.Schedule(timeSpan, self => { List <T> currentList; lock (gate) { if (currentTimerId != timerId) { return; } currentList = list; if (currentList.Count != 0) { list = new List <T>(); } } if (currentList.Count != 0) { observer.OnNext(currentList); } self(timeSpan); }); }; createTimer(); // subscription d.Add(source.Subscribe(x => { List <T> currentList = null; lock (gate) { list.Add(x); if (list.Count == count) { currentList = list; list = new List <T>(); timerId++; createTimer(); } } if (currentList != null) { observer.OnNext(currentList); } }, observer.OnError, () => { lock (gate) { timerId++; } var currentList = list; observer.OnNext(currentList); observer.OnCompleted(); })); return d; })); }
/// <summary> /// <para>Specialized for single async operations like Task.WhenAll, Zip.Take(1).</para> /// <para>If sequence is empty, return T[0] array.</para> /// </summary> public static IObservable <T[]> WhenAll <T>(this IEnumerable <IObservable <T> > sources) { var array = sources as IObservable <T>[]; if (array != null) { return(WhenAll(array)); } return(Observable.Create <T[]>(observer => { var _sources = sources as IList <IObservable <T> >; if (_sources == null) { _sources = new List <IObservable <T> >(); foreach (var item in sources) { _sources.Add(item); } } var gate = new object(); var length = _sources.Count; var completedCount = 0; var values = new T[length]; if (length == 0) { observer.OnNext(values); observer.OnCompleted(); return Disposable.Empty; } var subscriptions = new IDisposable[length]; for (int index = 0; index < length; index++) { var source = _sources[index]; var capturedIndex = index; var d = new SingleAssignmentDisposable(); d.Disposable = source.Subscribe(x => { lock (gate) { values[capturedIndex] = x; } }, ex => { lock (gate) { observer.OnError(ex); } }, () => { lock (gate) { completedCount++; if (completedCount == length) { observer.OnNext(values); observer.OnCompleted(); } } }); subscriptions[index] = d; } return new CompositeDisposable(subscriptions); })); }
public static IObservable <T> Merge <T>(this IObservable <IObservable <T> > sources, int maxConcurrent) { // this code is borrwed from RxOfficial(rx.codeplex.com) return(Observable.Create <T>(observer => { var gate = new object(); var q = new Queue <IObservable <T> >(); var isStopped = false; var group = new CompositeDisposable(); var activeCount = 0; var subscribe = default(Action <IObservable <T> >); subscribe = xs => { var subscription = new SingleAssignmentDisposable(); group.Add(subscription); subscription.Disposable = xs.Subscribe( x => { lock (gate) observer.OnNext(x); }, exception => { lock (gate) observer.OnError(exception); }, () => { group.Remove(subscription); lock (gate) { if (q.Count > 0) { var s = q.Dequeue(); subscribe(s); } else { activeCount--; if (isStopped && activeCount == 0) { observer.OnCompleted(); } } } }); }; group.Add(sources.Subscribe( innerSource => { lock (gate) { if (activeCount < maxConcurrent) { activeCount++; subscribe(innerSource); } else { q.Enqueue(innerSource); } } }, exception => { lock (gate) observer.OnError(exception); }, () => { lock (gate) { isStopped = true; if (activeCount == 0) { observer.OnCompleted(); } } })); return group; })); }
/// <summary> /// Non-Terminating Observable. It's no returns, never finish. /// </summary> public static IObservable <T> Never <T>() { return(Observable.Create <T>(observer => Disposable.Empty)); }
public static IObservable <TResult> Zip <TLeft, TRight, TResult>(this IObservable <TLeft> left, IObservable <TRight> right, Func <TLeft, TRight, TResult> selector) { return(Observable.Create <TResult>(observer => { var gate = new object(); var leftQ = new Queue <TLeft>(); bool leftCompleted = false; var rightQ = new Queue <TRight>(); var rightCompleted = false; Action dequeue = () => { TLeft lv; TRight rv; TResult v; if (leftQ.Count != 0 && rightQ.Count != 0) { lv = leftQ.Dequeue(); rv = rightQ.Dequeue(); } else if (leftCompleted || rightCompleted) { observer.OnCompleted(); return; } else { return; } try { v = selector(lv, rv); } catch (Exception ex) { observer.OnError(ex); return; } observer.OnNext(v); }; var lsubscription = left.Synchronize(gate).Subscribe(x => { leftQ.Enqueue(x); dequeue(); }, observer.OnError, () => { leftCompleted = true; if (rightCompleted) { observer.OnCompleted(); } }); var rsubscription = right.Synchronize(gate).Subscribe(x => { rightQ.Enqueue(x); dequeue(); }, observer.OnError, () => { rightCompleted = true; if (leftCompleted) { observer.OnCompleted(); } }); return new CompositeDisposable { lsubscription, rsubscription, Disposable.Create(() => { lock (gate) { leftQ.Clear(); rightQ.Clear(); } }) }; })); }
public static IObservable <T> DistinctUntilChanged <T>(this IObservable <T> source, IEqualityComparer <T> comparer) { if (source == null) { throw new ArgumentNullException("source"); } return(Observable.Create <T>(observer => { var isFirst = true; var prevKey = default(T); return source.Subscribe(x => { T currentKey; try { currentKey = x; } catch (Exception ex) { observer.OnError(ex); return; } var sameKey = false; if (isFirst) { isFirst = false; } else { try { if (comparer == null) { if (currentKey == null) { sameKey = (prevKey == null); } else { sameKey = currentKey.Equals(prevKey); } } else { sameKey = comparer.Equals(currentKey, prevKey); } } catch (Exception ex) { observer.OnError(ex); return; } } if (!sameKey) { prevKey = currentKey; observer.OnNext(x); } }, observer.OnError, observer.OnCompleted); })); }
private static IObservable <T> Tween <T>(Func <OperatableBase <T> > start, Func <OperatableBase <T> > finish, Func <float> duration, EaseType easeType, LoopType loopType, Action onCompleteTween) where T : struct { T startValue = default(T); T finishValue = default(T); onCompleteTween = onCompleteTween ?? (() => { }); Func <IObserver <T>, IDisposable> returnStartValue = (observer) => { observer.OnNext(startValue); return(null); }; Func <IObserver <T>, IDisposable> returnFinishValue = (observer) => { observer.OnNext(finishValue); return(null); }; IObservable <T> stream = Observable.Empty <TweenInformation <T> >() // Repeat() のために、毎回初期値を生成 .StartWith(() => new TweenInformation <T>(Time.time, start(), finish(), duration(), easeType, out startValue, out finishValue)) // Update のストリームに変換 .SelectMany(information => Observable.EveryUpdate().Do(_ => information.Time = Time.time - information.StartTime).Select(_ => information)) // Tween 時間が処理時間よりも小さい間流し続ける .TakeWhile(information => information.Time <= information.Duration) // 実際の Easing 処理実行 .Select(information => Easing(information.Time, information.Start, (information.Finish - information.Start), information.Duration, information.EaseType).Value) // 最終フレームの値を確実に流すために OnCompleted が来たら値を一つ流すストリームに繋ぐ // 1回分の Tween が終わったらコールバックを呼ぶ .Concat(Observable.Create(returnFinishValue).Take(1).Do(_ => onCompleteTween())); switch (loopType) { case LoopType.None: // Do nothing. break; case LoopType.Repeat: stream = stream.Repeat(); break; case LoopType.PingPong: stream = stream .Concat( Observable.Empty <TweenInformation <T> >() // Repeat() のために、毎回初期値を生成 .StartWith(() => new TweenInformation <T>(Time.time, start(), finish(), duration(), easeType, out startValue, out finishValue)) // Update のストリームに変換 .SelectMany(information => Observable.EveryUpdate().Do(_ => information.Time = Time.time - information.StartTime).Select(_ => information)) // Tween 時間が処理時間よりも小さい間流し続ける .TakeWhile(information => information.Time <= information.Duration) // start と finish を入れ替えて、実際の Easing 処理実行 .Select(information => Easing(information.Time, information.Finish, (information.Start - information.Finish), information.Duration, information.EaseType).Value) // 最終フレームの値を確実に流すために OnCompleted が来たら最終値を一つ流すストリームに繋ぐ // 1回分の Tween が終わったらコールバックを呼ぶ .Concat(Observable.Create(returnStartValue).Take(1).Do(_ => onCompleteTween())) ) .Repeat(); break; case LoopType.Mirror: stream = stream .Concat( Observable.Empty <TweenInformation <T> >() // Repeat() のために、毎回初期値を生成 .StartWith(() => new TweenInformation <T>(Time.time, start(), finish(), duration(), easeType, out startValue, out finishValue)) // Update のストリームに変換 .SelectMany(information => Observable.EveryUpdate().Do(_ => information.Time = Time.time - information.StartTime).Select(_ => information)) // Tween 時間が処理時間よりも小さい間流し続ける .TakeWhile(information => information.Time <= information.Duration) // start と finish を入れ替えて、実際の Easing 処理実行 .Select(information => Easing(information.Time, information.Finish, (information.Start - information.Finish), information.Duration, MirrorEaseTypeMap[information.EaseType]).Value) // 最終フレームの値を確実に流すために OnCompleted が来たら最終値を一つ流すストリームに繋ぐ // 1回分の Tween が終わったらコールバックを呼ぶ .Concat(Observable.Create(returnStartValue).Take(1).Do(_ => onCompleteTween())) ) .Repeat(); break; } return(stream); }