To be frank I am not competent to review this.

The code in the nod library seems well written and commented. It has
not be touched for 2 years, but it is maybe because it does not need
to. This code should be maintainable at worst.

The fact that Yuriy has researched all the possible alternatives is
good too.

If we get rid of Boost.signal2, then the size of boost will not be a
concern anymore.

Any thoughts?


I'm not really competent, either. But if there were going to be
problems, I think we would see them fairly quickly. Maybe it would be a
good time, after this were committed, to do another alpha-ish release.

Riki

Here is an update moving nod library to proper place in 3rdparty directory. I've updated build files, but not sure about automake. Cmake should be OK.

Guillaume worked on sgnals-slots code. If he is still on this list, his review will be helpful.


Yuriy

From bcd428de7ec46cd79e7f7a797fd6e159a51f09bb Mon Sep 17 00:00:00 2001
From: Yuriy Skalko <yuriy.ska...@gmail.com>
Date: Sat, 12 Dec 2020 17:36:22 +0200
Subject: [PATCH] Use new signal library `nod` instead of `boost::signals2`

---
 3rdparty/Makefile.am               |   6 +-
 3rdparty/nod/Makefile.am           |   3 +
 3rdparty/nod/README.md             | 257 +++++++++++
 3rdparty/nod/nod.hpp               | 680 +++++++++++++++++++++++++++++
 CMakeLists.txt                     |   2 +
 src/graphics/GraphicsConverter.cpp |  27 +-
 src/graphics/GraphicsConverter.h   |   6 +-
 src/graphics/GraphicsLoader.cpp    |   1 +
 src/graphics/PreviewLoader.cpp     |  19 +-
 src/graphics/PreviewLoader.h       |   7 +-
 src/support/ForkedCalls.cpp        |   1 +
 src/support/signals.h              |   4 +-
 12 files changed, 974 insertions(+), 39 deletions(-)
 create mode 100644 3rdparty/nod/Makefile.am
 create mode 100644 3rdparty/nod/README.md
 create mode 100644 3rdparty/nod/nod.hpp

diff --git a/3rdparty/Makefile.am b/3rdparty/Makefile.am
index ec12adb883..8a205fdff1 100644
--- a/3rdparty/Makefile.am
+++ b/3rdparty/Makefile.am
@@ -1,6 +1,6 @@
 include $(top_srcdir)/config/common.am
 
-DIST_SUBDIRS = boost dtl hunspell mythes libiconv zlib
+DIST_SUBDIRS = boost dtl hunspell mythes libiconv zlib nod
 
 if USE_INCLUDED_BOOST
 BOOST = boost
@@ -26,7 +26,9 @@ if BUILD_INCLUDED_DTL
 DTL=dtl
 endif
 
-SUBDIRS = $(BOOST) $(DTL) $(HUNSPELL) $(MYTHES) $(ICONV) $(ZLIB)
+NOD=nod
+
+SUBDIRS = $(BOOST) $(DTL) $(HUNSPELL) $(MYTHES) $(ICONV) $(ZLIB) $(NOD)
 
 EXTRA_DIST = \
        scripts/evince_sync/evince_backward_search \
