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 | out = qobject_cast<controller::InputController*>(object.toQObject()); | 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 | _scriptContents(scriptContents), | 123 | _scriptContents(scriptContents), |
| 92 | _isFinished(false), | 124 | _isFinished(false), |
| 93 | _isRunning(false), | 125 | _isRunning(false), |
| @@ -95,9 +127,6 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam · 1 unmodified line | |||
| 95 | _timerFunctionMap(), | 127 | _timerFunctionMap(), |
| 96 | _wantSignals(wantSignals), | 128 | _wantSignals(wantSignals), |
| 97 | _fileNameString(fileNameString), | 129 | _fileNameString(fileNameString), |
| 98 | _quatLibrary(), | ||
| 99 | _vec3Library(), | ||
| 100 | _uuidLibrary(), | ||
| 101 | _isUserLoaded(false), | 130 | _isUserLoaded(false), |
| 102 | _isReloading(false), | 131 | _isReloading(false), |
| 103 | _arrayBufferClass(new ArrayBufferClass(this)) | 132 | _arrayBufferClass(new ArrayBufferClass(this)) |
| @@ -505,26 +534,33 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& · 401 unmodified lines | |||
| 505 | // Connect up ALL the handlers to the global entities object's signals. | 534 | // Connect up ALL the handlers to the global entities object's signals. |
| 506 | // (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.) | 535 | // (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.) |
| 507 | auto entities = DependencyManager::get<EntityScriptingInterface>(); | 536 | auto entities = DependencyManager::get<EntityScriptingInterface>(); |
| 508 | connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, | 537 | connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityID) { |
| 509 | [=](const EntityItemID& entityID) { | 538 | _registeredHandlers.remove(entityID); |
| 510 | _registeredHandlers.remove(entityID); | 539 | }); |
| 511 | }); | 540 | |
| 512 | |||
| 513 | // Two common cases of event handler, differing only in argument signature. | 541 | // Two common cases of event handler, differing only in argument signature. |
| 514 | auto makeSingleEntityHandler = [=](const QString& eventName) -> std::function<void(const EntityItemID&)> { | 542 | using SingleEntityHandler = std::function<void(const EntityItemID&)>; |
| 515 | return [=](const EntityItemID& entityItemID) -> void { | 543 | auto makeSingleEntityHandler = [this](QString eventName) -> SingleEntityHandler { |
| 516 | generalHandler(entityItemID, eventName, [=]() -> QScriptValueList { | 544 | return [this, eventName](const EntityItemID& entityItemID) { |
| 517 | return QScriptValueList() << entityItemID.toScriptValue(this); | 545 | forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this) }); |
| 518 | }); | ||
| 519 | }; | 546 | }; |
| 520 | }; | 547 | }; |
| 521 | auto makeMouseHandler = [=](const QString& eventName) -> std::function<void(const EntityItemID&, const MouseEvent&)> { | 548 | |
| 522 | return [=](const EntityItemID& entityItemID, const MouseEvent& event) -> void { | 549 | using MouseHandler = std::function<void(const EntityItemID&, const MouseEvent&)>; |
| 523 | generalHandler(entityItemID, eventName, [=]() -> QScriptValueList { | 550 | auto makeMouseHandler = [this](QString eventName) -> MouseHandler { |
| 524 | return QScriptValueList() << entityItemID.toScriptValue(this) << event.toScriptValue(this); | 551 | return [this, eventName](const EntityItemID& entityItemID, const MouseEvent& event) { |
| 525 | }); | 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 | connect(entities.data(), &EntityScriptingInterface::enterEntity, this, makeSingleEntityHandler("enterEntity")); | 564 | connect(entities.data(), &EntityScriptingInterface::enterEntity, this, makeSingleEntityHandler("enterEntity")); |
| 529 | connect(entities.data(), &EntityScriptingInterface::leaveEntity, this, makeSingleEntityHandler("leaveEntity")); | 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 | connect(entities.data(), &EntityScriptingInterface::hoverOverEntity, this, makeMouseHandler("hoverOverEntity")); | 576 | connect(entities.data(), &EntityScriptingInterface::hoverOverEntity, this, makeMouseHandler("hoverOverEntity")); |
| 541 | connect(entities.data(), &EntityScriptingInterface::hoverLeaveEntity, this, makeMouseHandler("hoverLeaveEntity")); | 577 | connect(entities.data(), &EntityScriptingInterface::hoverLeaveEntity, this, makeMouseHandler("hoverLeaveEntity")); |
| 542 | 578 | ||
| 543 | connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this, | 579 | connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this, makeCollisionHandler("collisionWithEntity")); |
| 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 | }); | ||
| 549 | } | 580 | } |
| 550 | if (!_registeredHandlers.contains(entityID)) { | 581 | if (!_registeredHandlers.contains(entityID)) { |
| 551 | _registeredHandlers[entityID] = RegisteredEventHandlers(); | 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 | if (_stoppingAllScripts) { | 590 | if (_stoppingAllScripts) { |
| 560 | return QScriptValue(); // bail early | 591 | return QScriptValue(); // bail early |
| 561 | } | 592 | } |
| @@ -564,27 +595,30 @@ QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileN · 2 unmodified lines | |||
| 564 | QScriptValue result; | 595 | QScriptValue result; |
| 565 | #ifdef THREAD_DEBUGGING | 596 | #ifdef THREAD_DEBUGGING |
| 566 | qDebug() << "*** WARNING *** ScriptEngine::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " | 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 | #endif | 599 | #endif |
| 569 | QMetaObject::invokeMethod(this, "evaluate", Qt::BlockingQueuedConnection, | 600 | QMetaObject::invokeMethod(this, "evaluate", Qt::BlockingQueuedConnection, |
| 570 | Q_RETURN_ARG(QScriptValue, result), | 601 | Q_RETURN_ARG(QScriptValue, result), |
| 571 | Q_ARG(const QString&, program), | 602 | Q_ARG(const QString&, sourceCode), |
| 572 | Q_ARG(const QString&, fileName), | 603 | Q_ARG(const QString&, fileName), |
| 573 | Q_ARG(int, lineNumber)); | 604 | Q_ARG(int, lineNumber)); |
| 574 | return result; | 605 | return result; |
| 575 | } | 606 | } |
| 576 | 607 | ||
| 577 | _evaluatesPending++; | 608 | // Check syntax |
| 578 | QScriptValue result = QScriptEngine::evaluate(program, fileName, lineNumber); | 609 | const QScriptProgram program(sourceCode, fileName, lineNumber); |
| 579 | if (hasUncaughtException()) { | 610 | if (!hasCorrectSyntax(program)) { |
| 580 | int line = uncaughtExceptionLineNumber(); | 611 | return QScriptValue(); |
| 581 | qCDebug(scriptengine) << "Uncaught exception at (" << _fileNameString << " : " << fileName << ") line" << line << ": " << result.toString(); | ||
| 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 | if (_wantSignals) { | 619 | if (_wantSignals) { |
| 585 | emit evaluationFinished(result, hasUncaughtException()); | 620 | emit evaluationFinished(result, hadUncaughtException); |
| 586 | } | 621 | } |
| 587 | clearExceptions(); | ||
| 588 | return result; | 622 | return result; |
| 589 | } | 623 | } |
| 590 | 624 | ||
| @@ -602,7 +636,7 @@ void ScriptEngine::run() { · 11 unmodified lines | |||
| 602 | emit runningStateChanged(); | 636 | emit runningStateChanged(); |
| 603 | } | 637 | } |
| 604 | 638 | ||
| 605 | QScriptValue result = evaluate(_scriptContents); | 639 | QScriptValue result = evaluate(_scriptContents, _fileNameString); |
| 606 | 640 | ||
| 607 | QElapsedTimer startTime; | 641 | QElapsedTimer startTime; |
| 608 | startTime.start(); | 642 | startTime.start(); |
| @@ -643,15 +677,6 @@ void ScriptEngine::run() { · 34 unmodified lines | |||
| 643 | qint64 now = usecTimestampNow(); | 677 | qint64 now = usecTimestampNow(); |
| 644 | float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND; | 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 | if (!_isFinished) { | 680 | if (!_isFinished) { |
| 656 | if (_wantSignals) { | 681 | if (_wantSignals) { |
| 657 | emit update(deltaTime); | 682 | emit update(deltaTime); |
| @@ -659,6 +684,9 @@ void ScriptEngine::run() { · 1 unmodified line | |||
| 659 | } | 684 | } |
| 660 | lastUpdate = now; | 685 | lastUpdate = now; |
| 661 | 686 | ||
| 687 | if (hadUncauchtExceptions(*this, _fileNameString)) { | ||
| 688 | stop(); | ||
| 689 | } | ||
| 662 | } | 690 | } |
| 663 | 691 | ||
| 664 | stopAllTimers(); // make sure all our timers are stopped if the script is ending | 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 | // Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args | 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 | if (QThread::currentThread() != thread()) { | 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 | assert(false); | 930 | assert(false); |
| 903 | return; | 931 | return; |
| 904 | } | 932 | } |
| @@ -912,9 +940,8 @@ void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& e · 7 unmodified lines | |||
| 912 | } | 940 | } |
| 913 | QScriptValueList handlersForEvent = handlersOnEntity[eventName]; | 941 | QScriptValueList handlersForEvent = handlersOnEntity[eventName]; |
| 914 | if (!handlersForEvent.isEmpty()) { | 942 | if (!handlersForEvent.isEmpty()) { |
| 915 | QScriptValueList args = argGenerator(); | ||
| 916 | for (int i = 0; i < handlersForEvent.count(); ++i) { | 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 | auto scriptCache = DependencyManager::get<ScriptCache>(); | 1006 | auto scriptCache = DependencyManager::get<ScriptCache>(); |
| 980 | bool isFileUrl = isURL && scriptOrURL.startsWith("file://"); | 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 | 1010 | QScriptProgram program(contents, fileName); |
| 983 | QScriptSyntaxCheckResult syntaxCheck = QScriptEngine::checkSyntax(contents); | 1011 | if (!hasCorrectSyntax(program)) { |
| 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; | ||
| 989 | if (!isFileUrl) { | 1012 | if (!isFileUrl) { |
| 990 | scriptCache->addScriptToBadScriptList(scriptOrURL); | 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 | QScriptEngine sandbox; | 1022 | QScriptEngine sandbox; |
| 1000 | QScriptValue testConstructor = sandbox.evaluate(contents); | 1023 | QScriptValue testConstructor = sandbox.evaluate(program); |
| 1001 | 1024 | if (hadUncauchtExceptions(sandbox, program.fileName())) { | |
| 1025 | return; | ||
| 1026 | } | ||
| 1027 | |||
| 1002 | if (!testConstructor.isFunction()) { | 1028 | if (!testConstructor.isFunction()) { |
| 1003 | qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID; | 1029 | qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID << "\n" |
| 1004 | qCDebug(scriptengine) << " NOT CONSTRUCTOR"; | 1030 | " NOT CONSTRUCTOR\n" |
| 1005 | qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL; | 1031 | " SCRIPT:" << scriptOrURL; |
| 1006 | if (!isFileUrl) { | 1032 | if (!isFileUrl) { |
| 1007 | scriptCache->addScriptToBadScriptList(scriptOrURL); | 1033 | scriptCache->addScriptToBadScriptList(scriptOrURL); |
| 1008 | } | 1034 | } |
| @@ -1015,7 +1041,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co · 6 unmodified lines | |||
| 1015 | lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch(); | 1041 | lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch(); |
| 1016 | } | 1042 | } |
| 1017 | 1043 | ||
| 1018 | QScriptValue entityScriptConstructor = evaluate(contents); | 1044 | QScriptValue entityScriptConstructor = evaluate(contents, fileName); |
| 1019 | QScriptValue entityScriptObject = entityScriptConstructor.construct(); | 1045 | QScriptValue entityScriptObject = entityScriptConstructor.construct(); |
| 1020 | EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified }; | 1046 | EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified }; |
| 1021 | _entityScripts[entityID] = newDetails; | 1047 | _entityScripts[entityID] = newDetails; |