fabric/moduleapi.h

fabric/moduleapi.h

fabric/moduleapi.h

Namespaces

Name
Syntalos

Classes

Name
classSyntalos::ModuleInfo
Static information about a module.
structSyntalos::TestSubject
The TestSubject struct Data about a test subject.
classSyntalos::AbstractStreamPort
classSyntalos::VarStreamInputPort
classSyntalos::StreamInputPort
classSyntalos::StreamOutputPort
classSyntalos::AbstractModule
Abstract base class for all modules.

Defines

Name
SYNTALOS_DECLARE_MODULE
SYNTALOS_MODULE(MI)

Macros Documentation

define SYNTALOS_DECLARE_MODULE

#define SYNTALOS_DECLARE_MODULE     _Pragma("GCC visibility push(default)") extern "C" ModuleInfo *syntalos_module_info(); \
    extern "C" const char *syntalos_module_api_id();                                       \
    _Pragma("GCC visibility pop")

Define interfaces for a dynamically loaded Syntalos module, so we can find it at runtime.

define SYNTALOS_MODULE

#define SYNTALOS_MODULE(
    MI
)
ModuleInfo *syntalos_module_info()   \
    {                                    \
        return new MI##Info;             \
    }                                    \
    const char *syntalos_module_api_id() \
    {                                    \
        return SY_MODULE_API_TAG;        \
    }

Define interfaces for a dynamically loaded Syntalos module, so we can find it at runtime.

Source code

