Initial commit

This commit is contained in:
2026-07-03 16:37:12 +07:00
commit 63b8c1ea8b
1931 changed files with 640587 additions and 0 deletions

View File

@@ -0,0 +1,239 @@
using System;
using System.Runtime.InteropServices;
namespace RobotNet10.Realtime;
/// <summary>
/// Provides high-resolution real-time timers using Linux timerfd.
/// More accurate than System.Threading.Timer for real-time applications.
/// </summary>
public class RealtimeTimer : IDisposable
{
private int _fd = -1;
private readonly RealtimeClock _clock;
private bool _disposed = false;
/// <summary>
/// Initializes a new instance of RealtimeTimer.
/// </summary>
/// <param name="clock">Clock type to use (default: CLOCK_MONOTONIC)</param>
/// <param name="nonBlocking">Whether the timer should be non-blocking</param>
public RealtimeTimer(RealtimeClockType clock = RealtimeClockType.Monotonic, bool nonBlocking = false)
{
_clock = new RealtimeClock(clock);
int flags = LinuxNative.TFD_CLOEXEC;
if (nonBlocking)
{
flags |= LinuxNative.TFD_NONBLOCK;
}
_fd = LinuxNative.timerfd_create((int)clock, flags);
if (_fd < 0)
{
LinuxNative.ThrowLastError("timerfd_create");
}
}
/// <summary>
/// Gets the file descriptor for the timer.
/// </summary>
public int FileDescriptor => _fd;
/// <summary>
/// Sets the timer to fire periodically.
/// </summary>
/// <param name="interval">Interval between timer expirations</param>
public void SetPeriodic(TimeSpan interval)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(RealtimeTimer));
}
var spec = new LinuxNative.itimerspec
{
it_interval = TimeSpanToTimespec(interval),
it_value = TimeSpanToTimespec(interval) // First expiration
};
var oldSpec = new LinuxNative.itimerspec();
int result = LinuxNative.timerfd_settime(_fd, 0, ref spec, ref oldSpec);
if (result != 0)
{
LinuxNative.ThrowLastError("timerfd_settime");
}
}
/// <summary>
/// Sets the timer to fire once after a delay.
/// </summary>
/// <param name="delay">Delay before timer expiration</param>
public void SetOneShot(TimeSpan delay)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(RealtimeTimer));
}
var spec = new LinuxNative.itimerspec
{
it_interval = new LinuxNative.timespec { tv_sec = 0, tv_nsec = 0 }, // No repeat
it_value = TimeSpanToTimespec(delay)
};
var oldSpec = new LinuxNative.itimerspec();
int result = LinuxNative.timerfd_settime(_fd, 0, ref spec, ref oldSpec);
if (result != 0)
{
LinuxNative.ThrowLastError("timerfd_settime");
}
}
/// <summary>
/// Sets the timer to fire at an absolute time.
/// </summary>
/// <param name="absoluteTime">Absolute time when timer should expire</param>
public void SetAbsolute(DateTime absoluteTime)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(RealtimeTimer));
}
var clockTime = _clock.GetTime();
var targetTime = absoluteTime.ToUniversalTime();
var delay = targetTime - DateTime.UtcNow;
var spec = new LinuxNative.itimerspec
{
it_interval = new LinuxNative.timespec { tv_sec = 0, tv_nsec = 0 },
it_value = TimeSpanToTimespec(delay)
};
var oldSpec = new LinuxNative.itimerspec();
int result = LinuxNative.timerfd_settime(_fd, LinuxNative.TFD_TIMER_ABSTIME, ref spec, ref oldSpec);
if (result != 0)
{
LinuxNative.ThrowLastError("timerfd_settime");
}
}
/// <summary>
/// Disarms the timer.
/// </summary>
public void Disarm()
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(RealtimeTimer));
}
var spec = new LinuxNative.itimerspec
{
it_interval = new LinuxNative.timespec { tv_sec = 0, tv_nsec = 0 },
it_value = new LinuxNative.timespec { tv_sec = 0, tv_nsec = 0 }
};
var oldSpec = new LinuxNative.itimerspec();
int result = LinuxNative.timerfd_settime(_fd, 0, ref spec, ref oldSpec);
if (result != 0)
{
LinuxNative.ThrowLastError("timerfd_settime");
}
}
/// <summary>
/// Gets the current timer value.
/// </summary>
/// <returns>Current timer interval and value</returns>
public (TimeSpan interval, TimeSpan value) GetTime()
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(RealtimeTimer));
}
var spec = new LinuxNative.itimerspec();
int result = LinuxNative.timerfd_gettime(_fd, ref spec);
if (result != 0)
{
LinuxNative.ThrowLastError("timerfd_gettime");
}
return (TimespecToTimeSpan(spec.it_interval), TimespecToTimeSpan(spec.it_value));
}
/// <summary>
/// Reads the timer expiration count (number of times timer has expired since last read).
/// In non-blocking mode, returns 0 if timer hasn't expired yet (EAGAIN).
/// </summary>
/// <returns>Number of expirations, or 0 if timer hasn't expired (non-blocking mode)</returns>
public ulong ReadExpirations()
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(RealtimeTimer));
}
IntPtr bufferPtr = Marshal.AllocHGlobal(8);
try
{
long bytesRead = LinuxNative.read(_fd, bufferPtr, new IntPtr(8));
if (bytesRead < 0)
{
int errno = LinuxNative.GetLastError();
if (errno == 11) // EAGAIN - timer hasn't expired yet (non-blocking mode)
{
return 0;
}
LinuxNative.ThrowLastError("read");
}
if (bytesRead != 8)
{
throw new InvalidOperationException($"Expected to read 8 bytes, got {bytesRead}");
}
return (ulong)Marshal.ReadInt64(bufferPtr);
}
finally
{
Marshal.FreeHGlobal(bufferPtr);
}
}
private static LinuxNative.timespec TimeSpanToTimespec(TimeSpan ts)
{
long totalSeconds = (long)ts.TotalSeconds;
long nanoseconds = (long)((ts.TotalMilliseconds % 1000) * 1000000);
return new LinuxNative.timespec
{
tv_sec = totalSeconds,
tv_nsec = nanoseconds
};
}
private static TimeSpan TimespecToTimeSpan(LinuxNative.timespec ts)
{
return TimeSpan.FromSeconds(ts.tv_sec) + TimeSpan.FromTicks(ts.tv_nsec / 100);
}
public void Dispose()
{
if (!_disposed && _fd >= 0)
{
LinuxNative.close(_fd);
_fd = -1;
_disposed = true;
}
}
}
// Additional P/Invoke declarations needed for timerfd
internal static partial class LinuxNative
{
[LibraryImport("libc", SetLastError = true)]
public static partial long read(int fd, IntPtr buf, IntPtr count);
[LibraryImport("libc", SetLastError = true)]
public static partial int close(int fd);
}