GUI2: finished refactoring out usage of boost::mpl

dispatcher::has_event called the private find() function, which after a whole bunch of
jumping around essentially checked that the signal queue for the given event was not
empty. This was a run-time op, yet the code was set up using SFINAE in order to for
has_handler::oper() to call the event_signal function corresponding to the event's queue
(ie, the mouse queue for a mouse-type event).

Since this code was written before the era of constexpr, event type validation was done
using boost::mpl, which, in this case, resulted in a monstrous amalgamation of build-time
template specialization for a run-time check. I'm not certain, but I believe it might
have resulted in an specialization of find() (or at least, implementation::find()) for
every single event type (each member of the ui_event enum).

This converts the code to a purely run-time check and throws out all the template stuff.
It also removes the relevant event_signal overload dealing with events in a set. The
version dealing with function types is preserved as it's used in the fire_event()
implementation and is a fairly standard usecase of SFINAE.

The has_handler class has also been converted to a static function since it's no longer
needed for template specializations in find().

And finally, is_raw_event had to be renamed to is_raw_event_event to allow simple name
completion in IMPLEMENT_RUNTIME_EVENT_SIGNAL_CHECK. I could have also renamed the raw event
queue to signal_raw_queue but I figured keeping the name as signal_raw_event_queue made
its purpose clearer.
This commit is contained in:
Charles Dang 2018-04-17 21:52:28 +11:00
parent dd4b525c40
commit 0b45363c95
5 changed files with 103 additions and 308 deletions

View file

