Use new signal library nod instead of boost::signals2

Thanks Enrico for updating autotools files.
This commit is contained in:
Yuriy Skalko 2020-12-13 23:06:05 +02:00
parent 9602d24039
commit bda4570400
21 changed files with 1020 additions and 47 deletions

View File

@ -1,6 +1,10 @@
include $(top_srcdir)/config/common.am 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 if USE_INCLUDED_BOOST
BOOST = boost BOOST = boost
@ -26,7 +30,7 @@ if BUILD_INCLUDED_DTL
DTL=dtl DTL=dtl
endif endif
SUBDIRS = $(BOOST) $(DTL) $(HUNSPELL) $(MYTHES) $(ICONV) $(ZLIB) SUBDIRS = $(BOOST) $(DTL) $(HUNSPELL) $(MYTHES) $(ICONV) $(ZLIB) $(NOD)
EXTRA_DIST = \ EXTRA_DIST = \
scripts/evince_sync/evince_backward_search \ scripts/evince_sync/evince_backward_search \

4
3rdparty/nod/Makefile.am vendored Normal file
View File

@ -0,0 +1,4 @@
include $(top_srcdir)/config/common.am
EXTRA_DIST = nod \
README.md

257
3rdparty/nod/README.md vendored Normal file
View File

@ -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.

680
3rdparty/nod/nod.hpp vendored Normal file
View File

@ -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

View File

@ -940,6 +940,8 @@ set(Lyx_Boost_Libraries)
add_definitions(-DBOOST_USER_CONFIG=<config.h>) add_definitions(-DBOOST_USER_CONFIG=<config.h>)
include_directories(${TOP_SRC_DIR}/3rdparty/boost) include_directories(${TOP_SRC_DIR}/3rdparty/boost)
include_directories(${TOP_SRC_DIR}/3rdparty/nod)
if(WIN32) if(WIN32)
if(LYX_CONSOLE) if(LYX_CONSOLE)
set(LYX_QTMAIN_LIBRARY) set(LYX_QTMAIN_LIBRARY)

View File

