diff --git a/Nuclex.Support (x86).csproj b/Nuclex.Support (x86).csproj
index ea47a40..da7467a 100644
--- a/Nuclex.Support (x86).csproj
+++ b/Nuclex.Support (x86).csproj
@@ -145,20 +145,20 @@
-
-
+
+
-
-
- ProgressionTracker.cs
+
+
+ ProgressTracker.cs
- ProgressionTracker.cs
+ SetProgression.cs
diff --git a/Source/Scheduling/QueueOperation.cs b/Source/Scheduling/QueueOperation.cs
index 1cba412..18279c3 100644
--- a/Source/Scheduling/QueueOperation.cs
+++ b/Source/Scheduling/QueueOperation.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(operation));
+ this.children.Add(new WeightedWaitable(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 {
/// Initializes a new queue operation with custom weights
/// Child operations to execute in this operation
- public QueueOperation(IEnumerable> childs) : this() {
+ public QueueOperation(IEnumerable> childs) : this() {
// Construct an ObservedProgression around each of the WeightedProgressions
- foreach(WeightedProgression operation in childs) {
+ foreach(WeightedWaitable 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>();
+ this.children = new List>();
}
/// Provides access to the child operations of this queue
- public IList> Children {
+ public IList> Children {
get { return this.children; }
}
@@ -125,7 +125,7 @@ namespace Nuclex.Support.Scheduling {
/// and launches the operation by calling its Begin() method.
///
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.
///
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 {
/// Delegate to the asyncOperationProgressUpdated() method
private EventHandler asyncOperationProgressChangedDelegate;
/// Operations being managed in the queue
- private List> children;
+ private List> children;
/// Summed weight of all operations in the queue
private float totalWeight;
/// Accumulated weight of the operations already completed
diff --git a/Source/Tracking/Internal/ObservedWeightedProgression.cs b/Source/Tracking/Internal/ObservedWeightedWaitable.cs
similarity index 88%
rename from Source/Tracking/Internal/ObservedWeightedProgression.cs
rename to Source/Tracking/Internal/ObservedWeightedWaitable.cs
index 0c7c8de..0020b4a 100644
--- a/Source/Tracking/Internal/ObservedWeightedProgression.cs
+++ b/Source/Tracking/Internal/ObservedWeightedWaitable.cs
@@ -27,7 +27,7 @@ namespace Nuclex.Support.Tracking {
///
/// Type of the progression that is being observed
///
- internal class ObservedWeightedProgression : IDisposable
+ internal class ObservedWeightedWaitable : IDisposable
where ProgressionType : Waitable {
/// Delegate for reporting progress updates
@@ -41,15 +41,15 @@ namespace Nuclex.Support.Tracking {
///
/// Callback to invoke when the progression has ended
///
- internal ObservedWeightedProgression(
- WeightedProgression weightedProgression,
+ internal ObservedWeightedWaitable(
+ WeightedWaitable 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(
@@ -90,7 +90,7 @@ namespace Nuclex.Support.Tracking {
}
/// Weighted progression being observed
- public WeightedProgression WeightedProgression {
+ public WeightedWaitable 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 {
/// The observed progression's progress reporting interface
private IProgressReporter progressReporter;
/// The weighted progression that is being observed
- private WeightedProgression weightedProgression;
+ private WeightedWaitable weightedProgression;
/// Callback to invoke when the progress updates
private volatile ReportDelegate progressUpdateCallback;
/// Callback to invoke when the progression ends
diff --git a/Source/Tracking/Internal/WeightedProgressionWrapperCollection.cs b/Source/Tracking/Internal/WeightedWaitableWrapperCollection.cs
similarity index 88%
rename from Source/Tracking/Internal/WeightedProgressionWrapperCollection.cs
rename to Source/Tracking/Internal/WeightedWaitableWrapperCollection.cs
index e67163d..b01c183 100644
--- a/Source/Tracking/Internal/WeightedProgressionWrapperCollection.cs
+++ b/Source/Tracking/Internal/WeightedWaitableWrapperCollection.cs
@@ -49,14 +49,14 @@ namespace Nuclex.Support.Tracking {
///
internal class WeightedProgressionWrapperCollection :
TransformingReadOnlyCollection<
- ObservedWeightedProgression, WeightedProgression
+ ObservedWeightedWaitable, WeightedWaitable
>
where ProgressionType : Waitable {
/// Initializes a new weighted progression collection wrapper
/// Items to be exposed as weighted progressions
internal WeightedProgressionWrapperCollection(
- IList> items
+ IList> 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.
///
- protected override WeightedProgression Transform(
- ObservedWeightedProgression item
+ protected override WeightedWaitable Transform(
+ ObservedWeightedWaitable item
) {
- return item.WeightedProgression;
+ return item.WeightedWaitable;
}
}
diff --git a/Source/Tracking/ProgressionTracker.Test.cs b/Source/Tracking/ProgressTracker.Test.cs
similarity index 89%
rename from Source/Tracking/ProgressionTracker.Test.cs
rename to Source/Tracking/ProgressTracker.Test.cs
index adc8cf8..aacaf66 100644
--- a/Source/Tracking/ProgressionTracker.Test.cs
+++ b/Source/Tracking/ProgressTracker.Test.cs
@@ -148,7 +148,7 @@ namespace Nuclex.Support.Tracking {
/// Validates that the tracker properly sums the progress
[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 {
///
[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 {
///
[Test]
public void TestSoleEndedProgression() {
- ProgressionTracker tracker = new ProgressionTracker();
+ ProgressTracker tracker = new ProgressTracker();
IProgressionTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
@@ -280,7 +280,7 @@ namespace Nuclex.Support.Tracking {
///
[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 {
///
[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 {
/// Mocks a subscriber for the events of a tracker
/// Tracker to mock an event subscriber for
/// The mocked event subscriber
- private IProgressionTrackerSubscriber mockSubscriber(ProgressionTracker tracker) {
+ private IProgressionTrackerSubscriber mockSubscriber(ProgressTracker tracker) {
IProgressionTrackerSubscriber mockedSubscriber =
this.mockery.NewMock();
tracker.AsyncIdleStateChanged +=
new EventHandler(mockedSubscriber.IdleStateChanged);
- tracker.AsyncProgressUpdated +=
+ tracker.AsyncProgressChanged +=
new EventHandler(mockedSubscriber.ProgressUpdated);
return mockedSubscriber;
diff --git a/Source/Tracking/ProgressTracker.cs b/Source/Tracking/ProgressTracker.cs
new file mode 100644
index 0000000..b28a917
--- /dev/null
+++ b/Source/Tracking/ProgressTracker.cs
@@ -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 {
+
+ ///
+ /// Helps tracking the progress of one or more waitable background processes
+ ///
+ ///
+ ///
+ /// 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.
+ ///
+ ///
+ /// This class does not implement the interface itself
+ /// in order to not violate the design principles of Waitables which
+ /// guarantee that a will only finish once (whereas the
+ /// progress tracker might 'finish' any number of times).
+ ///
+ ///
+ public class ProgressTracker : IDisposable, IProgressReporter {
+
+ #region class WaitableMatcher
+
+ /// Matches a direct Waitable to a fully wrapped one
+ private class WaitableMatcher {
+
+ ///
+ /// Initializes a new Waitable matcher that matches against
+ /// the specified Waitable
+ ///
+ /// Waitable to match against
+ public WaitableMatcher(Waitable toMatch) {
+ this.toMatch = toMatch;
+ }
+
+ ///
+ /// Checks whether the provided Waitable matches the comparison
+ /// Waitable of the instance
+ ///
+ /// Waitable to match to the comparison Waitable
+ public bool Matches(ObservedWeightedWaitable other) {
+ return ReferenceEquals(other.WeightedWaitable.Waitable, this.toMatch);
+ }
+
+ /// Waitable this instance compares against
+ private Waitable toMatch;
+
+ }
+
+ #endregion // class WaitableMatcher
+
+ /// Triggered when the idle state of the tracker changes
+ ///
+ /// 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.
+ ///
+ public event EventHandler AsyncIdleStateChanged;
+
+ /// Triggered when the total progress has changed
+ public event EventHandler AsyncProgressChanged;
+
+ /// Initializes a new Waitable tracker
+ public ProgressTracker() {
+
+ this.trackedWaitables = new List>();
+ this.idle = true;
+
+ this.asyncEndedDelegate =
+ new ObservedWeightedWaitable.ReportDelegate(asyncEnded);
+ this.asyncProgressUpdatedDelegate =
+ new ObservedWeightedWaitable.ReportDelegate(asyncProgressChanged);
+
+ }
+
+ /// Immediately releases all resources owned by the instance
+ 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
+ }
+
+ /// Begins tracking the specified waitable background process
+ /// Waitable background process to be tracked
+ public void Track(Waitable waitable) {
+ Track(waitable, 1.0f);
+ }
+
+ /// Begins tracking the specified waitable background process
+ /// Waitable background process to be tracked
+ /// Weight to assign to this waitable background process
+ 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(
+ new WeightedWaitable(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 observedWaitable =
+ new ObservedWeightedWaitable(
+ new WeightedWaitable(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
+
+ }
+
+ /// Stops tracking the specified waitable background process
+ /// Waitable background process to stop tracking of
+ public void Untrack(Waitable waitable) {
+ lock(this.trackedWaitables) {
+
+ // Locate the object to be untracked in our collection
+ int removeIndex = this.trackedWaitables.FindIndex(
+ new Predicate>(
+ 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 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
+ }
+
+ /// Whether the tracker is currently idle
+ public bool Idle {
+ get { return this.idle; }
+ }
+
+ /// Current summed progress of the tracked Waitables
+ public float Progress {
+ get { return this.progress; }
+ }
+
+ /// Fires the AsyncIdleStateChanged event
+ /// New idle state to report
+ protected virtual void OnAsyncIdleStateChanged(bool idle) {
+ EventHandler copy = AsyncIdleStateChanged;
+ if(copy != null)
+ copy(this, new IdleStateEventArgs(idle));
+ }
+
+ /// Fires the AsyncProgressUpdated event
+ /// New progress to report
+ protected virtual void OnAsyncProgressUpdated(float progress) {
+ EventHandler copy = AsyncProgressChanged;
+ if(copy != null)
+ copy(this, new ProgressReportEventArgs(progress));
+ }
+
+ /// Recalculates the total progress of the tracker
+ 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
+ }
+
+ /// Called when one of the tracked Waitables has ended
+ 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
+ }
+
+ /// Called when one of the tracked Waitables has achieved progress
+ private void asyncProgressChanged() {
+ recalculateProgress();
+ }
+
+ /// Changes the idle state
+ /// Whether or not the tracker is currently idle
+ ///
+ /// This method expects to be called during a lock() on trackedWaitables!
+ ///
+ private void setIdle(bool idle) {
+ this.idle = idle;
+
+ OnAsyncIdleStateChanged(idle);
+ }
+
+ /// Whether the tracker is currently idle
+ private volatile bool idle;
+ /// Current summed progress of the tracked Waitables
+ private volatile float progress;
+ /// Total weight of all Waitables being tracked
+ private volatile float totalWeight;
+ /// Waitables being tracked by this tracker
+ private List> trackedWaitables;
+ /// Delegate for the asyncEnded() method
+ private ObservedWeightedWaitable.ReportDelegate asyncEndedDelegate;
+ /// Delegate for the asyncProgressUpdated() method
+ private ObservedWeightedWaitable.ReportDelegate asyncProgressUpdatedDelegate;
+
+ }
+
+} // namespace Nuclex.Support.Tracking
diff --git a/Source/Tracking/ProgressionTracker.cs b/Source/Tracking/ProgressionTracker.cs
deleted file mode 100644
index 441cefc..0000000
--- a/Source/Tracking/ProgressionTracker.cs
+++ /dev/null
@@ -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 {
-
- /// Helps tracking the progress of one or more progressions
- ///
- ///
- /// 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.
- ///
- ///
- /// 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).
- ///
- ///
- public class ProgressionTracker : IDisposable {
-
- #region class ProgressionMatcher
-
- /// Matches a progression to a fully wrapped one
- private class ProgressionMatcher {
-
- ///
- /// Initializes a new progression matcher that matches against
- /// the specified progression
- ///
- /// Progression to match against
- public ProgressionMatcher(Waitable toMatch) {
- this.toMatch = toMatch;
- }
-
- ///
- /// Checks whether the provided progression matches the comparison
- /// progression of the instance
- ///
- /// Progression to match to the comparison progression
- public bool Matches(ObservedWeightedProgression other) {
- return ReferenceEquals(other.WeightedProgression.Progression, this.toMatch);
- }
-
- /// Progression this instance compares against
- private Waitable toMatch;
-
- }
-
- #endregion // class ProgressionMatcher
-
- /// Triggered when the idle state of the tracker changes
- ///
- /// 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.
- ///
- public event EventHandler AsyncIdleStateChanged;
-
- /// Triggered when the total progress has changed
- public event EventHandler AsyncProgressUpdated;
-
- /// Initializes a new progression tracker
- public ProgressionTracker() {
-
- this.trackedProgressions = new List>();
- this.idle = true;
-
- this.asyncEndedDelegate =
- new ObservedWeightedProgression.ReportDelegate(asyncEnded);
- this.asyncProgressUpdatedDelegate =
- new ObservedWeightedProgression.ReportDelegate(asyncProgressUpdated);
-
- }
-
- /// Immediately releases all resources owned by the instance
- 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
- }
-
- /// Begins tracking the specified progression
- /// Progression to be tracked
- public void Track(Waitable progression) {
- Track(progression, 1.0f);
- }
-
- /// Begins tracking the specified progression
- /// Progression to be tracked
- /// Weight to assign to this progression
- 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(
- new WeightedProgression(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 observedProgression =
- new ObservedWeightedProgression(
- new WeightedProgression(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
-
- }
-
- /// Stops tracking the specified progression
- /// Progression to stop tracking of
- public void Untrack(Waitable progression) {
- lock(this.trackedProgressions) {
-
- // Locate the object to be untracked in our collection
- int removeIndex = this.trackedProgressions.FindIndex(
- new Predicate>(
- 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 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
- }
-
- /// Whether the tracker is currently idle
- public bool Idle {
- get { return this.idle; }
- }
-
- /// Current summed progress of the tracked progressions
- public float Progress {
- get { return this.progress; }
- }
-
- /// Fires the AsyncIdleStateChanged event
- /// New idle state to report
- protected virtual void OnAsyncIdleStateChanged(bool idle) {
- EventHandler copy = AsyncIdleStateChanged;
- if(copy != null)
- copy(this, new IdleStateEventArgs(idle));
- }
-
- /// Fires the AsyncProgressUpdated event
- /// New progress to report
- protected virtual void OnAsyncProgressUpdated(float progress) {
- EventHandler copy = AsyncProgressUpdated;
- if(copy != null)
- copy(this, new ProgressReportEventArgs(progress));
- }
-
- /// Recalculates the total progress of the tracker
- 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
- }
-
- /// Called when one of the tracked progressions has ended
- 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
- }
-
- /// Called when one of the tracked progression has achieved progress
- private void asyncProgressUpdated() {
- recalculateProgress();
- }
-
- /// Changes the idle state
- /// Whether or not the tracker is currently idle
- ///
- /// This method expects to be called during a lock() on trackedProgressions!
- ///
- private void setIdle(bool idle) {
- this.idle = idle;
-
- OnAsyncIdleStateChanged(idle);
- }
-
- /// Whether the tracker is currently idle
- private volatile bool idle;
- /// Current summed progress of the tracked progressions
- private volatile float progress;
- /// Total weight of all progressions being tracked
- private volatile float totalWeight;
- /// Progressions being tracked by this tracker
- private List> trackedProgressions;
- /// Delegate for the asyncEnded() method
- private ObservedWeightedProgression.ReportDelegate asyncEndedDelegate;
- /// Delegate for the asyncProgressUpdated() method
- private ObservedWeightedProgression.ReportDelegate asyncProgressUpdatedDelegate;
-
- }
-
-} // namespace Nuclex.Support.Tracking
diff --git a/Source/Tracking/SetProgression.Test.cs b/Source/Tracking/SetProgression.Test.cs
index 097c474..0228dd9 100644
--- a/Source/Tracking/SetProgression.Test.cs
+++ b/Source/Tracking/SetProgression.Test.cs
@@ -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 testSetProgression =
new SetProgression(
- new WeightedProgression[] {
- new WeightedProgression(new TestWaitable(), 1.0f),
- new WeightedProgression(new TestWaitable(), 2.0f)
+ new WeightedWaitable[] {
+ new WeightedWaitable(new TestWaitable(), 1.0f),
+ new WeightedWaitable(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();
}
diff --git a/Source/Tracking/SetProgression.cs b/Source/Tracking/SetProgression.cs
index f8391e1..05743e5 100644
--- a/Source/Tracking/SetProgression.cs
+++ b/Source/Tracking/SetProgression.cs
@@ -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(
- new WeightedProgression(progression),
- new ObservedWeightedProgression.ReportDelegate(asyncProgressUpdated),
- new ObservedWeightedProgression.ReportDelegate(asyncEnded)
+ new ObservedWeightedWaitable(
+ new WeightedWaitable(progression),
+ new ObservedWeightedWaitable.ReportDelegate(asyncProgressUpdated),
+ new ObservedWeightedWaitable.ReportDelegate(asyncEnded)
)
);
}
@@ -63,17 +63,17 @@ namespace Nuclex.Support.Tracking {
/// Initializes a new set progression
/// Progressions to track with this set
public SetProgression(
- IEnumerable> childs
+ IEnumerable> childs
)
: this() {
// Construct an ObservedProgression around each of the WeightedProgressions
- foreach(WeightedProgression progression in childs) {
+ foreach(WeightedWaitable progression in childs) {
this.children.Add(
- new ObservedWeightedProgression(
+ new ObservedWeightedWaitable(
progression,
- new ObservedWeightedProgression.ReportDelegate(asyncProgressUpdated),
- new ObservedWeightedProgression.ReportDelegate(asyncEnded)
+ new ObservedWeightedWaitable.ReportDelegate(asyncProgressUpdated),
+ new ObservedWeightedWaitable.ReportDelegate(asyncEnded)
)
);
@@ -85,7 +85,7 @@ namespace Nuclex.Support.Tracking {
/// Performs common initialization for the public constructors
private SetProgression() {
- this.children = new List>();
+ this.children = new List>();
}
/// Immediately releases all resources owned by the object
@@ -106,7 +106,7 @@ namespace Nuclex.Support.Tracking {
}
/// Childs contained in the progression set
- public IList> Children {
+ public IList> 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 {
}
/// Progressions being managed in the set
- private List> children;
+ private List> children;
///
/// Wrapper collection for exposing the child progressions under the
/// WeightedProgression interface
diff --git a/Source/Tracking/WeightedProgression.cs b/Source/Tracking/WeightedProgression.cs
index ee49e30..df4d0f2 100644
--- a/Source/Tracking/WeightedProgression.cs
+++ b/Source/Tracking/WeightedProgression.cs
@@ -24,24 +24,24 @@ using System.Collections.Generic;
namespace Nuclex.Support.Tracking {
/// Progression with an associated weight for the total progress
- public class WeightedProgression where ProgressionType : Waitable {
+ public class WeightedWaitable where ProgressionType : Waitable {
///
/// Initializes a new weighted progression with a default weight of 1.0
///
/// Progression whose progress to monitor
- public WeightedProgression(ProgressionType progression) : this(progression, 1.0f) { }
+ public WeightedWaitable(ProgressionType progression) : this(progression, 1.0f) { }
/// Initializes a new weighted progression
/// Progression whose progress to monitor
/// Weighting of the progression's progress
- public WeightedProgression(ProgressionType progression, float weight) {
+ public WeightedWaitable(ProgressionType progression, float weight) {
this.progression = progression;
this.weight = weight;
}
/// Progression being wrapped by this weighted progression
- public ProgressionType Progression {
+ public ProgressionType Waitable {
get { return this.progression; }
}