Kit.Core/LibExternal/System.Reactive/Linq/Observable/GroupBy.cs

178 lines
6.5 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.Collections.Generic;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
namespace System.Reactive.Linq.ObservableImpl
{
internal sealed class GroupBy<TSource, TKey, TElement> : Producer<IGroupedObservable<TKey, TElement>, GroupBy<TSource, TKey, TElement>._>
{
private readonly IObservable<TSource> _source;
private readonly Func<TSource, TKey> _keySelector;
private readonly Func<TSource, TElement> _elementSelector;
private readonly int? _capacity;
private readonly IEqualityComparer<TKey> _comparer;
public GroupBy(IObservable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, int? capacity, IEqualityComparer<TKey> comparer)
{
_source = source;
_keySelector = keySelector;
_elementSelector = elementSelector;
_capacity = capacity;
_comparer = comparer;
}
protected override _ CreateSink(IObserver<IGroupedObservable<TKey, TElement>> observer) => new(this, observer);
protected override void Run(_ sink) => sink.Run(_source);
internal sealed class _ : Sink<TSource, IGroupedObservable<TKey, TElement>>
{
private readonly Func<TSource, TKey> _keySelector;
private readonly Func<TSource, TElement> _elementSelector;
private readonly Grouping<TKey, TElement> _map;
private RefCountDisposable? _refCountDisposable;
private Subject<TElement>? _null;
public _(GroupBy<TSource, TKey, TElement> parent, IObserver<IGroupedObservable<TKey, TElement>> observer)
: base(observer)
{
_keySelector = parent._keySelector;
_elementSelector = parent._elementSelector;
if (parent._capacity.HasValue)
{
_map = new Grouping<TKey, TElement>(parent._capacity.Value, parent._comparer);
}
else
{
_map = new Grouping<TKey, TElement>(parent._comparer);
}
}
public override void Run(IObservable<TSource> source)
{
var sourceSubscription = new SingleAssignmentDisposable();
_refCountDisposable = new RefCountDisposable(sourceSubscription);
sourceSubscription.Disposable = source.SubscribeSafe(this);
SetUpstream(_refCountDisposable);
}
public override void OnNext(TSource value)
{
TKey key;
try
{
key = _keySelector(value);
}
catch (Exception exception)
{
Error(exception);
return;
}
var fireNewMapEntry = false;
Subject<TElement>? writer;
try
{
//
// Note: The box instruction in the IL will be erased by the JIT in case T is
// a value type. In fact, the whole if block will go away and we'll end
// up with nothing but the TryGetValue check below.
//
// // var fireNewMapEntry = false;
// C:\Projects\Rx\Rx\Experimental\Main\Source\Rx\System.Reactive.Linq\Reactive\Linq\Observable\GroupBy.cs @ 67:
// 000007fb`6d544b80 48c7452800000000 mov qword ptr [rbp+28h],0
//
// // var writer = default(ISubject<TElement>);
// C:\Projects\Rx\Rx\Experimental\Main\Source\Rx\System.Reactive.Linq\Reactive\Linq\Observable\GroupBy.cs @ 66:
// 000007fb`6d544b88 c6453400 mov byte ptr [rbp+34h],0
//
// // if (!_map.TryGetValue(key, out writer))
// C:\Projects\Rx\Rx\Experimental\Main\Source\Rx\System.Reactive.Linq\Reactive\Linq\Observable\GroupBy.cs @ 86:
// 000007fb`6d544b8c 488b4560 mov rax,qword ptr [rbp+60h]
// ...
//
if (key == null)
{
if (_null == null)
{
_null = new Subject<TElement>();
fireNewMapEntry = true;
}
writer = _null;
}
else
{
if (!_map.TryGetValue(key, out writer))
{
writer = new Subject<TElement>();
_map.Add(key, writer);
fireNewMapEntry = true;
}
}
}
catch (Exception exception)
{
Error(exception);
return;
}
if (fireNewMapEntry)
{
var group = new GroupedObservable<TKey, TElement>(key, writer, _refCountDisposable!); // NB: _refCountDisposable is set in Run.
ForwardOnNext(group);
}
TElement element;
try
{
element = _elementSelector(value);
}
catch (Exception exception)
{
Error(exception);
return;
}
writer.OnNext(element);
}
public override void OnError(Exception error)
{
Error(error);
}
public override void OnCompleted()
{
_null?.OnCompleted();
foreach (var w in _map.Values)
{
w.OnCompleted();
}
ForwardOnCompleted();
}
private void Error(Exception exception)
{
_null?.OnError(exception);
foreach (var w in _map.Values)
{
w.OnError(exception);
}
ForwardOnError(exception);
}
}
}
}