diff --git a/CMakeLists.txt b/CMakeLists.txt index 927ce1e..fd0aef7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -131,6 +131,10 @@ if(BUILD_SDL3_APP) src/services/impl/pipeline_service.cpp src/services/impl/buffer_service.cpp src/services/impl/render_command_service.cpp + src/services/impl/sdl_audio_service.cpp + src/services/impl/vulkan_gui_service.cpp + src/services/impl/bullet_physics_service.cpp + src/services/impl/lua_script_service.cpp src/app/sdl3_app_core.cpp src/app/audio_player.cpp src/app/sdl3_app_device.cpp diff --git a/src/services/impl/bullet_physics_service.cpp b/src/services/impl/bullet_physics_service.cpp new file mode 100644 index 0000000..afbf0cd --- /dev/null +++ b/src/services/impl/bullet_physics_service.cpp @@ -0,0 +1,142 @@ +#include "bullet_physics_service.hpp" +#include "../../logging/logger.hpp" +#include + +namespace sdl3cpp::services::impl { + +BulletPhysicsService::~BulletPhysicsService() { + if (initialized_) { + Shutdown(); + } +} + +void BulletPhysicsService::Initialize(const btVector3& gravity) { + logging::TraceGuard trace; + + if (initialized_) { + return; + } + + physicsBridge_ = std::make_unique(); + initialized_ = true; + + logging::Logger::GetInstance().Info("Physics service initialized"); +} + +void BulletPhysicsService::Shutdown() noexcept { + logging::TraceGuard trace; + + if (!initialized_) { + return; + } + + physicsBridge_.reset(); + initialized_ = false; + + logging::Logger::GetInstance().Info("Physics service shutdown"); +} + +bool BulletPhysicsService::AddBoxRigidBody(const std::string& name, + const btVector3& halfExtents, + float mass, + const btTransform& transform) { + logging::TraceGuard trace; + + if (!physicsBridge_) { + throw std::runtime_error("Physics service not initialized"); + } + + std::string error; + return physicsBridge_->addBoxRigidBody(name, halfExtents, mass, transform, error); +} + +bool BulletPhysicsService::AddSphereRigidBody(const std::string& name, + float radius, + float mass, + const btTransform& transform) { + logging::TraceGuard trace; + + // PhysicsBridge doesn't support sphere rigid bodies in current implementation + logging::Logger::GetInstance().Warn("AddSphereRigidBody not supported by PhysicsBridge"); + return false; +} + +bool BulletPhysicsService::RemoveRigidBody(const std::string& name) { + logging::TraceGuard trace; + + // PhysicsBridge doesn't support removing bodies in current implementation + logging::Logger::GetInstance().Warn("RemoveRigidBody not supported by PhysicsBridge"); + return false; +} + +void BulletPhysicsService::StepSimulation(float deltaTime, int maxSubSteps) { + logging::TraceGuard trace; + + if (!physicsBridge_) { + throw std::runtime_error("Physics service not initialized"); + } + + physicsBridge_->stepSimulation(deltaTime); +} + +bool BulletPhysicsService::GetTransform(const std::string& name, btTransform& outTransform) const { + if (!physicsBridge_) { + return false; + } + + std::string error; + return physicsBridge_->getRigidBodyTransform(name, outTransform, error); +} + +bool BulletPhysicsService::SetTransform(const std::string& name, const btTransform& transform) { + logging::TraceGuard trace; + + // PhysicsBridge doesn't support setting transforms in current implementation + logging::Logger::GetInstance().Warn("SetTransform not supported by PhysicsBridge"); + return false; +} + +bool BulletPhysicsService::ApplyForce(const std::string& name, const btVector3& force) { + logging::TraceGuard trace; + + // PhysicsBridge doesn't support applying forces in current implementation + logging::Logger::GetInstance().Warn("ApplyForce not supported by PhysicsBridge"); + return false; +} + +bool BulletPhysicsService::ApplyImpulse(const std::string& name, const btVector3& impulse) { + logging::TraceGuard trace; + + // PhysicsBridge doesn't support applying impulses in current implementation + logging::Logger::GetInstance().Warn("ApplyImpulse not supported by PhysicsBridge"); + return false; +} + +bool BulletPhysicsService::SetLinearVelocity(const std::string& name, const btVector3& velocity) { + logging::TraceGuard trace; + + // PhysicsBridge doesn't support setting velocity in current implementation + logging::Logger::GetInstance().Warn("SetLinearVelocity not supported by PhysicsBridge"); + return false; +} + +size_t BulletPhysicsService::GetBodyCount() const { + // PhysicsBridge doesn't expose GetBodyCount in current implementation + // Returning 0 as stub - could track bodies in wrapper if needed + return 0; +} + +void BulletPhysicsService::Clear() { + logging::TraceGuard trace; + + if (!physicsBridge_) { + return; + } + + // PhysicsBridge doesn't expose Clear in current implementation + // Shutdown and reinitialize to clear all bodies + physicsBridge_.reset(); + physicsBridge_ = std::make_unique(); +} + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/bullet_physics_service.hpp b/src/services/impl/bullet_physics_service.hpp new file mode 100644 index 0000000..faea337 --- /dev/null +++ b/src/services/impl/bullet_physics_service.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include "../interfaces/i_physics_service.hpp" +#include "../../script/physics_bridge.hpp" +#include "../../di/lifecycle.hpp" +#include + +namespace sdl3cpp::services::impl { + +/** + * @brief Bullet physics service implementation. + * + * Small wrapper service (~120 lines) around PhysicsBridge. + * Provides rigid body physics simulation using Bullet Physics. + */ +class BulletPhysicsService : public IPhysicsService, + public di::IInitializable, + public di::IShutdownable { +public: + BulletPhysicsService() = default; + ~BulletPhysicsService() override; + + // IPhysicsService interface + void Initialize(const btVector3& gravity = btVector3(0, -9.8f, 0)) override; + void Shutdown() noexcept override; + + bool AddBoxRigidBody(const std::string& name, + const btVector3& halfExtents, + float mass, + const btTransform& transform) override; + + bool AddSphereRigidBody(const std::string& name, + float radius, + float mass, + const btTransform& transform) override; + + bool RemoveRigidBody(const std::string& name) override; + + void StepSimulation(float deltaTime, int maxSubSteps = 10) override; + + bool GetTransform(const std::string& name, btTransform& outTransform) const override; + bool SetTransform(const std::string& name, const btTransform& transform) override; + + bool ApplyForce(const std::string& name, const btVector3& force) override; + bool ApplyImpulse(const std::string& name, const btVector3& impulse) override; + bool SetLinearVelocity(const std::string& name, const btVector3& velocity) override; + + size_t GetBodyCount() const override; + void Clear() override; + +private: + std::unique_ptr physicsBridge_; + bool initialized_ = false; +}; + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/lua_script_service.cpp b/src/services/impl/lua_script_service.cpp new file mode 100644 index 0000000..5697d04 --- /dev/null +++ b/src/services/impl/lua_script_service.cpp @@ -0,0 +1,135 @@ +#include "lua_script_service.hpp" +#include "../../logging/logger.hpp" +#include + +namespace sdl3cpp::services::impl { + +LuaScriptService::LuaScriptService(const std::filesystem::path& scriptPath, bool debugEnabled) + : scriptPath_(scriptPath), debugEnabled_(debugEnabled) { +} + +LuaScriptService::~LuaScriptService() { + if (initialized_) { + Shutdown(); + } +} + +void LuaScriptService::Initialize() { + logging::TraceGuard trace; + + if (initialized_) { + return; + } + + engine_ = std::make_unique(scriptPath_, debugEnabled_); + initialized_ = true; + + logging::Logger::GetInstance().Info("Script service initialized"); +} + +void LuaScriptService::Shutdown() noexcept { + logging::TraceGuard trace; + + if (!initialized_) { + return; + } + + engine_.reset(); + initialized_ = false; + + logging::Logger::GetInstance().Info("Script service shutdown"); +} + +std::vector LuaScriptService::LoadSceneObjects() { + logging::TraceGuard trace; + + if (!engine_) { + throw std::runtime_error("Script service not initialized"); + } + + return engine_->LoadSceneObjects(); +} + +std::array LuaScriptService::ComputeModelMatrix(int functionRef, float time) { + if (!engine_) { + throw std::runtime_error("Script service not initialized"); + } + + return engine_->ComputeModelMatrix(functionRef, time); +} + +std::array LuaScriptService::GetViewProjectionMatrix(float aspect) { + if (!engine_) { + throw std::runtime_error("Script service not initialized"); + } + + return engine_->GetViewProjectionMatrix(aspect); +} + +std::unordered_map LuaScriptService::LoadShaderPathsMap() { + logging::TraceGuard trace; + + if (!engine_) { + throw std::runtime_error("Script service not initialized"); + } + + return engine_->LoadShaderPathsMap(); +} + +std::vector LuaScriptService::LoadGuiCommands() { + if (!engine_) { + return {}; + } + + return engine_->LoadGuiCommands(); +} + +void LuaScriptService::UpdateGuiInput(const script::GuiInputSnapshot& input) { + if (!engine_) { + return; + } + + engine_->UpdateGuiInput(input); +} + +bool LuaScriptService::HasGuiCommands() const { + if (!engine_) { + return false; + } + + return engine_->HasGuiCommands(); +} + +script::PhysicsBridge& LuaScriptService::GetPhysicsBridge() { + if (!engine_) { + throw std::runtime_error("Script service not initialized"); + } + + return engine_->GetPhysicsBridge(); +} + +void LuaScriptService::SetAudioPlayer(app::AudioPlayer* audioPlayer) { + if (!engine_) { + return; + } + + engine_->SetAudioPlayer(audioPlayer); +} + +std::filesystem::path LuaScriptService::GetScriptDirectory() const { + if (!engine_) { + return {}; + } + + return engine_->GetScriptDirectory(); +} + +std::string LuaScriptService::GetLuaError() { + if (!engine_) { + return "Script service not initialized"; + } + + return engine_->GetLuaError(); +} + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/lua_script_service.hpp b/src/services/impl/lua_script_service.hpp new file mode 100644 index 0000000..20997ea --- /dev/null +++ b/src/services/impl/lua_script_service.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include "../interfaces/i_script_service.hpp" +#include "../../script/script_engine.hpp" +#include "../../di/lifecycle.hpp" +#include +#include + +namespace sdl3cpp::services::impl { + +/** + * @brief Lua script service implementation. + * + * Small wrapper service (~100 lines) around ScriptEngine. + * Provides Lua script execution and integration with game systems. + */ +class LuaScriptService : public IScriptService, + public di::IInitializable, + public di::IShutdownable { +public: + explicit LuaScriptService(const std::filesystem::path& scriptPath, bool debugEnabled = false); + ~LuaScriptService() override; + + // Lifecycle + void Initialize() override; + void Shutdown() noexcept override; + + // IScriptService interface + std::vector LoadSceneObjects() override; + std::array ComputeModelMatrix(int functionRef, float time) override; + std::array GetViewProjectionMatrix(float aspect) override; + + std::unordered_map LoadShaderPathsMap() override; + + std::vector LoadGuiCommands() override; + void UpdateGuiInput(const script::GuiInputSnapshot& input) override; + bool HasGuiCommands() const override; + + script::PhysicsBridge& GetPhysicsBridge() override; + + void SetAudioPlayer(app::AudioPlayer* audioPlayer) override; + + std::filesystem::path GetScriptDirectory() const override; + std::string GetLuaError() override; + +private: + std::unique_ptr engine_; + std::filesystem::path scriptPath_; + bool debugEnabled_ = false; + bool initialized_ = false; +}; + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/sdl_audio_service.cpp b/src/services/impl/sdl_audio_service.cpp new file mode 100644 index 0000000..f6d5906 --- /dev/null +++ b/src/services/impl/sdl_audio_service.cpp @@ -0,0 +1,102 @@ +#include "sdl_audio_service.hpp" +#include "../../logging/logger.hpp" +#include + +namespace sdl3cpp::services::impl { + +SdlAudioService::SdlAudioService() = default; + +SdlAudioService::~SdlAudioService() { + if (initialized_) { + Shutdown(); + } +} + +void SdlAudioService::Initialize() { + logging::TraceGuard trace; + + if (initialized_) { + return; + } + + audioPlayer_ = std::make_unique(); + initialized_ = true; + + logging::Logger::GetInstance().Info("Audio service initialized"); +} + +void SdlAudioService::Shutdown() noexcept { + logging::TraceGuard trace; + + if (!initialized_) { + return; + } + + audioPlayer_.reset(); + initialized_ = false; + + logging::Logger::GetInstance().Info("Audio service shutdown"); +} + +void SdlAudioService::PlayBackground(const std::filesystem::path& path, bool loop) { + logging::TraceGuard trace; + + if (!audioPlayer_) { + throw std::runtime_error("Audio service not initialized"); + } + + audioPlayer_->PlayBackground(path, loop); +} + +void SdlAudioService::PlayEffect(const std::filesystem::path& path, bool loop) { + logging::TraceGuard trace; + + if (!audioPlayer_) { + throw std::runtime_error("Audio service not initialized"); + } + + audioPlayer_->PlayEffect(path, loop); +} + +void SdlAudioService::StopBackground() { + logging::TraceGuard trace; + + if (!audioPlayer_) { + return; + } + + // AudioPlayer doesn't have StopBackground(), so recreate to stop + auto oldPlayer = std::move(audioPlayer_); + oldPlayer.reset(); + audioPlayer_ = std::make_unique(); +} + +void SdlAudioService::StopAll() { + logging::TraceGuard trace; + + if (!audioPlayer_) { + return; + } + + // Recreate player to stop all audio + audioPlayer_.reset(); + audioPlayer_ = std::make_unique(); +} + +void SdlAudioService::SetVolume(float volume) { + volume_ = std::clamp(volume, 0.0f, 1.0f); + // Note: AudioPlayer doesn't expose volume control, + // this would need to be added to AudioPlayer implementation +} + +float SdlAudioService::GetVolume() const { + return volume_; +} + +bool SdlAudioService::IsBackgroundPlaying() const { + // AudioPlayer doesn't expose this state, + // would need to be added to AudioPlayer implementation + return false; +} + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/sdl_audio_service.hpp b/src/services/impl/sdl_audio_service.hpp new file mode 100644 index 0000000..50aec6c --- /dev/null +++ b/src/services/impl/sdl_audio_service.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "../interfaces/i_audio_service.hpp" +#include "../../app/audio_player.hpp" +#include "../../di/lifecycle.hpp" +#include + +namespace sdl3cpp::services::impl { + +/** + * @brief SDL audio service implementation. + * + * Small wrapper service (~80 lines) around AudioPlayer. + * Provides thread-safe audio playback for background music and sound effects. + */ +class SdlAudioService : public IAudioService, + public di::IInitializable, + public di::IShutdownable { +public: + SdlAudioService(); + ~SdlAudioService() override; + + // IAudioService interface + void Initialize() override; + void Shutdown() noexcept override; + + void PlayBackground(const std::filesystem::path& path, bool loop = true) override; + void PlayEffect(const std::filesystem::path& path, bool loop = false) override; + void StopBackground() override; + void StopAll() override; + + void SetVolume(float volume) override; + float GetVolume() const override; + bool IsBackgroundPlaying() const override; + +private: + std::unique_ptr audioPlayer_; + float volume_ = 1.0f; + bool initialized_ = false; +}; + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/vulkan_gui_service.cpp b/src/services/impl/vulkan_gui_service.cpp new file mode 100644 index 0000000..d649c3b --- /dev/null +++ b/src/services/impl/vulkan_gui_service.cpp @@ -0,0 +1,75 @@ +#include "vulkan_gui_service.hpp" +#include "../../logging/logger.hpp" +#include + +namespace sdl3cpp::services::impl { + +VulkanGuiService::~VulkanGuiService() { + if (initialized_) { + Shutdown(); + } +} + +void VulkanGuiService::Initialize(VkDevice device, + VkPhysicalDevice physicalDevice, + VkFormat format, + const std::filesystem::path& resourcePath) { + logging::TraceGuard trace; + + if (initialized_) { + return; + } + + renderer_ = std::make_unique(device, physicalDevice, format, resourcePath); + initialized_ = true; + + logging::Logger::GetInstance().Info("GUI service initialized"); +} + +void VulkanGuiService::PrepareFrame(const std::vector& commands, + uint32_t width, + uint32_t height) { + logging::TraceGuard trace; + + if (!renderer_) { + throw std::runtime_error("GUI service not initialized"); + } + + // GuiRenderer doesn't have a PrepareFrame method in the current implementation + // Commands would be processed during RenderToSwapchain +} + +void VulkanGuiService::RenderToSwapchain(VkCommandBuffer commandBuffer, VkImage image) { + logging::TraceGuard trace; + + if (!renderer_) { + throw std::runtime_error("GUI service not initialized"); + } + + renderer_->BlitToSwapchain(commandBuffer, image); +} + +void VulkanGuiService::Resize(uint32_t width, uint32_t height, VkFormat format) { + logging::TraceGuard trace; + + if (!renderer_) { + return; + } + + renderer_->Resize(width, height, format); +} + +void VulkanGuiService::Shutdown() noexcept { + logging::TraceGuard trace; + + if (!initialized_) { + return; + } + + renderer_.reset(); + initialized_ = false; + + logging::Logger::GetInstance().Info("GUI service shutdown"); +} + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/vulkan_gui_service.hpp b/src/services/impl/vulkan_gui_service.hpp new file mode 100644 index 0000000..36b3b0c --- /dev/null +++ b/src/services/impl/vulkan_gui_service.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "../interfaces/i_gui_service.hpp" +#include "../../gui/gui_renderer.hpp" +#include "../../di/lifecycle.hpp" +#include + +namespace sdl3cpp::services::impl { + +/** + * @brief Vulkan GUI service implementation. + * + * Small wrapper service (~60 lines) around GuiRenderer. + * Provides 2D GUI overlay rendering for SVG, text, and shapes. + */ +class VulkanGuiService : public IGuiService, + public di::IShutdownable { +public: + VulkanGuiService() = default; + ~VulkanGuiService() override; + + // IGuiService interface + void Initialize(VkDevice device, + VkPhysicalDevice physicalDevice, + VkFormat format, + const std::filesystem::path& resourcePath) override; + + void PrepareFrame(const std::vector& commands, + uint32_t width, + uint32_t height) override; + + void RenderToSwapchain(VkCommandBuffer commandBuffer, VkImage image) override; + + void Resize(uint32_t width, uint32_t height, VkFormat format) override; + + void Shutdown() noexcept override; + +private: + std::unique_ptr renderer_; + bool initialized_ = false; +}; + +} // namespace sdl3cpp::services::impl diff --git a/src/services/interfaces/i_script_service.hpp b/src/services/interfaces/i_script_service.hpp index df036e0..592bdd5 100644 --- a/src/services/interfaces/i_script_service.hpp +++ b/src/services/interfaces/i_script_service.hpp @@ -1,126 +1,52 @@ #pragma once -#include "../../core/vertex.hpp" -#include "i_graphics_service.hpp" -#include -#include +#include "../../script/scene_manager.hpp" +#include "../../script/shader_manager.hpp" +#include "../../script/gui_types.hpp" +#include "../../script/physics_bridge.hpp" #include -#include #include #include +#include +#include + +namespace sdl3cpp::app { +class AudioPlayer; +} namespace sdl3cpp::services { /** - * @brief Scene object data structure. - */ -struct SceneObject { - std::string id; - std::vector vertices; - std::vector indices; - std::string shaderKey; - int modelMatrixFuncRef; // Lua function reference -}; - -/** - * @brief GUI input snapshot for Lua. - */ -struct GuiInputSnapshot { - float mouseX; - float mouseY; - float mouseWheelDelta; - bool mouseLeftPressed; - bool mouseRightPressed; - std::string textInput; -}; - -// Forward declare GUI types (defined in script/gui_types.hpp) -struct GuiCommand; - -/** - * @brief Script service interface (Lua integration). + * @brief Script service interface. * - * Provides a clean API for Lua script execution and data exchange. - * Wraps the ScriptEngine and its internal managers (SceneManager, - * ShaderManager, GuiManager, AudioManager). + * Provides Lua script execution and integration with scene, shaders, GUI, physics, and audio. */ class IScriptService { public: virtual ~IScriptService() = default; - /** - * @brief Load and execute a Lua script. - * - * @param path Path to the Lua script file - * @param debug Whether to enable Lua debug mode - * @throws std::runtime_error if script loading fails - */ - virtual void LoadScript(const std::filesystem::path& path, bool debug) = 0; - - /** - * @brief Load scene objects from Lua. - * - * Calls the Lua function that generates the scene graph. - * - * @return Vector of scene objects with geometry and metadata - */ - virtual std::vector LoadSceneObjects() = 0; - - /** - * @brief Compute model matrix for an object using Lua function. - * - * @param funcRef Lua function reference (from SceneObject) - * @param time Current time in seconds - * @return 4x4 model transformation matrix (column-major) - */ - virtual std::array ComputeModelMatrix(int funcRef, float time) = 0; - - /** - * @brief Get view-projection matrix from Lua. - * - * @param aspect Window aspect ratio (width/height) - * @return 4x4 view-projection matrix (column-major) - */ + // Scene management + virtual std::vector LoadSceneObjects() = 0; + virtual std::array ComputeModelMatrix(int functionRef, float time) = 0; virtual std::array GetViewProjectionMatrix(float aspect) = 0; - /** - * @brief Load shader paths from Lua configuration. - * - * @return Map of shader key to shader file paths - */ - virtual std::unordered_map LoadShaderPaths() = 0; + // Shader management + virtual std::unordered_map LoadShaderPathsMap() = 0; - /** - * @brief Update GUI input state in Lua. - * - * Sends current input state to Lua for GUI processing. - * - * @param input Input snapshot for this frame - */ - virtual void UpdateGuiInput(const GuiInputSnapshot& input) = 0; - - /** - * @brief Get GUI rendering commands from Lua. - * - * Retrieves the list of GUI commands generated by Lua scripts. - * - * @return Vector of GUI commands to render - */ - virtual std::vector GetGuiCommands() = 0; - - /** - * @brief Check if there are pending GUI commands. - * - * @return true if GUI commands are available, false otherwise - */ + // GUI management + virtual std::vector LoadGuiCommands() = 0; + virtual void UpdateGuiInput(const script::GuiInputSnapshot& input) = 0; virtual bool HasGuiCommands() const = 0; - /** - * @brief Update physics simulation (calls Lua if needed). - * - * @param deltaTime Time since last update in seconds - */ - virtual void UpdatePhysics(float deltaTime) = 0; + // Physics bridge access + virtual script::PhysicsBridge& GetPhysicsBridge() = 0; + + // Audio integration + virtual void SetAudioPlayer(app::AudioPlayer* audioPlayer) = 0; + + // Utility + virtual std::filesystem::path GetScriptDirectory() const = 0; + virtual std::string GetLuaError() = 0; }; } // namespace sdl3cpp::services