/// <summary> /// Gets <see cref="TrackingFrame"/> from the queue with the specified timestamp, in ticks. If no frame exists for /// the specified timestamp, one will be created. /// </summary> /// <param name="ticks">Timestamp, in ticks, for which to get or create <see cref="TrackingFrame"/>.</param> /// <remarks> /// Ticks can be any point in time so long time requested is greater than time of last published frame; this queue is /// used in a real-time scenario with time moving forward. If a frame is requested for an old timestamp, null will /// be returned. Note that frame returned will be "best-fit" for given timestamp based on <see cref="FramesPerSecond"/>. /// </remarks> /// <returns>An existing or new <see cref="TrackingFrame"/> from the queue for the specified timestamp.</returns> public TrackingFrame GetFrame(long ticks) { TrackingFrame frame = null; bool nodeAdded = false; long destinationTicks; // Calculate destination ticks for this frame destinationTicks = m_roundToNearestTimestamp ? Ticks.RoundToSubsecondDistribution(ticks, m_framesPerSecond) : Ticks.AlignToSubsecondDistribution(ticks, m_framesPerSecond, m_timeResolution); // Make sure ticks are newer than latest published ticks... if (destinationTicks > Thread.VolatileRead(ref m_publishedTicks)) { // See if requested frame is already available (can do this outside lock with concurrent dictionary) if (m_frameHash.TryGetValue(destinationTicks, out frame)) { return(frame); } // Didn't find frame for this timestamp so we need to add a new one to the queue #if MONO lock (m_queueLock) { #else bool locked = false; try { m_queueLock.Enter(ref locked); #endif // Another thread may have gotten to this task already, so check for this contingency... if (m_frameHash.TryGetValue(destinationTicks, out frame)) { return(frame); } // TODO: Add a flag to add frames to publish even if no data arrives for them as an alternate mode // TODO: of operation. In this mode pre-populate frame queue with at least one second of data from // TODO: given start time (was thinking up next even second). This mode is useful for generically // TODO: using concentrator to create a set of frame data from a data set with possible missing // TODO: data (e.g., a historian) to create an evenly timestamped export // Create a new frame for this timestamp frame = new TrackingFrame(m_createNewFrame(destinationTicks), m_downsamplingMethod); if (m_frameList.Count > 0) { // Insert frame into proper sorted position... LinkedListNode <TrackingFrame> node = m_frameList.Last; do { if (destinationTicks > node.Value.Timestamp) { m_frameList.AddAfter(node, frame); nodeAdded = true; break; } node = node.Previous; }while (node != null); } if (!nodeAdded) { m_frameList.AddFirst(frame); m_head = frame; } // Since we'll be requesting this frame over and over, we'll use // a hash table for quick frame lookups by timestamp m_frameHash[destinationTicks] = frame; } #if !MONO finally { if (locked) { m_queueLock.Exit(); } } #endif } return(frame); } #region [ GetFrame Testing Algorithm ] // Copy this code into a console application and reference GSF.Core.dll to test. //using System; //using System.Collections.Generic; //using System.Linq; //using System.Text; //using GSF; //namespace FrameTimestampTest //{ // public class Program // { // private static long s_timeResolution; // private static long s_framesPerSecond; // private static double s_ticksPerFrame; // private static double s_millisecondsPerFrame; // public static void Main(string[] args) // { // s_framesPerSecond = 30; // s_ticksPerFrame = Ticks.PerSecond / (double)s_framesPerSecond; // s_timeResolution = Ticks.PerMillisecond; // s_millisecondsPerFrame = 1000.0 / s_framesPerSecond; // // Test BPA PDCstream style milliseconds // DateTime currentTime = DateTime.Now; // int[] bpaMilliseconds = new int[] { 000, 033, 066, 100, 133, 166, 200, 233, 266, 300, 333, 366, 400, 433, 466, 500, 533, 566, 599, 633, 666, 699, 733, 766, 800, 833, 866, 900, 933, 966 }; // s_timeResolution = Ticks.PerMillisecond * 33; // Console.WriteLine("BPA millisecond sorting test, TimeResolution = {0}", s_timeResolution); // for (int i = 0; i < bpaMilliseconds.Length; i++) // { // long longTicks = (new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, currentTime.Hour, currentTime.Minute, currentTime.Second, bpaMilliseconds[i])).Ticks; // long destination = GetFrame(longTicks); // Console.WriteLine(string.Format("{0:000} ms : {1:000} ms", bpaMilliseconds[i], (new DateTime(destination)).Millisecond)); // } // s_timeResolution = Ticks.PerMillisecond; // Ticks sourceTime = ((Ticks)DateTime.Now).BaselinedTimestamp(BaselineTimeInterval.Second); // for (int i = 0; i < s_framesPerSecond; i++) // { // int milliseconds = (int)(s_millisecondsPerFrame * i); // long longTicks = ((new DateTime(sourceTime)).AddMilliseconds((double)milliseconds)).Ticks; // long destination = GetFrame(longTicks); // Console.WriteLine(string.Format("{0} - {1:000} ms : {2} - {3:000} ms", longTicks, milliseconds, destination, (new DateTime(destination)).Millisecond)); // } // Console.WriteLine(); // double ticks = Ticks.PerSecond; // // Test truncated timestamps // for (int i = 0; i < s_framesPerSecond; i++) // { // long longTicks = (long)(ticks / s_timeResolution) * s_timeResolution; // Console.WriteLine(string.Format("{0} : {1}", longTicks, GetFrame(longTicks))); // ticks += s_ticksPerFrame; // } // Console.WriteLine(); // ticks = 2 * Ticks.PerSecond; // // Test rounded timestamps // for (int i = 0; i < s_framesPerSecond; i++) // { // long longTicks = (long)Math.Round(ticks / s_timeResolution) * s_timeResolution; // Console.WriteLine(string.Format("{0} : {1}", longTicks, GetFrame(longTicks))); // ticks += s_ticksPerFrame; // } // Console.WriteLine(); // ticks = 3 * Ticks.PerSecond; // // Test upper range limits // for (int i = 0; i < s_framesPerSecond; i++) // { // ticks += s_ticksPerFrame; // long longTicks = (long)(ticks / s_timeResolution) * s_timeResolution; // longTicks -= s_timeResolution; // Console.WriteLine(string.Format("{0} : {1}", longTicks, GetFrame(longTicks))); // } // Console.ReadLine(); // } // public static long GetFrame(long ticks) // { // long baseTicks, ticksBeyondSecond, frameIndex, destinationTicks, nextDestinationTicks; // // Baseline timestamp to the top of the second // baseTicks = ticks - ticks % Ticks.PerSecond; // // Remove the seconds from ticks // ticksBeyondSecond = ticks - baseTicks; // // Calculate a frame index between 0 and s_framesPerSecond-1, corresponding to ticks // // rounded down to the nearest frame // frameIndex = (long)(ticksBeyondSecond / s_ticksPerFrame); // // Calculate the timestamp of the nearest frame rounded up // nextDestinationTicks = (frameIndex + 1) * Ticks.PerSecond / s_framesPerSecond; // // Determine whether the desired frame is the nearest // // frame rounded down or the nearest frame rounded up // if (s_timeResolution <= 1) // { // if (nextDestinationTicks <= ticksBeyondSecond) // destinationTicks = nextDestinationTicks; // else // destinationTicks = frameIndex * Ticks.PerSecond / s_framesPerSecond; // } // else // { // // If, after translating nextDestinationTicks to the time resolution, it is less than // // or equal to ticks, nextDestinationTicks corresponds to the desired frame // if ((nextDestinationTicks / s_timeResolution) * s_timeResolution <= ticksBeyondSecond) // destinationTicks = nextDestinationTicks; // else // destinationTicks = frameIndex * Ticks.PerSecond / s_framesPerSecond; // } // // Recover the seconds that were removed // destinationTicks += baseTicks; // return destinationTicks; // } // } //} #endregion #endregion }