SetProgression partially working; wrote several unit tests using the NMock library; lots of smaller improvements to the progression classes
git-svn-id: file:///srv/devel/repo-conversion/nusu@12 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
parent
cefbc78868
commit
71b6dc90a2
|
@ -164,6 +164,11 @@
|
||||||
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
||||||
<Name>ThreadedMethodOperation</Name>
|
<Name>ThreadedMethodOperation</Name>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Source\Tracking\SetProgression.Test.cs">
|
||||||
|
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
||||||
|
<Name>SetProgression.Test</Name>
|
||||||
|
<DependentUpon>SetProgression.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
<Compile Include="Source\Tracking\WeightedProgression.cs">
|
<Compile Include="Source\Tracking\WeightedProgression.cs">
|
||||||
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
<XNAUseContentPipeline>false</XNAUseContentPipeline>
|
||||||
<Name>WeightedProgression</Name>
|
<Name>WeightedProgression</Name>
|
||||||
|
|
|
@ -49,35 +49,36 @@ namespace Nuclex.Support.Collections {
|
||||||
new EventHandler<ObservableCollection<int>.ItemEventArgs>(
|
new EventHandler<ObservableCollection<int>.ItemEventArgs>(
|
||||||
this.mockedSubscriber.ItemRemoved
|
this.mockedSubscriber.ItemRemoved
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Tests whether the Clearing event is fired</summary>
|
/// <summary>Tests whether the Clearing event is fired</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void TestClearingEvent() {
|
public void TestClearingEvent() {
|
||||||
|
|
||||||
Expect.Once.On(this.mockedSubscriber).
|
Expect.Once.On(this.mockedSubscriber).
|
||||||
Method("Clearing");
|
Method("Clearing");
|
||||||
|
|
||||||
this.observedCollection.Clear();
|
this.observedCollection.Clear();
|
||||||
|
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Tests whether the ItemAdded event is fired</summary>
|
/// <summary>Tests whether the ItemAdded event is fired</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void TestItemAddedEvent() {
|
public void TestItemAddedEvent() {
|
||||||
|
|
||||||
Expect.Once.On(this.mockedSubscriber).
|
Expect.Once.On(this.mockedSubscriber).
|
||||||
Method("ItemAdded").
|
Method("ItemAdded").
|
||||||
WithAnyArguments();
|
WithAnyArguments();
|
||||||
|
|
||||||
this.observedCollection.Add(123);
|
this.observedCollection.Add(123);
|
||||||
|
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Tests whether the ItemRemoved event is fired</summary>
|
/// <summary>Tests whether the ItemRemoved event is fired</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void TestItemRemovedEvent() {
|
public void TestItemRemovedEvent() {
|
||||||
|
|
||||||
Expect.Once.On(this.mockedSubscriber).
|
Expect.Once.On(this.mockedSubscriber).
|
||||||
Method("ItemAdded").
|
Method("ItemAdded").
|
||||||
WithAnyArguments();
|
WithAnyArguments();
|
||||||
|
@ -89,6 +90,7 @@ namespace Nuclex.Support.Collections {
|
||||||
this.observedCollection.Add(123);
|
this.observedCollection.Add(123);
|
||||||
this.observedCollection.Remove(123);
|
this.observedCollection.Remove(123);
|
||||||
|
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Mock object factory</summary>
|
/// <summary>Mock object factory</summary>
|
||||||
|
|
|
@ -8,34 +8,105 @@ namespace Nuclex.Support.Tracking {
|
||||||
/// <typeparam name="ProgressionType">
|
/// <typeparam name="ProgressionType">
|
||||||
/// Type of the progression that is being observed
|
/// Type of the progression that is being observed
|
||||||
/// </typeparam>
|
/// </typeparam>
|
||||||
internal class ObservedProgression<ProgressionType>
|
internal class ObservedProgression<ProgressionType> : IDisposable
|
||||||
where ProgressionType : Progression {
|
where ProgressionType : Progression {
|
||||||
|
|
||||||
|
/// <summary>Delegate for reporting progress updates</summary>
|
||||||
|
public delegate void ReportDelegate();
|
||||||
|
|
||||||
/// <summary>Initializes a new observed progression</summary>
|
/// <summary>Initializes a new observed progression</summary>
|
||||||
/// <param name="weightedProgression">Weighted progression being observed</param>
|
/// <param name="weightedProgression">Weighted progression being observed</param>
|
||||||
|
/// <param name="progressUpdateCallback">
|
||||||
|
/// Callback to invoke when the progression's progress changes
|
||||||
|
/// </param>
|
||||||
|
/// <param name="endedCallback">
|
||||||
|
/// Callback to invoke when the progression has ended
|
||||||
|
/// </param>
|
||||||
internal ObservedProgression(
|
internal ObservedProgression(
|
||||||
WeightedProgression<ProgressionType> weightedProgression
|
WeightedProgression<ProgressionType> weightedProgression,
|
||||||
|
ReportDelegate progressUpdateCallback,
|
||||||
|
ReportDelegate endedCallback
|
||||||
) {
|
) {
|
||||||
this.weightedProgression = weightedProgression;
|
this.weightedProgression = weightedProgression;
|
||||||
|
this.endedCallback = endedCallback;
|
||||||
|
this.progressUpdateCallback = progressUpdateCallback;
|
||||||
|
|
||||||
|
this.weightedProgression.Progression.AsyncEnded +=
|
||||||
|
new EventHandler(asyncEnded);
|
||||||
|
|
||||||
|
this.weightedProgression.Progression.AsyncProgressUpdated +=
|
||||||
|
new EventHandler<ProgressUpdateEventArgs>(asyncProgressUpdated);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Immediately releases all resources owned by the object</summary>
|
||||||
|
public void Dispose() {
|
||||||
|
disconnectEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Weighted progression being observed</summary>
|
/// <summary>Weighted progression being observed</summary>
|
||||||
public WeightedProgression<ProgressionType> WeightedProgression {
|
public WeightedProgression<ProgressionType> WeightedProgression {
|
||||||
get { return this.weightedProgression; }
|
get { return this.weightedProgression; }
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
|
|
||||||
internal void AsyncProgressUpdated(object sender, ProgressUpdateEventArgs e) {
|
/// <summary>Amount of progress this progression has achieved so far</summary>
|
||||||
this.Progress = e.Progress;
|
public float Progress {
|
||||||
|
get { return this.progress; }
|
||||||
}
|
}
|
||||||
internal void AsyncEnded(object sender, EventArgs e) { }
|
|
||||||
*/
|
/// <summary>Called when the observed progression has ended</summary>
|
||||||
|
/// <param name="sender">Progression that has ended</param>
|
||||||
|
/// <param name="e">Not used</param>
|
||||||
|
private void asyncEnded(object sender, EventArgs e) {
|
||||||
|
this.progress = 1.0f;
|
||||||
|
|
||||||
|
disconnectEvents(); // We don't need those anymore!
|
||||||
|
|
||||||
|
this.progressUpdateCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Called when the progress of the observed progression changes</summary>
|
||||||
|
/// <param name="sender">Progression whose progress has changed</param>
|
||||||
|
/// <param name="e">Contains the updated progress</param>
|
||||||
|
private void asyncProgressUpdated(object sender, ProgressUpdateEventArgs e) {
|
||||||
|
this.progress = e.Progress;
|
||||||
|
|
||||||
|
this.progressUpdateCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Unscribes from all events of the observed progression</summary>
|
||||||
|
private void disconnectEvents() {
|
||||||
|
|
||||||
|
// Make use of the double check locking idiom to avoid the costly lock when
|
||||||
|
// the events have already been unsubscribed
|
||||||
|
if(this.endedCallback != null) {
|
||||||
|
|
||||||
|
// This is an internal class with special knowledge that there
|
||||||
|
// is no risk of deadlock involved, so we don't need a fancy syncRoot!
|
||||||
|
lock(this) {
|
||||||
|
if(this.endedCallback != null) {
|
||||||
|
this.weightedProgression.Progression.AsyncEnded -=
|
||||||
|
new EventHandler(asyncEnded);
|
||||||
|
|
||||||
|
this.weightedProgression.Progression.AsyncProgressUpdated -=
|
||||||
|
new EventHandler<ProgressUpdateEventArgs>(asyncProgressUpdated);
|
||||||
|
|
||||||
|
this.endedCallback = null;
|
||||||
|
this.progressUpdateCallback = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // endedCallback != null
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>The weighted progression that is being observed</summary>
|
/// <summary>The weighted progression that is being observed</summary>
|
||||||
private WeightedProgression<ProgressionType> weightedProgression;
|
private WeightedProgression<ProgressionType> weightedProgression;
|
||||||
/*
|
/// <summary>Callback to invoke when the progress updates</summary>
|
||||||
/// <summary>Amount of progress this progression has achieved so far</summary>
|
private volatile ReportDelegate progressUpdateCallback;
|
||||||
|
/// <summary>Callback to invoke when the progression ends</summary>
|
||||||
|
private volatile ReportDelegate endedCallback;
|
||||||
|
/// <summary>Progress achieved so far</summary>
|
||||||
private volatile float progress;
|
private volatile float progress;
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Nuclex.Support.Tracking
|
} // namespace Nuclex.Support.Tracking
|
||||||
|
|
|
@ -27,7 +27,7 @@ namespace Nuclex.Support.Tracking {
|
||||||
/// SetProgression or QueueOperation classes was created.
|
/// SetProgression or QueueOperation classes was created.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
internal class WeightedProgressionCollection<ProgressionType> :
|
internal class WeightedProgressionWrapperCollection<ProgressionType> :
|
||||||
TransformingReadOnlyCollection<
|
TransformingReadOnlyCollection<
|
||||||
ObservedProgression<ProgressionType>, WeightedProgression<ProgressionType>
|
ObservedProgression<ProgressionType>, WeightedProgression<ProgressionType>
|
||||||
>
|
>
|
||||||
|
@ -35,7 +35,7 @@ namespace Nuclex.Support.Tracking {
|
||||||
|
|
||||||
/// <summary>Initializes a new weighted progression collection wrapper</summary>
|
/// <summary>Initializes a new weighted progression collection wrapper</summary>
|
||||||
/// <param name="items">Items to be exposed as weighted progressions</param>
|
/// <param name="items">Items to be exposed as weighted progressions</param>
|
||||||
internal WeightedProgressionCollection(
|
internal WeightedProgressionWrapperCollection(
|
||||||
IList<ObservedProgression<ProgressionType>> items
|
IList<ObservedProgression<ProgressionType>> items
|
||||||
)
|
)
|
||||||
: base(items) { }
|
: base(items) { }
|
||||||
|
|
|
@ -19,7 +19,7 @@ namespace Nuclex.Support.Tracking {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Achieved progress</summary>
|
/// <summary>Achieved progress</summary>
|
||||||
protected float progress;
|
private float progress;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ namespace Nuclex.Support.Tracking {
|
||||||
public event EventHandler AsyncEnded;
|
public event EventHandler AsyncEnded;
|
||||||
|
|
||||||
/// <summary>Whether the progression has ended already</summary>
|
/// <summary>Whether the progression has ended already</summary>
|
||||||
public virtual bool Ended {
|
public bool Ended {
|
||||||
get { return this.ended; }
|
get { return this.ended; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,6 +63,10 @@ namespace Nuclex.Support.Tracking {
|
||||||
// The WaitHandle will only be created when someone asks for it!
|
// The WaitHandle will only be created when someone asks for it!
|
||||||
// See the Double-Check Locking idiom on why the condition is checked twice
|
// See the Double-Check Locking idiom on why the condition is checked twice
|
||||||
// (primarily, it avoids an expensive lock when it isn't needed)
|
// (primarily, it avoids an expensive lock when it isn't needed)
|
||||||
|
//
|
||||||
|
// We can *not* optimize this lock away since we absolutely must not create
|
||||||
|
// two doneEvents -- someone might call .WaitOne() on the first one when only
|
||||||
|
// the second one is assigned to this.doneEvent and gets set in the end.
|
||||||
if(this.doneEvent == null) {
|
if(this.doneEvent == null) {
|
||||||
|
|
||||||
lock(this.syncRoot) {
|
lock(this.syncRoot) {
|
||||||
|
@ -109,16 +113,27 @@ namespace Nuclex.Support.Tracking {
|
||||||
/// seperately.
|
/// seperately.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected virtual void OnAsyncEnded() {
|
protected virtual void OnAsyncEnded() {
|
||||||
|
|
||||||
|
// TODO: Find a way around this. Interlocked.Exchange would be best!
|
||||||
|
// We do not lock here since we require this method to be called only once
|
||||||
|
// in the object's lifetime. If someone really badly wanted to break this
|
||||||
|
// he'd probably have a one-in-a-million chance of getting through.
|
||||||
|
if(this.ended)
|
||||||
|
throw new InvalidOperationException("The progression has already been ended");
|
||||||
|
|
||||||
this.ended = true;
|
this.ended = true;
|
||||||
|
|
||||||
lock(this.syncRoot) {
|
// Doesn't need a lock. If another thread wins the race and creates the event
|
||||||
if(this.doneEvent != null)
|
// after we just saw it being null, it would be created in an already set
|
||||||
this.doneEvent.Set();
|
// state due to the ended flag (see above) being set to true beforehand!
|
||||||
}
|
if(this.doneEvent != null)
|
||||||
|
this.doneEvent.Set();
|
||||||
|
|
||||||
|
// Finally, fire the AsyncEnded event
|
||||||
EventHandler copy = AsyncEnded;
|
EventHandler copy = AsyncEnded;
|
||||||
if(copy != null)
|
if(copy != null)
|
||||||
copy(this, EventArgs.Empty);
|
copy(this, EventArgs.Empty);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Used to synchronize multithreaded accesses to this object</summary>
|
/// <summary>Used to synchronize multithreaded accesses to this object</summary>
|
||||||
|
|
215
Source/Tracking/SetProgression.Test.cs
Normal file
215
Source/Tracking/SetProgression.Test.cs
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
#if UNITTEST
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NMock2;
|
||||||
|
|
||||||
|
namespace Nuclex.Support.Tracking {
|
||||||
|
|
||||||
|
/// <summary>Unit Test for the progression set class</summary>
|
||||||
|
[TestFixture]
|
||||||
|
public class SetProgressionTest {
|
||||||
|
|
||||||
|
#region interface ISetProgressionSubscriber
|
||||||
|
|
||||||
|
/// <summary>Interface used to test the set progression.</summary>
|
||||||
|
public interface ISetProgressionSubscriber {
|
||||||
|
|
||||||
|
/// <summary>Called when the set progression's progress changes</summary>
|
||||||
|
/// <param name="sender">Set progression whose progress has changed</param>
|
||||||
|
/// <param name="e">Contains the new progress achieved</param>
|
||||||
|
void ProgressUpdated(object sender, ProgressUpdateEventArgs e);
|
||||||
|
|
||||||
|
/// <summary>Called when the set progression has ended</summary>
|
||||||
|
/// <param name="sender">Set progression that as ended</param>
|
||||||
|
/// <param name="e">Not used</param>
|
||||||
|
void Ended(object sender, EventArgs e);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion // interface ISetProgressionSubscriber
|
||||||
|
|
||||||
|
#region class ProgressUpdateEventArgsMatcher
|
||||||
|
|
||||||
|
/// <summary>Compares two ProgressUpdateEventArgsInstances for NMock validation</summary>
|
||||||
|
private class ProgressUpdateEventArgsMatcher : Matcher {
|
||||||
|
|
||||||
|
/// <summary>Initializes a new ProgressUpdateEventArgsMatcher </summary>
|
||||||
|
/// <param name="expected">Expected progress update event arguments</param>
|
||||||
|
public ProgressUpdateEventArgsMatcher(ProgressUpdateEventArgs expected) {
|
||||||
|
this.expected = expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called by NMock to verfiy the ProgressUpdateEventArgs match the expected value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actualAsObject">Actual value to compare to the expected value</param>
|
||||||
|
/// <returns>
|
||||||
|
/// True if the actual value matches the expected value; otherwise false
|
||||||
|
/// </returns>
|
||||||
|
public override bool Matches(object actualAsObject) {
|
||||||
|
ProgressUpdateEventArgs actual = (actualAsObject as ProgressUpdateEventArgs);
|
||||||
|
if(actual == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return (actual.Progress == this.expected.Progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Creates a string representation of the expected value</summary>
|
||||||
|
/// <param name="writer">Writer to write the string representation into</param>
|
||||||
|
public override void DescribeTo(TextWriter writer) {
|
||||||
|
writer.Write(this.expected.Progress.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Expected progress update event args value</summary>
|
||||||
|
private ProgressUpdateEventArgs expected;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion // class ProgressUpdateEventArgsMatcher
|
||||||
|
|
||||||
|
#region class TestProgression
|
||||||
|
|
||||||
|
/// <summary>Progression used for testing in this unit test</summary>
|
||||||
|
private class TestProgression : Progression {
|
||||||
|
|
||||||
|
/// <summary>Changes the testing progression's indicated progress</summary>
|
||||||
|
/// <param name="progress">
|
||||||
|
/// New progress to be reported by the testing progression
|
||||||
|
/// </param>
|
||||||
|
public void ChangeProgress(float progress) {
|
||||||
|
OnAsyncProgressUpdated(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Transitions the progression into the ended state</summary>
|
||||||
|
public void End() {
|
||||||
|
OnAsyncEnded();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion // class TestProgression
|
||||||
|
|
||||||
|
/// <summary>Initialization routine executed before each test is run</summary>
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() {
|
||||||
|
this.mockery = new Mockery();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Validates that the set progression properly sums the progress</summary>
|
||||||
|
[Test]
|
||||||
|
public void TestSummedProgress() {
|
||||||
|
SetProgression<TestProgression> testSetProgression =
|
||||||
|
new SetProgression<TestProgression>(
|
||||||
|
new TestProgression[] {
|
||||||
|
new TestProgression(), new TestProgression()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
ISetProgressionSubscriber mockedSubscriber = mockSubscriber(testSetProgression);
|
||||||
|
|
||||||
|
Expect.Once.On(mockedSubscriber).
|
||||||
|
Method("ProgressUpdated").
|
||||||
|
With(
|
||||||
|
new Matcher[] {
|
||||||
|
new NMock2.Matchers.TypeMatcher(typeof(SetProgression<TestProgression>)),
|
||||||
|
new ProgressUpdateEventArgsMatcher(new ProgressUpdateEventArgs(0.25f))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
testSetProgression.Childs[0].Progression.ChangeProgress(0.5f);
|
||||||
|
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Validates that the set progression respects the weights</summary>
|
||||||
|
[Test]
|
||||||
|
public void TestWeightedSummedProgress() {
|
||||||
|
SetProgression<TestProgression> testSetProgression =
|
||||||
|
new SetProgression<TestProgression>(
|
||||||
|
new WeightedProgression<TestProgression>[] {
|
||||||
|
new WeightedProgression<TestProgression>(new TestProgression(), 1.0f),
|
||||||
|
new WeightedProgression<TestProgression>(new TestProgression(), 2.0f)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
ISetProgressionSubscriber mockedSubscriber = mockSubscriber(testSetProgression);
|
||||||
|
|
||||||
|
Expect.Once.On(mockedSubscriber).
|
||||||
|
Method("ProgressUpdated").
|
||||||
|
With(
|
||||||
|
new Matcher[] {
|
||||||
|
new NMock2.Matchers.TypeMatcher(typeof(SetProgression<TestProgression>)),
|
||||||
|
new ProgressUpdateEventArgsMatcher(new ProgressUpdateEventArgs(0.5f / 3.0f))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
testSetProgression.Childs[0].Progression.ChangeProgress(0.5f);
|
||||||
|
|
||||||
|
Expect.Once.On(mockedSubscriber).
|
||||||
|
Method("ProgressUpdated").
|
||||||
|
With(
|
||||||
|
new Matcher[] {
|
||||||
|
new NMock2.Matchers.TypeMatcher(typeof(SetProgression<TestProgression>)),
|
||||||
|
new ProgressUpdateEventArgsMatcher(new ProgressUpdateEventArgs(0.5f))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
testSetProgression.Childs[1].Progression.ChangeProgress(0.5f);
|
||||||
|
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Mocks a subscriber for the events of a progression</summary>
|
||||||
|
/// <param name="progression">Progression to mock an event subscriber for</param>
|
||||||
|
/// <returns>The mocked event subscriber</returns>
|
||||||
|
private ISetProgressionSubscriber mockSubscriber(Progression progression) {
|
||||||
|
ISetProgressionSubscriber mockedSubscriber =
|
||||||
|
this.mockery.NewMock<ISetProgressionSubscriber>();
|
||||||
|
|
||||||
|
progression.AsyncEnded += new EventHandler(mockedSubscriber.Ended);
|
||||||
|
progression.AsyncProgressUpdated +=
|
||||||
|
new EventHandler<ProgressUpdateEventArgs>(mockedSubscriber.ProgressUpdated);
|
||||||
|
|
||||||
|
return mockedSubscriber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates that the ended event is triggered when the last progression ends
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestEndedEvent() {
|
||||||
|
SetProgression<TestProgression> testSetProgression =
|
||||||
|
new SetProgression<TestProgression>(
|
||||||
|
new TestProgression[] {
|
||||||
|
new TestProgression(), new TestProgression()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
ISetProgressionSubscriber mockedSubscriber = mockSubscriber(testSetProgression);
|
||||||
|
|
||||||
|
Expect.Exactly(2).On(mockedSubscriber).
|
||||||
|
Method("ProgressUpdated").
|
||||||
|
WithAnyArguments();
|
||||||
|
|
||||||
|
Expect.Once.On(mockedSubscriber).
|
||||||
|
Method("Ended").
|
||||||
|
WithAnyArguments();
|
||||||
|
|
||||||
|
testSetProgression.Childs[0].Progression.End();
|
||||||
|
testSetProgression.Childs[1].Progression.End();
|
||||||
|
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Mock object factory</summary>
|
||||||
|
private Mockery mockery;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Support.Tracking
|
||||||
|
|
||||||
|
#endif // UNITTEST
|
|
@ -11,31 +11,139 @@ namespace Nuclex.Support.Tracking {
|
||||||
public class SetProgression<ProgressionType> : Progression
|
public class SetProgression<ProgressionType> : Progression
|
||||||
where ProgressionType : Progression {
|
where ProgressionType : Progression {
|
||||||
|
|
||||||
|
/// <summary>Performs common initialization for the public constructors</summary>
|
||||||
|
private SetProgression() {
|
||||||
|
this.childs = new List<ObservedProgression<ProgressionType>>();
|
||||||
|
this.asyncProgressUpdatedDelegate =
|
||||||
|
new ObservedProgression<ProgressionType>.ReportDelegate(asyncProgressUpdated);
|
||||||
|
this.asyncEndedDelegate =
|
||||||
|
new ObservedProgression<ProgressionType>.ReportDelegate(asyncEnded);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Initializes a new set progression</summary>
|
/// <summary>Initializes a new set progression</summary>
|
||||||
/// <param name="progressions">Progressions to track with this set</param>
|
/// <param name="childs">Progressions to track with this set</param>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Uses a default weighting factor of 1.0 for all progressions.
|
/// Uses a default weighting factor of 1.0 for all progressions.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public SetProgression(IEnumerable<ProgressionType> progressions) {
|
public SetProgression(IEnumerable<ProgressionType> childs)
|
||||||
|
: this() {
|
||||||
|
|
||||||
|
// Construct a WeightedProgression with the default weight for each
|
||||||
|
// progression and wrap it in an ObservedProgression
|
||||||
|
foreach(ProgressionType progression in childs) {
|
||||||
|
this.childs.Add(
|
||||||
|
new ObservedProgression<ProgressionType>(
|
||||||
|
new WeightedProgression<ProgressionType>(progression),
|
||||||
|
this.asyncProgressUpdatedDelegate, this.asyncEndedDelegate
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since all progressions have a weight of 1.0, the total weight is
|
||||||
|
// equal to the number of progressions in our list
|
||||||
|
this.totalWeight = (float)this.childs.Count;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Initializes a new set progression</summary>
|
/// <summary>Initializes a new set progression</summary>
|
||||||
/// <param name="progressions">Progressions to track with this set</param>
|
/// <param name="childs">Progressions to track with this set</param>
|
||||||
public SetProgression(IEnumerable<WeightedProgression<ProgressionType>> progressions) {
|
public SetProgression(
|
||||||
|
IEnumerable<WeightedProgression<ProgressionType>> childs
|
||||||
|
)
|
||||||
|
: this() {
|
||||||
|
|
||||||
|
// Construct an ObservedProgression around each of the WeightedProgressions
|
||||||
|
float totalWeight = 0.0f;
|
||||||
|
foreach(WeightedProgression<ProgressionType> progression in childs) {
|
||||||
|
this.childs.Add(
|
||||||
|
new ObservedProgression<ProgressionType>(
|
||||||
|
progression,
|
||||||
|
this.asyncProgressUpdatedDelegate, this.asyncEndedDelegate
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sum up the total weight
|
||||||
|
totalWeight += progression.Weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take over the summed weight of all progressions we were given
|
||||||
|
this.totalWeight = totalWeight;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Childs contained in the progression set</summary>
|
/// <summary>Childs contained in the progression set</summary>
|
||||||
public ReadOnlyCollection<WeightedProgression<ProgressionType>> Childs {
|
public IList<WeightedProgression<ProgressionType>> Childs {
|
||||||
get { return null; }
|
get {
|
||||||
|
|
||||||
|
// The wrapper is constructed only when needed. Most of the time, users will
|
||||||
|
// just create a SetProgression and monitor its progress without ever using
|
||||||
|
// the Childs collection.
|
||||||
|
if(this.wrapper == null) {
|
||||||
|
|
||||||
|
// This doesn't need a lock because it's only a stateless wrapper. If it
|
||||||
|
// is constructed twice, then so be it.
|
||||||
|
this.wrapper = new WeightedProgressionWrapperCollection<ProgressionType>(
|
||||||
|
this.childs
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.wrapper;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the progress of one of the observed progressions changes
|
||||||
|
/// </summary>
|
||||||
|
private void asyncProgressUpdated() {
|
||||||
|
|
||||||
|
// Calculate the sum of the progress reported by our child progressions,
|
||||||
|
// scaled to the weight each progression has assigned to it.
|
||||||
|
float totalProgress = 0.0f;
|
||||||
|
for(int index = 0; index < this.childs.Count; ++index) {
|
||||||
|
totalProgress +=
|
||||||
|
this.childs[index].Progress * this.childs[index].WeightedProgression.Weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the actual combined progress
|
||||||
|
if(this.totalWeight > 0.0f)
|
||||||
|
totalProgress /= this.totalWeight;
|
||||||
|
|
||||||
|
// Send out the progress update
|
||||||
|
OnAsyncProgressUpdated(totalProgress);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when an observed progressions ends
|
||||||
|
/// </summary>
|
||||||
|
private void asyncEnded() {
|
||||||
|
|
||||||
|
for(int index = 0; index < this.childs.Count; ++index)
|
||||||
|
if(!this.childs[index].WeightedProgression.Progression.Ended)
|
||||||
|
return;
|
||||||
|
|
||||||
|
OnAsyncEnded();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Progressions being managed in the set</summary>
|
/// <summary>Progressions being managed in the set</summary>
|
||||||
private ReadOnlyCollection<ProgressionType> progressions;
|
private List<ObservedProgression<ProgressionType>> childs;
|
||||||
/// <summary>whether the progress needs to be recalculated</summary>
|
/// <summary>
|
||||||
private volatile bool needProgressRecalculation;
|
/// Wrapper collection for exposing the child progressions under the
|
||||||
/// <summary>Total progress achieved by the progressions in this collection</summary>
|
/// WeightedProgression interface
|
||||||
private volatile float totalProgress;
|
/// </summary>
|
||||||
*/
|
private volatile WeightedProgressionWrapperCollection<ProgressionType> wrapper;
|
||||||
|
/// <summary>Summed weight of all progression in the set</summary>
|
||||||
|
private float totalWeight;
|
||||||
|
|
||||||
|
/// <summary>Delegate for the asyncProgressUpdated() method</summary>
|
||||||
|
private ObservedProgression<ProgressionType>.ReportDelegate asyncProgressUpdatedDelegate;
|
||||||
|
/// <summary>Delegate for the asyncEnded() method</summary>
|
||||||
|
private ObservedProgression<ProgressionType>.ReportDelegate asyncEndedDelegate;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Nuclex.Support.Tracking
|
} // namespace Nuclex.Support.Tracking
|
||||||
|
|
Loading…
Reference in New Issue
Block a user