#include #include #include #include #include #include #include #include "entities/world.hpp" #include "systems/program_system.hpp" #include "text/text.hpp" #include "utils/config.hpp" namespace Nimbin { static const char* kProgramPath = "/tmp/game_prog"; ProgramSystem::~ProgramSystem() { close_Child(); } void ProgramSystem::close_Child() { if (_in_fd >= 0) { ::close(_in_fd); _in_fd = -1; } if (_out_fd >= 0) { ::close(_out_fd); _out_fd = -1; } if (_child > 0) { int status; if (waitpid(_child, &status, WNOHANG) == 0) { kill(_child, SIGKILL); waitpid(_child, &status, 0); } _child = -1; } _pending.clear(); } void ProgramSystem::run(Text::Handler& out) { out.clear(); _count = 0; close_Child(); if (access(kProgramPath, X_OK) != 0) { push_Line(out, "no program - compile first"); return; } signal(SIGPIPE, SIG_IGN); int in_pipe[2] { -1, -1 }; // parent writes [1] → child reads [0] (stdin) int out_pipe[2] { -1, -1 }; // child writes [1] → parent reads [0] (stdout) if (pipe(in_pipe) != 0 || pipe(out_pipe) != 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ProgramSystem: pipe() failed"); return; } pid_t pid = fork(); if (pid < 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ProgramSystem: fork() failed"); ::close(in_pipe[0]); ::close(in_pipe[1]); ::close(out_pipe[0]); ::close(out_pipe[1]); return; } if (pid == 0) { // ── Child ──────────────────────────────────────────────────────── dup2(in_pipe[0], STDIN_FILENO); dup2(out_pipe[1], STDOUT_FILENO); dup2(out_pipe[1], STDERR_FILENO); ::close(in_pipe[0]); ::close(in_pipe[1]); ::close(out_pipe[0]); ::close(out_pipe[1]); // stdbuf -oL -eL line-buffers the child so a prompt printed before it // blocks on input still reaches the wall. LC_ALL=C for stable output. execl("/bin/sh", "sh", "-c", "LC_ALL=C stdbuf -oL -eL /tmp/game_prog", (char*)nullptr); _exit(127); // exec failed } // ── Parent ───────────────────────────────────────────────────────────── ::close(in_pipe[0]); // we only write the child's stdin ::close(out_pipe[1]); // we only read the child's stdout _in_fd = in_pipe[1]; _out_fd = out_pipe[0]; _child = pid; // Non-blocking both ways so neither read nor write can stall the frame. for (int fd : { _out_fd, _in_fd }) { int fl = fcntl(fd, F_GETFL, 0); if (fl != -1) fcntl(fd, F_SETFL, fl | O_NONBLOCK); } } void ProgramSystem::update(Text::Handler& out) { if (_out_fd < 0) return; char buf[512]; fd_set set; struct timeval tv{0, 0}; // poll, never block the frame for (;;) { FD_ZERO(&set); FD_SET(_out_fd, &set); int s = select(_out_fd + 1, &set, nullptr, nullptr, &tv); if (s <= 0) break; // nothing ready this frame ssize_t n = read(_out_fd, buf, sizeof(buf) - 1); if (n > 0) { buf[n] = '\0'; _pending += buf; // Emit each complete line; keep any trailing partial for next read. size_t nl; while ((nl = _pending.find('\n')) != std::string::npos) { std::string line = _pending.substr(0, nl); _pending.erase(0, nl + 1); if (!line.empty() && line.back() == '\r') line.pop_back(); push_Line(out, line); } } else if (n == 0) { // EOF — program finished. Flush any partial line and reap. if (!_pending.empty()) { push_Line(out, _pending); _pending.clear(); } if (_count == 0) push_Line(out, "(no output)"); close_Child(); return; } else { break; // EAGAIN / no data right now (or a read error) } } } void ProgramSystem::feed_Input(const std::string& text) { if (_in_fd < 0) return; // nothing is running std::string line = text; line += '\n'; // terminate the line so scanf/cin/getline unblock size_t off = 0; while (off < line.size()) { ssize_t w = write(_in_fd, line.data() + off, line.size() - off); if (w > 0) { off += static_cast(w); continue; } break; // EAGAIN (pipe full) or EPIPE (child closed stdin) → give up } } void ProgramSystem::echo_Input(Text::Handler& out, const std::string& line) { push_Line(out, line); // scrolls up, lands at the prompt row } void ProgramSystem::echo_Words(Text::Handler& out, DynArray& words) { if (words.empty()) return; // The flown word meshes are discarded; we echo their joined text as one // output line (append handles the scroll + colour/placement). std::string line; for (const Text::TokenPtr& w : words) { if (!w || w->type != ObjectType::Text) continue; if (!line.empty()) line += ' '; line += w->text; } words.clear(); if (!line.empty()) { out.append_Line(line); _count++; } } void ProgramSystem::push_Line(Text::Handler& out, const std::string& line) { out.append_Line(Text::ascii_Sanitize(line)); _count++; } } // namespace Nimbin