using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Ryujinx.Common.PreciseSleep
{
///
/// Access to Linux/MacOS nanosleep, with platform specific bias to improve precision.
///
[SupportedOSPlatform("macos")]
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("android")]
[SupportedOSPlatform("ios")]
internal static partial class Nanosleep
{
private const long LinuxBaseNanosleepBias = 50000; // 0.05ms
// Penalty for max allowed sleep duration
private const long LinuxNanosleepAccuracyPenaltyThreshold = 200000; // 0.2ms
private const long LinuxNanosleepAccuracyPenalty = 30000; // 0.03ms
// Penalty for base sleep duration
private const long LinuxNanosleepBasePenaltyThreshold = 500000; // 0.5ms
private const long LinuxNanosleepBasePenalty = 30000; // 0.03ms
private const long LinuxNanosleepPenaltyPerMillisecond = 18000; // 0.018ms
private const long LinuxNanosleepPenaltyCap = 18000; // 0.018ms
private const long LinuxStrictBiasOffset = 150_000; // 0.15ms
// Nanosleep duration is biased depending on the requested timeout on MacOS.
// These match the results when measuring on an M1 processor at AboveNormal priority.
private const long MacosBaseNanosleepBias = 5000; // 0.005ms
private const long MacosBiasPerMillisecond = 140000; // 0.14ms
private const long MacosBiasMaxNanoseconds = 20_000_000; // 20ms
private const long MacosStrictBiasOffset = 150_000; // 0.15ms
public static long Bias { get; }
///
/// Get bias for a given nanosecond timeout.
/// Some platforms calculate their bias differently, this method can be used to counteract it.
///
/// Nanosecond timeout
/// Bias in nanoseconds
public static long GetBias(long timeoutNs)
{
if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
{
long biasNs = Math.Min(timeoutNs, MacosBiasMaxNanoseconds);
return MacosBaseNanosleepBias + biasNs * MacosBiasPerMillisecond / 1_000_000;
}
else
{
long bias = LinuxBaseNanosleepBias;
if (timeoutNs > LinuxNanosleepBasePenaltyThreshold)
{
long penalty = (timeoutNs - LinuxNanosleepBasePenaltyThreshold) * LinuxNanosleepPenaltyPerMillisecond / 1_000_000;
bias += LinuxNanosleepBasePenalty + Math.Min(LinuxNanosleepPenaltyCap, penalty);
}
return bias;
}
}
///
/// Get a stricter bias for a given nanosecond timeout,
/// which can improve the chances the sleep completes before the timeout.
/// Some platforms calculate their bias differently, this method can be used to counteract it.
///
/// Nanosecond timeout
/// Strict bias in nanoseconds
public static long GetStrictBias(long timeoutNs)
{
if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
{
return GetBias(timeoutNs) + MacosStrictBiasOffset;
}
else
{
long bias = GetBias(timeoutNs) + LinuxStrictBiasOffset;
if (timeoutNs > LinuxNanosleepAccuracyPenaltyThreshold)
{
bias += LinuxNanosleepAccuracyPenalty;
}
return bias;
}
}
static Nanosleep()
{
Bias = GetBias(0);
}
[StructLayout(LayoutKind.Sequential)]
private struct Timespec
{
public long tv_sec; // Seconds
public long tv_nsec; // Nanoseconds
}
[LibraryImport("libc", SetLastError = true)]
private static partial int nanosleep(ref Timespec req, ref Timespec rem);
///
/// Convert a timeout in nanoseconds to a timespec for nanosleep.
///
/// Timeout in nanoseconds
/// Timespec for nanosleep
private static Timespec GetTimespecFromNanoseconds(ulong nanoseconds)
{
return new Timespec
{
tv_sec = (long)(nanoseconds / 1_000_000_000),
tv_nsec = (long)(nanoseconds % 1_000_000_000)
};
}
///
/// Sleep for approximately a given time period in nanoseconds.
///
/// Time to sleep for in nanoseconds
public static void Sleep(long nanoseconds)
{
nanoseconds -= GetBias(nanoseconds);
if (nanoseconds >= 0)
{
Timespec req = GetTimespecFromNanoseconds((ulong)nanoseconds);
Timespec rem = new();
nanosleep(ref req, ref rem);
}
}
///
/// Sleep for at most a given time period in nanoseconds.
/// Uses a stricter bias to wake before the requested duration.
///
///
/// Due to OS scheduling behaviour, this timeframe may still be missed.
///
/// Maximum allowed time for sleep
public static void SleepAtMost(long nanoseconds)
{
// Stricter bias to ensure we wake before the timepoint.
nanoseconds -= GetStrictBias(nanoseconds);
if (nanoseconds >= 0)
{
Timespec req = GetTimespecFromNanoseconds((ulong)nanoseconds);
Timespec rem = new();
nanosleep(ref req, ref rem);
}
}
}
}