Nimbin[12]?SDL & Graphics / sdl_runtime_compiler / commits / f00f020

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

Fix Center editor codebase With Claude

Christian Immanuel · 2026-06-06 12:24 · f00f02028c81cd1efff04eabbb0f7859671a775d

 src/states/game_state.cpp       |  49 +++++++++---
 src/systems/wall_connection.cpp | 166 ++++++++++++++++++++++++++--------------
 src/systems/wall_connection.hpp |  11 ++-
 src/wall/editor_wall.cpp        |  54 ++++++++-----
 src/wall/editor_wall.hpp        |   7 +-
 5 files changed, 194 insertions(+), 93 deletions(-)

diff --git a/src/states/game_state.cpp b/src/states/game_state.cpp
index 51308fd..d7d86e8 100644
--- a/src/states/game_state.cpp
+++ b/src/states/game_state.cpp
@@ -1177,25 +1177,52 @@ GameState::editor_PlaceLines(const DynArray<std::string>& lines)
     const double    x_wall = -static_cast<double>(ws) + 0.05;
     Wall::Transform tf     = Wall::get_Transform(Wall::Role::Editor, ws);
 
-    double       y       = static_cast<double>(ws) - 2.0;
-    const double z_start = static_cast<double>(ws) - 1.0;
+    const double lh      = ConfigWall::line_height;
+    const double gap     = 0.5;
+    const double floor_y = World::get_FloorY<double>();
+
+    size_t n_lines = 0;
+    for (const std::string& l : lines)
+        if (l.find_first_not_of(' ') != std::string::npos) n_lines++;
+    if (n_lines == 0) return;
+
+    const double bottom_y = floor_y + 1.2;
+    double y = bottom_y + static_cast<double>(n_lines - 1) * lh;
 
     for (const std::string& line : lines) {
-        double z = z_start;
+        if (line.find_first_not_of(' ') == std::string::npos)
+            continue;
+
+        struct Tok { Text3dPtr t; double half; };
+        DynArray<Tok> toks;
+        double total_w = 0.0;
 
         std::istringstream in(line);
         std::string        tk;
-
         while (in >> tk) {
             Text3dPtr t = make_Text3d(tk, Text3d::depth_wall, 0.003f,
-                                      Vec3{ x_wall, y, z }, ConfigColor::wall);
-            t->yaw       = tf.yaw;
-            Vec3 snapped = _editor_wall.on_WordPlaced(t.get(), Vec3{ x_wall, y, z });
-            t->pos       = snapped;
-            _ctx.add_WallText(Wall::Position::NegX, Wall::Role::Editor, std::move(t));
-            z = snapped.z - 0.5;
+                                      Vec3{ x_wall, y, 0.0 }, ConfigColor::wall);
+            t->yaw = tf.yaw;
+            TokenColor::apply(t.get());
+            const double half = static_cast<double>(t->half_extent);
+            total_w += 2.0 * half;
+            toks.push_back({ std::move(t), half });
+        }
+        if (toks.empty()) { y -= lh; continue; }
+        total_w += gap * static_cast<double>(toks.size() - 1);
+
+        DynArray<Text3d*> placed;
+        double cursor = total_w * 0.5;
+        for (Tok& tok : toks) {
+            const double center_z = cursor - tok.half;
+            tok.t->pos = Vec3{ x_wall, y, center_z };
+            placed.push_back(tok.t.get());
+            _ctx.add_WallText(Wall::Position::NegX, Wall::Role::Editor, std::move(tok.t));
+            cursor = center_z - tok.half - gap;
         }
-        y -= ConfigWall::line_height;
+        _editor_wall.add_PlacedLine(placed, y);
+
+        y -= lh;
     }
 }
 
diff --git a/src/systems/wall_connection.cpp b/src/systems/wall_connection.cpp
index f719601..fbf9732 100644
--- a/src/systems/wall_connection.cpp
+++ b/src/systems/wall_connection.cpp
@@ -1,6 +1,8 @@
 #include <SDL3/SDL.h>
 #include <cstdlib>
