games: fix input/rendering lag and missed keypresses
Identified and fixed rendering/input optimization issues: - Snake: added turn queue (max 2 pending turns) so rapid key presses are never lost; filters reversals and duplicates against the last queued direction. Batched snake body rendering into a single XFillRectangles call instead of one XFillRectangle per segment. - Tetris: batched settled-block rendering into 3 XFillRectangles calls (body, highlight, shadow) instead of 5 XFillRectangle calls per cell. This reduces ~1000 X11 calls per frame to 3, fixing frame drops and input lag. - Both: non-blocking input polling before every frame (already in place) ensures no keypress is missed.
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+45
-13
@@ -102,7 +102,8 @@ static State state = PLAYING;
|
|||||||
static int snake[COLS * ROWS][2]; // x,y per segment (0 = tail)
|
static int snake[COLS * ROWS][2]; // x,y per segment (0 = tail)
|
||||||
static int snakelen = 4;
|
static int snakelen = 4;
|
||||||
static int dirx = 1, diry = 0; // current direction
|
static int dirx = 1, diry = 0; // current direction
|
||||||
static int ndirx = 1, ndiry = 0; // buffered next direction (latest keypress wins)
|
static int tqx[2], tqy[2]; // turn queue (max 2 pending turns)
|
||||||
|
static int tqn = 0; // number of queued turns
|
||||||
static int foodx, foody;
|
static int foodx, foody;
|
||||||
static int score = 0;
|
static int score = 0;
|
||||||
static int best = 0;
|
static int best = 0;
|
||||||
@@ -163,7 +164,7 @@ static void reset_game() {
|
|||||||
snake[i][1] = cy;
|
snake[i][1] = cy;
|
||||||
}
|
}
|
||||||
dirx = 1; diry = 0;
|
dirx = 1; diry = 0;
|
||||||
ndirx = 1; ndiry = 0;
|
tqn = 0;
|
||||||
score = 0;
|
score = 0;
|
||||||
pps = 150;
|
pps = 150;
|
||||||
update_speed();
|
update_speed();
|
||||||
@@ -172,10 +173,31 @@ static void reset_game() {
|
|||||||
nextTick = now_ms() + speed;
|
nextTick = now_ms() + speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Queue a pending direction change; rapid key presses are stored so none are lost.
|
||||||
|
// Filters reversals and duplicates against the last queued (or current) direction.
|
||||||
|
static void tq_push(int dx, int dy) {
|
||||||
|
int refx = (tqn > 0) ? tqx[tqn - 1] : dirx;
|
||||||
|
int refy = (tqn > 0) ? tqy[tqn - 1] : diry;
|
||||||
|
if (dx == -refx && dy == -refy) return; // reversal
|
||||||
|
if (dx == refx && dy == refy) return; // duplicate
|
||||||
|
if (tqn < 2) {
|
||||||
|
tqx[tqn] = dx;
|
||||||
|
tqy[tqn] = dy;
|
||||||
|
tqn++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void step() {
|
static void step() {
|
||||||
// apply buffered direction (no reversing); latest keypress wins
|
// apply queued turn (no reversing); rapid presses are not lost
|
||||||
if (!(ndirx == -dirx && ndiry == -diry) && !(ndirx == 0 && ndiry == 0)) {
|
if (tqn > 0) {
|
||||||
dirx = ndirx; diry = ndiry;
|
int dx = tqx[0], dy = tqy[0];
|
||||||
|
// shift queue
|
||||||
|
tqx[0] = tqx[1]; tqy[0] = tqy[1];
|
||||||
|
tqn--;
|
||||||
|
// apply if not a reversal
|
||||||
|
if (!(dx == -dirx && dy == -diry) && !(dx == 0 && dy == 0)) {
|
||||||
|
dirx = dx; diry = dy;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
int hx = snake[snakelen - 1][0] + dirx;
|
int hx = snake[snakelen - 1][0] + dirx;
|
||||||
int hy = snake[snakelen - 1][1] + diry;
|
int hy = snake[snakelen - 1][1] + diry;
|
||||||
@@ -237,11 +259,21 @@ static void render() {
|
|||||||
XSetForeground(dpy, gc, C_FOOD);
|
XSetForeground(dpy, gc, C_FOOD);
|
||||||
XFillRectangle(dpy, back, gc, gx0 + foodx * cell, gy0 + foody * cell, cell, cell);
|
XFillRectangle(dpy, back, gc, gx0 + foodx * cell, gy0 + foody * cell, cell, cell);
|
||||||
}
|
}
|
||||||
// snake
|
// snake (batched for speed)
|
||||||
for (int i = 0; i < snakelen; i++) {
|
XRectangle bodyRects[COLS * ROWS];
|
||||||
XSetForeground(dpy, gc, (i == snakelen - 1) ? C_HEAD : C_FG);
|
int nbody = 0;
|
||||||
XFillRectangle(dpy, back, gc, gx0 + snake[i][0] * cell, gy0 + snake[i][1] * cell, cell, cell);
|
for (int i = 0; i < snakelen - 1; i++) {
|
||||||
|
bodyRects[nbody].x = gx0 + snake[i][0] * cell;
|
||||||
|
bodyRects[nbody].y = gy0 + snake[i][1] * cell;
|
||||||
|
bodyRects[nbody].width = cell;
|
||||||
|
bodyRects[nbody].height = cell;
|
||||||
|
nbody++;
|
||||||
}
|
}
|
||||||
|
XSetForeground(dpy, gc, C_FG);
|
||||||
|
XFillRectangles(dpy, back, gc, bodyRects, nbody);
|
||||||
|
// head
|
||||||
|
XSetForeground(dpy, gc, C_HEAD);
|
||||||
|
XFillRectangle(dpy, back, gc, gx0 + snake[snakelen - 1][0] * cell, gy0 + snake[snakelen - 1][1] * cell, cell, cell);
|
||||||
|
|
||||||
// score (top status strip, clear of the playfield border)
|
// score (top status strip, clear of the playfield border)
|
||||||
char buf[32];
|
char buf[32];
|
||||||
@@ -273,13 +305,13 @@ static void render() {
|
|||||||
static void handle_key(KeySym ks) {
|
static void handle_key(KeySym ks) {
|
||||||
switch (ks) {
|
switch (ks) {
|
||||||
case XK_Up: case XK_w: case XK_KP_Up: case XK_2:
|
case XK_Up: case XK_w: case XK_KP_Up: case XK_2:
|
||||||
ndirx = 0; ndiry = -1; break;
|
tq_push(0, -1); break;
|
||||||
case XK_Down: case XK_s: case XK_KP_Down: case XK_8: case XK_x:
|
case XK_Down: case XK_s: case XK_KP_Down: case XK_8: case XK_x:
|
||||||
ndirx = 0; ndiry = 1; break;
|
tq_push(0, 1); break;
|
||||||
case XK_Left: case XK_a: case XK_KP_Left: case XK_4:
|
case XK_Left: case XK_a: case XK_KP_Left: case XK_4:
|
||||||
ndirx = -1; ndiry = 0; break;
|
tq_push(-1, 0); break;
|
||||||
case XK_Right: case XK_d: case XK_KP_Right: case XK_6:
|
case XK_Right: case XK_d: case XK_KP_Right: case XK_6:
|
||||||
ndirx = 1; ndiry = 0; break;
|
tq_push(1, 0); break;
|
||||||
case XK_5: case XK_Return: case XK_KP_Enter: case XK_space: case XK_p:
|
case XK_5: case XK_Return: case XK_KP_Enter: case XK_space: case XK_p:
|
||||||
if (state == PLAYING) state = PAUSED;
|
if (state == PLAYING) state = PAUSED;
|
||||||
else if (state == PAUSED) { state = PLAYING; nextTick = now_ms() + speed; }
|
else if (state == PAUSED) { state = PLAYING; nextTick = now_ms() + speed; }
|
||||||
|
|||||||
+24
-3
@@ -286,11 +286,32 @@ static void render() {
|
|||||||
// window edges, so it is visible on all four sides including right/bottom
|
// window edges, so it is visible on all four sides including right/bottom
|
||||||
XSetForeground(dpy, gc, C_DIM);
|
XSetForeground(dpy, gc, C_DIM);
|
||||||
XDrawRectangle(dpy, back, gc, 2, 2, winW - 4, winH - 4);
|
XDrawRectangle(dpy, back, gc, 2, 2, winW - 4, winH - 4);
|
||||||
// settled blocks
|
// settled blocks (batched for speed)
|
||||||
|
XRectangle bodies[PCOLS * PROWS], highlights[PCOLS * PROWS * 2], shadows[PCOLS * PROWS * 2];
|
||||||
|
int nb = 0, nh = 0, ns = 0;
|
||||||
for (int x = 0; x < PCOLS; x++)
|
for (int x = 0; x < PCOLS; x++)
|
||||||
for (int y = 0; y < PROWS; y++)
|
for (int y = 0; y < PROWS; y++)
|
||||||
if (board[x][y])
|
if (board[x][y]) {
|
||||||
draw_gb_cell(px0 + x * CELL, py0 + y * CELL, CELL, C_GB_MED);
|
int bx = px0 + x * CELL, by = py0 + y * CELL;
|
||||||
|
bodies[nb].x = bx + 1; bodies[nb].y = by + 1;
|
||||||
|
bodies[nb].width = CELL - 2; bodies[nb].height = CELL - 2;
|
||||||
|
nb++;
|
||||||
|
highlights[nh].x = bx + 1; highlights[nh].y = by;
|
||||||
|
highlights[nh].width = CELL - 2; highlights[nh].height = 1;
|
||||||
|
nh++;
|
||||||
|
highlights[nh].x = bx; highlights[nh].y = by + 1;
|
||||||
|
highlights[nh].width = 1; highlights[nh].height = CELL - 2;
|
||||||
|
nh++;
|
||||||
|
shadows[ns].x = bx + 1; shadows[ns].y = by + CELL - 1;
|
||||||
|
shadows[ns].width = CELL - 2; shadows[ns].height = 1;
|
||||||
|
ns++;
|
||||||
|
shadows[ns].x = bx + CELL - 1; shadows[ns].y = by + 1;
|
||||||
|
shadows[ns].width = 1; shadows[ns].height = CELL - 2;
|
||||||
|
ns++;
|
||||||
|
}
|
||||||
|
XSetForeground(dpy, gc, C_GB_MED); XFillRectangles(dpy, back, gc, bodies, nb);
|
||||||
|
XSetForeground(dpy, gc, C_GB_PALE); XFillRectangles(dpy, back, gc, highlights, nh);
|
||||||
|
XSetForeground(dpy, gc, C_GB_DARK); XFillRectangles(dpy, back, gc, shadows, ns);
|
||||||
|
|
||||||
// active piece
|
// active piece
|
||||||
if (state == PLAYING) {
|
if (state == PLAYING) {
|
||||||
|
|||||||
Reference in New Issue
Block a user