/// <summary> /// Creates a view of the messages identified by the matching parameters and asynchronously fills it in. /// View mode can be one of three values: /// Fixed - fixed range based on start and end times /// TailCount - sliding dynamic range that includes the tail of the underlying data based on quantity /// TailRange - sliding dynamic range that includes the tail of the underlying data based on function. /// </summary> /// <typeparam name="TItem">The type of the message to read.</typeparam> /// <param name="viewMode">Mode the view will be created in.</param> /// <param name="startTime">Start time of messages to read.</param> /// <param name="endTime">End time of messages to read.</param> /// <param name="tailCount">Number of messages to included in tail.</param> /// <param name="tailRange">Function to determine range included in tail.</param> /// <returns>Observable view of data.</returns> public ObservableKeyedCache <DateTime, Message <TItem> > .ObservableKeyedView ReadStream <TItem>( ObservableKeyedViewMode viewMode, DateTime startTime, DateTime endTime, uint tailCount, Func <DateTime, DateTime> tailRange) { if (viewMode == ObservableKeyedViewMode.Fixed && endTime < startTime) { throw new ArgumentException("End time must be greater than or equal to start time.", nameof(endTime)); } else if (viewMode == ObservableKeyedViewMode.TailCount && tailCount <= 0) { throw new ArgumentException("Tail count must be greater than 0", nameof(tailCount)); } else if (viewMode == ObservableKeyedViewMode.TailRange && tailRange == null) { throw new ArgumentNullException(nameof(tailRange)); } lock (this.ReadRequestsInternal) { this.ReadRequestsInternal.AddRange(this.ComputeReadRequests(startTime, endTime)); } return((this.data as ObservableKeyedCache <DateTime, Message <TItem> >).GetView(viewMode, startTime, endTime, tailCount, tailRange)); }
/// <inheritdoc /> public ObservableKeyedCache <DateTime, IntervalData <TItem> > .ObservableKeyedView ReadSummary <TItem>( ObservableKeyedViewMode viewMode, DateTime startTime, DateTime endTime, uint tailCount, Func <DateTime, DateTime> tailRange) { if (viewMode == ObservableKeyedViewMode.TailRange) { // Just read directly from the stream with the same tail range in live mode this.ReadStream(tailRange); } else if (viewMode == ObservableKeyedViewMode.TailCount) { // We should read enough of the stream to generate the last tailCount intervals. So take the product of our // summarization interval and tailCount, and use that interval as the tail range to read from the stream. TimeSpan tailInterval = TimeSpan.FromTicks(this.Interval.Ticks * tailCount); this.ReadStream(last => last - tailInterval); } else if (viewMode == ObservableKeyedViewMode.Fixed) { // Ranges for which we have not yet computed summary data. foreach (var range in this.ComputeRangeRequests(startTime, endTime)) { this.ReadStream(range.Item1, range.Item2); } } else { throw new NotSupportedException($"Summarization not yet supported in {viewMode} view mode."); } // Get or create the summary view from the cache return(this.GetCachedSummaryView( viewMode, startTime, endTime, tailCount, tailRange) as ObservableKeyedCache <DateTime, IntervalData <TItem> > .ObservableKeyedView); }
/// <summary> /// Initializes a new instance of the <see cref="ObservableKeyedView"/> class. /// </summary> /// <param name="cache">Underlying cache.</param> /// <param name="mode">View mode.</param> /// <param name="startKey">Start key of the view.</param> /// <param name="endKey">End key of the 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> internal ObservableKeyedView(ObservableKeyedCache <TKey, TItem> cache, ObservableKeyedViewMode mode, TKey startKey, TKey endKey, uint tailCount, Func <TKey, TKey> tailRange) { this.cache = cache; this.mode = mode; this.startKey = startKey; this.endKey = endKey; this.tailCount = tailCount; this.tailRange = tailRange; // Subscribing to cache's DetailedCollectionChanged via WeakEventManager ensures that a strong // reference will not be held to the view, allowing it to be collected when no longer referenced. WeakEventManager <ObservableKeyedCache <TKey, TItem>, NotifyCollectionChangedEventArgs> .AddHandler( this.cache, nameof(this.cache.DetailedCollectionChanged), this.OnCacheCollectionChanged); // update start and end keys this.UpdateKeys(); // update start and end indexes this.UpdateIndexes(); }
/// <summary> /// Gets a view over the specified time range of the cached summary data. /// </summary> /// <typeparam name="T">The summary data type.</typeparam> /// <param name="streamSource">The stream source indicating which stream to read from.</param> /// <param name="viewMode">The view mode, which may be either fixed or live data.</param> /// <param name="startTime">The start time of the view range.</param> /// <param name="endTime">The end time of the view range.</param> /// <param name="interval">The time interval each summary value should cover.</param> /// <param name="tailCount">Not yet supported and should be set to zero.</param> /// <param name="tailRange">Tail duration function. Computes the view range start time given an end time. Applies to live view mode only.</param> /// <returns>A view over the cached summary data that covers the specified time range.</returns> public ObservableKeyedCache <DateTime, IntervalData <T> > .ObservableKeyedView ReadSummary <T>( StreamSource streamSource, ObservableKeyedViewMode viewMode, DateTime startTime, DateTime endTime, TimeSpan interval, uint tailCount, Func <DateTime, DateTime> tailRange) { if (startTime > DateTime.MinValue) { // Extend the start time to include the preceding data point to facilitate continuous plots. startTime = this.FindPreviousDataPoint <T>(startTime, interval); } if (endTime < DateTime.MaxValue) { // Extend the start time to include the next data point to facilitate continuous plots. endTime = this.FindNextDataPoint <T>(endTime, interval); } return(this.GetOrCreateStreamSummary <T>(streamSource, interval).ReadSummary <T>(viewMode, startTime, endTime, tailCount, tailRange)); }
/// <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(ObservableKeyedViewMode 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); ObservableKeyedView view = null; if (this.views.TryGetValue(viewKey, out WeakReference <ObservableKeyedView> 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); }