@ -416,6 +416,34 @@ if test x$GXX = xyes; then
fi 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.hpp>],
[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 Usage: LYX_USE_INCLUDED_BOOST : select if the included boost should
dnl be used. dnl be used.
AC_DEFUN([LYX_USE_INCLUDED_BOOST],[ AC_DEFUN([LYX_USE_INCLUDED_BOOST],[

View File

@ -94,6 +94,7 @@ AC_SUBST(LIBPSAPI)
AC_CHECK_LIB(gdi32, main) AC_CHECK_LIB(gdi32, main)
AC_CHECK_LIB(ole32, main) AC_CHECK_LIB(ole32, main)
LYX_USE_INCLUDED_NOD
LYX_USE_INCLUDED_BOOST LYX_USE_INCLUDED_BOOST
### we need to know the byte order for unicode conversions ### we need to know the byte order for unicode conversions
@ -337,6 +338,7 @@ AC_CONFIG_FILES([Makefile \
3rdparty/dtl/Makefile \ 3rdparty/dtl/Makefile \
3rdparty/hunspell/Makefile \ 3rdparty/hunspell/Makefile \
3rdparty/mythes/Makefile \ 3rdparty/mythes/Makefile \
3rdparty/nod/Makefile \
3rdparty/libiconv/Makefile \ 3rdparty/libiconv/Makefile \
$ICONV_ICONV_H_IN \ $ICONV_ICONV_H_IN \
3rdparty/zlib/Makefile \ 3rdparty/zlib/Makefile \

View File

@ -5,7 +5,7 @@ include $(top_srcdir)/config/common.am
AM_CPPFLAGS += -I$(top_srcdir)/src AM_CPPFLAGS += -I$(top_srcdir)/src
AM_CPPFLAGS += $(BOOST_INCLUDES) $(ICONV_INCLUDES) $(ZLIB_INCLUDES) AM_CPPFLAGS += $(BOOST_INCLUDES) $(ICONV_INCLUDES) $(ZLIB_INCLUDES)
AM_CPPFLAGS += $(ENCHANT_CFLAGS) $(HUNSPELL_CFLAGS) $(MYTHES_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 if BUILD_CLIENT_SUBDIR
CLIENT = client CLIENT = client

View File

@ -62,6 +62,10 @@
#ifdef _WIN32 #ifdef _WIN32
# include <io.h> # include <io.h>
# include <QCoreApplication> # include <QCoreApplication>
#else
# ifdef HAVE_UNISTD_H
# include <unistd.h>
# endif
#endif #endif
#include <QThread> #include <QThread>

View File

@ -9,7 +9,7 @@ bin_PROGRAMS = lyxclient
EXTRA_DIST = lyxclient.1in CMakeLists.txt EXTRA_DIST = lyxclient.1in CMakeLists.txt
AM_CPPFLAGS += -I$(srcdir)/.. \ AM_CPPFLAGS += -I$(srcdir)/.. \
$(BOOST_INCLUDES) $(ICONV_INCLUDES) $(ZLIB_INCLUDES) $(BOOST_INCLUDES) $(ICONV_INCLUDES) $(ZLIB_INCLUDES) $(NOD_INCLUDES)
lyxclient_LDADD = \ lyxclient_LDADD = \
$(top_builddir)/src/support/liblyxsupport.a \ $(top_builddir)/src/support/liblyxsupport.a \

View File

@ -7,7 +7,7 @@ DIST_SUBDIRS = qt .
noinst_LIBRARIES = liblyxfrontends.a noinst_LIBRARIES = liblyxfrontends.a
AM_CPPFLAGS += -I$(srcdir)/.. \ AM_CPPFLAGS += -I$(srcdir)/.. \
$(BOOST_INCLUDES) $(ICONV_INCLUDES) $(ZLIB_INCLUDES) $(BOOST_INCLUDES) $(ICONV_INCLUDES) $(ZLIB_INCLUDES) $(NOD_INCLUDES)
liblyxfrontends_a_SOURCES = \ liblyxfrontends_a_SOURCES = \
alert.h \ alert.h \

View File

@ -37,7 +37,7 @@ AM_CPPFLAGS += \
-I$(top_srcdir)/src/frontends \ -I$(top_srcdir)/src/frontends \
-I$(top_srcdir)/images \ -I$(top_srcdir)/images \
$(QT_INCLUDES) \ $(QT_INCLUDES) \
$(BOOST_INCLUDES) $(ICONV_INCLUDES) $(ZLIB_INCLUDES) $(BOOST_INCLUDES) $(ICONV_INCLUDES) $(ZLIB_INCLUDES) $(NOD_INCLUDES)
SOURCEFILES = \ SOURCEFILES = \
ButtonPolicy.cpp \ ButtonPolicy.cpp \

View File

@ -38,7 +38,7 @@ namespace graphics {
class Converter::Impl { class Converter::Impl {
public: public:
/// ///
Impl(FileName const & doc_fname, Impl(Converter const & parent, FileName const & doc_fname,
FileName const & from_file, string const & to_file_base, FileName const & from_file, string const & to_file_base,
string const & from_format, string const & to_format); string const & from_format, string const & to_format);
@ -59,6 +59,8 @@ public:
/// ///
sig finishedConversion; sig finishedConversion;
///
Converter const & parent_;
/// ///
FileName const doc_fname_; FileName const doc_fname_;
/// ///
@ -71,8 +73,6 @@ public:
bool valid_process_; bool valid_process_;
/// ///
bool finished_; bool finished_;
///
Trackable tracker_;
}; };
@ -86,16 +86,10 @@ bool Converter::isReachable(string const & from_format_name,
Converter::Converter(FileName const & doc_fname, Converter::Converter(FileName const & doc_fname,
FileName const & from_file, string const & to_file_base, FileName const & from_file, string const & to_file_base,
string const & from_format, string const & to_format) 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 void Converter::startConversion() const
{ {
pimpl_->startConversion(); pimpl_->startConversion();
@ -123,10 +117,10 @@ static void build_script(string const & doc_fname,
ostream & script); 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, FileName const & from_file, string const & to_file_base,
string const & from_format, string const & to_format) 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" LYXERR(Debug::GRAPHICS, "Converter c-tor:\n"
<< "doc_fname: " << doc_fname << "doc_fname: " << doc_fname
@ -188,9 +182,12 @@ void Converter::Impl::startConversion()
} }
ForkedCall::sigPtr ptr = ForkedCallQueue::add(script_command_); ForkedCall::sigPtr ptr = ForkedCallQueue::add(script_command_);
ptr->connect(ForkedCall::slot([this](pid_t pid, int retval){ weak_ptr<Converter::Impl> this_ = parent_.pimpl_;
converted(pid, retval); ptr->connect([this_](pid_t pid, int retval){
}).track_foreign(tracker_.p())); if (auto p = this_.lock()) {
p->converted(pid, retval);
}
});
} }

View File

@ -19,6 +19,7 @@
#include "support/signals.h" #include "support/signals.h"
#include <memory>
namespace lyx { namespace lyx {
@ -39,9 +40,6 @@ public:
support::FileName const & from_file, std::string const & to_file_base, support::FileName const & from_file, std::string const & to_file_base,
std::string const & from_format, std::string const & to_format); 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. /// We are explicit about when we begin the conversion process.
void startConversion() const; void startConversion() const;
@ -70,7 +68,7 @@ private:
/// Use the Pimpl idiom to hide the internals. /// Use the Pimpl idiom to hide the internals.
class Impl; class Impl;
/// The pointer never changes although *pimpl_'s contents may. /// The pointer never changes although *pimpl_'s contents may.
Impl * const pimpl_; std::shared_ptr<Impl> const pimpl_;
}; };
} // namespace graphics } // namespace graphics

View File

@ -21,6 +21,7 @@
#include "support/lassert.h" #include "support/lassert.h"
#include "support/Timeout.h" #include "support/Timeout.h"
#include <list>
#include <queue> #include <queue>
#include <memory> #include <memory>
#include <set> #include <set>

View File

@ -226,8 +226,6 @@ private:
/// ///
QTimer * delay_refresh_; QTimer * delay_refresh_;
/// ///
Trackable trackable_;
///
bool finished_generating_; bool finished_generating_;
/// We don't own this /// We don't own this
@ -244,16 +242,10 @@ lyx::Converter const * PreviewLoader::Impl::pconverter_;
// //
PreviewLoader::PreviewLoader(Buffer const & b) 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 PreviewImage const * PreviewLoader::preview(string const & latex_snippet) const
{ {
return pimpl_->preview(latex_snippet); return pimpl_->preview(latex_snippet);
@ -721,9 +713,12 @@ void PreviewLoader::Impl::startLoading(bool wait)
// Initiate the conversion from LaTeX to bitmap images files. // Initiate the conversion from LaTeX to bitmap images files.
ForkedCall::sigPtr convert_ptr = make_shared<ForkedCall::sig>(); ForkedCall::sigPtr convert_ptr = make_shared<ForkedCall::sig>();
convert_ptr->connect(ForkedProcess::slot([this](pid_t pid, int retval){ weak_ptr<PreviewLoader::Impl> this_ = parent_.pimpl_;
finishedGenerating(pid, retval); convert_ptr->connect([this_](pid_t pid, int retval){
}).track_foreign(trackable_.p())); if (auto p = this_.lock()) {
p->finishedGenerating(pid, retval);
}
});
ForkedCall call(buffer_.filePath()); ForkedCall call(buffer_.filePath());
int ret = call.startScript(command, convert_ptr); int ret = call.startScript(command, convert_ptr);

View File

@ -18,11 +18,12 @@
#ifndef PREVIEWLOADER_H #ifndef PREVIEWLOADER_H
#define PREVIEWLOADER_H #define PREVIEWLOADER_H
#include "ColorCode.h"
#include "support/signals.h" #include "support/signals.h"
#include <QObject> #include <QObject>
#include "ColorCode.h" #include <memory>
namespace lyx { namespace lyx {
@ -39,8 +40,6 @@ public:
* LaTeX file. * LaTeX file.
*/ */
PreviewLoader(Buffer const & buffer); PreviewLoader(Buffer const & buffer);
///
~PreviewLoader();
/** Is there an image already associated with this snippet of LaTeX? /** Is there an image already associated with this snippet of LaTeX?
* If so, returns a pointer to it, else returns 0. * If so, returns a pointer to it, else returns 0.
@ -108,7 +107,7 @@ private:
/// Use the Pimpl idiom to hide the internals. /// Use the Pimpl idiom to hide the internals.
class Impl; class Impl;
/// The pointer never changes although *pimpl_'s contents may. /// The pointer never changes although *pimpl_'s contents may.
Impl * const pimpl_; std::shared_ptr<Impl> const pimpl_;
}; };
} // namespace graphics } // namespace graphics

View File

@ -24,6 +24,8 @@
#include "support/bind.h" #include "support/bind.h"
#include <cerrno> #include <cerrno>
#include <cstring>
#include <list>
#include <queue> #include <queue>
#include <sstream> #include <sstream>
#include <utility> #include <utility>

View File

@ -29,7 +29,7 @@ liblyxsupport_a_DEPENDENCIES = $(MOCEDFILES)
AM_CPPFLAGS += -I$(srcdir)/.. \ AM_CPPFLAGS += -I$(srcdir)/.. \
$(BOOST_INCLUDES) $(ICONV_INCLUDES) $(ZLIB_INCLUDES) \ $(BOOST_INCLUDES) $(ICONV_INCLUDES) $(ZLIB_INCLUDES) \
$(QT_CPPFLAGS) $(QT_INCLUDES) $(NOD_INCLUDES) $(QT_CPPFLAGS) $(QT_INCLUDES)
liblyxsupport_a_SOURCES = \ liblyxsupport_a_SOURCES = \
FileMonitor.h \ FileMonitor.h \

View File

@ -12,13 +12,13 @@
#ifndef LYX_SIGNALS_H #ifndef LYX_SIGNALS_H
#define LYX_SIGNALS_H #define LYX_SIGNALS_H
#include <boost/signals2/signal.hpp> #include <nod.hpp>
#include <memory> #include <memory>
namespace lyx { namespace lyx {
namespace signals2 = ::boost::signals2; namespace signals2 = ::nod;
namespace support { namespace support {

View File

@ -17,7 +17,7 @@ bin_PROGRAMS = tex2lyx
AM_CPPFLAGS += -I$(top_srcdir)/src/tex2lyx \ AM_CPPFLAGS += -I$(top_srcdir)/src/tex2lyx \
-I$(top_srcdir)/src -I$(top_builddir) -I$(top_builddir)/src \ -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_FILES = \
test/runtests.cmake \ test/runtests.cmake \