@ -55,42 +55,12 @@ void dispatcher::connect()
bool dispatcher::has_event(const ui_event event, const event_queue_type event_type) bool dispatcher::has_event(const ui_event event, const event_queue_type event_type)
{ {
#if 0 #if 0
// Debug code to test whether the event is in the right queue. const bool res = dispatcher_implementation::has_handler(*this, event_type, event);
std::cerr << "Event '" << event std::cerr << "Event '" << event << " '" << (res ? "found" : "not found") << "in queue\n";
<< "' event " return res;
<< find<set_event>(event, dispatcher_implementation #else
::has_handler(event_type, *this)) return dispatcher_implementation::has_handler(*this, event_type, event);
<< " mouse "
<< find<set_event_mouse>(event, dispatcher_implementation
::has_handler(event_type, *this))
<< " keyboard "
<< find<set_event_keyboard>(event, dispatcher_implementation
::has_handler(event_type, *this))
<< " notification "
<< find<set_event_notification>(event, dispatcher_implementation
::has_handler(event_type, *this))
<< " message "
<< find<set_event_message>(event, dispatcher_implementation
::has_handler(event_type, *this))
<< ".\n";
#endif #endif
return find<set_event>(
event, dispatcher_implementation::has_handler(event_type, *this))
|| find<set_event_mouse>(
event, dispatcher_implementation::has_handler(event_type, *this))
|| find<set_event_keyboard>(
event, dispatcher_implementation::has_handler(event_type, *this))
|| find<set_event_text_input>(
event, dispatcher_implementation::has_handler(event_type, *this))
|| find<set_event_touch>(
event, dispatcher_implementation::has_handler(event_type, *this))
|| find<set_event_notification>(
event, dispatcher_implementation::has_handler(event_type, *this))
|| find<set_event_message>(
event, dispatcher_implementation::has_handler(event_type, *this))
|| find<set_event_raw_event>(
event, dispatcher_implementation::has_handler(event_type, *this));
} }
bool dispatcher::fire(const ui_event event, widget& target) bool dispatcher::fire(const ui_event event, widget& target)
@ -132,7 +102,7 @@ bool dispatcher::fire(const ui_event event,
bool dispatcher::fire(const ui_event event, widget& target, const SDL_Event& sdlevent) bool dispatcher::fire(const ui_event event, widget& target, const SDL_Event& sdlevent)
{ {
assert(is_raw_event(event)); assert(is_raw_event_event(event));
return fire_event<signal_raw_event_function>(event, this, &target, sdlevent); return fire_event<signal_raw_event_function>(event, this, &target, sdlevent);
} }

View file

@ -147,7 +147,7 @@ constexpr bool is_message_event(const ui_event event)
* *
* This version is for callbacks of raw events. * This version is for callbacks of raw events.
*/ */
constexpr bool is_raw_event(const ui_event event) constexpr bool is_raw_event_event(const ui_event event)
{ {
return event == SDL_RAW_EVENT; return event == SDL_RAW_EVENT;
} }
@ -681,7 +681,7 @@ public:
* @param position The position to place the callback. * @param position The position to place the callback.
*/ */
template<ui_event E> template<ui_event E>
std::enable_if_t<is_raw_event(E)> std::enable_if_t<is_raw_event_event(E)>
connect_signal(const signal_raw_event_function& signal, const queue_position position = back_child) connect_signal(const signal_raw_event_function& signal, const queue_position position = back_child)
{ {
signal_raw_event_queue_.connect_signal(E, position, signal); signal_raw_event_queue_.connect_signal(E, position, signal);
@ -698,7 +698,7 @@ public:
* was added in front or back.) * was added in front or back.)
*/ */
template<ui_event E> template<ui_event E>
std::enable_if_t<is_raw_event(E)> std::enable_if_t<is_raw_event_event(E)>
disconnect_signal(const signal_raw_event_function& signal, const queue_position position = back_child) disconnect_signal(const signal_raw_event_function& signal, const queue_position position = back_child)
{ {
signal_raw_event_queue_.disconnect_signal(E, position, signal); signal_raw_event_queue_.disconnect_signal(E, position, signal);
@ -803,6 +803,32 @@ public:
std::list<T> pre_child; std::list<T> pre_child;
std::list<T> child; std::list<T> child;
std::list<T> post_child; std::list<T> post_child;
/**
* Checks whether the queue of a given type is empty.
*
* @param queue_type The queue to check. This may be one or more types
* OR'd together (event_queue_type is bit-unique).
*
* @returns True if ALL the matching queues are empty, or false
* if any of the matching queues is NOT empty.
*/
bool empty(const dispatcher::event_queue_type queue_type) const
{
if((queue_type & dispatcher::pre) && !pre_child.empty()) {
return false;
}
if((queue_type & dispatcher::child) && !child.empty()) {
return false;
}
if((queue_type & dispatcher::post) && !post_child.empty()) {
return false;
}
return true;
}
}; };
/** Helper struct to generate the various event queues. */ /** Helper struct to generate the various event queues. */

View file

@ -20,7 +20,6 @@
#include <SDL_events.h> #include <SDL_events.h>
#include <boost/mpl/for_each.hpp>
#include <boost/range/adaptor/reversed.hpp> #include <boost/range/adaptor/reversed.hpp>
namespace gui2 namespace gui2
@ -65,27 +64,6 @@ struct dispatcher_implementation
{ \ { \
return dispatcher.QUEUE.queue[event]; \ return dispatcher.QUEUE.queue[event]; \
} \ } \
\
/** \
* Returns the signal structure for a key in SET. \
* \
* There are several functions that only overload the return value, in \
* order to do so they use SFINAE. \
* \
* @tparam K A key in set_event. \
* @param dispatcher The dispatcher whose signal queue is used. \
* @param event The event to get the signal for. \
* \
* @returns The signal of the type \
* dispatcher::signal_type<FUNCTION> \
*/ \
template<typename K> \
static std::enable_if_t<boost::mpl::has_key<SET, K>::value, dispatcher::signal_type<FUNCTION>>& \
event_signal(dispatcher& dispatcher, const ui_event event) \
{ \
return dispatcher.QUEUE.queue[event]; \
}
IMPLEMENT_EVENT_SIGNAL(set_event, signal_function, signal_queue_) IMPLEMENT_EVENT_SIGNAL(set_event, signal_function, signal_queue_)
@ -114,137 +92,44 @@ struct dispatcher_implementation
#undef IMPLEMENT_EVENT_SIGNAL_WRAPPER #undef IMPLEMENT_EVENT_SIGNAL_WRAPPER
#undef IMPLEMENT_EVENT_SIGNAL #undef IMPLEMENT_EVENT_SIGNAL
#define IMPLEMENT_RUNTIME_EVENT_SIGNAL_CHECK(TYPE) \
else if(is_##TYPE##_event(event)) { \
return queue_check(dispatcher.signal_##TYPE##_queue_); \
}
/** /**
* A helper class to find out whether dispatcher has an handler for a * A helper to test whether dispatcher has an handler for a certain event.
* certain event. *
* @param dispatcher The dispatcher whose signal queue is used.
* @param queue_type The type of event to look for.
* @param event The event to get the signal for.
*
* @returns Whether or not the handler is found.
*/ */
class has_handler static bool has_handler(dispatcher& dispatcher, const dispatcher::event_queue_type queue_type, ui_event event)
{ {
public: const auto queue_check = [&](auto& queue_set) {
/** return !queue_set.queue[event].empty(queue_type);
* Constructor. };
*
* @param event_type The type of event to look for. if(is_general_event(event)) {
* @param dispatcher The dispatcher whose signal queue is used. return queue_check(dispatcher.signal_queue_);
*/
has_handler(const dispatcher::event_queue_type event_type, dispatcher& dispatcher)
: event_type_(event_type), dispatcher_(dispatcher)
{
} }
/** IMPLEMENT_RUNTIME_EVENT_SIGNAL_CHECK(mouse)
* Tests whether a handler for an event is available. IMPLEMENT_RUNTIME_EVENT_SIGNAL_CHECK(keyboard)
* IMPLEMENT_RUNTIME_EVENT_SIGNAL_CHECK(touch)
* It tests for both the event and the event_type send in the IMPLEMENT_RUNTIME_EVENT_SIGNAL_CHECK(notification)
* constructor. IMPLEMENT_RUNTIME_EVENT_SIGNAL_CHECK(message)
* IMPLEMENT_RUNTIME_EVENT_SIGNAL_CHECK(raw_event)
* @tparam T A key from an event set used to instantiate IMPLEMENT_RUNTIME_EVENT_SIGNAL_CHECK(text_input)
* the proper @p event_signal function.
* @param event The event to get the signal for.
*
* @returns Whether or not the handler is found.
*/
// not called operator() to work around a problem in MSVC
// (known to affect all versions up to 2015)
template<typename T>
bool oper(ui_event event)
{
if((event_type_ & dispatcher::pre)
&& !event_signal<T>(dispatcher_, event).pre_child.empty()) {
return true;
}
if((event_type_ & dispatcher::child)
&& !event_signal<T>(dispatcher_, event).child.empty()) {
return true;
}
if((event_type_ & dispatcher::post)
&& !event_signal<T>(dispatcher_, event).post_child.empty()) {
return true;
}
return false;
}
private:
dispatcher::event_queue_type event_type_;
dispatcher& dispatcher_;
};
};
/** Contains the implementation details of the find function. */
namespace implementation
{
/** Specialized class when itor == end */
template<bool done = true>
struct find
{
template<typename itor, typename end, typename E, typename F>
static bool execute(itor*, end*, E, F)
{
return false; return false;
} }
#undef IMPLEMENT_RUNTIME_EVENT_SIGNAL_CHECK
}; };
/** Specialized class when itor != end */
template<>
struct find<false>
{
template<typename itor, typename end, typename E, typename F>
static bool execute(itor*, end*, E event, F functor)
{
typedef typename boost::mpl::deref<itor>::type item;
typedef typename boost::mpl::apply1<boost::mpl::identity<>, item>::type arg;
boost::value_initialized<arg> x;
if(boost::get(x) == event) {
return functor.template oper<item>(event);
} else {
typedef typename boost::mpl::next<itor>::type itor_t;
return find<std::is_same<itor_t, end>::value>::execute(
static_cast<itor_t*>(nullptr),
static_cast<end*>(nullptr),
event,
functor);
}
}
};
} // namespace implementation
/**
* Tests whether an event handler is available.
*
* The code is based on boost::mpl_for_each, which doesn't allow to call a
* template function with the dereferred iterator as template parameter.
*
* The function first tries to match whether the value in the sequence matches
* event, once that matched it will execute the functor with the key found as
* template parameter and the event as parameter.
*
* @tparam sequence The sequence to test upon.
* @tparam E The value type of the item in the sequence
* @tparam F Type of the functor.
*
* @param event The event to look for.
* @param functor The predicate which should is executed if the
* event is matched.
*
* @returns Whether or not the function found a result.
*/
template<typename sequence, typename E, typename F>
inline bool find(E event, F functor)
{
typedef typename boost::mpl::begin<sequence>::type begin;
typedef typename boost::mpl::end<sequence>::type end;
return implementation::find<std::is_same<begin, end>::value>::execute(
static_cast<begin*>(nullptr), static_cast<end*>(nullptr), event, functor);
}
namespace implementation namespace implementation
{ {

View file

@ -16,6 +16,7 @@
#include "gui/core/event/handler.hpp" #include "gui/core/event/handler.hpp"
#include "events.hpp"
#include "gui/core/event/dispatcher.hpp" #include "gui/core/event/dispatcher.hpp"
#include "gui/core/timer.hpp" #include "gui/core/timer.hpp"
#include "gui/core/log.hpp" #include "gui/core/log.hpp"
@ -33,6 +34,8 @@
#include <SDL.h> #include <SDL.h>
#include <memory>
/** /**
* @todo The items below are not implemented yet. * @todo The items below are not implemented yet.
* *
@ -49,9 +52,15 @@ namespace gui2
namespace event namespace event
{ {
static bool is_modal_window(dispatcher* dispatcher)
{
window* w = dynamic_cast<window*>(dispatcher);
return w && w->dialog() != nullptr;
}
/***** Static data. *****/ /***** Static data. *****/
static std::unique_ptr<class sdl_event_handler> handler_ = nullptr; static std::unique_ptr<class sdl_event_handler> handler_ = nullptr;
static std::unique_ptr<events::event_context> event_context_ = nullptr;
// TODO: note sure if this is useful for something so I'm leaving it here. // TODO: note sure if this is useful for something so I'm leaving it here.
#if 0 #if 0
@ -433,13 +442,22 @@ void sdl_event_handler::handle_window_event(const SDL_Event& event)
void sdl_event_handler::connect(dispatcher* dispatcher) void sdl_event_handler::connect(dispatcher* dispatcher)
{ {
assert(std::find(dispatchers_.begin(), dispatchers_.end(), dispatcher) assert(std::find(dispatchers_.begin(), dispatchers_.end(), dispatcher) == dispatchers_.end());
== dispatchers_.end());
if(dispatchers_.empty()) { if(event_context_ == nullptr && is_modal_window(dispatcher)) {
std::cerr << "joining modal event context" << std::endl;
event_context_.reset(new events::event_context());
join(); join();
} }
if(!has_joined()) {
join();
}
//if(dispatchers_.empty()) {
// join();
//}
dispatchers_.push_back(dispatcher); dispatchers_.push_back(dispatcher);
} }
@ -455,6 +473,7 @@ void sdl_event_handler::disconnect(dispatcher* disp)
if(disp == mouse_focus) { if(disp == mouse_focus) {
mouse_focus = nullptr; mouse_focus = nullptr;
} }
if(disp == keyboard_focus_) { if(disp == keyboard_focus_) {
keyboard_focus_ = nullptr; keyboard_focus_ = nullptr;
} }
@ -462,11 +481,27 @@ void sdl_event_handler::disconnect(dispatcher* disp)
activate(); activate();
/***** Validate post conditions. *****/ /***** Validate post conditions. *****/
assert(std::find(dispatchers_.begin(), dispatchers_.end(), disp) assert(std::find(dispatchers_.begin(), dispatchers_.end(), disp) == dispatchers_.end());
== dispatchers_.end());
// If this dispatcher is a modal window...
if(event_context_ && is_modal_window(disp)) {
itor = std::find_if(dispatchers_.begin(), dispatchers_.end(), is_modal_window);
// ... and there are no other modal window in the stack, close the modal contex.
if(itor == dispatchers_.end()) {
std::cerr << "Leaving modal event context" << std::endl;
leave();
event_context_.reset(nullptr);
}
}
if(!has_joined()) {
join();
}
if(dispatchers_.empty()) { if(dispatchers_.empty()) {
leave(); leave();
event_context_.reset(nullptr);
} }
} }

