From 7a19605fcc176c03563016836f208c88c1971987 Mon Sep 17 00:00:00 2001 From: No'am Rosenthal Date: Thu, 26 Nov 2009 14:37:34 -0800 Subject: many fixes and optimizations to SCXML --- examples/blackjack/blackjack.scxml | 19 ++-- examples/calc/calc.scxml | 226 ++++++++++++++++--------------------- scc/scc.xslt | 2 +- src/qscxml.cpp | 183 ++++++++++++++++++++++-------- src/qscxml.h | 21 ++-- src/qscxml.pri | 8 +- 6 files changed, 259 insertions(+), 200 deletions(-) diff --git a/examples/blackjack/blackjack.scxml b/examples/blackjack/blackjack.scxml index 4c73cd4..5362546 100644 --- a/examples/blackjack/blackjack.scxml +++ b/examples/blackjack/blackjack.scxml @@ -197,7 +197,7 @@ - + @@ -235,20 +235,18 @@ - + - - - + + + [[welcomeLabel,"text","Game Over"]] - + @@ -256,9 +254,8 @@ - + - - - 0 - + + 0 + - + - - - - - - - - - - - + + + + + + + + + + + - + - + - + - - + + - + - + - - + + - + - + - + - + - + - + - - + + - + - + - + diff --git a/scc/scc.xslt b/scc/scc.xslt index 1f5fc20..1c35319 100644 --- a/scc/scc.xslt +++ b/scc/scc.xslt @@ -274,7 +274,7 @@ class SMClass_ : public QStateMachine { if (event && !event->type() == QEvent::None) { switch (event->type()) { - case QEvent::Signal: { + case QEvent::StateMachineSignal: { QStateMachine::SignalEvent* e = (QStateMachine::SignalEvent*)event; _event.data = e->arguments(); _event.name = e->sender()->metaObject()->method(e->signalIndex()).signature(); diff --git a/src/qscxml.cpp b/src/qscxml.cpp index fd524ee..ec7eb5c 100644 --- a/src/qscxml.cpp +++ b/src/qscxml.cpp @@ -37,9 +37,7 @@ #include #include #include -#ifdef QT_GUI_LIB -#include "qscxmlgui.h" -#endif +#include @@ -168,6 +166,8 @@ class QScxmlPrivate void initScriptEngine(QScxml* thiz); + QScriptValue dataObj; + QScriptEngine* scriptEng; QList invokerFactories; @@ -175,6 +175,8 @@ class QScxmlPrivate QString sessionID; QString startScript; + QSet knownEvents; + QStack snapshotStack; QMultiHash anchorTransitions; @@ -189,22 +191,25 @@ class QScxmlTimer : public QObject { Q_OBJECT public: - QScxmlTimer(QScriptEngine* engine, const QScriptValue & scr, int delay) : QObject(engine),script(scr) + QScxmlTimer(QScriptEngine* engine, const QScriptValue & scr, int delay) : QObject(engine) { QTimer::singleShot(delay,this,SLOT(exec())); + if (scr.isString()) { + script = engine->evaluate(QString("function(){%1}").arg(scr.toString())); + } else + script = scr; } protected Q_SLOTS: void exec() { + if (script.isFunction()) script.call(); - else if (script.isString()) - script.engine()->evaluate(script.toString()); deleteLater(); } private: - QScriptValue script; + QScriptValue script; }; @@ -418,6 +423,23 @@ static QScriptValue invoke(QScriptContext *context, QScriptEngine *engine) return QScriptValue(); } +static QScriptValue dataAccess(QScriptContext *context, QScriptEngine *) +{ + if (context->argumentCount() == 0) { + // getter + return context->callee().property("value"); + } else if (context->argumentCount() == 1) { + // setter + QScriptValue val = context->argument(0); + context->callee().setProperty("value",val); + QScxml* scxml = qobject_cast(context->callee().property("scxml").toQObject()); + if (scxml) { + scxml->dataChanged(context->callee().property("key").toString(),val.toVariant()); + } + return val; + } else + return QScriptValue(); +} static QScriptValue isInState(QScriptContext *context, QScriptEngine *engine) { @@ -438,6 +460,8 @@ static QScriptValue isInState(QScriptContext *context, QScriptEngine *engine) } + + }; void QScxmlPrivate::initScriptEngine(QScxml* thiz) @@ -445,7 +469,6 @@ void QScxmlPrivate::initScriptEngine(QScxml* thiz) QScriptValue glob = scriptEng->globalObject(); QScriptValue scxmlObj = scriptEng->newQObject(thiz); glob.setProperty("In",scriptEng->newFunction(QScxmlFunctions::isInState)); -// glob.setProperty("_rcvSig",scriptEng->newFunction(QScxmlFunctions::receiveSignal)); scxmlObj.setProperty("print",scriptEng->newFunction(QScxmlFunctions::script_print)); scxmlObj.setProperty("postEvent",scriptEng->newFunction(QScxmlFunctions::postEvent)); scxmlObj.setProperty("invoke",scriptEng->newFunction(QScxmlFunctions::invoke)); @@ -455,7 +478,9 @@ void QScxmlPrivate::initScriptEngine(QScxml* thiz) scxmlObj.setProperty("clearTimeout",scriptEng->newFunction(QScxmlFunctions::clearTimeout)); scxmlObj.setProperty("connectSignalToEvent",scriptEng->newFunction(QScxmlFunctions::connectSignalToEvent)); QScriptValue dmObj = scriptEng->newObject(); - glob.setProperty("_data",scriptEng->newObject()); + dataObj = scriptEng->newObject(); + dataObj.setProperty("_values",scriptEng->newObject()); + glob.setProperty("_data",dataObj); glob.setProperty("_global",scriptEng->globalObject()); glob.setProperty("scxml",scxmlObj); } @@ -552,6 +577,16 @@ QScxmlTransition::QScxmlTransition (QState* state,QScxml* machine) : QAbstractTransition(state),scxml(machine) { } + +void QScxmlTransition::setConditionExpression(const QString & c) +{ + prog = QScriptProgram(c,scxml->baseUrl().toLocalFile()); +} + +QString QScxmlTransition::conditionExpression() const +{ + return prog.sourceCode(); +} /*! \reimp */ @@ -564,21 +599,26 @@ void QScxmlTransition::onTransition(QEvent*) bool QScxmlTransition::eventTest(QEvent *e) { QScriptEngine* engine = scxml->scriptEngine(); - QString ev; + QString event; if (e) { if (e->type() == QScxmlEvent::eventType()) { - ev = ((QScxmlEvent*)e)->eventName(); + event = ((QScxmlEvent*)e)->eventName(); + } + bool found = false; + for (int i=0; i < ev.size() && !found; ++i) { + QString prefix = ev[i]; + found = prefix=="*" || prefix==event || event.startsWith(prefix) + || (prefix.endsWith(".*") && event.startsWith(prefix.left(prefix.indexOf(".*")))) + ; } - if (!(eventPrefix() == "*" || eventPrefix() == ev || ev.startsWith(eventPrefix()+"."))) + if (!found ) return false; } if (!conditionExpression().isEmpty()) { - - - QScriptValue v = engine->evaluate(conditionExpression(),scxml->baseUrl().toLocalFile()); + QScriptValue v = engine->evaluate(prog); if (engine->hasUncaughtException()) { QScxmlEvent* e = new QScxmlEvent("error.illegalcond", @@ -588,7 +628,7 @@ bool QScxmlTransition::eventTest(QEvent *e) << QVariant(conditionExpression()) << QVariant(engine->uncaughtExceptionLineNumber()) << QVariant(engine->uncaughtExceptionBacktrace())); - qDebug() << engine->uncaughtException().toString(); + qDebug() << engine->uncaughtException().toString() << prog.sourceCode(); e->metaData.kind = QScxmlEvent::MetaData::Platform; scxml->postEvent(e); engine->clearExceptions(); @@ -732,12 +772,6 @@ void QScxml::init() connect(this,SIGNAL(started()),this,SLOT(registerSession())); connect(this,SIGNAL(stopped()),this,SLOT(unregisterSession())); pvt->initScriptEngine(this); -#ifdef QT_GUI_LIB - static QScxmlAutoInvokerFactory _s_menuInvokerFactory; - static QScxmlAutoInvokerFactory _s_msgboxInvokerFactory; - registerInvokerFactory(&_s_msgboxInvokerFactory); - registerInvokerFactory(&_s_menuInvokerFactory); -#endif } /*! Creates a new QScxml object, with parent \a parent. @@ -862,6 +896,8 @@ void QScxml::endMicrostep(QEvent*) pvt->snapshotStack.remove(0,pvt->snapshotStack.size()-100); } pvt->curSnapshot.clear(); + qDebug() << configuration(); + emit configurationChanged(); /* if (e->type() == QScxmlEvent::eventType()) { qDebug() << "\n" + _q_configToString(rootState(),-1,configuration()); @@ -907,30 +943,43 @@ void QScxml::postNamedEvent(const QString & event) e->metaData.kind = QScxmlEvent::MetaData::External; postEvent(e); } + +void QScxml::setData(const QString & id, const QVariant & val) +{ + QScriptValue accessor = pvt->dataObj.property(id); + if (accessor.isFunction()) { + accessor.setProperty("value",scriptEngine()->newVariant(val)); + } +} + /*! Executes script \a s in the attached script engine. If the script fails, a "error.illegalvalue" event is posted to the state machine. */ -void QScxml::executeScript (const QString & s) +void QScxml::executeScript (const QScriptProgram & s) { -// qDebug() << "Executing\n--------------------------\n"<scriptEng->evaluate (s,baseUrl().toLocalFile()); + qDebug() << "Executing\n--------------------------\n"<scriptEng->evaluate (s); if (pvt->scriptEng->hasUncaughtException()) { QScxmlEvent* e = new QScxmlEvent("error.illegalvalue", QStringList()<< "error" << "expr" << "line" << "backtrace", QVariantList() << QVariant(pvt->scriptEng->uncaughtException().toString()) - << QVariant(s) + << QVariant(s.sourceCode()) << QVariant(pvt->scriptEng->uncaughtExceptionLineNumber()) << QVariant(pvt->scriptEng->uncaughtExceptionBacktrace())); e->metaData.kind = QScxmlEvent::MetaData::Platform; - qDebug() << pvt->scriptEng->uncaughtException().toString(); + qDebug() << pvt->scriptEng->uncaughtException().toString() << s.sourceCode(); postEvent(e); pvt->scriptEng->clearExceptions(); } // qDebug() <<"\n--------------------\n"; } +void QScxml::executeScript (const QString & s) +{ + executeScript(QScriptProgram(s,baseUrl().toLocalFile())); +} /*! Enabled invoker factory \a f to be called from tags. @@ -1034,6 +1083,11 @@ void QScxml::setBaseUrl(const QUrl & u) pvt->burl = u; } +QStringList QScxml::knownEventNames() const +{ + return pvt->knownEvents.toList(); +} + void QScxml::registerSession() { pvt->sessionID = QUuid::createUuid().toString(); @@ -1073,16 +1127,17 @@ struct ScTransitionInfo class QScxmlScriptExec : public QObject { Q_OBJECT - QString script; + QScriptProgram prog; QScxml* scxml; public: - QScxmlScriptExec(const QString & scr, QScxml* scx) : script(scr),scxml(scx) + QScxmlScriptExec(const QString & scr, QScxml* scx) : + prog(QScriptProgram(scr,scx->baseUrl().toLocalFile())),scxml(scx) { } public Q_SLOTS: void exec() { - scxml->executeScript(script); + scxml->executeScript(prog); } }; @@ -1330,7 +1385,7 @@ void QScxmlLoader::loadState ( } else if (r.name().toString().compare("script",Qt::CaseInsensitive) == 0) { QString txt = r.readElementText().trimmed(); if (curExecContext.type == ScExecContext::None && curState == topLevelState) { - stateMachine->executeScript(txt); + stateMachine->executeScript(QScriptProgram(txt,stateMachine->baseUrl().toLocalFile())); } else curExecContext.script += txt; } else if (r.name().toString().compare("log",Qt::CaseInsensitive) == 0) { @@ -1365,7 +1420,9 @@ void QScxmlLoader::loadState ( curExecContext.type = ScExecContext::StateExit; curExecContext.script = ""; } else if (r.name().toString().compare("raise",Qt::CaseInsensitive) == 0 || r.name().toString().compare("event",Qt::CaseInsensitive) == 0 ) { - eventName = QString("\"%1\"").arg(r.attributes().value("event").toString()); + eventName = r.attributes().value("event").toString(); + stateMachine->pvt->knownEvents.insert(eventName); + eventName = QString("\"%1\"").arg(eventName); target = "'_internal'"; targetType = "scxml"; content = "{}"; @@ -1377,10 +1434,20 @@ void QScxmlLoader::loadState ( content = "{}"; target = r.attributes().value("target").toString(); - if (target == "") - target = "\"\""; + if (target == "") { + target = r.attributes().value("targetexpr").toString(); + if (target == "") + target = "\"\""; + } else + target = "\""+target+"\""; targetType = r.attributes().value("type").toString(); eventName = r.attributes().value("event").toString(); + if (eventName == "") { + eventName = r.attributes().value("eventexpr").toString(); + } else { + stateMachine->pvt->knownEvents.insert(eventName); + eventName = "'"+eventName+"'"; + } QStringList nameList = r.attributes().value("namelist").toString().split(" "); foreach (QString name,nameList) { if (name != "") { @@ -1393,10 +1460,14 @@ void QScxmlLoader::loadState ( idLocation = r.attributes().value("sendid").toString(); delay = r.attributes().value("delay").toString(); - if (delay == "") - delay = "0"; - else - delay = QString("scxml.cssTime(%1)").arg(delay); + if (delay == "") { + delay = r.attributes().value("delayexpr").toString(); + if (delay == "") + delay = "0"; + } else + delay = "'"+delay+"'"; + + delay = QString("scxml.cssTime(%1)").arg(delay); } else if (r.name().toString().compare("invoke",Qt::CaseInsensitive) == 0) { idLocation = r.attributes().value("idlocation").toString(); @@ -1429,7 +1500,11 @@ void QScxmlLoader::loadState ( curExecContext.script = ""; curTransition = new QScxmlTransition(curState,stateMachine); curTransition->setConditionExpression(r.attributes().value("cond").toString()); - curTransition->setEventPrefix(r.attributes().value("event").toString()); + curTransition->setEventPrefixes(r.attributes().value("event").toString().split(' ')); + foreach(QString pfx, curTransition->eventPrefixes()) { + if (pfx != "*") + stateMachine->pvt->knownEvents.insert(pfx); + } curExecContext.trans = curTransition; QString anc = r.attributes().value("anchor").toString(); if (!anc.isEmpty()) { @@ -1438,10 +1513,12 @@ void QScxmlLoader::loadState ( } inf.transition = curTransition; transitions.append(inf); - if (curTransition->eventPrefix().startsWith("q-signal:")) { - signalEvents.insert(curTransition->eventPrefix()); + foreach (QString prefix, curTransition->eventPrefixes()) { + if (prefix.startsWith("q-signal:")) { + signalEvents.insert(prefix); + } } - curTransition->setObjectName(QString ("%1 to %2 on %3 if %4 (anchor=%5)").arg(curState->objectName()).arg(inf.targets.join(" ")).arg(curTransition->eventPrefix()).arg(curTransition->conditionExpression()).arg(anc)); + curTransition->setObjectName(QString ("%1 to %2 on %3 if %4 (anchor=%5)").arg(curState->objectName()).arg(inf.targets.join(" ")).arg(curTransition->eventPrefixes().join(" ")).arg(curTransition->conditionExpression()).arg(anc)); } } else if (r.name().toString().compare("anchor",Qt::CaseInsensitive) == 0) { QObject::connect(curState,SIGNAL(exited()),new QScxmlAnchorSave(stateMachine,stateMachine->pvt,r.attributes().value("type").toString(),r.attributes().value("snapshot").toString(),curState),SLOT(save())); @@ -1459,8 +1536,11 @@ void QScxmlLoader::loadState ( val = stateMachine->scriptEngine()->evaluate(t); } } - stateMachine->scriptEngine()->evaluate("_data") - .setProperty(id,val); + QScriptValue func = stateMachine->scriptEngine()->newFunction(QScxmlFunctions::dataAccess); + func.setProperty("key",id); + qDebug() << id << val.toVariant(); + stateMachine->pvt->dataObj.setProperty(id,func,QScriptValue::PropertyGetter|QScriptValue::PropertySetter); + stateMachine->pvt->dataObj.setProperty(id,val); } else if (r.name().toString().compare("param",Qt::CaseInsensitive) == 0) { paramNames << r.attributes().value("name").toString(); paramVals << r.attributes().value("expr").toString(); @@ -1509,6 +1589,7 @@ void QScxmlLoader::loadState ( curExecContext.type = r.name().toString().compare("onexit",Qt::CaseInsensitive)==0 ? ScExecContext::StateExit : ScExecContext::StateEntry; curExecContext.applyScript(); curExecContext.type = ScExecContext::None; + curExecContext.script = ""; } else if (r.name().toString().compare("transition",Qt::CaseInsensitive) == 0) { if (!curHistoryState) { curExecContext.trans = curTransition; @@ -1544,6 +1625,16 @@ void QScxmlLoader::loadState ( } } +QMap QScxml::data() const +{ + QMap d; + QScriptValueIterator it (scriptEngine()->evaluate("_data")); + while (it.hasNext()) { + it.next(); + d[it.name()] = it.value().toVariant(); + } + return d; +} QScxml* QScxmlLoader::load(QIODevice* device, QObject* obj, const QString & filename) { @@ -1562,14 +1653,10 @@ QScxml* QScxmlLoader::load(QIODevice* device, QObject* obj, const QString & file foreach (QString s, signalEvents) { QString sig = s; sig = sig.mid(sig.indexOf(':')+1); -// sig = sig.left(sig.indexOf('(')); int liop = sig.lastIndexOf('.'); QString obj = sig.left(liop); sig = sig.mid(liop+1); stateMachine->pvt->startScript += QString("scxml.connectSignalToEvent(%1,'%2',\"%3\");").arg(obj).arg(sig).arg(s); - -// QString scr = QString("%1.connect({e:\"%2\"},_rcvSig);\n").arg(sig).arg(s); -/// stateMachine->pvt->startScript += scr; } foreach (QState* s, statesWithFinal) { diff --git a/src/qscxml.h b/src/qscxml.h index fc6b590..4cf4365 100644 --- a/src/qscxml.h +++ b/src/qscxml.h @@ -19,7 +19,7 @@ #include #include #include - +#include class QScriptEngine; class QScxml; @@ -60,22 +60,23 @@ class QScxmlTransition : public QAbstractTransition { Q_OBJECT Q_PROPERTY(QString conditionExpression READ conditionExpression WRITE setConditionExpression) - Q_PROPERTY(QString eventPrefix READ eventPrefix WRITE setEventPrefix) + Q_PROPERTY(QStringList eventPrefixes READ eventPrefixes WRITE setEventPrefixes) public: QScxmlTransition (QState* state, QScxml* machine); - QString conditionExpression () const { return cond; } - void setConditionExpression (const QString & c) { cond = c; } - QString eventPrefix () const { return ev; } - void setEventPrefix (const QString & e) { ev = e; } + QString conditionExpression () const; + void setConditionExpression (const QString & c); + QStringList eventPrefixes () const { return ev; } + void setEventPrefixes (const QStringList & e) { ev = e; } protected: bool eventTest(QEvent*); void onTransition (QEvent*); private: QScxml* scxml; - QString ev,cond; + QStringList ev; + QScriptProgram prog; }; class QScxmlInvoker : public QObject @@ -147,10 +148,14 @@ class QScxml : public QStateMachine void setBaseUrl (const QUrl &); QUrl baseUrl () const; static QScxml* load (const QString & filename, QObject* o = NULL); + QMap data() const; + QStringList knownEventNames() const; public Q_SLOTS: void postNamedEvent(const QString &); void executeScript (const QString &); + void executeScript (const QScriptProgram &); + void setData(const QString & id, const QVariant & value); private Q_SLOTS: void registerSession(); @@ -159,6 +164,8 @@ class QScxml : public QStateMachine Q_SIGNALS: void eventTriggered(const QString &); + void dataChanged (const QString &, const QVariant &); + void configurationChanged(); private: class QScxmlPrivate* pvt; diff --git a/src/qscxml.pri b/src/qscxml.pri index 873c038..97caa28 100644 --- a/src/qscxml.pri +++ b/src/qscxml.pri @@ -1,7 +1,5 @@ QT += script -SOURCES += $$PWD/qscxml.cpp \ - $$PWD/qscxmlgui.cpp +SOURCES += $$PWD/qscxml.cpp -HEADERS += $$PWD/qscxml.h \ - $$PWD/qscxmlgui.h -INCLUDEPATH += $$PWD \ No newline at end of file +HEADERS += $$PWD/qscxml.h +INCLUDEPATH += $$PWD -- cgit v1.2.3