Showing the changes to libraries/script-engine/src/ScriptEngine.cpp (1 of 53 files). Show all
Unified · Split
| @@ -86,8 +86,40 @@ void inputControllerFromScriptValue(const QScriptValue &object, controller::Inpu · 85 unmodified lines | ||
| 86 | 86 | out = qobject_cast<controller::InputController*>(object.toQObject()); |
| 87 | 87 | } |
| 88 | 88 | |
| 89 | −ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString, bool wantSignals) : | |
| 90 | 89 | |
| 90 | + | |
| 91 | +static bool hasCorrectSyntax(const QScriptProgram& program) { | |
| 92 | + const auto syntaxCheck = QScriptEngine::checkSyntax(program.sourceCode()); | |
| 93 | + if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { | |
| 94 | + const auto error = syntaxCheck.errorMessage(); | |
| 95 | + const auto line = QString::number(syntaxCheck.errorLineNumber()); | |
| 96 | + const auto column = QString::number(syntaxCheck.errorColumnNumber()); | |
| 97 | + const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, program.fileName(), line, column); | |
| 98 | + qCWarning(scriptengine) << qPrintable(message); | |
| 99 | + return false; | |
| 100 | + } | |
| 101 | + return true; | |
| 102 | +} | |
| 103 | + | |
| 104 | +static bool hadUncauchtExceptions(QScriptEngine& engine, const QString& fileName) { | |
| 105 | + if (engine.hasUncaughtException()) { | |
| 106 | + const auto backtrace = engine.uncaughtExceptionBacktrace(); | |
| 107 | + const auto exception = engine.uncaughtException().toString(); | |
| 108 | + const auto line = QString::number(engine.uncaughtExceptionLineNumber()); | |
| 109 | + engine.clearExceptions(); | |
| 110 | + | |
| 111 | + auto message = QString("[UncaughtException] %1 in %2:%3").arg(exception, fileName, line); | |
| 112 | + if (!backtrace.empty()) { | |
| 113 | + static const auto lineSeparator = "\n "; | |
| 114 | + message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator)); | |
| 115 | + } | |
| 116 | + qCWarning(scriptengine) << qPrintable(message); | |
| 117 | + return true; | |
| 118 | + } | |
| 119 | + return false; | |
| 120 | +} | |
| 121 | + | |
| 122 | +ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString, bool wantSignals) : | |
| 91 | 123 | _scriptContents(scriptContents), |
| 92 | 124 | _isFinished(false), |
| 93 | 125 | _isRunning(false), |
| @@ -95,9 +127,6 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam · 1 unmodified line | ||
| 95 | 127 | _timerFunctionMap(), |
| 96 | 128 | _wantSignals(wantSignals), |
| 97 | 129 | _fileNameString(fileNameString), |
| 98 | − _quatLibrary(), | |
| 99 | − _vec3Library(), | |
| 100 | − _uuidLibrary(), | |
| 101 | 130 | _isUserLoaded(false), |
| 102 | 131 | _isReloading(false), |
| 103 | 132 | _arrayBufferClass(new ArrayBufferClass(this)) |
| @@ -505,26 +534,33 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& · 401 unmodified lines | ||
| 505 | 534 | // Connect up ALL the handlers to the global entities object's signals. |
| 506 | 535 | // (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.) |
| 507 | 536 | auto entities = DependencyManager::get<EntityScriptingInterface>(); |
| 508 | − connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, | |
| 509 | − [=](const EntityItemID& entityID) { | |
| 510 | − _registeredHandlers.remove(entityID); | |
| 511 | − }); | |
| 512 | − | |
| 537 | + connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityID) { | |
| 538 | + _registeredHandlers.remove(entityID); | |
| 539 | + }); | |
| 540 | + | |
| 513 | 541 | // Two common cases of event handler, differing only in argument signature. |
| 514 | − auto makeSingleEntityHandler = [=](const QString& eventName) -> std::function<void(const EntityItemID&)> { | |
| 515 | − return [=](const EntityItemID& entityItemID) -> void { | |
| 516 | − generalHandler(entityItemID, eventName, [=]() -> QScriptValueList { | |
| 517 | − return QScriptValueList() << entityItemID.toScriptValue(this); | |
| 518 | − }); | |
| 542 | + using SingleEntityHandler = std::function<void(const EntityItemID&)>; | |
| 543 | + auto makeSingleEntityHandler = [this](QString eventName) -> SingleEntityHandler { | |
| 544 | + return [this, eventName](const EntityItemID& entityItemID) { | |
| 545 | + forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this) }); | |
| 519 | 546 | }; |
| 520 | 547 | }; |
| 521 | − auto makeMouseHandler = [=](const QString& eventName) -> std::function<void(const EntityItemID&, const MouseEvent&)> { | |
| 522 | − return [=](const EntityItemID& entityItemID, const MouseEvent& event) -> void { | |
| 523 | − generalHandler(entityItemID, eventName, [=]() -> QScriptValueList { | |
| 524 | − return QScriptValueList() << entityItemID.toScriptValue(this) << event.toScriptValue(this); | |
| 525 | − }); | |
| 548 | + | |
| 549 | + using MouseHandler = std::function<void(const EntityItemID&, const MouseEvent&)>; | |
| 550 | + auto makeMouseHandler = [this](QString eventName) -> MouseHandler { | |
| 551 | + return [this, eventName](const EntityItemID& entityItemID, const MouseEvent& event) { | |
| 552 | + forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this), event.toScriptValue(this) }); | |
| 526 | 553 | }; |
| 527 | 554 | }; |
| 555 | + | |
| 556 | + using CollisionHandler = std::function<void(const EntityItemID&, const EntityItemID&, const Collision&)>; | |
| 557 | + auto makeCollisionHandler = [this](QString eventName) -> CollisionHandler { | |
| 558 | + return [this, eventName](const EntityItemID& idA, const EntityItemID& idB, const Collision& collision) { | |
| 559 | + forwardHandlerCall(idA, eventName, { idA.toScriptValue(this), idB.toScriptValue(this), | |
| 560 | + collisionToScriptValue(this, collision) }); | |
| 561 | + }; | |
| 562 | + }; | |
| 563 | + | |
| 528 | 564 | connect(entities.data(), &EntityScriptingInterface::enterEntity, this, makeSingleEntityHandler("enterEntity")); |
| 529 | 565 | connect(entities.data(), &EntityScriptingInterface::leaveEntity, this, makeSingleEntityHandler("leaveEntity")); |
| 530 | 566 | |
| @@ -540,12 +576,7 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& · 9 unmodified lines | ||
| 540 | 576 | connect(entities.data(), &EntityScriptingInterface::hoverOverEntity, this, makeMouseHandler("hoverOverEntity")); |
| 541 | 577 | connect(entities.data(), &EntityScriptingInterface::hoverLeaveEntity, this, makeMouseHandler("hoverLeaveEntity")); |
| 542 | 578 | |
| 543 | − connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this, | |
| 544 | − [=](const EntityItemID& idA, const EntityItemID& idB, const Collision& collision) { | |
| 545 | − generalHandler(idA, "collisionWithEntity", [=]() { | |
| 546 | − return QScriptValueList () << idA.toScriptValue(this) << idB.toScriptValue(this) << collisionToScriptValue(this, collision); | |
| 547 | − }); | |
| 548 | − }); | |
| 579 | + connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this, makeCollisionHandler("collisionWithEntity")); | |
| 549 | 580 | } |
| 550 | 581 | if (!_registeredHandlers.contains(entityID)) { |
| 551 | 582 | _registeredHandlers[entityID] = RegisteredEventHandlers(); |
| @@ -555,7 +586,7 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& · 3 unmodified lines | ||
| 555 | 586 | } |
| 556 | 587 | |
| 557 | 588 | |
| 558 | −QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileName, int lineNumber) { | |
| 589 | +QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fileName, int lineNumber) { | |
| 559 | 590 | if (_stoppingAllScripts) { |
| 560 | 591 | return QScriptValue(); // bail early |
| 561 | 592 | } |
| @@ -564,27 +595,30 @@ QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileN · 2 unmodified lines | ||
| 564 | 595 | QScriptValue result; |
| 565 | 596 | #ifdef THREAD_DEBUGGING |
| 566 | 597 | qDebug() << "*** WARNING *** ScriptEngine::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " |
| 567 | − "program:" << program << " fileName:" << fileName << "lineNumber:" << lineNumber; | |
| 598 | + "sourceCode:" << sourceCode << " fileName:" << fileName << "lineNumber:" << lineNumber; | |
| 568 | 599 | #endif |
| 569 | 600 | QMetaObject::invokeMethod(this, "evaluate", Qt::BlockingQueuedConnection, |
| 570 | 601 | Q_RETURN_ARG(QScriptValue, result), |
| 571 | − Q_ARG(const QString&, program), | |
| 602 | + Q_ARG(const QString&, sourceCode), | |
| 572 | 603 | Q_ARG(const QString&, fileName), |
| 573 | 604 | Q_ARG(int, lineNumber)); |
| 574 | 605 | return result; |
| 575 | 606 | } |
| 576 | − | |
| 577 | − _evaluatesPending++; | |
| 578 | − QScriptValue result = QScriptEngine::evaluate(program, fileName, lineNumber); | |
| 579 | − if (hasUncaughtException()) { | |
| 580 | − int line = uncaughtExceptionLineNumber(); | |
| 581 | − qCDebug(scriptengine) << "Uncaught exception at (" << _fileNameString << " : " << fileName << ") line" << line << ": " << result.toString(); | |
| 607 | + | |
| 608 | + // Check syntax | |
| 609 | + const QScriptProgram program(sourceCode, fileName, lineNumber); | |
| 610 | + if (!hasCorrectSyntax(program)) { | |
| 611 | + return QScriptValue(); | |
| 582 | 612 | } |
| 583 | − _evaluatesPending--; | |
| 613 | + | |
| 614 | + ++_evaluatesPending; | |
| 615 | + const auto result = QScriptEngine::evaluate(program); | |
| 616 | + --_evaluatesPending; | |
| 617 | + | |
| 618 | + const auto hadUncaughtException = hadUncauchtExceptions(*this, program.fileName()); | |
| 584 | 619 | if (_wantSignals) { |
| 585 | − emit evaluationFinished(result, hasUncaughtException()); | |
| 620 | + emit evaluationFinished(result, hadUncaughtException); | |
| 586 | 621 | } |
| 587 | − clearExceptions(); | |
| 588 | 622 | return result; |
| 589 | 623 | } |
| 590 | 624 | |
| @@ -602,7 +636,7 @@ void ScriptEngine::run() { · 11 unmodified lines | ||
| 602 | 636 | emit runningStateChanged(); |
| 603 | 637 | } |
| 604 | 638 | |
| 605 | − QScriptValue result = evaluate(_scriptContents); | |
| 639 | + QScriptValue result = evaluate(_scriptContents, _fileNameString); | |
| 606 | 640 | |
| 607 | 641 | QElapsedTimer startTime; |
| 608 | 642 | startTime.start(); |
| @@ -643,15 +677,6 @@ void ScriptEngine::run() { · 34 unmodified lines | ||
| 643 | 677 | qint64 now = usecTimestampNow(); |
| 644 | 678 | float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND; |
| 645 | 679 | |
| 646 | − if (hasUncaughtException()) { | |
| 647 | − int line = uncaughtExceptionLineNumber(); | |
| 648 | − qCDebug(scriptengine) << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << uncaughtException().toString(); | |
| 649 | − if (_wantSignals) { | |
| 650 | − emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + uncaughtException().toString()); | |
| 651 | − } | |
| 652 | − clearExceptions(); | |
| 653 | − } | |
| 654 | − | |
| 655 | 680 | if (!_isFinished) { |
| 656 | 681 | if (_wantSignals) { |
| 657 | 682 | emit update(deltaTime); |
| @@ -659,6 +684,9 @@ void ScriptEngine::run() { · 1 unmodified line | ||
| 659 | 684 | } |
| 660 | 685 | lastUpdate = now; |
| 661 | 686 | |
| 687 | + if (hadUncauchtExceptions(*this, _fileNameString)) { | |
| 688 | + stop(); | |
| 689 | + } | |
| 662 | 690 | } |
| 663 | 691 | |
| 664 | 692 | stopAllTimers(); // make sure all our timers are stopped if the script is ending |
| @@ -896,9 +924,9 @@ void ScriptEngine::load(const QString& loadFile) { · 231 unmodified lines | ||
| 896 | 924 | } |
| 897 | 925 | |
| 898 | 926 | // Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args |
| 899 | −void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& eventName, std::function<QScriptValueList()> argGenerator) { | |
| 927 | +void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs) { | |
| 900 | 928 | if (QThread::currentThread() != thread()) { |
| 901 | − qDebug() << "*** ERROR *** ScriptEngine::generalHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; | |
| 929 | + qDebug() << "*** ERROR *** ScriptEngine::forwardHandlerCall() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; | |
| 902 | 930 | assert(false); |
| 903 | 931 | return; |
| 904 | 932 | } |
| @@ -912,9 +940,8 @@ void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& e · 7 unmodified lines | ||
| 912 | 940 | } |
| 913 | 941 | QScriptValueList handlersForEvent = handlersOnEntity[eventName]; |
| 914 | 942 | if (!handlersForEvent.isEmpty()) { |
| 915 | − QScriptValueList args = argGenerator(); | |
| 916 | 943 | for (int i = 0; i < handlersForEvent.count(); ++i) { |
| 917 | − handlersForEvent[i].call(QScriptValue(), args); | |
| 944 | + handlersForEvent[i].call(QScriptValue(), eventHanderArgs); | |
| 918 | 945 | } |
| 919 | 946 | } |
| 920 | 947 | } |
| @@ -978,14 +1005,10 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co · 57 unmodified lines | ||
| 978 | 1005 | |
| 979 | 1006 | auto scriptCache = DependencyManager::get<ScriptCache>(); |
| 980 | 1007 | bool isFileUrl = isURL && scriptOrURL.startsWith("file://"); |
| 1008 | + auto fileName = QString("(EntityID:%1, %2)").arg(entityID.toString(), isURL ? scriptOrURL : "EmbededEntityScript"); | |
| 981 | 1009 | |
| 982 | − // first check the syntax of the script contents | |
| 983 | − QScriptSyntaxCheckResult syntaxCheck = QScriptEngine::checkSyntax(contents); | |
| 984 | − if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { | |
| 985 | − qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID; | |
| 986 | − qCDebug(scriptengine) << " " << syntaxCheck.errorMessage() << ":" | |
| 987 | − << syntaxCheck.errorLineNumber() << syntaxCheck.errorColumnNumber(); | |
| 988 | − qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL; | |
| 1010 | + QScriptProgram program(contents, fileName); | |
| 1011 | + if (!hasCorrectSyntax(program)) { | |
| 989 | 1012 | if (!isFileUrl) { |
| 990 | 1013 | scriptCache->addScriptToBadScriptList(scriptOrURL); |
| 991 | 1014 | } |
| @@ -997,12 +1020,15 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co · 5 unmodified lines | ||
| 997 | 1020 | } |
| 998 | 1021 | |
| 999 | 1022 | QScriptEngine sandbox; |
| 1000 | − QScriptValue testConstructor = sandbox.evaluate(contents); | |
| 1001 | − | |
| 1023 | + QScriptValue testConstructor = sandbox.evaluate(program); | |
| 1024 | + if (hadUncauchtExceptions(sandbox, program.fileName())) { | |
| 1025 | + return; | |
| 1026 | + } | |
| 1027 | + | |
| 1002 | 1028 | if (!testConstructor.isFunction()) { |
| 1003 | − qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID; | |
| 1004 | − qCDebug(scriptengine) << " NOT CONSTRUCTOR"; | |
| 1005 | − qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL; | |
| 1029 | + qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID << "\n" | |
| 1030 | + " NOT CONSTRUCTOR\n" | |
| 1031 | + " SCRIPT:" << scriptOrURL; | |
| 1006 | 1032 | if (!isFileUrl) { |
| 1007 | 1033 | scriptCache->addScriptToBadScriptList(scriptOrURL); |
| 1008 | 1034 | } |
| @@ -1015,7 +1041,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co · 6 unmodified lines | ||
| 1015 | 1041 | lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch(); |
| 1016 | 1042 | } |
| 1017 | 1043 | |
| 1018 | − QScriptValue entityScriptConstructor = evaluate(contents); | |
| 1044 | + QScriptValue entityScriptConstructor = evaluate(contents, fileName); | |
| 1019 | 1045 | QScriptValue entityScriptObject = entityScriptConstructor.construct(); |
| 1020 | 1046 | EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified }; |
| 1021 | 1047 | _entityScripts[entityID] = newDetails; |