浏览代码

Python Console

Vitaliy Polonski 23 小时之前
父节点
当前提交
eb32e450a3

+ 3 - 1
CMakeLists.txt

@@ -1,8 +1,9 @@
 cmake_minimum_required(VERSION 3.22)
 project(kariokaEngine LANGUAGES CXX)
 
-set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD 20)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS OFF)
 
 find_package(SDL2 REQUIRED)
 find_package(SDL2_image REQUIRED)
@@ -43,6 +44,7 @@ add_executable(kariokaEngine
     # Python
     src/scripting/PythonEngine.cpp
     src/scripting/bindings/kariokaModule.cpp
+    src/ui/PythonConsole.cpp
 
 	# Systems
 

+ 4 - 0
imgui.ini

@@ -6,3 +6,7 @@ Size=400,400
 Pos=440,110
 Size=400,500
 
+[Window][Python Console (~)]
+Pos=100,100
+Size=800,500
+

+ 13 - 0
src/Engine.cpp

@@ -85,6 +85,15 @@ void Engine::HandleEvents()
             isRunning = false;
         }
         
+        if (e.type == SDL_KEYDOWN)
+        {
+            if (e.key.keysym.sym == SDLK_BACKQUOTE)
+            {
+                pythonConsole.Toggle();
+                continue;
+            }
+        }
+        
         stateManager.HandleEvents(*this, e);
     }
 }
@@ -104,6 +113,7 @@ void Engine::Render()
     ImGui_ImplSDL2_NewFrame();
     ImGui::NewFrame();
     stateManager.RenderImGui(*this);
+    pythonConsole.Render();
     ImGui::Render();
     ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), renderer);
     
@@ -123,6 +133,9 @@ void Engine::Run()
         Update(dt);
         Render();
         
