Skip to content

Instantly share code, notes, and snippets.

@stungeye
Created November 13, 2025 18:40
Show Gist options
  • Select an option

  • Save stungeye/29163b0113bbe8b938f41994d13fb6f5 to your computer and use it in GitHub Desktop.

Select an option

Save stungeye/29163b0113bbe8b938f41994d13fb6f5 to your computer and use it in GitHub Desktop.
C++ Managed Resources and a Resource Manager for Raylib Projects (Required CPP20+)
#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>;
#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