From d43397163e3c0a0b4e5f48e8ceda14ad366f45b7 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 1 Apr 2026 20:38:37 +0300 Subject: [PATCH] `refactor: decouple Application singleton by extracting core subsystems and updating interfaces` - Add `audio::AudioCoordinator` interface and implementation - Modify `Application` to reduce singleton usage and move controller responsibilities: - application.hpp - application.cpp - Update UI and audio headers/sources: - game_screen.hpp - game_screen.cpp - ui_manager.hpp - audio_coordinator.hpp - audio_coordinator.cpp - Project config touched: - CMakeLists.txt --- CMakeLists.txt | 1 + include/audio/audio_coordinator.hpp | 66 ++++++++++++++++++++++ include/core/application.hpp | 6 +- include/ui/game_screen.hpp | 6 ++ include/ui/ui_manager.hpp | 7 ++- src/audio/audio_coordinator.cpp | 87 +++++++++++++++++++++++++++++ src/core/application.cpp | 6 ++ src/ui/game_screen.cpp | 3 +- 8 files changed, 179 insertions(+), 3 deletions(-) create mode 100644 include/audio/audio_coordinator.hpp create mode 100644 src/audio/audio_coordinator.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 83fccadd..dbb31af1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -544,6 +544,7 @@ set(WOWEE_SOURCES # Audio src/audio/audio_engine.cpp + src/audio/audio_coordinator.cpp src/audio/music_manager.cpp src/audio/footstep_manager.cpp src/audio/activity_sound_manager.cpp diff --git a/include/audio/audio_coordinator.hpp b/include/audio/audio_coordinator.hpp new file mode 100644 index 00000000..f181164f --- /dev/null +++ b/include/audio/audio_coordinator.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include + +namespace wowee { +namespace pipeline { class AssetManager; } +namespace audio { + +class MusicManager; +class FootstepManager; +class ActivitySoundManager; +class MountSoundManager; +class NpcVoiceManager; +class AmbientSoundManager; +class UiSoundManager; +class CombatSoundManager; +class SpellSoundManager; +class MovementSoundManager; + +/// Coordinates all audio subsystems. +/// Extracted from Renderer to separate audio lifecycle from rendering. +/// Owned by Application; Renderer and UI components access through Application. +class AudioCoordinator { +public: + AudioCoordinator(); + ~AudioCoordinator(); + + /// Initialize the audio engine and all managers. + /// @return true if audio is available (engine initialized successfully) + bool initialize(); + + /// Initialize managers that need AssetManager (music lookups, sound banks). + void initializeWithAssets(pipeline::AssetManager* assetManager); + + /// Shutdown all audio managers and engine. + void shutdown(); + + // Accessors for all audio managers (same interface as Renderer had) + MusicManager* getMusicManager() { return musicManager_.get(); } + FootstepManager* getFootstepManager() { return footstepManager_.get(); } + ActivitySoundManager* getActivitySoundManager() { return activitySoundManager_.get(); } + MountSoundManager* getMountSoundManager() { return mountSoundManager_.get(); } + NpcVoiceManager* getNpcVoiceManager() { return npcVoiceManager_.get(); } + AmbientSoundManager* getAmbientSoundManager() { return ambientSoundManager_.get(); } + UiSoundManager* getUiSoundManager() { return uiSoundManager_.get(); } + CombatSoundManager* getCombatSoundManager() { return combatSoundManager_.get(); } + SpellSoundManager* getSpellSoundManager() { return spellSoundManager_.get(); } + MovementSoundManager* getMovementSoundManager() { return movementSoundManager_.get(); } + +private: + std::unique_ptr musicManager_; + std::unique_ptr footstepManager_; + std::unique_ptr activitySoundManager_; + std::unique_ptr mountSoundManager_; + std::unique_ptr npcVoiceManager_; + std::unique_ptr ambientSoundManager_; + std::unique_ptr uiSoundManager_; + std::unique_ptr combatSoundManager_; + std::unique_ptr spellSoundManager_; + std::unique_ptr movementSoundManager_; + + bool audioAvailable_ = false; +}; + +} // namespace audio +} // namespace wowee diff --git a/include/core/application.hpp b/include/core/application.hpp index 2f47e489..148131ac 100644 --- a/include/core/application.hpp +++ b/include/core/application.hpp @@ -29,7 +29,7 @@ namespace ui { class UIManager; } namespace auth { class AuthHandler; } namespace game { class GameHandler; class World; class ExpansionRegistry; } namespace pipeline { class AssetManager; class DBCLayout; struct M2Model; struct WMOModel; } -namespace audio { enum class VoiceType; } +namespace audio { enum class VoiceType; class AudioCoordinator; } namespace addons { class AddonManager; } namespace core { @@ -104,6 +104,9 @@ public: // World loader access WorldLoader* getWorldLoader() { return worldLoader_.get(); } + // Audio coordinator access (Section 4.1: extracted audio subsystem) + audio::AudioCoordinator* getAudioCoordinator() { return audioCoordinator_.get(); } + private: void update(float deltaTime); void render(); @@ -129,6 +132,7 @@ private: std::unique_ptr entitySpawner_; std::unique_ptr appearanceComposer_; std::unique_ptr worldLoader_; + std::unique_ptr audioCoordinator_; AppState state = AppState::AUTHENTICATION; bool running = false; diff --git a/include/ui/game_screen.hpp b/include/ui/game_screen.hpp index 0c29e66f..530d9778 100644 --- a/include/ui/game_screen.hpp +++ b/include/ui/game_screen.hpp @@ -23,6 +23,7 @@ #include namespace wowee { +namespace core { class AppearanceComposer; } namespace pipeline { class AssetManager; } namespace rendering { class Renderer; } namespace ui { @@ -50,7 +51,12 @@ public: void saveSettings(); void loadSettings(); + // Dependency injection for extracted classes (Phase A singleton breaking) + void setAppearanceComposer(core::AppearanceComposer* ac) { appearanceComposer_ = ac; } + private: + // Injected dependencies (replaces getInstance() calls) + core::AppearanceComposer* appearanceComposer_ = nullptr; // Chat panel (extracted from GameScreen — owns all chat state and rendering) ChatPanel chatPanel_; diff --git a/include/ui/ui_manager.hpp b/include/ui/ui_manager.hpp index ba1e4c09..f34228d7 100644 --- a/include/ui/ui_manager.hpp +++ b/include/ui/ui_manager.hpp @@ -13,7 +13,7 @@ union SDL_Event; namespace wowee { // Forward declarations -namespace core { class Window; enum class AppState; } +namespace core { class Window; class AppearanceComposer; enum class AppState; } namespace auth { class AuthHandler; } namespace game { class GameHandler; } @@ -69,6 +69,11 @@ public: CharacterScreen& getCharacterScreen() { return *characterScreen; } GameScreen& getGameScreen() { return *gameScreen; } + // Dependency injection forwarding (Phase A singleton breaking) + void setAppearanceComposer(core::AppearanceComposer* ac) { + if (gameScreen) gameScreen->setAppearanceComposer(ac); + } + private: core::Window* window = nullptr; diff --git a/src/audio/audio_coordinator.cpp b/src/audio/audio_coordinator.cpp new file mode 100644 index 00000000..346dd3fe --- /dev/null +++ b/src/audio/audio_coordinator.cpp @@ -0,0 +1,87 @@ +#include "audio/audio_coordinator.hpp" +#include "audio/audio_engine.hpp" +#include "audio/music_manager.hpp" +#include "audio/footstep_manager.hpp" +#include "audio/activity_sound_manager.hpp" +#include "audio/mount_sound_manager.hpp" +#include "audio/npc_voice_manager.hpp" +#include "audio/ambient_sound_manager.hpp" +#include "audio/ui_sound_manager.hpp" +#include "audio/combat_sound_manager.hpp" +#include "audio/spell_sound_manager.hpp" +#include "audio/movement_sound_manager.hpp" +#include "pipeline/asset_manager.hpp" +#include "core/logger.hpp" + +namespace wowee { +namespace audio { + +AudioCoordinator::AudioCoordinator() = default; + +AudioCoordinator::~AudioCoordinator() { + shutdown(); +} + +bool AudioCoordinator::initialize() { + // Initialize AudioEngine (singleton) + if (!AudioEngine::instance().initialize()) { + LOG_WARNING("Failed to initialize AudioEngine - audio will be disabled"); + audioAvailable_ = false; + return false; + } + audioAvailable_ = true; + + // Create all audio managers (initialized later with asset manager) + musicManager_ = std::make_unique(); + footstepManager_ = std::make_unique(); + activitySoundManager_ = std::make_unique(); + mountSoundManager_ = std::make_unique(); + npcVoiceManager_ = std::make_unique(); + ambientSoundManager_ = std::make_unique(); + uiSoundManager_ = std::make_unique(); + combatSoundManager_ = std::make_unique(); + spellSoundManager_ = std::make_unique(); + movementSoundManager_ = std::make_unique(); + + LOG_INFO("AudioCoordinator initialized with ", 10, " audio managers"); + return true; +} + +void AudioCoordinator::initializeWithAssets(pipeline::AssetManager* assetManager) { + if (!audioAvailable_ || !assetManager) return; + + // MusicManager needs asset manager for zone music lookups + if (musicManager_) { + musicManager_->initialize(assetManager); + } + + // Other managers may need asset manager for sound bank loading + // (Add similar calls as needed for other managers) + + LOG_INFO("AudioCoordinator initialized with asset manager"); +} + +void AudioCoordinator::shutdown() { + // Reset all managers first (they may reference AudioEngine) + movementSoundManager_.reset(); + spellSoundManager_.reset(); + combatSoundManager_.reset(); + uiSoundManager_.reset(); + ambientSoundManager_.reset(); + npcVoiceManager_.reset(); + mountSoundManager_.reset(); + activitySoundManager_.reset(); + footstepManager_.reset(); + musicManager_.reset(); + + // Shutdown audio engine last + if (audioAvailable_) { + AudioEngine::instance().shutdown(); + audioAvailable_ = false; + } + + LOG_INFO("AudioCoordinator shutdown complete"); +} + +} // namespace audio +} // namespace wowee diff --git a/src/core/application.cpp b/src/core/application.cpp index a91f8223..91a0aaff 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -31,6 +31,7 @@ #include "audio/footstep_manager.hpp" #include "audio/activity_sound_manager.hpp" #include "audio/audio_engine.hpp" +#include "audio/audio_coordinator.hpp" #include "addons/addon_manager.hpp" #include #include "pipeline/m2_loader.hpp" @@ -223,6 +224,11 @@ bool Application::initialize() { renderer.get(), assetManager.get(), gameHandler.get(), dbcLayout_.get(), entitySpawner_.get()); + // Wire AppearanceComposer to UI components (Phase A singleton breaking) + if (uiManager) { + uiManager->setAppearanceComposer(appearanceComposer_.get()); + } + // Ensure the main in-world CharacterRenderer can load textures immediately. // Previously this was only wired during terrain initialization, which meant early spawns // (before terrain load) would render with white fallback textures (notably hair). diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index e7a2e271..d18566fb 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -2,6 +2,7 @@ #include "ui/ui_colors.hpp" #include "rendering/vk_context.hpp" #include "core/application.hpp" +#include "core/appearance_composer.hpp" #include "addons/addon_manager.hpp" #include "core/coordinates.hpp" #include "core/input.hpp" @@ -631,7 +632,7 @@ void GameScreen::render(game::GameHandler& gameHandler) { if (inventoryScreen.consumeEquipmentDirty() || gameHandler.consumeOnlineEquipmentDirty()) { updateCharacterGeosets(gameHandler.getInventory()); updateCharacterTextures(gameHandler.getInventory()); - if (auto* ac = core::Application::getInstance().getAppearanceComposer()) ac->loadEquippedWeapons(); + if (appearanceComposer_) appearanceComposer_->loadEquippedWeapons(); inventoryScreen.markPreviewDirty(); // Update renderer weapon type for animation selection auto* r = core::Application::getInstance().getRenderer();