Skip to content

Commit

Permalink
Unix: use 'select' instead of 'poll' for waiting for input
Browse files Browse the repository at this point in the history
So that we can support older macOS versions.

See magiblot/turbo#65.
  • Loading branch information
magiblot committed Feb 4, 2024
1 parent 0917f04 commit be6e64f
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 81 deletions.
46 changes: 18 additions & 28 deletions include/tvision/internal/events.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,27 @@
#include <memory>
#include <vector>

#ifdef _TV_UNIX
#include <poll.h>
#else
#ifdef _WIN32
#include <tvision/compat/windows/windows.h>
#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;
Expand All @@ -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
}

Expand Down Expand Up @@ -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,
Expand All @@ -126,24 +116,24 @@ enum PollState : uint8_t

struct PollData
{
std::vector<PollItem> items;
std::vector<SysHandle> handles;
std::vector<PollState> 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();
}
};

Expand Down
9 changes: 4 additions & 5 deletions include/tvision/internal/stdioctl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
45 changes: 33 additions & 12 deletions source/platform/events.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ using std::chrono::steady_clock;
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#endif

namespace tvision
Expand Down Expand Up @@ -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)
{
Expand Down
59 changes: 23 additions & 36 deletions source/platform/stdioctl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

#include <internal/stdioctl.h>
#include <internal/getenv.h>
#include <initializer_list>

namespace tvision
{
Expand Down Expand Up @@ -39,46 +38,34 @@ namespace tvision

StdioCtl::StdioCtl() noexcept
{
if (getEnv<TStringView>("TVISION_USE_STDIO").empty())
if ( getEnv<TStringView>("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
Expand All @@ -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)
{
Expand All @@ -115,15 +102,15 @@ 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),
max(cfo.height, 0),
};
#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),
Expand All @@ -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)
Expand Down

0 comments on commit be6e64f

Please sign in to comment.