#include "AssetManager.h" #include "../../graphics/ui/Font.h" #include "../../audio/Audio.h" #include "../../audio/SoundEffect.h" #include #include #include #include #include "../../utils/ImagePathResolver.h" AssetManager::AssetManager() : m_renderer(nullptr) , m_audioSystem(nullptr) , m_soundSystem(nullptr) , m_totalLoadingTasks(0) , m_completedLoadingTasks(0) , m_loadingComplete(false) , m_defaultTexturePath("assets/images/") , m_defaultFontPath("assets/fonts/") , m_initialized(false) { } AssetManager::~AssetManager() { shutdown(); } bool AssetManager::initialize(SDL_Renderer* renderer) { if (m_initialized) { logError("AssetManager already initialized"); return false; } if (!renderer) { setError("Invalid renderer provided to AssetManager"); return false; } m_renderer = renderer; // Get references to singleton systems m_audioSystem = &Audio::instance(); m_soundSystem = &SoundEffectManager::instance(); m_initialized = true; logInfo("AssetManager initialized successfully"); return true; } void AssetManager::shutdown() { if (!m_initialized) { return; } logInfo("Shutting down AssetManager..."); // Clear loading tasks clearLoadingTasks(); // Cleanup textures for (auto& [id, texture] : m_textures) { if (texture) { SDL_DestroyTexture(texture); logInfo("Destroyed texture: " + id); } } m_textures.clear(); // Cleanup fonts (unique_ptr handles destruction) m_fonts.clear(); // Reset state m_renderer = nullptr; m_audioSystem = nullptr; m_soundSystem = nullptr; m_initialized = false; logInfo("AssetManager shutdown complete"); } SDL_Texture* AssetManager::loadTexture(const std::string& id, const std::string& filepath) { if (!validateRenderer()) { return nullptr; } // Check if already loaded auto it = m_textures.find(id); if (it != m_textures.end()) { logInfo("Texture already loaded: " + id); return it->second; } // Load new texture SDL_Texture* texture = loadTextureFromFile(filepath); if (!texture) { setError("Failed to load texture: " + filepath); return nullptr; } m_textures[id] = texture; logInfo("Loaded texture: " + id + " from " + filepath); return texture; } SDL_Texture* AssetManager::getTexture(const std::string& id) const { auto it = m_textures.find(id); return (it != m_textures.end()) ? it->second : nullptr; } bool AssetManager::unloadTexture(const std::string& id) { auto it = m_textures.find(id); if (it == m_textures.end()) { setError("Texture not found: " + id); return false; } if (it->second) { SDL_DestroyTexture(it->second); } m_textures.erase(it); logInfo("Unloaded texture: " + id); return true; } bool AssetManager::loadFont(const std::string& id, const std::string& filepath, int baseSize) { // Check if already loaded auto it = m_fonts.find(id); if (it != m_fonts.end()) { logInfo("Font already loaded: " + id); return true; } // Create new font auto font = std::make_unique(); if (!font->init(filepath, baseSize)) { setError("Failed to initialize font: " + filepath); return false; } m_fonts[id] = std::move(font); logInfo("Loaded font: " + id + " from " + filepath + " (size: " + std::to_string(baseSize) + ")"); return true; } FontAtlas* AssetManager::getFont(const std::string& id) const { auto it = m_fonts.find(id); return (it != m_fonts.end()) ? it->second.get() : nullptr; } bool AssetManager::unloadFont(const std::string& id) { auto it = m_fonts.find(id); if (it == m_fonts.end()) { setError("Font not found: " + id); return false; } // Shutdown the font before removing it->second->shutdown(); m_fonts.erase(it); logInfo("Unloaded font: " + id); return true; } bool AssetManager::loadMusicTrack(const std::string& filepath) { if (!m_audioSystem) { setError("Audio system not available"); return false; } if (!fileExists(filepath)) { setError("Music file not found: " + filepath); return false; } try { m_audioSystem->addTrackAsync(filepath); logInfo("Added music track for loading: " + filepath); return true; } catch (const std::exception& e) { setError("Failed to add music track: " + std::string(e.what())); return false; } } bool AssetManager::loadSoundEffect(const std::string& id, const std::string& filepath) { if (!m_soundSystem) { setError("Sound effect system not available"); return false; } if (!fileExists(filepath)) { setError("Sound effect file not found: " + filepath); return false; } if (m_soundSystem->loadSound(id, filepath)) { logInfo("Loaded sound effect: " + id + " from " + filepath); return true; } else { setError("Failed to load sound effect: " + id + " from " + filepath); return false; } } bool AssetManager::loadSoundEffectWithFallback(const std::string& id, const std::string& baseName) { if (!m_soundSystem) { setError("Sound effect system not available"); return false; } // Try WAV first, then MP3 fallback (matching main.cpp pattern) std::string wavPath = "assets/music/" + baseName + ".wav"; std::string mp3Path = "assets/music/" + baseName + ".mp3"; // Check WAV first if (fileExists(wavPath)) { if (m_soundSystem->loadSound(id, wavPath)) { logInfo("Loaded sound effect: " + id + " from " + wavPath + " (WAV)"); return true; } } // Fallback to MP3 if (fileExists(mp3Path)) { if (m_soundSystem->loadSound(id, mp3Path)) { logInfo("Loaded sound effect: " + id + " from " + mp3Path + " (MP3 fallback)"); return true; } } setError("Failed to load sound effect: " + id + " (tried both WAV and MP3)"); return false; } void AssetManager::startBackgroundMusicLoading() { if (!m_audioSystem) { setError("Audio system not available"); return; } m_audioSystem->startBackgroundLoading(); logInfo("Started background music loading"); } bool AssetManager::isMusicLoadingComplete() const { if (!m_audioSystem) { return false; } return m_audioSystem->isLoadingComplete(); } int AssetManager::getLoadedMusicTrackCount() const { if (!m_audioSystem) { return 0; } return m_audioSystem->getLoadedTrackCount(); } void AssetManager::addLoadingTask(const LoadingTask& task) { m_loadingTasks.push_back(task); logInfo("Added loading task: " + task.id + " (" + task.filepath + ")"); } void AssetManager::executeLoadingTasks(std::function progressCallback) { if (m_loadingTasks.empty()) { m_loadingComplete = true; if (progressCallback) progressCallback(1.0f); return; } logInfo("Starting progressive loading of " + std::to_string(m_loadingTasks.size()) + " loading tasks..."); m_totalLoadingTasks = m_loadingTasks.size(); m_completedLoadingTasks = 0; m_currentTaskIndex = 0; m_loadingComplete = false; m_isProgressiveLoading = true; m_lastLoadTime = SDL_GetTicks(); m_musicLoadingStarted = false; m_musicLoadingProgress = 0.0f; // Don't execute tasks immediately - let update() handle them progressively } void AssetManager::update(float deltaTime) { if (!m_isProgressiveLoading || m_loadingTasks.empty()) { // Handle music loading progress simulation if assets are done if (m_musicLoadingStarted && !m_loadingComplete) { m_musicLoadingProgress += deltaTime * 0.4f; // Simulate music loading progress if (m_musicLoadingProgress >= 1.0f) { m_musicLoadingProgress = 1.0f; m_loadingComplete = true; logInfo("Background music loading simulation complete"); } } return; } Uint64 currentTime = SDL_GetTicks(); // Add minimum delay between loading items (600ms per item for visual effect) if (currentTime - m_lastLoadTime < 600) { return; } // Load one item at a time if (m_currentTaskIndex < m_loadingTasks.size()) { const auto& task = m_loadingTasks[m_currentTaskIndex]; bool success = false; switch (task.type) { case LoadingTask::TEXTURE: success = (loadTexture(task.id, task.filepath) != nullptr); break; case LoadingTask::FONT: success = loadFont(task.id, task.filepath, task.fontSize); break; case LoadingTask::MUSIC: success = loadMusicTrack(task.filepath); break; case LoadingTask::SOUND_EFFECT: success = loadSoundEffect(task.id, task.filepath); break; } if (!success) { logError("Failed to load asset: " + task.id + " (" + task.filepath + ")"); } m_currentTaskIndex++; m_completedLoadingTasks = m_currentTaskIndex; m_lastLoadTime = currentTime; logInfo("Asset loading progress: " + std::to_string((float)m_completedLoadingTasks / m_totalLoadingTasks * 100.0f) + "%"); // Check if all asset tasks are complete if (m_currentTaskIndex >= m_loadingTasks.size()) { m_isProgressiveLoading = false; logInfo("Completed " + std::to_string(m_completedLoadingTasks) + "/" + std::to_string(m_totalLoadingTasks) + " loading tasks"); // Start background music loading simulation m_musicLoadingStarted = true; m_musicLoadingProgress = 0.0f; startBackgroundMusicLoading(); } } } void AssetManager::clearLoadingTasks() { m_loadingTasks.clear(); logInfo("Cleared loading tasks"); } bool AssetManager::isResourceLoaded(const std::string& id) const { return (m_textures.find(id) != m_textures.end()) || (m_fonts.find(id) != m_fonts.end()); } std::string AssetManager::getAssetPath(const std::string& relativePath) { // Simple path construction - could be enhanced with proper path handling if (relativePath.find("assets/") == 0) { return relativePath; // Already has assets/ prefix } return "assets/" + relativePath; } bool AssetManager::fileExists(const std::string& filepath) { // Use SDL file I/O for consistency with main.cpp pattern SDL_IOStream* file = SDL_IOFromFile(filepath.c_str(), "rb"); if (file) { SDL_CloseIO(file); return true; } return false; } SDL_Texture* AssetManager::loadTextureFromFile(const std::string& filepath) { if (!validateRenderer()) { return nullptr; } const std::string resolvedPath = AssetPath::resolveImagePath(filepath); SDL_Texture* texture = IMG_LoadTexture(m_renderer, resolvedPath.c_str()); if (!texture) { std::string message = "Failed to load texture from: "; message += filepath; message += " (resolved: "; message += resolvedPath; message += ") - "; message += SDL_GetError(); setError(message); return nullptr; } if (resolvedPath != filepath) { std::string message = "Loaded alternative image path for "; message += filepath; message += ": "; message += resolvedPath; logInfo(message); } return texture; } bool AssetManager::validateRenderer() const { if (!m_initialized) { const_cast(this)->setError("AssetManager not initialized"); return false; } if (!m_renderer) { const_cast(this)->setError("Invalid renderer"); return false; } return true; } void AssetManager::setError(const std::string& error) { m_lastError = error; logError(error); } void AssetManager::logInfo(const std::string& message) const { SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[AssetManager] %s", message.c_str()); } void AssetManager::logError(const std::string& message) const { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[AssetManager] %s", message.c_str()); } // Loading progress tracking methods bool AssetManager::isLoadingComplete() const { // Loading is complete when both asset tasks and music loading are done return m_loadingComplete && (!m_musicLoadingStarted || m_musicLoadingProgress >= 1.0f); } float AssetManager::getLoadingProgress() const { if (m_totalLoadingTasks == 0) { return 1.0f; // No tasks = complete } // Asset loading progress (80% of total progress) float assetProgress = static_cast(m_completedLoadingTasks) / static_cast(m_totalLoadingTasks) * 0.8f; // Music loading progress (20% of total progress) float musicProgress = m_musicLoadingStarted ? m_musicLoadingProgress * 0.2f : 0.0f; return assetProgress + musicProgress; } // IAssetLoader interface implementation SDL_Texture* AssetManager::loadTextureFromPath(const std::string& path) { // Use the path as both ID and filepath for the interface implementation return loadTexture(path, path); } bool AssetManager::loadFontAsset(const std::string& name, const std::string& path, int size) { // Delegate to the existing loadFont method return loadFont(name, path, size); } bool AssetManager::loadAudioAsset(const std::string& name, const std::string& path) { return loadSoundEffect(name, path); } SDL_Texture* AssetManager::getTextureAsset(const std::string& name) { // Delegate to the existing getTexture method return getTexture(name); } bool AssetManager::hasAsset(const std::string& name) const { return m_textures.find(name) != m_textures.end() || m_fonts.find(name) != m_fonts.end(); } void AssetManager::unloadAsset(const std::string& name) { // Try to unload as texture first, then as font if (!unloadTexture(name)) { unloadFont(name); } } void AssetManager::unloadAll() { shutdown(); }