TimeManager.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Core / CSharp / System / Windows / Media / Animation / TimeManager.cs / 1305600 / TimeManager.cs

                            //------------------------------------------------------------------------------ 
//  Microsoft Windows Client Platform
//  Copyright (c) Microsoft Corporation, 2003
//
//  File:       TimeManager.cs 
//-----------------------------------------------------------------------------
 
#if DEBUG 
#define TRACE
#endif // DEBUG 

using System;
using System.Collections;
using System.Collections.Generic; 
using System.ComponentModel;
using System.Diagnostics; 
using MS.Internal; 
using MS.Win32;
using MS.Utility; 
using System.Windows.Threading;
using System.Windows.Media;
using System.Windows.Media.Composition;
using System.Runtime.InteropServices; 
using System.Security;
using System.Security.Permissions; 
 
using SR=MS.Internal.PresentationCore.SR;
using SRID=MS.Internal.PresentationCore.SRID; 

namespace System.Windows.Media.Animation
{
    ///  
    /// The object that controls an entire timing tree.
    ///  
    ///  
    /// 

A time manager controls the flow of time in a timing tree. The clock is /// updated periodically by the rendering system, at which time the progress value /// of all active timelines is updated according to the elapsed time. This elapsed time /// can be controlled by the application by specifying a custom reference clock in /// the constructor.

///
internal sealed class TimeManager : DispatcherObject { #region External interface #region Construction /// /// Creates a time manager object in the stopped state. /// /// /// This constructor causes the time manager to use a default system clock to drive /// the timing engine. To start the clock moving, call the /// method. /// public TimeManager() : this(null) { } /// /// Creates a time manager object in the stopped state. /// /// /// An interface to an object that provides real-time clock values to the time /// manager. The manager will query this interface whenever it needs to know the /// current time. This parameter may be null. /// /// /// If the clock parameter is null, the time manager uses a default system clock /// to drive the timing engine. To start the clock moving, call the /// method. /// public TimeManager(IClock clock) { _eventQueue = new Queue(); Clock = clock; _timeState = TimeState.Stopped; _lastTimeState = TimeState.Stopped; _globalTime = new TimeSpan(-1); _lastTickTime = new TimeSpan(-1); _nextTickTimeQueried = false; _isInTick = false; ParallelTimeline timeManagerTimeline = new ParallelTimeline(new TimeSpan(0), Duration.Forever); timeManagerTimeline.Freeze(); _timeManagerClock = new ClockGroup(timeManagerTimeline); _timeManagerClock.MakeRoot(this); } #endregion // Construction #region Properties /// /// Accesses the reference clock used by this time manager to obtain /// real-world clock values. /// public IClock Clock { get { // VerifyAccess(); return _systemClock; } set { // VerifyAccess(); if (value != null) { _systemClock = value; } else { if (MediaContext.IsClockSupported) { _systemClock = (IClock)MediaContext.From(Dispatcher); } else { _systemClock = (IClock)new GTCClock(); } } } } /// /// The current position of the clock, relative to the starting time. /// public Nullable CurrentTime { get { // VerifyAccess(); if (_timeState == TimeState.Stopped) { return null; } else { return _globalTime; } } } /// /// True if the structure of the timing tree has changed since the last /// tick. /// public bool IsDirty { get { // VerifyAccess(); return _isDirty; } } #endregion // Properties #region Methods /// /// Pauses the clock. /// /// /// To start the clock again, call the method. This method has /// no effect if the clock is not in the running state. /// public void Pause() { // VerifyAccess(); if (_timeState == TimeState.Running) { // Record the pause time _pauseTime = _systemClock.CurrentTime; // Stop the tree from responding to ticks _timeState = TimeState.Paused; } } /// /// Resets the time manager and sets a new start time. /// /// /// Calling this method is equivalent to calling followed /// by . /// /// /// public void Restart() { // VerifyAccess(); // Remember the current state TimeState oldState = _timeState; // Stop and start Stop(); Start(); // Go back to the old state _timeState = oldState; // If we were paused, update the pause position to the start position if (_timeState == TimeState.Paused) { _pauseTime = _startTime; } } /// /// Resumes the clock. /// /// /// This function has no effect if the clock was not in the paused state. If the /// clock is stopped rather than paused, call the method /// instead. /// public void Resume() { // VerifyAccess(); if (_timeState == TimeState.Paused) { // Adjust the starting time _startTime = _startTime + _systemClock.CurrentTime - _pauseTime; // Restart the tree _timeState = TimeState.Running; // See if we need to tick sooner now that we are running if (GetNextTickNeeded() >= TimeSpan.Zero) { NotifyNewEarliestFutureActivity(); } } } /// /// Seeks the global clock to a new position. /// /// /// The seek offset, in milliseconds. The meaning of this parameter depends on the value of /// the origin parameter. /// /// /// The meaning of the offset parameter. See the enumeration /// for possible values. /// /// ///

This method seeks the global clock. The timelines of all timelines in the timing tree are /// also updated accordingly. Any events that those timelines would have fired in between the old /// and new clock positions are skipped.

/// ///

Because the global clock is infinite there is no defined end point, therefore seeking /// from the end position has no effect.

///
public void Seek(int offset, TimeSeekOrigin origin) { if (_timeState >= TimeState.Paused) { switch (origin) { case TimeSeekOrigin.BeginTime: // Use the offset as the new time; break; case TimeSeekOrigin.Duration: // Undefined for the global clock return; default: // Invalid enum argument throw new InvalidEnumArgumentException(SR.Get(SRID.Enum_Invalid, "TimeSeekOrigin")); } // Truncate to the beginning if (offset < 0) { offset = 0; } TimeSpan offsetTime = TimeSpan.FromMilliseconds((double)offset); // Only do the work if we actually moved if (offsetTime != _globalTime) { // Set the new global time _globalTime = offsetTime; // Adjust the starting time for future ticks _startTime =_systemClock.CurrentTime - _globalTime; // Update the timing tree accordingly _timeManagerClock.ComputeTreeState(); } } } /// /// Starts the time manager at the current time. /// /// /// The current time is determined by querying the reference clock for this /// time manager. /// public void Start() { // VerifyAccess(); if (_timeState == TimeState.Stopped) { // Reset the state of the timing tree _lastTickTime = TimeSpan.Zero; // Start the tree _startTime =_systemClock.CurrentTime; _globalTime = TimeSpan.Zero; _timeState = TimeState.Running; _timeManagerClock.RootActivate(); } } /// /// Stops the clock. /// /// /// After the clock is stopped, it can be restarted with a call to . /// This method has no effect if the clock was not in the running or paused state. /// /// /// public void Stop() { // VerifyAccess(); if (_timeState >= TimeState.Paused) { _timeManagerClock.RootDisable(); _timeState = TimeState.Stopped; } } /// /// Moves the clock forward to the current time and updates the state of /// all timing objects based on the time change. /// /// /// The associated reference clock is used to determine the current time. /// The new position of the clock will be equal to the difference between the /// starting system time and the current system time. The time manager requires /// the system time to move forward. /// //[CodeAnalysis("AptcaMethodsShouldOnlyCallAptcaMethods")] //Tracking Bug: 29647 public void Tick() { try { #if DEBUG // On a regular interval, clean up our tables of known // timelines and clocks if (++_frameCount >= 1000) // Should be about once every 10s { Timeline.CleanKnownTimelinesTable(); System.Windows.Media.Animation.Clock.CleanKnownClocksTable(); _frameCount = 0; } #endif // DEBUG // Don't need to worry about needing a tick sooner _nextTickTimeQueried = false; // Mark the tree as clean immediately. If any changes occur during // processing of the tick, the tree will be marked as dirty again. _isDirty = false; // No effect unless we are in the running state if (_timeState == TimeState.Running) { // Figure out the current time _globalTime = GetCurrentGlobalTime(); // Start the tick _isInTick = true; } // Trace the start of the tick and pass along the absolute time to which // we are ticking. EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnimation | EventTrace.Keyword.KeywordPerf, EventTrace.Event.WClientTimeManagerTickBegin, (_startTime + _globalTime).Ticks / TimeSpan.TicksPerMillisecond); // Run new property querying logic on the timing tree if (_lastTimeState == TimeState.Stopped && _timeState == TimeState.Stopped) // We were stopped the whole time { _currentTickInterval = TimeIntervalCollection.CreateNullPoint(); } else // We were not stopped at some time, so process the tick interval { _currentTickInterval = TimeIntervalCollection.CreateOpenClosedInterval(_lastTickTime, _globalTime); // If at either tick we were stopped, add the null point to represent that if (_lastTimeState == TimeState.Stopped || _timeState == TimeState.Stopped) { _currentTickInterval.AddNullPoint(); } } // Compute the tree state, using _currentTickInterval to compute the events that occured _timeManagerClock.ComputeTreeState(); // Cache TimeManager state at this time _lastTimeState = _timeState; // When the tick is done, we raise timing events RaiseEnqueuedEvents(); } finally { _isInTick = false; // Cache the tick time _lastTickTime = _globalTime; //trace the end of the tick EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnimation | EventTrace.Keyword.KeywordPerf, EventTrace.Event.WClientTimeManagerTickEnd); } // At the end of every tick clean up GC'ed clocks, if necessary CleanupClocks(); } // Get the maximumum desired framerate that has been set in one of our // top-level clock. internal int GetMaxDesiredFrameRate() { return _timeManagerClock.GetMaxDesiredFrameRate(); } #endregion // Methods #endregion // External interface #region Internal implementation #region Types /// /// A clock implementation that uses GetTickCount to get real-world clock values. /// internal class GTCClock : IClock { #region Internal implementation #region Methods /// /// Creates a GTCClock object. /// internal GTCClock() { } /// /// Gets the current real-world time. /// TimeSpan IClock.CurrentTime { get { return TimeSpan.FromTicks(DateTime.Now.Ticks); } } #endregion // Methods #endregion // Internal implementation } /// /// This is used by DrtTiming in order to specify the CurrentTime /// as it pleases. Note this class is internal /// internal class TestTimingClock : IClock { #region Internal implementation #region Methods /// /// Gets and sets the CurrentTime /// public TimeSpan CurrentTime { get { return _currentTime; } set { _currentTime = value; } } #endregion // Methods #region Data private TimeSpan _currentTime; #endregion // Data #endregion // Internal implementation } #endregion // Types #region Methods /// /// Removes references to garbage-collected clocks. /// private void CleanupClocks() { // We only run the clean-up procedure if we previously detected // that some clocks were GC'ed. The detection occurs in the // finalizer thread, perhaps concurrently with the execution of // this method. See the comments in the ScheduleClockCleanup // method for why we don't need to lock to protect against that // concurrency. if (_needClockCleanup) { // We are about to clean up, so clear the bit first. That way // if another thread sets the bit again then at worst we // have to run through the process one more time, with no ill // consequences other than a small run-time hit. If we wait to // set the bit until after we actually cleaned up we may fail // to detect any clocks that are GC'ed while we are cleaning // up. _needClockCleanup = false; _timeManagerClock.RootCleanChildren(); } } /// /// Puts a Clock into a queue for firing events. /// /// /// The object sending the event. /// /// /// The time at which the event is actually raised is implementation-specific and is /// subject to change at any time. /// internal void AddToEventQueue(Clock sender) { _eventQueue.Enqueue(sender.WeakReference); } /// /// Converts the current real world time into clock time. /// /// /// The global clock time. /// /// /// This function uses the custom clock provided in the TimeManager constructor, if any. /// internal TimeSpan GetCurrentGlobalTime() { // The conversion depends on our current state switch (_timeState) { case TimeState.Stopped: // If we are stopped, the current global time is always zero return TimeSpan.Zero; case TimeState.Paused: // If we are paused, the real-world clock appears to us as if it's // stopped at the pause time return _pauseTime - _startTime; case TimeState.Running: // If we are running, use the actual real-world clock. // However, keep time "frozen" while processing a tick or // by request. if (_isInTick || _lockTickTime) { return _globalTime; } else { return _systemClock.CurrentTime - _startTime; } default: Debug.Assert(false, "Unrecognized TimeState enumeration value"); return TimeSpan.Zero; } } /// /// Locks the current time for successive Tick calls. /// internal void LockTickTime() { _lockTickTime = true; } /// /// Called by timelines to tell the time manager that a future activity /// was scheduled at the head of the list. /// /// /// When a new future activity is added to the head, it means that the next /// tick is needed earlier than was thought before the activity was scheduled. /// This means that if the NextTickNeeded property was queried we need to /// notify the querying module that the value of the property needs to be /// queried again. /// internal void NotifyNewEarliestFutureActivity() { if (_nextTickTimeQueried && _userNeedTickSooner != null) { _nextTickTimeQueried = false; _userNeedTickSooner(this, EventArgs.Empty); } } /// /// Raises any events stored in the queue. /// private void RaiseEnqueuedEvents() { while (_eventQueue.Count > 0) { WeakReference instance = _eventQueue.Dequeue(); Clock clock = (Clock)instance.Target; if (clock != null) { clock.RaiseAccumulatedEvents(); } } } /// /// Schedules a clean-up pass to look for GC'ed clocks. /// /// /// This method can be called from any thread. In particular, it is /// safe to call this method from another object's finalizer. /// internal void ScheduleClockCleanup() { // To clean up GC'ed clocks we set a bit when a finalizer runs (in // the finalizer thread). Later, in the UI thraed, we run the // CleanupClocks method which checks whether this bit is set and // executes the clean-up procedure if it is. This can be done // entirely without locks, even on multiprocessor machines, // because of the sequence in which the variable is accessed. In // particular, note that this method sets the bit unconditionally, // which means we can essentially treat it as atomic. // // Consider the possible sequences: // // 1. The bit is already set when this method runs // In that case we are definitely in a safe situation because // we won't change the value of the bit, so this call is a // no-op. // // 2. The bit is not set when this method runs // In that case we need to consider what the UI thread may do. // Other threads running this method are irrelevant because // the method is essentially atomic, and thanks to UI context // affinity there can only be one UI thread running. If that // thread is running CleanupClocks at the same time that this // thread is running this method then there are three cases: // a) We set the bit before CleanupClocks checks the bit // This is obviously safe -- the other method will run the // clean-up procedure immediately. // b) We set the bit after CleanupClocks checks the bit but // before it clears it. This is actually case 1, because // CleanupClocks only does anything if the bit was already // set. Case 1 is a no-op, so we are safe. // c) We set the bit after CleanupClocks checks and clears // the bit. This may happen before, during or after the // clean-up procedure, but all cases are safe because this // bit won't be checked again by this invocation of // CleanupClocks. All that will happen is that we will // need to run CleanupClocks again at some point in the // future. In the worst case this is nothing more than a // small waste of time because the clock whose // finalization we are detecting may be cleaned up already // by the current invocation of CleanupClocks. However, // the operation is still safe. // // We are trading off the cost of locking every time against the // cost of an occasional unnecessary clean-up procedure. The latter // seems a lot less likely, and therefore smaller in the long run. _needClockCleanup = true; } /// /// Marks the timing tree as dirty. The tree is "cleaned" on the following /// tick. /// internal void SetDirty() { _isDirty = true; } /// /// Unlocks the current time such that the next tick will query the /// system time. /// internal void UnlockTickTime() { _lockTickTime = false; } #endregion // Methods #region Properties /// /// The current global time. /// internal TimeSpan InternalCurrentGlobalTime { get { return _globalTime; } } /// /// Whether the TimeManager is stopped /// internal bool InternalIsStopped { get { return (_timeState == TimeState.Stopped); } } internal TimeIntervalCollection InternalCurrentIntervals { get { return _currentTickInterval; } set { _currentTickInterval = value; // This allows temporarily setting custom interval values } } /// /// The time until the state of any time clock in the tree is expected /// to change. If the method returns a negative value, no state changes /// are expected any time in the future and therefore a tick will never /// be needed. /// /// /// This method is only a hint. Even if timelines don't change state, /// their progress values need to be updated regularly, requiring a tick at /// some minimum interval. In addition, certain interactive events can /// cause activity to be scheduled earlier than the previously known /// earliest activity, in which case a tick is needed sooner. To detect /// this condition, hook the event. /// internal TimeSpan GetNextTickNeeded() { // VerifyAccess(); _nextTickTimeQueried = true; if (_timeState == TimeState.Running) { // We are running, see how soon the tree needs updating TimeSpan? nextTickNeededTime = _timeManagerClock.InternalNextTickNeededTime; if (nextTickNeededTime.HasValue) { // Get the time the time manager has been running as of this moment. TimeSpan timeManagerCurrentTime = _systemClock.CurrentTime - _startTime; // Calculate how long from "now" we'll need another tick. TimeSpan nextTickNeededTimeFromCurrentTime = nextTickNeededTime.Value - timeManagerCurrentTime; if (nextTickNeededTimeFromCurrentTime <= TimeSpan.Zero) { // We've already past the time at which we need another // tick so tick again ASAP. return TimeSpan.Zero; } else { // Return the time until we need another tick return nextTickNeededTimeFromCurrentTime; } } else // The tree does not need any ticks in the future { return TimeSpan.FromTicks(-1); } } else { // Our state is not Running, so we don't have any ticks coming up return TimeSpan.FromTicks(-1); } } /// /// Unchecked internal access to the time shift since the last tick. /// internal TimeSpan LastTickDelta { get { return _globalTime - _lastTickTime; } } /// /// Internal access to the time since the last tick. /// internal TimeSpan LastTickTime { get { return _lastTickTime; } } /// /// Returns the clock that is the root of the timing tree managed by /// this time manager. /// internal ClockGroup TimeManagerClock { get { // VerifyAccess(); return _timeManagerClock; } } /// /// The present state of the time manager. /// internal TimeState State { get { return _timeState; } } #endregion // Properties #region Events /// /// Raised when a change has been made to the timing engine that may require /// it to be ticked sooner than was previously expected. /// internal event EventHandler NeedTickSooner { add { // VerifyAccess(); _userNeedTickSooner += value; } remove { // VerifyAccess(); _userNeedTickSooner -= value; } } #endregion // Events #region Debugging instrumentation #if DEBUG /// /// Dumps the internal state of the whole timing tree /// internal void Dump() { _timeManagerClock.Dump(); } #endif // DEBUG #endregion // Debugging instrumentation #region Data #region Timing data private TimeState _timeState; private TimeState _lastTimeState; private IClock _systemClock; private TimeSpan _globalTime; private TimeSpan _startTime; private TimeSpan _lastTickTime; private TimeSpan _pauseTime; private TimeIntervalCollection _currentTickInterval; private bool _nextTickTimeQueried, _isDirty, _isInTick, _lockTickTime; private EventHandler _userNeedTickSooner; private ClockGroup _timeManagerClock; private Queue _eventQueue; #endregion // Timing data #region Linking data private bool _needClockCleanup; #endregion // Linking data #region Debug data #if DEBUG private int _frameCount; #endif // DEBUG #endregion // Debug data #endregion // Data #endregion // Internal implementation } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------------------------ // Microsoft Windows Client Platform // Copyright (c) Microsoft Corporation, 2003 // // File: TimeManager.cs //----------------------------------------------------------------------------- #if DEBUG #define TRACE #endif // DEBUG using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using MS.Internal; using MS.Win32; using MS.Utility; using System.Windows.Threading; using System.Windows.Media; using System.Windows.Media.Composition; using System.Runtime.InteropServices; using System.Security; using System.Security.Permissions; using SR=MS.Internal.PresentationCore.SR; using SRID=MS.Internal.PresentationCore.SRID; namespace System.Windows.Media.Animation { /// /// The object that controls an entire timing tree. /// /// ///

A time manager controls the flow of time in a timing tree. The clock is /// updated periodically by the rendering system, at which time the progress value /// of all active timelines is updated according to the elapsed time. This elapsed time /// can be controlled by the application by specifying a custom reference clock in /// the constructor.

///
internal sealed class TimeManager : DispatcherObject { #region External interface #region Construction /// /// Creates a time manager object in the stopped state. /// /// /// This constructor causes the time manager to use a default system clock to drive /// the timing engine. To start the clock moving, call the /// method. /// public TimeManager() : this(null) { } /// /// Creates a time manager object in the stopped state. /// /// /// An interface to an object that provides real-time clock values to the time /// manager. The manager will query this interface whenever it needs to know the /// current time. This parameter may be null. /// /// /// If the clock parameter is null, the time manager uses a default system clock /// to drive the timing engine. To start the clock moving, call the /// method. /// public TimeManager(IClock clock) { _eventQueue = new Queue(); Clock = clock; _timeState = TimeState.Stopped; _lastTimeState = TimeState.Stopped; _globalTime = new TimeSpan(-1); _lastTickTime = new TimeSpan(-1); _nextTickTimeQueried = false; _isInTick = false; ParallelTimeline timeManagerTimeline = new ParallelTimeline(new TimeSpan(0), Duration.Forever); timeManagerTimeline.Freeze(); _timeManagerClock = new ClockGroup(timeManagerTimeline); _timeManagerClock.MakeRoot(this); } #endregion // Construction #region Properties /// /// Accesses the reference clock used by this time manager to obtain /// real-world clock values. /// public IClock Clock { get { // VerifyAccess(); return _systemClock; } set { // VerifyAccess(); if (value != null) { _systemClock = value; } else { if (MediaContext.IsClockSupported) { _systemClock = (IClock)MediaContext.From(Dispatcher); } else { _systemClock = (IClock)new GTCClock(); } } } } /// /// The current position of the clock, relative to the starting time. /// public Nullable CurrentTime { get { // VerifyAccess(); if (_timeState == TimeState.Stopped) { return null; } else { return _globalTime; } } } /// /// True if the structure of the timing tree has changed since the last /// tick. /// public bool IsDirty { get { // VerifyAccess(); return _isDirty; } } #endregion // Properties #region Methods /// /// Pauses the clock. /// /// /// To start the clock again, call the method. This method has /// no effect if the clock is not in the running state. /// public void Pause() { // VerifyAccess(); if (_timeState == TimeState.Running) { // Record the pause time _pauseTime = _systemClock.CurrentTime; // Stop the tree from responding to ticks _timeState = TimeState.Paused; } } /// /// Resets the time manager and sets a new start time. /// /// /// Calling this method is equivalent to calling followed /// by . /// /// /// public void Restart() { // VerifyAccess(); // Remember the current state TimeState oldState = _timeState; // Stop and start Stop(); Start(); // Go back to the old state _timeState = oldState; // If we were paused, update the pause position to the start position if (_timeState == TimeState.Paused) { _pauseTime = _startTime; } } /// /// Resumes the clock. /// /// /// This function has no effect if the clock was not in the paused state. If the /// clock is stopped rather than paused, call the method /// instead. /// public void Resume() { // VerifyAccess(); if (_timeState == TimeState.Paused) { // Adjust the starting time _startTime = _startTime + _systemClock.CurrentTime - _pauseTime; // Restart the tree _timeState = TimeState.Running; // See if we need to tick sooner now that we are running if (GetNextTickNeeded() >= TimeSpan.Zero) { NotifyNewEarliestFutureActivity(); } } } /// /// Seeks the global clock to a new position. /// /// /// The seek offset, in milliseconds. The meaning of this parameter depends on the value of /// the origin parameter. /// /// /// The meaning of the offset parameter. See the enumeration /// for possible values. /// /// ///

This method seeks the global clock. The timelines of all timelines in the timing tree are /// also updated accordingly. Any events that those timelines would have fired in between the old /// and new clock positions are skipped.

/// ///

Because the global clock is infinite there is no defined end point, therefore seeking /// from the end position has no effect.

///
public void Seek(int offset, TimeSeekOrigin origin) { if (_timeState >= TimeState.Paused) { switch (origin) { case TimeSeekOrigin.BeginTime: // Use the offset as the new time; break; case TimeSeekOrigin.Duration: // Undefined for the global clock return; default: // Invalid enum argument throw new InvalidEnumArgumentException(SR.Get(SRID.Enum_Invalid, "TimeSeekOrigin")); } // Truncate to the beginning if (offset < 0) { offset = 0; } TimeSpan offsetTime = TimeSpan.FromMilliseconds((double)offset); // Only do the work if we actually moved if (offsetTime != _globalTime) { // Set the new global time _globalTime = offsetTime; // Adjust the starting time for future ticks _startTime =_systemClock.CurrentTime - _globalTime; // Update the timing tree accordingly _timeManagerClock.ComputeTreeState(); } } } /// /// Starts the time manager at the current time. /// /// /// The current time is determined by querying the reference clock for this /// time manager. /// public void Start() { // VerifyAccess(); if (_timeState == TimeState.Stopped) { // Reset the state of the timing tree _lastTickTime = TimeSpan.Zero; // Start the tree _startTime =_systemClock.CurrentTime; _globalTime = TimeSpan.Zero; _timeState = TimeState.Running; _timeManagerClock.RootActivate(); } } /// /// Stops the clock. /// /// /// After the clock is stopped, it can be restarted with a call to . /// This method has no effect if the clock was not in the running or paused state. /// /// /// public void Stop() { // VerifyAccess(); if (_timeState >= TimeState.Paused) { _timeManagerClock.RootDisable(); _timeState = TimeState.Stopped; } } /// /// Moves the clock forward to the current time and updates the state of /// all timing objects based on the time change. /// /// /// The associated reference clock is used to determine the current time. /// The new position of the clock will be equal to the difference between the /// starting system time and the current system time. The time manager requires /// the system time to move forward. /// //[CodeAnalysis("AptcaMethodsShouldOnlyCallAptcaMethods")] //Tracking Bug: 29647 public void Tick() { try { #if DEBUG // On a regular interval, clean up our tables of known // timelines and clocks if (++_frameCount >= 1000) // Should be about once every 10s { Timeline.CleanKnownTimelinesTable(); System.Windows.Media.Animation.Clock.CleanKnownClocksTable(); _frameCount = 0; } #endif // DEBUG // Don't need to worry about needing a tick sooner _nextTickTimeQueried = false; // Mark the tree as clean immediately. If any changes occur during // processing of the tick, the tree will be marked as dirty again. _isDirty = false; // No effect unless we are in the running state if (_timeState == TimeState.Running) { // Figure out the current time _globalTime = GetCurrentGlobalTime(); // Start the tick _isInTick = true; } // Trace the start of the tick and pass along the absolute time to which // we are ticking. EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnimation | EventTrace.Keyword.KeywordPerf, EventTrace.Event.WClientTimeManagerTickBegin, (_startTime + _globalTime).Ticks / TimeSpan.TicksPerMillisecond); // Run new property querying logic on the timing tree if (_lastTimeState == TimeState.Stopped && _timeState == TimeState.Stopped) // We were stopped the whole time { _currentTickInterval = TimeIntervalCollection.CreateNullPoint(); } else // We were not stopped at some time, so process the tick interval { _currentTickInterval = TimeIntervalCollection.CreateOpenClosedInterval(_lastTickTime, _globalTime); // If at either tick we were stopped, add the null point to represent that if (_lastTimeState == TimeState.Stopped || _timeState == TimeState.Stopped) { _currentTickInterval.AddNullPoint(); } } // Compute the tree state, using _currentTickInterval to compute the events that occured _timeManagerClock.ComputeTreeState(); // Cache TimeManager state at this time _lastTimeState = _timeState; // When the tick is done, we raise timing events RaiseEnqueuedEvents(); } finally { _isInTick = false; // Cache the tick time _lastTickTime = _globalTime; //trace the end of the tick EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordAnimation | EventTrace.Keyword.KeywordPerf, EventTrace.Event.WClientTimeManagerTickEnd); } // At the end of every tick clean up GC'ed clocks, if necessary CleanupClocks(); } // Get the maximumum desired framerate that has been set in one of our // top-level clock. internal int GetMaxDesiredFrameRate() { return _timeManagerClock.GetMaxDesiredFrameRate(); } #endregion // Methods #endregion // External interface #region Internal implementation #region Types /// /// A clock implementation that uses GetTickCount to get real-world clock values. /// internal class GTCClock : IClock { #region Internal implementation #region Methods /// /// Creates a GTCClock object. /// internal GTCClock() { } /// /// Gets the current real-world time. /// TimeSpan IClock.CurrentTime { get { return TimeSpan.FromTicks(DateTime.Now.Ticks); } } #endregion // Methods #endregion // Internal implementation } /// /// This is used by DrtTiming in order to specify the CurrentTime /// as it pleases. Note this class is internal /// internal class TestTimingClock : IClock { #region Internal implementation #region Methods /// /// Gets and sets the CurrentTime /// public TimeSpan CurrentTime { get { return _currentTime; } set { _currentTime = value; } } #endregion // Methods #region Data private TimeSpan _currentTime; #endregion // Data #endregion // Internal implementation } #endregion // Types #region Methods /// /// Removes references to garbage-collected clocks. /// private void CleanupClocks() { // We only run the clean-up procedure if we previously detected // that some clocks were GC'ed. The detection occurs in the // finalizer thread, perhaps concurrently with the execution of // this method. See the comments in the ScheduleClockCleanup // method for why we don't need to lock to protect against that // concurrency. if (_needClockCleanup) { // We are about to clean up, so clear the bit first. That way // if another thread sets the bit again then at worst we // have to run through the process one more time, with no ill // consequences other than a small run-time hit. If we wait to // set the bit until after we actually cleaned up we may fail // to detect any clocks that are GC'ed while we are cleaning // up. _needClockCleanup = false; _timeManagerClock.RootCleanChildren(); } } /// /// Puts a Clock into a queue for firing events. /// /// /// The object sending the event. /// /// /// The time at which the event is actually raised is implementation-specific and is /// subject to change at any time. /// internal void AddToEventQueue(Clock sender) { _eventQueue.Enqueue(sender.WeakReference); } /// /// Converts the current real world time into clock time. /// /// /// The global clock time. /// /// /// This function uses the custom clock provided in the TimeManager constructor, if any. /// internal TimeSpan GetCurrentGlobalTime() { // The conversion depends on our current state switch (_timeState) { case TimeState.Stopped: // If we are stopped, the current global time is always zero return TimeSpan.Zero; case TimeState.Paused: // If we are paused, the real-world clock appears to us as if it's // stopped at the pause time return _pauseTime - _startTime; case TimeState.Running: // If we are running, use the actual real-world clock. // However, keep time "frozen" while processing a tick or // by request. if (_isInTick || _lockTickTime) { return _globalTime; } else { return _systemClock.CurrentTime - _startTime; } default: Debug.Assert(false, "Unrecognized TimeState enumeration value"); return TimeSpan.Zero; } } /// /// Locks the current time for successive Tick calls. /// internal void LockTickTime() { _lockTickTime = true; } /// /// Called by timelines to tell the time manager that a future activity /// was scheduled at the head of the list. /// /// /// When a new future activity is added to the head, it means that the next /// tick is needed earlier than was thought before the activity was scheduled. /// This means that if the NextTickNeeded property was queried we need to /// notify the querying module that the value of the property needs to be /// queried again. /// internal void NotifyNewEarliestFutureActivity() { if (_nextTickTimeQueried && _userNeedTickSooner != null) { _nextTickTimeQueried = false; _userNeedTickSooner(this, EventArgs.Empty); } } /// /// Raises any events stored in the queue. /// private void RaiseEnqueuedEvents() { while (_eventQueue.Count > 0) { WeakReference instance = _eventQueue.Dequeue(); Clock clock = (Clock)instance.Target; if (clock != null) { clock.RaiseAccumulatedEvents(); } } } /// /// Schedules a clean-up pass to look for GC'ed clocks. /// /// /// This method can be called from any thread. In particular, it is /// safe to call this method from another object's finalizer. /// internal void ScheduleClockCleanup() { // To clean up GC'ed clocks we set a bit when a finalizer runs (in // the finalizer thread). Later, in the UI thraed, we run the // CleanupClocks method which checks whether this bit is set and // executes the clean-up procedure if it is. This can be done // entirely without locks, even on multiprocessor machines, // because of the sequence in which the variable is accessed. In // particular, note that this method sets the bit unconditionally, // which means we can essentially treat it as atomic. // // Consider the possible sequences: // // 1. The bit is already set when this method runs // In that case we are definitely in a safe situation because // we won't change the value of the bit, so this call is a // no-op. // // 2. The bit is not set when this method runs // In that case we need to consider what the UI thread may do. // Other threads running this method are irrelevant because // the method is essentially atomic, and thanks to UI context // affinity there can only be one UI thread running. If that // thread is running CleanupClocks at the same time that this // thread is running this method then there are three cases: // a) We set the bit before CleanupClocks checks the bit // This is obviously safe -- the other method will run the // clean-up procedure immediately. // b) We set the bit after CleanupClocks checks the bit but // before it clears it. This is actually case 1, because // CleanupClocks only does anything if the bit was already // set. Case 1 is a no-op, so we are safe. // c) We set the bit after CleanupClocks checks and clears // the bit. This may happen before, during or after the // clean-up procedure, but all cases are safe because this // bit won't be checked again by this invocation of // CleanupClocks. All that will happen is that we will // need to run CleanupClocks again at some point in the // future. In the worst case this is nothing more than a // small waste of time because the clock whose // finalization we are detecting may be cleaned up already // by the current invocation of CleanupClocks. However, // the operation is still safe. // // We are trading off the cost of locking every time against the // cost of an occasional unnecessary clean-up procedure. The latter // seems a lot less likely, and therefore smaller in the long run. _needClockCleanup = true; } /// /// Marks the timing tree as dirty. The tree is "cleaned" on the following /// tick. /// internal void SetDirty() { _isDirty = true; } /// /// Unlocks the current time such that the next tick will query the /// system time. /// internal void UnlockTickTime() { _lockTickTime = false; } #endregion // Methods #region Properties /// /// The current global time. /// internal TimeSpan InternalCurrentGlobalTime { get { return _globalTime; } } /// /// Whether the TimeManager is stopped /// internal bool InternalIsStopped { get { return (_timeState == TimeState.Stopped); } } internal TimeIntervalCollection InternalCurrentIntervals { get { return _currentTickInterval; } set { _currentTickInterval = value; // This allows temporarily setting custom interval values } } /// /// The time until the state of any time clock in the tree is expected /// to change. If the method returns a negative value, no state changes /// are expected any time in the future and therefore a tick will never /// be needed. /// /// /// This method is only a hint. Even if timelines don't change state, /// their progress values need to be updated regularly, requiring a tick at /// some minimum interval. In addition, certain interactive events can /// cause activity to be scheduled earlier than the previously known /// earliest activity, in which case a tick is needed sooner. To detect /// this condition, hook the event. /// internal TimeSpan GetNextTickNeeded() { // VerifyAccess(); _nextTickTimeQueried = true; if (_timeState == TimeState.Running) { // We are running, see how soon the tree needs updating TimeSpan? nextTickNeededTime = _timeManagerClock.InternalNextTickNeededTime; if (nextTickNeededTime.HasValue) { // Get the time the time manager has been running as of this moment. TimeSpan timeManagerCurrentTime = _systemClock.CurrentTime - _startTime; // Calculate how long from "now" we'll need another tick. TimeSpan nextTickNeededTimeFromCurrentTime = nextTickNeededTime.Value - timeManagerCurrentTime; if (nextTickNeededTimeFromCurrentTime <= TimeSpan.Zero) { // We've already past the time at which we need another // tick so tick again ASAP. return TimeSpan.Zero; } else { // Return the time until we need another tick return nextTickNeededTimeFromCurrentTime; } } else // The tree does not need any ticks in the future { return TimeSpan.FromTicks(-1); } } else { // Our state is not Running, so we don't have any ticks coming up return TimeSpan.FromTicks(-1); } } /// /// Unchecked internal access to the time shift since the last tick. /// internal TimeSpan LastTickDelta { get { return _globalTime - _lastTickTime; } } /// /// Internal access to the time since the last tick. /// internal TimeSpan LastTickTime { get { return _lastTickTime; } } /// /// Returns the clock that is the root of the timing tree managed by /// this time manager. /// internal ClockGroup TimeManagerClock { get { // VerifyAccess(); return _timeManagerClock; } } /// /// The present state of the time manager. /// internal TimeState State { get { return _timeState; } } #endregion // Properties #region Events /// /// Raised when a change has been made to the timing engine that may require /// it to be ticked sooner than was previously expected. /// internal event EventHandler NeedTickSooner { add { // VerifyAccess(); _userNeedTickSooner += value; } remove { // VerifyAccess(); _userNeedTickSooner -= value; } } #endregion // Events #region Debugging instrumentation #if DEBUG /// /// Dumps the internal state of the whole timing tree /// internal void Dump() { _timeManagerClock.Dump(); } #endif // DEBUG #endregion // Debugging instrumentation #region Data #region Timing data private TimeState _timeState; private TimeState _lastTimeState; private IClock _systemClock; private TimeSpan _globalTime; private TimeSpan _startTime; private TimeSpan _lastTickTime; private TimeSpan _pauseTime; private TimeIntervalCollection _currentTickInterval; private bool _nextTickTimeQueried, _isDirty, _isInTick, _lockTickTime; private EventHandler _userNeedTickSooner; private ClockGroup _timeManagerClock; private Queue _eventQueue; #endregion // Timing data #region Linking data private bool _needClockCleanup; #endregion // Linking data #region Debug data #if DEBUG private int _frameCount; #endif // DEBUG #endregion // Debug data #endregion // Data #endregion // Internal implementation } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK