Untitled

 avatar
unknown
c_cpp
10 months ago
4.0 kB
16
Indexable
#pragma once

#include <limits>
#include <concepts>
#include <memory>
#include <unordered_map>
#include <vector>
#include <typeindex>
#include <stdexcept>
#include <any>

#include "rrc/Resource.hpp"

namespace rrc
{
	template<typename From, typename To, typename... Args>
	const To& convert(From&, To&, Args&&...);

	// Special tag to represent unknown resource type
	class Any
	{
	};

	template<typename T>
	concept ResourceType = std::derived_from<T, Resource>;

	template<typename T>
	concept ResourceOrAnyType = std::same_as<T, Any> || ResourceType<T>;

	using ResourceId = uint32_t;
	inline constexpr ResourceId InvalidId = std::numeric_limits<ResourceId>::max();
	
	using Hash = size_t;

	template <ResourceOrAnyType ResAny>
	struct ResourceHandle
	{
		ResourceId id = InvalidId;

		bool valid() const { return id != InvalidId; }
		explicit operator bool() const { return valid(); }

		operator ResourceHandle<Any>() const { return { id }; }
	};

	// TODO: const before T&
	template <typename T>
	concept HasHash = requires(T& t) {
		{ t.hash() } -> std::convertible_to<std::size_t>;
	};

	class Registry
	{
	public:
		template <ResourceType Res, typename... Args>
		ResourceHandle<Res> load(Args&&... args)
		{
			auto resource = std::make_unique<Res>(std::forward<Args>(args)...);

			if constexpr (Res::cachable)
			{
				static_assert(HasHash<Res>, "Cacheable resources must implement hash()");

				Hash h = resource->hash();

				auto it = cache_.find(h);
				if (it != cache_.end())
				{
					return makeHandle<Res>(it->second);
				}

				ResourceId id = nextId_++;
				
				cache_[h] = id;
				storage_[id] = std::move(resource);

				return makeHandle<Res>(id);
			}
			else
			{
				ResourceId id = nextId_++;
				
				storage_[id] = std::move(resource);

				return makeHandle<Res>(id);
			}
		}

		template <ResourceType Res>
		void remove(ResourceHandle<Res> handle)
		{
			// Remove from storage
			storage_.erase(handle.id);

			// Remove from cache if applicable
			if constexpr (Res::cachable)
			{
				for (auto it = cache_.begin(); it != cache_.end(); )
				{
					if (it->second == handle.id)
					{
						it = cache_.erase(it);
					}
					else {
						it++;
					}
				}
			}
		}

		template <typename Rep, ResourceOrAnyType ResAny>
		Rep& get(ResourceHandle<ResAny> handle)
		{
			Resource* resource = resolveHandle(handle);

			if constexpr (!std::same_as<ResAny, Any>)
			{
				if (typeid(*resource) != typeid(ResAny))
				{
					throw std::runtime_error("ResourceHandle does not match requested type");
				}
			}

			return resource->template get<Rep>();
		}

		template<typename From, typename To, ResourceType Res, typename... Args>
		const To& convert(ResourceHandle<Res> handle, Args&&... args)
		{
			Resource* resource = resolveHandle(handle);

			if (typeid(*resource) != typeid(Res))
			{
				throw std::runtime_error("ResourceHandle does not point to correct type");
			}
			
			using rrc::convert;

			return convert(
				resource->template get<From>(),
				resource->template get<To>(),
				std::forward<Args>(args)...
				);
		}

		// Type check for Any handles
		template <ResourceType Res>
		bool is(ResourceHandle<Any> handle)
		{
			Resource* res = resolveHandle(handle);
			return res && typeid(*res) == typeid(Res);
		}

	private:
		template <ResourceType Res>
		ResourceHandle<Res> makeHandle(ResourceId id)
		{
			return ResourceHandle<Res>{ id };
		}

		template <ResourceOrAnyType ResAny>
		Resource* resolveHandle(ResourceHandle<ResAny> handle)
		{
			auto it = storage_.find(handle.id);
			if (it != storage_.end())
			{
				return it->second.get();
			}

			throw std::runtime_error("Invalid resource handle");
		}

		std::unordered_map<ResourceId, std::unique_ptr<Resource>> storage_;
		std::unordered_map<Hash, ResourceId> cache_;
		ResourceId nextId_ = 0;
	};
}
Editor is loading...
Leave a Comment