Showing the changes to libraries/script-engine/src/ScriptEngine.cpp (1 of 194 files). Show all
Unified · Split
| @@ -9,17 +9,26 @@ · 8 unmodified lines | |||
| 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html | 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html |
| 10 | // | 10 | // |
| 11 | 11 | ||
| 12 | #include <chrono> | ||
| 13 | #include <thread> | ||
| 14 | |||
| 12 | #include <QtCore/QCoreApplication> | 15 | #include <QtCore/QCoreApplication> |
| 13 | #include <QtCore/QEventLoop> | 16 | #include <QtCore/QEventLoop> |
| 14 | #include <QtCore/QFileInfo> | 17 | #include <QtCore/QFileInfo> |
| 15 | #include <QtCore/QTimer> | 18 | #include <QtCore/QTimer> |
| 16 | #include <QtCore/QThread> | 19 | #include <QtCore/QThread> |
| 20 | #include <QtCore/QRegularExpression> | ||
| 21 | |||
| 22 | #include <QtWidgets/QMainWindow> | ||
| 23 | #include <QtWidgets/QApplication> | ||
| 24 | |||
| 17 | #include <QtNetwork/QNetworkRequest> | 25 | #include <QtNetwork/QNetworkRequest> |
| 18 | #include <QtNetwork/QNetworkReply> | 26 | #include <QtNetwork/QNetworkReply> |
| 19 | #include <QtScript/QScriptEngine> | 27 | |
| 20 | #include <QtScript/QScriptValue> | 28 | #include <QtScript/QScriptValue> |
| 21 | #include <QtScript/QScriptValueIterator> | 29 | #include <QtScript/QScriptValueIterator> |
| 22 | #include <QtCore/QStringList> | 30 | |
| 31 | #include <QtScriptTools/QScriptEngineDebugger> | ||
| 23 | 32 | ||
| 24 | #include <AudioConstants.h> | 33 | #include <AudioConstants.h> |
| 25 | #include <AudioEffectOptions.h> | 34 | #include <AudioEffectOptions.h> |
| @@ -31,6 +40,7 @@ · 5 unmodified lines | |||
| 31 | #include <NodeList.h> | 40 | #include <NodeList.h> |
| 32 | #include <udt/PacketHeaders.h> | 41 | #include <udt/PacketHeaders.h> |
| 33 | #include <UUID.h> | 42 | #include <UUID.h> |
| 43 | #include <ui/Menu.h> | ||
| 34 | 44 | ||
| 35 | #include <controllers/ScriptingInterface.h> | 45 | #include <controllers/ScriptingInterface.h> |
| 36 | #include <AnimationObject.h> | 46 | #include <AnimationObject.h> |
| @@ -164,6 +174,93 @@ void ScriptEngine::disconnectNonEssentialSignals() { · 127 unmodified lines | |||
| 164 | } | 174 | } |
| 165 | } | 175 | } |
| 166 | 176 | ||
| 177 | void ScriptEngine::runDebuggable() { | ||
| 178 | static QMenuBar* menuBar { nullptr }; | ||
| 179 | static QMenu* scriptDebugMenu { nullptr }; | ||
| 180 | static size_t scriptMenuCount { 0 }; | ||
| 181 | if (!scriptDebugMenu) { | ||
| 182 | for (auto window : qApp->topLevelWidgets()) { | ||
| 183 | auto mainWindow = qobject_cast<QMainWindow*>(window); | ||
| 184 | if (mainWindow) { | ||
| 185 | menuBar = mainWindow->menuBar(); | ||
| 186 | break; | ||
| 187 | } | ||
| 188 | } | ||
| 189 | if (menuBar) { | ||
| 190 | scriptDebugMenu = menuBar->addMenu("Script Debug"); | ||
| 191 | } | ||
| 192 | } | ||
| 193 | |||
| 194 | init(); | ||
| 195 | _isRunning = true; | ||
| 196 | _debuggable = true; | ||
| 197 | _debugger = new QScriptEngineDebugger(this); | ||
| 198 | _debugger->attachTo(this); | ||
| 199 | |||
| 200 | QMenu* parentMenu = scriptDebugMenu; | ||
| 201 | QMenu* scriptMenu { nullptr }; | ||
| 202 | if (parentMenu) { | ||
| 203 | ++scriptMenuCount; | ||
| 204 | scriptMenu = parentMenu->addMenu(_fileNameString); | ||
| 205 | scriptMenu->addMenu(_debugger->createStandardMenu(qApp->activeWindow())); | ||
| 206 | } else { | ||
| 207 | qWarning() << "Unable to add script debug menu"; | ||
| 208 | } | ||
| 209 | |||
| 210 | QScriptValue result = evaluate(_scriptContents, _fileNameString); | ||
| 211 | |||
| 212 | _lastUpdate = usecTimestampNow(); | ||
| 213 | QTimer* timer = new QTimer(this); | ||
| 214 | connect(this, &ScriptEngine::finished, [this, timer, parentMenu, scriptMenu] { | ||
| 215 | if (scriptMenu) { | ||
| 216 | parentMenu->removeAction(scriptMenu->menuAction()); | ||
| 217 | --scriptMenuCount; | ||
| 218 | if (0 == scriptMenuCount) { | ||
| 219 | menuBar->removeAction(scriptDebugMenu->menuAction()); | ||
| 220 | scriptDebugMenu = nullptr; | ||
| 221 | } | ||
| 222 | } | ||
| 223 | disconnect(timer); | ||
| 224 | }); | ||
| 225 | |||
| 226 | connect(timer, &QTimer::timeout, [this, timer] { | ||
| 227 | if (_isFinished) { | ||
| 228 | if (!_isRunning) { | ||
| 229 | return; | ||
| 230 | } | ||
| 231 | stopAllTimers(); // make sure all our timers are stopped if the script is ending | ||
| 232 | if (_wantSignals) { | ||
| 233 | emit scriptEnding(); | ||
| 234 | emit finished(_fileNameString, this); | ||
| 235 | } | ||
| 236 | _isRunning = false; | ||
| 237 | if (_wantSignals) { | ||
| 238 | emit runningStateChanged(); | ||
| 239 | emit doneRunning(); | ||
| 240 | } | ||
| 241 | timer->deleteLater(); | ||
| 242 | return; | ||
| 243 | } | ||
| 244 | |||
| 245 | qint64 now = usecTimestampNow(); | ||
| 246 | // we check for 'now' in the past in case people set their clock back | ||
| 247 | if (_lastUpdate < now) { | ||
| 248 | float deltaTime = (float)(now - _lastUpdate) / (float)USECS_PER_SECOND; | ||
| 249 | if (!_isFinished) { | ||
| 250 | if (_wantSignals) { | ||
| 251 | emit update(deltaTime); | ||
| 252 | } | ||
| 253 | } | ||
| 254 | } | ||
| 255 | _lastUpdate = now; | ||
| 256 | // Debug and clear exceptions | ||
| 257 | hadUncaughtExceptions(*this, _fileNameString); | ||
| 258 | }); | ||
| 259 | |||
| 260 | timer->start(10); | ||
| 261 | } | ||
| 262 | |||
| 263 | |||
| 167 | void ScriptEngine::runInThread() { | 264 | void ScriptEngine::runInThread() { |
| 168 | Q_ASSERT_X(!_isThreaded, "ScriptEngine::runInThread()", "runInThread should not be called more than once"); | 265 | Q_ASSERT_X(!_isThreaded, "ScriptEngine::runInThread()", "runInThread should not be called more than once"); |
| 169 | 266 | ||
| @@ -255,6 +352,10 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { · 85 unmodified lines | |||
| 255 | // FIXME - switch this to the new model of ScriptCache callbacks | 352 | // FIXME - switch this to the new model of ScriptCache callbacks |
| 256 | void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) { | 353 | void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) { |
| 257 | _scriptContents = scriptContents; | 354 | _scriptContents = scriptContents; |
| 355 | static const QString DEBUG_FLAG("#debug"); | ||
| 356 | if (QRegularExpression(DEBUG_FLAG).match(scriptContents).hasMatch()) { | ||
| 357 | _debuggable = true; | ||
| 358 | } | ||
| 258 | if (_wantSignals) { | 359 | if (_wantSignals) { |
| 259 | emit scriptLoaded(url.toString()); | 360 | emit scriptLoaded(url.toString()); |
| 260 | } | 361 | } |
| @@ -704,22 +805,45 @@ void ScriptEngine::run() { · 443 unmodified lines | |||
| 704 | 805 | ||
| 705 | QScriptValue result = evaluate(_scriptContents, _fileNameString); | 806 | QScriptValue result = evaluate(_scriptContents, _fileNameString); |
| 706 | 807 | ||
| 707 | QElapsedTimer startTime; | 808 | #ifdef _WIN32 |
| 708 | startTime.start(); | 809 | // VS13 does not sleep_until unless it uses the system_clock, see: |
| 810 | // https://www.reddit.com/r/cpp_questions/comments/3o71ic/sleep_until_not_working_with_a_time_pointsteady/ | ||
| 811 | using clock = std::chrono::system_clock; | ||
| 812 | #else | ||
| 813 | using clock = std::chrono::high_resolution_clock; | ||
| 814 | #endif | ||
| 709 | 815 | ||
| 816 | clock::time_point startTime = clock::now(); | ||
| 710 | int thisFrame = 0; | 817 | int thisFrame = 0; |
| 711 | 818 | ||
| 712 | auto nodeList = DependencyManager::get<NodeList>(); | 819 | auto nodeList = DependencyManager::get<NodeList>(); |
| 713 | auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); | 820 | auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); |
| 714 | 821 | ||
| 715 | qint64 lastUpdate = usecTimestampNow(); | 822 | _lastUpdate = usecTimestampNow(); |
| 716 | 823 | ||
| 824 | // TODO: Integrate this with signals/slots instead of reimplementing throttling for ScriptEngine | ||
| 717 | while (!_isFinished) { | 825 | while (!_isFinished) { |
| 718 | int usecToSleep = (thisFrame++ * SCRIPT_DATA_CALLBACK_USECS) - startTime.nsecsElapsed() / 1000; // nsec to usec | 826 | // Throttle to SCRIPT_FPS |
| 719 | if (usecToSleep > 0) { | 827 | const std::chrono::microseconds FRAME_DURATION(USECS_PER_SECOND / SCRIPT_FPS + 1); |
| 720 | usleep(usecToSleep); | 828 | clock::time_point sleepTime(startTime + thisFrame++ * FRAME_DURATION); |
| 829 | std::this_thread::sleep_until(sleepTime); | ||
| 830 | |||
| 831 | #ifdef SCRIPT_DELAY_DEBUG | ||
| 832 | { | ||
| 833 | auto now = clock::now(); | ||
| 834 | uint64_t seconds = std::chrono::duration_cast<std::chrono::seconds>(now - startTime).count(); | ||
| 835 | if (seconds > 0) { // avoid division by zero and time travel | ||
| 836 | uint64_t fps = thisFrame / seconds; | ||
| 837 | // Overreporting artificially reduces the reported rate | ||
| 838 | if (thisFrame % SCRIPT_FPS == 0) { | ||
| 839 | qCDebug(scriptengine) << | ||
| 840 | "Frame:" << thisFrame << | ||
| 841 | "Slept (us):" << std::chrono::duration_cast<std::chrono::microseconds>(now - sleepTime).count() << | ||
| 842 | "FPS:" << fps; | ||
| 843 | } | ||
| 844 | } | ||
| 721 | } | 845 | } |
| 722 | 846 | #endif | |
| 723 | if (_isFinished) { | 847 | if (_isFinished) { |
| 724 | break; | 848 | break; |
| 725 | } | 849 | } |
| @@ -743,15 +867,15 @@ void ScriptEngine::run() { · 17 unmodified lines | |||
| 743 | qint64 now = usecTimestampNow(); | 867 | qint64 now = usecTimestampNow(); |
| 744 | 868 | ||
| 745 | // we check for 'now' in the past in case people set their clock back | 869 | // we check for 'now' in the past in case people set their clock back |
| 746 | if (lastUpdate < now) { | 870 | if (_lastUpdate < now) { |
| 747 | float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND; | 871 | float deltaTime = (float) (now - _lastUpdate) / (float) USECS_PER_SECOND; |
| 748 | if (!_isFinished) { | 872 | if (!_isFinished) { |
| 749 | if (_wantSignals) { | 873 | if (_wantSignals) { |
| 750 | emit update(deltaTime); | 874 | emit update(deltaTime); |
| 751 | } | 875 | } |
| 752 | } | 876 | } |
| 753 | } | 877 | } |
| 754 | lastUpdate = now; | 878 | _lastUpdate = now; |
| 755 | 879 | ||
| 756 | // Debug and clear exceptions | 880 | // Debug and clear exceptions |
| 757 | hadUncaughtExceptions(*this, _fileNameString); | 881 | hadUncaughtExceptions(*this, _fileNameString); |
| @@ -967,7 +1091,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac · 209 unmodified lines | |||
| 967 | } | 1091 | } |
| 968 | QList<QUrl> urls; | 1092 | QList<QUrl> urls; |
| 969 | bool knowsSensitivity = false; | 1093 | bool knowsSensitivity = false; |
| 970 | Qt::CaseSensitivity sensitivity; | 1094 | Qt::CaseSensitivity sensitivity { Qt::CaseSensitive }; |
| 971 | auto getSensitivity = [&]() { | 1095 | auto getSensitivity = [&]() { |
| 972 | if (!knowsSensitivity) { | 1096 | if (!knowsSensitivity) { |
| 973 | QString path = currentSandboxURL.path(); | 1097 | QString path = currentSandboxURL.path(); |
| @@ -984,6 +1108,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac · 10 unmodified lines | |||
| 984 | const auto strippingFlags = QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment; | 1108 | const auto strippingFlags = QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment; |
| 985 | for (QString file : includeFiles) { | 1109 | for (QString file : includeFiles) { |
| 986 | QUrl thisURL; | 1110 | QUrl thisURL; |
| 1111 | bool isStandardLibrary = false; | ||
| 987 | if (file.startsWith("/~/")) { | 1112 | if (file.startsWith("/~/")) { |
| 988 | thisURL = expandScriptUrl(QUrl::fromLocalFile(expandScriptPath(file))); | 1113 | thisURL = expandScriptUrl(QUrl::fromLocalFile(expandScriptPath(file))); |
| 989 | QUrl defaultScriptsLoc = defaultScriptsLocation(); | 1114 | QUrl defaultScriptsLoc = defaultScriptsLocation(); |
| @@ -991,21 +1116,17 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac · 1 unmodified line | |||
| 991 | qDebug() << "ScriptEngine::include -- skipping" << file << "-- outside of standard libraries"; | 1116 | qDebug() << "ScriptEngine::include -- skipping" << file << "-- outside of standard libraries"; |
| 992 | continue; | 1117 | continue; |
| 993 | } | 1118 | } |
| 1119 | isStandardLibrary = true; | ||
| 994 | } else { | 1120 | } else { |
| 995 | thisURL = resolvePath(file); | 1121 | thisURL = resolvePath(file); |
| 996 | } | 1122 | } |
| 997 | 1123 | ||
| 998 | if (!_includedURLs.contains(thisURL)) { | 1124 | if (!_includedURLs.contains(thisURL)) { |
| 999 | if (!currentSandboxURL.isEmpty() && (thisURL.scheme() == "file") && | 1125 | if (!isStandardLibrary && !currentSandboxURL.isEmpty() && (thisURL.scheme() == "file") && |
| 1000 | ( | 1126 | (currentSandboxURL.scheme() != "file" || |
| 1001 | (currentSandboxURL.scheme() != "file") || | 1127 | !thisURL.toString(strippingFlags).startsWith(currentSandboxURL.toString(strippingFlags), getSensitivity()))) { |
| 1002 | ( | 1128 | qCWarning(scriptengine) << "Script.include() ignoring file path" |
| 1003 | !thisURL.toString(strippingFlags).startsWith(defaultScriptsLocation().toString(), getSensitivity()) && | 1129 | << thisURL << "outside of original entity script" << currentSandboxURL; |
| 1004 | !thisURL.toString(strippingFlags).startsWith(currentSandboxURL.toString(strippingFlags), getSensitivity()) | ||
| 1005 | ) | ||
| 1006 | ) | ||
| 1007 | ) { | ||
| 1008 | qCWarning(scriptengine) << "Script.include() ignoring file path" << thisURL << "outside of original entity script" << currentSandboxURL; | ||
| 1009 | } else { | 1130 | } else { |
| 1010 | // We could also check here for CORS, but we don't yet. | 1131 | // We could also check here for CORS, but we don't yet. |
| 1011 | // It turns out that QUrl.resolve will not change hosts and copy authority, so we don't need to check that here. | 1132 | // It turns out that QUrl.resolve will not change hosts and copy authority, so we don't need to check that here. |