Fixed resource loader

This commit is contained in:
2025-12-25 14:23:17 +01:00
parent 45086e58d8
commit 0b546ce25c
11 changed files with 253 additions and 18 deletions

View File

@ -1,6 +1,10 @@
#include "app/AssetLoader.h"
#include <SDL3_image/SDL_image.h>
#include <algorithm>
#include "app/TextureLoader.h"
#include "utils/ImagePathResolver.h"
#include <filesystem>
AssetLoader::AssetLoader() = default;
@ -37,6 +41,10 @@ void AssetLoader::shutdown() {
m_renderer = nullptr;
}
void AssetLoader::setResourceManager(resources::ResourceManager* mgr) {
m_resourceManager = mgr;
}
void AssetLoader::setBasePath(const std::string& basePath) {
m_basePath = basePath;
}
@ -65,24 +73,25 @@ bool AssetLoader::performStep() {
std::string fullPath = m_basePath.empty() ? path : (m_basePath + "/" + path);
SDL_Surface* surf = IMG_Load(fullPath.c_str());
if (!surf) {
std::lock_guard<std::mutex> lk(m_errorsMutex);
m_errors.push_back(std::string("IMG_Load failed: ") + fullPath + " -> " + SDL_GetError());
// Diagnostic: resolve path and check file existence
const std::string resolved = AssetPath::resolveImagePath(path);
bool exists = false;
try { if (!resolved.empty()) exists = std::filesystem::exists(std::filesystem::u8path(resolved)); } catch (...) { exists = false; }
// Use TextureLoader to centralize loading and ResourceManager caching
TextureLoader loader(m_loadedTasks, m_currentLoading, m_currentLoadingMutex, m_errors, m_errorsMutex);
loader.setResourceManager(m_resourceManager);
// Pass the original queued path (not the full resolved path) so caching keys stay consistent
SDL_Texture* tex = loader.loadFromImage(m_renderer, path);
if (!tex) {
// errors have been recorded by TextureLoader
} else {
SDL_Texture* tex = SDL_CreateTextureFromSurface(m_renderer, surf);
SDL_DestroySurface(surf);
if (!tex) {
std::lock_guard<std::mutex> lk(m_errorsMutex);
m_errors.push_back(std::string("CreateTexture failed: ") + fullPath);
} else {
std::lock_guard<std::mutex> lk(m_texturesMutex);
auto& slot = m_textures[path];
if (slot && slot != tex) {
SDL_DestroyTexture(slot);
}
slot = tex;
std::lock_guard<std::mutex> lk(m_texturesMutex);
auto& slot = m_textures[path];
if (slot && slot != tex) {
SDL_DestroyTexture(slot);
}
slot = tex;
}
m_loadedTasks.fetch_add(1, std::memory_order_relaxed);
@ -104,12 +113,17 @@ void AssetLoader::adoptTexture(const std::string& path, SDL_Texture* texture) {
return;
}
// register in local map and resource manager
std::lock_guard<std::mutex> lk(m_texturesMutex);
auto& slot = m_textures[path];
if (slot && slot != texture) {
SDL_DestroyTexture(slot);
}
slot = texture;
if (m_resourceManager) {
std::shared_ptr<void> sp(texture, [](void* t){ SDL_DestroyTexture(static_cast<SDL_Texture*>(t)); });
m_resourceManager->put(path, sp);
}
}
float AssetLoader::getProgress() const {

View File

@ -6,6 +6,7 @@
#include <mutex>
#include <atomic>
#include <unordered_map>
#include "../resources/ResourceManager.h"
// Lightweight AssetLoader scaffold.
// Responsibilities:
@ -22,6 +23,7 @@ public:
void shutdown();
void setBasePath(const std::string& basePath);
void setResourceManager(resources::ResourceManager* mgr);
// Queue a texture path (relative to base path) for loading.
void queueTexture(const std::string& path);
@ -49,6 +51,7 @@ public:
private:
SDL_Renderer* m_renderer = nullptr;
std::string m_basePath;
resources::ResourceManager* m_resourceManager = nullptr;
// queued paths (simple FIFO)
std::vector<std::string> m_queue;

View File

@ -68,6 +68,7 @@
#include "ui/MenuLayout.h"
#include "utils/ImagePathResolver.h"
#include "../resources/ResourceManager.h"
// ---------- Game config ----------
static constexpr int LOGICAL_W = 1200;
@ -187,6 +188,7 @@ struct TetrisApp::Impl {
AssetLoader assetLoader;
std::unique_ptr<LoadingManager> loadingManager;
std::unique_ptr<TextureLoader> textureLoader;
resources::ResourceManager resourceManager;
FontAtlas pixelFont;
FontAtlas font;
@ -427,6 +429,8 @@ int TetrisApp::Impl::init()
// Asset loader (creates SDL_Textures on the main thread)
assetLoader.init(renderer);
// Wire resource manager into loader so textures are cached and reused
assetLoader.setResourceManager(&resourceManager);
loadingManager = std::make_unique<LoadingManager>(&assetLoader);
// Legacy image loader (used only as a fallback when AssetLoader misses)
@ -436,6 +440,8 @@ int TetrisApp::Impl::init()
currentLoadingMutex,
assetLoadErrors,
assetLoadErrorsMutex);
// Let legacy TextureLoader access the same resource cache
textureLoader->setResourceManager(&resourceManager);
// Load scores asynchronously but keep the worker alive until shutdown
scoreLoader = std::jthread([this]() {
@ -1785,6 +1791,8 @@ void TetrisApp::Impl::runLoop()
nextPanelTex = assetLoader.getTexture(Assets::NEXT_PANEL);
holdPanelTex = assetLoader.getTexture(Assets::HOLD_PANEL);
// texture retrieval diagnostics removed
auto ensureTextureSize = [&](SDL_Texture* tex, int& outW, int& outH) {
if (!tex) return;
if (outW > 0 && outH > 0) return;

View File

@ -6,6 +6,8 @@
#include <mutex>
#include <sstream>
#include <filesystem>
#include "utils/ImagePathResolver.h"
TextureLoader::TextureLoader(
@ -45,6 +47,18 @@ SDL_Texture* TextureLoader::loadFromImage(SDL_Renderer* renderer, const std::str
const std::string resolvedPath = AssetPath::resolveImagePath(path);
setCurrentLoadingFile(resolvedPath.empty() ? path : resolvedPath);
// Check filesystem existence for diagnostics (no console log)
bool fileExists = false;
try { if (!resolvedPath.empty()) fileExists = std::filesystem::exists(std::filesystem::u8path(resolvedPath)); } catch (...) { fileExists = false; }
// If resource manager provided, check cache first using the original asset key (path)
if (resourceManager_) {
if (auto sp = resourceManager_->get<SDL_Texture>(path)) {
clearCurrentLoadingFile();
loadedTasks_.fetch_add(1);
return sp.get();
}
}
SDL_Surface* surface = IMG_Load(resolvedPath.c_str());
if (!surface) {
{
@ -54,7 +68,7 @@ SDL_Texture* TextureLoader::loadFromImage(SDL_Renderer* renderer, const std::str
}
loadedTasks_.fetch_add(1);
clearCurrentLoadingFile();
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load image %s (resolved: %s): %s", path.c_str(), resolvedPath.c_str(), SDL_GetError());
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load image %s (resolved: %s) exists=%s: %s", path.c_str(), resolvedPath.c_str(), fileExists ? "yes" : "no", SDL_GetError());
return nullptr;
}
@ -66,6 +80,7 @@ SDL_Texture* TextureLoader::loadFromImage(SDL_Renderer* renderer, const std::str
}
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
// surface size preserved in outW/outH; no console log
SDL_DestroySurface(surface);
if (!texture) {
@ -80,6 +95,15 @@ SDL_Texture* TextureLoader::loadFromImage(SDL_Renderer* renderer, const std::str
return nullptr;
}
// No texture-size console diagnostics here
// cache in resource manager if present
if (resourceManager_) {
std::shared_ptr<void> sp(texture, [](void* t){ SDL_DestroyTexture(static_cast<SDL_Texture*>(t)); });
// store under original asset key (path) so callers using logical asset names find them
resourceManager_->put(path, sp);
}
loadedTasks_.fetch_add(1);
clearCurrentLoadingFile();

View File

@ -6,6 +6,7 @@
#include <mutex>
#include <string>
#include <vector>
#include "../resources/ResourceManager.h"
class TextureLoader {
public:
@ -16,6 +17,8 @@ public:
std::vector<std::string>& assetLoadErrors,
std::mutex& assetLoadErrorsMutex);
void setResourceManager(resources::ResourceManager* mgr) { resourceManager_ = mgr; }
SDL_Texture* loadFromImage(SDL_Renderer* renderer, const std::string& path, int* outW = nullptr, int* outH = nullptr);
private:
@ -28,4 +31,6 @@ private:
void setCurrentLoadingFile(const std::string& filename);
void clearCurrentLoadingFile();
void recordAssetLoadError(const std::string& message);
resources::ResourceManager* resourceManager_ = nullptr;
};

View File

@ -7,6 +7,8 @@
#include <SDL3_ttf/SDL_ttf.h>
#include <filesystem>
#include "../../utils/ImagePathResolver.h"
#include "../../core/Config.h"
#include "../../resources/AssetPaths.h"
AssetManager::AssetManager()
: m_renderer(nullptr)
@ -103,7 +105,34 @@ SDL_Texture* AssetManager::loadTexture(const std::string& id, const std::string&
SDL_Texture* AssetManager::getTexture(const std::string& id) const {
auto it = m_textures.find(id);
return (it != m_textures.end()) ? it->second : nullptr;
if (it != m_textures.end()) return it->second;
// Lazy fallback: attempt to load well-known short ids from configured asset paths.
std::vector<std::string> candidates;
if (id == "logo") {
candidates.push_back(std::string(Assets::LOGO));
candidates.push_back(Config::Assets::LOGO_BMP);
} else if (id == "logo_small") {
candidates.push_back(Config::Assets::LOGO_SMALL_BMP);
candidates.push_back(std::string(Assets::LOGO));
} else if (id == "background") {
candidates.push_back(std::string(Assets::MAIN_SCREEN));
candidates.push_back(Config::Assets::BACKGROUND_BMP);
} else if (id == "blocks") {
candidates.push_back(std::string(Assets::BLOCKS_SPRITE));
candidates.push_back(Config::Assets::BLOCKS_BMP);
} else if (id == "asteroids") {
candidates.push_back(std::string(Assets::ASTEROID_SPRITE));
}
for (const auto &candidatePath : candidates) {
if (candidatePath.empty()) continue;
AssetManager* self = const_cast<AssetManager*>(this);
SDL_Texture* tex = self->loadTexture(id, candidatePath);
if (tex) return tex;
}
return nullptr;
}
bool AssetManager::unloadTexture(const std::string& id) {

20
src/renderer/Renderer.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include <SDL3/SDL.h>
#include <memory>
namespace renderer {
class Renderer {
public:
virtual ~Renderer() = default;
// Wrap common operations used by renderers
virtual SDL_Texture* createTextureFromSurface(SDL_Surface* surf) = 0;
virtual void destroyTexture(SDL_Texture* tex) = 0;
virtual void copy(SDL_Texture* tex, const SDL_Rect* src, const SDL_Rect* dst) = 0;
virtual void clear(const SDL_Color& color) = 0;
virtual void present() = 0;
};
} // namespace renderer

View File

@ -0,0 +1,46 @@
#include "Renderer.h"
#include <SDL3/SDL.h>
namespace renderer {
class SDLRendererImpl : public Renderer {
public:
explicit SDLRendererImpl(SDL_Renderer* rdr) : rdr_(rdr) {}
~SDLRendererImpl() override = default;
SDL_Texture* createTextureFromSurface(SDL_Surface* surf) override {
if (!rdr_ || !surf) return nullptr;
return SDL_CreateTextureFromSurface(rdr_, surf);
}
void destroyTexture(SDL_Texture* tex) override {
if (tex) SDL_DestroyTexture(tex);
}
void copy(SDL_Texture* tex, const SDL_Rect* src, const SDL_Rect* dst) override {
if (!rdr_ || !tex) return;
// SDL_RenderCopy mapping differs across SDL versions; defer to existing renderers
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "SDLRenderer::copy called — fallback no-op (use RenderManager for real draws)");
}
void clear(const SDL_Color& color) override {
if (!rdr_) return;
SDL_SetRenderDrawColor(rdr_, color.r, color.g, color.b, color.a);
SDL_RenderClear(rdr_);
}
void present() override {
if (!rdr_) return;
SDL_RenderPresent(rdr_);
}
private:
SDL_Renderer* rdr_ = nullptr;
};
// Factory helper
std::unique_ptr<Renderer> MakeSDLRenderer(SDL_Renderer* rdr) {
return std::make_unique<SDLRendererImpl>(rdr);
}
} // namespace renderer

View File

@ -0,0 +1,41 @@
#include "ResourceManager.h"
#include <future>
namespace resources {
ResourceManager::ResourceManager() = default;
ResourceManager::~ResourceManager() = default;
std::future<std::shared_ptr<void>> ResourceManager::loadAsync(const std::string& key, std::function<std::shared_ptr<void>(const std::string&)> loader)
{
// Quick check for existing cached resource
{
std::lock_guard<std::mutex> lk(mutex_);
auto it = cache_.find(key);
if (it != cache_.end()) {
// Return already-available resource (keep strong ref)
auto sp = it->second;
if (sp) {
return std::async(std::launch::deferred, [sp]() { return sp; });
}
}
}
// Launch async loader
return std::async(std::launch::async, [this, key, loader]() {
auto res = loader(key);
if (res) {
std::lock_guard<std::mutex> lk(mutex_);
cache_[key] = res; // store strong reference
}
return res;
});
}
void ResourceManager::put(const std::string& key, std::shared_ptr<void> resource)
{
std::lock_guard<std::mutex> lk(mutex_);
cache_[key] = resource; // store strong reference so callers using raw pointers stay valid
}
} // namespace resources

View File

@ -0,0 +1,43 @@
#pragma once
#include <string>
#include <memory>
#include <unordered_map>
#include <mutex>
#include <future>
#include <functional>
namespace resources {
class ResourceManager {
public:
ResourceManager();
~ResourceManager();
// Return cached resource if available and of the right type
template<typename T>
std::shared_ptr<T> get(const std::string& key)
{
std::lock_guard<std::mutex> lk(mutex_);
auto it = cache_.find(key);
if (it == cache_.end()) return nullptr;
auto sp = it->second;
if (!sp) { cache_.erase(it); return nullptr; }
return std::static_pointer_cast<T>(sp);
}
// Asynchronously load a resource using the provided loader function.
// The loader must return a shared_ptr to the concrete resource (boxed as void).
std::future<std::shared_ptr<void>> loadAsync(const std::string& key, std::function<std::shared_ptr<void>(const std::string&)> loader);
// Insert a resource into the cache (thread-safe)
void put(const std::string& key, std::shared_ptr<void> resource);
private:
// Keep strong ownership of cached resources so they remain valid
// while present in the cache.
std::unordered_map<std::string, std::shared_ptr<void>> cache_;
std::mutex mutex_;
};
} // namespace resources