Nimbin[12]?SDL & Graphics / sdl_runtime_compiler / commits / 6a2f534

sdl_runtime_compiler git · main

SDL3 game for running and compiling code at runtime

sdl3 c++ compiler dlopen cmake · first commit 2026-04-19 · last commit 2026-07-03 (3 months ago) · synced 3 days ago · upstream: git.ide3.de/hsnr/sdl-runtime-compiler

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.gz

Add Program-Run pult With Claude

Christian Immanuel · 2026-06-02 08:29 · 6a2f5348c0c5ff7de611d247c3c14d122d1894b9

 src/entities/pult.hpp          |  3 +-
 src/states/game_state.cpp      | 15 +++++++
 src/states/game_state.hpp      |  3 ++
 src/systems/program_system.cpp | 94 ++++++++++++++++++++++++++++++++++++++++++
 src/systems/program_system.hpp | 25 +++++++++++
 src/systems/pult_system.cpp    | 33 ++++++---------
 src/systems/pult_system.hpp    | 10 ++---
 7 files changed, 157 insertions(+), 26 deletions(-)

diff --git a/src/entities/pult.hpp b/src/entities/pult.hpp
index 011f9c1..4f96c54 100644
--- a/src/entities/pult.hpp
+++ b/src/entities/pult.hpp
@@ -17,6 +17,7 @@ enum class PultAction : uint8_t {
     ScrollEditorDown,
     ClearEditor,
     Compile,
+    RunProgram,
 };
 
 
@@ -26,7 +27,7 @@ struct PultButton final : public SolidBox
     PultAction  action;
 
     bool   pressed     { false };
-    bool   player_on   { false };   // player currently standing on it (edge-detect)
+    bool   player_on   { false };
     double press_timer { 0.0   };
     static constexpr double
            PRESS_DUR   = 0.18;
diff --git a/src/states/game_state.cpp b/src/states/game_state.cpp
index affbd72..7e9483c 100644
--- a/src/states/game_state.cpp
+++ b/src/states/game_state.cpp
@@ -37,6 +37,7 @@ GameState::GameState(size_t starting_level)
     _physics_sys     = cpputils::make_unique<PhysicsSystem>    ();
     _projectiles_sys = cpputils::make_unique<ProjectileSystem> ();
     _compiler_sys    = cpputils::make_unique<CompilerSystem>   ();
+    _program_sys     = cpputils::make_unique<ProgramSystem>    ();
     _word_anim_sys   = cpputils::make_unique<WordAnimSystem>   ();
     _freelook_sys    = cpputils::make_unique<FreelookSystem>   ();
     _particle_sys    = cpputils::make_unique<ParticleSystem>   ();
@@ -193,6 +194,9 @@ GameState::pult_DispatchAction(PultAction act)
         case PultAction::Compile:
             on_Compile();
             break;
+        case PultAction::RunProgram:
+            on_Run();
+            break;
     }
 }
 
@@ -346,6 +350,13 @@ void GameState::on_Compile() {
 }
 
 
+void
+GameState::on_Run()
+{
+    _program_sys->run(_ctx.wall_texts, _ctx.world->scale());
+}
+
+
 void
 GameState::on_DebugPrint()
 {
@@ -643,6 +654,10 @@ GameState::update(double dt)
         }
     }
 
