From 1c8cf1b080c81c40bedc9d7c342555585fc394b0 Mon Sep 17 00:00:00 2001 From: Aaron Kennedy Date: Mon, 30 Nov 2009 16:25:37 +1000 Subject: Basic WorkerScript functionality --- examples/declarative/workerscript/workerscript.js | 5 + examples/declarative/workerscript/workerscript.qml | 41 ++ src/declarative/qml/qml.pri | 8 +- src/declarative/qml/qmlengine.cpp | 13 +- src/declarative/qml/qmlengine_p.h | 4 + src/declarative/qml/qmlworkerscript.cpp | 418 +++++++++++++++++++++ src/declarative/qml/qmlworkerscript_p.h | 125 ++++++ 7 files changed, 609 insertions(+), 5 deletions(-) create mode 100644 examples/declarative/workerscript/workerscript.js create mode 100644 examples/declarative/workerscript/workerscript.qml create mode 100644 src/declarative/qml/qmlworkerscript.cpp create mode 100644 src/declarative/qml/qmlworkerscript_p.h diff --git a/examples/declarative/workerscript/workerscript.js b/examples/declarative/workerscript/workerscript.js new file mode 100644 index 0000000..060c04a --- /dev/null +++ b/examples/declarative/workerscript/workerscript.js @@ -0,0 +1,5 @@ +onmessage = function(message) { + print ("You clicked the " + message.rectangle + " rectangle."); + print ("The coordinates were: " + message.x + "," + message.y); +} + diff --git a/examples/declarative/workerscript/workerscript.qml b/examples/declarative/workerscript/workerscript.qml new file mode 100644 index 0000000..3729022 --- /dev/null +++ b/examples/declarative/workerscript/workerscript.qml @@ -0,0 +1,41 @@ +import Qt 4.6 + +Rectangle { + width: 480; height: 320; + + WorkerScript { + id: myWorker + source: "workerscript.js" + } + + Rectangle { + width: 200; height: 200 + anchors.left: parent.left + anchors.leftMargin: 20 + color: "red" + + MouseRegion { + anchors.fill: parent + onClicked: myWorker.sendMessage( { rectangle: "red", x: mouse.x, y: mouse.y } ); + } + } + + Rectangle { + width: 200; height: 200 + anchors.right: parent.right + anchors.rightMargin: 20 + color: "blue" + + MouseRegion { + anchors.fill: parent + onClicked: myWorker.sendMessage( { rectangle: "blue", x: mouse.x, y: mouse.y } ); + } + } + + Text { + text: "Click a Rectangle!" + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 50 + } +} diff --git a/src/declarative/qml/qml.pri b/src/declarative/qml/qml.pri index ca78797..2167ab3 100644 --- a/src/declarative/qml/qml.pri +++ b/src/declarative/qml/qml.pri @@ -46,7 +46,8 @@ SOURCES += qml/qmlparser.cpp \ qml/qmlglobalscriptclass.cpp \ qml/qmlvaluetypescriptclass.cpp \ qml/qmltypenamescriptclass.cpp \ - qml/qmllistscriptclass.cpp + qml/qmllistscriptclass.cpp \ + qml/qmlworkerscript.cpp HEADERS += qml/qmlparser_p.h \ qml/qmlglobal_p.h \ @@ -109,8 +110,9 @@ HEADERS += qml/qmlparser_p.h \ qml/qmlglobalscriptclass_p.h \ qml/qmlvaluetypescriptclass_p.h \ qml/qmltypenamescriptclass_p.h \ - qml/qmllistscriptclass_p.h - + qml/qmllistscriptclass_p.h \ + qml/qmlworkerscript_p.h + QT += sql include(parser/parser.pri) diff --git a/src/declarative/qml/qmlengine.cpp b/src/declarative/qml/qmlengine.cpp index aa66c17..c0692eb 100644 --- a/src/declarative/qml/qmlengine.cpp +++ b/src/declarative/qml/qmlengine.cpp @@ -85,6 +85,7 @@ #include #include #include +#include #ifdef Q_OS_WIN // for %APPDATA% #include "qt_windows.h" @@ -111,8 +112,8 @@ QmlEnginePrivate::QmlEnginePrivate(QmlEngine *e) : rootContext(0), currentExpression(0), isDebugging(false), contextClass(0), objectClass(0), valueTypeClass(0), globalClass(0), nodeListClass(0), namedNodeMapClass(0), sqlQueryClass(0), cleanup(0), erroredBindings(0), - inProgressCreations(0), scriptEngine(this), componentAttacheds(0), rootComponent(0), - networkAccessManager(0), typeManager(e), uniqueId(1) + inProgressCreations(0), scriptEngine(this), workerScriptEngine(0), componentAttacheds(0), + rootComponent(0), networkAccessManager(0), typeManager(e), uniqueId(1) { // Note that all documentation for stuff put on the global object goes in // doc/src/declarative/globalobject.qdoc @@ -243,6 +244,14 @@ void QmlEnginePrivate::init() } } +QmlWorkerScriptEngine *QmlEnginePrivate::getWorkerScriptEngine() +{ + Q_Q(QmlEngine); + if (!workerScriptEngine) + workerScriptEngine = new QmlWorkerScriptEngine(q); + return workerScriptEngine; +} + /*! \class QmlEngine \brief The QmlEngine class provides an environment for instantiating QML components. diff --git a/src/declarative/qml/qmlengine_p.h b/src/declarative/qml/qmlengine_p.h index cabd0ad..191be83 100644 --- a/src/declarative/qml/qmlengine_p.h +++ b/src/declarative/qml/qmlengine_p.h @@ -99,6 +99,7 @@ class QmlComponentAttached; class QmlListScriptClass; class QmlCleanup; class QmlBindingData; +class QmlWorkerScriptEngine; class QmlEnginePrivate : public QObjectPrivate { @@ -152,6 +153,9 @@ public: }; QmlScriptEngine scriptEngine; + QmlWorkerScriptEngine *getWorkerScriptEngine(); + QmlWorkerScriptEngine *workerScriptEngine; + QUrl baseUrl; template diff --git a/src/declarative/qml/qmlworkerscript.cpp b/src/declarative/qml/qmlworkerscript.cpp new file mode 100644 index 0000000..a6428dd --- /dev/null +++ b/src/declarative/qml/qmlworkerscript.cpp @@ -0,0 +1,418 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmlworkerscript_p.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class WorkerDataEvent : public QEvent +{ +public: + enum Type { WorkerData = QEvent::User }; + + WorkerDataEvent(int workerId, QmlWorkerScriptEngine::Data *data); + virtual ~WorkerDataEvent(); + + int workerId() const; + QmlWorkerScriptEngine::Data *takeData(); + +private: + int m_id; + QmlWorkerScriptEngine::Data *m_data; +}; + +class WorkerLoadEvent : public QEvent +{ +public: + enum Type { WorkerLoad = WorkerDataEvent::WorkerData + 1 }; + + WorkerLoadEvent(int workerId, const QUrl &url); + + int workerId() const; + QUrl url() const; + +private: + int m_id; + QUrl m_url; +}; + +class QmlWorkerScriptEnginePrivate : public QObject +{ +public: + QmlWorkerScriptEnginePrivate(); + + QScriptEngine *workerEngine; + + QMutex m_lock; + QWaitCondition m_wait; + + QScriptValue getWorker(int); + + int m_nextId; + QHash m_workers; + + static QVariant scriptValueToVariant(const QScriptValue &); + static QScriptValue variantToScriptValue(const QVariant &, QScriptEngine *); + + static QScriptValue onmessage(QScriptContext *ctxt, QScriptEngine *engine); + +protected: + virtual bool event(QEvent *); + +private: + void processMessage(int, QmlWorkerScriptEngine::Data *); + void processLoad(int, const QUrl &); +}; + +QmlWorkerScriptEnginePrivate::QmlWorkerScriptEnginePrivate() +: workerEngine(0), m_nextId(0) +{ +} + +QScriptValue QmlWorkerScriptEnginePrivate::onmessage(QScriptContext *ctxt, QScriptEngine *engine) +{ + if (ctxt->argumentCount() >= 1) + ctxt->thisObject().setData(ctxt->argument(0)); + + return ctxt->thisObject().data(); +} + +QScriptValue QmlWorkerScriptEnginePrivate::getWorker(int id) +{ + QHash::Iterator iter = m_workers.find(id); + if (iter == m_workers.end()) { + QScriptValue worker = workerEngine->newObject(); + + worker.setProperty(QLatin1String("onmessage"), workerEngine->newFunction(onmessage), + QScriptValue::PropertyGetter | QScriptValue::PropertySetter); + + iter = m_workers.insert(id, worker); + } + + return *iter; +} + +bool QmlWorkerScriptEnginePrivate::event(QEvent *event) +{ + if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) { + WorkerDataEvent *workerEvent = static_cast(event); + processMessage(workerEvent->workerId(), workerEvent->takeData()); + return true; + } else if (event->type() == (QEvent::Type)WorkerLoadEvent::WorkerLoad) { + WorkerLoadEvent *workerEvent = static_cast(event); + processLoad(workerEvent->workerId(), workerEvent->url()); + return true; + } else { + return QObject::event(event); + } +} + +void QmlWorkerScriptEnginePrivate::processMessage(int id, QmlWorkerScriptEngine::Data *data) +{ + QScriptValue worker = getWorker(id); + QScriptValue onmessage = worker.data(); + + if (onmessage.isFunction()) { + QScriptValue args = workerEngine->newArray(1); + args.setProperty(0, variantToScriptValue(data->var, workerEngine)); + + onmessage.call(worker, args); + } + + if (data) delete data; +} + +void QmlWorkerScriptEnginePrivate::processLoad(int id, const QUrl &url) +{ + if (url.isRelative() || url.scheme() != QLatin1String("file")) + return; + + QString fileName = url.toLocalFile(); + + QFile f(fileName); + if (f.open(QIODevice::ReadOnly)) { + QByteArray data = f.readAll(); + QString script = QString::fromUtf8(data); + + QScriptValue activation = getWorker(id); + + QScriptContext *ctxt = workerEngine->pushContext(); + ctxt->setActivationObject(activation); + + workerEngine->evaluate(script); + + workerEngine->popContext(); + } +} + +WorkerDataEvent::WorkerDataEvent(int workerId, QmlWorkerScriptEngine::Data *data) +: QEvent((QEvent::Type)WorkerData), m_id(workerId), m_data(data) +{ +} + +WorkerDataEvent::~WorkerDataEvent() +{ + if (m_data) { delete m_data; m_data = 0; } +} + +int WorkerDataEvent::workerId() const +{ + return m_id; +} + +QmlWorkerScriptEngine::Data *WorkerDataEvent::takeData() +{ + QmlWorkerScriptEngine::Data *rv = m_data; + m_data = 0; + return rv; +} + +WorkerLoadEvent::WorkerLoadEvent(int workerId, const QUrl &url) +: QEvent((QEvent::Type)WorkerLoad), m_id(workerId), m_url(url) +{ +} + +int WorkerLoadEvent::workerId() const +{ + return m_id; +} + +QUrl WorkerLoadEvent::url() const +{ + return m_url; +} + +QmlWorkerScriptEngine::QmlWorkerScriptEngine(QObject *parent) +: QThread(parent), d(new QmlWorkerScriptEnginePrivate) +{ + d->m_lock.lock(); + start(QThread::LowPriority); + d->m_wait.wait(&d->m_lock); + d->moveToThread(this); +} + +QmlWorkerScriptEngine::~QmlWorkerScriptEngine() +{ + delete d; d = 0; +} + +QmlWorkerScriptEngine::WorkerScript::WorkerScript() +: engine(0), id(0) +{ +} + +void QmlWorkerScriptEngine::WorkerScript::executeUrl(const QUrl &url) +{ + engine->executeUrl(this, url); +} + +void QmlWorkerScriptEngine::WorkerScript::sendMessage(Data *data) +{ + engine->sendMessage(this, data); +} + +void QmlWorkerScriptEngine::executeUrl(WorkerScript *script, const QUrl &data) +{ + QCoreApplication::postEvent(d, new WorkerLoadEvent(script->id, data)); +} + +/*! + Ownership of \a data transfers to QmlWorkerScriptEngine. It can not be modified by + the caller. +*/ +void QmlWorkerScriptEngine::sendMessage(WorkerScript *script, Data *data) +{ + QCoreApplication::postEvent(d, new WorkerDataEvent(script->id, data)); +} + +QmlWorkerScriptEngine::WorkerScript *QmlWorkerScriptEngine::createWorkerScript() +{ + WorkerScript *rv = new WorkerScript; + rv->engine = this; + rv->id = d->m_nextId++; + return rv; +} + +void QmlWorkerScriptEngine::run() +{ + d->m_lock.lock(); + + d->workerEngine = new QScriptEngine; + + d->m_wait.wakeAll(); + + d->m_lock.unlock(); + + exec(); + + delete d->workerEngine; d->workerEngine = 0; +} + +QmlWorkerScript::QmlWorkerScript(QObject *parent) +: QObject(parent), m_script(0) +{ +} + +QmlWorkerScript::~QmlWorkerScript() +{ + if (m_script) { delete m_script; m_script = 0; } +} + +QVariant QmlWorkerScriptEnginePrivate::scriptValueToVariant(const QScriptValue &value) +{ + if (value.isBool()) { + return QVariant(value.toBool()); + } else if (value.isString()) { + return QVariant(value.toString()); + } else if (value.isNumber()) { + return QVariant((qreal)value.toNumber()); + } else if (value.isArray()) { + QVariantList list; + + quint32 length = (quint32)value.property("length").toNumber(); + + for (quint32 ii = 0; ii < length; ++ii) { + QVariant v = scriptValueToVariant(ii); + list << v; + } + + return QVariant(list); + } else if (value.isObject()) { + QVariantHash hash; + + QScriptValueIterator iter(value); + + while (iter.hasNext()) { + iter.next(); + hash.insert(iter.name(), scriptValueToVariant(iter.value())); + } + + return QVariant(hash); + } + + return QVariant(); + +} + +QScriptValue QmlWorkerScriptEnginePrivate::variantToScriptValue(const QVariant &value, QScriptEngine *engine) +{ + if (value.type() == QVariant::Bool) { + return QScriptValue(value.toBool()); + } else if (value.type() == QVariant::String) { + return QScriptValue(value.toString()); + } else if (value.type() == (QVariant::Type)QMetaType::QReal) { + return QScriptValue(value.toReal()); + } else if (value.type() == (QVariant::Type)QMetaType::QVariantList) { + QVariantList list = qvariant_cast(value); + QScriptValue rv = engine->newArray(list.count()); + + for (quint32 ii = 0; ii < list.count(); ++ii) + rv.setProperty(ii, variantToScriptValue(list.at(ii), engine)); + + return rv; + } else if (value.type() == (QVariant::Type)QMetaType::QVariantHash) { + + QVariantHash hash = qvariant_cast(value); + + QScriptValue rv = engine->newObject(); + + for (QVariantHash::ConstIterator iter = hash.begin(); iter != hash.end(); ++iter) + rv.setProperty(iter.key(), variantToScriptValue(iter.value(), engine)); + + return rv; + } else { + return engine->nullValue(); + } +} + +QUrl QmlWorkerScript::source() const +{ + return m_source; +} + +void QmlWorkerScript::setSource(const QUrl &source) +{ + if (m_source == source) + return; + + m_source = source; + + if (m_script) + m_script->executeUrl(m_source); + + emit sourceChanged(); +} + +void QmlWorkerScript::sendMessage(const QScriptValue &message) +{ + if (!m_script) { + qWarning("QmlWorkerScript: Attempt to send message before WorkerScript establishment"); + return; + } + + QmlWorkerScriptEngine::Data *data = new QmlWorkerScriptEngine::Data; + data->var = QmlWorkerScriptEnginePrivate::scriptValueToVariant(message); + m_script->sendMessage(data); +} + +void QmlWorkerScript::componentComplete() +{ + if (!m_script) { + QmlEngine *engine = qmlEngine(this); + if (!engine) { + qWarning("QmlWorkerScript: componentComplete() called without qmlEngine() set"); + return; + } + m_script = QmlEnginePrivate::get(engine)->getWorkerScriptEngine()->createWorkerScript(); + + if (m_source.isValid()) + m_script->executeUrl(m_source); + } +} + +QML_DEFINE_TYPE(Qt, 4, 6, WorkerScript, QmlWorkerScript); diff --git a/src/declarative/qml/qmlworkerscript_p.h b/src/declarative/qml/qmlworkerscript_p.h new file mode 100644 index 0000000..90861f8 --- /dev/null +++ b/src/declarative/qml/qmlworkerscript_p.h @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMLWORKERSCRIPT_P_H +#define QMLWORKERSCRIPT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include + +class QmlWorkerScriptEnginePrivate; +class QmlWorkerScriptEngine : public QThread +{ +Q_OBJECT +public: + QmlWorkerScriptEngine(QObject *parent = 0); + virtual ~QmlWorkerScriptEngine(); + + class Data; + class WorkerScript { + public: + void sendMessage(Data *); + void executeUrl(const QUrl &); + + private: + WorkerScript(); + friend class QmlWorkerScriptEngine; + QmlWorkerScriptEngine *engine; + int id; + }; + WorkerScript *createWorkerScript(); + + struct Data { + QVariant var; + }; + + void executeUrl(WorkerScript *, const QUrl &); + void sendMessage(WorkerScript *, Data *); + +protected: + virtual void run(); + +private: + QmlWorkerScriptEnginePrivate *d; +}; + +class QmlWorkerScript : public QObject, public QmlParserStatus +{ + Q_OBJECT + Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged); + +public: + QmlWorkerScript(QObject *parent = 0); + virtual ~QmlWorkerScript(); + + QUrl source() const; + void setSource(const QUrl &); + +public slots: + void sendMessage(const QScriptValue &); + +signals: + void sourceChanged(); + +protected: + virtual void componentComplete(); + +private: + QmlWorkerScriptEngine::WorkerScript *m_script; + QUrl m_source; +}; +QML_DECLARE_TYPE(QmlWorkerScript); + +#endif // QMLWORKERSCRIPT_P_H -- cgit v0.12