From 2d850813d4a3386a4e5aa85f14ad83c0e5a42a82 Mon Sep 17 00:00:00 2001 From: MrDave1999 Date: Thu, 11 Jun 2026 18:16:11 -0500 Subject: [PATCH 1/3] Fix timer calculations mixing Stopwatch and TimeSpan ticks --- .../Timers/StopwatchTime.cs | 33 +++++++++++++++++++ .../Timers/TimerReference.cs | 10 +++++- .../Timers/TimerSystem.cs | 22 ++++++++++--- 3 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 src/SampSharp.OpenMp.Entities/Timers/StopwatchTime.cs diff --git a/src/SampSharp.OpenMp.Entities/Timers/StopwatchTime.cs b/src/SampSharp.OpenMp.Entities/Timers/StopwatchTime.cs new file mode 100644 index 00000000..983bf91e --- /dev/null +++ b/src/SampSharp.OpenMp.Entities/Timers/StopwatchTime.cs @@ -0,0 +1,33 @@ +using System.Diagnostics; + +namespace SampSharp.Entities; + +internal static class StopwatchTime +{ + /// + /// Converts a TimeSpan to Stopwatch ticks. + /// + /// IMPORTANT: + /// - TimeSpan ticks are fixed (1 tick = 100ns, 10,000,000 per second) + /// - Stopwatch ticks depend on hardware (Stopwatch.Frequency) + /// + /// This method converts a TimeSpan duration to the equivalent number + /// of Stopwatch ticks so both values can be used in the same time system. + /// + public static long ToStopwatchTicks(TimeSpan time) + { + return (long)(time.TotalSeconds * Stopwatch.Frequency); + } + + /// + /// Converts Stopwatch ticks to a TimeSpan. + /// + /// This is the inverse operation of ToStopwatchTicks. + /// It converts a duration measured in Stopwatch ticks back into + /// a TimeSpan using Stopwatch.Frequency. + /// + public static TimeSpan ToTimeSpan(long stopwatchTicks) + { + return TimeSpan.FromSeconds(stopwatchTicks / (double)Stopwatch.Frequency); + } +} diff --git a/src/SampSharp.OpenMp.Entities/Timers/TimerReference.cs b/src/SampSharp.OpenMp.Entities/Timers/TimerReference.cs index 20c58456..f2bfabb0 100644 --- a/src/SampSharp.OpenMp.Entities/Timers/TimerReference.cs +++ b/src/SampSharp.OpenMp.Entities/Timers/TimerReference.cs @@ -18,7 +18,15 @@ internal TimerReference(TimerInfo info, object? target, MethodInfo? method) /// /// Gets the time span until the next tick of this timer. /// - public TimeSpan NextTick => new(Info.NextTick - Stopwatch.GetTimestamp()); + public TimeSpan NextTick + { + get + { + // Difference between future tick and current time (both in Stopwatch ticks). + long delta = Info.NextTick - Stopwatch.GetTimestamp(); + return StopwatchTime.ToTimeSpan(delta); + } + } internal TimerInfo Info { get; set; } diff --git a/src/SampSharp.OpenMp.Entities/Timers/TimerSystem.cs b/src/SampSharp.OpenMp.Entities/Timers/TimerSystem.cs index ab6aa199..942e6206 100644 --- a/src/SampSharp.OpenMp.Entities/Timers/TimerSystem.cs +++ b/src/SampSharp.OpenMp.Entities/Timers/TimerSystem.cs @@ -42,6 +42,8 @@ public void Tick() { var timer = _timers[i]; + // IMPORTANT: + // Both NextTick and timestamp are in Stopwatch ticks, so the comparison is valid across platforms. while (timer.NextTick <= timestamp) { try @@ -97,7 +99,15 @@ public TimerReference Start(Action action, Tim throw new ArgumentOutOfRangeException(nameof(interval), interval, "The interval should be a nonzero positive value."); } - var invoker = new TimerInfo(intervalTicks: interval.Ticks, nextTick: Stopwatch.GetTimestamp() + interval.Ticks, invoke: null!, true); + long intervalTicks = StopwatchTime.ToStopwatchTicks(interval); + var invoker = new TimerInfo( + intervalTicks: intervalTicks, + // IMPORTANT: + // Keeping everything in Stopwatch ticks ensures correct behavior cross-plataform. + nextTick: Stopwatch.GetTimestamp() + intervalTicks, + invoke: null!, + isActive: true + ); var reference = new TimerReference(invoker, action.Target, action.Method); @@ -157,11 +167,15 @@ private void CreateTimersFromAssemblies() LogLowInterval(target, method.Name, attribute.IntervalTimeSpan); } + long intervalTicks = StopwatchTime.ToStopwatchTicks(attribute.IntervalTimeSpan); var timer = new TimerInfo( - intervalTicks: attribute.IntervalTimeSpan.Ticks, - nextTick: tick + attribute.IntervalTimeSpan.Ticks, + intervalTicks: intervalTicks, + // IMPORTANT: + // Keeping everything in Stopwatch ticks ensures correct behavior cross-plataform. + nextTick: tick + intervalTicks, invoke: () => compiled(service, null, _serviceProvider, null), - isActive: true); + isActive: true + ); timer.Reference = new TimerReference(timer, service, method); From 0aef2309f6adced6052d10e2fef90810a929efd6 Mon Sep 17 00:00:00 2001 From: MrDave1999 Date: Thu, 11 Jun 2026 20:17:32 -0500 Subject: [PATCH 2/3] Rename variables for clarity --- .../Timers/TimerReference.cs | 5 ++--- src/SampSharp.OpenMp.Entities/Timers/TimerSystem.cs | 12 ++++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/SampSharp.OpenMp.Entities/Timers/TimerReference.cs b/src/SampSharp.OpenMp.Entities/Timers/TimerReference.cs index f2bfabb0..8d242fee 100644 --- a/src/SampSharp.OpenMp.Entities/Timers/TimerReference.cs +++ b/src/SampSharp.OpenMp.Entities/Timers/TimerReference.cs @@ -22,9 +22,8 @@ public TimeSpan NextTick { get { - // Difference between future tick and current time (both in Stopwatch ticks). - long delta = Info.NextTick - Stopwatch.GetTimestamp(); - return StopwatchTime.ToTimeSpan(delta); + long remainingStopwatchTicks = Info.NextTick - Stopwatch.GetTimestamp(); + return StopwatchTime.ToTimeSpan(remainingStopwatchTicks); } } diff --git a/src/SampSharp.OpenMp.Entities/Timers/TimerSystem.cs b/src/SampSharp.OpenMp.Entities/Timers/TimerSystem.cs index 942e6206..d8336b80 100644 --- a/src/SampSharp.OpenMp.Entities/Timers/TimerSystem.cs +++ b/src/SampSharp.OpenMp.Entities/Timers/TimerSystem.cs @@ -99,12 +99,12 @@ public TimerReference Start(Action action, Tim throw new ArgumentOutOfRangeException(nameof(interval), interval, "The interval should be a nonzero positive value."); } - long intervalTicks = StopwatchTime.ToStopwatchTicks(interval); + long intervalStopwatchTicks = StopwatchTime.ToStopwatchTicks(interval); var invoker = new TimerInfo( - intervalTicks: intervalTicks, + intervalTicks: intervalStopwatchTicks, // IMPORTANT: // Keeping everything in Stopwatch ticks ensures correct behavior cross-plataform. - nextTick: Stopwatch.GetTimestamp() + intervalTicks, + nextTick: Stopwatch.GetTimestamp() + intervalStopwatchTicks, invoke: null!, isActive: true ); @@ -167,12 +167,12 @@ private void CreateTimersFromAssemblies() LogLowInterval(target, method.Name, attribute.IntervalTimeSpan); } - long intervalTicks = StopwatchTime.ToStopwatchTicks(attribute.IntervalTimeSpan); + long intervalStopwatchTicks = StopwatchTime.ToStopwatchTicks(attribute.IntervalTimeSpan); var timer = new TimerInfo( - intervalTicks: intervalTicks, + intervalTicks: intervalStopwatchTicks, // IMPORTANT: // Keeping everything in Stopwatch ticks ensures correct behavior cross-plataform. - nextTick: tick + intervalTicks, + nextTick: tick + intervalStopwatchTicks, invoke: () => compiled(service, null, _serviceProvider, null), isActive: true ); From 447c238dd360df3b94006a4d5c5d759bcf1584d7 Mon Sep 17 00:00:00 2001 From: MrDave1999 Date: Thu, 11 Jun 2026 20:23:33 -0500 Subject: [PATCH 3/3] Use expression body definition --- src/SampSharp.OpenMp.Entities/Timers/StopwatchTime.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/SampSharp.OpenMp.Entities/Timers/StopwatchTime.cs b/src/SampSharp.OpenMp.Entities/Timers/StopwatchTime.cs index 983bf91e..575180c9 100644 --- a/src/SampSharp.OpenMp.Entities/Timers/StopwatchTime.cs +++ b/src/SampSharp.OpenMp.Entities/Timers/StopwatchTime.cs @@ -15,9 +15,7 @@ internal static class StopwatchTime /// of Stopwatch ticks so both values can be used in the same time system. /// public static long ToStopwatchTicks(TimeSpan time) - { - return (long)(time.TotalSeconds * Stopwatch.Frequency); - } + => (long)(time.TotalSeconds * Stopwatch.Frequency); /// /// Converts Stopwatch ticks to a TimeSpan. @@ -27,7 +25,5 @@ public static long ToStopwatchTicks(TimeSpan time) /// a TimeSpan using Stopwatch.Frequency. /// public static TimeSpan ToTimeSpan(long stopwatchTicks) - { - return TimeSpan.FromSeconds(stopwatchTicks / (double)Stopwatch.Frequency); - } + => TimeSpan.FromSeconds(stopwatchTicks / (double)Stopwatch.Frequency); }