View file

@ -14,14 +14,6 @@
#pragma once #pragma once
#ifdef BOOST_MPL_LIMIT_SET_SIZE
#undef BOOST_MPL_LIMIT_SET_SIZE
#endif
#define BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS
#define BOOST_MPL_LIMIT_SET_SIZE 30
#include <boost/mpl/set.hpp>
#include <iosfwd> #include <iosfwd>
#include <vector> #include <vector>
@ -120,119 +112,6 @@ enum ui_event {
SDL_RAW_EVENT /**< Raw SDL event. */ SDL_RAW_EVENT /**< Raw SDL event. */
}; };
/**
* Helper for catching use error of dispatcher::connect_signal.
*
* This helper is needed as a user can't supply the wrong kind of callback
* functions to dispatcher::connect_signal. If a wrong callback would be send
* it will never get called.
*
* This version is for callbacks without extra parameters.
* NOTE some mouse functions like MOUSE_ENTER don't send the mouse coordinates
* to the callback function so they are also in this category.
*/
typedef boost::mpl::set<boost::mpl::int_<DRAW>,
boost::mpl::int_<CLOSE_WINDOW>,
boost::mpl::int_<MOUSE_ENTER>,
boost::mpl::int_<MOUSE_LEAVE>,
boost::mpl::int_<LEFT_BUTTON_DOWN>,
boost::mpl::int_<LEFT_BUTTON_UP>,
boost::mpl::int_<LEFT_BUTTON_CLICK>,
boost::mpl::int_<LEFT_BUTTON_DOUBLE_CLICK>,
boost::mpl::int_<MIDDLE_BUTTON_DOWN>,
boost::mpl::int_<MIDDLE_BUTTON_UP>,
boost::mpl::int_<MIDDLE_BUTTON_CLICK>,
boost::mpl::int_<MIDDLE_BUTTON_DOUBLE_CLICK>,
boost::mpl::int_<RIGHT_BUTTON_DOWN>,
boost::mpl::int_<RIGHT_BUTTON_UP>,
boost::mpl::int_<RIGHT_BUTTON_CLICK>,
boost::mpl::int_<RIGHT_BUTTON_DOUBLE_CLICK>>
set_event;
/**
* Helper for catching use error of dispatcher::connect_signal.
*
* This version is for callbacks with a coordinate as extra parameter.
*/
typedef boost::mpl::set<boost::mpl::int_<SDL_VIDEO_RESIZE>,
boost::mpl::int_<SDL_MOUSE_MOTION>,
boost::mpl::int_<MOUSE_MOTION>,
boost::mpl::int_<SDL_LEFT_BUTTON_DOWN>,
boost::mpl::int_<SDL_LEFT_BUTTON_UP>,
boost::mpl::int_<SDL_MIDDLE_BUTTON_DOWN>,
boost::mpl::int_<SDL_MIDDLE_BUTTON_UP>,
boost::mpl::int_<SDL_RIGHT_BUTTON_DOWN>,
boost::mpl::int_<SDL_RIGHT_BUTTON_UP>,
boost::mpl::int_<SHOW_TOOLTIP>,
boost::mpl::int_<SHOW_HELPTIP>,
boost::mpl::int_<SDL_WHEEL_UP>,
boost::mpl::int_<SDL_WHEEL_DOWN>,
boost::mpl::int_<SDL_WHEEL_LEFT>,
boost::mpl::int_<SDL_WHEEL_RIGHT>> set_event_mouse;
/**
* Helper for catching use error of dispatcher::connect_signal.
*
* This version is for callbacks with the keyboard values (these haven't been
* determined yet).
*/
typedef boost::mpl::set<boost::mpl::int_<SDL_KEY_DOWN>> set_event_keyboard;
/**
* Helper for catching use error of dispatcher::connect_signal.
*
* This version is for callbacks of touch events.
*/
typedef boost::mpl::set<boost::mpl::int_<SDL_TOUCH_MOTION>,
boost::mpl::int_<SDL_TOUCH_UP>,
boost::mpl::int_<SDL_TOUCH_DOWN>>
set_event_touch;
/**
* Helper for catching use error of dispatcher::connect_signal.
*
* This version is for callbacks with a sender aka notification messages. Like
*the
* ones in set_event it has no extra parameters, but this version is only
* send to the target and not using the pre and post queue.
*/
typedef boost::mpl::set<boost::mpl::int_<NOTIFY_REMOVAL>,
boost::mpl::int_<NOTIFY_MODIFIED>,
boost::mpl::int_<RECEIVE_KEYBOARD_FOCUS>,
boost::mpl::int_<LOSE_KEYBOARD_FOCUS>,
boost::mpl::int_<NOTIFY_REMOVE_TOOLTIP>,
boost::mpl::int_<SDL_ACTIVATE>>
set_event_notification;
/**
* Helper for catching use error of dispatcher::connect_signal.
*
* This version is for callbacks with a sender aka notification messages.
* Unlike the notifications this message is send through the chain. The event
* is send from a widget all the way up to the window, who always is the
* receiver of the message (unless somebody grabbed it before).
*/
typedef boost::mpl::set<boost::mpl::int_<MESSAGE_SHOW_TOOLTIP>,
boost::mpl::int_<MESSAGE_SHOW_HELPTIP>,
boost::mpl::int_<REQUEST_PLACEMENT>>
set_event_message;
/**
* Helper for catching use error of dispatcher::connect_signal.
*
* This version is for callbacks of raw events.
*/
typedef boost::mpl::set<boost::mpl::int_<SDL_RAW_EVENT>> set_event_raw_event;
/**
* Helper for catching use error of dispatcher::connect_signal.
*
* This version is for callbacks of text input events.
*/
typedef boost::mpl::set<boost::mpl::int_<SDL_TEXT_INPUT>,
boost::mpl::int_<SDL_TEXT_EDITING>>
set_event_text_input;
/** /**
* Connects a dispatcher to the event handler. * Connects a dispatcher to the event handler.
* *