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("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(&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文件的情况。

你可能感兴趣的:(Qt,状态机)