QtScxml學習筆記-數據模型的理解

dataModel

狀態機的數據模型,決定狀態機可以配置什麼樣的數據,可以使用哪些與數據有關的表達式和元素。

數據模型提供了狀態機存儲、讀取和修改內部數據集的能力。特定的數據模型定義了一組表達式,表達式用於引用到數據模型的位置、計算值或給數據模型賦值,也可產生布爾條件。另外,數據集也包含了一組系統變量,這些系統變量由處理器自動持有。

<datamodel>
	<data id="mydata"/>
</datamodel>

標準附錄描述了二種數據模型:

  • 空數據模型

  • 腳本數據模型

Qt定義了 一種數據模型:

  • c++數據模型

空數據模型

空數據模型只支持in(id)條件判斷,確定是否處理某種狀態,其他操作都是無效的,包括系統變量也是無法訪問的.

腳本數據模型

ecmascript數據模型是在狀態裏按ecmascript的方式定義數據元素,並使用ecmascript腳本實現scritp\cond\expr等功能.

c++數據模型

這種數據模型是直接使用自定義的一個c++類,並在狀態機初始化時使用外部的對象。在狀態機中腳本或表達式裏就可以直接寫c++代碼來直接訪問c++對象。c++數據模型只能使用預編譯的模式,不能動態加載,這裏因爲只有對scxml文件預編譯才能把scxml文件中的c++代碼轉換爲實際源文件中的生成c++代碼。

看一下mediaplayer-qml-cppdatamodel示例。

c++模型聲明

在狀態機中聲明c++數據類型,指定類名和聲明文件

<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" binding="early" name="MediaPlayerStateMachine" datamodel="cplusplus:TheDataModel:thedatamodel.h" qt:editorversion="4.8.2" xmlns:qt="http://www.qt.io/2015/02/scxml-ext" initial="stopped">

其中datamodel="cplusplus:TheDataModel:thedatamodel.h"是數據類型的定義部分。

首先必須自定義一個類,這個類從QScxmlCppDataModel繼承,並加上Q_OBJECT和一個特殊的宏Q_SCXML_DATAMODEL,並實現自己的其他自定義部分。

自定義c++模型類

文件thedatamodel.h:

#ifndef THEDATAMODEL_H
#define THEDATAMODEL_H

#include "qscxmlcppdatamodel.h"

class TheDataModel: public QScxmlCppDataModel
{
    Q_OBJECT
    Q_SCXML_DATAMODEL

private:
    bool isValidMedia() const;
    QVariantMap eventData() const;

    QString media;
};

#endif // THEDATAMODEL_H

Q_SCXML_DATAMODEL聲明瞭幾個公開函數:

#define Q_SCXML_DATAMODEL \
    public: \
        QString evaluateToString(QScxmlExecutableContent::EvaluatorId id, bool *ok) Q_DECL_OVERRIDE Q_DECL_FINAL; \
        bool evaluateToBool(QScxmlExecutableContent::EvaluatorId id, bool *ok) Q_DECL_OVERRIDE Q_DECL_FINAL; \
        QVariant evaluateToVariant(QScxmlExecutableContent::EvaluatorId id, bool *ok) Q_DECL_OVERRIDE Q_DECL_FINAL; \
        void evaluateToVoid(QScxmlExecutableContent::EvaluatorId id, bool *ok) Q_DECL_OVERRIDE Q_DECL_FINAL; \
    private:

這幾個公開函數由scxmlc編譯去實現,它是通過爲scxml文件中script、cond、 expr等中定義的c++代碼生成相應的邏輯代碼,這些函數在編譯生成的狀態機的cpp文件中。這些函數會由狀態機在執行到對應的位置時自動調用。

c++腳本代碼

腳本script中的c++代碼,注意引號被轉義:


<onentry>
    <script>media = eventData().value(QStringLiteral(&quot;media&quot;)).toString();</script>
    <send event="playbackStarted">
        <param name="media" expr="media"/>
    </send>
