Compile-time runtime change

I am trying to implement a shared ECS library in C ++ for learning. I've thought of many things to implement things, but I always have a problem. So if you could help me with this:

Let's say I have constexpr hana::tuple

components hana::type_c

, 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 is different for each component):

struct Storage {};

      

I want to associate each component or each group of components with its own type Storage

. So an easy way is to do something like this:

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 now the problem is runtime. If I initialize this tuple, but with real Storage and no longer type_c<Storage>

, I have to iterate over the tuple to find the Storage

one I need. All this at runtime is not? And this is really bad, my latest version had something like Component::getStorage()

, and it was free (but more restrictive).

So the question is, how do I manage to have some function getStorage<Component>()

that costs nothing at runtime?
Well, I don't mean anything, just return the repository reference.

EDIT: The only way I think so far is pretty simple (sounds like a good point).

Pseudo-code

struct LinkedStorage {

  hana::tuple<...>            storages;
  hana::tuple<hana::pair...>  index;
};

      

To 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)
);

      

Thus, I would have to find the index at compile time and just access the correct element at runtime. But I'm new to metaprogramming, so I think someone can do something much better.

+1


source to share


2 answers


First of all, you don't 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 a runtime, which makes sense):

// "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 function getStorage<Component>()

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 presented function in this answer , which will work on an arbitrary predicate instead of a specific element. This functionality will be added to Hana when I get some free time (see the associated ticket ).

+2


source


It looks like you are trying to create a map that can search for the same instance using different keys. Here is a snippet of an old implementation I wrote. I changed 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 happens above is what it storage

is hana::map

, containing the instances you need links to. Then Lookup

is hana::map

, which 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 match, but with it you can get a reference to one instance using any of the keys.

+1


source







All Articles