From b8bd56392c1f2e40fc40490c0f09f1f28a8a0acd Mon Sep 17 00:00:00 2001 From: binaryDiv Date: Wed, 26 Nov 2025 01:57:08 +0100 Subject: [PATCH] Implement ResourceManager and TextureLoader --- assets/neofox.png | Bin 0 -> 12902 bytes src/core/engine.cppm | 20 +++--- src/core/render_server.cppm | 81 ++++++++++++++++++++++ src/core/renderer.cppm | 47 ------------- src/core/resource_manager.cppm | 122 +++++++++++++++++++++++++++++++++ src/core/texture_loader.cppm | 35 ++++++++++ src/game/game.cppm | 54 ++++++++++----- src/game/sprite.cppm | 24 +++---- 8 files changed, 290 insertions(+), 93 deletions(-) create mode 100644 assets/neofox.png create mode 100644 src/core/render_server.cppm delete mode 100644 src/core/renderer.cppm create mode 100644 src/core/resource_manager.cppm create mode 100644 src/core/texture_loader.cppm diff --git a/assets/neofox.png b/assets/neofox.png new file mode 100644 index 0000000000000000000000000000000000000000..64dd563415c0341c930fd7fb94be62fb00ba7c26 GIT binary patch literal 12902 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 UCqdu*uL7j{NK3I&-tyJ|1LeTW!~g&Q literal 0 HcmV?d00001 diff --git a/src/core/engine.cppm b/src/core/engine.cppm index 3d50323..fda4019 100644 --- a/src/core/engine.cppm +++ b/src/core/engine.cppm @@ -7,13 +7,11 @@ module; export module core.engine; import config; -import core.renderer; +import core.render_server; import wrappers.sdl; export namespace core { - using Window = sdl::Window; - class Engine { // Whether this class is currently instantiated (to prevent multiple instances) @@ -22,13 +20,13 @@ export namespace core // If this is set to false, the application will exit bool keep_running_ = true; - Window window_; - Renderer renderer_; + sdl::Window window_; + RenderServer render_server_; // Private constructor - Engine(Window&& window, Renderer&& renderer) + Engine(sdl::Window&& window, sdl::Renderer&& renderer) : window_{std::move(window)}, - renderer_{std::move(renderer)} + render_server_{std::move(renderer)} { instantiated_ = true; } @@ -62,7 +60,7 @@ export namespace core return std::unique_ptr{ new Engine{ std::move(sdl_window), - Renderer{std::move(sdl_renderer)} + std::move(sdl_renderer) } }; } @@ -72,14 +70,14 @@ export namespace core return keep_running_; } - Window& get_window() + sdl::Window& get_window() { return window_; } - Renderer& get_renderer() + RenderServer& get_render_server() { - return renderer_; + return render_server_; } // Handles an SDL event. Returns true if the event has been handled. diff --git a/src/core/render_server.cppm b/src/core/render_server.cppm new file mode 100644 index 0000000..c159769 --- /dev/null +++ b/src/core/render_server.cppm @@ -0,0 +1,81 @@ +module; + +#include +#include + +export module core.render_server; + +import core.resource_manager; +import core.texture_loader; +import wrappers.sdl; + +export namespace core +{ + 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_} + {} + + // No copy or move operations + RenderServer(const RenderServer&) = delete; + RenderServer& operator=(const RenderServer&) = delete; + RenderServer(RenderServer&&) = delete; + RenderServer& operator=(RenderServer&&) = delete; + + ~RenderServer() = default; + + constexpr sdl::Renderer& get_renderer() + { + return renderer_; + } + + /** + * Loads a texture from a file (if not loaded yet) and returns its texture ID. + */ + constexpr TextureID load_texture(const std::string& filename) + { + return texture_manager_.load_resource_by_name(filename); + } + + void start_frame() const + { + renderer_.clear(); + } + + void finish_frame() const + { + renderer_.present(); + } + + constexpr void render_texture( + const TextureID texture_id, + const sdl::FRect* src_rect, + const sdl::FRect* dest_rect + ) const + { + render_texture(texture_manager_.get_resource(texture_id), src_rect, dest_rect); + } + + void render_texture( + const sdl::Texture& texture, + const sdl::FRect* src_rect, + const sdl::FRect* dest_rect + ) const + { + renderer_.render_texture(texture, src_rect, dest_rect); + } + }; +} diff --git a/src/core/renderer.cppm b/src/core/renderer.cppm deleted file mode 100644 index 1704c2f..0000000 --- a/src/core/renderer.cppm +++ /dev/null @@ -1,47 +0,0 @@ -module; - -#include - -export module core.renderer; - -import wrappers.sdl; - -export namespace core -{ - // TODO: Rename this class to RenderServer or something to distinguish it from sdl::Renderer? - class Renderer - { - sdl::Renderer sdl_renderer_; - - public: - Renderer() = delete; - - explicit Renderer(sdl::Renderer&& sdl_renderer) - : sdl_renderer_{std::move(sdl_renderer)} - {} - - constexpr sdl::Renderer& get_sdl_renderer() - { - return sdl_renderer_; - } - - void start_frame() const - { - sdl_renderer_.clear(); - } - - void finish_frame() const - { - sdl_renderer_.present(); - } - - void render_texture( - const sdl::Texture& texture, - const sdl::FRect* src_rect, - const sdl::FRect* dest_rect - ) const - { - sdl_renderer_.render_texture(texture, src_rect, dest_rect); - } - }; -} diff --git a/src/core/resource_manager.cppm b/src/core/resource_manager.cppm new file mode 100644 index 0000000..2e947f5 --- /dev/null +++ b/src/core/resource_manager.cppm @@ -0,0 +1,122 @@ +module; + +#include +#include +#include +#include +#include + +export module core.resource_manager; + +export namespace core +{ + 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/core/texture_loader.cppm b/src/core/texture_loader.cppm new file mode 100644 index 0000000..32548c4 --- /dev/null +++ b/src/core/texture_loader.cppm @@ -0,0 +1,35 @@ +module; + +#include + +export module core.texture_loader; + +import core.resource_manager; +import wrappers.sdl; +import wrappers.sdl_image; + +export namespace core +{ + class TextureLoader + { + sdl::Renderer& renderer_; + + public: + explicit TextureLoader(sdl::Renderer& renderer) + : renderer_{renderer} + {} + + // No copy or move operations (reference) + TextureLoader(const TextureLoader&) = delete; + TextureLoader& operator=(const TextureLoader&) = delete; + TextureLoader(TextureLoader&&) = delete; + TextureLoader& operator=(TextureLoader&&) = delete; + + ~TextureLoader() = default; + + sdl::Texture load_resource(const std::string& filename) const + { + return sdl_image::LoadTexture(renderer_, filename); + } + }; +} diff --git a/src/game/game.cppm b/src/game/game.cppm index 1d68ace..04f43f5 100644 --- a/src/game/game.cppm +++ b/src/game/game.cppm @@ -2,12 +2,13 @@ module; #include #include +#include #include export module game.game; import core.engine; -import core.renderer; +import core.render_server; import game.sprite; import wrappers.sdl; import wrappers.sdl_image; @@ -22,21 +23,18 @@ export namespace game // Reference to the engine core::Engine& engine_; - // Sprite for testing - std::unique_ptr sprite_{nullptr}; + // Sprites for testing + Sprite player_sprite_; + std::vector sprites_; // Private constructor explicit Game(core::Engine& engine) - : engine_(engine) - { - // TODO: Texture should be a reference/pointer to an object managed by a ResourceManager or similar. - auto texture = sdl_image::LoadTexture( - engine_.get_renderer().get_sdl_renderer(), - "assets/neocat.png" - ); - - sprite_ = std::make_unique(std::move(texture), 100, 100); - } + : engine_(engine), + player_sprite_{ + engine_.get_render_server().load_texture("assets/neocat.png"), + sdl::FRect{0, 0, 100, 100} + } + {} public: Game() = delete; @@ -62,15 +60,28 @@ export namespace game } // Handles an SDL event. Returns true if the event has been handled. - bool handle_event(const sdl::Event* event) const + bool handle_event(const sdl::Event* event) { if (event->type == SDL_EVENT_MOUSE_MOTION) { - sprite_->move( + player_sprite_.move( event->motion.x - 50, event->motion.y - 50 ); return true; } + + if (event->type == SDL_EVENT_MOUSE_BUTTON_UP) { + sprites_.emplace_back( + engine_.get_render_server().load_texture("assets/neofox.png"), + sdl::FRect{ + event->motion.x - 50, + event->motion.y - 50, + 100, 100 + } + ); + return true; + } + return false; } @@ -80,10 +91,15 @@ export namespace game void render() const { - const auto& renderer = engine_.get_renderer(); - renderer.start_frame(); - sprite_->draw(renderer); - renderer.finish_frame(); + const auto& render_server = engine_.get_render_server(); + render_server.start_frame(); + + for (const Sprite& sprite : sprites_) { + sprite.draw(render_server); + } + player_sprite_.draw(render_server); + + render_server.finish_frame(); } void shutdown() diff --git a/src/game/sprite.cppm b/src/game/sprite.cppm index 901ea28..cd297c3 100644 --- a/src/game/sprite.cppm +++ b/src/game/sprite.cppm @@ -1,11 +1,10 @@ module; -#include #include export module game.sprite; -import core.renderer; +import core.render_server; import wrappers.sdl; // TODO: Move this to a different namespace (core, drawing, ...?) @@ -13,21 +12,14 @@ export namespace game { class Sprite { - // TODO: Texture should be a reference/pointer to an object managed by a ResourceManager or similar. - sdl::Texture texture_; + core::TextureID texture_id_; sdl::FRect dest_rect_{0, 0, 0, 0}; public: - explicit Sprite( - sdl::Texture&& texture, - const int width, - const int height - ) - : texture_{std::move(texture)} - { - dest_rect_.w = static_cast(width); - dest_rect_.h = static_cast(height); - } + explicit Sprite(const core::TextureID texture_id, const sdl::FRect dest_rect) + : texture_id_{texture_id}, + dest_rect_{dest_rect} + {} void move(const float x, const float y) { @@ -35,9 +27,9 @@ export namespace game dest_rect_.y = y; } - void draw(const core::Renderer& renderer) const + void draw(const core::RenderServer& render_server) const { - renderer.render_texture(texture_, nullptr, &dest_rect_); + render_server.render_texture(texture_id_, nullptr, &dest_rect_); } }; }