+        // Hot-reload Python scripts maybe
+        // ...
+        
         // Safe State Transition
         if (pendingNewState)
         {

+ 6 - 4
src/Engine.h

@@ -7,12 +7,11 @@
 #include <imgui_impl_sdlrenderer2.h>
 #include <entt/entt.hpp>
 #include <sol/sol.hpp>
-
 #include "EngineConfig.h"
-
 #include "states/StateManager.h"
-
 #include <pybind11/embed.h>
+#include "ui/PythonConsole.h"
+
 namespace py = pybind11;
 
 class Engine
@@ -29,6 +28,9 @@ public:
     void          StopRunning()       { isRunning = false; }
 
     static Engine& Get(); // Singleton accessor
+
+    // Python stuff
+    void RunPythonScript(const std::string& filename);
     
     // StateManager
     StateManager  stateManager;
@@ -58,8 +60,8 @@ private:
     // Python support
     void InitPython();
     void ShutdownPython();
-    void RunPythonScript(const std::string& filename);
     static Engine* s_instance;
+    PythonConsole pythonConsole;
 
     // StateManager
     std::unique_ptr<BaseState> pendingNewState = nullptr;

+ 83 - 5
src/scripting/PythonEngine.cpp

@@ -2,10 +2,12 @@
 #include <pybind11/embed.h>
 #include <filesystem>
 #include <iostream>
-#include "../Engine.h"
 #include <SDL2/SDL_log.h>
+#include <thread>
+#include <chrono>
 
 namespace py = pybind11;
+namespace fs = std::filesystem;
 
 PythonEngine& PythonEngine::Get()
 {
@@ -25,12 +27,15 @@ void PythonEngine::Init()
     
     try
     {
-        py::module_::import("sys").attr("path").attr("append")("assets/scripts");
-        SDL_Log("[Python] scripts/ path added");
+        auto sys = py::module_::import("sys");
+        sys.attr("path").attr("append")(".");
+        sys.attr("path").attr("append")("scripts");
+        sys.attr("path").attr("append")("assets/scripts");
+        SDL_Log("[Python] scripts paths added");
     }
-    catch (const py::error_already_set& e)
+    catch (...)
     {
-        SDL_Log("[Python] Error setting sys.path: %s", e.what());
+        SDL_Log("[Python] Warning: Failed to update sys.path ");
     }
     
     // Release GIL immidiately so game loop can run freely
@@ -44,6 +49,8 @@ void PythonEngine::Shutdown()
 {
     if (initialized_) { return; }
     
+    StopAutoReload();
+    
     PyEval_RestoreThread(PyGILState_GetThisThreadState());
     py::finalize_interpreter();
     initialized_ = false;
@@ -65,4 +72,75 @@ void PythonEngine::Exec(const std::string& code)
     {
         SDL_Log("[Python] Error: %s", e.what());
     }
+}
+
+// Reload single module
+bool PythonEngine::ReloadModule(const std::string& module_name)
+{
+    py::gil_scoped_acquire gil;
+    try
+    {
+        auto importlib = py::module_::import("importlib");
+        auto sys = py::module_::import("sys");
+        
+        if (py::hasattr(sys, "modules") && py::hasattr(sys.attr("modules"), module_name.c_str()))
+        {
+            auto module = sys.attr("modules")[module_name.c_str()];
+            importlib.attr("reload")(module);
+            SDL_Log("[Python] Reloaded module: %s", module_name.c_str());
+            return true;
+        }
+        else // First-time import
+        {
+            py::module_::import(module_name.c_str());
+            SDL_Log("[Python] Imported module: %s", module_name.c_str());
+            return true;
+        }
+    }
+    catch (const py::error_already_set& e)
+    {
+        SDL_Log("[Python] Reload module %s error: %s", module_name.c_str(), e.what());
+        return false;
+    }
+}
+
+void PythonEngine::ReloadAllScripts()
+{
+    SDL_Log("[Python] Reloading all scripts");
+    
+    // ReloadModule("main_menu");
+    // ReloadModule("player_controller");
+    // ...
+}
+
+void PythonEngine::StartAutoReload(float intervalSeconds)
+{
+    if (autoReloadRunning_) { return; }
+    
+    autoReloadRunning_ = true;
+    autoReloadThread_ = std::thread([this, interval = std::chrono::milliseconds(static_cast<int>(intervalSeconds * 1000))]() {
+        while (autoReloadRunning_)
+        {
+            std::this_thread::sleep_for(interval);
+            
+            py::gil_scoped_acquire gil;
+            try
+            {
+                ReloadAllScripts();
+            }
+            catch (...)
+            {
+            }
+        }
+    });
+}
+
+void PythonEngine::StopAutoReload()
+{
+    if (!autoReloadRunning_) { return; }
+    autoReloadRunning_ = false;
+    if (autoReloadThread_.joinable())
+    {
+        autoReloadThread_.join();
+    }
 }

+ 17 - 0
src/scripting/PythonEngine.h

@@ -3,6 +3,7 @@
 
 #include <pybind11/embed.h>
 #include <string>
+#include <chrono>
 #include <functional>
 
 namespace py = pybind11;
@@ -16,11 +17,27 @@ public:
     void Shutdown();
     
     void Exec(const std::string& code);
+    py::object Call(const std::string& module, const std::string& func, py::args args = {});
+    
+    // Hot-Reloading API
+    bool ReloadModule(const std::string& module_name);
+    void ReloadAllScripts();
+    
+    // Background watcher
+    void StartAutoReload(float intervalSeconds = 1.0f);
+    void StopAutoReload();
     
 private:
     PythonEngine() = default;
+    ~PythonEngine() = default;
+    
     bool initialized_ = false;
 
+    // Auto-reload stuff
+    bool autoReloadRunning_ = false;
+    std::thread autoReloadThread_;
+    std::unordered_map<std::string, std::chrono::file_clock::time_point> lastWriteTimes_;
+
 };
 
 #endif

+ 0 - 4
src/scripting/bindings/kariokaModule.cpp

@@ -24,10 +24,6 @@ PYBIND11_EMBEDDED_MODULE(karioka, m)
     engine_cls.def("RequestStateChange", &Engine::RequestStateChange);
     engine_cls.def("RequestPopState", &Engine::RequestPopState);
     
-    // SDL resources
-    engine_cls.def("GetRenderer", &Engine::GetRenderer, py::return_value_policy::reference);
-    engine_cls.def("GetWindow", &Engine::GetWindow, py::return_value_policy::reference);
-    
     // Logging
     engine_cls.def("log",  [](Engine& self, const std::string& msg) {
         SDL_Log("[PY] %s", msg.c_str());

+ 93 - 0
src/ui/PythonConsole.cpp

@@ -0,0 +1,93 @@
+#include "PythonConsole.h"
+#include <imgui.h>
+#include <SDL2/SDL.h>
+#include <iostream>
+
+PythonConsole::PythonConsole()
+{
+    AddLog("=== kariokaEngine Python Console ===");
+    AddLog("Type Python code and press ENTER");
+    AddLog("Use karioka.engine(), karioka.reload(), etc");
+    AddLog("Press ~ to toggle this console");
+}
+
+void PythonConsole::Toggle()
+{
+    showConsole = !showConsole;
+    if (showConsole)
+    {
+        reclaimFocus = true;
+    }
+}
+
+void PythonConsole::AddLog(const std::string& message)
+{
+    logLines.push_back(message);
+    scrollToBottom = true;
+}
+
+void PythonConsole::ExecuteCommand(const std::string& command)
+{
+    if (command.empty()) { return; }
+    
+    history.push_back(command);
+    historyPos = -1;
+    
+    AddLog("> " + command);
+    
+    // Execute via PythonEngine
+    try
+    {
+        PythonEngine::Get().Exec(command);
+    }
+    catch (...)
+    {
+        AddLog("[Error] Failed to execute command");
+    }
+}
+
+void PythonConsole::Render()
+{
+    if (!showConsole) { return; }
+    
+    ImGui::SetNextWindowSize(ImVec2(800, 500), ImGuiCond_FirstUseEver);
+    ImGui::SetNextWindowPos(ImVec2(100, 100), ImGuiCond_FirstUseEver);
+    
+    if (ImGui::Begin("Python Console (~)", &showConsole, ImGuiWindowFlags_NoCollapse))
+    {
+        // Log area
+        ImGui::BeginChild("ScrollingRegion", ImVec2(0, -ImGui::GetFrameHeightWithSpacing() * 2), true);
+        for (const auto& line : logLines)
+        {
+            ImGui::TextUnformatted(line.c_str());
+        }
+        if (scrollToBottom)
+        {
+            ImGui::SetScrollHereY(1.0f);
+            scrollToBottom = false;
+        }
+        ImGui::EndChild();
+        
+        // Command Input
+        ImGui::Separator();
+        bool reclaimFocus = false;
+        if (ImGui::InputText("##Input", inputBuffer, IM_ARRAYSIZE(inputBuffer),
+                             ImGuiInputTextFlags_EnterReturnsTrue))
+        {
+            std::string cmd = inputBuffer;
+            ExecuteCommand(cmd);
+            inputBuffer[0] = '\0';
+            reclaimFocus = true;
+        }
+        
+        // Autofocus when window becomes active
+        //ImGui::SetItemDefaultFocus();
+        if (reclaimFocus)
+        {
+            ImGui::SetKeyboardFocusHere(-1);
+            reclaimFocus = false;
+        }
+        
+        ImGui::End();
+    }
+}

+ 33 - 0
src/ui/PythonConsole.h

@@ -0,0 +1,33 @@
+#ifndef __UI__PYTHON_CONSOLE_H__
+#define __UI__PYTHON_CONSOLE_H__
+
+#include <string>
+#include <vector>
+#include <imgui.h>
+#include "../scripting/PythonEngine.h"
+
+class PythonConsole
+{
+public:
+    PythonConsole();
+    ~PythonConsole() = default;
+    
+    void Toggle();
+    bool IsOpen() const { return showConsole; };
+    void Render();
+    void AddLog(const std::string& message);
+
+private:
+    void ExecuteCommand(const std::string& command);
+    
+    bool showConsole = false;
+    char inputBuffer[256] = {0};
+    std::vector<std::string> logLines;
+    std::vector<std::string> history;
+    int historyPos = -1;
+    bool scrollToBottom = true;
+    bool reclaimFocus = false;
+
+};
+
+#endif