diff --git a/Projects/Server.Tests/Fixtures/ServerFixture.cs b/Projects/Server.Tests/Fixtures/ServerFixture.cs index d7bb4056b..f286d1051 100644 --- a/Projects/Server.Tests/Fixtures/ServerFixture.cs +++ b/Projects/Server.Tests/Fixtures/ServerFixture.cs @@ -18,7 +18,7 @@ namespace Server.Tests // Configure the world World.Configure(); - Timer.Initialize(0); + Timer.Init(0); // Load the world World.Load(); @@ -26,7 +26,7 @@ namespace Server.Tests public void Dispose() { - Timer.Initialize(0); + Timer.Init(0); } } } diff --git a/Projects/Server.Tests/Tests/Timer/TimerTests.cs b/Projects/Server.Tests/Tests/Timer/TimerTests.cs index 1e42c076d..d7a005626 100644 --- a/Projects/Server.Tests/Tests/Timer/TimerTests.cs +++ b/Projects/Server.Tests/Tests/Timer/TimerTests.cs @@ -23,7 +23,7 @@ namespace Server.Tests timerTicks.ExecutedCount++; } - Timer.Initialize(timerTicks.Ticks); + Timer.Init(timerTicks.Ticks); var timer = Timer.DelayCall(TimeSpan.FromMilliseconds(ticks), action); timer.Start(); @@ -51,7 +51,7 @@ namespace Server.Tests Assert.Equal(timerTicks.ExpectedTicks, timerTicks.Ticks); } - Timer.Initialize(timerTicks.Ticks); + Timer.Init(timerTicks.Ticks); var timer = Timer.DelayCall(TimeSpan.FromMilliseconds(delay), TimeSpan.FromMilliseconds(interval), count, action); timer.Start(); diff --git a/Projects/Server/Main.cs b/Projects/Server/Main.cs index f4a261a9c..6944fc26d 100644 --- a/Projects/Server/Main.cs +++ b/Projects/Server/Main.cs @@ -467,7 +467,7 @@ namespace Server VerifySerialization(); - Timer.Initialize(TickCount); + Timer.Init(TickCount); AssemblyHandler.Invoke("Configure"); diff --git a/Projects/Server/Timer/Timer.DelayCall.cs b/Projects/Server/Timer/Timer.DelayCall.cs index 1e8783efc..2d9f43859 100644 --- a/Projects/Server/Timer/Timer.DelayCall.cs +++ b/Projects/Server/Timer/Timer.DelayCall.cs @@ -17,8 +17,6 @@ using System; namespace Server { - public delegate void TimerCallback(); - public delegate void TimerStateCallback(T state); public delegate void TimerStateCallback(T1 t1, T2 t2); @@ -32,15 +30,15 @@ namespace Server private static string FormatDelegate(Delegate callback) => callback == null ? "null" : $"{callback.Method.DeclaringType?.FullName ?? ""}.{callback.Method.Name}"; - public static Timer DelayCall(TimerCallback callback) => DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback); + public static Timer DelayCall(Action callback) => DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback); - public static Timer DelayCall(TimeSpan delay, TimerCallback callback) => + public static Timer DelayCall(TimeSpan delay, Action callback) => DelayCall(delay, TimeSpan.Zero, 1, callback); - public static Timer DelayCall(TimeSpan delay, TimeSpan interval, TimerCallback callback) => + public static Timer DelayCall(TimeSpan delay, TimeSpan interval, Action callback) => DelayCall(delay, interval, 0, callback); - public static Timer DelayCall(TimeSpan delay, TimeSpan interval, int count, TimerCallback callback) + public static Timer DelayCall(TimeSpan delay, TimeSpan interval, int count, Action callback) { Timer t = new DelayCallTimer(delay, interval, count, callback); t.Start(); @@ -143,7 +141,7 @@ namespace Server private class DelayCallTimer : Timer { - public DelayCallTimer(TimeSpan delay, TimeSpan interval, int count, TimerCallback callback) : base( + public DelayCallTimer(TimeSpan delay, TimeSpan interval, int count, Action callback) : base( delay, interval, count @@ -152,7 +150,7 @@ namespace Server Callback = callback; } - public TimerCallback Callback { get; } + public Action Callback { get; } protected override void OnTick() { diff --git a/Projects/Server/Timer/Timer.TimerWheel.cs b/Projects/Server/Timer/Timer.TimerWheel.cs index 77277d312..ec4beef46 100644 --- a/Projects/Server/Timer/Timer.TimerWheel.cs +++ b/Projects/Server/Timer/Timer.TimerWheel.cs @@ -33,7 +33,7 @@ namespace Server private static readonly Timer[][] _rings = new Timer[_ringLayers][]; private static readonly int[] _ringIndexes = new int[_ringLayers]; - public static void Initialize(long tickCount) + public static void Init(long tickCount) { _lastTickTurned = tickCount; @@ -107,21 +107,26 @@ namespace Server { var next = timer._nextTimer; var prof = timer.GetProfile(); + var finished = timer.Count != 0 && ++timer.Index >= timer.Count; + + // We remove it before `OnTick()` so time references can be nulled and returned to cache safely from within OnTick. + // This can be done in OnTick by checking if Index < Count - 1 (still more iterations left) + RemoveTimer(timer); + + if (finished) + { + timer.InternalStop(); + } prof?.Start(); timer.OnTick(); prof?.Finish(); - if (timer.Running) + if (timer.Running && !finished) { - RemoveTimer(timer); - - if (timer.Count == 0 || ++timer.Index < timer.Count) - { - timer.Delay = timer.Interval; - timer.Next = Core.Now + timer.Interval; - AddTimer(timer, (long)timer.Delay.TotalMilliseconds); - } + timer.Delay = timer.Interval; + timer.Next = Core.Now + timer.Interval; + AddTimer(timer, (long)timer.Delay.TotalMilliseconds); } timer = next; @@ -166,12 +171,7 @@ namespace Server slot -= _ringSize; } - timer._nextTimer = _rings[i][slot]; - if (timer._nextTimer != null) - { - timer._nextTimer._prevTimer = timer; - } - + timer.Attach(_rings[i][slot]); timer._remaining = remaining; timer._ring = i; timer._slot = (int)slot; @@ -188,22 +188,12 @@ namespace Server private static void RemoveTimer(Timer timer) { - if (timer._prevTimer != null) - { - timer._prevTimer._nextTimer = timer._nextTimer; - } - else + if (timer._prevTimer == null) { _rings[timer._ring][timer._slot] = timer._nextTimer; } - if (timer._nextTimer != null) - { - timer._nextTimer._prevTimer = timer._prevTimer; - } - - timer._nextTimer = null; - timer._prevTimer = null; + timer.Detach(); } public static void DumpInfo(TextWriter tw) diff --git a/Projects/Server/Timer/Timer.cs b/Projects/Server/Timer/Timer.cs index 0ece60e55..e2dcb562f 100644 --- a/Projects/Server/Timer/Timer.cs +++ b/Projects/Server/Timer/Timer.cs @@ -15,11 +15,14 @@ using System; using Server.Diagnostics; +using Server.Logging; namespace Server { public partial class Timer { + private static readonly ILogger logger = LogFactory.GetLogger(typeof(Timer)); + // We need to know what ring/slot we are in so we can be removed if we are "head" of the link list. private int _ring; private int _slot; @@ -32,8 +35,11 @@ namespace Server { } - public Timer(TimeSpan delay, TimeSpan interval, int count = 0) + public Timer(TimeSpan delay, TimeSpan interval, int count = 0) => Init(delay, interval, count); + + public void Init(TimeSpan delay, TimeSpan interval, int count) { + Running = false; Delay = delay; Interval = interval; Count = count; @@ -53,7 +59,8 @@ namespace Server public TimeSpan Delay { get; set; } public TimeSpan Interval { get; set; } public int Index { get; private set; } - public int Count { get; } + public int Count { get; private set; } + public int RemainingCount => Count - Index; public bool Running { get; private set; } public TimerProfile GetProfile() => !Core.Profiling ? null : TimerProfile.Acquire(ToString() ?? "null"); @@ -87,21 +94,50 @@ namespace Server return this; } - Running = false; RemoveTimer(this); + InternalStop(); + return this; + } + + private void InternalStop() + { + Running = false; var prof = GetProfile(); if (prof != null) { prof.Stopped++; } - - return this; } protected virtual void OnTick() { } + + private void Attach(Timer timer) + { + _nextTimer = timer; + if (timer != null) + { + timer._prevTimer = this; + } + } + + private void Detach() + { + if (_prevTimer != null) + { + _prevTimer._nextTimer = _nextTimer; + } + + if (_nextTimer != null) + { + _nextTimer._prevTimer = _prevTimer; + } + + _nextTimer = null; + _prevTimer = null; + } } } diff --git a/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs b/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs index 05a22aee0..cd2814ee8 100644 --- a/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs +++ b/Projects/UOContent/Engines/ConPVP/Games/BombingRun.cs @@ -1615,8 +1615,6 @@ namespace Server.Engines.ConPVP private Timer m_FinishTimer; - private TimerCallback m_UnhideCallback; - public BRGame(BRController controller, DuelContext context) : base(context) => Controller = controller; public BRController Controller { get; } @@ -1641,10 +1639,9 @@ namespace Server.Engines.ConPVP { if (m_Bomb != null && Controller != null) { - m_UnhideCallback ??= UnhideBomb; m_Bomb.Visible = false; m_Bomb.MoveToWorld(Controller.BombHome, Controller.Map); - Timer.DelayCall(TimeSpan.FromSeconds(Utility.RandomMinMax(5, 15)), m_UnhideCallback); + Timer.DelayCall(TimeSpan.FromSeconds(Utility.RandomMinMax(5, 15)), UnhideBomb); } }