Renamed ProgressionTracker to ProgressTracker; renamed most classes with 'Progression' in their name to use 'Waitable' instead; some minor documentation fixes
git-svn-id: file:///srv/devel/repo-conversion/nusu@65 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
		
							parent
							
								
									7dbfc3c422
								
							
						
					
					
						commit
						432ab888b9
					
				
					 10 changed files with 435 additions and 433 deletions
				
			
		|  | @ -145,20 +145,20 @@ | |||
|     <Compile Include="Source\SpatialPartitioning\RTreeNode2.cs" /> | ||||
|     <Compile Include="Source\SpatialPartitioning\SpatialIndex2.cs" /> | ||||
|     <Compile Include="Source\Tracking\IdleStateEventArgs.cs" /> | ||||
|     <Compile Include="Source\Tracking\Internal\ObservedWeightedProgression.cs" /> | ||||
|     <Compile Include="Source\Tracking\Internal\WeightedProgressionWrapperCollection.cs" /> | ||||
|     <Compile Include="Source\Tracking\Internal\ObservedWeightedWaitable.cs" /> | ||||
|     <Compile Include="Source\Tracking\Internal\WeightedWaitableWrapperCollection.cs" /> | ||||
|     <Compile Include="Source\Tracking\IProgressReporter.cs" /> | ||||
|     <Compile Include="Source\Tracking\IStatusReporter.cs" /> | ||||
|     <Compile Include="Source\Tracking\Request.cs" /> | ||||
|     <Compile Include="Source\Tracking\Waitable.cs" /> | ||||
|     <Compile Include="Source\Tracking\ProgressionTracker.cs" /> | ||||
|     <Compile Include="Source\Tracking\ProgressionTracker.Test.cs"> | ||||
|       <DependentUpon>ProgressionTracker.cs</DependentUpon> | ||||
|     <Compile Include="Source\Tracking\ProgressTracker.cs" /> | ||||
|     <Compile Include="Source\Tracking\ProgressTracker.Test.cs"> | ||||
|       <DependentUpon>ProgressTracker.cs</DependentUpon> | ||||
|     </Compile> | ||||
|     <Compile Include="Source\Tracking\ProgressUpdateEventArgs.cs" /> | ||||
|     <Compile Include="Source\Tracking\SetProgression.cs" /> | ||||
|     <Compile Include="Source\Tracking\SetProgression.Test.cs"> | ||||
|       <DependentUpon>ProgressionTracker.cs</DependentUpon> | ||||
|       <DependentUpon>SetProgression.cs</DependentUpon> | ||||
|     </Compile> | ||||
|     <Compile Include="Source\Tracking\StatusReportEventArgs.cs" /> | ||||
|     <Compile Include="Source\Tracking\WeightedProgression.cs" /> | ||||
|  |  | |||
|  | @ -45,7 +45,7 @@ namespace Nuclex.Support.Scheduling { | |||
|       // Construct a WeightedProgression with the default weight for each | ||||
|       // progression and wrap it in an ObservedProgression | ||||
|       foreach(OperationType operation in childs) | ||||
|         this.children.Add(new WeightedProgression<OperationType>(operation)); | ||||
|         this.children.Add(new WeightedWaitable<OperationType>(operation)); | ||||
| 
 | ||||
|       // Since all progressions have a weight of 1.0, the total weight is | ||||
|       // equal to the number of progressions in our list | ||||
|  | @ -55,10 +55,10 @@ namespace Nuclex.Support.Scheduling { | |||
| 
 | ||||
|     /// <summary>Initializes a new queue operation with custom weights</summary> | ||||
|     /// <param name="childs">Child operations to execute in this operation</param> | ||||
|     public QueueOperation(IEnumerable<WeightedProgression<OperationType>> childs) : this() { | ||||
|     public QueueOperation(IEnumerable<WeightedWaitable<OperationType>> childs) : this() { | ||||
| 
 | ||||
|       // Construct an ObservedProgression around each of the WeightedProgressions | ||||
|       foreach(WeightedProgression<OperationType> operation in childs) { | ||||
|       foreach(WeightedWaitable<OperationType> operation in childs) { | ||||
|         this.children.Add(operation); | ||||
| 
 | ||||
|         // Sum up the total weight | ||||
|  | @ -74,11 +74,11 @@ namespace Nuclex.Support.Scheduling { | |||
|         asyncOperationProgressChanged | ||||
|       ); | ||||
| 
 | ||||
|       this.children = new List<WeightedProgression<OperationType>>(); | ||||
|       this.children = new List<WeightedWaitable<OperationType>>(); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Provides access to the child operations of this queue</summary> | ||||
|     public IList<WeightedProgression<OperationType>> Children { | ||||
|     public IList<WeightedWaitable<OperationType>> Children { | ||||
|       get { return this.children; } | ||||
|     } | ||||
| 
 | ||||
|  | @ -125,7 +125,7 @@ namespace Nuclex.Support.Scheduling { | |||
|     ///   and launches the operation by calling its Begin() method. | ||||
|     /// </remarks> | ||||
|     private void startCurrentOperation() { | ||||
|       OperationType operation = this.children[this.currentOperationIndex].Progression; | ||||
|       OperationType operation = this.children[this.currentOperationIndex].Waitable; | ||||
| 
 | ||||
|       operation.AsyncEnded += this.asyncOperationEndedDelegate; | ||||
| 
 | ||||
|  | @ -143,7 +143,7 @@ namespace Nuclex.Support.Scheduling { | |||
|     ///   counts up the accumulated progress of the queue. | ||||
|     /// </remarks> | ||||
|     private void endCurrentOperation() { | ||||
|       OperationType operation = this.children[this.currentOperationIndex].Progression; | ||||
|       OperationType operation = this.children[this.currentOperationIndex].Waitable; | ||||
| 
 | ||||
|       // Disconnect from the operation's events | ||||
|       operation.AsyncEnded -= this.asyncOperationEndedDelegate; | ||||
|  | @ -217,7 +217,7 @@ namespace Nuclex.Support.Scheduling { | |||
|     /// <summary>Delegate to the asyncOperationProgressUpdated() method</summary> | ||||
|     private EventHandler<ProgressReportEventArgs> asyncOperationProgressChangedDelegate; | ||||
|     /// <summary>Operations being managed in the queue</summary> | ||||
|     private List<WeightedProgression<OperationType>> children; | ||||
|     private List<WeightedWaitable<OperationType>> children; | ||||
|     /// <summary>Summed weight of all operations in the queue</summary> | ||||
|     private float totalWeight; | ||||
|     /// <summary>Accumulated weight of the operations already completed</summary> | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ namespace Nuclex.Support.Tracking { | |||
|   /// <typeparam name="ProgressionType"> | ||||
|   ///   Type of the progression that is being observed | ||||
|   /// </typeparam> | ||||
|   internal class ObservedWeightedProgression<ProgressionType> : IDisposable | ||||
|   internal class ObservedWeightedWaitable<ProgressionType> : IDisposable | ||||
|     where ProgressionType : Waitable { | ||||
| 
 | ||||
|     /// <summary>Delegate for reporting progress updates</summary> | ||||
|  | @ -41,15 +41,15 @@ namespace Nuclex.Support.Tracking { | |||
|     /// <param name="endedCallback"> | ||||
|     ///   Callback to invoke when the progression has ended | ||||
|     /// </param> | ||||
|     internal ObservedWeightedProgression( | ||||
|       WeightedProgression<ProgressionType> weightedProgression, | ||||
|     internal ObservedWeightedWaitable( | ||||
|       WeightedWaitable<ProgressionType> weightedProgression, | ||||
|       ReportDelegate progressUpdateCallback, | ||||
|       ReportDelegate endedCallback | ||||
|     ) { | ||||
|       this.weightedProgression = weightedProgression; | ||||
| 
 | ||||
|       // See if this progression has already ended (initial check for performance) | ||||
|       if(weightedProgression.Progression.Ended) { | ||||
|       if(weightedProgression.Waitable.Ended) { | ||||
| 
 | ||||
|         this.progress = 1.0f; | ||||
| 
 | ||||
|  | @ -58,7 +58,7 @@ namespace Nuclex.Support.Tracking { | |||
|         this.endedCallback = endedCallback; | ||||
|         this.progressUpdateCallback = progressUpdateCallback; | ||||
| 
 | ||||
|         this.weightedProgression.Progression.AsyncEnded += | ||||
|         this.weightedProgression.Waitable.AsyncEnded += | ||||
|           new EventHandler(asyncEnded); | ||||
| 
 | ||||
|         // Check whether this progression might have ended before we were able to | ||||
|  | @ -66,10 +66,10 @@ namespace Nuclex.Support.Tracking { | |||
|         // other event and (important) set our progress to 1.0 because, since we | ||||
|         // might not have gotten the 'Ended' event, it might otherwise stay at 0.0 | ||||
|         // even though the progression is in the 'Ended' state. | ||||
|         if(weightedProgression.Progression.Ended) { | ||||
|         if(weightedProgression.Waitable.Ended) { | ||||
|           this.progress = 1.0f; | ||||
|         } else { | ||||
|           this.progressReporter = this.weightedProgression.Progression as IProgressReporter; | ||||
|           this.progressReporter = this.weightedProgression.Waitable as IProgressReporter; | ||||
| 
 | ||||
|           if(this.progressReporter != null) { | ||||
|             this.asyncProgressChangedEventHandler = new EventHandler<ProgressReportEventArgs>( | ||||
|  | @ -90,7 +90,7 @@ namespace Nuclex.Support.Tracking { | |||
|     } | ||||
| 
 | ||||
|     /// <summary>Weighted progression being observed</summary> | ||||
|     public WeightedProgression<ProgressionType> WeightedProgression { | ||||
|     public WeightedWaitable<ProgressionType> WeightedWaitable { | ||||
|       get { return this.weightedProgression; } | ||||
|     } | ||||
| 
 | ||||
|  | @ -143,7 +143,7 @@ namespace Nuclex.Support.Tracking { | |||
|         // is no risk of deadlock involved, so we don't need a fancy syncRoot! | ||||
|         lock(this) { | ||||
|           if(this.endedCallback != null) { | ||||
|             this.weightedProgression.Progression.AsyncEnded -= | ||||
|             this.weightedProgression.Waitable.AsyncEnded -= | ||||
|               new EventHandler(asyncEnded); | ||||
| 
 | ||||
|             if(this.progressReporter != null) { | ||||
|  | @ -166,7 +166,7 @@ namespace Nuclex.Support.Tracking { | |||
|     /// <summary>The observed progression's progress reporting interface</summary> | ||||
|     private IProgressReporter progressReporter; | ||||
|     /// <summary>The weighted progression that is being observed</summary> | ||||
|     private WeightedProgression<ProgressionType> weightedProgression; | ||||
|     private WeightedWaitable<ProgressionType> weightedProgression; | ||||
|     /// <summary>Callback to invoke when the progress updates</summary> | ||||
|     private volatile ReportDelegate progressUpdateCallback; | ||||
|     /// <summary>Callback to invoke when the progression ends</summary> | ||||
|  | @ -49,14 +49,14 @@ namespace Nuclex.Support.Tracking { | |||
|   /// </remarks> | ||||
|   internal class WeightedProgressionWrapperCollection<ProgressionType> : | ||||
|     TransformingReadOnlyCollection< | ||||
|       ObservedWeightedProgression<ProgressionType>, WeightedProgression<ProgressionType> | ||||
|       ObservedWeightedWaitable<ProgressionType>, WeightedWaitable<ProgressionType> | ||||
|     > | ||||
|     where ProgressionType : Waitable { | ||||
| 
 | ||||
|     /// <summary>Initializes a new weighted progression collection wrapper</summary> | ||||
|     /// <param name="items">Items to be exposed as weighted progressions</param> | ||||
|     internal WeightedProgressionWrapperCollection( | ||||
|       IList<ObservedWeightedProgression<ProgressionType>> items | ||||
|       IList<ObservedWeightedWaitable<ProgressionType>> items | ||||
|     ) | ||||
|       : base(items) { } | ||||
| 
 | ||||
|  | @ -69,10 +69,10 @@ namespace Nuclex.Support.Tracking { | |||
|     ///   be called frequently, because the TransformingReadOnlyCollection does | ||||
|     ///   not cache otherwise store the transformed items. | ||||
|     /// </remarks> | ||||
|     protected override WeightedProgression<ProgressionType> Transform( | ||||
|       ObservedWeightedProgression<ProgressionType> item | ||||
|     protected override WeightedWaitable<ProgressionType> Transform( | ||||
|       ObservedWeightedWaitable<ProgressionType> item | ||||
|     ) { | ||||
|       return item.WeightedProgression; | ||||
|       return item.WeightedWaitable; | ||||
|     } | ||||
| 
 | ||||
|   } | ||||
|  | @ -148,7 +148,7 @@ namespace Nuclex.Support.Tracking { | |||
|     /// <summary>Validates that the tracker properly sums the progress</summary> | ||||
|     [Test] | ||||
|     public void TestSummedProgress() { | ||||
|       ProgressionTracker tracker = new ProgressionTracker(); | ||||
|       ProgressTracker tracker = new ProgressTracker(); | ||||
| 
 | ||||
|       IProgressionTrackerSubscriber mockedSubscriber = mockSubscriber(tracker); | ||||
| 
 | ||||
|  | @ -160,7 +160,7 @@ namespace Nuclex.Support.Tracking { | |||
|         Method("ProgressUpdated"). | ||||
|         With( | ||||
|           new Matcher[] { | ||||
|             new NMock2.Matchers.TypeMatcher(typeof(ProgressionTracker)), | ||||
|             new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)), | ||||
|             new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.0f)) | ||||
|           } | ||||
|         ); | ||||
|  | @ -174,7 +174,7 @@ namespace Nuclex.Support.Tracking { | |||
|         Method("ProgressUpdated"). | ||||
|         With( | ||||
|           new Matcher[] { | ||||
|             new NMock2.Matchers.TypeMatcher(typeof(ProgressionTracker)), | ||||
|             new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)), | ||||
|             new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.25f)) | ||||
|           } | ||||
|         ); | ||||
|  | @ -194,7 +194,7 @@ namespace Nuclex.Support.Tracking { | |||
|     /// </remarks> | ||||
|     [Test] | ||||
|     public void TestDelayedRemoval() { | ||||
|       ProgressionTracker tracker = new ProgressionTracker(); | ||||
|       ProgressTracker tracker = new ProgressTracker(); | ||||
| 
 | ||||
|       IProgressionTrackerSubscriber mockedSubscriber = mockSubscriber(tracker); | ||||
| 
 | ||||
|  | @ -206,7 +206,7 @@ namespace Nuclex.Support.Tracking { | |||
|         Method("ProgressUpdated"). | ||||
|         With( | ||||
|           new Matcher[] { | ||||
|             new NMock2.Matchers.TypeMatcher(typeof(ProgressionTracker)), | ||||
|             new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)), | ||||
|             new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.0f)) | ||||
|           } | ||||
|         ); | ||||
|  | @ -220,7 +220,7 @@ namespace Nuclex.Support.Tracking { | |||
|         Method("ProgressUpdated"). | ||||
|         With( | ||||
|           new Matcher[] { | ||||
|             new NMock2.Matchers.TypeMatcher(typeof(ProgressionTracker)), | ||||
|             new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)), | ||||
|             new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.25f)) | ||||
|           } | ||||
|         ); | ||||
|  | @ -231,7 +231,7 @@ namespace Nuclex.Support.Tracking { | |||
|         Method("ProgressUpdated"). | ||||
|         With( | ||||
|           new Matcher[] { | ||||
|             new NMock2.Matchers.TypeMatcher(typeof(ProgressionTracker)), | ||||
|             new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)), | ||||
|             new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.75f)) | ||||
|           } | ||||
|         ); | ||||
|  | @ -245,7 +245,7 @@ namespace Nuclex.Support.Tracking { | |||
|         Method("ProgressUpdated"). | ||||
|         With( | ||||
|           new Matcher[] { | ||||
|             new NMock2.Matchers.TypeMatcher(typeof(ProgressionTracker)), | ||||
|             new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)), | ||||
|             new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(1.0f)) | ||||
|           } | ||||
|         ); | ||||
|  | @ -265,7 +265,7 @@ namespace Nuclex.Support.Tracking { | |||
|     /// </summary> | ||||
|     [Test] | ||||
|     public void TestSoleEndedProgression() { | ||||
|       ProgressionTracker tracker = new ProgressionTracker(); | ||||
|       ProgressTracker tracker = new ProgressTracker(); | ||||
| 
 | ||||
|       IProgressionTrackerSubscriber mockedSubscriber = mockSubscriber(tracker); | ||||
| 
 | ||||
|  | @ -280,7 +280,7 @@ namespace Nuclex.Support.Tracking { | |||
|     /// </summary> | ||||
|     [Test] | ||||
|     public void TestEndedProgression() { | ||||
|       ProgressionTracker tracker = new ProgressionTracker(); | ||||
|       ProgressTracker tracker = new ProgressTracker(); | ||||
| 
 | ||||
|       IProgressionTrackerSubscriber mockedSubscriber = mockSubscriber(tracker); | ||||
| 
 | ||||
|  | @ -292,7 +292,7 @@ namespace Nuclex.Support.Tracking { | |||
|         Method("ProgressUpdated"). | ||||
|         With( | ||||
|           new Matcher[] { | ||||
|             new NMock2.Matchers.TypeMatcher(typeof(ProgressionTracker)), | ||||
|             new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)), | ||||
|             new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.0f)) | ||||
|           } | ||||
|         ); | ||||
|  | @ -304,7 +304,7 @@ namespace Nuclex.Support.Tracking { | |||
|         Method("ProgressUpdated"). | ||||
|         With( | ||||
|           new Matcher[] { | ||||
|             new NMock2.Matchers.TypeMatcher(typeof(ProgressionTracker)), | ||||
|             new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)), | ||||
|             new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(0.5f)) | ||||
|           } | ||||
|         ); | ||||
|  | @ -315,7 +315,7 @@ namespace Nuclex.Support.Tracking { | |||
|         Method("ProgressUpdated"). | ||||
|         With( | ||||
|           new Matcher[] { | ||||
|             new NMock2.Matchers.TypeMatcher(typeof(ProgressionTracker)), | ||||
|             new NMock2.Matchers.TypeMatcher(typeof(ProgressTracker)), | ||||
|             new ProgressUpdateEventArgsMatcher(new ProgressReportEventArgs(1.0f)) | ||||
|           } | ||||
|         ); | ||||
|  | @ -335,7 +335,7 @@ namespace Nuclex.Support.Tracking { | |||
|     /// </summary> | ||||
|     [Test] | ||||
|     public void TestProvokedDeadlock() { | ||||
|       ProgressionTracker tracker = new ProgressionTracker(); | ||||
|       ProgressTracker tracker = new ProgressTracker(); | ||||
| 
 | ||||
|       TestProgression test1 = new TestProgression(); | ||||
|       tracker.Track(test1); | ||||
|  | @ -351,14 +351,14 @@ namespace Nuclex.Support.Tracking { | |||
|     /// <summary>Mocks a subscriber for the events of a tracker</summary> | ||||
|     /// <param name="tracker">Tracker to mock an event subscriber for</param> | ||||
|     /// <returns>The mocked event subscriber</returns> | ||||
|     private IProgressionTrackerSubscriber mockSubscriber(ProgressionTracker tracker) { | ||||
|     private IProgressionTrackerSubscriber mockSubscriber(ProgressTracker tracker) { | ||||
|       IProgressionTrackerSubscriber mockedSubscriber = | ||||
|         this.mockery.NewMock<IProgressionTrackerSubscriber>(); | ||||
| 
 | ||||
|       tracker.AsyncIdleStateChanged += | ||||
|         new EventHandler<IdleStateEventArgs>(mockedSubscriber.IdleStateChanged); | ||||
| 
 | ||||
|       tracker.AsyncProgressUpdated += | ||||
|       tracker.AsyncProgressChanged += | ||||
|         new EventHandler<ProgressReportEventArgs>(mockedSubscriber.ProgressUpdated); | ||||
| 
 | ||||
|       return mockedSubscriber; | ||||
							
								
								
									
										364
									
								
								Source/Tracking/ProgressTracker.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										364
									
								
								Source/Tracking/ProgressTracker.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,364 @@ | |||
| #region CPL License | ||||
| /* | ||||
| Nuclex Framework | ||||
| Copyright (C) 2002-2008 Nuclex Development Labs | ||||
| 
 | ||||
| This library is free software; you can redistribute it and/or | ||||
| modify it under the terms of the IBM Common Public License as | ||||
| published by the IBM Corporation; either version 1.0 of the | ||||
| License, or (at your option) any later version. | ||||
| 
 | ||||
| This library is distributed in the hope that it will be useful, | ||||
| but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| IBM Common Public License for more details. | ||||
| 
 | ||||
| You should have received a copy of the IBM Common Public | ||||
| License along with this library | ||||
| */ | ||||
| #endregion | ||||
| 
 | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Threading; | ||||
| 
 | ||||
| namespace Nuclex.Support.Tracking { | ||||
| 
 | ||||
|   /// <summary> | ||||
|   ///   Helps tracking the progress of one or more waitable background processes | ||||
|   /// </summary> | ||||
|   /// <remarks> | ||||
|   ///   <para> | ||||
|   ///     This is useful if you want to display a progress bar for multiple | ||||
|   ///     Waitables but can not guarantee that no additional Waitables | ||||
|   ///     will appear inmidst of execution. | ||||
|   ///   </para> | ||||
|   ///   <para> | ||||
|   ///     This class does not implement the <see cref="Waitable" /> interface itself | ||||
|   ///     in order to not violate the design principles of Waitables which | ||||
|   ///     guarantee that a <see cref="Waitable" /> will only finish once (whereas the | ||||
|   ///     progress tracker might 'finish' any number of times). | ||||
|   ///   </para> | ||||
|   /// </remarks> | ||||
|   public class ProgressTracker : IDisposable, IProgressReporter { | ||||
| 
 | ||||
|     #region class WaitableMatcher | ||||
| 
 | ||||
|     /// <summary>Matches a direct Waitable to a fully wrapped one</summary> | ||||
|     private class WaitableMatcher { | ||||
| 
 | ||||
|       /// <summary> | ||||
|       ///   Initializes a new Waitable matcher that matches against | ||||
|       ///   the specified Waitable | ||||
|       /// </summary> | ||||
|       /// <param name="toMatch">Waitable to match against</param> | ||||
|       public WaitableMatcher(Waitable toMatch) { | ||||
|         this.toMatch = toMatch; | ||||
|       } | ||||
| 
 | ||||
|       /// <summary> | ||||
|       ///   Checks whether the provided Waitable matches the comparison | ||||
|       ///   Waitable of the instance | ||||
|       /// </summary> | ||||
|       /// <param name="other">Waitable to match to the comparison Waitable</param> | ||||
|       public bool Matches(ObservedWeightedWaitable<Waitable> other) { | ||||
|         return ReferenceEquals(other.WeightedWaitable.Waitable, this.toMatch); | ||||
|       } | ||||
| 
 | ||||
|       /// <summary>Waitable this instance compares against</summary> | ||||
|       private Waitable toMatch; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     #endregion // class WaitableMatcher | ||||
| 
 | ||||
|     /// <summary>Triggered when the idle state of the tracker changes</summary> | ||||
|     /// <remarks> | ||||
|     ///   The tracker is idle when no Waitables are being tracked in it. If you're | ||||
|     ///   using this class to feed a progress bar, this would be the event to use for | ||||
|     ///   showing or hiding the progress bar. The tracker starts off as idle because, | ||||
|     ///   upon construction, its list of Waitables will be empty. | ||||
|     /// </remarks> | ||||
|     public event EventHandler<IdleStateEventArgs> AsyncIdleStateChanged; | ||||
| 
 | ||||
|     /// <summary>Triggered when the total progress has changed</summary> | ||||
|     public event EventHandler<ProgressReportEventArgs> AsyncProgressChanged; | ||||
| 
 | ||||
|     /// <summary>Initializes a new Waitable tracker</summary> | ||||
|     public ProgressTracker() { | ||||
| 
 | ||||
|       this.trackedWaitables = new List<ObservedWeightedWaitable<Waitable>>(); | ||||
|       this.idle = true; | ||||
| 
 | ||||
|       this.asyncEndedDelegate = | ||||
|         new ObservedWeightedWaitable<Waitable>.ReportDelegate(asyncEnded); | ||||
|       this.asyncProgressUpdatedDelegate = | ||||
|         new ObservedWeightedWaitable<Waitable>.ReportDelegate(asyncProgressChanged); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Immediately releases all resources owned by the instance</summary> | ||||
|     public void Dispose() { | ||||
|       lock(this.trackedWaitables) { | ||||
| 
 | ||||
|         // Get rid of all Waitables we're tracking. This unsubscribes the | ||||
|         // observers from the events of the Waitables and stops us from | ||||
|         // being kept alive and receiving any further events if some of the | ||||
|         // tracked Waitables are still executing. | ||||
|         for(int index = 0; index < this.trackedWaitables.Count; ++index) | ||||
|           this.trackedWaitables[index].Dispose(); | ||||
| 
 | ||||
|         // Help the GC a bit by untangling the references :) | ||||
|         this.trackedWaitables.Clear(); | ||||
|         this.trackedWaitables = null; | ||||
| 
 | ||||
|       } // lock | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Begins tracking the specified waitable background process</summary> | ||||
|     /// <param name="waitable">Waitable background process to be tracked</param> | ||||
|     public void Track(Waitable waitable) { | ||||
|       Track(waitable, 1.0f); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Begins tracking the specified waitable background process</summary> | ||||
|     /// <param name="waitable">Waitable background process to be tracked</param> | ||||
|     /// <param name="weight">Weight to assign to this waitable background process</param> | ||||
|     public void Track(Waitable waitable, float weight) { | ||||
| 
 | ||||
|       // Add the new Waitable into the tracking list. This has to be done | ||||
|       // inside a lock to prevent issues with the progressUpdate callback, which could | ||||
|       // access the totalWeight field before it has been updated to reflect the | ||||
|       // new Waitable added to the collection. | ||||
|       lock(this.trackedWaitables) { | ||||
| 
 | ||||
|         bool wasEmpty = (this.trackedWaitables.Count == 0); | ||||
| 
 | ||||
|         // This can be done after we registered the wrapper to our delegates because | ||||
|         // any incoming progress updates will be stopped from the danger of a | ||||
|         // division-by-zero from the potentially still zeroed totalWeight by the lock. | ||||
|         this.totalWeight += weight; | ||||
| 
 | ||||
|         if(waitable.Ended) { | ||||
| 
 | ||||
|           // If the ended Waitable would become the only Waitable in the list, | ||||
|           // there's no sense in doing anything at all because it would have to be | ||||
|           // thrown right out again. Only add the Waitable when there are other | ||||
|           // running Waitables to properly sum total progress for consistency. | ||||
|           if(!wasEmpty) { | ||||
| 
 | ||||
|             // Construct a new observation wrapper. This is done inside the lock | ||||
|             // because as soon as we are subscribed to the events, we can potentially | ||||
|             // receive them. The lock eliminates the risk of processing a progress update | ||||
|             // before the Waitable has been added to the tracked Waitables list. | ||||
|             this.trackedWaitables.Add( | ||||
|               new ObservedWeightedWaitable<Waitable>( | ||||
|                 new WeightedWaitable<Waitable>(waitable, weight), | ||||
|                 this.asyncProgressUpdatedDelegate, | ||||
|                 this.asyncEndedDelegate | ||||
|               ) | ||||
|             ); | ||||
| 
 | ||||
|             // All done, the total progress is different now, so force a recalculation and | ||||
|             // send out the AsyncProgressUpdated event. | ||||
|             recalculateProgress(); | ||||
| 
 | ||||
|           } | ||||
| 
 | ||||
|         } else { // Not ended -- Waitable is still running | ||||
| 
 | ||||
|           // Construct a new Waitable observer and add the Waitable to our | ||||
|           // list of tracked Waitables. | ||||
|           ObservedWeightedWaitable<Waitable> observedWaitable = | ||||
|             new ObservedWeightedWaitable<Waitable>( | ||||
|               new WeightedWaitable<Waitable>(waitable, weight), | ||||
|               this.asyncProgressUpdatedDelegate, | ||||
|               this.asyncEndedDelegate | ||||
|             ); | ||||
| 
 | ||||
|           this.trackedWaitables.Add(observedWaitable); | ||||
| 
 | ||||
|           // If this is the first Waitable to be added to the list, tell our | ||||
|           // owner that we're idle no longer! | ||||
|           if(wasEmpty) | ||||
|             setIdle(false); | ||||
| 
 | ||||
|           // All done, the total progress is different now, so force a recalculation and | ||||
|           // send out the AsyncProgressUpdated event. | ||||
|           recalculateProgress(); | ||||
| 
 | ||||
|           // The Waitable might have ended before we had registered to its AsyncEnded | ||||
|           // event, so we have to do this to be on the safe side. This might cause | ||||
|           // asyncEnded() to be called twice, but that's not a problem in this | ||||
|           // implementation and improves performance and simplicity for the normal path. | ||||
|           if(waitable.Ended) { | ||||
|             asyncEnded(); | ||||
|             observedWaitable.Dispose(); | ||||
|           } | ||||
| 
 | ||||
|         } // if Waitable ended | ||||
| 
 | ||||
|       } // lock | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Stops tracking the specified waitable background process</summary> | ||||
|     /// <param name="waitable">Waitable background process to stop tracking of</param> | ||||
|     public void Untrack(Waitable waitable) { | ||||
|       lock(this.trackedWaitables) { | ||||
| 
 | ||||
|         // Locate the object to be untracked in our collection | ||||
|         int removeIndex = this.trackedWaitables.FindIndex( | ||||
|           new Predicate<ObservedWeightedWaitable<Waitable>>( | ||||
|             new WaitableMatcher(waitable).Matches | ||||
|           ) | ||||
|         ); | ||||
|         if(removeIndex == -1) | ||||
|           throw new InvalidOperationException("Item is not being tracked"); | ||||
| 
 | ||||
|         // Remove and dispose the Waitable the user wants to untrack | ||||
|         { | ||||
|           ObservedWeightedWaitable<Waitable> wrappedWaitable = | ||||
|             this.trackedWaitables[removeIndex]; | ||||
| 
 | ||||
|           this.trackedWaitables.RemoveAt(removeIndex); | ||||
|           wrappedWaitable.Dispose(); | ||||
|         } | ||||
| 
 | ||||
|         // If the list is empty, then we're back in the idle state | ||||
|         if(this.trackedWaitables.Count == 0) { | ||||
| 
 | ||||
|           this.totalWeight = 0.0f; | ||||
| 
 | ||||
|           // If we entered the idle state with this call, report the state change! | ||||
|           setIdle(true); | ||||
| 
 | ||||
|         } else { | ||||
| 
 | ||||
|           // Rebuild the total weight from scratch. Subtracting the removed Waitable's | ||||
|           // weight would work, too, but we might accumulate rounding errors making the sum | ||||
|           // drift slowly away from the actual value. | ||||
|           this.totalWeight = 0.0f; | ||||
|           for(int index = 0; index < this.trackedWaitables.Count; ++index) | ||||
|             this.totalWeight += this.trackedWaitables[index].WeightedWaitable.Weight; | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|       } // lock | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Whether the tracker is currently idle</summary> | ||||
|     public bool Idle { | ||||
|       get { return this.idle; } | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Current summed progress of the tracked Waitables</summary> | ||||
|     public float Progress { | ||||
|       get { return this.progress; } | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Fires the AsyncIdleStateChanged event</summary> | ||||
|     /// <param name="idle">New idle state to report</param> | ||||
|     protected virtual void OnAsyncIdleStateChanged(bool idle) { | ||||
|       EventHandler<IdleStateEventArgs> copy = AsyncIdleStateChanged; | ||||
|       if(copy != null) | ||||
|         copy(this, new IdleStateEventArgs(idle)); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Fires the AsyncProgressUpdated event</summary> | ||||
|     /// <param name="progress">New progress to report</param> | ||||
|     protected virtual void OnAsyncProgressUpdated(float progress) { | ||||
|       EventHandler<ProgressReportEventArgs> copy = AsyncProgressChanged; | ||||
|       if(copy != null) | ||||
|         copy(this, new ProgressReportEventArgs(progress)); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Recalculates the total progress of the tracker</summary> | ||||
|     private void recalculateProgress() { | ||||
|       float totalProgress = 0.0f; | ||||
| 
 | ||||
|       // Lock the collection to avoid trouble when someone tries to remove one | ||||
|       // of our tracked Waitables while we're just doing a progress update | ||||
|       lock(this.trackedWaitables) { | ||||
| 
 | ||||
|         // This is a safety measure. In theory, even after all Waitables have | ||||
|         // ended and the collection of tracked Waitables is cleared, a waiting | ||||
|         // thread might deliver another progress update causing this method to | ||||
|         // be entered. In this case, the right thing is to do nothing at all. | ||||
|         if(this.totalWeight == 0.0f) | ||||
|           return; | ||||
| 
 | ||||
|         // Sum up the total progress | ||||
|         for(int index = 0; index < this.trackedWaitables.Count; ++index) { | ||||
|           float weight = this.trackedWaitables[index].WeightedWaitable.Weight; | ||||
|           totalProgress += this.trackedWaitables[index].Progress * weight; | ||||
|         } | ||||
| 
 | ||||
|         // This also needs to be in the lock to guarantee that the totalWeight is | ||||
|         // the one for the number of Waitables we just summed -- by design, | ||||
|         // the total weight always has to be updated at the same time as the collection. | ||||
|         totalProgress /= this.totalWeight; | ||||
| 
 | ||||
|         // Finally, trigger the event | ||||
|         this.progress = totalProgress; | ||||
|         OnAsyncProgressUpdated(totalProgress); | ||||
| 
 | ||||
|       } // lock | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Called when one of the tracked Waitables has ended</summary> | ||||
|     private void asyncEnded() { | ||||
|       lock(this.trackedWaitables) { | ||||
| 
 | ||||
|         // If any Waitables in the list are still going, keep the entire list. | ||||
|         // This behavior is intentional in order to prevent the tracker's progress from | ||||
|         // jumping back repeatedly when multiple tracked Waitables come to an end. | ||||
|         for(int index = 0; index < this.trackedWaitables.Count; ++index) | ||||
|           if(!this.trackedWaitables[index].WeightedWaitable.Waitable.Ended) | ||||
|             return; | ||||
| 
 | ||||
|         // All Waitables have finished, get rid of the wrappers and make a | ||||
|         // fresh start for future Waitables to be tracked. No need to call | ||||
|         // Dispose() since, as a matter of fact, when the Waitable | ||||
|         this.trackedWaitables.Clear(); | ||||
|         this.totalWeight = 0.0f; | ||||
| 
 | ||||
|         // Notify our owner that we're idle now. This line is only reached when all | ||||
|         // Waitables were finished, so it's safe to trigger this here. | ||||
|         setIdle(true); | ||||
| 
 | ||||
|       } // lock | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Called when one of the tracked Waitables has achieved progress</summary> | ||||
|     private void asyncProgressChanged() { | ||||
|       recalculateProgress(); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Changes the idle state</summary> | ||||
|     /// <param name="idle">Whether or not the tracker is currently idle</param> | ||||
|     /// <remarks> | ||||
|     ///   This method expects to be called during a lock() on trackedWaitables! | ||||
|     /// </remarks> | ||||
|     private void setIdle(bool idle) { | ||||
|       this.idle = idle; | ||||
| 
 | ||||
|       OnAsyncIdleStateChanged(idle); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Whether the tracker is currently idle</summary> | ||||
|     private volatile bool idle; | ||||
|     /// <summary>Current summed progress of the tracked Waitables</summary> | ||||
|     private volatile float progress; | ||||
|     /// <summary>Total weight of all Waitables being tracked</summary> | ||||
|     private volatile float totalWeight; | ||||
|     /// <summary>Waitables being tracked by this tracker</summary> | ||||
|     private List<ObservedWeightedWaitable<Waitable>> trackedWaitables; | ||||
|     /// <summary>Delegate for the asyncEnded() method</summary> | ||||
|     private ObservedWeightedWaitable<Waitable>.ReportDelegate asyncEndedDelegate; | ||||
|     /// <summary>Delegate for the asyncProgressUpdated() method</summary> | ||||
|     private ObservedWeightedWaitable<Waitable>.ReportDelegate asyncProgressUpdatedDelegate; | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
| } // namespace Nuclex.Support.Tracking | ||||
|  | @ -1,362 +0,0 @@ | |||
| #region CPL License | ||||
| /* | ||||
| Nuclex Framework | ||||
| Copyright (C) 2002-2008 Nuclex Development Labs | ||||
| 
 | ||||
| This library is free software; you can redistribute it and/or | ||||
| modify it under the terms of the IBM Common Public License as | ||||
| published by the IBM Corporation; either version 1.0 of the | ||||
| License, or (at your option) any later version. | ||||
| 
 | ||||
| This library is distributed in the hope that it will be useful, | ||||
| but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| IBM Common Public License for more details. | ||||
| 
 | ||||
| You should have received a copy of the IBM Common Public | ||||
| License along with this library | ||||
| */ | ||||
| #endregion | ||||
| 
 | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Threading; | ||||
| 
 | ||||
| namespace Nuclex.Support.Tracking { | ||||
| 
 | ||||
|   /// <summary>Helps tracking the progress of one or more progressions</summary> | ||||
|   /// <remarks> | ||||
|   ///   <para> | ||||
|   ///     This is useful if you want to display a progress bar for multiple | ||||
|   ///     progressions but can not guarantee that no additional progressions | ||||
|   ///     will appear inmidst of execution. | ||||
|   ///   </para> | ||||
|   ///   <para> | ||||
|   ///     This class does not implement the IProgression interface itself in | ||||
|   ///     order to not violate the design principles of progressions which | ||||
|   ///     guarantee that a progression will only finish once (whereas the | ||||
|   ///     progression tracker might 'finish' any number of times). | ||||
|   ///   </para> | ||||
|   /// </remarks> | ||||
|   public class ProgressionTracker : IDisposable { | ||||
| 
 | ||||
|     #region class ProgressionMatcher | ||||
| 
 | ||||
|     /// <summary>Matches a progression to a fully wrapped one</summary> | ||||
|     private class ProgressionMatcher { | ||||
| 
 | ||||
|       /// <summary> | ||||
|       ///   Initializes a new progression matcher that matches against | ||||
|       ///   the specified progression | ||||
|       /// </summary> | ||||
|       /// <param name="toMatch">Progression to match against</param> | ||||
|       public ProgressionMatcher(Waitable toMatch) { | ||||
|         this.toMatch = toMatch; | ||||
|       } | ||||
| 
 | ||||
|       /// <summary> | ||||
|       ///   Checks whether the provided progression matches the comparison | ||||
|       ///   progression of the instance | ||||
|       /// </summary> | ||||
|       /// <param name="other">Progression to match to the comparison progression</param> | ||||
|       public bool Matches(ObservedWeightedProgression<Waitable> other) { | ||||
|         return ReferenceEquals(other.WeightedProgression.Progression, this.toMatch); | ||||
|       } | ||||
| 
 | ||||
|       /// <summary>Progression this instance compares against</summary> | ||||
|       private Waitable toMatch; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     #endregion // class ProgressionMatcher | ||||
| 
 | ||||
|     /// <summary>Triggered when the idle state of the tracker changes</summary> | ||||
|     /// <remarks> | ||||
|     ///   The tracker is idle when no progressions are being tracked in it. If you're | ||||
|     ///   using this class to feed a progress bar, this would be the event to use for | ||||
|     ///   showing or hiding the progress bar. The tracker starts off as idle because, | ||||
|     ///   upon construction, its list of progressions will be empty. | ||||
|     /// </remarks> | ||||
|     public event EventHandler<IdleStateEventArgs> AsyncIdleStateChanged; | ||||
| 
 | ||||
|     /// <summary>Triggered when the total progress has changed</summary> | ||||
|     public event EventHandler<ProgressReportEventArgs> AsyncProgressUpdated; | ||||
| 
 | ||||
|     /// <summary>Initializes a new progression tracker</summary> | ||||
|     public ProgressionTracker() { | ||||
| 
 | ||||
|       this.trackedProgressions = new List<ObservedWeightedProgression<Waitable>>(); | ||||
|       this.idle = true; | ||||
| 
 | ||||
|       this.asyncEndedDelegate = | ||||
|         new ObservedWeightedProgression<Waitable>.ReportDelegate(asyncEnded); | ||||
|       this.asyncProgressUpdatedDelegate = | ||||
|         new ObservedWeightedProgression<Waitable>.ReportDelegate(asyncProgressUpdated); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Immediately releases all resources owned by the instance</summary> | ||||
|     public void Dispose() { | ||||
|       lock(this.trackedProgressions) { | ||||
| 
 | ||||
|         // Get rid of all progression we're tracking. This unsubscribes the | ||||
|         // observers from the events of the progressions and stops us from | ||||
|         // being kept alive and receiving any further events if some of the | ||||
|         // tracked progressions are still executing. | ||||
|         for(int index = 0; index < this.trackedProgressions.Count; ++index) | ||||
|           this.trackedProgressions[index].Dispose(); | ||||
| 
 | ||||
|         // Help the GC a bit by untangling the references :) | ||||
|         this.trackedProgressions.Clear(); | ||||
|         this.trackedProgressions = null; | ||||
| 
 | ||||
|       } // lock | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Begins tracking the specified progression</summary> | ||||
|     /// <param name="progression">Progression to be tracked</param> | ||||
|     public void Track(Waitable progression) { | ||||
|       Track(progression, 1.0f); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Begins tracking the specified progression</summary> | ||||
|     /// <param name="progression">Progression to be tracked</param> | ||||
|     /// <param name="weight">Weight to assign to this progression</param> | ||||
|     public void Track(Waitable progression, float weight) { | ||||
| 
 | ||||
|       // Add the new progression into the tracking list. This has to be done | ||||
|       // inside a lock to prevent issues with the progressUpdate callback, which could | ||||
|       // access the totalWeight field before it has been updated to reflect the | ||||
|       // new progression added to the collection. | ||||
|       lock(this.trackedProgressions) { | ||||
| 
 | ||||
|         bool wasEmpty = (this.trackedProgressions.Count == 0); | ||||
| 
 | ||||
|         // This can be done after we registered the wrapper to our delegates because | ||||
|         // any incoming progress updates will be stopped from the danger of a | ||||
|         // division-by-zero from the potentially still zeroed totalWeight by the lock. | ||||
|         this.totalWeight += weight; | ||||
| 
 | ||||
|         if(progression.Ended) { | ||||
| 
 | ||||
|           // If the ended progression would become the only progression in the list, | ||||
|           // there's no sense in doing anything at all because it would have to be | ||||
|           // thrown right out again. Only add the progression when there are other | ||||
|           // running progressions to properly sum total progress for consistency. | ||||
|           if(!wasEmpty) { | ||||
| 
 | ||||
|             // Construct a new observation wrapper. This is done inside the lock | ||||
|             // because as soon as we are subscribed to the events, we can potentially | ||||
|             // receive them. The lock eliminates the risk of processing a progress update | ||||
|             // before the progression has been added to the tracked progressions list. | ||||
|             this.trackedProgressions.Add( | ||||
|               new ObservedWeightedProgression<Waitable>( | ||||
|                 new WeightedProgression<Waitable>(progression, weight), | ||||
|                 this.asyncProgressUpdatedDelegate, | ||||
|                 this.asyncEndedDelegate | ||||
|               ) | ||||
|             ); | ||||
| 
 | ||||
|             // All done, the total progress is different now, so force a recalculation and | ||||
|             // send out the AsyncProgressUpdated event. | ||||
|             recalculateProgress(); | ||||
| 
 | ||||
|           } | ||||
| 
 | ||||
|         } else { // Not ended -- Progression is still running | ||||
| 
 | ||||
|           // Construct a new progression observer and add the progression to our | ||||
|           // list of tracked progressions. | ||||
|           ObservedWeightedProgression<Waitable> observedProgression = | ||||
|             new ObservedWeightedProgression<Waitable>( | ||||
|               new WeightedProgression<Waitable>(progression, weight), | ||||
|               this.asyncProgressUpdatedDelegate, | ||||
|               this.asyncEndedDelegate | ||||
|             ); | ||||
| 
 | ||||
|           this.trackedProgressions.Add(observedProgression); | ||||
| 
 | ||||
|           // If this is the first progression to be added to the list, tell our | ||||
|           // owner that we're idle no longer! | ||||
|           if(wasEmpty) | ||||
|             setIdle(false); | ||||
| 
 | ||||
|           // All done, the total progress is different now, so force a recalculation and | ||||
|           // send out the AsyncProgressUpdated event. | ||||
|           recalculateProgress(); | ||||
| 
 | ||||
|           // The progression might have ended before we had registered to its AsyncEnded | ||||
|           // event, so we have to do this to be on the safe side. This might cause | ||||
|           // asyncEnded() to be called twice, but that's not a problem in this | ||||
|           // implementation and improves performance and simplicity for the normal path. | ||||
|           if(progression.Ended) { | ||||
|             asyncEnded(); | ||||
|             observedProgression.Dispose(); | ||||
|           } | ||||
| 
 | ||||
|         } // if progression ended | ||||
| 
 | ||||
|       } // lock | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Stops tracking the specified progression</summary> | ||||
|     /// <param name="progression">Progression to stop tracking of</param> | ||||
|     public void Untrack(Waitable progression) { | ||||
|       lock(this.trackedProgressions) { | ||||
| 
 | ||||
|         // Locate the object to be untracked in our collection | ||||
|         int removeIndex = this.trackedProgressions.FindIndex( | ||||
|           new Predicate<ObservedWeightedProgression<Waitable>>( | ||||
|             new ProgressionMatcher(progression).Matches | ||||
|           ) | ||||
|         ); | ||||
|         if(removeIndex == -1) | ||||
|           throw new InvalidOperationException("Item is not being tracked"); | ||||
| 
 | ||||
|         // Remove and dispose the progression the user wants to untrack | ||||
|         { | ||||
|           ObservedWeightedProgression<Waitable> wrappedProgression = | ||||
|             this.trackedProgressions[removeIndex]; | ||||
| 
 | ||||
|           this.trackedProgressions.RemoveAt(removeIndex); | ||||
|           wrappedProgression.Dispose(); | ||||
|         } | ||||
| 
 | ||||
|         // If the list is empty, then we're back in the idle state | ||||
|         if(this.trackedProgressions.Count == 0) { | ||||
| 
 | ||||
|           this.totalWeight = 0.0f; | ||||
| 
 | ||||
|           // If we entered the idle state with this call, report the state change! | ||||
|           setIdle(true); | ||||
| 
 | ||||
|         } else { | ||||
| 
 | ||||
|           // Rebuild the total weight from scratch. Subtracting the removed progression's | ||||
|           // weight would work, too, but we might accumulate rounding errors making the sum | ||||
|           // drift slowly away from the actual value. | ||||
|           this.totalWeight = 0.0f; | ||||
|           for(int index = 0; index < this.trackedProgressions.Count; ++index) | ||||
|             this.totalWeight += this.trackedProgressions[index].WeightedProgression.Weight; | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|       } // lock | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Whether the tracker is currently idle</summary> | ||||
|     public bool Idle { | ||||
|       get { return this.idle; } | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Current summed progress of the tracked progressions</summary> | ||||
|     public float Progress { | ||||
|       get { return this.progress; } | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Fires the AsyncIdleStateChanged event</summary> | ||||
|     /// <param name="idle">New idle state to report</param> | ||||
|     protected virtual void OnAsyncIdleStateChanged(bool idle) { | ||||
|       EventHandler<IdleStateEventArgs> copy = AsyncIdleStateChanged; | ||||
|       if(copy != null) | ||||
|         copy(this, new IdleStateEventArgs(idle)); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Fires the AsyncProgressUpdated event</summary> | ||||
|     /// <param name="progress">New progress to report</param> | ||||
|     protected virtual void OnAsyncProgressUpdated(float progress) { | ||||
|       EventHandler<ProgressReportEventArgs> copy = AsyncProgressUpdated; | ||||
|       if(copy != null) | ||||
|         copy(this, new ProgressReportEventArgs(progress)); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Recalculates the total progress of the tracker</summary> | ||||
|     private void recalculateProgress() { | ||||
|       float totalProgress = 0.0f; | ||||
| 
 | ||||
|       // Lock the collection to avoid trouble when someone tries to remove one | ||||
|       // of our tracked progressions while we're just doing a progress update | ||||
|       lock(this.trackedProgressions) { | ||||
| 
 | ||||
|         // This is a safety measure. In theory, even after all progressions have | ||||
|         // ended and the collection of tracked progressions is cleared, a waiting | ||||
|         // thread might deliver another progress update causing this method to | ||||
|         // be entered. In this case, the right thing is to do nothing at all. | ||||
|         if(this.totalWeight == 0.0f) | ||||
|           return; | ||||
| 
 | ||||
|         // Sum up the total progress | ||||
|         for(int index = 0; index < this.trackedProgressions.Count; ++index) { | ||||
|           float weight = this.trackedProgressions[index].WeightedProgression.Weight; | ||||
|           totalProgress += this.trackedProgressions[index].Progress * weight; | ||||
|         } | ||||
| 
 | ||||
|         // This also needs to be in the lock to guarantee that the totalWeight is | ||||
|         // the one for the number of progressions we just summed -- by design, | ||||
|         // the total weight always has to be updated at the same time as the collection. | ||||
|         totalProgress /= this.totalWeight; | ||||
| 
 | ||||
|         // Finally, trigger the event | ||||
|         this.progress = totalProgress; | ||||
|         OnAsyncProgressUpdated(totalProgress); | ||||
| 
 | ||||
|       } // lock | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Called when one of the tracked progressions has ended</summary> | ||||
|     private void asyncEnded() { | ||||
|       lock(this.trackedProgressions) { | ||||
| 
 | ||||
|         // If any progressions in the list are still going, keep the entire list. | ||||
|         // This behavior is intentional in order to prevent the tracker's progress from | ||||
|         // jumping back repeatedly when multiple tracked progressions come to an end. | ||||
|         for(int index = 0; index < this.trackedProgressions.Count; ++index) | ||||
|           if(!this.trackedProgressions[index].WeightedProgression.Progression.Ended) | ||||
|             return; | ||||
| 
 | ||||
|         // All progressions have finished, get rid of the wrappers and make a | ||||
|         // fresh start for future progressions to be tracked. No need to call | ||||
|         // Dispose() since, as a matter of fact, when the progression | ||||
|         this.trackedProgressions.Clear(); | ||||
|         this.totalWeight = 0.0f; | ||||
| 
 | ||||
|         // Notify our owner that we're idle now. This line is only reached when all | ||||
|         // progressions were finished, so it's safe to trigger this here. | ||||
|         setIdle(true); | ||||
| 
 | ||||
|       } // lock | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Called when one of the tracked progression has achieved progress</summary> | ||||
|     private void asyncProgressUpdated() { | ||||
|       recalculateProgress(); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Changes the idle state</summary> | ||||
|     /// <param name="idle">Whether or not the tracker is currently idle</param> | ||||
|     /// <remarks> | ||||
|     ///   This method expects to be called during a lock() on trackedProgressions! | ||||
|     /// </remarks> | ||||
|     private void setIdle(bool idle) { | ||||
|       this.idle = idle; | ||||
| 
 | ||||
|       OnAsyncIdleStateChanged(idle); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Whether the tracker is currently idle</summary> | ||||
|     private volatile bool idle; | ||||
|     /// <summary>Current summed progress of the tracked progressions</summary> | ||||
|     private volatile float progress; | ||||
|     /// <summary>Total weight of all progressions being tracked</summary> | ||||
|     private volatile float totalWeight; | ||||
|     /// <summary>Progressions being tracked by this tracker</summary> | ||||
|     private List<ObservedWeightedProgression<Waitable>> trackedProgressions; | ||||
|     /// <summary>Delegate for the asyncEnded() method</summary> | ||||
|     private ObservedWeightedProgression<Waitable>.ReportDelegate asyncEndedDelegate; | ||||
|     /// <summary>Delegate for the asyncProgressUpdated() method</summary> | ||||
|     private ObservedWeightedProgression<Waitable>.ReportDelegate asyncProgressUpdatedDelegate; | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
| } // namespace Nuclex.Support.Tracking | ||||
|  | @ -164,7 +164,7 @@ namespace Nuclex.Support.Tracking { | |||
|           } | ||||
|         ); | ||||
| 
 | ||||
|       testSetProgression.Children[0].Progression.ChangeProgress(0.5f); | ||||
|       testSetProgression.Children[0].Waitable.ChangeProgress(0.5f); | ||||
| 
 | ||||
|       this.mockery.VerifyAllExpectationsHaveBeenMet(); | ||||
|     } | ||||
|  | @ -174,9 +174,9 @@ namespace Nuclex.Support.Tracking { | |||
|     public void TestWeightedSummedProgress() { | ||||
|       SetProgression<TestWaitable> testSetProgression = | ||||
|         new SetProgression<TestWaitable>( | ||||
|           new WeightedProgression<TestWaitable>[] { | ||||
|             new WeightedProgression<TestWaitable>(new TestWaitable(), 1.0f), | ||||
|             new WeightedProgression<TestWaitable>(new TestWaitable(), 2.0f) | ||||
|           new WeightedWaitable<TestWaitable>[] { | ||||
|             new WeightedWaitable<TestWaitable>(new TestWaitable(), 1.0f), | ||||
|             new WeightedWaitable<TestWaitable>(new TestWaitable(), 2.0f) | ||||
|           } | ||||
|         ); | ||||
| 
 | ||||
|  | @ -191,7 +191,7 @@ namespace Nuclex.Support.Tracking { | |||
|           } | ||||
|         ); | ||||
| 
 | ||||
|       testSetProgression.Children[0].Progression.ChangeProgress(0.5f); | ||||
|       testSetProgression.Children[0].Waitable.ChangeProgress(0.5f); | ||||
| 
 | ||||
|       Expect.Once.On(mockedSubscriber). | ||||
|         Method("ProgressChanged"). | ||||
|  | @ -202,7 +202,7 @@ namespace Nuclex.Support.Tracking { | |||
|           } | ||||
|         ); | ||||
| 
 | ||||
|       testSetProgression.Children[1].Progression.ChangeProgress(0.5f); | ||||
|       testSetProgression.Children[1].Waitable.ChangeProgress(0.5f); | ||||
| 
 | ||||
|       this.mockery.VerifyAllExpectationsHaveBeenMet(); | ||||
|     } | ||||
|  | @ -227,8 +227,8 @@ namespace Nuclex.Support.Tracking { | |||
|         Method("Ended"). | ||||
|         WithAnyArguments(); | ||||
|        | ||||
|       testSetProgression.Children[0].Progression.End(); | ||||
|       testSetProgression.Children[1].Progression.End(); | ||||
|       testSetProgression.Children[0].Waitable.End(); | ||||
|       testSetProgression.Children[1].Waitable.End(); | ||||
| 
 | ||||
|       this.mockery.VerifyAllExpectationsHaveBeenMet(); | ||||
|     } | ||||
|  |  | |||
|  | @ -46,10 +46,10 @@ namespace Nuclex.Support.Tracking { | |||
|       // progression and wrap it in an ObservedProgression | ||||
|       foreach(ProgressionType progression in childs) { | ||||
|         this.children.Add( | ||||
|           new ObservedWeightedProgression<ProgressionType>( | ||||
|             new WeightedProgression<ProgressionType>(progression), | ||||
|             new ObservedWeightedProgression<ProgressionType>.ReportDelegate(asyncProgressUpdated), | ||||
|             new ObservedWeightedProgression<ProgressionType>.ReportDelegate(asyncEnded) | ||||
|           new ObservedWeightedWaitable<ProgressionType>( | ||||
|             new WeightedWaitable<ProgressionType>(progression), | ||||
|             new ObservedWeightedWaitable<ProgressionType>.ReportDelegate(asyncProgressUpdated), | ||||
|             new ObservedWeightedWaitable<ProgressionType>.ReportDelegate(asyncEnded) | ||||
|           ) | ||||
|         ); | ||||
|       } | ||||
|  | @ -63,17 +63,17 @@ namespace Nuclex.Support.Tracking { | |||
|     /// <summary>Initializes a new set progression</summary> | ||||
|     /// <param name="childs">Progressions to track with this set</param> | ||||
|     public SetProgression( | ||||
|       IEnumerable<WeightedProgression<ProgressionType>> childs | ||||
|       IEnumerable<WeightedWaitable<ProgressionType>> childs | ||||
|     ) | ||||
|       : this() { | ||||
| 
 | ||||
|       // Construct an ObservedProgression around each of the WeightedProgressions | ||||
|       foreach(WeightedProgression<ProgressionType> progression in childs) { | ||||
|       foreach(WeightedWaitable<ProgressionType> progression in childs) { | ||||
|         this.children.Add( | ||||
|           new ObservedWeightedProgression<ProgressionType>( | ||||
|           new ObservedWeightedWaitable<ProgressionType>( | ||||
|             progression, | ||||
|             new ObservedWeightedProgression<ProgressionType>.ReportDelegate(asyncProgressUpdated), | ||||
|             new ObservedWeightedProgression<ProgressionType>.ReportDelegate(asyncEnded) | ||||
|             new ObservedWeightedWaitable<ProgressionType>.ReportDelegate(asyncProgressUpdated), | ||||
|             new ObservedWeightedWaitable<ProgressionType>.ReportDelegate(asyncEnded) | ||||
|           ) | ||||
|         ); | ||||
| 
 | ||||
|  | @ -85,7 +85,7 @@ namespace Nuclex.Support.Tracking { | |||
| 
 | ||||
|     /// <summary>Performs common initialization for the public constructors</summary> | ||||
|     private SetProgression() { | ||||
|       this.children = new List<ObservedWeightedProgression<ProgressionType>>(); | ||||
|       this.children = new List<ObservedWeightedWaitable<ProgressionType>>(); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Immediately releases all resources owned by the object</summary> | ||||
|  | @ -106,7 +106,7 @@ namespace Nuclex.Support.Tracking { | |||
|     } | ||||
| 
 | ||||
|     /// <summary>Childs contained in the progression set</summary> | ||||
|     public IList<WeightedProgression<ProgressionType>> Children { | ||||
|     public IList<WeightedWaitable<ProgressionType>> Children { | ||||
|       get { | ||||
| 
 | ||||
|         // The wrapper is constructed only when needed. Most of the time, users will | ||||
|  | @ -160,7 +160,7 @@ namespace Nuclex.Support.Tracking { | |||
|       // scaled to the weight each progression has assigned to it. | ||||
|       for(int index = 0; index < this.children.Count; ++index) { | ||||
|         totalProgress += | ||||
|           this.children[index].Progress * this.children[index].WeightedProgression.Weight; | ||||
|           this.children[index].Progress * this.children[index].WeightedWaitable.Weight; | ||||
|       } | ||||
| 
 | ||||
|       // Calculate the actual combined progress | ||||
|  | @ -179,7 +179,7 @@ namespace Nuclex.Support.Tracking { | |||
|       // If there's still at least one progression going, don't report that | ||||
|       // the SetProgression has finished yet. | ||||
|       for(int index = 0; index < this.children.Count; ++index) | ||||
|         if(!this.children[index].WeightedProgression.Progression.Ended) | ||||
|         if(!this.children[index].WeightedWaitable.Waitable.Ended) | ||||
|           return; | ||||
| 
 | ||||
|       // All child progressions have ended, so the set has now ended as well | ||||
|  | @ -188,7 +188,7 @@ namespace Nuclex.Support.Tracking { | |||
|     } | ||||
| 
 | ||||
|     /// <summary>Progressions being managed in the set</summary> | ||||
|     private List<ObservedWeightedProgression<ProgressionType>> children; | ||||
|     private List<ObservedWeightedWaitable<ProgressionType>> children; | ||||
|     /// <summary> | ||||
|     ///   Wrapper collection for exposing the child progressions under the | ||||
|     ///   WeightedProgression interface | ||||
|  |  | |||
|  | @ -24,24 +24,24 @@ using System.Collections.Generic; | |||
| namespace Nuclex.Support.Tracking { | ||||
| 
 | ||||
|   /// <summary>Progression with an associated weight for the total progress</summary> | ||||
|   public class WeightedProgression<ProgressionType> where ProgressionType : Waitable { | ||||
|   public class WeightedWaitable<ProgressionType> where ProgressionType : Waitable { | ||||
| 
 | ||||
|     /// <summary> | ||||
|     ///   Initializes a new weighted progression with a default weight of 1.0 | ||||
|     /// </summary> | ||||
|     /// <param name="progression">Progression whose progress to monitor</param> | ||||
|     public WeightedProgression(ProgressionType progression) : this(progression, 1.0f) { } | ||||
|     public WeightedWaitable(ProgressionType progression) : this(progression, 1.0f) { } | ||||
| 
 | ||||
|     /// <summary>Initializes a new weighted progression</summary> | ||||
|     /// <param name="progression">Progression whose progress to monitor</param> | ||||
|     /// <param name="weight">Weighting of the progression's progress</param> | ||||
|     public WeightedProgression(ProgressionType progression, float weight) { | ||||
|     public WeightedWaitable(ProgressionType progression, float weight) { | ||||
|       this.progression = progression; | ||||
|       this.weight = weight;        | ||||
|     } | ||||
| 
 | ||||
|     /// <summary>Progression being wrapped by this weighted progression</summary> | ||||
|     public ProgressionType Progression { | ||||
|     public ProgressionType Waitable { | ||||
|       get { return this.progression; } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue