Vitaliy Polonski 1 päivä sitten
vanhempi
commit
6b059dae4c

+ 4 - 0
CMakeLists.txt

@@ -40,6 +40,10 @@ add_executable(kariokaEngine
 	src/states/StateManager.cpp
 	src/states/MainMenuState.cpp
 
+    # Python
+    src/scripting/PythonEngine.cpp
+    src/scripting/bindings/kariokaModule.cpp
+
 	# Systems
 
 	# Components

+ 32 - 19
src/Engine.cpp

@@ -3,6 +3,11 @@
 #include <SDL2/SDL_log.h>
 #include <SDL2/SDL_mixer.h>
 
+#include "scripting/PythonEngine.h"
+#include "scripting/bindings/kariokaModule.h"
+
+Engine* Engine::s_instance = nullptr;
+
 Engine::Engine()
 {
     // Init systems
@@ -14,7 +19,6 @@ Engine::Engine()
     // Load assets ...
     
     // Run MainMenuState
-    //stateManager.ChangeState(std::make_unique<MainMenuState>(), *this);
     stateManager.PushState(std::make_unique<MainMenuState>(), *this);
 }
 
@@ -135,30 +139,25 @@ void Engine::Run()
 
 void Engine::InitPython()
 {
-    if (pythonInterpreter) { return; }
-    
-    try
-    {
-        pythonInterpreter = std::make_unique<py::scoped_interpreter>();
-        
-        py::module_ sys = py::module_::import("sys");
-        auto path = sys.attr("path").cast<py::list>();
-        SDL_Log("Python interpreter initialized");
-        RunPythonScript("assets/scripts/init.py");
-    }
-    catch (const py::error_already_set& e)
+    if (s_instance != nullptr)
     {
-        SDL_Log("Python initialization failed: %s", e.what());
+        throw std::runtime_error("Engine instance already created! Only one Engine allowed");
     }
+    s_instance = this;
+    
+    PythonEngine::Get().Init();
+    
+    PythonEngine::Get().Exec(R"(
+        import karioka
+        e = karioka.engine()
+        e.DebugInfo()
+        karioka.log(\"Python module for kariokaEngine initialized\")
+    )", *this);
 }
 
 void Engine::ShutdownPython()
 {
-    if (pythonInterpreter)
-    {
-        pythonInterpreter.reset();
-        SDL_Log("Python interpreter shut down");
-    }
+    PythonEngine::Get().Shutdown();
 }
 
 void Engine::RunPythonScript(const std::string& filename)
@@ -183,4 +182,18 @@ void Engine::RequestPopState()
 {
     pendingPop = true;
     pendingNewState = nullptr;
+}
+
+Engine& Engine::Get()
+{
+    if (s_instance == nullptr)
+    {
+        throw std::runtime_error("Engine not yet constructed. Create Engine object in main() first");
+    }
+    return *s_instance;
+}
+
+void Engine::DebugInfo()
+{
+    SDL_Log("kariokaEngine v0.1 DebugInfo()");
 }

+ 7 - 0
src/Engine.h

@@ -27,6 +27,8 @@ public:
     SDL_Window*   GetWindow()   const { return window; }
     bool          IsRunning()   const { return isRunning; }
     void          StopRunning()       { isRunning = false; }
+
+    static Engine& Get(); // Singleton accessor
     
     // StateManager
     StateManager  stateManager;
@@ -35,6 +37,9 @@ public:
     void RequestStateChange(std::unique_ptr<BaseState> newState);
     void RequestPopState();
     
+    // Debug
+    void DebugInfo();
+    
 private:
     SDL_Window*   window    = nullptr;
     SDL_Renderer* renderer  = nullptr;
@@ -55,10 +60,12 @@ private:
     void ShutdownPython();
     void RunPythonScript(const std::string& filename);
     std::unique_ptr<py::scoped_interpreter> pythonInterpreter;
+    static Engine* s_instance;
 
     // StateManager
     std::unique_ptr<BaseState> pendingNewState = nullptr;
     bool pendingPop = false;
+
 };
 
 #endif

+ 1 - 0
src/main.cpp

@@ -5,5 +5,6 @@ int main(int argc, char* argv[])
 {
     Engine engine;
     engine.Run();
+
     return 0;
 }

+ 44 - 0
src/scripting/PythonEngine.cpp

@@ -0,0 +1,44 @@
+#include "PythonEngine.h"
+#include <pybind11/embed.h>
+#include <filesystem>
+#include <iostream>
+#include "../Engine.h"
+
+namespace py = pybind11;
+
+PythonEngine& PythonEngine::Get()
+{
+    static PythonEngine instance;
+    return instance;
+}
+
+void PythonEngine::Init()
+{
+    if (initialized_) { return; }
+    
+    // Tell python where do scripts live
+    py::module_::import("sys").attr("path").attr("append")("scripts");
+    
+    // Initialize interpreter
+    py::initialize_interpreter();
+    
+    // Release GIL immidiately so game loop can run freely
+    PyEval_SaveThread();
+    
+    initialized_ = true;
+}
+
+void PythonEngine::Shutdown()
+{
+    if (initialized_) { return; }
+    
+    PyEval_RestoreThread(PyGILState_GetThisThreadState());
+    py::finalize_interpreter();
+    initialized_ = false;
+}
+
+void PythonEngine::Exec(const std::string& code, Engine& engine)
+{
+    py::gil_scoped_acquire gil;
+    py::exec(code);
+}

+ 35 - 0
src/scripting/PythonEngine.h

@@ -0,0 +1,35 @@
+#ifndef __SCRIPTING__PYTHON_ENGINE_H__
+#define __SCRIPTING__PYTHON_ENGINE_H__
+
+#include <pybind11/embed.h>
+#include <string>
+#include <functional>
+#include "../Engine.h"
+
+namespace py = pybind11;
+
+class PythonEngine
+{
+public:
+    static PythonEngine& Get();
+    
+    void Init();
+    void Shutdown();
+    
+    void Exec(const std::string& code, Engine& engine);
+    
+    py::object Call(const std::string& module, const std::string& func, py::args args = {});
+    
+    // Hot reload scripts for later
+    void ReloadAllScripts();
+
+private:
+    PythonEngine() = default;
+    ~PythonEngine() = default;
+    
+    py::scoped_interpreter guard_;
+    bool initialized_ = false;
+
+};
+
+#endif

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

@@ -0,0 +1,26 @@
+#include "kariokaModule.h"
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+#include "../../Engine.h"
+#include <SDL2/SDL_log.h>
+
+namespace py = pybind11;
+
+class Engine;
+
+PYBIND11_MODULE(karioka, m)
+{
+    m.doc() = "kariokaEngine Python API";
+    
+    // Bind the Engine class itself
+    py::class_<Engine, std::unique_ptr<Engine, py::nodelete>> engine_cls(m, "Engine");
+    
+    // Simple log function
+    m.def("log",  [](const std::string& msg) {
+        SDL_Log("[PY] %s", msg.c_str());
+    });
+    
+    // Get engine reference
+    engine_cls.def_static("get", &Engine::Get, py::return_value_policy::reference);
+    m.def("engine", &Engine::Get, py::return_value_policy::reference);
+}

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

@@ -0,0 +1,4 @@
+#ifndef __SCRIPTING__BINDINGS__KARIOKA_MODULE_H__
+#define __SCRIPTING__BINDINGS__KARIOKA_MODULE_H__
+
+#endif

+ 14 - 8
src/states/MainMenuState.cpp

@@ -53,26 +53,32 @@ void MainMenuState::RenderImGui(Engine& engine)
     ImGui::SetCursorPosX((ImGui::GetWindowSize().x - ImGui::CalcTextSize("kariokaEngine").x) * 0.5f);
     ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, fadeAlpha), "kariokaEngine");
 