/*
 * Copyright (C) 2016-2024 Matthias Klumpp <matthias@tenstral.net>
 *
 * Licensed under the GNU Lesser General Public License Version 3
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the license, or
 * (at your option) any later version.
 *
 * This software 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 software.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef MODULEAPI_H
#define MODULEAPI_H

#include <QByteArray>
#include <QDebug>
#include <QList>
#include <QObject>
#include <QIcon>

#include "modconfig.h"
#include "optionalwaitcondition.h"
#include "streams/stream.h"
#include "datactl/datatypes.h"
#include "datactl/edlstorage.h"
#include "datactl/syclock.h"
#include "datactl/timesync.h"

namespace Syntalos
{

class AbstractModule;
class StreamOutputPort;

enum class ModuleFeature {
    NONE = 0,
    SHOW_SETTINGS = 1 << 0,  
    SHOW_DISPLAY = 1 << 1,   
    REALTIME = 1 << 2,       
    CALL_UI_EVENTS = 1 << 3, 
    REQUEST_CPU_AFFINITY =
        1 << 4, 
    PROHIBIT_CPU_AFFINITY = 1
                            << 5 
};
Q_DECLARE_FLAGS(ModuleFeatures, ModuleFeature)
Q_DECLARE_OPERATORS_FOR_FLAGS(ModuleFeatures)


enum class ModuleDriverKind {
    NONE,             
    THREAD_DEDICATED, 
    EVENTS_DEDICATED, 
    EVENTS_SHARED     
};

enum class UsbHotplugEventKind {
    NONE,           
    DEVICE_ARRIVED, 
    DEVICE_LEFT,    
    DEVICES_CHANGE  
};

enum class ModuleCategory : uint32_t {
    NONE = 0,              
    SYNTALOS_DEV = 1 << 0, 
    EXAMPLES = 1 << 1,     
    DEVICES = 1 << 2,      
    GENERATORS = 1 << 3,   
    SCRIPTING = 1 << 4,    
    DISPLAY = 1 << 5,      
    WRITERS = 1 << 6,      
    PROCESSING = 1 << 7    
};
Q_DECLARE_FLAGS(ModuleCategories, ModuleCategory)
Q_DECLARE_OPERATORS_FOR_FLAGS(ModuleCategories)

QString toString(ModuleCategory category);
ModuleCategory moduleCategoryFromString(const QString &categoryStr);
ModuleCategories moduleCategoriesFromString(const QString &categoriesStr);

enum class ModuleModifier {
    NONE = 0,
    ENABLED = 1 << 0,         
    STOP_ON_FAILURE = 1 << 1, 
};
Q_DECLARE_FLAGS(ModuleModifiers, ModuleModifier)
Q_DECLARE_OPERATORS_FOR_FLAGS(ModuleModifiers)

inline uint qHash(ModuleModifier key, uint seed)
{
    return ::qHash(static_cast<int>(key), seed);
}

class Q_DECL_EXPORT ModuleInfo
{
    friend class Engine;

public:
    explicit ModuleInfo();
    virtual ~ModuleInfo();

    virtual QString id() const;

    virtual QString name() const;

    virtual QString summary() const;

    virtual QString description() const;

    virtual QString authors() const;

    virtual QString license() const;

    virtual QIcon icon() const;

    virtual void refreshIcon();

    virtual QColor color() const;

    virtual ModuleCategories categories() const;

    virtual QString storageGroupName() const;

    virtual bool singleton() const;

    virtual AbstractModule *createModule(QObject *parent = nullptr) = 0;

    int count() const;

    QString rootDir() const;
    void setRootDir(const QString &dir);

protected:
    void setIcon(const QIcon &icon);

private:
    Q_DISABLE_COPY(ModuleInfo)
    class Private;
    QScopedPointer<Private> d;

    void setCount(int count);
};

#define SYNTALOS_DECLARE_MODULE                                                            \
    _Pragma("GCC visibility push(default)") extern "C" ModuleInfo *syntalos_module_info(); \
    extern "C" const char *syntalos_module_api_id();                                       \
    _Pragma("GCC visibility pop")

#define SYNTALOS_MODULE(MI)              \
    ModuleInfo *syntalos_module_info()   \
    {                                    \
        return new MI##Info;             \
    }                                    \
    const char *syntalos_module_api_id() \
    {                                    \
        return SY_MODULE_API_TAG;        \
    }

struct Q_DECL_EXPORT TestSubject {
    QString id;
    QString group;
    bool active;
    QString comment;
    QVariant data;
};

enum class PortDirection {
    NONE,
    INPUT,
    OUTPUT
};

class Q_DECL_EXPORT AbstractStreamPort
{
public:
    virtual ~AbstractStreamPort() = default;

    virtual QString id() const = 0;
    virtual QString title() const = 0;

    virtual PortDirection direction() const
    {
        return PortDirection::NONE;
    }

    virtual int dataTypeId() const = 0;
    virtual QString dataTypeName() const = 0;

    virtual AbstractModule *owner() const = 0;
};

class Q_DECL_EXPORT VarStreamInputPort : public AbstractStreamPort
{
public:
    explicit VarStreamInputPort(AbstractModule *owner, const QString &id, const QString &title);
    virtual ~VarStreamInputPort();

    virtual bool acceptsSubscription(const QString &typeName) = 0;
    bool hasSubscription() const;
    void setSubscription(StreamOutputPort *src, std::shared_ptr<VariantStreamSubscription> sub);
    void resetSubscription();
    StreamOutputPort *outPort() const;

    std::shared_ptr<VariantStreamSubscription> subscriptionVar();

    QString id() const override;
    QString title() const override;
    PortDirection direction() const override;
    AbstractModule *owner() const override;

protected:
    std::optional<std::shared_ptr<VariantStreamSubscription>> m_sub;

private:
    Q_DISABLE_COPY(VarStreamInputPort)
    class Private;
    QScopedPointer<Private> d;
};

template<typename T>
class StreamInputPort : public VarStreamInputPort
{
public:
    explicit StreamInputPort(AbstractModule *owner, const QString &id, const QString &title)
        : VarStreamInputPort(owner, id, title)
    {
        m_acceptedTypeId = syDataTypeId<T>();
        m_acceptedTypeName = BaseDataType::typeIdToString(m_acceptedTypeId);
    }

    std::shared_ptr<StreamSubscription<T>> subscription()
    {
        auto sub = std::dynamic_pointer_cast<StreamSubscription<T>>(m_sub.value());
        if (sub == nullptr) {
            if (hasSubscription()) {
                qCritical().noquote() << "Conversion of variant subscription to dedicated type" << typeid(T).name()
                                      << "failed."
                                      << "Modules are connected in a way they shouldn't be (terminating now).";
                qFatal("Bad module connection.");
                assert(0);
            } else {
                qWarning().noquote() << "Tried to obtain" << typeid(T).name()
                                     << "subscription from a port that was not subscribed to anything.";
            }
        }
        return sub;
    }

    int dataTypeId() const override
    {
        return m_acceptedTypeId;
    }

    QString dataTypeName() const override
    {
        return m_acceptedTypeName;
    }

    bool acceptsSubscription(const QString &typeName) override
    {
        return m_acceptedTypeName == typeName;
    }

private:
    int m_acceptedTypeId;
    QString m_acceptedTypeName;
};

class Q_DECL_EXPORT StreamOutputPort : public AbstractStreamPort
{
public:
    explicit StreamOutputPort(
        AbstractModule *owner,
        const QString &id,
        const QString &title,
        std::shared_ptr<VariantDataStream> stream);
    virtual ~StreamOutputPort();

    bool canSubscribe(const QString &typeName);
    int dataTypeId() const override;
    QString dataTypeName() const override;

    template<typename T>
    std::shared_ptr<DataStream<T>> stream()
    {
        return std::dynamic_pointer_cast<DataStream<T>>(streamVar());
    }
    std::shared_ptr<VariantDataStream> streamVar();

    std::shared_ptr<VariantStreamSubscription> subscribe();

    void startStream();
    void stopStream();

    QString id() const override;
    QString title() const override;
    PortDirection direction() const override;
    AbstractModule *owner() const override;

private:
    Q_DISABLE_COPY(StreamOutputPort)
    class Private;
    QScopedPointer<Private> d;
};

VariantDataStream *newStreamForType(int typeId);

VarStreamInputPort *newInputPortForType(int typeId, AbstractModule *mod, const QString &id, const QString &title);

using intervalEventFunc_t = void (AbstractModule::*)(int &);

using recvDataEventFunc_t = void (AbstractModule::*)();

class Q_DECL_EXPORT AbstractModule : public QObject
{
    Q_OBJECT
    friend class Engine;
    friend class MLinkModule;

public:
    explicit AbstractModule(QObject *parent = nullptr);
    explicit AbstractModule(const QString &id, QObject *parent = nullptr);
    virtual ~AbstractModule();

    ModuleState state() const;

    void setStateDormant();

    void setStateReady();

    QString id() const;

    int index() const;

    virtual QString name() const;
    virtual void setName(const QString &name);

    virtual ModuleDriverKind driver() const;

    virtual ModuleFeatures features() const;

    template<typename T>
        requires std::is_base_of_v<BaseDataType, T>
    std::shared_ptr<DataStream<T>> registerOutputPort(const QString &id, const QString &title = QString())
    {
        if (m_outPorts.contains(id)) {
            qWarning().noquote() << "Module" << name() << "already registered an output port with ID:" << id;
            return m_outPorts[id]->stream<T>();
        }

        std::shared_ptr<DataStream<T>> stream(new DataStream<T>());
        std::shared_ptr<StreamOutputPort> outPort(new StreamOutputPort(this, id, title, stream));
        stream->setCommonMetadata(this->id(), name(), title);
        m_outPorts.insert(id, outPort);

        Q_EMIT portConfigurationUpdated();
        return stream;
    }

    template<typename T>
        requires std::is_base_of_v<BaseDataType, T>
    std::shared_ptr<StreamInputPort<T>> registerInputPort(const QString &id, const QString &title = QString())
    {
        if (m_inPorts.contains(id)) {
            qWarning().noquote() << "Module" << name() << "already registered an input port with ID:" << id;
            return std::dynamic_pointer_cast<StreamInputPort<T>>(m_inPorts[id]);
        }

        std::shared_ptr<StreamInputPort<T>> inPort(new StreamInputPort<T>(this, id, title));
        m_inPorts.insert(id, inPort);

        Q_EMIT portConfigurationUpdated();
        return inPort;
    }

    std::shared_ptr<VariantDataStream> registerOutputPortByTypeId(
        int typeId,
        const QString &id,
        const QString &title = QString())
    {
        if (m_outPorts.contains(id)) {
            qWarning().noquote() << "Module" << name() << "already registered an output port with ID:" << id;
            return m_outPorts[id]->streamVar();
        }

        auto varStream = newStreamForType(typeId);
        if (varStream == nullptr)
            return nullptr;

        std::shared_ptr<VariantDataStream> stream(varStream);
        std::shared_ptr<StreamOutputPort> outPort(new StreamOutputPort(this, id, title, stream));
        stream->setCommonMetadata(this->id(), name(), title);
        m_outPorts.insert(id, outPort);
        Q_EMIT portConfigurationUpdated();
        return stream;
    }

    std::shared_ptr<VarStreamInputPort> registerInputPortByTypeId(
        int typeId,
        const QString &id,
        const QString &title = QString())
    {
        if (m_inPorts.contains(id)) {
            qWarning().noquote() << "Module" << name() << "already registered an input port with ID:" << id;
            return m_inPorts[id];
        }

        auto varInPort = newInputPortForType(typeId, this, id, title);
        if (varInPort == nullptr)
            return nullptr;

        std::shared_ptr<VarStreamInputPort> inPort(varInPort);
        m_inPorts.insert(id, inPort);
        Q_EMIT portConfigurationUpdated();
        return inPort;
    }

    virtual bool initialize();

    virtual bool prepare(const TestSubject &testSubject) = 0;

    virtual void start();

    virtual void runThread(OptionalWaitCondition *startWaitCondition);

    virtual void processUiEvents();

    virtual void stop();

    virtual void finalize();

    virtual void showDisplayUi();
    virtual bool isDisplayUiVisible();

    virtual void showSettingsUi();
    virtual bool isSettingsUiVisible();

    virtual void hideDisplayUi();

    virtual void hideSettingsUi();

    virtual void serializeSettings(const QString &confBaseDir, QVariantHash &settings, QByteArray &extraData);

    virtual bool loadSettings(const QString &confBaseDir, const QVariantHash &settings, const QByteArray &extraData);

    virtual void inputPortConnected(VarStreamInputPort *inPort);

    virtual void updateStartWaitCondition(OptionalWaitCondition *waitCondition);

    QString lastError() const;

    QString moduleRootDir() const;

    void setEventsMaxModulesPerThread(int maxModuleCount);
    int eventsMaxModulesPerThread() const;

    void clearInPorts();
    void clearOutPorts();

    void removeInPortById(const QString &id);
    void removeOutPortById(const QString &id);

    QList<std::shared_ptr<VarStreamInputPort>> inPorts() const;
    QList<std::shared_ptr<StreamOutputPort>> outPorts() const;

    std::shared_ptr<VarStreamInputPort> inPortById(const QString &id) const;
    std::shared_ptr<StreamOutputPort> outPortById(const QString &id) const;

    QList<QPair<intervalEventFunc_t, int>> intervalEventCallbacks() const;
    QList<QPair<recvDataEventFunc_t, std::shared_ptr<VariantStreamSubscription>>> recvDataEventCallbacks() const;

    QVariant serializeDisplayUiGeometry();
    void restoreDisplayUiGeometry(const QVariant &var);

    void setTimer(std::shared_ptr<SyncTimer> timer);

    ModuleModifiers modifiers() const;
    void setModifiers(ModuleModifiers modifiers);

Q_SIGNALS:
    void stateChanged(ModuleState state);
    void error(const QString &message);
    void statusMessage(const QString &message);
    void nameChanged(const QString &name);
    void portsConnected(const VarStreamInputPort *inPort, const StreamOutputPort *outPort);
    void portConfigurationUpdated();
    void synchronizerDetailsChanged(
        const QString &id,
        const Syntalos::TimeSyncStrategies &strategies,
        const Syntalos::microseconds_t &tolerance);
    void synchronizerOffsetChanged(const QString &id, const Syntalos::microseconds_t &currentOffset);
    void modifiersUpdated();

protected:
    void raiseError(const QString &message);

    void setStatusMessage(const QString &message);
    bool makeDirectory(const QString &dir);
    void appProcessEvents();

    QString datasetNameSuggestion(bool lowercase = true) const;

    QString datasetNameFromSubMetadata(const QVariantHash &subMetadata) const;

    QString datasetNameFromParameters(const QString &preferredName, const QVariantHash &subMetadata) const;

    QString dataBasenameFromSubMetadata(
        const QVariantHash &subMetadata,
        const QString &defaultName = QStringLiteral("data"));

    std::shared_ptr<EDLDataset> createDefaultDataset(
        const QString &preferredName = QString(),
        const QVariantHash &subMetadata = QVariantHash());
    std::shared_ptr<EDLDataset> createDatasetInGroup(
        std::shared_ptr<EDLGroup> group,
        const QString &preferredName = QString(),
        const QVariantHash &subMetadata = QVariantHash());

    std::shared_ptr<EDLDataset> getDefaultDataset();
    std::shared_ptr<EDLDataset> getDatasetInGroup(
        std::shared_ptr<EDLGroup> group,
        const QString &preferredName = QString(),
        const QVariantHash &subMetadata = QVariantHash());

    std::shared_ptr<EDLGroup> createStorageGroup(const QString &groupName);

    QWidget *addDisplayWindow(QWidget *window, bool owned = true);

    QWidget *addSettingsWindow(QWidget *window, bool owned = true);

    template<typename T>
    void registerTimedEvent(void (T::*fn)(int &), const milliseconds_t &interval)
    {
        static_assert(
            std::is_base_of<AbstractModule, T>::value,
            "Callback needs to point to a member function of a class derived from AbstractModule");
        const auto amFn = static_cast<intervalEventFunc_t>(fn);
        m_intervalEventCBList.append(qMakePair(amFn, interval.count()));
    }

    template<typename T>
    void registerDataReceivedEvent(void (T::*fn)(), std::shared_ptr<VariantStreamSubscription> subscription)
    {
        static_assert(
            std::is_base_of<AbstractModule, T>::value,
            "Callback needs to point to a member function of a class derived from AbstractModule");
        const auto amFn = static_cast<recvDataEventFunc_t>(fn);
        m_recvDataEventCBList.append(qMakePair(amFn, subscription));
    }

    void clearDataReceivedEventRegistrations();

    std::unique_ptr<FreqCounterSynchronizer> initCounterSynchronizer(double frequencyHz);

    std::unique_ptr<SecondaryClockSynchronizer> initClockSynchronizer(double expectedFrequencyHz = 0);

    uint potentialNoaffinityCPUCount() const;

    int defaultRealtimePriority() const;

    bool isEphemeralRun() const;

    virtual void usbHotplugEvent(UsbHotplugEventKind kind);

    void setInitialized();
    bool initialized() const;

    std::atomic_bool m_running;

    std::shared_ptr<SyncTimer> m_syTimer;

private:
    Q_DISABLE_COPY(AbstractModule)
    class Private;
    std::unique_ptr<Private> d;

    QMap<QString, std::shared_ptr<StreamOutputPort>> m_outPorts;
    QMap<QString, std::shared_ptr<VarStreamInputPort>> m_inPorts;

    QList<QPair<intervalEventFunc_t, int>> m_intervalEventCBList;
    QList<QPair<recvDataEventFunc_t, std::shared_ptr<VariantStreamSubscription>>> m_recvDataEventCBList;

    void setState(ModuleState state);
    void setId(const QString &id);
    void setIndex(int index);
    void setSimpleStorageNames(bool enabled);
    void setStorageGroup(std::shared_ptr<EDLGroup> edlGroup);
    void resetEventCallbacks();
    void setPotentialNoaffinityCPUCount(uint coreN);
    void setDefaultRTPriority(int prio);
    void setEphemeralRun(bool isEphemeral);
};

} // namespace Syntalos

#endif // MODULEAPI_H

Updated on 2024-11-06 at 17:10:29 +0000