I’ve recently gotten back into C++ after many years in C# and java land, and am loving where C++ has gone in my absence (since pre C++11, am now soaking up C++20!). The power of templates is really exciting to me. However, I quickly ran into something that seems impossible to deal with.
I’ve written a template driven event system which am really happy with.
struct imgui_event_listener
{
void onCreatedWindow(events::ImGui_CreatedWindow f)
{
}
}
dispatcher::connect(&listener, &imgui_event_listener::onCreatedWindow);
...
dispatcher::fire(events::ImGui_CreatedWindow{ window });
I love it, it requires no template parameters, can use arbitary structs for events. There’s one big snag though, and that is the near infinite and completely unknowable potential invisible versions of my event system, all using a few unordered_maps that need to go somewhere. They are currently static within methods.
Stuff like…
template <typename ListenerType, typename Type>
using FPtr = void(ListenerType::*)(Type);
template<typename ListenerType, typename Type>
using Tuple = eastl::tuple<ListenerType*, FPtr<ListenerType, Type>>;
template <typename ListenerType, typename Type>
using ListenerMap = eastl::unordered_map<uint32_t, Tuple<ListenerType, Type>>;
class EventSystem
{
template<typename ListenerType, typename Type>
ListenerMap<ListenerType, Type>& listenerMap()
{
static ListenerMap<ListenerType, Type> listenerMap;
return listenerMap;
}
}
I’m a relative noob these days so forgive if anything’s weird.
This SEEMS to be too convenient and satisfying to be recommended, I expected people to be shouted down for some reason but it seems this is the way it’s by necessity done most of the time.
But the game engine I’m working with has a memory manager, and doesn’t use or recommend the STL, having its own replacement that’s using the one open-sourced by EA for games, and has their own allocator that is to be used for it so it can track memory.
This tracks all memory usage and reports with an exception any leaks that occur at the time static objects are being removed.
Unfortunately despite the containers being static, the nodes of those unordered_maps are malloced from the memory managed heap. Expected usage would be to clear them on clean up which is what happens everywhere else.
Now, I suspect in actuality, these maps static instances will automatically be dealt with after the memory manager has already complained, however there’s really no real way around avoiding it complain, even if only in debug, an exception on quit and seeing memory addresses reported as leaks is annoying, and it makes me feel ashamed when it does! It also could obscure real issues. I’m sure I could brute force normal STL maps with allocations, but I really want to follow the road laid out by the developers of the engine and avoid any future headaches. So its more about convenience and vanity than anything else but I need a way around this without sacrificing my event system’s elegance of usage.
This lead me into the world of template meta-programming to try and collate all these template parameters into some mega tuple as the event connections are formed and the template instances are created at compile time, and looking into every conceivable pattern where I’d somehow be able to unwind all the template instances I’ve created to get at these in advance and be able to manually clean up, but it seems that template meta-programming requiring new ‘variable names’ for each instruction as typedefs or using means I can’t iteratively do this from what I can see, but honestly the field is so complicated and light on relevant examples I don’t know if I’m missing anything.
I’ve tried to think about where else these could be stored, but I just can’t fathom how it’d be any different.
I could also just manually maintain a list of all my events, but then I’d lose one of its cooler and more flexible features.
OR somewhere else I can store them where I’d be able to access them. It seems like a catch-22 situation where their very existence demands they be in some inaccessible parallel universe..
So yeah, I know there are ways I could solve this without asking for help but they are all either sacrificing a feature of the system or potentially causing future issues. Not sure I’ve ever asked a question here, but this has really stumped me and I can’t really find a similar issue from someone else, most are happy to use std library, have no memory manager, and putting static instances within functions is a super satisfyingly simple way to do it.
Thanks for your time! Forgive if a bit rambly but felt I needed to provide context. I know there’s not really much code to show but you get my point I hope!
>Solution :
Something along these lines perhaps:
std::vector<std::function<void()>> cleanup_registry;
class EventSystem
{
template<typename ListenerType, typename Type>
ListenerMap<ListenerType, Type>& listenerMap()
{
static ListenerMap<ListenerType, Type> listenerMap;
static int dummy = (
cleanup_registry.push_back([&]() { listenerMap.clear(); }),
0);
return listenerMap;
}
}
void cleanup() {
for (auto& cleaner : cleanup_registry) {
cleaner();
}
std::vector<std::function<void()>>{}.swap(cleanup_registry);
}
Call cleanup() right before your game engine terminates.
You can use similar type-erasure techniques to eliminate ListenerType from your maps, having just one map per event type. Something like
template <typename Event>
using ListenerMap = eastl::unordered_map<uint32_t, std::function<void(Event)>>;
Then dispatcher::connect (where actual listener type is available) would manufacture a callback lambda to be placed into the map.