From 3addd01f54a98698d1c7d14ac8bd6f6c25b28d71 Mon Sep 17 00:00:00 2001 From: binaryDiv Date: Sun, 24 May 2026 22:54:27 +0200 Subject: [PATCH 1/3] Add nlohmann::json library --- shuriken.yaml | 1 + src/vendor/nlohmann/json.cppm | 51 +++++++++++++++++++++++++++++++++++ vendor/README.md | 13 +++++++++ 3 files changed, 65 insertions(+) create mode 100644 src/vendor/nlohmann/json.cppm diff --git a/shuriken.yaml b/shuriken.yaml index 0a07b3f..e231758 100644 --- a/shuriken.yaml +++ b/shuriken.yaml @@ -4,6 +4,7 @@ defaults: -Wall -Wextra -pedantic + -Ivendor/nlohmann_json/include -Ivendor/RmlUi/Include linker_args: >- -lSDL3 diff --git a/src/vendor/nlohmann/json.cppm b/src/vendor/nlohmann/json.cppm new file mode 100644 index 0000000..1941edc --- /dev/null +++ b/src/vendor/nlohmann/json.cppm @@ -0,0 +1,51 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 (develop branch) +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +module; + +// GCC workaround for C++ modules support. +// When using C++20 modules, some compilers (particularly GCC) may have issues +// with template instantiations in the module preamble. If you encounter +// "redefinition" errors when including nlohmann/json.hpp, try one of: +// 1. Include nlohmann/json.hpp in your module preamble BEFORE other #includes +// 2. Or use: import nlohmann.json; instead of #include +// 3. Or upgrade to a newer GCC version with better modules support. +// See: https://github.com/nlohmann/json/issues/5103 + +#include + +// NOTE: Module renamed from nlohmann.json to match directory structure +export module vendor.nlohmann.json; + +export NLOHMANN_JSON_NAMESPACE_BEGIN + using NLOHMANN_JSON_NAMESPACE::adl_serializer; + using NLOHMANN_JSON_NAMESPACE::basic_json; + using NLOHMANN_JSON_NAMESPACE::json; + using NLOHMANN_JSON_NAMESPACE::json_pointer; + using NLOHMANN_JSON_NAMESPACE::ordered_json; + using NLOHMANN_JSON_NAMESPACE::ordered_map; + using NLOHMANN_JSON_NAMESPACE::to_string; + + inline namespace literals + { + inline namespace json_literals + { + using NLOHMANN_JSON_NAMESPACE::literals::json_literals::operator""_json; + using NLOHMANN_JSON_NAMESPACE::literals::json_literals::operator""_json_pointer; + } // namespace json_literals + } // namespace literals + + // Note: the following nlohmann::detail symbols must be exported due to + // an MSVC bug failing to compile without these symbols visible (ticket #3970) + namespace detail + { + using NLOHMANN_JSON_NAMESPACE::detail::json_sax_dom_callback_parser; + using NLOHMANN_JSON_NAMESPACE::detail::unknown_size; + } // namespace detail + +NLOHMANN_JSON_NAMESPACE_END diff --git a/vendor/README.md b/vendor/README.md index c729343..9a43f1d 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -10,6 +10,19 @@ For now, SDL3 and SDL3_image need to be installed on the system. They should be Same for freetype, which is required by RmlUi. +## nlohmann::json + +[JSON for Modern C++](https://github.com/nlohmann/json). + +Within the `vendor` directory, run: + +```shell +# We're using the default branch (develop) here because the latest release does not support C++20 modules yet. +# (Last commit known working: 484483aca) +git clone git@github.com:nlohmann/json.git nlohmann_json +``` + + ## RmlUi [RmlUi](https://github.com/mikke89/RmlUi) is a UI library. From 718679c13b39638c751ebd24b3dcef44326b9ab1 Mon Sep 17 00:00:00 2001 From: binaryDiv Date: Tue, 16 Jun 2026 21:53:19 +0200 Subject: [PATCH 2/3] Implement resource server --- src/rutile/core/common.cppm | 12 ++ src/rutile/core/drawing/sprite.cppm | 22 ++- src/rutile/core/drawing/tile_map.cppm | 27 ++-- src/rutile/core/engine.cppm | 8 ++ src/rutile/core/render_server.cppm | 40 +----- src/rutile/core/resource_manager.cppm | 120 ---------------- src/rutile/core/resource_server.cppm | 61 +++++++++ .../loaders}/texture_loader.cppm | 6 +- .../resources/loaders/tile_set_loader.cppm | 57 ++++++++ .../core/resources/resource_registry.cppm | 128 ++++++++++++++++++ src/rutile/core/resources/texture.cppm | 12 ++ .../core/{drawing => resources}/tile_set.cppm | 33 ++--- src/rutile/game/game.cppm | 35 ++--- src/wrappers/sdl/video.cppm | 3 +- 14 files changed, 332 insertions(+), 232 deletions(-) create mode 100644 src/rutile/core/common.cppm delete mode 100644 src/rutile/core/resource_manager.cppm create mode 100644 src/rutile/core/resource_server.cppm rename src/rutile/core/{ => resources/loaders}/texture_loader.cppm (81%) create mode 100644 src/rutile/core/resources/loaders/tile_set_loader.cppm create mode 100644 src/rutile/core/resources/resource_registry.cppm create mode 100644 src/rutile/core/resources/texture.cppm rename src/rutile/core/{drawing => resources}/tile_set.cppm (71%) diff --git a/src/rutile/core/common.cppm b/src/rutile/core/common.cppm new file mode 100644 index 0000000..78103c1 --- /dev/null +++ b/src/rutile/core/common.cppm @@ -0,0 +1,12 @@ +module; + +export module rutile.core.common; + +import std; + +export namespace rutile +{ + // Alias for shared_ptr to be used for shared resource pointers (see ResourceServer) + template + using ResourcePtr = std::shared_ptr; +} diff --git a/src/rutile/core/drawing/sprite.cppm b/src/rutile/core/drawing/sprite.cppm index 1158bde..d6862e7 100644 --- a/src/rutile/core/drawing/sprite.cppm +++ b/src/rutile/core/drawing/sprite.cppm @@ -5,15 +5,17 @@ module; export module rutile.core.drawing.sprite; import std; +import rutile.core.common; import rutile.core.render_server; +import rutile.core.resources.texture; import wrappers.sdl; export namespace rutile { class Sprite { - // Texture ID (managed by TextureManager in RenderServer) - TextureID texture_id_; + // Shared pointer to a texture (managed by ResourceServer) + ResourcePtr texture_ptr_; // Texture source boundaries: which part of the texture to render sdl::FRect texture_boundaries_; @@ -30,13 +32,13 @@ export namespace rutile public: explicit Sprite( - const TextureID texture_id, + const ResourcePtr& texture_ptr, const sdl::FRect texture_boundaries, const sdl::FPoint position, const sdl::FPoint texture_offset = {0, 0}, const sdl::FPoint texture_scale = {1, 1} ) - : texture_id_{texture_id}, + : texture_ptr_{texture_ptr}, texture_boundaries_{texture_boundaries}, texture_offset_{texture_offset}, texture_scale_{texture_scale}, @@ -46,20 +48,16 @@ export namespace rutile } static Sprite create_from_texture( - RenderServer& render_server, - const std::string& filename, + const ResourcePtr& texture_ptr, std::optional texture_boundaries, auto... args ) { - const TextureID texture_id = render_server.load_texture(filename); - if (!texture_boundaries.has_value()) { - const sdl::Texture& texture = render_server.get_texture(texture_id); - texture_boundaries = texture.get_boundaries(); + texture_boundaries = texture_ptr->get_boundaries(); } - return Sprite(texture_id, texture_boundaries.value(), args...); + return Sprite(texture_ptr, texture_boundaries.value(), args...); } void set_position(const sdl::FPoint new_position) @@ -76,7 +74,7 @@ export namespace rutile texture_scale_.y * texture_boundaries_.h, }; - render_server.render_texture(texture_id_, &texture_boundaries_, &dest_rect); + render_server.render_texture(*texture_ptr_, &texture_boundaries_, &dest_rect); } }; } diff --git a/src/rutile/core/drawing/tile_map.cppm b/src/rutile/core/drawing/tile_map.cppm index de66b2c..fb319ab 100644 --- a/src/rutile/core/drawing/tile_map.cppm +++ b/src/rutile/core/drawing/tile_map.cppm @@ -5,8 +5,9 @@ module; export module rutile.core.drawing.tile_map; import std; -import rutile.core.drawing.tile_set; +import rutile.core.common; import rutile.core.render_server; +import rutile.core.resources.tile_set; import wrappers.sdl; export namespace rutile @@ -16,10 +17,8 @@ export namespace rutile class TileMap { - // Tile set ID - // TODO: Needed later when we centralize tile set management using a ResourceManager - [[maybe_unused]] - TileSetID tile_set_id_; + // Shared pointer to a tile set (managed by ResourceServer) + ResourcePtr tile_set_ptr_; // Tile size int tile_size_; @@ -36,16 +35,16 @@ export namespace rutile public: explicit TileMap( - const TileSetID tile_set_id, + const ResourcePtr& tile_set_ptr, const int tile_size, const MapCoord width, const MapCoord height, const sdl::FPoint position = {0, 0} ) - : tile_set_id_(tile_set_id), - tile_size_(tile_size), - width_(width), - height_(height), + : tile_set_ptr_{tile_set_ptr}, + tile_size_{tile_size}, + width_{width}, + height_{height}, position_{position} { // TODO: This maybe shouldn't be an assert... @@ -72,11 +71,9 @@ export namespace rutile tiles_[map_coords_to_index(x, y)] = tile_id; } - // TODO: Should we get the TileSet from the RenderServer, similar to how we get textures? - void draw(const RenderServer& render_server, const TileSet& tile_set) const + void draw(const RenderServer& render_server) const { - // Get tile set texture - const sdl::Texture& tile_set_texture = render_server.get_texture(tile_set.get_texture_id()); + const TileSet& tile_set = *tile_set_ptr_; // TODO: Implement more efficiently (we could just iterate over tiles_ directly) for (MapCoord y = 0; y < height_; y++) { @@ -97,7 +94,7 @@ export namespace rutile }; render_server.render_texture( - tile_set_texture, + tile_set.get_texture(), &tile_atlas_src_rect, &tile_dest_rect ); diff --git a/src/rutile/core/engine.cppm b/src/rutile/core/engine.cppm index 984f323..5ac1379 100644 --- a/src/rutile/core/engine.cppm +++ b/src/rutile/core/engine.cppm @@ -7,6 +7,7 @@ export module rutile.core.engine; import std; import rutile.config; import rutile.core.render_server; +import rutile.core.resource_server; import rutile.core.ui.ui_server; import wrappers.sdl; @@ -22,12 +23,14 @@ export namespace rutile sdl::Window window_; RenderServer render_server_; + ResourceServer resource_server_; UIServer ui_server_; // Private constructor Engine(sdl::Window&& window, sdl::Renderer&& renderer) : window_{std::move(window)}, render_server_{std::move(renderer)}, + resource_server_{render_server_.get_renderer()}, ui_server_{render_server_.get_renderer(), window_} { instantiated_ = true; @@ -84,6 +87,11 @@ export namespace rutile return render_server_; } + ResourceServer& get_resource_server() + { + return resource_server_; + } + UIServer& get_ui_server() { return ui_server_; diff --git a/src/rutile/core/render_server.cppm b/src/rutile/core/render_server.cppm index 7366b13..4bb7cf7 100644 --- a/src/rutile/core/render_server.cppm +++ b/src/rutile/core/render_server.cppm @@ -3,28 +3,21 @@ module; export module rutile.core.render_server; import std; -import rutile.core.resource_manager; -import rutile.core.texture_loader; +import rutile.core.resource_server; +import rutile.core.resources.texture; import wrappers.sdl; export namespace rutile { - using TextureID = unsigned int; - using TextureManager = ResourceManager; - class RenderServer { sdl::Renderer renderer_; - TextureLoader texture_loader_; - TextureManager texture_manager_; public: RenderServer() = delete; explicit RenderServer(sdl::Renderer&& renderer) - : renderer_{std::move(renderer)}, - texture_loader_{renderer_}, - texture_manager_{texture_loader_} + : renderer_{std::move(renderer)} { renderer_.set_vsync(1); } @@ -42,22 +35,6 @@ export namespace rutile return renderer_; } - /** - * Load a texture from a file (if not loaded yet) and return its texture ID. - */ - constexpr TextureID load_texture(const std::string& filename) - { - return texture_manager_.load_resource_by_name(filename); - } - - /** - * Get a reference to a texture by its texture ID. - */ - constexpr const sdl::Texture& get_texture(const TextureID texture_id) const - { - return texture_manager_.get_resource(texture_id); - } - void start_frame() const { renderer_.clear(); @@ -68,17 +45,8 @@ export namespace rutile renderer_.present(); } - void render_texture( - const TextureID texture_id, - const sdl::FRect* src_rect, - const sdl::FRect* dest_rect - ) const - { - render_texture(get_texture(texture_id), src_rect, dest_rect); - } - constexpr void render_texture( - const sdl::Texture& texture, + const Texture& texture, const sdl::FRect* src_rect, const sdl::FRect* dest_rect ) const diff --git a/src/rutile/core/resource_manager.cppm b/src/rutile/core/resource_manager.cppm deleted file mode 100644 index b73f184..0000000 --- a/src/rutile/core/resource_manager.cppm +++ /dev/null @@ -1,120 +0,0 @@ -module; - -#include - -export module rutile.core.resource_manager; - -import std; - -export namespace rutile -{ - template - concept IsResourceLoader = requires(ResourceLoaderType loader, const std::string& name) - { - { loader.load_resource(name) } -> std::same_as; - }; - - template < - typename ResourceIDType, - typename ResourceType, - IsResourceLoader ResourceLoaderType - > - class ResourceManager - { - // Resource loader - ResourceLoaderType& resource_loader_; - - // Registry of loaded resources, array index is resource ID - std::vector resource_registry_; - - // Mapping of all loaded resources from (file) name to resource ID - std::map resource_name_map_; - - public: - ResourceManager() = delete; - - explicit ResourceManager(ResourceLoaderType& resource_loader) - : resource_loader_{resource_loader} - {} - - // No copy or move operations - ResourceManager(const ResourceManager&) = delete; - ResourceManager& operator=(const ResourceManager&) = delete; - ResourceManager(ResourceManager&&) = delete; - ResourceManager& operator=(ResourceManager&&) = delete; - - ~ResourceManager() = default; - - /** - * Adds a new resource to the registry and returns its ID. - */ - ResourceIDType add_resource(ResourceType&& resource) - { - // Add resource to end of vector - resource_registry_.push_back(std::move(resource)); - - // Return the index of the newly added resource - return static_cast(resource_registry_.size() - 1); - } - - /** - * Adds a new resource to the registry, associates the name with it and returns its ID. - */ - ResourceIDType add_resource(ResourceType&& resource, const std::string& name) - { - // Add resource - ResourceIDType resource_id = add_resource(std::move(resource)); - - // Associate name with resource ID for future access - resource_name_map_.emplace(name, resource_id); - - return resource_id; - } - - /** - * Gets the resource ID for a given resource name, or `std::nullopt` if the resource was not found. - */ - std::optional get_resource_id_by_name(const std::string& name) const - { - // Check if resource is already loaded - if ( - const auto search = resource_name_map_.find(name); - search != resource_name_map_.end() - ) { - // Return resource ID - return search->second; - } - - return std::nullopt; - } - - /** - * Loads a resource (e.g. from a file) and adds it to the registry if it isn't loaded yet. - * Returns the resource ID. - */ - ResourceIDType load_resource_by_name(const std::string& name) - { - // Check if resource is already loaded - if ( - auto resource_id = get_resource_id_by_name(name); - resource_id.has_value() - ) { - return resource_id.value(); - } - - // Load resource and add it to the registry - return add_resource(resource_loader_.load_resource(name), name); - } - - /** - * Returns a reference to the resource with the given ID. - * The reference is not guaranteed to be valid after adding new resources to the registry. - * Assumes that the resource ID is valid. - */ - const ResourceType& get_resource(const ResourceIDType resource_id) const - { - assert(resource_id < resource_registry_.size()); - return resource_registry_[resource_id]; - } - }; -} diff --git a/src/rutile/core/resource_server.cppm b/src/rutile/core/resource_server.cppm new file mode 100644 index 0000000..7744429 --- /dev/null +++ b/src/rutile/core/resource_server.cppm @@ -0,0 +1,61 @@ +module; + +export module rutile.core.resource_server; + +import std; +import rutile.core.common; +import rutile.core.resources.resource_registry; +import rutile.core.resources.texture; +import rutile.core.resources.tile_set; +import rutile.core.resources.loaders.texture_loader; +import rutile.core.resources.loaders.tile_set_loader; +import wrappers.sdl; + +export namespace rutile +{ + using TextureRegistry = ResourceRegistry; + using TileSetRegistry = ResourceRegistry; + + class ResourceServer + { + // Resource registries + TextureRegistry texture_registry_; + TileSetRegistry tile_set_registry_; + + // Resource loaders + TextureLoader texture_loader_; + TileSetLoader tile_set_loader_; + + public: + ResourceServer() = delete; + + explicit ResourceServer(sdl::Renderer& renderer) + : texture_loader_{renderer}, + tile_set_loader_{std::bind(&ResourceServer::load_texture, this, std::placeholders::_1)} + {} + + // No copy or move operations + ResourceServer(const ResourceServer&) = delete; + ResourceServer& operator=(const ResourceServer&) = delete; + ResourceServer(ResourceServer&&) = delete; + ResourceServer& operator=(ResourceServer&&) = delete; + + ~ResourceServer() = default; + + /** + * Load a texture from a file (if not loaded yet) and return a reference to a shared Texture pointer. + */ + const ResourcePtr& load_texture(const std::string& filename) + { + return texture_registry_.load_and_get_resource(filename, texture_loader_); + } + + /** + * Load a tile set from a JSON file (if not loaded yet) and return a reference to a shared TileSet pointer. + */ + const ResourcePtr& load_tile_set(const std::string& filename) + { + return tile_set_registry_.load_and_get_resource(filename, tile_set_loader_); + } + }; +} diff --git a/src/rutile/core/texture_loader.cppm b/src/rutile/core/resources/loaders/texture_loader.cppm similarity index 81% rename from src/rutile/core/texture_loader.cppm rename to src/rutile/core/resources/loaders/texture_loader.cppm index 78cb3df..75c5f48 100644 --- a/src/rutile/core/texture_loader.cppm +++ b/src/rutile/core/resources/loaders/texture_loader.cppm @@ -1,9 +1,9 @@ module; -export module rutile.core.texture_loader; +export module rutile.core.resources.loaders.texture_loader; import std; -import rutile.core.resource_manager; +import rutile.core.resources.texture; import wrappers.sdl; import wrappers.sdl_image; @@ -26,7 +26,7 @@ export namespace rutile ~TextureLoader() = default; - sdl::Texture load_resource(const std::string& filename) const + Texture load_resource(const std::string& filename) const { return sdl_image::load_texture(renderer_, filename); } diff --git a/src/rutile/core/resources/loaders/tile_set_loader.cppm b/src/rutile/core/resources/loaders/tile_set_loader.cppm new file mode 100644 index 0000000..48f7142 --- /dev/null +++ b/src/rutile/core/resources/loaders/tile_set_loader.cppm @@ -0,0 +1,57 @@ +module; + +export module rutile.core.resources.loaders.tile_set_loader; + +import std; +import rutile.core.common; +import rutile.core.resources.texture; +import rutile.core.resources.tile_set; +// import vendor.nlohmann.json; + +// using json = nlohmann::json; + +namespace rutile +{ + using TextureLoadCallbackFunc = std::function< + const ResourcePtr&(const std::string&) + >; +} + +export namespace rutile +{ + class TileSetLoader + { + TextureLoadCallbackFunc texture_load_callback_; + + public: + explicit TileSetLoader(const TextureLoadCallbackFunc& texture_load_callback) + : texture_load_callback_{texture_load_callback} + {} + + // No copy or move operations (reference) + TileSetLoader(const TileSetLoader&) = delete; + TileSetLoader& operator=(const TileSetLoader&) = delete; + TileSetLoader(TileSetLoader&&) = delete; + TileSetLoader& operator=(TileSetLoader&&) = delete; + + ~TileSetLoader() = default; + + TileSet load_resource(const std::string& name) const + { + return TileSet::create_from_texture( + texture_load_callback_(name), + // TODO: Load this from a JSON file + 32, + { + // Tile 0: No tile + {0, 0}, + // Tile 1: Actual first tile in the texture + {0, 0}, + {1, 0}, + {2, 0}, + {3, 0}, + } + ); + } + }; +} diff --git a/src/rutile/core/resources/resource_registry.cppm b/src/rutile/core/resources/resource_registry.cppm new file mode 100644 index 0000000..748ce19 --- /dev/null +++ b/src/rutile/core/resources/resource_registry.cppm @@ -0,0 +1,128 @@ +module; + +#include + +export module rutile.core.resources.resource_registry; + +import std; +import rutile.core.common; + +export namespace rutile +{ + template + concept IsResourceLoader = requires(ResourceLoaderType loader, const std::string& name) + { + { loader.load_resource(name) } -> std::same_as; + }; + + template + class ResourceRegistry + { + // Vector containing all currently loaded resources as shared pointers; array index is resource ID + std::vector> resources_; + + // Mapping of all loaded resources from name (e.g. file name) to resource ID + std::map resource_name_map_; + + public: + ResourceRegistry() = default; + + // No copy or move operations + ResourceRegistry(const ResourceRegistry&) = delete; + ResourceRegistry& operator=(const ResourceRegistry&) = delete; + ResourceRegistry(ResourceRegistry&&) = delete; + ResourceRegistry& operator=(ResourceRegistry&&) = delete; + + ~ResourceRegistry() = default; + + /** + * Return a reference to a shared pointer of the resource with the given ID. + * + * The reference is not guaranteed to be valid after adding new resources to the registry. Copy the + * reference into a new shared_ptr if you want to keep using it. + * + * Assumes that the resource ID is valid (debug assertions only). + */ + const ResourcePtr& get_resource_by_id(const ResourceIDType resource_id) const + { + assert(resource_id < resources_.size()); + assert(resources_[resource_id].use_count() > 0); + return resources_[resource_id]; + } + + /** + * Find the resource ID for a given resource name, or return `std::nullopt` if the resource was not found. + */ + std::optional find_resource_id_by_name(const std::string& name) const + { + // Check if resource is already loaded + const auto search = resource_name_map_.find(name); + if (search != resource_name_map_.end()) { + // Return resource ID + return search->second; + } + + return std::nullopt; + } + + /** + * Add a new resource (without a name) to the registry and return its ID. + */ + ResourceIDType add_resource(ResourceType&& resource) + { + // Add resource to end of vector, moving the resource object into a shared_ptr + resources_.push_back( + std::make_shared(std::move(resource)) + ); + + // Return the index of the newly added resource + return static_cast(resources_.size() - 1); + } + + /** + * Add a new named resource to the registry, associate the name with it and return its ID. + */ + ResourceIDType add_resource(ResourceType&& resource, const std::string& name) + { + // Add resource + ResourceIDType resource_id = add_resource(std::move(resource)); + + // Associate name with resource ID for future access + resource_name_map_.emplace(name, resource_id); + + return resource_id; + } + + /** + * Load a resource by (file) name using the given resource loader and add it to the registry, unless the + * resource is already loaded. + * Return the resource ID (in both cases). + */ + template ResourceLoaderType> + ResourceIDType load_resource(const std::string& name, ResourceLoaderType& resource_loader) + { + // Check if resource is already loaded + auto resource_id = find_resource_id_by_name(name); + if (resource_id.has_value()) { + return resource_id.value(); + } + + // Load resource and add it to the registry + return add_resource(resource_loader.load_resource(name), name); + } + + /** + * Load a resource if it's not loaded yet (like `load_resource()`), then return a reference to a shared + * pointer to the resource (like `get_resource_by_id()`). + */ + template ResourceLoaderType> + const ResourcePtr& load_and_get_resource( + const std::string& name, + ResourceLoaderType& resource_loader + ) + { + ResourceIDType resource_id = load_resource(name, resource_loader); + return get_resource_by_id(resource_id); + } + }; +} diff --git a/src/rutile/core/resources/texture.cppm b/src/rutile/core/resources/texture.cppm new file mode 100644 index 0000000..3907a60 --- /dev/null +++ b/src/rutile/core/resources/texture.cppm @@ -0,0 +1,12 @@ +module; + +export module rutile.core.resources.texture; + +import std; +import wrappers.sdl; + +export namespace rutile +{ + using TextureID = unsigned int; + using Texture = sdl::Texture; +} diff --git a/src/rutile/core/drawing/tile_set.cppm b/src/rutile/core/resources/tile_set.cppm similarity index 71% rename from src/rutile/core/drawing/tile_set.cppm rename to src/rutile/core/resources/tile_set.cppm index ddad86c..9a8bb9c 100644 --- a/src/rutile/core/drawing/tile_set.cppm +++ b/src/rutile/core/resources/tile_set.cppm @@ -2,10 +2,11 @@ module; #include -export module rutile.core.drawing.tile_set; +export module rutile.core.resources.tile_set; import std; -import rutile.core.render_server; +import rutile.core.common; +import rutile.core.resources.texture; import wrappers.sdl; export namespace rutile @@ -20,8 +21,8 @@ export namespace rutile class TileSet { - // Texture ID of tileset atlas texture - TextureID texture_id_; + // Shared pointer to the tileset atlas texture (managed by ResourceServer) + ResourcePtr texture_ptr_; // Size of each tile in pixels // TODO: Do we need this? @@ -35,27 +36,23 @@ export namespace rutile TileSet() = delete; explicit TileSet( - const TextureID texture_id, + const ResourcePtr& texture_ptr, std::vector tile_definitions ) - : texture_id_{texture_id}, + : texture_ptr_{texture_ptr}, tile_definitions_{std::move(tile_definitions)} {} static TileSet create_from_texture( - RenderServer& render_server, - const std::string& filename, + const ResourcePtr& texture_ptr, const int tile_size, const std::vector& tile_atlas_coords ) { - const TextureID texture_id = render_server.load_texture(filename); - const sdl::Texture& texture = render_server.get_texture(texture_id); - assert(tile_size > 0); // TODO: These probably shouldn't be asserts... - assert(texture.get_width() % tile_size == 0); - assert(texture.get_height() % tile_size == 0); + assert(texture_ptr->get_width() % tile_size == 0); + assert(texture_ptr->get_height() % tile_size == 0); std::vector tile_definitions; @@ -71,17 +68,17 @@ export namespace rutile }; // TODO: These probably shouldn't be asserts... - assert(tile.atlas_src_rect.x + tile.atlas_src_rect.w <= texture.get_width()); - assert(tile.atlas_src_rect.y + tile.atlas_src_rect.h <= texture.get_height()); + assert(tile.atlas_src_rect.x + tile.atlas_src_rect.w <= texture_ptr->get_width()); + assert(tile.atlas_src_rect.y + tile.atlas_src_rect.h <= texture_ptr->get_height()); tile_definitions.push_back(tile); } - return TileSet(texture_id, tile_definitions); + return TileSet(texture_ptr, tile_definitions); } - constexpr TextureID get_texture_id() const + constexpr const Texture& get_texture() const { - return texture_id_; + return *texture_ptr_; } constexpr const TileDefinition& get_tile_definition(const TileID tile_id) const diff --git a/src/rutile/game/game.cppm b/src/rutile/game/game.cppm index 9f8ca08..6d422db 100644 --- a/src/rutile/game/game.cppm +++ b/src/rutile/game/game.cppm @@ -7,7 +7,6 @@ export module rutile.game.game; import std; import rutile.core.drawing.sprite; import rutile.core.drawing.tile_map; -import rutile.core.drawing.tile_set; import rutile.core.engine; import rutile.core.render_server; import wrappers.rmlui.core; @@ -24,12 +23,9 @@ export namespace rutile // Reference to the engine Engine& engine_; - // Sprites for testing + // Sprites and stuff for testing Sprite player_sprite_; std::vector sprites_; - - // Tile set and tile map (TODO: tile set should be moved to resource manager in engine) - TileSet tile_set_; TileMap tile_map_; // Private constructor @@ -37,30 +33,16 @@ export namespace rutile : engine_(engine), player_sprite_{ Sprite::create_from_texture( - engine_.get_render_server(), - "assets/sprites/neocat_64.png", + engine_.get_resource_server().load_texture("assets/sprites/neocat_64.png"), std::nullopt, sdl::FPoint{0, 0}, sdl::FPoint{-32, -32} ) }, - tile_set_{ - TileSet::create_from_texture( - engine_.get_render_server(), - "assets/tilesets/terrain.png", - 32, - { - // Tile 0: No tile - {0, 0}, - // Tile 1: Actual first tile in the texture - {0, 0}, - {1, 0}, - {2, 0}, - {3, 0}, - } - ) - }, - tile_map_(0, 32, 16, 12, {50, 50}) + tile_map_{ + engine_.get_resource_server().load_tile_set("assets/tilesets/terrain.png"), + 32, 16, 12, {50, 50} + } { // Initialize UI const auto& ui_server = engine_.get_ui_server(); @@ -113,8 +95,7 @@ export namespace rutile if (event.type == sdl::EventTypes::MouseButtonUp) { sprites_.push_back( Sprite::create_from_texture( - engine_.get_render_server(), - "assets/sprites/neofox_64.png", + engine_.get_resource_server().load_texture("assets/sprites/neofox_64.png"), std::nullopt, sdl::FPoint{event.motion.x, event.motion.y}, sdl::FPoint{-32, -32} @@ -138,7 +119,7 @@ export namespace rutile render_server.start_frame(); // Render tilemap - tile_map_.draw(render_server, tile_set_); + tile_map_.draw(render_server); // Render sprites for (const Sprite& sprite : sprites_) { diff --git a/src/wrappers/sdl/video.cppm b/src/wrappers/sdl/video.cppm index f393a43..6a2fd00 100644 --- a/src/wrappers/sdl/video.cppm +++ b/src/wrappers/sdl/video.cppm @@ -56,7 +56,8 @@ export namespace sdl */ std::pair get_size() const { - int width, height; + int width = 0; + int height = 0; if (!SDL_GetWindowSize(get_raw(), &width, &height)) { throw SDLException("SDL_GetWindowSize"); From b4635cdb99a144bf8fdd95c6c6d81706ff44275b Mon Sep 17 00:00:00 2001 From: binaryDiv Date: Thu, 25 Jun 2026 23:33:14 +0200 Subject: [PATCH 3/3] Implement loading tile sets from JSON files --- assets/tilesets/terrain.json | 26 ++++++++++++ .../resources/loaders/tile_set_loader.cppm | 41 ++++++++++++------- src/rutile/game/game.cppm | 2 +- 3 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 assets/tilesets/terrain.json diff --git a/assets/tilesets/terrain.json b/assets/tilesets/terrain.json new file mode 100644 index 0000000..fec51c9 --- /dev/null +++ b/assets/tilesets/terrain.json @@ -0,0 +1,26 @@ +{ + "texture": "terrain.png", + "tile_size": 32, + "tiles": [ + { + "x": 0, + "y": 0 + }, + { + "x": 0, + "y": 0 + }, + { + "x": 1, + "y": 0 + }, + { + "x": 2, + "y": 0 + }, + { + "x": 3, + "y": 0 + } + ] +} diff --git a/src/rutile/core/resources/loaders/tile_set_loader.cppm b/src/rutile/core/resources/loaders/tile_set_loader.cppm index 48f7142..1d423a4 100644 --- a/src/rutile/core/resources/loaders/tile_set_loader.cppm +++ b/src/rutile/core/resources/loaders/tile_set_loader.cppm @@ -6,9 +6,11 @@ import std; import rutile.core.common; import rutile.core.resources.texture; import rutile.core.resources.tile_set; -// import vendor.nlohmann.json; +import wrappers.sdl; +import vendor.nlohmann.json; -// using json = nlohmann::json; +namespace fs = std::filesystem; +using json = nlohmann::json; namespace rutile { @@ -38,20 +40,29 @@ export namespace rutile TileSet load_resource(const std::string& name) const { - return TileSet::create_from_texture( - texture_load_callback_(name), - // TODO: Load this from a JSON file - 32, - { - // Tile 0: No tile - {0, 0}, - // Tile 1: Actual first tile in the texture - {0, 0}, - {1, 0}, - {2, 0}, - {3, 0}, + try { + std::ifstream json_file(name); + json data = json::parse(json_file); + + // Texture path is relative to JSON file + fs::path texture_path = fs::path(name).replace_filename(data.at("texture")); + + std::vector tile_atlas_coords; + for (auto tile : data.at("tiles")) { + tile_atlas_coords.push_back({tile.at("x"), tile.at("y")}); } - ); + + return TileSet::create_from_texture( + texture_load_callback_(texture_path), + data.at("tile_size"), + tile_atlas_coords + ); + } + catch (const json::exception& e) { + throw std::runtime_error( + std::format("Error loading tile set from \"{}\": {}", name, e.what()) + ); + } } }; } diff --git a/src/rutile/game/game.cppm b/src/rutile/game/game.cppm index 6d422db..ff6822a 100644 --- a/src/rutile/game/game.cppm +++ b/src/rutile/game/game.cppm @@ -40,7 +40,7 @@ export namespace rutile ) }, tile_map_{ - engine_.get_resource_server().load_tile_set("assets/tilesets/terrain.png"), + engine_.get_resource_server().load_tile_set("assets/tilesets/terrain.json"), 32, 16, 12, {50, 50} } {