diff --git a/CMakeLists.txt b/CMakeLists.txt index d456b89..c78bd8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,7 +123,6 @@ else() endif() if(NOT ENABLE_VITA) -find_package(lua CONFIG REQUIRED) find_package(CLI11 CONFIG REQUIRED) find_package(RapidJSON CONFIG REQUIRED) find_package(EnTT CONFIG REQUIRED) @@ -352,16 +351,11 @@ if(BUILD_SDL3_APP) src/services/impl/scene/ecs_service.cpp src/services/impl/platform/platform_service.cpp src/services/impl/diagnostics/probe_service.cpp - src/services/impl/script/script_engine_service.cpp - src/services/impl/script/lua_helpers.cpp - src/services/impl/script/scene_script_service.cpp - src/services/impl/script/shader_script_service.cpp src/services/impl/shader/shader_system_registry.cpp src/services/impl/shader/materialx_shader_system.cpp src/services/impl/shader/glsl_shader_system.cpp src/services/impl/materialx/materialx_shader_generator.cpp src/services/impl/shader/shader_pipeline_validator.cpp - src/services/impl/script/gui_script_service.cpp $<$>:src/services/impl/gui/bgfx_gui_service.cpp> $<$>:src/services/impl/graphics/bgfx_shader_compiler.cpp> src/services/impl/audio/audio_command_service.cpp @@ -391,7 +385,6 @@ if(BUILD_SDL3_APP) target_include_directories(sdl3_app PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/bgfx_tools/shaderc") target_link_libraries(sdl3_app PRIVATE ${SDL_TARGET} - lua::lua CLI11::CLI11 rapidjson ${SDL3CPP_STB_LIBS} @@ -461,60 +454,6 @@ endif() enable_testing() if(NOT ENABLE_VITA) -add_executable(script_engine_tests - tests/test_cube_script.cpp - src/services/impl/graphics/bgfx_graphics_backend.cpp - src/services/impl/graphics/bgfx_shader_compiler.cpp - src/services/impl/diagnostics/logger_service.cpp - src/services/impl/scene/mesh_service.cpp - src/services/impl/scene/physics_bridge_service.cpp - src/services/impl/platform/platform_service.cpp - src/services/impl/script/script_engine_service.cpp - ${MATERIALX_SCRIPT_SOURCES} - src/services/impl/script/lua_helpers.cpp - src/services/impl/script/scene_script_service.cpp - src/services/impl/script/shader_script_service.cpp - src/services/impl/shader/shader_system_registry.cpp - src/services/impl/shader/materialx_shader_system.cpp - src/services/impl/shader/glsl_shader_system.cpp - src/services/impl/materialx/materialx_shader_generator.cpp - src/services/impl/shader/shader_pipeline_validator.cpp - src/services/impl/platform/sdl_window_service.cpp - src/services/impl/scene/ecs_service.cpp - src/services/impl/scene/scene_service.cpp - src/events/event_bus.cpp - src/stb_image.cpp - src/services/impl/shader/pipeline_compiler_service.cpp -) -target_include_directories(script_engine_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src") -target_include_directories(script_engine_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/bgfx_tools/shaderc") -target_link_libraries(script_engine_tests PRIVATE - ${SDL_TARGET} - lua::lua - rapidjson - ${SDL3CPP_RENDER_STACK_LIBS} - assimp::assimp - Bullet::Bullet - glm::glm - Vorbis::vorbisfile - Vorbis::vorbis - zip::zip - libzip::zip -) -if(TARGET shaderc_local) - target_link_libraries(script_engine_tests PRIVATE shaderc_local) -elseif(TARGET shaderc::shaderc) -elseif(TARGET shaderc::shaderc_combined) - target_link_libraries(script_engine_tests PRIVATE shaderc::shaderc_combined) -else() - if(TARGET shaderc_local) - target_link_libraries(script_engine_tests PRIVATE shaderc_local) - else() - message(WARNING "shaderc CMake target not found; skipping link for script_engine_tests.") - endif() -endif() -add_test(NAME script_engine_tests COMMAND script_engine_tests) - add_executable(bgfx_gui_service_tests tests/test_bgfx_gui_service.cpp src/services/impl/gui/bgfx_gui_service.cpp @@ -758,21 +697,6 @@ else() endif() add_test(NAME bgfx_backend_frame_guard_test COMMAND bgfx_backend_frame_guard_test) -# Test: Gui script service missing fields (logs trace and uses defaults) -add_executable(gui_script_service_missing_fields_test - tests/gui_script_service_missing_fields_test.cpp - src/services/impl/script/gui_script_service.cpp - src/services/impl/script/lua_helpers.cpp -) -target_include_directories(gui_script_service_missing_fields_test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src") -target_link_libraries(gui_script_service_missing_fields_test PRIVATE - GTest::gtest - GTest::gtest_main - lua::lua - glm::glm -) -add_test(NAME gui_script_service_missing_fields_test COMMAND gui_script_service_missing_fields_test) - # Test: Graphics service buffer lifecycle (TDD guard for VRAM leaks on reupload) add_executable(graphics_service_buffer_lifecycle_test tests/graphics_service_buffer_lifecycle_test.cpp diff --git a/conanfile.py b/conanfile.py index 3b48a7b..a41dc7d 100644 --- a/conanfile.py +++ b/conanfile.py @@ -8,14 +8,9 @@ class SDL3CppConan(ConanFile): options = {"build_app": [True, False]} default_options = { "build_app": True, - "lua/*:shared": False, - "lua/*:fPIC": True, - "lua/*:compile_as_cpp": False, - "lua/*:with_tools": False, } generators = "CMakeDeps", "VirtualRunEnv" BASE_REQUIRES = ( - "lua/5.4.8", "sdl/3.2.20", "shaderc/2023.6", "cpptrace/1.0.4", diff --git a/packages/assets/audio/modmusic.ogg b/packages/assets/assets/audio/modmusic.ogg similarity index 100% rename from packages/assets/audio/modmusic.ogg rename to packages/assets/assets/audio/modmusic.ogg diff --git a/packages/assets/audio/modmusic.xm b/packages/assets/assets/audio/modmusic.xm similarity index 100% rename from packages/assets/audio/modmusic.xm rename to packages/assets/assets/audio/modmusic.xm diff --git a/packages/assets/audio/piano.ogg b/packages/assets/assets/audio/piano.ogg similarity index 100% rename from packages/assets/audio/piano.ogg rename to packages/assets/assets/audio/piano.ogg diff --git a/packages/assets/audio/sfx/level_up.ogg b/packages/assets/assets/audio/sfx/level_up.ogg similarity index 100% rename from packages/assets/audio/sfx/level_up.ogg rename to packages/assets/assets/audio/sfx/level_up.ogg diff --git a/packages/assets/audio/sfx/menu_select.ogg b/packages/assets/assets/audio/sfx/menu_select.ogg similarity index 100% rename from packages/assets/audio/sfx/menu_select.ogg rename to packages/assets/assets/audio/sfx/menu_select.ogg diff --git a/packages/assets/audio/sfx/power_up.ogg b/packages/assets/assets/audio/sfx/power_up.ogg similarity index 100% rename from packages/assets/audio/sfx/power_up.ogg rename to packages/assets/assets/audio/sfx/power_up.ogg diff --git a/packages/assets/audio/sfx/swish.ogg b/packages/assets/assets/audio/sfx/swish.ogg similarity index 100% rename from packages/assets/audio/sfx/swish.ogg rename to packages/assets/assets/audio/sfx/swish.ogg diff --git a/packages/assets/audio/sfx/thud.ogg b/packages/assets/assets/audio/sfx/thud.ogg similarity index 100% rename from packages/assets/audio/sfx/thud.ogg rename to packages/assets/assets/audio/sfx/thud.ogg diff --git a/packages/assets/audio/tts/continue.ogg b/packages/assets/assets/audio/tts/continue.ogg similarity index 100% rename from packages/assets/audio/tts/continue.ogg rename to packages/assets/assets/audio/tts/continue.ogg diff --git a/packages/assets/audio/tts/game_over.ogg b/packages/assets/assets/audio/tts/game_over.ogg similarity index 100% rename from packages/assets/audio/tts/game_over.ogg rename to packages/assets/assets/audio/tts/game_over.ogg diff --git a/packages/assets/audio/tts/level_1.ogg b/packages/assets/assets/audio/tts/level_1.ogg similarity index 100% rename from packages/assets/audio/tts/level_1.ogg rename to packages/assets/assets/audio/tts/level_1.ogg diff --git a/packages/assets/audio/tts/level_2.ogg b/packages/assets/assets/audio/tts/level_2.ogg similarity index 100% rename from packages/assets/audio/tts/level_2.ogg rename to packages/assets/assets/audio/tts/level_2.ogg diff --git a/packages/assets/audio/tts/power_up.ogg b/packages/assets/assets/audio/tts/power_up.ogg similarity index 100% rename from packages/assets/audio/tts/power_up.ogg rename to packages/assets/assets/audio/tts/power_up.ogg diff --git a/packages/assets/fonts/Roboto-LICENSE.txt b/packages/assets/assets/fonts/Roboto-LICENSE.txt similarity index 100% rename from packages/assets/fonts/Roboto-LICENSE.txt rename to packages/assets/assets/fonts/Roboto-LICENSE.txt diff --git a/packages/assets/fonts/Roboto-Regular.ttf b/packages/assets/assets/fonts/Roboto-Regular.ttf similarity index 100% rename from packages/assets/fonts/Roboto-Regular.ttf rename to packages/assets/assets/fonts/Roboto-Regular.ttf diff --git a/packages/assets/images/logo.svg b/packages/assets/assets/images/logo.svg similarity index 100% rename from packages/assets/images/logo.svg rename to packages/assets/assets/images/logo.svg diff --git a/packages/assets/package.json b/packages/assets/package.json index 87d79e3..44b77c2 100644 --- a/packages/assets/package.json +++ b/packages/assets/package.json @@ -1,16 +1,14 @@ { "name": "assets", "version": "0.1.0", - "description": "assets", - "defaultWorkflow": null, + "description": "Shared runtime assets (audio, fonts, images) used by demo packages.", + "defaultWorkflow": "workflows/assets_catalog.json", "workflows": [ + "workflows/assets_catalog.json" ], "assets": [ - ], - "scene": [ - ], - "shaders": [ - ], - "dependencies": [ + "assets/audio", + "assets/fonts", + "assets/images" ] } diff --git a/packages/assets/workflows/assets_catalog.json b/packages/assets/workflows/assets_catalog.json new file mode 100644 index 0000000..4d74b04 --- /dev/null +++ b/packages/assets/workflows/assets_catalog.json @@ -0,0 +1,41 @@ +{ + "template": "package.assets", + "nodes": [ + { + "id": "asset_roots", + "plugin": "list.literal", + "position": [0, 0], + "outputs": { + "list": "assets.roots" + }, + "parameters": { + "items": [ + "assets/audio", + "assets/fonts", + "assets/images" + ], + "type": "string" + } + }, + { + "id": "assert_asset_roots", + "plugin": "value.assert.type", + "position": [260, 0], + "inputs": { + "value": "assets.roots" + }, + "parameters": { + "type": "string_list" + } + } + ], + "connections": { + "asset_roots": { + "main": [ + [ + { "node": "assert_asset_roots", "type": "main", "index": 0 } + ] + ] + } + } +} diff --git a/packages/bootstrap/package.json b/packages/bootstrap/package.json index 085821c..32327bc 100644 --- a/packages/bootstrap/package.json +++ b/packages/bootstrap/package.json @@ -6,10 +6,5 @@ "workflows": [ "workflows/boot_default.json", "workflows/frame_default.json" - ], - "assets": [ - ], - "scene": [ - ], - "dependencies": [] + ] } diff --git a/packages/materialx/assets/materialx_paths.json b/packages/materialx/assets/materialx_paths.json new file mode 100644 index 0000000..4b24d8b --- /dev/null +++ b/packages/materialx/assets/materialx_paths.json @@ -0,0 +1,5 @@ +{ + "libraries": "libraries", + "resources": "resources", + "documents": "documents" +} diff --git a/packages/materialx/package.json b/packages/materialx/package.json index dd7b332..c66c60c 100644 --- a/packages/materialx/package.json +++ b/packages/materialx/package.json @@ -1,16 +1,15 @@ { - "name": "MaterialX", + "name": "materialx", "version": "0.1.0", - "description": "MaterialX", - "defaultWorkflow": null, + "description": "MaterialX library bundle (libraries + resources) for shader generation.", + "defaultWorkflow": "workflows/materialx_catalog.json", "workflows": [ + "workflows/materialx_catalog.json" ], "assets": [ - ], - "scene": [ - ], - "shaders": [ - ], - "dependencies": [ + "libraries", + "resources", + "documents", + "assets/materialx_paths.json" ] } diff --git a/packages/materialx/workflows/materialx_catalog.json b/packages/materialx/workflows/materialx_catalog.json new file mode 100644 index 0000000..0e639c9 --- /dev/null +++ b/packages/materialx/workflows/materialx_catalog.json @@ -0,0 +1,41 @@ +{ + "template": "package.materialx", + "nodes": [ + { + "id": "materialx_paths", + "plugin": "list.literal", + "position": [0, 0], + "outputs": { + "list": "materialx.paths" + }, + "parameters": { + "items": [ + "libraries", + "resources", + "documents" + ], + "type": "string" + } + }, + { + "id": "assert_materialx_paths", + "plugin": "value.assert.type", + "position": [260, 0], + "inputs": { + "value": "materialx.paths" + }, + "parameters": { + "type": "string_list" + } + } + ], + "connections": { + "materialx_paths": { + "main": [ + [ + { "node": "assert_materialx_paths", "type": "main", "index": 0 } + ] + ] + } + } +} diff --git a/src/services/impl/script/gui_script_service.cpp b/src/services/impl/script/gui_script_service.cpp deleted file mode 100644 index 7c0162b..0000000 --- a/src/services/impl/script/gui_script_service.cpp +++ /dev/null @@ -1,396 +0,0 @@ -#include "gui_script_service.hpp" - -#include "lua_helpers.hpp" -#include "services/interfaces/i_logger.hpp" - -#include - -#include -#include -#include -#include -#include - -namespace sdl3cpp::services::impl { - -GuiScriptService::GuiScriptService(std::shared_ptr engineService, - std::shared_ptr logger) - : engineService_(std::move(engineService)), - logger_(std::move(logger)) { - if (logger_) { - logger_->Trace("GuiScriptService", "GuiScriptService", - "engineService=" + std::string(engineService_ ? "set" : "null")); - } -} - -void GuiScriptService::Initialize() { - if (logger_) { - logger_->Trace("GuiScriptService", "Initialize"); - } - lua_State* L = GetLuaState(); - - 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); - } -} - -void GuiScriptService::Shutdown() noexcept { - if (logger_) { - logger_->Trace("GuiScriptService", "Shutdown"); - } - - if (!engineService_ || !engineService_->IsInitialized()) { - guiInputRef_ = -1; - guiCommandsFnRef_ = -1; - return; - } - - lua_State* L = engineService_->GetLuaState(); - if (!L) { - guiInputRef_ = -1; - guiCommandsFnRef_ = -1; - return; - } - - if (guiInputRef_ >= 0) { - luaL_unref(L, LUA_REGISTRYINDEX, guiInputRef_); - } - if (guiCommandsFnRef_ >= 0) { - luaL_unref(L, LUA_REGISTRYINDEX, guiCommandsFnRef_); - } - guiInputRef_ = -1; - guiCommandsFnRef_ = -1; -} - -std::vector GuiScriptService::LoadGuiCommands() { - if (logger_) { - logger_->Trace("GuiScriptService", "LoadGuiCommands"); - } - if (guiCommandsFnRef_ < 0) { - return {}; - } - lua_State* L = GetLuaState(); - - std::vector commands; - lua_rawgeti(L, LUA_REGISTRYINDEX, guiCommandsFnRef_); - if (lua_pcall(L, 0, 1, 0) != LUA_OK) { - std::string message = lua::GetLuaError(L); - lua_pop(L, 1); - 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); - if (logger_) { - logger_->Error("'get_gui_commands' did not return a table"); - } - 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); - 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); - lua_getfield(L, commandIndex, "type"); - const char* typeName = lua_tostring(L, -1); - if (!typeName) { - lua_pop(L, 2); - 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{}; - if (std::strcmp(typeName, "rect") == 0) { - command.type = GuiCommand::Type::Rect; - command.rect = ReadRect(L, commandIndex); - command.color = ReadColorField(L, commandIndex, "color", GuiColor{0.0f, 0.0f, 0.0f, 1.0f}); - command.borderColor = ReadColorField(L, commandIndex, "borderColor", - 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); - bool hasX = false; - bool hasY = false; - lua_getfield(L, commandIndex, "x"); - if (lua_isnumber(L, -1)) { - command.rect.x = static_cast(lua_tonumber(L, -1)); - hasX = true; - } - lua_pop(L, 1); - lua_getfield(L, commandIndex, "y"); - if (lua_isnumber(L, -1)) { - command.rect.y = static_cast(lua_tonumber(L, -1)); - hasY = true; - } - lua_pop(L, 1); - if (logger_ && (!hasX || !hasY)) { - logger_->Trace("GuiScriptService", "LoadGuiCommands", - "Text command missing x/y; defaulting to 0"); - } - 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 = ReadColorField(L, commandIndex, "color", GuiColor{1.0f, 1.0f, 1.0f, 1.0f}); - } else if (std::strcmp(typeName, "clip_push") == 0) { - command.type = GuiCommand::Type::ClipPush; - lua_getfield(L, commandIndex, "rect"); - if (lua_istable(L, -1)) { - command.rect = ReadRect(L, -1); - } else { - command.rect = ReadRect(L, commandIndex); - if (logger_) { - logger_->Trace("GuiScriptService", "LoadGuiCommands", - "clipPushFallback=true"); - } - } - lua_pop(L, 1); - } 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 = ReadColorField(L, commandIndex, "tint", GuiColor{1.0f, 1.0f, 1.0f, 0.0f}); - command.svgTint = ReadColorField(L, commandIndex, "color", command.svgTint); - } - lua_pop(L, 1); - lua_pop(L, 1); - commands.push_back(std::move(command)); - } - - lua_pop(L, 1); - return commands; -} - -void GuiScriptService::UpdateGuiInput(const GuiInputSnapshot& input) { - if (logger_) { - logger_->Trace("GuiScriptService", "UpdateGuiInput", - "mouseX=" + std::to_string(input.mouseX) + - ", mouseY=" + std::to_string(input.mouseY) + - ", mouseDeltaX=" + std::to_string(input.mouseDeltaX) + - ", mouseDeltaY=" + std::to_string(input.mouseDeltaY) + - ", mouseDown=" + std::string(input.mouseDown ? "true" : "false") + - ", wheel=" + std::to_string(input.wheel) + - ", textInput.size=" + std::to_string(input.textInput.size()) + - ", keyStates.size=" + std::to_string(input.keyStates.size())); - } - if (guiInputRef_ < 0) { - return; - } - lua_State* L = GetLuaState(); - - 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_pushnumber(L, input.mouseDeltaX); - lua_pushnumber(L, input.mouseDeltaY); - lua_call(L, 6, 0); - - lua_getfield(L, stateIndex, "setWheel"); - lua_pushvalue(L, stateIndex); - lua_pushnumber(L, input.wheel); - lua_call(L, 2, 0); - - lua_getfield(L, stateIndex, "setGamepad"); - if (lua_isfunction(L, -1)) { - lua_pushvalue(L, stateIndex); - lua_pushboolean(L, input.gamepadConnected); - lua_pushnumber(L, input.gamepadLeftX); - lua_pushnumber(L, input.gamepadLeftY); - lua_pushnumber(L, input.gamepadRightX); - lua_pushnumber(L, input.gamepadRightY); - lua_pushboolean(L, input.gamepadTogglePressed); - lua_call(L, 7, 0); - } else { - lua_pop(L, 1); - } - - 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 GuiScriptService::HasGuiCommands() const { - if (logger_) { - logger_->Trace("GuiScriptService", "HasGuiCommands"); - } - return guiCommandsFnRef_ >= 0; -} - -GuiCommand::RectData GuiScriptService::ReadRect(lua_State* L, int index) const { - if (logger_) { - logger_->Trace("GuiScriptService", "ReadRect", - "index=" + std::to_string(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 GuiScriptService::ReadColor(lua_State* L, int index, const GuiColor& defaultColor) const { - if (logger_) { - logger_->Trace("GuiScriptService", "ReadColor", - "index=" + std::to_string(index)); - } - 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; -} - -GuiColor GuiScriptService::ReadColorField(lua_State* L, int index, const char* name, - const GuiColor& defaultColor) const { - if (logger_) { - logger_->Trace("GuiScriptService", "ReadColorField", - "index=" + std::to_string(index) + - ", name=" + std::string(name ? name : "")); - } - if (!lua_istable(L, index) || !name) { - return defaultColor; - } - int absIndex = lua_absindex(L, index); - lua_getfield(L, absIndex, name); - GuiColor color = defaultColor; - if (lua_istable(L, -1)) { - color = ReadColor(L, -1, defaultColor); - } else if (logger_) { - logger_->Trace("GuiScriptService", "ReadColorField", - "Field not found or not table: " + std::string(name)); - } - lua_pop(L, 1); - return color; -} - -bool GuiScriptService::ReadStringField(lua_State* L, int index, const char* name, std::string& outString) const { - if (logger_) { - logger_->Trace("GuiScriptService", "ReadStringField", - "index=" + std::to_string(index) + - ", name=" + std::string(name ? name : "")); - } - 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; -} - -lua_State* GuiScriptService::GetLuaState() const { - if (logger_) { - logger_->Trace("GuiScriptService", "GetLuaState"); - } - if (!engineService_) { - throw std::runtime_error("GUI script service is missing script engine service"); - } - lua_State* state = engineService_->GetLuaState(); - if (!state) { - throw std::runtime_error("Lua state is not initialized"); - } - return state; -} - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/script/gui_script_service.hpp b/src/services/impl/script/gui_script_service.hpp deleted file mode 100644 index 48e690b..0000000 --- a/src/services/impl/script/gui_script_service.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include "services/interfaces/i_gui_script_service.hpp" -#include "services/interfaces/i_script_engine_service.hpp" -#include "services/interfaces/i_logger.hpp" -#include "../../../di/lifecycle.hpp" -#include -#include - -struct lua_State; - -namespace sdl3cpp::services::impl { - -/** - * @brief Script-facing GUI service implementation. - */ -class GuiScriptService : public IGuiScriptService, - public di::IInitializable, - public di::IShutdownable { -public: - GuiScriptService(std::shared_ptr engineService, - std::shared_ptr logger); - - void Initialize() override; - void Shutdown() noexcept override; - - std::vector LoadGuiCommands() override; - void UpdateGuiInput(const GuiInputSnapshot& input) override; - bool HasGuiCommands() const override; - -private: - lua_State* GetLuaState() const; - GuiCommand::RectData ReadRect(lua_State* L, int index) const; - float ReadNumberField(lua_State* L, int index, const char* name, float defaultValue) const; - GuiColor ReadColor(lua_State* L, int index, const GuiColor& defaultColor) const; - GuiColor ReadColorField(lua_State* L, int index, const char* name, const GuiColor& defaultColor) const; - bool ReadStringField(lua_State* L, int index, const char* name, std::string& outString) const; - - std::shared_ptr engineService_; - std::shared_ptr logger_; - int guiInputRef_ = -1; - int guiCommandsFnRef_ = -1; -}; - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/script/lua_helpers.cpp b/src/services/impl/script/lua_helpers.cpp deleted file mode 100644 index a2c5107..0000000 --- a/src/services/impl/script/lua_helpers.cpp +++ /dev/null @@ -1,196 +0,0 @@ -#include "lua_helpers.hpp" - -#include -#include -#include -#include -#include -#include -#include - -namespace sdl3cpp::services::impl::lua { - -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 ReadVector2(lua_State* L, int index) { - std::array result{}; - int absIndex = lua_absindex(L, index); - size_t len = lua_rawlen(L, absIndex); - if (len != 2) { - throw std::runtime_error("Expected vector with 2 components"); - } - for (size_t i = 1; i <= 2; ++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 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; -} - -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::string GetLuaError(lua_State* L) { - const char* message = lua_tostring(L, -1); - return message ? message : "unknown lua error"; -} - -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 { - -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); - } -} - -} // namespace - -int LuaGlmMatrixIdentity(lua_State* L) { - glm::mat4 matrix(1.0f); - PushMatrix(L, matrix); - return 1; -} - -int LuaGlmMatrixMultiply(lua_State* L) { - std::array left = ReadMatrix(L, 1); - std::array right = ReadMatrix(L, 2); - glm::mat4 leftMat = glm::make_mat4(left.data()); - glm::mat4 rightMat = glm::make_mat4(right.data()); - glm::mat4 combined = leftMat * rightMat; - PushMatrix(L, combined); - return 1; -} - -int LuaGlmMatrixTranslation(lua_State* L) { - float x = static_cast(luaL_checknumber(L, 1)); - float y = static_cast(luaL_checknumber(L, 2)); - float z = static_cast(luaL_checknumber(L, 3)); - glm::mat4 matrix = glm::translate(glm::mat4(1.0f), glm::vec3(x, y, z)); - PushMatrix(L, matrix); - return 1; -} - -int LuaGlmMatrixRotationX(lua_State* L) { - float radians = static_cast(luaL_checknumber(L, 1)); - glm::mat4 matrix = glm::rotate(glm::mat4(1.0f), -radians, glm::vec3(1.0f, 0.0f, 0.0f)); - PushMatrix(L, matrix); - return 1; -} - -int LuaGlmMatrixRotationY(lua_State* L) { - float radians = static_cast(luaL_checknumber(L, 1)); - glm::mat4 matrix = glm::rotate(glm::mat4(1.0f), -radians, glm::vec3(0.0f, 1.0f, 0.0f)); - PushMatrix(L, matrix); - return 1; -} - -int LuaGlmMatrixLookAt(lua_State* L) { - std::array eye = ReadVector3(L, 1); - std::array center = ReadVector3(L, 2); - std::array up = ReadVector3(L, 3); - glm::mat4 matrix = glm::lookAt(ToVec3(eye), ToVec3(center), ToVec3(up)); - PushMatrix(L, matrix); - return 1; -} - -int LuaGlmMatrixPerspective(lua_State* L) { - float fov = static_cast(luaL_checknumber(L, 1)); - float aspect = static_cast(luaL_checknumber(L, 2)); - float zNear = static_cast(luaL_checknumber(L, 3)); - float zFar = static_cast(luaL_checknumber(L, 4)); - float tanHalf = std::tan(fov * 0.5f); - glm::mat4 matrix(0.0f); - matrix[0][0] = 1.0f / (aspect * tanHalf); - matrix[1][1] = -1.0f / tanHalf; - matrix[2][2] = zFar / (zNear - zFar); - matrix[2][3] = -1.0f; - matrix[3][2] = (zNear * zFar) / (zNear - zFar); - PushMatrix(L, matrix); - return 1; -} - -int LuaGlmMatrixFromTransform(lua_State* L) { - std::array translation = ReadVector3(L, 1); - std::array rotation = 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; -} - -} // namespace sdl3cpp::services::impl::lua diff --git a/src/services/impl/script/lua_helpers.hpp b/src/services/impl/script/lua_helpers.hpp deleted file mode 100644 index d7abaf9..0000000 --- a/src/services/impl/script/lua_helpers.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include -#include - -struct lua_State; - -namespace sdl3cpp::services::impl::lua { - -std::array ReadVector3(lua_State* L, int index); -std::array ReadVector2(lua_State* L, int index); -std::array ReadQuaternion(lua_State* L, int index); -std::array ReadMatrix(lua_State* L, int index); -std::string GetLuaError(lua_State* L); -std::array IdentityMatrix(); -int LuaGlmMatrixIdentity(lua_State* L); -int LuaGlmMatrixMultiply(lua_State* L); -int LuaGlmMatrixTranslation(lua_State* L); -int LuaGlmMatrixRotationX(lua_State* L); -int LuaGlmMatrixRotationY(lua_State* L); -int LuaGlmMatrixLookAt(lua_State* L); -int LuaGlmMatrixPerspective(lua_State* L); -int LuaGlmMatrixFromTransform(lua_State* L); - -} // namespace sdl3cpp::services::impl::lua diff --git a/src/services/impl/script/scene_script_service.cpp b/src/services/impl/script/scene_script_service.cpp deleted file mode 100644 index 21e1abd..0000000 --- a/src/services/impl/script/scene_script_service.cpp +++ /dev/null @@ -1,428 +0,0 @@ -#include "scene_script_service.hpp" - -#include "lua_helpers.hpp" -#include "services/interfaces/i_logger.hpp" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace sdl3cpp::services::impl { -namespace { - -std::array MultiplyMatrices(const std::array& left, - const std::array& right) { - glm::mat4 leftMat = glm::make_mat4(left.data()); - glm::mat4 rightMat = glm::make_mat4(right.data()); - glm::mat4 combined = leftMat * rightMat; - std::array result{}; - std::memcpy(result.data(), glm::value_ptr(combined), sizeof(float) * result.size()); - return result; -} - -bool ReadMatrixField(lua_State* L, int tableIndex, const char* field, std::array& target) { - lua_getfield(L, tableIndex, field); - if (lua_isnil(L, -1)) { - lua_pop(L, 1); - return false; - } - if (!lua_istable(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error(std::string("Field '") + field + "' must be a 4x4 matrix"); - } - target = lua::ReadMatrix(L, -1); - lua_pop(L, 1); - return true; -} - -bool ReadVector3Field(lua_State* L, int tableIndex, const char* field, std::array& target) { - lua_getfield(L, tableIndex, field); - if (lua_isnil(L, -1)) { - lua_pop(L, 1); - return false; - } - if (!lua_istable(L, -1)) { - lua_pop(L, 1); - throw std::runtime_error(std::string("Field '") + field + "' must be a vec3"); - } - target = lua::ReadVector3(L, -1); - lua_pop(L, 1); - return true; -} - -std::vector ReadVertexArray(lua_State* L, int index, const std::shared_ptr& logger) { - 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"); - } - - 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); - 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"); - } - - int vertexIndex = lua_gettop(L); - core::Vertex vertex{}; - - lua_getfield(L, vertexIndex, "position"); - vertex.position = lua::ReadVector3(L, -1); - lua_pop(L, 1); - - lua_getfield(L, vertexIndex, "normal"); - if (lua_istable(L, -1)) { - vertex.normal = lua::ReadVector3(L, -1); - } else { - vertex.normal = {0.0f, 0.0f, 1.0f}; - } - lua_pop(L, 1); - - lua_getfield(L, vertexIndex, "tangent"); - if (lua_istable(L, -1)) { - vertex.tangent = lua::ReadVector3(L, -1); - } else { - vertex.tangent = {1.0f, 0.0f, 0.0f}; - } - lua_pop(L, 1); - - lua_getfield(L, vertexIndex, "color"); - vertex.color = lua::ReadVector3(L, -1); - lua_pop(L, 1); - - lua_getfield(L, vertexIndex, "texcoord"); - if (lua_istable(L, -1)) { - vertex.texcoord = lua::ReadVector2(L, -1); - } else { - vertex.texcoord = {0.0f, 0.0f}; - } - lua_pop(L, 1); - - lua_pop(L, 1); - vertices.push_back(vertex); - } - - return vertices; -} - -std::vector ReadIndexArray(lua_State* L, int index, const std::shared_ptr& logger) { - 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"); - } - - 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); - 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)); - } - - return indices; -} - -} // namespace - -SceneScriptService::SceneScriptService(std::shared_ptr engineService, - std::shared_ptr logger) - : engineService_(std::move(engineService)), - logger_(std::move(logger)) { - if (logger_) { - logger_->Trace("SceneScriptService", "SceneScriptService", - "engineService=" + std::string(engineService_ ? "set" : "null")); - } -} - -std::vector SceneScriptService::LoadSceneObjects() { - if (logger_) { - logger_->Trace("SceneScriptService", "LoadSceneObjects"); - } - lua_State* L = GetLuaState(); - - 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 = lua::GetLuaError(L); - 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"); - } - - 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); - 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"); - } - - SceneObject object; - lua_getfield(L, -1, "vertices"); - object.vertices = ReadVertexArray(L, -1, logger_); - 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"); - } - - lua_getfield(L, -1, "indices"); - object.indices = ReadIndexArray(L, -1, logger_); - 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"); - } - - 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 = -1; - } - - object.shaderKeys.clear(); - lua_getfield(L, -1, "shader_keys"); - if (lua_istable(L, -1)) { - const size_t shaderKeyCount = lua_rawlen(L, -1); - object.shaderKeys.reserve(shaderKeyCount); - for (size_t keyIndex = 1; keyIndex <= shaderKeyCount; ++keyIndex) { - lua_rawgeti(L, -1, static_cast(keyIndex)); - if (lua_isstring(L, -1)) { - object.shaderKeys.emplace_back(lua_tostring(L, -1)); - } - lua_pop(L, 1); - } - } - lua_pop(L, 1); - - if (object.shaderKeys.empty()) { - lua_getfield(L, -1, "shader_key"); - if (lua_isstring(L, -1)) { - object.shaderKeys.emplace_back(lua_tostring(L, -1)); - } - lua_pop(L, 1); - } - - lua_getfield(L, -1, "object_type"); - if (lua_isstring(L, -1)) { - object.objectType = 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 SceneScriptService::ComputeModelMatrix(int functionRef, float time) { - if (logger_) { - logger_->Trace("SceneScriptService", "ComputeModelMatrix", - "functionRef=" + std::to_string(functionRef) + - ", time=" + std::to_string(time)); - } - lua_State* L = GetLuaState(); - - if (functionRef < 0) { - lua_getglobal(L, "compute_model_matrix"); - if (!lua_isfunction(L, -1)) { - lua_pop(L, 1); - return lua::IdentityMatrix(); - } - } else { - lua_rawgeti(L, LUA_REGISTRYINDEX, functionRef); - } - - lua_pushnumber(L, time); - if (lua_pcall(L, 1, 1, 0) != LUA_OK) { - std::string message = lua::GetLuaError(L); - 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"); - } - - std::array matrix = lua::ReadMatrix(L, -1); - lua_pop(L, 1); - return matrix; -} - -ViewState SceneScriptService::GetViewState(float aspect) { - if (logger_) { - logger_->Trace("SceneScriptService", "GetViewState", "aspect=" + std::to_string(aspect)); - } - lua_State* L = GetLuaState(); - - ViewState state; - state.view = lua::IdentityMatrix(); - state.proj = lua::IdentityMatrix(); - state.viewProj = lua::IdentityMatrix(); - state.cameraPosition = {0.0f, 0.0f, 0.0f}; - - lua_getglobal(L, "get_view_state"); - if (lua_isfunction(L, -1)) { - lua_pushnumber(L, aspect); - if (lua_pcall(L, 1, 1, 0) != LUA_OK) { - std::string message = lua::GetLuaError(L); - lua_pop(L, 1); - if (logger_) { - logger_->Error("Lua get_view_state failed: " + message); - } - throw std::runtime_error("Lua get_view_state failed: " + message); - } - if (!lua_istable(L, -1)) { - lua_pop(L, 1); - if (logger_) { - logger_->Error("'get_view_state' did not return a table"); - } - throw std::runtime_error("'get_view_state' did not return a table"); - } - bool hasView = false; - bool hasProj = false; - bool hasViewProj = false; - - try { - hasView = ReadMatrixField(L, -1, "view", state.view); - hasProj = ReadMatrixField(L, -1, "proj", state.proj); - hasViewProj = ReadMatrixField(L, -1, "view_proj", state.viewProj); - if (!hasViewProj) { - hasViewProj = ReadMatrixField(L, -1, "viewProj", state.viewProj); - } - ReadVector3Field(L, -1, "camera_pos", state.cameraPosition); - ReadVector3Field(L, -1, "camera_position", state.cameraPosition); - } catch (const std::exception& ex) { - lua_pop(L, 1); - if (logger_) { - logger_->Error("Lua get_view_state returned invalid data: " + std::string(ex.what())); - } - throw; - } - - lua_pop(L, 1); - - if (!hasViewProj && hasView && hasProj) { - state.viewProj = MultiplyMatrices(state.proj, state.view); - } - return state; - } - - lua_pop(L, 1); - 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 = lua::GetLuaError(L); - 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"); - } - state.viewProj = lua::ReadMatrix(L, -1); - lua_pop(L, 1); - return state; -} - -lua_State* SceneScriptService::GetLuaState() const { - if (logger_) { - logger_->Trace("SceneScriptService", "GetLuaState"); - } - if (!engineService_) { - throw std::runtime_error("Scene script service is missing script engine service"); - } - lua_State* state = engineService_->GetLuaState(); - if (!state) { - throw std::runtime_error("Lua state is not initialized"); - } - return state; -} - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/script/scene_script_service.hpp b/src/services/impl/script/scene_script_service.hpp deleted file mode 100644 index 8a27ae4..0000000 --- a/src/services/impl/script/scene_script_service.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "services/interfaces/i_scene_script_service.hpp" -#include "services/interfaces/i_script_engine_service.hpp" -#include "services/interfaces/i_logger.hpp" -#include - -struct lua_State; - -namespace sdl3cpp::services::impl { - -/** - * @brief Script-facing scene service implementation. - */ -class SceneScriptService : public ISceneScriptService { -public: - SceneScriptService(std::shared_ptr engineService, - std::shared_ptr logger); - - std::vector LoadSceneObjects() override; - std::array ComputeModelMatrix(int functionRef, float time) override; - ViewState GetViewState(float aspect) override; - -private: - lua_State* GetLuaState() const; - - std::shared_ptr engineService_; - std::shared_ptr logger_; -}; - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/script/script_engine_service.cpp b/src/services/impl/script/script_engine_service.cpp deleted file mode 100644 index 042c75f..0000000 --- a/src/services/impl/script/script_engine_service.cpp +++ /dev/null @@ -1,1475 +0,0 @@ -#include "script_engine_service.hpp" - -#include "lua_helpers.hpp" -#include "services/impl/materialx/materialx_document_loader.hpp" -#include "services/impl/materialx/materialx_path_resolver.hpp" -#include "services/impl/materialx/materialx_surface_node_resolver.hpp" -#include "services/impl/materialx/materialx_surface_parameter_reader.hpp" -#include "services/interfaces/i_logger.hpp" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace { -std::array TransformPoint(const std::array& matrix, - const std::array& point) { - const float x = point[0]; - const float y = point[1]; - const float z = point[2]; - return { - matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12], - matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13], - matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14], - }; -} - -bool TryParseMouseButtonName(const char* name, uint8_t& button) { - if (!name) { - return false; - } - std::string lower(name); - std::transform(lower.begin(), lower.end(), lower.begin(), - [](unsigned char ch) { return static_cast(std::tolower(ch)); }); - if (lower == "left") { - button = SDL_BUTTON_LEFT; - return true; - } - if (lower == "right") { - button = SDL_BUTTON_RIGHT; - return true; - } - if (lower == "middle") { - button = SDL_BUTTON_MIDDLE; - return true; - } - if (lower == "x1" || lower == "extra1") { - button = SDL_BUTTON_X1; - return true; - } - if (lower == "x2" || lower == "extra2") { - button = SDL_BUTTON_X2; - return true; - } - return false; -} - -SDL_Keycode ReadKeycodeFromLua(lua_State* L, int index, bool& ok) { - ok = true; - if (lua_isnumber(L, index)) { - return static_cast(lua_tointeger(L, index)); - } - if (lua_isstring(L, index)) { - const char* name = lua_tostring(L, index); - SDL_Keycode key = SDL_GetKeyFromName(name); - if (key == SDLK_UNKNOWN) { - ok = false; - } - return key; - } - ok = false; - return SDLK_UNKNOWN; -} - -void PushJsonValue(lua_State* L, const rapidjson::Value& value) { - if (value.IsObject()) { - lua_newtable(L); - for (auto it = value.MemberBegin(); it != value.MemberEnd(); ++it) { - lua_pushlstring(L, it->name.GetString(), it->name.GetStringLength()); - PushJsonValue(L, it->value); - lua_settable(L, -3); - } - return; - } - if (value.IsArray()) { - lua_newtable(L); - for (rapidjson::SizeType i = 0; i < value.Size(); ++i) { - PushJsonValue(L, value[i]); - lua_rawseti(L, -2, static_cast(i + 1)); - } - return; - } - if (value.IsString()) { - lua_pushlstring(L, value.GetString(), value.GetStringLength()); - return; - } - if (value.IsBool()) { - lua_pushboolean(L, value.GetBool()); - return; - } - if (value.IsNumber()) { - lua_pushnumber(L, value.GetDouble()); - return; - } - lua_pushnil(L); -} - -bool PushConfigTableFromJson(lua_State* L, const std::string& json, std::string& error) { - if (json.empty()) { - error = "Config JSON not available"; - return false; - } - rapidjson::Document document; - rapidjson::ParseResult result = document.Parse(json.c_str()); - if (!result) { - error = std::string("Config JSON parse failed: ") + rapidjson::GetParseError_En(result.Code()); - return false; - } - PushJsonValue(L, document); - return true; -} -} // namespace - -namespace sdl3cpp::services::impl { - -ScriptEngineService::ScriptEngineService(const std::filesystem::path& scriptPath, - std::shared_ptr logger, - std::shared_ptr meshService, - std::shared_ptr audioCommandService, - std::shared_ptr physicsBridgeService, - std::shared_ptr inputService, - std::shared_ptr windowService, - std::shared_ptr configService, - bool debugEnabled) - : logger_(std::move(logger)), - meshService_(std::move(meshService)), - audioCommandService_(std::move(audioCommandService)), - physicsBridgeService_(std::move(physicsBridgeService)), - inputService_(std::move(inputService)), - windowService_(std::move(windowService)), - configService_(std::move(configService)), - scriptPath_(scriptPath), - debugEnabled_(debugEnabled) { - if (logger_) { - logger_->Trace("ScriptEngineService", "ScriptEngineService", - "scriptPath=" + scriptPath_.string() + - ", debugEnabled=" + std::string(debugEnabled_ ? "true" : "false") + - ", meshService=" + std::string(meshService_ ? "set" : "null") + - ", audioCommandService=" + std::string(audioCommandService_ ? "set" : "null") + - ", physicsBridgeService=" + std::string(physicsBridgeService_ ? "set" : "null") + - ", inputService=" + std::string(inputService_ ? "set" : "null") + - ", windowService=" + std::string(windowService_ ? "set" : "null") + - ", configService=" + std::string(configService_ ? "set" : "null")); - } -} - -ScriptEngineService::~ScriptEngineService() { - if (logger_) { - logger_->Trace("ScriptEngineService", "~ScriptEngineService"); - } - if (initialized_) { - Shutdown(); - } -} - -void ScriptEngineService::Initialize() { - if (initialized_) { - return; - } - logger_->Trace("ScriptEngineService", "Initialize", - "scriptPath=" + scriptPath_.string() + - ", debugEnabled=" + std::string(debugEnabled_ ? "true" : "false")); - - bindingContext_ = std::make_shared(); - bindingContext_->meshService = meshService_; - bindingContext_->audioCommandService = audioCommandService_; - bindingContext_->physicsBridgeService = physicsBridgeService_; - bindingContext_->inputService = inputService_; - bindingContext_->windowService = windowService_; - bindingContext_->configService = configService_; - bindingContext_->logger = logger_; - - luaState_ = luaL_newstate(); - if (!luaState_) { - logger_->Error("Failed to create Lua state"); - throw std::runtime_error("Failed to create Lua state"); - } - - luaL_openlibs(luaState_); - RegisterBindings(luaState_); - - lua_pushboolean(luaState_, debugEnabled_); - lua_setglobal(luaState_, "lua_debug"); - - if (configService_) { - std::string error; - if (PushConfigTableFromJson(luaState_, configService_->GetConfigJson(), error)) { - lua_setglobal(luaState_, "config"); - } else { - if (logger_) { - logger_->Error("ScriptEngineService: " + error); - } - lua_newtable(luaState_); - lua_setglobal(luaState_, "config"); - } - } else { - if (logger_) { - logger_->Error("ScriptEngineService: Config service not available"); - } - lua_newtable(luaState_); - lua_setglobal(luaState_, "config"); - } - - scriptDirectory_ = scriptPath_.parent_path(); - if (!scriptDirectory_.empty()) { - lua_getglobal(luaState_, "package"); - if (lua_istable(luaState_, -1)) { - lua_getfield(luaState_, -1, "path"); - const char* currentPath = lua_tostring(luaState_, -1); - std::string newPath = scriptDirectory_.string() + "/?.lua;"; - if (currentPath) { - newPath += currentPath; - } - lua_pop(luaState_, 1); - lua_pushstring(luaState_, newPath.c_str()); - lua_setfield(luaState_, -2, "path"); - } - lua_pop(luaState_, 1); - } - - if (luaL_dofile(luaState_, scriptPath_.string().c_str()) != LUA_OK) { - std::string message = lua::GetLuaError(luaState_); - lua_pop(luaState_, 1); - lua_close(luaState_); - luaState_ = nullptr; - if (logger_) { - logger_->Error("Failed to load Lua script: " + message); - } - throw std::runtime_error("Failed to load Lua script: " + message); - } - - initialized_ = true; - logger_->Info("Script engine service initialized"); -} - -void ScriptEngineService::Shutdown() noexcept { - if (!initialized_) { - return; - } - logger_->Trace("ScriptEngineService", "Shutdown"); - - if (luaState_) { - lua_close(luaState_); - luaState_ = nullptr; - } - bindingContext_.reset(); - initialized_ = false; - - logger_->Info("Script engine service shutdown"); -} - -lua_State* ScriptEngineService::GetLuaState() const { - if (logger_) { - logger_->Trace("ScriptEngineService", "GetLuaState"); - } - if (!luaState_) { - throw std::runtime_error("Script engine service not initialized"); - } - return luaState_; -} - -std::filesystem::path ScriptEngineService::GetScriptDirectory() const { - if (logger_) { - logger_->Trace("ScriptEngineService", "GetScriptDirectory"); - } - if (!luaState_) { - throw std::runtime_error("Script engine service not initialized"); - } - return scriptDirectory_; -} - -void ScriptEngineService::RegisterBindings(lua_State* L) { - if (logger_) { - logger_->Trace("ScriptEngineService", "RegisterBindings"); - } - if (!bindingContext_) { - throw std::runtime_error("Lua binding context not initialized"); - } - - auto bind = [&](const char* name, lua_CFunction fn) { - lua_pushlightuserdata(L, bindingContext_.get()); - lua_pushcclosure(L, fn, 1); - lua_setglobal(L, name); - }; - - bind("load_mesh_from_file", &ScriptEngineService::LoadMeshFromFile); - bind("load_mesh_from_pk3", &ScriptEngineService::LoadMeshFromArchive); - bind("physics_create_box", &ScriptEngineService::PhysicsCreateBox); - bind("physics_create_sphere", &ScriptEngineService::PhysicsCreateSphere); - bind("physics_create_static_mesh", &ScriptEngineService::PhysicsCreateStaticMesh); - bind("physics_remove_body", &ScriptEngineService::PhysicsRemoveBody); - bind("physics_set_transform", &ScriptEngineService::PhysicsSetTransform); - bind("physics_apply_force", &ScriptEngineService::PhysicsApplyForce); - bind("physics_apply_impulse", &ScriptEngineService::PhysicsApplyImpulse); - bind("physics_set_linear_velocity", &ScriptEngineService::PhysicsSetLinearVelocity); - bind("physics_get_linear_velocity", &ScriptEngineService::PhysicsGetLinearVelocity); - bind("physics_set_gravity", &ScriptEngineService::PhysicsSetGravity); - bind("physics_step_simulation", &ScriptEngineService::PhysicsStepSimulation); - bind("physics_get_transform", &ScriptEngineService::PhysicsGetTransform); - bind("physics_get_body_count", &ScriptEngineService::PhysicsGetBodyCount); - bind("physics_clear", &ScriptEngineService::PhysicsClear); - bind("glm_matrix_identity", &ScriptEngineService::GlmMatrixIdentity); - bind("glm_matrix_multiply", &ScriptEngineService::GlmMatrixMultiply); - bind("glm_matrix_translation", &ScriptEngineService::GlmMatrixTranslation); - bind("glm_matrix_rotation_x", &ScriptEngineService::GlmMatrixRotationX); - bind("glm_matrix_rotation_y", &ScriptEngineService::GlmMatrixRotationY); - bind("glm_matrix_look_at", &ScriptEngineService::GlmMatrixLookAt); - bind("glm_matrix_perspective", &ScriptEngineService::GlmMatrixPerspective); - bind("glm_matrix_from_transform", &ScriptEngineService::GlmMatrixFromTransform); - bind("audio_play_background", &ScriptEngineService::AudioPlayBackground); - bind("audio_play_sound", &ScriptEngineService::AudioPlaySound); - bind("audio_stop_background", &ScriptEngineService::AudioStopBackground); - bind("input_get_mouse_position", &ScriptEngineService::InputGetMousePosition); - bind("input_get_mouse_delta", &ScriptEngineService::InputGetMouseDelta); - bind("input_get_mouse_wheel", &ScriptEngineService::InputGetMouseWheel); - bind("input_is_key_down", &ScriptEngineService::InputIsKeyDown); - bind("input_is_action_down", &ScriptEngineService::InputIsActionDown); - bind("input_is_mouse_down", &ScriptEngineService::InputIsMouseDown); - bind("input_get_text", &ScriptEngineService::InputGetText); - bind("config_get_json", &ScriptEngineService::ConfigGetJson); - bind("config_get_table", &ScriptEngineService::ConfigGetTable); - bind("materialx_get_surface_parameters", &ScriptEngineService::MaterialXGetSurfaceParameters); - bind("time_get_seconds", &ScriptEngineService::TimeGetSeconds); - bind("window_get_size", &ScriptEngineService::WindowGetSize); - bind("window_set_title", &ScriptEngineService::WindowSetTitle); - bind("window_is_minimized", &ScriptEngineService::WindowIsMinimized); - bind("window_set_mouse_grabbed", &ScriptEngineService::WindowSetMouseGrabbed); - bind("window_get_mouse_grabbed", &ScriptEngineService::WindowGetMouseGrabbed); - bind("window_set_relative_mouse_mode", &ScriptEngineService::WindowSetRelativeMouseMode); - bind("window_get_relative_mouse_mode", &ScriptEngineService::WindowGetRelativeMouseMode); - bind("window_set_cursor_visible", &ScriptEngineService::WindowSetCursorVisible); - bind("window_is_cursor_visible", &ScriptEngineService::WindowIsCursorVisible); - bind("print", &ScriptEngineService::LuaPrint); -} - -int ScriptEngineService::LoadMeshFromFile(lua_State* L) { - 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"); - return 2; - } - - const char* path = luaL_checkstring(L, 1); - if (logger) { - logger->Trace("ScriptEngineService", "LoadMeshFromFile", - "path=" + std::string(path)); - } - - MeshPayload payload; - std::string error; - if (!context->meshService->LoadFromFile(path, payload, error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - - context->meshService->PushMeshToLua(L, payload); - lua_pushnil(L); - return 2; -} - -int ScriptEngineService::LoadMeshFromArchive(lua_State* L) { - 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"); - return 2; - } - - const char* archivePath = luaL_checkstring(L, 1); - const char* entryPath = luaL_checkstring(L, 2); - if (logger) { - logger->Trace("ScriptEngineService", "LoadMeshFromArchive", - "archivePath=" + std::string(archivePath) + - ", entryPath=" + std::string(entryPath)); - } - - MeshPayload payload; - std::string error; - if (!context->meshService->LoadFromArchive(archivePath, entryPath, payload, error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - - context->meshService->PushMeshToLua(L, payload); - lua_pushnil(L); - return 2; -} - -int ScriptEngineService::PhysicsCreateBox(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("ScriptEngineService", "PhysicsCreateBox", - "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"); - } - - std::array halfExtents = lua::ReadVector3(L, 2); - float mass = static_cast(luaL_checknumber(L, 3)); - std::array origin = lua::ReadVector3(L, 4); - std::array rotation = lua::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 (!context->physicsBridgeService->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 ScriptEngineService::PhysicsCreateSphere(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("ScriptEngineService", "PhysicsCreateSphere", - "name=" + std::string(name)); - } - - if (!lua_istable(L, 4) || !lua_istable(L, 5)) { - luaL_error(L, "physics_create_sphere expects vector tables for origin and rotation"); - } - - float radius = static_cast(luaL_checknumber(L, 2)); - float mass = static_cast(luaL_checknumber(L, 3)); - std::array origin = lua::ReadVector3(L, 4); - std::array rotation = lua::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 (!context->physicsBridgeService->AddSphereRigidBody(name, radius, mass, transform, error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - - lua_pushboolean(L, 1); - return 1; -} - -int ScriptEngineService::PhysicsCreateStaticMesh(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("ScriptEngineService", "PhysicsCreateStaticMesh", - "name=" + std::string(name)); - } - - if (!lua_istable(L, 2) || !lua_istable(L, 3)) { - luaL_error(L, "physics_create_static_mesh expects vertex and index tables"); - } - - std::array transformMatrix = lua::IdentityMatrix(); - if (lua_gettop(L) >= 4) { - if (!lua_istable(L, 4)) { - luaL_error(L, "physics_create_static_mesh expects a transform matrix table"); - } - transformMatrix = lua::ReadMatrix(L, 4); - } - - const int verticesIndex = lua_absindex(L, 2); - const int indicesIndex = lua_absindex(L, 3); - const size_t vertexCount = lua_rawlen(L, verticesIndex); - const size_t indexCount = lua_rawlen(L, indicesIndex); - - std::vector> vertices; - vertices.reserve(vertexCount); - - for (size_t vertexIndex = 1; vertexIndex <= vertexCount; ++vertexIndex) { - lua_rawgeti(L, verticesIndex, static_cast(vertexIndex)); - if (!lua_istable(L, -1)) { - luaL_error(L, "physics_create_static_mesh vertices must be tables"); - } - - std::array position; - lua_getfield(L, -1, "position"); - if (lua_istable(L, -1)) { - position = lua::ReadVector3(L, -1); - lua_pop(L, 1); - } else { - lua_pop(L, 1); - position = lua::ReadVector3(L, -1); - } - lua_pop(L, 1); - - vertices.push_back(TransformPoint(transformMatrix, position)); - } - - std::vector indices; - indices.reserve(indexCount); - for (size_t index = 1; index <= indexCount; ++index) { - lua_rawgeti(L, indicesIndex, static_cast(index)); - if (!lua_isinteger(L, -1) && !lua_isnumber(L, -1)) { - luaL_error(L, "physics_create_static_mesh indices must be numbers"); - } - lua_Integer rawIndex = lua_tointeger(L, -1); - lua_pop(L, 1); - if (rawIndex <= 0) { - luaL_error(L, "physics_create_static_mesh indices must be 1-based positive integers"); - } - indices.push_back(static_cast(rawIndex - 1)); - } - - btTransform transform; - transform.setIdentity(); - std::string error; - if (!context->physicsBridgeService->AddTriangleMeshRigidBody( - name, - vertices, - indices, - transform, - error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - - lua_pushboolean(L, 1); - return 1; -} - -int ScriptEngineService::PhysicsRemoveBody(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("ScriptEngineService", "PhysicsRemoveBody", - "name=" + std::string(name)); - } - - std::string error; - if (!context->physicsBridgeService->RemoveRigidBody(name, error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - - lua_pushboolean(L, 1); - return 1; -} - -int ScriptEngineService::PhysicsSetTransform(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("ScriptEngineService", "PhysicsSetTransform", - "name=" + std::string(name)); - } - - if (!lua_istable(L, 2) || !lua_istable(L, 3)) { - luaL_error(L, "physics_set_transform expects vector tables for origin and rotation"); - } - - std::array origin = lua::ReadVector3(L, 2); - std::array rotation = lua::ReadQuaternion(L, 3); - - 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 (!context->physicsBridgeService->SetRigidBodyTransform(name, transform, error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - - lua_pushboolean(L, 1); - return 1; -} - -int ScriptEngineService::PhysicsApplyForce(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("ScriptEngineService", "PhysicsApplyForce", - "name=" + std::string(name)); - } - - if (!lua_istable(L, 2)) { - luaL_error(L, "physics_apply_force expects a vector table for force"); - } - - std::array force = lua::ReadVector3(L, 2); - - std::string error; - if (!context->physicsBridgeService->ApplyForce( - name, - btVector3(force[0], force[1], force[2]), - error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - - lua_pushboolean(L, 1); - return 1; -} - -int ScriptEngineService::PhysicsApplyImpulse(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("ScriptEngineService", "PhysicsApplyImpulse", - "name=" + std::string(name)); - } - - if (!lua_istable(L, 2)) { - luaL_error(L, "physics_apply_impulse expects a vector table for impulse"); - } - - std::array impulse = lua::ReadVector3(L, 2); - - std::string error; - if (!context->physicsBridgeService->ApplyImpulse( - name, - btVector3(impulse[0], impulse[1], impulse[2]), - error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - - lua_pushboolean(L, 1); - return 1; -} - -int ScriptEngineService::PhysicsSetLinearVelocity(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("ScriptEngineService", "PhysicsSetLinearVelocity", - "name=" + std::string(name)); - } - - if (!lua_istable(L, 2)) { - luaL_error(L, "physics_set_linear_velocity expects a vector table for velocity"); - } - - std::array velocity = lua::ReadVector3(L, 2); - - std::string error; - if (!context->physicsBridgeService->SetLinearVelocity( - name, - btVector3(velocity[0], velocity[1], velocity[2]), - error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - - lua_pushboolean(L, 1); - return 1; -} - -int ScriptEngineService::PhysicsGetLinearVelocity(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("ScriptEngineService", "PhysicsGetLinearVelocity", - "name=" + std::string(name)); - } - - btVector3 velocity; - std::string error; - if (!context->physicsBridgeService->GetLinearVelocity(name, velocity, error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - - lua_newtable(L); - lua_pushnumber(L, velocity.getX()); - lua_rawseti(L, -2, 1); - lua_pushnumber(L, velocity.getY()); - lua_rawseti(L, -2, 2); - lua_pushnumber(L, velocity.getZ()); - lua_rawseti(L, -2, 3); - return 1; -} - -int ScriptEngineService::PhysicsSetGravity(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; - } - - if (!lua_istable(L, 1)) { - luaL_error(L, "physics_set_gravity expects a vector table for gravity"); - } - - std::array gravity = lua::ReadVector3(L, 1); - if (logger) { - logger->Trace("ScriptEngineService", "PhysicsSetGravity", - "gravity.x=" + std::to_string(gravity[0]) + - ", gravity.y=" + std::to_string(gravity[1]) + - ", gravity.z=" + std::to_string(gravity[2])); - } - - std::string error; - if (!context->physicsBridgeService->SetGravity( - btVector3(gravity[0], gravity[1], gravity[2]), - error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - - lua_pushboolean(L, 1); - return 1; -} - -int ScriptEngineService::PhysicsStepSimulation(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("ScriptEngineService", "PhysicsStepSimulation"); - } - float deltaTime = static_cast(luaL_checknumber(L, 1)); - int maxSubSteps = 10; - if (lua_gettop(L) >= 2 && lua_isnumber(L, 2)) { - maxSubSteps = static_cast(lua_tointeger(L, 2)); - } - int steps = context->physicsBridgeService->StepSimulation(deltaTime, maxSubSteps); - lua_pushinteger(L, steps); - return 1; -} - -int ScriptEngineService::PhysicsGetTransform(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("ScriptEngineService", "PhysicsGetTransform", - "name=" + std::string(name)); - } - - btTransform transform; - std::string error; - if (!context->physicsBridgeService->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 ScriptEngineService::PhysicsGetBodyCount(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; - } - if (logger) { - logger->Trace("ScriptEngineService", "PhysicsGetBodyCount"); - } - lua_pushinteger(L, static_cast(context->physicsBridgeService->GetBodyCount())); - return 1; -} - -int ScriptEngineService::PhysicsClear(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; - } - if (logger) { - logger->Trace("ScriptEngineService", "PhysicsClear"); - } - context->physicsBridgeService->Clear(); - lua_pushboolean(L, 1); - return 1; -} - -int ScriptEngineService::AudioPlayBackground(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"); - return 2; - } - const char* path = luaL_checkstring(L, 1); - bool loop = true; - if (lua_gettop(L) >= 2 && lua_isboolean(L, 2)) { - loop = lua_toboolean(L, 2); - } - if (logger) { - logger->Trace("ScriptEngineService", "AudioPlayBackground", - "path=" + std::string(path) + - ", loop=" + std::string(loop ? "true" : "false")); - } - - std::string error; - if (!context->audioCommandService->QueueAudioCommand( - AudioCommandType::Background, path, loop, error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - - lua_pushboolean(L, 1); - return 1; -} - -int ScriptEngineService::AudioPlaySound(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"); - return 2; - } - const char* path = luaL_checkstring(L, 1); - bool loop = false; - if (lua_gettop(L) >= 2 && lua_isboolean(L, 2)) { - loop = lua_toboolean(L, 2); - } - if (logger) { - logger->Trace("ScriptEngineService", "AudioPlaySound", - "path=" + std::string(path) + - ", loop=" + std::string(loop ? "true" : "false")); - } - - std::string error; - if (!context->audioCommandService->QueueAudioCommand( - AudioCommandType::Effect, path, loop, error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - - lua_pushboolean(L, 1); - return 1; -} - -int ScriptEngineService::AudioStopBackground(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"); - return 2; - } - if (logger) { - logger->Trace("ScriptEngineService", "AudioStopBackground"); - } - - std::string error; - if (!context->audioCommandService->StopBackground(error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - - lua_pushboolean(L, 1); - return 1; -} - -int ScriptEngineService::InputGetMousePosition(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - if (!context || !context->inputService) { - lua_pushnil(L); - lua_pushstring(L, "Input service not available"); - return 2; - } - auto [x, y] = context->inputService->GetMousePosition(); - lua_pushnumber(L, x); - lua_pushnumber(L, y); - return 2; -} - -int ScriptEngineService::InputGetMouseDelta(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - if (!context || !context->inputService) { - lua_pushnil(L); - lua_pushstring(L, "Input service not available"); - return 2; - } - const auto& state = context->inputService->GetState(); - lua_pushnumber(L, state.mouseDeltaX); - lua_pushnumber(L, state.mouseDeltaY); - return 2; -} - -int ScriptEngineService::InputGetMouseWheel(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - if (!context || !context->inputService) { - lua_pushnil(L); - lua_pushstring(L, "Input service not available"); - return 2; - } - const auto& state = context->inputService->GetState(); - lua_pushnumber(L, state.mouseWheelDeltaX); - lua_pushnumber(L, state.mouseWheelDeltaY); - return 2; -} - -int ScriptEngineService::InputIsKeyDown(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - if (!context || !context->inputService) { - lua_pushnil(L); - lua_pushstring(L, "Input service not available"); - return 2; - } - bool ok = false; - SDL_Keycode key = ReadKeycodeFromLua(L, 1, ok); - if (!ok) { - lua_pushboolean(L, 0); - return 1; - } - lua_pushboolean(L, context->inputService->IsKeyPressed(key)); - return 1; -} - -int ScriptEngineService::InputIsActionDown(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - if (!context || !context->inputService) { - lua_pushnil(L); - lua_pushstring(L, "Input service not available"); - return 2; - } - const char* action = luaL_checkstring(L, 1); - lua_pushboolean(L, context->inputService->IsActionPressed(action)); - return 1; -} - -int ScriptEngineService::InputIsMouseDown(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - if (!context || !context->inputService) { - lua_pushnil(L); - lua_pushstring(L, "Input service not available"); - return 2; - } - uint8_t button = SDL_BUTTON_LEFT; - if (lua_isnumber(L, 1)) { - button = static_cast(lua_tointeger(L, 1)); - } else if (lua_isstring(L, 1)) { - const char* name = lua_tostring(L, 1); - if (!TryParseMouseButtonName(name, button)) { - lua_pushboolean(L, 0); - return 1; - } - } else { - lua_pushboolean(L, 0); - return 1; - } - lua_pushboolean(L, context->inputService->IsMouseButtonPressed(button)); - return 1; -} - -int ScriptEngineService::InputGetText(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - if (!context || !context->inputService) { - lua_pushnil(L); - lua_pushstring(L, "Input service not available"); - return 2; - } - const auto& state = context->inputService->GetState(); - lua_pushstring(L, state.textInput.c_str()); - return 1; -} - -int ScriptEngineService::ConfigGetJson(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - if (!context || !context->configService) { - lua_pushnil(L); - lua_pushstring(L, "Config service not available"); - return 2; - } - const std::string& json = context->configService->GetConfigJson(); - if (json.empty()) { - lua_pushnil(L); - lua_pushstring(L, "Config JSON not available"); - return 2; - } - lua_pushlstring(L, json.c_str(), json.size()); - return 1; -} - -int ScriptEngineService::ConfigGetTable(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - if (!context || !context->configService) { - lua_pushnil(L); - lua_pushstring(L, "Config service not available"); - return 2; - } - std::string error; - if (!PushConfigTableFromJson(L, context->configService->GetConfigJson(), error)) { - lua_pushnil(L); - lua_pushstring(L, error.c_str()); - return 2; - } - return 1; -} - -int ScriptEngineService::TimeGetSeconds(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - auto logger = context ? context->logger : nullptr; - const Uint64 counter = SDL_GetPerformanceCounter(); - const Uint64 frequency = SDL_GetPerformanceFrequency(); - double seconds = 0.0; - if (frequency > 0) { - seconds = static_cast(counter) / static_cast(frequency); - } - if (logger) { - logger->Trace("ScriptEngineService", "TimeGetSeconds", - "seconds=" + std::to_string(seconds)); - } - lua_pushnumber(L, seconds); - return 1; -} - -int ScriptEngineService::MaterialXGetSurfaceParameters(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - auto logger = context ? context->logger : nullptr; - if (!context || !context->configService) { - lua_pushnil(L); - lua_pushstring(L, "Config service not available"); - return 2; - } - - const char* documentArg = luaL_checkstring(L, 1); - const char* materialArg = luaL_optstring(L, 2, ""); - std::filesystem::path scriptDirectory = context->configService->GetScriptPath().parent_path(); - if (logger) { - logger->Trace("ScriptEngineService", "MaterialXGetSurfaceParameters", - "document=" + std::string(documentArg ? documentArg : "") + - ", material=" + std::string(materialArg ? materialArg : "")); - } - - MaterialXPathResolver pathResolver; - std::filesystem::path documentPath = pathResolver.Resolve(documentArg ? documentArg : "", scriptDirectory); - if (documentPath.empty()) { - lua_pushnil(L); - lua_pushstring(L, "MaterialX document path could not be resolved"); - return 2; - } - if (!std::filesystem::exists(documentPath)) { - lua_pushnil(L); - std::string message = "MaterialX document not found: " + documentPath.string(); - lua_pushstring(L, message.c_str()); - return 2; - } - - const auto& materialConfig = context->configService->GetMaterialXConfig(); - MaterialXDocumentLoader documentLoader; - MaterialXSurfaceNodeResolver nodeResolver; - MaterialXSurfaceParameterReader parameterReader; - MaterialX::DocumentPtr document; - try { - document = documentLoader.Load(documentPath, materialConfig, scriptDirectory); - } catch (const std::exception& ex) { - lua_pushnil(L); - std::string message = "MaterialX document load failed: "; - message += ex.what(); - lua_pushstring(L, message.c_str()); - return 2; - } - - auto surfaceNode = nodeResolver.ResolveSurfaceNode(document, materialArg ? materialArg : ""); - if (!surfaceNode) { - lua_pushnil(L); - lua_pushstring(L, "MaterialX document has no standard_surface material"); - return 2; - } - - MaterialXSurfaceParameters parameters = parameterReader.ReadStandardSurfaceParameters(surfaceNode); - if (!parameters.hasAlbedo && !parameters.hasRoughness && !parameters.hasMetallic) { - lua_pushnil(L); - lua_pushstring(L, "MaterialX material does not expose supported PBR parameters"); - return 2; - } - - lua_newtable(L); - if (parameters.hasAlbedo) { - lua_newtable(L); - lua_pushnumber(L, parameters.albedo[0]); - lua_rawseti(L, -2, 1); - lua_pushnumber(L, parameters.albedo[1]); - lua_rawseti(L, -2, 2); - lua_pushnumber(L, parameters.albedo[2]); - lua_rawseti(L, -2, 3); - lua_setfield(L, -2, "material_albedo"); - } - if (parameters.hasRoughness) { - lua_pushnumber(L, parameters.roughness); - lua_setfield(L, -2, "material_roughness"); - } - if (parameters.hasMetallic) { - lua_pushnumber(L, parameters.metallic); - lua_setfield(L, -2, "material_metallic"); - } - - lua_pushnil(L); - return 2; -} - -int ScriptEngineService::WindowGetSize(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - if (!context || !context->windowService) { - lua_pushnil(L); - lua_pushstring(L, "Window service not available"); - return 2; - } - auto [width, height] = context->windowService->GetSize(); - lua_pushinteger(L, static_cast(width)); - lua_pushinteger(L, static_cast(height)); - return 2; -} - -int ScriptEngineService::WindowSetTitle(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - if (!context || !context->windowService) { - lua_pushnil(L); - lua_pushstring(L, "Window service not available"); - return 2; - } - const char* title = luaL_checkstring(L, 1); - context->windowService->SetTitle(title); - lua_pushboolean(L, 1); - return 1; -} - -int ScriptEngineService::WindowIsMinimized(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - if (!context || !context->windowService) { - lua_pushnil(L); - lua_pushstring(L, "Window service not available"); - return 2; - } - lua_pushboolean(L, context->windowService->IsMinimized()); - return 1; -} - -int ScriptEngineService::WindowSetMouseGrabbed(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - if (!context || !context->windowService) { - lua_pushnil(L); - lua_pushstring(L, "Window service not available"); - return 2; - } - bool grabbed = lua_toboolean(L, 1); - context->windowService->SetMouseGrabbed(grabbed); - lua_pushboolean(L, 1); - return 1; -} - -int ScriptEngineService::WindowGetMouseGrabbed(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - if (!context || !context->windowService) { - lua_pushnil(L); - lua_pushstring(L, "Window service not available"); - return 2; - } - lua_pushboolean(L, context->windowService->IsMouseGrabbed()); - return 1; -} - -int ScriptEngineService::WindowSetRelativeMouseMode(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - if (!context || !context->windowService) { - lua_pushnil(L); - lua_pushstring(L, "Window service not available"); - return 2; - } - bool enabled = lua_toboolean(L, 1); - context->windowService->SetRelativeMouseMode(enabled); - if (context->inputService) { - context->inputService->SetRelativeMouseMode(enabled); - } - lua_pushboolean(L, 1); - return 1; -} - -int ScriptEngineService::WindowGetRelativeMouseMode(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - if (!context || !context->windowService) { - lua_pushnil(L); - lua_pushstring(L, "Window service not available"); - return 2; - } - lua_pushboolean(L, context->windowService->IsRelativeMouseMode()); - return 1; -} - -int ScriptEngineService::WindowSetCursorVisible(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - if (!context || !context->windowService) { - lua_pushnil(L); - lua_pushstring(L, "Window service not available"); - return 2; - } - bool visible = lua_toboolean(L, 1); - context->windowService->SetCursorVisible(visible); - lua_pushboolean(L, 1); - return 1; -} - -int ScriptEngineService::WindowIsCursorVisible(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - if (!context || !context->windowService) { - lua_pushnil(L); - lua_pushstring(L, "Window service not available"); - return 2; - } - lua_pushboolean(L, context->windowService->IsCursorVisible()); - return 1; -} - -int ScriptEngineService::GlmMatrixIdentity(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - auto logger = context ? context->logger : nullptr; - if (logger) { - logger->Trace("ScriptEngineService", "GlmMatrixIdentity"); - } - return lua::LuaGlmMatrixIdentity(L); -} - -int ScriptEngineService::GlmMatrixMultiply(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - auto logger = context ? context->logger : nullptr; - if (logger) { - logger->Trace("ScriptEngineService", "GlmMatrixMultiply"); - } - return lua::LuaGlmMatrixMultiply(L); -} - -int ScriptEngineService::GlmMatrixTranslation(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - auto logger = context ? context->logger : nullptr; - if (logger) { - logger->Trace("ScriptEngineService", "GlmMatrixTranslation"); - } - return lua::LuaGlmMatrixTranslation(L); -} - -int ScriptEngineService::GlmMatrixRotationX(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - auto logger = context ? context->logger : nullptr; - if (logger) { - logger->Trace("ScriptEngineService", "GlmMatrixRotationX"); - } - return lua::LuaGlmMatrixRotationX(L); -} - -int ScriptEngineService::GlmMatrixRotationY(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - auto logger = context ? context->logger : nullptr; - if (logger) { - logger->Trace("ScriptEngineService", "GlmMatrixRotationY"); - } - return lua::LuaGlmMatrixRotationY(L); -} - -int ScriptEngineService::GlmMatrixLookAt(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - auto logger = context ? context->logger : nullptr; - if (logger) { - logger->Trace("ScriptEngineService", "GlmMatrixLookAt"); - } - return lua::LuaGlmMatrixLookAt(L); -} - -int ScriptEngineService::GlmMatrixPerspective(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - auto logger = context ? context->logger : nullptr; - if (logger) { - logger->Trace("ScriptEngineService", "GlmMatrixPerspective"); - } - return lua::LuaGlmMatrixPerspective(L); -} - -int ScriptEngineService::GlmMatrixFromTransform(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - auto logger = context ? context->logger : nullptr; - if (logger) { - logger->Trace("ScriptEngineService", "GlmMatrixFromTransform", - "luaStateIsNull=" + std::string(L ? "false" : "true")); - } - return lua::LuaGlmMatrixFromTransform(L); -} - -int ScriptEngineService::LuaPrint(lua_State* L) { - auto* context = static_cast(lua_touserdata(L, lua_upvalueindex(1))); - auto logger = context ? context->logger : nullptr; - - int nargs = lua_gettop(L); - std::string message; - - for (int i = 1; i <= nargs; ++i) { - if (i > 1) { - message += "\t"; - } - - const char* str = nullptr; - if (lua_isstring(L, i)) { - str = lua_tostring(L, i); - } else if (lua_isnil(L, i)) { - str = "nil"; - } else if (lua_isboolean(L, i)) { - str = lua_toboolean(L, i) ? "true" : "false"; - } else if (lua_isnumber(L, i)) { - lua_pushvalue(L, i); - str = lua_tostring(L, -1); - lua_pop(L, 1); - } else { - lua_pushvalue(L, i); - str = lua_tostring(L, -1); - if (!str) { - str = lua_typename(L, lua_type(L, i)); - } - lua_pop(L, 1); - } - - if (str) { - message += str; - } - } - - if (logger) { - logger->Info("[Lua] " + message); - } - - return 0; -} - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/script/script_engine_service.hpp b/src/services/impl/script/script_engine_service.hpp deleted file mode 100644 index 94abe81..0000000 --- a/src/services/impl/script/script_engine_service.hpp +++ /dev/null @@ -1,127 +0,0 @@ -#pragma once - -#include "services/interfaces/i_script_engine_service.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_input_service.hpp" -#include "services/interfaces/i_window_service.hpp" -#include "services/interfaces/i_config_service.hpp" -#include "services/interfaces/i_logger.hpp" -#include "../../../di/lifecycle.hpp" -#include -#include - -struct lua_State; - -namespace sdl3cpp::services::impl { - -/** - * @brief Service that owns the Lua runtime and script bindings. - */ -class ScriptEngineService : public IScriptEngineService, - public di::IInitializable, - public di::IShutdownable { -public: - ScriptEngineService(const std::filesystem::path& scriptPath, - std::shared_ptr logger, - std::shared_ptr meshService, - std::shared_ptr audioCommandService, - std::shared_ptr physicsBridgeService, - std::shared_ptr inputService, - std::shared_ptr windowService, - std::shared_ptr configService, - bool debugEnabled = false); - ~ScriptEngineService() override; - - // Lifecycle - void Initialize() override; - void Shutdown() noexcept override; - - // IScriptEngineService interface - lua_State* GetLuaState() const override; - std::filesystem::path GetScriptDirectory() const override; - bool IsInitialized() const override { - if (logger_) { - logger_->Trace("ScriptEngineService", "IsInitialized"); - } - return initialized_; - } - -private: - struct LuaBindingContext { - std::shared_ptr meshService; - std::shared_ptr audioCommandService; - std::shared_ptr physicsBridgeService; - std::shared_ptr inputService; - std::shared_ptr windowService; - std::shared_ptr configService; - std::shared_ptr logger; - }; - - void RegisterBindings(lua_State* L); - static int LoadMeshFromFile(lua_State* L); - static int LoadMeshFromArchive(lua_State* L); - static int PhysicsCreateBox(lua_State* L); - static int PhysicsCreateSphere(lua_State* L); - static int PhysicsCreateStaticMesh(lua_State* L); - static int PhysicsRemoveBody(lua_State* L); - static int PhysicsSetTransform(lua_State* L); - static int PhysicsApplyForce(lua_State* L); - static int PhysicsApplyImpulse(lua_State* L); - static int PhysicsSetLinearVelocity(lua_State* L); - static int PhysicsGetLinearVelocity(lua_State* L); - static int PhysicsSetGravity(lua_State* L); - static int PhysicsStepSimulation(lua_State* L); - static int PhysicsGetTransform(lua_State* L); - static int PhysicsGetBodyCount(lua_State* L); - static int PhysicsClear(lua_State* L); - static int AudioPlayBackground(lua_State* L); - static int AudioPlaySound(lua_State* L); - static int AudioStopBackground(lua_State* L); - static int GlmMatrixIdentity(lua_State* L); - static int GlmMatrixMultiply(lua_State* L); - static int GlmMatrixTranslation(lua_State* L); - static int GlmMatrixRotationX(lua_State* L); - static int GlmMatrixRotationY(lua_State* L); - static int GlmMatrixLookAt(lua_State* L); - static int GlmMatrixPerspective(lua_State* L); - static int GlmMatrixFromTransform(lua_State* L); - static int InputGetMousePosition(lua_State* L); - static int InputGetMouseDelta(lua_State* L); - static int InputGetMouseWheel(lua_State* L); - static int InputIsKeyDown(lua_State* L); - static int InputIsActionDown(lua_State* L); - static int InputIsMouseDown(lua_State* L); - static int InputGetText(lua_State* L); - static int ConfigGetJson(lua_State* L); - static int ConfigGetTable(lua_State* L); - static int MaterialXGetSurfaceParameters(lua_State* L); - static int TimeGetSeconds(lua_State* L); - static int WindowGetSize(lua_State* L); - static int WindowSetTitle(lua_State* L); - static int WindowIsMinimized(lua_State* L); - static int WindowSetMouseGrabbed(lua_State* L); - static int WindowGetMouseGrabbed(lua_State* L); - static int WindowSetRelativeMouseMode(lua_State* L); - static int WindowGetRelativeMouseMode(lua_State* L); - static int WindowSetCursorVisible(lua_State* L); - static int WindowIsCursorVisible(lua_State* L); - static int LuaPrint(lua_State* L); - - std::shared_ptr logger_; - std::shared_ptr meshService_; - std::shared_ptr audioCommandService_; - std::shared_ptr physicsBridgeService_; - std::shared_ptr inputService_; - std::shared_ptr windowService_; - std::shared_ptr configService_; - std::filesystem::path scriptPath_; - std::filesystem::path scriptDirectory_; - bool debugEnabled_ = false; - bool initialized_ = false; - lua_State* luaState_ = nullptr; - std::shared_ptr bindingContext_; -}; - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/script/shader_script_service.cpp b/src/services/impl/script/shader_script_service.cpp deleted file mode 100644 index 6521502..0000000 --- a/src/services/impl/script/shader_script_service.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "shader_script_service.hpp" - -#include "services/interfaces/i_logger.hpp" - -#include -#include -#include - -namespace sdl3cpp::services::impl { - -ShaderScriptService::ShaderScriptService(std::shared_ptr engineService, - std::shared_ptr shaderSystemRegistry, - std::shared_ptr logger) - : engineService_(std::move(engineService)), - shaderSystemRegistry_(std::move(shaderSystemRegistry)), - logger_(std::move(logger)) { - if (logger_) { - logger_->Trace("ShaderScriptService", "ShaderScriptService", - "engineService=" + std::string(engineService_ ? "set" : "null") + - ", shaderSystemRegistry=" + std::string(shaderSystemRegistry_ ? "set" : "null")); - } -} - -std::unordered_map ShaderScriptService::LoadShaderPathsMap() { - if (!shaderSystemRegistry_) { - throw std::runtime_error("Shader script service requires a shader system registry"); - } - return shaderSystemRegistry_->BuildShaderMap(); -} - -lua_State* ShaderScriptService::GetLuaState() const { - if (logger_) { - logger_->Trace("ShaderScriptService", "GetLuaState"); - } - if (!engineService_) { - throw std::runtime_error("Shader script service is missing script engine service"); - } - lua_State* state = engineService_->GetLuaState(); - if (!state) { - throw std::runtime_error("Lua state is not initialized"); - } - return state; -} - -} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/script/shader_script_service.hpp b/src/services/impl/script/shader_script_service.hpp deleted file mode 100644 index 08ebae4..0000000 --- a/src/services/impl/script/shader_script_service.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include "services/interfaces/i_shader_script_service.hpp" -#include "services/interfaces/i_script_engine_service.hpp" -#include "services/interfaces/i_logger.hpp" -#include "services/interfaces/i_shader_system_registry.hpp" -#include - -struct lua_State; - -namespace sdl3cpp::services::impl { - -/** - * @brief Script-facing shader service implementation. - */ -class ShaderScriptService : public IShaderScriptService { -public: - ShaderScriptService(std::shared_ptr engineService, - std::shared_ptr shaderSystemRegistry, - std::shared_ptr logger); - - std::unordered_map LoadShaderPathsMap() override; - -private: - lua_State* GetLuaState() const; - ShaderPaths ReadShaderPathsTable(lua_State* L, int index) const; - std::string ResolveShaderPath(const std::string& path) const; - - std::shared_ptr engineService_; - std::shared_ptr shaderSystemRegistry_; - std::shared_ptr logger_; -}; - -} // namespace sdl3cpp::services::impl diff --git a/src/services/interfaces/i_gui_script_service.hpp b/src/services/interfaces/i_gui_script_service.hpp deleted file mode 100644 index 2b20a1f..0000000 --- a/src/services/interfaces/i_gui_script_service.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "gui_types.hpp" -#include - -namespace sdl3cpp::services { - -/** - * @brief Script-facing GUI command service interface. - */ -class IGuiScriptService { -public: - virtual ~IGuiScriptService() = default; - - virtual std::vector LoadGuiCommands() = 0; - virtual void UpdateGuiInput(const GuiInputSnapshot& input) = 0; - virtual bool HasGuiCommands() const = 0; -}; - -} // namespace sdl3cpp::services diff --git a/src/services/interfaces/i_scene_script_service.hpp b/src/services/interfaces/i_scene_script_service.hpp deleted file mode 100644 index 264deee..0000000 --- a/src/services/interfaces/i_scene_script_service.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "scene_types.hpp" -#include "graphics_types.hpp" -#include - -namespace sdl3cpp::services { - -/** - * @brief Script-facing scene service interface. - */ -class ISceneScriptService { -public: - virtual ~ISceneScriptService() = default; - - virtual std::vector LoadSceneObjects() = 0; - virtual std::array ComputeModelMatrix(int functionRef, float time) = 0; - virtual ViewState GetViewState(float aspect) = 0; -}; - -} // namespace sdl3cpp::services diff --git a/src/services/interfaces/i_script_engine_service.hpp b/src/services/interfaces/i_script_engine_service.hpp deleted file mode 100644 index 22fef3b..0000000 --- a/src/services/interfaces/i_script_engine_service.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include - -struct lua_State; - -namespace sdl3cpp::services { - -/** - * @brief Service for owning and exposing the Lua script engine. - */ -class IScriptEngineService { -public: - virtual ~IScriptEngineService() = default; - - virtual lua_State* GetLuaState() const = 0; - virtual std::filesystem::path GetScriptDirectory() const = 0; - virtual bool IsInitialized() const = 0; -}; - -} // namespace sdl3cpp::services diff --git a/src/services/interfaces/i_shader_script_service.hpp b/src/services/interfaces/i_shader_script_service.hpp deleted file mode 100644 index 1cf5421..0000000 --- a/src/services/interfaces/i_shader_script_service.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include "graphics_types.hpp" -#include -#include - -namespace sdl3cpp::services { - -/** - * @brief Script-facing shader lookup service interface. - */ -class IShaderScriptService { -public: - virtual ~IShaderScriptService() = default; - - virtual std::unordered_map LoadShaderPathsMap() = 0; -}; - -} // namespace sdl3cpp::services diff --git a/tests/gui_script_service_missing_fields_test.cpp b/tests/gui_script_service_missing_fields_test.cpp deleted file mode 100644 index cfb79f8..0000000 --- a/tests/gui_script_service_missing_fields_test.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#include - -#include "services/impl/script/gui_script_service.hpp" -#include "services/interfaces/i_logger.hpp" -#include "services/interfaces/i_script_engine_service.hpp" - -#include - -#include -#include -#include -#include -#include - -namespace { - -class CapturingLogger final : public sdl3cpp::services::ILogger { -public: - void SetLevel(sdl3cpp::services::LogLevel level) override { level_ = level; } - sdl3cpp::services::LogLevel GetLevel() const override { return level_; } - void SetOutputFile(const std::string&) override {} - void SetMaxLinesPerFile(size_t) override {} - void EnableConsoleOutput(bool) override {} - void Log(sdl3cpp::services::LogLevel, const std::string& message) override { - entries_.push_back(message); - } - void Trace(const std::string& message) override { entries_.push_back(message); } - void Trace(const std::string& className, - const std::string& methodName, - const std::string& args = "", - const std::string& message = "") override { - std::string formatted = className + "::" + methodName; - if (!args.empty()) { - formatted += "(" + args + ")"; - } - if (!message.empty()) { - formatted += ": " + message; - } - entries_.push_back(formatted); - } - void Debug(const std::string& message) override { entries_.push_back(message); } - void Info(const std::string& message) override { entries_.push_back(message); } - void Warn(const std::string& message) override { entries_.push_back(message); } - void Error(const std::string& message) override { entries_.push_back(message); } - void TraceFunction(const std::string&) override {} - void TraceVariable(const std::string&, const std::string&) override {} - void TraceVariable(const std::string&, int) override {} - void TraceVariable(const std::string&, size_t) override {} - void TraceVariable(const std::string&, bool) override {} - void TraceVariable(const std::string&, float) override {} - void TraceVariable(const std::string&, double) override {} - - bool HasSubstring(const std::string& fragment) const { - for (const auto& entry : entries_) { - if (entry.find(fragment) != std::string::npos) { - return true; - } - } - return false; - } - -private: - sdl3cpp::services::LogLevel level_ = sdl3cpp::services::LogLevel::TRACE; - std::vector entries_; -}; - -class StubScriptEngineService final : public sdl3cpp::services::IScriptEngineService { -public: - explicit StubScriptEngineService(lua_State* state) : state_(state) {} - - lua_State* GetLuaState() const override { return state_; } - std::filesystem::path GetScriptDirectory() const override { return {}; } - bool IsInitialized() const override { return state_ != nullptr; } - -private: - lua_State* state_; -}; - -TEST(GuiScriptServiceMissingFieldsTest, MissingBorderColorDefaultsToTransparent) { - std::unique_ptr state(luaL_newstate(), &lua_close); - ASSERT_NE(state.get(), nullptr); - luaL_openlibs(state.get()); - - const char* script = R"( - function get_gui_commands() - return { - { - type = "rect", - x = 10, - y = 20, - width = 30, - height = 40, - color = {0.1, 0.2, 0.3, 0.4} - } - } - end - )"; - - ASSERT_EQ(luaL_dostring(state.get(), script), LUA_OK) << lua_tostring(state.get(), -1); - - auto engineService = std::make_shared(state.get()); - auto logger = std::make_shared(); - sdl3cpp::services::impl::GuiScriptService service(engineService, logger); - - service.Initialize(); - - std::vector commands; - ASSERT_NO_THROW(commands = service.LoadGuiCommands()); - ASSERT_EQ(commands.size(), 1u); - - const auto& command = commands.front(); - EXPECT_EQ(command.type, sdl3cpp::services::GuiCommand::Type::Rect); - EXPECT_FLOAT_EQ(command.borderColor.r, 0.0f); - EXPECT_FLOAT_EQ(command.borderColor.g, 0.0f); - EXPECT_FLOAT_EQ(command.borderColor.b, 0.0f); - EXPECT_FLOAT_EQ(command.borderColor.a, 0.0f); - EXPECT_TRUE(logger->HasSubstring("Field not found or not table: borderColor")); - - service.Shutdown(); -} - -} // namespace diff --git a/tests/test_cube_script.cpp b/tests/test_cube_script.cpp deleted file mode 100644 index 630d97d..0000000 --- a/tests/test_cube_script.cpp +++ /dev/null @@ -1,875 +0,0 @@ -#include "services/impl/graphics/bgfx_graphics_backend.hpp" -#include "services/impl/diagnostics/logger_service.hpp" -#include "services/impl/scene/mesh_service.hpp" -#include "services/impl/shader/pipeline_compiler_service.hpp" -#include "services/impl/scene/physics_bridge_service.hpp" -#include "services/impl/platform/platform_service.hpp" -#include "services/impl/script/script_engine_service.hpp" -#include "services/impl/script/scene_script_service.hpp" -#include "services/impl/script/shader_script_service.hpp" -#include "services/impl/shader/shader_system_registry.hpp" -#include "services/impl/scene/ecs_service.hpp" -#include "services/impl/scene/scene_service.hpp" -#include "services/impl/platform/sdl_window_service.hpp" -#include "services/interfaces/i_audio_command_service.hpp" -#include "services/interfaces/i_config_service.hpp" -#include "events/event_bus.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace { -constexpr std::array kIdentityMatrix = { - 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, -}; - -bool ApproximatelyEqual(float a, float b, float eps = 1e-5f) { - return std::fabs(a - b) <= eps; -} - -bool ExpectIdentity(const std::array& actual, const std::string& label, int& failures) { - for (size_t i = 0; i < actual.size(); ++i) { - if (!ApproximatelyEqual(actual[i], kIdentityMatrix[i])) { - std::cerr << label << " differs at index " << i << " (" << actual[i] << " vs " - << kIdentityMatrix[i] << ")\n"; - ++failures; - return false; - } - } - return true; -} - -std::filesystem::path GetTestScriptPath() { - auto testDir = std::filesystem::path(__FILE__).parent_path(); - return testDir / "scripts" / "unit_cube_logic.lua"; -} - -std::filesystem::path GetCubeScriptPath() { - auto repoRoot = std::filesystem::path(__FILE__).parent_path().parent_path(); - return repoRoot / "scripts" / "cube_logic.lua"; -} - -std::filesystem::path GetSeedConfigPath() { - auto repoRoot = std::filesystem::path(__FILE__).parent_path().parent_path(); - return repoRoot / "config" / "seed_runtime.json"; -} - -std::string DeterminePreferredRenderer() { - if (const char* envRenderer = std::getenv("BGFX_RENDERER")) { - return envRenderer; - } - if (const char* videoDriver = std::getenv("SDL_VIDEODRIVER")) { - std::string driver(videoDriver); - if (driver == "offscreen" || driver == "dummy") { - return "opengl"; - } - } - return "opengl"; -} - -std::optional ReadFileContents(const std::filesystem::path& path) { - std::ifstream input(path); - if (!input) { - return std::nullopt; - } - std::string contents((std::istreambuf_iterator(input)), - std::istreambuf_iterator()); - return contents; -} - -class StubConfigService final : public sdl3cpp::services::IConfigService { -public: - StubConfigService() { - materialXConfig_.enabled = true; - materialXConfig_.useConstantColor = true; - materialXConfig_.shaderKey = "test"; - materialXConfig_.libraryPath = ResolveMaterialXLibraryPath(); - auto configJson = ReadFileContents(GetSeedConfigPath()); - if (configJson) { - configJson_ = *configJson; - } else { - configJson_ = "{}"; - } - } - - uint32_t GetWindowWidth() const override { return 1; } - uint32_t GetWindowHeight() const override { return 1; } - std::filesystem::path GetScriptPath() const override { return {}; } - bool IsLuaDebugEnabled() const override { return false; } - std::string GetWindowTitle() const override { return ""; } - sdl3cpp::services::SceneSource GetSceneSource() const override { - return sdl3cpp::services::SceneSource::Lua; - } - const sdl3cpp::services::InputBindings& GetInputBindings() const override { return inputBindings_; } - const sdl3cpp::services::MouseGrabConfig& GetMouseGrabConfig() const override { return mouseGrabConfig_; } - const sdl3cpp::services::BgfxConfig& GetBgfxConfig() const override { return bgfxConfig_; } - const sdl3cpp::services::MaterialXConfig& GetMaterialXConfig() const override { return materialXConfig_; } - const std::vector& GetMaterialXMaterialConfigs() const override { - return materialXMaterials_; - } - const sdl3cpp::services::GuiFontConfig& GetGuiFontConfig() const override { return guiFontConfig_; } - const sdl3cpp::services::RenderBudgetConfig& GetRenderBudgetConfig() const override { return budgets_; } - const sdl3cpp::services::CrashRecoveryConfig& GetCrashRecoveryConfig() const override { return crashRecovery_; } - const sdl3cpp::services::ValidationTourConfig& GetValidationTourConfig() const override { - return validationTour_; - } - const std::string& GetConfigJson() const override { return configJson_; } - -private: - static std::filesystem::path ResolveMaterialXLibraryPath() { - auto repoRoot = std::filesystem::path(__FILE__).parent_path().parent_path(); - return repoRoot / "MaterialX" / "libraries"; - } - - sdl3cpp::services::InputBindings inputBindings_{}; - sdl3cpp::services::MouseGrabConfig mouseGrabConfig_{}; - sdl3cpp::services::BgfxConfig bgfxConfig_{}; - sdl3cpp::services::MaterialXConfig materialXConfig_{}; - std::vector materialXMaterials_{}; - sdl3cpp::services::GuiFontConfig guiFontConfig_{}; - sdl3cpp::services::RenderBudgetConfig budgets_{}; - sdl3cpp::services::CrashRecoveryConfig crashRecovery_{}; - sdl3cpp::services::ValidationTourConfig validationTour_{}; - std::string configJson_{}; -}; - -class CubeDemoConfigService final : public sdl3cpp::services::IConfigService { -public: - CubeDemoConfigService(std::filesystem::path scriptPath, std::string configJson, std::string renderer = "auto") - : scriptPath_(std::move(scriptPath)), - configJson_(std::move(configJson)) { - materialXConfig_.enabled = true; - materialXConfig_.useConstantColor = true; - materialXConfig_.shaderKey = "test"; - materialXConfig_.libraryPath = ResolveMaterialXLibraryPath(); - renderer_ = std::move(renderer); - bgfxConfig_.renderer = renderer_; - } - - uint32_t GetWindowWidth() const override { return 1; } - uint32_t GetWindowHeight() const override { return 1; } - std::filesystem::path GetScriptPath() const override { return scriptPath_; } - bool IsLuaDebugEnabled() const override { return false; } - std::string GetWindowTitle() const override { return ""; } - sdl3cpp::services::SceneSource GetSceneSource() const override { - return sdl3cpp::services::SceneSource::Lua; - } - const sdl3cpp::services::InputBindings& GetInputBindings() const override { return inputBindings_; } - const sdl3cpp::services::MouseGrabConfig& GetMouseGrabConfig() const override { return mouseGrabConfig_; } - const sdl3cpp::services::BgfxConfig& GetBgfxConfig() const override { return bgfxConfig_; } - const sdl3cpp::services::MaterialXConfig& GetMaterialXConfig() const override { return materialXConfig_; } - const std::vector& GetMaterialXMaterialConfigs() const override { - return materialXMaterials_; - } - const sdl3cpp::services::GuiFontConfig& GetGuiFontConfig() const override { return guiFontConfig_; } - const sdl3cpp::services::RenderBudgetConfig& GetRenderBudgetConfig() const override { return budgets_; } - const sdl3cpp::services::CrashRecoveryConfig& GetCrashRecoveryConfig() const override { return crashRecovery_; } - const sdl3cpp::services::ValidationTourConfig& GetValidationTourConfig() const override { - return validationTour_; - } - const std::string& GetConfigJson() const override { return configJson_; } - -private: - static std::filesystem::path ResolveMaterialXLibraryPath() { - auto repoRoot = std::filesystem::path(__FILE__).parent_path().parent_path(); - return repoRoot / "MaterialX" / "libraries"; - } - - std::filesystem::path scriptPath_; - std::string configJson_; - sdl3cpp::services::InputBindings inputBindings_{}; - sdl3cpp::services::MouseGrabConfig mouseGrabConfig_{}; - sdl3cpp::services::BgfxConfig bgfxConfig_{}; - sdl3cpp::services::MaterialXConfig materialXConfig_{}; - std::vector materialXMaterials_{}; - sdl3cpp::services::GuiFontConfig guiFontConfig_{}; - sdl3cpp::services::RenderBudgetConfig budgets_{}; - sdl3cpp::services::CrashRecoveryConfig crashRecovery_{}; - sdl3cpp::services::ValidationTourConfig validationTour_{}; - std::string renderer_; -}; - -class StubAudioCommandService final : public sdl3cpp::services::IAudioCommandService { -public: - bool QueueAudioCommand(sdl3cpp::services::AudioCommandType, - const std::string&, - bool, - std::string&) override { - return true; - } - - bool StopBackground(std::string&) override { - return true; - } -}; - -void Assert(bool condition, const std::string& message, int& failures) { - if (!condition) { - std::cerr << "test failure: " << message << '\n'; - ++failures; - } -} - -struct MatrixSummary { - std::array translation{}; - std::array scale{}; -}; - -MatrixSummary ExtractMatrixSummary(const std::array& matrix) { - MatrixSummary summary; - summary.translation = {matrix[12], matrix[13], matrix[14]}; - - // Extract scale as magnitude of basis vectors (correct for rotation+scale matrices) - // X-axis: matrix[0,1,2], Y-axis: matrix[4,5,6], Z-axis: matrix[8,9,10] - auto length = [](float x, float y, float z) { - return std::sqrt(x*x + y*y + z*z); - }; - summary.scale = { - length(matrix[0], matrix[1], matrix[2]), - length(matrix[4], matrix[5], matrix[6]), - length(matrix[8], matrix[9], matrix[10]) - }; - - return summary; -} - -std::array ToArray(const glm::mat4& matrix) { - std::array values{}; - std::memcpy(values.data(), glm::value_ptr(matrix), sizeof(float) * values.size()); - return values; -} - -bool ExpectMatrixNear(const std::array& actual, - const std::array& expected, - const std::string& label, - int& failures, - float eps = 1e-4f) { - for (size_t i = 0; i < actual.size(); ++i) { - if (!ApproximatelyEqual(actual[i], expected[i], eps)) { - std::cerr << label << " differs at index " << i << " (" << actual[i] - << " vs " << expected[i] << ")\n"; - ++failures; - return false; - } - } - return true; -} - -bool ExpectColorNear(const sdl3cpp::core::Vertex& vertex, - const std::array& expected, - const std::string& label, - int& failures, - float eps = 1e-4f) { - for (size_t i = 0; i < expected.size(); ++i) { - if (!ApproximatelyEqual(vertex.color[i], expected[i], eps)) { - std::cerr << label << " color differs at index " << i << " (" << vertex.color[i] - << " vs " << expected[i] << ")\n"; - ++failures; - return false; - } - } - return true; -} - -bool RunGpuRenderTest(int& failures, - const std::shared_ptr& configService, - const std::shared_ptr& logger) { - const char* preferredDrivers[] = {"x11", "wayland", "offscreen", "dummy", nullptr}; - const char* selectedDriver = nullptr; - - auto setVideoDriver = [](const char* driver) { -#ifdef _WIN32 - if (driver) { - _putenv_s("SDL_VIDEODRIVER", driver); - } else { - _putenv_s("SDL_VIDEODRIVER", ""); - } -#else - if (driver) { - setenv("SDL_VIDEODRIVER", driver, 1); - } else { - unsetenv("SDL_VIDEODRIVER"); - } -#endif - if (driver) { - SDL_SetHint(SDL_HINT_VIDEO_DRIVER, driver); - } else { - SDL_SetHint(SDL_HINT_VIDEO_DRIVER, ""); - } - }; - - auto platformService = std::make_shared(logger); - auto eventBus = std::make_shared(); - auto windowService = std::make_shared(logger, platformService, eventBus); - - SDL_Window* window = nullptr; - bool windowCreated = false; - - for (const char* driver : preferredDrivers) { - setVideoDriver(driver); - SDL_ClearError(); - - try { - windowService->Initialize(); - sdl3cpp::services::WindowConfig config{}; - config.width = 256; - config.height = 256; - config.title = "cube_gpu_test"; - config.resizable = false; - windowService->CreateWindow(config); - window = windowService->GetNativeHandle(); - windowCreated = true; - selectedDriver = driver; - break; - } catch (const std::exception& ex) { - std::cerr << "Window creation failed for driver [" << (driver ? driver : "default") - << "]: " << ex.what() << '\n'; - windowService->Shutdown(); - windowService = std::make_shared(logger, platformService, eventBus); - } - } - - if (!windowCreated) { - std::string driverList; - const int driverCount = SDL_GetNumVideoDrivers(); - for (int i = 0; i < driverCount; ++i) { - const char* driver = SDL_GetVideoDriver(i); - if (!driver) { - continue; - } - if (!driverList.empty()) { - driverList += ", "; - } - driverList += driver; - } - if (driverList.empty()) { - driverList = "none"; - } - std::cerr << "GPU render test failed: no SDL driver available" - << " (available drivers: " << driverList << ")\n"; - ++failures; - return false; - } - - if (selectedDriver) { - std::cout << "SDL video driver selected for GPU test: " << selectedDriver << '\n'; - } else { - std::cout << "SDL video driver selected for GPU test: default\n"; - } - - auto pipelineCompiler = std::make_shared(logger); - sdl3cpp::services::impl::BgfxGraphicsBackend backend(configService, platformService, logger, pipelineCompiler); - bool success = true; - bool skipGpuRenderTest = false; - - try { - sdl3cpp::services::GraphicsConfig graphicsConfig{}; - backend.Initialize(window, graphicsConfig); - } catch (const std::exception& ex) { - std::cerr << "GPU render test failed: bgfx init threw: " << ex.what() << '\n'; - ++failures; - success = false; - } - - if (success && bgfx::getRendererType() == bgfx::RendererType::Noop) { - const std::string activeDriver = selectedDriver ? selectedDriver : ""; - if (activeDriver == "offscreen" || activeDriver == "dummy") { - std::cerr << "GPU render test skipped: bgfx selected Noop renderer for headless driver '" << - activeDriver << "'\n"; - skipGpuRenderTest = true; - } else { - std::cerr << "GPU render test failed: bgfx selected Noop renderer despite SDL success\n"; - ++failures; - success = false; - } - } - - if (success && !skipGpuRenderTest) { - std::cout << "GPU render test: Validating full render pipeline with scene geometry\n"; - - // Load and render the actual cube scene to catch color, geometry, and animation issues - auto scriptPath = GetCubeScriptPath(); - auto sceneConfigService = configService; - auto meshService = std::make_shared(sceneConfigService, logger); - auto audioService = std::make_shared(); - auto physicsService = std::make_shared(logger); - - auto engineService = std::make_shared( - scriptPath, - logger, - meshService, - audioService, - physicsService, - nullptr, - nullptr, - sceneConfigService, - false); - engineService->Initialize(); - - auto sceneScriptService = std::make_shared(engineService, logger); - auto objects = sceneScriptService->LoadSceneObjects(); - - if (objects.size() != 15) { - std::cerr << "GPU render test: Scene loaded " << objects.size() << " objects, expected 15\n"; - ++failures; - success = false; - } - - // Validate all geometry is present - bool hasFloor = false; - bool hasCeiling = false; - int wallCount = 0; - int lanternCount = 0; - bool hasCube = false; - - for (const auto& obj : objects) { - const auto& type = obj.objectType; - - if (type == "floor") { - hasFloor = true; - } else if (type == "ceiling") { - hasCeiling = true; - } else if (type == "wall") { - wallCount++; - } else if (type == "lantern") { - lanternCount++; - } else if (type == "physics_cube" || type == "spinning_cube") { - hasCube = true; - } - } - - Assert(hasFloor, "GPU render test: Missing floor geometry", failures); - Assert(hasCeiling, "GPU render test: Missing ceiling geometry", failures); - Assert(wallCount == 4, "GPU render test: Expected 4 walls, got " + std::to_string(wallCount), failures); - Assert(lanternCount == 8, "GPU render test: Expected 8 lanterns, got " + std::to_string(lanternCount), failures); - Assert(hasCube, "GPU render test: Missing physics cube geometry", failures); - - // Validate all scene objects have valid shader keys (critical for rendering) - for (size_t i = 0; i < objects.size(); ++i) { - const auto& obj = objects[i]; - Assert(!obj.shaderKeys.empty(), - "GPU render test: Object " + std::to_string(i) + " (" + obj.objectType + ") has no shader keys", - failures); - - // Validate room geometry (floor, ceiling, walls) has expected shader keys - if (obj.objectType == "floor") { - Assert(!obj.shaderKeys.empty() && obj.shaderKeys.front() == "floor", - "GPU render test: Floor should have shader key 'floor'", failures); - Assert(obj.vertices.size() >= 100, - "GPU render test: Floor should have tessellated geometry (expected >= 100 vertices, got " + - std::to_string(obj.vertices.size()) + ")", failures); - } else if (obj.objectType == "ceiling") { - Assert(!obj.shaderKeys.empty() && obj.shaderKeys.front() == "ceiling", - "GPU render test: Ceiling should have shader key 'ceiling'", failures); - Assert(obj.vertices.size() >= 100, - "GPU render test: Ceiling should have tessellated geometry (expected >= 100 vertices, got " + - std::to_string(obj.vertices.size()) + ")", failures); - } else if (obj.objectType == "wall") { - Assert(!obj.shaderKeys.empty() && obj.shaderKeys.front() == "wall", - "GPU render test: Wall should have shader key 'wall'", failures); - } - } - - // Create actual render buffers and render multiple frames to test animation - auto ecsService = std::make_shared(logger); - auto sceneService = std::make_shared(sceneScriptService, ecsService, logger); - sceneService->LoadScene(objects); - - auto device = backend.CreateDevice(); - const int testFrames = 5; - std::cout << "GPU render test: Rendering " << testFrames << " frames to validate pipeline\n"; - - for (int frame = 0; frame < testFrames; ++frame) { - float elapsedTime = frame * 0.016f; // ~60 FPS - auto renderCommands = sceneService->GetRenderCommands(elapsedTime); - - if (renderCommands.size() != objects.size()) { - std::cerr << "GPU render test: Frame " << frame << " produced " - << renderCommands.size() << " commands, expected " << objects.size() << '\n'; - ++failures; - success = false; - break; - } - - backend.BeginFrame(device); - - // Validate cube is animating (matrix should change between frames) - if (frame == 0) { - for (size_t i = 0; i < renderCommands.size(); ++i) { - if (objects[i].shaderKeys.empty()) continue; - const auto& key = objects[i].shaderKeys.front(); - if (key == "floor" && objects[i].vertices.size() < 100) { - // This is the cube - verify it has non-identity matrix (spinning) - bool hasRotation = false; - const auto& matrix = renderCommands[i].modelMatrix; - // Check off-diagonal elements for rotation - if (std::abs(matrix[1]) > 0.01f || std::abs(matrix[2]) > 0.01f || - std::abs(matrix[4]) > 0.01f || std::abs(matrix[6]) > 0.01f || - std::abs(matrix[8]) > 0.01f || std::abs(matrix[9]) > 0.01f) { - hasRotation = true; - } - if (!hasRotation) { - std::cerr << "GPU render test: Cube is not spinning (rotation matrix is identity)\n"; - ++failures; - success = false; - } - break; - } - } - } - - backend.EndFrame(device); - } - - backend.DestroyDevice(device); - sceneService->Shutdown(); - engineService->Shutdown(); - - std::cout << "GPU render test: Successfully rendered and validated scene pipeline\n"; - } - - backend.Shutdown(); - windowService->DestroyWindow(); - windowService->Shutdown(); - - return success; -} - -void RunCubeDemoSceneTests(int& failures) { - auto scriptPath = GetCubeScriptPath(); - auto configPath = GetSeedConfigPath(); - auto configJson = ReadFileContents(configPath); - Assert(static_cast(configJson), "seed runtime config missing", failures); - if (!configJson) { - return; - } - - auto logger = std::make_shared(); - const std::string preferredRenderer = DeterminePreferredRenderer(); - auto configService = std::make_shared(scriptPath, *configJson, preferredRenderer); - auto meshService = std::make_shared(configService, logger); - auto audioService = std::make_shared(); - auto physicsService = std::make_shared(logger); - - if (!RunGpuRenderTest(failures, configService, logger)) { - std::cerr << "Aborting cube scene checks because GPU render test failed\n"; - return; - } - - auto engineService = std::make_shared( - scriptPath, - logger, - meshService, - audioService, - physicsService, - nullptr, - nullptr, - configService, - false); - engineService->Initialize(); - - auto sceneScriptService = std::make_shared(engineService, logger); - auto objects = sceneScriptService->LoadSceneObjects(); - - Assert(objects.size() == 15, "cube demo should return 15 scene objects", failures); - if (objects.empty()) { - engineService->Shutdown(); - return; - } - - for (const auto& object : objects) { - Assert(!object.vertices.empty(), "scene object missing vertices", failures); - Assert(!object.indices.empty(), "scene object missing indices", failures); - Assert(!object.shaderKeys.empty(), "scene object missing shader key", failures); - Assert(object.computeModelMatrixRef >= 0, "scene object must keep a Lua reference", failures); - } - - auto ecsService = std::make_shared(logger); - auto sceneManager = std::make_shared(sceneScriptService, ecsService, logger); - sceneManager->LoadScene(objects); - Assert(sceneManager->GetObjectCount() == objects.size(), "scene service object count mismatch", failures); - - size_t expectedVertexCount = 0; - size_t expectedIndexCount = 0; - for (const auto& object : objects) { - expectedVertexCount += object.vertices.size(); - expectedIndexCount += object.indices.size(); - } - - const auto& combinedVertices = sceneManager->GetCombinedVertices(); - const auto& combinedIndices = sceneManager->GetCombinedIndices(); - Assert(combinedVertices.size() == expectedVertexCount, "combined vertex count mismatch", failures); - Assert(combinedIndices.size() == expectedIndexCount, "combined index count mismatch", failures); - - const std::array white = {1.0f, 1.0f, 1.0f}; - const std::array lanternColor = {1.0f, 0.9f, 0.6f}; - const std::array cubeColor = {0.92f, 0.34f, 0.28f}; - - const float roomHalfSize = 15.0f; - const float wallThickness = 0.5f; - const float wallHeight = 4.0f; - const float floorHalfThickness = 0.3f; - const float floorTop = 0.0f; - const float floorCenterY = floorTop - floorHalfThickness; - const float wallCenterY = floorTop + wallHeight; - const float ceilingY = floorTop + wallHeight * 2.0f + floorHalfThickness; - const float wallOffset = roomHalfSize + wallThickness; - const float lanternHeight = 8.0f; - const float lanternSize = 0.2f; - const float lanternOffset = roomHalfSize - 2.0f; - const float cubeSpawnY = floorTop + wallHeight + 1.5f + 0.5f; - - auto staticCommands = sceneManager->GetRenderCommands(0.0f); - auto dynamicCommands = sceneManager->GetRenderCommands(0.1f); - Assert(staticCommands.size() == objects.size(), "render command count mismatch", failures); - Assert(dynamicCommands.size() == objects.size(), "dynamic render command count mismatch", failures); - - std::vector floorIndices; - std::vector wallIndices; - std::vector ceilingIndices; - std::vector solidIndices; - std::vector otherIndices; - std::vector> wallTranslations; - wallTranslations.reserve(4); - std::vector> lanternTranslations; - lanternTranslations.reserve(8); - - for (size_t index = 0; index < staticCommands.size(); ++index) { - const auto& command = staticCommands[index]; - const auto& object = objects[index]; - Assert(!command.shaderKeys.empty(), "scene object missing shader key", failures); - Assert(!object.indices.empty(), "scene object should have indices", failures); - - const auto summary = ExtractMatrixSummary(command.modelMatrix); - const std::string& objectType = object.objectType; - - if (objectType == "floor") { - floorIndices.push_back(index); - if (!object.vertices.empty()) { - ExpectColorNear(object.vertices.front(), white, "floor vertex color", failures); - } - } else if (objectType == "wall") { - wallIndices.push_back(index); - wallTranslations.push_back(summary.translation); - Assert(ApproximatelyEqual(summary.scale[1], wallHeight), "wall scale height mismatch", failures); - if (!object.vertices.empty()) { - ExpectColorNear(object.vertices.front(), white, "wall vertex color", failures); - } - } else if (objectType == "ceiling") { - ceilingIndices.push_back(index); - Assert(ApproximatelyEqual(summary.translation[1], ceilingY), "ceiling translation mismatch", failures); - // Ceiling now uses tessellated plane with scale 1.0 (geometry is pre-sized) - Assert(ApproximatelyEqual(summary.scale[0], 1.0f, 0.1f), "ceiling scale mismatch", failures); - if (!object.vertices.empty()) { - ExpectColorNear(object.vertices.front(), white, "ceiling vertex color", failures); - } - } else if (objectType == "lantern") { - solidIndices.push_back(index); - lanternTranslations.push_back(summary.translation); - Assert(ApproximatelyEqual(summary.scale[0], lanternSize), "lantern scale mismatch", failures); - if (!object.vertices.empty()) { - ExpectColorNear(object.vertices.front(), lanternColor, "lantern vertex color", failures); - } - } else if (objectType == "physics_cube" || objectType == "spinning_cube") { - // Physics cube is tracked separately below - } else { - otherIndices.push_back(index); - } - } - - Assert(ceilingIndices.size() == 1, "expected 1 ceiling object", failures); - Assert(wallIndices.size() == 4, "expected 4 wall objects", failures); - Assert(solidIndices.size() == 8, "expected 8 lantern objects", failures); - Assert(floorIndices.size() == 1, "expected 1 floor object", failures); - Assert(otherIndices.empty(), "unexpected object types in cube demo scene", failures); - - const std::vector> expectedWallTranslations = { - {0.0f, wallCenterY, -wallOffset}, - {0.0f, wallCenterY, wallOffset}, - {-wallOffset, wallCenterY, 0.0f}, - {wallOffset, wallCenterY, 0.0f}, - }; - for (const auto& expected : expectedWallTranslations) { - bool found = false; - for (const auto& actual : wallTranslations) { - if (ApproximatelyEqual(actual[0], expected[0]) - && ApproximatelyEqual(actual[1], expected[1]) - && ApproximatelyEqual(actual[2], expected[2])) { - found = true; - break; - } - } - Assert(found, "missing wall at expected translation", failures); - } - - const std::vector> expectedLanternTranslations = { - {lanternOffset, lanternHeight, lanternOffset}, - {-lanternOffset, lanternHeight, lanternOffset}, - {lanternOffset, lanternHeight, -lanternOffset}, - {-lanternOffset, lanternHeight, -lanternOffset}, - {0.0f, lanternHeight, lanternOffset}, - {0.0f, lanternHeight, -lanternOffset}, - {lanternOffset, lanternHeight, 0.0f}, - {-lanternOffset, lanternHeight, 0.0f}, - }; - for (const auto& expected : expectedLanternTranslations) { - bool found = false; - for (const auto& actual : lanternTranslations) { - if (ApproximatelyEqual(actual[0], expected[0]) - && ApproximatelyEqual(actual[1], expected[1]) - && ApproximatelyEqual(actual[2], expected[2])) { - found = true; - break; - } - } - Assert(found, "missing lantern at expected translation", failures); - } - - // Find floor and physics cube by object type - size_t floorObjectIndex = std::numeric_limits::max(); - size_t cubeObjectIndex = std::numeric_limits::max(); - - for (size_t idx = 0; idx < objects.size(); ++idx) { - if (objects[idx].objectType == "floor") { - floorObjectIndex = idx; - } else if (objects[idx].objectType == "physics_cube" || objects[idx].objectType == "spinning_cube") { - cubeObjectIndex = idx; - } - } - - Assert(floorObjectIndex != std::numeric_limits::max(), "floor object not found", failures); - Assert(cubeObjectIndex != std::numeric_limits::max(), "dynamic cube object not found", failures); - - if (floorObjectIndex != std::numeric_limits::max()) { - auto summary = ExtractMatrixSummary(staticCommands[floorObjectIndex].modelMatrix); - Assert(ApproximatelyEqual(summary.translation[1], floorCenterY), "floor translation mismatch", failures); - // Floor now has scale 1.0 (geometry is pre-sized) - Assert(ApproximatelyEqual(summary.scale[0], 1.0f, 0.1f), "floor scale mismatch", failures); - if (!objects[floorObjectIndex].vertices.empty()) { - ExpectColorNear(objects[floorObjectIndex].vertices.front(), white, "floor vertex color", failures); - } - Assert(!objects[floorObjectIndex].indices.empty(), "floor indices should not be empty", failures); - } - - if (cubeObjectIndex != std::numeric_limits::max()) { - auto summary = ExtractMatrixSummary(dynamicCommands[cubeObjectIndex].modelMatrix); - Assert(ApproximatelyEqual(summary.translation[0], 0.0f, 0.05f), - "physics cube x translation mismatch", failures); - Assert(ApproximatelyEqual(summary.translation[2], 0.0f, 0.05f), - "physics cube z translation mismatch", failures); - Assert(ApproximatelyEqual(summary.translation[1], cubeSpawnY, 0.25f), - "physics cube y translation mismatch", failures); - // Physics cube scale is 1.5, now correctly extracted from rotated matrix - Assert(ApproximatelyEqual(summary.scale[0], 1.5f, 0.1f), "physics cube scale mismatch", failures); - if (!objects[cubeObjectIndex].vertices.empty()) { - ExpectColorNear(objects[cubeObjectIndex].vertices.front(), cubeColor, "physics cube vertex color", failures); - } - Assert(!objects[cubeObjectIndex].indices.empty(), "cube indices should not be empty", failures); - } - - sceneManager->Shutdown(); - engineService->Shutdown(); -} -} // namespace - -// When invoking this test locally, prefer `python scripts/dev_commands.py cmake -- --target script_engine_tests` -// so the helper picks the right build dir/generator. -int main() { - int failures = 0; - auto scriptPath = GetTestScriptPath(); - std::cout << "Loading Lua fixture: " << scriptPath << '\n'; - - try { - auto logger = std::make_shared(); - auto configService = std::make_shared(); - auto engineService = std::make_shared( - scriptPath, - logger, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - configService, - false); - engineService->Initialize(); - - sdl3cpp::services::impl::SceneScriptService sceneService(engineService, logger); - auto shaderSystemRegistry = std::make_shared( - configService, - nullptr, - engineService, - logger); - sdl3cpp::services::impl::ShaderScriptService shaderService( - engineService, - shaderSystemRegistry, - logger); - - auto objects = sceneService.LoadSceneObjects(); - Assert(objects.size() == 1, "expected exactly one scene object", failures); - if (!objects.empty()) { - const auto& object = objects.front(); - Assert(object.vertices.size() == 3, "scene object should yield three vertices", failures); - Assert(object.indices.size() == 3, "scene object should yield three indices", failures); - Assert(object.shaderKeys.size() == 1, "shader keys should contain one entry", failures); - if (!object.shaderKeys.empty()) { - Assert(object.shaderKeys.front() == "test", "shader key should match fixture", failures); - } - const std::vector expectedIndices{0, 1, 2}; - Assert(object.indices == expectedIndices, "indices should be zero-based", failures); - Assert(object.computeModelMatrixRef >= 0, - "vertex object must keep a Lua reference", failures); - - auto objectMatrix = sceneService.ComputeModelMatrix(object.computeModelMatrixRef, 0.5f); - ExpectIdentity(objectMatrix, "object compute_model_matrix", failures); - } - - auto fallbackMatrix = sceneService.ComputeModelMatrix(-1, 1.0f); - ExpectIdentity(fallbackMatrix, "global compute_model_matrix", failures); - - auto viewState = sceneService.GetViewState(1.33f); - ExpectIdentity(viewState.viewProj, "view_projection matrix", failures); - - auto shaderMap = shaderService.LoadShaderPathsMap(); - Assert(shaderMap.size() == 1, "expected a single shader variant", failures); - auto testEntry = shaderMap.find("test"); - Assert(testEntry != shaderMap.end(), "shader map missing test entry", failures); - if (testEntry != shaderMap.end()) { - Assert(!testEntry->second.vertexSource.empty(), "vertex shader source missing", failures); - Assert(!testEntry->second.fragmentSource.empty(), "fragment shader source missing", failures); - } - - RunCubeDemoSceneTests(failures); - } catch (const std::exception& ex) { - std::cerr << "exception during tests: " << ex.what() << '\n'; - return 1; - } - - if (failures == 0) { - std::cout << "script_engine_tests: PASSED\n"; - } else { - std::cerr << "script_engine_tests: FAILED (" << failures << " errors)\n"; - } - - return failures; -}