diff --git a/src/blackmisc/sharedstate/datalink.cpp b/src/blackmisc/sharedstate/datalink.cpp new file mode 100644 index 000000000..8ec536485 --- /dev/null +++ b/src/blackmisc/sharedstate/datalink.cpp @@ -0,0 +1,44 @@ +/* Copyright (C) 2017 + * swift Project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#include "blackmisc/sharedstate/datalink.h" +#include "blackmisc/promise.h" +#include "blackmisc/variant.h" + +namespace BlackMisc +{ + namespace SharedState + { + void CDataLinkConnectionWatcher::setStatus(bool status) + { + if (status != m_connected) + { + m_connected = status; + if (m_connected) { emit connected(); } + else { emit disconnected(); } + } + } + + IDataLink::~IDataLink() = default; + + IDataLink::IDataLink() + { + qRegisterMetaType>(); + } + + QString IDataLink::getChannelName(const QObject *object) + { + const QMetaObject *meta = object->parent()->metaObject(); + const char *info = meta->classInfo(meta->indexOfClassInfo("SharedStateChannel")).value(); + const QString name = object->parent()->objectName(); + return name.isEmpty() ? QString(info) : (info % QLatin1Char(':') % name); + } + } +} diff --git a/src/blackmisc/sharedstate/datalink.h b/src/blackmisc/sharedstate/datalink.h new file mode 100644 index 000000000..56de85f32 --- /dev/null +++ b/src/blackmisc/sharedstate/datalink.h @@ -0,0 +1,111 @@ +/* Copyright (C) 2017 + * swift Project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKMISC_SHAREDSTATE_DATALINK_H +#define BLACKMISC_SHAREDSTATE_DATALINK_H + +#include "blackmisc/blackmiscexport.h" +#include "blackmisc/promise.h" +#include "blackmisc/variant.h" +#include +#include +#include +#include + +/*! + * \defgroup SharedState Utilities for sharing state between multiple objects + */ + +/*! + * Put this macro in the private section of a class to set the channel of its child endpoints. + * \ingroup SharedState + */ +#define BLACK_SHARED_STATE_CHANNEL(ID) Q_CLASSINFO("SharedStateChannel", ID) + +namespace BlackMisc +{ + namespace SharedState + { + class IDataLink; + class CPassiveMutator; + class CActiveMutator; + class CPassiveObserver; + class CActiveObserver; + + /*! + * Observe the connection state of an IDataLink. + */ + class CDataLinkConnectionWatcher : public QObject + { + Q_OBJECT + + public: + //! True if connected to the transport layer. + bool isConnected() const { return m_connected; } + + signals: + //! Connection established. + void connected(); + + //! Connection dropped. + void disconnected(); + + private: + friend class IDataLink; + CDataLinkConnectionWatcher() = default; + void setStatus(bool connected); + bool m_connected = false; + }; + + /*! + * Interface that provides a transport mechanism for sharing state. + * \ingroup SharedState + */ + class BLACKMISC_EXPORT IDataLink + { + public: + //! Constructor. + IDataLink(); + + //! Destructor. + virtual ~IDataLink() = 0; + + //! Get a connection status watcher. + CDataLinkConnectionWatcher *watcher() { return &m_watcher; } + + //! Register a mutator with this transport mechanism. + //! @{ + virtual void publish(const CPassiveMutator *mutator) = 0; + virtual void publish(const CActiveMutator *mutator) = 0; + //! @} + + //! Register an observer with this transport mechanism. + //! @{ + virtual void subscribe(const CPassiveObserver *observer) = 0; + virtual void subscribe(const CActiveObserver *observer) = 0; + //! @} + + protected: + //! Set the connection status visible through the watcher. + void setConnectionStatus(bool connected) { m_watcher.setStatus(connected); } + + //! Get the channel name for child endpoints of the given object. + static QString getChannelName(const QObject *object); + + private: + CDataLinkConnectionWatcher m_watcher; + }; + } +} + +Q_DECLARE_INTERFACE(BlackMisc::SharedState::IDataLink, "BlackMisc::SharedState::IDataLink") +Q_DECLARE_METATYPE(BlackMisc::CPromise) + +#endif diff --git a/src/blackmisc/sharedstate/datalinklocal.cpp b/src/blackmisc/sharedstate/datalinklocal.cpp new file mode 100644 index 000000000..9db1fdc76 --- /dev/null +++ b/src/blackmisc/sharedstate/datalinklocal.cpp @@ -0,0 +1,89 @@ +/* Copyright (C) 2017 + * swift Project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#include "blackmisc/sharedstate/datalinklocal.h" +#include "blackmisc/sharedstate/activeobserver.h" +#include "blackmisc/sharedstate/activemutator.h" +#include "blackmisc/promise.h" + +namespace BlackMisc +{ + namespace SharedState + { + CDataLinkLocal::CDataLinkLocal(QObject *parent) : QObject(parent) + { + setConnectionStatus(true); + } + + CDataLinkLocal::~CDataLinkLocal() + { + setConnectionStatus(false); + } + + void CDataLinkLocal::publish(const CPassiveMutator *mutator) + { + connect(mutator, &CPassiveMutator::eventPosted, this, [this, channel = getChannelName(mutator)](const CVariant ¶m) { dispatchEvent(param, channel); }); + } + + void CDataLinkLocal::publish(const CActiveMutator *mutator) + { + publish(static_cast(mutator)); + + auto &channel = getChannel(mutator); + Q_ASSERT_X(! channel.activeMutator, Q_FUNC_INFO, "Tried to publish two active mutators on one channel"); + channel.activeMutator = mutator->weakRef(); + } + + void CDataLinkLocal::subscribe(const CPassiveObserver *observer) + { + getChannel(observer).passiveObservers.push_back(observer->weakRef()); + } + + void CDataLinkLocal::subscribe(const CActiveObserver *observer) + { + subscribe(static_cast(observer)); + + connect(observer, &CActiveObserver::requestPosted, this, [this, channel = getChannelName(observer)](const CVariant ¶m, CPromise reply) + { + reply.chainResult(handleRequest(param, channel)); + }); + } + + void CDataLinkLocal::dispatchEvent(const CVariant ¶m, const QString &channel) + { + for (const auto &observerWeak : as_const(getChannel(channel).passiveObservers)) + { + auto observer = observerWeak.lock(); + if (observer && observer->eventSubscription().matches(param)) + { + observer->handleEvent(param); + } + } + } + + QFuture CDataLinkLocal::handleRequest(const CVariant ¶m, const QString &channel) + { + auto mutator = getChannel(channel).activeMutator.lock(); + if (mutator) { return mutator->handleRequest(param); } + return {}; + } + + CDataLinkLocal::Channel &CDataLinkLocal::getChannel(const QString &name) + { + QMutexLocker lock(&m_channelsMutex); + return m_channels[name]; + } + + CDataLinkLocal::Channel &CDataLinkLocal::getChannel(const QObject *object) + { + return getChannel(getChannelName(object)); + } + } +} diff --git a/src/blackmisc/sharedstate/datalinklocal.h b/src/blackmisc/sharedstate/datalinklocal.h new file mode 100644 index 000000000..078544061 --- /dev/null +++ b/src/blackmisc/sharedstate/datalinklocal.h @@ -0,0 +1,64 @@ +/* Copyright (C) 2017 + * swift Project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKMISC_SHAREDSTATE_DATALINKLOCAL_H +#define BLACKMISC_SHAREDSTATE_DATALINKLOCAL_H + +#include "blackmisc/sharedstate/datalink.h" +#include "blackmisc/variant.h" +#include "blackmisc/blackmiscexport.h" +#include +#include +#include + +namespace BlackMisc +{ + namespace SharedState + { + /*! + * A transport mechanism using signals and slots in the local process. + * \ingroup SharedState + */ + class BLACKMISC_EXPORT CDataLinkLocal : public QObject, public IDataLink + { + Q_OBJECT + Q_INTERFACES(BlackMisc::SharedState::IDataLink) + + public: + //! Constructor. + CDataLinkLocal(QObject *parent = nullptr); + + //! Destructor. + virtual ~CDataLinkLocal() override; + + virtual void publish(const CPassiveMutator *mutator) override; + virtual void publish(const CActiveMutator *mutator) override; + virtual void subscribe(const CPassiveObserver *observer) override; + virtual void subscribe(const CActiveObserver *observer) override; + + private: + struct Channel + { + QWeakPointer activeMutator; + QVector> passiveObservers; + }; + + void dispatchEvent(const CVariant ¶m, const QString &channel); + QFuture handleRequest(const CVariant ¶m, const QString &channel); + Channel &getChannel(const QString &name); + Channel &getChannel(const QObject *object); + + QMap m_channels; + mutable QMutex m_channelsMutex { QMutex::Recursive }; + }; + } +} + +#endif