+    { FP_ZONE("update._program_sys");
+        _program_sys->update(_ctx.wall_texts, _ctx.world->scale());
+    }
+
     { FP_ZONE("update.goals");
         _celebration_sys->tick(dt, *_particle_sys);
         level_TryAdvance();
diff --git a/src/states/game_state.hpp b/src/states/game_state.hpp
index 3c8ffe5..a61e501 100644
--- a/src/states/game_state.hpp
+++ b/src/states/game_state.hpp
@@ -20,6 +20,7 @@
 #include "systems/particle_system.hpp"
 #include "systems/physics_system.hpp"
 #include "systems/plate_system.hpp"
+#include "systems/program_system.hpp"
 #include "systems/projectile_system.hpp"
 #include "systems/pult_system.hpp"
 #include "systems/word_ai_system.hpp"
@@ -86,6 +87,7 @@ class GameState : public IState
     cpputils::unique_ptr<ProjectileSystem> _projectiles_sys;
     cpputils::unique_ptr<WordAnimSystem>   _word_anim_sys;
     cpputils::unique_ptr<CompilerSystem>   _compiler_sys;
+    cpputils::unique_ptr<ProgramSystem>    _program_sys;
     cpputils::unique_ptr<FreelookSystem>   _freelook_sys;
     cpputils::unique_ptr<PultSystem>       _pult_sys;
     cpputils::unique_ptr<HintSystem>       _hint_sys;
@@ -108,6 +110,7 @@ class GameState : public IState
     void on_FreelookStart();
     void on_FreelookEnd  ();
     void on_Compile      ();
+    void on_Run          ();
     void on_NumberAdjust (int dt);
     void on_TextStart    (FrameInput&);
     void on_TextCommit   (FrameInput&);
diff --git a/src/systems/program_system.cpp b/src/systems/program_system.cpp
new file mode 100644
index 0000000..4bb5699
--- /dev/null
+++ b/src/systems/program_system.cpp
@@ -0,0 +1,94 @@
+#include <SDL3/SDL.h>
+#include <algorithm>
+#include <unistd.h>
+#include <sys/select.h>
+
+#include "systems/program_system.hpp"
+#include "text_3d/text_3d.hpp"
+#include "utils/ascii.hpp"
+
+
+static const char* kProgramPath = "/tmp/game_prog";
+
+
+void
+ProgramSystem::run(TextWallList& texts, float world_scale)
+{
+    // Clear previous output from the Program wall.
+    texts.erase(
+        std::remove_if(texts.begin(), texts.end(),
+            [](const TextWallPtr& wt) {
+                return wt->wall_position == Wall::position_of(Wall::Role::Program);
+            }),
+        texts.end());
+    _count = 0;
+
+    if (_pipe) { pclose(_pipe); _pipe = nullptr; }
+
+    // Nothing built yet — tell the player to compile first.
+    if (access(kProgramPath, X_OK) != 0) {
+        push_Line(texts, "no program - compile first", world_scale);
+        return;
+    }
+
+    // stderr → stdout so runtime messages show up too.
+    _pipe = popen("LC_ALL=C /tmp/game_prog 2>&1", "r");
+    if (!_pipe) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ProgramSystem: popen failed");
+}
+
+
+void
+ProgramSystem::update(TextWallList& texts, float world_scale)
+{
+    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 — program finished.
+            pclose(_pipe);
+            _pipe = nullptr;
+            if (_count == 0)
+                push_Line(texts, "(no output)", world_scale);
+            return;
+        }
+        std::string line(buf);
+        if (!line.empty() && line.back() == '\n') line.pop_back();
+        if (!line.empty())
+            push_Line(texts, line, world_scale);
+
+        FD_ZERO(&set); FD_SET(fd, &set);
+    }
+}
+
+
+void
+ProgramSystem::push_Line(TextWallList& texts, const std::string& line, float world_scale)
+{
+    std::string clean = ascii_Sanitize(line);
+
+    // Shift existing program-wall lines up so new output appears at the bottom.
+    for (TextWallPtr& wt : texts)
+        if (wt->role == Wall::Role::Program)
+            wt->text3d->pos.y += static_cast<double>(line_height);
+
+    Wall::Transform tf = Wall::get_Transform(Wall::Role::Program, world_scale);
+    Vec3 pos = tf.position;
+    pos.y = start_y;
+
+    // Program wall is light ivory → use a dark text colour for contrast.
+    Text3dPtr t = make_Text3d(clean, Text3d::depth_wall, 0.003f,
+                              pos, Text3d::color_dark_gray);
+    t->yaw = tf.yaw;
+
+    texts.push_back(cpputils::make_unique<TextWall>(TextWall{
+        tf.wall_position,
+        Wall::Role::Program,
+        std::move(t)
+    }));
+    _count++;
+}
diff --git a/src/systems/program_system.hpp b/src/systems/program_system.hpp
new file mode 100644
index 0000000..b550221
--- /dev/null
+++ b/src/systems/program_system.hpp
@@ -0,0 +1,25 @@
+#pragma once
+#include <string>
+#include <cstdio>
+
+#include "utils/globals.hpp"
+#include "wall/text_wall.hpp"
+
+// ProgramSystem: runs the executable built by CompilerSystem (/tmp/game_prog)
+// and streams its stdout/stderr onto the Program wall (+X). Mirrors
+// CompilerSystem's non-blocking pipe pattern so it never stalls the frame.
+struct ProgramSystem
+{
+private:
+    FILE* _pipe  {nullptr};
+    int   _count {0};
+
+    void push_Line(TextWallList&, const std::string& line, float world_scale);
+public:
+    static inline float line_height = 0.4f;
+    static inline float start_y     = -WORLD_BOX_SCALE + 1;
+
+    void run    (TextWallList&, float world_scale);
+    void update (TextWallList&, float world_scale);
+    bool is_Running() const { return _pipe != nullptr; }
+};
diff --git a/src/systems/pult_system.cpp b/src/systems/pult_system.cpp
index c42ae46..2a6a6c7 100644
--- a/src/systems/pult_system.cpp
+++ b/src/systems/pult_system.cpp
@@ -36,12 +36,9 @@ PultSystem::init(DynArray<GameObjectPtr>& scenery,
     };
 
     auto spawn_pult = [&](const std::string& name,
-                          Wall::Role role, float along,
-                          float half_w = Pult::W,
-                          float half_d = Pult::D) -> Pult*
+                          Wall::Role role, float along) -> Pult*
     {
-        auto owned = cpputils::make_unique<Pult>(name, make_pult_pos(role, along),
-                                                 half_w, half_d);
+        auto owned = cpputils::make_unique<Pult>(name, make_pult_pos(role, along));
         Pult* raw  = owned.get();
         _pults.push_back(raw);
         scenery.push_back(std::move(owned));
@@ -64,18 +61,18 @@ PultSystem::init(DynArray<GameObjectPtr>& scenery,
         scenery.push_back(std::move(owned));
     };
 
-    // The two buttons sit side-by-side along Z, so this pult is twice as deep
-    // (its visible width facing the editor wall) and the buttons get a wider gap.
-    Pult* scroll = spawn_pult("Scroll", Wall::Role::Editor, lane_1,
-                              Pult::W, Pult::D * 2.0f);
-    add_btn(scroll, "UP",   PultAction::ScrollEditorUp,   0.0f, -0.46f);
-    add_btn(scroll, "DOWN", PultAction::ScrollEditorDown, 0.0f,  0.46f);
+    Pult* scroll  = spawn_pult("Scroll",  Wall::Role::Editor,   lane_1);
+    add_btn(scroll,  "UP",    PultAction::ScrollEditorUp,   0.0f, -0.42f);
+    add_btn(scroll,  "DOWN",  PultAction::ScrollEditorDown, 0.0f,  0.42f);
 
-    Pult* clr = spawn_pult("Clear", Wall::Role::Editor, lane_2);
-    add_btn(clr, "CLEAR", PultAction::ClearEditor, 0.0f, 0.0f);
+    Pult* clr     = spawn_pult("Clear",   Wall::Role::Editor,   lane_2);
+    add_btn(clr,     "CLEAR", PultAction::ClearEditor,      0.0f, 0.0f);
 
     Pult* compile = spawn_pult("Compile", Wall::Role::Compiler, lane_3);
-    add_btn(compile, "RUN", PultAction::Compile, 0.0f, 0.0f);
+    add_btn(compile, "BUILD", PultAction::Compile,          0.0f, 0.0f);
+
+    Pult* run     = spawn_pult("Run",     Wall::Role::Program,  lane_2);
+    add_btn(run,     "RUN",   PultAction::RunProgram,       0.0f, 0.0f);
 }
 
 
