aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/core/CMakeLists.txt2
-rw-r--r--include/core/connection.h162
-rw-r--r--include/core/property.h181
-rw-r--r--include/core/signal.h297
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_