diff --git a/src/SampSharp.OpenMp.Entities/Timers/StopwatchTime.cs b/src/SampSharp.OpenMp.Entities/Timers/StopwatchTime.cs new file mode 100644 index 00000000..575180c9 --- /dev/null +++ b/src/SampSharp.OpenMp.Entities/Timers/StopwatchTime.cs @@ -0,0 +1,29 @@ +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) + => (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) + => 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..8d242fee 100644 --- a/src/SampSharp.OpenMp.Entities/Timers/TimerReference.cs +++ b/src/SampSharp.OpenMp.Entities/Timers/TimerReference.cs @@ -18,7 +18,14 @@ 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 + { + long remainingStopwatchTicks = Info.NextTick - Stopwatch.GetTimestamp(); + return StopwatchTime.ToTimeSpan(remainingStopwatchTicks); + } + } 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..d8336b80 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 intervalStopwatchTicks = StopwatchTime.ToStopwatchTicks(interval); + var invoker = new TimerInfo( + intervalTicks: intervalStopwatchTicks, + // IMPORTANT: + // Keeping everything in Stopwatch ticks ensures correct behavior cross-plataform. + nextTick: Stopwatch.GetTimestamp() + intervalStopwatchTicks, + 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 intervalStopwatchTicks = StopwatchTime.ToStopwatchTicks(attribute.IntervalTimeSpan); var timer = new TimerInfo( - intervalTicks: attribute.IntervalTimeSpan.Ticks, - nextTick: tick + attribute.IntervalTimeSpan.Ticks, + intervalTicks: intervalStopwatchTicks, + // IMPORTANT: + // Keeping everything in Stopwatch ticks ensures correct behavior cross-plataform. + nextTick: tick + intervalStopwatchTicks, invoke: () => compiled(service, null, _serviceProvider, null), - isActive: true); + isActive: true + ); timer.Reference = new TimerReference(timer, service, method);