问题
I'm trying to implement a generic ECS library in C++ for learning purpose. I was thinking about a lot of way to implement things but I always run into a problem. So if you could help me with this one :
Let say I have a constexpr hana::tuple
of hana::type_c
Components, something like :
struct C1 {};
struct C2 {};
struct C3 {};
constexpr auto components = hana::to_tuple(hana::tuple_t<C1, C2, C3>);
And now I have a component storage type, which is not a problem here, so let's call it Storage (the type differ for each component):
struct Storage {};
I want to link each component or each component group, with their Storage
type. So the easy way is to do something like that:
constexpr auto component_storage = hana::make_tuple(
hana::make_pair(hana::to_tuple(hana::tuple_t<C1, C2>), type_c<Storage>),
hana::make_pair(hana::to_tuple(hana::tuple_t<C3>), type_c<Storage>)
);
But the problem now is runtime. If I initialize that tuple but with the real Storage and no longer type_c<Storage>
, I'll have to loop through the tuple to find the Storage
that I need. All of this at runtime no?
And this is really bad, my last version had something like Component::getStorage()
and it was free (but more restrictive).
So the question is : how can I manage to have some getStorage<Component>()
function which will cost nothing at runtime? Well by nothing I mean just return the reference of the Storage.
EDIT: The only way I have think so far is quite simple (sounds like a good point).
Pseudo-Code
struct LinkedStorage {
hana::tuple<...> storages;
hana::tuple<hana::pair...> index;
};
At lest something like:
constexpr auto components = hana::to_tuple(hana::tuple_t<C1, C2, C3>);
constexpr auto storage = hana::to_tuple(hana::tuple_t<Storage, Storage>);
constexpr auto index = hana::make_tuple(
hana::make_pair(hana::to_tuple(hana::tuple_t<C1>, 0),
hana::make_pair(hana::to_tuple(hana::tuple_t<C2, C3>, 1)
);
Like that I should be able to found the index at compile time and just access the right element at runtime. But I'm new at metaprogramming, so I guess someone could make something far better.
回答1:
First of all, no need to use to_tuple(tuple_t<...>)
; you can just use tuple_t<...>
. Now, I think what you actually want to do (since you seem to need runtime storage, which makes sense) is:
// "map" of a set of types to a storage of some type
using StorageMap = hana::tuple<
hana::pair<hana::tuple<hana::type<C1>, hana::type<C2>>, StorageA>,
hana::pair<hana::tuple<hana::type<C3>>, StorageB>
>;
// Actual object that contains the runtime storage (and the free mapping between types)
StorageMap map;
Now, you can implement your getStorage<Component>()
function like this:
template <typename Component>
decltype(auto) getStorage() {
auto found = index_if(map, [](auto const& pair) {
return hana::contains(hana::first(pair), hana::type<Component>{});
});
return hana::second(hana::at(map, found));
}
where index_if
is a trivial variant of the function presented in this answer that would work on an arbitrary predicate instead of a specific element. This functionality will be added to Hana when I get some free time (see related ticket).
回答2:
It looks like you are trying to make a map that can look up a single instance using different keys. Here is a snippet from an old implementation that I wrote. I modified it a bit, but it should convey the idea.
namespace detail {
// extractKeys - returns pairs of each element and itself
struct extract_keys_fn
{
template<typename TypesType>
constexpr auto operator()(TypesType s) const {
return decltype(hana::unpack(typename TypesType::type{},
hana::make_tuple
^hana::on^
hana::reverse_partial(hana::make_pair, s)
)){};
}
};
constexpr extract_keys_fn extract_keys{};
}//detail
template<typename ...Pair>
struct multi_map
{
// the keys must be `type<tuple<path...>>`
using Storage = decltype(hana::make_map(std::declval<Pair>()...));
// each key is a hana::tuple which contain the keys we
// want to use to lookup an element
using Lookup = decltype(hana::unpack(
hana::flatten(hana::unpack(hana::keys(std::declval<Storage>()),
hana::make_tuple ^hana::on^ detail::extract_keys)),
hana::make_map
));
constexpr multi_map()
: storage()
{ }
constexpr multi_map(Pair&&... p)
: storage(hana::make_map(std::forward<Pair>(p)...))
{ }
constexpr multi_map(Pair const&... p)
: storage(hana::make_map(p...))
{ }
constexpr multi_map(Pair&... p)
: storage(hana::make_map(p...))
{ }
template<typename T>
constexpr decltype(auto) operator[](T t) const&
{
return hana::at_key(storage, hana::at_key(Lookup{}, t));
}
template<typename T>
constexpr decltype(auto) operator[](T t) &
{
return hana::at_key(storage, hana::at_key(Lookup{}, t));
}
template<typename T>
constexpr decltype(auto) operator[](T t) &&
{
return hana::at_key(storage, hana::at_key(Lookup{}, t));
}
Storage storage;
};
The basics of what is happening above is that storage
is a hana::map
containing the instances that you need references to. Then Lookup
is a hana::map
that points each key to the key that is used in storage
(which is a tuple of all the keys that point to it). It's basically just a map to map, but with it you can get a reference to a single instance using any one of the keys.
来源:https://stackoverflow.com/questions/43089587/change-runtime-research-for-a-compile-time-one