@@ -126,9 +123,7 @@ player_OnButton(const Player& p, const PultButton& b)
     if (std::fabs(p.pos.x - b.pos.x) > hx + TOL) return false;
     if (std::fabs(p.pos.z - b.pos.z) > hz + TOL) return false;
 
-    // On/above the top face, not sailing far overhead. No tight lower bound is
-    // needed: being over the footprint already implies on-top (can't be beside
-    // it — that space is solid pult), so this only rejects a high fly-over.
+    // On/above the top face, not sailing far overhead.
     const double dy = p.pos.y - (b.pos.y + hy);
     return dy >= -0.10 && dy <= 1.8;
 }
@@ -139,12 +134,10 @@ PultSystem::handle_PlayerContact(const Player&                    player,
                                  const DynArray<PultButton*>&  buttons)
 {
     std::optional<PultAction> fired;
-
     for (PultButton* btn : buttons) {
         const bool over = player_OnButton(player, *btn);
 
-        // Rising edge only: fire once when the player lands, not every frame
-        // they keep standing on it.
+        // Rising edge only: fire once on landing, not every frame stood on it.
         if (over && !btn->player_on) {
             btn->press();
             if (!fired) fired = btn->action;
diff --git a/src/systems/pult_system.hpp b/src/systems/pult_system.hpp
index ba1d11b..92d5079 100644
--- a/src/systems/pult_system.hpp
+++ b/src/systems/pult_system.hpp
@@ -19,16 +19,16 @@ class PultSystem
 public:
     void init(DynArray<GameObjectPtr>& scenery,
               DynArray<PultButton*>&   buttons_out,
-              float                       world_scale);
+              float                    world_scale);
     std::optional<PultAction>
-         try_Activate(GameObject*                     obj,
+         try_Activate(GameObject* obj,
                       const DynArray<PultButton*>& buttons);
     std::optional<PultAction>
-         handle_ProjectileContact(const ProjectileContactEvent&    ev,
+         handle_ProjectileContact(const ProjectileContactEvent& ev,
                                   const DynArray<PultButton*>&  buttons);
     std::optional<PultAction>
-         handle_PlayerContact(const Player&                    player,
-                              const DynArray<PultButton*>&  buttons);
+         handle_PlayerContact(const Player& player,
+                              const DynArray<PultButton*>& buttons);
     void update(double dt, const Camera& camera);
     void draw_Labels(GlRenderer& gl, const Camera& camera, const Light& light) const;