479 lines · 17.2 KB · C#
RawDownload
//******************************************************************************************************//  PrecisionInputTimer.cs - Gbtc////  Copyright © 2010, Grid Protection Alliance.  All Rights Reserved.////  Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See//  the NOTICE file distributed with this work for additional information regarding copyright ownership.//  The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may//  not use this file except in compliance with the License. You may obtain a copy of the License at:////      http://www.opensource.org/licenses/eclipse-1.0.php////  Unless agreed to in writing, the subject software distributed under the License is distributed on an//  "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the//  License for the specific language governing permissions and limitations.////  Code Modification History://  ----------------------------------------------------------------------------------------------------//  07/25/2012 - J. Ritchie Carroll//       Generated original version of source code.////****************************************************************************************************** using System;using System.Collections.Generic;using System.Threading; namespace GSF.TimeSeries{    /// <summary>    /// Precision input timer.    /// </summary>    /// <remarks>    /// This class is used to create highly accurate simulated data inputs aligned to the local clock.<br/>    /// One static instance of this internal class is created per encountered frame rate.    /// </remarks>    public sealed class PrecisionInputTimer : IDisposable    {        #region [ Members ]         // Fields        private PrecisionTimer m_timer;        private bool m_useWaitHandleA;        private SpinLock m_timerTickLock;        private ManualResetEventSlim m_frameWaitHandleA;        private ManualResetEventSlim m_frameWaitHandleB;        private readonly int m_framesPerSecond;        private readonly int m_frameWindowSize;        private readonly int[] m_frameMilliseconds;        private int m_lastFrameIndex;        private long m_lastFrameTime;        private long m_missedPublicationWindows;        private long m_lastMissedWindowTime;        private long m_resynchronizations;        private int m_referenceCount;        private Action<Exception> m_exceptionHandler;        private bool m_disposed;         #endregion         #region [ Constructors ]         /// <summary>        /// Create a new <see cref="PrecisionInputTimer"/> class.        /// </summary>        /// <param name="framesPerSecond">Desired frame rate for <see cref="PrecisionTimer"/>.</param>        internal PrecisionInputTimer(int framesPerSecond)        {            // Create synchronization objects            m_timerTickLock = new SpinLock();            m_frameWaitHandleA = new ManualResetEventSlim(false);            m_frameWaitHandleB = new ManualResetEventSlim(false);            m_useWaitHandleA = true;            m_framesPerSecond = framesPerSecond;             // Create a new precision timer for this timer state            m_timer = new PrecisionTimer();            m_timer.Resolution = 1;            m_timer.Period = 1;            m_timer.AutoReset = true;             // Attach handler for timer ticks            m_timer.Tick += m_timer_Tick;             m_frameWindowSize = (int)Math.Round(1000.0D / framesPerSecond) * 2;            m_frameMilliseconds = new int[framesPerSecond];             for (int frameIndex = 0; frameIndex < framesPerSecond; frameIndex++)            {                m_frameMilliseconds[frameIndex] = (int)(1.0D / framesPerSecond * (frameIndex * 1000.0D));            }             // Start high resolution timer on a separate thread so the start            // time can synchronized to the top of the millisecond            ThreadPool.QueueUserWorkItem(SynchronizeInputTimer);        }         /// <summary>        /// Releases the unmanaged resources before the <see cref="PrecisionInputTimer"/> object is reclaimed by <see cref="GC"/>.        /// </summary>        ~PrecisionInputTimer()        {            Dispose(false);        }         #endregion         #region [ Properties ]         /// <summary>        /// Gets frames per second for this <see cref="PrecisionInputTimer"/>.        /// </summary>        public int FramesPerSecond        {            get            {                return m_framesPerSecond;            }        }         /// <summary>        /// Gets array of frame millisecond times for this <see cref="PrecisionInputTimer"/>.        /// </summary>        public int[] FrameMilliseconds        {            get            {                return m_frameMilliseconds;            }        }         /// <summary>        /// Gets reference count for this <see cref="PrecisionInputTimer"/>.        /// </summary>        public int ReferenceCount        {            get            {                return m_referenceCount;            }        }         /// <summary>        /// Gets number of resynchronizations that have occurred for this <see cref="PrecisionInputTimer"/>.        /// </summary>        public long Resynchronizations        {            get            {                return m_resynchronizations;            }        }         /// <summary>        /// Gets time of last frame, in ticks.        /// </summary>        public long LastFrameTime        {            get            {                return m_lastFrameTime;            }        }         /// <summary>        /// Gets a reference to the frame wait handle.        /// </summary>        public ManualResetEventSlim FrameWaitHandle        {            get            {                if (m_useWaitHandleA)                    return m_frameWaitHandleA;                 return m_frameWaitHandleB;            }        }         /// <summary>        /// Gets or sets function used to handle exceptions.        /// </summary>        public Action<Exception> ExceptionHandler        {            get            {                return m_exceptionHandler;            }            set            {                m_exceptionHandler = value;            }        }         #endregion         #region [ Methods ]         /// <summary>        /// Releases all the resources used by the <see cref="PrecisionInputTimer"/> object.        /// </summary>        public void Dispose()        {            Dispose(true);            GC.SuppressFinalize(this);        }         /// <summary>        /// Releases the unmanaged resources used by the <see cref="PrecisionInputTimer"/> object and optionally releases the managed resources.        /// </summary>        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>        private void Dispose(bool disposing)        {            if (!m_disposed)            {                try                {                    if (disposing)                    {                        if (m_timer != null)                        {                            m_timer.Tick -= m_timer_Tick;                            m_timer.Dispose();                        }                        m_timer = null;                         if (m_frameWaitHandleA != null)                        {                            m_frameWaitHandleA.Set();                            m_frameWaitHandleA.Dispose();                        }                        m_frameWaitHandleA = null;                         if (m_frameWaitHandleB != null)                        {                            m_frameWaitHandleB.Set();                            m_frameWaitHandleB.Dispose();                        }                        m_frameWaitHandleB = null;                    }                }                finally                {                    m_disposed = true;  // Prevent duplicate dispose.                }            }        }         /// <summary>        /// Adds a reference to this <see cref="PrecisionInputTimer"/>.        /// </summary>        public void AddReference()        {            m_referenceCount++;        }         /// <summary>        /// Removes a reference to this <see cref="PrecisionInputTimer"/>.        /// </summary>        public void RemoveReference()        {            m_referenceCount--;        }         // This timer function is called every millisecond so that frames can be published at the exact desired time         private void m_timer_Tick(object sender, EventArgs e)        {            // Slower systems or systems under stress may have trouble keeping up with a 1-ms timer, so            // we only process this code if it's not already processing...            bool locked = false;             try            {                m_timerTickLock.TryEnter(2, ref locked);                 if (locked)                {                    DateTime now = PrecisionTimer.UtcNow;                    int frameMilliseconds, milliseconds = now.Millisecond;                    long ticks = now.Ticks;                    bool releaseTimer = false, resync = false;                     // Make sure current time is reasonably close to current frame index                    if (Math.Abs(milliseconds - m_frameMilliseconds[m_lastFrameIndex]) > m_frameWindowSize)                        m_lastFrameIndex = 0;                     // See if it is time to publish                    for (int frameIndex = m_lastFrameIndex; frameIndex < m_frameMilliseconds.Length; frameIndex++)                    {                        frameMilliseconds = m_frameMilliseconds[frameIndex];                         if (frameMilliseconds == milliseconds)                        {                            // See if system skipped a publication window                            if (m_lastFrameIndex != frameIndex)                            {                                // We monitor for missed windows in quick succession (within 1.5 seconds)                                if (ticks - m_lastMissedWindowTime > 15000000L)                                {                                    // Threshold has passed since last missed window, so we reset counters                                    m_lastMissedWindowTime = ticks;                                    m_missedPublicationWindows = 0;                                }                                 m_missedPublicationWindows++;                                 // If the system is starting to skip publications it could need resynchronization,                                // so in this case we restart the high-resolution timer to get the timer started                                // closer to the top of the millisecond                                resync = (m_missedPublicationWindows > 4);                            }                             // Prepare index for next check, time moving forward                            m_lastFrameIndex = frameIndex + 1;                             if (m_lastFrameIndex >= m_frameMilliseconds.Length)                                m_lastFrameIndex = 0;                             if (resync)                            {                                if (m_timer != null)                                {                                    m_timer.Stop();                                    ThreadPool.QueueUserWorkItem(SynchronizeInputTimer);                                    m_resynchronizations++;                                }                            }                             releaseTimer = true;                            break;                        }                        else if (frameMilliseconds > milliseconds)                        {                            // Time has yet to pass, wait till the next tick                            break;                        }                    }                     if (releaseTimer)                    {                        // Baseline timestamp to the top of the millisecond for frame publication                        m_lastFrameTime = ticks - ticks % Ticks.PerMillisecond;                         // Pulse all waiting threads toggling between ready handles                        if (m_useWaitHandleA)                        {                            m_frameWaitHandleB.Reset();                            m_useWaitHandleA = false;                            m_frameWaitHandleA.Set();                        }                        else                        {                            m_frameWaitHandleA.Reset();                            m_useWaitHandleA = true;                            m_frameWaitHandleB.Set();                        }                    }                }            }            catch (Exception ex)            {                ExceptionHandler(new InvalidOperationException("Exception thrown by precision input timer: " + ex.Message, ex));            }            finally            {                if (locked)                    m_timerTickLock.Exit();            }        }         private void SynchronizeInputTimer(object state)        {            // Start timer at as close to the top of the millisecond as possible             bool repeat = true;            long last = 0, next;             while (repeat)            {                next = PrecisionTimer.UtcNow.Ticks % Ticks.PerMillisecond % 1000;                repeat = (next > last);                last = next;            }             m_lastMissedWindowTime = 0;            m_missedPublicationWindows = 0;             if (m_timer != null)                m_timer.Start();        }         #endregion         #region [ Static ]         // Static Fields        private static readonly Dictionary<int, PrecisionInputTimer> s_inputTimers = new Dictionary<int, PrecisionInputTimer>();         // Static Methods          public static PrecisionInputTimer Attach(int framesPerSecond)        {            return Attach(framesPerSecond, null);        }                /// <summary>        /// Attach to a <see cref="PrecisionInputTimer"/> for the specified <paramref name="framesPerSecond"/>.        /// </summary>        /// <param name="framesPerSecond">Desired frames per second for the input timer.</param>        /// <param name="exceptionHandler">Optional delegate to handle exception reporting from the timer.</param>                /// /// <returns>A <see cref="PrecisionInputTimer"/> that can be used for the specified <paramref name="framesPerSecond"/>.</returns>        public static PrecisionInputTimer Attach(int framesPerSecond, Action<Exception> exceptionHandler)        {            PrecisionInputTimer timer;             lock (s_inputTimers)            {                // Get static input timer for given frames per second creating it if needed                if (!s_inputTimers.TryGetValue(framesPerSecond, out timer))                {                    try                    {                        // Create a new precision input timer                        timer = new PrecisionInputTimer(framesPerSecond);                        timer.ExceptionHandler = exceptionHandler;                         // Add timer state for given rate to static collection                        s_inputTimers.Add(framesPerSecond, timer);                    }                    catch (Exception ex)                    {                        // Process exception for logging                        if ((object)exceptionHandler != null)                            exceptionHandler(new InvalidOperationException("Failed to create precision input timer due to exception: " + ex.Message, ex));                        else                            throw;                    }                }                 // Increment reference count for input timer at given frame rate                timer.AddReference();            }             return timer;        }         /// <summary>        /// Detatch from the <see cref="PrecisionInputTimer"/>.        /// </summary>        /// <param name="timer">Timer instance to detach from.</param>        /// <remarks>        /// Timer reference will be set to <c>null</c> after detatch.        /// </remarks>        public static void Detach(ref PrecisionInputTimer timer)        {            if ((object)timer != null)            {                lock (s_inputTimers)                {                    // Verify static frame rate timer for given frames per second exists                    if (s_inputTimers.ContainsKey(timer.FramesPerSecond))                    {                        // Decrement reference count                        timer.RemoveReference();                         // If timer is no longer being referenced we stop it and remove it from static collection                        if (timer.ReferenceCount == 0)                        {                            timer.Dispose();                            s_inputTimers.Remove(timer.FramesPerSecond);                        }                    }                }            }             timer = null;        }         #endregion    }}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t