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
|
||||
|
||||
# Game - Scenes
|
||||
source/game/scenes/boot_loader.cpp
|
||||
source/game/scenes/credits.cpp
|
||||
source/game/scenes/ending.cpp
|
||||
source/game/scenes/ending2.cpp
|
||||
|
||||
@@ -43,12 +43,16 @@ struct JA_Channel_t {
|
||||
|
||||
struct JA_Music_t {
|
||||
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};
|
||||
|
||||
int pos{0};
|
||||
int times{0};
|
||||
int times{0}; // Loops restants (-1 = infinit, 0 = un sol play)
|
||||
SDL_AudioStream* stream{nullptr};
|
||||
JA_Music_state state{JA_MUSIC_INVALID};
|
||||
};
|
||||
@@ -76,6 +80,57 @@ inline void JA_StopMusic();
|
||||
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);
|
||||
|
||||
// --- 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 ---
|
||||
|
||||
inline void JA_Update() {
|
||||
@@ -93,13 +148,11 @@ inline void JA_Update() {
|
||||
}
|
||||
}
|
||||
|
||||
if (current_music->times != 0) {
|
||||
if ((Uint32)SDL_GetAudioStreamAvailable(current_music->stream) < (current_music->length / 2)) {
|
||||
SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length);
|
||||
}
|
||||
if (current_music->times > 0) current_music->times--;
|
||||
} else {
|
||||
if (SDL_GetAudioStreamAvailable(current_music->stream) == 0) JA_StopMusic();
|
||||
// Streaming: rellenem l'stream fins al low-water-mark i parem si el
|
||||
// vorbis s'ha esgotat i no queden loops.
|
||||
JA_PumpMusic(current_music);
|
||||
if (current_music->times == 0 && SDL_GetAudioStreamAvailable(current_music->stream) == 0) {
|
||||
JA_StopMusic();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,19 +192,31 @@ inline void JA_Quit() {
|
||||
// --- Music Functions ---
|
||||
|
||||
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;
|
||||
short* output;
|
||||
music->length = stb_vorbis_decode_memory(buffer, length, &chan, &samplerate, &output) * chan * 2;
|
||||
// Còpia del OGG comprimit: stb_vorbis llig de forma persistent aquesta
|
||||
// memòria mentre el handle estiga viu, així que hem de posseir-la nosaltres.
|
||||
Uint8* ogg_copy = static_cast<Uint8*>(SDL_malloc(length));
|
||||
if (!ogg_copy) return nullptr;
|
||||
SDL_memcpy(ogg_copy, buffer, length);
|
||||
|
||||
music->spec.channels = chan;
|
||||
music->spec.freq = samplerate;
|
||||
int error = 0;
|
||||
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->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;
|
||||
|
||||
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) {
|
||||
if (!JA_musicEnabled || !music) return; // Añadida comprobación de music
|
||||
if (!JA_musicEnabled || !music || !music->vorbis) return;
|
||||
|
||||
JA_StopMusic();
|
||||
|
||||
current_music = music;
|
||||
current_music->pos = 0;
|
||||
current_music->state = JA_MUSIC_PLAYING;
|
||||
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);
|
||||
if (!current_music->stream) { // Comprobar creación de stream
|
||||
if (!current_music->stream) {
|
||||
SDL_Log("Failed to create audio stream!");
|
||||
current_music->state = JA_MUSIC_STOPPED;
|
||||
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);
|
||||
|
||||
// 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");
|
||||
}
|
||||
|
||||
@@ -235,13 +306,17 @@ inline void JA_ResumeMusic() {
|
||||
inline void JA_StopMusic() {
|
||||
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;
|
||||
if (current_music->stream) {
|
||||
SDL_DestroyAudioStream(current_music->stream);
|
||||
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) {
|
||||
@@ -267,9 +342,10 @@ inline void JA_DeleteMusic(JA_Music_t* music) {
|
||||
JA_StopMusic();
|
||||
current_music = nullptr;
|
||||
}
|
||||
SDL_free(music->buffer);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -281,17 +357,14 @@ inline float JA_SetMusicVolume(float volume) {
|
||||
return JA_musicVolume;
|
||||
}
|
||||
|
||||
inline void JA_SetMusicPosition(float value) {
|
||||
if (!current_music) return;
|
||||
current_music->pos = value * current_music->spec.freq;
|
||||
// Nota: Esta implementación de 'pos' no parece usarse en JA_Update para
|
||||
// el streaming. El streaming siempre parece empezar desde el principio.
|
||||
inline void JA_SetMusicPosition(float /*value*/) {
|
||||
// No implementat amb el backend de streaming. Mai va arribar a usar-se
|
||||
// en el codi existent, així que es manté com a stub.
|
||||
}
|
||||
|
||||
inline float JA_GetMusicPosition() {
|
||||
if (!current_music) return 0;
|
||||
return float(current_music->pos) / float(current_music->spec.freq);
|
||||
// Nota: Ver `JA_SetMusicPosition`
|
||||
// Veure nota a JA_SetMusicPosition.
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
inline void JA_EnableMusic(const bool value) {
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten/emscripten.h> // Para emscripten_sleep
|
||||
#endif
|
||||
|
||||
#include <algorithm> // Para find_if
|
||||
#include <cstdlib> // Para exit, size_t
|
||||
#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
|
||||
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()
|
||||
: loading_text_(Screen::get()->getText()) {
|
||||
load();
|
||||
}
|
||||
|
||||
// Vacia todos los vectores de recursos
|
||||
@@ -57,12 +53,11 @@ namespace Resource {
|
||||
text_files_.clear();
|
||||
texts_.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() {
|
||||
// Nota: el overlay de debug (RenderInfo) se inicializa después de esta carga,
|
||||
// por lo que updateZoomFactor() se llamará correctamente en RenderInfo::init().
|
||||
calculateTotal();
|
||||
Screen::get()->setBorderColor(static_cast<Uint8>(PaletteColor::BLACK));
|
||||
std::cout << "\n** LOADING RESOURCES" << '\n';
|
||||
@@ -77,7 +72,162 @@ namespace Resource {
|
||||
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() {
|
||||
clear();
|
||||
load();
|
||||
@@ -221,29 +371,42 @@ namespace Resource {
|
||||
throw;
|
||||
}
|
||||
|
||||
// Carga los sonidos
|
||||
void Cache::loadSounds() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::cout << "\n>> SOUND FILES" << '\n';
|
||||
auto list = List::get()->getListByType(List::Type::SOUND);
|
||||
sounds_.clear();
|
||||
// Lista fija de text objects. Compartida entre createText() y createOneText(i).
|
||||
namespace {
|
||||
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
|
||||
};
|
||||
|
||||
for (const auto& l : list) {
|
||||
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);
|
||||
const auto& l = list[index];
|
||||
try {
|
||||
auto name = getFileName(l);
|
||||
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");
|
||||
}
|
||||
@@ -255,51 +418,37 @@ namespace Resource {
|
||||
throwLoadError("SOUND", l, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Carga las musicas
|
||||
void Cache::loadMusics() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::cout << "\n>> MUSIC FILES" << '\n';
|
||||
void Cache::loadOneMusic(size_t index) {
|
||||
auto list = List::get()->getListByType(List::Type::MUSIC);
|
||||
musics_.clear();
|
||||
|
||||
for (const auto& l : list) {
|
||||
const auto& l = list[index];
|
||||
try {
|
||||
auto name = getFileName(l);
|
||||
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);
|
||||
updateLoadingProgress();
|
||||
} catch (const std::exception& e) {
|
||||
throwLoadError("MUSIC", l, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Carga las texturas
|
||||
void Cache::loadSurfaces() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::cout << "\n>> SURFACES" << '\n';
|
||||
void Cache::loadOneSurface(size_t index) {
|
||||
auto list = List::get()->getListByType(List::Type::BITMAP);
|
||||
surfaces_.clear();
|
||||
|
||||
for (const auto& l : list) {
|
||||
const auto& l = list[index];
|
||||
try {
|
||||
auto name = getFileName(l);
|
||||
setCurrentLoading(name);
|
||||
@@ -311,6 +460,7 @@ namespace Resource {
|
||||
}
|
||||
}
|
||||
|
||||
void Cache::finalizeSurfaces() {
|
||||
// Reconfigura el color transparente de algunas surfaces
|
||||
getSurface("loading_screen_color.gif")->setTransparentColor();
|
||||
getSurface("ending1.gif")->setTransparentColor();
|
||||
@@ -321,13 +471,9 @@ namespace Resource {
|
||||
getSurface("standard.gif")->setTransparentColor(16);
|
||||
}
|
||||
|
||||
// Carga las paletas
|
||||
void Cache::loadPalettes() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::cout << "\n>> PALETTES" << '\n';
|
||||
void Cache::loadOnePalette(size_t index) {
|
||||
auto list = List::get()->getListByType(List::Type::PALETTE);
|
||||
palettes_.clear();
|
||||
|
||||
for (const auto& l : list) {
|
||||
const auto& l = list[index];
|
||||
try {
|
||||
auto name = getFileName(l);
|
||||
setCurrentLoading(name);
|
||||
@@ -337,15 +483,10 @@ namespace Resource {
|
||||
throwLoadError("PALETTE", l, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Carga los ficheros de texto
|
||||
void Cache::loadTextFiles() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::cout << "\n>> TEXT FILES" << '\n';
|
||||
void Cache::loadOneTextFile(size_t index) {
|
||||
auto list = List::get()->getListByType(List::Type::FONT);
|
||||
text_files_.clear();
|
||||
|
||||
for (const auto& l : list) {
|
||||
const auto& l = list[index];
|
||||
try {
|
||||
auto name = getFileName(l);
|
||||
setCurrentLoading(name);
|
||||
@@ -355,22 +496,15 @@ namespace Resource {
|
||||
throwLoadError("FONT", l, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Carga las animaciones
|
||||
void Cache::loadAnimations() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::cout << "\n>> ANIMATIONS" << '\n';
|
||||
void Cache::loadOneAnimation(size_t index) {
|
||||
auto list = List::get()->getListByType(List::Type::ANIMATION);
|
||||
animations_.clear();
|
||||
|
||||
for (const auto& l : list) {
|
||||
const auto& l = list[index];
|
||||
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");
|
||||
}
|
||||
@@ -382,15 +516,10 @@ namespace Resource {
|
||||
throwLoadError("ANIMATION", l, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Carga las habitaciones desde archivos YAML
|
||||
void Cache::loadRooms() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::cout << "\n>> ROOMS" << '\n';
|
||||
void Cache::loadOneRoom(size_t index) {
|
||||
auto list = List::get()->getListByType(List::Type::ROOM);
|
||||
rooms_.clear();
|
||||
|
||||
for (const auto& l : list) {
|
||||
const auto& l = list[index];
|
||||
try {
|
||||
auto name = getFileName(l);
|
||||
setCurrentLoading(name);
|
||||
@@ -401,28 +530,73 @@ namespace Resource {
|
||||
throwLoadError("ROOM", l, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Cache::createText() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
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::vector<ResourceInfo> resources = {
|
||||
{.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"}};
|
||||
|
||||
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))});
|
||||
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';
|
||||
auto list = List::get()->getListByType(List::Type::PALETTE);
|
||||
palettes_.clear();
|
||||
for (size_t i = 0; i < list.size(); ++i) loadOnePalette(i);
|
||||
}
|
||||
|
||||
void Cache::loadTextFiles() {
|
||||
std::cout << "\n>> TEXT FILES" << '\n';
|
||||
auto list = List::get()->getListByType(List::Type::FONT);
|
||||
text_files_.clear();
|
||||
for (size_t i = 0; i < list.size(); ++i) loadOneTextFile(i);
|
||||
}
|
||||
|
||||
void Cache::loadAnimations() {
|
||||
std::cout << "\n>> ANIMATIONS" << '\n';
|
||||
auto list = List::get()->getListByType(List::Type::ANIMATION);
|
||||
animations_.clear();
|
||||
for (size_t i = 0; i < list.size(); ++i) loadOneAnimation(i);
|
||||
}
|
||||
|
||||
void Cache::loadRooms() {
|
||||
std::cout << "\n>> ROOMS" << '\n';
|
||||
auto list = List::get()->getListByType(List::Type::ROOM);
|
||||
rooms_.clear();
|
||||
for (size_t i = 0; i < list.size(); ++i) loadOneRoom(i);
|
||||
}
|
||||
|
||||
void Cache::createText() {
|
||||
std::cout << "\n>> CREATING TEXT_OBJECTS" << '\n';
|
||||
texts_.clear();
|
||||
const auto& infos = getTextObjectInfos();
|
||||
for (size_t i = 0; i < infos.size(); ++i) createOneText(i);
|
||||
}
|
||||
|
||||
// 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};
|
||||
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
|
||||
if (!current_loading_name_.empty()) {
|
||||
const float TEXT_Y = BAR_POSITION - static_cast<float>(TEXT_HEIGHT) - 2.0F;
|
||||
@@ -522,51 +695,19 @@ namespace Resource {
|
||||
current_loading_name_,
|
||||
LOADING_TEXT_COLOR);
|
||||
}
|
||||
#endif
|
||||
|
||||
Screen::get()->render();
|
||||
}
|
||||
|
||||
// Desa el nom del recurs que s'està a punt de carregar i repinta immediatament.
|
||||
// A wasm/debug serveix per veure exactament en quin fitxer es penja la càrrega.
|
||||
// Guarda el nombre del recurso que se está a punto de cargar. El repintado
|
||||
// lo hace el BootLoader (una vez por frame) — aquí solo se actualiza el estado.
|
||||
void Cache::setCurrentLoading(const std::string& 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
|
||||
void Cache::checkEvents() {
|
||||
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) {
|
||||
// Incrementa el contador de recursos cargados
|
||||
void Cache::updateLoadingProgress() {
|
||||
count_.add(1);
|
||||
if (count_.loaded % steps == 0 || count_.loaded == count_.total) {
|
||||
renderProgress();
|
||||
}
|
||||
checkEvents();
|
||||
}
|
||||
|
||||
} // namespace Resource
|
||||
|
||||
@@ -25,7 +25,13 @@ namespace Resource {
|
||||
auto getRoom(const std::string& name) -> std::shared_ptr<Room::Data>;
|
||||
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
|
||||
void reloadRoom(const std::string& name); // Recarga una habitación desde disco
|
||||
#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 loadMusics();
|
||||
void loadSurfaces();
|
||||
@@ -57,18 +77,27 @@ namespace Resource {
|
||||
void loadRooms();
|
||||
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
|
||||
void clear();
|
||||
void clearSounds();
|
||||
void clearMusics();
|
||||
|
||||
// Métodos de gestión de carga
|
||||
void load();
|
||||
void load(); // Carga completa síncrona (usado solo por reload())
|
||||
void calculateTotal();
|
||||
void renderProgress();
|
||||
static void checkEvents();
|
||||
void updateLoadingProgress(int steps = 5);
|
||||
void setCurrentLoading(const std::string& name); // Desa el nom del recurs en curs i repinta (wasm/debug)
|
||||
void updateLoadingProgress();
|
||||
void setCurrentLoading(const std::string& name); // Desa el nom del recurs en curs
|
||||
|
||||
// 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);
|
||||
@@ -92,7 +121,11 @@ namespace Resource {
|
||||
|
||||
ResourceCount count_{}; // Contador de recursos
|
||||
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
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "game/gameplay/cheevos.hpp" // Para Cheevos
|
||||
#include "game/options.hpp" // Para Options, options, OptionsVideo
|
||||
#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/ending.hpp" // Para Ending
|
||||
#include "game/scenes/ending2.hpp" // Para Ending2
|
||||
@@ -177,12 +178,12 @@ Director::Director() {
|
||||
// Crea los objetos
|
||||
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();
|
||||
Notifier::init("", "8bithud");
|
||||
RenderInfo::init();
|
||||
Console::init("8bithud");
|
||||
Screen::get()->setNotificationsEnabled(true);
|
||||
Resource::Cache::get()->beginLoad();
|
||||
|
||||
// Special handling for gamecontrollerdb.txt - SDL needs filesystem path
|
||||
#if defined(RELEASE_BUILD) && !defined(__EMSCRIPTEN__)
|
||||
@@ -198,6 +199,22 @@ Director::Director() {
|
||||
Input::get()->applyKeyboardBindingsFromOptions();
|
||||
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
|
||||
Debug::init();
|
||||
#ifdef __EMSCRIPTEN__
|
||||
@@ -210,10 +227,11 @@ Director::Director() {
|
||||
SceneManager::current = Debug::get()->getInitialScene();
|
||||
#endif
|
||||
MapEditor::init();
|
||||
#else
|
||||
// En release, pasamos a LOGO siempre tras la carga
|
||||
SceneManager::current = SceneManager::Scene::LOGO;
|
||||
#endif
|
||||
|
||||
std::cout << "\n"; // Fin de inicialización de sistemas
|
||||
|
||||
// Inicializa el sistema de localización (antes de Cheevos que usa textos traducidos)
|
||||
#if defined(RELEASE_BUILD) && !defined(__EMSCRIPTEN__)
|
||||
{
|
||||
@@ -234,15 +252,17 @@ Director::Director() {
|
||||
#else
|
||||
Cheevos::init(Resource::List::get()->get("cheevos.bin"));
|
||||
#endif
|
||||
|
||||
// Construeix la primera escena (LOGO per defecte, o la que digui Debug)
|
||||
switchToActiveScene();
|
||||
}
|
||||
|
||||
Director::~Director() {
|
||||
// Guarda las opciones a un fichero
|
||||
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
|
||||
Cheevos::destroy();
|
||||
Locale::destroy();
|
||||
@@ -356,6 +376,10 @@ void Director::switchToActiveScene() {
|
||||
active_scene_.reset();
|
||||
|
||||
switch (SceneManager::current) {
|
||||
case SceneManager::Scene::BOOT_LOADER:
|
||||
active_scene_ = std::make_unique<BootLoader>();
|
||||
break;
|
||||
|
||||
case SceneManager::Scene::LOGO:
|
||||
active_scene_ = std::make_unique<Logo>();
|
||||
break;
|
||||
@@ -406,6 +430,26 @@ auto Director::iterate() -> SDL_AppResult {
|
||||
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
|
||||
if (SceneManager::current != current_scene_ || SceneManager::current == SceneManager::Scene::RESTART_CURRENT) {
|
||||
switchToActiveScene();
|
||||
|
||||
@@ -23,10 +23,12 @@ class Director {
|
||||
std::string system_folder_; // Carpeta del sistema donde guardar datos
|
||||
|
||||
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 ---
|
||||
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 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 ---
|
||||
enum class Scene {
|
||||
BOOT_LOADER, // Carga inicial de recursos dirigida por iterate()
|
||||
LOGO, // Pantalla del logo
|
||||
LOADING_SCREEN, // Pantalla de carga
|
||||
TITLE, // Pantalla de título/menú principal
|
||||
@@ -34,7 +35,7 @@ namespace SceneManager {
|
||||
};
|
||||
|
||||
// --- 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 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