a575963da9
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
187 lines
5.4 KiB
C#
187 lines
5.4 KiB
C#
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
|
|
|
#if !NO_PERF
|
|
using System.Collections.Generic;
|
|
using System.Reactive.Concurrency;
|
|
using System.Reactive.Disposables;
|
|
|
|
namespace System.Reactive
|
|
{
|
|
abstract class TailRecursiveSink<TSource> : Sink<TSource>, IObserver<TSource>
|
|
{
|
|
public TailRecursiveSink(IObserver<TSource> observer, IDisposable cancel)
|
|
: base(observer, cancel)
|
|
{
|
|
}
|
|
|
|
private bool _isDisposed;
|
|
private SerialDisposable _subscription;
|
|
private AsyncLock _gate;
|
|
private Stack<IEnumerator<IObservable<TSource>>> _stack;
|
|
private Stack<int?> _length;
|
|
protected Action _recurse;
|
|
|
|
public IDisposable Run(IEnumerable<IObservable<TSource>> sources)
|
|
{
|
|
_isDisposed = false;
|
|
_subscription = new SerialDisposable();
|
|
_gate = new AsyncLock();
|
|
_stack = new Stack<IEnumerator<IObservable<TSource>>>();
|
|
_length = new Stack<int?>();
|
|
|
|
var e = default(IEnumerator<IObservable<TSource>>);
|
|
if (!TryGetEnumerator(sources, out e))
|
|
return Disposable.Empty;
|
|
|
|
_stack.Push(e);
|
|
_length.Push(Helpers.GetLength(sources));
|
|
|
|
var cancelable = SchedulerDefaults.TailRecursion.Schedule(self =>
|
|
{
|
|
_recurse = self;
|
|
_gate.Wait(MoveNext);
|
|
});
|
|
|
|
return new CompositeDisposable(_subscription, cancelable, Disposable.Create(() => _gate.Wait(Dispose)));
|
|
}
|
|
|
|
protected abstract IEnumerable<IObservable<TSource>> Extract(IObservable<TSource> source);
|
|
|
|
private void MoveNext()
|
|
{
|
|
var hasNext = false;
|
|
var next = default(IObservable<TSource>);
|
|
|
|
do
|
|
{
|
|
if (_stack.Count == 0)
|
|
break;
|
|
|
|
if (_isDisposed)
|
|
return;
|
|
|
|
var e = _stack.Peek();
|
|
var l = _length.Peek();
|
|
|
|
var current = default(IObservable<TSource>);
|
|
try
|
|
{
|
|
hasNext = e.MoveNext();
|
|
if (hasNext)
|
|
current = e.Current;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
e.Dispose();
|
|
|
|
base._observer.OnError(ex);
|
|
base.Dispose();
|
|
return;
|
|
}
|
|
|
|
if (!hasNext)
|
|
{
|
|
e.Dispose();
|
|
|
|
_stack.Pop();
|
|
_length.Pop();
|
|
}
|
|
else
|
|
{
|
|
var r = l - 1;
|
|
_length.Pop();
|
|
_length.Push(r);
|
|
|
|
try
|
|
{
|
|
next = Helpers.Unpack(current);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
e.Dispose();
|
|
base._observer.OnError(exception);
|
|
base.Dispose();
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Tail recursive case; drop the current frame.
|
|
//
|
|
if (r == 0)
|
|
{
|
|
e.Dispose();
|
|
_stack.Pop();
|
|
_length.Pop();
|
|
}
|
|
|
|
//
|
|
// Flattening of nested sequences. Prevents stack overflow in observers.
|
|
//
|
|
var nextSeq = Extract(next);
|
|
if (nextSeq != null)
|
|
{
|
|
var nextEnumerator = default(IEnumerator<IObservable<TSource>>);
|
|
if (!TryGetEnumerator(nextSeq, out nextEnumerator))
|
|
return;
|
|
|
|
_stack.Push(nextEnumerator);
|
|
_length.Push(Helpers.GetLength(nextSeq));
|
|
|
|
hasNext = false;
|
|
}
|
|
}
|
|
} while (!hasNext);
|
|
|
|
if (!hasNext)
|
|
{
|
|
Done();
|
|
return;
|
|
}
|
|
|
|
var d = new SingleAssignmentDisposable();
|
|
_subscription.Disposable = d;
|
|
d.Disposable = next.SubscribeSafe(this);
|
|
}
|
|
|
|
private new void Dispose()
|
|
{
|
|
while (_stack.Count > 0)
|
|
{
|
|
var e = _stack.Pop();
|
|
_length.Pop();
|
|
|
|
e.Dispose();
|
|
}
|
|
|
|
_isDisposed = true;
|
|
}
|
|
|
|
private bool TryGetEnumerator(IEnumerable<IObservable<TSource>> sources, out IEnumerator<IObservable<TSource>> result)
|
|
{
|
|
try
|
|
{
|
|
result = sources.GetEnumerator();
|
|
return true;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
base._observer.OnError(exception);
|
|
base.Dispose();
|
|
|
|
result = null;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public abstract void OnCompleted();
|
|
public abstract void OnError(Exception error);
|
|
public abstract void OnNext(TSource value);
|
|
|
|
protected virtual void Done()
|
|
{
|
|
base._observer.OnCompleted();
|
|
base.Dispose();
|
|
}
|
|
}
|
|
}
|
|
#endif |