/ src / Ryujinx.Common / PreciseSleep / PreciseSleepHelper.cs
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  }