135 lines
6 KiB
C++
135 lines
6 KiB
C++
/*
|
|
UI-DISPATCHER.hpp - dispatch invocations into the UI event thread
|
|
|
|
Copyright (C) Lumiera.org
|
|
2017, Hermann Vosseler <Ichthyostega@web.de>
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of
|
|
the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
*/
|
|
|
|
|
|
/** @file ui-dispatcher.hpp
|
|
** Allow dispatch of self-contained code blocks (lambdas) into the main UI event thread.
|
|
** GTK is _not threadsafe by design_ -- thus it is mandatory to dispatch any asynchronous invocations
|
|
** from external facilities in a controlled way into the main event loop. Unfortunately, this becomes
|
|
** a tricky undertaking when these external invocations need to pass argument data. This helper serves
|
|
** to accommodate such problems, relying on the automatic (heap based) argument storage of std::function.
|
|
** Client code provides the actual invocation in the form of _completely closed lambdas._
|
|
**
|
|
** @warning these lambdas will be stored in a synchronised queue and invoked out of the original
|
|
** call stack. It is the client's responsibility to ensure that all bindings in the closure
|
|
** are either *by value*, or *by smart-ptr*, or alternatively to ensure the lifecycle of any
|
|
** referred entity exceeds the lifespan of the UI-Loop. Since the shutdown-order of Lumiera's
|
|
** subsystems is not deterministic, this rules out passing references to anything tied to some
|
|
** subsystem lifecycle. Referring to a static singleton is acceptable though.
|
|
**
|
|
** # implementation considerations
|
|
**
|
|
** Basically the implementation relies on the [standard mechanism][Gtkmm-tutorial] for multithreaded
|
|
** UI applications. But on top we use our own [dispatcher queue](\ref lib::CallQueue) to allow passing
|
|
** arbitrary argument data, based on the argument storage of `std::function`. Which in the end effectively
|
|
** involves two disjoint thread collaboration mechanisms:
|
|
** - the caller creates a closure of the operation to be invoked, binding all arguments by value
|
|
** - this closure is wrapped into a `std::function` instance
|
|
** - which in turn is added into the dispatcher queue. Depending on the implementation,
|
|
** this might incur explicit locking
|
|
** - after successfully enqueuing the closure, the GTK event thread is signalled through
|
|
** the [Glib-Dispatcher], which actually messages through an OS-pipe (kernel based IO)
|
|
** - the Dispatcher need to be created within the UI event thread (which is the case, since
|
|
** all of Lumiera's UI top-level context is created in the thread dedicated to run GTK)
|
|
** - relying on internal GLib / GIO ``magic'', the dispatcher hooks into the respective GLib
|
|
** ``main context'' to ensure this signalling is picked up from the event thread, which...
|
|
** - ...finally leads to invocation of the Dispatcher's signal from within the event loop
|
|
**
|
|
** This hybrid approach is rather simple to establish, but creates additional complexities at runtime.
|
|
** More specifically, we have to pay the penalty of chaining the overhead and the inherent limitations
|
|
** of two thread signalling mechanisms (in our dispatcher queue and within kernel based IO).
|
|
** It would be conceivable to implement all of the hand-over mechanism solely within our framework,
|
|
** yet unfortunately there seems to be no easily accessible and thus ``official'' way to hook into
|
|
** the event processing, at least without digging deep into GLib internals.
|
|
**
|
|
** @note as detailed in the documentation for [Glib-Dispatcher], all instances built within a
|
|
** given receiver thread (here the UI event loop thread) will _share a single pipe for signalling._
|
|
** Under heavy load this queue might fill up and block the sender on dispatch.
|
|
**
|
|
** [Glib-Dispatcher]: https://developer.gnome.org/glibmm/2.42/classGlib_1_1Dispatcher.html
|
|
** [GTKmm-tutorial]: https://developer.gnome.org/gtkmm-tutorial/3.12/sec-using-glib-dispatcher.html.en
|
|
** @see NotificationService
|
|
** @see CallQueue_test
|
|
*/
|
|
|
|
|
|
#ifndef GUI_CTRL_UI_DISPATCHER_H
|
|
#define GUI_CTRL_UI_DISPATCHER_H
|
|
|
|
#include "gui/gtk-base.hpp"
|
|
#include "lib/call-queue.hpp"
|
|
|
|
#include <boost/noncopyable.hpp>
|
|
#include <utility>
|
|
|
|
|
|
namespace gui {
|
|
namespace ctrl {
|
|
|
|
using std::move;
|
|
|
|
|
|
|
|
/**
|
|
* Helper to dispatch code blocks into the UI event thread for execution.
|
|
* Implementation of the actual dispatch is based on `Glib::Dispatcher`
|
|
* @warning any UiDispatcher instance must be created such as to ensure
|
|
* it outlives the GTK event loop
|
|
*/
|
|
class UiDispatcher
|
|
: boost::noncopyable
|
|
{
|
|
lib::CallQueue queue_;
|
|
Glib::Dispatcher dispatcher_;
|
|
|
|
using Operation = lib::CallQueue::Operation;
|
|
|
|
public:
|
|
UiDispatcher()
|
|
: queue_{}
|
|
, dispatcher_{}
|
|
{
|
|
dispatcher_.connect(
|
|
[this]() {
|
|
queue_.invoke(); /////////////////////TICKET #1098 : ensure no exception escapes from here!!
|
|
});
|
|
}
|
|
|
|
/**
|
|
* move the given operation into our private dispatcher queue and
|
|
* then schedule dequeuing and invocation into the UI event thread.
|
|
* @param op a completely closed lambda or functor
|
|
* @warning closure need to be by value or equivalent, since
|
|
* the operation will be executed in another call stack
|
|
*/
|
|
void
|
|
event (Operation&& op)
|
|
{
|
|
queue_.feed (move(op));
|
|
dispatcher_.emit();
|
|
}
|
|
};
|
|
|
|
|
|
|
|
}}// namespace gui::ctrl
|
|
#endif /*GUI_CTRL_UI_DISPATCHER_H*/
|