mirror of
https://github.com/SK-la/osu-framework.git
synced 2026-03-15 03:20:30 +00:00
Merge pull request #6392 from hwsmm/nanosleep
Use nanosleep for non-Windows platforms
This commit is contained in:
39
osu.Framework.Benchmarks/BenchmarkSleep.cs
Normal file
39
osu.Framework.Benchmarks/BenchmarkSleep.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Platform.Linux.Native;
|
||||
using osu.Framework.Platform.Windows.Native;
|
||||
|
||||
namespace osu.Framework.Benchmarks
|
||||
{
|
||||
public class BenchmarkSleep : BenchmarkTest
|
||||
{
|
||||
private INativeSleep nativeSleep = null!;
|
||||
|
||||
private readonly TimeSpan timeSpan = TimeSpan.FromMilliseconds(1.5);
|
||||
|
||||
public override void SetUp()
|
||||
{
|
||||
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
|
||||
nativeSleep = new WindowsNativeSleep();
|
||||
else if (RuntimeInfo.IsUnix && UnixNativeSleep.Available)
|
||||
nativeSleep = new UnixNativeSleep();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void TestThreadSleep()
|
||||
{
|
||||
Thread.Sleep(timeSpan);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void TestNativeSleep()
|
||||
{
|
||||
nativeSleep.Sleep(timeSpan);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
osu.Framework/Platform/INativeSleep.cs
Normal file
12
osu.Framework/Platform/INativeSleep.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
|
||||
namespace osu.Framework.Platform
|
||||
{
|
||||
public interface INativeSleep : IDisposable
|
||||
{
|
||||
bool Sleep(TimeSpan duration);
|
||||
}
|
||||
}
|
||||
79
osu.Framework/Platform/Linux/Native/UnixNativeSleep.cs
Normal file
79
osu.Framework/Platform/Linux/Native/UnixNativeSleep.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace osu.Framework.Platform.Linux.Native
|
||||
{
|
||||
internal class UnixNativeSleep : INativeSleep
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct TimeSpec
|
||||
{
|
||||
public nint Seconds;
|
||||
public nint NanoSeconds;
|
||||
}
|
||||
|
||||
[DllImport("libc", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
|
||||
private static extern int nanosleep(in TimeSpec duration, out TimeSpec rem);
|
||||
|
||||
private const int interrupt_error = 4;
|
||||
|
||||
public static bool Available { get; private set; }
|
||||
|
||||
// Just a safe check before actually using it.
|
||||
// .NET tries possible library names if 'libc' is given, but it may fail to find it.
|
||||
private static bool testNanoSleep()
|
||||
{
|
||||
TimeSpec test = new TimeSpec
|
||||
{
|
||||
Seconds = 0,
|
||||
NanoSeconds = 1,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
nanosleep(in test, out _);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static UnixNativeSleep()
|
||||
{
|
||||
Available = testNanoSleep();
|
||||
}
|
||||
|
||||
public bool Sleep(TimeSpan duration)
|
||||
{
|
||||
const int ns_per_second = 1000 * 1000 * 1000;
|
||||
|
||||
long ns = (long)duration.TotalNanoseconds;
|
||||
|
||||
TimeSpec timeSpec = new TimeSpec
|
||||
{
|
||||
Seconds = (nint)(ns / ns_per_second),
|
||||
NanoSeconds = (nint)(ns % ns_per_second),
|
||||
};
|
||||
|
||||
int ret;
|
||||
|
||||
while ((ret = nanosleep(in timeSpec, out var remaining)) == -1 && Marshal.GetLastPInvokeError() == interrupt_error)
|
||||
{
|
||||
// The pause can be interrupted by a signal that was delivered to the thread.
|
||||
// Sleep again with remaining time if it happened.
|
||||
timeSpec = remaining;
|
||||
}
|
||||
|
||||
return ret == 0; // Any errors other than interrupt_error should return false.
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
60
osu.Framework/Platform/Windows/Native/WindowsNativeSleep.cs
Normal file
60
osu.Framework/Platform/Windows/Native/WindowsNativeSleep.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
|
||||
namespace osu.Framework.Platform.Windows.Native
|
||||
{
|
||||
internal class WindowsNativeSleep : INativeSleep
|
||||
{
|
||||
private IntPtr waitableTimer;
|
||||
|
||||
public WindowsNativeSleep()
|
||||
{
|
||||
createWaitableTimer();
|
||||
}
|
||||
|
||||
private void createWaitableTimer()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Attempt to use CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, only available since Windows 10, version 1803.
|
||||
waitableTimer = Execution.CreateWaitableTimerEx(IntPtr.Zero, null,
|
||||
Execution.CreateWaitableTimerFlags.CREATE_WAITABLE_TIMER_MANUAL_RESET | Execution.CreateWaitableTimerFlags.CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, Execution.TIMER_ALL_ACCESS);
|
||||
|
||||
if (waitableTimer == IntPtr.Zero)
|
||||
{
|
||||
// Fall back to a more supported version. This is still far more accurate than Thread.Sleep.
|
||||
waitableTimer = Execution.CreateWaitableTimerEx(IntPtr.Zero, null, Execution.CreateWaitableTimerFlags.CREATE_WAITABLE_TIMER_MANUAL_RESET, Execution.TIMER_ALL_ACCESS);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Any kind of unexpected exception should fall back to Thread.Sleep.
|
||||
}
|
||||
}
|
||||
|
||||
public bool Sleep(TimeSpan duration)
|
||||
{
|
||||
if (waitableTimer == IntPtr.Zero) return false;
|
||||
|
||||
// Not sure if we want to fall back to Thread.Sleep on failure here, needs further investigation.
|
||||
if (Execution.SetWaitableTimerEx(waitableTimer, Execution.CreateFileTime(duration), 0, null, default, IntPtr.Zero, 0))
|
||||
{
|
||||
Execution.WaitForSingleObject(waitableTimer, Execution.INFINITE);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (waitableTimer != IntPtr.Zero)
|
||||
{
|
||||
Execution.CloseHandle(waitableTimer);
|
||||
waitableTimer = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,9 @@
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Platform.Linux.Native;
|
||||
using osu.Framework.Platform.Windows.Native;
|
||||
|
||||
namespace osu.Framework.Timing
|
||||
@@ -32,11 +33,14 @@ namespace osu.Framework.Timing
|
||||
/// </summary>
|
||||
public double TimeSlept { get; private set; }
|
||||
|
||||
private IntPtr waitableTimer;
|
||||
private readonly INativeSleep? nativeSleep;
|
||||
|
||||
internal ThrottledFrameClock()
|
||||
{
|
||||
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) createWaitableTimer();
|
||||
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
|
||||
nativeSleep = new WindowsNativeSleep();
|
||||
else if (RuntimeInfo.IsUnix && UnixNativeSleep.Available)
|
||||
nativeSleep = new UnixNativeSleep();
|
||||
}
|
||||
|
||||
public override void ProcessFrame()
|
||||
@@ -91,7 +95,7 @@ namespace osu.Framework.Timing
|
||||
|
||||
TimeSpan timeSpan = TimeSpan.FromMilliseconds(milliseconds);
|
||||
|
||||
if (!waitWaitableTimer(timeSpan))
|
||||
if (nativeSleep?.Sleep(timeSpan) != true)
|
||||
Thread.Sleep(timeSpan);
|
||||
|
||||
return (CurrentTime = SourceTime) - before;
|
||||
@@ -99,43 +103,7 @@ namespace osu.Framework.Timing
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (waitableTimer != IntPtr.Zero)
|
||||
Execution.CloseHandle(waitableTimer);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool waitWaitableTimer(TimeSpan timeSpan)
|
||||
{
|
||||
if (waitableTimer == IntPtr.Zero) return false;
|
||||
|
||||
// Not sure if we want to fall back to Thread.Sleep on failure here, needs further investigation.
|
||||
if (Execution.SetWaitableTimerEx(waitableTimer, Execution.CreateFileTime(timeSpan), 0, null, default, IntPtr.Zero, 0))
|
||||
{
|
||||
Execution.WaitForSingleObject(waitableTimer, Execution.INFINITE);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void createWaitableTimer()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Attempt to use CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, only available since Windows 10, version 1803.
|
||||
waitableTimer = Execution.CreateWaitableTimerEx(IntPtr.Zero, null,
|
||||
Execution.CreateWaitableTimerFlags.CREATE_WAITABLE_TIMER_MANUAL_RESET | Execution.CreateWaitableTimerFlags.CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, Execution.TIMER_ALL_ACCESS);
|
||||
|
||||
if (waitableTimer == IntPtr.Zero)
|
||||
{
|
||||
// Fall back to a more supported version. This is still far more accurate than Thread.Sleep.
|
||||
waitableTimer = Execution.CreateWaitableTimerEx(IntPtr.Zero, null, Execution.CreateWaitableTimerFlags.CREATE_WAITABLE_TIMER_MANUAL_RESET, Execution.TIMER_ALL_ACCESS);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Any kind of unexpected exception should fall back to Thread.Sleep.
|
||||
}
|
||||
nativeSleep?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user