diff options
-rw-r--r-- | include/core/CMakeLists.txt | 2 | ||||
-rw-r--r-- | include/core/connection.h | 162 | ||||
-rw-r--r-- | include/core/property.h | 181 | ||||
-rw-r--r-- | include/core/signal.h | 297 |
4 files changed, 642 insertions, 0 deletions
diff --git a/include/core/CMakeLists.txt b/include/core/CMakeLists.txt new file mode 100644 index 0000000..139597f --- /dev/null +++ b/include/core/CMakeLists.txt @@ -0,0 +1,2 @@ + + diff --git a/include/core/connection.h b/include/core/connection.h new file mode 100644 index 0000000..37b1355 --- /dev/null +++ b/include/core/connection.h @@ -0,0 +1,162 @@ +/* + * Copyright © 2013 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authored by: Thomas Voß <thomas.voss@canonical.com> + */ +#ifndef COM_UBUNTU_CONNECTION_H_ +#define COM_UBUNTU_CONNECTION_H_ + +#include <functional> +#include <memory> +#include <mutex> + +namespace core +{ +/** + * @brief The Connection class models a signal-slot connection. + */ +class Connection +{ +public: + typedef std::function<void(const std::function<void()>&)> Dispatcher; + + /** + * @brief Checks if this instance corresponds to an active signal-slot connection. + * @return true iff the instance corresponds to an active signal-slot connection. + */ + inline bool is_connected() const + { + return (d->disconnector ? true : false); + } + + /** + * @brief End a signal-slot connection. + */ + inline void disconnect() + { + d->disconnect(); + } + + /** + * @brief Installs a dispatcher for this signal-slot connection. + * @param dispatcher The dispatcher to be used for signal emissions. + */ + inline void dispatch_via(const Dispatcher& dispatcher) + { + if (d->dispatcher_installer) + d->dispatcher_installer(dispatcher); + } + +private: + typedef std::function<void()> Disconnector; + typedef std::function<void(const Dispatcher&)> DispatcherInstaller; + + template<typename ... Arguments> friend class Signal; + + inline Connection(const Disconnector& disconnector, + const DispatcherInstaller& installer) + : d(std::make_shared<Private>(disconnector, installer)) + { + } + + inline void reset() + { + d->reset(); + } + + struct Private + { + Private(const Connection::Disconnector& disconnector_, + const Connection::DispatcherInstaller& dispatcher_installer_) + : disconnector(disconnector_), + dispatcher_installer(dispatcher_installer_) + { + } + + inline void reset() + { + std::lock_guard<std::mutex> lg(guard); + reset_locked(); + } + + inline void reset_locked() + { + static const Connection::Disconnector empty_disconnector{}; + static const Connection::DispatcherInstaller empty_dispatcher_installer{}; + + disconnector = empty_disconnector; + dispatcher_installer = empty_dispatcher_installer; + } + + inline void disconnect() + { + static const Connection::Disconnector empty_disconnector{}; + + std::lock_guard<std::mutex> lg(guard); + + if (disconnector) + disconnector(); + + reset_locked(); + } + + std::mutex guard; + Connection::Disconnector disconnector; + Connection::DispatcherInstaller dispatcher_installer; + }; + + // The whole class is implicitly shared and we thus forward our complete + // shared state to a private structure that is lifetime-managed by a shared_ptr. + std::shared_ptr<Private> d; +}; + +/** + * @brief Scoped helper class to map signal-slot connection mgmt. to RAII. + */ +class ScopedConnection +{ +public: + /** + * @brief Constructs an instance for an existing signal-slot connection. + * @param c The existing signal-slot connection. + */ + inline ScopedConnection(const Connection& c) : connection(c) + { + } + + ScopedConnection(const ScopedConnection&) = delete; + + /** + * @brief Disconnects the signal-slot connection. + */ + inline ~ScopedConnection() noexcept(true) + { + try + { + connection.disconnect(); + } catch(...) + { + } + } + + ScopedConnection& operator=(const ScopedConnection&) = delete; + bool operator==(const ScopedConnection&) = delete; + +private: + Connection connection; +}; +} + +#endif // COM_UBUNTU_CONNECTION_H_ diff --git a/include/core/property.h b/include/core/property.h new file mode 100644 index 0000000..996ba8a --- /dev/null +++ b/include/core/property.h @@ -0,0 +1,181 @@ +/* + * Copyright © 2013 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authored by: Thomas Voß <thomas.voss@canonical.com> + */ +#ifndef CORE_PROPERTY_H_ +#define CORE_PROPERTY_H_ + +#include <core/signal.h> + +#include <iostream> + +namespace core +{ +/** + * @brief A very simple, templated class that allows for uniform declaration of get-able/set-able/observable members. + * @tparam The type of the value contained within the property. + */ +template<typename T> +class Property +{ + public: + /** + * @brief ValueType refers to the type of the contained value. + */ + typedef T ValueType; + + /** + * @brief Property creates a new instance of property and initializes the contained value. + * @param t The initial value, defaults to Property<T>::default_value(). + */ + inline explicit Property(const T& t = T{}) : value{t} + { + } + + /** + * @brief Copy c'tor, only copies the contained value, not the changed signal and its connections. + * @param rhs + */ + inline Property(const Property<T>& rhs) : value{rhs.value} + { + } + + inline virtual ~Property() = default; + + /** + * @brief Assignment operator, only assigns to the contained value. + * @param rhs The right-hand-side, raw value to assign to this property. + */ + inline Property& operator=(const T& rhs) + { + set(rhs); + return *this; + } + + /** + * @brief Assignment operator, only assigns to the contained value, not the changed signal and its connections. + * @param rhs The right-hand-side property to assign from. + */ + inline Property& operator=(const Property<T>& rhs) + { + set(rhs.value); + return *this; + } + + /** + * @brief Explicit casting operator to the contained value type. + * @return A non-mutable reference to the contained value. + */ + inline operator const T&() const + { + return get(); + } + + /** + * @brief Provides access to a pointer to the contained value. + */ + inline const T* operator->() const + { + return &get(); + } + + /** + * @brief operator == checks if the value of a property and a raw value are equal. + * @param lhs Non-mutable reference to a property. + * @param rhs Non-mutable reference to a raw value. + * @return True iff the value contained in lhs equals rhs. + */ + friend inline bool operator==(const Property<T>& lhs, const T& rhs) + { + return lhs.get() == rhs; + } + + /** + * @brief operator == checks if the value of two properties are equal. + * @param lhs Non-mutable reference to a property. + * @param rhs Non-mutable reference to a property. + * @return True iff the value contained in lhs equals the value contained in rhs. + */ + friend inline bool operator==(const Property<T>& lhs, const Property<T>& rhs) + { + return lhs.get() == rhs.get(); + } + + /** + * @brief Set the contained value to the provided value. Notify observers of the change. + * @param [in] new_value The new value to assign to this property. + * @post get() == new_value; + */ + inline virtual void set(const T& new_value) + { + if (value != new_value) + { + value = new_value; + signal_changed(value); + } + } + + /** + * @brief Access the value contained within this property. + * @return A non-mutable reference to the property value. + */ + inline virtual const T& get() const + { + return value; + } + + /** + * @brief Access to the changed signal, allows observers to subscribe to change notifications. + * @return A non-mutable reference to the changed signal. + */ + inline const Signal<T>& changed() const + { + return signal_changed; + } + + /** + * @brief Provides in-place update facilities. + * + * The provided update_functor is applied to the contained value. If the update functor + * returns true, indicating that the value has been changed, the changed signal is emitted. + * + * @param update_functor The update function to be applied to the contained value. + * @return true iff application of the update functor has been successful. + */ + inline virtual bool update(const std::function<bool(T& t)>& update_functor) + { + if (update_functor(mutable_get())) + { + signal_changed(value); + return true; + } + + return false; + } + + protected: + inline T& mutable_get() const + { + return value; + } + + private: + mutable T value; + Signal<T> signal_changed; +}; +} + +#endif // CORE_PROPERTY_H_ diff --git a/include/core/signal.h b/include/core/signal.h new file mode 100644 index 0000000..be2984b --- /dev/null +++ b/include/core/signal.h @@ -0,0 +1,297 @@ +/* + * Copyright © 2013 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authored by: Thomas Voß <thomas.voss@canonical.com> + */ +#ifndef COM_UBUNTU_SIGNAL_H_ +#define COM_UBUNTU_SIGNAL_H_ + +#include <core/connection.h> + +#include <functional> +#include <iostream> +#include <list> +#include <mutex> +#include <set> + +namespace core +{ +/** + * @brief A signal class that observers can subscribe to. + * @tparam Arguments List of argument types passed on to observers when the signal is emitted. + */ +template<typename ...Arguments> +class Signal +{ +public: + /** + * @brief Slot is the function type that observers have to provide to connect to this signal. + */ + typedef std::function<void(Arguments...)> Slot; + +private: + struct SlotWrapper + { + void operator()(Arguments... args) + { + dispatcher(std::bind(slot, args...)); + } + + Slot slot; + Connection::Dispatcher dispatcher; + Connection connection; + }; + +public: + /** + * @brief Signal constructs a new instance. Never throws. + */ + inline Signal() noexcept(true) : d(new Private()) + { + } + + inline ~Signal() + { + std::lock_guard<std::mutex> lg(d->guard); + for (auto slot : d->slots) + slot.connection.reset(); + } + + // Copy construction, assignment and equality comparison are disabled. + Signal(const Signal&) = delete; + Signal& operator=(const Signal&) = delete; + bool operator==(const Signal&) const = delete; + + /** + * @brief Connects the provided slot to this signal instance. + * + * Calling this method is thread-safe and synchronized with any + * other connect, signal emission or disconnect calls. + * + * @param slot The function to be called when the signal is emitted. + * @return A connection object corresponding to the signal-slot connection. + */ + inline Connection connect(const Slot& slot) const + { + // Helpers to initialize an invalid connection. + static const Connection::Disconnector empty_disconnector{}; + static const Connection::DispatcherInstaller empty_dispatcher_installer{}; + + // The default dispatcher immediately executes the function object + // provided as argument on whatever thread is currently running. + static const Connection::Dispatcher default_dispatcher + = [](const std::function<void()>& handler) { handler(); }; + + Connection conn{empty_disconnector, empty_dispatcher_installer}; + + std::lock_guard<std::mutex> lg(d->guard); + + auto result = d->slots.insert( + d->slots.end(), + SlotWrapper{slot, default_dispatcher, conn}); + + // We implicitly share our internal state with the connection here + // by passing in our private bits contained in 'd' to the std::bind call. + // This admittedly uncommon approach allows us to cleanly manage connection + // and signal lifetimes without the need to mark everything as mutable. + conn.d->disconnector = std::bind( + &Private::disconnect_slot_for_iterator, + d, + result); + conn.d->dispatcher_installer = std::bind( + &Private::install_dispatcher_for_iterator, + d, + std::placeholders::_1, + result); + + return conn; + } + + /** + * @brief operator () emits the signal with the provided parameters. + * + * Please note that signal emissions might not be delivered immediately to + * registered slots, depending on whether the respective connection is dispatched + * via a queueing dispatcher. For that reason, the lifetime of the arguments has to + * exceed the scope of the call to this operator and its surrounding scope. + * + * @param args The arguments to be passed on to registered slots. + */ + inline void operator()(Arguments... args) + { + std::lock_guard<std::mutex> lg(d->guard); + for(auto slot : d->slots) + { + slot(args...); + } + } + +private: + struct Private + { + typedef std::list<SlotWrapper> SlotContainer; + + inline void disconnect_slot_for_iterator(typename SlotContainer::iterator it) + { + std::lock_guard<std::mutex> lg(guard); + slots.erase(it); + } + + inline void install_dispatcher_for_iterator(const Connection::Dispatcher& dispatcher, + typename SlotContainer::iterator it) + { + std::lock_guard<std::mutex> lg(guard); + it->dispatcher = dispatcher; + } + + std::mutex guard; + SlotContainer slots; + }; + std::shared_ptr<Private> d; +}; + +/** + * @brief A signal class that observers can subscribe to, + * template specialization for signals without arguments. + */ +template<> +class Signal<void> +{ +public: + /** + * @brief Slot is the function type that observers have to provide to connect to this signal. + */ + typedef std::function<void()> Slot; + +private: + struct SlotWrapper + { + void operator()() + { + dispatcher(slot); + } + + Slot slot; + Connection::Dispatcher dispatcher; + Connection connection; + }; + +public: + /** + * @brief Signal constructs a new instance. Never throws. + */ + inline Signal() noexcept(true) : d(new Private()) + { + } + + inline ~Signal() + { + std::lock_guard<std::mutex> lg(d->guard); + for (auto slot : d->slots) + slot.connection.reset(); + } + + // Copy construction, assignment and equality comparison are disabled. + Signal(const Signal&) = delete; + Signal& operator=(const Signal&) = delete; + bool operator==(const Signal&) const = delete; + + /** + * @brief Connects the provided slot to this signal instance. + * + * Calling this method is thread-safe and synchronized with any + * other connect, signal emission or disconnect calls. + * + * @param slot The function to be called when the signal is emitted. + * @return A connection object corresponding to the signal-slot connection. + */ + inline Connection connect(const Slot& slot) const + { + // Helpers to initialize an invalid connection. + static const Connection::Disconnector empty_disconnector{}; + static const Connection::DispatcherInstaller empty_dispatcher_installer{}; + + // The default dispatcher immediately executes the function object + // provided as argument on whatever thread is currently running. + static const Connection::Dispatcher default_dispatcher + = [](const std::function<void()>& handler) { handler(); }; + + Connection conn{empty_disconnector, empty_dispatcher_installer}; + + std::lock_guard<std::mutex> lg(d->guard); + + auto result = d->slots.insert( + d->slots.end(), + SlotWrapper{slot, default_dispatcher, conn}); + + // We implicitly share our internal state with the connection here + // by passing in our private bits contained in 'd' to the std::bind call. + // This admittedly uncommon approach allows us to cleanly manage connection + // and signal lifetimes without the need to mark everything as mutable. + conn.d->disconnector = std::bind( + &Private::disconnect_slot_for_iterator, + d, + result); + conn.d->dispatcher_installer = std::bind( + &Private::install_dispatcher_for_iterator, + d, + std::placeholders::_1, + result); + + return conn; + } + + /** + * @brief operator () emits the signal. + * + * Please note that signal emissions might not be delivered immediately to + * registered slots, depending on whether the respective connection is dispatched + * via a queueing dispatcher. + */ + inline void operator()() + { + std::lock_guard<std::mutex> lg(d->guard); + for(auto slot : d->slots) + { + slot(); + } + } + +private: + struct Private + { + typedef std::list<SlotWrapper> SlotContainer; + + inline void disconnect_slot_for_iterator(typename SlotContainer::iterator it) + { + std::lock_guard<std::mutex> lg(guard); + slots.erase(it); + } + + inline void install_dispatcher_for_iterator(const Connection::Dispatcher& dispatcher, + typename SlotContainer::iterator it) + { + std::lock_guard<std::mutex> lg(guard); + it->dispatcher = dispatcher; + } + + std::mutex guard; + SlotContainer slots; + }; + std::shared_ptr<Private> d; +}; +} + +#endif // COM_UBUNTU_SIGNAL_H_ |