PreciseSleepHelper.cs
1 using Ryujinx.Common.SystemInterop; 2 using System; 3 using System.Threading; 4 5 namespace Ryujinx.Common.PreciseSleep 6 { 7 public static class PreciseSleepHelper 8 { 9 /// <summary> 10 /// Create a precise sleep event for the current platform. 11 /// </summary> 12 /// <returns>A precise sleep event</returns> 13 public static IPreciseSleepEvent CreateEvent() 14 { 15 if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS() || OperatingSystem.IsAndroid()) 16 { 17 return new NanosleepEvent(); 18 } 19 else if (OperatingSystem.IsWindows()) 20 { 21 return new WindowsSleepEvent(); 22 } 23 else 24 { 25 return new SleepEvent(); 26 } 27 } 28 29 /// <summary> 30 /// Sleeps up to the closest point to the timepoint that the OS reasonably allows. 31 /// The provided event is used by the timer to wake the current thread, and should not be signalled from any other source. 32 /// </summary> 33 /// <param name="evt">Event used to wake this thread</param> 34 /// <param name="timePoint">Target timepoint in host ticks</param> 35 public static void SleepUntilTimePoint(EventWaitHandle evt, long timePoint) 36 { 37 if (OperatingSystem.IsWindows()) 38 { 39 WindowsGranularTimer.Instance.SleepUntilTimePointWithoutExternalSignal(evt, timePoint); 40 } 41 else 42 { 43 // Events might oversleep by a little, depending on OS. 44 // We don't want to miss the timepoint, so bias the wait to be lower. 45 // Nanosleep can possibly handle it better, too. 46 long accuracyBias = PerformanceCounter.TicksPerMillisecond / 2; 47 long now = PerformanceCounter.ElapsedTicks + accuracyBias; 48 long ms = Math.Min((timePoint - now) / PerformanceCounter.TicksPerMillisecond, int.MaxValue); 49 50 if (ms > 0) 51 { 52 evt.WaitOne((int)ms); 53 } 54 55 if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS() || OperatingSystem.IsAndroid()) 56 { 57 // Do a nanosleep. 58 now = PerformanceCounter.ElapsedTicks; 59 long ns = ((timePoint - now) * 1_000_000) / PerformanceCounter.TicksPerMillisecond; 60 61 Nanosleep.SleepAtMost(ns); 62 } 63 } 64 } 65 66 /// <summary> 67 /// Spinwait until the given timepoint. If wakeSignal is or becomes 1, return early. 68 /// Thread is allowed to yield. 69 /// </summary> 70 /// <param name="timePoint">Target timepoint in host ticks</param> 71 /// <param name="wakeSignal">Returns early if this is set to 1</param> 72 public static void SpinWaitUntilTimePoint(long timePoint, ref long wakeSignal) 73 { 74 SpinWait spinWait = new(); 75 76 while (Interlocked.Read(ref wakeSignal) != 1 && PerformanceCounter.ElapsedTicks < timePoint) 77 { 78 // Our time is close - don't let SpinWait go off and potentially Thread.Sleep(). 79 if (spinWait.NextSpinWillYield) 80 { 81 Thread.Yield(); 82 83 spinWait.Reset(); 84 } 85 else 86 { 87 spinWait.SpinOnce(); 88 } 89 } 90 } 91 92 /// <summary> 93 /// Spinwait until the given timepoint, with no opportunity to wake early. 94 /// </summary> 95 /// <param name="timePoint">Target timepoint in host ticks</param> 96 public static void SpinWaitUntilTimePoint(long timePoint) 97 { 98 while (PerformanceCounter.ElapsedTicks < timePoint) 99 { 100 Thread.SpinWait(5); 101 } 102 } 103 } 104 }