refactor: improve TUI polling, FIFO reliability, and add stress tests

This commit is contained in:
Loic Coenen
2026-05-23 12:29:13 +00:00
committed by Loic Coenen (aider)
parent 7c289e1496
commit d6bd31fed5
6 changed files with 418 additions and 122 deletions

View File

@@ -1,3 +1,4 @@
#define _POSIX_C_SOURCE 199309L
#include "tui.h"
#include <ncurses.h>
#include <string.h>
@@ -6,9 +7,11 @@
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#include <dirent.h>
#include <sys/stat.h>
#include <time.h>
#include <math.h>
#include "carla_host.h"
#include "client_cmd.h"
@@ -23,13 +26,27 @@ static bool debug_mode = false;
/* ---------- FIFO command helper ---------- */
int send_command(const char *cmd) {
if (debug_mode) {
if (debug_mode)
fprintf(stderr, "DEBUG: send_command(%s)\n", cmd);
}
const char *fifo_path = getenv("LOOPER_CMD_FIFO");
if (!fifo_path) fifo_path = "/tmp/looper_cmd";
int fd = open(fifo_path, O_WRONLY | O_NONBLOCK);
// Retry open up to 5 times with a short sleep, blocking mode
int fd = -1;
for (int attempt = 0; attempt < 5 && fd < 0; attempt++) {
fd = open(fifo_path, O_WRONLY); // blocking waits for reader
if (fd < 0) {
if (errno == ENXIO && attempt < 4)
{
struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 };
nanosleep(&ts, NULL);
}
else
break;
}
}
if (fd < 0) return -1;
size_t len = strlen(cmd);
int n = write(fd, cmd, len);
if (n == (int)len && cmd[len-1] != '\n')
@@ -235,43 +252,39 @@ static char colon_buf[256];
static int colon_len = 0;
static bool in_colon = false;
void tui_run(void) {
draw_grid();
while (1) {
/* read any available status lines */
int fd = open(STATUS_FIFO, O_RDONLY | O_NONBLOCK);
if (fd >= 0) {
char buf[256];
int n = read(fd, buf, sizeof(buf)-1);
if (n > 0) {
buf[n] = '\0';
char *line = buf;
while (*line) {
char *nl = strchr(line, '\n');
if (nl) *nl = '\0';
int ch, sc;
ChannelState st;
if (parse_status_line(line, &ch, &sc, &st)) {
int idx = sc * GRID_COLS + ch;
if (idx >= 0 && idx < GRID_ROWS * GRID_COLS) {
log_msg("DIAG status: line=\"%s\" ch=%d sc=%d st=%d idx=%d", line, ch, sc, (int)st, idx);
cell_state[idx] = st;
} else {
log_msg("DIAG status out of range: line=\"%s\" ch=%d sc=%d idx=%d", line, ch, sc, idx);
}
} else {
log_msg("DIAG status parse failed: \"%s\"", line);
}
if (nl) {
*nl = '\n';
line = nl + 1;
} else break;
/* Read the status FIFO once and update cell_state array */
static void tui_read_status(void) {
int fd = open(STATUS_FIFO, O_RDONLY | O_NONBLOCK);
if (fd < 0) return;
char buf[256];
int n = read(fd, buf, sizeof(buf)-1);
if (n > 0) {
buf[n] = '\0';
char *line = buf;
while (*line) {
char *nl = strchr(line, '\n');
if (nl) *nl = '\0';
int ch, sc; ChannelState st;
if (parse_status_line(line, &ch, &sc, &st)) {
if (ch >= 0 && ch < GRID_COLS && sc >= 0 && sc < GRID_ROWS) {
int idx = sc * GRID_COLS + ch;
cell_state[idx] = st;
}
}
close(fd);
if (nl) { *nl = '\n'; line = nl + 1; } else break;
}
}
close(fd);
}
/* Check if engine is alive by testing existence of status FIFO */
void tui_run(void) {
draw_grid();
nodelay(stdscr, TRUE); // nonblocking input getch returns ERR when no key is pressed
while (1) {
/* read status FIFO once per iteration always */
tui_read_status();
/* Check if engine is alive */
engine_running = (access(STATUS_FIFO, F_OK) == 0);
/* read any available note events (for script macros) */
@@ -296,16 +309,16 @@ void tui_run(void) {
close(nfd);
}
/* Immediately redraw the grid so status changes appear without waiting for next keypress */
/* redraw grid (status may have changed no extra key needed) */
draw_grid();
int chc = getch();
if (in_colon) {
int chc = getch();
if (chc == '\n') {
colon_buf[colon_len] = '\0';
colon_len = 0;
in_colon = false;
// Check first token before calling handle_client_command
char cmd_copy[256];
strncpy(cmd_copy, colon_buf, sizeof(cmd_copy)-1);
cmd_copy[sizeof(cmd_copy)-1] = '\0';
@@ -336,10 +349,10 @@ void tui_run(void) {
clrtoeol();
move(LINES-1, colon_len+1);
refresh();
napms(50);
continue;
}
int chc = getch();
if (chc == ':') {
in_colon = true;
colon_len = 0;
@@ -350,6 +363,7 @@ void tui_run(void) {
refresh();
continue;
}
switch (chc) {
case 'h': case KEY_LEFT: selected_col = (selected_col-1+GRID_COLS)%GRID_COLS; break;
case 'j': case KEY_DOWN: selected_row = (selected_row+1)%GRID_ROWS; break;
@@ -358,13 +372,19 @@ void tui_run(void) {
case 't': {
char cmd[32];
log_msg("DIAG t pressed: selected_row=%d selected_col=%d", selected_row, selected_col);
// channel = col, scene = row
// First bind to the selected channel so engine knows which channel to operate on
snprintf(cmd, sizeof(cmd), "bind %d\n", selected_col);
send_command(cmd);
log_msg("DIAG sent: %s", cmd);
// Then set the scene for that channel
snprintf(cmd, sizeof(cmd), "set_scene %d %d\n", selected_col, selected_row);
send_command(cmd);
log_msg("DIAG sent: %s", cmd);
// Finally trigger record
snprintf(cmd, sizeof(cmd), "record %d\n", selected_col);
send_command(cmd);
log_msg("DIAG sent: %s", cmd);
// tui_read_status already called at top of loop
break;
}
case 's':
@@ -387,7 +407,6 @@ void tui_run(void) {
break;
case 'b': {
char cmd[16];
// channel = col, scene = row
snprintf(cmd, sizeof(cmd), "set_scene %d %d\n", selected_col, selected_row);
send_command(cmd);
snprintf(cmd, sizeof(cmd), "bind %d\n", selected_col);
@@ -408,6 +427,9 @@ void tui_run(void) {
break;
}
return;
case ERR:
/* no key pressed just continue the loop */
break;
default:
if (rack_mode) {
switch (chc) {
@@ -427,7 +449,6 @@ void tui_run(void) {
break;
case 'b': case 'B':
plugin_set_bypass(rack_selected, true);
// toggle would be better, but for now just enable bypass
break;
case 'd': case 'D':
plugin_unload(rack_selected);
@@ -444,7 +465,7 @@ void tui_run(void) {
}
break;
}
draw_grid();
napms(50); // avoid busywaste grid redraws frequently enough
}
}