pantalla de carrega no bloquejant
streaming de audio per evitar precárrega i descompresió a memoria
This commit is contained in:
@@ -87,6 +87,7 @@ set(APP_SOURCES
|
|||||||
source/game/gameplay/tilemap_renderer.cpp
|
source/game/gameplay/tilemap_renderer.cpp
|
||||||
|
|
||||||
# Game - Scenes
|
# Game - Scenes
|
||||||
|
source/game/scenes/boot_loader.cpp
|
||||||
source/game/scenes/credits.cpp
|
source/game/scenes/credits.cpp
|
||||||
source/game/scenes/ending.cpp
|
source/game/scenes/ending.cpp
|
||||||
source/game/scenes/ending2.cpp
|
source/game/scenes/ending2.cpp
|
||||||
|
|||||||
@@ -43,12 +43,16 @@ struct JA_Channel_t {
|
|||||||
|
|
||||||
struct JA_Music_t {
|
struct JA_Music_t {
|
||||||
SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
|
SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
|
||||||
Uint32 length{0};
|
|
||||||
Uint8* buffer{nullptr};
|
// OGG comprimit en memòria. Propietat nostra; es copia des del fitxer una
|
||||||
|
// sola vegada en JA_LoadMusic i es descomprimix en chunks per streaming.
|
||||||
|
Uint8* ogg_data{nullptr};
|
||||||
|
Uint32 ogg_length{0};
|
||||||
|
stb_vorbis* vorbis{nullptr}; // Handle del decoder, viu tot el cicle del JA_Music_t
|
||||||
|
|
||||||
char* filename{nullptr};
|
char* filename{nullptr};
|
||||||
|
|
||||||
int pos{0};
|
int times{0}; // Loops restants (-1 = infinit, 0 = un sol play)
|
||||||
int times{0};
|
|
||||||
SDL_AudioStream* stream{nullptr};
|
SDL_AudioStream* stream{nullptr};
|
||||||
JA_Music_state state{JA_MUSIC_INVALID};
|
JA_Music_state state{JA_MUSIC_INVALID};
|
||||||
};
|
};
|
||||||
@@ -76,6 +80,57 @@ inline void JA_StopMusic();
|
|||||||
inline void JA_StopChannel(const int channel);
|
inline void JA_StopChannel(const int channel);
|
||||||
inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int loop = 0, const int group = 0);
|
inline int JA_PlaySoundOnChannel(JA_Sound_t* sound, const int channel, const int loop = 0, const int group = 0);
|
||||||
|
|
||||||
|
// --- Music streaming internals ---
|
||||||
|
// Bytes-per-sample per canal (sempre s16)
|
||||||
|
static constexpr int JA_MUSIC_BYTES_PER_SAMPLE = 2;
|
||||||
|
// Quants shorts decodifiquem per crida a get_samples_short_interleaved.
|
||||||
|
// 8192 shorts = 4096 samples/channel en estèreo ≈ 85ms de so a 48kHz.
|
||||||
|
static constexpr int JA_MUSIC_CHUNK_SHORTS = 8192;
|
||||||
|
// Umbral d'audio per davant del cursor de reproducció. Mantenim ≥ 0.5 s a
|
||||||
|
// l'SDL_AudioStream per absorbir jitter de frame i evitar underruns.
|
||||||
|
static constexpr float JA_MUSIC_LOW_WATER_SECONDS = 0.5f;
|
||||||
|
|
||||||
|
// Decodifica un chunk del vorbis i el volca a l'stream. Retorna samples
|
||||||
|
// decodificats per canal (0 = EOF de l'stream vorbis).
|
||||||
|
inline int JA_FeedMusicChunk(JA_Music_t* music) {
|
||||||
|
if (!music || !music->vorbis || !music->stream) return 0;
|
||||||
|
|
||||||
|
short chunk[JA_MUSIC_CHUNK_SHORTS];
|
||||||
|
const int channels = music->spec.channels;
|
||||||
|
const int samples_per_channel = stb_vorbis_get_samples_short_interleaved(
|
||||||
|
music->vorbis,
|
||||||
|
channels,
|
||||||
|
chunk,
|
||||||
|
JA_MUSIC_CHUNK_SHORTS);
|
||||||
|
if (samples_per_channel <= 0) return 0;
|
||||||
|
|
||||||
|
const int bytes = samples_per_channel * channels * JA_MUSIC_BYTES_PER_SAMPLE;
|
||||||
|
SDL_PutAudioStreamData(music->stream, chunk, bytes);
|
||||||
|
return samples_per_channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reompli l'stream fins que tinga ≥ JA_MUSIC_LOW_WATER_SECONDS bufferats.
|
||||||
|
// En arribar a EOF del vorbis, aplica el loop (times) o deixa drenar.
|
||||||
|
inline void JA_PumpMusic(JA_Music_t* music) {
|
||||||
|
if (!music || !music->vorbis || !music->stream) return;
|
||||||
|
|
||||||
|
const int bytes_per_second = music->spec.freq * music->spec.channels * JA_MUSIC_BYTES_PER_SAMPLE;
|
||||||
|
const int low_water_bytes = static_cast<int>(JA_MUSIC_LOW_WATER_SECONDS * static_cast<float>(bytes_per_second));
|
||||||
|
|
||||||
|
while (SDL_GetAudioStreamAvailable(music->stream) < low_water_bytes) {
|
||||||
|
const int decoded = JA_FeedMusicChunk(music);
|
||||||
|
if (decoded > 0) continue;
|
||||||
|
|
||||||
|
// EOF: si queden loops, rebobinar; si no, tallar i deixar drenar.
|
||||||
|
if (music->times != 0) {
|
||||||
|
stb_vorbis_seek_start(music->vorbis);
|
||||||
|
if (music->times > 0) music->times--;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Core Functions ---
|
// --- Core Functions ---
|
||||||
|
|
||||||
inline void JA_Update() {
|
inline void JA_Update() {
|
||||||
@@ -93,13 +148,11 @@ inline void JA_Update() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current_music->times != 0) {
|
// Streaming: rellenem l'stream fins al low-water-mark i parem si el
|
||||||
if ((Uint32)SDL_GetAudioStreamAvailable(current_music->stream) < (current_music->length / 2)) {
|
// vorbis s'ha esgotat i no queden loops.
|
||||||
SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length);
|
JA_PumpMusic(current_music);
|
||||||
}
|
if (current_music->times == 0 && SDL_GetAudioStreamAvailable(current_music->stream) == 0) {
|
||||||
if (current_music->times > 0) current_music->times--;
|
JA_StopMusic();
|
||||||
} else {
|
|
||||||
if (SDL_GetAudioStreamAvailable(current_music->stream) == 0) JA_StopMusic();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,19 +192,31 @@ inline void JA_Quit() {
|
|||||||
// --- Music Functions ---
|
// --- Music Functions ---
|
||||||
|
|
||||||
inline JA_Music_t* JA_LoadMusic(const Uint8* buffer, Uint32 length) {
|
inline JA_Music_t* JA_LoadMusic(const Uint8* buffer, Uint32 length) {
|
||||||
JA_Music_t* music = new JA_Music_t();
|
if (!buffer || length == 0) return nullptr;
|
||||||
|
|
||||||
int chan, samplerate;
|
// Còpia del OGG comprimit: stb_vorbis llig de forma persistent aquesta
|
||||||
short* output;
|
// memòria mentre el handle estiga viu, així que hem de posseir-la nosaltres.
|
||||||
music->length = stb_vorbis_decode_memory(buffer, length, &chan, &samplerate, &output) * chan * 2;
|
Uint8* ogg_copy = static_cast<Uint8*>(SDL_malloc(length));
|
||||||
|
if (!ogg_copy) return nullptr;
|
||||||
|
SDL_memcpy(ogg_copy, buffer, length);
|
||||||
|
|
||||||
music->spec.channels = chan;
|
int error = 0;
|
||||||
music->spec.freq = samplerate;
|
stb_vorbis* vorbis = stb_vorbis_open_memory(ogg_copy, static_cast<int>(length), &error, nullptr);
|
||||||
|
if (!vorbis) {
|
||||||
|
SDL_free(ogg_copy);
|
||||||
|
SDL_Log("JA_LoadMusic: stb_vorbis_open_memory failed (error %d)", error);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* music = new JA_Music_t();
|
||||||
|
music->ogg_data = ogg_copy;
|
||||||
|
music->ogg_length = length;
|
||||||
|
music->vorbis = vorbis;
|
||||||
|
|
||||||
|
const stb_vorbis_info info = stb_vorbis_get_info(vorbis);
|
||||||
|
music->spec.channels = info.channels;
|
||||||
|
music->spec.freq = static_cast<int>(info.sample_rate);
|
||||||
music->spec.format = SDL_AUDIO_S16;
|
music->spec.format = SDL_AUDIO_S16;
|
||||||
music->buffer = static_cast<Uint8*>(SDL_malloc(music->length));
|
|
||||||
SDL_memcpy(music->buffer, output, music->length);
|
|
||||||
free(output);
|
|
||||||
music->pos = 0;
|
|
||||||
music->state = JA_MUSIC_STOPPED;
|
music->state = JA_MUSIC_STOPPED;
|
||||||
|
|
||||||
return music;
|
return music;
|
||||||
@@ -190,23 +255,29 @@ inline JA_Music_t* JA_LoadMusic(const char* filename) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline void JA_PlayMusic(JA_Music_t* music, const int loop = -1) {
|
inline void JA_PlayMusic(JA_Music_t* music, const int loop = -1) {
|
||||||
if (!JA_musicEnabled || !music) return; // Añadida comprobación de music
|
if (!JA_musicEnabled || !music || !music->vorbis) return;
|
||||||
|
|
||||||
JA_StopMusic();
|
JA_StopMusic();
|
||||||
|
|
||||||
current_music = music;
|
current_music = music;
|
||||||
current_music->pos = 0;
|
|
||||||
current_music->state = JA_MUSIC_PLAYING;
|
current_music->state = JA_MUSIC_PLAYING;
|
||||||
current_music->times = loop;
|
current_music->times = loop;
|
||||||
|
|
||||||
|
// Rebobinem l'stream de vorbis al principi. Cobreix tant play-per-primera-
|
||||||
|
// vegada com replays/canvis de track que tornen a la mateixa pista.
|
||||||
|
stb_vorbis_seek_start(current_music->vorbis);
|
||||||
|
|
||||||
current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &JA_audioSpec);
|
current_music->stream = SDL_CreateAudioStream(¤t_music->spec, &JA_audioSpec);
|
||||||
if (!current_music->stream) { // Comprobar creación de stream
|
if (!current_music->stream) {
|
||||||
SDL_Log("Failed to create audio stream!");
|
SDL_Log("Failed to create audio stream!");
|
||||||
current_music->state = JA_MUSIC_STOPPED;
|
current_music->state = JA_MUSIC_STOPPED;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length)) printf("[ERROR] SDL_PutAudioStreamData failed!\n");
|
|
||||||
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
|
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
|
||||||
|
|
||||||
|
// Pre-cargem el buffer abans de bindejar per evitar un underrun inicial.
|
||||||
|
JA_PumpMusic(current_music);
|
||||||
|
|
||||||
if (!SDL_BindAudioStream(sdlAudioDevice, current_music->stream)) printf("[ERROR] SDL_BindAudioStream failed!\n");
|
if (!SDL_BindAudioStream(sdlAudioDevice, current_music->stream)) printf("[ERROR] SDL_BindAudioStream failed!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,13 +306,17 @@ inline void JA_ResumeMusic() {
|
|||||||
inline void JA_StopMusic() {
|
inline void JA_StopMusic() {
|
||||||
if (!current_music || current_music->state == JA_MUSIC_INVALID || current_music->state == JA_MUSIC_STOPPED) return;
|
if (!current_music || current_music->state == JA_MUSIC_INVALID || current_music->state == JA_MUSIC_STOPPED) return;
|
||||||
|
|
||||||
current_music->pos = 0;
|
|
||||||
current_music->state = JA_MUSIC_STOPPED;
|
current_music->state = JA_MUSIC_STOPPED;
|
||||||
if (current_music->stream) {
|
if (current_music->stream) {
|
||||||
SDL_DestroyAudioStream(current_music->stream);
|
SDL_DestroyAudioStream(current_music->stream);
|
||||||
current_music->stream = nullptr;
|
current_music->stream = nullptr;
|
||||||
}
|
}
|
||||||
// No liberamos filename aquí, se debería liberar en JA_DeleteMusic
|
// Deixem el handle de vorbis viu — es tanca en JA_DeleteMusic.
|
||||||
|
// Rebobinem perquè un futur JA_PlayMusic comence des del principi.
|
||||||
|
if (current_music->vorbis) {
|
||||||
|
stb_vorbis_seek_start(current_music->vorbis);
|
||||||
|
}
|
||||||
|
// No liberem filename aquí; es fa en JA_DeleteMusic.
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void JA_FadeOutMusic(const int milliseconds) {
|
inline void JA_FadeOutMusic(const int milliseconds) {
|
||||||
@@ -267,9 +342,10 @@ inline void JA_DeleteMusic(JA_Music_t* music) {
|
|||||||
JA_StopMusic();
|
JA_StopMusic();
|
||||||
current_music = nullptr;
|
current_music = nullptr;
|
||||||
}
|
}
|
||||||
SDL_free(music->buffer);
|
|
||||||
if (music->stream) SDL_DestroyAudioStream(music->stream);
|
if (music->stream) SDL_DestroyAudioStream(music->stream);
|
||||||
free(music->filename); // filename se libera aquí
|
if (music->vorbis) stb_vorbis_close(music->vorbis);
|
||||||
|
SDL_free(music->ogg_data);
|
||||||
|
free(music->filename); // filename es libera aquí
|
||||||
delete music;
|
delete music;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,17 +357,14 @@ inline float JA_SetMusicVolume(float volume) {
|
|||||||
return JA_musicVolume;
|
return JA_musicVolume;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void JA_SetMusicPosition(float value) {
|
inline void JA_SetMusicPosition(float /*value*/) {
|
||||||
if (!current_music) return;
|
// No implementat amb el backend de streaming. Mai va arribar a usar-se
|
||||||
current_music->pos = value * current_music->spec.freq;
|
// en el codi existent, així que es manté com a stub.
|
||||||
// Nota: Esta implementación de 'pos' no parece usarse en JA_Update para
|
|
||||||
// el streaming. El streaming siempre parece empezar desde el principio.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline float JA_GetMusicPosition() {
|
inline float JA_GetMusicPosition() {
|
||||||
if (!current_music) return 0;
|
// Veure nota a JA_SetMusicPosition.
|
||||||
return float(current_music->pos) / float(current_music->spec.freq);
|
return 0.0f;
|
||||||
// Nota: Ver `JA_SetMusicPosition`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void JA_EnableMusic(const bool value) {
|
inline void JA_EnableMusic(const bool value) {
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#ifdef __EMSCRIPTEN__
|
|
||||||
#include <emscripten/emscripten.h> // Para emscripten_sleep
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <algorithm> // Para find_if
|
#include <algorithm> // Para find_if
|
||||||
#include <cstdlib> // Para exit, size_t
|
#include <cstdlib> // Para exit, size_t
|
||||||
#include <fstream> // Para ifstream, istreambuf_iterator
|
#include <fstream> // Para ifstream, istreambuf_iterator
|
||||||
@@ -42,10 +38,10 @@ namespace Resource {
|
|||||||
// [SINGLETON] Con este método obtenemos el objeto cache y podemos trabajar con él
|
// [SINGLETON] Con este método obtenemos el objeto cache y podemos trabajar con él
|
||||||
auto Cache::get() -> Cache* { return Cache::cache; }
|
auto Cache::get() -> Cache* { return Cache::cache; }
|
||||||
|
|
||||||
// Constructor
|
// Constructor — no dispara la carga. Director llama a beginLoad() + loadStep()
|
||||||
|
// desde iterate() para que el bucle SDL3 esté vivo durante la carga.
|
||||||
Cache::Cache()
|
Cache::Cache()
|
||||||
: loading_text_(Screen::get()->getText()) {
|
: loading_text_(Screen::get()->getText()) {
|
||||||
load();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vacia todos los vectores de recursos
|
// Vacia todos los vectores de recursos
|
||||||
@@ -57,12 +53,11 @@ namespace Resource {
|
|||||||
text_files_.clear();
|
text_files_.clear();
|
||||||
texts_.clear();
|
texts_.clear();
|
||||||
animations_.clear();
|
animations_.clear();
|
||||||
|
rooms_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carga todos los recursos
|
// Carga todos los recursos de golpe (usado solo por reload() en hot-reload de debug)
|
||||||
void Cache::load() {
|
void Cache::load() {
|
||||||
// Nota: el overlay de debug (RenderInfo) se inicializa después de esta carga,
|
|
||||||
// por lo que updateZoomFactor() se llamará correctamente en RenderInfo::init().
|
|
||||||
calculateTotal();
|
calculateTotal();
|
||||||
Screen::get()->setBorderColor(static_cast<Uint8>(PaletteColor::BLACK));
|
Screen::get()->setBorderColor(static_cast<Uint8>(PaletteColor::BLACK));
|
||||||
std::cout << "\n** LOADING RESOURCES" << '\n';
|
std::cout << "\n** LOADING RESOURCES" << '\n';
|
||||||
@@ -77,7 +72,162 @@ namespace Resource {
|
|||||||
std::cout << "\n** RESOURCES LOADED" << '\n';
|
std::cout << "\n** RESOURCES LOADED" << '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recarga todos los recursos
|
// Prepara el loader incremental. Director lo llama una vez tras Cache::init().
|
||||||
|
void Cache::beginLoad() {
|
||||||
|
calculateTotal();
|
||||||
|
Screen::get()->setBorderColor(static_cast<Uint8>(PaletteColor::BLACK));
|
||||||
|
std::cout << "\n** LOADING RESOURCES (incremental)" << '\n';
|
||||||
|
stage_ = LoadStage::SOUNDS;
|
||||||
|
stage_index_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Cache::isLoadDone() const -> bool {
|
||||||
|
return stage_ == LoadStage::DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carga assets hasta agotar el presupuesto de tiempo o completar todas las etapas.
|
||||||
|
// Devuelve true cuando ya no queda nada por cargar.
|
||||||
|
auto Cache::loadStep(int budget_ms) -> bool {
|
||||||
|
if (stage_ == LoadStage::DONE) return true;
|
||||||
|
|
||||||
|
const Uint64 start_ns = SDL_GetTicksNS();
|
||||||
|
const Uint64 budget_ns = static_cast<Uint64>(budget_ms) * 1'000'000ULL;
|
||||||
|
|
||||||
|
auto listOf = [](List::Type t) { return List::get()->getListByType(t); };
|
||||||
|
|
||||||
|
while (stage_ != LoadStage::DONE) {
|
||||||
|
switch (stage_) {
|
||||||
|
case LoadStage::SOUNDS: {
|
||||||
|
auto list = listOf(List::Type::SOUND);
|
||||||
|
if (stage_index_ == 0) {
|
||||||
|
std::cout << "\n>> SOUND FILES" << '\n';
|
||||||
|
sounds_.clear();
|
||||||
|
}
|
||||||
|
if (stage_index_ >= list.size()) {
|
||||||
|
stage_ = LoadStage::MUSICS;
|
||||||
|
stage_index_ = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
loadOneSound(stage_index_++);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LoadStage::MUSICS: {
|
||||||
|
auto list = listOf(List::Type::MUSIC);
|
||||||
|
if (stage_index_ == 0) {
|
||||||
|
std::cout << "\n>> MUSIC FILES" << '\n';
|
||||||
|
musics_.clear();
|
||||||
|
}
|
||||||
|
if (stage_index_ >= list.size()) {
|
||||||
|
stage_ = LoadStage::SURFACES;
|
||||||
|
stage_index_ = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
loadOneMusic(stage_index_++);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LoadStage::SURFACES: {
|
||||||
|
auto list = listOf(List::Type::BITMAP);
|
||||||
|
if (stage_index_ == 0) {
|
||||||
|
std::cout << "\n>> SURFACES" << '\n';
|
||||||
|
surfaces_.clear();
|
||||||
|
}
|
||||||
|
if (stage_index_ >= list.size()) {
|
||||||
|
stage_ = LoadStage::SURFACES_POST;
|
||||||
|
stage_index_ = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
loadOneSurface(stage_index_++);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LoadStage::SURFACES_POST: {
|
||||||
|
finalizeSurfaces();
|
||||||
|
stage_ = LoadStage::PALETTES;
|
||||||
|
stage_index_ = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LoadStage::PALETTES: {
|
||||||
|
auto list = listOf(List::Type::PALETTE);
|
||||||
|
if (stage_index_ == 0) {
|
||||||
|
std::cout << "\n>> PALETTES" << '\n';
|
||||||
|
palettes_.clear();
|
||||||
|
}
|
||||||
|
if (stage_index_ >= list.size()) {
|
||||||
|
stage_ = LoadStage::TEXT_FILES;
|
||||||
|
stage_index_ = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
loadOnePalette(stage_index_++);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LoadStage::TEXT_FILES: {
|
||||||
|
auto list = listOf(List::Type::FONT);
|
||||||
|
if (stage_index_ == 0) {
|
||||||
|
std::cout << "\n>> TEXT FILES" << '\n';
|
||||||
|
text_files_.clear();
|
||||||
|
}
|
||||||
|
if (stage_index_ >= list.size()) {
|
||||||
|
stage_ = LoadStage::ANIMATIONS;
|
||||||
|
stage_index_ = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
loadOneTextFile(stage_index_++);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LoadStage::ANIMATIONS: {
|
||||||
|
auto list = listOf(List::Type::ANIMATION);
|
||||||
|
if (stage_index_ == 0) {
|
||||||
|
std::cout << "\n>> ANIMATIONS" << '\n';
|
||||||
|
animations_.clear();
|
||||||
|
}
|
||||||
|
if (stage_index_ >= list.size()) {
|
||||||
|
stage_ = LoadStage::ROOMS;
|
||||||
|
stage_index_ = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
loadOneAnimation(stage_index_++);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LoadStage::ROOMS: {
|
||||||
|
auto list = listOf(List::Type::ROOM);
|
||||||
|
if (stage_index_ == 0) {
|
||||||
|
std::cout << "\n>> ROOMS" << '\n';
|
||||||
|
rooms_.clear();
|
||||||
|
}
|
||||||
|
if (stage_index_ >= list.size()) {
|
||||||
|
stage_ = LoadStage::TEXTS;
|
||||||
|
stage_index_ = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
loadOneRoom(stage_index_++);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LoadStage::TEXTS: {
|
||||||
|
// createText itera sobre una lista fija de 5 fuentes
|
||||||
|
constexpr size_t TEXT_COUNT = 5;
|
||||||
|
if (stage_index_ == 0) {
|
||||||
|
std::cout << "\n>> CREATING TEXT_OBJECTS" << '\n';
|
||||||
|
texts_.clear();
|
||||||
|
}
|
||||||
|
if (stage_index_ >= TEXT_COUNT) {
|
||||||
|
stage_ = LoadStage::DONE;
|
||||||
|
stage_index_ = 0;
|
||||||
|
std::cout << "\n** RESOURCES LOADED" << '\n';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
createOneText(stage_index_++);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LoadStage::DONE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((SDL_GetTicksNS() - start_ns) >= budget_ns) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stage_ == LoadStage::DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recarga todos los recursos (síncrono, solo para hot-reload de debug)
|
||||||
void Cache::reload() {
|
void Cache::reload() {
|
||||||
clear();
|
clear();
|
||||||
load();
|
load();
|
||||||
@@ -221,96 +371,96 @@ namespace Resource {
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carga los sonidos
|
// Lista fija de text objects. Compartida entre createText() y createOneText(i).
|
||||||
void Cache::loadSounds() { // NOLINT(readability-convert-member-functions-to-static)
|
namespace {
|
||||||
std::cout << "\n>> SOUND FILES" << '\n';
|
struct TextObjectInfo {
|
||||||
|
std::string key; // Identificador del recurso
|
||||||
|
std::string texture_file; // Nombre del archivo de textura
|
||||||
|
std::string text_file; // Nombre del archivo de texto
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<TextObjectInfo>& getTextObjectInfos() {
|
||||||
|
static const std::vector<TextObjectInfo> info = {
|
||||||
|
{.key = "aseprite", .texture_file = "aseprite.gif", .text_file = "aseprite.fnt"},
|
||||||
|
{.key = "gauntlet", .texture_file = "gauntlet.gif", .text_file = "gauntlet.fnt"},
|
||||||
|
{.key = "smb2", .texture_file = "smb2.gif", .text_file = "smb2.fnt"},
|
||||||
|
{.key = "subatomic", .texture_file = "subatomic.gif", .text_file = "subatomic.fnt"},
|
||||||
|
{.key = "8bithud", .texture_file = "8bithud.gif", .text_file = "8bithud.fnt"}};
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// --- Helpers incrementales (un asset por llamada) ---
|
||||||
|
|
||||||
|
void Cache::loadOneSound(size_t index) {
|
||||||
auto list = List::get()->getListByType(List::Type::SOUND);
|
auto list = List::get()->getListByType(List::Type::SOUND);
|
||||||
sounds_.clear();
|
const auto& l = list[index];
|
||||||
|
try {
|
||||||
|
auto name = getFileName(l);
|
||||||
|
setCurrentLoading(name);
|
||||||
|
JA_Sound_t* sound = nullptr;
|
||||||
|
|
||||||
for (const auto& l : list) {
|
auto audio_data = Helper::loadFile(l);
|
||||||
try {
|
if (!audio_data.empty()) {
|
||||||
auto name = getFileName(l);
|
sound = JA_LoadSound(audio_data.data(), static_cast<Uint32>(audio_data.size()));
|
||||||
setCurrentLoading(name);
|
|
||||||
JA_Sound_t* sound = nullptr;
|
|
||||||
|
|
||||||
// Try loading from resource pack first
|
|
||||||
auto audio_data = Helper::loadFile(l);
|
|
||||||
if (!audio_data.empty()) {
|
|
||||||
sound = JA_LoadSound(audio_data.data(), static_cast<Uint32>(audio_data.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to file path if memory loading failed
|
|
||||||
if (sound == nullptr) {
|
|
||||||
sound = JA_LoadSound(l.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sound == nullptr) {
|
|
||||||
throw std::runtime_error("Failed to decode audio file");
|
|
||||||
}
|
|
||||||
|
|
||||||
sounds_.emplace_back(SoundResource{.name = name, .sound = sound});
|
|
||||||
printWithDots("Sound : ", name, "[ LOADED ]");
|
|
||||||
updateLoadingProgress();
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
throwLoadError("SOUND", l, e);
|
|
||||||
}
|
}
|
||||||
|
if (sound == nullptr) {
|
||||||
|
sound = JA_LoadSound(l.c_str());
|
||||||
|
}
|
||||||
|
if (sound == nullptr) {
|
||||||
|
throw std::runtime_error("Failed to decode audio file");
|
||||||
|
}
|
||||||
|
|
||||||
|
sounds_.emplace_back(SoundResource{.name = name, .sound = sound});
|
||||||
|
printWithDots("Sound : ", name, "[ LOADED ]");
|
||||||
|
updateLoadingProgress();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
throwLoadError("SOUND", l, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carga las musicas
|
void Cache::loadOneMusic(size_t index) {
|
||||||
void Cache::loadMusics() { // NOLINT(readability-convert-member-functions-to-static)
|
|
||||||
std::cout << "\n>> MUSIC FILES" << '\n';
|
|
||||||
auto list = List::get()->getListByType(List::Type::MUSIC);
|
auto list = List::get()->getListByType(List::Type::MUSIC);
|
||||||
musics_.clear();
|
const auto& l = list[index];
|
||||||
|
try {
|
||||||
|
auto name = getFileName(l);
|
||||||
|
setCurrentLoading(name);
|
||||||
|
JA_Music_t* music = nullptr;
|
||||||
|
|
||||||
for (const auto& l : list) {
|
auto audio_data = Helper::loadFile(l);
|
||||||
try {
|
if (!audio_data.empty()) {
|
||||||
auto name = getFileName(l);
|
music = JA_LoadMusic(audio_data.data(), static_cast<Uint32>(audio_data.size()));
|
||||||
setCurrentLoading(name);
|
|
||||||
JA_Music_t* music = nullptr;
|
|
||||||
|
|
||||||
// Try loading from resource pack first
|
|
||||||
auto audio_data = Helper::loadFile(l);
|
|
||||||
if (!audio_data.empty()) {
|
|
||||||
music = JA_LoadMusic(audio_data.data(), static_cast<Uint32>(audio_data.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to file path if memory loading failed
|
|
||||||
if (music == nullptr) {
|
|
||||||
music = JA_LoadMusic(l.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (music == nullptr) {
|
|
||||||
throw std::runtime_error("Failed to decode music file");
|
|
||||||
}
|
|
||||||
|
|
||||||
musics_.emplace_back(MusicResource{.name = name, .music = music});
|
|
||||||
printWithDots("Music : ", name, "[ LOADED ]");
|
|
||||||
updateLoadingProgress(1);
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
throwLoadError("MUSIC", l, e);
|
|
||||||
}
|
}
|
||||||
|
if (music == nullptr) {
|
||||||
|
music = JA_LoadMusic(l.c_str());
|
||||||
|
}
|
||||||
|
if (music == nullptr) {
|
||||||
|
throw std::runtime_error("Failed to decode music file");
|
||||||
|
}
|
||||||
|
|
||||||
|
musics_.emplace_back(MusicResource{.name = name, .music = music});
|
||||||
|
printWithDots("Music : ", name, "[ LOADED ]");
|
||||||
|
updateLoadingProgress();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
throwLoadError("MUSIC", l, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carga las texturas
|
void Cache::loadOneSurface(size_t index) {
|
||||||
void Cache::loadSurfaces() { // NOLINT(readability-convert-member-functions-to-static)
|
|
||||||
std::cout << "\n>> SURFACES" << '\n';
|
|
||||||
auto list = List::get()->getListByType(List::Type::BITMAP);
|
auto list = List::get()->getListByType(List::Type::BITMAP);
|
||||||
surfaces_.clear();
|
const auto& l = list[index];
|
||||||
|
try {
|
||||||
for (const auto& l : list) {
|
auto name = getFileName(l);
|
||||||
try {
|
setCurrentLoading(name);
|
||||||
auto name = getFileName(l);
|
surfaces_.emplace_back(SurfaceResource{.name = name, .surface = std::make_shared<Surface>(l)});
|
||||||
setCurrentLoading(name);
|
surfaces_.back().surface->setTransparentColor(0);
|
||||||
surfaces_.emplace_back(SurfaceResource{.name = name, .surface = std::make_shared<Surface>(l)});
|
updateLoadingProgress();
|
||||||
surfaces_.back().surface->setTransparentColor(0);
|
} catch (const std::exception& e) {
|
||||||
updateLoadingProgress();
|
throwLoadError("BITMAP", l, e);
|
||||||
} catch (const std::exception& e) {
|
|
||||||
throwLoadError("BITMAP", l, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cache::finalizeSurfaces() {
|
||||||
// Reconfigura el color transparente de algunas surfaces
|
// Reconfigura el color transparente de algunas surfaces
|
||||||
getSurface("loading_screen_color.gif")->setTransparentColor();
|
getSurface("loading_screen_color.gif")->setTransparentColor();
|
||||||
getSurface("ending1.gif")->setTransparentColor();
|
getSurface("ending1.gif")->setTransparentColor();
|
||||||
@@ -321,108 +471,132 @@ namespace Resource {
|
|||||||
getSurface("standard.gif")->setTransparentColor(16);
|
getSurface("standard.gif")->setTransparentColor(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carga las paletas
|
void Cache::loadOnePalette(size_t index) {
|
||||||
void Cache::loadPalettes() { // NOLINT(readability-convert-member-functions-to-static)
|
auto list = List::get()->getListByType(List::Type::PALETTE);
|
||||||
|
const auto& l = list[index];
|
||||||
|
try {
|
||||||
|
auto name = getFileName(l);
|
||||||
|
setCurrentLoading(name);
|
||||||
|
palettes_.emplace_back(ResourcePalette{.name = name, .palette = readPalFile(l)});
|
||||||
|
updateLoadingProgress();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
throwLoadError("PALETTE", l, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cache::loadOneTextFile(size_t index) {
|
||||||
|
auto list = List::get()->getListByType(List::Type::FONT);
|
||||||
|
const auto& l = list[index];
|
||||||
|
try {
|
||||||
|
auto name = getFileName(l);
|
||||||
|
setCurrentLoading(name);
|
||||||
|
text_files_.emplace_back(TextFileResource{.name = name, .text_file = Text::loadTextFile(l)});
|
||||||
|
updateLoadingProgress();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
throwLoadError("FONT", l, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cache::loadOneAnimation(size_t index) {
|
||||||
|
auto list = List::get()->getListByType(List::Type::ANIMATION);
|
||||||
|
const auto& l = list[index];
|
||||||
|
try {
|
||||||
|
auto name = getFileName(l);
|
||||||
|
setCurrentLoading(name);
|
||||||
|
|
||||||
|
auto yaml_bytes = Helper::loadFile(l);
|
||||||
|
if (yaml_bytes.empty()) {
|
||||||
|
throw std::runtime_error("File is empty or could not be loaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
animations_.emplace_back(AnimationResource{.name = name, .yaml_data = yaml_bytes});
|
||||||
|
printWithDots("Animation : ", name, "[ LOADED ]");
|
||||||
|
updateLoadingProgress();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
throwLoadError("ANIMATION", l, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cache::loadOneRoom(size_t index) {
|
||||||
|
auto list = List::get()->getListByType(List::Type::ROOM);
|
||||||
|
const auto& l = list[index];
|
||||||
|
try {
|
||||||
|
auto name = getFileName(l);
|
||||||
|
setCurrentLoading(name);
|
||||||
|
rooms_.emplace_back(RoomResource{.name = name, .room = std::make_shared<Room::Data>(Room::loadYAML(l))});
|
||||||
|
printWithDots("Room : ", name, "[ LOADED ]");
|
||||||
|
updateLoadingProgress();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
throwLoadError("ROOM", l, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cache::createOneText(size_t index) {
|
||||||
|
const auto& infos = getTextObjectInfos();
|
||||||
|
const auto& res_info = infos[index];
|
||||||
|
texts_.emplace_back(TextResource{
|
||||||
|
.name = res_info.key,
|
||||||
|
.text = std::make_shared<Text>(getSurface(res_info.texture_file), getTextFile(res_info.text_file))});
|
||||||
|
printWithDots("Text : ", res_info.key, "[ DONE ]");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Bucles completos (solo usados por reload() síncrono) ---
|
||||||
|
|
||||||
|
void Cache::loadSounds() {
|
||||||
|
std::cout << "\n>> SOUND FILES" << '\n';
|
||||||
|
auto list = List::get()->getListByType(List::Type::SOUND);
|
||||||
|
sounds_.clear();
|
||||||
|
for (size_t i = 0; i < list.size(); ++i) loadOneSound(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cache::loadMusics() {
|
||||||
|
std::cout << "\n>> MUSIC FILES" << '\n';
|
||||||
|
auto list = List::get()->getListByType(List::Type::MUSIC);
|
||||||
|
musics_.clear();
|
||||||
|
for (size_t i = 0; i < list.size(); ++i) loadOneMusic(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cache::loadSurfaces() {
|
||||||
|
std::cout << "\n>> SURFACES" << '\n';
|
||||||
|
auto list = List::get()->getListByType(List::Type::BITMAP);
|
||||||
|
surfaces_.clear();
|
||||||
|
for (size_t i = 0; i < list.size(); ++i) loadOneSurface(i);
|
||||||
|
finalizeSurfaces();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cache::loadPalettes() {
|
||||||
std::cout << "\n>> PALETTES" << '\n';
|
std::cout << "\n>> PALETTES" << '\n';
|
||||||
auto list = List::get()->getListByType(List::Type::PALETTE);
|
auto list = List::get()->getListByType(List::Type::PALETTE);
|
||||||
palettes_.clear();
|
palettes_.clear();
|
||||||
|
for (size_t i = 0; i < list.size(); ++i) loadOnePalette(i);
|
||||||
for (const auto& l : list) {
|
|
||||||
try {
|
|
||||||
auto name = getFileName(l);
|
|
||||||
setCurrentLoading(name);
|
|
||||||
palettes_.emplace_back(ResourcePalette{.name = name, .palette = readPalFile(l)});
|
|
||||||
updateLoadingProgress();
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
throwLoadError("PALETTE", l, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carga los ficheros de texto
|
void Cache::loadTextFiles() {
|
||||||
void Cache::loadTextFiles() { // NOLINT(readability-convert-member-functions-to-static)
|
|
||||||
std::cout << "\n>> TEXT FILES" << '\n';
|
std::cout << "\n>> TEXT FILES" << '\n';
|
||||||
auto list = List::get()->getListByType(List::Type::FONT);
|
auto list = List::get()->getListByType(List::Type::FONT);
|
||||||
text_files_.clear();
|
text_files_.clear();
|
||||||
|
for (size_t i = 0; i < list.size(); ++i) loadOneTextFile(i);
|
||||||
for (const auto& l : list) {
|
|
||||||
try {
|
|
||||||
auto name = getFileName(l);
|
|
||||||
setCurrentLoading(name);
|
|
||||||
text_files_.emplace_back(TextFileResource{.name = name, .text_file = Text::loadTextFile(l)});
|
|
||||||
updateLoadingProgress();
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
throwLoadError("FONT", l, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carga las animaciones
|
void Cache::loadAnimations() {
|
||||||
void Cache::loadAnimations() { // NOLINT(readability-convert-member-functions-to-static)
|
|
||||||
std::cout << "\n>> ANIMATIONS" << '\n';
|
std::cout << "\n>> ANIMATIONS" << '\n';
|
||||||
auto list = List::get()->getListByType(List::Type::ANIMATION);
|
auto list = List::get()->getListByType(List::Type::ANIMATION);
|
||||||
animations_.clear();
|
animations_.clear();
|
||||||
|
for (size_t i = 0; i < list.size(); ++i) loadOneAnimation(i);
|
||||||
for (const auto& l : list) {
|
|
||||||
try {
|
|
||||||
auto name = getFileName(l);
|
|
||||||
setCurrentLoading(name);
|
|
||||||
|
|
||||||
// Cargar bytes del archivo YAML sin parsear (carga lazy)
|
|
||||||
auto yaml_bytes = Helper::loadFile(l);
|
|
||||||
|
|
||||||
if (yaml_bytes.empty()) {
|
|
||||||
throw std::runtime_error("File is empty or could not be loaded");
|
|
||||||
}
|
|
||||||
|
|
||||||
animations_.emplace_back(AnimationResource{.name = name, .yaml_data = yaml_bytes});
|
|
||||||
printWithDots("Animation : ", name, "[ LOADED ]");
|
|
||||||
updateLoadingProgress();
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
throwLoadError("ANIMATION", l, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carga las habitaciones desde archivos YAML
|
void Cache::loadRooms() {
|
||||||
void Cache::loadRooms() { // NOLINT(readability-convert-member-functions-to-static)
|
|
||||||
std::cout << "\n>> ROOMS" << '\n';
|
std::cout << "\n>> ROOMS" << '\n';
|
||||||
auto list = List::get()->getListByType(List::Type::ROOM);
|
auto list = List::get()->getListByType(List::Type::ROOM);
|
||||||
rooms_.clear();
|
rooms_.clear();
|
||||||
|
for (size_t i = 0; i < list.size(); ++i) loadOneRoom(i);
|
||||||
for (const auto& l : list) {
|
|
||||||
try {
|
|
||||||
auto name = getFileName(l);
|
|
||||||
setCurrentLoading(name);
|
|
||||||
rooms_.emplace_back(RoomResource{.name = name, .room = std::make_shared<Room::Data>(Room::loadYAML(l))});
|
|
||||||
printWithDots("Room : ", name, "[ LOADED ]");
|
|
||||||
updateLoadingProgress();
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
throwLoadError("ROOM", l, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Cache::createText() { // NOLINT(readability-convert-member-functions-to-static)
|
void Cache::createText() {
|
||||||
struct ResourceInfo {
|
|
||||||
std::string key; // Identificador del recurso
|
|
||||||
std::string texture_file; // Nombre del archivo de textura
|
|
||||||
std::string text_file; // Nombre del archivo de texto
|
|
||||||
};
|
|
||||||
|
|
||||||
std::cout << "\n>> CREATING TEXT_OBJECTS" << '\n';
|
std::cout << "\n>> CREATING TEXT_OBJECTS" << '\n';
|
||||||
|
texts_.clear();
|
||||||
std::vector<ResourceInfo> resources = {
|
const auto& infos = getTextObjectInfos();
|
||||||
{.key = "aseprite", .texture_file = "aseprite.gif", .text_file = "aseprite.fnt"},
|
for (size_t i = 0; i < infos.size(); ++i) createOneText(i);
|
||||||
{.key = "gauntlet", .texture_file = "gauntlet.gif", .text_file = "gauntlet.fnt"},
|
|
||||||
{.key = "smb2", .texture_file = "smb2.gif", .text_file = "smb2.fnt"},
|
|
||||||
{.key = "subatomic", .texture_file = "subatomic.gif", .text_file = "subatomic.fnt"},
|
|
||||||
{.key = "8bithud", .texture_file = "8bithud.gif", .text_file = "8bithud.fnt"}};
|
|
||||||
|
|
||||||
for (const auto& res_info : resources) {
|
|
||||||
texts_.emplace_back(TextResource{.name = res_info.key, .text = std::make_shared<Text>(getSurface(res_info.texture_file), getTextFile(res_info.text_file))});
|
|
||||||
printWithDots("Text : ", res_info.key, "[ DONE ]");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vacía el vector de sonidos
|
// Vacía el vector de sonidos
|
||||||
@@ -512,7 +686,6 @@ namespace Resource {
|
|||||||
SDL_FRect rect_full = {.x = X_PADDING, .y = BAR_POSITION, .w = FULL_BAR_WIDTH, .h = BAR_HEIGHT};
|
SDL_FRect rect_full = {.x = X_PADDING, .y = BAR_POSITION, .w = FULL_BAR_WIDTH, .h = BAR_HEIGHT};
|
||||||
surface->fillRect(&rect_full, BAR_COLOR);
|
surface->fillRect(&rect_full, BAR_COLOR);
|
||||||
|
|
||||||
#if defined(__EMSCRIPTEN__) || defined(_DEBUG)
|
|
||||||
// Mostra el nom del recurs que està a punt de carregar-se, centrat sobre la barra
|
// Mostra el nom del recurs que està a punt de carregar-se, centrat sobre la barra
|
||||||
if (!current_loading_name_.empty()) {
|
if (!current_loading_name_.empty()) {
|
||||||
const float TEXT_Y = BAR_POSITION - static_cast<float>(TEXT_HEIGHT) - 2.0F;
|
const float TEXT_Y = BAR_POSITION - static_cast<float>(TEXT_HEIGHT) - 2.0F;
|
||||||
@@ -522,51 +695,19 @@ namespace Resource {
|
|||||||
current_loading_name_,
|
current_loading_name_,
|
||||||
LOADING_TEXT_COLOR);
|
LOADING_TEXT_COLOR);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
Screen::get()->render();
|
Screen::get()->render();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Desa el nom del recurs que s'està a punt de carregar i repinta immediatament.
|
// Guarda el nombre del recurso que se está a punto de cargar. El repintado
|
||||||
// A wasm/debug serveix per veure exactament en quin fitxer es penja la càrrega.
|
// lo hace el BootLoader (una vez por frame) — aquí solo se actualiza el estado.
|
||||||
void Cache::setCurrentLoading(const std::string& name) {
|
void Cache::setCurrentLoading(const std::string& name) {
|
||||||
current_loading_name_ = name;
|
current_loading_name_ = name;
|
||||||
#if defined(__EMSCRIPTEN__) || defined(_DEBUG)
|
|
||||||
renderProgress();
|
|
||||||
checkEvents();
|
|
||||||
#endif
|
|
||||||
#ifdef __EMSCRIPTEN__
|
|
||||||
// Cedeix el control al navegador perquè pinte el canvas i processe
|
|
||||||
// events. Sense això, el thread principal queda bloquejat durant tota
|
|
||||||
// la precàrrega i el jugador només veu pantalla negra.
|
|
||||||
emscripten_sleep(0);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comprueba los eventos de la pantalla de carga
|
// Incrementa el contador de recursos cargados
|
||||||
void Cache::checkEvents() {
|
void Cache::updateLoadingProgress() {
|
||||||
SDL_Event event;
|
|
||||||
while (SDL_PollEvent(&event)) {
|
|
||||||
switch (event.type) {
|
|
||||||
case SDL_EVENT_QUIT:
|
|
||||||
exit(0);
|
|
||||||
break;
|
|
||||||
case SDL_EVENT_KEY_DOWN:
|
|
||||||
if (event.key.key == SDLK_ESCAPE) {
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Actualiza el progreso de carga
|
|
||||||
void Cache::updateLoadingProgress(int steps) {
|
|
||||||
count_.add(1);
|
count_.add(1);
|
||||||
if (count_.loaded % steps == 0 || count_.loaded == count_.total) {
|
|
||||||
renderProgress();
|
|
||||||
}
|
|
||||||
checkEvents();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Resource
|
} // namespace Resource
|
||||||
|
|||||||
@@ -25,7 +25,13 @@ namespace Resource {
|
|||||||
auto getRoom(const std::string& name) -> std::shared_ptr<Room::Data>;
|
auto getRoom(const std::string& name) -> std::shared_ptr<Room::Data>;
|
||||||
auto getRooms() -> std::vector<RoomResource>&;
|
auto getRooms() -> std::vector<RoomResource>&;
|
||||||
|
|
||||||
void reload(); // Recarga todos los recursos
|
// --- Incremental loading (Director drives this from iterate()) ---
|
||||||
|
void beginLoad(); // Prepara el estado del loader incremental
|
||||||
|
auto loadStep(int budget_ms) -> bool; // Carga assets durante budget_ms; devuelve true si ha terminado
|
||||||
|
void renderProgress(); // Dibuja la barra de progreso (usada por BootLoader)
|
||||||
|
[[nodiscard]] auto isLoadDone() const -> bool;
|
||||||
|
|
||||||
|
void reload(); // Recarga todos los recursos (síncrono, usado en hot-reload de debug)
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
void reloadRoom(const std::string& name); // Recarga una habitación desde disco
|
void reloadRoom(const std::string& name); // Recarga una habitación desde disco
|
||||||
#endif
|
#endif
|
||||||
@@ -47,7 +53,21 @@ namespace Resource {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Métodos de carga de recursos
|
// Etapas del loader incremental
|
||||||
|
enum class LoadStage {
|
||||||
|
SOUNDS,
|
||||||
|
MUSICS,
|
||||||
|
SURFACES,
|
||||||
|
SURFACES_POST, // Ajuste de transparent colors tras cargar todas las surfaces
|
||||||
|
PALETTES,
|
||||||
|
TEXT_FILES,
|
||||||
|
ANIMATIONS,
|
||||||
|
ROOMS,
|
||||||
|
TEXTS,
|
||||||
|
DONE
|
||||||
|
};
|
||||||
|
|
||||||
|
// Métodos de carga de recursos (bucle completo, usados por reload() síncrono)
|
||||||
void loadSounds();
|
void loadSounds();
|
||||||
void loadMusics();
|
void loadMusics();
|
||||||
void loadSurfaces();
|
void loadSurfaces();
|
||||||
@@ -57,18 +77,27 @@ namespace Resource {
|
|||||||
void loadRooms();
|
void loadRooms();
|
||||||
void createText();
|
void createText();
|
||||||
|
|
||||||
|
// Helpers incrementales: cargan un único asset de la categoría correspondiente
|
||||||
|
void loadOneSound(size_t index);
|
||||||
|
void loadOneMusic(size_t index);
|
||||||
|
void loadOneSurface(size_t index);
|
||||||
|
void finalizeSurfaces(); // Ajuste de transparent colors tras cargar surfaces
|
||||||
|
void loadOnePalette(size_t index);
|
||||||
|
void loadOneTextFile(size_t index);
|
||||||
|
void loadOneAnimation(size_t index);
|
||||||
|
void loadOneRoom(size_t index);
|
||||||
|
void createOneText(size_t index);
|
||||||
|
|
||||||
// Métodos de limpieza
|
// Métodos de limpieza
|
||||||
void clear();
|
void clear();
|
||||||
void clearSounds();
|
void clearSounds();
|
||||||
void clearMusics();
|
void clearMusics();
|
||||||
|
|
||||||
// Métodos de gestión de carga
|
// Métodos de gestión de carga
|
||||||
void load();
|
void load(); // Carga completa síncrona (usado solo por reload())
|
||||||
void calculateTotal();
|
void calculateTotal();
|
||||||
void renderProgress();
|
void updateLoadingProgress();
|
||||||
static void checkEvents();
|
void setCurrentLoading(const std::string& name); // Desa el nom del recurs en curs
|
||||||
void updateLoadingProgress(int steps = 5);
|
|
||||||
void setCurrentLoading(const std::string& name); // Desa el nom del recurs en curs i repinta (wasm/debug)
|
|
||||||
|
|
||||||
// Helper para mensajes de error de carga
|
// Helper para mensajes de error de carga
|
||||||
[[noreturn]] static void throwLoadError(const std::string& asset_type, const std::string& file_path, const std::exception& e);
|
[[noreturn]] static void throwLoadError(const std::string& asset_type, const std::string& file_path, const std::exception& e);
|
||||||
@@ -92,7 +121,11 @@ namespace Resource {
|
|||||||
|
|
||||||
ResourceCount count_{}; // Contador de recursos
|
ResourceCount count_{}; // Contador de recursos
|
||||||
std::shared_ptr<Text> loading_text_; // Texto para la pantalla de carga
|
std::shared_ptr<Text> loading_text_; // Texto para la pantalla de carga
|
||||||
std::string current_loading_name_; // Nom del recurs que s'està a punt de carregar (debug/wasm)
|
std::string current_loading_name_; // Nom del recurs que s'està a punt de carregar
|
||||||
|
|
||||||
|
// Estado del loader incremental
|
||||||
|
LoadStage stage_{LoadStage::DONE}; // Arranca en DONE hasta que beginLoad() lo cambie
|
||||||
|
size_t stage_index_{0}; // Cursor dentro de la categoría actual
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Resource
|
} // namespace Resource
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "game/gameplay/cheevos.hpp" // Para Cheevos
|
#include "game/gameplay/cheevos.hpp" // Para Cheevos
|
||||||
#include "game/options.hpp" // Para Options, options, OptionsVideo
|
#include "game/options.hpp" // Para Options, options, OptionsVideo
|
||||||
#include "game/scene_manager.hpp" // Para SceneManager
|
#include "game/scene_manager.hpp" // Para SceneManager
|
||||||
|
#include "game/scenes/boot_loader.hpp" // Para BootLoader
|
||||||
#include "game/scenes/credits.hpp" // Para Credits
|
#include "game/scenes/credits.hpp" // Para Credits
|
||||||
#include "game/scenes/ending.hpp" // Para Ending
|
#include "game/scenes/ending.hpp" // Para Ending
|
||||||
#include "game/scenes/ending2.hpp" // Para Ending2
|
#include "game/scenes/ending2.hpp" // Para Ending2
|
||||||
@@ -177,12 +178,12 @@ Director::Director() {
|
|||||||
// Crea los objetos
|
// Crea los objetos
|
||||||
Screen::init();
|
Screen::init();
|
||||||
|
|
||||||
// Initialize resources (works for both release and development)
|
// Inicializa el singleton del cache sin disparar la carga. La carga real
|
||||||
|
// la hace Director::iterate() llamando a Cache::loadStep() en cada frame,
|
||||||
|
// de forma que la ventana, los eventos y la barra de progreso están vivos
|
||||||
|
// desde el primer tick.
|
||||||
Resource::Cache::init();
|
Resource::Cache::init();
|
||||||
Notifier::init("", "8bithud");
|
Resource::Cache::get()->beginLoad();
|
||||||
RenderInfo::init();
|
|
||||||
Console::init("8bithud");
|
|
||||||
Screen::get()->setNotificationsEnabled(true);
|
|
||||||
|
|
||||||
// Special handling for gamecontrollerdb.txt - SDL needs filesystem path
|
// Special handling for gamecontrollerdb.txt - SDL needs filesystem path
|
||||||
#if defined(RELEASE_BUILD) && !defined(__EMSCRIPTEN__)
|
#if defined(RELEASE_BUILD) && !defined(__EMSCRIPTEN__)
|
||||||
@@ -198,6 +199,22 @@ Director::Director() {
|
|||||||
Input::get()->applyKeyboardBindingsFromOptions();
|
Input::get()->applyKeyboardBindingsFromOptions();
|
||||||
Input::get()->applyGamepadBindingsFromOptions();
|
Input::get()->applyGamepadBindingsFromOptions();
|
||||||
|
|
||||||
|
std::cout << "\n"; // Fin de inicialización mínima de sistemas
|
||||||
|
|
||||||
|
// Construeix l'escena inicial (BootLoader). finishBoot() la canviarà a
|
||||||
|
// LOGO (o la que digui Debug) quan Cache::loadStep() complete la càrrega.
|
||||||
|
SceneManager::current = SceneManager::Scene::BOOT_LOADER;
|
||||||
|
switchToActiveScene();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inicialitzacions que depenen del cache poblat. Es crida des d'iterate()
|
||||||
|
// just quan Cache::loadStep() retorna true, amb la finestra i el bucle ja vius.
|
||||||
|
void Director::finishBoot() {
|
||||||
|
Notifier::init("", "8bithud");
|
||||||
|
RenderInfo::init();
|
||||||
|
Console::init("8bithud");
|
||||||
|
Screen::get()->setNotificationsEnabled(true);
|
||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
Debug::init();
|
Debug::init();
|
||||||
#ifdef __EMSCRIPTEN__
|
#ifdef __EMSCRIPTEN__
|
||||||
@@ -210,10 +227,11 @@ Director::Director() {
|
|||||||
SceneManager::current = Debug::get()->getInitialScene();
|
SceneManager::current = Debug::get()->getInitialScene();
|
||||||
#endif
|
#endif
|
||||||
MapEditor::init();
|
MapEditor::init();
|
||||||
|
#else
|
||||||
|
// En release, pasamos a LOGO siempre tras la carga
|
||||||
|
SceneManager::current = SceneManager::Scene::LOGO;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::cout << "\n"; // Fin de inicialización de sistemas
|
|
||||||
|
|
||||||
// Inicializa el sistema de localización (antes de Cheevos que usa textos traducidos)
|
// Inicializa el sistema de localización (antes de Cheevos que usa textos traducidos)
|
||||||
#if defined(RELEASE_BUILD) && !defined(__EMSCRIPTEN__)
|
#if defined(RELEASE_BUILD) && !defined(__EMSCRIPTEN__)
|
||||||
{
|
{
|
||||||
@@ -234,15 +252,17 @@ Director::Director() {
|
|||||||
#else
|
#else
|
||||||
Cheevos::init(Resource::List::get()->get("cheevos.bin"));
|
Cheevos::init(Resource::List::get()->get("cheevos.bin"));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Construeix la primera escena (LOGO per defecte, o la que digui Debug)
|
|
||||||
switchToActiveScene();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Director::~Director() {
|
Director::~Director() {
|
||||||
// Guarda las opciones a un fichero
|
// Guarda las opciones a un fichero
|
||||||
Options::saveToFile();
|
Options::saveToFile();
|
||||||
|
|
||||||
|
// Destruir l'escena activa ABANS dels singletons. Si no, el unique_ptr membre
|
||||||
|
// destrueix l'escena al final del destructor — quan Audio, Screen, Resource...
|
||||||
|
// ja són morts — i qualsevol accés en els seus destructors és un UAF.
|
||||||
|
active_scene_.reset();
|
||||||
|
|
||||||
// Destruye los singletones
|
// Destruye los singletones
|
||||||
Cheevos::destroy();
|
Cheevos::destroy();
|
||||||
Locale::destroy();
|
Locale::destroy();
|
||||||
@@ -356,6 +376,10 @@ void Director::switchToActiveScene() {
|
|||||||
active_scene_.reset();
|
active_scene_.reset();
|
||||||
|
|
||||||
switch (SceneManager::current) {
|
switch (SceneManager::current) {
|
||||||
|
case SceneManager::Scene::BOOT_LOADER:
|
||||||
|
active_scene_ = std::make_unique<BootLoader>();
|
||||||
|
break;
|
||||||
|
|
||||||
case SceneManager::Scene::LOGO:
|
case SceneManager::Scene::LOGO:
|
||||||
active_scene_ = std::make_unique<Logo>();
|
active_scene_ = std::make_unique<Logo>();
|
||||||
break;
|
break;
|
||||||
@@ -406,6 +430,26 @@ auto Director::iterate() -> SDL_AppResult {
|
|||||||
return SDL_APP_SUCCESS;
|
return SDL_APP_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fase de boot: anem cridant loadStep() fins que el cache estiga ple.
|
||||||
|
// Durant aquesta fase l'escena activa és BootLoader (una barra de progrés).
|
||||||
|
if (boot_loading_) {
|
||||||
|
try {
|
||||||
|
// Budget de 50ms: durant el boot el joc va a ~15-20 FPS, suficient
|
||||||
|
// per veure la barra avançar suau i processar events del WM/ESC,
|
||||||
|
// i evita el 50% d'ineficiència que provocaria un budget < vsync.
|
||||||
|
if (Resource::Cache::get()->loadStep(50 /*ms*/)) {
|
||||||
|
finishBoot();
|
||||||
|
boot_loading_ = false;
|
||||||
|
// finishBoot() ja ha fixat SceneManager::current a LOGO (o la que
|
||||||
|
// digui Debug). El canvi d'escena es fa just a sota.
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Fatal error during resource load: " << e.what() << '\n';
|
||||||
|
SceneManager::current = SceneManager::Scene::QUIT;
|
||||||
|
return SDL_APP_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Si l'escena ha canviat (o s'ha demanat RESTART_CURRENT), canviar-la abans del frame
|
// Si l'escena ha canviat (o s'ha demanat RESTART_CURRENT), canviar-la abans del frame
|
||||||
if (SceneManager::current != current_scene_ || SceneManager::current == SceneManager::Scene::RESTART_CURRENT) {
|
if (SceneManager::current != current_scene_ || SceneManager::current == SceneManager::Scene::RESTART_CURRENT) {
|
||||||
switchToActiveScene();
|
switchToActiveScene();
|
||||||
|
|||||||
@@ -22,11 +22,13 @@ class Director {
|
|||||||
std::string executable_path_; // Path del ejecutable
|
std::string executable_path_; // Path del ejecutable
|
||||||
std::string system_folder_; // Carpeta del sistema donde guardar datos
|
std::string system_folder_; // Carpeta del sistema donde guardar datos
|
||||||
|
|
||||||
std::unique_ptr<Scene> active_scene_; // Escena activa
|
std::unique_ptr<Scene> active_scene_; // Escena activa
|
||||||
SceneManager::Scene current_scene_{SceneManager::Scene::LOGO}; // Tipus d'escena activa
|
SceneManager::Scene current_scene_{SceneManager::Scene::BOOT_LOADER}; // Tipus d'escena activa
|
||||||
|
bool boot_loading_{true}; // True mientras Cache::loadStep() no haya acabado
|
||||||
|
|
||||||
// --- Funciones ---
|
// --- Funciones ---
|
||||||
void createSystemFolder(const std::string& folder); // Crea la carpeta del sistema donde guardar datos
|
void createSystemFolder(const std::string& folder); // Crea la carpeta del sistema donde guardar datos
|
||||||
void setFileList(); // Carga la configuración de assets desde assets.yaml
|
void setFileList(); // Carga la configuración de assets desde assets.yaml
|
||||||
void switchToActiveScene(); // Construeix l'escena segons SceneManager::current
|
void switchToActiveScene(); // Construeix l'escena segons SceneManager::current
|
||||||
|
void finishBoot(); // Inits que dependen del cache, ejecutado tras loadStep==done
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace SceneManager {
|
|||||||
|
|
||||||
// --- Escenas del programa ---
|
// --- Escenas del programa ---
|
||||||
enum class Scene {
|
enum class Scene {
|
||||||
|
BOOT_LOADER, // Carga inicial de recursos dirigida por iterate()
|
||||||
LOGO, // Pantalla del logo
|
LOGO, // Pantalla del logo
|
||||||
LOADING_SCREEN, // Pantalla de carga
|
LOADING_SCREEN, // Pantalla de carga
|
||||||
TITLE, // Pantalla de título/menú principal
|
TITLE, // Pantalla de título/menú principal
|
||||||
@@ -34,7 +35,7 @@ namespace SceneManager {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// --- Variables de estado globales ---
|
// --- Variables de estado globales ---
|
||||||
inline Scene current = Scene::LOGO; // Escena actual (en _DEBUG sobrescrito por Director tras cargar debug.yaml)
|
inline Scene current = Scene::BOOT_LOADER; // Arranca siempre cargando recursos; Director conmuta a LOGO al terminar
|
||||||
inline Options options = Options::LOGO_TO_LOADING_SCREEN; // Opciones de la escena actual
|
inline Options options = Options::LOGO_TO_LOADING_SCREEN; // Opciones de la escena actual
|
||||||
inline Scene scene_before_restart = Scene::LOGO; // escena a relanzar tras RESTART_CURRENT
|
inline Scene scene_before_restart = Scene::LOGO; // escena a relanzar tras RESTART_CURRENT
|
||||||
|
|
||||||
|
|||||||
14
source/game/scenes/boot_loader.cpp
Normal file
14
source/game/scenes/boot_loader.cpp
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#include "game/scenes/boot_loader.hpp"
|
||||||
|
|
||||||
|
#include "core/resources/resource_cache.hpp"
|
||||||
|
#include "game/scene_manager.hpp"
|
||||||
|
|
||||||
|
void BootLoader::iterate() {
|
||||||
|
Resource::Cache::get()->renderProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BootLoader::handleEvent(const SDL_Event& event) {
|
||||||
|
if (event.type == SDL_EVENT_QUIT) {
|
||||||
|
SceneManager::current = SceneManager::Scene::QUIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
source/game/scenes/boot_loader.hpp
Normal file
17
source/game/scenes/boot_loader.hpp
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include "game/scenes/scene.hpp"
|
||||||
|
|
||||||
|
// Escena mínima que Director usa mientras el cache se carga incrementalmente.
|
||||||
|
// No avanza la carga — lo hace Director::iterate() llamando a Cache::loadStep()
|
||||||
|
// antes de despachar la escena. Aquí solo se pinta la barra de progreso.
|
||||||
|
class BootLoader : public Scene {
|
||||||
|
public:
|
||||||
|
BootLoader() = default;
|
||||||
|
~BootLoader() override = default;
|
||||||
|
|
||||||
|
void iterate() override;
|
||||||
|
void handleEvent(const SDL_Event& event) override;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user