// 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.Collections.Generic; using System.Reactive.Concurrency; using System.Reactive.Disposables; namespace System.Reactive.Linq.ObservableImpl { internal sealed class ToObservableRecursive : Producer._> { private readonly IEnumerable _source; private readonly IScheduler _scheduler; public ToObservableRecursive(IEnumerable source, IScheduler scheduler) { _source = source; _scheduler = scheduler; } protected override _ CreateSink(IObserver observer) => new(observer); protected override void Run(_ sink) => sink.Run(_source, _scheduler); internal sealed class _ : IdentitySink { private IEnumerator? _enumerator; private volatile bool _disposed; public _(IObserver observer) : base(observer) { } public void Run(IEnumerable source, IScheduler scheduler) { try { _enumerator = source.GetEnumerator(); } catch (Exception exception) { ForwardOnError(exception); return; } // // We never allow the scheduled work to be cancelled. Instead, the _disposed flag // is used to have LoopRec bail out and perform proper clean-up of the // enumerator. // scheduler.Schedule(this, static (innerScheduler, @this) => @this.LoopRec(innerScheduler)); } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing) { _disposed = true; } } private IDisposable LoopRec(IScheduler scheduler) { var hasNext = false; var ex = default(Exception); var current = default(TSource); var enumerator = _enumerator!; // NB: Loop only runs after enumerator is assigned. if (_disposed) { enumerator.Dispose(); _enumerator = null; return Disposable.Empty; } try { hasNext = enumerator.MoveNext(); if (hasNext) { current = enumerator.Current; } } catch (Exception exception) { ex = exception; } if (ex != null) { enumerator.Dispose(); _enumerator = null; ForwardOnError(ex); return Disposable.Empty; } if (!hasNext) { enumerator.Dispose(); _enumerator = null; ForwardOnCompleted(); return Disposable.Empty; } ForwardOnNext(current!); // NB: Non-null when hasNext is true. // // We never allow the scheduled work to be cancelled. Instead, the _disposed flag // is used to have LoopRec bail out and perform proper clean-up of the // enumerator. // scheduler.Schedule(this, static (innerScheduler, @this) => @this.LoopRec(innerScheduler)); return Disposable.Empty; } } } internal sealed class ToObservableLongRunning : Producer._> { private readonly IEnumerable _source; private readonly ISchedulerLongRunning _scheduler; public ToObservableLongRunning(IEnumerable source, ISchedulerLongRunning scheduler) { _source = source; _scheduler = scheduler; } protected override _ CreateSink(IObserver observer) => new(observer); protected override void Run(_ sink) => sink.Run(_source, _scheduler); internal sealed class _ : IdentitySink { public _(IObserver observer) : base(observer) { } public void Run(IEnumerable source, ISchedulerLongRunning scheduler) { IEnumerator e; try { e = source.GetEnumerator(); } catch (Exception exception) { ForwardOnError(exception); return; } SetUpstream(scheduler.ScheduleLongRunning((@this: this, e), static (tuple, cancelable) => tuple.@this.Loop(tuple.e, cancelable))); } private void Loop(IEnumerator enumerator, ICancelable cancel) { while (!cancel.IsDisposed) { var hasNext = false; var ex = default(Exception); var current = default(TSource); try { hasNext = enumerator.MoveNext(); if (hasNext) { current = enumerator.Current; } } catch (Exception exception) { ex = exception; } if (ex != null) { ForwardOnError(ex); break; } if (!hasNext) { ForwardOnCompleted(); break; } ForwardOnNext(current!); // NB: Non-null when hasNext is true. } enumerator.Dispose(); Dispose(); } } } }