-    ImGui::Dummy(ImVec2(0, 40));    
-    if (ImGui::Button("Enter World", ImVec2(300, 30)))
+    // Main Menu Buttons
+    ImGui::Dummy(ImVec2(0.0f, 100.0f));
+    
+    float buttonWidth = 200.f;
+    float windowWidth = ImGui::GetWindowSize().x;
+
+    ImGui::SetCursorPosX((windowWidth - buttonWidth) * 0.5f);
+    if (ImGui::Button("Enter World", ImVec2(buttonWidth, 30)))
     {
         SDL_Log("TODO: GamePlayState");
     }
     
-    ImGui::Dummy(ImVec2(0, 50));
-    if (ImGui::Button("Map Editor", ImVec2(300, 30)))
+    ImGui::SetCursorPosX((windowWidth - buttonWidth) * 0.5f);
+    if (ImGui::Button("Map Editor", ImVec2(buttonWidth, 30)))
     {
         SDL_Log("TODO: MapEditorState");
     }
     
-    ImGui::Dummy(ImVec2(0, 60));
-    if (ImGui::Button("Asset Editor", ImVec2(300, 30)))
+    ImGui::SetCursorPosX((windowWidth - buttonWidth) * 0.5f);
+    if (ImGui::Button("Asset Editor", ImVec2(buttonWidth, 30)))
     {
         SDL_Log("TODO: AssetEditorState");
     }
     
-    ImGui::Dummy(ImVec2(0, 70));
-    if (ImGui::Button("Exit", ImVec2(300, 30)))
+    ImGui::SetCursorPosX((windowWidth - buttonWidth) * 0.5f);
+    if (ImGui::Button("Exit", ImVec2(buttonWidth, 30)))
     {
         engine.StopRunning();
     }