</onentry>

上述腳本將會轉換爲一段c++代碼

void TheDataModel::evaluateToVoid(QScxmlExecutableContent::EvaluatorId id, bool *ok)
{
    *ok = true;
    switch (id) {
    case 1:
        [this]()->void{ media = eventData().value(QStringLiteral("media")).toString(); }();
        return;
    default: break;
    }
    Q_UNREACHABLE();
    *ok = false;
}

注意,腳本中的代碼被放進了一個lamada表達式中,由於沒有返回值,因此被放到了evaluateToVoid函數中,這段腳本的EvaluatorId被scxmlc編譯成了1。

c++條件表達式

條件cond中的c++代碼,引號同樣被轉義:

<transition type="internal" event="tap" cond="!isValidMedia() || media == eventData().value(QStringLiteral(&quot;media&quot;))" target="stopped"/>
<transition type="internal" event="tap" cond="isValidMedia() &amp;&amp; media != eventData().value(QStringLiteral(&quot;media&quot;))" target="playing"/>

這裏因爲是條件表達式,求值方法爲evaluateToBool,這些表達式代碼放在了這個函數裏,並且表達式代碼同樣被放進了lamada表達式。這兩個表達式的求值器id被編譯成了4和5。

bool TheDataModel::evaluateToBool(QScxmlExecutableContent::EvaluatorId id, bool *ok)
{
    *ok = true;
    switch (id) {
    case 4:
        return [this]()->bool{ return !isValidMedia() || media == eventData().value(QStringLiteral("media")); }();
    case 5:
        return [this]()->bool{ return isValidMedia() && media != eventData().value(QStringLiteral("media")); }();
    case 0:
        return [this]()->bool{ return isValidMedia(); }();
    default: break;
    }
    Q_UNREACHABLE();
    *ok = false;
    return false;
}

腳本代碼或條件表達式爲什麼要放進lamada表達式呢?布爾表達式,應該是可以不需要的。大概是爲了實現方便,統一這樣處理,因爲script元素裏的腳本代碼可能是一條或多條代碼,並不一定是個表達式,所以就封裝進一個lamada表達式,實際上也不一定要這樣封裝,用一個大括號也行吧?哪位知道一定要這麼做的理由?

c++數值表達式

而非條件表達式被轉換爲函數evaluateToVariant

scxml中的表達式:

<onentry>
    <script>media = eventData().value(QStringLiteral(&quot;media&quot;)).toString();</script>
    <send event="playbackStarted">
        <param name="media" expr="media"/>
    </send>
</onentry>
<onexit>
    <send event="playbackStopped">
        <param name="media" expr="media"/>
    </send>
</onexit>

scxmlc生成的c++代碼:

QVariant TheDataModel::evaluateToVariant(QScxmlExecutableContent::EvaluatorId id, bool *ok)
{
    *ok = true;
    switch (id) {
    case 2:
        return [this]()->QVariant{ return media; }();
    case 3:
        return [this]()->QVariant{ return media; }();
    default: break;
    }
    Q_UNREACHABLE();
    *ok = false;
    return QVariant();
}

scxml中出現兩次expr="media",scxmlc爲它們各自生成了一個id, 2和3。所有的代碼塊,包括script、cond和expr中定義的都被統一生成了一個唯一的EvaluatorId。

c++數據模型對象

c++數據模型是scxml的新特性,取代了先前的registerObject方案。由於這種數據模型是通過編譯生成c++代碼,因此具有最高的效率,因爲可以隨意使用類成員變量及函數,因此具備最完備與外部系統的交互能力。通過QScxmlStateMachinesetDataModel方法將外部創建的數據對象置入到狀態機中,只能置入一次,一旦置入就不可再修改。c++數據模型先天編譯型,因此無法用在動態加載的情況,也就是運行時加載scxml文件的情況。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章