I'm trying to create a flexible multi-track animation system using only C 11.
So far I have this code:
#include <vector>
#include <array>
#include <unordered_map>
#include <map>
template<typename T>
struct AnimationTrack
{
using map_type = std::map<float, T>;
map_type keyframes;
AnimationTrack(std::initializer_list<typename map_type::value_type>&& keyframes) :
keyframes(std::move(keyframes)) {}
};
template<typename T>
struct AnimationTrackView
{
AnimationTrack<T>* track;
AnimationTrackView() :
track(nullptr) {}
AnimationTrackView(AnimationTrack<T>* track) :
track(track) {}
};
template<typename... TrackTs>
class Animation
{
public:
Animation(AnimationTrack<TrackTs>&... tracks)
{
StoreViews(tracks...);
}
template<std::size_t Index>
auto& GetTrack()
{
return GetStorage<std::tuple_element<Index, std::tuple<TrackTs...>>::type>()[this].track;
}
private:
template<typename T>
static auto& GetStorage()
{
static std::unordered_map<Animation*, AnimationTrackView<T>> storage;
return storage;
}
template<typename T, typename... Ts>
void StoreViews(AnimationTrack<T>& current, AnimationTrack<Ts>&... rest)
{
auto& storage = GetStorage<T>();
storage.emplace(this, std::addressof(current));
using expander = int[];
(void)expander {
0, (void(GetStorage<Ts>().emplace(this, std::addressof(rest))), 0)...
};
}
};
int main()
{
AnimationTrack<float> test1(
{
{ 0.2f, 1.0f },
{ 0.5f, 2.0f },
{ 0.9f, 3.0f }
});
AnimationTrack<int> test2(
{
{ 0.2f, 1 },
{ 0.5f, 2 },
{ 0.9f, 3 }
});
Animation<float, int> anim(test1, test2);
auto track0 = anim.GetTrack<0>();
auto track1 = anim.GetTrack<1>();
}
This example works fine in this use case, but what if I wanted to keep two tracks of the same type? With my current code both tracks would grab from the same static storage when calling GetTrack
, but the problem is I can't figure out a way to make it work with repeats of the same type. Any suggestions?
CodePudding user response:
This is probably simpler than you anticipated. Just add a 2nd template parameter to getStorage()
, Index
, and simply forward it from getTrack()
:
template<std::size_t Index>
auto& GetTrack()
{
return GetStorage<std::tuple_element<Index, std::tuple<TrackTs...>>::type,
Index>()[this].track;
}
template<typename T, std::size_t Index>
static auto& GetStorage()
{
static std::unordered_map<Animation*, AnimationTrackView<T>> storage;
return storage;
}
Let's just say, for a lack of imagination, that TrackTs...
is <int, float, int>
.
GetTrack<0>
and GetTrack<2>
will now call GetStorage<int, 0>
and GetStorage<int, 2>
. They will be distinct template instances, each with its own distinct static
storage instance.
It is not necessary to, somehow, in this instance, to renumber the additional template parameter so this becomes <int, 0>
, and <int, 1>
. This doesn't accomplish anything practical. Hijacking the original index into the tuple will accomplish, effectively, the same thing.