Fixed some possible issues in the ProgressionTracker; wrote several new NMock-assisted tests for it
git-svn-id: file:///srv/devel/repo-conversion/nusu@38 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
parent
ba1cee917d
commit
81cb56f468
|
@ -47,6 +47,13 @@ namespace Nuclex.Support.Tracking {
|
||||||
ReportDelegate endedCallback
|
ReportDelegate endedCallback
|
||||||
) {
|
) {
|
||||||
this.weightedProgression = weightedProgression;
|
this.weightedProgression = weightedProgression;
|
||||||
|
|
||||||
|
if(weightedProgression.Progression.Ended) {
|
||||||
|
|
||||||
|
this.progress = 1.0f;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
this.endedCallback = endedCallback;
|
this.endedCallback = endedCallback;
|
||||||
this.progressUpdateCallback = progressUpdateCallback;
|
this.progressUpdateCallback = progressUpdateCallback;
|
||||||
|
|
||||||
|
@ -55,6 +62,8 @@ namespace Nuclex.Support.Tracking {
|
||||||
|
|
||||||
this.weightedProgression.Progression.AsyncProgressUpdated +=
|
this.weightedProgression.Progression.AsyncProgressUpdated +=
|
||||||
new EventHandler<ProgressUpdateEventArgs>(asyncProgressUpdated);
|
new EventHandler<ProgressUpdateEventArgs>(asyncProgressUpdated);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Immediately releases all resources owned by the object</summary>
|
/// <summary>Immediately releases all resources owned by the object</summary>
|
||||||
|
|
|
@ -232,6 +232,76 @@ namespace Nuclex.Support.Tracking {
|
||||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates that the tracker behaves correctly if it is fed with progressions
|
||||||
|
/// that have already ended.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestSoleEndedProgression() {
|
||||||
|
ProgressionTracker tracker = new ProgressionTracker();
|
||||||
|
|
||||||
|
IProgressionTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
|
||||||
|
|
||||||
|
tracker.Track(Progression.EndedDummy);
|
||||||
|
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates that the tracker behaves correctly if it is fed with progressions
|
||||||
|
/// that have already ended in addition to progressions that are actively executing.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestEndedProgression() {
|
||||||
|
ProgressionTracker tracker = new ProgressionTracker();
|
||||||
|
|
||||||
|
IProgressionTrackerSubscriber mockedSubscriber = mockSubscriber(tracker);
|
||||||
|
|
||||||
|
Expect.Once.On(mockedSubscriber).
|
||||||
|
Method("IdleStateChanged").
|
||||||
|
WithAnyArguments();
|
||||||
|
|
||||||
|
Expect.Once.On(mockedSubscriber).
|
||||||
|
Method("ProgressUpdated").
|
||||||
|
With(
|
||||||
|
new Matcher[] {
|
||||||
|
new NMock2.Matchers.TypeMatcher(typeof(ProgressionTracker)),
|
||||||
|
new ProgressUpdateEventArgsMatcher(new ProgressUpdateEventArgs(0.0f))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
TestProgression test1 = new TestProgression();
|
||||||
|
tracker.Track(test1);
|
||||||
|
|
||||||
|
Expect.Once.On(mockedSubscriber).
|
||||||
|
Method("ProgressUpdated").
|
||||||
|
With(
|
||||||
|
new Matcher[] {
|
||||||
|
new NMock2.Matchers.TypeMatcher(typeof(ProgressionTracker)),
|
||||||
|
new ProgressUpdateEventArgsMatcher(new ProgressUpdateEventArgs(0.5f))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
tracker.Track(Progression.EndedDummy);
|
||||||
|
|
||||||
|
Expect.Once.On(mockedSubscriber).
|
||||||
|
Method("ProgressUpdated").
|
||||||
|
With(
|
||||||
|
new Matcher[] {
|
||||||
|
new NMock2.Matchers.TypeMatcher(typeof(ProgressionTracker)),
|
||||||
|
new ProgressUpdateEventArgsMatcher(new ProgressUpdateEventArgs(1.0f))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Expect.Once.On(mockedSubscriber).
|
||||||
|
Method("IdleStateChanged").
|
||||||
|
WithAnyArguments();
|
||||||
|
|
||||||
|
test1.End();
|
||||||
|
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Mocks a subscriber for the events of a tracker</summary>
|
/// <summary>Mocks a subscriber for the events of a tracker</summary>
|
||||||
/// <param name="tracker">Tracker to mock an event subscriber for</param>
|
/// <param name="tracker">Tracker to mock an event subscriber for</param>
|
||||||
/// <returns>The mocked event subscriber</returns>
|
/// <returns>The mocked event subscriber</returns>
|
||||||
|
|
|
@ -105,6 +105,7 @@ namespace Nuclex.Support.Tracking {
|
||||||
for(int index = 0; index < this.trackedProgressions.Count; ++index)
|
for(int index = 0; index < this.trackedProgressions.Count; ++index)
|
||||||
this.trackedProgressions[index].Dispose();
|
this.trackedProgressions[index].Dispose();
|
||||||
|
|
||||||
|
// Help the GC a bit by untangling the references :)
|
||||||
this.trackedProgressions.Clear();
|
this.trackedProgressions.Clear();
|
||||||
this.trackedProgressions = null;
|
this.trackedProgressions = null;
|
||||||
|
|
||||||
|
@ -128,6 +129,24 @@ namespace Nuclex.Support.Tracking {
|
||||||
// new progression added to the collection.
|
// new progression added to the collection.
|
||||||
lock(this.trackedProgressions) {
|
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;
|
||||||
|
|
||||||
|
// 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. This would otherwise risk processing a progress update
|
||||||
|
// before the progression has been added to the tracked progressions list.
|
||||||
|
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
|
||||||
|
if(!wasEmpty) {
|
||||||
|
|
||||||
this.trackedProgressions.Add(
|
this.trackedProgressions.Add(
|
||||||
new ObservedWeightedProgression<Progression>(
|
new ObservedWeightedProgression<Progression>(
|
||||||
new WeightedProgression<Progression>(progression, weight),
|
new WeightedProgression<Progression>(progression, weight),
|
||||||
|
@ -136,22 +155,44 @@ namespace Nuclex.Support.Tracking {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// This can be done after we registered the wrapper to our delegates because
|
// All done, the total progress is different now, so force a recalculation and
|
||||||
// any incoming progress updates will be stopped from the danger of a
|
// send out the AsyncProgressUpdated event.
|
||||||
// division-by-zero from the potentially still zeroed totalWeight by the lock.
|
recalculateProgress();
|
||||||
this.totalWeight += weight;
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { // Progression is still running
|
||||||
|
|
||||||
|
ObservedWeightedProgression<Progression> observedProgression =
|
||||||
|
new ObservedWeightedProgression<Progression>(
|
||||||
|
new WeightedProgression<Progression>(progression, weight),
|
||||||
|
this.asyncProgressUpdatedDelegate,
|
||||||
|
this.asyncEndedDelegate
|
||||||
|
);
|
||||||
|
|
||||||
|
this.trackedProgressions.Add(observedProgression);
|
||||||
|
|
||||||
// If this is the first progression to be added to the list, tell our
|
// If this is the first progression to be added to the list, tell our
|
||||||
// owner that we're idle no longer!
|
// owner that we're idle no longer!
|
||||||
if(this.trackedProgressions.Count == 1)
|
if(wasEmpty)
|
||||||
OnAsyncIdleStateChanged(false);
|
setIdle(false);
|
||||||
|
|
||||||
} // lock
|
|
||||||
|
|
||||||
// All done, the total progress is different now, so force a recalculation and
|
// All done, the total progress is different now, so force a recalculation and
|
||||||
// send out the AsyncProgressUpdated event.
|
// send out the AsyncProgressUpdated event.
|
||||||
recalculateProgress();
|
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 at all.
|
||||||
|
if(progression.Ended) {
|
||||||
|
asyncEnded();
|
||||||
|
observedProgression.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // lock
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Stops tracking the specified progression</summary>
|
/// <summary>Stops tracking the specified progression</summary>
|
||||||
|
@ -181,8 +222,8 @@ namespace Nuclex.Support.Tracking {
|
||||||
|
|
||||||
this.totalWeight = 0.0f;
|
this.totalWeight = 0.0f;
|
||||||
|
|
||||||
// Report that we're idle now!
|
// If we entered the idle state with this call, report the state change!
|
||||||
OnAsyncIdleStateChanged(true);
|
setIdle(true);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
@ -211,8 +252,6 @@ namespace Nuclex.Support.Tracking {
|
||||||
/// <summary>Fires the AsyncIdleStateChanged event</summary>
|
/// <summary>Fires the AsyncIdleStateChanged event</summary>
|
||||||
/// <param name="idle">New idle state to report</param>
|
/// <param name="idle">New idle state to report</param>
|
||||||
protected virtual void OnAsyncIdleStateChanged(bool idle) {
|
protected virtual void OnAsyncIdleStateChanged(bool idle) {
|
||||||
this.idle = idle;
|
|
||||||
|
|
||||||
EventHandler<IdleStateEventArgs> copy = AsyncIdleStateChanged;
|
EventHandler<IdleStateEventArgs> copy = AsyncIdleStateChanged;
|
||||||
if(copy != null)
|
if(copy != null)
|
||||||
copy(this, new IdleStateEventArgs(idle));
|
copy(this, new IdleStateEventArgs(idle));
|
||||||
|
@ -221,8 +260,6 @@ namespace Nuclex.Support.Tracking {
|
||||||
/// <summary>Fires the AsyncProgressUpdated event</summary>
|
/// <summary>Fires the AsyncProgressUpdated event</summary>
|
||||||
/// <param name="progress">New progress to report</param>
|
/// <param name="progress">New progress to report</param>
|
||||||
protected virtual void OnAsyncProgressUpdated(float progress) {
|
protected virtual void OnAsyncProgressUpdated(float progress) {
|
||||||
this.progress = progress;
|
|
||||||
|
|
||||||
EventHandler<ProgressUpdateEventArgs> copy = AsyncProgressUpdated;
|
EventHandler<ProgressUpdateEventArgs> copy = AsyncProgressUpdated;
|
||||||
if(copy != null)
|
if(copy != null)
|
||||||
copy(this, new ProgressUpdateEventArgs(progress));
|
copy(this, new ProgressUpdateEventArgs(progress));
|
||||||
|
@ -253,10 +290,12 @@ namespace Nuclex.Support.Tracking {
|
||||||
// the one for the number of progressions we just summed -- by design,
|
// 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.
|
// the total weight always has to be updated at the same time as the collection.
|
||||||
totalProgress /= this.totalWeight;
|
totalProgress /= this.totalWeight;
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, trigger the event
|
// Finally, trigger the event
|
||||||
|
this.progress = totalProgress;
|
||||||
OnAsyncProgressUpdated(totalProgress);
|
OnAsyncProgressUpdated(totalProgress);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Called when one of the tracked progressions has ended</summary>
|
/// <summary>Called when one of the tracked progressions has ended</summary>
|
||||||
|
@ -270,16 +309,15 @@ namespace Nuclex.Support.Tracking {
|
||||||
if(!this.trackedProgressions[index].WeightedProgression.Progression.Ended)
|
if(!this.trackedProgressions[index].WeightedProgression.Progression.Ended)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// All progressions have finished, get rid of the wrappers and disconnect
|
// All progressions have finished, get rid of the wrappers and make a
|
||||||
// their events.
|
// fresh start for future progressions to be tracked. No need to call
|
||||||
for(int index = 0; index < this.trackedProgressions.Count; ++index)
|
// Dispose() since, as a matter of fact, when the progression
|
||||||
this.trackedProgressions[index].Dispose();
|
|
||||||
|
|
||||||
this.trackedProgressions.Clear();
|
this.trackedProgressions.Clear();
|
||||||
this.totalWeight = 0.0f;
|
this.totalWeight = 0.0f;
|
||||||
|
|
||||||
// Notify our owner that we're idle now.
|
// Notify our owner that we're idle now. This line is only reached when all
|
||||||
OnAsyncIdleStateChanged(true);
|
// progressions were finished, so it's safe to trigger this here.
|
||||||
|
setIdle(true);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -289,6 +327,17 @@ namespace Nuclex.Support.Tracking {
|
||||||
recalculateProgress();
|
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>Total weight of all progressions being tracked</summary>
|
/// <summary>Total weight of all progressions being tracked</summary>
|
||||||
private volatile float totalWeight;
|
private volatile float totalWeight;
|
||||||
/// <summary>Progressions being tracked by this tracker</summary>
|
/// <summary>Progressions being tracked by this tracker</summary>
|
||||||
|
@ -300,7 +349,7 @@ namespace Nuclex.Support.Tracking {
|
||||||
/// <summary>Whether the tracker is currently idle</summary>
|
/// <summary>Whether the tracker is currently idle</summary>
|
||||||
private bool idle;
|
private bool idle;
|
||||||
/// <summary>Current summed progress of the tracked progressions</summary>
|
/// <summary>Current summed progress of the tracked progressions</summary>
|
||||||
private float progress;
|
private volatile float progress;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user