Zepha/src/util/EventEmitter.h

251 lines
7.5 KiB
C++

#pragma once
#include <list>
#include <functional>
#include "util/Types.h"
/** Supporting functions and structs for EventEmitter. */
namespace detail {
/**
* Stores a listener function and a validity, when the listener is unbound,
* it will have the `valid` member set to false instead of removing it from the listeners map directly,
* to avoid iterator offset errors when listeners are removed during an emit.
* The invalidatable listener will then be removed next time an event is emitted.
*
* @tparam E - The event type that the listener is for.
*/
template<typename E>
struct InvalidatableListener {
InvalidatableListener(const typename E::function_type& listener): listener(listener) {}
/** The listener function. */
typename E::function_type listener;
/** Whether or not the function is still valid. */
bool valid = true;
};
/**
* A nasty recursive compile time function that takes a tuple of event types and
* generates a tuple of lists of pairs of event indices and maps (oh god).
* This type is then initialized to store the listeners on the EventEmitter.
*
* getEventListTuple(std::tuple<Event<A, ...>, Event<B, ...>) =
* std::tuple<
* std::pair<usize, std::unordered_map<usize, InvalidatableListener<Event<A, ...>>>>,
* std::pair<usize, std::unordered_map<usize, InvalidatableListener<Event<B, ...>>>>
* >
*
* @tparam E - The events tuple to generate the list off of.
* @returns a tuple type as specified above.
*/
template<typename E, usize N = 0, typename... Args>
inline constexpr auto getEventListTuple() {
if constexpr (N < std::tuple_size_v<E>) {
return getEventListTuple<E, N + 1, Args..., std::pair<usize,
std::unordered_map<usize, InvalidatableListener<
typename std::tuple_element_t<N, E>>>>>();
}
else {
return std::tuple<Args...>{};
}
}
}
/**
* A reference to a listener added using `bind`.
* When all instances of it go out of scope, the listener
* that was bound to return this instance will be unbound from its emitter.
*/
class ListenerRef {
public:
ListenerRef() = default;
explicit ListenerRef(std::function<void()> unbind): unbind(make_shared<std::function<void()>>(unbind)) {};
~ListenerRef() { if (unbind && unbind.unique()) unbind->operator()(); }
private:
sptr<std::function<void()>> unbind = nullptr;
};
/**
* Specifies a single event of an EventEmitter, with an id and parameter list.
* Provided to a specialization of the EventEmitter class to specify its events.
*
* @tparam T - The integral identifier for the event.
* @tparam Args - The arguments that will be provided to listener functions.
*/
template <let T, typename... Args>
struct Event {
/** The integral identifier of the event as a u32. */
static constexpr u32 id_type = (u32)T;
/** A tuple of the event parameters. */
typedef std::tuple<Args...> args_type;
/** The type of the event listeners for this event. */
typedef std::function<void(Args...)> function_type;
};
/**
* Manages listeners for a set of events specified at compile time,
* allowing event listeners to be bound and events to be emitted at runtime.
* Each event may have its own parameter types. Objects that should emit events should
* derive from this class publicly, and make available an Event typedef that
* corresponds to the valid event identifiers provided to this class.
*
* @tparam E - The list of Events this listener should contain.
*/
template <typename... E>
class EventEmitter {
/** A tuple of the provided event types. */
typedef std::tuple<E...> event_types;
/** The type of the event list, which contains event listeners. */
typedef decltype(detail::getEventListTuple<event_types>()) event_list;
public:
/**
* Binds a listener to the specified event,
* which will be unbound when the ref goes out of scope.
*
* @tparam T - The event identifier to bind to.
* @param listener - The event listener, its parameters must match the event parameters.
* @returns a ListenerRef corresponding to the listener, which will unbind the listener when it goes out of scope.
*/
template <let T, typename L>
ListenerRef bind(const L& listener) {
usize ind = bindRaw<T>(listener);
return ListenerRef {[ind, this]() { unbind<T>(ind); }};
}
/**
* Binds a listener to the specified event, returning the raw listener id,
* which must be used to manually unbind the event later.
* Usually, `bind` should be called instead.
*
* @tparam T - The event identifier to bind to.
* @param listener - The event listener, its parameters must match the event parameters.
* @returns the listener's id, to be used in `unbind`.
*/
template <let T, typename L>
usize bindRaw(const L& listener) {
return addEventListener<T>(listener);
}
/**
* Unbinds the listener matching the ind from the specified event.
* Usually this won't need to be called, as ListenerRef handles it automatically.
*
* @tparam T - The event identifier to unbind the listener from.
* @param ind - The id of the bound listener.
*/
template <let T>
void unbind(usize ind) {
return removeEventListener<T>(ind);
}
protected:
/**
* Emits the specified event with the arguments provided.
* The arguments must match the Event specification.
*
* @tparam T - The event identifier to emit.
* @param args - The arguments to pass to the listener functions.
*/
template <let T, typename... Args>
void emit(const Args&... args) {
invokeEventListeners<T>(args...);
}
private:
/**
* Recursively finds the right list to add the specified
* listener to at compile time, adds it, and returns its index.
*
* @tparam T - The event identifier to bind to.
* @param listener - The listener function.
* @returns the index of the bound event listener.
*/
template<let T, usize N = 0, typename L>
inline usize addEventListener(const L& listener) {
if constexpr (std::tuple_element_t<N, event_types>::id_type == static_cast<u32>(T)) {
let& list = std::get<N>(eventList);
usize ind = list.first++;
list.second.emplace(ind, detail::InvalidatableListener<
std::tuple_element_t<N, event_types>> { listener });
return ind;
}
else {
return addEventListener<T, N + 1>(listener);
}
}
/**
* Recursively finds the right list to remove the specified
* listener from at compile time, and marks it as invaid.
*
* @tparam T - The event identifier to unbind from.
* @param listener - The listener function.
*/
template<let T, usize N = 0>
inline void removeEventListener(const usize& ind) {
if constexpr (std::tuple_element_t<N, event_types>::id_type == static_cast<u32>(T)) {
let& list = std::get<N>(eventList).second;
list.at(ind).valid = false;
return;
}
else {
return removeEventListener<T, N + 1>(ind);
}
}
/**
* Recursively finds the right list to invoke the listeners of,
* removes all invalid listeners, and invokes the remaining ones.
*
* @tparam T - The event identifier to invoke.
* @param args - The arguments to pass to the listener functions.
*/
template<let T, usize N = 0, typename... Args>
inline void invokeEventListeners(const Args&... args) {
if constexpr (std::tuple_element_t<N, event_types>::id_type == static_cast<u32>(T)) {
let& list = std::get<N>(eventList).second;
for (let it = list.begin(); it != list.end();) {
if (it->second.valid) {
it->second.listener(args...);
it++;
}
else {
it = list.erase(it);
}
}
return;
}
else {
return invokeEventListeners<T, N + 1>(args...);
}
}
/** The list of event listeners. */
event_list eventList {};
};