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("media")).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("media"))" target="stopped"/>
<transition type="internal" event="tap" cond="isValidMedia() && media != eventData().value(QStringLiteral("media"))" 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("media")).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++代碼,因此具有最高的效率,因爲可以隨意使用類成員變量及函數,因此具備最完備與外部系統的交互能力。通過QScxmlStateMachine
的setDataModel
方法將外部創建的數據對象置入到狀態機中,只能置入一次,一旦置入就不可再修改。c++數據模型先天編譯型,因此無法用在動態加載的情況,也就是運行時加載scxml文件的情況。