public IChangeSet<TObject, TKey> Change(IChangeSet<ExpirableItem<TObject, TKey>, TKey> updates) { _cache.Clone(updates); var itemstoexpire = _cache.KeyValues .OrderByDescending(exp => exp.Value.ExpireAt) .Skip(_sizeLimit) .Select(exp => new Change<TObject, TKey>(ChangeReason.Remove, exp.Key, exp.Value.Value)) .ToList(); if (itemstoexpire.Count > 0) { _cache.Remove(itemstoexpire.Select(exp => exp.Key)); } var notifications = _cache.CaptureChanges(); var changed = notifications.Select(update => new Change<TObject, TKey> ( update.Reason, update.Key, update.Current.Value, update.Previous.HasValue ? update.Previous.Value.Value : Optional<TObject>.None )); return new ChangeSet<TObject, TKey>(changed); }
private IChangeSet <TDestination, TKey> ProcessUpdates(ChangeAwareCache <TDestination, 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.Destination.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)); } } return(cache.CaptureChanges()); }
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.Length > 0) { errors.ForEach(t => _exceptionCallback?.Invoke(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.Refresh: cache.Refresh(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)); }
public static IChangeSet <TObject, TKey> RefreshFilteredFrom <TObject, TKey>(this ChangeAwareCache <TObject, TKey> filtered, Cache <TObject, TKey> allData, Func <TObject, bool> predicate) where TKey : notnull { if (allData.Count == 0) { return(ChangeSet <TObject, TKey> .Empty); } foreach (var kvp in allData.KeyValues) { var existing = filtered.Lookup(kvp.Key); var matches = predicate(kvp.Value); if (matches) { if (!existing.HasValue) { filtered.Add(kvp.Value, kvp.Key); } } else { if (existing.HasValue) { filtered.Remove(kvp.Key); } } } return(filtered.CaptureChanges()); }
/// <summary> /// Initializes a new instance of the <see cref="LockFreeObservableCache{TObject, TKey}"/> class. /// </summary> /// <param name="source">The source.</param> public LockFreeObservableCache(IObservable <IChangeSet <TObject, TKey> > source) { _updater = new CacheUpdater <TObject, TKey>(_innerCache); var loader = source.Select(changes => { _innerCache.Clone(changes); return(_innerCache.CaptureChanges()); }).SubscribeSafe(_changes); _cleanUp = Disposable.Create(() => { loader.Dispose(); _changes.OnCompleted(); _countChanged.OnCompleted(); }); }
public IObservable <IChangeSet <TObject, TKey> > Run() { return(Observable.Create <IChangeSet <TObject, TKey> >(observer => { long orderItemWasAdded = -1; var locker = new object(); 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 == null ? Observable.Never <IChangeSet <ExpirableItem <TObject, TKey>, TKey> >() : 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 => { cache.Remove(grouping.Keys); return cache.CaptureChanges(); }); var publisher = sizeLimited .Merge(timeLimited) .Cast(ei => ei.Value) .NotEmpty() .SubscribeSafe(observer); return new CompositeDisposable(publisher, sizeLimited.Connect()); })); }
public ChangeSet <TObject, TKey> Write(IChangeSet <TObject, TKey> changes, bool notifyChanges) { if (changes == null) { throw new ArgumentNullException(nameof(changes)); } ChangeSet <TObject, TKey> result; lock (_locker) { if (notifyChanges) { _changeAwareCache.Clone(changes); result = _changeAwareCache.CaptureChanges(); } else { _data.Clone(changes); result = ChangeSet <TObject, TKey> .Empty; } } return(result); }
private IObservable <IChangeSet <TDestination, TDestinationKey> > CreateWithChangeSet() { if (_childChanges is null) { throw new InvalidOperationException("The childChanges is null and should not be."); } return(Observable.Create <IChangeSet <TDestination, TDestinationKey> >( observer => { var result = new ChangeAwareCache <TDestination, TDestinationKey>(); var transformed = _source.Transform( (t, _) => { // Only skip initial for first time Adds where there is initial 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 initial = transformed.Synchronize(outerLock).Select(changes => new ChangeSet <TDestination, TDestinationKey>(new DestinationEnumerator(changes))); var subsequent = transformed.MergeMany(x => x.Changes).Synchronize(outerLock); var allChanges = initial.Merge(subsequent).Select( changes => { result.Clone(changes); return result.CaptureChanges(); }); return new CompositeDisposable(allChanges.SubscribeSafe(observer), transformed.Connect()); })); }
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 = t => 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 // .Finally(observer.OnCompleted) .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 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)); }
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) => { 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))); }, 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()); })); }
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 <TObject, TKey> UpdateCombined(IChangeSet <TObject, TKey> updates) { //child caches have been updated before we reached this point. foreach (var update in updates) { TKey key = update.Key; switch (update.Reason) { case ChangeReason.Add: case ChangeReason.Update: { // get the current key. //check whether the item should belong to the cache var cached = _combinedCache.Lookup(key); var contained = cached.HasValue; var match = MatchesConstraint(key); if (match) { if (contained) { if (!ReferenceEquals(update.Current, cached.Value)) { _combinedCache.AddOrUpdate(update.Current, key); } } else { _combinedCache.AddOrUpdate(update.Current, key); } } else { if (contained) { _combinedCache.Remove(key); } } } break; case ChangeReason.Remove: { var cached = _combinedCache.Lookup(key); var contained = cached.HasValue; bool shouldBeIncluded = MatchesConstraint(key); if (shouldBeIncluded) { var firstOne = _sourceCaches.Select(s => s.Lookup(key)) .SelectValues() .First(); if (!cached.HasValue) { _combinedCache.AddOrUpdate(firstOne, key); } else if (!ReferenceEquals(firstOne, cached.Value)) { _combinedCache.AddOrUpdate(firstOne, key); } } else { if (contained) { _combinedCache.Remove(key); } } } break; case ChangeReason.Refresh: { _combinedCache.Refresh(key); } break; } } return(_combinedCache.CaptureChanges()); }
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 IChangeSet <TObject, TKey> AsChangeSet() { return(_cache.CaptureChanges()); }
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()); })); }
/// <summary> /// Sorts using the specified sorter. Will return null if there are no changes /// </summary> /// <param name="sortReason">The sort reason.</param> /// <param name="changes">The changes.</param> /// <returns></returns> private ISortedChangeSet <TObject, TKey> DoSort(SortReason sortReason, IChangeSet <TObject, TKey> changes = null) { if (changes != null) { _cache.Clone(changes); changes = _cache.CaptureChanges(); _haveReceivedData = true; if (_comparer == null) { return(null); } } //if the comparer is not set, return nothing if (_comparer == null || !_haveReceivedData) { return(null); } if (!_initialised) { sortReason = SortReason.InitialLoad; _initialised = true; } else if (changes != null && (_resetThreshold > 0 && changes.Count >= _resetThreshold)) { sortReason = SortReason.Reset; } IChangeSet <TObject, TKey> changeSet; switch (sortReason) { case SortReason.InitialLoad: { //For the first batch, changes may have arrived before the comparer was set. //therefore infer the first batch of changes from the cache _calculator = new IndexCalculator <TObject, TKey>(_comparer, _optimisations); changeSet = _calculator.Load(_cache); } break; case SortReason.Reset: { _calculator.Reset(_cache); changeSet = changes; } break; case SortReason.DataChanged: { changeSet = _calculator.Calculate(changes); } break; case SortReason.ComparerChanged: { changeSet = _calculator.ChangeComparer(_comparer); if (_resetThreshold > 0 && _cache.Count >= _resetThreshold) { sortReason = SortReason.Reset; _calculator.Reset(_cache); } else { sortReason = SortReason.Reorder; changeSet = _calculator.Reorder(); } } break; case SortReason.Reorder: { changeSet = _calculator.Reorder(); } break; default: throw new ArgumentOutOfRangeException(nameof(sortReason)); } Debug.Assert(changeSet != null, "changeSet != null"); if ((sortReason == SortReason.InitialLoad || sortReason == SortReason.DataChanged) && changeSet.Count == 0) { return(null); } if (sortReason == SortReason.Reorder && changeSet.Count == 0) { return(null); } _sorted = new KeyValueCollection <TObject, TKey>((IReadOnlyCollection <KeyValuePair <TKey, TObject> >)_calculator.List, _comparer, sortReason, _optimisations); return(new SortedChangeSet <TObject, TKey>(_sorted, changeSet)); }