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.
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
Ncurses does not detect key presses and key releases separately. You cannot move an object while a key is being held, and stop immediately after it is released.
The phenomenon you observe results from a ximbination of two factors: an auto-repeating keyboard, and a buffering keyboard driver. That is, the user holds a key, this generates a large amount of key events, and they are buffered by the driver and given to your application as it asks for key presses.
Neither the driver nor keyboard auto-repeat feature are under control of your application. The only thing you can hope to achieve is to process key events faster than they come out of the keyboard. If you want to do this, you have to get rid of napms in your main loop and process key presses as they come, between frame repaints. There are many ways to do that but the most straightforward is to use the timeout function.
timeout (timeToRefresh);
ch = getch();
if (ch == ERR) refresh();
else processKey(ch);
You need to calculate timeToRefresh each time using a real time clock.
Related
I have a thread that catches a key pressed through getch and if the key pressed is arrowUp or arrowDown then it scrolls my terminal using ncurses functions (incrementing an integer variable used to show elements from a linked list). This works fine most of the times but sometimes (usually when i hold an arrow pressed) ncurses prints on terminal weird and unexpected characters like 9;32H (it seems like an uncatched input). Does anyone know how i can solve this?
Here an MCVE
#include <ncurses.h>
#include <stdlib.h>
#include <pthread.h>
#define MAX(a,b) ((a) > (b) ? (a) : (b))
void * listener(void* p){
int* shift = (int*) p;
int ch;
while (1)
{
ch = getch();
if(ch == KEY_UP){
*shift = MAX(0, *shift - 1);
}
else if(ch == KEY_DOWN){
*shift = *shift + 1;
}
}
}
int main(){
char* c = malloc(100);
for(int i = 0; i < 100; i++){
c[i] = 'A' + (random() % 26);
}
int shift = 0;
pthread_t th;
initscr();
raw();
noecho();
keypad(stdscr, TRUE);
start_color();
curs_set(0);
pthread_create(&th, NULL, listener, (void*) &shift);
while(1){
for(int j = shift; j < shift+stdscr->_maxy; ++j){
move(j-shift,0);
clrtoeol();
mvaddch(j-shift, 0, c[j]);
refresh();
}
}
endwin();
free(c);
return 0;
}
This snippet shows my issue if you hold arrow down
EDIT:
The issue seems to be related to getch() from different thread that modifies global variables of ncurses library. Does anyone know a thread-safe way to get a char input?
I actually solved my issue using getchar instead of getch since it seems not to be thread safe. I found out that getch modifies ncurses global variable and calls refresh() at the end, so if another thread is changing ncurses stuff (e.g. cursor position) your program may have an unexpected behavior (in my case printing escape characters representing set cursor position)
I'm making a whack-a-mole program, and currently I have the setup for the mole to appear and disappear at random; however, while this is all going on I'll need to accept user input in order to "whack" the mole. Is there any way to do this without pausing the loop to wait for the user to input something, and rather have the loop run WHILE scanning for input? my code is below.
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <stdbool.h>
int main(){
// sets the mole to be initially under_ground
bool above_ground = false;
char mole_presence[1] = {0};
// keeps the mole running as long as the game is in play
while(1){
// while the mole is not above ground, wait until he randomly is
while(above_ground == false){
int r = rand() % 6;
if (r == 5){
printf("a mole has appeared, he's dancing around!\n");
mole_presence[0] = 1;
above_ground = true;
}
else{
printf("%d\n", mole_presence[0]);
sleep(1);
}
}
// while the mole is above ground, he dances until he randomly escapes
while(above_ground == true){
bool escaped = false;
// while he hasn't escaped, continue this loop
while (escaped == false){
int x = rand() % 10;
// if he randomly escapes, break out and show he is no longer above ground
if (x == 5){
printf("he disappeared!\n");
mole_presence[0] = 0;
escaped = true;
}
else{
printf("%d\n", mole_presence[0]);
sleep(1);
}
}
above_ground = false;
}
}
}
I faced the same problem while writing a snake-xenia kind of game. In Windows there is function called _kbhit which can be used to check whether the key is pressed or not. It's prototype is
int _kbhit(void)
Itreturns a nonzero value if a key has been pressed. Otherwise, it returns 0. Read more here : https://msdn.microsoft.com/en-us/library/58w7c94c.aspx
It's not available on linux as there is no conio.h in linux So for linux this answer can help Using kbhit() and getch() on Linux
I am trying to program the logomatic by sparkfun, and yes I have used their forum with no responses, and having some issues. I am trying to send characters to the UART0 and I want the logomatic to respond with specific characters and not just an echo. For example, I send 'ID?' over the terminal (using RealTerm), and the logomatic sends back '1'. All it will so now is echo.
I am using c with programmers notepad with the WinARM toolchain. The following snippet is from the main.c file. I only included this, because I am fairly certain that this is where my problem lies
void Initialize(void)
{
rprintf_devopen(putc_serial0);
PINSEL0 = 0xCF351505;
PINSEL1 = 0x15441801;
IODIR0 |= 0x00000884;
IOSET0 = 0x00000080;
S0SPCR = 0x08; // SPI clk to be pclk/8
S0SPCR = 0x30; // master, msb, first clk edge, active high, no ints
}
Notice the rprintf_devopen function, below is from the rprintf.c file, and due to my mediocre skills, I do not understand this bit of code. If I comment out the rprintf_devopen in main, the chip never initializes correctly.
static int (*putcharfunc)(int c);
void rprintf_devopen( int(*put)(int) )
{
putcharfunc = put;
}
static void myputchar(unsigned char c)
{
if(c == '\n') putcharfunc('\r');
putcharfunc(c);
}
Now, below is from the serial.c file. So my thought was that I should be able to just call one of these putchar functions in main.c and that it would work, but it still just echoes.
int putchar_serial0 (int ch)
{
if (ch == '\n')
{
while (!(U0LSR & 0x20));
U0THR = CR; // output CR
}
while (!(U0LSR & 0x20));
return (U0THR = ch);
}
// Write character to Serial Port 0 without \n -> \r\n
int putc_serial0 (int ch)
{
while (!(U0LSR & 0x20));
return (U0THR = ch);
}
// Write character to Serial Port 1 without \n -> \r\n
int putc_serial1 (int ch)
{
while (!(U1LSR & 0x20));
return (U1THR = ch);
}
void putstring_serial0 (const char *string)
{
char ch;
while ((ch = *string))
{
putchar_serial0(ch);
string++;
}
}
I have tried calling the different putchar functions in main, also with the rprintf_devopen. Still just echoes. I have altered the putchar functions and still just echoes. I have tried just writing to the U0THR register in main.c and no luck. Keep in mind that I am still a student and my major is electrical engineering, so the only programming classes that I have taken are intro to c, and an intro to vhdl. I am more of a math and physics guy. I was working on this for an internship I was doing. The internship ended, but it just bugs me that I cannot figure this out. Honestly, working on this program taught me more that the c class that I took. Anyways, I appreciate any help that can be offered, and let me know if you want to see the entire code.
Below is an update to the question. This function is in main.c
static void UART0ISR(void)
{
char temp;
trig = 13; //This is where you set the trigger character in decimal, in this case a carriage return.
temp = U0RBR; //U0RBR is the receive buffer on the chip, refer to datasheet.
if(temp == query1[counter1]) //This segment looks for the characters "ID?" from the U0RBR
{ //query1 is defined at the top of the program
counter1++;
if(counter1 >= 3)
{
flag1 = 1; //This keeps track of whether or not query1 was found
counter1 = 0;
stat(1,ON);
delay_ms(50);
stat(1,OFF);
RX_in = 0;
temp = 0;
//rprintf("\n\rtransmission works\n");
putc_serial1(49);
}
}
if(temp == query2[counter2] && flag1 == 1) //This segment looks for "protov?" from the U0RBR, but only after query1 has been found
{
counter2++;
if(counter2 >= 7)
{
flag2 = 1; //This keeps track of whether or not query2 was found
counter2 = 0;
stat(1,ON);
delay_ms(50);
stat(1,OFF);
RX_in = 0;
temp = 0;
putc_serial1(49);
}
}
if(temp == stop[counter3]) //This if segment looks for certain characters in the receive buffer to stop logging
{
counter3++;
if(counter3 >= 2)
{
flagstop = 1; //This flagstop keeps track of whether or not stop was found. When the stop characters are found,
flag1 = 0; //the query1 and query2 flags will be reset. So, in order to log again these queries must be sent again
flag2 = 0; //this may seem obvious, but deserves mention.
counter3 = 0;
stat(1,ON);
delay_ms(500);
stat(1,OFF);
RX_in = 0;
temp = 0;
}
flagstop = 0; //Reset the stop flag in order to wait once again for the query 1&2
}
if(RX_in == 0)
{
memset (RX_array1, 0, 512); // This clears the RX_array to make way for new data
memset (RX_array2, 0, 512);
}
if(RX_in < 512 && flag1 == 1 && flag2 == 1) //We cannot log data until we see both flags 1 & 2 and after we see these flags,
{ //we must then see the trigger character "carriage return"
RX_array1[RX_in] = temp;
RX_in++;
if(temp == trig)
{
RX_array1[RX_in] = 10; // delimiters
log_array1 = 1;
RX_in = 0;
}
}
else if(RX_in >= 512 && flag1 == 1 && flag2 == 1) //This else if is here in case the RX_in is greater than 512 because the RX_arrays are defined to
{ //be of size 512. If this happens we don't want to lose data, so we must put the overflow into another register.
RX_array2[RX_in - 512] = temp;
RX_in++;
RX_array1[512] = 10; // delimiters
RX_array1[512 + 1] = 13;
log_array1 = 1;
if(RX_in == 1024 || temp == trig)
{
RX_array2[RX_in - 512] = 10; // delimiters
log_array2 = 1;
RX_in = 0;
}
}
temp = U0IIR; // have to read this to clear the interrupt
VICVectAddr = 0;
}
I am trying to make a controller for a game with SDL 2(didn't want to ask on gamedev since it is not a game issue directly) I use SDL_GetKeyboardEvent to see if the navigation arrows are being pressed but it apparently doesn't work, it is supposed to print a value 1 or -1 if one of those keys is pressed but it doesn't it just prints 0 even if I hold the key down for several seconds, it is like it doesn't detect that the key is being pressed. I searched all over the internet and this is how they do it, but it doesn't work for me.
#define SDL_MAIN_HANDLED
#include <stdio.h>
#include "SDL.h"
/* I'll add some code later
so something that isn't used
might be initialized
*/
int main (void)
{
int a = 1;
int x;
int z;
SDL_Event quit;
const Uint8 *keys = SDL_GetKeyboardState(NULL);
SDL_Init(SDL_INIT_VIDEO);
while(a)
{
SDL_PollEvent(&quit);
if(quit.type == SDL_QUIT)
a = 0;
if(keys[SDL_SCANCODE_UP])
z = 1;
else if(keys[SDL_SCANCODE_DOWN])
z = -1;
else
z = 0;
if(keys[SDL_SCANCODE_LEFT])
x = -1;
else if(keys[SDL_SCANCODE_RIGHT])
x = 1;
else
x = 0;
printf("%d, %d\n", x, z);
//This is supposed to print
//x, z values so if up arrow is pressed it will print 0, 1 and if
//down arrow is pressed it will print 0, -1: the same with horizontal ones.
//1 or -1, 1 or -1
}
SDL_Quit();
return 0;
}
Read the documentation: wiki
Note: This function gives you the current state after all events have been processed, so if a key or button has been pressed and released before you process events, then the pressed state will never show up in the SDL_GetKeyboardState() calls
What it means is?
You need to process all events. How? Looping the PollEvent, after the loop (or if you want to check in the loop, check at the end), the SDL_GetKeyboardState is usable.
So, go through the loop, check for keyboards states. Do not forget to always go through the loop before checking for keys
e.g.
while (game)
{
/*! updates the array of keystates */
while ((SDL_PollEvent(&e)) != 0)
{
/*! request quit */
if (e.type == SDL_QUIT)
{
game = false;
}
}
if (keys[SDL_SCANCODE_RIGHT])
std::cout << "Right key";
}
You'll need to create a window with SDL_SetVideoMode to get mouse and keyboard events.
Here is my InputManager update function that updates the user input from the keyboard and mouse. Notice the const_cast needed to be able to update the class variable Uint8* keysArray;. This way, more than one event can be processed at once.
void update() {
SDL_PumpEvents();
// update keyboard state
keysArray = const_cast <Uint8*> (SDL_GetKeyboardState(NULL));
if (keysArray[SDL_SCANCODE_RETURN])
printf("MESSAGE: <RETURN> is pressed...\n");
if (keysArray[SDL_SCANCODE_RIGHT] && keysArray[SDL_SCANCODE_UP])
printf("MESSAGE: Right and Up arrows are pressed...\n");
// update mouse location and button states
SDL_GetMouseState(&x, &y);
getMouseButtonStates();
}
I have an older ncurses-based program that does some simple IO on a few files (i.e.: setup program). However, from a terminal different from PuTTY, it crashes with SIGBUS
Program received signal SIGBUS, Bus error.
0x00000000004028b1 in fDisplay (ptr=Variable "ptr" is not available.
) at file_cpy.c:676
676 sprintf(p, " %-36s ", (*ptr)->datainfo.option);
(gdb) where
#0 0x00000000004028b1 in fDisplay (ptr=Variable "ptr" is not available.
) at file_cpy.c:676
#1 0x0000000000402cdb in fredraw (c=0x7fffffffe860) at file_cpy.c:696
#2 0x0000000000401996 in ls_dispatch (c=0x2020202020202020) at ds_cell.c:324
#3 0x0000000000403bf2 in main_dir (c=0x2020202020202020) at file_cpy.c:811
#4 0x0000000000403cb3 in main () at file_cpy.c:1345
(gdb) x/i $pc
0x4028b1 <fDisplay+17>: mov (%rax),%rdx
(gdb)
This happens on Linux and FreeBSD, regardless of ncurses version and 32/64bit architecture. I'm completely stumped.
fDisplay() is called here:
/*
* File redraw routine. Draws current list on screen.
*/
int fredraw (CELL * c)
{
register int row = c->srow;
dlistptr p = c->list_start;
int i = 0;
char buff[200];
if (c->ecol - c->scol)
sprintf(buff, "%*s",c->ecol - c->scol + 1, " ");
while (i <= c->erow - c->srow && p != NULL)
{
if (p == c->current) wattron(c->window,A_REVERSE);
mvaddstr (row , c->scol, fDisplay(&p));
if (p == c->current) wattroff(c->window,A_REVERSE);
row++; i++;
p = p->nextlistptr;
}
if (row <= c -> erow)
for (; row <= c -> erow ; row++)
mvaddstr(row, c->scol, buff);
wrefresh(c->window);
c -> redraw = FALSE;
return TRUE;
}
fredraw() is called here:
int main_dir(CELL *c) {
int i;
getcwd(current_path, sizeof(current_path));
strcat(current_path, "/.config.h");
load_file(current_path);
c->keytable = file_cpy_menu;
c->func_table = file_cpy_table;
c->ListEntryProc = File_Entry;
c->UpdateStatusProc = status_update;
c->redraw = TRUE;
c->ListExitProc = List_Exit;
c->ListPaintProc = fredraw;
c->srow = 3;
c->scol = 1;
c->erow = c->window->_maxy - 5;
c->ecol = c->window->_maxx - 1;
c->max_rows = c->window->_maxy;
c->max_cols = c->window->_maxx;
c->filename = "[ Config ]";
c->menu_bar = 0;
c->normcolor = 0x07;
c->barcolor = 0x1f;
init_dlist(c);
for (i = 0; config_type[i].option; i++)
insert_fdlist(c, &config_type[i]);
/*
* Go Do It
*/
do {
c->redraw = TRUE;
ls_dispatch(c);
} while (c->termkey != ESC && c->termkey != ALT_X);
return TRUE;
}
Finally, main() calls the above functions:
int main() {
CELL file_cpy = {0};
WINDOW *mainwin;
mainwin = initscr();
start_color();
setup_colors();
cbreak();
noecho();
keypad(mainwin, TRUE);
meta(mainwin, TRUE);
raw();
leaveok(mainwin, TRUE);
wbkgd(mainwin, COLOR_PAIR(COLOR_MAIN));
wattron(mainwin, COLOR_PAIR(COLOR_MAIN));
werase(mainwin);
refresh();
file_cpy.window = mainwin;
main_dir(&file_cpy);
wbkgd(mainwin, A_NORMAL);
werase(mainwin);
echo();
nocbreak();
noraw();
refresh();
endwin();
return TRUE;
}
Apparently you are calling main_dir and ls_dispatch with a pointer c initialized to 0x2020202020202020. While not completely impossible, I find it very unlikely, and it looks to me as if you're overwriting the pointer with spaces.
valgrind would probably help, or some kind of memory or stack protection check.
Why this depends on the terminal, I could not say; possibly there's some TERM-depending code (such as "allocate as many rows as there are on screen"). Anyway, that is not the bug: it's only the condition bringing the bug in the open. This kind of error is usually caused by a hard-wired constant which sometimes is exceeded by the real value (in the example above I could say "allocate 96 rows", assuming that 96 rows will always be enough for everyone; whereas a TERM with 100 rows would foil the assumption and lead to a buffer overrun).
It might also be an artifact of the debugger, seeing as how c is allocated on the stack (but I don't think so: it should be c=0x7fffsomething - at least on the systems where I checked) . Anyway, I'd repeat the test like this, by making file_cpy heap dynamic:
int main() {
CELL *file_cpy = NULL;
WINDOW *mainwin;
file_cpy = malloc(sizeof(CELL));
...
file_cpy->window = mainwin;
main_dir(file_cpy);
...
free(file_cpy); // file_cpy = NULL; // we immediately return
return TRUE;
and then I'd try and tabulate the pointer's value throughout the main_dir function, in case it's overwritten.