using Ryujinx.Common.SystemInterop; using System; using System.Threading; namespace Ryujinx.Common.PreciseSleep { public static class PreciseSleepHelper { /// /// Create a precise sleep event for the current platform. /// /// A precise sleep event public static IPreciseSleepEvent CreateEvent() { if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS() || OperatingSystem.IsAndroid()) { return new NanosleepEvent(); } else if (OperatingSystem.IsWindows()) { return new WindowsSleepEvent(); } else { return new SleepEvent(); } } /// /// Sleeps up to the closest point to the timepoint that the OS reasonably allows. /// The provided event is used by the timer to wake the current thread, and should not be signalled from any other source. /// /// Event used to wake this thread /// Target timepoint in host ticks public static void SleepUntilTimePoint(EventWaitHandle evt, long timePoint) { if (OperatingSystem.IsWindows()) { WindowsGranularTimer.Instance.SleepUntilTimePointWithoutExternalSignal(evt, timePoint); } else { // Events might oversleep by a little, depending on OS. // We don't want to miss the timepoint, so bias the wait to be lower. // Nanosleep can possibly handle it better, too. long accuracyBias = PerformanceCounter.TicksPerMillisecond / 2; long now = PerformanceCounter.ElapsedTicks + accuracyBias; long ms = Math.Min((timePoint - now) / PerformanceCounter.TicksPerMillisecond, int.MaxValue); if (ms > 0) { evt.WaitOne((int)ms); } if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS() || OperatingSystem.IsAndroid()) { // Do a nanosleep. now = PerformanceCounter.ElapsedTicks; long ns = ((timePoint - now) * 1_000_000) / PerformanceCounter.TicksPerMillisecond; Nanosleep.SleepAtMost(ns); } } } /// /// Spinwait until the given timepoint. If wakeSignal is or becomes 1, return early. /// Thread is allowed to yield. /// /// Target timepoint in host ticks /// Returns early if this is set to 1 public static void SpinWaitUntilTimePoint(long timePoint, ref long wakeSignal) { SpinWait spinWait = new(); while (Interlocked.Read(ref wakeSignal) != 1 && PerformanceCounter.ElapsedTicks < timePoint) { // Our time is close - don't let SpinWait go off and potentially Thread.Sleep(). if (spinWait.NextSpinWillYield) { Thread.Yield(); spinWait.Reset(); } else { spinWait.SpinOnce(); } } } /// /// Spinwait until the given timepoint, with no opportunity to wake early. /// /// Target timepoint in host ticks public static void SpinWaitUntilTimePoint(long timePoint) { while (PerformanceCounter.ElapsedTicks < timePoint) { Thread.SpinWait(5); } } } }