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 | 79 | QScriptValue inputControllerToScriptValue(QScriptEngine *engine, AbstractInputController* const &in) { |
| 80 | 80 | return engine->newQObject(in); |
| 81 | 81 | } |
| 82 | − | |
| 83 | 82 | void inputControllerFromScriptValue(const QScriptValue &object, AbstractInputController* &out) { |
| 84 | 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 | 117 | ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString, |
| 88 | 118 | AbstractControllerScriptingInterface* controllerScriptingInterface, bool wantSignals) : |
| 89 | 119 | |
| @@ -95,9 +125,6 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam · 5 unmodified lines | ||
| 95 | 125 | _wantSignals(wantSignals), |
| 96 | 126 | _controllerScriptingInterface(controllerScriptingInterface), |
| 97 | 127 | _fileNameString(fileNameString), |
| 98 | − _quatLibrary(), | |
| 99 | − _vec3Library(), | |
| 100 | − _uuidLibrary(), | |
| 101 | 128 | _isUserLoaded(false), |
| 102 | 129 | _isReloading(false), |
| 103 | 130 | _arrayBufferClass(new ArrayBufferClass(this)) |
| @@ -531,26 +558,33 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& · 427 unmodified lines | ||
| 531 | 558 | // Connect up ALL the handlers to the global entities object's signals. |
| 532 | 559 | // (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.) |
| 533 | 560 | auto entities = DependencyManager::get<EntityScriptingInterface>(); |
| 534 | − connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, | |
| 535 | − [=](const EntityItemID& entityID) { | |
| 536 | − _registeredHandlers.remove(entityID); | |
| 537 | − }); | |
| 538 | − | |
| 561 | + connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityID) { | |
| 562 | + _registeredHandlers.remove(entityID); | |
| 563 | + }); | |
| 564 | + | |
| 539 | 565 | // Two common cases of event handler, differing only in argument signature. |
| 540 | − auto makeSingleEntityHandler = [=](const QString& eventName) -> std::function<void(const EntityItemID&)> { | |
| 541 | − return [=](const EntityItemID& entityItemID) -> void { | |
| 542 | − generalHandler(entityItemID, eventName, [=]() -> QScriptValueList { | |
| 543 | − return QScriptValueList() << entityItemID.toScriptValue(this); | |
| 544 | − }); | |
| 566 | + using SingleEntityHandler = std::function<void(const EntityItemID&)>; | |
| 567 | + auto makeSingleEntityHandler = [this](QString eventName) -> SingleEntityHandler { | |
| 568 | + return [this, eventName](const EntityItemID& entityItemID) { | |
| 569 | + forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this) }); | |
| 545 | 570 | }; |
| 546 | 571 | }; |
| 547 | − auto makeMouseHandler = [=](const QString& eventName) -> std::function<void(const EntityItemID&, const MouseEvent&)> { | |
| 548 | − return [=](const EntityItemID& entityItemID, const MouseEvent& event) -> void { | |
| 549 | − generalHandler(entityItemID, eventName, [=]() -> QScriptValueList { | |
| 550 | − return QScriptValueList() << entityItemID.toScriptValue(this) << event.toScriptValue(this); | |
| 551 | − }); | |
| 572 | + | |
| 573 | + using MouseHandler = std::function<void(const EntityItemID&, const MouseEvent&)>; | |
| 574 | + auto makeMouseHandler = [this](QString eventName) -> MouseHandler { | |
| 575 | + return [this, eventName](const EntityItemID& entityItemID, const MouseEvent& event) { | |
| 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 | 588 | connect(entities.data(), &EntityScriptingInterface::enterEntity, this, makeSingleEntityHandler("enterEntity")); |
| 555 | 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 | 600 | connect(entities.data(), &EntityScriptingInterface::hoverOverEntity, this, makeMouseHandler("hoverOverEntity")); |
| 567 | 601 | connect(entities.data(), &EntityScriptingInterface::hoverLeaveEntity, this, makeMouseHandler("hoverLeaveEntity")); |
| 568 | 602 | |
| 569 | − connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this, | |
| 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 | − }); | |
| 603 | + connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this, makeCollisionHandler("collisionWithEntity")); | |
| 575 | 604 | } |
| 576 | 605 | if (!_registeredHandlers.contains(entityID)) { |
| 577 | 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 | 614 | if (_stoppingAllScripts) { |
| 586 | 615 | return QScriptValue(); // bail early |
| 587 | 616 | } |
| @@ -590,27 +619,30 @@ QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileN · 2 unmodified lines | ||
| 590 | 619 | QScriptValue result; |
| 591 | 620 | #ifdef THREAD_DEBUGGING |
| 592 | 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 | 623 | #endif |
| 595 | 624 | QMetaObject::invokeMethod(this, "evaluate", Qt::BlockingQueuedConnection, |
| 596 | 625 | Q_RETURN_ARG(QScriptValue, result), |
| 597 | − Q_ARG(const QString&, program), | |
| 626 | + Q_ARG(const QString&, sourceCode), | |
| 598 | 627 | Q_ARG(const QString&, fileName), |
| 599 | 628 | Q_ARG(int, lineNumber)); |
| 600 | 629 | return result; |
| 601 | 630 | } |
| 602 | − | |
| 603 | − _evaluatesPending++; | |
| 604 | − QScriptValue result = QScriptEngine::evaluate(program, fileName, lineNumber); | |
| 605 | − if (hasUncaughtException()) { | |
| 606 | − int line = uncaughtExceptionLineNumber(); | |
| 607 | − qCDebug(scriptengine) << "Uncaught exception at (" << _fileNameString << " : " << fileName << ") line" << line << ": " << result.toString(); | |
| 631 | + | |
| 632 | + // Check syntax | |
| 633 | + const QScriptProgram program(sourceCode, fileName, lineNumber); | |
| 634 | + if (!hasCorrectSyntax(program)) { | |
| 635 | + return QScriptValue(); | |
| 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 | 643 | if (_wantSignals) { |
| 611 | − emit evaluationFinished(result, hasUncaughtException()); | |
| 644 | + emit evaluationFinished(result, hadUncaughtException); | |
| 612 | 645 | } |
| 613 | − clearExceptions(); | |
| 614 | 646 | return result; |
| 615 | 647 | } |
| 616 | 648 | |
| @@ -628,7 +660,7 @@ void ScriptEngine::run() { · 11 unmodified lines | ||
| 628 | 660 | emit runningStateChanged(); |
| 629 | 661 | } |
| 630 | 662 | |
| 631 | − QScriptValue result = evaluate(_scriptContents); | |
| 663 | + QScriptValue result = evaluate(_scriptContents, _fileNameString); | |
| 632 | 664 | |
| 633 | 665 | QElapsedTimer startTime; |
| 634 | 666 | startTime.start(); |
| @@ -669,15 +701,6 @@ void ScriptEngine::run() { · 34 unmodified lines | ||
| 669 | 701 | qint64 now = usecTimestampNow(); |
| 670 | 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 | 704 | if (!_isFinished) { |
| 682 | 705 | if (_wantSignals) { |
| 683 | 706 | emit update(deltaTime); |
| @@ -685,6 +708,8 @@ void ScriptEngine::run() { · 1 unmodified line | ||
| 685 | 708 | } |
| 686 | 709 | lastUpdate = now; |
| 687 | 710 | |
| 711 | + // Debug and clear exceptions | |
| 712 | + hadUncaughtExceptions(*this, _fileNameString); | |
| 688 | 713 | } |
| 689 | 714 | |
| 690 | 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 | 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 | 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 | 974 | assert(false); |
| 950 | 975 | return ; |
| 951 | 976 | } |
| @@ -958,9 +983,8 @@ void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& e · 6 unmodified lines | ||
| 958 | 983 | } |
| 959 | 984 | QScriptValueList handlersForEvent = handlersOnEntity[eventName]; |
| 960 | 985 | if (!handlersForEvent.isEmpty()) { |
| 961 | − QScriptValueList args = argGenerator(); | |
| 962 | 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 | 1049 | auto scriptCache = DependencyManager::get<ScriptCache>(); |
| 1026 | 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 | |
| 1029 | − QScriptSyntaxCheckResult syntaxCheck = QScriptEngine::checkSyntax(contents); | |
| 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; | |
| 1053 | + QScriptProgram program(contents, fileName); | |
| 1054 | + if (!hasCorrectSyntax(program)) { | |
| 1035 | 1055 | if (!isFileUrl) { |
| 1036 | 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 | 1065 | QScriptEngine sandbox; |
| 1046 | − QScriptValue testConstructor = sandbox.evaluate(contents); | |
| 1047 | − | |
| 1066 | + QScriptValue testConstructor = sandbox.evaluate(program); | |
| 1067 | + if (hadUncaughtExceptions(sandbox, program.fileName())) { | |
| 1068 | + return; | |
| 1069 | + } | |
| 1070 | + | |
| 1048 | 1071 | if (!testConstructor.isFunction()) { |
| 1049 | − qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID; | |
| 1050 | − qCDebug(scriptengine) << " NOT CONSTRUCTOR"; | |
| 1051 | − qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL; | |
| 1072 | + qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID << "\n" | |
| 1073 | + " NOT CONSTRUCTOR\n" | |
| 1074 | + " SCRIPT:" << scriptOrURL; | |
| 1052 | 1075 | if (!isFileUrl) { |
| 1053 | 1076 | scriptCache->addScriptToBadScriptList(scriptOrURL); |
| 1054 | 1077 | } |
| @@ -1061,7 +1084,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co · 6 unmodified lines | ||
| 1061 | 1084 | lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch(); |
| 1062 | 1085 | } |
| 1063 | 1086 | |
| 1064 | − QScriptValue entityScriptConstructor = evaluate(contents); | |
| 1087 | + QScriptValue entityScriptConstructor = evaluate(contents, fileName); | |
| 1065 | 1088 | QScriptValue entityScriptObject = entityScriptConstructor.construct(); |
| 1066 | 1089 | EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified }; |
| 1067 | 1090 | _entityScripts[entityID] = newDetails; |