fixed main screen
This commit is contained in:
122
src/app/AssetLoader.cpp
Normal file
122
src/app/AssetLoader.cpp
Normal file
@ -0,0 +1,122 @@
|
||||
#include "app/AssetLoader.h"
|
||||
#include <SDL3_image/SDL_image.h>
|
||||
#include <algorithm>
|
||||
|
||||
AssetLoader::AssetLoader() = default;
|
||||
|
||||
AssetLoader::~AssetLoader() {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
void AssetLoader::init(SDL_Renderer* renderer) {
|
||||
m_renderer = renderer;
|
||||
}
|
||||
|
||||
void AssetLoader::shutdown() {
|
||||
// Destroy textures
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_texturesMutex);
|
||||
for (auto &p : m_textures) {
|
||||
if (p.second) SDL_DestroyTexture(p.second);
|
||||
}
|
||||
m_textures.clear();
|
||||
}
|
||||
|
||||
// Clear queue and errors
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_queueMutex);
|
||||
m_queue.clear();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_errorsMutex);
|
||||
m_errors.clear();
|
||||
}
|
||||
|
||||
m_totalTasks = 0;
|
||||
m_loadedTasks = 0;
|
||||
m_renderer = nullptr;
|
||||
}
|
||||
|
||||
void AssetLoader::setBasePath(const std::string& basePath) {
|
||||
m_basePath = basePath;
|
||||
}
|
||||
|
||||
void AssetLoader::queueTexture(const std::string& path) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_queueMutex);
|
||||
m_queue.push_back(path);
|
||||
}
|
||||
m_totalTasks.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
bool AssetLoader::performStep() {
|
||||
std::string path;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_queueMutex);
|
||||
if (m_queue.empty()) return true;
|
||||
path = m_queue.front();
|
||||
m_queue.erase(m_queue.begin());
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_currentLoadingMutex);
|
||||
m_currentLoading = path;
|
||||
}
|
||||
|
||||
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());
|
||||
} 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);
|
||||
m_textures[path] = tex;
|
||||
}
|
||||
}
|
||||
|
||||
m_loadedTasks.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_currentLoadingMutex);
|
||||
m_currentLoading.clear();
|
||||
}
|
||||
|
||||
// Return true when no more queued tasks
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_queueMutex);
|
||||
return m_queue.empty();
|
||||
}
|
||||
}
|
||||
|
||||
float AssetLoader::getProgress() const {
|
||||
int total = m_totalTasks.load(std::memory_order_relaxed);
|
||||
if (total <= 0) return 1.0f;
|
||||
int loaded = m_loadedTasks.load(std::memory_order_relaxed);
|
||||
return static_cast<float>(loaded) / static_cast<float>(total);
|
||||
}
|
||||
|
||||
std::vector<std::string> AssetLoader::getAndClearErrors() {
|
||||
std::lock_guard<std::mutex> lk(m_errorsMutex);
|
||||
std::vector<std::string> out = m_errors;
|
||||
m_errors.clear();
|
||||
return out;
|
||||
}
|
||||
|
||||
SDL_Texture* AssetLoader::getTexture(const std::string& path) const {
|
||||
std::lock_guard<std::mutex> lk(m_texturesMutex);
|
||||
auto it = m_textures.find(path);
|
||||
if (it == m_textures.end()) return nullptr;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::string AssetLoader::getCurrentLoading() const {
|
||||
std::lock_guard<std::mutex> lk(m_currentLoadingMutex);
|
||||
return m_currentLoading;
|
||||
}
|
||||
64
src/app/AssetLoader.h
Normal file
64
src/app/AssetLoader.h
Normal file
@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <unordered_map>
|
||||
|
||||
// Lightweight AssetLoader scaffold.
|
||||
// Responsibilities:
|
||||
// - Queue textures to load (main thread) and perform incremental loads via performStep().
|
||||
// - Store loaded SDL_Texture* instances and provide accessors.
|
||||
// - Collect loading errors thread-safely.
|
||||
// NOTE: All SDL texture creation MUST happen on the thread that owns the SDL_Renderer.
|
||||
class AssetLoader {
|
||||
public:
|
||||
AssetLoader();
|
||||
~AssetLoader();
|
||||
|
||||
void init(SDL_Renderer* renderer);
|
||||
void shutdown();
|
||||
|
||||
void setBasePath(const std::string& basePath);
|
||||
|
||||
// Queue a texture path (relative to base path) for loading.
|
||||
void queueTexture(const std::string& path);
|
||||
|
||||
// Perform a single loading step (load one queued asset).
|
||||
// Returns true when all queued tasks are complete, false otherwise.
|
||||
bool performStep();
|
||||
|
||||
// Progress in [0,1]. If no tasks, returns 1.0f.
|
||||
float getProgress() const;
|
||||
|
||||
// Retrieve and clear accumulated error messages.
|
||||
std::vector<std::string> getAndClearErrors();
|
||||
|
||||
// Get a loaded texture (or nullptr if not loaded).
|
||||
SDL_Texture* getTexture(const std::string& path) const;
|
||||
|
||||
// Return currently-loading path (empty when idle).
|
||||
std::string getCurrentLoading() const;
|
||||
|
||||
private:
|
||||
SDL_Renderer* m_renderer = nullptr;
|
||||
std::string m_basePath;
|
||||
|
||||
// queued paths (simple FIFO)
|
||||
std::vector<std::string> m_queue;
|
||||
mutable std::mutex m_queueMutex;
|
||||
|
||||
std::unordered_map<std::string, SDL_Texture*> m_textures;
|
||||
mutable std::mutex m_texturesMutex;
|
||||
|
||||
std::vector<std::string> m_errors;
|
||||
mutable std::mutex m_errorsMutex;
|
||||
|
||||
std::atomic<int> m_totalTasks{0};
|
||||
std::atomic<int> m_loadedTasks{0};
|
||||
|
||||
std::string m_currentLoading;
|
||||
mutable std::mutex m_currentLoadingMutex;
|
||||
};
|
||||
363
src/main.cpp
363
src/main.cpp
@ -38,6 +38,8 @@
|
||||
#include "states/LevelSelectorState.h"
|
||||
#include "states/PlayingState.h"
|
||||
#include "audio/MenuWrappers.h"
|
||||
#include "app/AssetLoader.h"
|
||||
#include "states/LoadingManager.h"
|
||||
#include "utils/ImagePathResolver.h"
|
||||
#include "graphics/renderers/GameRenderer.h"
|
||||
#include "core/Config.h"
|
||||
@ -486,6 +488,11 @@ int main(int, char **)
|
||||
SDL_GetError());
|
||||
}
|
||||
|
||||
// Asset loader (creates SDL_Textures on the main thread)
|
||||
AssetLoader assetLoader;
|
||||
assetLoader.init(renderer);
|
||||
LoadingManager loadingManager(&assetLoader);
|
||||
|
||||
// Font and UI asset handles (actual loading deferred until Loading state)
|
||||
FontAtlas pixelFont;
|
||||
FontAtlas font;
|
||||
@ -544,108 +551,10 @@ int main(int, char **)
|
||||
std::atomic_bool g_loadingComplete{false};
|
||||
std::atomic<size_t> g_loadingStep{0};
|
||||
|
||||
// Define performLoadingStep to execute one load operation per frame on main thread
|
||||
auto performLoadingStep = [&]() -> bool {
|
||||
size_t step = g_loadingStep.fetch_add(1);
|
||||
|
||||
// Initialize counters on first step
|
||||
if (step == 0) {
|
||||
constexpr int baseTasks = 25; // 2 fonts + 2 logos + 1 main + 1 blocks + 3 panels + 16 SFX
|
||||
g_totalLoadingTasks.store(baseTasks);
|
||||
g_loadedTasks.store(0);
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_assetLoadErrorsMutex);
|
||||
g_assetLoadErrors.clear();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_currentLoadingMutex);
|
||||
g_currentLoadingFile.clear();
|
||||
}
|
||||
|
||||
// Initialize background music loading
|
||||
Audio::instance().init();
|
||||
for (int i = 1; i <= 100; ++i) {
|
||||
char base[128];
|
||||
std::snprintf(base, sizeof(base), "assets/music/music%03d", i);
|
||||
std::string path = AssetPath::resolveWithExtensions(base, { ".mp3" });
|
||||
if (path.empty()) break;
|
||||
Audio::instance().addTrackAsync(path);
|
||||
totalTracks++;
|
||||
}
|
||||
// Expand task budget to account for music tracks so the loading bar waits for them
|
||||
g_totalLoadingTasks.store(baseTasks + totalTracks);
|
||||
if (totalTracks > 0) {
|
||||
Audio::instance().startBackgroundLoading();
|
||||
musicLoadingStarted = true;
|
||||
} else {
|
||||
// No music files found, mark as loaded so game can continue
|
||||
musicLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute one load operation per step
|
||||
switch (step) {
|
||||
case 0: return false; // Init step
|
||||
case 1: pixelFont.init(AssetPath::resolveWithBase("assets/fonts/Orbitron.ttf"), 22); g_loadedTasks.fetch_add(1); break;
|
||||
case 2: font.init(AssetPath::resolveWithBase("assets/fonts/Exo2.ttf"), 20); g_loadedTasks.fetch_add(1); break;
|
||||
case 3: logoTex = loadTextureFromImage(renderer, "assets/images/spacetris.png"); break;
|
||||
case 4: logoSmallTex = loadTextureFromImage(renderer, "assets/images/spacetris.png", &logoSmallW, &logoSmallH); break;
|
||||
case 5: mainScreenTex = loadTextureFromImage(renderer, "assets/images/main_screen.png", &mainScreenW, &mainScreenH);
|
||||
if (mainScreenTex) SDL_SetTextureBlendMode(mainScreenTex, SDL_BLENDMODE_BLEND); break;
|
||||
case 6:
|
||||
blocksTex = loadTextureFromImage(renderer, "assets/images/blocks90px_001.bmp");
|
||||
if (!blocksTex) {
|
||||
blocksTex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 630, 90);
|
||||
SDL_SetRenderTarget(renderer, blocksTex);
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer);
|
||||
for (int i = 0; i < PIECE_COUNT; ++i) {
|
||||
SDL_Color c = COLORS[i + 1];
|
||||
SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a);
|
||||
SDL_FRect rect{(float)(i * 90), 0, 90, 90};
|
||||
SDL_RenderFillRect(renderer, &rect);
|
||||
}
|
||||
SDL_SetRenderTarget(renderer, nullptr);
|
||||
g_loadedTasks.fetch_add(1);
|
||||
}
|
||||
break;
|
||||
case 7: scorePanelTex = loadTextureFromImage(renderer, "assets/images/panel_score.png");
|
||||
if (scorePanelTex) SDL_SetTextureBlendMode(scorePanelTex, SDL_BLENDMODE_BLEND); break;
|
||||
case 8: statisticsPanelTex = loadTextureFromImage(renderer, "assets/images/statistics_panel.png");
|
||||
if (statisticsPanelTex) SDL_SetTextureBlendMode(statisticsPanelTex, SDL_BLENDMODE_BLEND); break;
|
||||
case 9: nextPanelTex = loadTextureFromImage(renderer, "assets/images/next_panel.png");
|
||||
if (nextPanelTex) SDL_SetTextureBlendMode(nextPanelTex, SDL_BLENDMODE_BLEND); break;
|
||||
case 10: SoundEffectManager::instance().init(); g_loadedTasks.fetch_add(1); break;
|
||||
|
||||
// Audio loading steps
|
||||
default: {
|
||||
const std::vector<std::string> audioIds = {"clear_line","nice_combo","you_fire","well_played","keep_that_ryhtm","great_move","smooth_clear","impressive","triple_strike","amazing","you_re_unstoppable","boom_tetris","wonderful","lets_go","hard_drop","new_level"};
|
||||
size_t audioIdx = step - 11;
|
||||
if (audioIdx < audioIds.size()) {
|
||||
std::string id = audioIds[audioIdx];
|
||||
std::string basePath = "assets/music/" + (id == "hard_drop" ? "hard_drop_001" : id);
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_currentLoadingMutex);
|
||||
g_currentLoadingFile = basePath;
|
||||
}
|
||||
std::string resolved = AssetPath::resolveWithExtensions(basePath, { ".wav", ".mp3" });
|
||||
if (!resolved.empty()) {
|
||||
SoundEffectManager::instance().loadSound(id, resolved);
|
||||
}
|
||||
g_loadedTasks.fetch_add(1);
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_currentLoadingMutex);
|
||||
g_currentLoadingFile.clear();
|
||||
}
|
||||
} else {
|
||||
// All done
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false; // More steps remaining
|
||||
};
|
||||
// Loading is now handled by AssetLoader + LoadingManager.
|
||||
// Old incremental lambda removed; use LoadingManager to queue texture loads and
|
||||
// perform a single step per frame. Non-texture initialization (fonts, SFX)
|
||||
// is performed on the first loading frame below when the loader is started.
|
||||
|
||||
Game game(startLevelSelection);
|
||||
// Apply global gravity speed multiplier from config
|
||||
@ -1261,9 +1170,10 @@ int main(int, char **)
|
||||
currentTrackLoading = Audio::instance().getLoadedTrackCount();
|
||||
if (Audio::instance().isLoadingComplete() || (totalTracks > 0 && currentTrackLoading >= totalTracks)) {
|
||||
Audio::instance().shuffle();
|
||||
if (musicEnabled) {
|
||||
Audio::instance().start();
|
||||
}
|
||||
// Defer starting playback until the app has entered the Menu/Playing state.
|
||||
// Actual playback is started below when `musicLoaded` is observed and
|
||||
// the state is Menu or Playing (so the user doesn't hear music while
|
||||
// still on the Loading screen).
|
||||
musicLoaded = true;
|
||||
}
|
||||
}
|
||||
@ -1300,10 +1210,154 @@ int main(int, char **)
|
||||
}
|
||||
else if (state == AppState::Loading)
|
||||
{
|
||||
// Execute one loading step per frame on main thread
|
||||
static int queuedTextureCount = 0;
|
||||
// Execute one loading step per frame on main thread via LoadingManager
|
||||
if (g_loadingStarted.load() && !g_loadingComplete.load()) {
|
||||
if (performLoadingStep()) {
|
||||
g_loadingComplete.store(true);
|
||||
static bool queuedTextures = false;
|
||||
static std::vector<std::string> queuedPaths;
|
||||
if (!queuedTextures) {
|
||||
queuedTextures = true;
|
||||
// Initialize counters and clear previous errors
|
||||
constexpr int baseTasks = 25; // keep same budget as before
|
||||
g_totalLoadingTasks.store(baseTasks);
|
||||
g_loadedTasks.store(0);
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_assetLoadErrorsMutex);
|
||||
g_assetLoadErrors.clear();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_currentLoadingMutex);
|
||||
g_currentLoadingFile.clear();
|
||||
}
|
||||
|
||||
// Initialize background music loading
|
||||
Audio::instance().init();
|
||||
totalTracks = 0;
|
||||
for (int i = 1; i <= 100; ++i) {
|
||||
char base[128];
|
||||
std::snprintf(base, sizeof(base), "assets/music/music%03d", i);
|
||||
std::string path = AssetPath::resolveWithExtensions(base, { ".mp3" });
|
||||
if (path.empty()) break;
|
||||
Audio::instance().addTrackAsync(path);
|
||||
totalTracks++;
|
||||
}
|
||||
g_totalLoadingTasks.store(baseTasks + totalTracks);
|
||||
if (totalTracks > 0) {
|
||||
Audio::instance().startBackgroundLoading();
|
||||
musicLoadingStarted = true;
|
||||
} else {
|
||||
musicLoaded = true;
|
||||
}
|
||||
|
||||
// Initialize fonts (synchronous, cheap)
|
||||
pixelFont.init(AssetPath::resolveWithBase("assets/fonts/Orbitron.ttf"), 22);
|
||||
g_loadedTasks.fetch_add(1);
|
||||
font.init(AssetPath::resolveWithBase("assets/fonts/Exo2.ttf"), 20);
|
||||
g_loadedTasks.fetch_add(1);
|
||||
|
||||
// Queue UI textures for incremental loading
|
||||
queuedPaths = {
|
||||
"assets/images/spacetris.png",
|
||||
"assets/images/spacetris.png", // small logo uses same source
|
||||
"assets/images/main_screen.png",
|
||||
"assets/images/blocks90px_001.bmp",
|
||||
"assets/images/panel_score.png",
|
||||
"assets/images/statistics_panel.png",
|
||||
"assets/images/next_panel.png"
|
||||
};
|
||||
for (auto &p : queuedPaths) {
|
||||
loadingManager.queueTexture(p);
|
||||
}
|
||||
queuedTextureCount = static_cast<int>(queuedPaths.size());
|
||||
|
||||
// Initialize sound effects manager (counts as a loaded task)
|
||||
SoundEffectManager::instance().init();
|
||||
g_loadedTasks.fetch_add(1);
|
||||
|
||||
// Load small set of voice/audio SFX synchronously for now (keeps behavior)
|
||||
const std::vector<std::string> audioIds = {"clear_line","nice_combo","you_fire","well_played","keep_that_ryhtm","great_move","smooth_clear","impressive","triple_strike","amazing","you_re_unstoppable","boom_tetris","wonderful","lets_go","hard_drop","new_level"};
|
||||
for (const auto &id : audioIds) {
|
||||
std::string basePath = "assets/music/" + (id == "hard_drop" ? "hard_drop_001" : id);
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_currentLoadingMutex);
|
||||
g_currentLoadingFile = basePath;
|
||||
}
|
||||
std::string resolved = AssetPath::resolveWithExtensions(basePath, { ".wav", ".mp3" });
|
||||
if (!resolved.empty()) {
|
||||
SoundEffectManager::instance().loadSound(id, resolved);
|
||||
}
|
||||
g_loadedTasks.fetch_add(1);
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_currentLoadingMutex);
|
||||
g_currentLoadingFile.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Perform a single texture loading step via LoadingManager
|
||||
bool texturesDone = loadingManager.update();
|
||||
if (texturesDone) {
|
||||
// Bind loaded textures into the runtime context
|
||||
logoTex = assetLoader.getTexture("assets/images/spacetris.png");
|
||||
logoSmallTex = assetLoader.getTexture("assets/images/spacetris.png");
|
||||
mainScreenTex = assetLoader.getTexture("assets/images/main_screen.png");
|
||||
blocksTex = assetLoader.getTexture("assets/images/blocks90px_001.bmp");
|
||||
scorePanelTex = assetLoader.getTexture("assets/images/panel_score.png");
|
||||
statisticsPanelTex = assetLoader.getTexture("assets/images/statistics_panel.png");
|
||||
nextPanelTex = assetLoader.getTexture("assets/images/next_panel.png");
|
||||
|
||||
auto ensureTextureSize = [&](SDL_Texture* tex, int& outW, int& outH) {
|
||||
if (!tex) return;
|
||||
if (outW > 0 && outH > 0) return;
|
||||
float w = 0.0f, h = 0.0f;
|
||||
if (SDL_GetTextureSize(tex, &w, &h)) {
|
||||
outW = static_cast<int>(std::lround(w));
|
||||
outH = static_cast<int>(std::lround(h));
|
||||
}
|
||||
};
|
||||
|
||||
// If a texture was created by AssetLoader (not legacy IMG_Load),
|
||||
// its stored width/height may still be 0. Query the real size.
|
||||
ensureTextureSize(logoSmallTex, logoSmallW, logoSmallH);
|
||||
ensureTextureSize(mainScreenTex, mainScreenW, mainScreenH);
|
||||
|
||||
// Fallback: if any critical UI texture failed to load via AssetLoader,
|
||||
// load synchronously using the legacy helper so the Menu can render.
|
||||
auto legacyLoad = [&](const std::string& p, SDL_Texture*& outTex, int* outW = nullptr, int* outH = nullptr) {
|
||||
if (!outTex) {
|
||||
outTex = loadTextureFromImage(renderer, p, outW, outH);
|
||||
}
|
||||
};
|
||||
|
||||
legacyLoad("assets/images/spacetris.png", logoTex);
|
||||
legacyLoad("assets/images/spacetris.png", logoSmallTex, &logoSmallW, &logoSmallH);
|
||||
legacyLoad("assets/images/main_screen.png", mainScreenTex, &mainScreenW, &mainScreenH);
|
||||
legacyLoad("assets/images/blocks90px_001.bmp", blocksTex);
|
||||
legacyLoad("assets/images/panel_score.png", scorePanelTex);
|
||||
legacyLoad("assets/images/statistics_panel.png", statisticsPanelTex);
|
||||
legacyLoad("assets/images/next_panel.png", nextPanelTex);
|
||||
|
||||
// If blocks texture failed, create fallback and count it as loaded
|
||||
if (!blocksTex) {
|
||||
blocksTex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 630, 90);
|
||||
SDL_SetRenderTarget(renderer, blocksTex);
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer);
|
||||
for (int i = 0; i < PIECE_COUNT; ++i) {
|
||||
SDL_Color c = COLORS[i + 1];
|
||||
SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a);
|
||||
SDL_FRect rect{(float)(i * 90), 0, 90, 90};
|
||||
SDL_RenderFillRect(renderer, &rect);
|
||||
}
|
||||
SDL_SetRenderTarget(renderer, nullptr);
|
||||
// Do not update global task counter here; textures are accounted
|
||||
// for via the LoadingManager/AssetLoader progress below.
|
||||
}
|
||||
|
||||
// Mark loading complete when music also loaded
|
||||
if (musicLoaded) {
|
||||
g_loadingComplete.store(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1311,6 +1365,13 @@ int main(int, char **)
|
||||
const int totalTasks = g_totalLoadingTasks.load(std::memory_order_acquire);
|
||||
const int musicDone = std::min(totalTracks, currentTrackLoading);
|
||||
int doneTasks = g_loadedTasks.load(std::memory_order_acquire) + musicDone;
|
||||
// Include texture progress reported by the LoadingManager/AssetLoader
|
||||
if (queuedTextureCount > 0) {
|
||||
float texProg = loadingManager.getProgress();
|
||||
int texDone = static_cast<int>(std::floor(texProg * queuedTextureCount + 0.5f));
|
||||
if (texDone > queuedTextureCount) texDone = queuedTextureCount;
|
||||
doneTasks += texDone;
|
||||
}
|
||||
if (doneTasks > totalTasks) doneTasks = totalTasks;
|
||||
if (totalTasks > 0) {
|
||||
loadingProgress = std::min(1.0, double(doneTasks) / double(totalTasks));
|
||||
@ -1696,8 +1757,53 @@ int main(int, char **)
|
||||
}
|
||||
break;
|
||||
case AppState::Menu:
|
||||
// Delegate full menu rendering to MenuState object now
|
||||
menuState->render(renderer, logicalScale, logicalVP);
|
||||
// Ensure overlay is loaded (drawn after highscores so it sits above that layer)
|
||||
if (!mainScreenTex) {
|
||||
mainScreenTex = loadTextureFromImage(renderer, "assets/images/main_screen.png", &mainScreenW, &mainScreenH);
|
||||
}
|
||||
|
||||
// Render menu content that should appear *behind* the overlay (highscores/logo).
|
||||
// Bottom buttons are drawn separately on top.
|
||||
if (menuState) {
|
||||
menuState->drawMainButtonNormally = false;
|
||||
menuState->render(renderer, logicalScale, logicalVP);
|
||||
}
|
||||
|
||||
// Draw main screen overlay above highscores
|
||||
if (mainScreenTex) {
|
||||
SDL_SetRenderViewport(renderer, nullptr);
|
||||
SDL_SetRenderScale(renderer, 1.f, 1.f);
|
||||
float texW = mainScreenW > 0 ? static_cast<float>(mainScreenW) : 0.0f;
|
||||
float texH = mainScreenH > 0 ? static_cast<float>(mainScreenH) : 0.0f;
|
||||
if (texW <= 0.0f || texH <= 0.0f) {
|
||||
float iwf = 0.0f, ihf = 0.0f;
|
||||
if (!SDL_GetTextureSize(mainScreenTex, &iwf, &ihf)) {
|
||||
iwf = ihf = 0.0f;
|
||||
}
|
||||
texW = iwf;
|
||||
texH = ihf;
|
||||
}
|
||||
if (texW > 0.0f && texH > 0.0f) {
|
||||
const float drawH = static_cast<float>(winH);
|
||||
const float scale = drawH / texH;
|
||||
const float drawW = texW * scale;
|
||||
SDL_FRect dst{
|
||||
(winW - drawW) * 0.5f,
|
||||
0.0f,
|
||||
drawW,
|
||||
drawH
|
||||
};
|
||||
SDL_SetTextureBlendMode(mainScreenTex, SDL_BLENDMODE_BLEND);
|
||||
SDL_RenderTexture(renderer, mainScreenTex, nullptr, &dst);
|
||||
}
|
||||
SDL_SetRenderViewport(renderer, &logicalVP);
|
||||
SDL_SetRenderScale(renderer, logicalScale, logicalScale);
|
||||
}
|
||||
|
||||
// Draw bottom menu buttons above the overlay
|
||||
if (menuState) {
|
||||
menuState->renderMainButtonTop(renderer, logicalScale, logicalVP);
|
||||
}
|
||||
break;
|
||||
case AppState::Options:
|
||||
optionsState->render(renderer, logicalScale, logicalVP);
|
||||
@ -1924,43 +2030,6 @@ int main(int, char **)
|
||||
HelpOverlay::Render(renderer, pixelFont, LOGICAL_W, LOGICAL_H, contentOffsetX, contentOffsetY);
|
||||
}
|
||||
|
||||
// Top-layer overlay: render `mainScreenTex` above all other layers when in Menu
|
||||
if (state == AppState::Menu && mainScreenTex) {
|
||||
SDL_SetRenderViewport(renderer, nullptr);
|
||||
SDL_SetRenderScale(renderer, 1.f, 1.f);
|
||||
float texW = mainScreenW > 0 ? static_cast<float>(mainScreenW) : 0.0f;
|
||||
float texH = mainScreenH > 0 ? static_cast<float>(mainScreenH) : 0.0f;
|
||||
if (texW <= 0.0f || texH <= 0.0f) {
|
||||
float iwf = 0.0f, ihf = 0.0f;
|
||||
if (SDL_GetTextureSize(mainScreenTex, &iwf, &ihf) != 0) {
|
||||
iwf = ihf = 0.0f;
|
||||
}
|
||||
texW = iwf;
|
||||
texH = ihf;
|
||||
}
|
||||
if (texW > 0.0f && texH > 0.0f) {
|
||||
const float drawH = static_cast<float>(winH);
|
||||
const float scale = drawH / texH;
|
||||
const float drawW = texW * scale;
|
||||
SDL_FRect dst{
|
||||
(winW - drawW) * 0.5f,
|
||||
0.0f,
|
||||
drawW,
|
||||
drawH
|
||||
};
|
||||
SDL_SetTextureBlendMode(mainScreenTex, SDL_BLENDMODE_BLEND);
|
||||
SDL_RenderTexture(renderer, mainScreenTex, nullptr, &dst);
|
||||
}
|
||||
// Restore logical viewport/scale and draw the main PLAY button above the overlay
|
||||
SDL_SetRenderViewport(renderer, &logicalVP);
|
||||
SDL_SetRenderScale(renderer, logicalScale, logicalScale);
|
||||
if (menuState) {
|
||||
menuState->drawMainButtonNormally = false; // ensure it isn't double-drawn
|
||||
menuState->renderMainButtonTop(renderer, logicalScale, logicalVP);
|
||||
menuState->drawMainButtonNormally = true;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_RenderPresent(renderer);
|
||||
SDL_SetRenderScale(renderer, 1.f, 1.f);
|
||||
}
|
||||
|
||||
39
src/states/LoadingManager.cpp
Normal file
39
src/states/LoadingManager.cpp
Normal file
@ -0,0 +1,39 @@
|
||||
#include "states/LoadingManager.h"
|
||||
#include "app/AssetLoader.h"
|
||||
|
||||
LoadingManager::LoadingManager(AssetLoader* loader)
|
||||
: m_loader(loader)
|
||||
{
|
||||
}
|
||||
|
||||
void LoadingManager::queueTexture(const std::string& path) {
|
||||
if (!m_loader) return;
|
||||
m_loader->queueTexture(path);
|
||||
}
|
||||
|
||||
void LoadingManager::start() {
|
||||
m_started = true;
|
||||
}
|
||||
|
||||
bool LoadingManager::update() {
|
||||
if (!m_loader) return true;
|
||||
// perform a single step on the loader; AssetLoader::performStep returns true when
|
||||
// there are no more queued tasks.
|
||||
bool done = m_loader->performStep();
|
||||
return done;
|
||||
}
|
||||
|
||||
float LoadingManager::getProgress() const {
|
||||
if (!m_loader) return 1.0f;
|
||||
return m_loader->getProgress();
|
||||
}
|
||||
|
||||
std::vector<std::string> LoadingManager::getAndClearErrors() {
|
||||
if (!m_loader) return {};
|
||||
return m_loader->getAndClearErrors();
|
||||
}
|
||||
|
||||
std::string LoadingManager::getCurrentLoading() const {
|
||||
if (!m_loader) return {};
|
||||
return m_loader->getCurrentLoading();
|
||||
}
|
||||
36
src/states/LoadingManager.h
Normal file
36
src/states/LoadingManager.h
Normal file
@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
class AssetLoader;
|
||||
|
||||
// LoadingManager: thin facade over AssetLoader for incremental loading.
|
||||
// Main thread only. Call update() once per frame to perform a single step.
|
||||
class LoadingManager {
|
||||
public:
|
||||
explicit LoadingManager(AssetLoader* loader);
|
||||
|
||||
// Queue a texture path (relative to base path) for loading.
|
||||
void queueTexture(const std::string& path);
|
||||
|
||||
// Start loading (idempotent).
|
||||
void start();
|
||||
|
||||
// Perform a single loading step. Returns true when loading complete.
|
||||
bool update();
|
||||
|
||||
// Progress in [0,1]
|
||||
float getProgress() const;
|
||||
|
||||
// Return and clear any accumulated loading errors.
|
||||
std::vector<std::string> getAndClearErrors();
|
||||
|
||||
// Current path being loaded (or empty)
|
||||
std::string getCurrentLoading() const;
|
||||
|
||||
private:
|
||||
AssetLoader* m_loader = nullptr;
|
||||
bool m_started = false;
|
||||
};
|
||||
Reference in New Issue
Block a user