Showing the changes to libraries/script-engine/src/ScriptEngine.cpp (1 of 6 files). Show all
Unified · Split
| @@ -44,6 +44,8 @@ · 43 unmodified lines | ||
| 44 | 44 | |
| 45 | 45 | #include "MIDIEvent.h" |
| 46 | 46 | |
| 47 | +Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature) | |
| 48 | +static int functionSignatureMetaID = qRegisterMetaType<QScriptEngine::FunctionSignature>(); | |
| 47 | 49 | |
| 48 | 50 | static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){ |
| 49 | 51 | QString message = ""; |
| @@ -97,7 +99,6 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam · 47 unmodified lines | ||
| 97 | 99 | _controllerScriptingInterface(controllerScriptingInterface), |
| 98 | 100 | _wantSignals(wantSignals), |
| 99 | 101 | _avatarData(NULL), |
| 100 | − _scriptName(), | |
| 101 | 102 | _fileNameString(fileNameString), |
| 102 | 103 | _quatLibrary(), |
| 103 | 104 | _vec3Library(), |
| @@ -122,6 +123,27 @@ ScriptEngine::~ScriptEngine() { · 18 unmodified lines | ||
| 122 | 123 | } |
| 123 | 124 | } |
| 124 | 125 | |
| 126 | +void ScriptEngine::runInThread() { | |
| 127 | + QThread* workerThread = new QThread(this); | |
| 128 | + QString scriptEngineName = QString("Script Thread:") + getFilename(); | |
| 129 | + workerThread->setObjectName(scriptEngineName); | |
| 130 | + | |
| 131 | + // when the worker thread is started, call our engine's run.. | |
| 132 | + connect(workerThread, &QThread::started, this, &ScriptEngine::run); | |
| 133 | + | |
| 134 | + // when the thread is terminated, add both scriptEngine and thread to the deleteLater queue | |
| 135 | + connect(this, &ScriptEngine::doneRunning, this, &ScriptEngine::deleteLater); | |
| 136 | + connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater); | |
| 137 | + | |
| 138 | + // tell the thread to stop when the script engine is done | |
| 139 | + connect(this, &ScriptEngine::destroyed, workerThread, &QThread::quit); | |
| 140 | + | |
| 141 | + moveToThread(workerThread); | |
| 142 | + | |
| 143 | + // Starts an event loop, and emits workerThread->started() | |
| 144 | + workerThread->start(); | |
| 145 | +} | |
| 146 | + | |
| 125 | 147 | QSet<ScriptEngine*> ScriptEngine::_allKnownScriptEngines; |
| 126 | 148 | QMutex ScriptEngine::_allScriptsMutex; |
| 127 | 149 | bool ScriptEngine::_stoppingAllScripts = false; |
| @@ -180,8 +202,6 @@ void ScriptEngine::stopAllScripts(QObject* application) { · 52 unmodified lines | ||
| 180 | 202 | |
| 181 | 203 | |
| 182 | 204 | void ScriptEngine::waitTillDoneRunning() { |
| 183 | − QString scriptName = getFilename(); | |
| 184 | − | |
| 185 | 205 | // If the script never started running or finished running before we got here, we don't need to wait for it |
| 186 | 206 | if (_isRunning) { |
| 187 | 207 | |
| @@ -244,14 +264,6 @@ void ScriptEngine::setAvatarData(AvatarData* avatarData, const QString& objectNa · 56 unmodified lines | ||
| 244 | 264 | registerGlobalObject(objectName, _avatarData); |
| 245 | 265 | } |
| 246 | 266 | |
| 247 | −void ScriptEngine::setAvatarHashMap(AvatarHashMap* avatarHashMap, const QString& objectName) { | |
| 248 | − // remove the old Avatar property, if it exists | |
| 249 | − globalObject().setProperty(objectName, QScriptValue()); | |
| 250 | − | |
| 251 | − // give the script engine the new avatar hash map | |
| 252 | − registerGlobalObject(objectName, avatarHashMap); | |
| 253 | −} | |
| 254 | − | |
| 255 | 267 | bool ScriptEngine::setScriptContents(const QString& scriptContents, const QString& fileNameString) { |
| 256 | 268 | if (_isRunning) { |
| 257 | 269 | return false; |
| @@ -261,7 +273,10 @@ bool ScriptEngine::setScriptContents(const QString& scriptContents, const QStrin · 3 unmodified lines | ||
| 261 | 273 | return true; |
| 262 | 274 | } |
| 263 | 275 | |
| 276 | +// FIXME - remove the file/url scheme code here and let the script cache handle things | |
| 264 | 277 | void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { |
| 278 | + qDebug() << "ScriptEngine::loadURL(" << scriptURL << ", " << reload << ");"; | |
| 279 | + | |
| 265 | 280 | if (_isRunning) { |
| 266 | 281 | return; |
| 267 | 282 | } |
| @@ -298,7 +313,10 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { · 30 unmodified lines | ||
| 298 | 313 | } else { |
| 299 | 314 | bool isPending; |
| 300 | 315 | auto scriptCache = DependencyManager::get<ScriptCache>(); |
| 316 | + qDebug() << "calling scriptCache->getScript(" << url << ", " << reload << ");"; | |
| 301 | 317 | scriptCache->getScript(url, this, isPending, reload); |
| 318 | + qDebug() << " scriptCache->getScript(" << url << ", " << reload << ") ... isPending:" << isPending; | |
| 319 | + | |
| 302 | 320 | } |
| 303 | 321 | } |
| 304 | 322 | } |
| @@ -383,57 +401,97 @@ void ScriptEngine::init() { · 78 unmodified lines | ||
| 383 | 401 | globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE))); |
| 384 | 402 | } |
| 385 | 403 | |
| 386 | −QScriptValue ScriptEngine::registerGlobalObject(const QString& name, QObject* object) { | |
| 404 | +void ScriptEngine::registerGlobalObject(const QString& name, QObject* object) { | |
| 405 | + if (QThread::currentThread() != thread()) { | |
| 406 | + qDebug() << "*** WARNING *** ScriptEngine::registerGlobalObject() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name; | |
| 407 | + QMetaObject::invokeMethod(this, "registerGlobalObject", | |
| 408 | + Q_ARG(const QString&, name), | |
| 409 | + Q_ARG(QObject*, object)); | |
| 410 | + return; | |
| 411 | + } | |
| 412 | + //qDebug() << "ScriptEngine::registerGlobalObject() called on thread [" << QThread::currentThread() << "] name:" << name; | |
| 413 | + | |
| 387 | 414 | if (object) { |
| 388 | 415 | QScriptValue value = newQObject(object); |
| 389 | 416 | globalObject().setProperty(name, value); |
| 390 | − return value; | |
| 391 | 417 | } |
| 392 | − return QScriptValue::NullValue; | |
| 393 | 418 | } |
| 394 | 419 | |
| 395 | −void ScriptEngine::registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments) { | |
| 396 | − registerFunction(globalObject(), name, fun, numArguments); | |
| 420 | +void ScriptEngine::registerFunction(const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) { | |
| 421 | + if (QThread::currentThread() != thread()) { | |
| 422 | + qDebug() << "*** WARNING *** ScriptEngine::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name; | |
| 423 | + QMetaObject::invokeMethod(this, "registerFunction", | |
| 424 | + Q_ARG(const QString&, name), | |
| 425 | + Q_ARG(QScriptEngine::FunctionSignature, functionSignature), | |
| 426 | + Q_ARG(int, numArguments)); | |
| 427 | + return; | |
| 428 | + } | |
| 429 | + //qDebug() << "ScriptEngine::registerFunction() called on thread [" << QThread::currentThread() << "] name:" << name; | |
| 430 | + | |
| 431 | + QScriptValue scriptFun = newFunction(functionSignature, numArguments); | |
| 432 | + globalObject().setProperty(name, scriptFun); | |
| 397 | 433 | } |
| 398 | 434 | |
| 399 | −void ScriptEngine::registerFunction(QScriptValue parent, const QString& name, QScriptEngine::FunctionSignature fun, int numArguments) { | |
| 400 | − QScriptValue scriptFun = newFunction(fun, numArguments); | |
| 401 | − parent.setProperty(name, scriptFun); | |
| 435 | +void ScriptEngine::registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) { | |
| 436 | + if (QThread::currentThread() != thread()) { | |
| 437 | + qDebug() << "*** WARNING *** ScriptEngine::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] parent:" << parent << "name:" << name; | |
| 438 | + QMetaObject::invokeMethod(this, "registerFunction", | |
| 439 | + Q_ARG(const QString&, name), | |
| 440 | + Q_ARG(QScriptEngine::FunctionSignature, functionSignature), | |
| 441 | + Q_ARG(int, numArguments)); | |
| 442 | + return; | |
| 443 | + } | |
| 444 | + //qDebug() << "ScriptEngine::registerFunction() called on thread [" << QThread::currentThread() << "] parent:" << parent << "name:" << name; | |
| 445 | + | |
| 446 | + QScriptValue object = globalObject().property(parent); | |
| 447 | + if (object.isValid()) { | |
| 448 | + QScriptValue scriptFun = newFunction(functionSignature, numArguments); | |
| 449 | + object.setProperty(name, scriptFun); | |
| 450 | + } | |
| 402 | 451 | } |
| 403 | 452 | |
| 404 | 453 | void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, |
| 405 | − QScriptEngine::FunctionSignature setter, QScriptValue object) { | |
| 454 | + QScriptEngine::FunctionSignature setter, const QString& parent) { | |
| 455 | + if (QThread::currentThread() != thread()) { | |
| 456 | + qDebug() << "*** WARNING *** ScriptEngine::registerGetterSetter() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " | |
| 457 | + " name:" << name << "parent:" << parent; | |
| 458 | + QMetaObject::invokeMethod(this, "registerGetterSetter", | |
| 459 | + Q_ARG(const QString&, name), | |
| 460 | + Q_ARG(QScriptEngine::FunctionSignature, getter), | |
| 461 | + Q_ARG(QScriptEngine::FunctionSignature, setter), | |
| 462 | + Q_ARG(const QString&, parent)); | |
| 463 | + return; | |
| 464 | + } | |
| 465 | + //qDebug() << "ScriptEngine::registerGetterSetter() called on thread [" << QThread::currentThread() << "] name:" << name << "parent:" << parent; | |
| 466 | + | |
| 406 | 467 | QScriptValue setterFunction = newFunction(setter, 1); |
| 407 | 468 | QScriptValue getterFunction = newFunction(getter); |
| 408 | 469 | |
| 409 | − if (!object.isNull()) { | |
| 410 | − object.setProperty(name, setterFunction, QScriptValue::PropertySetter); | |
| 411 | − object.setProperty(name, getterFunction, QScriptValue::PropertyGetter); | |
| 470 | + if (!parent.isNull()) { | |
| 471 | + QScriptValue object = globalObject().property(parent); | |
| 472 | + if (object.isValid()) { | |
| 473 | + object.setProperty(name, setterFunction, QScriptValue::PropertySetter); | |
| 474 | + object.setProperty(name, getterFunction, QScriptValue::PropertyGetter); | |
| 475 | + } | |
| 412 | 476 | } else { |
| 413 | 477 | globalObject().setProperty(name, setterFunction, QScriptValue::PropertySetter); |
| 414 | 478 | globalObject().setProperty(name, getterFunction, QScriptValue::PropertyGetter); |
| 415 | 479 | } |
| 416 | 480 | } |
| 417 | 481 | |
| 418 | −// Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args | |
| 419 | −void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& eventName, std::function<QScriptValueList()> argGenerator) { | |
| 420 | − if (!_registeredHandlers.contains(entityID)) { | |
| 421 | − return; | |
| 422 | − } | |
| 423 | − const RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID]; | |
| 424 | − if (!handlersOnEntity.contains(eventName)) { | |
| 425 | − return; | |
| 426 | − } | |
| 427 | − QScriptValueList handlersForEvent = handlersOnEntity[eventName]; | |
| 428 | − if (!handlersForEvent.isEmpty()) { | |
| 429 | − QScriptValueList args = argGenerator(); | |
| 430 | − for (int i = 0; i < handlersForEvent.count(); ++i) { | |
| 431 | − handlersForEvent[i].call(QScriptValue(), args); | |
| 432 | − } | |
| 433 | − } | |
| 434 | −} | |
| 435 | 482 | // Unregister the handlers for this eventName and entityID. |
| 436 | 483 | void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { |
| 484 | + if (QThread::currentThread() != thread()) { | |
| 485 | + qDebug() << "*** WARNING *** ScriptEngine::removeEventHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " | |
| 486 | + "entityID:" << entityID << " eventName:" << eventName; | |
| 487 | + QMetaObject::invokeMethod(this, "removeEventHandler", | |
| 488 | + Q_ARG(const EntityItemID&, entityID), | |
| 489 | + Q_ARG(const QString&, eventName), | |
| 490 | + Q_ARG(QScriptValue, handler)); | |
| 491 | + return; | |
| 492 | + } | |
| 493 | + //qDebug() << "ScriptEngine::removeEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName; | |
| 494 | + | |
| 437 | 495 | if (!_registeredHandlers.contains(entityID)) { |
| 438 | 496 | return; |
| 439 | 497 | } |
| @@ -449,6 +507,17 @@ void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QStrin · 9 unmodified lines | ||
| 449 | 507 | } |
| 450 | 508 | // Register the handler. |
| 451 | 509 | void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) { |
| 510 | + if (QThread::currentThread() != thread()) { | |
| 511 | + qDebug() << "*** WARNING *** ScriptEngine::addEventHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " | |
| 512 | + "entityID:" << entityID << " eventName:" << eventName; | |
| 513 | + QMetaObject::invokeMethod(this, "addEventHandler", | |
| 514 | + Q_ARG(const EntityItemID&, entityID), | |
| 515 | + Q_ARG(const QString&, eventName), | |
| 516 | + Q_ARG(QScriptValue, handler)); | |
| 517 | + return; | |
| 518 | + } | |
| 519 | + //qDebug() << "ScriptEngine::addEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName; | |
| 520 | + | |
| 452 | 521 | if (_registeredHandlers.count() == 0) { // First time any per-entity handler has been added in this script... |
| 453 | 522 | // Connect up ALL the handlers to the global entities object's signals. |
| 454 | 523 | // (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.) |
| @@ -503,34 +572,23 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& · 48 unmodified lines | ||
| 503 | 572 | } |
| 504 | 573 | |
| 505 | 574 | |
| 506 | −void ScriptEngine::evaluate() { | |
| 507 | − if (_stoppingAllScripts) { | |
| 508 | − return; // bail early | |
| 509 | − } | |
| 510 | − | |
| 511 | − if (!_isInitialized) { | |
| 512 | − init(); | |
| 513 | − } | |
| 514 | − | |
| 515 | − QScriptValue result = evaluate(_scriptContents); | |
| 516 | − | |
| 517 | − // TODO: why do we check this twice? It seems like the call to clearExceptions() in the lower level evaluate call | |
| 518 | − // will cause this code to never actually run... | |
| 519 | − if (hasUncaughtException()) { | |
| 520 | − int line = uncaughtExceptionLineNumber(); | |
| 521 | − qCDebug(scriptengine) << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << result.toString(); | |
| 522 | − if (_wantSignals) { | |
| 523 | − emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + result.toString()); | |
| 524 | − } | |
| 525 | − clearExceptions(); | |
| 526 | − } | |
| 527 | −} | |
| 528 | − | |
| 529 | 575 | QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileName, int lineNumber) { |
| 530 | 576 | if (_stoppingAllScripts) { |
| 531 | 577 | return QScriptValue(); // bail early |
| 532 | 578 | } |
| 533 | 579 | |
| 580 | + if (QThread::currentThread() != thread()) { | |
| 581 | + QScriptValue result; | |
| 582 | + qDebug() << "*** WARNING *** ScriptEngine::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " | |
| 583 | + "program:" << program << " fileName:" << fileName << "lineNumber:" << lineNumber; | |
| 584 | + QMetaObject::invokeMethod(this, "evaluate", Qt::BlockingQueuedConnection, | |
| 585 | + Q_RETURN_ARG(QScriptValue, result), | |
| 586 | + Q_ARG(const QString&, program), | |
| 587 | + Q_ARG(const QString&, fileName), | |
| 588 | + Q_ARG(int, lineNumber)); | |
| 589 | + return result; | |
| 590 | + } | |
| 591 | + | |
| 534 | 592 | _evaluatesPending++; |
| 535 | 593 | QScriptValue result = QScriptEngine::evaluate(program, fileName, lineNumber); |
| 536 | 594 | if (hasUncaughtException()) { |
| @@ -967,6 +1025,26 @@ void ScriptEngine::load(const QString& loadFile) { · 430 unmodified lines | ||
| 967 | 1025 | } |
| 968 | 1026 | } |
| 969 | 1027 | |
| 970 | −void ScriptEngine::nodeKilled(SharedNodePointer node) { | |
| 971 | − _outgoingScriptAudioSequenceNumbers.remove(node->getUUID()); | |
| 1028 | +// Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args | |
| 1029 | +void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& eventName, std::function<QScriptValueList()> argGenerator) { | |
| 1030 | + if (QThread::currentThread() != thread()) { | |
| 1031 | + qDebug() << "*** ERROR *** ScriptEngine::generalHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; | |
| 1032 | + assert(false); | |
| 1033 | + return; | |
| 1034 | + } | |
| 1035 | + | |
| 1036 | + if (!_registeredHandlers.contains(entityID)) { | |
| 1037 | + return; | |
| 1038 | + } | |
| 1039 | + const RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID]; | |
| 1040 | + if (!handlersOnEntity.contains(eventName)) { | |
| 1041 | + return; | |
| 1042 | + } | |
| 1043 | + QScriptValueList handlersForEvent = handlersOnEntity[eventName]; | |
| 1044 | + if (!handlersForEvent.isEmpty()) { | |
| 1045 | + QScriptValueList args = argGenerator(); | |
| 1046 | + for (int i = 0; i < handlersForEvent.count(); ++i) { | |
| 1047 | + handlersForEvent[i].call(QScriptValue(), args); | |
| 1048 | + } | |
| 1049 | + } | |
| 972 | 1050 | } |