WARNING: USE THIS SOFTWARE AT YOUR OWN RISK! THIS IS EXPERIMENTAL SOFTWARE NOT INTENDED FOR PRODUCTION USE! Zuble is currently an early stage prototype. As such Zuble is minimally tested and inherently unstable. It is provided for experimental, development, and demonstration purposes only. Zuble QML Types   |  Zuble C++ Classes   |  Zuble Overview
Zuble  0.1
Zuble Framework C++/QML extension API
ZScriptThread.cpp
Go to the documentation of this file.
1 /*
2  * Zuble - A run-time system for QML/Javascript applications
3  * Copyright (C) 2013, 2014 Bob Dinitto
4  *
5  * Filename: ZScriptThread.cpp
6  * Created on: 11/9/2014
7  * Author: Bob Dinitto
8  *
9  * Zuble is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22  *
23  */
24 
25 #include "ZScriptThread.h"
26 #include "ZScriptWorker.h"
27 #include "ZblApp.h"
28 #include "ZblJsonHelper.h"
29 
30 #include <QtQml>
31 #include <QVariant>
32 
33 
34 namespace Zbl
35 {
36 
38 
39 ZScriptThread::ZScriptThread(QObject *parent) :
40  QObject(parent), m_initReady(false), m_threadCompleted(false)
41 {
42  zDebug() << "ZScriptThread::ZScriptThread";
43 
44  m_thread = new QThread(this);
45 
46  m_worker = new ZScriptWorker(this);
47 
48  m_worker->moveToThread(m_thread);
49 
50  connect(m_worker, &ZScriptWorker::execComplete, this,
51  &ZScriptThread::onWorkerExecComplete, Qt::QueuedConnection);
52 
53  connect(m_thread, &QThread::started, m_worker,
54  &ZScriptWorker::onStarted, Qt::QueuedConnection);
55 
56  connect(m_thread, &QThread::started, this,
57  &ZScriptThread::onStarted, Qt::BlockingQueuedConnection);
58 
59  connect(m_thread, &QThread::finished, this,
60  &ZScriptThread::onFinished, Qt::BlockingQueuedConnection);
61 
62 }
63 
65 {
66  qDebug("ZScriptThread::~ZScriptThread");
67 
68  destroyThread();
69 }
70 
72 {
73  qDebug("ZScriptThread::destroyThread");
74 
75  if(!m_thread)
76  return;
77 
78  if(m_initReady)
79  {
80  m_initReady = false;
81 
82  emit readyChanged();
83  }
84 
85  if(m_thread->isRunning())
86  {
87  m_thread->quit();
88 
90  }
91 
92  delete m_worker;
93 
94  m_worker = nullptr;
95 
96  delete m_thread;
97 
98  m_thread = nullptr;
99 }
100 
102 {
103  return m_initScript;
104 }
105 
106 void ZScriptThread::setInitScript(const QString& script)
107 {
108  if(m_initReady)
109  zWarning() << "WARNING - programming error: initScript "
110  "can't be set after initizlization";
111  else
112  m_initScript = script;
113 }
114 
116 {
117  return m_initError;
118 }
119 
121 {
122  return m_initReady;
123 }
124 
126 {
127  return m_threadCompleted;
128 }
129 
131 {
132  if(!m_thread)
133  {
134  zWarning() << "Programming error; Thread already destroyed.";
135  return;
136  }
137 
138  if(!m_initReady && !m_initScript.isEmpty())
139  {
140  // We must check m_initReady BEFORE calling QThread::start
141  // since QThread::starting signal could set it before executing
142  // the following code, at least in theory
143 
144  zDebug() << "Starting thread: " << ptrToString(m_thread);
145  m_thread->start();
146 
147  static int snipLength = 150;
148  QString snipText = m_initScript.left(snipLength);
149  if(m_initScript.size() > 150)
150  snipText += "...";
151  zDebug() << "Loading init script: " << snipText;
152  execScript(static_cast<qulonglong>(-1), m_initScript);
153  }
154  else
155  {
156  zDebug() << "Starting thread: " << ptrToString(m_thread);
157  m_thread->start();
158 
159  zDebug() << "No init script";
160  }
161 }
162 
164 {
165  if(m_thread)
166  {
167  m_thread->quit();
168 
169  //zDebug() << "Quit! running = " << m_thread->isRunning();
170  }
171 }
172 
173 void ZScriptThread::execScript(qulonglong requestID, const QString& script, QVariantMap userData)
174 {
175  zDebug() << "execScript ID=" << requestID << ", script = " << script.left(150) << ", userData = " << userData;
176 
177  if(!m_thread)
178  {
179  zWarning() << "Programming error: Thread already destroyed.";
180  return;
181  }
182 
183  QVariant data(userData);
184  QString jsonUserData = variantToJson(data);
185 
186  QMetaObject::invokeMethod(m_worker,"execScript",
187  Qt::QueuedConnection,
188  Q_ARG(qulonglong, requestID),
189  Q_ARG(const QString&, script),
190  Q_ARG(QString, jsonUserData)
191  );
192 }
193 
194 void ZScriptThread::onWorkerExecComplete(qulonglong requestID, QString result, QString userData)
195 {
196  QVariant varResult = jsonToVariant(result);
197  QVariant varUserData = jsonToVariant(userData);
198 
199  if(requestID == static_cast<qulonglong>(-1) && !m_initReady && !m_initScript.isEmpty())
200  {
201  m_initError = varResult;
202 
203  m_initReady = true;
204 
205  emit readyChanged();
206 
207  emit initComplete(varResult);
208  }
209  else
210  {
211  emit execComplete(requestID, varResult, varUserData);
212  }
213 }
214 
215 void ZScriptThread::onThreadAlert(QString signalID,
216  QString payload,
217  int conversion)
218 {
219  zDebug() << "ZScriptThread ALERT id=" << signalID << ", payload=" << payload;
220 
221  QVariant var;
222 
223  switch(conversion)
224  {
225  case INTEGER:
226 
227  var = QVariant(payload.toInt());
228 
229  break;
230 
231  case DOUBLE:
232 
233  var = QVariant(payload.toDouble());
234 
235  break;
236 
237  case STRING:
238 
239  var = QVariant(payload);
240 
241  break;
242 
243  case JSON:
244 
245  var = jsonToVariant(payload);
246 
247  break;
248 
249  case UNDEFINED:
250 
251  zWarning() << "ZScriptThread::onThreadAlert received UNDEFINED conversion value: " << conversion;
252 
253  return;
254 
255  default:
256 
257  zWarning() << "ZScriptThread::onThreadAlert received unrecognized conversion value: " << conversion;
258 
259  return;
260 
261  }
262 
263  emit alert(signalID, var);
264 }
265 
267 {
268  zDebug() << "QThread STARTED: " << ptrToString(m_thread);
269 
270  if(m_initScript.isEmpty())
271  {
272  emit initComplete(QVariant());
273 
274  m_initReady = true;
275 
276  zDebug() << "ZScriptThread::readyChanged";
277 
278  emit readyChanged();
279  }
280 }
281 
282 
284 {
285  zDebug() << "QThread FINISHED: " << ptrToString(m_thread);
286 
287  if(m_initReady)
288  {
289  m_initReady = false;
290 
291  emit readyChanged();
292  }
293 
294  m_threadCompleted = true;
295 
296  emit completeChanged();
297 
298  emit finished();
299 }
300 
301 
302 
304 {
305  if(!m_thread)
306  return true;
307  else
308  return m_thread->isInterruptionRequested();
309 }
310 
312 {
313  m_thread->requestInterruption();
314 }
315 
316 bool ZScriptThread::waitThreadComplete(unsigned long time)
317 {
318 
319  // We keep our message pump running in case the thread we're waiting on is
320  // communicating with objects in our thread, to prevent deadlock.
321 
322  bool status = false;
323 
324  unsigned long waitTime = 25;
325  unsigned long count = time > waitTime ? time / waitTime : 1;
326  QAbstractEventDispatcher* d = QThread::currentThread()->eventDispatcher();
327 
328  for(unsigned long i=0; i < count; i++)
329  {
330  if(m_thread->wait(waitTime))
331  {
332  status = true;
333  break;
334  }
335 
336  d->processEvents(QEventLoop::AllEvents);
337  }
338 
339  if(!status)
340  zWarning() << "THREAD WAIT TIMED OUT";
341 
342  return status;
343 }
344 
346 {
348 
349  qmlRegisterType<ZScriptThread>("org.zuble.qml", 1, 0, "ZScriptThread");
350 }
351 
352 QString ZScriptThread::variantToJson(QVariant& var)
353 {
354  QJsonDocument doc = QJsonDocument::fromVariant(var);
355  QByteArray jsonText = doc.toJson(QJsonDocument::Compact);
356  return QString(jsonText);
357 }
358 
359 QVariant ZScriptThread::jsonToVariant(const QString& json)
360 {
361  QVariant retVal;
362 
363  if(!json.isEmpty())
364  {
365  QJsonDocument doc = QJsonDocument::fromJson(json.toUtf8().constData());
366  retVal = doc.toVariant();
367  }
368 
369  return retVal;
370 }
371 
372 
373 } // Zbl
void execComplete(qulonglong requestID, QVariant result, QVariant userData)
Sent when an asynchronous background script execution has completed.
virtual ~ZScriptThread()
void finished()
Sent from the associated thread right before it finishes executing.
QString ptrToString(void *ptr)
Definition: zglobal.h:56
QVariant m_initError
The error message generated when running the initScript, or an empty string if initScript was success...
static void registerType()
Registers ZScriptThread as a QML type.
void setInitScript(const QString &script)
void onWorkerExecComplete(qulonglong requestID, QString result, QString userData)
Receives and propagates the ZScriptWorker::execComplete signal.
void execScript(qulonglong requestID, const QString &script, QVariantMap userData=QVariantMap())
Execute the specified javascript program fragment asynchronously in the background thread&#39;s javascrip...
void onFinished()
Called when QThread event loop has exited.
#define ZBL_REGISTER_LOGGED_OBJECT
Definition: zglobal.h:104
Q_INVOKABLE bool isInterrupted() const
Calls the QThread::isInterruptionRequested() method for this thread. Can be used to determine if this...
A thread class to support Zuble&#39;s background Javascript processing.
Definition: ZScriptThread.h:62
A class for executing javascript programs in a background worker thread.
Definition: ZScriptWorker.h:51
void quit()
Causes the thread to stop processing events and exit the event loop.
static QString variantToJson(QVariant &var)
Converts QVariant to json string.
Q_INVOKABLE void onThreadAlert(QString signalID, QString payload, int conversion)
Called by ZblApp in background thread to trigger ZScriptThread::alert signal in foreground thread...
ZScriptWorker * m_worker
Script worker object maintains this thread&#39;s javascript context.
Definition: ZAndGate.cpp:6
#define ZBL_DEFINE_LOGGED_OBJECT(class_name)
Definition: zglobal.h:99
#define zWarning()
Definition: zglobal.h:111
void execComplete(qulonglong requestID, QString result, QString userData)
Sent when the a javascript program execution started with execScript() has been completed.
void onStarted()
Called when QThread has started running.
void onStarted()
Called when QThread has started running.
void readyChanged()
Sent when ready property value changes.
static QVariant jsonToVariant(const QString &json)
Converts json string to QVariant.
#define zDebug()
Definition: zglobal.h:113
void sendInterruptRequest()
Calls the QThread::requestInterruption() method for this thread. If the thread is checking Zbl...
bool m_initReady
true if thread initialization is complete, false otherwise
void completeChanged()
Sent when complete property value changes.
void destroyThread()
Stops the QThread, waits for it to exit and then deletes it and the ZScriptWorker object...
void alert(const QString signalID, QVariant payload)
Background scripts send this signal by calling the ZblApp::alert() method.
bool waitThreadComplete(unsigned long time=ULONG_MAX)
Blocks the current thread until the background thread finishes running its event loop or a timeout oc...
ZBL_DECLARE_LOGGED_OBJECT QThread * m_thread
QThread object in which ZScriptWorker object will run.
QString m_initScript
Thread&#39;s javascript context will be initialized by running this script.
void initComplete(QVariant status)
Sent when the javascript execution environment has completed initialization and is ready for use...
bool m_threadCompleted
true if thread has been shut down, false otherwise
void start()
Causes the tread to start execution.