From 878fade1bd79a866fc60c49793b4468d1d47c6d2 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 8 May 2019 09:48:57 +0200 Subject: [PATCH] Timers get cancellation support. --- .../Interfaces/Timers/ITimerManager.cs | 5 ++-- Robust.Shared/Timers/Timer.cs | 21 +++++++++------ Robust.Shared/Timers/TimerManager.cs | 27 ++++++++++++++----- Robust.UnitTesting/Shared/Timers/TimerTest.cs | 24 ++++++++++++++++- 4 files changed, 60 insertions(+), 17 deletions(-) diff --git a/Robust.Shared/Interfaces/Timers/ITimerManager.cs b/Robust.Shared/Interfaces/Timers/ITimerManager.cs index 5c3c12c1e0..4ac5b94903 100644 --- a/Robust.Shared/Interfaces/Timers/ITimerManager.cs +++ b/Robust.Shared/Interfaces/Timers/ITimerManager.cs @@ -1,10 +1,11 @@ -using Robust.Shared.Timers; +using System.Threading; +using Timer = Robust.Shared.Timers.Timer; namespace Robust.Shared.Interfaces.Timers { public interface ITimerManager { - void AddTimer(Timer timer); + void AddTimer(Timer timer, CancellationToken cancellationToken = default); void UpdateTimers(float frameTime); } diff --git a/Robust.Shared/Timers/Timer.cs b/Robust.Shared/Timers/Timer.cs index 381be0c4f8..73fdeceda0 100644 --- a/Robust.Shared/Timers/Timer.cs +++ b/Robust.Shared/Timers/Timer.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using Robust.Shared.Interfaces.Timers; using Robust.Shared.IoC; @@ -74,11 +75,12 @@ namespace Robust.Shared.Timers /// The task is resumed on the main game logic thread. /// /// The length of time, in milliseconds, to delay for. + /// /// The task that can be awaited. - public static Task Delay(int milliseconds) + public static Task Delay(int milliseconds, CancellationToken cancellationToken = default) { var tcs = new TaskCompletionSource(); - Spawn(milliseconds, () => tcs.SetResult(null)); + Spawn(milliseconds, () => tcs.SetResult(null), cancellationToken); return tcs.Task; } @@ -87,10 +89,11 @@ namespace Robust.Shared.Timers /// The task is resumed on the main game logic thread. /// /// The length of time to delay for. + /// /// The task that can be awaited. - public static Task Delay(TimeSpan duration) + public static Task Delay(TimeSpan duration, CancellationToken cancellationToken = default) { - return Delay((int)duration.TotalMilliseconds); + return Delay((int)duration.TotalMilliseconds, cancellationToken); } /// @@ -99,10 +102,11 @@ namespace Robust.Shared.Timers /// /// The length of time, in milliseconds, to wait before firing the action. /// The action to fire. - public static void Spawn(int milliseconds, Action onFired) + /// + public static void Spawn(int milliseconds, Action onFired, CancellationToken cancellationToken = default) { var timer = new Timer(milliseconds, false, onFired); - IoCManager.Resolve().AddTimer(timer); + IoCManager.Resolve().AddTimer(timer, cancellationToken); } /// @@ -111,9 +115,10 @@ namespace Robust.Shared.Timers /// /// The length of time, to wait before firing the action. /// The action to fire. - public static void Spawn(TimeSpan duration, Action onFired) + /// + public static void Spawn(TimeSpan duration, Action onFired, CancellationToken cancellationToken = default) { - Spawn((int)duration.TotalMilliseconds, onFired); + Spawn((int)duration.TotalMilliseconds, onFired, cancellationToken); } } } diff --git a/Robust.Shared/Timers/TimerManager.cs b/Robust.Shared/Timers/TimerManager.cs index 205579d17b..7c7e2efb29 100644 --- a/Robust.Shared/Timers/TimerManager.cs +++ b/Robust.Shared/Timers/TimerManager.cs @@ -1,21 +1,36 @@ using Robust.Shared.Interfaces.Timers; using System.Collections.Generic; +using System.Threading; namespace Robust.Shared.Timers { - public class TimerManager : ITimerManager + internal sealed class TimerManager : ITimerManager { - private List _timers = new List(); + private readonly List<(Timer, CancellationToken)> _timers + = new List<(Timer, CancellationToken)>(); - public void AddTimer(Timer timer) + public void AddTimer(Timer timer, CancellationToken cancellationToken = default) { - _timers.Add(timer); + _timers.Add((timer, cancellationToken)); } public void UpdateTimers(float frameTime) { - new List(_timers).ForEach(timer => timer.Update(frameTime)); - _timers.RemoveAll(timer => !timer.IsActive); + // Manual for loop so we can modify the list while enumerating. + // ReSharper disable once ForCanBeConvertedToForeach + for (var i = 0; i < _timers.Count; i++) + { + var (timer, cancellationToken) = _timers[i]; + + if (cancellationToken.IsCancellationRequested) + { + continue; + } + + timer.Update(frameTime); + } + + _timers.RemoveAll(timer => !timer.Item1.IsActive || timer.Item2.IsCancellationRequested); } } } diff --git a/Robust.UnitTesting/Shared/Timers/TimerTest.cs b/Robust.UnitTesting/Shared/Timers/TimerTest.cs index de4c497022..88f268dcac 100644 --- a/Robust.UnitTesting/Shared/Timers/TimerTest.cs +++ b/Robust.UnitTesting/Shared/Timers/TimerTest.cs @@ -1,12 +1,13 @@ using System; using System.Linq; +using System.Threading; using NUnit.Framework; using Robust.Shared.Asynchronous; using Robust.Shared.Interfaces.Log; using Robust.Shared.Interfaces.Timers; using Robust.Shared.IoC; using Robust.Shared.Log; -using Robust.Shared.Timers; +using Timer = Robust.Shared.Timers.Timer; namespace Robust.UnitTesting.Shared.Timers { @@ -145,5 +146,26 @@ namespace Robust.UnitTesting.Shared.Timers Assert.That(threw, Is.True); Assert.That(DidThrow(), Is.True); } + + [Test] + public void TestCancellation() + { + var timerManager = IoCManager.Resolve(); + var taskManager = IoCManager.Resolve(); + + var cts = new CancellationTokenSource(); + var ran = false; + Timer.Spawn(1000, () => ran = true, cts.Token); + + timerManager.UpdateTimers(0.5f); + + Assert.That(ran, Is.False); + + cts.Cancel(); + + timerManager.UpdateTimers(0.6f); + + Assert.That(ran, Is.False); + } } }