public IChangeSet <T> WriteWithPreview(Action <IExtendedList <T> > updateAction, Action <IChangeSet <T> > previewHandler) { if (updateAction == null) { throw new ArgumentNullException(nameof(updateAction)); } if (previewHandler == null) { throw new ArgumentNullException(nameof(previewHandler)); } IChangeSet <T> result; // Make a copy, apply changes on the main list, perform the preview callback with the old list and swap the lists again to finalize the update. lock (_locker) { ChangeAwareList <T> copy = new ChangeAwareList <T>(_data, false); _updateInProgress = true; updateAction(_data); _updateInProgress = false; result = _data.CaptureChanges(); InternalEx.Swap(ref _data, ref copy); previewHandler(result); InternalEx.Swap(ref _data, ref copy); } return(result); }
public IObservable <IVirtualChangeSet <T> > Run() { return(Observable.Create <IVirtualChangeSet <T> >( observer => { var locker = new object(); var all = new List <T>(); var virtualised = new ChangeAwareList <T>(); IVirtualRequest parameters = new VirtualRequest(0, 25); var requestStream = _requests.Synchronize(locker).Select( request => { parameters = request; return CheckParamsAndVirtualise(all, virtualised, request); }); var dataChanged = _source.Synchronize(locker).Select(changes => Virtualise(all, virtualised, parameters, changes)); // TODO: Remove this shared state stuff ie. _parameters return requestStream.Merge(dataChanged).Where(changes => changes is not null && changes.Count != 0) .Select(x => x !) .Select(changes => new VirtualChangeSet <T>(changes, new VirtualResponse(virtualised.Count, parameters.StartIndex, all.Count))).SubscribeSafe(observer); })); }
private void Requery(Func<T, bool> predicate, List<ItemWithMatch> all, ChangeAwareList<ItemWithMatch> filtered) { var mutatedMatches = new List<Action>(all.Count); var newState = all.Select(item => { var match = predicate(item.Item); var wasMatch = item.IsMatch; //Mutate match - defer until filtered list has been modified //[to prevent potential IndexOf failures] if (item.IsMatch != match) mutatedMatches.Add(()=> item.IsMatch = match); return new { Item = item, IsMatch = match, WasMatch = wasMatch }; }).ToList(); //reflect items which are no longer matched var noLongerMatched = newState.Where(state => !state.IsMatch && state.WasMatch).Select(state => state.Item); filtered.RemoveMany(noLongerMatched); //reflect new matches in the list var newMatched = newState.Where(state => state.IsMatch && !state.WasMatch).Select(state => state.Item); filtered.AddRange(newMatched); //finally apply mutations mutatedMatches.ForEach(m => m()); }
public IObservable <IChangeSet <T> > Run() { return(Observable.Create <IChangeSet <T> >(observer => { var locker = new object(); Func <T, bool> predicate = t => false; var all = new List <ItemWithMatch>(); var filtered = new ChangeAwareList <ItemWithMatch>(); var immutableFilter = _predicate != null; IObservable <IChangeSet <ItemWithMatch> > predicateChanged; if (immutableFilter) { predicateChanged = Observable.Never <IChangeSet <ItemWithMatch> >(); predicate = _predicate; } else { predicateChanged = _predicates .Synchronize(locker) .Select(newPredicate => { predicate = newPredicate; return Requery(predicate, all, filtered); }); } /* * Apply the transform operator so 'IsMatch' state can be evaluated and captured one time only * This is to eliminate the need to re-apply the predicate when determining whether an item was previously matched, * which is essential when we have mutable state */ //Need to get item by index and store it in the transform var filteredResult = _source .Synchronize(locker) .Transform <T, ItemWithMatch>((t, previous) => { var wasMatch = previous.ConvertOr(p => p.IsMatch, () => false); return new ItemWithMatch(t, predicate(t), wasMatch); }, true) .Select(changes => { //keep track of all changes if filtering on an observable if (!immutableFilter) { all.Clone(changes); } return Process(filtered, changes); }); return predicateChanged.Merge(filteredResult) .NotEmpty() .Select(changes => changes.Transform(iwm => iwm.Item)) // use convert, not transform .SubscribeSafe(observer); })); }
/// <summary> /// Convert a binding list into an observable change set. /// Change set observes list change events. /// </summary> /// <typeparam name="TCollection">The collection type.</typeparam> /// <typeparam name="T">The type of the object.</typeparam> /// <param name="source">The source.</param> /// <returns>An observable which emits change set values.</returns> /// <exception cref="System.ArgumentNullException">source.</exception> public static IObservable <IChangeSet <T> > ToObservableChangeSet <TCollection, T>(this TCollection source) where TCollection : IBindingList, IEnumerable <T> { if (source is null) { throw new ArgumentNullException(nameof(source)); } return(Observable.Create <IChangeSet <T> >( observer => { var data = new ChangeAwareList <T>(source); if (data.Count > 0) { observer.OnNext(data.CaptureChanges()); } return source.ObserveCollectionChanges().Scan( data, (list, args) => { var changes = args.EventArgs; switch (changes.ListChangedType) { case ListChangedType.ItemAdded when source[changes.NewIndex] is T newItem: { list.Add(newItem); break; }
private IObservable <IChangeSet <TDestination> > CreateWithChangeset() { return(Observable.Create <IChangeSet <TDestination> >(observer => { var result = new ChangeAwareList <TDestination>(); var transformed = _source.Transform(t => { var locker = new object(); var collection = _manyselector(t); var changes = _childChanges(t).Synchronize(locker).Skip(1); return new ManyContainer(collection, changes); }) .Publish(); var outerLock = new object(); var intial = transformed .Synchronize(outerLock) .Select(changes => new ChangeSet <TDestination>(new DestinationEnumerator(changes, _equalityComparer))); var subsequent = transformed .MergeMany(x => x.Changes) .Synchronize(outerLock); var allChanges = intial.Merge(subsequent).Select(changes => { result.Clone(changes); return result.CaptureChanges(); }); return new CompositeDisposable(allChanges.SubscribeSafe(observer), transformed.Connect()); })); }
public IObservable <IChangeSet <TDestination> > Run() { if (_childChanges != null) { return(CreateWithChangeset()); } return(Observable.Create <IChangeSet <TDestination> >(observer => { //NB: ChangeAwareList is used internally by dd to capture changes to a list and ensure they can be replayed by subsequent operators var result = new ChangeAwareList <TDestination>(); return _source.Transform(item => new ManyContainer(_manyselector(item).ToArray()), true) .Select(changes => { var destinationChanges = new ChangeSet <TDestination>(new DestinationEnumerator(changes, _equalityComparer)); result.Clone(destinationChanges, _equalityComparer); return result.CaptureChanges(); }) .NotEmpty() .SubscribeSafe(observer); } )); }
public IObservable <IChangeSet <T> > Run() { return(Observable.Create <IChangeSet <T> >( observer => { var locker = new object(); var original = new List <T>(); var target = new ChangeAwareList <T>(); var changed = _source.Synchronize(locker).Select( changes => { if (_resetThreshold > 1) { original.Clone(changes); } return changes.TotalChanges > _resetThreshold ? Reset(original, target) : Process(target, changes); }); var resort = _resort.Synchronize(locker).Select(_ => Reorder(target)); var changeComparer = _comparerObservable.Synchronize(locker).Select(comparer => ChangeComparer(target, comparer)); return changed.Merge(resort).Merge(changeComparer).Where(changes => changes.Count != 0).SubscribeSafe(observer); })); }
public IObservable <IChangeSet <IGroup <TObject, TGroupKey> > > Run() { return(Observable.Create <IChangeSet <IGroup <TObject, TGroupKey> > >( observer => { var groupings = new ChangeAwareList <IGroup <TObject, TGroupKey> >(); var groupCache = new Dictionary <TGroupKey, Group <TObject, TGroupKey> >(); // capture the grouping up front which has the benefit that the group key is only selected once var itemsWithGroup = _source.Transform <TObject, ItemWithGroupKey>((t, previous) => new ItemWithGroupKey(t, _groupSelector(t), previous.Convert(p => p.Group)), true); var locker = new object(); var shared = itemsWithGroup.Synchronize(locker).Publish(); var grouper = shared.Select(changes => Process(groupings, groupCache, changes)); IObservable <IChangeSet <IGroup <TObject, TGroupKey> > > regrouper = _regrouper is null ? Observable.Never <IChangeSet <IGroup <TObject, TGroupKey> > >() : _regrouper.Synchronize(locker).CombineLatest(shared.ToCollection(), (_, collection) => Regroup(groupings, groupCache, collection)); var publisher = grouper.Merge(regrouper).DisposeMany() // dispose removes as the grouping is disposable .NotEmpty().SubscribeSafe(observer); return new CompositeDisposable(publisher, shared.Connect()); })); }
private IChangeSet <IGrouping <TObject, TGroupKey> > CreateChangeSet(ChangeAwareList <IGrouping <TObject, TGroupKey> > result, IDictionary <TGroupKey, GroupContainer> allGroupings, IDictionary <TGroupKey, IGrouping <TObject, TGroupKey> > initialStateOfGroups) { //Now maintain target list foreach (var intialGroup in initialStateOfGroups) { var key = intialGroup.Key; var current = allGroupings[intialGroup.Key]; if (current.List.Count == 0) { //remove if empty allGroupings.Remove(key); result.Remove(intialGroup.Value); } else { var currentState = GetGroupState(current); if (intialGroup.Value.Count == 0) { //an add result.Add(currentState); } else { //a replace (or add if the old group has already been removed) result.Replace(intialGroup.Value, currentState); } } } return(result.CaptureChanges()); }
public IObservable <IPageChangeSet <T> > Run() { return(Observable.Create <IPageChangeSet <T> >(observer => { var locker = new object(); var all = new List <T>(); var paged = new ChangeAwareList <T>(); IPageRequest parameters = new PageRequest(0, 25); var requestStream = _requests .Synchronize(locker) .Select(request => { parameters = request; return CheckParametersAndPage(all, paged, request); }); var datachanged = _source .Synchronize(locker) .Select(changes => Page(all, paged, parameters, changes)); return requestStream.Merge(datachanged) .Where(changes => changes != null && changes.Count != 0) .SubscribeSafe(observer); })); }
private void Process(ChangeAwareList <T> filtered, IChangeSet <T> changes) { foreach (var item in changes) { switch (item.Reason) { case ListChangeReason.Add: { var change = item.Item; if (_predicate(change.Current)) { filtered.Add(change.Current); } break; } case ListChangeReason.AddRange: { var matches = item.Range.Where(t => _predicate(t)).ToList(); filtered.AddRange(matches); break; } case ListChangeReason.Replace: { var change = item.Item; var match = _predicate(change.Current); if (match) { filtered.ReplaceOrAdd(change.Previous.Value, change.Current); } else { filtered.Remove(change.Previous.Value); } break; } case ListChangeReason.Remove: { filtered.Remove(item.Item.Current); break; } case ListChangeReason.RemoveRange: { filtered.RemoveMany(item.Range); break; } case ListChangeReason.Clear: { filtered.ClearOrRemoveMany(item); break; } } } }
public IObservable <IChangeSet <List.IGrouping <TObject, TGroupKey> > > Run() { return(Observable.Create <IChangeSet <IGrouping <TObject, TGroupKey> > >(observer => { var groupings = new ChangeAwareList <IGrouping <TObject, TGroupKey> >(); var groupCache = new Dictionary <TGroupKey, GroupContainer>(); var itemsWithGroup = _source .Transform(t => new ItemWithValue <TObject, TGroupKey>(t, _groupSelector(t))); var locker = new object(); var shared = itemsWithGroup.Synchronize(locker).Publish(); var grouper = shared .Select(changes => Process(groupings, groupCache, changes)); IObservable <IChangeSet <IGrouping <TObject, TGroupKey> > > regrouper; if (_regrouper == null) { regrouper = Observable.Never <IChangeSet <IGrouping <TObject, TGroupKey> > >(); } else { regrouper = _regrouper.Synchronize(locker) .CombineLatest(shared.ToCollection(), (_, collection) => Regroup(groupings, groupCache, collection)); } var publisher = grouper.Merge(regrouper) .NotEmpty() .SubscribeSafe(observer); return new CompositeDisposable(publisher, shared.Connect()); })); }
public IObservable <IChangeSet <T> > Run() { return(Observable.Create <IChangeSet <T> >(observer => { long orderItemWasAdded = -1; var locker = new object(); var sourceList = new ChangeAwareList <ExpirableItem <T> >(); var sizeLimited = _source.Synchronize(locker) .Scan(sourceList, (state, latest) => { var items = latest.AsArray(); var expirable = items.Select(t => CreateExpirableItem(t, ref orderItemWasAdded)); if (items.Length == 1) { sourceList.Add(expirable); } else { sourceList.AddRange(expirable); } if (_limitSizeTo > 0 && state.Count > _limitSizeTo) { //remove oldest items [these will always be the first x in the list] var toRemove = state.Count - _limitSizeTo; state.RemoveRange(0, toRemove); } return state; }) .Select(state => state.CaptureChanges()) .Publish(); var timeLimited = (_expireAfter == null ? Observable.Never <IChangeSet <ExpirableItem <T> > >() : sizeLimited) .Filter(ei => ei.ExpireAt != DateTime.MaxValue) .GroupWithImmutableState(ei => ei.ExpireAt) .MergeMany(grouping => { var expireAt = grouping.Key.Subtract(_scheduler.Now.DateTime); return Observable.Timer(expireAt, _scheduler).Select(_ => grouping); }) .Synchronize(locker) .Select(grouping => { sourceList.RemoveMany(grouping.Items); return sourceList.CaptureChanges(); }); var publisher = sizeLimited .Merge(timeLimited) .Cast(ei => ei.Item) .NotEmpty() .SubscribeSafe(observer); return new CompositeDisposable(publisher, sizeLimited.Connect()); })); }
private IChangeSet <T> Reset(List <T> original, ChangeAwareList <T> target) { var sorted = original.OrderBy(t => t, _comparer).ToList(); target.Clear(); target.AddRange(sorted); return(target.CaptureChanges()); }
private static IChangeSet <T>?CheckParamsAndVirtualise(IList <T> all, ChangeAwareList <T> virtualised, IVirtualRequest?request) { if (request is null || request.StartIndex < 0 || request.Size < 1) { return(null); } return(Virtualise(all, virtualised, request)); }
private PageChangeSet <T> CheckParametersAndPage(List <T> all, ChangeAwareList <T> paged, IPageRequest request) { if (request == null || request.Page < 0 || request.Size < 1) { return(null); } return(Page(all, paged, request)); }
private IChangeSet <T> ChangeComparer(ChangeAwareList <T> target, IComparer <T> comparer) { _comparer = comparer; var sorted = target.OrderBy(t => t, _comparer).ToList(); target.Clear(); target.AddRange(sorted); return(target.CaptureChanges()); }
/// <summary> /// Convert a binding list into an observable change set /// </summary> /// <typeparam name="T">The type of the object.</typeparam> /// <typeparam name="TCollection"></typeparam> /// <param name="source">The source.</param> /// <returns></returns> /// <exception cref="System.ArgumentNullException">source</exception> public static IObservable <IChangeSet <T> > ToObservableChangeSet <TCollection, T>(this TCollection source) where TCollection : IBindingList, IEnumerable <T> { if (source == null) { throw new ArgumentNullException(nameof(source)); } return(Observable.Create <IChangeSet <T> >(observer => { var data = new ChangeAwareList <T>(source); if (data.Count > 0) { observer.OnNext(data.CaptureChanges()); } return source.ObserveCollectionChanges() .Scan(data, (list, args) => { var changes = args.EventArgs; switch (changes.ListChangedType) { case ListChangedType.ItemAdded: { list.Add((T)source[changes.NewIndex]); break; } case ListChangedType.ItemDeleted: { list.RemoveAt(changes.NewIndex); break; } case ListChangedType.ItemChanged: { list[changes.NewIndex] = (T)source[changes.NewIndex]; break; } case ListChangedType.Reset: { list.Clear(); list.AddRange(source); break; } } return list; }) .Select(list => list.CaptureChanges()) .SubscribeSafe(observer); })); }
private int GetCurrentPosition(ChangeAwareList <T> target, T item) { var index = _sortOptions == SortOptions.UseBinarySearch ? target.BinarySearch(item, _comparer) : target.IndexOf(item); if (index < 0) { throw new SortException($"Cannot find item: {typeof(T).Name} -> {item}"); } return(index); }
private int GetInsertPositionLinear(ChangeAwareList <T> target, T item) { for (var i = 0; i < target.Count; i++) { if (_comparer.Compare(item, target[i]) < 0) { return(i); } } return(target.Count); }
private void Requery(Func <T, bool> predicate, List <ItemWithMatch> all, ChangeAwareList <ItemWithMatch> filtered) { if (_policy == ListFilterPolicy.ClearAndReplace) { var newMatches = all.Where(iwm => predicate(iwm.Item)).ToList(); //mark items as matched? filtered.Clear(); filtered.AddRange(newMatches); //reset state all.Where(iwm => iwm.IsMatch).ForEach(iwm => iwm.IsMatch = false); newMatches.ForEach(iwm => iwm.IsMatch = true); return; } var mutatedMatches = new List <Action>(all.Count); var newState = all.Select(item => { var match = predicate(item.Item); var wasMatch = item.IsMatch; //Mutate match - defer until filtered list has been modified //[to prevent potential IndexOf failures] if (item.IsMatch != match) { mutatedMatches.Add(() => item.IsMatch = match); } return(new { Item = item, IsMatch = match, WasMatch = wasMatch }); }).ToList(); //reflect items which are no longer matched var noLongerMatched = newState.Where(state => !state.IsMatch && state.WasMatch).Select(state => state.Item); filtered.RemoveMany(noLongerMatched); //reflect new matches in the list var newMatched = newState.Where(state => state.IsMatch && !state.WasMatch).Select(state => state.Item); filtered.AddRange(newMatched); //finally apply mutations mutatedMatches.ForEach(m => m()); }
private IChangeSet <T> Process(ChangeAwareList <T> target, IChangeSet <T> changes) { // if all removes and not Clear, then more efficient to try clear range if (changes.TotalChanges == changes.Removes && changes.All(c => c.Reason != ListChangeReason.Clear) && changes.Removes > 1) { var removed = changes.Unified().Select(u => u.Current); target.RemoveMany(removed); return(target.CaptureChanges()); } return(ProcessImpl(target, changes)); }
private int GetInsertPositionBinary(ChangeAwareList <T> target, T item) { int index = target.BinarySearch(item, _comparer); int insertIndex = ~index; //sort is not returning uniqueness if (insertIndex < 0) { throw new SortException("Binary search has been specified, yet the sort does not yeild uniqueness"); } return(insertIndex); }
public IObservable <IChangeSet <TValue> > Run() { return(Observable.Create <IChangeSet <TValue> >( observer => { var valueCounters = new Dictionary <TValue, int>(); var result = new ChangeAwareList <TValue>(); return _source.Transform <T, ItemWithMatch>( (t, previous, _) => { var previousValue = previous.ConvertOr(p => p is null ? default : p.Value, () => default);
private static IChangeSet <T> Virtualise(List <T> all, ChangeAwareList <T> virtualised, IVirtualRequest request, IChangeSet <T> changeset = null) { if (changeset != null) { all.Clone(changeset); } var previous = virtualised; var current = all.Skip(request.StartIndex) .Take(request.Size) .ToList(); var adds = current.Except(previous); var removes = previous.Except(current); virtualised.RemoveMany(removes); adds.ForEach(t => { var index = current.IndexOf(t); virtualised.Insert(index, t); }); var moves = changeset.EmptyIfNull() .Where(change => change.Reason == ListChangeReason.Moved && change.MovedWithinRange(request.StartIndex, request.StartIndex + request.Size)); foreach (var change in moves) { //check whether an item has moved within the same page var currentIndex = change.Item.CurrentIndex - request.StartIndex; var previousIndex = change.Item.PreviousIndex - request.StartIndex; virtualised.Move(previousIndex, currentIndex); } //find replaces [Is this ever the case that it can be reached] for (var i = 0; i < current.Count; i++) { var currentItem = current[i]; var previousItem = previous[i]; if (ReferenceEquals(currentItem, previousItem)) { continue; } var index = virtualised.IndexOf(currentItem); virtualised.Move(i, index); } return(virtualised.CaptureChanges()); }
public IObservable <IChangeSet <TValue> > Run() { return(Observable.Create <IChangeSet <TValue> >(observer => { var valueCounters = new Dictionary <TValue, int>(); var result = new ChangeAwareList <TValue>(); return _source.Transform(t => new ItemWithValue <T, TValue>(t, _valueSelector(t))) .Select(changes => Process(valueCounters, result, changes)) .NotEmpty() .SubscribeSafe(observer); })); }
private int GetCurrentPosition(ChangeAwareList <T> target, T item) { int index = _sortOptions == SortOptions.UseBinarySearch ? target.BinarySearch(item, _comparer) : target.IndexOf(item); if (index < 0) { throw new SortException("Current item cannot be found"); } return(index); }
private IChangeSet <T> ChangeComparer(ChangeAwareList <T> target, IComparer <T> comparer) { _comparer = comparer; if (_resetThreshold > 0 && target.Count <= _resetThreshold) { return(Reorder(target)); } var sorted = target.OrderBy(t => t, _comparer).ToList(); target.Clear(); target.AddRange(sorted); return(target.CaptureChanges()); }
public IObservable <IChangeSet <TValue> > Run() { return(Observable.Create <IChangeSet <TValue> >(observer => { var valueCounters = new Dictionary <TValue, int>(); var result = new ChangeAwareList <TValue>(); return _source.Transform <T, ItemWithMatch>((t, previous, idx) => { var previousValue = previous.ConvertOr(p => p.Value, () => default(TValue)); return new ItemWithMatch(t, _valueSelector(t), previousValue); }, true) .Select(changes => Process(valueCounters, result, changes)) .NotEmpty() .SubscribeSafe(observer); })); }
public void Setup() { _source = new ChangeAwareList<int>(); _clone = new List<int>(); }
public void Setup() { _list = new ChangeAwareList<int>(); }