A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

238 lines
6.6 KiB

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using Avalonia.Controls.Platform;
using Avalonia.Threading;
namespace Avalonia.LinuxFramebuffer;
internal unsafe class EpollDispatcherImpl : IControlledDispatcherImpl
{
private readonly ManagedDispatcherImpl.IManagedDispatcherInputProvider _inputProvider;
private readonly Thread _mainThread;
[StructLayout(LayoutKind.Explicit)]
private struct epoll_data
{
[FieldOffset(0)] public IntPtr ptr;
[FieldOffset(0)] public int fd;
[FieldOffset(0)] public uint u32;
[FieldOffset(0)] public ulong u64;
}
private const int CLOCK_MONOTONIC = 1;
private const int EPOLLIN = 1;
private const int EPOLL_CTL_ADD = 1;
private const int O_NONBLOCK = 2048;
private const int O_CLOEXEC = 0x80000;
private const int EPOLL_CLOEXEC = 0x80000;
[StructLayout(LayoutKind.Sequential)]
private struct epoll_event
{
public uint events;
public epoll_data data;
}
[DllImport("libc")]
private extern static int epoll_create1(int flags);
[DllImport("libc")]
private extern static int epoll_ctl(int epfd, int op, int fd, ref epoll_event __event);
[DllImport("libc")]
private extern static int epoll_wait(int epfd, epoll_event* events, int maxevents, int timeout);
[DllImport("libc")]
private extern static int pipe2(int* fds, int flags);
[DllImport("libc")]
private extern static IntPtr write(int fd, void* buf, IntPtr count);
[DllImport("libc")]
private extern static IntPtr read(int fd, void* buf, IntPtr count);
#pragma warning disable CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language.
struct timespec
{
public IntPtr tv_sec;
public IntPtr tv_nsec;
}
struct itimerspec
{
public timespec it_interval; // Interval for periodic timer
public timespec it_value; // Initial expiration
};
#pragma warning restore CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language.
[DllImport("libc")]
private extern static int timerfd_create(int clockid, int flags);
[DllImport("libc")]
private extern static int timerfd_settime(int fd, int flags, itimerspec* new_value, itimerspec* old_value);
private enum EventCodes
{
Timer = 1,
Signal = 2
}
private int _sigread, _sigwrite;
private int _timerfd;
private object _lock = new();
private bool _signaled;
private bool _wakeupRequested;
private TimeSpan? _nextTimer;
private int _epoll;
private Stopwatch _clock = Stopwatch.StartNew();
public EpollDispatcherImpl(ManagedDispatcherImpl.IManagedDispatcherInputProvider inputProvider)
{
_inputProvider = inputProvider;
_mainThread = Thread.CurrentThread;
_epoll = epoll_create1(EPOLL_CLOEXEC);
if (_epoll == -1)
throw new Win32Exception("epoll_create1 failed");
var fds = stackalloc int[2];
pipe2(fds, O_NONBLOCK | O_CLOEXEC);
_sigread = fds[0];
_sigwrite = fds[1];
var ev = new epoll_event
{
events = EPOLLIN,
data = { u32 = (int)EventCodes.Signal }
};
if (epoll_ctl(_epoll, EPOLL_CTL_ADD, _sigread, ref ev) == -1)
throw new Win32Exception("Unable to attach signal pipe to epoll");
_timerfd = timerfd_create(CLOCK_MONOTONIC, O_NONBLOCK | O_CLOEXEC);
ev.data.u32 = (int)EventCodes.Timer;
if (epoll_ctl(_epoll, EPOLL_CTL_ADD, _timerfd, ref ev) == -1)
throw new Win32Exception("Unable to attach timer fd to epoll");
}
private bool CheckSignaled()
{
lock (_lock)
{
if (!_signaled)
return false;
_signaled = false;
}
Signaled?.Invoke();
return true;
}
public void RunLoop(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var now = _clock.Elapsed;
if (_nextTimer.HasValue && now > _nextTimer.Value)
{
Timer?.Invoke();
continue;
}
if (CheckSignaled())
continue;
if (_inputProvider.HasInput)
{
_inputProvider.DispatchNextInputEvent();
continue;
}
epoll_event ev;
if (_nextTimer != null)
{
var waitFor = _nextTimer.Value - now;
if (waitFor.Ticks < 0)
continue;
itimerspec timer = new()
{
it_interval = default,
it_value = new()
{
tv_sec = new IntPtr(Math.Min((int)waitFor.TotalSeconds, 100)),
tv_nsec = new IntPtr((waitFor.Ticks % 10000000) * 100)
}
};
timerfd_settime(_timerfd, 0, &timer, null);
}
else
{
itimerspec none = default;
timerfd_settime(_timerfd, 0, &none, null);
}
epoll_wait(_epoll, &ev, 1, (int)-1);
// Drain the signaled pipe
long buf = 0;
while (read(_sigread, &buf, new IntPtr(8)).ToInt64() > 0)
{
}
// Drain timer fd
while (read(_timerfd, &buf, new IntPtr(8)).ToInt64() > 0)
{
}
lock (_lock)
_wakeupRequested = false;
}
}
private void Wakeup()
{
lock (_lock)
{
if (_wakeupRequested)
return;
_wakeupRequested = true;
int buf = 0;
write(_sigwrite, &buf, new IntPtr(1));
}
}
public void Signal()
{
lock (_lock)
{
if (_signaled)
return;
_signaled = true;
Wakeup();
}
}
public bool CurrentThreadIsLoopThread => Thread.CurrentThread == _mainThread;
public event Action? Signaled;
public event Action? Timer;
public void UpdateTimer(long? dueTimeInMs)
{
_nextTimer = dueTimeInMs == null ? null : TimeSpan.FromMilliseconds(dueTimeInMs.Value);
if (_nextTimer != null)
Wakeup();
}
public long Now => _clock.ElapsedMilliseconds;
public bool CanQueryPendingInput => true;
public bool HasPendingInput => _inputProvider.HasInput;
}