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;