Search code examples
cloopsinputdelayncurses

How do I reduce input lag in an NCurses C Application


I am getting major amounts of input lag when I run my application.

More details: When I press 'w', 'a', 's', 'd' (My assigned input keys) the object moves however it continues to move for an extended period of time after the key has been released. The source code is below however small parts of the code have been cut out to shorten the questions however if the source code below does not compile I have all of the code up on github. https://github.com/TreeStain/DodgeLinuxGame.git Thankyou for your time. -Tristan

dodge.c:

#define ASPECT_RATIO_X 2
#define ASPECT_RATIO_Y 1
#define FRAMES_PER_SECOND 60

#include <ncurses.h>
#include "object.h"
#include "render.h"

int main()
{
    initscr();
    cbreak();
    noecho();
    nodelay(stdscr, 1);

    object objs[1];

    object colObj; colObj.x = 10; colObj.y = 6;
                   colObj.w = 2;  colObj.h = 2;
                   colObj.sprite = '*';
                   colObj.ySpeed = 1;
                   colObj.xSpeed = 1;

    objs[0] = colObj;

    //halfdelay(1);

    while (1)
    {
        char in = getch();
        if (in == 'w')
            objs[0].y -= objs[0].ySpeed * ASPECT_RATIO_Y;
        if (in == 's')
            objs[0].y += objs[0].ySpeed * ASPECT_RATIO_Y;
        if (in == 'a')
            objs[0].x -= objs[0].xSpeed * ASPECT_RATIO_X;
        if (in == 'd')
            objs[0].x += objs[0].xSpeed * ASPECT_RATIO_X;
        render(objs, 1);
        napms(FRAMES_PER_SECOND);
    }

    getch();

    endwin();
    return 0;
 }

render.h:

void render(object obj[], int objectNum);

void render(object obj[], int objectNum)            //Takes array of objects and prints them to screen
 {
    int x, y, i, scrWidth, scrHeight;
    getmaxyx(stdscr, scrHeight, scrWidth);          //Get terminal height and width

    for (y = 0; y < scrHeight; y++)
    {
        for (x = 0; x < scrWidth; x++)
        {
            mvprintw(y, x, " ");
        }
    }

    for (i = 0; i < objectNum; i++)
    {
        int xprint = 0, yprint = 0;
        for (yprint = obj[i].y; yprint < obj[i].y + (obj[i].h * ASPECT_RATIO_Y); yprint++)
        {
            for (xprint = obj[i].x; xprint < obj[i].x + (obj[i].w * ASPECT_RATIO_X); xprint++)
                mvprintw(yprint, xprint, "%c", obj[i].sprite);
        }
    }
    refresh();
}

object.h:

typedef struct
{
    int x, y, w, h, ySpeed, xSpeed;
    char sprite;
}object;

P.S. please feel free to critique my methods and code as I am fairly new at programming and can take all the criticism I can get.


Solution

  • I believe the reason is because getch() will only release one input-character at a time (even if there are many queued up in the input stream) so if they queue up faster than you 'remove' them from the stream, the loop will continue until the queue is emptied even after you release the key. Also, you'll want to go (1000 / FRAMES_PER_SECOND) to get your desired delay-time in milliseconds (this creates 60 frames per second).

    Try this in your while loop instead.

    while (1)
        {
            char in;
            /* We are ready for a new frame. Keep calling getch() until we hear a keypress */
            while( (in = getch()) == ERR) {}
    
            if (in == 'w')
                objs[0].y -= objs[0].ySpeed * ASPECT_RATIO_Y;
            if (in == 's')
                objs[0].y += objs[0].ySpeed * ASPECT_RATIO_Y;
            if (in == 'a')
                objs[0].x -= objs[0].xSpeed * ASPECT_RATIO_X;
            if (in == 'd')
                objs[0].x += objs[0].xSpeed * ASPECT_RATIO_X;
            render(objs, 1);
    
            /* Clear out any other characters that have been buffered */
            while(getch() != ERR) {}
    
            napms(1000 / FRAMES_PER_SECOND);
        }
    

    From the top of your loop: while( (in = getch()) == ERR) {} will call getch() rapidly until a keypress is detected. If a keypress isn't detected, getch() will return ERR. What while(getch() != ERR) {} does is keep calling getch() until all buffered input characters are removed from the queue, then getch() returns ERR and moves on. Then the loop should sleep ~17ms and repeat. These lines should force the loop to only 'count' one keypress every ~17ms, and no more often than that.

    See: http://linux.die.net/man/3/getch