diff --git a/3rdparty/nod/Makefile.am b/3rdparty/nod/Makefile.am
new file mode 100644
index 0000000000..1b73dffdd9
--- /dev/null
+++ b/3rdparty/nod/Makefile.am
@@ -0,0 +1,3 @@
+include $(top_srcdir)/config/common.am
+
+EXTRA_DIST = README.md
diff --git a/3rdparty/nod/README.md b/3rdparty/nod/README.md
new file mode 100644
index 0000000000..0cf74f1d4f
--- /dev/null
+++ b/3rdparty/nod/README.md
@@ -0,0 +1,257 @@
+# Nod
+[![Build 
Status](https://travis-ci.org/fr00b0/nod.svg?branch=master)](https://travis-ci.org/fr00b0/nod)
+[![GitHub 
tag](https://img.shields.io/github/tag/fr00b0/nod.svg?label=version)](https://github.com/fr00b0/nod/releases)
+
+Dependency free, header only signals and slot library implemented with C++11.
+
+## Usage
+
+### Simple usage
+The following example creates a signal and then connects a lambda as a slot.
+
+```cpp
+// Create a signal which accepts slots with no arguments and void return value.
+nod::signal<void()> signal;
+// Connect a lambda slot that writes "Hello, World!" to stdout
+signal.connect([](){
+               std::cout << "Hello, World!" << std::endl;
+       });
+// Call the slots
+signal();
+```
+
+### Connecting multiple slots
+If multiple slots are connected to the same signal, all of the slots will be
+called when the signal is invoked. The slots will be called in the same order
+as they where connected.
+
+```cpp
+void endline() {
+       std::cout << std::endl;
+}
+
+// Create a signal
+nod::signal<void()> signal;
+// Connect a lambda that prints a message
+signal.connect([](){
+               std::cout << "Message without endline!";
+       });
+// Connect a function that prints a endline
+signal.connect(endline);
+
+// Call the slots
+signal();
+```
+
+#### Slot type
+The signal types in the library support connection of the same types that is
+supported by `std::function<T>`.
+
+### Slot arguments
+When a signal calls it's connected slots, any arguments passed to the signal
+are propagated to the slots. To make this work, we do need to specify the 
+signature of the signal to accept the arguments.
+
+```cpp
+void print_sum( int x, int y ) {
+       std::cout << x << "+" << y << "=" << (x+y) << std::endl;
+}
+void print_product( int x, int y ) {
+       std::cout << x << "*" << y << "=" << (x*y) << std::endl;
+}
+
+
+// We create a signal with two integer arguments.
+nod::signal<void(int,int)> signal;
+// Let's connect our slot
+signal.connect( print_sum );
+signal.connect( print_product );
+
+// Call the slots
+signal(10, 15);
+signal(-5, 7); 
+
+```
+
+### Disconnecting slots
+There are many circumstances where the programmer needs to diconnect a slot 
that
+no longer want to recieve events from the signal. This can be really important
+if the lifetime of the slots are shorter than the lifetime of the signal. That
+could cause the signal to call slots that have been destroyed but not
+disconnected, leading to undefined behaviour and probably segmentation faults.
+
+When a slot is connected, the return value from the  `connect` method returns
+an instance of the class `nod::connection`, that can be used to disconnect
+that slot.
+
+```cpp
+// Let's create a signal
+nod::signal<void()> signal;
+// Connect a slot, and save the connection
+nod::connection connection = signal.connect([](){
+                                                                std::cout << 
"I'm connected!" << std::endl;
+                                                        });
+// Triggering the signal will call the slot
+signal();
+// Now we disconnect the slot
+connection.disconnect();
+// Triggering the signal will no longer call the slot
+signal();
+```    
+
+### Scoped connections
+To assist in disconnecting slots, one can use the class 
`nod::scoped_connection`
+to capture a slot connection. A scoped connection will automatically disconnect
+the slot when the connection object goes out of scope.
+
+```cpp
+// We create a signal
+nod::signal<void()> signal;
+// Let's use a scope to control lifetime
+{ 
+       // Let's save the connection in a scoped_connection
+       nod::scoped_connection connection =
+               signal.connect([](){
+                       std::cout << "This message should only be emitted 
once!" << std::endl; 
+               });
+       // If we trigger the signal, the slot will be called
+       signal();
+} // Our scoped connection is destructed, and disconnects the slot
+// Triggering the signal now will not call the slot
+signal();      
+```
+
+### Slot return values
+
+#### Accumulation of return values
+It is possible for slots to have a return value. The return values can be
+returned from the signal using a *accumulator*, which is a function object that
+acts as a proxy object that processes the slot return values. When triggering a
+signal through a accumulator, the accumulator gets called for each slot return
+value, does the desired accumulation and then return the result to the code
+triggering the signal. The accumulator is designed to work in a similar way as 
+the STL numerical algorithm `std::accumulate`.
+
+```cpp
+// We create a singal with slots that return a value
+nod::signal<int(int, int)> signal;
+// Then we connect some signals
+signal.connect( std::plus<int>{} );
+signal.connect( std::multiplies<int>{} );
+signal.connect( std::minus<int>{} );           
+// Let's say we want to calculate the sum of all the slot return values
+// when triggering the singal with the parameters 10 and 100.
+// We do this by accumulating the return values with the initial value 0
+// and a plus function object, like so:
+std::cout << "Sum: " << signal.accumulate(0, std::plus<int>{})(10,100) << 
std::endl;
+// Or accumulate by multiplying (this needs 1 as initial value):
+std::cout << "Product: " << signal.accumulate(1, 
std::multiplies<int>{})(10,100) << std::endl;
+// If we instead want to build a vector with all the return values
+// we can accumulate them this way (start with a empty vector and add each 
value):                     
+auto vec = signal.accumulate( std::vector<int>{}, []( std::vector<int> result, 
int value ) {
+               result.push_back( value );
+               return result;
+       })(10,100);
+
+std::cout << "Vector: ";
+for( auto const& element : vec ) {
+       std::cout << element << " "; 
+}
+std::cout << std::endl;
+```
+#### Aggregation
+As we can see from the previous example, we can use the `accumulate` method if
+we want to aggregate all the return values of the slots. Doing the aggregation
+that way is not very optimal. It is both a inefficient algorithm for doing
+aggreagtion to a container, and it obscures the call site as the caller needs 
to
+express the aggregation using the verb *accumulate*. To remedy these
+shortcomings we can turn to the method `aggregate` instead. This is a template
+method, taking the type of container to aggregate to as a template parameter.
+
+```cpp
+// We create a singal
+nod::signal<int(int, int)> signal;
+// Let's connect some slots
+signal.connect( std::plus<int>{} );
+signal.connect( std::multiplies<int>{} );
+signal.connect( std::minus<int>{} );
+// We can now trigger the signal and aggregate the slot return values
+auto vec = signal.aggregate<std::vector<int>>(10,100);
+
+std::cout << "Result: ";
+for( auto const& element : vec ) {
+       std::cout << element << " "; 
+}
+std::cout << std::endl;
+```
+
+## Thread safety
+There are two types of signals in the library. The first is `nod::signal<T>`
+which is safe to use in a multi threaded environment. Multiple threads can 
read,
+write, connect slots and disconnect slots simultaneously, and the signal will 
+provide the nessesary synchronization. When triggering a slignal, all the
+registered slots will be called and executed by the thread that triggered the
+signal.
+
+The second type of signal is `nod::unsafe_signal<T>` which is **not** safe to
+use in a multi threaded environment. No syncronization will be performed on the
+internal state of the signal. Instances of the signal should theoretically be
+safe to read from multiple thread simultaneously, as long as no thread is
+writing to the same object at the same time. There can be a performance gain
+involved in using the unsafe version of a signal, since no syncronization
+primitives will be used.
+
+`nod::connection` and `nod::scoped_connection` are thread safe for reading from
+multiple threads, as long as no thread is writing to the same object. Writing 
in
+this context means calling any non const member function, including destructing
+the object. If an object is being written by one thread, then all reads and
+writes to that object from the same or other threads needs to be prevented.
+This basically means that a connection is only allowed to be disconnected from
+one thread, and you should not check connection status or reassign the
+connection while it is being disconnected.
+
+## Building the tests
+The test project uses [premake5](https://premake.github.io/download.html) to 
+generate make files or similiar.
+
+### Linux
+To build and run the tests using gcc and gmake on linux, execute the following
+from the test directory:
+```bash
+premake5 gmake
+make -C build/gmake
+bin/gmake/debug/nod_tests
+```
+
+### Visual Studio 2013
+To build and run the tests, execute the following from the test directory:
+
+```batchfile
+REM Adjust paths to suite your environment
+c:\path\to\premake\premake5.exe vs2013
+"c:\Program Files (x86)\Microsoft Visual Studio 
12.0\Common7\Tools\vsvars32.bat"
+msbuild /m build\vs2013\nod_tests.sln
+bin\vs2013\debug\nod_tests.exe
+```
+
+## The MIT License (MIT)
+
+Copyright (c) 2015 Fredrik Berggren
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/3rdparty/nod/nod.hpp b/3rdparty/nod/nod.hpp
new file mode 100644
index 0000000000..5c4a93cb85
--- /dev/null
+++ b/3rdparty/nod/nod.hpp
@@ -0,0 +1,680 @@
+#ifndef IG_NOD_INCLUDE_NOD_HPP
+#define IG_NOD_INCLUDE_NOD_HPP
+
+#include <vector>       // std::vector
+#include <functional>   // std::function
+#include <mutex>        // std::mutex, std::lock_guard
+#include <memory>       // std::shared_ptr, std::weak_ptr
+#include <algorithm>    // std::find_if()
+#include <cassert>      // assert()
+#include <thread>       // std::this_thread::yield()
+#include <type_traits>  // std::is_same
+#include <iterator>     // std::back_inserter
+
+namespace nod {
+       // implementational details
+       namespace detail {
+               /// Interface for type erasure when disconnecting slots
+               struct disconnector {
+                       virtual void operator()( std::size_t index ) const = 0;
+               };
+               /// Deleter that doesn't delete
+               inline void no_delete(disconnector*){
+               };
+       } // namespace detail
+
+       /// Base template for the signal class
+       template <class P, class T>
+       class signal_type;
+
+
+       /// Connection class.
+       ///
+       /// This is used to be able to disconnect slots after they have been 
connected.
+       /// Used as return type for the connect method of the signals.
+       ///
+       /// Connections are default constructible.
+       /// Connections are not copy constructible or copy assignable.
+       /// Connections are move constructible and move assignable.
+       ///
+       class connection {
+               public:
+                       /// Default constructor
+                       connection() :
+                               _index()
+                       {}
+
+                       // Connection are not copy constructible or copy 
assignable
+                       connection( connection const& ) = delete;
+                       connection& operator=( connection const& ) = delete;
+
+                       /// Move constructor
+                       /// @param other   The instance to move from.
+                       connection( connection&& other ) :
+                               _weak_disconnector( 
std::move(other._weak_disconnector) ),
+                               _index( other._index )
+                       {}
+
+                       /// Move assign operator.
+                       /// @param other   The instance to move from.
+                       connection& operator=( connection&& other ) {
+                               _weak_disconnector = std::move( 
other._weak_disconnector );
+                               _index = other._index;
+                               return *this;
+                       }
+
+                       /// @returns `true` if the connection is connected to a 
signal object,
+                       ///          and `false` otherwise.
+                       bool connected() const {
+                               return !_weak_disconnector.expired();
+                       }
+
+                       /// Disconnect the slot from the connection.
+                       ///
+                       /// If the connection represents a slot that is 
connected to a signal object, calling
+                       /// this method will disconnect the slot from that 
object. The result of this operation
+                       /// is that the slot will stop receiving calls when the 
signal is invoked.
+                       void disconnect();
+
+               private:
+                       /// The signal template is a friend of the connection, 
since it is the
+                       /// only one allowed to create instances using the 
meaningful constructor.
+                       template<class P,class T> friend class signal_type;
+
+                       /// Create a connection.
+                       /// @param shared_disconnector   Disconnector instance 
that will be used to disconnect
+                       ///                              the connection when 
the time comes. A weak pointer
+                       ///                              to the disconnector 
will be held within the connection
+                       ///                              object.
+                       /// @param index                 The slot index of the 
connection.
+                       connection( std::shared_ptr<detail::disconnector> 
const& shared_disconnector, std::size_t index ) :
+                               _weak_disconnector( shared_disconnector ),
+                               _index( index )
+                       {}
+
+                       /// Weak pointer to the current disconnector functor.
+                       std::weak_ptr<detail::disconnector> _weak_disconnector;
+                       /// Slot index of the connected slot.
+                       std::size_t _index;
+       };
+
+       /// Scoped connection class.
+       ///
+       /// This type of connection is automatically disconnected when
+       /// the connection object is destructed.
+       ///
+       class scoped_connection
+       {
+               public:
+                       /// Scoped are default constructible
+                       scoped_connection() = default;
+                       /// Scoped connections are not copy constructible
+                       scoped_connection( scoped_connection const& ) = delete;
+                       /// Scoped connections are not copy assingable
+                       scoped_connection& operator=( scoped_connection const& 
) = delete;
+
+                       /// Move constructor
+                       scoped_connection( scoped_connection&& other ) :
+                               _connection( std::move(other._connection) )
+                       {}
+
+                       /// Move assign operator.
+                       /// @param other   The instance to move from.
+                       scoped_connection& operator=( scoped_connection&& other 
) {
+                               reset( std::move( other._connection ) );
+                               return *this;
+                       }
+
+                       /// Construct a scoped connection from a connection 
object
+                       /// @param connection   The connection object to manage
+                       scoped_connection( connection&& c ) :
+                               _connection( std::forward<connection>(c) )
+                       {}
+
+                       /// destructor
+                       ~scoped_connection() {
+                               disconnect();
+                       }
+
+                       /// Assignment operator moving a new connection into 
the instance.
+                       /// @note If the scoped_connection instance already 
contains a
+                       ///       connection, that connection will be 
disconnected as if
+                       ///       the scoped_connection was destroyed.
+                       /// @param c   New connection to manage
+                       scoped_connection& operator=( connection&& c ) {
+                               reset( std::forward<connection>(c) );
+                               return *this;
+                       }
+
+                       /// Reset the underlying connection to another 
connection.
+                       /// @note The connection currently managed by the 
scoped_connection
+                       ///       instance will be disconnected when resetting.
+                       /// @param c   New connection to manage
+                       void reset( connection&& c = {} ) {
+                               disconnect();
+                               _connection = std::move(c);
+                       }
+
+                       /// Release the underlying connection, without 
disconnecting it.
+                       /// @returns The newly released connection instance is 
returned.
+                       connection release() {
+                               connection c = std::move(_connection);
+                               _connection = connection{};
+                               return c;
+                       }
+
+                       ///
+                       /// @returns `true` if the connection is connected to a 
signal object,
+                       ///          and `false` otherwise.
+                       bool connected() const {
+                               return _connection.connected();
+                       }
+
+                       /// Disconnect the slot from the connection.
+                       ///
+                       /// If the connection represents a slot that is 
connected to a signal object, calling
+                       /// this method will disconnect the slot from that 
object. The result of this operation
+                       /// is that the slot will stop receiving calls when the 
signal is invoked.
+                       void disconnect() {
+                               _connection.disconnect();
+                       }
+
+               private:
+                       /// Underlying connection object
+                       connection _connection;
+       };
+
+       /// Policy for multi threaded use of signals.
+       ///
+       /// This policy provides mutex and lock types for use in
+       /// a multithreaded environment, where signals and slots
+       /// may exists in different threads.
+       ///
+       /// This policy is used in the `nod::signal` type provided
+       /// by the library.
+       struct multithread_policy
+       {
+               using mutex_type = std::mutex;
+               using mutex_lock_type = std::unique_lock<mutex_type>;
+               /// Function that yields the current thread, allowing
+               /// the OS to reschedule.
+               static void yield_thread() {
+                       std::this_thread::yield();
+               }
+               /// Function that defers a lock to a lock function that 
prevents deadlock
+               static mutex_lock_type defer_lock(mutex_type & m){
+                       return mutex_lock_type{m, std::defer_lock};
+               }
+               /// Function that locks two mutexes and prevents deadlock
+               static void lock(mutex_lock_type & a,mutex_lock_type & b) {
+                       std::lock(a,b);
+               }
+       };
+
+       /// Policy for single threaded use of signals.
+       ///
+       /// This policy provides dummy implementations for mutex
+       /// and lock types, resulting in that no synchronization
+       /// will take place.
+       ///
+       /// This policy is used in the `nod::unsafe_signal` type
+       /// provided by the library.
+       struct singlethread_policy
+       {
+               /// Dummy mutex type that doesn't do anything
+               struct mutex_type{};
+               /// Dummy lock type, that doesn't do any locking.
+               struct mutex_lock_type
+               {
+                       /// A lock type must be constructible from a
+                       /// mutex type from the same thread policy.
+                       explicit mutex_lock_type( mutex_type const& ) {
+                       }
+               };
+               /// Dummy implementation of thread yielding, that
+               /// doesn't do any actual yielding.
+               static void yield_thread() {
+               }
+               /// Dummy implemention of defer_lock that doesn't
+               /// do anything
+               static mutex_lock_type defer_lock(mutex_type &m){
+                       return mutex_lock_type{m};
+               }
+               /// Dummy implemention of lock that doesn't
+               /// do anything
+               static void lock(mutex_lock_type &,mutex_lock_type &) {
+               }
+       };
+
+       /// Signal accumulator class template.
+       ///
+       /// This acts sort of as a proxy for triggering a signal and
+       /// accumulating the slot return values.
+       ///
+       /// This class is not really intended to instantiate by client code.
+       /// Instances are aquired as return values of the method `accumulate()`
+       /// called on signals.
+       ///
+       /// @tparam S      Type of signal. The signal_accumulator acts
+       ///                as a type of proxy for a signal instance of
+       ///                this type.
+       /// @tparam T      Type of initial value of the accumulate algorithm.
+       ///                This type must meet the requirements of 
`CopyAssignable`
+       ///                and `CopyConstructible`
+       /// @tparam F      Type of accumulation function.
+       /// @tparam A...   Argument types of the underlying signal type.
+       ///
+       template <class S, class T, class F, class...A>
+       class signal_accumulator
+       {
+               public:
+                       /// Result type when calling the accumulating function 
operator.
+                       using result_type = typename std::result_of<F(T, 
typename S::slot_type::result_type)>::type;
+
+                       /// Construct a signal_accumulator as a proxy to a 
given signal
+                       //
+                       /// @param signal   Signal instance.
+                       /// @param init     Initial value of the accumulate 
algorithm.
+                       /// @param func     Binary operation function object 
that will be
+                       ///                 applied to all slot return values.
+                       ///                 The signature of the function 
should be
+                       ///                 equivalent of the following:
+                       ///                   `R func( T1 const& a, T2 const& b 
)`
+                       ///                  - The signature does not need to 
have `const&`.
+                       ///                  - The initial value, type `T`, 
must be implicitly
+                       ///                    convertible to `R`
+                       ///                  - The return type `R` must be 
implicitly convertible
+                       ///                    to type `T1`.
+                       ///                  - The type `R` must be 
`CopyAssignable`.
+                       ///                  - The type 
`S::slot_type::result_type` (return type of
+                       ///                    the signals slots) must be 
implicitly convertible to
+                       ///                    type `T2`.
+                       signal_accumulator( S const& signal, T init, F func ) :
+                               _signal( signal ),
+                               _init( init ),
+                               _func( func )
+                       {}
+
+                       /// Function call operator.
+                       ///
+                       /// Calling this will trigger the underlying signal and 
accumulate
+                       /// all of the connected slots return values with the 
current
+                       /// initial value and accumulator function.
+                       ///
+                       /// When called, this will invoke the accumulator 
function will
+                       /// be called for each return value of the slots. The 
semantics
+                       /// are similar to the `std::accumulate` algorithm.
+                       ///
+                       /// @param args   Arguments to propagate to the slots 
of the
+                       ///               underlying when triggering the signal.
+                       result_type operator()( A const& ... args ) const {
+                               return _signal.trigger_with_accumulator( _init, 
_func, args... );
+                       }
+
+               private:
+
+                       /// Reference to the underlying signal to proxy.
+                       S const& _signal;
+                       /// Initial value of the accumulate algorithm.
+                       T _init;
+                       /// Accumulator function.
+                       F _func;
+
+       };
+
+       /// Signal template specialization.
+       ///
+       /// This is the main signal implementation, and it is used to
+       /// implement the observer pattern whithout the overhead
+       /// boilerplate code that typically comes with it.
+       ///
+       /// Any function or function object is considered a slot, and
+       /// can be connected to a signal instance, as long as the signature
+       /// of the slot matches the signature of the signal.
+       ///
+       /// @tparam P      Threading policy for the signal.
+       ///                A threading policy must provide two type definitions:
+       ///                 - P::mutex_type, this type will be used as a mutex
+       ///                   in the signal_type class template.
+       ///                 - P::mutex_lock_type, this type must implement a
+       ///                   constructor that takes a P::mutex_type as a 
parameter,
+       ///                   and it must have the semantics of a scoped mutex 
lock
+       ///                   like std::lock_guard, i.e. locking in the 
constructor
+       ///                   and unlocking in the destructor.
+       ///
+       /// @tparam R      Return value type of the slots connected to the 
signal.
+       /// @tparam A...   Argument types of the slots connected to the signal.
+       template <class P, class R, class... A >
+       class signal_type<P,R(A...)>
+       {
+               public:
+                       /// signals are not copy constructible
+                       signal_type( signal_type const& ) = delete;
+                       /// signals are not copy assignable
+                       signal_type& operator=( signal_type const& ) = delete;
+                       /// signals are move constructible
+                       signal_type(signal_type&& other)
+                       {
+                               mutex_lock_type lock{other._mutex};
+                               _slot_count = std::move(other._slot_count);
+                               _slots = std::move(other._slots);
+                               if(other._shared_disconnector != nullptr)
+                               {
+                                       _disconnector = disconnector{ this };
+                                       _shared_disconnector = 
std::move(other._shared_disconnector);
+                                       // replace the disconnector with our 
own disconnector
+                                       
*static_cast<disconnector*>(_shared_disconnector.get()) = _disconnector;
+                               }
+                       }
+                       /// signals are move assignable
+                       signal_type& operator=(signal_type&& other)
+                       {
+                               auto lock = thread_policy::defer_lock(_mutex);
+                               auto other_lock = 
thread_policy::defer_lock(other._mutex);
+                               thread_policy::lock(lock,other_lock);
+
+                               _slot_count = std::move(other._slot_count);
+                               _slots = std::move(other._slots);
+                               if(other._shared_disconnector != nullptr)
+                               {
+                                       _disconnector = disconnector{ this };
+                                       _shared_disconnector = 
std::move(other._shared_disconnector);
+                                       // replace the disconnector with our 
own disconnector
+                                       
*static_cast<disconnector*>(_shared_disconnector.get()) = _disconnector;
+                               }
+                               return *this;
+                       }
+
+                       /// signals are default constructible
+                       signal_type() :
+                               _slot_count(0)
+                       {}
+
+                       // Destruct the signal object.
+                       ~signal_type() {
+                               invalidate_disconnector();
+                       }
+
+                       /// Type that will be used to store the slots for this 
signal type.
+                       using slot_type = std::function<R(A...)>;
+                       /// Type that is used for counting the slots connected 
to this signal.
+                       using size_type = typename 
std::vector<slot_type>::size_type;
+
+
+                       /// Connect a new slot to the signal.
+                       ///
+                       /// The connected slot will be called every time the 
signal
+                       /// is triggered.
+                       /// @param slot   The slot to connect. This must be a 
callable with
+                       ///               the same signature as the signal 
itself.
+                       /// @return       A connection object is returned, and 
can be used to
+                       ///               disconnect the slot.
+                       template <class T>
+                       connection connect( T&& slot ) {
+                               mutex_lock_type lock{ _mutex };
+                               _slots.push_back( std::forward<T>(slot) );
+                               std::size_t index = _slots.size()-1;
+                               if( _shared_disconnector == nullptr ) {
+                                       _disconnector = disconnector{ this };
+                                       _shared_disconnector = 
std::shared_ptr<detail::disconnector>{&_disconnector, detail::no_delete};
+                               }
+                               ++_slot_count;
+                               return connection{ _shared_disconnector, index 
};
+                       }
+
+                       /// Function call operator.
+                       ///
+                       /// Calling this is how the signal is triggered and the
+                       /// connected slots are called.
+                       ///
+                       /// @note The slots will be called in the order they 
were
+                       ///       connected to the signal.
+                       ///
+                       /// @param args   Arguments that will be propagated to 
the
+                       ///               connected slots when they are called.
+                       void operator()( A const&... args ) const {
+                               for( auto const& slot : copy_slots() ) {
+                                       if( slot ) {
+                                               slot( args... );
+                                       }
+                               }
+                       }
+
+                       /// Construct a accumulator proxy object for the signal.
+                       ///
+                       /// The intended purpose of this function is to create 
a function
+                       /// object that can be used to trigger the signal and 
accumulate
+                       /// all the slot return values.
+                       ///
+                       /// The algorithm used to accumulate slot return values 
is similar
+                       /// to `std::accumulate`. A given binary function is 
called for
+                       /// each return value with the parameters consisting of 
the
+                       /// return value of the accumulator function applied to 
the
+                       /// previous slots return value, and the current slots 
return value.
+                       /// A initial value must be provided for the first slot 
return type.
+                       ///
+                       /// @note This can only be used on signals that have 
slots with
+                       ///       non-void return types, since we can't 
accumulate void
+                       ///       values.
+                       ///
+                       /// @tparam T      The type of the initial value given 
to the accumulator.
+                       /// @tparam F      The accumulator function type.
+                       /// @param init    Initial value given to the 
accumulator.
+                       /// @param op      Binary operator function object to 
apply by the accumulator.
+                       ///                The signature of the function should 
be
+                       ///                equivalent of the following:
+                       ///                  `R func( T1 const& a, T2 const& b 
)`
+                       ///                 - The signature does not need to 
have `const&`.
+                       ///                 - The initial value, type `T`, must 
be implicitly
+                       ///                   convertible to `R`
+                       ///                 - The return type `R` must be 
implicitly convertible
+                       ///                   to type `T1`.
+                       ///                 - The type `R` must be 
`CopyAssignable`.
+                       ///                 - The type 
`S::slot_type::result_type` (return type of
+                       ///                   the signals slots) must be 
implicitly convertible to
+                       ///                   type `T2`.
+                       template <class T, class F>
+                       signal_accumulator<signal_type, T, F, A...> accumulate( 
T init, F op ) const {
+                               static_assert( std::is_same<R,void>::value == 
false, "Unable to accumulate slot return values with 'void' as return type." );
+                               return { *this, init, op };
+                       }
+
+
+                       /// Trigger the signal, calling the slots and aggregate 
all
+                       /// the slot return values into a container.
+                       ///
+                       /// @tparam C     The type of container. This type must 
be
+                       ///               `DefaultConstructible`, and usable 
with
+                       ///               `std::back_insert_iterator`. 
Additionally it
+                       ///               must be either copyable or moveable.
+                       /// @param args   The arguments to propagate to the 
slots.
+                       template <class C>
+                       C aggregate( A const&... args ) const {
+                               static_assert( std::is_same<R,void>::value == 
false, "Unable to aggregate slot return values with 'void' as return type." );
+                               C container;
+                               auto iterator = std::back_inserter( container );
+                               for( auto const& slot : copy_slots() ) {
+                                       if( slot ) {
+                                               (*iterator) = slot( args... );
+                                       }
+                               }
+                               return container;
+                       }
+
+                       /// Count the number of slots connected to this signal
+                       /// @returns   The number of connected slots
+                       size_type slot_count() const {
+                               return _slot_count;
+                       }
+
+                       /// Determine if the signal is empty, i.e. no slots are 
connected
+                       /// to it.
+                       /// @returns   `true` is returned if the signal has no 
connected
+                       ///            slots, and `false` otherwise.
+                       bool empty() const {
+                               return slot_count() == 0;
+                       }
+
+                       /// Disconnects all slots
+                       /// @note This operation invalidates all 
scoped_connection objects
+                       void disconnect_all_slots() {
+                               mutex_lock_type lock{ _mutex };
+                               _slots.clear();
+                               _slot_count = 0;
+                               invalidate_disconnector();
+                       }
+
+               private:
+                       template<class, class, class, class...> friend class 
signal_accumulator;
+                       /// Thread policy currently in use
+                       using thread_policy = P;
+                       /// Type of mutex, provided by threading policy
+                       using mutex_type = typename thread_policy::mutex_type;
+                       /// Type of mutex lock, provided by threading policy
+                       using mutex_lock_type = typename 
thread_policy::mutex_lock_type;
+
+                       /// Invalidate the internal disconnector object in a way
+                       /// that is safe according to the current thread policy.
+                       ///
+                       /// This will effectively make all current connection 
objects to
+                       /// to this signal incapable of disconnecting, since 
they keep a
+                       /// weak pointer to the shared disconnector object.
+                       void invalidate_disconnector() {
+                               // If we are unlucky, some of the connected 
slots
+                               // might be in the process of disconnecting 
from other threads.
+                               // If this happens, we are risking to destruct 
the disconnector
+                               // object managed by our shared pointer before 
they are done
+                               // disconnecting. This would be bad. To solve 
this problem, we
+                               // discard the shared pointer (that is pointing 
to the disconnector
+                               // object within our own instance), but keep a 
weak pointer to that
+                               // instance. We then stall the destruction 
until all other weak
+                               // pointers have released their "lock" 
(indicated by the fact that
+                               // we will get a nullptr when locking our weak 
pointer).
+                               std::weak_ptr<detail::disconnector> 
weak{_shared_disconnector};
+                               _shared_disconnector.reset();
+                               while( weak.lock() != nullptr ) {
+                                       // we just yield here, allowing the OS 
to reschedule. We do
+                                       // this until all threads has released 
the disconnector object.
+                                       thread_policy::yield_thread();
+                               }
+                       }
+
+                       /// Retrieve a copy of the current slots
+                       ///
+                       /// It's useful and necessary to copy the slots so we 
don't need
+                       /// to hold the lock while calling the slots. If we 
hold the lock
+                       /// we prevent the called slots from modifying the 
slots vector.
+                       /// This simple "double buffering" will allow slots to 
disconnect
+                       /// themself or other slots and connect new slots.
+                       std::vector<slot_type> copy_slots() const
+                       {
+                               mutex_lock_type lock{ _mutex };
+                               return _slots;
+                       }
+
+                       /// Implementation of the signal accumulator function 
call
+                       template <class T, class F>
+                       typename signal_accumulator<signal_type, T, F, 
A...>::result_type trigger_with_accumulator( T value, F& func, A const&... args 
) const {
+                               for( auto const& slot : copy_slots() ) {
+                                       if( slot ) {
+                                               value = func( value, slot( 
args... ) );
+                                       }
+                               }
+                               return value;
+                       }
+
+                       /// Implementation of the disconnection operation.
+                       ///
+                       /// This is private, and only called by the connection
+                       /// objects created when connecting slots to this 
signal.
+                       /// @param index   The slot index of the slot that 
should
+                       ///                be disconnected.
+                       void disconnect( std::size_t index ) {
+                               mutex_lock_type lock( _mutex );
+                               assert( _slots.size() > index );
+                               if( _slots[ index ] != nullptr ) {
+                                       --_slot_count;
+                               }
+                               _slots[ index ] = slot_type{};
+                               while( _slots.size()>0 && !_slots.back() ) {
+                                       _slots.pop_back();
+                               }
+                       }
+
+                       /// Implementation of the shared disconnection state
+                       /// used by all connection created by signal instances.
+                       ///
+                       /// This inherits the @ref detail::disconnector 
interface
+                       /// for type erasure.
+                       struct disconnector :
+                               detail::disconnector
+                       {
+                               /// Default constructor, resulting in a no-op 
disconnector.
+                               disconnector() :
+                                       _ptr(nullptr)
+                               {}
+
+                               /// Create a disconnector that works with a 
given signal instance.
+                               /// @param ptr   Pointer to the signal instance 
that the disconnector
+                               ///              should work with.
+                               disconnector( signal_type<P,R(A...)>* ptr ) :
+                                       _ptr( ptr )
+                               {}
+
+                               /// Disconnect a given slot on the current 
signal instance.
+                               /// @note If the instance is default 
constructed, or created
+                               ///       with `nullptr` as signal pointer this 
operation will
+                               ///       effectively be a no-op.
+                               /// @param index   The index of the slot to 
disconnect.
+                               void operator()( std::size_t index ) const 
override {
+                                       if( _ptr ) {
+                                               _ptr->disconnect( index );
+                                       }
+                               }
+
+                               /// Pointer to the current signal.
+                               signal_type<P,R(A...)>* _ptr;
+                       };
+
+                       /// Mutex to synchronize access to the slot vector
+                       mutable mutex_type _mutex;
+                       /// Vector of all connected slots
+                       std::vector<slot_type> _slots;
+                       /// Number of connected slots
+                       size_type _slot_count;
+                       /// Disconnector operation, used for executing 
disconnection in a
+                       /// type erased manner.
+                       disconnector _disconnector;
+                       /// Shared pointer to the disconnector. All connection 
objects has a
+                       /// weak pointer to this pointer for performing 
disconnections.
+                       std::shared_ptr<detail::disconnector> 
_shared_disconnector;
+       };
+
+       // Implementation of the disconnect operation of the connection class
+       inline void connection::disconnect() {
+               auto ptr = _weak_disconnector.lock();
+               if( ptr ) {
+                       (*ptr)( _index );
+               }
+               _weak_disconnector.reset();
+       }
+
+       /// Signal type that is safe to use in multithreaded environments,
+       /// where the signal and slots exists in different threads.
+       /// The multithreaded policy provides mutexes and locks to synchronize
+       /// access to the signals internals.
+       ///
+       /// This is the recommended signal type, even for single threaded
+       /// environments.
+       template <class T> using signal = signal_type<multithread_policy, T>;
+
+       /// Signal type that is unsafe in multithreaded environments.
+       /// No synchronizations are provided to the signal_type for accessing
+       /// the internals.
+       ///
+       /// Only use this signal type if you are sure that your environment is
+       /// single threaded and performance is of importance.
+       template <class T> using unsafe_signal = 
signal_type<singlethread_policy, T>;
+} // namespace nod
+
+#endif // IG_NOD_INCLUDE_NOD_HPP
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1805a9c6b8..015fcc31b7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -940,6 +940,8 @@ set(Lyx_Boost_Libraries)
 add_definitions(-DBOOST_USER_CONFIG=<config.h>)
 include_directories(${TOP_SRC_DIR}/3rdparty/boost)
 
+include_directories(${TOP_SRC_DIR}/3rdparty/nod)
+
 if(WIN32)
        if(LYX_CONSOLE)
                set(LYX_QTMAIN_LIBRARY)
diff --git a/src/graphics/GraphicsConverter.cpp 
b/src/graphics/GraphicsConverter.cpp
index 1461fdb27c..66e198ace4 100644
--- a/src/graphics/GraphicsConverter.cpp
+++ b/src/graphics/GraphicsConverter.cpp
@@ -38,7 +38,7 @@ namespace graphics {
 class Converter::Impl {
 public:
        ///
-       Impl(FileName const & doc_fname,
+       Impl(Converter const & parent, FileName const & doc_fname,
             FileName const & from_file, string const & to_file_base,
             string const & from_format, string const & to_format);
 
@@ -59,6 +59,8 @@ public:
        ///
        sig finishedConversion;
 
+       ///
+       Converter const & parent_;
        ///
        FileName const doc_fname_;
        ///
@@ -71,8 +73,6 @@ public:
        bool valid_process_;
        ///
        bool finished_;
-       ///
-       Trackable tracker_;
 };
 
 
@@ -86,16 +86,10 @@ bool Converter::isReachable(string const & from_format_name,
 Converter::Converter(FileName const & doc_fname,
                      FileName const & from_file, string const & to_file_base,
                      string const & from_format, string const & to_format)
-       : pimpl_(new Impl(doc_fname, from_file, to_file_base, from_format, 
to_format))
+       : pimpl_(make_shared<Impl>(*this, doc_fname, from_file, to_file_base, 
from_format, to_format))
 {}
 
 
-Converter::~Converter()
-{
-       delete pimpl_;
-}
-
-
 void Converter::startConversion() const
 {
        pimpl_->startConversion();
@@ -123,10 +117,10 @@ static void build_script(string const & doc_fname,
                  ostream & script);
 
 
-Converter::Impl::Impl(FileName const & doc_fname,
+Converter::Impl::Impl(Converter const & parent, FileName const & doc_fname,
                      FileName const & from_file, string const & to_file_base,
                      string const & from_format, string const & to_format)
-       : doc_fname_(doc_fname), valid_process_(false), finished_(false)
+       : parent_(parent), doc_fname_(doc_fname), valid_process_(false), 
finished_(false)
 {
        LYXERR(Debug::GRAPHICS, "Converter c-tor:\n"
                << "doc_fname:        " << doc_fname
@@ -188,9 +182,12 @@ void Converter::Impl::startConversion()
        }
 
        ForkedCall::sigPtr ptr = ForkedCallQueue::add(script_command_);
-       ptr->connect(ForkedCall::slot([this](pid_t pid, int retval){
-                               converted(pid, retval);
-                       }).track_foreign(tracker_.p()));
+       weak_ptr<Converter::Impl> this_ = parent_.pimpl_;
+       ptr->connect([this_](pid_t pid, int retval){
+                       if (auto p = this_.lock()) {
+                               p->converted(pid, retval);
+                       }
+               });
 }
 
 
diff --git a/src/graphics/GraphicsConverter.h b/src/graphics/GraphicsConverter.h
index c038029bd8..7eb3689ad1 100644
--- a/src/graphics/GraphicsConverter.h
+++ b/src/graphics/GraphicsConverter.h
@@ -19,6 +19,7 @@
 
 #include "support/signals.h"
 
+#include <memory>
 
 namespace lyx {
 
@@ -39,9 +40,6 @@ public:
                  support::FileName const & from_file, std::string const & 
to_file_base,
                  std::string const & from_format, std::string const & 
to_format);
 
-       /// Needed for the pimpl
-       ~Converter();
-
        /// We are explicit about when we begin the conversion process.
        void startConversion() const;
 
@@ -70,7 +68,7 @@ private:
        /// Use the Pimpl idiom to hide the internals.
        class Impl;
        /// The pointer never changes although *pimpl_'s contents may.
-       Impl * const pimpl_;
+       std::shared_ptr<Impl> const pimpl_;
 };
 
 } // namespace graphics
diff --git a/src/graphics/GraphicsLoader.cpp b/src/graphics/GraphicsLoader.cpp
index f0a0934091..2316a4ac69 100644
--- a/src/graphics/GraphicsLoader.cpp
+++ b/src/graphics/GraphicsLoader.cpp
@@ -21,6 +21,7 @@
 #include "support/lassert.h"
 #include "support/Timeout.h"
 
+#include <list>
 #include <queue>
 #include <memory>
 #include <set>
diff --git a/src/graphics/PreviewLoader.cpp b/src/graphics/PreviewLoader.cpp
index d0590f6b28..e1177a8d87 100644
--- a/src/graphics/PreviewLoader.cpp
+++ b/src/graphics/PreviewLoader.cpp
@@ -226,8 +226,6 @@ private:
        ///
        QTimer * delay_refresh_;
        ///
-       Trackable trackable_;
-       ///
        bool finished_generating_;
 
        /// We don't own this
@@ -244,16 +242,10 @@ lyx::Converter const * PreviewLoader::Impl::pconverter_;
 //
 
 PreviewLoader::PreviewLoader(Buffer const & b)
-       : pimpl_(new Impl(*this, b))
+       : pimpl_(make_shared<Impl>(*this, b))
 {}
 
 
-PreviewLoader::~PreviewLoader()
-{
-       delete pimpl_;
-}
-
-
 PreviewImage const * PreviewLoader::preview(string const & latex_snippet) const
 {
        return pimpl_->preview(latex_snippet);
@@ -721,9 +713,12 @@ void PreviewLoader::Impl::startLoading(bool wait)
 
        // Initiate the conversion from LaTeX to bitmap images files.
        ForkedCall::sigPtr convert_ptr = make_shared<ForkedCall::sig>();
-       convert_ptr->connect(ForkedProcess::slot([this](pid_t pid, int retval){
-                               finishedGenerating(pid, retval);
-                       }).track_foreign(trackable_.p()));
+       weak_ptr<PreviewLoader::Impl> this_ = parent_.pimpl_;
+       convert_ptr->connect([this_](pid_t pid, int retval){
+                       if (auto p = this_.lock()) {
+                               p->finishedGenerating(pid, retval);
+                       }
+               });
 
        ForkedCall call(buffer_.filePath());
        int ret = call.startScript(command, convert_ptr);
diff --git a/src/graphics/PreviewLoader.h b/src/graphics/PreviewLoader.h
index ca22a9fa5c..5d0bf8e234 100644
--- a/src/graphics/PreviewLoader.h
+++ b/src/graphics/PreviewLoader.h
@@ -18,11 +18,12 @@
 #ifndef PREVIEWLOADER_H
 #define PREVIEWLOADER_H
 
+#include "ColorCode.h"
 #include "support/signals.h"
 
 #include <QObject>
 
-#include "ColorCode.h"
+#include <memory>
 
 namespace lyx {
 
@@ -39,8 +40,6 @@ public:
         *  LaTeX file.
         */
        PreviewLoader(Buffer const & buffer);
-       ///
-       ~PreviewLoader();
 
        /** Is there an image already associated with this snippet of LaTeX?
         *  If so, returns a pointer to it, else returns 0.
@@ -108,7 +107,7 @@ private:
        /// Use the Pimpl idiom to hide the internals.
        class Impl;
        /// The pointer never changes although *pimpl_'s contents may.
-       Impl * const pimpl_;
+       std::shared_ptr<Impl> const pimpl_;
 };
 
 } // namespace graphics
diff --git a/src/support/ForkedCalls.cpp b/src/support/ForkedCalls.cpp
index 7718a745ae..a14d99d7b9 100644
--- a/src/support/ForkedCalls.cpp
+++ b/src/support/ForkedCalls.cpp
@@ -24,6 +24,7 @@
 #include "support/bind.h"
 
 #include <cerrno>
+#include <list>
 #include <queue>
 #include <sstream>
 #include <utility>
diff --git a/src/support/signals.h b/src/support/signals.h
index f768f2f873..18bc03e117 100644
--- a/src/support/signals.h
+++ b/src/support/signals.h
@@ -12,13 +12,13 @@
 #ifndef LYX_SIGNALS_H
 #define LYX_SIGNALS_H
 
-#include <boost/signals2/signal.hpp>
+#include "nod.hpp"
 
 #include <memory>
 
 namespace lyx {
 
-namespace signals2 = ::boost::signals2;
+namespace signals2 = ::nod;
 
 namespace support {
 
-- 
2.28.0.windows.1

-- 
lyx-devel mailing list
lyx-devel@lists.lyx.org
http://lists.lyx.org/mailman/listinfo/lyx-devel

Reply via email to