mirror of
https://github.com/VCMP-SqMod/SqMod.git
synced 2024-11-08 08:47:17 +01:00
247 lines
7.0 KiB
C++
247 lines
7.0 KiB
C++
/************************************************************************************
|
|
*
|
|
* D++, A Lightweight C++ library for Discord
|
|
*
|
|
* Copyright 2021 Craig Edwards and D++ contributors
|
|
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
************************************************************************************/
|
|
|
|
#pragma once
|
|
|
|
#include <dpp/export.h>
|
|
#include <string>
|
|
#include <map>
|
|
#include <variant>
|
|
#include <dpp/snowflake.h>
|
|
#include <dpp/misc-enum.h>
|
|
#include <dpp/json_fwd.h>
|
|
#include <algorithm>
|
|
#include <mutex>
|
|
#include <shared_mutex>
|
|
#include <cstring>
|
|
#include <dpp/coro.h>
|
|
|
|
using json = nlohmann::json;
|
|
|
|
namespace dpp {
|
|
|
|
/**
|
|
* @brief A returned event handle for an event which was attached
|
|
*/
|
|
typedef size_t event_handle;
|
|
|
|
/**
|
|
* @brief Handles routing of an event to multiple listeners.
|
|
*
|
|
* Multiple listeners may attach to the event_router_t by means of operator(). Passing a
|
|
* lambda into operator() attaches to the event.
|
|
*
|
|
* Dispatchers of the event may call the event_router_t::call() method to cause all listeners
|
|
* to receive the event.
|
|
*
|
|
* The event_router_t::empty() method will return true if there are no listeners attached
|
|
* to the event_router_t (this can be used to save time by not constructing objects that
|
|
* nobody will ever see).
|
|
*
|
|
* The event_router_t::detach() method removes an existing listener from the event,
|
|
* using the event_handle ID returned by operator().
|
|
*
|
|
* This class is used by the library to route all websocket events to listening code.
|
|
*
|
|
* Example:
|
|
*
|
|
* ```cpp
|
|
* // Declare an event that takes log_t as its parameter
|
|
* event_router_t<log_t> my_event;
|
|
*
|
|
* // Attach a listener to the event
|
|
* event_handle id = my_event([&](const log_t& cc) {
|
|
* std::cout << cc.message << "\n";
|
|
* });
|
|
*
|
|
* // Construct a log_t and call the event (listeners will receive the log_t object)
|
|
* log_t lt;
|
|
* lt.message = "foo";
|
|
* my_event.call(lt);
|
|
*
|
|
* // Detach from an event using the handle returned by operator()
|
|
* my_event.detach(id);
|
|
* ```
|
|
*
|
|
* @tparam T type of single parameter passed to event lambda derived from event_dispatch_t
|
|
*/
|
|
template<class T> class event_router_t {
|
|
private:
|
|
friend class cluster;
|
|
|
|
event_handle next_handle = 1;
|
|
|
|
/**
|
|
* @brief Thread safety mutex
|
|
*/
|
|
mutable std::shared_mutex lock;
|
|
/**
|
|
* @brief Container of event listeners keyed by handle,
|
|
* as handles are handed out sequentially they will always
|
|
* be called in they order they are bound to the event
|
|
* as std::map is an ordered container.
|
|
*/
|
|
std::map<event_handle, std::function<void(const T&)>> dispatch_container;
|
|
|
|
|
|
#ifdef DPP_CORO
|
|
/**
|
|
* @brief Container for event listeners (coroutines only)
|
|
*/
|
|
std::map<event_handle, std::function<dpp::task(T)>> coroutine_container;
|
|
#else
|
|
/**
|
|
* @brief Dummy container to keep the struct size same
|
|
*/
|
|
std::map<event_handle, std::function<void(T)>> dummy_container;
|
|
#endif
|
|
|
|
|
|
/**
|
|
* @brief A function to be called whenever the method is called, to check
|
|
* some condition that is required for this event to trigger correctly.
|
|
*/
|
|
std::function<void(const T&)> warning;
|
|
|
|
/**
|
|
* @brief Next handle to be given out by the event router
|
|
*/
|
|
|
|
protected:
|
|
|
|
/**
|
|
* @brief Set the warning callback object used to check that this
|
|
* event is capable of running properly
|
|
*
|
|
* @param warning_function A checking function to call
|
|
*/
|
|
void set_warning_callback(std::function<void(const T&)> warning_function) {
|
|
warning = warning_function;
|
|
}
|
|
|
|
public:
|
|
/**
|
|
* @brief Construct a new event_router_t object.
|
|
*/
|
|
event_router_t() = default;
|
|
|
|
/**
|
|
* @brief Call all attached listeners.
|
|
* Listeners may cancel, by calling the event.cancel method.
|
|
*
|
|
* @param event Class to pass as parameter to all listeners.
|
|
*/
|
|
void call(const T& event) const {
|
|
if (warning) {
|
|
warning(event);
|
|
}
|
|
std::shared_lock l(lock);
|
|
std::for_each(dispatch_container.begin(), dispatch_container.end(), [&](auto &ev) {
|
|
if (!event.is_cancelled()) {
|
|
ev.second(event);
|
|
}
|
|
});
|
|
#ifdef DPP_CORO
|
|
std::for_each(coroutine_container.begin(), coroutine_container.end(), [&](auto &ev) {
|
|
if (!event.is_cancelled()) {
|
|
ev.second(event);
|
|
}
|
|
});
|
|
#endif
|
|
};
|
|
|
|
/**
|
|
* @brief Returns true if the container of listeners is empty,
|
|
* i.e. there is nothing listening for this event right now.
|
|
*
|
|
* @return true if there are no listeners
|
|
* @return false if there are some listeners
|
|
*/
|
|
bool empty() const {
|
|
std::shared_lock l(lock);
|
|
return dispatch_container.empty();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns true if any listeners are attached.
|
|
*
|
|
* This is the boolean opposite of event_router_t::empty().
|
|
* @return true if listeners are attached
|
|
* @return false if no listeners are attached
|
|
*/
|
|
operator bool() const {
|
|
return !empty();
|
|
}
|
|
|
|
/**
|
|
* @brief Attach a lambda to the event, adding a listener.
|
|
* The lambda should follow the signature specified when declaring
|
|
* the event object and should take exactly one parameter derived
|
|
* from event_dispatch_t.
|
|
*
|
|
* @param func Function lambda to attach to event
|
|
* @return event_handle An event handle unique to this event, used to
|
|
* detach the listener from the event later if necessary.
|
|
*/
|
|
event_handle operator()(std::function<void(const T&)> func) {
|
|
return this->attach(func);
|
|
}
|
|
|
|
/**
|
|
* @brief Attach a lambda to the event, adding a listener.
|
|
* The lambda should follow the signature specified when declaring
|
|
* the event object and should take exactly one parameter derived
|
|
* from event_dispatch_t.
|
|
*
|
|
* @param func Function lambda to attach to event
|
|
* @return event_handle An event handle unique to this event, used to
|
|
* detach the listener from the event later if necessary.
|
|
*/
|
|
event_handle attach(std::function<void(const T&)> func) {
|
|
std::unique_lock l(lock);
|
|
event_handle h = next_handle++;
|
|
dispatch_container.emplace(h, func);
|
|
return h;
|
|
}
|
|
|
|
#ifdef DPP_CORO
|
|
event_handle co_attach(std::function<dpp::task(T)> func) {
|
|
std::unique_lock l(lock);
|
|
event_handle h = next_handle++;
|
|
coroutine_container.emplace(h, func);
|
|
return h;
|
|
}
|
|
#endif
|
|
/**
|
|
* @brief Detach a listener from the event using a previously obtained ID.
|
|
*
|
|
* @param handle An ID obtained from event_router_t::operator()
|
|
* @return true The event was successfully detached
|
|
* @return false The ID is invalid (possibly already detached, or does not exist)
|
|
*/
|
|
bool detach(const event_handle& handle) {
|
|
std::unique_lock l(lock);
|
|
return this->dispatch_container.erase(handle);
|
|
}
|
|
};
|
|
|
|
};
|