diff --git a/.clang-tidy b/.clang-tidy index 91d231a..6cbebda 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -11,8 +11,8 @@ Checks: - -modernize-avoid-c-arrays,-warnings-as-errors WarningsAsErrors: '*' -# Solo headers del propio código fuente (external/ y spv/ tienen su propio .clang-tidy dummy) -HeaderFilterRegex: 'source/.*' +# Headers nostres (excloem source/external/ que conté dependències de tercers no editables) +HeaderFilterRegex: 'source/(core|game|utils)/' FormatStyle: file CheckOptions: diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 2d414d6..af2f1ce 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -78,7 +78,8 @@ if ! cppcheck \ --inline-suppr \ --suppress=missingIncludeSystem \ --suppress=toomanyconfigs \ - --suppress='*:*/source/external/*' \ + --suppress='*:*source/external/*' \ + --suppress=normalCheckLevelMaxBranches \ -D_DEBUG \ -DLINUX_BUILD \ --quiet \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 2321431..ff7d0e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,16 @@ set(APP_SOURCES source/main.cpp source/core/system/director.cpp source/core/rendering/screen.cpp - source/game/scenes/black_scene.cpp + source/core/rendering/gif.cpp + source/core/rendering/surface.cpp + source/core/rendering/sprite/sprite.cpp + source/core/rendering/sprite/moving_sprite.cpp + source/core/rendering/sprite/animated_sprite.cpp + source/core/resources/resource_helper.cpp + source/core/resources/resource_cache.cpp + source/utils/utils.cpp + source/game/scenes/game_scene.cpp + source/game/entities/player.cpp ) find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3) @@ -48,6 +57,15 @@ target_include_directories(${PROJECT_NAME} PRIVATE target_link_libraries(${PROJECT_NAME} PRIVATE SDL3::SDL3) +# Copia recursius de la carpeta data/ al costat del binari per que les rutes +# relatives ("data/player/player.gif") funcionen sense desplegament. +add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_SOURCE_DIR}/data" + "$/data" + COMMENT "Copying data/ next to the binary" +) + target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic) target_compile_definitions(${PROJECT_NAME} PRIVATE $<$:_DEBUG> @@ -120,7 +138,8 @@ if(CPPCHECK_EXE) --inline-suppr --suppress=missingIncludeSystem --suppress=toomanyconfigs - --suppress=*:*/source/external/* + --suppress=*:*source/external/* + --suppress=normalCheckLevelMaxBranches -D_DEBUG -DLINUX_BUILD --quiet diff --git a/data/player/player.gif b/data/player/player.gif new file mode 100644 index 0000000..0b80dbb Binary files /dev/null and b/data/player/player.gif differ diff --git a/data/player/player.yaml b/data/player/player.yaml new file mode 100644 index 0000000..b88c2f8 --- /dev/null +++ b/data/player/player.yaml @@ -0,0 +1,24 @@ +# player animation +tileSetFile: player.gif +frameWidth: 12 +frameHeight: 24 + +animations: + - name: stand + frames: [1] + + - name: walk + speed: 0.07 + loopFrom: 0 + frames: [2, 3, 4, 1] + + - name: turn_walk + speed: [0.1, 0.07, 0.07, 0.07, 0.07] + loopFrom: 1 + frames: [0, 2, 3, 4, 1] + + - name: jump + frames: [6] + + - name: jump_peak + frames: [5] diff --git a/source/core/rendering/gif.cpp b/source/core/rendering/gif.cpp new file mode 100644 index 0000000..e39cd7f --- /dev/null +++ b/source/core/rendering/gif.cpp @@ -0,0 +1,295 @@ +#include "core/rendering/gif.hpp" + +#include // Para memcpy, size_t +#include // Para std::cout +#include // Para runtime_error +#include // Para allocator, char_traits, operator==, basic_string + +namespace GIF { + + // Función inline para reemplazar el macro READ. + // Actualiza el puntero 'buffer' tras copiar 'size' bytes a 'dst'. + inline void readBytes(const uint8_t*& buffer, void* dst, size_t size) { + std::memcpy(dst, buffer, size); + buffer += size; + } + + // Inicializa el diccionario LZW con los valores iniciales + inline void initializeDictionary(std::vector& dictionary, int code_length, int& dictionary_ind) { // NOLINT(readability-identifier-naming) + int size = 1 << code_length; + dictionary.resize(1 << (code_length + 1)); + for (dictionary_ind = 0; dictionary_ind < size; dictionary_ind++) { + dictionary[dictionary_ind].byte = static_cast(dictionary_ind); + dictionary[dictionary_ind].prev = -1; + dictionary[dictionary_ind].len = 1; + } + dictionary_ind += 2; // Reservamos espacio para clear y stop codes + } + + // Lee los próximos bits del stream de entrada para formar un código + inline auto readNextCode(const uint8_t*& input, int& input_length, unsigned int& mask, int code_length) -> int { + int code = 0; + for (int i = 0; i < (code_length + 1); i++) { + if (input_length <= 0) { + throw std::runtime_error("Unexpected end of input in decompress"); + } + int bit = ((*input & mask) != 0) ? 1 : 0; + mask <<= 1; + if (mask == 0x100) { + mask = 0x01; + input++; + input_length--; + } + code |= (bit << i); + } + return code; + } + + // Encuentra el primer byte de una cadena del diccionario + inline auto findFirstByte(const std::vector& dictionary, int code) -> uint8_t { + int ptr = code; + while (dictionary[ptr].prev != -1) { + ptr = dictionary[ptr].prev; + } + return dictionary[ptr].byte; + } + + // Agrega una nueva entrada al diccionario + inline void addDictionaryEntry(std::vector& dictionary, int& dictionary_ind, int& code_length, int prev, int code) { // NOLINT(readability-identifier-naming) + uint8_t first_byte; + if (code == dictionary_ind) { + first_byte = findFirstByte(dictionary, prev); + } else { + first_byte = findFirstByte(dictionary, code); + } + + dictionary[dictionary_ind].byte = first_byte; + dictionary[dictionary_ind].prev = prev; + dictionary[dictionary_ind].len = dictionary[prev].len + 1; + dictionary_ind++; + + if ((dictionary_ind == (1 << (code_length + 1))) && (code_length < 11)) { + code_length++; + dictionary.resize(1 << (code_length + 1)); + } + } + + // Escribe la cadena decodificada al buffer de salida + inline auto writeDecodedString(const std::vector& dictionary, int code, uint8_t*& out) -> int { + int cur_code = code; + int match_len = dictionary[cur_code].len; + while (cur_code != -1) { + out[dictionary[cur_code].len - 1] = dictionary[cur_code].byte; + if (dictionary[cur_code].prev == cur_code) { + std::cerr << "Internal error; self-reference detected." << '\n'; + throw std::runtime_error("Internal error in decompress: self-reference"); + } + cur_code = dictionary[cur_code].prev; + } + out += match_len; + return match_len; + } + + void Gif::decompress(int code_length, const uint8_t* input, int input_length, uint8_t* out) { + // Verifica que el code_length tenga un rango razonable. + if (code_length < 2 || code_length > 12) { + throw std::runtime_error("Invalid LZW code length"); + } + + int prev = -1; + std::vector dictionary; + int dictionary_ind; + unsigned int mask = 0x01; + int reset_code_length = code_length; + int clear_code = 1 << code_length; + int stop_code = clear_code + 1; + + // Inicializamos el diccionario con el tamaño correspondiente. + initializeDictionary(dictionary, code_length, dictionary_ind); + + // Bucle principal: procesar el stream comprimido. + while (input_length > 0) { + int code = readNextCode(input, input_length, mask, code_length); + + if (code == clear_code) { + // Reinicia el diccionario. + code_length = reset_code_length; + initializeDictionary(dictionary, code_length, dictionary_ind); + prev = -1; + continue; + } + + if (code == stop_code) { + break; + } + + if (prev > -1 && code_length < 12) { + if (code > dictionary_ind) { + std::cerr << "code = " << std::hex << code + << ", but dictionary_ind = " << dictionary_ind << '\n'; + throw std::runtime_error("LZW error: code exceeds dictionary_ind."); + } + + addDictionaryEntry(dictionary, dictionary_ind, code_length, prev, code); + } + + prev = code; + + // Verifica que 'code' sea un índice válido antes de usarlo. + if (code < 0 || static_cast(code) >= dictionary.size()) { + std::cerr << "Invalid LZW code " << code + << ", dictionary size " << dictionary.size() << '\n'; + throw std::runtime_error("LZW error: invalid code encountered"); + } + + writeDecodedString(dictionary, code, out); + } + } + + auto Gif::readSubBlocks(const uint8_t*& buffer) -> std::vector { + std::vector data; + uint8_t block_size = *buffer; + buffer++; + while (block_size != 0) { + data.insert(data.end(), buffer, buffer + block_size); + buffer += block_size; + block_size = *buffer; + buffer++; + } + return data; + } + + auto Gif::processImageDescriptor(const uint8_t*& buffer, [[maybe_unused]] const std::vector& gct, [[maybe_unused]] int resolution_bits) -> std::vector { + ImageDescriptor image_descriptor; + // Lee 9 bytes para el image descriptor. + readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor)); + + uint8_t lzw_code_size; + readBytes(buffer, &lzw_code_size, sizeof(uint8_t)); + + std::vector compressed_data = readSubBlocks(buffer); + int uncompressed_data_length = image_descriptor.image_width * image_descriptor.image_height; + std::vector uncompressed_data(uncompressed_data_length); + + decompress(lzw_code_size, compressed_data.data(), static_cast(compressed_data.size()), uncompressed_data.data()); + return uncompressed_data; + } + + auto Gif::loadPalette(const uint8_t* buffer) -> std::vector { + uint8_t header[6]; + std::memcpy(header, buffer, 6); + buffer += 6; + + ScreenDescriptor screen_descriptor; + std::memcpy(&screen_descriptor, buffer, sizeof(ScreenDescriptor)); + buffer += sizeof(ScreenDescriptor); + + std::vector global_color_table; + if ((screen_descriptor.fields & 0x80) != 0) { + int global_color_table_size = 1 << ((screen_descriptor.fields & 0x07) + 1); + global_color_table.resize(global_color_table_size); + for (int i = 0; i < global_color_table_size; ++i) { + uint8_t r = buffer[0]; + uint8_t g = buffer[1]; + uint8_t b = buffer[2]; + global_color_table[i] = (r << 16) | (g << 8) | b; + buffer += 3; + } + } + return global_color_table; + } + + auto Gif::processGifStream(const uint8_t* buffer, uint16_t& w, uint16_t& h) -> std::vector { + // Leer la cabecera de 6 bytes ("GIF87a" o "GIF89a") + uint8_t header[6]; + std::memcpy(header, buffer, 6); + buffer += 6; + + // Opcional: Validar header + std::string header_str(reinterpret_cast(header), 6); + if (header_str != "GIF87a" && header_str != "GIF89a") { + throw std::runtime_error("Formato de archivo GIF inválido."); + } + + // Leer el Screen Descriptor (7 bytes, empaquetado sin padding) + ScreenDescriptor screen_descriptor; + readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor)); + + // Asigna ancho y alto + w = screen_descriptor.width; + h = screen_descriptor.height; + + int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1; + std::vector global_color_table; + if ((screen_descriptor.fields & 0x80) != 0) { + int global_color_table_size = 1 << ((screen_descriptor.fields & 0x07) + 1); + global_color_table.resize(global_color_table_size); + std::memcpy(global_color_table.data(), buffer, static_cast(3) * static_cast(global_color_table_size)); + buffer += static_cast(3) * global_color_table_size; + } + + // Supongamos que 'buffer' es el puntero actual y TRAILER es 0x3B + uint8_t block_type = *buffer++; + while (block_type != TRAILER) { + if (block_type == EXTENSION_INTRODUCER) // 0x21 + { + // Se lee la etiqueta de extensión, la cual indica el tipo de extensión. + uint8_t extension_label = *buffer++; + switch (extension_label) { + case GRAPHIC_CONTROL: // 0xF9 + { + // Procesar Graphic Control Extension: + uint8_t block_size = *buffer++; // Normalmente, blockSize == 4 + buffer += block_size; // Saltamos los 4 bytes del bloque fijo + // Saltar los sub-bloques + uint8_t sub_block_size = *buffer++; + while (sub_block_size != 0) { + buffer += sub_block_size; + sub_block_size = *buffer++; + } + break; + } + case APPLICATION_EXTENSION: // 0xFF + case COMMENT_EXTENSION: // 0xFE + case PLAINTEXT_EXTENSION: // 0x01 + { + // Para estas extensiones, saltamos el bloque fijo y los sub-bloques. + uint8_t block_size = *buffer++; + buffer += block_size; + uint8_t sub_block_size = *buffer++; + while (sub_block_size != 0) { + buffer += sub_block_size; + sub_block_size = *buffer++; + } + break; + } + default: { + // Si la etiqueta de extensión es desconocida, saltarla también: + uint8_t block_size = *buffer++; + buffer += block_size; + uint8_t sub_block_size = *buffer++; + while (sub_block_size != 0) { + buffer += sub_block_size; + sub_block_size = *buffer++; + } + break; + } + } + } else if (block_type == IMAGE_DESCRIPTOR) { + // Procesar el Image Descriptor y retornar los datos de imagen + return processImageDescriptor(buffer, global_color_table, color_resolution_bits); + } else { + std::cerr << "Unrecognized block type " << std::hex << static_cast(block_type) << '\n'; + return std::vector{}; + } + block_type = *buffer++; + } + + return std::vector{}; + } + + auto Gif::loadGif(const uint8_t* buffer, uint16_t& w, uint16_t& h) -> std::vector { + return processGifStream(buffer, w, h); + } + +} // namespace GIF diff --git a/source/core/rendering/gif.hpp b/source/core/rendering/gif.hpp new file mode 100644 index 0000000..b6443f1 --- /dev/null +++ b/source/core/rendering/gif.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include // Para uint8_t, uint16_t, uint32_t +#include // Para vector + +namespace GIF { + + // Constantes definidas con constexpr, en lugar de macros + constexpr uint8_t EXTENSION_INTRODUCER = 0x21; + constexpr uint8_t IMAGE_DESCRIPTOR = 0x2C; + constexpr uint8_t TRAILER = 0x3B; + constexpr uint8_t GRAPHIC_CONTROL = 0xF9; + constexpr uint8_t APPLICATION_EXTENSION = 0xFF; + constexpr uint8_t COMMENT_EXTENSION = 0xFE; + constexpr uint8_t PLAINTEXT_EXTENSION = 0x01; + +#pragma pack(push, 1) + struct ScreenDescriptor { + uint16_t width; + uint16_t height; + uint8_t fields; + uint8_t background_color_index; + uint8_t pixel_aspect_ratio; + }; + + struct RGB { + uint8_t r, g, b; + }; + + struct ImageDescriptor { + uint16_t image_left_position; + uint16_t image_top_position; + uint16_t image_width; + uint16_t image_height; + uint8_t fields; + }; +#pragma pack(pop) + + struct DictionaryEntry { + uint8_t byte; + int prev; + int len; + }; + + struct Extension { + uint8_t extension_code; + uint8_t block_size; + }; + + struct GraphicControlExtension { + uint8_t fields; + uint16_t delay_time; + uint8_t transparent_color_index; + }; + + struct ApplicationExtension { + uint8_t application_id[8]; + uint8_t version[3]; + }; + + struct PlaintextExtension { + uint16_t left, top, width, height; + uint8_t cell_width, cell_height; + uint8_t foreground_color, background_color; + }; + + class Gif { + public: + // Descompone (uncompress) el bloque comprimido usando LZW. + // Este método puede lanzar std::runtime_error en caso de error. + static void decompress(int code_length, const uint8_t* input, int input_length, uint8_t* out); + + // Carga la paleta (global color table) a partir de un buffer, + // retornándola en un vector de uint32_t (cada color se compone de R, G, B). + static auto loadPalette(const uint8_t* buffer) -> std::vector; + + // Carga el stream GIF; devuelve un vector con los datos de imagen sin comprimir y + // asigna el ancho y alto mediante referencias. + static auto loadGif(const uint8_t* buffer, uint16_t& w, uint16_t& h) -> std::vector; + + private: + // Lee los sub-bloques de datos y los acumula en un std::vector. + static auto readSubBlocks(const uint8_t*& buffer) -> std::vector; + + // Procesa el Image Descriptor y retorna el vector de datos sin comprimir. + static auto processImageDescriptor(const uint8_t*& buffer, const std::vector& gct, int resolution_bits) -> std::vector; + + // Procesa el stream completo del GIF y devuelve los datos sin comprimir. + static auto processGifStream(const uint8_t* buffer, uint16_t& w, uint16_t& h) -> std::vector; + }; + +} // namespace GIF diff --git a/source/core/rendering/screen.cpp b/source/core/rendering/screen.cpp index c7450c5..bb7ab4a 100644 --- a/source/core/rendering/screen.cpp +++ b/source/core/rendering/screen.cpp @@ -2,8 +2,11 @@ #include +#include +#include #include #include +#include #include "utils/defaults.hpp" #include "utils/defines.hpp" @@ -32,24 +35,15 @@ namespace { } // namespace -Screen* Screen::instance = nullptr; - -void Screen::init() { - if (instance == nullptr) { - instance = new Screen(); - } +auto Screen::get() -> Screen& { + static Screen instance_; + return instance_; } -void Screen::destroy() { - delete instance; - instance = nullptr; -} - -auto Screen::get() -> Screen* { - return instance; -} - -Screen::Screen() { +Screen::Screen() + : canvas_width_(GameCanvas::WIDTH), + canvas_height_(GameCanvas::HEIGHT), + canvas_(static_cast(GameCanvas::WIDTH) * static_cast(GameCanvas::HEIGHT), 0xFF000000U) { if (!SDL_InitSubSystem(SDL_INIT_VIDEO)) { std::fprintf(stderr, "SDL_InitSubSystem(VIDEO) failed: %s\n", SDL_GetError()); std::exit(EXIT_FAILURE); @@ -76,9 +70,9 @@ Screen::Screen() { SDL_SetRenderVSync(renderer_, Defaults::Video::VSYNC ? 1 : 0); SDL_SetRenderLogicalPresentation(renderer_, GameCanvas::WIDTH, GameCanvas::HEIGHT, toSdlPresentation(Defaults::Video::SCALING_MODE)); - // Destination ARGB texture: paletted surfaces will be blitted onto this - // (each surface contributes its own palette, so the final image is full ARGB). - game_texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, GameCanvas::WIDTH, GameCanvas::HEIGHT); + // STREAMING: el framebuffer ARGB del joc s'actualitza per CPU cada frame + // i s'envia a la GPU via SDL_UpdateTexture en present(). + game_texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, GameCanvas::WIDTH, GameCanvas::HEIGHT); if (game_texture_ == nullptr) { std::fprintf(stderr, "SDL_CreateTexture failed: %s\n", SDL_GetError()); std::exit(EXIT_FAILURE); @@ -94,13 +88,13 @@ Screen::~Screen() { } void Screen::clear(std::uint8_t r, std::uint8_t g, std::uint8_t b) { - SDL_SetRenderTarget(renderer_, game_texture_); - SDL_SetRenderDrawColor(renderer_, r, g, b, 0xFF); - SDL_RenderClear(renderer_); - SDL_SetRenderTarget(renderer_, nullptr); + const std::uint32_t COLOR = (0xFFU << 24) | (static_cast(r) << 16) | (static_cast(g) << 8) | static_cast(b); + std::ranges::fill(canvas_, COLOR); } void Screen::present() { + SDL_UpdateTexture(game_texture_, nullptr, canvas_.data(), canvas_width_ * static_cast(sizeof(std::uint32_t))); + SDL_SetRenderDrawColor(renderer_, 0x00, 0x00, 0x00, 0xFF); SDL_RenderClear(renderer_); SDL_RenderTexture(renderer_, game_texture_, nullptr, nullptr); diff --git a/source/core/rendering/screen.hpp b/source/core/rendering/screen.hpp index 936bed9..57403a8 100644 --- a/source/core/rendering/screen.hpp +++ b/source/core/rendering/screen.hpp @@ -3,26 +3,38 @@ #include #include +#include class Screen { public: - static void init(); - static void destroy(); - static auto get() -> Screen*; + // Meyers singleton: la destrucció ocorre durant la termianció estàtica + // (LIFO respecte a Resource::Cache). El SDL_Quit s'ha de registrar via + // std::atexit ABANS de la primera crida a get(); així Screen::~Screen + // (que crida SDL_Destroy*/SDL_QuitSubSystem) s'executa amb SDL encara viu. + static auto get() -> Screen&; + + Screen(const Screen&) = delete; + Screen(Screen&&) = delete; + auto operator=(const Screen&) -> Screen& = delete; + auto operator=(Screen&&) -> Screen& = delete; void clear(std::uint8_t r = 0, std::uint8_t g = 0, std::uint8_t b = 0); void present(); [[nodiscard]] auto getRenderer() const -> SDL_Renderer* { return renderer_; } - [[nodiscard]] auto getTarget() const -> SDL_Texture* { return game_texture_; } + [[nodiscard]] auto getCanvasArgb() -> std::uint32_t* { return canvas_.data(); } + [[nodiscard]] auto getCanvasWidth() const -> int { return canvas_width_; } + [[nodiscard]] auto getCanvasHeight() const -> int { return canvas_height_; } private: Screen(); ~Screen(); - static Screen* instance; - SDL_Window* window_{nullptr}; SDL_Renderer* renderer_{nullptr}; SDL_Texture* game_texture_{nullptr}; + + int canvas_width_{0}; + int canvas_height_{0}; + std::vector canvas_; }; diff --git a/source/core/rendering/sprite/animated_sprite.cpp b/source/core/rendering/sprite/animated_sprite.cpp new file mode 100644 index 0000000..1b3c91e --- /dev/null +++ b/source/core/rendering/sprite/animated_sprite.cpp @@ -0,0 +1,201 @@ +#include "core/rendering/sprite/animated_sprite.hpp" + +#include +#include +#include + +#include "core/rendering/surface.hpp" +#include "core/resources/resource_cache.hpp" +#include "external/fkyaml_node.hpp" + +namespace { + + auto convertYamlFramesToRects(const fkyaml::node& frames_node, float frame_width, float frame_height, int frames_per_row, int max_tiles) -> std::vector { + std::vector frames; + SDL_FRect rect = {.x = 0.0F, .y = 0.0F, .w = frame_width, .h = frame_height}; + for (const auto& frame_index_node : frames_node) { + const int NUM_TILE = frame_index_node.get_value(); + if (NUM_TILE <= max_tiles) { + rect.x = static_cast(NUM_TILE % frames_per_row) * frame_width; + rect.y = static_cast(NUM_TILE / frames_per_row) * frame_height; + frames.emplace_back(rect); + } + } + return frames; + } + + auto parseAnimations(const fkyaml::node& yaml, float frame_width, float frame_height, int frames_per_row, int max_tiles) -> std::vector { + std::vector animations; + if (!yaml.contains("animations") || !yaml["animations"].is_sequence()) { + return animations; + } + + for (const auto& anim_node : yaml["animations"]) { + AnimatedSprite::AnimationData animation; + + if (anim_node.contains("name")) { + animation.name = anim_node["name"].get_value(); + } + if (anim_node.contains("frames") && anim_node["frames"].is_sequence()) { + animation.frames = convertYamlFramesToRects( + anim_node["frames"], + frame_width, + frame_height, + frames_per_row, + max_tiles); + } + if (anim_node.contains("speed")) { + const auto& speed_node = anim_node["speed"]; + if (speed_node.is_sequence()) { + for (const auto& s : speed_node) { + animation.speeds.push_back(s.get_value()); + } + } else { + auto spd = speed_node.get_value(); + if (spd > 0.0F) { animation.speeds.assign(animation.frames.size(), spd); } + } + } + if (anim_node.contains("loopFrom")) { + animation.loop_from = anim_node["loopFrom"].get_value(); + } + + animations.push_back(animation); + } + return animations; + } + + struct SheetParams { + float frame_width{0.0F}; + float frame_height{0.0F}; + int frames_per_row{1}; + int max_tiles{1}; + }; + + auto parseGlobalConfig(const fkyaml::node& yaml, std::shared_ptr& surface) -> SheetParams { + SheetParams params; + if (yaml.contains("tileSetFile")) { + surface = Resource::Cache::get().getSurface(yaml["tileSetFile"].get_value()); + } + if (yaml.contains("frameWidth")) { + params.frame_width = static_cast(yaml["frameWidth"].get_value()); + } + if (yaml.contains("frameHeight")) { + params.frame_height = static_cast(yaml["frameHeight"].get_value()); + } + if (surface && params.frame_width > 0.0F && params.frame_height > 0.0F) { + params.frames_per_row = surface->getWidth() / static_cast(params.frame_width); + const int H_TILES = surface->getHeight() / static_cast(params.frame_height); + params.max_tiles = params.frames_per_row * H_TILES; + } + return params; + } + +} // namespace + +AnimatedSprite::AnimatedSprite(const AnimationResource& cached_data) { + std::string yaml_content(cached_data.yaml_data.begin(), cached_data.yaml_data.end()); + try { + auto yaml = fkyaml::node::deserialize(yaml_content); + auto params = parseGlobalConfig(yaml, surface_); + animations_ = parseAnimations(yaml, params.frame_width, params.frame_height, params.frames_per_row, params.max_tiles); + buildNameIndex(); + + setWidth(params.frame_width); + setHeight(params.frame_height); + + if (!animations_.empty() && !animations_[0].frames.empty()) { + setClip(animations_[0].frames[0]); + } + } catch (const fkyaml::exception& e) { + std::cerr << "YAML parsing error in animation " << cached_data.name << ": " << e.what() << '\n'; + throw; + } catch (const std::exception& e) { + std::cerr << "Error loading animation " << cached_data.name << ": " << e.what() << '\n'; + throw; + } +} + +void AnimatedSprite::buildNameIndex() { + animation_index_.clear(); + for (int i = 0; i < static_cast(animations_.size()); ++i) { + animation_index_[animations_[i].name] = i; + } +} + +void AnimatedSprite::animate(float delta_time) { + if (animations_.empty()) { return; } + auto& anim = animations_[current_animation_]; + if (anim.speeds.empty()) { return; } + + anim.accumulated_time += delta_time; + + const float TOTAL = std::accumulate(anim.speeds.begin(), anim.speeds.end(), 0.0F); + + if (anim.accumulated_time >= TOTAL) { + if (anim.loop_from < 0) { + anim.current_frame = static_cast(anim.frames.size()) - 1; + anim.completed = true; + setClip(anim.frames[anim.current_frame]); + return; + } + float loop_start = 0.0F; + for (int i = 0; i < anim.loop_from; ++i) { loop_start += anim.speeds[i]; } + float loop_len = TOTAL - loop_start; + anim.accumulated_time = loop_start + std::fmod(anim.accumulated_time - loop_start, loop_len); + } + + float cursor = 0.0F; + for (int i = 0; i < static_cast(anim.frames.size()); ++i) { + cursor += anim.speeds[i]; + if (anim.accumulated_time < cursor) { + anim.current_frame = i; + setClip(anim.frames[i]); + return; + } + } + + anim.current_frame = static_cast(anim.frames.size()) - 1; + setClip(anim.frames[anim.current_frame]); +} + +auto AnimatedSprite::animationIsCompleted() -> bool { + return animations_[current_animation_].completed; +} + +void AnimatedSprite::setCurrentAnimation(const std::string& name) { + auto it = animation_index_.find(name); + if (it == animation_index_.end()) { + std::cout << "** Warning: could not find \"" << name << "\" animation" << '\n'; + return; + } + setCurrentAnimation(it->second); +} + +void AnimatedSprite::setCurrentAnimation(int index) { + if (current_animation_ != index) { + current_animation_ = index; + animations_[current_animation_].current_frame = 0; + animations_[current_animation_].accumulated_time = 0.0F; + animations_[current_animation_].completed = false; + setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]); + } +} + +void AnimatedSprite::update(float delta_time) { + animate(delta_time); + MovingSprite::update(delta_time); +} + +void AnimatedSprite::resetAnimation() { + animations_[current_animation_].current_frame = 0; + animations_[current_animation_].accumulated_time = 0.0F; + animations_[current_animation_].completed = false; +} + +void AnimatedSprite::setCurrentAnimationFrame(int num) { + if (num < 0 || num >= static_cast(animations_[current_animation_].frames.size())) { + num = 0; + } + animations_[current_animation_].current_frame = num; + setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]); +} diff --git a/source/core/rendering/sprite/animated_sprite.hpp b/source/core/rendering/sprite/animated_sprite.hpp new file mode 100644 index 0000000..bead9ac --- /dev/null +++ b/source/core/rendering/sprite/animated_sprite.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include + +#include +#include +#include + +#include "core/rendering/sprite/moving_sprite.hpp" +#include "core/resources/resource_types.hpp" + +class AnimatedSprite : public MovingSprite { + public: + struct AnimationData { + std::string name; + std::vector frames; + std::vector speeds; + int loop_from{-1}; + bool completed{false}; + int current_frame{0}; + float accumulated_time{0.0F}; + }; + + explicit AnimatedSprite(const AnimationResource& cached_data); + ~AnimatedSprite() override = default; + + void update(float delta_time) override; + + auto animationIsCompleted() -> bool; + auto getCurrentAnimationSize() -> int { return static_cast(animations_[current_animation_].frames.size()); } + + void setCurrentAnimation(const std::string& name = "default"); + void setCurrentAnimation(int index = 0); + void resetAnimation(); + void setCurrentAnimationFrame(int num); + void animate(float delta_time); + + private: + void buildNameIndex(); + + std::vector animations_; + std::unordered_map animation_index_; + int current_animation_{0}; +}; diff --git a/source/core/rendering/sprite/moving_sprite.cpp b/source/core/rendering/sprite/moving_sprite.cpp new file mode 100644 index 0000000..b4cfeea --- /dev/null +++ b/source/core/rendering/sprite/moving_sprite.cpp @@ -0,0 +1,72 @@ +#include "core/rendering/sprite/moving_sprite.hpp" + +#include + +#include "core/rendering/surface.hpp" + +MovingSprite::MovingSprite() { Sprite::clear(); } + +MovingSprite::MovingSprite(std::shared_ptr surface) + : Sprite(std::move(surface)) { Sprite::clear(); } + +MovingSprite::MovingSprite(std::shared_ptr surface, SDL_FRect pos) + : Sprite(std::move(surface), pos), + x_(pos.x), + y_(pos.y) { Sprite::pos_ = pos; } + +MovingSprite::MovingSprite(std::shared_ptr surface, SDL_FRect pos, SDL_FlipMode flip) + : Sprite(std::move(surface), pos), + x_(pos.x), + y_(pos.y), + flip_(flip) { Sprite::pos_ = pos; } + +void MovingSprite::clear() { + x_ = 0.0F; + y_ = 0.0F; + vx_ = 0.0F; + vy_ = 0.0F; + ax_ = 0.0F; + ay_ = 0.0F; + flip_ = SDL_FLIP_NONE; + Sprite::clear(); +} + +void MovingSprite::move(float delta_time) { + vx_ += ax_ * delta_time; + vy_ += ay_ * delta_time; + x_ += vx_ * delta_time; + y_ += vy_ * delta_time; + pos_.x = static_cast(x_); + pos_.y = static_cast(y_); +} + +void MovingSprite::update(float delta_time) { + move(delta_time); +} + +void MovingSprite::render() { + surface_->render(static_cast(pos_.x), static_cast(pos_.y), &clip_, flip_); +} + +void MovingSprite::setPos(SDL_FRect rect) { + x_ = rect.x; + y_ = rect.y; + pos_ = rect; +} + +void MovingSprite::setPos(float x, float y) { + x_ = x; + y_ = y; + pos_.x = static_cast(x_); + pos_.y = static_cast(y_); +} + +void MovingSprite::setPosX(float value) { + x_ = value; + pos_.x = static_cast(x_); +} + +void MovingSprite::setPosY(float value) { + y_ = value; + pos_.y = static_cast(y_); +} diff --git a/source/core/rendering/sprite/moving_sprite.hpp b/source/core/rendering/sprite/moving_sprite.hpp new file mode 100644 index 0000000..be6932d --- /dev/null +++ b/source/core/rendering/sprite/moving_sprite.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include + +#include + +#include "core/rendering/sprite/sprite.hpp" + +class Surface; + +class MovingSprite : public Sprite { + public: + MovingSprite(); + explicit MovingSprite(std::shared_ptr surface); + MovingSprite(std::shared_ptr surface, SDL_FRect pos); + MovingSprite(std::shared_ptr surface, SDL_FRect pos, SDL_FlipMode flip); + ~MovingSprite() override = default; + + void update(float delta_time) override; + void render() override; + void clear() override; + + [[nodiscard]] auto getPosX() const -> float { return x_; } + [[nodiscard]] auto getPosY() const -> float { return y_; } + [[nodiscard]] auto getVelX() const -> float { return vx_; } + [[nodiscard]] auto getVelY() const -> float { return vy_; } + [[nodiscard]] auto getAccelX() const -> float { return ax_; } + [[nodiscard]] auto getAccelY() const -> float { return ay_; } + + void setPos(SDL_FRect rect); + void setPos(float x, float y); + void setPosX(float value); + void setPosY(float value); + void setVelX(float value) { vx_ = value; } + void setVelY(float value) { vy_ = value; } + void setAccelX(float value) { ax_ = value; } + void setAccelY(float value) { ay_ = value; } + + void setFlip(SDL_FlipMode flip) { flip_ = flip; } + auto getFlip() -> SDL_FlipMode { return flip_; } + void flip() { flip_ = (flip_ == SDL_FLIP_HORIZONTAL) ? SDL_FLIP_NONE : SDL_FLIP_HORIZONTAL; } + + protected: + void move(float delta_time); + + float x_{0.0F}; + float y_{0.0F}; + float vx_{0.0F}; + float vy_{0.0F}; + float ax_{0.0F}; + float ay_{0.0F}; + SDL_FlipMode flip_{SDL_FLIP_NONE}; +}; diff --git a/source/core/rendering/sprite/sprite.cpp b/source/core/rendering/sprite/sprite.cpp new file mode 100644 index 0000000..cfa659f --- /dev/null +++ b/source/core/rendering/sprite/sprite.cpp @@ -0,0 +1,45 @@ +#include "core/rendering/sprite/sprite.hpp" + +#include + +#include "core/rendering/surface.hpp" + +Sprite::Sprite() = default; + +Sprite::Sprite(std::shared_ptr surface, float x, float y, float w, float h) + : surface_(std::move(surface)), + pos_{.x = x, .y = y, .w = w, .h = h}, + clip_{.x = 0.0F, .y = 0.0F, .w = pos_.w, .h = pos_.h} {} + +Sprite::Sprite(std::shared_ptr surface, SDL_FRect rect) + : surface_(std::move(surface)), + pos_(rect), + clip_{.x = 0.0F, .y = 0.0F, .w = pos_.w, .h = pos_.h} {} + +Sprite::Sprite(std::shared_ptr surface) + : surface_(std::move(surface)), + pos_{0.0F, 0.0F, static_cast(surface_->getWidth()), static_cast(surface_->getHeight())}, + clip_(pos_) {} + +void Sprite::render() { + surface_->render(static_cast(pos_.x), static_cast(pos_.y), &clip_); +} + +void Sprite::setPosition(float x, float y) { + pos_.x = x; + pos_.y = y; +} + +void Sprite::setPosition(SDL_FPoint p) { + pos_.x = p.x; + pos_.y = p.y; +} + +void Sprite::clear() { + pos_ = {.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F}; + clip_ = {.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F}; +} + +void Sprite::update(float delta_time) { + (void)delta_time; +} diff --git a/source/core/rendering/sprite/sprite.hpp b/source/core/rendering/sprite/sprite.hpp new file mode 100644 index 0000000..7ffab5c --- /dev/null +++ b/source/core/rendering/sprite/sprite.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include + +#include +#include + +class Surface; + +class Sprite { + public: + Sprite(); + Sprite(std::shared_ptr surface, float x, float y, float w, float h); + Sprite(std::shared_ptr surface, SDL_FRect rect); + explicit Sprite(std::shared_ptr surface); + + virtual ~Sprite() = default; + + virtual void update(float delta_time); + virtual void render(); + virtual void clear(); + + [[nodiscard]] auto getX() const -> float { return pos_.x; } + [[nodiscard]] auto getY() const -> float { return pos_.y; } + [[nodiscard]] auto getWidth() const -> float { return pos_.w; } + [[nodiscard]] auto getHeight() const -> float { return pos_.h; } + [[nodiscard]] auto getPosition() const -> SDL_FRect { return pos_; } + [[nodiscard]] auto getClip() const -> SDL_FRect { return clip_; } + [[nodiscard]] auto getSurface() const -> std::shared_ptr { return surface_; } + auto getRect() -> SDL_FRect& { return pos_; } + + void setX(float x) { pos_.x = x; } + void setY(float y) { pos_.y = y; } + void setWidth(float w) { pos_.w = w; } + void setHeight(float h) { pos_.h = h; } + void setPosition(float x, float y); + void setPosition(SDL_FPoint p); + void setPosition(SDL_FRect r) { pos_ = r; } + void incX(float value) { pos_.x += value; } + void incY(float value) { pos_.y += value; } + + void setClip(SDL_FRect rect) { clip_ = rect; } + void setClip(float x, float y, float w, float h) { clip_ = SDL_FRect{.x = x, .y = y, .w = w, .h = h}; } + void setSurface(std::shared_ptr surface) { surface_ = std::move(surface); } + + protected: + std::shared_ptr surface_{nullptr}; + SDL_FRect pos_{.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F}; + SDL_FRect clip_{.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F}; +}; diff --git a/source/core/rendering/surface.cpp b/source/core/rendering/surface.cpp new file mode 100644 index 0000000..32cf7eb --- /dev/null +++ b/source/core/rendering/surface.cpp @@ -0,0 +1,115 @@ +#include "core/rendering/surface.hpp" + +#include +#include +#include +#include +#include + +#include "core/rendering/gif.hpp" +#include "core/rendering/screen.hpp" +#include "core/resources/resource_helper.hpp" +#include "utils/utils.hpp" + +auto loadPaletteFromGif(const std::string& file_path) -> Palette { + auto buffer = Resource::Helper::loadFile(file_path); + if (buffer.empty()) { + throw std::runtime_error("Surface: cannot open palette file " + file_path); + } + + std::vector rgb = GIF::Gif::loadPalette(buffer.data()); + if (rgb.empty()) { + throw std::runtime_error("Surface: no palette in GIF " + file_path); + } + + Palette palette = {}; + // Convertim RGB → ARGB amb canal alpha 0xFF. + const std::size_t COUNT = std::min(rgb.size(), palette.size()); + for (std::size_t i = 0; i < COUNT; ++i) { + palette[i] = 0xFF000000U | rgb[i]; + } + return palette; +} + +Surface::Surface(int w, int h) + : surface_data_(std::make_shared(w, h)) { + initializeSubPalette(sub_palette_); +} + +Surface::Surface(const std::string& file_path) { + SurfaceData loaded = loadSurface(file_path); + surface_data_ = std::make_shared(std::move(loaded)); + initializeSubPalette(sub_palette_); + palette_ = loadPaletteFromGif(file_path); +} + +auto Surface::loadSurface(const std::string& file_path) -> SurfaceData { + auto buffer = Resource::Helper::loadFile(file_path); + if (buffer.empty()) { + throw std::runtime_error("Surface: cannot open " + file_path); + } + + Uint16 w = 0; + Uint16 h = 0; + std::vector raw_pixels = GIF::Gif::loadGif(buffer.data(), w, h); + if (raw_pixels.empty()) { + throw std::runtime_error("Surface: failed to decode GIF " + file_path); + } + + auto pixels = std::shared_ptr(new Uint8[raw_pixels.size()], std::default_delete()); + std::memcpy(pixels.get(), raw_pixels.data(), raw_pixels.size()); + + printWithDots("Surface : ", getFileName(file_path), "[ LOADED ]"); + return {static_cast(w), static_cast(h), pixels}; +} + +void Surface::loadPalette(const std::string& file_path) { + palette_ = loadPaletteFromGif(file_path); +} + +void Surface::loadPalette(const Palette& palette) { + palette_ = palette; +} + +void Surface::clear(Uint8 color) { + const std::size_t TOTAL = static_cast(surface_data_->width) * static_cast(surface_data_->height); + std::fill(surface_data_->data.get(), surface_data_->data.get() + TOTAL, color); +} + +void Surface::putPixel(int x, int y, Uint8 color) { + if (x < 0 || y < 0 || x >= surface_data_->width || y >= surface_data_->height) { return; } + surface_data_->data.get()[x + (y * surface_data_->width)] = color; +} + +auto Surface::getPixel(int x, int y) const -> Uint8 { + return surface_data_->data.get()[x + (y * surface_data_->width)]; +} + +void Surface::render(int x, int y, const SDL_FRect* src_rect, SDL_FlipMode flip) const { + Uint32* dst = Screen::get().getCanvasArgb(); + const int DST_W = Screen::get().getCanvasWidth(); + const int DST_H = Screen::get().getCanvasHeight(); + + const int SX = (src_rect != nullptr) ? static_cast(src_rect->x) : 0; + const int SY = (src_rect != nullptr) ? static_cast(src_rect->y) : 0; + int sw = (src_rect != nullptr) ? static_cast(src_rect->w) : surface_data_->width; + int sh = (src_rect != nullptr) ? static_cast(src_rect->h) : surface_data_->height; + + sw = std::min(sw, surface_data_->width - SX); + sh = std::min(sh, surface_data_->height - SY); + + const Uint8* src = surface_data_->data.get(); + for (int iy = 0; iy < sh; ++iy) { + for (int ix = 0; ix < sw; ++ix) { + const int SRC_X = (flip == SDL_FLIP_HORIZONTAL) ? (SX + sw - 1 - ix) : (SX + ix); + const int SRC_Y = (flip == SDL_FLIP_VERTICAL) ? (SY + sh - 1 - iy) : (SY + iy); + const int DST_X = x + ix; + const int DST_Y = y + iy; + if (DST_X < 0 || DST_X >= DST_W || DST_Y < 0 || DST_Y >= DST_H) { continue; } + + const Uint8 COLOR = src[SRC_X + (SRC_Y * surface_data_->width)]; + if (COLOR == transparent_color_) { continue; } + dst[DST_X + (DST_Y * DST_W)] = palette_[sub_palette_[COLOR]]; + } + } +} diff --git a/source/core/rendering/surface.hpp b/source/core/rendering/surface.hpp new file mode 100644 index 0000000..246d937 --- /dev/null +++ b/source/core/rendering/surface.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +// Cada Surface manté la seua propia paleta (256 colors ARGB). El destí del +// render es l'ARGB framebuffer del joc, mantingut per Screen, així que +// l'arquitectura és: sprites paletats → imatge final ARGB (>256 colors). +using Palette = std::array; +using SubPalette = std::array; + +auto loadPaletteFromGif(const std::string& file_path) -> Palette; + +struct SurfaceData { + std::shared_ptr data; + int width{0}; + int height{0}; + + SurfaceData() = default; + SurfaceData(int w, int h) + : data(std::shared_ptr(new Uint8[static_cast(w) * static_cast(h)](), std::default_delete())), + width(w), + height(h) {} + SurfaceData(int w, int h, std::shared_ptr pixels) + : data(std::move(pixels)), + width(w), + height(h) {} + + SurfaceData(SurfaceData&&) noexcept = default; + auto operator=(SurfaceData&&) noexcept -> SurfaceData& = default; + SurfaceData(const SurfaceData&) = delete; + auto operator=(const SurfaceData&) -> SurfaceData& = delete; +}; + +class Surface { + public: + // Crea una surface buida (paleta a zero) amb les dimensions donades. + Surface(int w, int h); + // Carrega pixels + paleta des d'un fitxer GIF. + explicit Surface(const std::string& file_path); + ~Surface() = default; + + static auto loadSurface(const std::string& file_path) -> SurfaceData; + + void loadPalette(const std::string& file_path); + void loadPalette(const Palette& palette); + + // Vobla la regió indicada al framebuffer ARGB del joc usant la paleta propia. + void render(int x, int y, const SDL_FRect* src_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE) const; + + void clear(Uint8 color); + void putPixel(int x, int y, Uint8 color); + [[nodiscard]] auto getPixel(int x, int y) const -> Uint8; + + [[nodiscard]] auto getSurfaceData() const -> std::shared_ptr { return surface_data_; } + [[nodiscard]] auto getWidth() const -> int { return surface_data_->width; } + [[nodiscard]] auto getHeight() const -> int { return surface_data_->height; } + + [[nodiscard]] auto getTransparentColor() const -> Uint8 { return transparent_color_; } + void setTransparentColor(Uint8 color = 255) { transparent_color_ = color; } + + void setPalette(const Palette& palette) { palette_ = palette; } + [[nodiscard]] auto getPaletteColor(Uint8 index) const -> Uint32 { return palette_[index]; } + + static void initializeSubPalette(SubPalette& palette) { std::iota(palette.begin(), palette.end(), 0); } + + private: + std::shared_ptr surface_data_; + Palette palette_{}; + SubPalette sub_palette_{}; + Uint8 transparent_color_{255}; +}; diff --git a/source/core/resources/resource_cache.cpp b/source/core/resources/resource_cache.cpp new file mode 100644 index 0000000..4ba7def --- /dev/null +++ b/source/core/resources/resource_cache.cpp @@ -0,0 +1,49 @@ +#include "core/resources/resource_cache.hpp" + +#include +#include +#include + +#include "core/resources/resource_helper.hpp" +#include "utils/utils.hpp" + +namespace Resource { + + auto Cache::get() -> Cache& { + static Cache instance_; + return instance_; + } + + void Cache::loadSurface(const std::string& name, const std::string& file_path) { + auto surface = std::make_shared(file_path); + surfaces_.emplace_back(SurfaceResource{.name = name, .surface = std::move(surface)}); + } + + void Cache::loadAnimation(const std::string& name, const std::string& file_path) { + auto bytes = Helper::loadFile(file_path); + if (bytes.empty()) { + throw std::runtime_error("Cache: cannot load animation " + file_path); + } + animations_.emplace_back(AnimationResource{.name = name, .yaml_data = std::move(bytes)}); + printWithDots("Animation : ", getFileName(file_path), "[ LOADED ]"); + } + + auto Cache::getSurface(const std::string& name) -> std::shared_ptr { + auto it = std::ranges::find_if(surfaces_, [&name](const auto& s) { return s.name == name; }); + if (it == surfaces_.end()) { + std::cerr << "Cache: surface not found: " << name << '\n'; + throw std::runtime_error("Surface not found: " + name); + } + return it->surface; + } + + auto Cache::getAnimationData(const std::string& name) -> const AnimationResource& { + auto it = std::ranges::find_if(animations_, [&name](const auto& a) { return a.name == name; }); + if (it == animations_.end()) { + std::cerr << "Cache: animation not found: " << name << '\n'; + throw std::runtime_error("Animation not found: " + name); + } + return *it; + } + +} // namespace Resource diff --git a/source/core/resources/resource_cache.hpp b/source/core/resources/resource_cache.hpp new file mode 100644 index 0000000..135fc30 --- /dev/null +++ b/source/core/resources/resource_cache.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +#include "core/resources/resource_types.hpp" + +namespace Resource { + + // Cache mínim per a la fase 2: només surfaces i animacions, càrrega + // explícita per nom + path. Per ara la cerca és lineal sobre vector. + // Meyers singleton: la destrucció estàtica només toca smart pointers, no + // depén d'SDL ni de Screen, així que és segura sense ordenació explícita. + class Cache { + public: + static auto get() -> Cache&; + + Cache(const Cache&) = delete; + Cache(Cache&&) = delete; + auto operator=(const Cache&) -> Cache& = delete; + auto operator=(Cache&&) -> Cache& = delete; + + // Carrega una surface des de fitxer i la registra amb el nom donat. + void loadSurface(const std::string& name, const std::string& file_path); + // Llig el YAML d'animació en cru i el registra amb el nom donat. + void loadAnimation(const std::string& name, const std::string& file_path); + + auto getSurface(const std::string& name) -> std::shared_ptr; + auto getAnimationData(const std::string& name) -> const AnimationResource&; + + private: + Cache() = default; + ~Cache() = default; + + std::vector surfaces_; + std::vector animations_; + }; + +} // namespace Resource diff --git a/source/core/resources/resource_helper.cpp b/source/core/resources/resource_helper.cpp new file mode 100644 index 0000000..9e7f6a5 --- /dev/null +++ b/source/core/resources/resource_helper.cpp @@ -0,0 +1,24 @@ +#include "core/resources/resource_helper.hpp" + +#include +#include + +namespace Resource::Helper { + + auto loadFile(const std::string& filepath) -> std::vector { + std::ifstream file(filepath, std::ios::binary | std::ios::ate); + if (!file) { + std::cerr << "Resource::Helper: cannot open " << filepath << '\n'; + return {}; + } + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + std::vector data(static_cast(size)); + if (!file.read(reinterpret_cast(data.data()), size)) { + std::cerr << "Resource::Helper: failed to read " << filepath << '\n'; + return {}; + } + return data; + } + +} // namespace Resource::Helper diff --git a/source/core/resources/resource_helper.hpp b/source/core/resources/resource_helper.hpp new file mode 100644 index 0000000..6a3bf69 --- /dev/null +++ b/source/core/resources/resource_helper.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include +#include +#include + +namespace Resource::Helper { + + // Carga un fitxer del filesystem (a aquesta fase encara no hi ha pack). + auto loadFile(const std::string& filepath) -> std::vector; + +} // namespace Resource::Helper diff --git a/source/core/resources/resource_types.hpp b/source/core/resources/resource_types.hpp new file mode 100644 index 0000000..79d19f0 --- /dev/null +++ b/source/core/resources/resource_types.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include +#include + +#include "core/rendering/surface.hpp" + +struct SurfaceResource { + std::string name; + std::shared_ptr surface; +}; + +struct AnimationResource { + std::string name; + std::vector yaml_data; +}; diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index a40daac..6cdd72a 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -4,29 +4,41 @@ #include #include +#include #include #include #include "core/rendering/screen.hpp" +#include "core/resources/resource_cache.hpp" #include "core/scene/scene.hpp" #include "core/system/event_buffer.hpp" -#include "game/scenes/black_scene.hpp" +#include "game/scenes/game_scene.hpp" Director::Director() { if (!SDL_Init(0)) { std::fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError()); } - Screen::init(); + // Registrem SDL_Quit ABANS de tocar cap singleton: així el handler atexit + // s'executa DESPRÉS dels destructors estàtics de Screen i Cache (atexit + // registrat abans de la construcció d'un objecte estàtic corre després + // del seu destructor). Sense açò, Screen::~Screen cridaria SDL_Destroy* + // amb SDL ja apagat → caos de destrucció clàssic. + std::atexit(+[] { SDL_Quit(); }); - current_scene_ = std::make_unique(); + // Forcem la construcció dels singletons en l'ordre desitjat: primer + // Screen (necessari per a renderitzar), després Cache. La destrucció + // estàtica anirà LIFO: Cache primer, Screen últim. + (void)Screen::get(); + (void)Resource::Cache::get(); + + current_scene_ = std::make_unique(); last_ticks_ms_ = SDL_GetTicks(); } Director::~Director() { current_scene_.reset(); - Screen::destroy(); - SDL_Quit(); + // SDL_Quit corre via atexit; els singletons via destrucció estàtica. } void Director::processEvents() { diff --git a/source/external/fkyaml_node.hpp b/source/external/fkyaml_node.hpp new file mode 100644 index 0000000..05e7957 --- /dev/null +++ b/source/external/fkyaml_node.hpp @@ -0,0 +1,14726 @@ +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#ifndef FK_YAML_NODE_HPP +#define FK_YAML_NODE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#ifndef FK_YAML_DETAIL_MACROS_DEFINE_MACROS_HPP +#define FK_YAML_DETAIL_MACROS_DEFINE_MACROS_HPP + +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +// Check version definitions if already defined. +#if defined(FK_YAML_MAJOR_VERSION) && defined(FK_YAML_MINOR_VERSION) && defined(FK_YAML_PATCH_VERSION) +#if FK_YAML_MAJOR_VERSION != 0 || FK_YAML_MINOR_VERSION != 4 || FK_YAML_PATCH_VERSION != 2 +#warning Already included a different version of the fkYAML library! +#else +// define macros to skip defining macros down below. +#define FK_YAML_VERCHECK_SUCCEEDED +#endif +#endif + +#ifndef FK_YAML_VERCHECK_SUCCEEDED + +#define FK_YAML_MAJOR_VERSION 0 +#define FK_YAML_MINOR_VERSION 4 +#define FK_YAML_PATCH_VERSION 2 + +#define FK_YAML_NAMESPACE_VERSION_CONCAT_IMPL(major, minor, patch) v##major##_##minor##_##patch + +#define FK_YAML_NAMESPACE_VERSION_CONCAT(major, minor, patch) FK_YAML_NAMESPACE_VERSION_CONCAT_IMPL(major, minor, patch) + +#define FK_YAML_NAMESPACE_VERSION \ + FK_YAML_NAMESPACE_VERSION_CONCAT(FK_YAML_MAJOR_VERSION, FK_YAML_MINOR_VERSION, FK_YAML_PATCH_VERSION) + +#define FK_YAML_NAMESPACE_BEGIN \ + namespace fkyaml { \ + inline namespace FK_YAML_NAMESPACE_VERSION { + +#define FK_YAML_NAMESPACE_END \ + } /* inline namespace FK_YAML_NAMESPACE_VERSION */ \ + } // namespace fkyaml + +#define FK_YAML_DETAIL_NAMESPACE_BEGIN \ + FK_YAML_NAMESPACE_BEGIN \ + namespace detail { + +#define FK_YAML_DETAIL_NAMESPACE_END \ + } /* namespace detail */ \ + FK_YAML_NAMESPACE_END + +#endif // !defined(FK_YAML_VERCHECK_SUCCEEDED) + +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#ifndef FK_YAML_DETAIL_MACROS_CPP_CONFIG_MACROS_HPP +#define FK_YAML_DETAIL_MACROS_CPP_CONFIG_MACROS_HPP + +// This file is assumed to be included only by version_macros.hpp file. +// To avoid redundant inclusion, do not include version_macros.hpp file as the other files do. + +// With the MSVC compilers, the value of __cplusplus is by default always "199611L"(C++98). +// To avoid that, the library instead references _MSVC_LANG which is always set a correct value. +// See https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ for more details. +#if defined(_MSVC_LANG) && !defined(__clang__) +#define FK_YAML_CPLUSPLUS _MSVC_LANG +#else +#define FK_YAML_CPLUSPLUS __cplusplus +#endif + +// C++ language standard detection +// Skip detection if the definitions listed below already exist. +#if !defined(FK_YAML_HAS_CXX_23) && !defined(FK_YAML_HAS_CXX_20) && !defined(FK_YAML_HAS_CXX_17) && \ + !defined(FK_YAML_HAS_CXX_14) && !defined(FK_YAML_CXX_11) +#if FK_YAML_CPLUSPLUS >= 202302L +#define FK_YAML_HAS_CXX_23 +#define FK_YAML_HAS_CXX_20 +#define FK_YAML_HAS_CXX_17 +#define FK_YAML_HAS_CXX_14 +#elif FK_YAML_CPLUSPLUS >= 202002L +#define FK_YAML_HAS_CXX_20 +#define FK_YAML_HAS_CXX_17 +#define FK_YAML_HAS_CXX_14 +#elif FK_YAML_CPLUSPLUS >= 201703L +#define FK_YAML_HAS_CXX_17 +#define FK_YAML_HAS_CXX_14 +#elif FK_YAML_CPLUSPLUS >= 201402L +#define FK_YAML_HAS_CXX_14 +#endif + +// C++11 is the minimum required version of the fkYAML library. +#define FK_YAML_HAS_CXX_11 +#endif + +// switch usage of the deprecated attribute. [[deprecated]] is available since C++14. +#if defined(FK_YAML_HAS_CXX_14) +#define FK_YAML_DEPRECATED(msg) [[deprecated(msg)]] +#else +#if defined(_MSC_VER) +#define FK_YAML_DEPRECATED(msg) __declspec(deprecated(msg)) +#elif defined(__GNUC__) || defined(__clang__) +#define FK_YAML_DEPRECATED(msg) __attribute__((deprecated(msg))) +#else +#define FK_YAML_DEPRECATED(msg) +#endif +#endif + +// switch usage of inline variables which have been available since C++17. +#if defined(FK_YAML_HAS_CXX_17) +#define FK_YAML_INLINE_VAR inline +#else +#define FK_YAML_INLINE_VAR +#endif + +// switch usage of constexpr keyword depending on active C++ standard. +#if defined(FK_YAML_HAS_CXX_17) +#define FK_YAML_CXX17_CONSTEXPR constexpr +#else +#define FK_YAML_CXX17_CONSTEXPR +#endif + +// Detect __has_* macros. +// The following macros replace redundant `defined(__has_*) && __has_*(...)`. + +#ifdef __has_include +#define FK_YAML_HAS_INCLUDE(header) __has_include(header) +#else +#define FK_YAML_HAS_INCLUDE(header) (0) +#endif + +#ifdef __has_builtin +#define FK_YAML_HAS_BUILTIN(builtin) __has_builtin(builtin) +#else +#define FK_YAML_HAS_BUILTIN(builtin) (0) +#endif + +#ifdef __has_cpp_attribute +#define FK_YAML_HAS_CPP_ATTRIBUTE(attr) __has_cpp_attribute(attr) +#else +#define FK_YAML_HAS_CPP_ATTRIBUTE(attr) (0) +#endif + +#ifdef __has_feature +#define FK_YAML_HAS_FEATURE(feat) __has_feature(feat) +#else +#define FK_YAML_HAS_FEATURE(feat) (0) +#endif + +// switch usage of the no_sanitize attribute only when Clang sanitizer is active. +#if defined(__clang__) && FK_YAML_HAS_FEATURE(address_sanitizer) +#define FK_YAML_NO_SANITIZE(...) __attribute__((no_sanitize(__VA_ARGS__))) +#else +#define FK_YAML_NO_SANITIZE(...) +#endif + +#if FK_YAML_HAS_INCLUDE() +// is available since C++20 +#include +#endif + +// +// C++ feature detections +// + +// switch usages of the std::to_chars()/std::from_chars() functions which have been available since C++17. +#if defined(FK_YAML_HAS_CXX_17) && defined(__cpp_lib_to_chars) && __cpp_lib_to_chars >= 201611L +#define FK_YAML_HAS_TO_CHARS (1) +#else +#define FK_YAML_HAS_TO_CHARS (0) +#endif + +// switch usage of char8_t which has been available since C++20. +#if defined(FK_YAML_HAS_CXX_20) && defined(__cpp_char8_t) && __cpp_char8_t >= 201811L +#define FK_YAML_HAS_CHAR8_T (1) +#else +#define FK_YAML_HAS_CHAR8_T (0) +#endif + +// +// utility macros +// + +// switch usage of [[likely]] C++ attribute which has been available since C++20. +#if defined(FK_YAML_HAS_CXX_20) && FK_YAML_HAS_CPP_ATTRIBUTE(likely) >= 201803L +#define FK_YAML_LIKELY(expr) (!!(expr)) [[likely]] +#elif FK_YAML_HAS_BUILTIN(__builtin_expect) +#define FK_YAML_LIKELY(expr) (__builtin_expect(!!(expr), 1)) +#else +#define FK_YAML_LIKELY(expr) (!!(expr)) +#endif + +// switch usage of [[unlikely]] C++ attribute which has been available since C++20. +#if defined(FK_YAML_HAS_CXX_20) && FK_YAML_HAS_CPP_ATTRIBUTE(unlikely) >= 201803L +#define FK_YAML_UNLIKELY(expr) (!!(expr)) [[unlikely]] +#elif FK_YAML_HAS_BUILTIN(__builtin_expect) +#define FK_YAML_UNLIKELY(expr) (__builtin_expect(!!(expr), 0)) +#else +#define FK_YAML_UNLIKELY(expr) (!!(expr)) +#endif + +#endif /* FK_YAML_DETAIL_MACROS_CPP_CONFIG_MACROS_HPP */ + + +#endif /* FK_YAML_DETAIL_MACROS_DEFINE_MACROS_HPP */ + +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#ifndef FK_YAML_DETAIL_ASSERT_HPP +#define FK_YAML_DETAIL_ASSERT_HPP + +// if FK_YAML_ASSERT is not user-defined. apply the default assert impl. +#ifndef FK_YAML_ASSERT +#ifndef NDEBUG +#include +#define FK_YAML_ASSERT(x) assert(x) +#else +#define FK_YAML_ASSERT(x) +#endif +#endif + +#endif /* FK_YAML_DETAIL_ASSERT_HPP */ + +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#ifndef FK_YAML_DETAIL_DOCUMENT_METAINFO_HPP +#define FK_YAML_DETAIL_DOCUMENT_METAINFO_HPP + +#include +#include + +// #include + +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#ifndef FK_YAML_DETAIL_META_NODE_TRAITS_HPP +#define FK_YAML_DETAIL_META_NODE_TRAITS_HPP + +// #include + +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#ifndef FK_YAML_DETAIL_META_DETECT_HPP +#define FK_YAML_DETAIL_META_DETECT_HPP + +#include +#include + +// #include + +// #include +// _______ __ __ __ _____ __ __ __ +// | __| |_/ | \_/ |/ _ \ / \/ \| | fkYAML: A C++ header-only YAML library +// | __| _ < \_ _/| ___ | _ | |___ version 0.4.2 +// |__| |_| \__| |_| |_| |_|___||___|______| https://github.com/fktn-k/fkYAML +// +// SPDX-FileCopyrightText: 2023-2025 Kensuke Fukutani +// SPDX-License-Identifier: MIT + +#ifndef FK_YAML_DETAIL_META_STL_SUPPLEMENT_HPP +#define FK_YAML_DETAIL_META_STL_SUPPLEMENT_HPP + +#include +#include + +// #include + + +#ifdef FK_YAML_HAS_CXX_14 +#include +#endif + +FK_YAML_DETAIL_NAMESPACE_BEGIN + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// For contributors: +// This file is for supplementing future C++ STL implementations to utilize some useful features +// implemented in C++14 or better. +// This file is needed to keep the fkYAML library requirement to C++11. +// **DO NOT** implement features which are not included any version of STL in this file. +// Such implementations must be in the type_traits.hpp file. +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifndef FK_YAML_HAS_CXX_14 + +/// @brief An alias template for std::add_pointer::type with C++11. +/// @note std::add_pointer_t is available since C++14. +/// @sa https://en.cppreference.com/w/cpp/types/add_pointer +/// @tparam T A type to be added a pointer. +template +using add_pointer_t = typename std::add_pointer::type; + +/// @brief An alias template for std::enable_if::type with C++11. +/// @note std::enable_if_t is available since C++14. +/// @sa https://en.cppreference.com/w/cpp/types/enable_if +/// @tparam Condition A condition tested at compile time. +/// @tparam T The type defined only if Condition is true. +template +using enable_if_t = typename std::enable_if::type; + +/// @brief A simple implementation to use std::is_null_pointer with C++11. +/// @note std::is_null_pointer is available since C++14. +/// @sa https://en.cppreference.com/w/cpp/types/is_null_pointer +/// @tparam T The type to be checked if it's equal to std::nullptr_t. +template +struct is_null_pointer : std::is_same::type> {}; + +/// @brief An alias template for std::remove_cv::type with C++11. +/// @note std::remove_cv_t is available since C++14. +/// @sa https://en.cppreference.com/w/cpp/types/remove_cv +/// @tparam T A type from which const-volatile qualifiers are removed. +template +using remove_cv_t = typename std::remove_cv::type; + +/// @brief An alias template for std::remove_pointer::type with C++11. +/// @note std::remove_pointer_t is available since C++14. +/// @sa https://en.cppreference.com/w/cpp/types/remove_pointer +/// @tparam T A type from which a pointer is removed. +template +using remove_pointer_t = typename std::remove_pointer::type; + +/// @brief An alias template for std::remove_reference::type with C++11. +/// @note std::remove_reference_t is available since C++14. +/// @sa https://en.cppreference.com/w/cpp/types/remove_reference +/// @tparam T A type from which a reference is removed. +template +using remove_reference_t = typename std::remove_reference::type; + +template +struct integer_sequence { + using value_type = T; + static constexpr std::size_t size() noexcept { + return sizeof...(I); + } +}; + +#if !FK_YAML_HAS_BUILTIN(__make_integer_seq) && !FK_YAML_HAS_BUILTIN(__integer_pack) + +namespace make_int_seq_impl { + +template +struct merger; + +template +struct merger, integer_sequence> { + using type = integer_sequence; +}; + +template +struct generator { + using type = + typename merger::type, typename generator::type>::type; +}; + +template +struct generator { + using type = integer_sequence; +}; + +template +struct generator { + using type = integer_sequence; +}; + +} // namespace make_int_seq_impl + +#endif + +template +using make_integer_sequence +#if FK_YAML_HAS_BUILTIN(__make_integer_seq) + // clang defines built-in __make_integer_seq to generate an integer sequence. + = __make_integer_seq; +#elif FK_YAML_HAS_BUILTIN(__integer_pack) + // GCC or other compilers may implement built-in __integer_pack to generate an + // integer sequence. + = integer_sequence; +#else + // fallback to the library implementation of make_integer_sequence. + = typename make_int_seq_impl::generator::type; +#endif + +template +using index_sequence = integer_sequence; + +template +using make_index_sequence = make_integer_sequence; + +template +using index_sequence_for = make_index_sequence; + +#else // !defined(FK_YAML_HAS_CXX_14) + +using std::add_pointer_t; +using std::enable_if_t; +using std::index_sequence; +using std::index_sequence_for; +using std::integer_sequence; +using std::is_null_pointer; +using std::make_index_sequence; +using std::make_integer_sequence; +using std::remove_cv_t; +using std::remove_pointer_t; +using std::remove_reference_t; + +#endif // !defined(FK_YAML_HAS_CXX_14) + +#ifndef FK_YAML_HAS_CXX_17 + +/// @brief A simple implementation to use std::bool_constant with C++11/C++14. +/// @tparam Val +template +using bool_constant = std::integral_constant; + +/// @brief A simple implementation to use std::void_t with C++11/C++14. +/// @note +/// std::conjunction is available since C++17. +/// This is applied when no traits are specified as inputs. +/// @sa https://en.cppreference.com/w/cpp/types/conjunction +/// @tparam Traits Type traits to be checked if their ::value are all true. +template +struct conjunction : std::true_type {}; + +/// @brief A partial specialization of conjunction if only one Trait is given. +/// @tparam Trait Type trait to be checked if its ::value is true. +template +struct conjunction : Trait {}; + +/// @brief A partial specialization of conjunction if more than one traits are given. +/// @tparam First The first type trait to be checked if its ::value is true. +/// @tparam Rest The rest of traits passed as another conjunction template arguments if First::value is true. +template +struct conjunction : std::conditional, First>::type {}; + +/// @brief A simple implementation to use std::disjunction with C++11/C++14. +/// @note +/// std::disjunction is available since C++17. +/// This is applied when no traits are specified as inputs. +/// @sa https://en.cppreference.com/w/cpp/types/disjunction +/// @tparam Traits Type traits to be checked if at least one of their ::value is true. +template +struct disjunction : std::false_type {}; + +/// @brief A partial specialization of disjunction if only one Trait is given. +/// @tparam Trait Type trait to be checked if its ::value is true. +template +struct disjunction : Trait {}; + +/// @brief A partial specialization of disjunction if more than one traits are given. +/// @tparam First The first type trait to be checked if its ::value is true. +/// @tparam Rest The rest of traits passed as another conjunction template arguments if First::value is false. +template +struct disjunction : std::conditional>::type {}; + +/// @brief A simple implementation to use std::negation with C++11/C++14. +/// @note std::negation is available since C++17. +/// @sa https://en.cppreference.com/w/cpp/types/negation +/// @tparam Trait Type trait whose ::value is negated. +template +struct negation : std::integral_constant {}; + +/// @brief A helper for void_t. +/// @tparam Types Any types to be transformed to void type. +template +struct make_void { + using type = void; +}; + +/// @brief A simple implementation to use std::void_t with C++11/C++14. +/// @note std::void_t is available since C++17. +/// @sa https://en.cppreference.com/w/cpp/types/void_t +/// @tparam Types Any types to be transformed to void type. +template +using void_t = typename make_void::type; + +#else // !defined(FK_YAML_HAS_CXX_17) + +using std::bool_constant; +using std::conjunction; +using std::disjunction; +using std::negation; +using std::void_t; + +#endif // !defined(FK_YAML_HAS_CXX_17) + +#ifndef FK_YAML_HAS_CXX_20 + +/// @brief A simple implementation to use std::remove_cvref_t with C++11/C++14/C++17. +/// @note std::remove_cvref & std::remove_cvref_t are available since C++20. +/// @sa https://en.cppreference.com/w/cpp/types/remove_cvref +/// @tparam T A type from which cv-qualifiers and reference are removed. +template +using remove_cvref_t = typename std::remove_cv::type>::type; + +#else + +using std::remove_cvref_t; + +#endif + +/// @brief A wrapper function to call std::unreachable() (since C++23) or similar compiler specific extensions. +/// @note This function is implemented only for better code optimization against dead code and thus excluded from +/// coverage report. +// LCOV_EXCL_START +[[noreturn]] inline void unreachable() { + // use compiler specific extensions if possible. + // undefined behavior should be raised by an empty function with noreturn attribute. + +#if defined(FK_YAML_HAS_CXX_23) || (defined(__cpp_lib_unreachable) && __cpp_lib_unreachable >= 202202L) + std::unreachable(); +#elif defined(_MSC_VER) && !defined(__clang__) // MSVC + __assume(false); +#else + __builtin_unreachable(); +#endif +} +// LCOV_EXCL_STOP + +FK_YAML_DETAIL_NAMESPACE_END + +#endif /* FK_YAML_DETAIL_META_STL_SUPPLEMENT_HPP */ + + +FK_YAML_DETAIL_NAMESPACE_BEGIN + +/// @brief A dummy struct to represent detection failure. +struct nonesuch { + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(const nonesuch&) = delete; + nonesuch(nonesuch&&) = delete; + nonesuch& operator=(const nonesuch&) = delete; + nonesuch& operator=(nonesuch&&) = delete; +}; + +/// @brief namespace to implement detector type traits +namespace detector_impl { + +/// @brief A helper for general type detection. +/// @tparam Default A type to represent detection failure. +/// @tparam AlwaysVoid This must be void type. +/// @tparam Op A type for desired operation type. +/// @tparam Args Argument types passed to desired operation. +template class Op, typename... Args> +struct detector : std::false_type { + /// @brief A type which represents detection failure. + using type = Default; +}; + +/// @brief A partial specialization of detector if desired operation type is found. +/// @tparam Default A type to represent detection failure. +/// @tparam Op A type for desired operation type. +/// @tparam Args Argument types passed to desired operation. +template class Op, typename... Args> +struct detector>, Op, Args...> : std::true_type { + /// @brief A detected type. + using type = Op; +}; + +} // namespace detector_impl + +/// @brief Type traits to detect Op operation with Args argument types +/// @tparam Op A desired operation type. +/// @tparam Args Argument types passed to desired operation. +template