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)); }
public void Add() { var person = new Person("Adult1", 50); _updater.AddOrUpdate(person, "Adult1"); IChangeSet <Person, string> updates = _cache.CaptureChanges(); _cache.Lookup("Adult1").Value.Should().Be(person); _cache.Count.Should().Be(1); 1.Should().Be(updates.Count, "Should be 1 updates"); updates.First().Should().Be(new Change <Person, string>(ChangeReason.Add, person.Name, person), "Should be 1 updates"); }
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.ValueOrDefault(), 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()); }
public static IChangeSet <TObject, TKey> RefreshFilteredFrom <TObject, TKey>( this ChangeAwareCache <TObject, TKey> filtered, Cache <TObject, TKey> allData, Func <TObject, bool> predicate) { foreach (var kvp in allData.KeyValues) { var exisiting = filtered.Lookup(kvp.Key); var matches = predicate(kvp.Value); if (matches) { if (!exisiting.HasValue) { filtered.AddOrUpdate(kvp.Value, kvp.Key); } } else { if (exisiting.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 static IObservable <IChangeSet <TCache, TKey> > TransformAndCache <TObject, TKey, TCache>( this IObservable <IChangeSet <TObject, TKey> > obs, Func <TKey, TObject, TCache> onAdded, Action <Change <TObject, TKey>, TCache> onUpdated) { var cache = new ChangeAwareCache <TCache, TKey>(); return(obs .Select(changeSet => { foreach (var change in changeSet) { switch (change.Reason) { case ChangeReason.Add: case ChangeReason.Update: case ChangeReason.Refresh: var lookup = cache.Lookup(change.Key); TCache val; if (lookup.HasValue) { val = lookup.Value; } else { val = onAdded(change.Key, change.Current); cache.Add(val, change.Key); } onUpdated(change, val); break; case ChangeReason.Remove: cache.Remove(change.Key); break; case ChangeReason.Moved: break; default: throw new NotImplementedException(); } } return cache.CaptureChanges(); }) .Where(cs => cs.Count > 0)); }
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, 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)) { var 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)); }
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))); }, 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 <IChangeSet <TObject, TKey> > Run() { return(Observable.Create <IChangeSet <TObject, TKey> >(observer => { long orderItemWasAdded = -1; var locker = new object(); if (_expireAfter == null && _limitSizeTo < 1) { return _source.Scan(new ChangeAwareCache <TObject, TKey>(), (state, latest) => { latest.ForEach(t => state.AddOrUpdate(t, _keySelector(t))); 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 == 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 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(); //merge the items back together var allChanges = sourceLists.Connect() .MergeMany(mc => mc.Source) .Synchronize(locker) .Subscribe(changes => { //Populate result list and chck 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 = sourceLists.Connect() .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 = sourceLists.Connect() .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); })); }
/// <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>(_calculator.List, _comparer, sortReason, _optimisations); return(new SortedChangeSet <TObject, TKey>(_sorted, changeSet)); }
public IChangeSet <TObject, TKey> AsChangeSet() { return(_cache.CaptureChanges()); }
private IChangeSet <TObject, TKey> UpdateCombined(IChangeSet <TObject, TKey> updates) { //child caches have been updated before we reached this point. foreach (var update in updates) { var 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; var 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 result = 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 = result.CaptureChanges(); if (changes.Count > 0) { observer.OnNext(changes); } } 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 // .StartWith(initalp) .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 => { result.Clone(changes); //publish if not paused if (!paused) { observer.OnNext(result.CaptureChanges()); } }); return new CompositeDisposable(publisher, pausedHander, timeoutDisposer, intervalTimerDisposer); } )); }