public IObservable <IChangeSet <TDestination, TKey> > Run() { return(Observable.Create <IChangeSet <TDestination, TKey> >(observer => { var cache = new ChangeAwareCache <TransformedItemContainer, TKey>(); var transformer = _source.SelectTask(changes => DoTransform(cache, changes)); if (_forceTransform != null) { var locker = new object(); var forced = _forceTransform .Synchronize(locker) .SelectTask(shouldTransform => DoTransform(cache, shouldTransform)); transformer = transformer.Synchronize(locker).Merge(forced); } return transformer.SubscribeSafe(observer); })); }
private void ProcessChanges(ChangeAwareCache <TObject, TKey> target, MergeContainer[] sourceLists, IEnumerable <KeyValuePair <TKey, TObject> > items) { //check whether the item should be removed from the list (or in the case of And, added) if (items is IList <KeyValuePair <TKey, TObject> > list) { //zero allocation enumerator var enumerable = EnumerableIList.Create(list); foreach (var item in enumerable) { ProcessItem(target, sourceLists, item.Value, item.Key); } } else { foreach (var item in items) { ProcessItem(target, sourceLists, item.Value, item.Key); } } }
private IChangeSet <TDestination, TKey> ProcessUpdates(ChangeAwareCache <TransformedItemContainer, TKey> cache, TransformResult[] transformedItems) { //check for errors and callback if a handler has been specified var errors = transformedItems.Where(t => !t.Success).ToArray(); if (errors.Any()) { errors.ForEach(t => _exceptionCallback(new Error <TSource, TKey>(t.Error, t.Change.Current, t.Change.Key))); } foreach (var result in transformedItems.Where(t => t.Success)) { TKey key = result.Key; switch (result.Change.Reason) { case ChangeReason.Add: case ChangeReason.Update: cache.AddOrUpdate(result.Container.Value, key); break; case ChangeReason.Remove: cache.Remove(key); break; case ChangeReason.Evaluate: cache.Evaluate(key); break; } } var changes = cache.CaptureChanges(); var transformed = changes.Select(change => new Change <TDestination, TKey>(change.Reason, change.Key, change.Current.Destination, change.Previous.Convert(x => x.Destination), change.CurrentIndex, change.PreviousIndex)); return(new ChangeSet <TDestination, TKey>(transformed)); }
private IObservable <IChangeSet <TDestination, TDestinationKey> > CreateWithChangeset() { return(Observable.Create <IChangeSet <TDestination, TDestinationKey> >(observer => { var result = new ChangeAwareCache <TDestination, TDestinationKey>(); var transformed = _source.Transform((t, key) => { //Only skip initial for first time Adds where there is isinitial data records var locker = new object(); var collection = _manyselector(t); var changes = _childChanges(t).Synchronize(locker).Skip(1); return new ManyContainer(() => { lock (locker) return collection .Select(m => new DestinationContainer(m, _keySelector(m))) .ToArray(); }, changes); }).Publish(); var outerLock = new object(); var intial = transformed .Synchronize(outerLock) .Select(changes => new ChangeSet <TDestination, TDestinationKey>(new DestinationEnumerator(changes))); 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()); })); }
private IChangeSet <TDestination, TKey> ProcessUpdates(ChangeAwareCache <TransformedItemContainer, TKey> cache, IEnumerable <TransformResult> transformedItems) { foreach (var result in transformedItems) { if (result.Success) { switch (result.Change.Reason) { case ChangeReason.Add: case ChangeReason.Update: cache.AddOrUpdate(result.Container.Value, result.Key); break; case ChangeReason.Remove: cache.Remove(result.Key); break; case ChangeReason.Refresh: cache.Refresh(result.Key); break; } } else { _exceptionCallback(new Error <TSource, TKey>(result.Error, result.Change.Current, result.Change.Key)); } } var changes = cache.CaptureChanges(); var transformed = changes.Select(change => new Change <TDestination, TKey>(change.Reason, change.Key, change.Current.Destination, change.Previous.Convert(x => x.Destination), change.CurrentIndex, change.PreviousIndex)); return(new ChangeSet <TDestination, TKey>(transformed)); }
public IObservable <IChangeSet <TObject, TKey> > Run() { return(Observable.Create <IChangeSet <TObject, TKey> >( observer => { var allData = new Cache <TObject, TKey>(); var filteredData = new ChangeAwareCache <TObject, TKey>(); Func <TObject, bool> predicate = _ => false; var locker = new object(); var refresher = LatestPredicateObservable().Synchronize(locker).Select( p => { // set the local predicate predicate = p; // reapply filter using all data from the cache return filteredData.RefreshFilteredFrom(allData, predicate); }); var dataChanged = _source.Synchronize(locker).Select( changes => { // maintain all data [required to re-apply filter] allData.Clone(changes); // maintain filtered data filteredData.FilterChanges(changes, predicate); // get latest changes return filteredData.CaptureChanges(); }); return refresher.Merge(dataChanged).NotEmpty().SubscribeSafe(observer); })); }
private void ProcessItem(ChangeAwareCache <TObject, TKey> target, MergeContainer[] sourceLists, TObject item, TKey key) { var cached = target.Lookup(key); var shouldBeInResult = MatchesConstraint(sourceLists, key); if (shouldBeInResult) { if (!cached.HasValue) { target.AddOrUpdate(item, key); } else if (!ReferenceEquals(item, cached.Value)) { target.AddOrUpdate(item, key); } } else { if (cached.HasValue) { target.Remove(key); } } }
public CacheUpdater(ChangeAwareCache <TObject, TKey> cache, IKeySelector <TObject, TKey> keySelector = null) { _cache = cache ?? throw new ArgumentNullException(nameof(cache)); _keySelector = keySelector; }
/// <summary> /// Initialises the specified changes. /// </summary> /// <param name="cache">The cache.</param> /// <returns></returns> public void Reset(ChangeAwareCache <TObject, TKey> cache) { _list = cache.KeyValues.OrderBy(kv => kv, _comparer).ToList(); }
private async Task <IChangeSet <TDestination, TKey> > DoTransform(ChangeAwareCache <TransformedItemContainer, TKey> cache, IChangeSet <TSource, TKey> changes) { var transformed = await changes.SelectParallel(Transform, _maximumConcurrency); return(ProcessUpdates(cache, transformed.ToArray())); }
protected virtual TransformResult[] TransformChanges(ChangeAwareCache <TransformedItemContainer, TKey> cache, IEnumerable <Change <TSource, TKey> > changes) { return(changes.Select(Select).AsArray()); }
protected AbstractFilter(ChangeAwareCache <TObject, TKey> cache, Func <TObject, bool>?filter) { _cache = cache ?? throw new ArgumentNullException(nameof(cache)); Filter = filter ?? (_ => true); }
public IObservable <IChangeSet <TObject, TKey> > Run() { return(Observable.Create <IChangeSet <TObject, TKey> >( observer => { long orderItemWasAdded = -1; var locker = new object(); if (_expireAfter is null && _limitSizeTo < 1) { return _source.Scan( new ChangeAwareCache <TObject, TKey>(), (state, latest) => { if (latest is IList <TObject> list) { // zero allocation enumerator var enumerableList = EnumerableIList.Create(list); if (!_singleValueSource) { state.Remove(state.Keys.Except(enumerableList.Select(_keySelector)).ToList()); } foreach (var item in enumerableList) { state.AddOrUpdate(item, _keySelector(item)); } } else { var enumerable = latest.ToList(); if (!_singleValueSource) { state.Remove(state.Keys.Except(enumerable.Select(_keySelector)).ToList()); } foreach (var item in enumerable) { state.AddOrUpdate(item, _keySelector(item)); } } return state; }).Select(state => state.CaptureChanges()).SubscribeSafe(observer); } var cache = new ChangeAwareCache <ExpirableItem <TObject, TKey>, TKey>(); var sizeLimited = _source.Synchronize(locker).Scan( cache, (state, latest) => { latest.Select( t => { var key = _keySelector(t); return CreateExpirableItem(t, key, ref orderItemWasAdded); }).ForEach(ei => cache.AddOrUpdate(ei, ei.Key)); if (_limitSizeTo > 0 && state.Count > _limitSizeTo) { var toRemove = state.Count - _limitSizeTo; // remove oldest items cache.KeyValues.OrderBy(exp => exp.Value.Index).Take(toRemove).ForEach(ei => cache.Remove(ei.Key)); } return state; }).Select(state => state.CaptureChanges()).Publish(); var timeLimited = (_expireAfter is null ? Observable.Never <IChangeSet <ExpirableItem <TObject, TKey>, TKey> >() : sizeLimited).Filter(ei => ei.ExpireAt != DateTime.MaxValue).MergeMany( grouping => { var expireAt = grouping.ExpireAt.Subtract(_scheduler.Now.DateTime); return Observable.Timer(expireAt, _scheduler).Select(_ => grouping); }).Synchronize(locker).Select( item => { cache.Remove(item.Key); return cache.CaptureChanges(); }); var publisher = sizeLimited.Merge(timeLimited).Cast(ei => ei.Value).NotEmpty().SubscribeSafe(observer); return new CompositeDisposable(publisher, sizeLimited.Connect()); })); }
public IObservable <IChangeSet <TDestination, TKey> > Run() { return(_source.Scan((ChangeAwareCache <TDestination, TKey>)null, (cache, changes) => { if (cache == null) { cache = new ChangeAwareCache <TDestination, TKey>(changes.Count); } var concreteType = changes.ToConcreteType(); foreach (var change in concreteType) { switch (change.Reason) { case ChangeReason.Add: case ChangeReason.Update: { TDestination transformed; if (_exceptionCallback != null) { try { transformed = _transformFactory(change.Current, change.Previous, change.Key); cache.AddOrUpdate(transformed, change.Key); } catch (Exception ex) { _exceptionCallback(new Error <TSource, TKey>(ex, change.Current, change.Key)); } } else { transformed = _transformFactory(change.Current, change.Previous, change.Key); cache.AddOrUpdate(transformed, change.Key); } } break; case ChangeReason.Remove: cache.Remove(change.Key); break; case ChangeReason.Refresh: { if (_transformOnRefresh) { var transformed = _transformFactory(change.Current, change.Previous, change.Key); cache.AddOrUpdate(transformed, change.Key); } else { cache.Refresh(change.Key); } } break; case ChangeReason.Moved: //Do nothing ! break; } } return cache; }) .Select(cache => cache.CaptureChanges()) .NotEmpty()); }
private IChangeSet <TDestination, TKey> DoTransform(ChangeAwareCache <TDestination, TKey> cache, IChangeSet <TSource, TKey> changes) { var transformed = changes.Select(ToDestination); return(ProcessUpdates(cache, transformed)); }
public IObservable <IChangeSet <TObject, TKey> > Run() { return(Observable.Create <IChangeSet <TObject, TKey> >(observer => { var locker = new object(); //this is the resulting cache which produces all notifications var resultCache = new ChangeAwareCache <TObject, TKey>(); //Transform to a merge container. //This populates a RefTracker when the original source is subscribed to var sourceLists = _source.Connect() .Synchronize(locker) .Transform(changeset => new MergeContainer(changeset)) .AsObservableList(); var sharedLists = sourceLists.Connect().Publish(); //merge the items back together var allChanges = sharedLists .MergeMany(mc => mc.Source) .Synchronize(locker) .Subscribe(changes => { //Populate result list and check for changes UpdateResultList(resultCache, sourceLists.Items.AsArray(), changes); var notifications = resultCache.CaptureChanges(); if (notifications.Count != 0) { observer.OnNext(notifications); } }); //when an list is removed, need to var removedItem = sharedLists .OnItemRemoved(mc => { //Remove items if required ProcessChanges(resultCache, sourceLists.Items.AsArray(), mc.Cache.KeyValues); if (_type == CombineOperator.And || _type == CombineOperator.Except) { var itemsToCheck = sourceLists.Items.SelectMany(mc2 => mc2.Cache.KeyValues); ProcessChanges(resultCache, sourceLists.Items.AsArray(), itemsToCheck); } var notifications = resultCache.CaptureChanges(); if (notifications.Count != 0) { observer.OnNext(notifications); } }) .Subscribe(); //when an list is added or removed, need to var sourceChanged = sharedLists .WhereReasonsAre(ListChangeReason.Add, ListChangeReason.AddRange) .ForEachItemChange(mc => { ProcessChanges(resultCache, sourceLists.Items.AsArray(), mc.Current.Cache.KeyValues); if (_type == CombineOperator.And || _type == CombineOperator.Except) { ProcessChanges(resultCache, sourceLists.Items.AsArray(), resultCache.KeyValues.ToArray()); } var notifications = resultCache.CaptureChanges(); if (notifications.Count != 0) { observer.OnNext(notifications); } }) .Subscribe(); return new CompositeDisposable(sourceLists, allChanges, removedItem, sourceChanged, sharedLists.Connect()); })); }
public IObservable <ChangeSet <TObject, TKey> > Run() { return(Observable.Create <ChangeSet <TObject, TKey> > ( observer => { var localCache = new ChangeAwareCache <TObject, TKey>(); var locker = new object(); var paused = _intialPauseState; var timeoutDisposer = new SerialDisposable(); var intervalTimerDisposer = new SerialDisposable(); void ResumeAction() { //publish changes (if there are any) var changes = localCache.CaptureChanges(); if (changes.Count > 0) { observer.OnNext(changes); } localCache = new ChangeAwareCache <TObject, TKey>(); } IDisposable IntervalFunction() { return _intervalTimer .Synchronize(locker) .Finally(() => paused = false) .Subscribe(_ => { paused = false; ResumeAction(); if (_intervalTimer != null) { paused = true; } }); } if (_intervalTimer != null) { intervalTimerDisposer.Disposable = IntervalFunction(); } var pausedHander = _pauseIfTrueSelector .Synchronize(locker) .Subscribe(p => { paused = p; if (!p) { //pause window has closed, so reset timer if (_timeOut.HasValue) { timeoutDisposer.Disposable = Disposable.Empty; } ResumeAction(); } else { if (_timeOut.HasValue) { timeoutDisposer.Disposable = Observable.Timer(_timeOut.Value, _scheduler) .Synchronize(locker) .Subscribe(_ => { paused = false; ResumeAction(); }); } } }); var publisher = _source .Synchronize(locker) .Subscribe(changes => { localCache.Clone(changes); //publish if not paused if (!paused) { ResumeAction(); } }); return new CompositeDisposable(publisher, pausedHander, timeoutDisposer, intervalTimerDisposer); } )); }
private IChangeSet <TDestination, TKey> DoTransform(ChangeAwareCache <TransformedItemContainer, TKey> cache, IChangeSet <TSource, TKey> changes) { var transformed = TransformChanges(changes); return(ProcessUpdates(cache, transformed.ToArray())); }
private void UpdateResultList(ChangeAwareCache <TObject, TKey> target, MergeContainer[] sourceLists, IChangeSet <TObject, TKey> changes) { changes.ForEach(change => { ProcessItem(target, sourceLists, change.Current, change.Key); }); }
private void ProcessChanges(ChangeAwareCache <TObject, TKey> target, MergeContainer[] sourceLists, IEnumerable <KeyValuePair <TKey, TObject> > items) { //check whether the item should be removed from the list (or in the case of And, added) items.ForEach(item => { ProcessItem(target, sourceLists, item.Value, item.Key); }); }
public static void FilterChanges <TObject, TKey>(this ChangeAwareCache <TObject, TKey> cache, IChangeSet <TObject, TKey> changes, Func <TObject, bool> predicate) where TKey : notnull { foreach (var change in changes.ToConcreteType()) { var key = change.Key; switch (change.Reason) { case ChangeReason.Add: { var current = change.Current; if (predicate(current)) { cache.AddOrUpdate(current, key); } } break; case ChangeReason.Update: { var current = change.Current; if (predicate(current)) { cache.AddOrUpdate(current, key); } else { cache.Remove(key); } } break; case ChangeReason.Remove: cache.Remove(key); break; case ChangeReason.Refresh: { var existing = cache.Lookup(key); if (predicate(change.Current)) { if (!existing.HasValue) { cache.AddOrUpdate(change.Current, key); } else { cache.Refresh(key); } } else { if (existing.HasValue) { cache.Remove(key); } } } break; } } }
public static IChangeSet <TObject, TKey> GetInitialUpdates <TObject, TKey>(this ChangeAwareCache <TObject, TKey> source, Func <TObject, bool> filter = null) { var filtered = filter == null ? source.KeyValues : source.KeyValues.Where(kv => filter(kv.Value)); return(new ChangeSet <TObject, TKey>(filtered.Select(i => new Change <TObject, TKey>(ChangeReason.Add, i.Key, i.Value)))); }
public FilteredUpdater(ChangeAwareCache <TObject, TKey> cache, Func <TObject, bool> filter) : base(cache, filter) { }
private async Task <IChangeSet <TDestination, TKey> > DoTransform(ChangeAwareCache <TransformedItemContainer, TKey> cache, IChangeSet <TSource, TKey> changes) { var transformed = await Task.WhenAll(changes.Select(Transform)).ConfigureAwait(false); return(ProcessUpdates(cache, transformed)); }
public ReaderWriter(Func <TObject, TKey> keySelector = null) { _keySelector = keySelector; _changeAwareCache = new ChangeAwareCache <TObject, TKey>(_data); }
/// <summary> /// Initialises the specified changes. /// </summary> /// <param name="cache">The cache.</param> /// <returns></returns> public void Reset(ChangeAwareCache <TObject, TKey> cache) { _list = new LinkedList <KeyValuePair <TKey, TObject> >(cache.KeyValues.OrderBy(kv => kv, _comparer)); }