Showing the changes to libraries/script-engine/src/ScriptEngine.cpp (1 of 66 files). Show all
Unified · Split
| @@ -79,11 +79,41 @@ void avatarDataFromScriptValue(const QScriptValue &object, AvatarData* &out) { · 78 unmodified lines | |||
| 79 | QScriptValue inputControllerToScriptValue(QScriptEngine *engine, AbstractInputController* const &in) { | 79 | QScriptValue inputControllerToScriptValue(QScriptEngine *engine, AbstractInputController* const &in) { |
| 80 | return engine->newQObject(in); | 80 | return engine->newQObject(in); |
| 81 | } | 81 | } |
| 82 | |||
| 83 | void inputControllerFromScriptValue(const QScriptValue &object, AbstractInputController* &out) { | 82 | void inputControllerFromScriptValue(const QScriptValue &object, AbstractInputController* &out) { |
| 84 | out = qobject_cast<AbstractInputController*>(object.toQObject()); | 83 | out = qobject_cast<AbstractInputController*>(object.toQObject()); |
| 85 | } | 84 | } |
| 86 | 85 | ||
| 86 | static bool hasCorrectSyntax(const QScriptProgram& program) { | ||
| 87 | const auto syntaxCheck = QScriptEngine::checkSyntax(program.sourceCode()); | ||
| 88 | if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { | ||
| 89 | const auto error = syntaxCheck.errorMessage(); | ||
| 90 | const auto line = QString::number(syntaxCheck.errorLineNumber()); | ||
| 91 | const auto column = QString::number(syntaxCheck.errorColumnNumber()); | ||
| 92 | const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, program.fileName(), line, column); | ||
| 93 | qCWarning(scriptengine) << qPrintable(message); | ||
| 94 | return false; | ||
| 95 | } | ||
| 96 | return true; | ||
| 97 | } | ||
| 98 | |||
| 99 | static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName) { | ||
| 100 | if (engine.hasUncaughtException()) { | ||
| 101 | const auto backtrace = engine.uncaughtExceptionBacktrace(); | ||
| 102 | const auto exception = engine.uncaughtException().toString(); | ||
| 103 | const auto line = QString::number(engine.uncaughtExceptionLineNumber()); | ||
| 104 | engine.clearExceptions(); | ||
| 105 | |||
| 106 | auto message = QString("[UncaughtException] %1 in %2:%3").arg(exception, fileName, line); | ||
| 107 | if (!backtrace.empty()) { | ||
| 108 | static const auto lineSeparator = "\n "; | ||
| 109 | message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator)); | ||
| 110 | } | ||
| 111 | qCWarning(scriptengine) << qPrintable(message); | ||
| 112 | return true; | ||
| 113 | } | ||
| 114 | return false; | ||
| 115 | } | ||
| 116 | |||
| 87 | ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString, | 117 | ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString, |
| 88 | AbstractControllerScriptingInterface* controllerScriptingInterface, bool wantSignals) : | 118 | AbstractControllerScriptingInterface* controllerScriptingInterface, bool wantSignals) : |
| 89 | 119 | ||
| @@ -95,9 +125,6 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam · 5 unmodified lines | |||
| 95 | _wantSignals(wantSignals), | 125 | _wantSignals(wantSignals), |
| 96 | _controllerScriptingInterface(controllerScriptingInterface), | 126 | _controllerScriptingInterface(controllerScriptingInterface), |
| 97 | _fileNameString(fileNameString), | 127 | _fileNameString(fileNameString), |
| 98 | _quatLibrary(), | ||
| 99 | _vec3Library(), | ||
| 100 | _uuidLibrary(), | ||
| 101 | _isUserLoaded(false), | 128 | _isUserLoaded(false), |
| 102 | _isReloading(false), | 129 | _isReloading(false), |
| 103 | _arrayBufferClass(new ArrayBufferClass(this)) | 130 | _arrayBufferClass(new ArrayBufferClass(this)) |
| @@ -531,26 +558,33 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& · 427 unmodified lines | |||
| 531 | // Connect up ALL the handlers to the global entities object's signals. | 558 | // Connect up ALL the handlers to the global entities object's signals. |
| 532 | // (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.) | 559 | // (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.) |
| 533 | auto entities = DependencyManager::get<EntityScriptingInterface>(); | 560 | auto entities = DependencyManager::get<EntityScriptingInterface>(); |
| 534 | connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, | 561 | connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityID) { |
| 535 | [=](const EntityItemID& entityID) { | 562 | _registeredHandlers.remove(entityID); |
| 536 | _registeredHandlers.remove(entityID); | 563 | }); |
| 537 | }); | 564 | |
| 538 | |||
| 539 | // Two common cases of event handler, differing only in argument signature. | 565 | // Two common cases of event handler, differing only in argument signature. |
| 540 | auto makeSingleEntityHandler = [=](const QString& eventName) -> std::function<void(const EntityItemID&)> { | 566 | using SingleEntityHandler = std::function<void(const EntityItemID&)>; |
| 541 | return [=](const EntityItemID& entityItemID) -> void { | 567 | auto makeSingleEntityHandler = [this](QString eventName) -> SingleEntityHandler { |
| 542 | generalHandler(entityItemID, eventName, [=]() -> QScriptValueList { | 568 | return [this, eventName](const EntityItemID& entityItemID) { |
| 543 | return QScriptValueList() << entityItemID.toScriptValue(this); | 569 | forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this) }); |
| 544 | }); | ||
| 545 | }; | 570 | }; |
| 546 | }; | 571 | }; |
| 547 | auto makeMouseHandler = [=](const QString& eventName) -> std::function<void(const EntityItemID&, const MouseEvent&)> { | 572 | |
| 548 | return [=](const EntityItemID& entityItemID, const MouseEvent& event) -> void { | 573 | using MouseHandler = std::function<void(const EntityItemID&, const MouseEvent&)>; |
| 549 | generalHandler(entityItemID, eventName, [=]() -> QScriptValueList { | 574 | auto makeMouseHandler = [this](QString eventName) -> MouseHandler { |
| 550 | return QScriptValueList() << entityItemID.toScriptValue(this) << event.toScriptValue(this); | 575 | return [this, eventName](const EntityItemID& entityItemID, const MouseEvent& event) { |
| 551 | }); | 576 | forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this), event.toScriptValue(this) }); |
| 552 | }; | 577 | }; |
| 553 | }; | 578 | }; |
| 579 | |||
| 580 | using CollisionHandler = std::function<void(const EntityItemID&, const EntityItemID&, const Collision&)>; | ||
| 581 | auto makeCollisionHandler = [this](QString eventName) -> CollisionHandler { | ||
| 582 | return [this, eventName](const EntityItemID& idA, const EntityItemID& idB, const Collision& collision) { | ||
| 583 | forwardHandlerCall(idA, eventName, { idA.toScriptValue(this), idB.toScriptValue(this), | ||
| 584 | collisionToScriptValue(this, collision) }); | ||
| 585 | }; | ||
| 586 | }; | ||
| 587 | |||
| 554 | connect(entities.data(), &EntityScriptingInterface::enterEntity, this, makeSingleEntityHandler("enterEntity")); | 588 | connect(entities.data(), &EntityScriptingInterface::enterEntity, this, makeSingleEntityHandler("enterEntity")); |
| 555 | connect(entities.data(), &EntityScriptingInterface::leaveEntity, this, makeSingleEntityHandler("leaveEntity")); | 589 | connect(entities.data(), &EntityScriptingInterface::leaveEntity, this, makeSingleEntityHandler("leaveEntity")); |
| 556 | 590 | ||
| @@ -566,12 +600,7 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& · 9 unmodified lines | |||
| 566 | connect(entities.data(), &EntityScriptingInterface::hoverOverEntity, this, makeMouseHandler("hoverOverEntity")); | 600 | connect(entities.data(), &EntityScriptingInterface::hoverOverEntity, this, makeMouseHandler("hoverOverEntity")); |
| 567 | connect(entities.data(), &EntityScriptingInterface::hoverLeaveEntity, this, makeMouseHandler("hoverLeaveEntity")); | 601 | connect(entities.data(), &EntityScriptingInterface::hoverLeaveEntity, this, makeMouseHandler("hoverLeaveEntity")); |
| 568 | 602 | ||
| 569 | connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this, | 603 | connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this, makeCollisionHandler("collisionWithEntity")); |
| 570 | [=](const EntityItemID& idA, const EntityItemID& idB, const Collision& collision) { | ||
| 571 | generalHandler(idA, "collisionWithEntity", [=]() { | ||
| 572 | return QScriptValueList () << idA.toScriptValue(this) << idB.toScriptValue(this) << collisionToScriptValue(this, collision); | ||
| 573 | }); | ||
| 574 | }); | ||
| 575 | } | 604 | } |
| 576 | if (!_registeredHandlers.contains(entityID)) { | 605 | if (!_registeredHandlers.contains(entityID)) { |
| 577 | _registeredHandlers[entityID] = RegisteredEventHandlers(); | 606 | _registeredHandlers[entityID] = RegisteredEventHandlers(); |
| @@ -581,7 +610,7 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& · 3 unmodified lines | |||
| 581 | } | 610 | } |
| 582 | 611 | ||
| 583 | 612 | ||
| 584 | QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileName, int lineNumber) { | 613 | QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fileName, int lineNumber) { |
| 585 | if (_stoppingAllScripts) { | 614 | if (_stoppingAllScripts) { |
| 586 | return QScriptValue(); // bail early | 615 | return QScriptValue(); // bail early |
| 587 | } | 616 | } |
| @@ -590,27 +619,30 @@ QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileN · 2 unmodified lines | |||
| 590 | QScriptValue result; | 619 | QScriptValue result; |
| 591 | #ifdef THREAD_DEBUGGING | 620 | #ifdef THREAD_DEBUGGING |
| 592 | qDebug() << "*** WARNING *** ScriptEngine::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " | 621 | qDebug() << "*** WARNING *** ScriptEngine::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " |
| 593 | "program:" << program << " fileName:" << fileName << "lineNumber:" << lineNumber; | 622 | "sourceCode:" << sourceCode << " fileName:" << fileName << "lineNumber:" << lineNumber; |
| 594 | #endif | 623 | #endif |
| 595 | QMetaObject::invokeMethod(this, "evaluate", Qt::BlockingQueuedConnection, | 624 | QMetaObject::invokeMethod(this, "evaluate", Qt::BlockingQueuedConnection, |
| 596 | Q_RETURN_ARG(QScriptValue, result), | 625 | Q_RETURN_ARG(QScriptValue, result), |
| 597 | Q_ARG(const QString&, program), | 626 | Q_ARG(const QString&, sourceCode), |
| 598 | Q_ARG(const QString&, fileName), | 627 | Q_ARG(const QString&, fileName), |
| 599 | Q_ARG(int, lineNumber)); | 628 | Q_ARG(int, lineNumber)); |
| 600 | return result; | 629 | return result; |
| 601 | } | 630 | } |
| 602 | 631 | ||
| 603 | _evaluatesPending++; | 632 | // Check syntax |
| 604 | QScriptValue result = QScriptEngine::evaluate(program, fileName, lineNumber); | 633 | const QScriptProgram program(sourceCode, fileName, lineNumber); |
| 605 | if (hasUncaughtException()) { | 634 | if (!hasCorrectSyntax(program)) { |
| 606 | int line = uncaughtExceptionLineNumber(); | 635 | return QScriptValue(); |
| 607 | qCDebug(scriptengine) << "Uncaught exception at (" << _fileNameString << " : " << fileName << ") line" << line << ": " << result.toString(); | ||
| 608 | } | 636 | } |
| 609 | _evaluatesPending--; | 637 | |
| 638 | ++_evaluatesPending; | ||
| 639 | const auto result = QScriptEngine::evaluate(program); | ||
| 640 | --_evaluatesPending; | ||
| 641 | |||
| 642 | const auto hadUncaughtException = hadUncaughtExceptions(*this, program.fileName()); | ||
| 610 | if (_wantSignals) { | 643 | if (_wantSignals) { |
| 611 | emit evaluationFinished(result, hasUncaughtException()); | 644 | emit evaluationFinished(result, hadUncaughtException); |
| 612 | } | 645 | } |
| 613 | clearExceptions(); | ||
| 614 | return result; | 646 | return result; |
| 615 | } | 647 | } |
| 616 | 648 | ||
| @@ -628,7 +660,7 @@ void ScriptEngine::run() { · 11 unmodified lines | |||
| 628 | emit runningStateChanged(); | 660 | emit runningStateChanged(); |
| 629 | } | 661 | } |
| 630 | 662 | ||
| 631 | QScriptValue result = evaluate(_scriptContents); | 663 | QScriptValue result = evaluate(_scriptContents, _fileNameString); |
| 632 | 664 | ||
| 633 | QElapsedTimer startTime; | 665 | QElapsedTimer startTime; |
| 634 | startTime.start(); | 666 | startTime.start(); |
| @@ -669,15 +701,6 @@ void ScriptEngine::run() { · 34 unmodified lines | |||
| 669 | qint64 now = usecTimestampNow(); | 701 | qint64 now = usecTimestampNow(); |
| 670 | float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND; | 702 | float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND; |
| 671 | 703 | ||
| 672 | if (hasUncaughtException()) { | ||
| 673 | int line = uncaughtExceptionLineNumber(); | ||
| 674 | qCDebug(scriptengine) << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << uncaughtException().toString(); | ||
| 675 | if (_wantSignals) { | ||
| 676 | emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + uncaughtException().toString()); | ||
| 677 | } | ||
| 678 | clearExceptions(); | ||
| 679 | } | ||
| 680 | |||
| 681 | if (!_isFinished) { | 704 | if (!_isFinished) { |
| 682 | if (_wantSignals) { | 705 | if (_wantSignals) { |
| 683 | emit update(deltaTime); | 706 | emit update(deltaTime); |
| @@ -685,6 +708,8 @@ void ScriptEngine::run() { · 1 unmodified line | |||
| 685 | } | 708 | } |
| 686 | lastUpdate = now; | 709 | lastUpdate = now; |
| 687 | 710 | ||
| 711 | // Debug and clear exceptions | ||
| 712 | hadUncaughtExceptions(*this, _fileNameString); | ||
| 688 | } | 713 | } |
| 689 | 714 | ||
| 690 | stopAllTimers(); // make sure all our timers are stopped if the script is ending | 715 | stopAllTimers(); // make sure all our timers are stopped if the script is ending |
| @@ -943,9 +968,9 @@ void ScriptEngine::load(const QString& loadFile) { · 252 unmodified lines | |||
| 943 | } | 968 | } |
| 944 | 969 | ||
| 945 | // Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args | 970 | // Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args |
| 946 | void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& eventName, std::function<QScriptValueList()> argGenerator) { | 971 | void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs) { |
| 947 | if (QThread::currentThread() != thread()) { | 972 | if (QThread::currentThread() != thread()) { |
| 948 | qDebug() << "*** ERROR *** ScriptEngine::generalHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; | 973 | qDebug() << "*** ERROR *** ScriptEngine::forwardHandlerCall() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; |
| 949 | assert(false); | 974 | assert(false); |
| 950 | return ; | 975 | return ; |
| 951 | } | 976 | } |
| @@ -958,9 +983,8 @@ void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& e · 6 unmodified lines | |||
| 958 | } | 983 | } |
| 959 | QScriptValueList handlersForEvent = handlersOnEntity[eventName]; | 984 | QScriptValueList handlersForEvent = handlersOnEntity[eventName]; |
| 960 | if (!handlersForEvent.isEmpty()) { | 985 | if (!handlersForEvent.isEmpty()) { |
| 961 | QScriptValueList args = argGenerator(); | ||
| 962 | for (int i = 0; i < handlersForEvent.count(); ++i) { | 986 | for (int i = 0; i < handlersForEvent.count(); ++i) { |
| 963 | handlersForEvent[i].call(QScriptValue(), args); | 987 | handlersForEvent[i].call(QScriptValue(), eventHanderArgs); |
| 964 | } | 988 | } |
| 965 | } | 989 | } |
| 966 | } | 990 | } |
| @@ -1024,14 +1048,10 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co · 57 unmodified lines | |||
| 1024 | 1048 | ||
| 1025 | auto scriptCache = DependencyManager::get<ScriptCache>(); | 1049 | auto scriptCache = DependencyManager::get<ScriptCache>(); |
| 1026 | bool isFileUrl = isURL && scriptOrURL.startsWith("file://"); | 1050 | bool isFileUrl = isURL && scriptOrURL.startsWith("file://"); |
| 1051 | auto fileName = QString("(EntityID:%1, %2)").arg(entityID.toString(), isURL ? scriptOrURL : "EmbededEntityScript"); | ||
| 1027 | 1052 | ||
| 1028 | // first check the syntax of the script contents | 1053 | QScriptProgram program(contents, fileName); |
| 1029 | QScriptSyntaxCheckResult syntaxCheck = QScriptEngine::checkSyntax(contents); | 1054 | if (!hasCorrectSyntax(program)) { |
| 1030 | if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { | ||
| 1031 | qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID; | ||
| 1032 | qCDebug(scriptengine) << " " << syntaxCheck.errorMessage() << ":" | ||
| 1033 | << syntaxCheck.errorLineNumber() << syntaxCheck.errorColumnNumber(); | ||
| 1034 | qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL; | ||
| 1035 | if (!isFileUrl) { | 1055 | if (!isFileUrl) { |
| 1036 | scriptCache->addScriptToBadScriptList(scriptOrURL); | 1056 | scriptCache->addScriptToBadScriptList(scriptOrURL); |
| 1037 | } | 1057 | } |
| @@ -1043,12 +1063,15 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co · 5 unmodified lines | |||
| 1043 | } | 1063 | } |
| 1044 | 1064 | ||
| 1045 | QScriptEngine sandbox; | 1065 | QScriptEngine sandbox; |
| 1046 | QScriptValue testConstructor = sandbox.evaluate(contents); | 1066 | QScriptValue testConstructor = sandbox.evaluate(program); |
| 1047 | 1067 | if (hadUncaughtExceptions(sandbox, program.fileName())) { | |
| 1068 | return; | ||
| 1069 | } | ||
| 1070 | |||
| 1048 | if (!testConstructor.isFunction()) { | 1071 | if (!testConstructor.isFunction()) { |
| 1049 | qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID; | 1072 | qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID << "\n" |
| 1050 | qCDebug(scriptengine) << " NOT CONSTRUCTOR"; | 1073 | " NOT CONSTRUCTOR\n" |
| 1051 | qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL; | 1074 | " SCRIPT:" << scriptOrURL; |
| 1052 | if (!isFileUrl) { | 1075 | if (!isFileUrl) { |
| 1053 | scriptCache->addScriptToBadScriptList(scriptOrURL); | 1076 | scriptCache->addScriptToBadScriptList(scriptOrURL); |
| 1054 | } | 1077 | } |
| @@ -1061,7 +1084,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co · 6 unmodified lines | |||
| 1061 | lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch(); | 1084 | lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch(); |
| 1062 | } | 1085 | } |
| 1063 | 1086 | ||
| 1064 | QScriptValue entityScriptConstructor = evaluate(contents); | 1087 | QScriptValue entityScriptConstructor = evaluate(contents, fileName); |
| 1065 | QScriptValue entityScriptObject = entityScriptConstructor.construct(); | 1088 | QScriptValue entityScriptObject = entityScriptConstructor.construct(); |
| 1066 | EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified }; | 1089 | EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified }; |
| 1067 | _entityScripts[entityID] = newDetails; | 1090 | _entityScripts[entityID] = newDetails; |