5 singletons afectats: Audio, Screen, Director, Resource::Cache, Resource::List. - static T* instance → static std::unique_ptr<T> instance - init(): new T() adoptat immediatament per unique_ptr (ownership RAII) - destroy(): instance.reset() (sense delete manual) - get(): retorna instance.get() - Destructors moguts a public perquè std::default_delete hi pugui accedir (ctors privats + copy/move deleted → encapsulació efectiva mantinguda) Ordre de destrucció preservat: SDL_AppQuit segueix cridant destroy() en l'ordre invers a init() — la RAII automàtica no s'activa fins al final del programa (LIFO de variables static). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
270 lines
10 KiB
C++
270 lines
10 KiB
C++
#include "core/resources/resource_cache.hpp"
|
|
|
|
#include <SDL3/SDL.h>
|
|
|
|
#include <algorithm>
|
|
#include <cstdlib>
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
|
|
#include "core/audio/jail_audio.hpp"
|
|
#include "core/resources/resource_helper.hpp"
|
|
#include "core/resources/resource_list.hpp"
|
|
|
|
// gif.h ja s'inclou des de jdraw8.cpp i text.cpp; el seu codi no és static
|
|
// ni inline, així que no podem tornar-lo a incloure aquí. Ens fiem de les
|
|
// declaracions extern dels símbols que ens calen (linkatge C++ normal,
|
|
// igual que fa text.cpp).
|
|
extern unsigned char* LoadGif(unsigned char* data, unsigned short* w, unsigned short* h);
|
|
extern unsigned char* LoadPalette(unsigned char* data);
|
|
|
|
namespace Resource {
|
|
|
|
std::unique_ptr<Cache> Cache::instance;
|
|
|
|
void Cache::init() { instance = std::unique_ptr<Cache>(new Cache()); }
|
|
void Cache::destroy() { instance.reset(); }
|
|
auto Cache::get() -> Cache* { return instance.get(); }
|
|
|
|
namespace {
|
|
auto basename(const std::string& path) -> std::string {
|
|
auto pos = path.find_last_of("/\\");
|
|
return pos == std::string::npos ? path : path.substr(pos + 1);
|
|
}
|
|
} // namespace
|
|
|
|
auto Cache::getMusic(const std::string& name) -> JA_Music_t* {
|
|
auto it = std::ranges::find_if(musics_, [&](const auto& m) { return m.name == name; });
|
|
if (it != musics_.end()) {
|
|
return it->music.get();
|
|
}
|
|
std::cerr << "Resource::Cache: música no trobada: " << name << '\n';
|
|
throw std::runtime_error("Music not found: " + name);
|
|
}
|
|
|
|
auto Cache::getSound(const std::string& name) -> JA_Sound_t* {
|
|
auto it = std::ranges::find_if(sounds_, [&](const auto& s) { return s.name == name; });
|
|
if (it != sounds_.end()) {
|
|
return it->sound.get();
|
|
}
|
|
std::cerr << "Resource::Cache: so no trobat: " << name << '\n';
|
|
throw std::runtime_error("Sound not found: " + name);
|
|
}
|
|
|
|
auto Cache::getSurfacePixels(const std::string& name) -> const std::vector<Uint8>& {
|
|
auto it = std::ranges::find_if(surfaces_, [&](const auto& s) { return s.name == name; });
|
|
if (it != surfaces_.end()) {
|
|
return it->pixels;
|
|
}
|
|
std::cerr << "Resource::Cache: surface no trobada: " << name << '\n';
|
|
throw std::runtime_error("Surface not found: " + name);
|
|
}
|
|
|
|
auto Cache::getPaletteBytes(const std::string& name) -> const std::vector<Uint8>& {
|
|
auto it = std::ranges::find_if(surfaces_, [&](const auto& s) { return s.name == name; });
|
|
if (it != surfaces_.end()) {
|
|
return it->palette;
|
|
}
|
|
std::cerr << "Resource::Cache: paleta no trobada: " << name << '\n';
|
|
throw std::runtime_error("Palette not found: " + name);
|
|
}
|
|
|
|
auto Cache::getTextFile(const std::string& name) -> const std::vector<uint8_t>& {
|
|
auto it = std::ranges::find_if(text_files_, [&](const auto& t) { return t.name == name; });
|
|
if (it != text_files_.end()) {
|
|
return it->bytes;
|
|
}
|
|
std::cerr << "Resource::Cache: text file no trobat: " << name << '\n';
|
|
throw std::runtime_error("TextFile not found: " + name);
|
|
}
|
|
|
|
void Cache::calculateTotal() {
|
|
auto* list = List::get();
|
|
total_count_ = static_cast<int>(
|
|
list->getListByType(List::Type::MUSIC).size() +
|
|
list->getListByType(List::Type::SOUND).size() +
|
|
list->getListByType(List::Type::BITMAP).size() +
|
|
list->getListByType(List::Type::DATA).size() +
|
|
list->getListByType(List::Type::FONT).size());
|
|
loaded_count_ = 0;
|
|
}
|
|
|
|
auto Cache::getProgress() const -> float {
|
|
if (total_count_ == 0) return 1.0F;
|
|
return static_cast<float>(loaded_count_) / static_cast<float>(total_count_);
|
|
}
|
|
|
|
void Cache::beginLoad() {
|
|
calculateTotal();
|
|
stage_ = LoadStage::MUSICS;
|
|
stage_index_ = 0;
|
|
std::cout << "Resource::Cache: precarregant " << total_count_ << " assets\n";
|
|
}
|
|
|
|
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* list = List::get();
|
|
|
|
while (stage_ != LoadStage::DONE) {
|
|
switch (stage_) {
|
|
case LoadStage::MUSICS: {
|
|
auto items = list->getListByType(List::Type::MUSIC);
|
|
if (stage_index_ == 0) musics_.clear();
|
|
if (stage_index_ >= items.size()) {
|
|
stage_ = LoadStage::SOUNDS;
|
|
stage_index_ = 0;
|
|
break;
|
|
}
|
|
loadOneMusic(stage_index_++);
|
|
break;
|
|
}
|
|
case LoadStage::SOUNDS: {
|
|
auto items = list->getListByType(List::Type::SOUND);
|
|
if (stage_index_ == 0) sounds_.clear();
|
|
if (stage_index_ >= items.size()) {
|
|
stage_ = LoadStage::BITMAPS;
|
|
stage_index_ = 0;
|
|
break;
|
|
}
|
|
loadOneSound(stage_index_++);
|
|
break;
|
|
}
|
|
case LoadStage::BITMAPS: {
|
|
auto items = list->getListByType(List::Type::BITMAP);
|
|
if (stage_index_ == 0) surfaces_.clear();
|
|
if (stage_index_ >= items.size()) {
|
|
stage_ = LoadStage::TEXT_FILES;
|
|
stage_index_ = 0;
|
|
break;
|
|
}
|
|
loadOneBitmap(stage_index_++);
|
|
break;
|
|
}
|
|
case LoadStage::TEXT_FILES: {
|
|
auto data_items = list->getListByType(List::Type::DATA);
|
|
auto font_items = list->getListByType(List::Type::FONT);
|
|
auto items = data_items;
|
|
items.insert(items.end(), font_items.begin(), font_items.end());
|
|
if (stage_index_ == 0) text_files_.clear();
|
|
if (stage_index_ >= items.size()) {
|
|
stage_ = LoadStage::DONE;
|
|
stage_index_ = 0;
|
|
std::cout << "Resource::Cache: precarrega completada (" << loaded_count_ << "/" << total_count_ << ")\n";
|
|
break;
|
|
}
|
|
loadOneTextFile(stage_index_++);
|
|
break;
|
|
}
|
|
case LoadStage::DONE:
|
|
break;
|
|
}
|
|
if ((SDL_GetTicksNS() - start_ns) >= budget_ns) break;
|
|
}
|
|
|
|
return stage_ == LoadStage::DONE;
|
|
}
|
|
|
|
void Cache::loadOneMusic(size_t index) {
|
|
auto items = List::get()->getListByType(List::Type::MUSIC);
|
|
const auto& path = items[index];
|
|
auto name = basename(path);
|
|
current_loading_name_ = name;
|
|
|
|
auto bytes = ResourceHelper::loadFile(path);
|
|
if (bytes.empty()) {
|
|
std::cerr << "Resource::Cache: no s'ha pogut llegir " << path << '\n';
|
|
return;
|
|
}
|
|
JA_Music_t* music = JA_LoadMusic(bytes.data(), static_cast<Uint32>(bytes.size()), path.c_str());
|
|
if (music == nullptr) {
|
|
std::cerr << "Resource::Cache: JA_LoadMusic ha fallat per " << path << '\n';
|
|
return;
|
|
}
|
|
musics_.push_back(MusicResource{.name = name, .music = std::unique_ptr<JA_Music_t, MusicDeleter>(music)});
|
|
++loaded_count_;
|
|
std::cout << " [music ] " << name << '\n';
|
|
}
|
|
|
|
void Cache::loadOneSound(size_t index) {
|
|
auto items = List::get()->getListByType(List::Type::SOUND);
|
|
const auto& path = items[index];
|
|
auto name = basename(path);
|
|
current_loading_name_ = name;
|
|
|
|
auto bytes = ResourceHelper::loadFile(path);
|
|
if (bytes.empty()) {
|
|
std::cerr << "Resource::Cache: no s'ha pogut llegir " << path << '\n';
|
|
return;
|
|
}
|
|
JA_Sound_t* sound = JA_LoadSound(bytes.data(), static_cast<uint32_t>(bytes.size()));
|
|
if (sound == nullptr) {
|
|
std::cerr << "Resource::Cache: JA_LoadSound ha fallat per " << path << '\n';
|
|
return;
|
|
}
|
|
sounds_.push_back(SoundResource{.name = name, .sound = std::unique_ptr<JA_Sound_t, SoundDeleter>(sound)});
|
|
++loaded_count_;
|
|
std::cout << " [sound ] " << name << '\n';
|
|
}
|
|
|
|
void Cache::loadOneBitmap(size_t index) {
|
|
auto items = List::get()->getListByType(List::Type::BITMAP);
|
|
const auto& path = items[index];
|
|
auto name = basename(path);
|
|
current_loading_name_ = name;
|
|
|
|
auto bytes = ResourceHelper::loadFile(path);
|
|
if (bytes.empty()) {
|
|
std::cerr << "Resource::Cache: no s'ha pogut llegir " << path << '\n';
|
|
return;
|
|
}
|
|
|
|
// Decodifica píxels.
|
|
unsigned short w = 0;
|
|
unsigned short h = 0;
|
|
unsigned char* pixels = LoadGif(bytes.data(), &w, &h);
|
|
if (pixels == nullptr) {
|
|
std::cerr << "Resource::Cache: LoadGif ha fallat per " << path << '\n';
|
|
return;
|
|
}
|
|
SurfaceResource res;
|
|
res.name = name;
|
|
res.pixels.assign(pixels, pixels + 64000);
|
|
std::free(pixels);
|
|
|
|
// Decodifica paleta des del mateix GIF (necessita una segona passada
|
|
// perquè LoadGif no exposa la paleta).
|
|
unsigned char* palette = LoadPalette(bytes.data());
|
|
if (palette != nullptr) {
|
|
res.palette.assign(palette, palette + 768);
|
|
std::free(palette);
|
|
}
|
|
|
|
surfaces_.push_back(std::move(res));
|
|
++loaded_count_;
|
|
std::cout << " [bitmap] " << name << '\n';
|
|
}
|
|
|
|
void Cache::loadOneTextFile(size_t index) {
|
|
auto data_items = List::get()->getListByType(List::Type::DATA);
|
|
auto font_items = List::get()->getListByType(List::Type::FONT);
|
|
auto items = data_items;
|
|
items.insert(items.end(), font_items.begin(), font_items.end());
|
|
const auto& path = items[index];
|
|
auto name = basename(path);
|
|
current_loading_name_ = name;
|
|
|
|
auto bytes = ResourceHelper::loadFile(path);
|
|
if (bytes.empty()) {
|
|
std::cerr << "Resource::Cache: no s'ha pogut llegir " << path << '\n';
|
|
return;
|
|
}
|
|
text_files_.push_back(TextFileResource{.name = name, .bytes = std::move(bytes)});
|
|
++loaded_count_;
|
|
std::cout << " [text ] " << name << '\n';
|
|
}
|
|
|
|
} // namespace Resource
|