using System; using System.Runtime.InteropServices; using System.Threading; using TerraFX.Interop.Windows; namespace Robust.Shared.Timing; /// /// Helper for more precise sleeping functionality than . /// internal abstract class PrecisionSleep : IDisposable { /// /// Sleep for the specified amount of time. /// public abstract void Sleep(TimeSpan time); /// /// Create the most optimal optimization for the current platform. /// public static PrecisionSleep Create() { // Check Windows 10 1803 if (OperatingSystem.IsWindows() && Environment.OSVersion.Version.Build >= 17134) return new PrecisionSleepWindowsHighResolution(); if (OperatingSystem.IsLinux()) return new PrecisionSleepLinuxNanosleep(); return new PrecisionSleepUniversal(); } public virtual void Dispose() { } } /// /// Universal cross-platform implementation of . Not very precise! /// internal sealed class PrecisionSleepUniversal : PrecisionSleep { public override void Sleep(TimeSpan time) { Thread.Sleep(time); } } /// /// High-precision implementation of that is available since Windows 10 1803. /// internal sealed unsafe class PrecisionSleepWindowsHighResolution : PrecisionSleep { private HANDLE _timerHandle; public PrecisionSleepWindowsHighResolution() { // CREATE_WAITABLE_TIMER_HIGH_RESOLUTION is only supported since Windows 10 1803 _timerHandle = Windows.CreateWaitableTimerExW( null, null, CREATE.CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, Windows.TIMER_ALL_ACCESS); if (_timerHandle == HANDLE.NULL) Marshal.ThrowExceptionForHR(Windows.HRESULT_FROM_WIN32(Marshal.GetLastSystemError())); } public override void Sleep(TimeSpan time) { LARGE_INTEGER due; // negative = relative time. due.QuadPart = -time.Ticks; var success = Windows.SetWaitableTimer( _timerHandle, &due, 0, null, null, BOOL.FALSE ); if (!success) Marshal.ThrowExceptionForHR(Windows.HRESULT_FROM_WIN32(Marshal.GetLastSystemError())); var waitResult = Windows.WaitForSingleObject(_timerHandle, Windows.INFINITE); if (waitResult == WAIT.WAIT_FAILED) Marshal.ThrowExceptionForHR(Windows.HRESULT_FROM_WIN32(Marshal.GetLastSystemError())); GC.KeepAlive(this); } private void DisposeCore() { Windows.CloseHandle(_timerHandle); _timerHandle = default; } public override void Dispose() { DisposeCore(); GC.SuppressFinalize(this); } ~PrecisionSleepWindowsHighResolution() { DisposeCore(); } } /// /// High-precision implementation of that is available on Linux. /// internal sealed unsafe class PrecisionSleepLinuxNanosleep : PrecisionSleep { public override void Sleep(TimeSpan time) { timespec timeSpec; timeSpec.tv_sec = Math.DivRem(time.Ticks, TimeSpan.TicksPerSecond, out var ticksRem); timeSpec.tv_nsec = ticksRem * TimeSpan.NanosecondsPerTick; while (true) { timespec rem; var result = nanosleep(&timeSpec, &rem); if (result == 0) return; var error = Marshal.GetLastSystemError(); if (error != 4) // EINTR throw new Exception($"nanosleep failed: {error}"); timeSpec = rem; } } #pragma warning disable CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language. #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value // ReSharper disable IdentifierTypo // ReSharper disable InconsistentNaming [DllImport("libc.so.6", SetLastError=true)] private static extern int nanosleep(timespec* req, timespec* rem); private struct timespec { public long tv_sec; public long tv_nsec; } private struct timeval { public long tv_sec; public long tv_usec; } // ReSharper restore InconsistentNaming // ReSharper restore IdentifierTypo #pragma warning restore CS0649 // Field is never assigned to, and will always have its default value #pragma warning restore CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language. }