#include #include #include #include #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 \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 DynLoaderSystem::load_Objects() { DynArray 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(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(n)); return result; } DynLoaderSystem::~DynLoaderSystem() { if (_handle) dlclose(_handle); if (_pipe) pclose(_pipe); } } // namespace Nimbin