diff --git a/src/Controller/DogAgilityCompetition.Controller.csproj b/src/Controller/DogAgilityCompetition.Controller.csproj
index 21fd9ff..a3fbec0 100644
--- a/src/Controller/DogAgilityCompetition.Controller.csproj
+++ b/src/Controller/DogAgilityCompetition.Controller.csproj
@@ -85,9 +85,10 @@
+
-
+
diff --git a/src/Controller/Engine/CompetitionClassController.cs b/src/Controller/Engine/CompetitionClassController.cs
index e5a7d08..66d6953 100644
--- a/src/Controller/Engine/CompetitionClassController.cs
+++ b/src/Controller/Engine/CompetitionClassController.cs
@@ -490,7 +490,8 @@ private void HandlePassStart([NotNull] RecordedTime passageTime)
runData.Timings = new CompetitionRunTimings(passageTime);
SetState(CompetitionClassState.StartPassed);
- runData.EliminationTracker.StartMonitorCourseTime(modelSnapshot.ClassInfo.MaximumCourseTime);
+ runData.CourseTimeTracker.StartMonitorCourseTime(modelSnapshot.ClassInfo.StandardCourseTime,
+ modelSnapshot.ClassInfo.MaximumCourseTime);
using (var collector = new VisualizationUpdateCollector(visualizer))
{
@@ -573,7 +574,7 @@ private void HandlePassFinish([NotNull] RecordedTime passageTime)
TimeSpanWithAccuracy elapsed = passageTime.ElapsedSince(runData.Timings.StartTime);
Log.Info($"Passed Finish at {elapsed}.");
- runData.EliminationTracker.StopMonitorCourseTime();
+ runData.CourseTimeTracker.StopMonitorCourseTime();
collector.Include(PrimaryTimeStopAndSet.FromTimeSpanWithAccuracy(elapsed));
}
});
@@ -688,7 +689,7 @@ private void PrepareForRun([NotNull] VisualizationUpdateCollector collector, boo
private void CompleteActiveRun([NotNull] VisualizationUpdateCollector collector)
{
- runData.EliminationTracker.StopMonitorCourseTime();
+ runData.CourseTimeTracker.StopMonitorCourseTime();
if (!runData.HasFinished)
{
@@ -1087,8 +1088,10 @@ private sealed class CompetitionRunData : IDisposable
public CompetitionRunTimings Timings { get; set; }
[NotNull]
- public EliminationTracker EliminationTracker { get; } =
- new EliminationTracker(CompetitionRunResult.RefusalStepSize, CompetitionRunResult.EliminationThreshold);
+ public CourseTimeTracker CourseTimeTracker { get; } = new CourseTimeTracker();
+
+ [NotNull]
+ public EliminationTracker EliminationTracker { get; }
public int FaultCount { get; set; }
@@ -1097,6 +1100,12 @@ public bool HasFaultsOrRefusalsOrIsEliminated
public bool HasFinished => Timings?.FinishTime != null;
+ public CompetitionRunData()
+ {
+ EliminationTracker = new EliminationTracker(CompetitionRunResult.RefusalStepSize,
+ CompetitionRunResult.EliminationThreshold, CourseTimeTracker);
+ }
+
[NotNull]
public CompetitionRunResult ToRunResultFor([NotNull] Competitor competitor)
{
@@ -1174,7 +1183,7 @@ public override string ToString()
public void Dispose()
{
- EliminationTracker.Dispose();
+ CourseTimeTracker.Dispose();
}
private bool isShowingExistingRunResult;
@@ -1195,6 +1204,7 @@ public void Reset(bool hasExistingRunResult)
Timings = null;
FaultCount = 0;
+ CourseTimeTracker.StopMonitorCourseTime();
EliminationTracker.Reset();
isShowingExistingRunResult = hasExistingRunResult;
diff --git a/src/Controller/Engine/CourseTimeTracker.cs b/src/Controller/Engine/CourseTimeTracker.cs
new file mode 100644
index 0000000..d725b27
--- /dev/null
+++ b/src/Controller/Engine/CourseTimeTracker.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Threading;
+using JetBrains.Annotations;
+
+namespace DogAgilityCompetition.Controller.Engine
+{
+ public sealed class CourseTimeTracker : IDisposable
+ {
+ private static readonly TimeSpan InfiniteTime = TimeSpan.FromMilliseconds(-1);
+
+ [CanBeNull]
+ private Timer standardCourseTimeTimer; // Protected by stateLock
+
+ [CanBeNull]
+ private Timer maximumCourseTimeTimer; // Protected by stateLock
+
+ [NotNull]
+ private readonly object stateLock = new object();
+
+ public event EventHandler StandardCourseTimeElapsed = delegate { };
+ public event EventHandler MaximumCourseTimeElapsed = delegate { };
+
+ public void StartMonitorCourseTime([CanBeNull] TimeSpan? standardCourseTime,
+ [CanBeNull] TimeSpan? maximumCourseTime)
+ {
+ lock (stateLock)
+ {
+ if (standardCourseTimeTimer != null || maximumCourseTimeTimer != null)
+ {
+ throw new InvalidOperationException("Already started.");
+ }
+
+ if (standardCourseTime != null)
+ {
+ standardCourseTimeTimer = new Timer(state => CourseTimeTimerTick(true), null,
+ standardCourseTime.Value, InfiniteTime);
+ }
+
+ if (maximumCourseTime != null)
+ {
+ maximumCourseTimeTimer = new Timer(state => CourseTimeTimerTick(false), null,
+ maximumCourseTime.Value, InfiniteTime);
+ }
+ }
+ }
+
+ private void CourseTimeTimerTick(bool isStandardCourseTime)
+ {
+ if (isStandardCourseTime)
+ {
+ StandardCourseTimeElapsed(this, EventArgs.Empty);
+ }
+ else
+ {
+ MaximumCourseTimeElapsed(this, EventArgs.Empty);
+ }
+ }
+
+ public void StopMonitorCourseTime()
+ {
+ lock (stateLock)
+ {
+ if (standardCourseTimeTimer != null)
+ {
+ standardCourseTimeTimer.Dispose();
+ standardCourseTimeTimer = null;
+ }
+
+ if (maximumCourseTimeTimer != null)
+ {
+ maximumCourseTimeTimer.Dispose();
+ maximumCourseTimeTimer = null;
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ StopMonitorCourseTime();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Controller/Engine/EliminationTracker.cs b/src/Controller/Engine/EliminationTracker.cs
index 49ec37a..3b50e9a 100644
--- a/src/Controller/Engine/EliminationTracker.cs
+++ b/src/Controller/Engine/EliminationTracker.cs
@@ -1,28 +1,20 @@
using System;
using System.Reflection;
-using System.Threading;
using DogAgilityCompetition.Circe;
using JetBrains.Annotations;
namespace DogAgilityCompetition.Controller.Engine
{
- public sealed class EliminationTracker : IDisposable
+ public sealed class EliminationTracker
{
[NotNull]
private static readonly ISystemLogger Log = new Log4NetSystemLogger(MethodBase.GetCurrentMethod().DeclaringType);
- private static readonly TimeSpan InfiniteTime = TimeSpan.FromMilliseconds(-1);
-
private readonly int refusalStepSize;
private readonly int eliminationThreshold;
- [NotNull]
- private readonly Timer maximumCourseTimeTimer;
-
- private bool maximumCourseTimeElapsed; // Protected by stateLock
-
+ private bool hasMaximumCourseTimeElapsed; // Protected by stateLock
private bool isManuallyEliminated; // Protected by stateLock
-
private int refusalCount; // Protected by stateLock
[NotNull]
@@ -50,7 +42,7 @@ public bool IsEliminated
}
private bool UnsafeIsEliminated
- => isManuallyEliminated || refusalCount >= MaxRefusalsValue || maximumCourseTimeElapsed;
+ => isManuallyEliminated || refusalCount >= MaxRefusalsValue || hasMaximumCourseTimeElapsed;
public bool IsManuallyEliminated
{
@@ -88,17 +80,20 @@ public int RefusalCount
}
}
- public EliminationTracker(int refusalStepSize, int eliminationThreshold)
+ public EliminationTracker(int refusalStepSize, int eliminationThreshold,
+ [NotNull] CourseTimeTracker courseTimeTracker)
{
+ Guard.NotNull(courseTimeTracker, nameof(courseTimeTracker));
+
this.refusalStepSize = refusalStepSize;
this.eliminationThreshold = eliminationThreshold;
- maximumCourseTimeTimer = new Timer(state => MaximumCourseTimeTimerTick());
+ courseTimeTracker.MaximumCourseTimeElapsed += (sender, args) => HandleMaximumCourseTimeElapsed();
}
- private void MaximumCourseTimeTimerTick()
+ private void HandleMaximumCourseTimeElapsed()
{
- RaiseEventsOnChangeWithLock(() => { maximumCourseTimeElapsed = true; });
+ RaiseEventsOnChangeWithLock(() => { hasMaximumCourseTimeElapsed = true; });
}
public void IncreaseRefusals()
@@ -123,44 +118,24 @@ public void DecreaseRefusals()
});
}
- public void StartMonitorCourseTime([CanBeNull] TimeSpan? maximumCourseTime)
- {
- if (maximumCourseTime != null)
- {
- maximumCourseTimeTimer.Change(maximumCourseTime.Value, InfiniteTime);
- }
- }
-
- public void StopMonitorCourseTime()
- {
- maximumCourseTimeTimer.Change(Timeout.Infinite, Timeout.Infinite);
- }
-
public void Reset()
{
// Note: Intentionally not raising any events here, because caller wants to
// combine multiple changes in single network packet (performance optimization).
- StopMonitorCourseTime();
-
using (var lockTracker = new LockTracker(Log, MethodBase.GetCurrentMethod()))
{
lock (stateLock)
{
lockTracker.Acquired();
- maximumCourseTimeElapsed = false;
+ hasMaximumCourseTimeElapsed = false;
isManuallyEliminated = false;
refusalCount = 0;
}
}
}
- public void Dispose()
- {
- maximumCourseTimeTimer.Dispose();
- }
-
private void RaiseEventsOnChangeWithLock([NotNull] Action action)
{
EliminationEventArgs argsForEliminationChanged;