I've got a cross-platform (windows and unix+xcb) terminal+graphics_window application and it mostly works ok, until you wait too long at the input prompt, where under heavy load the image may disappear. :(
I've got a mainloop (REPL) for the interpreter (postscript interpreter) which calls an event handler function each time around its loop. The event handler performs one iteration of what would normally be the window's message/event loop. But input is processed with normal C i/o and so the event handler never gets called when blocked in fgetc()
.
The graphics window is output-only. It has no buttons and only needs to respond to events like Raise, Map, Expose, etc.
How can I arrange to have the event handler called during input reading loops deeper in the call stack? This needs to be possible to implement with both POSIX and win32 APIs.
The options appear to be
- Non-blocking i/o
relatively simple in unix. looks like a pain in windows - Polling
- Input thread
pthreads? - Window thread
pthreads?
Are any of these likely to be less painful than the others?
If I could just stay on unix, then this would seem to do the whole trick:
#include <errno.h>
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
void idleproc () { /* simulates calling the event handler
(ie. one slice of the window loop) */
//printf("idle\n");
putchar('.');
}
int idlefgetc (FILE *stream) {
int ret;
do {
ret = fgetc(stream);
idleproc();
} while(ret == EOF &&
(errno == EAGAIN || errno == EINTR));
return ret;
}
int setraw (FILE *stream) {
struct termios tbuf;
if (tcgetattr(fileno(stream), &tbuf) == -1)
return -1;
tbuf.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
| INLCR | IGNCR | ICRNL | IXON);
tbuf.c_oflag &= ~OPOST;
tbuf.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
tbuf.c_cflag &= ~(CSIZE | PARENB);
tbuf.c_cflag |= CS8;
if (tcsetattr(fileno(stream), TCSANOW, &tbuf) == -1)
return -1;
return 0;
}
int setnonblocking (FILE *stream) {
int flags;
if (setraw(stream) != 0)
return -1;
if (!((flags = fcntl(fileno(stream), F_GETFL)) & O_NONBLOCK)) {
flags |= O_NONBLOCK;
fcntl(fileno(stream), F_SETFL, flags);
}
return 0;
}
int main (int argc, char **argv) {
if (setnonblocking(stdin)) {
perror(argv[0]);
return 0;
}
printf("'%d'\n", idlefgetc(stdin));
system("stty sane");
return 0;
}
Another solution seems possible due to the fact that this is a programming-language interpreter after all.
It should be possible to re-implement the code that uses
fgetc
to instead use the ps primitive:-file-
read
int bool
. This postscript operator itself uses the stdio call, but it can be called by the other file-reading functions with a continuation-passing style by pushing on the exec stack and returning. This would naturally interleave more calls to the event handler since it returns to the main loop more often.I may still need to use a non-blocking read. But this will be easier to manage if it is called in only one place. Continuation-passing has the advantage of breaking-up larger functions into separate stages and has been successfully used to implement the window devices themselves by overriding methods in base class (the base class is implemented as a postscript dictionary). But it's still fairly new to me, so it's not my go-to duct-tape just yet. :)
I'll re-work this prototype to illustrate the approach after work. :)
Edit: Took a few days. But here's the new idea. For windows, it will need to do the nonblocking call differently, but the call can be isolated to this one place. And with the continuation-passing, the file-reading function does not need access to (or knowledge of) the event-handler, so better encapsulation.
This program behaves the same as the one in the question, it prints
.
repeatedly until a keypress, then it prints the ascii code of the keypress. The.
simulates repeatedly calling the event handler while waiting for a keystroke. I had to mock-up a bit of the interpreter guts: an object type, some stacks, and aneval()
function. So this illustrates the REPL better as well.Under Windows you need to use the Console API. You can perform asynchronous, non-blocking, reading of characters with ReadFileEx. Another possibility is ReadConsoleInput, and poll continously for input, without blocking. With SetConsole you can decide, what events to capture.
For more details on asynchronous I/O under Windows, see here.