I’m just a beginner in general. I’m trying to implement a resource manager for games. I want to preserve order of insertion (like layers in tilemap you want to render layer by layer, not shuffle) and also be able to get data by a name, so I have both unordered_map and vector inside a class call resource
. And it works like expected
template<typename T>
using umap_str = std::unordered_map<std::string, std::shared_ptr<T>>;
template<typename T>
using vec_sptr = std::vector<std::shared_ptr<T>>;
template<typename T>
struct resource
{
inline static std::size_t _max_id; //inline to not have to init out of line
umap_str<T> _map;
vec_sptr<T> _vec;
};
template<typename... RsrcTs>
struct resource_manager
{
template<typename T>
using rsrc_t = resource<T>;
std::tuple<rsrc_t<RsrcTs>...> storage;
template<typename RsrcT>
inline vec_sptr<RsrcT>& get_vec() {return std::get<rsrc_t<RsrcT>>(storage)._vec;};
template<typename RsrcT>
inline umap_str<RsrcT>& get_map() {return std::get<rsrc_t<RsrcT>>(storage)._map;};
template<typename RsrcT>
inline std::size_t& get_maxid() {return std::get<rsrc_t<RsrcT>>(storage)._max_id;};
template<typename RsrcT>
std::optional<std::shared_ptr<RsrcT>> get(std::string&& name)
{
auto& to_get = get_map<RsrcT>();
if (!to_get.contains(name)) return std::nullopt;
else return to_get.at(name);
};
// add resource with a name, or with an existing shared_ptr //
template<typename RsrcT>
std::shared_ptr<RsrcT> add(std::string&& custom_name = "")
{
static_assert(contains<RsrcT, RsrcTs...>().value, "resource type not in resource declaration");
std::shared_ptr<RsrcT> new_resource = std::make_shared<RsrcT>();
if (custom_name.empty())
get_map<RsrcT>().insert({std::to_string(get_maxid<RsrcT>()), new_resource});
else
{
if (get_map<RsrcT>().contains(custom_name))
{
std::cout << custom_name << "currently in use, using id instead";
get_map<RsrcT>().insert({std::to_string(get_maxid<RsrcT>()), new_resource});
}
else get_map<RsrcT>().insert({custom_name, new_resource});
};
get_vec<RsrcT>().push_back(new_resource);
get_maxid<RsrcT>()++;
return new_resource;
};
template<typename RsrcT>
std::shared_ptr<RsrcT> add(std::shared_ptr<RsrcT> arg, std::string&& custom_name = "")
{
static_assert(contains<RsrcT, RsrcTs...>().value, "resource type not in resource declaration");
if (custom_name.empty())
get_map<RsrcT>().insert({std::to_string(get_maxid<RsrcT>()), arg});
else
{
if (get_map<RsrcT>().contains(custom_name))
{
std::cout << custom_name << "currently in use, using id instead";
get_map<RsrcT>().insert({std::to_string(get_maxid<RsrcT>()), arg});
}
else get_map<RsrcT>().insert({custom_name, arg});
};
get_vec<RsrcT>().push_back(arg);
get_maxid<RsrcT>()++;
return arg;
};
};
—– I can smell that this approach may not be efficient and intuitive (maybe).
Now I want to try storing data with unique_ptr. How should I implement that. I know that I can’t keep more than 1 copy of each, so i’m thinking of storing raw pointers (in the map or in the vec).
Any insights and recommendations would be much appriciated.
1
I have double-stored smart pointers in a fashion similar to what you’re doing, but that may be an old mindset. How big are your vectors?
With modern hardware, searching a vector vs. doing a lookup in a hash map may not be significantly slower. I’d set up a test. Of course, you know the nature of your data, and I don’t.
Regardless — are you having performance issues using the shared_ptrs? Why do you want to switch to unique_ptr? I think what you’re doing is fine.