From be6e64f85355c6c96b7c09174986fafb6a411c6a Mon Sep 17 00:00:00 2001 From: magiblot Date: Sun, 4 Feb 2024 23:34:48 +0100 Subject: [PATCH] Unix: use 'select' instead of 'poll' for waiting for input So that we can support older macOS versions. See magiblot/turbo#65. --- include/tvision/internal/events.h | 46 +++++++++------------- include/tvision/internal/stdioctl.h | 9 ++--- source/platform/events.cpp | 45 ++++++++++++++++------ source/platform/stdioctl.cpp | 59 +++++++++++------------------ 4 files changed, 78 insertions(+), 81 deletions(-) diff --git a/include/tvision/internal/events.h b/include/tvision/internal/events.h index 78aa213d..2f851f79 100644 --- a/include/tvision/internal/events.h +++ b/include/tvision/internal/events.h @@ -7,29 +7,27 @@ #include #include -#ifdef _TV_UNIX -#include -#else +#ifdef _WIN32 #include #endif namespace tvision { -#ifdef _TV_UNIX -using SysHandle = int; -#else +#ifdef _WIN32 using SysHandle = HANDLE; +#else +using SysHandle = int; #endif struct SysManualEvent { -#ifdef _TV_UNIX - using Handle = int[2]; - Handle fds; -#else +#ifdef _WIN32 using Handle = HANDLE; Handle hEvent; +#else + using Handle = int[2]; + Handle fds; #endif static bool createHandle(Handle &handle) noexcept; @@ -42,20 +40,20 @@ struct SysManualEvent }; inline SysManualEvent::SysManualEvent(Handle aHandle) noexcept : -#ifdef _TV_UNIX - fds {aHandle[0], aHandle[1]} -#else +#ifdef _WIN32 hEvent {aHandle} +#else + fds {aHandle[0], aHandle[1]} #endif { } inline SysHandle SysManualEvent::getWaitableHandle(Handle handle) noexcept { -#ifdef _TV_UNIX - return handle[0]; -#else +#ifdef _WIN32 return handle; +#else + return handle[0]; #endif } @@ -109,14 +107,6 @@ inline WakeUpEventSource::WakeUpEventSource( SysManualEvent::Handle aHandle, { } -#ifdef _TV_UNIX -using PollItem = struct pollfd; -static inline PollItem pollItem(SysHandle fd) noexcept { return {fd, POLLIN}; } -#else -using PollItem = HANDLE; -static inline PollItem pollItem(SysHandle h) noexcept { return h; } -#endif - enum PollState : uint8_t { psNothing, @@ -126,24 +116,24 @@ enum PollState : uint8_t struct PollData { - std::vector items; + std::vector handles; std::vector states; void push_back(SysHandle h) { - items.push_back(pollItem(h)); + handles.push_back(h); states.push_back(psNothing); } void erase(size_t i) { - items.erase(items.begin() + i); + handles.erase(handles.begin() + i); states.erase(states.begin() + i); } size_t size() { - return items.size(); + return handles.size(); } }; diff --git a/include/tvision/internal/stdioctl.h b/include/tvision/internal/stdioctl.h index 6e324675..870a6fb1 100644 --- a/include/tvision/internal/stdioctl.h +++ b/include/tvision/internal/stdioctl.h @@ -22,10 +22,9 @@ class StdioCtl final } cn[3]; bool ownsConsole {false}; #else - int ttyfd {-1}; int fds[2] {-1, -1}; - FILE *infile {nullptr}; - FILE *outfile {nullptr}; + FILE *files[2] {nullptr, nullptr}; + bool ownsFiles {false}; #endif // _WIN32 static StdioCtl *instance; @@ -56,8 +55,8 @@ class StdioCtl final #else int in() const noexcept { return fds[0]; } int out() const noexcept { return fds[1]; } - FILE *fin() const noexcept { return infile; } - FILE *fout() const noexcept { return outfile; } + FILE *fin() const noexcept { return files[0]; } + FILE *fout() const noexcept { return files[1]; } #ifdef __linux__ bool isLinuxConsole() const noexcept; #endif diff --git a/source/platform/events.cpp b/source/platform/events.cpp index 346316ad..cca9b982 100644 --- a/source/platform/events.cpp +++ b/source/platform/events.cpp @@ -10,6 +10,7 @@ using std::chrono::steady_clock; #include #include #include +#include #endif namespace tvision @@ -133,33 +134,53 @@ static bool fdEmpty(int fd) noexcept static void pollHandles(PollData &pd, int ms) noexcept { - auto &fds = pd.items; + auto &fds = pd.handles; auto &states = pd.states; - if (poll(fds.data(), fds.size(), ms) > 0) - for (size_t i = 0; i < fds.size(); ++i) + // We use 'select' instead of 'poll' because it is more portable, especially + // on macOS. However, 'select' only supports file descriptors smaller than + // 'FD_SETSIZE'. But this should not be an issue, since we just open a few + // of them at program startup and when suspending/resuming the application. + fd_set readFds; + FD_ZERO(&readFds); + int maxFd = -1; + for (size_t i = 0; i < fds.size(); ++i) + if (fds[i] < FD_SETSIZE) { - if ( (fds[i].revents & POLLHUP) || - ((fds[i].revents & POLLIN) && fdEmpty(fds[i].fd)) ) - // Broken pipe or EOF will cause poll to return immediately, - // so remove it from the list. - states[i] = psDisconnect; - else if (fds[i].revents & POLLIN) - states[i] = psReady; + FD_SET(fds[i], &readFds); + if (fds[i] > maxFd) + maxFd = fds[i]; } + + if (maxFd >= 0) + { + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = ms*1000; + if ( select( maxFd + 1, &readFds, nullptr, nullptr, + (ms < 0 ? nullptr : &timeout) ) >= 0 ) + for (size_t i = 0; i < fds.size(); ++i) + if (fds[i] < FD_SETSIZE && FD_ISSET(fds[i], &readFds)) + { + if (fdEmpty(fds[i])) + states[i] = psDisconnect; + else + states[i] = psReady; + } + } } #else static void pollHandles(PollData &pd, int ms) noexcept { - auto &handles = pd.items; + auto &handles = pd.handles; auto &states = pd.states; if (handles.size() == 0) Sleep(ms); else { DWORD res = WaitForMultipleObjects( - handles.size(), handles.data(), FALSE, ms < 0 ? INFINITE : ms); + handles.size(), &handles[0], FALSE, (ms < 0 ? INFINITE : ms)); size_t i = 0; while (WAIT_OBJECT_0 <= res && res <= WAIT_OBJECT_0 + handles.size() - i - 1) { diff --git a/source/platform/stdioctl.cpp b/source/platform/stdioctl.cpp index 9cc98e5e..7099a73c 100644 --- a/source/platform/stdioctl.cpp +++ b/source/platform/stdioctl.cpp @@ -3,7 +3,6 @@ #include #include -#include namespace tvision { @@ -39,46 +38,34 @@ namespace tvision StdioCtl::StdioCtl() noexcept { - if (getEnv("TVISION_USE_STDIO").empty()) + if ( getEnv("TVISION_USE_STDIO").empty() + && (files[0] = fopen("/dev/tty", "r")) != nullptr + && (files[1] = fopen("/dev/tty", "w")) != nullptr ) { - for (int fd : {0, 1, 2}) - if (auto *name = ::ttyname(fd)) - if ((ttyfd = ::open(name, O_RDWR)) != -1) - break; - // Last resort, although this may lead to 100% CPU usage because - // /dev/tty is not supported by macOS's poll(), - if (ttyfd == -1) - ttyfd = ::open("/dev/tty", O_RDWR); - } - - if (ttyfd != -1) - { - for (auto &fd : fds) - fd = ttyfd; - int ttyfd2 = dup(ttyfd); - if (ttyfd2 == -1) - ttyfd2 = ttyfd; // This is wrong, but aborting is worse. - infile = ::fdopen(ttyfd, "r"); - outfile = ::fdopen(ttyfd2, "w"); - fcntl(ttyfd, F_SETFD, FD_CLOEXEC); - fcntl(ttyfd2, F_SETFD, FD_CLOEXEC); + ownsFiles = true; + fds[0] = fileno(files[0]); + fds[1] = fileno(files[1]); + // Subprocesses must not inherit these file descriptors. + for (int fd : fds) + fcntl(fd, F_SETFD, FD_CLOEXEC); } else { - for (int i = 0; i < 2; ++i) - fds[i] = i; - infile = stdin; - outfile = stdout; + for (FILE *file : files) + if (file != nullptr) + fclose(file); + fds[0] = STDIN_FILENO; + fds[1] = STDOUT_FILENO; + files[0] = stdin; + files[1] = stdout; } } StdioCtl::~StdioCtl() { - if (ttyfd != -1) - { - ::fclose(infile); - ::fclose(outfile); - } + if (ownsFiles) + for (FILE *file : files) + fclose(file); } void StdioCtl::write(const char *data, size_t bytes) const noexcept @@ -94,7 +81,7 @@ void StdioCtl::write(const char *data, size_t bytes) const noexcept TPoint StdioCtl::getSize() const noexcept { struct winsize w; - for (int fd : {in(), out()}) + for (int fd : fds) { if (ioctl(fd, TIOCGWINSZ, &w) != -1) { @@ -115,7 +102,7 @@ TPoint StdioCtl::getFontSize() const noexcept struct console_font_op cfo {}; cfo.op = KD_FONT_OP_GET; cfo.width = cfo.height = 32; - for (int fd : {in(), out()}) + for (int fd : fds) if (ioctl(fd, KDFONTOP, &cfo) != -1) return { max(cfo.width, 0), @@ -123,7 +110,7 @@ TPoint StdioCtl::getFontSize() const noexcept }; #endif struct winsize w; - for (int fd : {in(), out()}) + for (int fd : fds) if (ioctl(fd, TIOCGWINSZ, &w) != -1) return { w.ws_xpixel / max(w.ws_col, 1), @@ -138,7 +125,7 @@ bool StdioCtl::isLinuxConsole() const noexcept { // This is the same function used to get the Shift/Ctrl/Alt modifiers // on the console. It only succeeds if a console file descriptor is used. - for (int fd : {in(), out()}) + for (int fd : fds) { char subcode = 6; if (ioctl(fd, TIOCLINUX, &subcode) != -1)