diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 800cadf72f..84aa0328b0 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -44,7 +44,7 @@ else () endif () find_package(Qt5 COMPONENTS - Gui Multimedia Network OpenGL Qml Quick Script Svg + Gui Multimedia Network OpenGL Qml Quick Script ScriptTools Svg WebChannel WebEngine WebEngineWidgets WebKitWidgets WebSockets) # grab the ui files in resources/ui @@ -201,7 +201,7 @@ include_directories("${PROJECT_SOURCE_DIR}/src") target_link_libraries( ${TARGET_NAME} Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL - Qt5::Qml Qt5::Quick Qt5::Script Qt5::Svg + Qt5::Qml Qt5::Quick Qt5::Script Qt5::ScriptTools Qt5::Svg Qt5::WebChannel Qt5::WebEngine Qt5::WebEngineWidgets Qt5::WebKitWidgets ) diff --git a/interface/src/scripting/MenuScriptingInterface.cpp b/interface/src/scripting/MenuScriptingInterface.cpp index da55a8829f..7eb80b5946 100644 --- a/interface/src/scripting/MenuScriptingInterface.cpp +++ b/interface/src/scripting/MenuScriptingInterface.cpp @@ -11,8 +11,11 @@ #include "MenuScriptingInterface.h" -#include "Menu.h" +#include +#include + #include +#include "Menu.h" MenuScriptingInterface* MenuScriptingInterface::getInstance() { static MenuScriptingInterface sharedInstance; @@ -36,6 +39,9 @@ void MenuScriptingInterface::removeMenu(const QString& menu) { } bool MenuScriptingInterface::menuExists(const QString& menu) { + if (QThread::currentThread() == qApp->thread()) { + return Menu::getInstance()->menuExists(menu); + } bool result; QMetaObject::invokeMethod(Menu::getInstance(), "menuExists", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result), @@ -76,11 +82,14 @@ void MenuScriptingInterface::removeMenuItem(const QString& menu, const QString& }; bool MenuScriptingInterface::menuItemExists(const QString& menu, const QString& menuitem) { + if (QThread::currentThread() == qApp->thread()) { + return Menu::getInstance()->menuItemExists(menu, menuitem); + } bool result; QMetaObject::invokeMethod(Menu::getInstance(), "menuItemExists", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(bool, result), - Q_ARG(const QString&, menu), - Q_ARG(const QString&, menuitem)); + Q_RETURN_ARG(bool, result), + Q_ARG(const QString&, menu), + Q_ARG(const QString&, menuitem)); return result; } @@ -101,6 +110,9 @@ void MenuScriptingInterface::removeActionGroup(const QString& groupName) { } bool MenuScriptingInterface::isOptionChecked(const QString& menuOption) { + if (QThread::currentThread() == qApp->thread()) { + return Menu::getInstance()->isOptionChecked(menuOption); + } bool result; QMetaObject::invokeMethod(Menu::getInstance(), "isOptionChecked", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result), @@ -109,7 +121,7 @@ bool MenuScriptingInterface::isOptionChecked(const QString& menuOption) { } void MenuScriptingInterface::setIsOptionChecked(const QString& menuOption, bool isChecked) { - QMetaObject::invokeMethod(Menu::getInstance(), "setIsOptionChecked", Qt::BlockingQueuedConnection, + QMetaObject::invokeMethod(Menu::getInstance(), "setIsOptionChecked", Q_ARG(const QString&, menuOption), Q_ARG(bool, isChecked)); } diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index 8ff13aba70..48fda99b9d 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -1,3 +1,3 @@ set(TARGET_NAME script-engine) -setup_hifi_library(Gui Network Script WebSockets Widgets) +setup_hifi_library(Gui Network Script ScriptTools WebSockets Widgets) link_hifi_libraries(shared networking octree gpu ui procedural model model-networking recording avatars fbx entities controllers animation audio physics) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index d1eaffd2c5..f7ac7894ff 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -17,12 +17,18 @@ #include #include #include +#include + +#include +#include + #include #include -#include + #include #include -#include + +#include #include #include @@ -34,6 +40,7 @@ #include #include #include +#include #include #include @@ -169,6 +176,93 @@ void ScriptEngine::disconnectNonEssentialSignals() { } } +void ScriptEngine::runDebuggable() { + static QMenuBar* menuBar { nullptr }; + static QMenu* scriptDebugMenu { nullptr }; + static size_t scriptMenuCount { 0 }; + if (!scriptDebugMenu) { + for (auto window : qApp->topLevelWidgets()) { + auto mainWindow = qobject_cast(window); + if (mainWindow) { + menuBar = mainWindow->menuBar(); + break; + } + } + if (menuBar) { + scriptDebugMenu = menuBar->addMenu("Script Debug"); + } + } + + init(); + _isRunning = true; + _debuggable = true; + _debugger = new QScriptEngineDebugger(this); + _debugger->attachTo(this); + + QMenu* parentMenu = scriptDebugMenu; + QMenu* scriptMenu { nullptr }; + if (parentMenu) { + ++scriptMenuCount; + scriptMenu = parentMenu->addMenu(_fileNameString); + scriptMenu->addMenu(_debugger->createStandardMenu(qApp->activeWindow())); + } else { + qWarning() << "Unable to add script debug menu"; + } + + QScriptValue result = evaluate(_scriptContents, _fileNameString); + + _lastUpdate = usecTimestampNow(); + QTimer* timer = new QTimer(this); + connect(this, &ScriptEngine::finished, [this, timer, parentMenu, scriptMenu] { + if (scriptMenu) { + parentMenu->removeAction(scriptMenu->menuAction()); + --scriptMenuCount; + if (0 == scriptMenuCount) { + menuBar->removeAction(scriptDebugMenu->menuAction()); + scriptDebugMenu = nullptr; + } + } + disconnect(timer); + }); + + connect(timer, &QTimer::timeout, [this, timer] { + if (_isFinished) { + if (!_isRunning) { + return; + } + stopAllTimers(); // make sure all our timers are stopped if the script is ending + if (_wantSignals) { + emit scriptEnding(); + emit finished(_fileNameString, this); + } + _isRunning = false; + if (_wantSignals) { + emit runningStateChanged(); + emit doneRunning(); + } + timer->deleteLater(); + return; + } + + qint64 now = usecTimestampNow(); + // we check for 'now' in the past in case people set their clock back + if (_lastUpdate < now) { + float deltaTime = (float)(now - _lastUpdate) / (float)USECS_PER_SECOND; + if (!_isFinished) { + if (_wantSignals) { + emit update(deltaTime); + } + } + } + _lastUpdate = now; + // Debug and clear exceptions + hadUncaughtExceptions(*this, _fileNameString); + }); + + timer->start(10); +} + + void ScriptEngine::runInThread() { Q_ASSERT_X(!_isThreaded, "ScriptEngine::runInThread()", "runInThread should not be called more than once"); @@ -260,6 +354,10 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { // FIXME - switch this to the new model of ScriptCache callbacks void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) { _scriptContents = scriptContents; + static const QString DEBUG_FLAG("#debug"); + if (QRegularExpression(DEBUG_FLAG).match(scriptContents).hasMatch()) { + _debuggable = true; + } if (_wantSignals) { emit scriptLoaded(url.toString()); } @@ -723,7 +821,7 @@ void ScriptEngine::run() { auto nodeList = DependencyManager::get(); auto entityScriptingInterface = DependencyManager::get(); - qint64 lastUpdate = usecTimestampNow(); + _lastUpdate = usecTimestampNow(); // TODO: Integrate this with signals/slots instead of reimplementing throttling for ScriptEngine while (!_isFinished) { @@ -771,15 +869,15 @@ void ScriptEngine::run() { qint64 now = usecTimestampNow(); // we check for 'now' in the past in case people set their clock back - if (lastUpdate < now) { - float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND; + if (_lastUpdate < now) { + float deltaTime = (float) (now - _lastUpdate) / (float) USECS_PER_SECOND; if (!_isFinished) { if (_wantSignals) { emit update(deltaTime); } } } - lastUpdate = now; + _lastUpdate = now; // Debug and clear exceptions hadUncaughtExceptions(*this, _fileNameString); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index f050997235..d37e3eb177 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -18,9 +18,10 @@ #include #include #include -#include #include +#include + #include #include #include @@ -39,6 +40,8 @@ #include "ScriptUUID.h" #include "Vec3.h" +class QScriptEngineDebugger; + static const QString NO_SCRIPT(""); static const int SCRIPT_FPS = 60; @@ -75,6 +78,8 @@ public: /// services before calling this. void runInThread(); + void runDebuggable(); + /// run the script in the callers thread, exit when stop() is called. void run(); @@ -140,6 +145,8 @@ public: bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget bool isRunning() const { return _isRunning; } // used by ScriptWidget + bool isDebuggable() const { return _debuggable; } + void disconnectNonEssentialSignals(); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -187,6 +194,9 @@ protected: bool _wantSignals { true }; QHash _entityScripts; bool _isThreaded { false }; + QScriptEngineDebugger* _debugger { nullptr }; + bool _debuggable { false }; + qint64 _lastUpdate; void init(); QString getFilename() const; diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index c6070e0598..cf82d9db4f 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -9,7 +9,8 @@ #include "ScriptEngines.h" #include -#include + +#include #include #include @@ -490,7 +491,12 @@ void ScriptEngines::launchScriptEngine(ScriptEngine* scriptEngine) { for (auto initializer : _scriptInitializers) { initializer(scriptEngine); } - scriptEngine->runInThread(); + + if (scriptEngine->isDebuggable() || (qApp->queryKeyboardModifiers() & Qt::ShiftModifier)) { + scriptEngine->runDebuggable(); + } else { + scriptEngine->runInThread(); + } }