< Summary

Information
Class: MyNet.Observable.Collections.Sources.SourceEngine<T>
Assembly: MyNet.Observable
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Observable/Collections/Sources/SourceEngine.cs
Tag: 323_28699572109
Line coverage
71%
Covered lines: 59
Uncovered lines: 23
Coverable lines: 82
Total lines: 295
Line coverage: 71.9%
Branch coverage
60%
Covered branches: 26
Total branches: 43
Branch coverage: 60.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.ctor(...)100%11100%
.ctor(...)100%11100%
get_Count()100%22100%
Empty()100%11100%
From(...)100%11100%
FromObservable(...)100%11100%
FromSnapshot(...)100%11100%
FromProvider(...)100%210%
Materialize(...)0%620%
ReloadSnapshot()50%66100%
Refresh()50%2275%
Connect()100%11100%
Add(...)50%22100%
AddRange(...)50%22100%
Remove(...)50%22100%
RemoveMany(...)50%22100%
Clear()50%22100%
Set(...)0%620%
Edit(...)0%620%
EnsureWritable()100%22100%
TrackExternalCount(...)81.81%131175%
Dispose()100%44100%

File(s)

https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Observable/Collections/Sources/SourceEngine.cs

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="SourceEngine.cs" company="Stéphane ANDRE">
 3// Copyright (c) Stéphane ANDRE. All rights reserved.
 4// </copyright>
 5// -----------------------------------------------------------------------
 6
 7using System;
 8using System.Collections.Generic;
 9using DynamicData;
 10using MyNet.Primitives.Providers;
 11
 12namespace MyNet.Observable.Collections.Sources;
 13
 14/// <summary>
 15/// Represents a source engine that manages a collection of items of type T and provides an observable stream of changes
 16/// </summary>
 17/// <typeparam name="T">The type of items managed by the source engine.</typeparam>
 18public sealed class SourceEngine<T> : ISourceEngine<T>, ISourceWriter<T>, IRefreshableSource
 19    where T : notnull
 20{
 21    private readonly SourceList<T>? _source;
 22    private readonly Func<IEnumerable<T>>? _snapshotFactory;
 23    private readonly IObservable<IChangeSet<T>> _external;
 24    private readonly IDisposable? _externalCountSubscription;
 25    private int _externalCount;
 26
 27    /// <summary>
 28    /// Initializes a new instance of the <see cref="SourceEngine{T}"/> class with the specified source list and collect
 29    /// </summary>
 30    /// <param name="source">The source list managed by the source engine.</param>
 31    /// <param name="isReadOnly">Indicates whether the source engine is read-only.</param>
 32    private SourceEngine(SourceList<T> source, bool isReadOnly)
 33    {
 21634        _source = source;
 21635        _external = source.Connect();
 21636        IsReadOnly = isReadOnly;
 21637    }
 38
 39    /// <summary>
 40    /// Initializes a new instance of the <see cref="SourceEngine{T}"/> class with the specified snapshot factory. This 
 41    /// </summary>
 42    /// <param name="snapshotFactory">The function that provides a snapshot of the collection's state.</param>
 43    private SourceEngine(Func<IEnumerable<T>> snapshotFactory)
 644        : this(new SourceList<T>(), true)
 45    {
 646        _snapshotFactory = snapshotFactory;
 647        ReloadSnapshot();
 648    }
 49
 50    /// <summary>
 51    /// Initializes a new instance of the <see cref="SourceEngine{T}"/> class with the specified external observable str
 52    /// </summary>
 53    /// <param name="external">The external observable stream of changes.</param>
 54    /// <param name="isReadOnly">Indicates whether the source engine is read-only.</param>
 55    private SourceEngine(IObservable<IChangeSet<T>> external, bool isReadOnly)
 56    {
 357        _external = external;
 358        IsReadOnly = isReadOnly;
 359        _externalCountSubscription = external.Subscribe(TrackExternalCount);
 360    }
 61
 62    /// <summary>
 63    /// Gets a value indicating whether the source engine is in read-only snapshot mode. If this property is true, it me
 64    /// </summary>
 65    public bool IsReadOnly { get; }
 66
 67    /// <summary>
 68    /// Gets the number of items currently in the collection managed by the source engine. This property allows subscrib
 69    /// </summary>
 44170    public int Count => _source?.Count ?? _externalCount;
 71
 72    #region Factories
 73
 74    /// <summary>
 75    /// Creates a new instance of the <see cref="SourceEngine{T}"/> class in owned mutable mode, where the collection is
 76    /// </summary>
 77    /// <returns>A new instance of the <see cref="SourceEngine{T}"/> class.</returns>
 4878    public static SourceEngine<T> Empty() => new(new SourceList<T>(), false);
 79
 80    /// <summary>
 81    /// Creates a new instance of the <see cref="SourceEngine{T}"/> class from an enumerable collection of items. This m
 82    /// </summary>
 83    /// <param name="items">The initial set of items for the source engine.</param>
 84    /// <param name="readOnly">Determines whether the source engine operates in read-only snapshot mode.</param>
 85    /// <returns>A new instance of the <see cref="SourceEngine{T}"/> class.</returns>
 86    public static SourceEngine<T> From(IEnumerable<T> items, bool readOnly)
 87    {
 16288        var list = new SourceList<T>();
 16289        list.AddRange(items);
 90
 16291        return new(list, readOnly);
 92    }
 93
 94    /// <summary>
 95    /// Creates a new instance of the <see cref="SourceEngine{T}"/> class from an external observable stream of changes.
 96    /// </summary>
 97    /// <param name="source">The external observable stream of changes.</param>
 98    /// <returns>A new instance of the <see cref="SourceEngine{T}"/> class.</returns>
 399    public static SourceEngine<T> FromObservable(IObservable<IChangeSet<T>> source) => new(source, true);
 100
 101    /// <summary>
 102    /// Creates a new instance of the <see cref="SourceEngine{T}"/> class from a snapshot factory function. This method 
 103    /// </summary>
 104    /// <param name="factory">The snapshot factory function that provides the current state of the collection.</param>
 105    /// <returns>A new instance of the <see cref="SourceEngine{T}"/> class.</returns>
 6106    public static SourceEngine<T> FromSnapshot(Func<IEnumerable<T>> factory) => new(factory);
 107
 108    /// <summary>
 109    /// Creates a new instance of the <see cref="SourceEngine{T}"/> class from an items provider. This method allows sub
 110    /// </summary>
 111    /// <param name="provider">The items provider that provides the current state of the collection.</param>
 112    /// <returns>A new instance of the <see cref="SourceEngine{T}"/> class.</returns>
 113    public static SourceEngine<T> FromProvider(IItemsProvider<T> provider) =>
 0114        FromSnapshot(() => Materialize(provider.GetItemsAsync()));
 115
 116    #endregion
 117
 118    private static List<T> Materialize(IAsyncEnumerable<T> source)
 119    {
 0120        var list = new List<T>();
 0121        var enumerator = source.GetAsyncEnumerator();
 122
 123        try
 124        {
 0125            while (enumerator.MoveNextAsync().AsTask().GetAwaiter().GetResult())
 0126                list.Add(enumerator.Current);
 0127        }
 128        finally
 129        {
 0130            enumerator.DisposeAsync().AsTask().GetAwaiter().GetResult();
 0131        }
 132
 0133        return list;
 134    }
 135
 136    /// <summary>
 137    /// Reloads the snapshot of the collection by calling the snapshot factory function to get the current state of the 
 138    /// </summary>
 139    private void ReloadSnapshot()
 140    {
 9141        var items = _snapshotFactory?.Invoke() ?? throw new InvalidOperationException("Snapshot factory is not defined."
 142
 9143        _source?.Edit(list =>
 9144        {
 9145            list.Clear();
 9146            list.AddRange(items);
 9147        });
 9148    }
 149
 150    /// <summary>
 151    /// Refreshes the snapshot of the collection by calling the ReloadSnapshot method to update the internal source list
 152    /// </summary>
 153    /// <exception cref="InvalidOperationException">Thrown if the source engine is not operating in read-only snapshot m
 154    public void Refresh()
 155    {
 3156        if (_snapshotFactory is null)
 0157            throw new InvalidOperationException("Refresh is only supported for snapshot sources.");
 158
 3159        ReloadSnapshot();
 3160    }
 161
 162    /// <summary>
 163    /// Connects to the source engine and returns an observable stream of changes to the collection. Subscribers can use
 164    /// </summary>
 165    /// <returns>An observable stream of changes to the collection.</returns>
 372166    public IObservable<IChangeSet<T>> Connect() => _external;
 167
 168    /// <summary>
 169    /// Adds an item to the collection managed by the source engine. This method allows subscribers to easily add new it
 170    /// </summary>
 171    /// <param name="item">The item to add to the collection.</param>
 172    public void Add(T item)
 173    {
 21174        EnsureWritable();
 18175        _source?.Add(item);
 18176    }
 177
 178    /// <summary>
 179    /// Adds a range of items to the collection managed by the source engine. This method allows subscribers to easily a
 180    /// </summary>
 181    /// <param name="items">The items to add to the collection.</param>
 182    public void AddRange(IEnumerable<T> items)
 183    {
 33184        EnsureWritable();
 33185        _source?.AddRange(items);
 33186    }
 187
 188    /// <summary>
 189    /// Removes an item from the collection managed by the source engine. This method allows subscribers to easily remov
 190    /// </summary>
 191    /// <param name="item">The item to remove from the collection.</param>
 192    /// <returns>True if the item was successfully removed; otherwise, false.</returns>
 193    public bool Remove(T item)
 194    {
 12195        EnsureWritable();
 12196        return _source?.Remove(item) ?? false;
 197    }
 198
 199    /// <summary>
 200    /// Removes a range of items from the collection managed by the source engine. This method allows subscribers to eas
 201    /// </summary>
 202    /// <param name="items">The items to remove from the collection.</param>
 203    public void RemoveMany(IEnumerable<T> items)
 204    {
 3205        EnsureWritable();
 3206        _source?.RemoveMany(items);
 3207    }
 208
 209    /// <summary>
 210    /// Clears all items from the collection managed by the source engine. This method allows subscribers to easily remo
 211    /// </summary>
 212    public void Clear()
 213    {
 6214        EnsureWritable();
 6215        _source?.Clear();
 6216    }
 217
 218    /// <summary>
 219    /// Sets the collection managed by the source engine to the specified items. This method allows subscribers to easil
 220    /// </summary>
 221    /// <param name="items">The items to set in the collection.</param>
 222    public void Set(IEnumerable<T> items)
 223    {
 0224        EnsureWritable();
 0225        _source?.Edit(x =>
 0226        {
 0227            x.Clear();
 0228            x.AddRange(items);
 0229        });
 0230    }
 231
 232    /// <summary>
 233    /// Edits the collection managed by the source engine using the specified update action. This method allows subscrib
 234    /// </summary>
 235    /// <param name="update">The action to perform on the collection.</param>
 236    public void Edit(Action<IExtendedList<T>> update)
 237    {
 0238        EnsureWritable();
 0239        _source?.Edit(update);
 0240    }
 241
 242    /// <summary>
 243    /// Ensures that the source engine is in a writable mode before allowing modifications to the collection. If the sou
 244    /// </summary>
 245    /// <exception cref="InvalidOperationException">Thrown when the source engine is in read-only snapshot mode and modi
 246    private void EnsureWritable()
 247    {
 75248        if (IsReadOnly)
 3249            throw new InvalidOperationException("Collection is read only and cannot be modified.");
 72250    }
 251
 252    /// <summary>
 253    /// Tracks the count of items in the collection when the source engine is operating in external live mode, where the
 254    /// </summary>
 255    /// <param name="changes">The set of changes to the collection.</param>
 256    private void TrackExternalCount(IChangeSet<T> changes)
 257    {
 36258        foreach (var change in changes)
 259        {
 9260            switch (change.Reason)
 261            {
 262                case ListChangeReason.Add:
 3263                    _externalCount++;
 3264                    break;
 265                case ListChangeReason.AddRange:
 3266                    _externalCount += change.Range.Count;
 3267                    break;
 268                case ListChangeReason.Remove:
 3269                    _externalCount = Math.Max(0, _externalCount - 1);
 3270                    break;
 271                case ListChangeReason.RemoveRange:
 0272                    _externalCount = Math.Max(0, _externalCount - change.Range.Count);
 0273                    break;
 274                case ListChangeReason.Clear:
 0275                    _externalCount = 0;
 276                    break;
 277                case ListChangeReason.Replace:
 278                case ListChangeReason.Moved:
 279                case ListChangeReason.Refresh:
 280                default:
 281                    break;
 282            }
 283        }
 9284    }
 285
 286    /// <summary>
 287    /// Disposes the source engine and releases all resources. This method ensures that any subscriptions and resources 
 288    /// </summary>
 289    public void Dispose()
 290    {
 189291        _externalCountSubscription?.Dispose();
 189292        _source?.Dispose();
 186293    }
 294}
 295