feat: Add services for audio, physics, and GUI rendering integration

This commit is contained in:
2026-01-04 13:35:31 +00:00
parent dcba88a76b
commit a720e4a446
10 changed files with 681 additions and 103 deletions

View File

@@ -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

View File

@@ -0,0 +1,142 @@
#include "bullet_physics_service.hpp"
#include "../../logging/logger.hpp"
#include <stdexcept>
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<script::PhysicsBridge>();
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<script::PhysicsBridge>();
}
} // namespace sdl3cpp::services::impl

View File

@@ -0,0 +1,56 @@
#pragma once
#include "../interfaces/i_physics_service.hpp"
#include "../../script/physics_bridge.hpp"
#include "../../di/lifecycle.hpp"
#include <memory>
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<script::PhysicsBridge> physicsBridge_;
bool initialized_ = false;
};
} // namespace sdl3cpp::services::impl

View File

@@ -0,0 +1,135 @@
#include "lua_script_service.hpp"
#include "../../logging/logger.hpp"
#include <stdexcept>
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<script::ScriptEngine>(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<script::SceneManager::SceneObject> LuaScriptService::LoadSceneObjects() {
logging::TraceGuard trace;
if (!engine_) {
throw std::runtime_error("Script service not initialized");
}
return engine_->LoadSceneObjects();
}
std::array<float, 16> LuaScriptService::ComputeModelMatrix(int functionRef, float time) {
if (!engine_) {
throw std::runtime_error("Script service not initialized");
}
return engine_->ComputeModelMatrix(functionRef, time);
}
std::array<float, 16> LuaScriptService::GetViewProjectionMatrix(float aspect) {
if (!engine_) {
throw std::runtime_error("Script service not initialized");
}
return engine_->GetViewProjectionMatrix(aspect);
}
std::unordered_map<std::string, script::ShaderManager::ShaderPaths> LuaScriptService::LoadShaderPathsMap() {
logging::TraceGuard trace;
if (!engine_) {
throw std::runtime_error("Script service not initialized");
}
return engine_->LoadShaderPathsMap();
}
std::vector<script::GuiCommand> 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

View File

@@ -0,0 +1,53 @@
#pragma once
#include "../interfaces/i_script_service.hpp"
#include "../../script/script_engine.hpp"
#include "../../di/lifecycle.hpp"
#include <memory>
#include <filesystem>
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<script::SceneManager::SceneObject> LoadSceneObjects() override;
std::array<float, 16> ComputeModelMatrix(int functionRef, float time) override;
std::array<float, 16> GetViewProjectionMatrix(float aspect) override;
std::unordered_map<std::string, script::ShaderManager::ShaderPaths> LoadShaderPathsMap() override;
std::vector<script::GuiCommand> 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<script::ScriptEngine> engine_;
std::filesystem::path scriptPath_;
bool debugEnabled_ = false;
bool initialized_ = false;
};
} // namespace sdl3cpp::services::impl

View File

@@ -0,0 +1,102 @@
#include "sdl_audio_service.hpp"
#include "../../logging/logger.hpp"
#include <stdexcept>
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<app::AudioPlayer>();
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<app::AudioPlayer>();
}
void SdlAudioService::StopAll() {
logging::TraceGuard trace;
if (!audioPlayer_) {
return;
}
// Recreate player to stop all audio
audioPlayer_.reset();
audioPlayer_ = std::make_unique<app::AudioPlayer>();
}
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

View File

@@ -0,0 +1,42 @@
#pragma once
#include "../interfaces/i_audio_service.hpp"
#include "../../app/audio_player.hpp"
#include "../../di/lifecycle.hpp"
#include <memory>
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<app::AudioPlayer> audioPlayer_;
float volume_ = 1.0f;
bool initialized_ = false;
};
} // namespace sdl3cpp::services::impl

View File

@@ -0,0 +1,75 @@
#include "vulkan_gui_service.hpp"
#include "../../logging/logger.hpp"
#include <stdexcept>
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<gui::GuiRenderer>(device, physicalDevice, format, resourcePath);
initialized_ = true;
logging::Logger::GetInstance().Info("GUI service initialized");
}
void VulkanGuiService::PrepareFrame(const std::vector<GuiCommand>& 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

View File

@@ -0,0 +1,43 @@
#pragma once
#include "../interfaces/i_gui_service.hpp"
#include "../../gui/gui_renderer.hpp"
#include "../../di/lifecycle.hpp"
#include <memory>
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<GuiCommand>& 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<gui::GuiRenderer> renderer_;
bool initialized_ = false;
};
} // namespace sdl3cpp::services::impl

View File

@@ -1,126 +1,52 @@
#pragma once
#include "../../core/vertex.hpp"
#include "i_graphics_service.hpp"
#include <array>
#include <cstdint>
#include "../../script/scene_manager.hpp"
#include "../../script/shader_manager.hpp"
#include "../../script/gui_types.hpp"
#include "../../script/physics_bridge.hpp"
#include <filesystem>
#include <string>
#include <unordered_map>
#include <vector>
#include <array>
#include <string>
namespace sdl3cpp::app {
class AudioPlayer;
}
namespace sdl3cpp::services {
/**
* @brief Scene object data structure.
*/
struct SceneObject {
std::string id;
std::vector<core::Vertex> vertices;
std::vector<uint16_t> 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<SceneObject> 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<float, 16> 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<script::SceneManager::SceneObject> LoadSceneObjects() = 0;
virtual std::array<float, 16> ComputeModelMatrix(int functionRef, float time) = 0;
virtual std::array<float, 16> GetViewProjectionMatrix(float aspect) = 0;
/**
* @brief Load shader paths from Lua configuration.
*
* @return Map of shader key to shader file paths
*/
virtual std::unordered_map<std::string, ShaderPaths> LoadShaderPaths() = 0;
// Shader management
virtual std::unordered_map<std::string, script::ShaderManager::ShaderPaths> 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<GuiCommand> GetGuiCommands() = 0;
/**
* @brief Check if there are pending GUI commands.
*
* @return true if GUI commands are available, false otherwise
*/
// GUI management
virtual std::vector<script::GuiCommand> 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