Created
November 13, 2025 18:40
-
-
Save stungeye/29163b0113bbe8b938f41994d13fb6f5 to your computer and use it in GitHub Desktop.
C++ Managed Resources and a Resource Manager for Raylib Projects (Required CPP20+)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #pragma once | |
| #include "raylib.h" | |
| #include <string> | |
| #include <type_traits> | |
| // Forward declaration of ResourceManager for friending. | |
| template <typename ResourceType> | |
| class ResourceManager; | |
| // Traits to specialize resource loading/unloading for different raylib types | |
| template <typename ResourceType> | |
| struct ResourceTraits; | |
| // Specialization for Texture2D | |
| template <> | |
| struct ResourceTraits<Texture2D> { | |
| static Texture2D Load(const char* path) { return LoadTexture(path); } | |
| static void Unload(Texture2D resource) { UnloadTexture(resource); } | |
| static bool IsValid(const Texture2D& resource) { return resource.id != 0; } | |
| }; | |
| // Specialization for Font | |
| template <> | |
| struct ResourceTraits<Font> { | |
| static Font Load(const char* path) { return LoadFont(path); } | |
| static void Unload(Font resource) { UnloadFont(resource); } | |
| static bool IsValid(const Font& resource) { return resource.texture.id != 0; } | |
| }; | |
| // Specialization for Sound | |
| template <> | |
| struct ResourceTraits<Sound> { | |
| static Sound Load(const char* path) { return LoadSound(path); } | |
| static void Unload(Sound resource) { UnloadSound(resource); } | |
| static bool IsValid(const Sound& resource) { return resource.frameCount > 0; } | |
| }; | |
| // Specialization for Music | |
| template <> | |
| struct ResourceTraits<Music> { | |
| static Music Load(const char* path) { return LoadMusicStream(path); } | |
| static void Unload(Music resource) { UnloadMusicStream(resource); } | |
| static bool IsValid(const Music& resource) { return resource.frameCount > 0; } | |
| }; | |
| // Generic RAII wrapper for raylib resources | |
| // | |
| // ManagedResource ensures that raylib resources are properly | |
| // unloaded when the object is destroyed, preventing memory leaks. | |
| // | |
| // **IMPORTANT: ManagedResource objects cannot be constructed directly.** | |
| // Use ResourceManager<ResourceType>::GetOrLoad() instead, which provides: | |
| // - Automatic caching (same file loaded only once) | |
| // - Shared ownership via std::shared_ptr | |
| // - Automatic cleanup when all references are dropped | |
| // | |
| // ManagedResource is non-copyable and non-movable because: | |
| // - Each instance owns a unique resource | |
| // - ResourceManager handles sharing via std::shared_ptr | |
| // - Moving/copying would complicate resource ownership | |
| template <typename ResourceType> | |
| class ManagedResource final { | |
| public: | |
| // Disable copy and move | |
| ManagedResource(const ManagedResource&) = delete; | |
| ManagedResource& operator=(const ManagedResource&) = delete; | |
| ManagedResource(ManagedResource&&) = delete; | |
| ManagedResource& operator=(ManagedResource&&) = delete; | |
| ~ManagedResource() { | |
| if (ResourceTraits<ResourceType>::IsValid(resource)) { | |
| ResourceTraits<ResourceType>::Unload(resource); | |
| } | |
| } | |
| bool isValid() const { | |
| return ResourceTraits<ResourceType>::IsValid(resource); | |
| } | |
| // Implicit conversion for use with raylib functions. For example: | |
| // TextureManager::Handle unrevealedTile = textureManager.GetOrLoad(unrevealedPath); | |
| // DrawTexture(*unrevealedTile, x, y, WHITE); // Dereference | |
| operator const ResourceType& () const { | |
| return resource; | |
| } | |
| // Texture-specific helpers (only exist for Texture2D) | |
| int width() const | |
| requires std::is_same_v<ResourceType, Texture2D> { // CPP20 | |
| return resource.width; | |
| } | |
| int height() const | |
| requires std::is_same_v<ResourceType, Texture2D> { // CPP20 | |
| return resource.height; | |
| } | |
| private: | |
| friend class ResourceManager<ResourceType>; // Allow Resource Manager to construct | |
| explicit ManagedResource(const std::string& path) | |
| : resource(ResourceTraits<ResourceType>::Load(path.c_str())) { | |
| } | |
| // The managed resource | |
| ResourceType resource{}; | |
| }; | |
| // Type aliases for convenience | |
| using ManagedTexture = ManagedResource<Texture2D>; | |
| using ManagedFont = ManagedResource<Font>; | |
| using ManagedSound = ManagedResource<Sound>; | |
| using ManagedMusic = ManagedResource<Music>; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #pragma once | |
| #include "ManagedResource.hpp" | |
| #include <unordered_map> | |
| #include <memory> | |
| #include <string> | |
| // Generic resource manager with caching for raylib resources. | |
| // | |
| // The ResourceManager ensures each resource file is loaded only once | |
| // and shared across all users via std::shared_ptr. | |
| // | |
| // Key features: | |
| // - Automatic caching: Repeated loads of the same path return the same resource | |
| // - Shared ownership: Multiple objects can safely hold references to resources | |
| // - Automatic cleanup: Resources are unloaded when no longer referenced | |
| // - Thread-safety note: Must be used from the main thread (raylib requirement) | |
| // | |
| // Usage pattern: | |
| // 1. Create a ResourceManager (typically in main) | |
| // 2. Call GetOrLoad(path) to get a Handle to a resource | |
| // 3. Store the Handle as a member variable of type ResourceManager::Handle | |
| // 4. Use the resource directly with raylib functions with a deref | |
| // 5. Resources are automatically unloaded when all Handles are destroyed | |
| // | |
| // Example in a game object: | |
| // class Player { | |
| // TextureManager::Handle spriteTexture; | |
| // public: | |
| // Player(TextureManager& tm, const std::string& filePath) { | |
| // spriteTexture = tm.GetOrLoad(filePath); | |
| // } | |
| // | |
| // void Draw() const { | |
| // if (spriteTexture && spriteTexture->isValid()) { | |
| // DrawTexture(*spriteTexture, x, y, WHITE); | |
| // } | |
| // } | |
| // }; | |
| template <typename ResourceType> | |
| class ResourceManager { | |
| public: | |
| using ManagedType = ManagedResource<ResourceType>; | |
| using Handle = std::shared_ptr<const ManagedType>; | |
| ~ResourceManager() { unloadAll(); } | |
| // Load if missing; otherwise return the existing shared resource | |
| Handle GetOrLoad(const std::string& path) { | |
| if (auto it = cache.find(path); it != cache.end()) { | |
| TraceLog(LOG_INFO, "ResourceManager: Reusing cached resource: %s", path.c_str()); | |
| return it->second; | |
| } | |
| // Have to use 'new' here because ManagedResource's constructor is private | |
| // and ResourceManager is declared as a friend. | |
| std::shared_ptr<ManagedType> resource{ new ManagedType(path) }; | |
| if (!resource->isValid()) { | |
| TraceLog(LOG_WARNING, "ResourceManager: Failed to load: %s", path.c_str()); | |
| return nullptr; | |
| } | |
| cache.emplace(path, resource); | |
| TraceLog(LOG_INFO, "ResourceManager: Loaded new resource: %s", path.c_str()); | |
| return resource; | |
| } | |
| // Drop all references. Resource destructors handle cleanup. | |
| void unloadAll() { | |
| cache.clear(); | |
| } | |
| private: | |
| std::unordered_map<std::string, std::shared_ptr<ManagedType>> cache; | |
| }; | |
| // Type aliases for specific managers | |
| using TextureManager = ResourceManager<Texture2D>; | |
| using FontManager = ResourceManager<Font>; | |
| using SoundManager = ResourceManager<Sound>; | |
| using MusicManager = ResourceManager<Music>; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment