aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Common/PreciseSleep/PreciseSleepHelper.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Common/PreciseSleep/PreciseSleepHelper.cs')
-rw-r--r--src/Ryujinx.Common/PreciseSleep/PreciseSleepHelper.cs104
1 files changed, 104 insertions, 0 deletions
diff --git a/src/Ryujinx.Common/PreciseSleep/PreciseSleepHelper.cs b/src/Ryujinx.Common/PreciseSleep/PreciseSleepHelper.cs
new file mode 100644
index 00000000..3c30a7f6
--- /dev/null
+++ b/src/Ryujinx.Common/PreciseSleep/PreciseSleepHelper.cs
@@ -0,0 +1,104 @@
+using Ryujinx.Common.SystemInterop;
+using System;
+using System.Threading;
+
+namespace Ryujinx.Common.PreciseSleep
+{
+ public static class PreciseSleepHelper
+ {
+ /// <summary>
+ /// Create a precise sleep event for the current platform.
+ /// </summary>
+ /// <returns>A precise sleep event</returns>
+ 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();
+ }
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <param name="evt">Event used to wake this thread</param>
+ /// <param name="timePoint">Target timepoint in host ticks</param>
+ 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);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Spinwait until the given timepoint. If wakeSignal is or becomes 1, return early.
+ /// Thread is allowed to yield.
+ /// </summary>
+ /// <param name="timePoint">Target timepoint in host ticks</param>
+ /// <param name="wakeSignal">Returns early if this is set to 1</param>
+ 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();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Spinwait until the given timepoint, with no opportunity to wake early.
+ /// </summary>
+ /// <param name="timePoint">Target timepoint in host ticks</param>
+ public static void SpinWaitUntilTimePoint(long timePoint)
+ {
+ while (PerformanceCounter.ElapsedTicks < timePoint)
+ {
+ Thread.SpinWait(5);
+ }
+ }
+ }
+}