+#include <sstream>
 
+#include "c_token/token_color.hpp"
 #include "entities/camera.hpp"
 #include "entities/world.hpp"
 #include "rendering/gl_renderer.hpp"
@@ -45,7 +47,6 @@ WallConnection::end()
 {
     _target = Target::None;
     _prog_input.reset();  _prog_last.clear();
-    _render.    clear();  _linenums. clear();
     _cmdline.   reset();  _status.   clear();
     _blink = 0.0;
 }
@@ -123,7 +124,7 @@ WallConnection::lines() const
 void
 WallConnection::rebuild_Editor()
 {
-    _render.clear();
+    _rows.clear();
     _linenums.clear();
 
     const float ws = World::get_BoxScale();
@@ -139,60 +140,124 @@ WallConnection::rebuild_Editor()
 
     const auto& lines = _vim.lines();
 
-    // Pass 1: build renderables in a local left-aligned frame, find block width.
-    struct Row { Text3dPtr num; Text3dPtr txt; double txt_w; };
-    DynArray<Row> rows;
-    rows.reserve(lines.size());
+    // ── Pass 1: build line-number labels (orange), measure gutter width. ──
+    struct NumRow { Text3dPtr num; };
+    DynArray<NumRow> nums;
+    nums.reserve(lines.size());
 
     double gutter_w = 0.0;
     for (size_t i = 0; i < lines.size(); ++i) {
         Text3dPtr num = make_Text3d(std::to_string(i + 1), Text3d::depth_wall, 0.0022f,
-                                    tf.position, ConfigColor::editor);
+                                    tf.position, ConfigColor::orange);
         num->yaw = tf.yaw;
+        num->set_Color(ConfigColor::orange);   // digit would token-lookup to red; force orange
         gutter_w = SDL_max(gutter_w, 2.0 * static_cast<double>(num->half_extent));
-        Row r; r.num = std::move(num); r.txt_w = 0.0; rows.push_back(std::move(r));
+        nums.push_back({ std::move(num) });
     }
     const double gutter_gap = 0.6;
     const double code_u0    = gutter_w + gutter_gap;
 
-    double block_w = code_u0;
+    // ── Pass 2: build per-token meshes for each line; measure block width. ──
+    // We need a uniform char width per row so the block-relative cursor lands
+    // correctly. Measure each line by rendering one hidden full-line mesh just
+    // to get its total width; tokens are then laid out within that width.
+    struct BuiltRow {
+        DynArray<Text3dPtr> toks;     // colored token meshes
+        DynArray<double>    tok_half; // half-extent per token
+        double line_w   { 0.0 };      // full line render width (incl. spaces)
+        double char_w   { 0.18 };
+        double he_y     { 0.2 };
+        size_t n_chars  { 0 };
+    };
+    DynArray<BuiltRow> built;
+    built.reserve(lines.size());
+
+    double block_code_w = 0.0;
     for (size_t i = 0; i < lines.size(); ++i) {
-        const bool drawable = lines[i].find_first_not_of(' ') != std::string::npos;
+        BuiltRow br;
+        const std::string& s = lines[i];
+        br.n_chars = s.size();
+
+        // Measure the full line (spaces included) for a faithful char width.
+        const bool drawable = s.find_first_not_of(' ') != std::string::npos;
         if (drawable) {
-            Text3dPtr t = make_Text3d(lines[i], Text3d::depth_wall, 0.003f, tf.position, ConfigColor::wall);
-            t->yaw      = tf.yaw;
-            rows[i].txt_w = 2.0 * static_cast<double>(t->half_extent);
-            rows[i].txt   = std::move(t);
+            Text3dPtr meas = make_Text3d(s, Text3d::depth_wall, 0.003f, tf.position, ConfigColor::white);
+            br.line_w = 2.0 * static_cast<double>(meas->half_extent);
+            br.he_y   = static_cast<double>(meas->half_extent_y);
+            br.char_w = (s.empty() ? 0.18 : br.line_w / static_cast<double>(s.size()));
+            // meas is discarded — measurement only.
+
+            // Build per-token colored meshes.
+            std::istringstream in(s);
+            std::string tk;
+            while (in >> tk) {
+                Text3dPtr t = make_Text3d(tk, Text3d::depth_wall, 0.003f, tf.position, ConfigColor::white);
+                t->yaw = tf.yaw;
+                TokenColor::apply(t.get());
+                br.tok_half.push_back(static_cast<double>(t->half_extent));
+                br.toks.push_back(std::move(t));
+            }
         }
-        block_w = SDL_max(block_w, code_u0 + rows[i].txt_w);
+        block_code_w = SDL_max(block_code_w, br.line_w);
+        built.push_back(std::move(br));
     }
 
