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/dyn_loader_system.cpp raw
#include <SDL3/SDL.h>
#include <dlfcn.h>
#include <string>
#include <sys/select.h>
#include "systems/dyn_loader_system.hpp"
#include "text/text.hpp"
namespace
Nimbin
{
// Where game_mod_abi.h is installed at runtime, and the build scratch dir.
static constexpr const char* MOD_INCLUDE_DIR = "/tmp/game_mod_include";
static constexpr const char* MOD_SRC_PATH = "/tmp/game_mod.c";
// ── Harness ──────────────────────────────────────────────────────────────
// Wraps the player's spawn-helper calls in a real translation unit. The
// player's tokens become the body of game_build(); box()/text() append to
// the engine-provided output array. Bounds-checked so a player can't
// overflow `out`.
std::string
DynLoaderSystem::wrap_Source(const std::string& user_body)
{
std::string s;
s += "#include \"game_mod_abi.h\"\n";
s += "#include <string.h>\n";
s += "\n";
s += "static DynObject* g_out;\n";
s += "static int g_max;\n";
s += "static int g_n;\n";
s += "\n";
s += "static void box(double x,double y,double z,"
"double hx,double hy,double hz){\n";
s += " if(g_n>=g_max) return;\n";
s += " DynObject* o=&g_out[g_n++];\n";
s += " o->kind=DYN_BOX; o->x=x;o->y=y;o->z=z;\n";
s += " o->hx=hx;o->hy=hy;o->hz=hz;\n";
s += " o->r=0.55f;o->g=0.55f;o->b=0.75f; o->text[0]=0;\n";
s += "}\n";
s += "\n";
s += "static void text(double x,double y,double z,const char* t){\n";
s += " if(g_n>=g_max) return;\n";
s += " DynObject* o=&g_out[g_n++];\n";
s += " o->kind=DYN_TEXT; o->x=x;o->y=y;o->z=z;\n";
s += " o->hx=o->hy=o->hz=0.0;\n";
s += " o->r=1.0f;o->g=1.0f;o->b=1.0f;\n";
s += " strncpy(o->text, t?t:\"\", sizeof(o->text)-1);\n";
s += " o->text[sizeof(o->text)-1]=0;\n";
s += "}\n";
s += "\n";
s += "int game_build(DynObject* out,int max){\n";
s += " g_out=out; g_max=max; g_n=0;\n";
s += " /* ---- player source begins ---- */\n";
s += user_body;
s += "\n /* ---- player source ends ---- */\n";
s += " return g_n;\n";
s += "}\n";
return s;
}
void
DynLoaderSystem::build(Text::Handler& out, const std::string& user_src, bool full_source)
{
last_build_ok = false;
out.clear();
_count = 0;
FILE* f = fopen(MOD_SRC_PATH, "w");
if (!f) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"DynLoaderSystem: cannot write %s", MOD_SRC_PATH);
push_Line(out, "internal error: cannot write source");
return;
}
const std::string src = full_source
? user_src
: wrap_Source(user_src);
fputs(src.c_str(), f);
fclose(f);
_pending_so = "/tmp/game_mod_" + std::to_string(_build_serial++) + ".so";
std::string cmd = "LC_ALL=C cc -shared -fPIC -O0 -I";
cmd += MOD_INCLUDE_DIR;
cmd += " ";
cmd += MOD_SRC_PATH;
cmd += " -o ";
cmd += _pending_so;
cmd += " 2>&1";
if (_pipe) { pclose(_pipe); _pipe = nullptr; }
_pipe = popen(cmd.c_str(), "r");
if (!_pipe)
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "DynLoaderSystem: popen failed");
}
void
DynLoaderSystem::update(Text::Handler& out)
{
if (!_pipe) return;
char buf[512];
int fd = fileno(_pipe);
fd_set set; FD_ZERO(&set); FD_SET(fd, &set);
struct timeval tv{0, 0}; // poll, never block the frame
while (select(fd + 1, &set, nullptr, nullptr, &tv) > 0) {
if (!fgets(buf, sizeof(buf), _pipe)) {
// EOF — build finished. No error lines means success.
pclose(_pipe);
_pipe = nullptr;
if (_count == 0) {
last_build_ok = true;
_last_so_path = _pending_so;
push_Line(out, "BUILD OK");
}
return;
}
std::string line(buf);
if (!line.empty() && line.back() == '\n') line.pop_back();
if (!line.empty())
push_Line(out, line);
FD_ZERO(&set); FD_SET(fd, &set);
}
}
void
DynLoaderSystem::push_Line(Text::Handler& out, const std::string& line)
{
out.append_Line(Text::ascii_Sanitize(line));
_count++;
}
DynArray<DynObject>
DynLoaderSystem::load_Objects()
{
DynArray<DynObject> result;
if (_last_so_path.empty()) return result;
if (_handle) {
dlclose(_handle);
_handle = nullptr;
}
dlerror();
_handle = dlopen(_last_so_path.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!_handle) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"DynLoaderSystem: dlopen failed: %s", dlerror());
return result;
}
using BuildFn = int(*)(DynObject*, int);
BuildFn game_build = reinterpret_cast<BuildFn>(dlsym(_handle, "game_build"));
if (!game_build) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"DynLoaderSystem: dlsym(game_build) failed: %s", dlerror());
return result;
}
constexpr int MAX_OBJ = 256;
result.resize(MAX_OBJ);
int n = game_build(result.data(), MAX_OBJ);
if (n < 0) n = 0;
if (n > MAX_OBJ) n = MAX_OBJ;
result.resize(static_cast<size_t>(n));
return result;
}
DynLoaderSystem::~DynLoaderSystem()
{
if (_handle) dlclose(_handle);
if (_pipe) pclose(_pipe);
}
} // namespace Nimbin