550 lines
19 KiB
C#
550 lines
19 KiB
C#
// Licensed to the .NET Foundation under one or more agreements.
|
|
// The .NET Foundation licenses this file to you under the MIT License.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
using System.Diagnostics;
|
|
using System.Reactive.Concurrency;
|
|
using System.Reactive.Disposables;
|
|
|
|
namespace System.Reactive.Linq.ObservableImpl
|
|
{
|
|
internal static class AppendPrepend<TSource>
|
|
{
|
|
internal interface IAppendPrepend : IObservable<TSource>
|
|
{
|
|
IAppendPrepend Append(TSource value);
|
|
IAppendPrepend Prepend(TSource value);
|
|
IScheduler Scheduler { get; }
|
|
}
|
|
|
|
internal abstract class SingleBase<TSink> : Producer<TSource, TSink>, IAppendPrepend
|
|
where TSink : IDisposable
|
|
{
|
|
protected readonly IObservable<TSource> _source;
|
|
protected readonly TSource _value;
|
|
protected readonly bool _append;
|
|
|
|
public abstract IScheduler Scheduler { get; }
|
|
|
|
public SingleBase(IObservable<TSource> source, TSource value, bool append)
|
|
{
|
|
_source = source;
|
|
_value = value;
|
|
_append = append;
|
|
}
|
|
|
|
public IAppendPrepend Append(TSource value)
|
|
{
|
|
var prev = new Node<TSource>(_value);
|
|
|
|
Node<TSource> appendNode;
|
|
Node<TSource>? prependNode = null;
|
|
|
|
if (_append)
|
|
{
|
|
appendNode = new Node<TSource>(prev, value);
|
|
}
|
|
else
|
|
{
|
|
prependNode = prev;
|
|
appendNode = new Node<TSource>(value);
|
|
}
|
|
|
|
return CreateAppendPrepend(prependNode, appendNode);
|
|
}
|
|
|
|
public IAppendPrepend Prepend(TSource value)
|
|
{
|
|
var prev = new Node<TSource>(_value);
|
|
|
|
Node<TSource>? appendNode = null;
|
|
Node<TSource> prependNode;
|
|
|
|
if (_append)
|
|
{
|
|
prependNode = new Node<TSource>(value);
|
|
appendNode = prev;
|
|
}
|
|
else
|
|
{
|
|
prependNode = new Node<TSource>(prev, value);
|
|
}
|
|
|
|
return CreateAppendPrepend(prependNode, appendNode);
|
|
}
|
|
|
|
private IAppendPrepend CreateAppendPrepend(Node<TSource>? prepend, Node<TSource>? append)
|
|
{
|
|
if (Scheduler is ISchedulerLongRunning longRunning)
|
|
{
|
|
return new LongRunning(_source, prepend, append, Scheduler, longRunning);
|
|
}
|
|
|
|
return new Recursive(_source, prepend, append, Scheduler);
|
|
}
|
|
}
|
|
|
|
|
|
internal sealed class SingleValue : SingleBase<SingleValue._>
|
|
{
|
|
public override IScheduler Scheduler { get; }
|
|
|
|
public SingleValue(IObservable<TSource> source, TSource value, IScheduler scheduler, bool append)
|
|
: base (source, value, append)
|
|
{
|
|
Scheduler = scheduler;
|
|
}
|
|
|
|
protected override _ CreateSink(IObserver<TSource> observer) => new(this, observer);
|
|
|
|
protected override void Run(_ sink) => sink.Run();
|
|
|
|
internal sealed class _ : IdentitySink<TSource>
|
|
{
|
|
private readonly IObservable<TSource> _source;
|
|
private readonly TSource _value;
|
|
private readonly IScheduler _scheduler;
|
|
private readonly bool _append;
|
|
private SingleAssignmentDisposableValue _schedulerDisposable;
|
|
|
|
public _(SingleValue parent, IObserver<TSource> observer)
|
|
: base(observer)
|
|
{
|
|
_source = parent._source;
|
|
_value = parent._value;
|
|
_scheduler = parent.Scheduler;
|
|
_append = parent._append;
|
|
}
|
|
|
|
public void Run()
|
|
{
|
|
var disp = _append
|
|
? _source.SubscribeSafe(this)
|
|
: _scheduler.ScheduleAction(this, PrependValue);
|
|
|
|
SetUpstream(disp);
|
|
}
|
|
|
|
private static IDisposable PrependValue(_ sink)
|
|
{
|
|
sink.ForwardOnNext(sink._value);
|
|
return sink._source.SubscribeSafe(sink);
|
|
}
|
|
|
|
public override void OnCompleted()
|
|
{
|
|
if (_append)
|
|
{
|
|
var disposable = _scheduler.ScheduleAction(this, AppendValue);
|
|
_schedulerDisposable.Disposable = disposable;
|
|
}
|
|
else
|
|
{
|
|
ForwardOnCompleted();
|
|
}
|
|
}
|
|
|
|
private static void AppendValue(_ sink)
|
|
{
|
|
sink.ForwardOnNext(sink._value);
|
|
sink.ForwardOnCompleted();
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
_schedulerDisposable.Dispose();
|
|
}
|
|
base.Dispose(disposing);
|
|
}
|
|
}
|
|
}
|
|
|
|
private sealed class Recursive : Producer<TSource, Recursive._>, IAppendPrepend
|
|
{
|
|
private readonly IObservable<TSource> _source;
|
|
private readonly Node<TSource>? _appends;
|
|
private readonly Node<TSource>? _prepends;
|
|
|
|
public IScheduler Scheduler { get; }
|
|
|
|
public Recursive(IObservable<TSource> source, Node<TSource>? prepends, Node<TSource>? appends, IScheduler scheduler)
|
|
{
|
|
_source = source;
|
|
_appends = appends;
|
|
_prepends = prepends;
|
|
Scheduler = scheduler;
|
|
}
|
|
|
|
public IAppendPrepend Append(TSource value)
|
|
{
|
|
return new Recursive(_source,
|
|
_prepends, new Node<TSource>(_appends, value), Scheduler);
|
|
}
|
|
|
|
public IAppendPrepend Prepend(TSource value)
|
|
{
|
|
return new Recursive(_source,
|
|
new Node<TSource>(_prepends, value), _appends, Scheduler);
|
|
}
|
|
|
|
protected override _ CreateSink(IObserver<TSource> observer) => new(this, observer);
|
|
|
|
protected override void Run(_ sink) => sink.Run();
|
|
|
|
// The sink is based on the sink of the ToObervalbe class and does basically
|
|
// the same twice, once for the append list and once for the prepend list.
|
|
// Inbetween it forwards the values of the source class.
|
|
//
|
|
internal sealed class _ : IdentitySink<TSource>
|
|
{
|
|
private readonly IObservable<TSource> _source;
|
|
private readonly Node<TSource>? _appends;
|
|
private readonly IScheduler _scheduler;
|
|
|
|
private Node<TSource>? _currentPrependNode;
|
|
private TSource[]? _appendArray;
|
|
private int _currentAppendIndex;
|
|
private volatile bool _disposed;
|
|
|
|
public _(Recursive parent, IObserver<TSource> observer)
|
|
: base(observer)
|
|
{
|
|
_source = parent._source;
|
|
_scheduler = parent.Scheduler;
|
|
_currentPrependNode = parent._prepends;
|
|
_appends = parent._appends;
|
|
}
|
|
|
|
public void Run()
|
|
{
|
|
if (_currentPrependNode == null)
|
|
{
|
|
SetUpstream(_source.SubscribeSafe(this));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We never allow the scheduled work to be cancelled. Instead, the _disposed flag
|
|
// is used to have PrependValues() bail out.
|
|
//
|
|
_scheduler.Schedule(this, static (innerScheduler, @this) => @this.PrependValues(innerScheduler));
|
|
}
|
|
}
|
|
|
|
public override void OnCompleted()
|
|
{
|
|
if (_appends == null)
|
|
{
|
|
ForwardOnCompleted();
|
|
}
|
|
else
|
|
{
|
|
_appendArray = _appends.ToReverseArray();
|
|
|
|
//
|
|
// We never allow the scheduled work to be cancelled. Instead, the _disposed flag
|
|
// is used to have `AppendValues` bail out.
|
|
//
|
|
_scheduler.Schedule(this, static (innerScheduler, @this) => @this.AppendValues(innerScheduler));
|
|
}
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
_disposed = true;
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
private IDisposable PrependValues(IScheduler scheduler)
|
|
{
|
|
if (_disposed)
|
|
{
|
|
return Disposable.Empty;
|
|
}
|
|
|
|
Debug.Assert(_currentPrependNode != null);
|
|
|
|
var current = _currentPrependNode!.Value;
|
|
ForwardOnNext(current);
|
|
|
|
_currentPrependNode = _currentPrependNode.Parent;
|
|
if (_currentPrependNode == null)
|
|
{
|
|
SetUpstream(_source.SubscribeSafe(this));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We never allow the scheduled work to be cancelled. Instead, the _disposed flag
|
|
// is used to have PrependValues() bail out.
|
|
//
|
|
scheduler.Schedule(this, static (innerScheduler, @this) => @this.PrependValues(innerScheduler));
|
|
}
|
|
|
|
return Disposable.Empty;
|
|
}
|
|
|
|
private IDisposable AppendValues(IScheduler scheduler)
|
|
{
|
|
if (_disposed)
|
|
{
|
|
return Disposable.Empty;
|
|
}
|
|
|
|
Debug.Assert(_appendArray != null);
|
|
|
|
var current = _appendArray![_currentAppendIndex];
|
|
ForwardOnNext(current);
|
|
|
|
_currentAppendIndex++;
|
|
|
|
if (_currentAppendIndex == _appendArray.Length)
|
|
{
|
|
ForwardOnCompleted();
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We never allow the scheduled work to be cancelled. Instead, the _disposed flag
|
|
// is used to have AppendValues() bail out.
|
|
//
|
|
scheduler.Schedule(this, static (innerScheduler, @this) => @this.AppendValues(innerScheduler));
|
|
}
|
|
|
|
return Disposable.Empty;
|
|
}
|
|
}
|
|
}
|
|
|
|
private sealed class LongRunning : Producer<TSource, LongRunning._>, IAppendPrepend
|
|
{
|
|
private readonly IObservable<TSource> _source;
|
|
private readonly Node<TSource>? _appends;
|
|
private readonly Node<TSource>? _prepends;
|
|
private readonly ISchedulerLongRunning _longRunningScheduler;
|
|
|
|
public IScheduler Scheduler { get; }
|
|
|
|
public LongRunning(IObservable<TSource> source, Node<TSource>? prepends, Node<TSource>? appends, IScheduler scheduler, ISchedulerLongRunning longRunningScheduler)
|
|
{
|
|
_source = source;
|
|
_appends = appends;
|
|
_prepends = prepends;
|
|
Scheduler = scheduler;
|
|
_longRunningScheduler = longRunningScheduler;
|
|
}
|
|
|
|
public IAppendPrepend Append(TSource value)
|
|
{
|
|
return new LongRunning(_source,
|
|
_prepends, new Node<TSource>(_appends, value), Scheduler, _longRunningScheduler);
|
|
}
|
|
|
|
public IAppendPrepend Prepend(TSource value)
|
|
{
|
|
return new LongRunning(_source,
|
|
new Node<TSource>(_prepends, value), _appends, Scheduler, _longRunningScheduler);
|
|
}
|
|
|
|
protected override _ CreateSink(IObserver<TSource> observer) => new(this, observer);
|
|
|
|
protected override void Run(_ sink) => sink.Run();
|
|
|
|
// The sink is based on the sink of the ToObervalbe class and does basically
|
|
// the same twice, once for the append list and once for the prepend list.
|
|
// Inbetween it forwards the values of the source class.
|
|
//
|
|
internal sealed class _ : IdentitySink<TSource>
|
|
{
|
|
private readonly IObservable<TSource> _source;
|
|
private readonly Node<TSource>? _prepends;
|
|
private readonly Node<TSource>? _appends;
|
|
private readonly ISchedulerLongRunning _scheduler;
|
|
|
|
private SerialDisposableValue _schedulerDisposable;
|
|
|
|
public _(LongRunning parent, IObserver<TSource> observer)
|
|
: base(observer)
|
|
{
|
|
_source = parent._source;
|
|
_scheduler = parent._longRunningScheduler;
|
|
_prepends = parent._prepends;
|
|
_appends = parent._appends;
|
|
}
|
|
|
|
public void Run()
|
|
{
|
|
if (_prepends == null)
|
|
{
|
|
SetUpstream(_source.SubscribeSafe(this));
|
|
}
|
|
else
|
|
{
|
|
var disposable = _scheduler.ScheduleLongRunning(this, static (@this, cancel) => @this.PrependValues(cancel));
|
|
_schedulerDisposable.TrySetFirst(disposable);
|
|
}
|
|
}
|
|
|
|
public override void OnCompleted()
|
|
{
|
|
if (_appends == null)
|
|
{
|
|
ForwardOnCompleted();
|
|
}
|
|
else
|
|
{
|
|
var disposable = _scheduler.ScheduleLongRunning(this, static (@this, cancel) => @this.AppendValues(cancel));
|
|
_schedulerDisposable.Disposable = disposable;
|
|
}
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
_schedulerDisposable.Dispose();
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
private void PrependValues(ICancelable cancel)
|
|
{
|
|
Debug.Assert(_prepends != null);
|
|
|
|
var current = _prepends!;
|
|
|
|
while (!cancel.IsDisposed)
|
|
{
|
|
ForwardOnNext(current.Value);
|
|
current = current.Parent;
|
|
|
|
if (current == null)
|
|
{
|
|
SetUpstream(_source.SubscribeSafe(this));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void AppendValues(ICancelable cancel)
|
|
{
|
|
Debug.Assert(_appends != null);
|
|
|
|
var array = _appends!.ToReverseArray();
|
|
var i = 0;
|
|
|
|
while (!cancel.IsDisposed)
|
|
{
|
|
ForwardOnNext(array[i]);
|
|
i++;
|
|
|
|
if (i == array.Length)
|
|
{
|
|
ForwardOnCompleted();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private sealed class Node<T>
|
|
{
|
|
public readonly Node<T>? Parent;
|
|
public readonly T Value;
|
|
public readonly int Count;
|
|
|
|
public Node(T value)
|
|
: this(null, value)
|
|
{
|
|
}
|
|
|
|
public Node(Node<T>? parent, T value)
|
|
{
|
|
Parent = parent;
|
|
Value = value;
|
|
|
|
if (parent == null)
|
|
{
|
|
Count = 1;
|
|
}
|
|
else
|
|
{
|
|
if (parent.Count == int.MaxValue)
|
|
{
|
|
throw new NotSupportedException($"Consecutive appends or prepends with a count of more than int.MaxValue ({int.MaxValue}) are not supported.");
|
|
}
|
|
|
|
Count = parent.Count + 1;
|
|
}
|
|
}
|
|
|
|
public T[] ToReverseArray()
|
|
{
|
|
var array = new T[Count];
|
|
var current = this;
|
|
for (var i = Count - 1; i >= 0; i--)
|
|
{
|
|
array[i] = current!.Value; // NB: Count property ensures non-nullability.
|
|
current = current.Parent;
|
|
}
|
|
return array;
|
|
}
|
|
}
|
|
|
|
internal sealed class SingleImmediate : SingleBase<SingleImmediate._>
|
|
{
|
|
public override IScheduler Scheduler => ImmediateScheduler.Instance;
|
|
|
|
public SingleImmediate(IObservable<TSource> source, TSource value, bool append)
|
|
: base(source, value, append)
|
|
{
|
|
}
|
|
|
|
protected override _ CreateSink(IObserver<TSource> observer) => new(this, observer);
|
|
|
|
protected override void Run(_ sink) => sink.Run();
|
|
|
|
internal sealed class _ : IdentitySink<TSource>
|
|
{
|
|
private readonly IObservable<TSource> _source;
|
|
private readonly TSource _value;
|
|
private readonly bool _append;
|
|
|
|
public _(SingleImmediate parent, IObserver<TSource> observer)
|
|
: base(observer)
|
|
{
|
|
_source = parent._source;
|
|
_value = parent._value;
|
|
_append = parent._append;
|
|
}
|
|
|
|
public void Run()
|
|
{
|
|
if (!_append)
|
|
{
|
|
ForwardOnNext(_value);
|
|
}
|
|
Run(_source);
|
|
}
|
|
|
|
public override void OnCompleted()
|
|
{
|
|
if (_append)
|
|
{
|
|
ForwardOnNext(_value);
|
|
}
|
|
ForwardOnCompleted();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|