-    // Pass 2: center the whole block on the wall (z = block_w/2 → -block_w/2).
+    const double block_w      = code_u0 + block_code_w;
     const double z_block_left = block_w * 0.5;
+    _code_left_z = z_block_left - code_u0;   // left edge of the CODE area (char 0 starts here)
 
-    for (size_t i = 0; i < rows.size(); ++i) {
+    // ── Pass 3: place numbers + tokens, fill _rows for the cursor. ──
+    for (size_t i = 0; i < lines.size(); ++i) {
         const double row_y = _row0_y - static_cast<double>(i) * lh;
 
-        if (rows[i].num) {
-            const double he = static_cast<double>(rows[i].num->half_extent);
-            rows[i].num->pos   = tf.position;
-            rows[i].num->pos.y = row_y;
-            rows[i].num->pos.z = z_block_left - he;
-            _linenums.push_back(std::move(rows[i].num));
-        }
-        if (rows[i].txt) {
-            const double he = static_cast<double>(rows[i].txt->half_extent);
-            rows[i].txt->pos   = tf.position;
-            rows[i].txt->pos.y = row_y;
-            rows[i].txt->pos.z = z_block_left - code_u0 - he;
-            _render.push_back(std::move(rows[i].txt));
+        // line number in the gutter (right-edge of number at z_block_left)
+        if (nums[i].num) {
+            const double he = static_cast<double>(nums[i].num->half_extent);
+            nums[i].num->pos   = tf.position;
+            nums[i].num->pos.y = row_y;
+            nums[i].num->pos.z = z_block_left - he;
+            _linenums.push_back(std::move(nums[i].num));
         }
-        else {
-            _render.push_back(Text3dPtr{});
+
+        EditorRow er;
+        er.left_z   = _code_left_z;
+        er.char_w   = built[i].char_w;
+        er.he_y     = built[i].he_y;
+        er.line_x   = tf.position.x;
+        er.has_text = !built[i].toks.empty();
+
+        // Lay tokens left→right from the code-left edge, advancing by token
+        // width + one space-width so columns stay aligned with char_w.
+        const std::string& s = lines[i];
+        double cursor_z = _code_left_z;   // left edge of next token
+        size_t char_pos = 0;              // running character column
+        std::istringstream in(s);
+        std::string tk;
+        size_t ti = 0;
+        // Walk the string to honor leading/internal spaces between tokens.
+        {
+            size_t pos = 0;
+            while (ti < built[i].toks.size()) {
+                // advance over spaces
+                while (pos < s.size() && s[pos] == ' ') { cursor_z -= built[i].char_w; ++pos; ++char_pos; }
+                // place token ti at cursor_z (left edge), center it
+                const double half = built[i].tok_half[ti];
+                Text3dPtr& t = built[i].toks[ti];
+                t->pos   = tf.position;
+                t->pos.y = row_y;
+                t->pos.z = cursor_z - half;          // center = left edge − half
+                // advance cursor by the token's character count
+                size_t tok_len = 0;
+                while (pos < s.size() && s[pos] != ' ') { ++pos; ++tok_len; ++char_pos; }
+                cursor_z -= built[i].char_w * static_cast<double>(tok_len);
+                er.toks.push_back(std::move(t));
+                ++ti;
+            }
         }
+
+        _rows.push_back(std::move(er));
     }
 
-    _code_left_z = z_block_left - code_u0;
     rebuild_Cmdline();
 }
 
@@ -231,11 +296,11 @@ WallConnection::render(GlRenderer& gl, const Camera& cam, const Light& light) co
         draw_Electric     (gl, cam);
     }
     else if (_target == Target::Editor) {
-        for (const Text3dPtr& t : _render)   if (t) t->draw(gl, cam, light);
+        for (const EditorRow& er : _rows)
+            for (const Text3dPtr& t : er.toks)
+                if (t) t->draw(gl, cam, light);
         for (const Text3dPtr& n : _linenums) if (n) n->draw(gl, cam, light);
-
         if (_cmdline) _cmdline->draw(gl, cam, light);
-
         draw_EditorCursor(gl, cam, light);
         draw_Electric    (gl, cam);
     }
@@ -270,31 +335,18 @@ WallConnection::draw_EditorCursor(GlRenderer& gl, const Camera& cam, const Light
     if (SDL_fmod(_blink, 0.8) > 0.4) return;
 
     const int row = _vim.row();
-    if (row < 0 || row >= (int)_vim.lines().size()) return;
+    if (row < 0 || row >= (int)_rows.size()) return;
 
-    const float ws     = World::get_BoxScale();
-    Wall::Transform tf = Wall::get_Transform(Wall::Role::Editor, ws);
+    Wall::Transform tf = Wall::get_Transform(Wall::Role::Editor, World::get_BoxScale());
     const double lh    = ConfigWall::line_height;
+    const double row_y = _row0_y - static_cast<double>(row) * lh;
 
-    const std::string& s = _vim.lines()[(size_t)row];
-    const double row_y   = _row0_y - static_cast<double>(row) * lh;
-
-    double cw, line_x = tf.position.x, he_y = 0.2;
-    if (row < (int)_render.size() && _render[(size_t)row]) {
-        const Text3dPtr& line = _render[(size_t)row];
-        const int N = s.empty() ? 1 : (int)s.size();
-        cw     = (2.0 * static_cast<double>(line->half_extent)) / static_cast<double>(N);
-        line_x = line->pos.x;
-        he_y   = static_cast<double>(line->half_extent_y);
-    }
-    else {
-        cw = 0.18;
-    }
-
-    const int    col = _vim.col();
-    const double cz  = _code_left_z - (static_cast<double>(col) + 0.5) * cw;
+    const EditorRow& er = _rows[(size_t)row];
+    const double cw     = er.char_w;
+    const int    col    = _vim.col();
+    const double cz     = er.left_z - (static_cast<double>(col) + 0.5) * cw;
 
-    Vec3 c { line_x, row_y - (he_y + 0.06), cz };
+    Vec3 c { er.line_x, row_y - (er.he_y + 0.06), cz };
 
     Mat4 view  = buildViewMat4(cam);
     Mat4 model = buildModelMat4(c, tf.yaw);
diff --git a/src/systems/wall_connection.hpp b/src/systems/wall_connection.hpp
index 7a1b6d4..df20388 100644
--- a/src/systems/wall_connection.hpp
+++ b/src/systems/wall_connection.hpp
@@ -12,6 +12,15 @@ class Camera;
 class GlRenderer;
 struct Light;
 
+struct EditorRow {
+    DynArray<Text3dPtr> toks;     // per-token meshes, left→right (decreasing z)
+    double left_z   { 0.0 };      // z of the row's left edge (start of char 0)
+    double char_w   { 0.18 };     // mean character width in z (for cursor placement)
+    double he_y     { 0.2 };      // glyph half-height (for cursor vertical offset)
+    double line_x   { 0.0 };      // wall-plane x
+    bool   has_text { false };
+};
+
 class WallConnection
 {
 public:
@@ -55,7 +64,7 @@ private:
 
     VimBuffer _vim;
 
-    DynArray<Text3dPtr>   _render;
+    DynArray<EditorRow> _rows;
     DynArray<Text3dPtr>   _linenums;
     DynArray<std::string> _status;
 
diff --git a/src/wall/editor_wall.cpp b/src/wall/editor_wall.cpp
index 088c156..a6905ea 100644
--- a/src/wall/editor_wall.cpp
+++ b/src/wall/editor_wall.cpp
@@ -186,27 +186,6 @@ EditorWall::place_OnLine(Line& line, double new_half_z, double impact_z)
 }
 
 
-Vec3
-EditorWall::on_WordPlaced(Text3d* word, const Vec3& impact_pos)
-{
-    if (!word) return impact_pos;
-
-    Target  t      = resolve_Target(impact_pos.y);
-    Line&   target = apply_Target(t);
-
-    const double new_half_z = word->half_extent;
-    const Placement p = place_OnLine(target, new_half_z, impact_pos.z);
-
-    Vec3 snapped { impact_pos.x, target.y, p.z };
-
-    target.words.insert(
-        target.words.begin() + static_cast<std::ptrdiff_t>(p.insert_index),
-        word);
-
-    return snapped;
-}
-
-
 void
 EditorWall::on_LinePlaced(const DynArray<Text3d*>& words_in, const Vec3& impact)
 {
@@ -304,6 +283,39 @@ EditorWall::on_WordRemoved(Text3d* word)
 }
 
 
+Vec3
+EditorWall::on_WordPlaced(Text3d* word, const Vec3& impact_pos)
+{
+    if (!word) return impact_pos;
+
+    Target  t      = resolve_Target(impact_pos.y);
+    Line&   target = apply_Target(t);
+
+    const double new_half_z = word->half_extent;
+    const Placement p = place_OnLine(target, new_half_z, impact_pos.z);
+
+    Vec3 snapped { impact_pos.x, target.y, p.z };
+
+    target.words.insert(
+        target.words.begin() + static_cast<std::ptrdiff_t>(p.insert_index),
+        word);
+
+    return snapped;
+}
+
+
+void
+EditorWall::add_PlacedLine(const DynArray<Text3d*>& words, double row_y)
+{
+    if (words.empty()) return;
+    Line line;
+    line.y = row_y;
+    for (Text3d* w : words)
+        if (w) line.words.push_back(w);
+    _lines.push_back(std::move(line));
+}
+
+
 // ─────────────────────────────────────────────────────────────────────────────
 // Line numbers
 // ─────────────────────────────────────────────────────────────────────────────
diff --git a/src/wall/editor_wall.hpp b/src/wall/editor_wall.hpp
index bb88ab1..e64baa1 100644
--- a/src/wall/editor_wall.hpp
+++ b/src/wall/editor_wall.hpp
@@ -58,17 +58,18 @@ public:
     void clear  ();
     void update (double dt, const Vec3& eye_pos);
     void draw   (GlRenderer& gl, const Camera& cam, const Light& light) const;
-
+    void bounce (size_t line_id);
     void scroll(double delta) { for (Line& l : _lines) l.y += delta; }
+
     void on_LinePlaced (const DynArray<Text3d*>& words, const Vec3& impact);
     void on_WordRemoved(Text3d* word);
     Vec3 on_WordPlaced (Text3d* word, const Vec3& impact_pos);
+    void add_PlacedLine(const DynArray<Text3d*>& words, double row_y);
 
-    size_t line_Count       () const { return _lines.size(); }
     bool   row_IsEmpty(double y) const;
     double snap_RowY  (double y) const;
     double scroll_DeltaToEye(size_t line_id, double eye_y) const;
-    void   bounce           (size_t line_id);
+    size_t line_Count       () const { return _lines.size(); }
 
     DynArray<NumberHit>
            number_Hitboxes() const;