C++ function which wait for input without block the looping

Discussion in 'Mac Programming' started by Kew, Aug 6, 2015.

  1. Kew, Aug 6, 2015
    Last edited by a moderator: Aug 6, 2015

    Kew macrumors newbie

    Joined:
    Aug 6, 2015
    #1
    i am using Mac os X, which running Unix system. i had tried getch() function which can't work in this case. I am wondering is that any method to make the function wait for an input without blocking? Thank you

    Here is the code:

    Code:
    #include <iostream>
    #include <curses.h>
    
    // checks if a key a has been pressed(non-blocking)
    int kbhit()
    {
     
        int ch;
        //getchar();
        ch = getch();
        //ch = 'a';
        //system("stty cbreak -echo");
     
        if (ch != ERR) {
            //ungetch(ch);
            return 1;
        } else {
            return 0;
        }
    }
    
    using namespace std;
    
    
    int main()
    {
    
        bool gameover = false;
        nodelay(stdscr, TRUE);
        //system("stty raw");
    
        while(!gameover)
        {
         
            if(kbhit())
            {
                cout << "You enter something" << endl;
                cout << kbhit() << endl;
                gameover = true;
            }
            else
            {
                cout << "nothing is enter\n";
                cout << kbhit() << endl;
                gameover = false;
            }
        }
    
        //system ("stty cooked");
        //system("stty cooked echo");
     
    
        return 0;
    }
     
  2. dmi macrumors regular

    Joined:
    Dec 21, 2010
  3. cqexbesd, Aug 6, 2015
    Last edited: Aug 9, 2015

    cqexbesd macrumors regular

    Joined:
    Jun 4, 2009
    #3
    getch is a curses function. Are you otherwise using curses? Have you called initscr? I think curses can probably do what I think it is you want but if you want to use curses you should do all your I/O with it, not just bits an pieces. Go find a curses tutorial and follow it and skip the rest of this.

    Waiting for input without blocking doesn't make too much sense because blocking _is_ waiting. Perhaps you mean you wish to read input without blocking? If so there are a number of issues you need to consider - it's not that simple and it's not that portable.

    First you should block on something. If not then you will just spin on the CPU, checking millions of times a second if anything has happened only to be told no. You will quickly hit 100% CPU, flattening batteries, running fans, and slowing down everything else. The question of what you block on will depend on what the other inputs to your program are (maybe your program reads from the network as well?) but you should checkout select(2) or kqueue(2) if you aren't already aware of them. That way you can wait for some input to be ready before trying to read. Input, especially from a user, is very slow compared to your CPU so if you use select or kqueue your program can spend most of its time sleeping.

    Second is that a terminal will buffer input by default so it's not available to your application until the user has hit return. You can adjust that (and I see you have tried to with your call to stty) using tcgetattr(3) and tcsetattr(3).

    Third you need to open your file descriptors in non blocking mode so if you do attempt to read when there is no data available to be read you get an error, rather than blocking. This unfortunately precludes the use of stdio functions and (though I'm not a c++ guy) I think also C++'s default iostream stuff.

    I see you are using C++ but I never do so here is a small example in C that might help. It is hopefully fairly portable to most UNIX like systems though this stuff is inherently OS specific so I make no guarantees. I've also used blocks - probably not portable to older compilers but very nice to use and support under OSX by default so why not :)

    Code:
    #include <sys/select.h>
    
    #include <err.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <termios.h>
    #include <unistd.h>
    
    
    /*
       Example code to do non-blocking reads from stdin
    
       NB this uses blocks so on some platforms you might need "-fblocks" to
       compile
    */
    int main(void) {
    
        /* make stdin non-blocking */
        {
            int f;
    
            // fetch the current flags
            if ((f = fcntl(STDIN_FILENO, F_GETFL, 0)) == -1) {
                err(1, "fcntl(GETFL)");
            }
            // now set the flags to what they are + non-blocking
            if ((f = fcntl(STDIN_FILENO, F_SETFL, f | O_NONBLOCK)) == -1) {
                err(1, "fcntl(SETFL)");
            }
        }
    
        /* try to unbuffer our terminal (if indeed that is what stdin is connected
          to) */
        {
            struct termios termios;
    
            // get our current terminal settings
            if (tcgetattr(STDIN_FILENO, &termios) != 0) {
                // NB if stdin isn't a terminal (e.g. we are reading from a file
                // or a pipe) then this will fail. good software wouldn't exit in
                // that case - luckily we are just example code.
                err(1, "tcgetattr");
            }
            // when we exit we need to restore the terminal the way it was rather
            // than leave it in a broken state. most shells will actually clean up
            // after us so this won't always be needed but best to be polite.
            atexit_b(^{
                    tcsetattr(STDIN_FILENO, TCSASOFT, &termios);
                    // we don't error check this becuase if it fails there isn't
                    // anything we can do about it
                });
            // build a description of the mode we would like the terminal to be in
            // by starting with the current settings and changing a few things.
            // first switch of canonical mode and echo. switching off echo isn't
            // actually required but often wanted. switching off canonical mode
            // means we don't have to wait for a newline to be able to read
            termios.c_lflag &= ~(ICANON | ECHO);
            // then make sure read will return straight away
            termios.c_cc[VMIN] = 0;
            termios.c_cc[VTIME] = 0;
            // now apply those settings to the terminal
            if (tcsetattr(STDIN_FILENO, TCSASOFT, &termios) != 0) {
                err(1, "tcsetattr");
            }
        }
    
        /* now it should be safe for us to read from the terminal without
          blocking. to prevent us spinning we use select to wait till there is
          (probably) something to read */
        {
            fd_set fd_set;
    
            for (;;) {
                // select needs a set of descriptors we want to know about. that's
                // only 1 in this trivial example
                FD_ZERO(&fd_set);
                FD_SET(STDIN_FILENO, &fd_set);
    
                // now call select. NB the first argument is the maximum fd + 1 -
                // not the number of fds in the set. our timeout is NULL so
                // select should never return 0. we should block here until there
                // is data to be read
                if (select(STDIN_FILENO + 1, &fd_set, NULL, NULL, NULL) == -1) {
                    if (errno == EINTR) {
                        // this isn't really an error so just try again
                        continue;
                    } else {
                        err(1, "select");
                    }
                }
    
                // check if the fd of interest is in the set of read fds (we only
                // have one so this is very unlikely to ever be false). the set contains
                // the gds that can be read from without blocking
                if (FD_ISSET(STDIN_FILENO, &fd_set)) {
                    char buffer[32];
                    int n;
    
                    // we should have something to read from stdin so try it - it may
                    // be more than 1 byte though unlikely if coming from a keyboard
                    // we should, but don't, check for read returning all 32 bytes as
                    // that could mean we should call read again without looping
                    // through the select again (just for efficiency)
                    switch (n = read(STDIN_FILENO, (void *)buffer, sizeof(buffer))) {
                        case -1:
                            if (errno == EAGAIN) {
                                // there wasn't anything to read after all
                                continue;
                            } else {
                                err(1, "read");
                            }
                            break;
                        case 0:
                            // EOF
                            exit(0);
                            break;
                        default:
                            // print out what we read
                            printf("Read: %*s\n", n, buffer);
                            break;
                    }
                } else {
                    // we only had one fd in the set yet "another" fd caused
                    // select to return. I doubt this will ever happen (famous
                    // last words)
                    warnx("select returned with unknown fd");
                }
            }
        }
    
        return 0;
    }
    
     
  4. subsonix macrumors 68040

    Joined:
    Feb 2, 2008
    #4
    You can do this with curses, but as been hinted at you need to initialize a window and then clean up before you exit to restore the terminal. For a non-blocking read you can set timeout(0) at the initialization, that's it.

    Reading input this way is problematic though because your process will consume 100% of your CPU, doing nothing most of the time.
     
  5. subsonix macrumors 68040

    Joined:
    Feb 2, 2008
    #5
    I add a small example showing how you can do this with curses, letting you draw a "line" with the w,a,s,d keys in the terminal window, since it looks like your purpose is some kind of game. I just use the default window size, but you can set this up and also check your current terminal size first, look for a curses tutorial online.

    Code:
    #include <curses.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    struct cursor {
      char x;
      char y;
    };
    
    void read_wasd(struct cursor *c) {
    
      char ch = getch();
    
      switch(ch) {
      case 'a':
        c->x--;
        break;
      case 'd':
        c->x++;
        break;
      case 'w':
        c->y--;
        break;
      case 's':
        c->y++;
        break;
      }
    }
    
    int main(void)
    {
      // init curses
      initscr();
      cbreak();
      noecho();
      timeout(0);  // sets input to non-blocking
    
      struct cursor cursor = { 0, 0 };
    
      atexit((void*)&endwin);
      mvprintw(cursor.y, cursor.x, "*");
    
      while(1) {
        read_wasd(&cursor);
        mvprintw(cursor.y, cursor.x, "*");
        usleep(10000);
      }
    
      return 0;
    }
    
    I added a usleep() at the end to reduce CPU use, you could add game logic in this loop as well, so it seems this would work since you need to update this periodically anyway, so you may as well check if your cursor position has changed at the same time.
     

Share This Page