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
This commit is contained in:
Paul 2026-04-01 20:38:37 +03:00
parent 9b38e64f84
commit d43397163e
8 changed files with 179 additions and 3 deletions

View file

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

View file

@ -0,0 +1,66 @@
#pragma once
#include <memory>
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> musicManager_;
std::unique_ptr<FootstepManager> footstepManager_;
std::unique_ptr<ActivitySoundManager> activitySoundManager_;
std::unique_ptr<MountSoundManager> mountSoundManager_;
std::unique_ptr<NpcVoiceManager> npcVoiceManager_;
std::unique_ptr<AmbientSoundManager> ambientSoundManager_;
std::unique_ptr<UiSoundManager> uiSoundManager_;
std::unique_ptr<CombatSoundManager> combatSoundManager_;
std::unique_ptr<SpellSoundManager> spellSoundManager_;
std::unique_ptr<MovementSoundManager> movementSoundManager_;
bool audioAvailable_ = false;
};
} // namespace audio
} // namespace wowee

View file

@ -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> entitySpawner_;
std::unique_ptr<AppearanceComposer> appearanceComposer_;
std::unique_ptr<WorldLoader> worldLoader_;
std::unique_ptr<audio::AudioCoordinator> audioCoordinator_;
AppState state = AppState::AUTHENTICATION;
bool running = false;

View file

@ -23,6 +23,7 @@
#include <unordered_map>
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_;

View file

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

View file

@ -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<MusicManager>();
footstepManager_ = std::make_unique<FootstepManager>();
activitySoundManager_ = std::make_unique<ActivitySoundManager>();
mountSoundManager_ = std::make_unique<MountSoundManager>();
npcVoiceManager_ = std::make_unique<NpcVoiceManager>();
ambientSoundManager_ = std::make_unique<AmbientSoundManager>();
uiSoundManager_ = std::make_unique<UiSoundManager>();
combatSoundManager_ = std::make_unique<CombatSoundManager>();
spellSoundManager_ = std::make_unique<SpellSoundManager>();
movementSoundManager_ = std::make_unique<MovementSoundManager>();
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

View file

@ -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 <imgui.h>
#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).

View file

@ -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();