sdl_runtime_compiler git · main
SDL3 game for running and compiling code at runtime
C++ 72.3% C 26.2%git clone https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler.gitwget https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler/archive/sdl_runtime_compiler.tar.gzsrc/systems/program_system.cpp raw
#include <SDL3/SDL.h>
#include <cerrno>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/wait.h>
#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<size_t>(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<Text::TokenPtr>& 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