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
485485 return;
486486 }
487487 RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID];
488− QScriptValueList& handlersForEvent = handlersOnEntity[eventName];
488+ CallbackList& handlersForEvent = handlersOnEntity[eventName];
489489 // QScriptValue does not have operator==(), so we can't use QList::removeOne and friends. So iterate.
490490 for (int i = 0; i < handlersForEvent.count(); ++i) {
491− if (handlersForEvent[i].equals(handler)) {
491+ if (handlersForEvent[i].function.equals(handler)) {
492492 handlersForEvent.removeAt(i);
493493 return; // Design choice: since comparison is relatively expensive, just remove the first matching handler.
494494 }
@@ -516,6 +516,13 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& · 21 unmodified lines
516516 // Connect up ALL the handlers to the global entities object's signals.
517517 // (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.)
518518 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
519526 connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityID) {
520527 _registeredHandlers.remove(entityID);
521528 });
@@ -563,8 +570,9 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& · 41 unmodified lines
563570 if (!_registeredHandlers.contains(entityID)) {
564571 _registeredHandlers[entityID] = RegisteredEventHandlers();
565572 }
566− QScriptValueList& handlersForEvent = _registeredHandlers[entityID][eventName];
567− handlersForEvent << handler; // Note that the same handler can be added many times. See removeEntityEventHandler().
573+ CallbackList& handlersForEvent = _registeredHandlers[entityID][eventName];
574+ CallbackData handlerData = {handler, currentEntityIdentifier};
575+ handlersForEvent << handlerData; // Note that the same handler can be added many times. See removeEntityEventHandler().
568576 }
569577
570578
@@ -705,13 +713,30 @@ void ScriptEngine::run() { · 134 unmodified lines
705713 // NOTE: This is private because it must be called on the same thread that created the timers, which is why
706714 // we want to only call it in our own run "shutdown" processing.
707715 void ScriptEngine::stopAllTimers() {
708− QMutableHashIterator<QTimer*, QScriptValue> i(_timerFunctionMap);
716+ QMutableHashIterator<QTimer*, CallbackData> i(_timerFunctionMap);
709717 while (i.hasNext()) {
710718 i.next();
711719 QTimer* timer = i.key();
712720 stopTimer(timer);
713721 }
714722 }
723+void 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+}
715740
716741 void ScriptEngine::stop() {
717742 if (!_isFinished) {
@@ -743,13 +768,14 @@ void ScriptEngine::callAnimationStateHandler(QScriptValue callback, AnimVariantM · 25 unmodified lines
743768 QScriptValue javascriptParameters = parameters.animVariantMapToScriptValue(this, names, useNames);
744769 QScriptValueList callingArguments;
745770 callingArguments << javascriptParameters;
771+ assert(currentEntityIdentifier.isInvalidID()); // No animation state handlers from entity scripts.
746772 QScriptValue result = callback.call(QScriptValue(), callingArguments);
747773 resultHandler(result);
748774 }
749775
750776 void ScriptEngine::timerFired() {
751777 QTimer* callingTimer = reinterpret_cast<QTimer*>(sender());
752− QScriptValue timerFunction = _timerFunctionMap.value(callingTimer);
778+ CallbackData timerData = _timerFunctionMap.value(callingTimer);
753779
754780 if (!callingTimer->isActive()) {
755781 // this timer is done, we can kill it
@@ -758,11 +784,12 @@ void ScriptEngine::timerFired() { · 2 unmodified lines
758784 }
759785
760786 // call the associated JS function, if it exists
761− if (timerFunction.isValid()) {
762− timerFunction.call();
787+ if (timerData.function.isValid()) {
788+ callWithEnvironment(timerData.definingEntityIdentifier, timerData.function, timerData.function, QScriptValueList());
763789 }
764790 }
765791
792+
766793 QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot) {
767794 // create the timer, add it to the map, and start it
768795 QTimer* newTimer = new QTimer(this);
@@ -773,7 +800,8 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int · 4 unmodified lines
773800 // make sure the timer stops when the script does
774801 connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop);
775802
776− _timerFunctionMap.insert(newTimer, function);
803+ CallbackData timerData = {function, currentEntityIdentifier};
804+ _timerFunctionMap.insert(newTimer, timerData);
777805
778806 newTimer->start(intervalMS);
779807 return newTimer;
@@ -859,6 +887,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac · 79 unmodified lines
859887 }
860888
861889 BatchLoader* loader = new BatchLoader(urls);
890+ EntityItemID capturedEntityIdentifier = currentEntityIdentifier;
862891
863892 auto evaluateScripts = [=](const QMap<QUrl, QString>& data) {
864893 auto parentURL = _parentURL;
@@ -870,13 +899,16 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac · 5 unmodified lines
870899 // Set the parent url so that path resolution will be relative
871900 // to this script's url during its initial evaluation
872901 _parentURL = url.toString();
873− QScriptValue result = evaluate(contents, url.toString());
902+ auto operation = [&]() {
903+ evaluate(contents, url.toString());
904+ };
905+ doWithEnvironment(capturedEntityIdentifier, operation);
874906 }
875907 }
876908 _parentURL = parentURL;
877909
878910 if (callback.isFunction()) {
879− QScriptValue(callback).call();
911+ callWithEnvironment(capturedEntityIdentifier, QScriptValue(callback), QScriptValue(), QScriptValueList());
880912 }
881913
882914 loader->deleteLater();
@@ -917,6 +949,11 @@ void ScriptEngine::load(const QString& loadFile) { · 34 unmodified lines
917949 << "loadFile:" << loadFile << "parent script:" << getFilename();
918950 return; // bail early
919951 }
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+ }
920957
921958 QUrl url = resolvePath(loadFile);
922959 if (_isReloading) {
@@ -933,7 +970,7 @@ void ScriptEngine::load(const QString& loadFile) { · 10 unmodified lines
933970 }
934971
935972 // Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those args
936−void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs) {
973+void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHandlerArgs) {
937974 if (QThread::currentThread() != thread()) {
938975 qDebug() << "*** ERROR *** ScriptEngine::forwardHandlerCall() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]";
939976 assert(false);
@@ -946,10 +983,13 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin · 6 unmodified lines
946983 if (!handlersOnEntity.contains(eventName)) {
947984 return;
948985 }
949− QScriptValueList handlersForEvent = handlersOnEntity[eventName];
986+ CallbackList handlersForEvent = handlersOnEntity[eventName];
950987 if (!handlersForEvent.isEmpty()) {
951988 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);
953993 }
954994 }
955995 }
@@ -1055,9 +1095,13 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co · 99 unmodified lines
10551095 QString file = QUrl(scriptOrURL).toLocalFile();
10561096 lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch();
10571097 }
1098+ QScriptValue entityScriptConstructor, entityScriptObject;
1099+ auto initialization = [&]{
1100+ entityScriptConstructor = evaluate(contents, fileName);
1101+ entityScriptObject = entityScriptConstructor.construct();
1102+ };
1103+ doWithEnvironment(entityID, initialization);
10581104
1059− QScriptValue entityScriptConstructor = evaluate(contents, fileName);
1060− QScriptValue entityScriptObject = entityScriptConstructor.construct();
10611105 EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified };
10621106 _entityScripts[entityID] = newDetails;
10631107 if (isURL) {
@@ -1087,6 +1131,7 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) { · 23 unmodified lines
10871131 if (_entityScripts.contains(entityID)) {
10881132 callEntityScriptMethod(entityID, "unload");
10891133 _entityScripts.remove(entityID);
1134+ stopAllTimersForEntityScript(entityID);
10901135 }
10911136 }
10921137
@@ -1142,6 +1187,32 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) { · 49 unmodified lines
11421187 recurseGuard = false;
11431188 }
11441189
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).
1194+void 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+}
1209+void 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+
11451216 void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params) {
11461217 if (QThread::currentThread() != thread()) {
11471218 #ifdef THREAD_DEBUGGING
@@ -1168,7 +1239,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS · 20 unmodified lines
11681239 QScriptValueList args;
11691240 args << entityID.toScriptValue(this);
11701241 args << qScriptValueFromSequence(this, params);
1171− entityScript.property(methodName).call(entityScript, args);
1242+ callWithEnvironment(entityID, entityScript.property(methodName), entityScript, args);
11721243 }
11731244
11741245 }
@@ -1200,7 +1271,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS · 25 unmodified lines
12001271 QScriptValueList args;
12011272 args << entityID.toScriptValue(this);
12021273 args << event.toScriptValue(this);
1203− entityScript.property(methodName).call(entityScript, args);
1274+ callWithEnvironment(entityID, entityScript.property(methodName), entityScript, args);
12041275 }
12051276 }
12061277 }
@@ -1234,7 +1305,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS · 27 unmodified lines
12341305 args << entityID.toScriptValue(this);
12351306 args << otherID.toScriptValue(this);
12361307 args << collisionToScriptValue(this, collision);
1237− entityScript.property(methodName).call(entityScript, args);
1308+ callWithEnvironment(entityID, entityScript.property(methodName), entityScript, args);
12381309 }
12391310 }
12401311 }