/// <summary> /// Gets a dynamic view of the underlying cache based on the parameters given. /// </summary> /// <param name="mode">View mode.</param> /// <param name="startKey">Start key of view.</param> /// <param name="endKey">End key of view.</param> /// <param name="tailCount">Number of items to include in view.</param> /// <param name="tailRange">Tail duration function. Takes last item's key and returns a new startKey.</param> /// <returns>An instance of <see cref="ObservableKeyedView"/>.</returns> /// <exception cref="ArgumentException"><paramref name="startKey"/> must be less than or equal to <paramref name="endKey"/>.</exception> public ObservableKeyedView GetView(ObservableKeyedView.ViewMode mode, TKey startKey, TKey endKey, uint tailCount, Func <TKey, TKey> tailRange) { if (this.keyComparer.Compare(startKey, endKey) > 0) { throw new ArgumentException($"startKey ({startKey}) must be less than or equal to endKey ({endKey})."); } var viewKey = Tuple.Create(startKey, endKey, tailCount, tailRange); WeakReference <ObservableKeyedView> weakView = null; ObservableKeyedView view = null; if (this.views.TryGetValue(viewKey, out weakView)) { if (weakView.TryGetTarget(out view)) { return(view); } else { view = new ObservableKeyedView(this, mode, startKey, endKey, tailCount, tailRange); weakView.SetTarget(view); } } else { view = new ObservableKeyedView(this, mode, startKey, endKey, tailCount, tailRange); weakView = new WeakReference <ObservableKeyedView>(view); this.views.Add(viewKey, weakView); } return(view); }
/// <summary> /// Gets a dynamic view of the underlying cache based on the parameters given. /// </summary> /// <param name="mode">View mode.</param> /// <param name="startKey">Start key of view.</param> /// <param name="endKey">End key of view.</param> /// <param name="tailCount">Number of items to include in view.</param> /// <param name="tailRange">Tail duration function. Takes last item's key and returns a new startKey.</param> /// <returns>An instance of <see cref="ObservableKeyedView"/>.</returns> /// <exception cref="ArgumentException"><paramref name="startKey"/> must be less than or equal to <paramref name="endKey"/>.</exception> public ObservableKeyedView GetView(ObservableKeyedView.ViewMode mode, TKey startKey, TKey endKey, uint tailCount, Func <TKey, TKey> tailRange) { if (this.keyComparer.Compare(startKey, endKey) > 0) { throw new ArgumentException($"startKey ({startKey}) must be less than or equal to endKey ({endKey})."); } var viewKey = Tuple.Create(startKey, endKey, tailCount, tailRange); WeakReference <ObservableKeyedView> weakView = null; ObservableKeyedView view = null; if (this.views.TryGetValue(viewKey, out weakView)) { if (weakView.TryGetTarget(out view)) { return(view); } else { view = new ObservableKeyedView(this, mode, startKey, endKey, tailCount, tailRange); weakView.SetTarget(view); // Sometimes the weak view gets deleted between when we grab it to check // if it has a hard reference and when we actually set the new hard reference // that we just created. If that happens, then the following code makes sure // the weak view gets put back into the collection. this.views[viewKey] = weakView; } } else { view = new ObservableKeyedView(this, mode, startKey, endKey, tailCount, tailRange); weakView = new WeakReference <ObservableKeyedView>(view); this.views.Add(viewKey, weakView); } return(view); }
public Enumerator(ObservableKeyedView view) { this.view = view; this.index = 0; this.value = default(TItem); }
/// <summary> /// Walks views and prunes out dead (GC'd) views and the resulting unreferenced data. /// </summary> private void PruneCache() { // More than one call to PruneCache can be queued. Only process the first one. if (!this.needsPruning) { return; } this.needsPruning = false; // track whether we did any pruning to adjust our threshold bool didPrune = false; // find dead views ObservableKeyedView view = null; var deadViews = this.views.Where(v => !v.Value.TryGetTarget(out view)); // remove dead views from views collection foreach (var deadView in deadViews.ToList()) { this.views.Remove(deadView.Key); } // remove cached items that are no longer referenced by a view var liveViews = this.views.OrderBy(lv => lv.Key.Item1, this.keyComparer).ThenBy(lv => lv.Key.Item2, this.keyComparer); int startIndex = 0; foreach (var liveView in liveViews) { // find exclusive endIndex of range to be removed int endIndex = startIndex; while (this.Count > endIndex && this.keyComparer.Compare(this.getKeyForItem(this[endIndex]), liveView.Key.Item1) < 0) { endIndex++; } // remove items preceding current live view if (endIndex > startIndex) { this.RemoveRange(startIndex, endIndex - startIndex); didPrune = true; } // advance startIndex to end of current live view while (this.Count > startIndex && this.keyComparer.Compare(this.getKeyForItem(this[startIndex]), liveView.Key.Item2) < 0) { startIndex++; } } // remove items after last live view if (startIndex == 0) { // no live view that contains any data - clear all this.Clear(); didPrune = true; } else { // at least one live view that contains data - remove to the end if (this.Count > startIndex) { this.RemoveRange(startIndex, this.Count - startIndex); didPrune = true; } } // adjust pruning threshold this.pruneThreshold = didPrune ? Math.Max(this.pruneThreshold >> 1, ObservableSortedCollection <TItem> .DefaultCapacity >> 2) : Math.Min(this.pruneThreshold << 1, ObservableSortedCollection <TItem> .DefaultCapacity << 2); }