History

Merge branch 'master' of github.com:highfidelity/hifi into fix/shutdown

Zach Pomerantz committed 10 years ago

Commit 59e4b9c2 parents 13610b1 987b348194 files · +3212 −1716Browse files

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

Unified · Split

libraries/script-engine/src/ScriptEngine.cpp+144−23
@@ -9,17 +9,26 @@ · 8 unmodified lines
99 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
1010 //
1111
12+#include <chrono>
13+#include <thread>
14+
1215 #include <QtCore/QCoreApplication>
1316 #include <QtCore/QEventLoop>
1417 #include <QtCore/QFileInfo>
1518 #include <QtCore/QTimer>
1619 #include <QtCore/QThread>
20+#include <QtCore/QRegularExpression>
21+
22+#include <QtWidgets/QMainWindow>
23+#include <QtWidgets/QApplication>
24+
1725 #include <QtNetwork/QNetworkRequest>
1826 #include <QtNetwork/QNetworkReply>
19−#include <QtScript/QScriptEngine>
27+
2028 #include <QtScript/QScriptValue>
2129 #include <QtScript/QScriptValueIterator>
22−#include <QtCore/QStringList>
30+
31+#include <QtScriptTools/QScriptEngineDebugger>
2332
2433 #include <AudioConstants.h>
2534 #include <AudioEffectOptions.h>
@@ -31,6 +40,7 @@ · 5 unmodified lines
3140 #include <NodeList.h>
3241 #include <udt/PacketHeaders.h>
3342 #include <UUID.h>
43+#include <ui/Menu.h>
3444
3545 #include <controllers/ScriptingInterface.h>
3646 #include <AnimationObject.h>
@@ -164,6 +174,93 @@ void ScriptEngine::disconnectNonEssentialSignals() { · 127 unmodified lines
164174 }
165175 }
166176
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+
167264 void ScriptEngine::runInThread() {
168265 Q_ASSERT_X(!_isThreaded, "ScriptEngine::runInThread()", "runInThread should not be called more than once");
169266
@@ -255,6 +352,10 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { · 85 unmodified lines
255352 // FIXME - switch this to the new model of ScriptCache callbacks
256353 void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) {
257354 _scriptContents = scriptContents;
355+ static const QString DEBUG_FLAG("#debug");
356+ if (QRegularExpression(DEBUG_FLAG).match(scriptContents).hasMatch()) {
357+ _debuggable = true;
358+ }
258359 if (_wantSignals) {
259360 emit scriptLoaded(url.toString());
260361 }
@@ -704,22 +805,45 @@ void ScriptEngine::run() { · 443 unmodified lines
704805
705806 QScriptValue result = evaluate(_scriptContents, _fileNameString);
706807
707− QElapsedTimer startTime;
708− startTime.start();
808+#ifdef _WIN32
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
709815
816+ clock::time_point startTime = clock::now();
710817 int thisFrame = 0;
711818
712819 auto nodeList = DependencyManager::get<NodeList>();
713820 auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
714821
715− qint64 lastUpdate = usecTimestampNow();
822+ _lastUpdate = usecTimestampNow();
716823
824+ // TODO: Integrate this with signals/slots instead of reimplementing throttling for ScriptEngine
717825 while (!_isFinished) {
718− int usecToSleep = (thisFrame++ * SCRIPT_DATA_CALLBACK_USECS) - startTime.nsecsElapsed() / 1000; // nsec to usec
719− if (usecToSleep > 0) {
720− usleep(usecToSleep);
826+ // Throttle to SCRIPT_FPS
827+ const std::chrono::microseconds FRAME_DURATION(USECS_PER_SECOND / SCRIPT_FPS + 1);
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+ }
721845 }
722−
846+#endif
723847 if (_isFinished) {
724848 break;
725849 }
@@ -743,15 +867,15 @@ void ScriptEngine::run() { · 17 unmodified lines
743867 qint64 now = usecTimestampNow();
744868
745869 // we check for 'now' in the past in case people set their clock back
746− if (lastUpdate < now) {
747− float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND;
870+ if (_lastUpdate < now) {
871+ float deltaTime = (float) (now - _lastUpdate) / (float) USECS_PER_SECOND;
748872 if (!_isFinished) {
749873 if (_wantSignals) {
750874 emit update(deltaTime);
751875 }
752876 }
753877 }
754− lastUpdate = now;
878+ _lastUpdate = now;
755879
756880 // Debug and clear exceptions
757881 hadUncaughtExceptions(*this, _fileNameString);
@@ -967,7 +1091,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac · 209 unmodified lines
9671091 }
9681092 QList<QUrl> urls;
9691093 bool knowsSensitivity = false;
970− Qt::CaseSensitivity sensitivity;
1094+ Qt::CaseSensitivity sensitivity { Qt::CaseSensitive };
9711095 auto getSensitivity = [&]() {
9721096 if (!knowsSensitivity) {
9731097 QString path = currentSandboxURL.path();
@@ -984,6 +1108,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac · 10 unmodified lines
9841108 const auto strippingFlags = QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment;
9851109 for (QString file : includeFiles) {
9861110 QUrl thisURL;
1111+ bool isStandardLibrary = false;
9871112 if (file.startsWith("/~/")) {
9881113 thisURL = expandScriptUrl(QUrl::fromLocalFile(expandScriptPath(file)));
9891114 QUrl defaultScriptsLoc = defaultScriptsLocation();
@@ -991,21 +1116,17 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac · 1 unmodified line
9911116 qDebug() << "ScriptEngine::include -- skipping" << file << "-- outside of standard libraries";
9921117 continue;
9931118 }
1119+ isStandardLibrary = true;
9941120 } else {
9951121 thisURL = resolvePath(file);
9961122 }
9971123
9981124 if (!_includedURLs.contains(thisURL)) {
999− if (!currentSandboxURL.isEmpty() && (thisURL.scheme() == "file") &&
1000− (
1001− (currentSandboxURL.scheme() != "file") ||
1002− (
1003− !thisURL.toString(strippingFlags).startsWith(defaultScriptsLocation().toString(), getSensitivity()) &&
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;
1125+ if (!isStandardLibrary && !currentSandboxURL.isEmpty() && (thisURL.scheme() == "file") &&
1126+ (currentSandboxURL.scheme() != "file" ||
1127+ !thisURL.toString(strippingFlags).startsWith(currentSandboxURL.toString(strippingFlags), getSensitivity()))) {
1128+ qCWarning(scriptengine) << "Script.include() ignoring file path"
1129+ << thisURL << "outside of original entity script" << currentSandboxURL;
10091130 } else {
10101131 // We could also check here for CORS, but we don't yet.
10111132 // It turns out that QUrl.resolve will not change hosts and copy authority, so we don't need to check that here.