Skip to content

Commit

Permalink
Improve implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
dwcullop committed Oct 16, 2023
1 parent 76b6376 commit 08da7ce
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reactive.Linq;
Expand All @@ -18,10 +19,14 @@ public class EditDiffChangeSetOptionalFixture
private const int MaxItems = 1097;

[Fact]
[Description("Required to maintain test coverage percentage")]
public void NullChecksArePerformed()
{
Assert.Throws<ArgumentNullException>(() => Observable.Empty<Optional<Person>>().EditDiff<Person, int>(null!));
Assert.Throws<ArgumentNullException>(() => default(IObservable<Optional<Person>>)!.EditDiff<Person, int>(null!));
Action actionNullKeySelector = () => Observable.Empty<Optional<Person>>().EditDiff<Person, int>(null!);
Action actionNullObservable = () => default(IObservable<Optional<Person>>)!.EditDiff<Person, int>(null!);

actionNullKeySelector.Should().Throw<ArgumentNullException>().WithParameterName("keySelector");
actionNullObservable.Should().Throw<ArgumentNullException>().WithParameterName("source");
}

[Fact]
Expand Down
91 changes: 52 additions & 39 deletions src/DynamicData/Cache/Internal/EditDiffChangeSetOptional.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Roland Pheasant licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System.Reactive.Disposables;
using System.Reactive.Linq;
using DynamicData.Kernel;

Expand All @@ -12,8 +11,6 @@ internal sealed class EditDiffChangeSetOptional<TObject, TKey>
where TObject : notnull
where TKey : notnull
{
private static readonly IEqualityComparer<TKey> KeyComparer = EqualityComparer<TKey>.Default;

private readonly IObservable<Optional<TObject>> _source;

private readonly IEqualityComparer<TObject> _equalityComparer;
Expand All @@ -31,49 +28,65 @@ public IObservable<IChangeSet<TObject, TKey>> Run()
{
return Observable.Create<IChangeSet<TObject, TKey>>(observer =>
{
var shared = _source.StartWith(Optional.None<TObject>()).Synchronize().Publish();
var previous = Optional.None<ValueContainer>();

var cleanup = shared.Zip(shared.Skip(1)).Select(
tuple =>
return _source.Synchronize().Subscribe(
nextValue =>
{
var previous = tuple.First;
var current = tuple.Second;
var current = nextValue.Convert(val => new ValueContainer(val, _keySelector(val)));

if (previous.HasValue && current.HasValue)
{
var previousKey = _keySelector(previous.Value);
var currentKey = _keySelector(current.Value);

if (KeyComparer.Equals(previousKey, currentKey))
{
if (!_equalityComparer.Equals(previous.Value, current.Value))
{
return new[] { new Change<TObject, TKey>(ChangeReason.Update, currentKey, current.Value, previous.Value) };
}
}
else
{
return new[] { new Change<TObject, TKey>(ChangeReason.Remove, previousKey, previous.Value), new Change<TObject, TKey>(ChangeReason.Add, currentKey, current.Value) };
}
}
else if (previous.HasValue)
// Determine the changes
var changes = (previous.HasValue, current.HasValue) switch
{
var previousKey = _keySelector(previous.Value);
return new[] { new Change<TObject, TKey>(ChangeReason.Remove, previousKey, previous.Value) };
}
else if (current.HasValue)
(true, true) => CreateUpdateChanges(previous.Value, current.Value),
(false, true) => new[] { new Change<TObject, TKey>(ChangeReason.Add, current.Value.Key, current.Value.Object) },
(true, false) => new[] { new Change<TObject, TKey>(ChangeReason.Remove, previous.Value.Key, previous.Value.Object) },
(false, false) => Array.Empty<Change<TObject, TKey>>(),
};

// Save the value for the next round
previous = current;

// If there are changes, emit as a ChangeSet
if (changes.Length > 0)
{
var currentKey = _keySelector(current.Value);
return new[] { new Change<TObject, TKey>(ChangeReason.Add, currentKey, current.Value) };
observer.OnNext(new ChangeSet<TObject, TKey>(changes));
}
}, observer.OnError, observer.OnCompleted);
});
}

return Array.Empty<Change<TObject, TKey>>();
})
.Where(changes => changes.Length > 0)
.Select(changes => new ChangeSet<TObject, TKey>(changes))
.SubscribeSafe(observer);
private Change<TObject, TKey>[] CreateUpdateChanges(in ValueContainer prev, in ValueContainer curr)
{
if (EqualityComparer<TKey>.Default.Equals(prev.Key, curr.Key))
{
// Key is the same, so Update (unless values are equal)
if (!_equalityComparer.Equals(prev.Object, curr.Object))
{
return new[] { new Change<TObject, TKey>(ChangeReason.Update, curr.Key, curr.Object, prev.Object) };
}

return new CompositeDisposable(cleanup, shared.Connect());
});
return Array.Empty<Change<TObject, TKey>>();
}

// Key Change means Remove/Add
return new[]
{
new Change<TObject, TKey>(ChangeReason.Remove, prev.Key, prev.Object),
new Change<TObject, TKey>(ChangeReason.Add, curr.Key, curr.Object)
};
}

private readonly struct ValueContainer
{
public ValueContainer(TObject obj, TKey key)
{
Object = obj;
Key = key;
}

public TObject Object { get; }

public TKey Key { get; }
}
}
1 change: 1 addition & 0 deletions src/DynamicData/Cache/ObservableCacheEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1298,6 +1298,7 @@ public static IObservable<IChangeSet<TObject, TKey>> EditDiff<TObject, TKey>(thi
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}

if (keySelector is null)
{
Expand Down

0 comments on commit 08da7ce

Please sign in to comment.