Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 13 additions & 17 deletions src/RxSharp/Extras/FromEventBuffered.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,21 +69,24 @@ public static partial class RxExtensions
/// <param name="removeHandler">Called on <see cref="BufferedEventSource{TEventArgs}.Dispose"/>, with the same handler, to remove it from the event.</param>
/// <param name="conversion">Converts an <see cref="Action{TEventArgs}"/> callback into the event's actual delegate shape.</param>
/// <param name="bufferSize">
/// The maximum number of most-recent payloads kept for replay. Defaults to 1 - fine if the caller only
/// cares about the single most recent payload, but a real risk if the caller filters for a specific match
/// among possibly-several payloads that could arrive in the pre-subscribe gap this method exists to cover:
/// with the default of 1, a matching payload can be silently evicted from the buffer by a later
/// non-matching one before anyone subscribes to see it. Pass a larger value whenever more than one payload
/// could plausibly arrive before subscription and only some of them are what the caller is looking for.
/// The maximum number of most-recent payloads kept for replay. Defaults to <see langword="null"/>, which
/// defers entirely to <see cref="ReplaySubject{T}"/>'s own default (unbounded) - this wrapper has no
/// buffering opinion of its own beyond what the primitive it's built on already does. A caller filtering
/// for a specific match among payloads that could arrive in the pre-subscribe gap this method exists to
/// cover needs more than 1: with too small a buffer, a matching payload can be silently evicted by a later
/// non-matching one before anyone subscribes to see it. Pass an explicit bound only if this source stays
/// attached for a long time without being subscribed to and unbounded growth is a real concern for that
/// specific usage - the gap this method targets is normally microseconds, not something an unbounded
/// buffer meaningfully grows during.
/// </param>
/// <returns>A handle exposing the buffered payloads as an observable, and detaching the handler on disposal.</returns>
public static BufferedEventSource<TEventArgs> FromEventBuffered<TDelegate, TEventArgs>(
Action<TDelegate> addHandler,
Action<TDelegate> removeHandler,
Func<Action<TEventArgs>, TDelegate> conversion,
int bufferSize = 1)
int? bufferSize = null)
{
var subject = new ReplaySubject<TEventArgs>(bufferSize);
var subject = bufferSize is { } size ? new ReplaySubject<TEventArgs>(size) : new ReplaySubject<TEventArgs>();
var handler = conversion(subject.OnNext);
addHandler(handler);
return new BufferedEventSource<TEventArgs>(subject, () => removeHandler(handler));
Expand All @@ -93,18 +96,11 @@ public static BufferedEventSource<TEventArgs> FromEventBuffered<TDelegate, TEven
/// <typeparam name="TEventArgs">The type of the event's payload.</typeparam>
/// <param name="addHandler">Called immediately, with the handler to add to the event.</param>
/// <param name="removeHandler">Called on <see cref="BufferedEventSource{TEventArgs}.Dispose"/>, with the same handler, to remove it from the event.</param>
/// <param name="bufferSize">
/// The maximum number of most-recent payloads kept for replay. Defaults to 1 - fine if the caller only
/// cares about the single most recent payload, but a real risk if the caller filters for a specific match
/// among possibly-several payloads that could arrive in the pre-subscribe gap this method exists to cover:
/// with the default of 1, a matching payload can be silently evicted from the buffer by a later
/// non-matching one before anyone subscribes to see it. Pass a larger value whenever more than one payload
/// could plausibly arrive before subscription and only some of them are what the caller is looking for.
/// </param>
/// <param name="bufferSize">See <see cref="FromEventBuffered{TDelegate, TEventArgs}"/>'s parameter of the same name.</param>
/// <returns>A handle exposing the buffered payloads as an observable, and detaching the handler on disposal.</returns>
public static BufferedEventSource<TEventArgs> FromEventBuffered<TEventArgs>(
Action<EventHandler<TEventArgs>> addHandler,
Action<EventHandler<TEventArgs>> removeHandler,
int bufferSize = 1)
int? bufferSize = null)
=> FromEventBuffered<EventHandler<TEventArgs>, TEventArgs>(addHandler, removeHandler, onNext => (_, args) => onNext(args), bufferSize);
}
6 changes: 3 additions & 3 deletions src/RxSharp/RxSharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
<PackageProjectUrl>https://github.com/hardkoded/ReactiveExtensions-Sharp</PackageProjectUrl>
<RepositoryUrl>https://github.com/hardkoded/ReactiveExtensions-Sharp</RepositoryUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageVersion>0.1.3</PackageVersion>
<AssemblyVersion>0.1.3.0</AssemblyVersion>
<FileVersion>0.1.3.0</FileVersion>
<PackageVersion>0.1.4</PackageVersion>
<AssemblyVersion>0.1.4.0</AssemblyVersion>
<FileVersion>0.1.4.0</FileVersion>
</PropertyGroup>

<PropertyGroup>
Expand Down
20 changes: 20 additions & 0 deletions test/RxSharp.Tests/Extras/FromEventBufferedExtrasTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,26 @@ public void ShouldDeliverToEveryIndependentSubscriber()
Assert.That(second, Is.EqualTo(new[] { "shared-target" }));
}

[Test]
public void ShouldBufferEveryPreSubscribeValueByDefault()
{
// Locks in the default: null defers to ReplaySubject's own default (unbounded), not a small opinionated
// number - a matching payload must never be evicted by a later non-matching one before subscription.
var manager = new FakeTargetManager();
using var source = RxExtensions.FromEventBuffered<string>(
h => manager.TargetCreated += h,
h => manager.TargetCreated -= h);

manager.RaiseTargetCreated("one");
manager.RaiseTargetCreated("two");
manager.RaiseTargetCreated("three");

var received = new List<string>();
source.AsObservable().Subscribe(received.Add);

Assert.That(received, Is.EqualTo(new[] { "one", "two", "three" }));
}

[Test]
public void ShouldOnlyReplayUpToTheRequestedBufferSize()
{
Expand Down