490 lines
14 KiB
C++
490 lines
14 KiB
C++
#include "AssetManager.h"
|
|
#include "../../graphics/ui/Font.h"
|
|
#include "../../audio/Audio.h"
|
|
#include "../../audio/SoundEffect.h"
|
|
#include <SDL3/SDL.h>
|
|
#include <SDL3_image/SDL_image.h>
|
|
#include <SDL3_ttf/SDL_ttf.h>
|
|
#include <filesystem>
|
|
#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<FontAtlas>();
|
|
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<void(float)> 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<AssetManager*>(this)->setError("AssetManager not initialized");
|
|
return false;
|
|
}
|
|
|
|
if (!m_renderer) {
|
|
const_cast<AssetManager*>(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<float>(m_completedLoadingTasks) / static_cast<float>(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();
|
|
}
|