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
9// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html9// 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
177void 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
167void ScriptEngine::runInThread() {264void 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 callbacks352// FIXME - switch this to the new model of ScriptCache callbacks
256void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) {353void 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 usec826 // 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 back869 // 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 exceptions880 // 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.