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.gzFixed cmdline input for vim With Claude
src/core/game.cpp | 2 +- src/rendering/gl_renderer.cpp | 24 +++++++++ src/rendering/gl_renderer.hpp | 5 +- src/states/game_state.cpp | 43 ++++++++++------ src/states/game_state.hpp | 6 +-- src/states/istate.hpp | 4 +- src/states/menu_state.cpp | 2 +- src/states/menu_state.hpp | 6 +-- src/systems/wall_connection.cpp | 72 +++++++++++++++++++++----- src/systems/wall_connection.hpp | 15 ++++-- src/text_3d/vim_buffer.hpp | 109 ++++++++++++++++++++++++++-------------- 11 files changed, 208 insertions(+), 80 deletions(-) diff --git a/src/core/game.cpp b/src/core/game.cpp index adeae78..871e286 100644 --- a/src/core/game.cpp +++ b/src/core/game.cpp @@ -72,7 +72,7 @@ Game::handle_Event(const SDL_Event& event) void Game::update(double dt_ns) { - if (m_state) m_state->update(dt_ns * 1e-9); + if (m_state) m_state->update(dt_ns * 1e-9, _gl_renderer.get()); } diff --git a/src/rendering/gl_renderer.cpp b/src/rendering/gl_renderer.cpp index 0e149d3..fb9a861 100644 --- a/src/rendering/gl_renderer.cpp +++ b/src/rendering/gl_renderer.cpp @@ -631,6 +631,13 @@ GlRenderer::end_Frame(SDL_Window* window) } } + +void +GlRenderer::clear_Depth() +{ + glClear(GL_DEPTH_BUFFER_BIT); +} + // ───────────────────────────────────────────────────────────────────────────── // Uniform setters // ───────────────────────────────────────────────────────────────────────────── @@ -721,6 +728,23 @@ GlRenderer::set_FaceShade(float r, float g, float b, float back_dim, bool use_fr } +void +GlRenderer::set_DepthTest(bool on) +{ + if (on) glEnable (GL_DEPTH_TEST); + else glDisable(GL_DEPTH_TEST); +} + + +void GlRenderer::set_CullBack(bool on) +{ + if (on) { glEnable(GL_CULL_FACE); glCullFace(GL_FRONT); } + else glDisable(GL_CULL_FACE); +} + +///////////////// +/// DRAW + void GlRenderer::draw_Crosshair_() { diff --git a/src/rendering/gl_renderer.hpp b/src/rendering/gl_renderer.hpp index 465dc99..d45c782 100644 --- a/src/rendering/gl_renderer.hpp +++ b/src/rendering/gl_renderer.hpp @@ -155,8 +155,8 @@ public: bool init(SDL_Window* window); void begin_Frame(int viewport_w, int viewport_h); void end_Frame(SDL_Window* window); + void clear_Depth(); - // ── Shader uniform setters ──────────────────────────────────────────────── void set_MVP (const Mat4& model, const Mat4& mvp); void set_NormalMat (const Mat4& model); void set_Color (float r, float g, float b); @@ -169,10 +169,13 @@ public: void set_Emissive (float r, float g, float b, float strength); void set_BloomStrength (float s) { _bloom_strength = s; } void set_Crosshair(bool on) { _crosshair_enabled = on; } + void set_DepthTest(bool on); + void set_CullBack (bool on); void enable_MandelbrotMode(bool on); void enable_WallPaperMode (bool on); + float fov_y() const { return SDL_PI_F / 3.0f; } float aspect() const; const Mat4& projection() const; diff --git a/src/states/game_state.cpp b/src/states/game_state.cpp index d7d86e8..f0d6240 100644 --- a/src/states/game_state.cpp +++ b/src/states/game_state.cpp @@ -561,17 +561,32 @@ GameState::handle_Input(FrameInput& input, double dt) { case InputCommand::ConnectPultExit: on_WallConnectExit(); break; } } + if (_wall_connection.target() == WallConnection::Target::Editor) { WallConnection::Request req = _wall_connection.feed_Vim(input.vim_keys, _ctx.camera->pos.y); input.vim_keys.clear(); - if (req == WallConnection::Request::Exit) { - editor_CommitToWall(_wall_connection.lines()); - _wall_connection.end(); - _frame_input.capture_vim = false; - } - else if (req == WallConnection::Request::EditorWrite) { - editor_CommitToWall(_wall_connection.lines()); - _wall_connection.load_Editor(editor_AbsorbLines(), _ctx.camera->pos.y); + switch (req) { + case WallConnection::Request::Write: + editor_CommitToWall(_wall_connection.lines()); + _wall_connection.mark_Saved(); + break; + case WallConnection::Request::WriteQuit: + editor_CommitToWall(_wall_connection.lines()); + _wall_connection.end(); + _frame_input.capture_vim = false; + break; + case WallConnection::Request::Exit: + editor_CommitToWall(_wall_connection.lines()); + _wall_connection.end(); + _frame_input.capture_vim = false; + break; + case WallConnection::Request::QuitDiscard: + editor_CommitToWall(_wall_connection.lines()); + _wall_connection.end(); + _frame_input.capture_vim = false; + break; + case WallConnection::Request::None: + break; } } else if (_wall_connection.target() == WallConnection::Target::Program) { @@ -603,7 +618,7 @@ GameState::update_PlayerCamera(FrameInput& input, double dt) } void -GameState::update(double dt) +GameState::update(double dt, const GlRenderer* gl) { FrameInput& input = _frame_input; @@ -637,7 +652,7 @@ GameState::update(double dt) _plate_sys-> update(dt, *_ctx.player, _level_director->step_Plates()); _editor_wall.update(dt, _ctx.camera->pos); - _wall_connection.update(dt); + _wall_connection.update(dt, *_ctx.camera, gl->aspect(), gl->fov_y()); } @@ -720,10 +735,6 @@ GameState::render(SDL_Window& window, GlRenderer& renderer) lbl.draw(renderer, *_ctx.camera, *_ctx.light); } - { FP_ZONE("render.wall_edit"); - _wall_connection.render(renderer, *_ctx.camera, *_ctx.light); - } - { FP_ZONE("render._carried"); DynArray<CarriedWordNode*> nodes = _carried.nodes(); for (const CarriedWordNode* node : nodes) @@ -761,6 +772,10 @@ GameState::render(SDL_Window& window, GlRenderer& renderer) _celebration_sys->draw(renderer, *_ctx.camera, *_ctx.light); } + { FP_ZONE("render.wall_edit"); + _wall_connection.render(renderer, *_ctx.camera, *_ctx.light); + } + { FP_ZONE("render.end_Frame"); renderer.end_Frame(&window); } } diff --git a/src/states/game_state.hpp b/src/states/game_state.hpp index 16e8b1a..fa84f45 100644 --- a/src/states/game_state.hpp +++ b/src/states/game_state.hpp @@ -137,7 +137,7 @@ class GameState : public IState public: explicit GameState (size_t starting_level = 0); ~GameState () = default; - void handle_Event (Game&, const SDL_Event&) override; - void update (double dt) override; - void render (SDL_Window&, GlRenderer&) override; + void handle_Event (Game&, const SDL_Event&) override; + void update (double dt, const GlRenderer*) override; + void render (SDL_Window&, GlRenderer&) override; }; diff --git a/src/states/istate.hpp b/src/states/istate.hpp index 4579a93..bf8b062 100644 --- a/src/states/istate.hpp +++ b/src/states/istate.hpp @@ -14,6 +14,6 @@ public: virtual void on_Exit(Game&) {} virtual void handle_Event(Game& game, const SDL_Event&) = 0; - virtual void update(double dt) = 0; - virtual void render(SDL_Window&, GlRenderer&) = 0; + virtual void update(double dt, const GlRenderer*) = 0; + virtual void render(SDL_Window&, GlRenderer&) = 0; }; diff --git a/src/states/menu_state.cpp b/src/states/menu_state.cpp index 0bba412..ed5e474 100644 --- a/src/states/menu_state.cpp +++ b/src/states/menu_state.cpp @@ -272,7 +272,7 @@ MenuState::pick_FromCrosshair() const void -MenuState::update(double dt) +MenuState::update(double dt, const GlRenderer*) { _t += dt; diff --git a/src/states/menu_state.hpp b/src/states/menu_state.hpp index a82866b..cf26887 100644 --- a/src/states/menu_state.hpp +++ b/src/states/menu_state.hpp @@ -50,7 +50,7 @@ class MenuState : public IState public: MenuState(); - void handle_Event(Game&, const SDL_Event&) override; - void update (double dt) override; - void render (SDL_Window&, GlRenderer&) override; + void handle_Event(Game&, const SDL_Event&) override; + void update (double dt, const GlRenderer*) override; + void render (SDL_Window&, GlRenderer&) override; }; diff --git a/src/systems/wall_connection.cpp b/src/systems/wall_connection.cpp index fbf9732..23b2e63 100644 --- a/src/systems/wall_connection.cpp +++ b/src/systems/wall_connection.cpp @@ -93,14 +93,16 @@ WallConnection::feed_Vim(DynArray<int>& keys, double eye_y) Request req = Request::None; switch (_vim.take_Signal()) { - case VimBuffer::Signal::Exit: return Request::Exit; - case VimBuffer::Signal::Write: req = Request::EditorWrite; break; + case VimBuffer::Signal::Exit: return Request::Exit; + case VimBuffer::Signal::WriteQuit: return Request::WriteQuit; + case VimBuffer::Signal::QuitDiscard: return Request::QuitDiscard; + case VimBuffer::Signal::Write: req = Request::Write; break; case VimBuffer::Signal::Help: _status.clear(); _status.push_back("-- VIM HELP --"); _status.push_back("i a A o O insert Esc normal h j k l move"); _status.push_back("0 $ line ends gg G :N jump line x dd delete"); - _status.push_back(":w write to wall :exit leave ctrl+WASD move"); + _status.push_back(":w write :q quit :wq write+quit :q! discard"); break; default: break; } @@ -178,7 +180,7 @@ WallConnection::rebuild_Editor() const std::string& s = lines[i]; br.n_chars = s.size(); - // Measure the full line (spaces included) for a faithful char width. + // 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 meas = make_Text3d(s, Text3d::depth_wall, 0.003f, tf.position, ConfigColor::white); @@ -261,6 +263,7 @@ WallConnection::rebuild_Editor() rebuild_Cmdline(); } + void WallConnection::rebuild_Cmdline() { @@ -273,16 +276,45 @@ WallConnection::rebuild_Cmdline() else text = (_vim.mode() == VimBuffer::Mode::Insert) ? "-- INSERT --" : "-- NORMAL --"; if (text.empty()) return; - const float ws = World::get_BoxScale(); - Wall::Transform tf = Wall::get_Transform(Wall::Role::Editor, ws); + // Built at origin; render() places it at the eased _cmd_pos each frame. + _cmdline = make_Text3d(text, Text3d::depth_wall, 0.0016f, Vec3{0,0,0}, ConfigColor::white); +} - Vec3 pos = tf.position; - pos.x += 0.8; - pos.y = -static_cast<double>(ws) + 0.8; - pos.z = 0.0; - _cmdline = make_Text3d(text, Text3d::depth_wall, 0.0028f, pos, ConfigColor::white); - _cmdline->yaw = tf.yaw; +void +WallConnection::update(double dt, const Camera& cam, float aspect, float fov_y) +{ + if (!active()) return; + _blink += dt; + + const Vec3 up = Math::cross(cam.right, cam.fwd); + + const double dist = 2.2; + const double half_h = dist * static_cast<double>(SDL_tanf(fov_y * 0.5f)); + const double half_w = half_h * static_cast<double>(aspect); + + // Sit at a fraction of the way toward the lower-left corner. 0 = center, + // 1 = exactly the frustum edge (text would be clipped at the very corner), + // so keep a margin. + const double fx = 0.82; // horizontal: 82% toward the left edge + const double fy = 0.78; // vertical: 78% toward the bottom edge + const double left = half_w * fx; + const double down = half_h * fy; + + Vec3 target { + cam.pos.x + cam.fwd.x * dist - cam.right.x * left - up.x * down, + cam.pos.y + cam.fwd.y * dist - cam.right.y * left - up.y * down, + cam.pos.z + cam.fwd.z * dist - cam.right.z * left - up.z * down, + }; + + if (!_cmd_pos_init) { _cmd_pos = target; _cmd_pos_init = true; } + + const double k = 1.0 - SDL_exp(-6.0 * dt); + _cmd_pos.x += (target.x - _cmd_pos.x) * k; + _cmd_pos.y += (target.y - _cmd_pos.y) * k; + _cmd_pos.z += (target.z - _cmd_pos.z) * k; + + _cmd_yaw = static_cast<float>(SDL_atan2(-cam.fwd.x, -cam.fwd.z)); } @@ -300,7 +332,21 @@ WallConnection::render(GlRenderer& gl, const Camera& cam, const Light& light) co 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); + + if (_cmdline) { + const double hw = static_cast<double>(_cmdline->half_extent); + _cmdline->pos = Vec3{ + _cmd_pos.x + cam.right.x * hw, + _cmd_pos.y + cam.right.y * hw, + _cmd_pos.z + cam.right.z * hw, + }; + _cmdline->yaw = _cmd_yaw; + _cmdline->pitch = 0.0f; + + gl.clear_Depth(); + _cmdline->draw(gl, cam, light); + } + draw_EditorCursor(gl, cam, light); draw_Electric (gl, cam); } diff --git a/src/systems/wall_connection.hpp b/src/systems/wall_connection.hpp index df20388..48cbc00 100644 --- a/src/systems/wall_connection.hpp +++ b/src/systems/wall_connection.hpp @@ -25,7 +25,7 @@ class WallConnection { public: enum class Target : uint8_t { None, Program, Editor }; - enum class Request : uint8_t { None, Exit, EditorWrite }; + enum class Request : uint8_t { None, Exit, Write, WriteQuit, QuitDiscard }; bool active() const { return _target != Target::None; } Target target() const { return _target; } @@ -42,15 +42,19 @@ public: DynArray<std::string> lines () const; - void update(double dt) { if (active()) _blink += dt; } + void update(double dt, const Camera& cam, float aspect, float fov_y); void render(GlRenderer& gl, const Camera& cam, const Light& light) const; + void mark_Saved() { _vim.mark_Saved(); } private: + bool _cmd_pos_init { false }; unsigned _vim_seen_version { 0 }; - double _row0_y{ 0.0 }; - double _eye_y { 0.0 }; - double _blink { 0.0 }; + float _cmd_pitch { 0.0f }; + float _cmd_yaw { 0.0f }; + double _row0_y { 0.0 }; + double _eye_y { 0.0 }; + double _blink { 0.0 }; double _code_left_z { 0.0 }; Target _target{ Target::None }; @@ -58,6 +62,7 @@ private: Text3dPtr _cmdline; Text3dPtr _prog_input; + Vec3 _cmd_pos {}; Vec3 _pult_pos{}; std::string _prog_last; diff --git a/src/text_3d/vim_buffer.hpp b/src/text_3d/vim_buffer.hpp index d8f4d39..a9dd8d2 100644 --- a/src/text_3d/vim_buffer.hpp +++ b/src/text_3d/vim_buffer.hpp @@ -8,7 +8,14 @@ class VimBuffer { public: enum class Mode : uint8_t { Normal, Insert, Command }; - enum class Signal : uint8_t { None, Exit, Help, Write }; + enum class Signal : uint8_t { + None, + Exit, + Help, + Write, + WriteQuit, + QuitDiscard + }; void load(const std::vector<std::string>& src) { _lines = src; @@ -16,8 +23,11 @@ public: _row = _col = 0; _mode = Mode::Normal; _count = 0; _pending.clear(); _cmd.clear(); _status.clear(); _sig = Signal::None; ++_version; + _saved = _lines; + _dirty = false; } + void key_char(char c) { if (_mode == Mode::Insert) { insert_Char(c); } else if (_mode == Mode::Command) { _cmd.push_back(c); touch(); } @@ -51,17 +61,22 @@ public: const std::string& cmdline() const { return _cmd; } const std::string& status() const { return _status; } Signal take_Signal() { Signal s = _sig; _sig = Signal::None; return s; } + void mark_Saved() { _saved = _lines; _dirty = false; touch(); } private: - std::vector<std::string> _lines { "" }; + bool _dirty = false; + unsigned _version = 0; int _row = 0, _col = 0; - Mode _mode = Mode::Normal; int _count = 0; - std::string _pending; // "d" or "g" awaiting second key - std::string _cmd; // command-line text (":...") + + std::string _pending; + std::string _cmd; std::string _status; + + Mode _mode = Mode::Normal; Signal _sig = Signal::None; - unsigned _version = 0; + std::vector<std::string> _saved; + std::vector<std::string> _lines { "" }; void touch() { ++_version; } int lineLen() const { return (int)_lines[(size_t)_row].size(); } @@ -73,29 +88,6 @@ private: } int eff() const { return _count > 0 ? _count : 1; } - void insert_Char(char c) { - std::string& L = _lines[(size_t)_row]; - if (_col > (int)L.size()) _col = (int)L.size(); - L.insert((size_t)_col, 1, c); ++_col; touch(); - } - void split_Line() { - std::string& L = _lines[(size_t)_row]; - std::string tail = L.substr((size_t)_col); - L.erase((size_t)_col); - _lines.insert(_lines.begin() + _row + 1, tail); - ++_row; _col = 0; touch(); - } - void backspace_Insert() { - if (_col > 0) { _lines[(size_t)_row].erase((size_t)(_col-1), 1); --_col; } - else if (_row > 0) { - int prevlen = (int)_lines[(size_t)(_row-1)].size(); - _lines[(size_t)(_row-1)] += _lines[(size_t)_row]; - _lines.erase(_lines.begin() + _row); - --_row; _col = prevlen; - } - touch(); - } - void normal_Key(char c) { if (c == ':') { _mode = Mode::Command; _cmd = ":"; _status.clear(); touch(); return; } @@ -135,28 +127,68 @@ private: _count = 0; clampCol(); touch(); } + void insert_Char(char c) { + std::string& L = _lines[(size_t)_row]; + if (_col > (int)L.size()) _col = (int)L.size(); + L.insert((size_t)_col, 1, c); ++_col; _dirty = true; touch(); + } + void split_Line() { + std::string& L = _lines[(size_t)_row]; + std::string tail = L.substr((size_t)_col); + L.erase((size_t)_col); + _lines.insert(_lines.begin() + _row + 1, tail); + ++_row; _col = 0; _dirty = true; touch(); + } + void backspace_Insert() { + if (_col > 0) { _lines[(size_t)_row].erase((size_t)(_col-1), 1); --_col; _dirty = true; } + else if (_row > 0) { + int prevlen = (int)_lines[(size_t)(_row-1)].size(); + _lines[(size_t)(_row-1)] += _lines[(size_t)_row]; + _lines.erase(_lines.begin() + _row); + --_row; _col = prevlen; _dirty = true; + } + touch(); + } void delete_Chars(int n) { std::string& L = _lines[(size_t)_row]; - for (int i = 0; i < n && _col < (int)L.size(); ++i) L.erase((size_t)_col, 1); + for (int i = 0; i < n && _col < (int)L.size(); ++i) { L.erase((size_t)_col, 1); _dirty = true; } clampCol(); } void delete_Lines(int n) { for (int i = 0; i < n && (int)_lines.size() > 0; ++i) { - if (_lines.size() == 1) { _lines[0].clear(); break; } + if (_lines.size() == 1) { _lines[0].clear(); _dirty = true; break; } _lines.erase(_lines.begin() + _row); if (_row >= (int)_lines.size()) _row = (int)_lines.size() - 1; + _dirty = true; } clampCol(); } - void open_Below() { _lines.insert(_lines.begin() + _row + 1, ""); ++_row; _col = 0; } - void open_Above() { _lines.insert(_lines.begin() + _row, ""); _col = 0; } + void open_Below() { _lines.insert(_lines.begin() + _row + 1, ""); ++_row; _col = 0; _dirty = true; } + void open_Above() { _lines.insert(_lines.begin() + _row, ""); _col = 0; _dirty = true; } void run_Command() { - std::string c = _cmd.size() ? _cmd.substr(1) : ""; // strip ':' + std::string c = _cmd.size() ? _cmd.substr(1) : ""; _cmd.clear(); _mode = Mode::Normal; - if (c == "exit" || c == "q" || c == "q!") _sig = Signal::Exit; - else if (c == "help") _sig = Signal::Help; - else if (c == "w" || c == "write") _sig = Signal::Write; + + if (c == "w" || c == "write") { + _sig = Signal::Write; + } + else if (c == "wq" || c == "x") { + _sig = Signal::WriteQuit; + } + else if (c == "q" || c == "quit") { + if (_dirty) { + _status = "No write since last change (add ! to override)"; + } else { + _sig = Signal::Exit; + } + } + else if (c == "q!" || c == "quit!") { + _lines = _saved; + _row = clampRow(_row); clampCol(); + _dirty = false; + _sig = Signal::QuitDiscard; + } else if (!c.empty() && std::all_of(c.begin(), c.end(), [](char ch){ return std::isdigit((unsigned char)ch); })) { _row = clampRow(std::stoi(c) - 1); clampCol(); @@ -165,4 +197,7 @@ private: touch(); } void cancel_Command() { _cmd.clear(); _mode = Mode::Normal; touch(); } + + bool dirty() const { return _dirty; } + };