diff --git a/CMakeLists.txt b/CMakeLists.txt index 0267bec..a851bdd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,8 +118,6 @@ find_package(glm CONFIG REQUIRED) if(BUILD_SDL3_APP) add_executable(sdl3_app src/main.cpp - src/logging/logger.cpp - src/logging/string_utils.cpp src/core/platform.cpp src/di/service_registry.cpp src/events/event_bus.cpp @@ -145,7 +143,6 @@ if(BUILD_SDL3_APP) src/services/impl/bullet_physics_service.cpp src/services/impl/scene_service.cpp src/services/impl/graphics_service.cpp - src/services/impl/lua_script_service.cpp src/controllers/application_controller.cpp src/controllers/render_controller.cpp src/controllers/lifecycle_controller.cpp @@ -181,8 +178,6 @@ enable_testing() add_executable(script_engine_tests tests/test_cube_script.cpp - src/logging/logger.cpp - src/logging/string_utils.cpp src/core/platform.cpp src/script/script_engine.cpp src/script/physics_bridge.cpp diff --git a/src/app/service_based_app.cpp b/src/app/service_based_app.cpp index 1e29a0c..ce98e64 100644 --- a/src/app/service_based_app.cpp +++ b/src/app/service_based_app.cpp @@ -12,7 +12,6 @@ #include "services/impl/render_command_service.hpp" #include "services/impl/graphics_service.hpp" #include "services/impl/script_engine_service.hpp" -#include "services/impl/lua_script_service.hpp" #include "services/impl/scene_script_service.hpp" #include "services/impl/shader_script_service.hpp" #include "services/impl/gui_script_service.hpp" @@ -25,7 +24,7 @@ #include "services/impl/bullet_physics_service.hpp" #include "services/impl/crash_recovery_service.hpp" #include "services/impl/logger_service.hpp" -#include "logging/logger.hpp" +#include #include namespace sdl3cpp::app { @@ -36,9 +35,6 @@ ServiceBasedApp::ServiceBasedApp(const std::filesystem::path& scriptPath) registry_.RegisterService(); logger_ = registry_.GetService(); - // Set global logger for legacy compatibility - g_logger = logger_; - logger_->Trace("ServiceBasedApp", "ServiceBasedApp", "scriptPath=" + scriptPath_.string(), "constructor starting"); try { @@ -112,20 +108,6 @@ void ServiceBasedApp::Run() { graphicsService->InitializeSwapchain(); } - // Connect services that depend on each other - auto scriptService = registry_.GetService(); - auto audioService = registry_.GetService(); - if (scriptService && audioService) { - // The script service needs access to the audio player for Lua audio commands - // This is a bit of a hack - ideally the audio service would provide an interface - // But for now, we'll get the audio player from the impl - auto audioServiceImpl = std::dynamic_pointer_cast(audioService); - if (audioServiceImpl) { - // We need to access the private audioPlayer_ - this is not ideal - // TODO: Refactor to provide proper interface - } - } - // Run the main application loop with crash recovery if (crashRecoveryService_) { bool success = crashRecoveryService_->ExecuteWithTimeout( @@ -241,11 +223,6 @@ void ServiceBasedApp::RegisterServices() { registry_.RegisterService( registry_.GetService()); - // Script service (facade) - registry_.RegisterService( - registry_.GetService(), - registry_.GetService()); - // Connect input service to GUI script service for GUI input processing auto inputService = registry_.GetService(); auto guiScriptService = registry_.GetService(); diff --git a/src/controllers/application_controller.cpp b/src/controllers/application_controller.cpp index 498a062..328b21a 100644 --- a/src/controllers/application_controller.cpp +++ b/src/controllers/application_controller.cpp @@ -6,7 +6,6 @@ #include "../services/interfaces/i_physics_service.hpp" #include "../services/interfaces/i_scene_service.hpp" #include "../services/interfaces/i_audio_service.hpp" -#include "../services/impl/sdl_audio_service.hpp" #include "../events/event_bus.hpp" #include "../events/event_types.hpp" #include @@ -97,11 +96,7 @@ void ApplicationController::ProcessFrame(float deltaTime) { // Update audio auto audioService = registry_.GetService(); if (audioService) { - // Cast to implementation to access Update method - auto audioServiceImpl = std::dynamic_pointer_cast(audioService); - if (audioServiceImpl) { - audioServiceImpl->Update(); - } + audioService->Update(); } // Update GUI input to script service diff --git a/src/core/platform.cpp b/src/core/platform.cpp index 4274de3..b534720 100644 --- a/src/core/platform.cpp +++ b/src/core/platform.cpp @@ -1,6 +1,4 @@ #include "core/platform.hpp" -#include "logging/logger.hpp" -#include "logging/string_utils.hpp" #include @@ -11,7 +9,6 @@ namespace sdl3cpp::platform { std::optional GetUserConfigDirectory() { - sdl3cpp::logging::TraceGuard trace; #ifdef _WIN32 if (const char* appData = std::getenv("APPDATA")) { return std::filesystem::path(appData) / "sdl3cpp"; @@ -30,8 +27,6 @@ std::optional GetUserConfigDirectory() { #ifdef _WIN32 namespace { std::string FormatWin32Error(DWORD errorCode) { - using sdl3cpp::logging::ToString; - sdl3cpp::logging::Logger::GetInstance().TraceFunctionWithArgs("FormatWin32Error", ToString(static_cast(errorCode))); if (errorCode == ERROR_SUCCESS) { return "ERROR_SUCCESS"; } @@ -63,7 +58,6 @@ std::string FormatWin32Error(DWORD errorCode) { #endif std::string GetPlatformError() { - sdl3cpp::logging::TraceGuard trace; #ifdef _WIN32 DWORD win32Error = ::GetLastError(); if (win32Error != ERROR_SUCCESS) { diff --git a/src/core/vulkan_utils.cpp b/src/core/vulkan_utils.cpp index a570204..8f3bb12 100644 --- a/src/core/vulkan_utils.cpp +++ b/src/core/vulkan_utils.cpp @@ -1,8 +1,8 @@ #include "vulkan_utils.hpp" -#include "../logging/logger.hpp" #include #include +#include namespace sdl3cpp::vulkan::utils { @@ -15,12 +15,10 @@ uint32_t FindMemoryType(VkPhysicalDevice physicalDevice, uint32_t typeFilter, Vk for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) { if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { - sdl3cpp::logging::Logger::GetInstance().Debug("Found suitable memory type: " + std::to_string(i)); return i; } } - sdl3cpp::logging::Logger::GetInstance().Error("Failed to find suitable memory type"); throw std::runtime_error("Failed to find suitable memory type"); } @@ -41,10 +39,8 @@ VkExtent2D ChooseSwapExtent(VkSurfaceCapabilitiesKHR capabilities, SDL_Window* w void CreateBuffer(VkDevice device, VkPhysicalDevice physicalDevice, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { - sdl3cpp::logging::Logger::GetInstance().Debug("Creating buffer with size " + std::to_string(size) + " bytes"); // Validate buffer size if (size == 0) { - sdl3cpp::logging::Logger::GetInstance().Error("Cannot create buffer with size 0"); throw std::runtime_error("Cannot create buffer with size 0"); } @@ -115,4 +111,4 @@ void CreateBuffer(VkDevice device, VkPhysicalDevice physicalDevice, VkDeviceSize vkBindBufferMemory(device, buffer, bufferMemory, 0); } -} // namespace sdl3cpp::vulkan::utils \ No newline at end of file +} // namespace sdl3cpp::vulkan::utils diff --git a/src/gui/gui_renderer.cpp b/src/gui/gui_renderer.cpp index 7e9a083..09aa177 100644 --- a/src/gui/gui_renderer.cpp +++ b/src/gui/gui_renderer.cpp @@ -1,8 +1,6 @@ - #include "gui/gui_renderer.hpp" #include "../core/vulkan_utils.hpp" -#include "logging/logger.hpp" #include #include @@ -358,7 +356,6 @@ GuiRenderer::GuiRenderer(VkDevice device, VkPhysicalDevice physicalDevice, VkFor swapchainFormat_(swapchainFormat), scriptDirectory_(scriptDirectory), canvas_(std::make_unique()) { - sdl3cpp::logging::TraceGuard trace; } GuiRenderer::~GuiRenderer() { diff --git a/src/logging/string_utils.cpp b/src/logging/string_utils.cpp deleted file mode 100644 index d2e9659..0000000 --- a/src/logging/string_utils.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "string_utils.hpp" -#include -#include - -namespace sdl3cpp::logging { - -std::string ToString(int value) { - return std::to_string(value); -} - -std::string ToString(size_t value) { - return std::to_string(value); -} - -std::string ToString(bool value) { - return value ? "true" : "false"; -} - -std::string ToString(float value) { - return std::to_string(value); -} - -std::string ToString(double value) { - return std::to_string(value); -} - -std::string ToString(const void* value) { - std::ostringstream oss; - oss << value; - return oss.str(); -} - -#if !defined(__LP64__) || defined(_WIN64) -std::string ToString(unsigned long value) { - return std::to_string(value); -} -#endif - -} // namespace sdl3cpp::logging diff --git a/src/logging/string_utils.hpp b/src/logging/string_utils.hpp deleted file mode 100644 index ac6c4a3..0000000 --- a/src/logging/string_utils.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef SDL3CPP_LOGGING_STRING_UTILS_HPP -#define SDL3CPP_LOGGING_STRING_UTILS_HPP - -#include - -namespace sdl3cpp::logging { - -// Helper functions to convert common types to strings for logging -std::string ToString(int value); -std::string ToString(size_t value); -std::string ToString(bool value); -std::string ToString(float value); -std::string ToString(double value); -std::string ToString(const void* value); -#if !defined(__LP64__) || defined(_WIN64) -// Only define unsigned long overload if it's different from size_t -// On LP64 systems (Linux x64), size_t is unsigned long, causing redefinition -std::string ToString(unsigned long value); -#endif - -} // namespace sdl3cpp::logging - -#endif // SDL3CPP_LOGGING_STRING_UTILS_HPP diff --git a/src/script/gui_manager.cpp b/src/script/gui_manager.cpp index fb0b807..7d55e8a 100644 --- a/src/script/gui_manager.cpp +++ b/src/script/gui_manager.cpp @@ -1,5 +1,5 @@ #include "script/gui_manager.hpp" -#include "logging/logger.hpp" +#include "services/interfaces/i_logger.hpp" #include @@ -10,8 +10,11 @@ namespace sdl3cpp::script { -GuiManager::GuiManager(lua_State* L) : L_(L) { - sdl3cpp::logging::TraceGuard trace; +GuiManager::GuiManager(lua_State* L, std::shared_ptr logger) + : L_(L), logger_(logger) { + if (logger_) { + logger_->Trace("GuiManager", "GuiManager"); + } lua_getglobal(L_, "gui_input"); if (!lua_isnil(L_, -1)) { guiInputRef_ = luaL_ref(L_, LUA_REGISTRYINDEX); @@ -28,6 +31,9 @@ GuiManager::GuiManager(lua_State* L) : L_(L) { } std::vector GuiManager::LoadGuiCommands() { + if (logger_) { + logger_->Trace("GuiManager", "LoadGuiCommands"); + } std::vector commands; if (guiCommandsFnRef_ == LUA_REFNIL) { return commands; @@ -36,12 +42,16 @@ std::vector GuiManager::LoadGuiCommands() { if (lua_pcall(L_, 0, 1, 0) != LUA_OK) { std::string message = GetLuaError(); lua_pop(L_, 1); - sdl3cpp::logging::Logger::GetInstance().Error("Lua get_gui_commands failed: " + message); + if (logger_) { + logger_->Error("Lua get_gui_commands failed: " + message); + } throw std::runtime_error("Lua get_gui_commands failed: " + message); } if (!lua_istable(L_, -1)) { lua_pop(L_, 1); - sdl3cpp::logging::Logger::GetInstance().Error("'get_gui_commands' did not return a table"); + if (logger_) { + logger_->Error("'get_gui_commands' did not return a table"); + } throw std::runtime_error("'get_gui_commands' did not return a table"); } @@ -52,7 +62,9 @@ std::vector GuiManager::LoadGuiCommands() { lua_rawgeti(L_, -1, static_cast(i)); if (!lua_istable(L_, -1)) { lua_pop(L_, 1); - sdl3cpp::logging::Logger::GetInstance().Error("GUI command at index " + std::to_string(i) + " is not a table"); + if (logger_) { + logger_->Error("GUI command at index " + std::to_string(i) + " is not a table"); + } throw std::runtime_error("GUI command at index " + std::to_string(i) + " is not a table"); } int commandIndex = lua_gettop(L_); @@ -60,7 +72,9 @@ std::vector GuiManager::LoadGuiCommands() { const char* typeName = lua_tostring(L_, -1); if (!typeName) { lua_pop(L_, 2); - sdl3cpp::logging::Logger::GetInstance().Error("GUI command at index " + std::to_string(i) + " is missing a type"); + if (logger_) { + logger_->Error("GUI command at index " + std::to_string(i) + " is missing a type"); + } throw std::runtime_error("GUI command at index " + std::to_string(i) + " is missing a type"); } GuiCommand command{}; @@ -128,6 +142,9 @@ std::vector GuiManager::LoadGuiCommands() { } void GuiManager::UpdateGuiInput(const GuiInputSnapshot& input) { + if (logger_) { + logger_->Trace("GuiManager", "UpdateGuiInput"); + } if (guiInputRef_ == LUA_REFNIL) { return; } @@ -233,4 +250,4 @@ std::string GuiManager::GetLuaError() { return message ? message : "unknown lua error"; } -} // namespace sdl3cpp::script \ No newline at end of file +} // namespace sdl3cpp::script diff --git a/src/script/gui_manager.hpp b/src/script/gui_manager.hpp index c661318..d6cace4 100644 --- a/src/script/gui_manager.hpp +++ b/src/script/gui_manager.hpp @@ -5,13 +5,18 @@ #include +#include #include +namespace sdl3cpp::services { +class ILogger; +} + namespace sdl3cpp::script { class GuiManager { public: - explicit GuiManager(lua_State* L); + GuiManager(lua_State* L, std::shared_ptr logger); std::vector LoadGuiCommands(); void UpdateGuiInput(const GuiInputSnapshot& input); @@ -21,6 +26,7 @@ private: lua_State* L_; int guiInputRef_ = LUA_REFNIL; int guiCommandsFnRef_ = LUA_REFNIL; + std::shared_ptr logger_; GuiCommand::RectData ReadRect(int index); GuiColor ReadColor(int index, const GuiColor& defaultColor); @@ -28,4 +34,4 @@ private: std::string GetLuaError(); }; -} // namespace sdl3cpp::script \ No newline at end of file +} // namespace sdl3cpp::script diff --git a/src/script/lua_bindings.cpp b/src/script/lua_bindings.cpp index c0edcd2..3e471b1 100644 --- a/src/script/lua_bindings.cpp +++ b/src/script/lua_bindings.cpp @@ -1,89 +1,53 @@ #include "script/lua_bindings.hpp" -#include "script/script_engine.hpp" #include "script/lua_helpers.hpp" -#include "logging/logger.hpp" #include "services/interfaces/i_audio_command_service.hpp" #include "services/interfaces/i_mesh_service.hpp" #include "services/interfaces/i_physics_bridge_service.hpp" +#include "services/interfaces/i_logger.hpp" #include #include +#include namespace sdl3cpp::script { -void LuaBindings::RegisterBindings(lua_State* L, ScriptEngine* engine) { - sdl3cpp::logging::TraceGuard trace;; - lua_pushlightuserdata(L, engine); +void LuaBindings::RegisterBindings(lua_State* L, LuaBindingContext* context) { + auto logger = context ? context->logger : nullptr; + if (logger) { + logger->Trace("LuaBindings", "RegisterBindings"); + } + lua_pushlightuserdata(L, context); lua_pushcclosure(L, &LoadMeshFromFile, 1); lua_setglobal(L, "load_mesh_from_file"); - lua_pushlightuserdata(L, engine); + lua_pushlightuserdata(L, context); lua_pushcclosure(L, &PhysicsCreateBox, 1); lua_setglobal(L, "physics_create_box"); - lua_pushlightuserdata(L, engine); + lua_pushlightuserdata(L, context); lua_pushcclosure(L, &PhysicsStepSimulation, 1); lua_setglobal(L, "physics_step_simulation"); - lua_pushlightuserdata(L, engine); + lua_pushlightuserdata(L, context); lua_pushcclosure(L, &PhysicsGetTransform, 1); lua_setglobal(L, "physics_get_transform"); - lua_pushlightuserdata(L, engine); + lua_pushlightuserdata(L, context); lua_pushcclosure(L, &GlmMatrixFromTransform, 1); lua_setglobal(L, "glm_matrix_from_transform"); - lua_pushlightuserdata(L, engine); + lua_pushlightuserdata(L, context); lua_pushcclosure(L, &AudioPlayBackground, 1); lua_setglobal(L, "audio_play_background"); - lua_pushlightuserdata(L, engine); + lua_pushlightuserdata(L, context); lua_pushcclosure(L, &AudioPlaySound, 1); lua_setglobal(L, "audio_play_sound"); } -void LuaBindings::RegisterBindings(lua_State* L, LuaBindingContext* context) { - sdl3cpp::logging::TraceGuard trace; - lua_pushlightuserdata(L, context); - lua_pushcclosure(L, &LoadMeshFromFileWithServices, 1); - lua_setglobal(L, "load_mesh_from_file"); - - lua_pushlightuserdata(L, context); - lua_pushcclosure(L, &PhysicsCreateBoxWithServices, 1); - lua_setglobal(L, "physics_create_box"); - - lua_pushlightuserdata(L, context); - lua_pushcclosure(L, &PhysicsStepSimulationWithServices, 1); - lua_setglobal(L, "physics_step_simulation"); - - lua_pushlightuserdata(L, context); - lua_pushcclosure(L, &PhysicsGetTransformWithServices, 1); - lua_setglobal(L, "physics_get_transform"); - - lua_pushlightuserdata(L, context); - lua_pushcclosure(L, &GlmMatrixFromTransform, 1); - lua_setglobal(L, "glm_matrix_from_transform"); - - lua_pushlightuserdata(L, context); - lua_pushcclosure(L, &AudioPlayBackgroundWithServices, 1); - lua_setglobal(L, "audio_play_background"); - - lua_pushlightuserdata(L, context); - lua_pushcclosure(L, &AudioPlaySoundWithServices, 1); - lua_setglobal(L, "audio_play_sound"); -} - int LuaBindings::LoadMeshFromFile(lua_State* L) { - sdl3cpp::logging::TraceGuard trace;; - (void)lua_touserdata(L, lua_upvalueindex(1)); - lua_pushnil(L); - lua_pushstring(L, "Mesh service not available"); - return 2; -} - -int LuaBindings::LoadMeshFromFileWithServices(lua_State* L) { - sdl3cpp::logging::TraceGuard trace; auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + auto logger = context ? context->logger : nullptr; if (!context || !context->meshService) { lua_pushnil(L); lua_pushstring(L, "Mesh service not available"); @@ -91,7 +55,10 @@ int LuaBindings::LoadMeshFromFileWithServices(lua_State* L) { } const char* path = luaL_checkstring(L, 1); - sdl3cpp::logging::Logger::GetInstance().TraceVariable("path", path); + if (logger) { + logger->Trace("LuaBindings", "LoadMeshFromFile"); + logger->TraceVariable("path", std::string(path)); + } MeshPayload payload; std::string error; @@ -107,17 +74,8 @@ int LuaBindings::LoadMeshFromFileWithServices(lua_State* L) { } int LuaBindings::PhysicsCreateBox(lua_State* L) { - sdl3cpp::logging::TraceGuard trace;; - (void)lua_touserdata(L, lua_upvalueindex(1)); - (void)luaL_checkstring(L, 1); - lua_pushnil(L); - lua_pushstring(L, "Physics service not available"); - return 2; -} - -int LuaBindings::PhysicsCreateBoxWithServices(lua_State* L) { - sdl3cpp::logging::TraceGuard trace; auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + auto logger = context ? context->logger : nullptr; if (!context || !context->physicsBridgeService) { lua_pushnil(L); lua_pushstring(L, "Physics service not available"); @@ -125,7 +83,10 @@ int LuaBindings::PhysicsCreateBoxWithServices(lua_State* L) { } const char* name = luaL_checkstring(L, 1); - sdl3cpp::logging::Logger::GetInstance().TraceVariable("name", name); + if (logger) { + logger->Trace("LuaBindings", "PhysicsCreateBox"); + logger->TraceVariable("name", std::string(name)); + } if (!lua_istable(L, 2) || !lua_istable(L, 4) || !lua_istable(L, 5)) { luaL_error(L, "physics_create_box expects vector tables for half extents, origin, and rotation"); @@ -158,18 +119,15 @@ int LuaBindings::PhysicsCreateBoxWithServices(lua_State* L) { } int LuaBindings::PhysicsStepSimulation(lua_State* L) { - (void)lua_touserdata(L, lua_upvalueindex(1)); - (void)luaL_checknumber(L, 1); - lua_pushinteger(L, 0); - return 1; -} - -int LuaBindings::PhysicsStepSimulationWithServices(lua_State* L) { auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + auto logger = context ? context->logger : nullptr; if (!context || !context->physicsBridgeService) { lua_pushinteger(L, 0); return 1; } + if (logger) { + logger->Trace("LuaBindings", "PhysicsStepSimulation"); + } float deltaTime = static_cast(luaL_checknumber(L, 1)); int steps = context->physicsBridgeService->StepSimulation(deltaTime); lua_pushinteger(L, steps); @@ -177,21 +135,18 @@ int LuaBindings::PhysicsStepSimulationWithServices(lua_State* L) { } int LuaBindings::PhysicsGetTransform(lua_State* L) { - (void)lua_touserdata(L, lua_upvalueindex(1)); - (void)luaL_checkstring(L, 1); - lua_pushnil(L); - lua_pushstring(L, "Physics service not available"); - return 2; -} - -int LuaBindings::PhysicsGetTransformWithServices(lua_State* L) { auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + auto logger = context ? context->logger : nullptr; if (!context || !context->physicsBridgeService) { lua_pushnil(L); lua_pushstring(L, "Physics service not available"); return 2; } const char* name = luaL_checkstring(L, 1); + if (logger) { + logger->Trace("LuaBindings", "PhysicsGetTransform"); + logger->TraceVariable("name", std::string(name)); + } btTransform transform; std::string error; @@ -228,15 +183,8 @@ int LuaBindings::PhysicsGetTransformWithServices(lua_State* L) { } int LuaBindings::AudioPlayBackground(lua_State* L) { - (void)lua_touserdata(L, lua_upvalueindex(1)); - (void)luaL_checkstring(L, 1); - lua_pushnil(L); - lua_pushstring(L, "Audio service not available"); - return 2; -} - -int LuaBindings::AudioPlayBackgroundWithServices(lua_State* L) { auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + auto logger = context ? context->logger : nullptr; if (!context || !context->audioCommandService) { lua_pushnil(L); lua_pushstring(L, "Audio service not available"); @@ -247,6 +195,11 @@ int LuaBindings::AudioPlayBackgroundWithServices(lua_State* L) { if (lua_gettop(L) >= 2 && lua_isboolean(L, 2)) { loop = lua_toboolean(L, 2); } + if (logger) { + logger->Trace("LuaBindings", "AudioPlayBackground"); + logger->TraceVariable("path", std::string(path)); + logger->TraceVariable("loop", loop); + } std::string error; if (!context->audioCommandService->QueueAudioCommand( @@ -261,15 +214,8 @@ int LuaBindings::AudioPlayBackgroundWithServices(lua_State* L) { } int LuaBindings::AudioPlaySound(lua_State* L) { - (void)lua_touserdata(L, lua_upvalueindex(1)); - (void)luaL_checkstring(L, 1); - lua_pushnil(L); - lua_pushstring(L, "Audio service not available"); - return 2; -} - -int LuaBindings::AudioPlaySoundWithServices(lua_State* L) { auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + auto logger = context ? context->logger : nullptr; if (!context || !context->audioCommandService) { lua_pushnil(L); lua_pushstring(L, "Audio service not available"); @@ -280,6 +226,11 @@ int LuaBindings::AudioPlaySoundWithServices(lua_State* L) { if (lua_gettop(L) >= 2 && lua_isboolean(L, 2)) { loop = lua_toboolean(L, 2); } + if (logger) { + logger->Trace("LuaBindings", "AudioPlaySound"); + logger->TraceVariable("path", std::string(path)); + logger->TraceVariable("loop", loop); + } std::string error; if (!context->audioCommandService->QueueAudioCommand( diff --git a/src/script/lua_bindings.hpp b/src/script/lua_bindings.hpp index de61f3a..ffba78c 100644 --- a/src/script/lua_bindings.hpp +++ b/src/script/lua_bindings.hpp @@ -9,36 +9,29 @@ namespace sdl3cpp::services { class IAudioCommandService; class IMeshService; class IPhysicsBridgeService; +class ILogger; } namespace sdl3cpp::script { -class ScriptEngine; - struct LuaBindingContext { std::shared_ptr meshService; std::shared_ptr audioCommandService; std::shared_ptr physicsBridgeService; + std::shared_ptr logger; }; class LuaBindings { public: - static void RegisterBindings(lua_State* L, ScriptEngine* engine); static void RegisterBindings(lua_State* L, LuaBindingContext* context); private: static int LoadMeshFromFile(lua_State* L); - static int LoadMeshFromFileWithServices(lua_State* L); static int PhysicsCreateBox(lua_State* L); - static int PhysicsCreateBoxWithServices(lua_State* L); static int PhysicsStepSimulation(lua_State* L); - static int PhysicsStepSimulationWithServices(lua_State* L); static int PhysicsGetTransform(lua_State* L); - static int PhysicsGetTransformWithServices(lua_State* L); static int AudioPlayBackground(lua_State* L); - static int AudioPlayBackgroundWithServices(lua_State* L); static int AudioPlaySound(lua_State* L); - static int AudioPlaySoundWithServices(lua_State* L); static int GlmMatrixFromTransform(lua_State* L); }; diff --git a/src/script/lua_helpers.cpp b/src/script/lua_helpers.cpp index d3c067e..8f25574 100644 --- a/src/script/lua_helpers.cpp +++ b/src/script/lua_helpers.cpp @@ -1,6 +1,4 @@ #include "script/lua_helpers.hpp" -#include "logging/logger.hpp" -#include "logging/string_utils.hpp" #include #include @@ -12,8 +10,6 @@ namespace sdl3cpp::script { std::array ReadVector3(lua_State* L, int index) { - using sdl3cpp::logging::ToString; - sdl3cpp::logging::Logger::GetInstance().TraceFunctionWithArgs("ReadVector3", ToString(static_cast(L)) + " " + ToString(index)); std::array result{}; int absIndex = lua_absindex(L, index); size_t len = lua_rawlen(L, absIndex); @@ -33,8 +29,6 @@ std::array ReadVector3(lua_State* L, int index) { } std::array ReadQuaternion(lua_State* L, int index) { - using sdl3cpp::logging::ToString; - sdl3cpp::logging::Logger::GetInstance().TraceFunctionWithArgs("ReadQuaternion", ToString(static_cast(L)) + " " + ToString(index)); std::array result{}; int absIndex = lua_absindex(L, index); size_t len = lua_rawlen(L, absIndex); @@ -54,8 +48,6 @@ std::array ReadQuaternion(lua_State* L, int index) { } std::array ReadMatrix(lua_State* L, int index) { - using sdl3cpp::logging::ToString; - sdl3cpp::logging::Logger::GetInstance().TraceFunctionWithArgs("ReadMatrix", ToString(static_cast(L)) + " " + ToString(index)); std::array result{}; int absIndex = lua_absindex(L, index); size_t len = lua_rawlen(L, absIndex); @@ -75,14 +67,11 @@ std::array ReadMatrix(lua_State* L, int index) { } std::string GetLuaError(lua_State* L) { - using sdl3cpp::logging::ToString; - sdl3cpp::logging::Logger::GetInstance().TraceFunctionWithArgs("GetLuaError", ToString(static_cast(L))); const char* message = lua_tostring(L, -1); return message ? message : "unknown lua error"; } std::array IdentityMatrix() { - sdl3cpp::logging::TraceGuard trace; return {1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, @@ -90,20 +79,14 @@ std::array IdentityMatrix() { } glm::vec3 ToVec3(const std::array& value) { - using sdl3cpp::logging::ToString; - sdl3cpp::logging::Logger::GetInstance().TraceFunctionWithArgs("ToVec3", ToString(value[0]) + " " + ToString(value[1]) + " " + ToString(value[2])); return glm::vec3(value[0], value[1], value[2]); } glm::quat ToQuat(const std::array& value) { - using sdl3cpp::logging::ToString; - sdl3cpp::logging::Logger::GetInstance().TraceFunctionWithArgs("ToQuat", ToString(value[0]) + " " + ToString(value[1]) + " " + ToString(value[2]) + " " + ToString(value[3])); return glm::quat(value[3], value[0], value[1], value[2]); } void PushMatrix(lua_State* L, const glm::mat4& matrix) { - using sdl3cpp::logging::ToString; - sdl3cpp::logging::Logger::GetInstance().TraceFunctionWithArgs("PushMatrix", ToString(static_cast(L))); lua_newtable(L); const float* ptr = glm::value_ptr(matrix); for (int i = 0; i < 16; ++i) { @@ -113,8 +96,6 @@ void PushMatrix(lua_State* L, const glm::mat4& matrix) { } int LuaGlmMatrixFromTransform(lua_State* L) { - using sdl3cpp::logging::ToString; - sdl3cpp::logging::Logger::GetInstance().TraceFunctionWithArgs("LuaGlmMatrixFromTransform", ToString(static_cast(L))); std::array translation = ReadVector3(L, 1); std::array rotation = ReadQuaternion(L, 2); glm::vec3 pos = ToVec3(translation); diff --git a/src/script/physics_bridge.cpp b/src/script/physics_bridge.cpp index 85e0965..2981455 100644 --- a/src/script/physics_bridge.cpp +++ b/src/script/physics_bridge.cpp @@ -1,11 +1,11 @@ #include "script/physics_bridge.hpp" -#include "logging/logger.hpp" +#include "services/interfaces/i_logger.hpp" #include namespace sdl3cpp::script { -PhysicsBridge::PhysicsBridge() +PhysicsBridge::PhysicsBridge(std::shared_ptr logger) : collisionConfig_(std::make_unique()), dispatcher_(std::make_unique(collisionConfig_.get())), broadphase_(std::make_unique()), @@ -14,8 +14,11 @@ PhysicsBridge::PhysicsBridge() dispatcher_.get(), broadphase_.get(), solver_.get(), - collisionConfig_.get())) { - sdl3cpp::logging::TraceGuard trace; + collisionConfig_.get())), + logger_(logger) { + if (logger_) { + logger_->Trace("PhysicsBridge", "PhysicsBridge"); + } world_->setGravity(btVector3(0.0f, -9.81f, 0.0f)); } @@ -34,6 +37,9 @@ bool PhysicsBridge::addBoxRigidBody(const std::string& name, float mass, const btTransform& transform, std::string& error) { + if (logger_) { + logger_->Trace("PhysicsBridge", "addBoxRigidBody", "name=" + name); + } if (name.empty()) { error = "Rigid body name must not be empty"; return false; @@ -68,6 +74,9 @@ bool PhysicsBridge::addBoxRigidBody(const std::string& name, } int PhysicsBridge::stepSimulation(float deltaTime) { + if (logger_) { + logger_->Trace("PhysicsBridge", "stepSimulation", "deltaTime=" + std::to_string(deltaTime)); + } if (!world_) { return 0; } @@ -77,6 +86,9 @@ int PhysicsBridge::stepSimulation(float deltaTime) { bool PhysicsBridge::getRigidBodyTransform(const std::string& name, btTransform& outTransform, std::string& error) const { + if (logger_) { + logger_->Trace("PhysicsBridge", "getRigidBodyTransform", "name=" + name); + } auto it = bodies_.find(name); if (it == bodies_.end()) { error = "Rigid body not found: " + name; diff --git a/src/script/physics_bridge.hpp b/src/script/physics_bridge.hpp index f4671d0..57917b3 100644 --- a/src/script/physics_bridge.hpp +++ b/src/script/physics_bridge.hpp @@ -5,6 +5,10 @@ #include #include +namespace sdl3cpp::services { +class ILogger; +} + class btVector3; class btTransform; class btCollisionShape; @@ -20,7 +24,7 @@ namespace sdl3cpp::script { class PhysicsBridge { public: - PhysicsBridge(); + explicit PhysicsBridge(std::shared_ptr logger); ~PhysicsBridge(); PhysicsBridge(const PhysicsBridge&) = delete; @@ -49,6 +53,7 @@ private: std::unique_ptr solver_; std::unique_ptr world_; std::unordered_map bodies_; + std::shared_ptr logger_; }; } // namespace sdl3cpp::script diff --git a/src/script/scene_manager.cpp b/src/script/scene_manager.cpp index 66c3c7a..b3f9b9f 100644 --- a/src/script/scene_manager.cpp +++ b/src/script/scene_manager.cpp @@ -1,5 +1,5 @@ #include "script/scene_manager.hpp" -#include "logging/logger.hpp" +#include "services/interfaces/i_logger.hpp" #include @@ -9,23 +9,38 @@ namespace sdl3cpp::script { -SceneManager::SceneManager(lua_State* L) : L_(L) { - sdl3cpp::logging::TraceGuard trace; +SceneManager::SceneManager(lua_State* L, std::shared_ptr logger) + : L_(L), logger_(logger) { + if (logger_) { + logger_->Trace("SceneManager", "SceneManager"); + } } std::vector SceneManager::LoadSceneObjects() { + if (logger_) { + logger_->Trace("SceneManager", "LoadSceneObjects"); + } lua_getglobal(L_, "get_scene_objects"); if (!lua_isfunction(L_, -1)) { lua_pop(L_, 1); + if (logger_) { + logger_->Error("Lua function 'get_scene_objects' is missing"); + } throw std::runtime_error("Lua function 'get_scene_objects' is missing"); } if (lua_pcall(L_, 0, 1, 0) != LUA_OK) { std::string message = GetLuaError(); lua_pop(L_, 1); + if (logger_) { + logger_->Error("Lua get_scene_objects failed: " + message); + } throw std::runtime_error("Lua get_scene_objects failed: " + message); } if (!lua_istable(L_, -1)) { lua_pop(L_, 1); + if (logger_) { + logger_->Error("'get_scene_objects' did not return a table"); + } throw std::runtime_error("'get_scene_objects' did not return a table"); } @@ -37,6 +52,9 @@ std::vector SceneManager::LoadSceneObjects() { lua_rawgeti(L_, -1, static_cast(i)); if (!lua_istable(L_, -1)) { lua_pop(L_, 1); + if (logger_) { + logger_->Error("Scene object at index " + std::to_string(i) + " is not a table"); + } throw std::runtime_error("Scene object at index " + std::to_string(i) + " is not a table"); } @@ -46,6 +64,9 @@ std::vector SceneManager::LoadSceneObjects() { lua_pop(L_, 1); if (object.vertices.empty()) { lua_pop(L_, 1); + if (logger_) { + logger_->Error("Scene object " + std::to_string(i) + " must supply at least one vertex"); + } throw std::runtime_error("Scene object " + std::to_string(i) + " must supply at least one vertex"); } @@ -54,6 +75,9 @@ std::vector SceneManager::LoadSceneObjects() { lua_pop(L_, 1); if (object.indices.empty()) { lua_pop(L_, 1); + if (logger_) { + logger_->Error("Scene object " + std::to_string(i) + " must supply indices"); + } throw std::runtime_error("Scene object " + std::to_string(i) + " must supply indices"); } @@ -80,6 +104,9 @@ std::vector SceneManager::LoadSceneObjects() { } std::array SceneManager::ComputeModelMatrix(int functionRef, float time) { + if (logger_) { + logger_->Trace("SceneManager", "ComputeModelMatrix", "time=" + std::to_string(time)); + } if (functionRef == LUA_REFNIL) { lua_getglobal(L_, "compute_model_matrix"); if (!lua_isfunction(L_, -1)) { @@ -94,10 +121,16 @@ std::array SceneManager::ComputeModelMatrix(int functionRef, float ti if (lua_pcall(L_, 1, 1, 0) != LUA_OK) { std::string message = GetLuaError(); lua_pop(L_, 1); + if (logger_) { + logger_->Error("Lua compute_model_matrix failed: " + message); + } throw std::runtime_error("Lua compute_model_matrix failed: " + message); } if (!lua_istable(L_, -1)) { lua_pop(L_, 1); + if (logger_) { + logger_->Error("'compute_model_matrix' did not return a table"); + } throw std::runtime_error("'compute_model_matrix' did not return a table"); } @@ -107,19 +140,31 @@ std::array SceneManager::ComputeModelMatrix(int functionRef, float ti } std::array SceneManager::GetViewProjectionMatrix(float aspect) { + if (logger_) { + logger_->Trace("SceneManager", "GetViewProjectionMatrix", "aspect=" + std::to_string(aspect)); + } lua_getglobal(L_, "get_view_projection"); if (!lua_isfunction(L_, -1)) { lua_pop(L_, 1); + if (logger_) { + logger_->Error("Lua function 'get_view_projection' is missing"); + } throw std::runtime_error("Lua function 'get_view_projection' is missing"); } lua_pushnumber(L_, aspect); if (lua_pcall(L_, 1, 1, 0) != LUA_OK) { std::string message = GetLuaError(); lua_pop(L_, 1); + if (logger_) { + logger_->Error("Lua get_view_projection failed: " + message); + } throw std::runtime_error("Lua get_view_projection failed: " + message); } if (!lua_istable(L_, -1)) { lua_pop(L_, 1); + if (logger_) { + logger_->Error("'get_view_projection' did not return a table"); + } throw std::runtime_error("'get_view_projection' did not return a table"); } std::array matrix = ReadMatrix(L_, -1); @@ -130,6 +175,9 @@ std::array SceneManager::GetViewProjectionMatrix(float aspect) { std::vector SceneManager::ReadVertexArray(int index) { int absIndex = lua_absindex(L_, index); if (!lua_istable(L_, absIndex)) { + if (logger_) { + logger_->Error("Expected table for vertex data"); + } throw std::runtime_error("Expected table for vertex data"); } @@ -141,6 +189,9 @@ std::vector SceneManager::ReadVertexArray(int index) { lua_rawgeti(L_, absIndex, static_cast(i)); if (!lua_istable(L_, -1)) { lua_pop(L_, 1); + if (logger_) { + logger_->Error("Vertex entry at index " + std::to_string(i) + " is not a table"); + } throw std::runtime_error("Vertex entry at index " + std::to_string(i) + " is not a table"); } @@ -165,6 +216,9 @@ std::vector SceneManager::ReadVertexArray(int index) { std::vector SceneManager::ReadIndexArray(int index) { int absIndex = lua_absindex(L_, index); if (!lua_istable(L_, absIndex)) { + if (logger_) { + logger_->Error("Expected table for index data"); + } throw std::runtime_error("Expected table for index data"); } @@ -176,11 +230,17 @@ std::vector SceneManager::ReadIndexArray(int index) { lua_rawgeti(L_, absIndex, static_cast(i)); if (!lua_isinteger(L_, -1)) { lua_pop(L_, 1); + if (logger_) { + logger_->Error("Index entry at position " + std::to_string(i) + " is not an integer"); + } throw std::runtime_error("Index entry at position " + std::to_string(i) + " is not an integer"); } lua_Integer value = lua_tointeger(L_, -1); lua_pop(L_, 1); if (value < 1) { + if (logger_) { + logger_->Error("Index values must be 1 or greater"); + } throw std::runtime_error("Index values must be 1 or greater"); } indices.push_back(static_cast(value - 1)); @@ -194,4 +254,4 @@ std::string SceneManager::GetLuaError() { return message ? message : "unknown lua error"; } -} // namespace sdl3cpp::script \ No newline at end of file +} // namespace sdl3cpp::script diff --git a/src/script/scene_manager.hpp b/src/script/scene_manager.hpp index a4be83c..20a8122 100644 --- a/src/script/scene_manager.hpp +++ b/src/script/scene_manager.hpp @@ -7,9 +7,14 @@ #include #include +#include #include #include +namespace sdl3cpp::services { +class ILogger; +} + namespace sdl3cpp::script { class SceneManager { @@ -21,7 +26,7 @@ public: std::string shaderKey = "default"; }; - explicit SceneManager(lua_State* L); + SceneManager(lua_State* L, std::shared_ptr logger); std::vector LoadSceneObjects(); std::array ComputeModelMatrix(int functionRef, float time); @@ -33,6 +38,8 @@ private: std::vector ReadVertexArray(int index); std::vector ReadIndexArray(int index); std::string GetLuaError(); + + std::shared_ptr logger_; }; -} // namespace sdl3cpp::script \ No newline at end of file +} // namespace sdl3cpp::script diff --git a/src/script/script_engine.cpp b/src/script/script_engine.cpp index f360a26..3d8b3cd 100644 --- a/src/script/script_engine.cpp +++ b/src/script/script_engine.cpp @@ -3,14 +3,11 @@ #include "script/shader_manager.hpp" #include "script/gui_manager.hpp" #include "script/lua_bindings.hpp" -#include "logging/logger.hpp" +#include "services/interfaces/i_logger.hpp" #include -#include -#include #include -#include namespace sdl3cpp::script { @@ -21,28 +18,35 @@ ScriptEngine::ScriptEngine(const std::filesystem::path& scriptPath, bool debugEn ScriptEngine::ScriptEngine(const std::filesystem::path& scriptPath, LuaBindingContext* bindingContext, bool debugEnabled) : L_(luaL_newstate()), scriptDirectory_(scriptPath.parent_path()), - debugEnabled_(debugEnabled), - sceneManager_(std::make_unique(L_)), - shaderManager_(std::make_unique(L_)), - guiManager_(std::make_unique(L_)) { - sdl3cpp::logging::TraceGuard trace;; + debugEnabled_(debugEnabled) { + LuaBindingContext* resolvedContext = bindingContext; + if (!resolvedContext) { + ownedBindingContext_ = std::make_unique(); + resolvedContext = ownedBindingContext_.get(); + } + if (resolvedContext) { + logger_ = resolvedContext->logger; + } + if (logger_) { + logger_->Trace("ScriptEngine", "ScriptEngine"); + } if (!L_) { - sdl3cpp::logging::Logger::GetInstance().Error("Failed to create Lua state"); + if (logger_) { + logger_->Error("Failed to create Lua state"); + } throw std::runtime_error("Failed to create Lua state"); } - - sdl3cpp::logging::Logger::GetInstance().Debug("Lua state created successfully"); - luaL_openlibs(L_); - - if (bindingContext) { - LuaBindings::RegisterBindings(L_, bindingContext); - } else { - LuaBindings::RegisterBindings(L_, this); + + if (logger_) { + logger_->Debug("Lua state created successfully"); } - + luaL_openlibs(L_); + + LuaBindings::RegisterBindings(L_, resolvedContext); + lua_pushboolean(L_, debugEnabled_); lua_setglobal(L_, "lua_debug"); - + auto scriptDir = scriptPath.parent_path(); if (!scriptDir.empty()) { lua_getglobal(L_, "package"); @@ -59,17 +63,25 @@ ScriptEngine::ScriptEngine(const std::filesystem::path& scriptPath, LuaBindingCo } lua_pop(L_, 1); } - + if (luaL_dofile(L_, scriptPath.string().c_str()) != LUA_OK) { std::string message = sdl3cpp::script::GetLuaError(L_); lua_pop(L_, 1); lua_close(L_); L_ = nullptr; - sdl3cpp::logging::Logger::GetInstance().Error("Failed to load Lua script: " + message); + if (logger_) { + logger_->Error("Failed to load Lua script: " + message); + } throw std::runtime_error("Failed to load Lua script: " + message); } - sdl3cpp::logging::Logger::GetInstance().Info("Lua script loaded successfully: " + scriptPath.string()); + if (logger_) { + logger_->Info("Lua script loaded successfully: " + scriptPath.string()); + } + + sceneManager_ = std::make_unique(L_, logger_); + shaderManager_ = std::make_unique(L_, logger_); + guiManager_ = std::make_unique(L_, logger_); } ScriptEngine::~ScriptEngine() { @@ -79,30 +91,51 @@ ScriptEngine::~ScriptEngine() { } std::vector ScriptEngine::LoadSceneObjects() { + if (!sceneManager_) { + throw std::runtime_error("Scene manager not initialized"); + } return sceneManager_->LoadSceneObjects(); } std::array ScriptEngine::ComputeModelMatrix(int functionRef, float time) { + if (!sceneManager_) { + throw std::runtime_error("Scene manager not initialized"); + } return sceneManager_->ComputeModelMatrix(functionRef, time); } std::array ScriptEngine::GetViewProjectionMatrix(float aspect) { + if (!sceneManager_) { + throw std::runtime_error("Scene manager not initialized"); + } return sceneManager_->GetViewProjectionMatrix(aspect); } std::unordered_map ScriptEngine::LoadShaderPathsMap() { + if (!shaderManager_) { + throw std::runtime_error("Shader manager not initialized"); + } return shaderManager_->LoadShaderPathsMap(); } std::vector ScriptEngine::LoadGuiCommands() { + if (!guiManager_) { + throw std::runtime_error("Gui manager not initialized"); + } return guiManager_->LoadGuiCommands(); } void ScriptEngine::UpdateGuiInput(const GuiInputSnapshot& input) { + if (!guiManager_) { + throw std::runtime_error("Gui manager not initialized"); + } guiManager_->UpdateGuiInput(input); } bool ScriptEngine::HasGuiCommands() const { + if (!guiManager_) { + return false; + } return guiManager_->HasGuiCommands(); } diff --git a/src/script/script_engine.cpp.backup b/src/script/script_engine.cpp.backup deleted file mode 100644 index 4af61d2..0000000 --- a/src/script/script_engine.cpp.backup +++ /dev/null @@ -1,954 +0,0 @@ -#include "script/script_engine.hpp" -#include "app/audio_player.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace sdl3cpp::script { - -namespace detail { - -std::array ReadVector3(lua_State* L, int index) { - std::array result{}; - int absIndex = lua_absindex(L, index); - size_t len = lua_rawlen(L, absIndex); - if (len != 3) { - throw std::runtime_error("Expected vector with 3 components"); - } - for (size_t i = 1; i <= 3; ++i) { - lua_rawgeti(L, absIndex, static_cast(i)); - if (!lua_isnumber(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error("Vector component is not a number"); - } - result[i - 1] = static_cast(lua_tonumber(L, -1)); - lua_pop(L, 1); - } - return result; -} - -std::array ReadMatrix(lua_State* L, int index) { - std::array result{}; - int absIndex = lua_absindex(L, index); - size_t len = lua_rawlen(L, absIndex); - if (len != 16) { - throw std::runtime_error("Expected 4x4 matrix with 16 components"); - } - for (size_t i = 1; i <= 16; ++i) { - lua_rawgeti(L, absIndex, static_cast(i)); - if (!lua_isnumber(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error("Matrix component is not a number"); - } - result[i - 1] = static_cast(lua_tonumber(L, -1)); - lua_pop(L, 1); - } - return result; -} - -std::array ReadQuaternion(lua_State* L, int index) { - std::array result{}; - int absIndex = lua_absindex(L, index); - size_t len = lua_rawlen(L, absIndex); - if (len != 4) { - throw std::runtime_error("Expected quaternion with 4 components"); - } - for (size_t i = 1; i <= 4; ++i) { - lua_rawgeti(L, absIndex, static_cast(i)); - if (!lua_isnumber(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error("Quaternion component is not a number"); - } - result[i - 1] = static_cast(lua_tonumber(L, -1)); - lua_pop(L, 1); - } - return result; -} - -} // namespace detail - -namespace { - -struct MeshPayload { - std::vector> positions; - std::vector> colors; - std::vector indices; -}; - -bool TryLoadMeshPayload(const ScriptEngine* script, - const std::string& requestedPath, - MeshPayload& payload, - std::string& error) { - std::filesystem::path resolved(requestedPath); - if (!resolved.is_absolute()) { - resolved = script->GetScriptDirectory() / resolved; - } - std::error_code ec; - resolved = std::filesystem::weakly_canonical(resolved, ec); - if (ec) { - error = "Failed to resolve mesh path: " + ec.message(); - return false; - } - if (!std::filesystem::exists(resolved)) { - error = "Mesh file not found: " + resolved.string(); - return false; - } - - Assimp::Importer importer; - const aiScene* scene = importer.ReadFile( - resolved.string(), - aiProcess_Triangulate | aiProcess_JoinIdenticalVertices | aiProcess_PreTransformVertices); - if (!scene) { - error = importer.GetErrorString() ? importer.GetErrorString() : "Assimp failed to load mesh"; - return false; - } - if (scene->mNumMeshes == 0) { - error = "Scene contains no meshes"; - return false; - } - - const aiMesh* mesh = scene->mMeshes[0]; - if (!mesh->mNumVertices) { - error = "Mesh contains no vertices"; - return false; - } - - payload.positions.reserve(mesh->mNumVertices); - payload.colors.reserve(mesh->mNumVertices); - payload.indices.reserve(mesh->mNumFaces * 3); - - aiColor3D defaultColor(0.6f, 0.8f, 1.0f); - - aiColor3D materialColor = defaultColor; - if (mesh->mMaterialIndex < scene->mNumMaterials) { - const aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex]; - aiColor4D diffuse; - if (material && material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse) == AI_SUCCESS) { - materialColor = aiColor3D(diffuse.r, diffuse.g, diffuse.b); - } - } - - for (unsigned i = 0; i < mesh->mNumVertices; ++i) { - const aiVector3D& vertex = mesh->mVertices[i]; - payload.positions.push_back({vertex.x, vertex.y, vertex.z}); - - aiColor3D color = materialColor; - if (mesh->HasVertexColors(0) && mesh->mColors[0]) { - const aiColor4D& vertexColor = mesh->mColors[0][i]; - color = aiColor3D(vertexColor.r, vertexColor.g, vertexColor.b); - } - payload.colors.push_back({color.r, color.g, color.b}); - } - - for (unsigned faceIndex = 0; faceIndex < mesh->mNumFaces; ++faceIndex) { - const aiFace& face = mesh->mFaces[faceIndex]; - if (face.mNumIndices != 3) { - continue; - } - payload.indices.push_back(face.mIndices[0]); - payload.indices.push_back(face.mIndices[1]); - payload.indices.push_back(face.mIndices[2]); - } - - if (payload.indices.empty()) { - error = "Mesh contains no triangle faces"; - return false; - } - return true; -} - -glm::vec3 ToVec3(const std::array& value) { - return glm::vec3(value[0], value[1], value[2]); -} - -glm::quat ToQuat(const std::array& value) { - return glm::quat(value[3], value[0], value[1], value[2]); -} - -void PushMatrix(lua_State* L, const glm::mat4& matrix) { - lua_newtable(L); - const float* ptr = glm::value_ptr(matrix); - for (int i = 0; i < 16; ++i) { - lua_pushnumber(L, ptr[i]); - lua_rawseti(L, -2, i + 1); - } -} - -int PushMeshToLua(lua_State* L, const MeshPayload& payload) { - lua_newtable(L); - - lua_newtable(L); - for (size_t vertexIndex = 0; vertexIndex < payload.positions.size(); ++vertexIndex) { - lua_newtable(L); - - lua_newtable(L); - for (int component = 0; component < 3; ++component) { - lua_pushnumber(L, payload.positions[vertexIndex][component]); - lua_rawseti(L, -2, component + 1); - } - lua_setfield(L, -2, "position"); - - lua_newtable(L); - for (int component = 0; component < 3; ++component) { - lua_pushnumber(L, payload.colors[vertexIndex][component]); - lua_rawseti(L, -2, component + 1); - } - lua_setfield(L, -2, "color"); - - lua_rawseti(L, -2, static_cast(vertexIndex + 1)); - } - lua_setfield(L, -2, "vertices"); - - lua_newtable(L); - for (size_t index = 0; index < payload.indices.size(); ++index) { - lua_pushinteger(L, static_cast(payload.indices[index]) + 1); - lua_rawseti(L, -2, static_cast(index + 1)); - } - lua_setfield(L, -2, "indices"); - - return 1; -} - -int LuaLoadMeshFromFile(lua_State* L) { - auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - const char* path = luaL_checkstring(L, 1); - MeshPayload payload; - std::string error; - if (!TryLoadMeshPayload(script, path, payload, error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - PushMeshToLua(L, payload); - lua_pushnil(L); - return 2; -} - -int LuaPhysicsCreateBox(lua_State* L) { - auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - const char* name = luaL_checkstring(L, 1); - if (!lua_istable(L, 2) || !lua_istable(L, 4) || !lua_istable(L, 5)) { - luaL_error(L, "physics_create_box expects vector tables for half extents, origin, and rotation"); - } - std::array halfExtents = detail::ReadVector3(L, 2); - float mass = static_cast(luaL_checknumber(L, 3)); - std::array origin = detail::ReadVector3(L, 4); - std::array rotation = detail::ReadQuaternion(L, 5); - - btTransform transform; - transform.setIdentity(); - transform.setOrigin(btVector3(origin[0], origin[1], origin[2])); - transform.setRotation(btQuaternion(rotation[0], rotation[1], rotation[2], rotation[3])); - - std::string error; - if (!script->GetPhysicsBridge().addBoxRigidBody( - name, - btVector3(halfExtents[0], halfExtents[1], halfExtents[2]), - mass, - transform, - error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - - lua_pushboolean(L, 1); - return 1; -} - -int LuaPhysicsStepSimulation(lua_State* L) { - auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - float deltaTime = static_cast(luaL_checknumber(L, 1)); - int steps = script->GetPhysicsBridge().stepSimulation(deltaTime); - lua_pushinteger(L, steps); - return 1; -} - -int LuaPhysicsGetTransform(lua_State* L) { - auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - const char* name = luaL_checkstring(L, 1); - btTransform transform; - std::string error; - if (!script->GetPhysicsBridge().getRigidBodyTransform(name, transform, error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - - lua_newtable(L); - lua_newtable(L); - const btVector3& origin = transform.getOrigin(); - lua_pushnumber(L, origin.x()); - lua_rawseti(L, -2, 1); - lua_pushnumber(L, origin.y()); - lua_rawseti(L, -2, 2); - lua_pushnumber(L, origin.z()); - lua_rawseti(L, -2, 3); - lua_setfield(L, -2, "position"); - - lua_newtable(L); - const btQuaternion& orientation = transform.getRotation(); - lua_pushnumber(L, orientation.x()); - lua_rawseti(L, -2, 1); - lua_pushnumber(L, orientation.y()); - lua_rawseti(L, -2, 2); - lua_pushnumber(L, orientation.z()); - lua_rawseti(L, -2, 3); - lua_pushnumber(L, orientation.w()); - lua_rawseti(L, -2, 4); - lua_setfield(L, -2, "rotation"); - - return 1; -} - -int LuaAudioPlayBackground(lua_State* L) { - auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - const char* path = luaL_checkstring(L, 1); - bool loop = true; - if (lua_gettop(L) >= 2 && lua_isboolean(L, 2)) { - loop = lua_toboolean(L, 2); - } - std::string error; - if (!script->QueueAudioCommand(ScriptEngine::AudioCommandType::Background, path, loop, error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - lua_pushboolean(L, 1); - return 1; -} - -int LuaAudioPlaySound(lua_State* L) { - auto* script = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - const char* path = luaL_checkstring(L, 1); - bool loop = false; - if (lua_gettop(L) >= 2 && lua_isboolean(L, 2)) { - loop = lua_toboolean(L, 2); - } - std::string error; - if (!script->QueueAudioCommand(ScriptEngine::AudioCommandType::Effect, path, loop, error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - lua_pushboolean(L, 1); - return 1; -} - -int LuaGlmMatrixFromTransform(lua_State* L) { - std::array translation = detail::ReadVector3(L, 1); - std::array rotation = detail::ReadQuaternion(L, 2); - glm::vec3 pos = ToVec3(translation); - glm::quat quat = ToQuat(rotation); - glm::mat4 matrix = glm::translate(glm::mat4(1.0f), pos) * glm::mat4_cast(quat); - PushMatrix(L, matrix); - return 1; -} - -std::array IdentityMatrix() { - return {1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f}; -} - -} // namespace - -ScriptEngine::ScriptEngine(const std::filesystem::path& scriptPath, bool debugEnabled) - : L_(luaL_newstate()), - scriptDirectory_(scriptPath.parent_path()), - debugEnabled_(debugEnabled), - physicsBridge_(std::make_unique()) { - if (!L_) { - throw std::runtime_error("Failed to create Lua state"); - } - luaL_openlibs(L_); - lua_pushlightuserdata(L_, this); - lua_pushcclosure(L_, &LuaLoadMeshFromFile, 1); - lua_setglobal(L_, "load_mesh_from_file"); - lua_pushlightuserdata(L_, this); - lua_pushcclosure(L_, &LuaPhysicsCreateBox, 1); - lua_setglobal(L_, "physics_create_box"); - lua_pushlightuserdata(L_, this); - lua_pushcclosure(L_, &LuaPhysicsStepSimulation, 1); - lua_setglobal(L_, "physics_step_simulation"); - lua_pushlightuserdata(L_, this); - lua_pushcclosure(L_, &LuaPhysicsGetTransform, 1); - lua_setglobal(L_, "physics_get_transform"); - lua_pushlightuserdata(L_, this); - lua_pushcclosure(L_, &LuaGlmMatrixFromTransform, 1); - lua_setglobal(L_, "glm_matrix_from_transform"); - lua_pushlightuserdata(L_, this); - lua_pushcclosure(L_, &LuaAudioPlayBackground, 1); - lua_setglobal(L_, "audio_play_background"); - lua_pushlightuserdata(L_, this); - lua_pushcclosure(L_, &LuaAudioPlaySound, 1); - lua_setglobal(L_, "audio_play_sound"); - lua_pushboolean(L_, debugEnabled_); - lua_setglobal(L_, "lua_debug"); - auto scriptDir = scriptPath.parent_path(); - if (!scriptDir.empty()) { - lua_getglobal(L_, "package"); - if (lua_istable(L_, -1)) { - lua_getfield(L_, -1, "path"); - const char* currentPath = lua_tostring(L_, -1); - std::string newPath = scriptDir.string() + "/?.lua;"; - if (currentPath) { - newPath += currentPath; - } - lua_pop(L_, 1); - lua_pushstring(L_, newPath.c_str()); - lua_setfield(L_, -2, "path"); - } - lua_pop(L_, 1); - } - if (luaL_dofile(L_, scriptPath.string().c_str()) != LUA_OK) { - std::string message = LuaErrorMessage(L_); - lua_pop(L_, 1); - lua_close(L_); - L_ = nullptr; - throw std::runtime_error("Failed to load Lua script: " + message); - } - - lua_getglobal(L_, "gui_input"); - if (!lua_isnil(L_, -1)) { - guiInputRef_ = luaL_ref(L_, LUA_REGISTRYINDEX); - } else { - lua_pop(L_, 1); - } - - lua_getglobal(L_, "get_gui_commands"); - if (lua_isfunction(L_, -1)) { - guiCommandsFnRef_ = luaL_ref(L_, LUA_REGISTRYINDEX); - } else { - lua_pop(L_, 1); - } -} - -ScriptEngine::~ScriptEngine() { - if (L_) { - if (guiInputRef_ != LUA_REFNIL) { - luaL_unref(L_, LUA_REGISTRYINDEX, guiInputRef_); - } - if (guiCommandsFnRef_ != LUA_REFNIL) { - luaL_unref(L_, LUA_REGISTRYINDEX, guiCommandsFnRef_); - } - lua_close(L_); - } -} - -std::vector ScriptEngine::LoadSceneObjects() { - lua_getglobal(L_, "get_scene_objects"); - if (!lua_isfunction(L_, -1)) { - lua_pop(L_, 1); - throw std::runtime_error("Lua function 'get_scene_objects' is missing"); - } - if (lua_pcall(L_, 0, 1, 0) != LUA_OK) { - std::string message = LuaErrorMessage(L_); - lua_pop(L_, 1); - throw std::runtime_error("Lua get_scene_objects failed: " + message); - } - if (!lua_istable(L_, -1)) { - lua_pop(L_, 1); - throw std::runtime_error("'get_scene_objects' did not return a table"); - } - - size_t count = lua_rawlen(L_, -1); - std::vector objects; - objects.reserve(count); - - for (size_t i = 1; i <= count; ++i) { - lua_rawgeti(L_, -1, static_cast(i)); - if (!lua_istable(L_, -1)) { - lua_pop(L_, 1); - throw std::runtime_error("Scene object at index " + std::to_string(i) + " is not a table"); - } - - SceneObject object; - lua_getfield(L_, -1, "vertices"); - object.vertices = ReadVertexArray(L_, -1); - lua_pop(L_, 1); - if (object.vertices.empty()) { - lua_pop(L_, 1); - throw std::runtime_error("Scene object " + std::to_string(i) + " must supply at least one vertex"); - } - - lua_getfield(L_, -1, "indices"); - object.indices = ReadIndexArray(L_, -1); - lua_pop(L_, 1); - if (object.indices.empty()) { - lua_pop(L_, 1); - throw std::runtime_error("Scene object " + std::to_string(i) + " must supply indices"); - } - - lua_getfield(L_, -1, "compute_model_matrix"); - if (lua_isfunction(L_, -1)) { - object.computeModelMatrixRef = luaL_ref(L_, LUA_REGISTRYINDEX); - } else { - lua_pop(L_, 1); - object.computeModelMatrixRef = LUA_REFNIL; - } - - lua_getfield(L_, -1, "shader_key"); - if (lua_isstring(L_, -1)) { - object.shaderKey = lua_tostring(L_, -1); - } - lua_pop(L_, 1); - - objects.push_back(std::move(object)); - lua_pop(L_, 1); - } - - lua_pop(L_, 1); - return objects; -} - -std::array ScriptEngine::ComputeModelMatrix(int functionRef, float time) { - if (functionRef == LUA_REFNIL) { - lua_getglobal(L_, "compute_model_matrix"); - if (!lua_isfunction(L_, -1)) { - lua_pop(L_, 1); - return IdentityMatrix(); - } - } else { - lua_rawgeti(L_, LUA_REGISTRYINDEX, functionRef); - } - - lua_pushnumber(L_, time); - if (lua_pcall(L_, 1, 1, 0) != LUA_OK) { - std::string message = LuaErrorMessage(L_); - lua_pop(L_, 1); - throw std::runtime_error("Lua compute_model_matrix failed: " + message); - } - if (!lua_istable(L_, -1)) { - lua_pop(L_, 1); - throw std::runtime_error("'compute_model_matrix' did not return a table"); - } - - std::array matrix = detail::ReadMatrix(L_, -1); - lua_pop(L_, 1); - return matrix; -} - -std::array ScriptEngine::GetViewProjectionMatrix(float aspect) { - lua_getglobal(L_, "get_view_projection"); - if (!lua_isfunction(L_, -1)) { - lua_pop(L_, 1); - throw std::runtime_error("Lua function 'get_view_projection' is missing"); - } - lua_pushnumber(L_, aspect); - if (lua_pcall(L_, 1, 1, 0) != LUA_OK) { - std::string message = LuaErrorMessage(L_); - lua_pop(L_, 1); - throw std::runtime_error("Lua get_view_projection failed: " + message); - } - if (!lua_istable(L_, -1)) { - lua_pop(L_, 1); - throw std::runtime_error("'get_view_projection' did not return a table"); - } - std::array matrix = detail::ReadMatrix(L_, -1); - lua_pop(L_, 1); - return matrix; -} - -std::vector ScriptEngine::ReadVertexArray(lua_State* L, int index) { - int absIndex = lua_absindex(L, index); - if (!lua_istable(L, absIndex)) { - throw std::runtime_error("Expected table for vertex data"); - } - - size_t count = lua_rawlen(L, absIndex); - std::vector vertices; - vertices.reserve(count); - - for (size_t i = 1; i <= count; ++i) { - lua_rawgeti(L, absIndex, static_cast(i)); - if (!lua_istable(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error("Vertex entry at index " + std::to_string(i) + " is not a table"); - } - - int vertexIndex = lua_gettop(L); - core::Vertex vertex{}; - - lua_getfield(L, vertexIndex, "position"); - vertex.position = detail::ReadVector3(L, -1); - lua_pop(L, 1); - - lua_getfield(L, vertexIndex, "color"); - vertex.color = detail::ReadVector3(L, -1); - lua_pop(L, 1); - - lua_pop(L, 1); - vertices.push_back(vertex); - } - - return vertices; -} - -std::vector ScriptEngine::ReadIndexArray(lua_State* L, int index) { - int absIndex = lua_absindex(L, index); - if (!lua_istable(L, absIndex)) { - throw std::runtime_error("Expected table for index data"); - } - - size_t count = lua_rawlen(L, absIndex); - std::vector indices; - indices.reserve(count); - - for (size_t i = 1; i <= count; ++i) { - lua_rawgeti(L, absIndex, static_cast(i)); - if (!lua_isinteger(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error("Index entry at position " + std::to_string(i) + " is not an integer"); - } - lua_Integer value = lua_tointeger(L, -1); - lua_pop(L, 1); - if (value < 1) { - throw std::runtime_error("Index values must be 1 or greater"); - } - indices.push_back(static_cast(value - 1)); - } - - return indices; -} - -std::unordered_map ScriptEngine::LoadShaderPathsMap() { - lua_getglobal(L_, "get_shader_paths"); - if (!lua_isfunction(L_, -1)) { - lua_pop(L_, 1); - throw std::runtime_error("Lua function 'get_shader_paths' is missing"); - } - if (lua_pcall(L_, 0, 1, 0) != LUA_OK) { - std::string message = LuaErrorMessage(L_); - lua_pop(L_, 1); - throw std::runtime_error("Lua get_shader_paths failed: " + message); - } - if (!lua_istable(L_, -1)) { - lua_pop(L_, 1); - throw std::runtime_error("'get_shader_paths' did not return a table"); - } - - std::unordered_map shaderMap; - lua_pushnil(L_); - while (lua_next(L_, -2) != 0) { - if (lua_isstring(L_, -2) && lua_istable(L_, -1)) { - std::string key = lua_tostring(L_, -2); - shaderMap.emplace(key, ReadShaderPathsTable(L_, -1)); - } - lua_pop(L_, 1); - } - - lua_pop(L_, 1); - if (shaderMap.empty()) { - throw std::runtime_error("'get_shader_paths' did not return any shader variants"); - } - return shaderMap; -} - -ScriptEngine::ShaderPaths ScriptEngine::ReadShaderPathsTable(lua_State* L, int index) { - ShaderPaths paths; - int absIndex = lua_absindex(L, index); - - lua_getfield(L, absIndex, "vertex"); - if (!lua_isstring(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error("Shader path 'vertex' must be a string"); - } - paths.vertex = lua_tostring(L, -1); - lua_pop(L, 1); - - lua_getfield(L, absIndex, "fragment"); - if (!lua_isstring(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error("Shader path 'fragment' must be a string"); - } - paths.fragment = lua_tostring(L, -1); - lua_pop(L, 1); - - return paths; -} - -std::string ScriptEngine::LuaErrorMessage(lua_State* L) { - const char* message = lua_tostring(L, -1); - return message ? message : "unknown lua error"; -} - -PhysicsBridge& ScriptEngine::GetPhysicsBridge() { - return *physicsBridge_; -} - -std::vector ScriptEngine::LoadGuiCommands() { - std::vector commands; - if (guiCommandsFnRef_ == LUA_REFNIL) { - return commands; - } - lua_rawgeti(L_, LUA_REGISTRYINDEX, guiCommandsFnRef_); - if (lua_pcall(L_, 0, 1, 0) != LUA_OK) { - std::string message = LuaErrorMessage(L_); - lua_pop(L_, 1); - throw std::runtime_error("Lua get_gui_commands failed: " + message); - } - if (!lua_istable(L_, -1)) { - lua_pop(L_, 1); - throw std::runtime_error("'get_gui_commands' did not return a table"); - } - - size_t count = lua_rawlen(L_, -1); - commands.reserve(count); - - for (size_t i = 1; i <= count; ++i) { - lua_rawgeti(L_, -1, static_cast(i)); - if (!lua_istable(L_, -1)) { - lua_pop(L_, 1); - throw std::runtime_error("GUI command at index " + std::to_string(i) + " is not a table"); - } - int commandIndex = lua_gettop(L_); - lua_getfield(L_, commandIndex, "type"); - const char* typeName = lua_tostring(L_, -1); - if (!typeName) { - lua_pop(L_, 2); - throw std::runtime_error("GUI command at index " + std::to_string(i) + " is missing a type"); - } - GuiCommand command{}; - if (std::strcmp(typeName, "rect") == 0) { - command.type = GuiCommand::Type::Rect; - command.rect = ReadRect(L_, commandIndex); - command.color = ReadColor(L_, commandIndex, GuiColor{0.0f, 0.0f, 0.0f, 1.0f}); - command.borderColor = ReadColor(L_, commandIndex, GuiColor{0.0f, 0.0f, 0.0f, 0.0f}); - lua_getfield(L_, commandIndex, "borderWidth"); - if (lua_isnumber(L_, -1)) { - command.borderWidth = static_cast(lua_tonumber(L_, -1)); - } - lua_pop(L_, 1); - } else if (std::strcmp(typeName, "text") == 0) { - command.type = GuiCommand::Type::Text; - ReadStringField(L_, commandIndex, "text", command.text); - lua_getfield(L_, commandIndex, "fontSize"); - if (lua_isnumber(L_, -1)) { - command.fontSize = static_cast(lua_tonumber(L_, -1)); - } - lua_pop(L_, 1); - std::string align; - if (ReadStringField(L_, commandIndex, "alignX", align)) { - command.alignX = align; - } - if (ReadStringField(L_, commandIndex, "alignY", align)) { - command.alignY = align; - } - lua_getfield(L_, commandIndex, "clipRect"); - if (lua_istable(L_, -1)) { - command.clipRect = ReadRect(L_, -1); - command.hasClipRect = true; - } - lua_pop(L_, 1); - lua_getfield(L_, commandIndex, "bounds"); - if (lua_istable(L_, -1)) { - command.bounds = ReadRect(L_, -1); - command.hasBounds = true; - } - lua_pop(L_, 1); - command.color = ReadColor(L_, commandIndex, GuiColor{1.0f, 1.0f, 1.0f, 1.0f}); - } else if (std::strcmp(typeName, "clip_push") == 0) { - command.type = GuiCommand::Type::ClipPush; - command.rect = ReadRect(L_, commandIndex); - } else if (std::strcmp(typeName, "clip_pop") == 0) { - command.type = GuiCommand::Type::ClipPop; - } else if (std::strcmp(typeName, "svg") == 0) { - command.type = GuiCommand::Type::Svg; - ReadStringField(L_, commandIndex, "path", command.svgPath); - command.rect = ReadRect(L_, commandIndex); - command.svgTint = ReadColor(L_, commandIndex, GuiColor{1.0f, 1.0f, 1.0f, 0.0f}); - lua_getfield(L_, commandIndex, "tint"); - if (lua_istable(L_, -1)) { - command.svgTint = ReadColor(L_, -1, command.svgTint); - } - lua_pop(L_, 1); - } - lua_pop(L_, 1); - lua_pop(L_, 1); - commands.push_back(std::move(command)); - } - - lua_pop(L_, 1); - return commands; -} - -void ScriptEngine::UpdateGuiInput(const GuiInputSnapshot& input) { - if (guiInputRef_ == LUA_REFNIL) { - return; - } - lua_rawgeti(L_, LUA_REGISTRYINDEX, guiInputRef_); - int stateIndex = lua_gettop(L_); - - lua_getfield(L_, stateIndex, "resetTransient"); - lua_pushvalue(L_, stateIndex); - lua_call(L_, 1, 0); - - lua_getfield(L_, stateIndex, "setMouse"); - lua_pushvalue(L_, stateIndex); - lua_pushnumber(L_, input.mouseX); - lua_pushnumber(L_, input.mouseY); - lua_pushboolean(L_, input.mouseDown); - lua_call(L_, 4, 0); - - lua_getfield(L_, stateIndex, "setWheel"); - lua_pushvalue(L_, stateIndex); - lua_pushnumber(L_, input.wheel); - lua_call(L_, 2, 0); - - if (!input.textInput.empty()) { - lua_getfield(L_, stateIndex, "addTextInput"); - lua_pushvalue(L_, stateIndex); - lua_pushstring(L_, input.textInput.c_str()); - lua_call(L_, 2, 0); - } - - for (const auto& [key, pressed] : input.keyStates) { - lua_getfield(L_, stateIndex, "setKey"); - lua_pushvalue(L_, stateIndex); - lua_pushstring(L_, key.c_str()); - lua_pushboolean(L_, pressed); - lua_call(L_, 3, 0); - } - - lua_pop(L_, 1); -} - -bool ScriptEngine::HasGuiCommands() const { - return guiCommandsFnRef_ != LUA_REFNIL; -} - -std::filesystem::path ScriptEngine::GetScriptDirectory() const { - return scriptDirectory_; -} - -GuiCommand::RectData ScriptEngine::ReadRect(lua_State* L, int index) { - GuiCommand::RectData rect{}; - if (!lua_istable(L, index)) { - return rect; - } - int absIndex = lua_absindex(L, index); - auto readField = [&](const char* name, float defaultValue) -> float { - lua_getfield(L, absIndex, name); - float value = defaultValue; - if (lua_isnumber(L, -1)) { - value = static_cast(lua_tonumber(L, -1)); - } - lua_pop(L, 1); - return value; - }; - rect.x = readField("x", rect.x); - rect.y = readField("y", rect.y); - rect.width = readField("width", rect.width); - rect.height = readField("height", rect.height); - return rect; -} - -GuiColor ScriptEngine::ReadColor(lua_State* L, int index, const GuiColor& defaultColor) { - GuiColor color = defaultColor; - if (!lua_istable(L, index)) { - return color; - } - int absIndex = lua_absindex(L, index); - for (int component = 0; component < 4; ++component) { - lua_rawgeti(L, absIndex, component + 1); - if (lua_isnumber(L, -1)) { - float value = static_cast(lua_tonumber(L, -1)); - switch (component) { - case 0: color.r = value; break; - case 1: color.g = value; break; - case 2: color.b = value; break; - case 3: color.a = value; break; - } - } - lua_pop(L, 1); - } - return color; -} - -bool ScriptEngine::ReadStringField(lua_State* L, int index, const char* name, std::string& outString) { - int absIndex = lua_absindex(L, index); - lua_getfield(L, absIndex, name); - if (lua_isstring(L, -1)) { - outString = lua_tostring(L, -1); - lua_pop(L, 1); - return true; - } - lua_pop(L, 1); - return false; -} - -void ScriptEngine::SetAudioPlayer(app::AudioPlayer* audioPlayer) { - audioPlayer_ = audioPlayer; - if (!audioPlayer_) { - return; - } - for (const auto& command : pendingAudioCommands_) { - try { - ExecuteAudioCommand(audioPlayer_, command); - } catch (const std::exception& exc) { - std::cerr << "AudioPlayer: " << exc.what() << '\n'; - } - } - pendingAudioCommands_.clear(); -} - -bool ScriptEngine::QueueAudioCommand(AudioCommandType type, std::string path, bool loop, std::string& error) { - if (audioPlayer_) { - try { - AudioCommand command{type, std::move(path), loop}; - ExecuteAudioCommand(audioPlayer_, command); - return true; - } catch (const std::exception& exc) { - error = exc.what(); - return false; - } - } - pendingAudioCommands_.push_back(AudioCommand{type, std::move(path), loop}); - return true; -} - -void ScriptEngine::ExecuteAudioCommand(app::AudioPlayer* player, const AudioCommand& command) { - auto resolved = ResolveScriptPath(command.path); - if (!std::filesystem::exists(resolved)) { - throw std::runtime_error("Audio file not found: " + resolved.string()); - } - switch (command.type) { - case AudioCommandType::Background: - player->PlayBackground(resolved, command.loop); - break; - case AudioCommandType::Effect: - player->PlayEffect(resolved, command.loop); - break; - } -} - -std::filesystem::path ScriptEngine::ResolveScriptPath(const std::string& requested) const { - std::filesystem::path resolved(requested); - if (!resolved.is_absolute()) { - resolved = scriptDirectory_ / resolved; - } - std::error_code ec; - auto canonical = std::filesystem::weakly_canonical(resolved, ec); - if (!ec) { - resolved = canonical; - } - return resolved; -} - -} // namespace sdl3cpp::script diff --git a/src/script/script_engine.hpp b/src/script/script_engine.hpp index 4616f78..46a0be9 100644 --- a/src/script/script_engine.hpp +++ b/src/script/script_engine.hpp @@ -10,7 +10,6 @@ #include -#include "core/vertex.hpp" #include "script/gui_types.hpp" #include "script/scene_manager.hpp" #include "script/shader_manager.hpp" @@ -20,6 +19,14 @@ namespace sdl3cpp::script { struct LuaBindingContext; +} + +namespace sdl3cpp::services { +class ILogger; +} + +namespace sdl3cpp::script { + class ScriptEngine { public: explicit ScriptEngine(const std::filesystem::path& scriptPath, bool debugEnabled = false); @@ -40,21 +47,14 @@ public: std::string GetLuaError(); private: - static std::vector ReadVertexArray(lua_State* L, int index); - static std::vector ReadIndexArray(lua_State* L, int index); - static std::string LuaErrorMessage(lua_State* L); - static GuiCommand::RectData ReadRect(lua_State* L, int index); - static GuiColor ReadColor(lua_State* L, int index, const GuiColor& defaultColor); - static bool ReadStringField(lua_State* L, int index, const char* name, std::string& outString); - lua_State* L_ = nullptr; - int guiInputRef_ = LUA_REFNIL; - int guiCommandsFnRef_ = LUA_REFNIL; std::filesystem::path scriptDirectory_; bool debugEnabled_ = false; + std::unique_ptr ownedBindingContext_; std::unique_ptr sceneManager_; std::unique_ptr shaderManager_; std::unique_ptr guiManager_; + std::shared_ptr logger_; }; } // namespace sdl3cpp::script diff --git a/src/script/shader_manager.cpp b/src/script/shader_manager.cpp index 1c2af0f..5780f31 100644 --- a/src/script/shader_manager.cpp +++ b/src/script/shader_manager.cpp @@ -1,5 +1,5 @@ #include "script/shader_manager.hpp" -#include "logging/logger.hpp" +#include "services/interfaces/i_logger.hpp" #include @@ -9,23 +9,38 @@ namespace sdl3cpp::script { -ShaderManager::ShaderManager(lua_State* L) : L_(L) { - sdl3cpp::logging::TraceGuard trace; +ShaderManager::ShaderManager(lua_State* L, std::shared_ptr logger) + : L_(L), logger_(logger) { + if (logger_) { + logger_->Trace("ShaderManager", "ShaderManager"); + } } std::unordered_map ShaderManager::LoadShaderPathsMap() { + if (logger_) { + logger_->Trace("ShaderManager", "LoadShaderPathsMap"); + } lua_getglobal(L_, "get_shader_paths"); if (!lua_isfunction(L_, -1)) { lua_pop(L_, 1); + if (logger_) { + logger_->Error("Lua function 'get_shader_paths' is missing"); + } throw std::runtime_error("Lua function 'get_shader_paths' is missing"); } if (lua_pcall(L_, 0, 1, 0) != LUA_OK) { std::string message = GetLuaError(); lua_pop(L_, 1); + if (logger_) { + logger_->Error("Lua get_shader_paths failed: " + message); + } throw std::runtime_error("Lua get_shader_paths failed: " + message); } if (!lua_istable(L_, -1)) { lua_pop(L_, 1); + if (logger_) { + logger_->Error("'get_shader_paths' did not return a table"); + } throw std::runtime_error("'get_shader_paths' did not return a table"); } @@ -41,6 +56,9 @@ std::unordered_map ShaderManager::L lua_pop(L_, 1); if (shaderMap.empty()) { + if (logger_) { + logger_->Error("'get_shader_paths' did not return any shader variants"); + } throw std::runtime_error("'get_shader_paths' did not return any shader variants"); } return shaderMap; @@ -53,6 +71,9 @@ sdl3cpp::services::ShaderPaths ShaderManager::ReadShaderPathsTable(int index) { lua_getfield(L_, absIndex, "vertex"); if (!lua_isstring(L_, -1)) { lua_pop(L_, 1); + if (logger_) { + logger_->Error("Shader path 'vertex' must be a string"); + } throw std::runtime_error("Shader path 'vertex' must be a string"); } paths.vertex = lua_tostring(L_, -1); @@ -61,6 +82,9 @@ sdl3cpp::services::ShaderPaths ShaderManager::ReadShaderPathsTable(int index) { lua_getfield(L_, absIndex, "fragment"); if (!lua_isstring(L_, -1)) { lua_pop(L_, 1); + if (logger_) { + logger_->Error("Shader path 'fragment' must be a string"); + } throw std::runtime_error("Shader path 'fragment' must be a string"); } paths.fragment = lua_tostring(L_, -1); @@ -74,4 +98,4 @@ std::string ShaderManager::GetLuaError() { return message ? message : "unknown lua error"; } -} // namespace sdl3cpp::script \ No newline at end of file +} // namespace sdl3cpp::script diff --git a/src/script/shader_manager.hpp b/src/script/shader_manager.hpp index 1eedbe7..7e14af9 100644 --- a/src/script/shader_manager.hpp +++ b/src/script/shader_manager.hpp @@ -3,22 +3,28 @@ #include "../services/interfaces/graphics_types.hpp" #include +#include #include #include +namespace sdl3cpp::services { +class ILogger; +} + namespace sdl3cpp::script { class ShaderManager { public: - explicit ShaderManager(lua_State* L); + ShaderManager(lua_State* L, std::shared_ptr logger); std::unordered_map LoadShaderPathsMap(); private: lua_State* L_; + std::shared_ptr logger_; sdl3cpp::services::ShaderPaths ReadShaderPathsTable(int index); std::string GetLuaError(); }; -} // namespace sdl3cpp::script \ No newline at end of file +} // namespace sdl3cpp::script diff --git a/src/services/impl/json_config_service.cpp b/src/services/impl/json_config_service.cpp index ad0f344..678b313 100644 --- a/src/services/impl/json_config_service.cpp +++ b/src/services/impl/json_config_service.cpp @@ -1,6 +1,5 @@ #include "json_config_service.hpp" #include "../interfaces/i_logger.hpp" -#include "../../logging/string_utils.hpp" #include #include #include @@ -57,8 +56,8 @@ std::filesystem::path JsonConfigService::FindScriptPath(const char* argv0) { } RuntimeConfig JsonConfigService::LoadFromJson(std::shared_ptr logger, const std::filesystem::path& configPath, bool dumpConfig) { - using logging::ToString; - logger->Trace("JsonConfigService::LoadFromJson", "LoadFromJson", configPath.string() + " " + ToString(dumpConfig), ""); + std::string args = configPath.string() + " dumpConfig=" + (dumpConfig ? "true" : "false"); + logger->Trace("JsonConfigService", "LoadFromJson", args); std::ifstream configStream(configPath); if (!configStream) { diff --git a/src/services/impl/lua_script_service.cpp b/src/services/impl/lua_script_service.cpp deleted file mode 100644 index a267512..0000000 --- a/src/services/impl/lua_script_service.cpp +++ /dev/null @@ -1,121 +0,0 @@ -#include "lua_script_service.hpp" -#include -#include - -namespace sdl3cpp::services::impl { - -LuaScriptService::LuaScriptService(std::shared_ptr engineService, std::shared_ptr logger) - : engineService_(std::move(engineService)), logger_(std::move(logger)) { -} - -LuaScriptService::~LuaScriptService() { - if (initialized_) { - Shutdown(); - } -} - -void LuaScriptService::Initialize() { - logger_->TraceFunction(__func__); - - if (initialized_) { - return; - } - - if (!engineService_ || !engineService_->IsInitialized()) { - throw std::runtime_error("Script engine service not initialized"); - } - - initialized_ = true; - - logger_->Info("Script service initialized"); -} - -void LuaScriptService::Shutdown() noexcept { - logger_->TraceFunction(__func__); - - if (!initialized_) { - return; - } - - initialized_ = false; - - logger_->Info("Script service shutdown"); -} - -std::vector LuaScriptService::LoadSceneObjects() { - logger_->TraceFunction(__func__); - - if (!engineService_ || !engineService_->IsInitialized()) { - throw std::runtime_error("Script service not initialized"); - } - - return engineService_->GetEngine().LoadSceneObjects(); -} - -std::array LuaScriptService::ComputeModelMatrix(int functionRef, float time) { - if (!engineService_ || !engineService_->IsInitialized()) { - throw std::runtime_error("Script service not initialized"); - } - - return engineService_->GetEngine().ComputeModelMatrix(functionRef, time); -} - -std::array LuaScriptService::GetViewProjectionMatrix(float aspect) { - if (!engineService_ || !engineService_->IsInitialized()) { - throw std::runtime_error("Script service not initialized"); - } - - return engineService_->GetEngine().GetViewProjectionMatrix(aspect); -} - -std::unordered_map LuaScriptService::LoadShaderPathsMap() { - logger_->TraceFunction(__func__); - - if (!engineService_ || !engineService_->IsInitialized()) { - throw std::runtime_error("Script service not initialized"); - } - - return engineService_->GetEngine().LoadShaderPathsMap(); -} - -std::vector LuaScriptService::LoadGuiCommands() { - if (!engineService_ || !engineService_->IsInitialized()) { - return {}; - } - - return engineService_->GetEngine().LoadGuiCommands(); -} - -void LuaScriptService::UpdateGuiInput(const script::GuiInputSnapshot& input) { - if (!engineService_ || !engineService_->IsInitialized()) { - return; - } - - engineService_->GetEngine().UpdateGuiInput(input); -} - -bool LuaScriptService::HasGuiCommands() const { - if (!engineService_ || !engineService_->IsInitialized()) { - return false; - } - - return engineService_->GetEngine().HasGuiCommands(); -} - -std::filesystem::path LuaScriptService::GetScriptDirectory() const { - if (!engineService_ || !engineService_->IsInitialized()) { - return {}; - } - - return engineService_->GetEngine().GetScriptDirectory(); -} - -std::string LuaScriptService::GetLuaError() { - if (!engineService_ || !engineService_->IsInitialized()) { - return "Script service not initialized"; - } - - return engineService_->GetEngine().GetLuaError(); -} - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/lua_script_service.hpp b/src/services/impl/lua_script_service.hpp deleted file mode 100644 index 55c9e56..0000000 --- a/src/services/impl/lua_script_service.hpp +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include "../interfaces/i_script_service.hpp" -#include "../interfaces/i_script_engine_service.hpp" -#include "../interfaces/i_logger.hpp" -#include "../../di/lifecycle.hpp" -#include -#include - -namespace sdl3cpp::services::impl { - -/** - * @brief Lua script service implementation. - * - * Small wrapper service (~100 lines) around ScriptEngine. - * Provides Lua script execution and integration with game systems. - */ -class LuaScriptService : public IScriptService, - public di::IInitializable, - public di::IShutdownable { -public: - explicit LuaScriptService(std::shared_ptr engineService, std::shared_ptr logger); - ~LuaScriptService() override; - - // Lifecycle - void Initialize() override; - void Shutdown() noexcept override; - - // IScriptService interface - std::vector LoadSceneObjects() override; - std::array ComputeModelMatrix(int functionRef, float time) override; - std::array GetViewProjectionMatrix(float aspect) override; - - std::unordered_map LoadShaderPathsMap() override; - - std::vector LoadGuiCommands() override; - void UpdateGuiInput(const script::GuiInputSnapshot& input) override; - bool HasGuiCommands() const override; - - std::filesystem::path GetScriptDirectory() const override; - std::string GetLuaError() override; - -private: - std::shared_ptr engineService_; - std::shared_ptr logger_; - bool initialized_ = false; -}; - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/physics_bridge_service.cpp b/src/services/impl/physics_bridge_service.cpp index 8dc4272..812ef8d 100644 --- a/src/services/impl/physics_bridge_service.cpp +++ b/src/services/impl/physics_bridge_service.cpp @@ -4,8 +4,11 @@ namespace sdl3cpp::services::impl { PhysicsBridgeService::PhysicsBridgeService(std::shared_ptr logger) - : logger_(std::move(logger)), - bridge_(std::make_unique()) { + : bridge_(std::make_unique(logger)), + logger_(std::move(logger)) { + if (logger_) { + logger_->Trace("PhysicsBridgeService", "PhysicsBridgeService"); + } } bool PhysicsBridgeService::AddBoxRigidBody(const std::string& name, @@ -13,6 +16,9 @@ bool PhysicsBridgeService::AddBoxRigidBody(const std::string& name, float mass, const btTransform& transform, std::string& error) { + if (logger_) { + logger_->Trace("PhysicsBridgeService", "AddBoxRigidBody", "name=" + name); + } if (!bridge_) { error = "Physics bridge not initialized"; return false; @@ -21,6 +27,9 @@ bool PhysicsBridgeService::AddBoxRigidBody(const std::string& name, } int PhysicsBridgeService::StepSimulation(float deltaTime) { + if (logger_) { + logger_->Trace("PhysicsBridgeService", "StepSimulation", "deltaTime=" + std::to_string(deltaTime)); + } if (!bridge_) { return 0; } @@ -30,6 +39,9 @@ int PhysicsBridgeService::StepSimulation(float deltaTime) { bool PhysicsBridgeService::GetRigidBodyTransform(const std::string& name, btTransform& outTransform, std::string& error) const { + if (logger_) { + logger_->Trace("PhysicsBridgeService", "GetRigidBodyTransform", "name=" + name); + } if (!bridge_) { error = "Physics bridge not initialized"; return false; diff --git a/src/services/impl/physics_bridge_service.hpp b/src/services/impl/physics_bridge_service.hpp index 175c51a..700fd5a 100644 --- a/src/services/impl/physics_bridge_service.hpp +++ b/src/services/impl/physics_bridge_service.hpp @@ -25,8 +25,8 @@ public: std::string& error) const override; private: - std::shared_ptr logger_; std::unique_ptr bridge_; + std::shared_ptr logger_; }; } // namespace sdl3cpp::services::impl diff --git a/src/services/impl/script_engine_service.cpp b/src/services/impl/script_engine_service.cpp index 7e9dd97..96b6ec3 100644 --- a/src/services/impl/script_engine_service.cpp +++ b/src/services/impl/script_engine_service.cpp @@ -37,6 +37,7 @@ void ScriptEngineService::Initialize() { bindingContext_->meshService = meshService_; bindingContext_->audioCommandService = audioCommandService_; bindingContext_->physicsBridgeService = physicsBridgeService_; + bindingContext_->logger = logger_; engine_ = std::make_unique(scriptPath_, bindingContext_.get(), debugEnabled_); initialized_ = true; diff --git a/src/services/impl/sdl_audio_service.cpp b/src/services/impl/sdl_audio_service.cpp index 16ab8d4..b4f6ce8 100644 --- a/src/services/impl/sdl_audio_service.cpp +++ b/src/services/impl/sdl_audio_service.cpp @@ -1,10 +1,16 @@ #include "sdl_audio_service.hpp" -#include -#include + #include +#include +#include namespace sdl3cpp::services::impl { +namespace { +constexpr int kDecodeChunkSize = 4096; +constexpr int kMixFrames = 1024; +} // namespace + SdlAudioService::SdlAudioService(std::shared_ptr logger) : logger_(logger) {} @@ -15,7 +21,9 @@ SdlAudioService::~SdlAudioService() { } void SdlAudioService::Initialize() { - logger_->TraceFunction(__func__); + if (logger_) { + logger_->TraceFunction(__func__); + } if (initialized_) { return; @@ -27,8 +35,7 @@ void SdlAudioService::Initialize() { } // Set up desired audio spec - SDL_AudioSpec desiredSpec; - SDL_zero(desiredSpec); + SDL_AudioSpec desiredSpec{}; desiredSpec.format = SDL_AUDIO_S16; desiredSpec.channels = 2; desiredSpec.freq = 44100; @@ -40,6 +47,12 @@ void SdlAudioService::Initialize() { throw std::runtime_error("Failed to open audio device stream: " + std::string(SDL_GetError())); } + mixSpec_ = desiredSpec; + SDL_AudioSpec inputSpec{}; + if (SDL_GetAudioStreamFormat(audioStream_, &inputSpec, nullptr)) { + mixSpec_ = inputSpec; + } + // Start the audio stream if (!SDL_ResumeAudioStreamDevice(audioStream_)) { SDL_DestroyAudioStream(audioStream_); @@ -48,11 +61,15 @@ void SdlAudioService::Initialize() { } initialized_ = true; - logger_->Info("SDL audio service initialized successfully"); + if (logger_) { + logger_->Info("SDL audio service initialized successfully"); + } } void SdlAudioService::Shutdown() noexcept { - logger_->TraceFunction(__func__); + if (logger_) { + logger_->TraceFunction(__func__); + } if (!initialized_) { return; @@ -65,21 +82,30 @@ void SdlAudioService::Shutdown() noexcept { audioStream_ = nullptr; } - { - std::lock_guard lock(audioMutex_); - if (backgroundAudio_) { - CleanupAudioData(*backgroundAudio_); - backgroundAudio_.reset(); - } + std::lock_guard lock(audioMutex_); + if (backgroundAudio_) { + CleanupAudioData(*backgroundAudio_); + backgroundAudio_.reset(); } + for (auto& effect : effectAudio_) { + CleanupAudioData(*effect); + } + effectAudio_.clear(); + mixBuffer_.clear(); + tempBuffer_.clear(); + outputBuffer_.clear(); SDL_QuitSubSystem(SDL_INIT_AUDIO); initialized_ = false; - logger_->Info("SDL audio service shutdown"); + if (logger_) { + logger_->Info("SDL audio service shutdown"); + } } void SdlAudioService::PlayBackground(const std::filesystem::path& path, bool loop) { - logger_->TraceFunction(__func__); + if (logger_) { + logger_->TraceFunction(__func__); + } if (!initialized_) { throw std::runtime_error("Audio service not initialized"); @@ -101,24 +127,40 @@ void SdlAudioService::PlayBackground(const std::filesystem::path& path, bool loo } backgroundAudio_->loop = loop; - backgroundAudio_->position = 0; + backgroundAudio_->finished = false; - logger_->Info("Playing background audio: " + path.string() + " (loop: " + std::to_string(loop) + ")"); + if (logger_) { + logger_->Info("Playing background audio: " + path.string() + " (loop: " + std::to_string(loop) + ")"); + } } void SdlAudioService::PlayEffect(const std::filesystem::path& path, bool loop) { - logger_->TraceFunction(__func__); + if (logger_) { + logger_->TraceFunction(__func__); + } if (!initialized_) { throw std::runtime_error("Audio service not initialized"); } - // For now, effects are not implemented - could be added later - logger_->Info("Playing effect audio: " + path.string() + " (loop: " + std::to_string(loop) + ") - NOT IMPLEMENTED"); + std::lock_guard lock(audioMutex_); + auto effect = std::make_unique(); + if (!LoadAudioFile(path, *effect)) { + throw std::runtime_error("Failed to load audio file: " + path.string()); + } + effect->loop = loop; + effect->finished = false; + effectAudio_.push_back(std::move(effect)); + + if (logger_) { + logger_->Info("Playing effect audio: " + path.string() + " (loop: " + std::to_string(loop) + ")"); + } } void SdlAudioService::StopBackground() { - logger_->TraceFunction(__func__); + if (logger_) { + logger_->TraceFunction(__func__); + } if (!initialized_) { return; @@ -130,29 +172,51 @@ void SdlAudioService::StopBackground() { backgroundAudio_.reset(); } - logger_->Info("Stopped background audio"); + if (logger_) { + logger_->Info("Stopped background audio"); + } } void SdlAudioService::StopAll() { - logger_->TraceFunction(__func__); + if (logger_) { + logger_->TraceFunction(__func__); + } - StopBackground(); - // Effects would be stopped here too - logger_->Info("Stopped all audio"); + if (!initialized_) { + return; + } + + std::lock_guard lock(audioMutex_); + if (backgroundAudio_) { + CleanupAudioData(*backgroundAudio_); + backgroundAudio_.reset(); + } + for (auto& effect : effectAudio_) { + CleanupAudioData(*effect); + } + effectAudio_.clear(); + + if (logger_) { + logger_->Info("Stopped all audio"); + } } void SdlAudioService::SetVolume(float volume) { + std::lock_guard lock(audioMutex_); volume_ = std::clamp(volume, 0.0f, 1.0f); - logger_->TraceVariable("volume", volume_); + if (logger_) { + logger_->TraceVariable("volume", volume_); + } } float SdlAudioService::GetVolume() const { + std::lock_guard lock(const_cast(audioMutex_)); return volume_; } bool SdlAudioService::IsBackgroundPlaying() const { std::lock_guard lock(const_cast(audioMutex_)); - return backgroundAudio_ != nullptr; + return backgroundAudio_ && backgroundAudio_->isOpen && !backgroundAudio_->finished; } void SdlAudioService::Update() { @@ -162,66 +226,214 @@ void SdlAudioService::Update() { std::lock_guard lock(audioMutex_); - if (!backgroundAudio_ || !backgroundAudio_->isOpen) { + if (!backgroundAudio_ && effectAudio_.empty()) { return; } - // Check if we need more audio data - if (SDL_GetAudioStreamQueued(audioStream_) < 4096) { - // Read audio data from vorbis file - char buffer[4096]; - int bytesRead = 0; - int totalBytesRead = 0; + const int bytesPerFrame = SDL_AUDIO_FRAMESIZE(mixSpec_); + if (bytesPerFrame <= 0) { + return; + } - while (totalBytesRead < 4096) { - bytesRead = ov_read(&backgroundAudio_->vorbisFile, buffer + totalBytesRead, 4096 - totalBytesRead, 0, 2, 1, nullptr); - if (bytesRead <= 0) { - // End of file - if (backgroundAudio_->loop) { - // Loop back to beginning - ov_pcm_seek(&backgroundAudio_->vorbisFile, 0); - continue; - } else { - // Stop playback - CleanupAudioData(*backgroundAudio_); - backgroundAudio_.reset(); - break; - } - } - totalBytesRead += bytesRead; + int queuedBytes = SDL_GetAudioStreamQueued(audioStream_); + if (queuedBytes < 0) { + if (logger_) { + logger_->Error("Failed to query audio queue: " + std::string(SDL_GetError())); } + return; + } - if (totalBytesRead > 0) { - // Queue the audio data to the stream - if (SDL_PutAudioStreamData(audioStream_, buffer, totalBytesRead) < 0) { - logger_->Error("Failed to queue audio data: " + std::string(SDL_GetError())); + if (queuedBytes >= bytesPerFrame * kMixFrames) { + return; + } + + const int frames = kMixFrames; + const int channels = mixSpec_.channels; + const size_t sampleCount = static_cast(frames * channels); + + mixBuffer_.assign(sampleCount, 0); + bool hasAudio = false; + + if (backgroundAudio_ && backgroundAudio_->isOpen) { + int bytesRead = ReadStreamSamples(*backgroundAudio_, tempBuffer_, frames); + if (bytesRead > 0) { + size_t samplesRead = static_cast(bytesRead / static_cast(sizeof(int16_t))); + for (size_t i = 0; i < samplesRead; ++i) { + mixBuffer_[i] += tempBuffer_[i]; } + hasAudio = true; + } + if (backgroundAudio_->finished && + backgroundAudio_->convertStream && + SDL_GetAudioStreamAvailable(backgroundAudio_->convertStream) == 0) { + CleanupAudioData(*backgroundAudio_); + backgroundAudio_.reset(); + } + } + + for (auto it = effectAudio_.begin(); it != effectAudio_.end(); ) { + AudioData& effect = *(*it); + int bytesRead = ReadStreamSamples(effect, tempBuffer_, frames); + if (bytesRead > 0) { + size_t samplesRead = static_cast(bytesRead / static_cast(sizeof(int16_t))); + for (size_t i = 0; i < samplesRead; ++i) { + mixBuffer_[i] += tempBuffer_[i]; + } + hasAudio = true; + } + bool drained = effect.finished && effect.convertStream && + SDL_GetAudioStreamAvailable(effect.convertStream) == 0; + if (!effect.isOpen || drained) { + CleanupAudioData(effect); + it = effectAudio_.erase(it); + } else { + ++it; + } + } + + if (!hasAudio) { + return; + } + + outputBuffer_.assign(sampleCount, 0); + const float volume = volume_; + for (size_t i = 0; i < sampleCount; ++i) { + int32_t value = static_cast(mixBuffer_[i] * volume); + value = std::clamp(value, -32768, 32767); + outputBuffer_[i] = static_cast(value); + } + + int bytesToQueue = static_cast(sampleCount * sizeof(int16_t)); + if (!SDL_PutAudioStreamData(audioStream_, outputBuffer_.data(), bytesToQueue)) { + if (logger_) { + logger_->Error("Failed to queue audio data: " + std::string(SDL_GetError())); } } } bool SdlAudioService::LoadAudioFile(const std::filesystem::path& path, AudioData& audioData) { - FILE* file = fopen(path.c_str(), "rb"); + std::string pathText = path.string(); + FILE* file = fopen(pathText.c_str(), "rb"); if (!file) { - logger_->Error("Failed to open audio file: " + path.string()); + if (logger_) { + logger_->Error("Failed to open audio file: " + path.string()); + } return false; } if (ov_open(file, &audioData.vorbisFile, nullptr, 0) < 0) { fclose(file); - logger_->Error("Failed to open vorbis file: " + path.string()); + if (logger_) { + logger_->Error("Failed to open vorbis file: " + path.string()); + } + return false; + } + + const vorbis_info* info = ov_info(&audioData.vorbisFile, -1); + if (!info) { + ov_clear(&audioData.vorbisFile); + if (logger_) { + logger_->Error("Failed to read vorbis info: " + path.string()); + } + return false; + } + + audioData.sourceSpec.format = SDL_AUDIO_S16; + audioData.sourceSpec.channels = info->channels; + audioData.sourceSpec.freq = info->rate; + audioData.convertStream = SDL_CreateAudioStream(&audioData.sourceSpec, &mixSpec_); + if (!audioData.convertStream) { + ov_clear(&audioData.vorbisFile); + if (logger_) { + logger_->Error("Failed to create audio stream: " + std::string(SDL_GetError())); + } return false; } audioData.isOpen = true; + audioData.finished = false; return true; } void SdlAudioService::CleanupAudioData(AudioData& audioData) { + if (audioData.convertStream) { + SDL_DestroyAudioStream(audioData.convertStream); + audioData.convertStream = nullptr; + } if (audioData.isOpen) { ov_clear(&audioData.vorbisFile); audioData.isOpen = false; } + audioData.finished = false; +} + +int SdlAudioService::ReadStreamSamples(AudioData& audioData, std::vector& output, int frames) { + if (!audioData.isOpen || !audioData.convertStream) { + return 0; + } + + const int bytesPerFrame = SDL_AUDIO_FRAMESIZE(mixSpec_); + const int bytesNeeded = frames * bytesPerFrame; + const size_t sampleCount = static_cast(bytesNeeded / static_cast(sizeof(int16_t))); + output.assign(sampleCount, 0); + + while (!audioData.finished) { + int available = SDL_GetAudioStreamAvailable(audioData.convertStream); + if (available < 0) { + if (logger_) { + logger_->Error("Failed to query audio stream: " + std::string(SDL_GetError())); + } + audioData.finished = true; + break; + } + if (available >= bytesNeeded) { + break; + } + + char decodeBuffer[kDecodeChunkSize]; + int bytesRead = ov_read(&audioData.vorbisFile, decodeBuffer, kDecodeChunkSize, 0, 2, 1, nullptr); + if (bytesRead > 0) { + if (!SDL_PutAudioStreamData(audioData.convertStream, decodeBuffer, bytesRead)) { + if (logger_) { + logger_->Error("Failed to queue decoded audio: " + std::string(SDL_GetError())); + } + audioData.finished = true; + break; + } + } else if (bytesRead == 0) { + if (audioData.loop) { + ov_pcm_seek(&audioData.vorbisFile, 0); + continue; + } + audioData.finished = true; + SDL_FlushAudioStream(audioData.convertStream); + break; + } else { + if (logger_) { + logger_->Error("Vorbis decode error"); + } + audioData.finished = true; + break; + } + } + + int bytesRead = SDL_GetAudioStreamData(audioData.convertStream, output.data(), bytesNeeded); + if (bytesRead < 0) { + if (logger_) { + logger_->Error("Failed to read audio stream data: " + std::string(SDL_GetError())); + } + audioData.finished = true; + return 0; + } + + if (bytesRead < bytesNeeded) { + const size_t samplesRead = static_cast(bytesRead / static_cast(sizeof(int16_t))); + if (samplesRead < output.size()) { + std::fill(output.begin() + samplesRead, output.end(), 0); + } + } + + return bytesRead; } } // namespace sdl3cpp::services::impl diff --git a/src/services/impl/sdl_audio_service.hpp b/src/services/impl/sdl_audio_service.hpp index 162fb46..736d28a 100644 --- a/src/services/impl/sdl_audio_service.hpp +++ b/src/services/impl/sdl_audio_service.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -40,27 +41,35 @@ public: bool IsBackgroundPlaying() const override; // Update method to be called regularly (e.g., from main loop) - void Update(); + void Update() override; private: struct AudioData { - OggVorbis_File vorbisFile; + OggVorbis_File vorbisFile{}; + SDL_AudioStream* convertStream = nullptr; + SDL_AudioSpec sourceSpec{}; bool isOpen = false; bool loop = false; - size_t position = 0; + bool finished = false; }; bool LoadAudioFile(const std::filesystem::path& path, AudioData& audioData); void CleanupAudioData(AudioData& audioData); + int ReadStreamSamples(AudioData& audioData, std::vector& output, int frames); std::shared_ptr logger_; float volume_ = 1.0f; bool initialized_ = false; SDL_AudioStream* audioStream_ = nullptr; + SDL_AudioSpec mixSpec_{}; std::unique_ptr backgroundAudio_; + std::vector> effectAudio_; std::mutex audioMutex_; + std::vector mixBuffer_; + std::vector tempBuffer_; + std::vector outputBuffer_; }; } // namespace sdl3cpp::services::impl diff --git a/src/services/interfaces/i_audio_service.hpp b/src/services/interfaces/i_audio_service.hpp index 9dc7aff..e426c3e 100644 --- a/src/services/interfaces/i_audio_service.hpp +++ b/src/services/interfaces/i_audio_service.hpp @@ -79,6 +79,13 @@ public: * @return true if playing, false otherwise */ virtual bool IsBackgroundPlaying() const = 0; + + /** + * @brief Update streaming audio buffers. + * + * Call regularly from the main loop to feed audio data. + */ + virtual void Update() = 0; }; } // namespace sdl3cpp::services diff --git a/src/services/interfaces/i_scene_service.hpp b/src/services/interfaces/i_scene_service.hpp index ac98da6..4cd5511 100644 --- a/src/services/interfaces/i_scene_service.hpp +++ b/src/services/interfaces/i_scene_service.hpp @@ -1,7 +1,7 @@ #pragma once #include "i_graphics_service.hpp" -#include "i_script_service.hpp" +#include "../../script/scene_manager.hpp" #include namespace sdl3cpp::services { diff --git a/src/services/interfaces/i_script_service.hpp b/src/services/interfaces/i_script_service.hpp deleted file mode 100644 index c6c9220..0000000 --- a/src/services/interfaces/i_script_service.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include "../../script/scene_manager.hpp" -#include "../../script/shader_manager.hpp" -#include "../../script/gui_types.hpp" -#include -#include -#include -#include -#include - -namespace sdl3cpp::services { - -/** - * @brief Script service interface. - * - * Provides Lua script execution and integration with scene, shaders, and GUI. - */ -class IScriptService { -public: - virtual ~IScriptService() = default; - - // Scene management - virtual std::vector LoadSceneObjects() = 0; - virtual std::array ComputeModelMatrix(int functionRef, float time) = 0; - virtual std::array GetViewProjectionMatrix(float aspect) = 0; - - // Shader management - virtual std::unordered_map LoadShaderPathsMap() = 0; - - // GUI management - virtual std::vector LoadGuiCommands() = 0; - virtual void UpdateGuiInput(const script::GuiInputSnapshot& input) = 0; - virtual bool HasGuiCommands() const = 0; - - // Utility - virtual std::filesystem::path GetScriptDirectory() const = 0; - virtual std::string GetLuaError() = 0; -}; - -} // namespace sdl3cpp::services