Implement ResourceManager and TextureLoader

This commit is contained in:
Lexi / Zoe 2025-11-26 01:57:08 +01:00
parent e37eb0b03f
commit 772f4b665a
Signed by: binaryDiv
GPG Key ID: F8D4956E224DA232
7 changed files with 290 additions and 93 deletions

View File

@ -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<Engine>{
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.

View File

@ -0,0 +1,81 @@
module;
#include <string>
#include <utility>
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<TextureID, sdl::Texture, TextureLoader>;
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);
}
};
}

View File

@ -1,47 +0,0 @@
module;
#include <utility>
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);
}
};
}

View File

@ -0,0 +1,122 @@
module;
#include <cassert>
#include <map>
#include <optional>
#include <string>
#include <vector>
export module core.resource_manager;
export namespace core
{
template <typename ResourceLoaderType, typename ResourceType>
concept IsResourceLoader = requires(ResourceLoaderType loader, const std::string& name)
{
{ loader.load_resource(name) } -> std::same_as<ResourceType>;
};
template <
typename ResourceIDType,
typename ResourceType,
IsResourceLoader<ResourceType> ResourceLoaderType
>
class ResourceManager
{
// Resource loader
ResourceLoaderType& resource_loader_;
// Registry of loaded resources, array index is resource ID
std::vector<ResourceType> resource_registry_;
// Mapping of all loaded resources from (file) name to resource ID
std::map<std::string, ResourceIDType> 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<ResourceIDType>(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<ResourceIDType> 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];
}
};
}

View File

@ -0,0 +1,35 @@
module;
#include <string>
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);
}
};
}

View File

@ -2,12 +2,13 @@ module;
#include <cassert>
#include <memory>
#include <vector>
#include <SDL3/SDL.h>
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> sprite_{nullptr};
// Sprites for testing
Sprite player_sprite_;
std::vector<Sprite> 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<Sprite>(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()

View File

@ -1,11 +1,10 @@
module;
#include <utility>
#include <SDL3/SDL.h>
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<float>(width);
dest_rect_.h = static_cast<float>(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_);
}
};
}