2

クロスプラットフォーム (windows と unix+xcb) の terminal+graphics_window アプリケーションを使用していますが、負荷が高いと画像が消える可能性がある入力プロンプトで長時間待機するまで、ほとんど問題なく動作します。:(

ループのたびにイベント ハンドラー関数を呼び出すインタープリター (ポストスクリプト インタープリター) 用のメインループ (REPL) があります。イベント ハンドラーは、通常はウィンドウのメッセージ/イベント ループを 1 回繰り返します。しかし、入力は通常の C i/o で処理されるため、ブロックされたときにイベント ハンドラが呼び出されることはありませんfgetc()

グラフィックウィンドウは出力専用です。ボタンはなく、Raise、Map、Expose などのイベントに応答するだけで済みます。

コール スタックの奥深くにある入力読み取りループ中にイベント ハンドラが呼び出されるようにするにはどうすればよいですか? これは、POSIX API と win32 API の両方で実装できる必要があります。

オプションは

  • Unix では比較的単純なノンブロッキング I/O 。
    窓の痛みのように見える
  • ポーリング
  • 入力スレッド
    pthreads?
  • ウィンドウスレッド
    pthreads?

これらのうち、他のものよりも痛みが少ないと思われるものはありますか?

私がUNIXにとどまることができれば、これはすべてのトリックを行うようです:

#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;
}
4

2 に答える 2

2

結局のところ、これはプログラミング言語のインタープリターであるため、別の解決策が考えられます。

fgetc代わりに ps プリミティブを使用するコードを再実装できるはずです: . この postscript 演算子自体は stdio 呼び出しを使用しますが、exec スタックをプッシュして戻ることにより、継続渡しスタイルの他のファイル読み取り関数によって呼び出すことができます。これは、より頻繁にメイン ループに戻るため、当然、イベント ハンドラーへのより多くの呼び出しをインターリーブします。-file-readint bool

非ブロッキング読み取りを使用する必要があるかもしれません。ただし、これを 1 か所だけで呼び出すと、管理が容易になります。継続渡しには、より大きな関数を別々の段階に分割するという利点があり、基本クラスのメソッドをオーバーライドすることでウィンドウ デバイス自体を実装するために使用されてきました (基本クラスは PostScript 辞書として実装されます)。しかし、それは私にとってまだかなり新しいものなので、まだ私の頼りになるダクトテープではありません. :)

このプロトタイプを作り直して、作業後にアプローチを説明します。:)

編集:数日かかりました。しかし、ここに新しいアイデアがあります。Windows の場合、非ブロッキング呼び出しを別の方法で行う必要がありますが、呼び出しはこの 1 つの場所に分離できます。また、継続渡しを使用すると、ファイル読み取り関数はイベント ハンドラーへのアクセス (またはその知識) を必要としないため、カプセル化が向上します

このプログラムは問題のものと同じように動作し.、キーが押されるまで繰り返し出力し、次にキーが押されたASCIIコードを出力します。は.、キーストロークを待っている間にイベント ハンドラーを繰り返し呼び出すことをシミュレートします。オブジェクト型、いくつかのスタック、および関数など、インタープリターの内臓の一部をモックアップする必要がありました。eval()したがって、これは REPL をよりよく示しています。

#include <errno.h>
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>

int set_raw_term (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 set_nonblocking (FILE *stream) {
    int flags;

    if (set_raw_term(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 event_handler() {
    putchar('.');
}

enum { null, integer, file, operator };
typedef union {
    short tag;
    struct {
        short tag;
        int val;
    } int_;
    struct {
        short tag;
        FILE *f; 
    } file_;
    struct {
        short tag;
        int (*fp)();
    } oper_;
} object; /* object union allows multiple types on the stacks */

object os[100];   /* operand stack */
object *tos = os; /* top of operand stack */
object es[100];   /* execution stack */
object *tes = es; /* top of execution stack */

int eval () {
    if (tes == es) /* execution stack is empty */
        return -1; /* return "finished" */

    event_handler();

    switch(tes[-1].tag) { /* type of object on top of execution stack */
        case integer:
        case file:
            *tos++ = *--tes;     /* push file or integer to operand stack */
            break;

        case operator:
            (--tes)->oper_.fp(); /* call operator function */
            break;
    }
    return 0; /* return "not finished" */
}

int file_read_byte () {
    int ret;
    object arg;

    arg = *--tos; /* pop argument from operand stack */
    ret = fgetc(arg.file_.f);
    if (ret == EOF && (errno == EAGAIN || errno == EINTR)) { /* if no data */
        *tos++ = arg;   /* restore argument to operand stack */
        *tes++ = (object){ .oper_.tag = operator, .oper_.fp = file_read_byte }; /* push continuation to execution stack */
        return 0;
    } else {
        *tos++ = (object){ .int_.tag = integer, .int_.val = ret }; /* push result to operand stack */
        return 0;
    }
}


int main(int argc, char **argv) {
    int ret;

    if (set_nonblocking(stdin) != 0) {
        perror(argv[0]);
        return 0;
    }

    //printf("'%d'\n", file_read_byte(stdin));
    *tos++ = (object){ .file_.tag = file, .file_.f = stdin }; /* push file argument to operand stack */
    *tes++ = (object){ .oper_.tag = operator, .oper_.fp = file_read_byte }; /* push operator object to execution stack */

    ret = 0;
    while (ret == 0) { /* call eval until execution is "finished" */
        ret = eval();
    }
    printf("'%d'\n", (--tos)->int_.val); /* pop returned value */

    system("stty sane");
    return 0;
}
于 2014-01-07T19:14:01.097 に答える