History

Entity scripts keep of the which entity they are executing for. When the entity goes away, any created timers do, too. Also, loading interface scripts is not allowed from entity scripts.

Howard Stearns committed 10 years ago

Commit 05943ed1 parent 0268ca02 files · +104 −21Browse files

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

Unified · Split

libraries/script-engine/src/ScriptEngine.cpp+90−19
@@ -485,10 +485,10 @@ void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QStrin · 484 unmodified lines
485 return;485 return;
486 }486 }
487 RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID];487 RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID];
488 QScriptValueList& handlersForEvent = handlersOnEntity[eventName];488 CallbackList& handlersForEvent = handlersOnEntity[eventName];
489 // QScriptValue does not have operator==(), so we can't use QList::removeOne and friends. So iterate.489 // QScriptValue does not have operator==(), so we can't use QList::removeOne and friends. So iterate.
490 for (int i = 0; i < handlersForEvent.count(); ++i) {490 for (int i = 0; i < handlersForEvent.count(); ++i) {
491 if (handlersForEvent[i].equals(handler)) {491 if (handlersForEvent[i].function.equals(handler)) {
492 handlersForEvent.removeAt(i);492 handlersForEvent.removeAt(i);
493 return; // Design choice: since comparison is relatively expensive, just remove the first matching handler.493 return; // Design choice: since comparison is relatively expensive, just remove the first matching handler.
494 }494 }
@@ -516,6 +516,13 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& · 21 unmodified lines
516 // Connect up ALL the handlers to the global entities object's signals.516 // Connect up ALL the handlers to the global entities object's signals.
517 // (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.)517 // (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.)
518 auto entities = DependencyManager::get<EntityScriptingInterface>();518 auto entities = DependencyManager::get<EntityScriptingInterface>();
519 // Bug? These handlers are deleted when entityID is deleted, which is nice.
520 // But if they are created by an entity script on a different entity, should they also be deleted when the entity script unloads?
521 // E.g., suppose a bow has an entity script that causes arrows to be created with a potential lifetime greater than the bow,
522 // and that the entity script adds (e.g., collision) handlers to the arrows. Should those handlers fire if the bow is unloaded?
523 // Also, what about when the entity script is REloaded?
524 // For now, we are leaving them around. Changing that would require some non-trivial digging around to find the
525 // handlers that were added while a given currentEntityIdentifier was in place. I don't think this is dangerous. Just perhaps unexpected. -HRS
519 connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityID) {526 connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityID) {
520 _registeredHandlers.remove(entityID);527 _registeredHandlers.remove(entityID);
521 });528 });
@@ -563,8 +570,9 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& · 41 unmodified lines
563 if (!_registeredHandlers.contains(entityID)) {570 if (!_registeredHandlers.contains(entityID)) {
564 _registeredHandlers[entityID] = RegisteredEventHandlers();571 _registeredHandlers[entityID] = RegisteredEventHandlers();
565 }572 }
566 QScriptValueList& handlersForEvent = _registeredHandlers[entityID][eventName];573 CallbackList& handlersForEvent = _registeredHandlers[entityID][eventName];
567 handlersForEvent << handler; // Note that the same handler can be added many times. See removeEntityEventHandler().574 CallbackData handlerData = {handler, currentEntityIdentifier};
575 handlersForEvent << handlerData; // Note that the same handler can be added many times. See removeEntityEventHandler().
568}576}
569 577
570 578
@@ -705,13 +713,30 @@ void ScriptEngine::run() { · 134 unmodified lines
705// NOTE: This is private because it must be called on the same thread that created the timers, which is why713// NOTE: This is private because it must be called on the same thread that created the timers, which is why
706// we want to only call it in our own run "shutdown" processing.714// we want to only call it in our own run "shutdown" processing.
707void ScriptEngine::stopAllTimers() {715void ScriptEngine::stopAllTimers() {
708 QMutableHashIterator<QTimer*, QScriptValue> i(_timerFunctionMap);716 QMutableHashIterator<QTimer*, CallbackData> i(_timerFunctionMap);
709 while (i.hasNext()) {717 while (i.hasNext()) {
710 i.next();718 i.next();
711 QTimer* timer = i.key();719 QTimer* timer = i.key();
712 stopTimer(timer);720 stopTimer(timer);
713 }721 }
714}722}
723void ScriptEngine::stopAllTimersForEntityScript(const EntityItemID& entityID) {
724 // We could maintain a separate map of entityID => QTimer, but someone will have to prove to me that it's worth the complexity. -HRS
725 QVector<QTimer*> toDelete;
726 QMutableHashIterator<QTimer*, CallbackData> i(_timerFunctionMap);
727 while (i.hasNext()) {
728 i.next();
729 if (i.value().definingEntityIdentifier != entityID) {
730 continue;
731 }
732 QTimer* timer = i.key();
733 toDelete << timer; // don't delete while we're iterating. save it.
734 }
735 for (auto timer:toDelete) { // now reap 'em
736 stopTimer(timer);
737 }
738
739}
715 740
716void ScriptEngine::stop() {741void ScriptEngine::stop() {
717 if (!_isFinished) {742 if (!_isFinished) {
@@ -743,13 +768,14 @@ void ScriptEngine::callAnimationStateHandler(QScriptValue callback, AnimVariantM · 25 unmodified lines
743 QScriptValue javascriptParameters = parameters.animVariantMapToScriptValue(this, names, useNames);768 QScriptValue javascriptParameters = parameters.animVariantMapToScriptValue(this, names, useNames);
744 QScriptValueList callingArguments;769 QScriptValueList callingArguments;
745 callingArguments << javascriptParameters;770 callingArguments << javascriptParameters;
771 assert(currentEntityIdentifier.isInvalidID()); // No animation state handlers from entity scripts.
746 QScriptValue result = callback.call(QScriptValue(), callingArguments);772 QScriptValue result = callback.call(QScriptValue(), callingArguments);
747 resultHandler(result);773 resultHandler(result);
748}774}
749 775
750void ScriptEngine::timerFired() {776void ScriptEngine::timerFired() {
751 QTimer* callingTimer = reinterpret_cast<QTimer*>(sender());777 QTimer* callingTimer = reinterpret_cast<QTimer*>(sender());
752 QScriptValue timerFunction = _timerFunctionMap.value(callingTimer);778 CallbackData timerData = _timerFunctionMap.value(callingTimer);
753 779
754 if (!callingTimer->isActive()) {780 if (!callingTimer->isActive()) {
755 // this timer is done, we can kill it781 // this timer is done, we can kill it
@@ -758,11 +784,12 @@ void ScriptEngine::timerFired() { · 2 unmodified lines
758 }784 }
759 785
760 // call the associated JS function, if it exists786 // call the associated JS function, if it exists
761 if (timerFunction.isValid()) {787 if (timerData.function.isValid()) {
762 timerFunction.call();788 callWithEnvironment(timerData.definingEntityIdentifier, timerData.function, timerData.function, QScriptValueList());
763 }789 }
764}790}
765 791
792
766QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot) {793QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot) {
767 // create the timer, add it to the map, and start it794 // create the timer, add it to the map, and start it
768 QTimer* newTimer = new QTimer(this);795 QTimer* newTimer = new QTimer(this);
@@ -773,7 +800,8 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int · 4 unmodified lines
773 // make sure the timer stops when the script does800 // make sure the timer stops when the script does
774 connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop);801 connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop);
775 802
776 _timerFunctionMap.insert(newTimer, function);803 CallbackData timerData = {function, currentEntityIdentifier};
804 _timerFunctionMap.insert(newTimer, timerData);
777 805
778 newTimer->start(intervalMS);806 newTimer->start(intervalMS);
779 return newTimer;807 return newTimer;
@@ -859,6 +887,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac · 79 unmodified lines
859 }887 }
860 888
861 BatchLoader* loader = new BatchLoader(urls);889 BatchLoader* loader = new BatchLoader(urls);
890 EntityItemID capturedEntityIdentifier = currentEntityIdentifier;
862 891
863 auto evaluateScripts = [=](const QMap<QUrl, QString>& data) {892 auto evaluateScripts = [=](const QMap<QUrl, QString>& data) {
864 auto parentURL = _parentURL;893 auto parentURL = _parentURL;
@@ -870,13 +899,16 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac · 5 unmodified lines
870 // Set the parent url so that path resolution will be relative899 // Set the parent url so that path resolution will be relative
871 // to this script's url during its initial evaluation900 // to this script's url during its initial evaluation
872 _parentURL = url.toString();901 _parentURL = url.toString();
873 QScriptValue result = evaluate(contents, url.toString());902 auto operation = [&]() {
903 evaluate(contents, url.toString());
904 };
905 doWithEnvironment(capturedEntityIdentifier, operation);
874 }906 }
875 }907 }
876 _parentURL = parentURL;908 _parentURL = parentURL;
877 909
878 if (callback.isFunction()) {910 if (callback.isFunction()) {
879 QScriptValue(callback).call();911 callWithEnvironment(capturedEntityIdentifier, QScriptValue(callback), QScriptValue(), QScriptValueList());
880 }912 }
881 913
882 loader->deleteLater();914 loader->deleteLater();
@@ -917,6 +949,11 @@ void ScriptEngine::load(const QString& loadFile) { · 34 unmodified lines
917 << "loadFile:" << loadFile << "parent script:" << getFilename();949 << "loadFile:" << loadFile << "parent script:" << getFilename();
918 return; // bail early950 return; // bail early
919 }951 }
952 if (!currentEntityIdentifier.isInvalidID()) {
953 qCWarning(scriptengine) << "Script.load() from entity script is ignored... "
954 << "loadFile:" << loadFile << "parent script:" << getFilename();
955 return; // bail early
956 }
920 957
921 QUrl url = resolvePath(loadFile);958 QUrl url = resolvePath(loadFile);
922 if (_isReloading) {959 if (_isReloading) {
@@ -933,7 +970,7 @@ void ScriptEngine::load(const QString& loadFile) { · 10 unmodified lines
933}970}
934 971
935// Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args972// Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args
936void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs) {973void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHandlerArgs) {
937 if (QThread::currentThread() != thread()) {974 if (QThread::currentThread() != thread()) {
938 qDebug() << "*** ERROR *** ScriptEngine::forwardHandlerCall() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]";975 qDebug() << "*** ERROR *** ScriptEngine::forwardHandlerCall() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]";
939 assert(false);976 assert(false);
@@ -946,10 +983,13 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin · 6 unmodified lines
946 if (!handlersOnEntity.contains(eventName)) {983 if (!handlersOnEntity.contains(eventName)) {
947 return;984 return;
948 }985 }
949 QScriptValueList handlersForEvent = handlersOnEntity[eventName];986 CallbackList handlersForEvent = handlersOnEntity[eventName];
950 if (!handlersForEvent.isEmpty()) {987 if (!handlersForEvent.isEmpty()) {
951 for (int i = 0; i < handlersForEvent.count(); ++i) {988 for (int i = 0; i < handlersForEvent.count(); ++i) {
952 handlersForEvent[i].call(QScriptValue(), eventHanderArgs);989 // handlersForEvent[i] can tonain many handlers that may have each been added by different interface or entity scripts,
990 // and the entity scripts may be for entities other than the one this is a handler for.
991 // Fortunately, the definingEntityIdentifier captured the entity script id (if any) when the handler was added.
992 callWithEnvironment(handlersForEvent[i].definingEntityIdentifier, handlersForEvent[i].function, QScriptValue(), eventHandlerArgs);
953 }993 }
954 }994 }
955}995}
@@ -1055,9 +1095,13 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co · 99 unmodified lines
1055 QString file = QUrl(scriptOrURL).toLocalFile();1095 QString file = QUrl(scriptOrURL).toLocalFile();
1056 lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch();1096 lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch();
1057 }1097 }
1098 QScriptValue entityScriptConstructor, entityScriptObject;
1099 auto initialization = [&]{
1100 entityScriptConstructor = evaluate(contents, fileName);
1101 entityScriptObject = entityScriptConstructor.construct();
1102 };
1103 doWithEnvironment(entityID, initialization);
1058 1104
1059 QScriptValue entityScriptConstructor = evaluate(contents, fileName);
1060 QScriptValue entityScriptObject = entityScriptConstructor.construct();
1061 EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified };1105 EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified };
1062 _entityScripts[entityID] = newDetails;1106 _entityScripts[entityID] = newDetails;
1063 if (isURL) {1107 if (isURL) {
@@ -1087,6 +1131,7 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) { · 23 unmodified lines
1087 if (_entityScripts.contains(entityID)) {1131 if (_entityScripts.contains(entityID)) {
1088 callEntityScriptMethod(entityID, "unload");1132 callEntityScriptMethod(entityID, "unload");
1089 _entityScripts.remove(entityID);1133 _entityScripts.remove(entityID);
1134 stopAllTimersForEntityScript(entityID);
1090 }1135 }
1091}1136}
1092 1137
@@ -1142,6 +1187,32 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) { · 49 unmodified lines
1142 recurseGuard = false;1187 recurseGuard = false;
1143}1188}
1144 1189
1190// Execute operation in the appropriate context for (the possibly empty) entityID.
1191// Even if entityID is supplied as currentEntityIdentifier, this still documents the source
1192// of the code being executed (e.g., if we ever sandbox different entity scripts, or provide different
1193// global values for different entity scripts).
1194void ScriptEngine::doWithEnvironment(const EntityItemID& entityID, std::function<void()> operation) {
1195 EntityItemID oldIdentifier = currentEntityIdentifier;
1196 currentEntityIdentifier = entityID;
1197
1198#if DEBUG_CURRENT_ENTITY
1199 QScriptValue oldData = this->globalObject().property("debugEntityID");
1200 this->globalObject().setProperty("debugEntityID", entityID.toScriptValue(this)); // Make the entityID available to javascript as a global.
1201 operation();
1202 this->globalObject().setProperty("debugEntityID", oldData);
1203#else
1204 operation();
1205#endif
1206
1207 currentEntityIdentifier = oldIdentifier;
1208}
1209void ScriptEngine::callWithEnvironment(const EntityItemID& entityID, QScriptValue function, QScriptValue thisObject, QScriptValueList args) {
1210 auto operation = [&]() {
1211 function.call(thisObject, args);
1212 };
1213 doWithEnvironment(entityID, operation);
1214}
1215
1145void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params) {1216void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params) {
1146 if (QThread::currentThread() != thread()) {1217 if (QThread::currentThread() != thread()) {
1147#ifdef THREAD_DEBUGGING1218#ifdef THREAD_DEBUGGING
@@ -1168,7 +1239,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS · 20 unmodified lines
1168 QScriptValueList args;1239 QScriptValueList args;
1169 args << entityID.toScriptValue(this);1240 args << entityID.toScriptValue(this);
1170 args << qScriptValueFromSequence(this, params);1241 args << qScriptValueFromSequence(this, params);
1171 entityScript.property(methodName).call(entityScript, args);1242 callWithEnvironment(entityID, entityScript.property(methodName), entityScript, args);
1172 }1243 }
1173 1244
1174 }1245 }
@@ -1200,7 +1271,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS · 25 unmodified lines
1200 QScriptValueList args;1271 QScriptValueList args;
1201 args << entityID.toScriptValue(this);1272 args << entityID.toScriptValue(this);
1202 args << event.toScriptValue(this);1273 args << event.toScriptValue(this);
1203 entityScript.property(methodName).call(entityScript, args);1274 callWithEnvironment(entityID, entityScript.property(methodName), entityScript, args);
1204 }1275 }
1205 }1276 }
1206}1277}
@@ -1234,7 +1305,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS · 27 unmodified lines
1234 args << entityID.toScriptValue(this);1305 args << entityID.toScriptValue(this);
1235 args << otherID.toScriptValue(this);1306 args << otherID.toScriptValue(this);
1236 args << collisionToScriptValue(this, collision);1307 args << collisionToScriptValue(this, collision);
1237 entityScript.property(methodName).call(entityScript, args);1308 callWithEnvironment(entityID, entityScript.property(methodName), entityScript, args);
1238 }1309 }
1239 }1310 }
1240}1311}