Asset Management fixed
This commit is contained in:
@ -34,6 +34,7 @@ add_executable(tetris
|
|||||||
# New core architecture classes
|
# New core architecture classes
|
||||||
src/core/ApplicationManager.cpp
|
src/core/ApplicationManager.cpp
|
||||||
src/core/InputManager.cpp
|
src/core/InputManager.cpp
|
||||||
|
src/core/AssetManager.cpp
|
||||||
src/graphics/RenderManager.cpp
|
src/graphics/RenderManager.cpp
|
||||||
src/persistence/Scores.cpp
|
src/persistence/Scores.cpp
|
||||||
src/graphics/Starfield.cpp
|
src/graphics/Starfield.cpp
|
||||||
@ -125,6 +126,7 @@ add_executable(tetris_refactored
|
|||||||
# New core architecture classes
|
# New core architecture classes
|
||||||
src/core/ApplicationManager.cpp
|
src/core/ApplicationManager.cpp
|
||||||
src/core/InputManager.cpp
|
src/core/InputManager.cpp
|
||||||
|
src/core/AssetManager.cpp
|
||||||
src/graphics/RenderManager.cpp
|
src/graphics/RenderManager.cpp
|
||||||
src/persistence/Scores.cpp
|
src/persistence/Scores.cpp
|
||||||
src/graphics/Starfield.cpp
|
src/graphics/Starfield.cpp
|
||||||
|
|||||||
@ -44,9 +44,9 @@
|
|||||||
- [ ] Define window size constants
|
- [ ] Define window size constants
|
||||||
- [ ] Set up gameplay timing constants
|
- [ ] Set up gameplay timing constants
|
||||||
|
|
||||||
## 🔧 Phase 2: Core Systems (Week 3-4) - HIGH PRIORITY
|
## 🔧 Phase 2: Core Systems (Week 3-4) - HIGH PRIORITY ✅ **COMPLETED**
|
||||||
|
|
||||||
### Input Management
|
### Input Management ✅ **COMPLETED**
|
||||||
- [x] Create `InputManager` class
|
- [x] Create `InputManager` class
|
||||||
- [x] Implement keyboard state tracking
|
- [x] Implement keyboard state tracking
|
||||||
- [x] Add mouse input handling
|
- [x] Add mouse input handling
|
||||||
@ -60,19 +60,20 @@
|
|||||||
- [x] Simplify main loop event processing
|
- [x] Simplify main loop event processing
|
||||||
- [x] Test all input scenarios (keyboard, mouse, gamepad)
|
- [x] Test all input scenarios (keyboard, mouse, gamepad)
|
||||||
|
|
||||||
### Asset Management
|
### Asset Management ✅ **COMPLETED**
|
||||||
- [ ] Create `AssetManager` class
|
|
||||||
- [ ] Design resource loading interface
|
|
||||||
- [ ] Implement texture management
|
|
||||||
- [ ] Add font loading and management
|
|
||||||
- [ ] Create sound asset handling
|
|
||||||
- [ ] Add resource cleanup and error handling
|
|
||||||
|
|
||||||
- [ ] Migrate existing asset loading
|
- [x] Create `AssetManager` class
|
||||||
- [ ] Move texture loading from main() to AssetManager
|
- [x] Design resource loading interface
|
||||||
- [ ] Convert font initialization to use AssetManager
|
- [x] Implement texture management
|
||||||
- [ ] Update StateContext to use AssetManager
|
- [x] Add font loading and management
|
||||||
- [ ] Test asset loading and memory management
|
- [x] Create sound asset handling
|
||||||
|
- [x] Add resource cleanup and error handling
|
||||||
|
|
||||||
|
- [x] Migrate existing asset loading
|
||||||
|
- [x] Move texture loading from main() to AssetManager
|
||||||
|
- [x] Convert font initialization to use AssetManager
|
||||||
|
- [x] Update StateContext to use AssetManager
|
||||||
|
- [x] Test asset loading and memory management
|
||||||
|
|
||||||
### State System Enhancement
|
### State System Enhancement
|
||||||
- [ ] Implement `IGameState` interface
|
- [ ] Implement `IGameState` interface
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#include "ApplicationManager.h"
|
#include "ApplicationManager.h"
|
||||||
#include "StateManager.h"
|
#include "StateManager.h"
|
||||||
#include "InputManager.h"
|
#include "InputManager.h"
|
||||||
|
#include "AssetManager.h"
|
||||||
#include "../graphics/RenderManager.h"
|
#include "../graphics/RenderManager.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <SDL3_ttf/SDL_ttf.h>
|
#include <SDL3_ttf/SDL_ttf.h>
|
||||||
@ -134,6 +135,13 @@ bool ApplicationManager::initializeManagers() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create and initialize AssetManager
|
||||||
|
m_assetManager = std::make_unique<AssetManager>();
|
||||||
|
if (!m_assetManager->initialize(m_renderManager->getSDLRenderer())) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize AssetManager");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Create StateManager (will be enhanced in next steps)
|
// Create StateManager (will be enhanced in next steps)
|
||||||
m_stateManager = std::make_unique<StateManager>(AppState::Loading);
|
m_stateManager = std::make_unique<StateManager>(AppState::Loading);
|
||||||
|
|
||||||
@ -142,20 +150,62 @@ bool ApplicationManager::initializeManagers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ApplicationManager::initializeGame() {
|
bool ApplicationManager::initializeGame() {
|
||||||
// TODO: Initialize game-specific systems
|
// Load essential assets using AssetManager
|
||||||
// For now, just set a basic loading state to test the window
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loading essential assets...");
|
||||||
|
|
||||||
// Initialize a basic test render handler
|
// Set up asset loading tasks
|
||||||
m_stateManager->registerRenderHandler(AppState::Loading,
|
AssetManager::LoadingTask logoTask{AssetManager::LoadingTask::TEXTURE, "logo", "assets/images/logo.bmp"};
|
||||||
[this](RenderManager& renderer) {
|
AssetManager::LoadingTask backgroundTask{AssetManager::LoadingTask::TEXTURE, "background", "assets/images/main_background.bmp"};
|
||||||
// Simple test render - just clear screen and show a colored rectangle
|
AssetManager::LoadingTask blocksTask{AssetManager::LoadingTask::TEXTURE, "blocks", "assets/images/blocks90px_001.bmp"};
|
||||||
renderer.clear(20, 30, 40, 255);
|
AssetManager::LoadingTask fontTask{AssetManager::LoadingTask::FONT, "main_font", "FreeSans.ttf", 24};
|
||||||
|
AssetManager::LoadingTask pixelFontTask{AssetManager::LoadingTask::FONT, "pixel_font", "assets/fonts/PressStart2P-Regular.ttf", 16};
|
||||||
|
|
||||||
SDL_FRect testRect = { 400, 300, 400, 200 };
|
// Add tasks to AssetManager
|
||||||
renderer.renderRect(testRect, 255, 100, 100, 255);
|
m_assetManager->addLoadingTask(logoTask);
|
||||||
|
m_assetManager->addLoadingTask(backgroundTask);
|
||||||
|
m_assetManager->addLoadingTask(blocksTask);
|
||||||
|
m_assetManager->addLoadingTask(fontTask);
|
||||||
|
m_assetManager->addLoadingTask(pixelFontTask);
|
||||||
|
|
||||||
|
// Execute loading tasks with progress callback
|
||||||
|
m_assetManager->executeLoadingTasks([](float progress) {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Asset loading progress: %.1f%%", progress * 100.0f);
|
||||||
});
|
});
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Game systems initialized");
|
// Load sound effects with fallback
|
||||||
|
m_assetManager->loadSoundEffectWithFallback("clear_line", "clear_line");
|
||||||
|
m_assetManager->loadSoundEffectWithFallback("nice_combo", "nice_combo");
|
||||||
|
m_assetManager->loadSoundEffectWithFallback("amazing", "amazing");
|
||||||
|
|
||||||
|
// Start background music loading
|
||||||
|
m_assetManager->startBackgroundMusicLoading();
|
||||||
|
|
||||||
|
// Initialize a basic test render handler that shows loaded assets
|
||||||
|
m_stateManager->registerRenderHandler(AppState::Loading,
|
||||||
|
[this](RenderManager& renderer) {
|
||||||
|
// Simple test render - just clear screen and show loaded assets
|
||||||
|
renderer.clear(20, 30, 40, 255);
|
||||||
|
|
||||||
|
// Try to render background if loaded
|
||||||
|
SDL_Texture* background = m_assetManager->getTexture("background");
|
||||||
|
if (background) {
|
||||||
|
SDL_FRect bgRect = { 0, 0, 1200, 1000 };
|
||||||
|
renderer.renderTexture(background, nullptr, &bgRect);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to render logo if loaded
|
||||||
|
SDL_Texture* logo = m_assetManager->getTexture("logo");
|
||||||
|
if (logo) {
|
||||||
|
SDL_FRect logoRect = { 300, 200, 600, 200 };
|
||||||
|
renderer.renderTexture(logo, nullptr, &logoRect);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show asset loading status
|
||||||
|
SDL_FRect statusRect = { 50, 50, 400, 30 };
|
||||||
|
renderer.renderRect(statusRect, 0, 100, 200, 200);
|
||||||
|
});
|
||||||
|
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Game systems initialized with asset loading");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,6 +255,7 @@ void ApplicationManager::render() {
|
|||||||
void ApplicationManager::cleanupManagers() {
|
void ApplicationManager::cleanupManagers() {
|
||||||
// Cleanup managers in reverse order
|
// Cleanup managers in reverse order
|
||||||
m_stateManager.reset();
|
m_stateManager.reset();
|
||||||
|
m_assetManager.reset();
|
||||||
m_inputManager.reset();
|
m_inputManager.reset();
|
||||||
m_renderManager.reset();
|
m_renderManager.reset();
|
||||||
|
|
||||||
|
|||||||
@ -41,6 +41,7 @@ public:
|
|||||||
// Access to managers (for now, will be replaced with dependency injection later)
|
// Access to managers (for now, will be replaced with dependency injection later)
|
||||||
RenderManager* getRenderManager() const { return m_renderManager.get(); }
|
RenderManager* getRenderManager() const { return m_renderManager.get(); }
|
||||||
InputManager* getInputManager() const { return m_inputManager.get(); }
|
InputManager* getInputManager() const { return m_inputManager.get(); }
|
||||||
|
AssetManager* getAssetManager() const { return m_assetManager.get(); }
|
||||||
StateManager* getStateManager() const { return m_stateManager.get(); }
|
StateManager* getStateManager() const { return m_stateManager.get(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -61,6 +62,7 @@ private:
|
|||||||
// Core managers
|
// Core managers
|
||||||
std::unique_ptr<RenderManager> m_renderManager;
|
std::unique_ptr<RenderManager> m_renderManager;
|
||||||
std::unique_ptr<InputManager> m_inputManager;
|
std::unique_ptr<InputManager> m_inputManager;
|
||||||
|
std::unique_ptr<AssetManager> m_assetManager;
|
||||||
std::unique_ptr<StateManager> m_stateManager;
|
std::unique_ptr<StateManager> m_stateManager;
|
||||||
|
|
||||||
// Application state
|
// Application state
|
||||||
|
|||||||
384
src/core/AssetManager.cpp
Normal file
384
src/core/AssetManager.cpp
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
#include "AssetManager.h"
|
||||||
|
#include "../graphics/Font.h"
|
||||||
|
#include "../audio/Audio.h"
|
||||||
|
#include "../audio/SoundEffect.h"
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3_ttf/SDL_ttf.h>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
AssetManager::AssetManager()
|
||||||
|
: m_renderer(nullptr)
|
||||||
|
, m_audioSystem(nullptr)
|
||||||
|
, m_soundSystem(nullptr)
|
||||||
|
, 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()) {
|
||||||
|
if (progressCallback) progressCallback(1.0f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logInfo("Executing " + std::to_string(m_loadingTasks.size()) + " loading tasks...");
|
||||||
|
|
||||||
|
size_t totalTasks = m_loadingTasks.size();
|
||||||
|
size_t completedTasks = 0;
|
||||||
|
|
||||||
|
for (const auto& task : m_loadingTasks) {
|
||||||
|
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 + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
completedTasks++;
|
||||||
|
|
||||||
|
if (progressCallback) {
|
||||||
|
float progress = static_cast<float>(completedTasks) / static_cast<float>(totalTasks);
|
||||||
|
progressCallback(progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logInfo("Completed " + std::to_string(completedTasks) + "/" + std::to_string(totalTasks) + " loading tasks");
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load using SDL_LoadBMP (matching main.cpp pattern)
|
||||||
|
SDL_Surface* surface = SDL_LoadBMP(filepath.c_str());
|
||||||
|
if (!surface) {
|
||||||
|
setError("Failed to load surface from: " + filepath + " - " + SDL_GetError());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Texture* texture = SDL_CreateTextureFromSurface(m_renderer, surface);
|
||||||
|
SDL_DestroySurface(surface);
|
||||||
|
|
||||||
|
if (!texture) {
|
||||||
|
setError("Failed to create texture from surface: " + filepath + " - " + SDL_GetError());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
109
src/core/AssetManager.h
Normal file
109
src/core/AssetManager.h
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3_ttf/SDL_ttf.h>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <memory>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
class FontAtlas;
|
||||||
|
class Audio;
|
||||||
|
class SoundEffectManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AssetManager - Centralized resource management following SOLID principles
|
||||||
|
*
|
||||||
|
* Responsibilities:
|
||||||
|
* - Texture loading and management (BMP, PNG via SDL)
|
||||||
|
* - Font loading and caching (TTF via FontAtlas)
|
||||||
|
* - Audio resource coordination (MP3 via Audio, WAV via SoundEffectManager)
|
||||||
|
* - Resource lifecycle management (loading, caching, cleanup)
|
||||||
|
* - Error handling and fallback mechanisms
|
||||||
|
*
|
||||||
|
* Design Principles:
|
||||||
|
* - Single Responsibility: Only handles asset loading/management
|
||||||
|
* - Open/Closed: Easy to extend with new asset types
|
||||||
|
* - Dependency Inversion: Uses interfaces for audio systems
|
||||||
|
* - Interface Segregation: Separate methods for different asset types
|
||||||
|
*/
|
||||||
|
class AssetManager {
|
||||||
|
public:
|
||||||
|
AssetManager();
|
||||||
|
~AssetManager();
|
||||||
|
|
||||||
|
// Lifecycle management
|
||||||
|
bool initialize(SDL_Renderer* renderer);
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
// Texture management
|
||||||
|
SDL_Texture* loadTexture(const std::string& id, const std::string& filepath);
|
||||||
|
SDL_Texture* getTexture(const std::string& id) const;
|
||||||
|
bool unloadTexture(const std::string& id);
|
||||||
|
void setDefaultTexturePath(const std::string& path) { m_defaultTexturePath = path; }
|
||||||
|
|
||||||
|
// Font management
|
||||||
|
bool loadFont(const std::string& id, const std::string& filepath, int baseSize = 24);
|
||||||
|
FontAtlas* getFont(const std::string& id) const;
|
||||||
|
bool unloadFont(const std::string& id);
|
||||||
|
void setDefaultFontPath(const std::string& path) { m_defaultFontPath = path; }
|
||||||
|
|
||||||
|
// Audio management (coordinates with existing Audio and SoundEffectManager)
|
||||||
|
bool loadMusicTrack(const std::string& filepath);
|
||||||
|
bool loadSoundEffect(const std::string& id, const std::string& filepath);
|
||||||
|
bool loadSoundEffectWithFallback(const std::string& id, const std::string& baseName);
|
||||||
|
void startBackgroundMusicLoading();
|
||||||
|
bool isMusicLoadingComplete() const;
|
||||||
|
int getLoadedMusicTrackCount() const;
|
||||||
|
|
||||||
|
// Batch loading operations
|
||||||
|
struct LoadingTask {
|
||||||
|
enum Type { TEXTURE, FONT, MUSIC, SOUND_EFFECT };
|
||||||
|
Type type;
|
||||||
|
std::string id;
|
||||||
|
std::string filepath;
|
||||||
|
int fontSize = 24; // For fonts only
|
||||||
|
};
|
||||||
|
|
||||||
|
void addLoadingTask(const LoadingTask& task);
|
||||||
|
void executeLoadingTasks(std::function<void(float)> progressCallback = nullptr);
|
||||||
|
void clearLoadingTasks();
|
||||||
|
|
||||||
|
// Resource queries
|
||||||
|
size_t getTextureCount() const { return m_textures.size(); }
|
||||||
|
size_t getFontCount() const { return m_fonts.size(); }
|
||||||
|
bool isResourceLoaded(const std::string& id) const;
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
std::string getLastError() const { return m_lastError; }
|
||||||
|
void clearLastError() { m_lastError.clear(); }
|
||||||
|
|
||||||
|
// Asset path utilities
|
||||||
|
static std::string getAssetPath(const std::string& relativePath);
|
||||||
|
static bool fileExists(const std::string& filepath);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Resource storage
|
||||||
|
std::unordered_map<std::string, SDL_Texture*> m_textures;
|
||||||
|
std::unordered_map<std::string, std::unique_ptr<FontAtlas>> m_fonts;
|
||||||
|
std::vector<LoadingTask> m_loadingTasks;
|
||||||
|
|
||||||
|
// System references
|
||||||
|
SDL_Renderer* m_renderer;
|
||||||
|
Audio* m_audioSystem; // Pointer to singleton
|
||||||
|
SoundEffectManager* m_soundSystem; // Pointer to singleton
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
std::string m_defaultTexturePath;
|
||||||
|
std::string m_defaultFontPath;
|
||||||
|
std::string m_lastError;
|
||||||
|
bool m_initialized;
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
SDL_Texture* loadTextureFromFile(const std::string& filepath);
|
||||||
|
bool validateRenderer() const;
|
||||||
|
void setError(const std::string& error);
|
||||||
|
void logInfo(const std::string& message) const;
|
||||||
|
void logError(const std::string& message) const;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user