History

Merge branch 'master' of https://github.com/highfidelity/hifi into expose-anim-vars

Howard Stearns committed 11 years ago

Commit d79d0bc2 parents 4b4907c d32477166 files · +728 −542Browse files

Showing the changes to libraries/script-engine/src/ScriptEngine.cpp (1 of 66 files). Show all

Unified · Split

libraries/script-engine/src/ScriptEngine.cpp+87−64
@@ -79,11 +79,41 @@ void avatarDataFromScriptValue(const QScriptValue &object, AvatarData* &out) { · 78 unmodified lines
79QScriptValue inputControllerToScriptValue(QScriptEngine *engine, AbstractInputController* const &in) {79QScriptValue inputControllerToScriptValue(QScriptEngine *engine, AbstractInputController* const &in) {
80 return engine->newQObject(in);80 return engine->newQObject(in);
81}81}
82
83void inputControllerFromScriptValue(const QScriptValue &object, AbstractInputController* &out) {82void inputControllerFromScriptValue(const QScriptValue &object, AbstractInputController* &out) {
84 out = qobject_cast<AbstractInputController*>(object.toQObject());83 out = qobject_cast<AbstractInputController*>(object.toQObject());
85}84}
86 85
86static 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
99static 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
87ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString,117ScriptEngine::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
584QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileName, int lineNumber) {613QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fileName, int lineNumber) {
585 if (_stoppingAllScripts) {614 if (_stoppingAllScripts) {
586 return QScriptValue(); // bail early615 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_DEBUGGING620 #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 #endif623 #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 ending715 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 args970// Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args
946void ScriptEngine::generalHandler(const EntityItemID& entityID, const QString& eventName, std::function<QScriptValueList()> argGenerator) {971void 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 contents1053 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;