From bda45704005d6b328e18457a07d05e56883c2874 Mon Sep 17 00:00:00 2001 From: Yuriy Skalko Date: Sun, 13 Dec 2020 23:06:05 +0200 Subject: [PATCH] Use new signal library `nod` instead of `boost::signals2` Thanks Enrico for updating autotools files. --- 3rdparty/Makefile.am | 8 +- 3rdparty/nod/Makefile.am | 4 + 3rdparty/nod/README.md | 257 +++++++++++ 3rdparty/nod/nod.hpp | 680 +++++++++++++++++++++++++++++ CMakeLists.txt | 2 + config/lyxinclude.m4 | 28 ++ configure.ac | 2 + src/Makefile.am | 2 +- src/Server.cpp | 8 +- src/client/Makefile.am | 2 +- src/frontends/Makefile.am | 2 +- src/frontends/qt/Makefile.am | 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 | 2 + src/support/Makefile.am | 2 +- src/support/signals.h | 4 +- src/tex2lyx/Makefile.am | 2 +- 21 files changed, 1020 insertions(+), 47 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..7b5c569f32 100644 --- a/3rdparty/Makefile.am +++ b/3rdparty/Makefile.am @@ -1,6 +1,10 @@ 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_NOD +NOD = nod +endif if USE_INCLUDED_BOOST BOOST = boost @@ -26,7 +30,7 @@ if BUILD_INCLUDED_DTL DTL=dtl endif -SUBDIRS = $(BOOST) $(DTL) $(HUNSPELL) $(MYTHES) $(ICONV) $(ZLIB) +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..07caaa47fb --- /dev/null +++ b/3rdparty/nod/Makefile.am @@ -0,0 +1,4 @@ +include $(top_srcdir)/config/common.am + +EXTRA_DIST = nod \ + 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 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 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`. + +### 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 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 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 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 signal; +// Then we connect some signals +signal.connect( std::plus{} ); +signal.connect( std::multiplies{} ); +signal.connect( std::minus{} ); +// 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{})(10,100) << std::endl; +// Or accumulate by multiplying (this needs 1 as initial value): +std::cout << "Product: " << signal.accumulate(1, std::multiplies{})(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{}, []( std::vector 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 signal; +// Let's connect some slots +signal.connect( std::plus{} ); +signal.connect( std::multiplies{} ); +signal.connect( std::minus{} ); +// We can now trigger the signal and aggregate the slot return values +auto vec = signal.aggregate>(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` +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` 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 // std::vector +#include // std::function +#include // std::mutex, std::lock_guard +#include // std::shared_ptr, std::weak_ptr +#include // std::find_if() +#include // assert() +#include // std::this_thread::yield() +#include // std::is_same +#include // 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 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 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 const& shared_disconnector, std::size_t index ) : + _weak_disconnector( shared_disconnector ), + _index( index ) + {} + + /// Weak pointer to the current disconnector functor. + std::weak_ptr _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(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(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; + /// 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 signal_accumulator + { + public: + /// Result type when calling the accumulating function operator. + using result_type = typename std::result_of::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 signal_type + { + 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(_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(_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; + /// Type that is used for counting the slots connected to this signal. + using size_type = typename std::vector::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 + connection connect( T&& slot ) { + mutex_lock_type lock{ _mutex }; + _slots.push_back( std::forward(slot) ); + std::size_t index = _slots.size()-1; + if( _shared_disconnector == nullptr ) { + _disconnector = disconnector{ this }; + _shared_disconnector = std::shared_ptr{&_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 + signal_accumulator accumulate( T init, F op ) const { + static_assert( std::is_same::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 + C aggregate( A const&... args ) const { + static_assert( std::is_same::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 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 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 copy_slots() const + { + mutex_lock_type lock{ _mutex }; + return _slots; + } + + /// Implementation of the signal accumulator function call + template + typename signal_accumulator::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* 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* _ptr; + }; + + /// Mutex to synchronize access to the slot vector + mutable mutex_type _mutex; + /// Vector of all connected slots + std::vector _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 _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 using signal = signal_type; + + /// 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 using unsafe_signal = signal_type; +} // namespace nod + +#endif // IG_NOD_INCLUDE_NOD_HPP diff --git a/CMakeLists.txt b/CMakeLists.txt index 02a1b3000d..91dd05a547 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -940,6 +940,8 @@ set(Lyx_Boost_Libraries) add_definitions(-DBOOST_USER_CONFIG=) 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/config/lyxinclude.m4 b/config/lyxinclude.m4 index d07e1bcfcc..0e9ff19b5b 100644 --- a/config/lyxinclude.m4 +++ b/config/lyxinclude.m4 @@ -416,6 +416,34 @@ if test x$GXX = xyes; then fi ]) +dnl Usage: LYX_USE_INCLUDED_NOD : select if the included nod should be used. +AC_DEFUN([LYX_USE_INCLUDED_NOD],[ + AC_MSG_CHECKING([whether to use included nod library]) + AC_ARG_WITH(included-nod, + [AS_HELP_STRING([--without-included-nod], [do not use the nod lib supplied with LyX, try to find one in the system directories - compilation will abort if nothing suitable is found])], + [lyx_cv_with_included_nod=$withval], + [lyx_cv_with_included_nod=yes]) + AM_CONDITIONAL(USE_INCLUDED_NOD, test x$lyx_cv_with_included_nod = xyes) + AC_MSG_RESULT([$lyx_cv_with_included_nod]) + if test x$lyx_cv_with_included_nod = xyes ; then + lyx_included_libs="$lyx_included_libs nod" + NOD_INCLUDES='-I$(top_srcdir)/3rdparty/nod' + else + NOD_INCLUDES= + AC_LANG_PUSH(C++) + AC_MSG_CHECKING([for nod library]) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([#include ], + [nod::scoped_connection conn;])], + [AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([no]) + AC_MSG_ERROR([cannot find suitable nod library (do not use --without-included-nod)]) + ]) + AC_LANG_POP(C++) + fi + AC_SUBST(NOD_INCLUDES) +]) + dnl Usage: LYX_USE_INCLUDED_BOOST : select if the included boost should dnl be used. AC_DEFUN([LYX_USE_INCLUDED_BOOST],[ diff --git a/configure.ac b/configure.ac index 318986b4e2..9853fa53da 100644 --- a/configure.ac +++ b/configure.ac @@ -94,6 +94,7 @@ AC_SUBST(LIBPSAPI) AC_CHECK_LIB(gdi32, main) AC_CHECK_LIB(ole32, main) +LYX_USE_INCLUDED_NOD LYX_USE_INCLUDED_BOOST ### we need to know the byte order for unicode conversions @@ -337,6 +338,7 @@ AC_CONFIG_FILES([Makefile \ 3rdparty/dtl/Makefile \ 3rdparty/hunspell/Makefile \ 3rdparty/mythes/Makefile \ + 3rdparty/nod/Makefile \ 3rdparty/libiconv/Makefile \ $ICONV_ICONV_H_IN \ 3rdparty/zlib/Makefile \ diff --git a/src/Makefile.am b/src/Makefile.am index 032b174cf3..31701f2fb1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -5,7 +5,7 @@ include $(top_srcdir)/config/common.am AM_CPPFLAGS += -I$(top_srcdir)/src AM_CPPFLAGS += $(BOOST_INCLUDES) $(ICONV_INCLUDES) $(ZLIB_INCLUDES) AM_CPPFLAGS += $(ENCHANT_CFLAGS) $(HUNSPELL_CFLAGS) $(MYTHES_INCLUDES) -AM_CPPFLAGS += $(QT_CPPFLAGS) $(QT_CORE_INCLUDES) +AM_CPPFLAGS += $(NOD_INCLUDES) $(QT_CPPFLAGS) $(QT_CORE_INCLUDES) if BUILD_CLIENT_SUBDIR CLIENT = client diff --git a/src/Server.cpp b/src/Server.cpp index 79340492c6..8d9e0c61db 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -60,8 +60,12 @@ #include #ifdef _WIN32 -#include -#include +# include +# include +#else +# ifdef HAVE_UNISTD_H +# include +# endif #endif #include diff --git a/src/client/Makefile.am b/src/client/Makefile.am index 5a6db831af..580556d6b1 100644 --- a/src/client/Makefile.am +++ b/src/client/Makefile.am @@ -9,7 +9,7 @@ bin_PROGRAMS = lyxclient EXTRA_DIST = lyxclient.1in CMakeLists.txt AM_CPPFLAGS += -I$(srcdir)/.. \ - $(BOOST_INCLUDES) $(ICONV_INCLUDES) $(ZLIB_INCLUDES) + $(BOOST_INCLUDES) $(ICONV_INCLUDES) $(ZLIB_INCLUDES) $(NOD_INCLUDES) lyxclient_LDADD = \ $(top_builddir)/src/support/liblyxsupport.a \ diff --git a/src/frontends/Makefile.am b/src/frontends/Makefile.am index 2acdeebb26..a8f233e25a 100644 --- a/src/frontends/Makefile.am +++ b/src/frontends/Makefile.am @@ -7,7 +7,7 @@ DIST_SUBDIRS = qt . noinst_LIBRARIES = liblyxfrontends.a AM_CPPFLAGS += -I$(srcdir)/.. \ - $(BOOST_INCLUDES) $(ICONV_INCLUDES) $(ZLIB_INCLUDES) + $(BOOST_INCLUDES) $(ICONV_INCLUDES) $(ZLIB_INCLUDES) $(NOD_INCLUDES) liblyxfrontends_a_SOURCES = \ alert.h \ diff --git a/src/frontends/qt/Makefile.am b/src/frontends/qt/Makefile.am index 2a497af887..d559623310 100644 --- a/src/frontends/qt/Makefile.am +++ b/src/frontends/qt/Makefile.am @@ -37,7 +37,7 @@ AM_CPPFLAGS += \ -I$(top_srcdir)/src/frontends \ -I$(top_srcdir)/images \ $(QT_INCLUDES) \ - $(BOOST_INCLUDES) $(ICONV_INCLUDES) $(ZLIB_INCLUDES) + $(BOOST_INCLUDES) $(ICONV_INCLUDES) $(ZLIB_INCLUDES) $(NOD_INCLUDES) SOURCEFILES = \ ButtonPolicy.cpp \ 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(*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 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 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 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 #include #include #include 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(*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(); - convert_ptr->connect(ForkedProcess::slot([this](pid_t pid, int retval){ - finishedGenerating(pid, retval); - }).track_foreign(trackable_.p())); + weak_ptr 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 -#include "ColorCode.h" +#include 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 const pimpl_; }; } // namespace graphics diff --git a/src/support/ForkedCalls.cpp b/src/support/ForkedCalls.cpp index 7718a745ae..387f8561bf 100644 --- a/src/support/ForkedCalls.cpp +++ b/src/support/ForkedCalls.cpp @@ -24,6 +24,8 @@ #include "support/bind.h" #include +#include +#include #include #include #include diff --git a/src/support/Makefile.am b/src/support/Makefile.am index 83e67eabdf..3dedf57fea 100644 --- a/src/support/Makefile.am +++ b/src/support/Makefile.am @@ -29,7 +29,7 @@ liblyxsupport_a_DEPENDENCIES = $(MOCEDFILES) AM_CPPFLAGS += -I$(srcdir)/.. \ $(BOOST_INCLUDES) $(ICONV_INCLUDES) $(ZLIB_INCLUDES) \ - $(QT_CPPFLAGS) $(QT_INCLUDES) + $(NOD_INCLUDES) $(QT_CPPFLAGS) $(QT_INCLUDES) liblyxsupport_a_SOURCES = \ FileMonitor.h \ diff --git a/src/support/signals.h b/src/support/signals.h index f768f2f873..ceca3709c6 100644 --- a/src/support/signals.h +++ b/src/support/signals.h @@ -12,13 +12,13 @@ #ifndef LYX_SIGNALS_H #define LYX_SIGNALS_H -#include +#include #include namespace lyx { -namespace signals2 = ::boost::signals2; +namespace signals2 = ::nod; namespace support { diff --git a/src/tex2lyx/Makefile.am b/src/tex2lyx/Makefile.am index 923d6950ec..7ad91a0828 100644 --- a/src/tex2lyx/Makefile.am +++ b/src/tex2lyx/Makefile.am @@ -17,7 +17,7 @@ bin_PROGRAMS = tex2lyx AM_CPPFLAGS += -I$(top_srcdir)/src/tex2lyx \ -I$(top_srcdir)/src -I$(top_builddir) -I$(top_builddir)/src \ - $(BOOST_INCLUDES) $(ICONV_INCLUDES) $(ZLIB_INCLUDES) + $(BOOST_INCLUDES) $(ICONV_INCLUDES) $(ZLIB_INCLUDES) $(NOD_INCLUDES) TEST_FILES = \ test/runtests.cmake \ -- 2.39.2