From 8372d73fec32336f3b6dc20f06909f930601f320 Mon Sep 17 00:00:00 2001
From: Brad Davis <bdavis@saintandreas.org>
Date: Fri, 30 Jun 2017 09:15:06 -0700
Subject: [PATCH] Additional thread safety

---
 gvr-interface/src/RenderingClient.cpp         |   5 +-
 interface/src/Application.cpp                 |   2 +-
 .../src/scripting/HMDScriptingInterface.cpp   |  32 ++--
 .../src/scripting/HMDScriptingInterface.h     |   8 +-
 .../src/scripting/TestScriptingInterface.cpp  |  26 +--
 interface/src/ui/overlays/Image3DOverlay.cpp  |   2 +
 interface/src/ui/overlays/Overlay.h           |   6 +-
 interface/src/ui/overlays/OverlayPanel.cpp    |   3 +
 interface/src/ui/overlays/OverlayPanel.h      |   3 +
 interface/src/ui/overlays/Overlays.cpp        | 167 +++++++++++++----
 interface/src/ui/overlays/Overlays.h          |  41 +++--
 interface/src/ui/overlays/PanelAttachable.cpp |   6 +
 interface/src/ui/overlays/PanelAttachable.h   |   9 +-
 interface/src/ui/overlays/QmlOverlay.cpp      |  50 +++---
 interface/src/ui/overlays/QmlOverlay.h        |   2 +-
 libraries/audio-client/src/AudioClient.cpp    |  15 +-
 libraries/ui/src/QmlWebWindowClass.cpp        |  59 +++---
 libraries/ui/src/QmlWebWindowClass.h          |   2 +-
 libraries/ui/src/QmlWindowClass.cpp           | 170 +++++++++++-------
 libraries/ui/src/QmlWindowClass.h             |   8 +-
 libraries/ui/src/ui/OffscreenQmlSurface.cpp   |  22 ---
 libraries/ui/src/ui/OffscreenQmlSurface.h     |   4 -
 .../ui/src/ui/TabletScriptingInterface.cpp    |  20 +--
 23 files changed, 413 insertions(+), 249 deletions(-)

diff --git a/gvr-interface/src/RenderingClient.cpp b/gvr-interface/src/RenderingClient.cpp
index b7c6f30a73..f04be5cb7f 100644
--- a/gvr-interface/src/RenderingClient.cpp
+++ b/gvr-interface/src/RenderingClient.cpp
@@ -63,10 +63,7 @@ void RenderingClient::sendAvatarPacket() {
 }
 
 void RenderingClient::cleanupBeforeQuit() {
-    
-    QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(),
-                              "stop", Qt::BlockingQueuedConnection);
-    
+    DependencyManager::get<AudioClient>()->cleanupBeforeQuit();
     // destroy the AudioClient so it and its thread will safely go down
     DependencyManager::destroy<AudioClient>();
 }
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 1a31877c1b..2b6c6033a2 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -1844,7 +1844,7 @@ void Application::cleanupBeforeQuit() {
 
     // FIXME: something else is holding a reference to AudioClient,
     // so it must be explicitly synchronously stopped here
-    QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "cleanupBeforeQuit", Qt::BlockingQueuedConnection);
+    DependencyManager::get<AudioClient>()->cleanupBeforeQuit();
 
     // destroy Audio so it and its threads have a chance to go down safely
     // this must happen after QML, as there are unexplained audio crashes originating in qtwebengine
diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp
index 883a6e758e..81a806b8ab 100644
--- a/interface/src/scripting/HMDScriptingInterface.cpp
+++ b/interface/src/scripting/HMDScriptingInterface.cpp
@@ -13,6 +13,7 @@
 
 #include <QtScript/QScriptContext>
 
+#include <shared/QtHelpers.h>
 #include <avatar/AvatarManager.h>
 #include <display-plugins/DisplayPlugin.h>
 #include <display-plugins/CompositorHelper.h>
@@ -152,22 +153,31 @@ QString HMDScriptingInterface::preferredAudioOutput() const {
     return qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice();
 }
 
-bool HMDScriptingInterface::setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) const {
+bool HMDScriptingInterface::setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) {
+    if (QThread::currentThread() != thread()) {
+        bool result;
+        hifi::qt::blockingInvokeMethod(this, "setHandLasers", Q_RETURN_ARG(bool, result), 
+            Q_ARG(int, hands), Q_ARG(bool, enabled), Q_ARG(glm::vec4, color), Q_ARG(glm::vec3, direction));
+        return result;
+    }
+
     auto offscreenUi = DependencyManager::get<OffscreenUi>();
-    offscreenUi->executeOnUiThread([offscreenUi, enabled] {
-        offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", enabled);
-    });
+    offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", enabled);
     return qApp->getActiveDisplayPlugin()->setHandLaser(hands,
         enabled ? DisplayPlugin::HandLaserMode::Overlay : DisplayPlugin::HandLaserMode::None,
         color, direction);
 }
 
-bool HMDScriptingInterface::setExtraLaser(const glm::vec3& worldStart, bool enabled, const glm::vec4& color, const glm::vec3& direction) const {
-    auto offscreenUi = DependencyManager::get<OffscreenUi>();
-    offscreenUi->executeOnUiThread([offscreenUi, enabled] {
-        offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", enabled);
-    });
+bool HMDScriptingInterface::setExtraLaser(const glm::vec3& worldStart, bool enabled, const glm::vec4& color, const glm::vec3& direction) {
+    if (QThread::currentThread() != thread()) {
+        bool result;
+        hifi::qt::blockingInvokeMethod(this, "setExtraLaser", Q_RETURN_ARG(bool, result), 
+            Q_ARG(glm::vec3, worldStart), Q_ARG(bool, enabled), Q_ARG(glm::vec4, color), Q_ARG(glm::vec3, direction));
+        return result;
+    }
 
+    auto offscreenUi = DependencyManager::get<OffscreenUi>();
+    offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", enabled);
 
     auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
     auto sensorToWorld = myAvatar->getSensorToWorldMatrix();
@@ -179,11 +189,11 @@ bool HMDScriptingInterface::setExtraLaser(const glm::vec3& worldStart, bool enab
         color, sensorStart, sensorDirection);
 }
 
-void HMDScriptingInterface::disableExtraLaser() const {
+void HMDScriptingInterface::disableExtraLaser() {
     setExtraLaser(vec3(0), false, vec4(0), vec3(0));
 }
 
-void HMDScriptingInterface::disableHandLasers(int hands) const {
+void HMDScriptingInterface::disableHandLasers(int hands) {
     setHandLasers(hands, false, vec4(0), vec3(0));
 }
 
diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h
index 4657e61d05..3ed7db0232 100644
--- a/interface/src/scripting/HMDScriptingInterface.h
+++ b/interface/src/scripting/HMDScriptingInterface.h
@@ -51,11 +51,11 @@ public:
     Q_INVOKABLE void requestHideHandControllers();
     Q_INVOKABLE bool shouldShowHandControllers() const;
 
-    Q_INVOKABLE bool setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) const;
-    Q_INVOKABLE void disableHandLasers(int hands) const;
+    Q_INVOKABLE bool setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction);
+    Q_INVOKABLE void disableHandLasers(int hands);
 
-    Q_INVOKABLE bool setExtraLaser(const glm::vec3& worldStart, bool enabled, const glm::vec4& color, const glm::vec3& direction) const;
-    Q_INVOKABLE void disableExtraLaser() const;
+    Q_INVOKABLE bool setExtraLaser(const glm::vec3& worldStart, bool enabled, const glm::vec4& color, const glm::vec3& direction);
+    Q_INVOKABLE void disableExtraLaser();
 
 
     /// Suppress the activation of any on-screen keyboard so that a script operation will
diff --git a/interface/src/scripting/TestScriptingInterface.cpp b/interface/src/scripting/TestScriptingInterface.cpp
index 84c742d0ab..4d3e3b94a1 100644
--- a/interface/src/scripting/TestScriptingInterface.cpp
+++ b/interface/src/scripting/TestScriptingInterface.cpp
@@ -11,6 +11,7 @@
 #include <QtCore/QLoggingCategory>
 #include <QtCore/QThread>
 
+#include <shared/QtHelpers.h>
 #include <DependencyManager.h>
 #include <Trace.h>
 #include <StatTracker.h>
@@ -57,20 +58,25 @@ void TestScriptingInterface::waitIdle() {
 }
 
 bool TestScriptingInterface::loadTestScene(QString scene) {
+    if (QThread::currentThread() != thread()) {
+        bool result;
+        hifi::qt::blockingInvokeMethod(this, "loadTestScene", Q_RETURN_ARG(bool, result), Q_ARG(QString, scene));
+        return result;
+    }
+
     static const QString TEST_ROOT = "https://raw.githubusercontent.com/highfidelity/hifi_tests/master/";
     static const QString TEST_BINARY_ROOT = "https://hifi-public.s3.amazonaws.com/test_scene_data/";
     static const QString TEST_SCRIPTS_ROOT = TEST_ROOT + "scripts/";
     static const QString TEST_SCENES_ROOT = TEST_ROOT + "scenes/";
-    return DependencyManager::get<OffscreenUi>()->returnFromUiThread([scene]()->QVariant {
-        DependencyManager::get<ResourceManager>()->setUrlPrefixOverride("atp:/", TEST_BINARY_ROOT + scene + ".atp/");
-        auto tree = qApp->getEntities()->getTree();
-        auto treeIsClient = tree->getIsClient();
-        // Force the tree to accept the load regardless of permissions
-        tree->setIsClient(false);
-        auto result = tree->readFromURL(TEST_SCENES_ROOT + scene + ".json");
-        tree->setIsClient(treeIsClient);
-        return result;
-    }).toBool();
+    
+    DependencyManager::get<ResourceManager>()->setUrlPrefixOverride("atp:/", TEST_BINARY_ROOT + scene + ".atp/");
+    auto tree = qApp->getEntities()->getTree();
+    auto treeIsClient = tree->getIsClient();
+    // Force the tree to accept the load regardless of permissions
+    tree->setIsClient(false);
+    auto result = tree->readFromURL(TEST_SCENES_ROOT + scene + ".json");
+    tree->setIsClient(treeIsClient);
+    return result;
 }
 
 bool TestScriptingInterface::startTracing(QString logrules) {
diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp
index 3e0bb74bc4..c8c9c36a1d 100644
--- a/interface/src/ui/overlays/Image3DOverlay.cpp
+++ b/interface/src/ui/overlays/Image3DOverlay.cpp
@@ -45,11 +45,13 @@ Image3DOverlay::~Image3DOverlay() {
 }
 
 void Image3DOverlay::update(float deltatime) {
+#if OVERLAY_PANELS
     if (usecTimestampNow() > _transformExpiry) {
         Transform transform = getTransform();
         applyTransformTo(transform);
         setTransform(transform);
     }
+#endif
 }
 
 void Image3DOverlay::render(RenderArgs* args) {
diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h
index 4ad1b070b1..494c287676 100644
--- a/interface/src/ui/overlays/Overlay.h
+++ b/interface/src/ui/overlays/Overlay.h
@@ -84,9 +84,9 @@ public:
     void setColorPulse(float value) { _colorPulse = value; }
     void setAlphaPulse(float value) { _alphaPulse = value; }
 
-    virtual void setProperties(const QVariantMap& properties);
-    virtual Overlay* createClone() const = 0;
-    virtual QVariant getProperty(const QString& property);
+    Q_INVOKABLE virtual void setProperties(const QVariantMap& properties);
+    Q_INVOKABLE virtual Overlay* createClone() const = 0;
+    Q_INVOKABLE virtual QVariant getProperty(const QString& property);
 
     render::ItemID getRenderItemID() const { return _renderItemID; }
     void setRenderItemID(render::ItemID renderItemID) { _renderItemID = renderItemID; }
diff --git a/interface/src/ui/overlays/OverlayPanel.cpp b/interface/src/ui/overlays/OverlayPanel.cpp
index df2b91c4ef..06480109ce 100644
--- a/interface/src/ui/overlays/OverlayPanel.cpp
+++ b/interface/src/ui/overlays/OverlayPanel.cpp
@@ -11,6 +11,8 @@
 
 #include "OverlayPanel.h"
 
+#if OVERLAY_PANELS
+
 #include <QVariant>
 #include <RegisteredMetaTypes.h>
 #include <DependencyManager.h>
@@ -185,3 +187,4 @@ void OverlayPanel::applyTransformTo(Transform& transform, bool force) {
         pointTransformAtCamera(transform, getOffsetRotation());
     }
 }
+#endif
\ No newline at end of file
diff --git a/interface/src/ui/overlays/OverlayPanel.h b/interface/src/ui/overlays/OverlayPanel.h
index 5bffe3851e..cff2bc224d 100644
--- a/interface/src/ui/overlays/OverlayPanel.h
+++ b/interface/src/ui/overlays/OverlayPanel.h
@@ -22,6 +22,7 @@
 #include "Billboardable.h"
 #include "Overlay.h"
 
+#if OVERLAY_PANELS
 class PropertyBinding {
 public:
     PropertyBinding() {}
@@ -80,4 +81,6 @@ private:
     QScriptEngine* _scriptEngine;
 };
 
+#endif
+
 #endif // hifi_OverlayPanel_h
diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp
index a9efd51a3e..c27800a5bb 100644
--- a/interface/src/ui/overlays/Overlays.cpp
+++ b/interface/src/ui/overlays/Overlays.cpp
@@ -14,6 +14,7 @@
 
 #include <QtScript/QScriptValueIterator>
 
+#include <shared/QtHelpers.h>
 #include <OffscreenUi.h>
 #include <render/Scene.h>
 #include <RegisteredMetaTypes.h>
@@ -40,8 +41,6 @@ Q_LOGGING_CATEGORY(trace_render_overlays, "trace.render.overlays")
 
 void Overlays::cleanupAllOverlays() {
     {
-        QWriteLocker lock(&_lock);
-        QWriteLocker deleteLock(&_deleteLock);
         foreach(Overlay::Pointer overlay, _overlaysHUD) {
             _overlaysToDelete.push_back(overlay);
         }
@@ -50,19 +49,22 @@ void Overlays::cleanupAllOverlays() {
         }
         _overlaysHUD.clear();
         _overlaysWorld.clear();
+#if OVERLAY_PANELS
         _panels.clear();
+#endif
     }
     cleanupOverlaysToDelete();
 }
 
 void Overlays::init() {
+#if OVERLAY_PANELS
     _scriptEngine = new QScriptEngine();
+#endif
 }
 
 void Overlays::update(float deltatime) {
 
     {
-        QWriteLocker lock(&_lock);
         foreach(Overlay::Pointer thisOverlay, _overlaysHUD) {
             thisOverlay->update(deltatime);
         }
@@ -80,8 +82,6 @@ void Overlays::cleanupOverlaysToDelete() {
         render::Transaction transaction;
 
         {
-            QWriteLocker lock(&_deleteLock);
-
             do {
                 Overlay::Pointer overlay = _overlaysToDelete.takeLast();
 
@@ -100,7 +100,6 @@ void Overlays::cleanupOverlaysToDelete() {
 
 void Overlays::renderHUD(RenderArgs* renderArgs) {
     PROFILE_RANGE(render_overlays, __FUNCTION__);
-    QReadLocker lock(&_lock);
     gpu::Batch& batch = *renderArgs->_batch;
 
     auto geometryCache = DependencyManager::get<GeometryCache>();
@@ -126,12 +125,10 @@ void Overlays::renderHUD(RenderArgs* renderArgs) {
 }
 
 void Overlays::disable() {
-    QWriteLocker lock(&_lock);
     _enabled = false;
 }
 
 void Overlays::enable() {
-    QWriteLocker lock(&_lock);
     _enabled = true;
 }
 
@@ -146,6 +143,12 @@ Overlay::Pointer Overlays::getOverlay(OverlayID id) const {
 }
 
 OverlayID Overlays::addOverlay(const QString& type, const QVariant& properties) {
+    if (QThread::currentThread() != thread()) {
+        OverlayID result;
+        hifi::qt::blockingInvokeMethod(this, "addOverlay", Q_RETURN_ARG(OverlayID, result), Q_ARG(QString, type), Q_ARG(QVariant, properties));
+        return result;
+    }
+
     Overlay::Pointer thisOverlay = nullptr;
 
     if (type == ImageOverlay::TYPE) {
@@ -185,8 +188,7 @@ OverlayID Overlays::addOverlay(const QString& type, const QVariant& properties)
     return UNKNOWN_OVERLAY_ID;
 }
 
-OverlayID Overlays::addOverlay(Overlay::Pointer overlay) {
-    QWriteLocker lock(&_lock);
+OverlayID Overlays::addOverlay(const Overlay::Pointer& overlay) {
     OverlayID thisID = OverlayID(QUuid::createUuid());
     overlay->setOverlayID(thisID);
     overlay->setStackOrder(_stackOrder++);
@@ -205,14 +207,22 @@ OverlayID Overlays::addOverlay(Overlay::Pointer overlay) {
 }
 
 OverlayID Overlays::cloneOverlay(OverlayID id) {
+    if (QThread::currentThread() != thread()) {
+        OverlayID result;
+        hifi::qt::blockingInvokeMethod(this, "cloneOverlay", Q_RETURN_ARG(OverlayID, result), Q_ARG(OverlayID, id));
+        return result;
+    }
+
     Overlay::Pointer thisOverlay = getOverlay(id);
 
     if (thisOverlay) {
         OverlayID cloneId = addOverlay(Overlay::Pointer(thisOverlay->createClone()));
+#if OVERLAY_PANELS
         auto attachable = std::dynamic_pointer_cast<PanelAttachable>(thisOverlay);
         if (attachable && attachable->getParentPanel()) {
             attachable->getParentPanel()->addChild(cloneId);
         }
+#endif
         return cloneId;
     }
 
@@ -220,21 +230,29 @@ OverlayID Overlays::cloneOverlay(OverlayID id) {
 }
 
 bool Overlays::editOverlay(OverlayID id, const QVariant& properties) {
-    QWriteLocker lock(&_lock);
+    if (QThread::currentThread() != thread()) {
+        bool result;
+        hifi::qt::blockingInvokeMethod(this, "editOverlay", Q_RETURN_ARG(bool, result), Q_ARG(OverlayID, id), Q_ARG(QVariant, properties));
+        return result;
+    }
 
     Overlay::Pointer thisOverlay = getOverlay(id);
     if (thisOverlay) {
         thisOverlay->setProperties(properties.toMap());
-
         return true;
     }
     return false;
 }
 
 bool Overlays::editOverlays(const QVariant& propertiesById) {
+    if (QThread::currentThread() != thread()) {
+        bool result;
+        hifi::qt::blockingInvokeMethod(this, "editOverlays", Q_RETURN_ARG(bool, result), Q_ARG(QVariant, propertiesById));
+        return result;
+    }
+
     QVariantMap map = propertiesById.toMap();
     bool success = true;
-    QWriteLocker lock(&_lock);
     for (const auto& key : map.keys()) {
         OverlayID id = OverlayID(key);
         Overlay::Pointer thisOverlay = getOverlay(id);
@@ -249,10 +267,14 @@ bool Overlays::editOverlays(const QVariant& propertiesById) {
 }
 
 void Overlays::deleteOverlay(OverlayID id) {
+    if (QThread::currentThread() != thread()) {
+        QMetaObject::invokeMethod(this, "deleteOverlay", Q_ARG(OverlayID, id));
+        return;
+    }
+
     Overlay::Pointer overlayToDelete;
 
     {
-        QWriteLocker lock(&_lock);
         if (_overlaysHUD.contains(id)) {
             overlayToDelete = _overlaysHUD.take(id);
         } else if (_overlaysWorld.contains(id)) {
@@ -262,19 +284,25 @@ void Overlays::deleteOverlay(OverlayID id) {
         }
     }
 
+#if OVERLAY_PANELS
     auto attachable = std::dynamic_pointer_cast<PanelAttachable>(overlayToDelete);
     if (attachable && attachable->getParentPanel()) {
         attachable->getParentPanel()->removeChild(id);
         attachable->setParentPanel(nullptr);
     }
+#endif
 
-    QWriteLocker lock(&_deleteLock);
     _overlaysToDelete.push_back(overlayToDelete);
-
     emit overlayDeleted(id);
 }
 
-QString Overlays::getOverlayType(OverlayID overlayId) const {
+QString Overlays::getOverlayType(OverlayID overlayId) {
+    if (QThread::currentThread() != thread()) {
+        QString result;
+        hifi::qt::blockingInvokeMethod(this, "getOverlayType", Q_RETURN_ARG(QString, result), Q_ARG(OverlayID, overlayId));
+        return result;
+    }
+
     Overlay::Pointer overlay = getOverlay(overlayId);
     if (overlay) {
         return overlay->getType();
@@ -283,6 +311,12 @@ QString Overlays::getOverlayType(OverlayID overlayId) const {
 }
 
 QObject* Overlays::getOverlayObject(OverlayID id) {
+    if (QThread::currentThread() != thread()) {
+        QObject* result;
+        hifi::qt::blockingInvokeMethod(this, "getOverlayObject", Q_RETURN_ARG(QObject*, result), Q_ARG(OverlayID, id));
+        return result;
+    }
+
     Overlay::Pointer thisOverlay = getOverlay(id);
     if (thisOverlay) {
         return qobject_cast<QObject*>(&(*thisOverlay));
@@ -290,6 +324,7 @@ QObject* Overlays::getOverlayObject(OverlayID id) {
     return nullptr;
 }
 
+#if OVERLAY_PANELS
 OverlayID Overlays::getParentPanel(OverlayID childId) const {
     Overlay::Pointer overlay = getOverlay(childId);
     auto attachable = std::dynamic_pointer_cast<PanelAttachable>(overlay);
@@ -330,10 +365,16 @@ void Overlays::setParentPanel(OverlayID childId, OverlayID panelId) {
         }
     }
 }
+#endif
 
 OverlayID Overlays::getOverlayAtPoint(const glm::vec2& point) {
+    if (QThread::currentThread() != thread()) {
+        OverlayID result;
+        hifi::qt::blockingInvokeMethod(this, "getOverlayAtPoint", Q_RETURN_ARG(OverlayID, result), Q_ARG(glm::vec2, point));
+        return result;
+    }
+
     glm::vec2 pointCopy = point;
-    QReadLocker lock(&_lock);
     if (!_enabled) {
         return UNKNOWN_OVERLAY_ID;
     }
@@ -365,9 +406,14 @@ OverlayID Overlays::getOverlayAtPoint(const glm::vec2& point) {
 }
 
 OverlayPropertyResult Overlays::getProperty(OverlayID id, const QString& property) {
+    if (QThread::currentThread() != thread()) {
+        OverlayPropertyResult result;
+        hifi::qt::blockingInvokeMethod(this, "getProperty", Q_RETURN_ARG(OverlayPropertyResult, result), Q_ARG(OverlayID, id), Q_ARG(QString, property));
+        return result;
+    }
+
     OverlayPropertyResult result;
     Overlay::Pointer thisOverlay = getOverlay(id);
-    QReadLocker lock(&_lock);
     if (thisOverlay && thisOverlay->supportsGetProperty()) {
         result.value = thisOverlay->getProperty(property);
     }
@@ -405,7 +451,18 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionInternal(const PickR
                                                                      const QVector<OverlayID>& overlaysToInclude,
                                                                      const QVector<OverlayID>& overlaysToDiscard,
                                                                      bool visibleOnly, bool collidableOnly) {
-    QReadLocker lock(&_lock);
+    if (QThread::currentThread() != thread()) {
+        RayToOverlayIntersectionResult result;
+        hifi::qt::blockingInvokeMethod(this, "findRayIntersectionInternal", Q_RETURN_ARG(RayToOverlayIntersectionResult, result), 
+            Q_ARG(PickRay, ray), 
+            Q_ARG(bool, precisionPicking), 
+            Q_ARG(QVector<OverlayID>, overlaysToInclude), 
+            Q_ARG(QVector<OverlayID>, overlaysToDiscard), 
+            Q_ARG(bool, visibleOnly), 
+            Q_ARG(bool, collidableOnly));
+        return result;
+    }
+
     float bestDistance = std::numeric_limits<float>::max();
     bool bestIsFront = false;
 
@@ -448,16 +505,6 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionInternal(const PickR
     return result;
 }
 
-RayToOverlayIntersectionResult::RayToOverlayIntersectionResult() :
-    intersects(false),
-    overlayID(UNKNOWN_OVERLAY_ID),
-    distance(0),
-    face(),
-    intersection(),
-    extraInfo()
-{
-}
-
 QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value) {
     auto obj = engine->newObject();
     obj.setProperty("intersects", value.intersects);
@@ -531,7 +578,12 @@ void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& objectVar
 }
 
 bool Overlays::isLoaded(OverlayID id) {
-    QReadLocker lock(&_lock);
+    if (QThread::currentThread() != thread()) {
+        bool result;
+        hifi::qt::blockingInvokeMethod(this, "isLoaded", Q_RETURN_ARG(bool, result), Q_ARG(OverlayID, id));
+        return result;
+    }
+
     Overlay::Pointer thisOverlay = getOverlay(id);
     if (!thisOverlay) {
         return false; // not found
@@ -539,7 +591,13 @@ bool Overlays::isLoaded(OverlayID id) {
     return thisOverlay->isLoaded();
 }
 
-QSizeF Overlays::textSize(OverlayID id, const QString& text) const {
+QSizeF Overlays::textSize(OverlayID id, const QString& text) {
+    if (QThread::currentThread() != thread()) {
+        QSizeF result;
+        hifi::qt::blockingInvokeMethod(this, "textSize", Q_RETURN_ARG(QSizeF, result), Q_ARG(OverlayID, id), Q_ARG(QString, text));
+        return result;
+    }
+
     Overlay::Pointer thisOverlay = _overlaysHUD[id];
     if (thisOverlay) {
         if (auto textOverlay = std::dynamic_pointer_cast<TextOverlay>(thisOverlay)) {
@@ -554,6 +612,7 @@ QSizeF Overlays::textSize(OverlayID id, const QString& text) const {
     return QSizeF(0.0f, 0.0f);
 }
 
+#if OVERLAY_PANELS
 OverlayID Overlays::addPanel(OverlayPanel::Pointer panel) {
     QWriteLocker lock(&_lock);
 
@@ -607,8 +666,15 @@ void Overlays::deletePanel(OverlayID panelId) {
 
     emit panelDeleted(panelId);
 }
+#endif
 
 bool Overlays::isAddedOverlay(OverlayID id) {
+    if (QThread::currentThread() != thread()) {
+        bool result;
+        hifi::qt::blockingInvokeMethod(this, "isAddedOverlay", Q_RETURN_ARG(bool, result), Q_ARG(OverlayID, id));
+        return result;
+    }
+
     return _overlaysHUD.contains(id) || _overlaysWorld.contains(id);
 }
 
@@ -636,20 +702,43 @@ void Overlays::sendHoverLeaveOverlay(OverlayID  id, PointerEvent event) {
     emit hoverLeaveOverlay(id, event);
 }
 
-OverlayID Overlays::getKeyboardFocusOverlay() const {
+OverlayID Overlays::getKeyboardFocusOverlay() {
+    if (QThread::currentThread() != thread()) {
+        OverlayID result;
+        hifi::qt::blockingInvokeMethod(this, "getKeyboardFocusOverlay", Q_RETURN_ARG(OverlayID, result));
+        return result;
+    }
+
     return qApp->getKeyboardFocusOverlay();
 }
 
 void Overlays::setKeyboardFocusOverlay(OverlayID id) {
+    if (QThread::currentThread() != thread()) {
+        QMetaObject::invokeMethod(this, "setKeyboardFocusOverlay", Q_ARG(OverlayID, id));
+        return;
+    }
+
     qApp->setKeyboardFocusOverlay(id);
 }
 
-float Overlays::width() const {
+float Overlays::width() {
+    if (QThread::currentThread() != thread()) {
+        float result;
+        hifi::qt::blockingInvokeMethod(this, "width", Q_RETURN_ARG(float, result));
+        return result;
+    }
+
     auto offscreenUi = DependencyManager::get<OffscreenUi>();
     return offscreenUi->getWindow()->size().width();
 }
 
-float Overlays::height() const {
+float Overlays::height() {
+    if (QThread::currentThread() != thread()) {
+        float result;
+        hifi::qt::blockingInvokeMethod(this, "height", Q_RETURN_ARG(float, result));
+        return result;
+    }
+
     auto offscreenUi = DependencyManager::get<OffscreenUi>();
     return offscreenUi->getWindow()->size().height();
 }
@@ -705,7 +794,6 @@ PointerEvent Overlays::calculatePointerEvent(Overlay::Pointer overlay, PickRay r
 
     auto thisOverlay = std::dynamic_pointer_cast<Web3DOverlay>(overlay);
 
-    QReadLocker lock(&_lock);
     auto position = thisOverlay->getPosition();
     auto rotation = thisOverlay->getRotation();
     auto dimensions = thisOverlay->getSize();
@@ -854,8 +942,13 @@ bool Overlays::mouseMoveEvent(QMouseEvent* event) {
     return false;
 }
 
-QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) const {
+QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
     QVector<QUuid> result;
+    if (QThread::currentThread() != thread()) {
+        hifi::qt::blockingInvokeMethod(this, "findOverlays", Q_RETURN_ARG(QVector<QUuid>, result), Q_ARG(glm::vec3, center), Q_ARG(float, radius));
+        return result;
+    }
+
 
     QMapIterator<OverlayID, Overlay::Pointer> i(_overlaysWorld);
     int checked = 0;
diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h
index a1d4be8376..d3fa70225e 100644
--- a/interface/src/ui/overlays/Overlays.h
+++ b/interface/src/ui/overlays/Overlays.h
@@ -25,8 +25,9 @@
 #include <PointerEvent.h>
 
 #include "Overlay.h"
-#include "OverlayPanel.h"
+
 #include "PanelAttachable.h"
+#include "OverlayPanel.h"
 
 class PickRay;
 
@@ -41,6 +42,8 @@ Q_DECLARE_METATYPE(OverlayPropertyResult);
 QScriptValue OverlayPropertyResultToScriptValue(QScriptEngine* engine, const OverlayPropertyResult& value);
 void OverlayPropertyResultFromScriptValue(const QScriptValue& object, OverlayPropertyResult& value);
 
+const OverlayID UNKNOWN_OVERLAY_ID = OverlayID();
+
 /**jsdoc
  * @typedef Overlays.RayToOverlayIntersectionResult
  * @property {bool} intersects True if the PickRay intersected with a 3D overlay.
@@ -51,10 +54,9 @@ void OverlayPropertyResultFromScriptValue(const QScriptValue& object, OverlayPro
  */
 class RayToOverlayIntersectionResult {
 public:
-    RayToOverlayIntersectionResult();
-    bool intersects;
-    OverlayID overlayID;
-    float distance;
+    bool intersects { false };
+    OverlayID overlayID { UNKNOWN_OVERLAY_ID };
+    float distance { 0 };
     BoxFace face;
     glm::vec3 surfaceNormal;
     glm::vec3 intersection;
@@ -77,8 +79,6 @@ void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, R
  * @namespace Overlays
  */
 
-const OverlayID UNKNOWN_OVERLAY_ID = OverlayID();
-
 class Overlays : public QObject {
     Q_OBJECT
 
@@ -94,11 +94,13 @@ public:
     void enable();
 
     Overlay::Pointer getOverlay(OverlayID id) const;
+#if OVERLAY_PANELS
     OverlayPanel::Pointer getPanel(OverlayID id) const { return _panels[id]; }
+#endif
 
     /// adds an overlay that's already been created
     OverlayID addOverlay(Overlay* overlay) { return addOverlay(Overlay::Pointer(overlay)); }
-    OverlayID addOverlay(Overlay::Pointer overlay);
+    OverlayID addOverlay(const Overlay::Pointer& overlay);
 
     bool mousePressEvent(QMouseEvent* event);
     bool mouseDoublePressEvent(QMouseEvent* event);
@@ -156,7 +158,7 @@ public slots:
      * @param {Overlays.OverlayID} overlayID The ID of the overlay to get the type of.
      * @return {string} The type of the overlay if found, otherwise the empty string.
      */
-    QString getOverlayType(OverlayID overlayId) const;
+    QString getOverlayType(OverlayID overlayId);
 
     /**jsdoc
     * Get the overlay Script object.
@@ -215,7 +217,7 @@ public slots:
      * @param {float} radius search radius
      * @return {Overlays.OverlayID[]} list of overlays withing the radius
      */
-    QVector<QUuid> findOverlays(const glm::vec3& center, float radius) const;
+    QVector<QUuid> findOverlays(const glm::vec3& center, float radius);
 
     /**jsdoc
      * Check whether an overlay's assets have been loaded. For example, if the
@@ -237,7 +239,7 @@ public slots:
      * @param {string} The string to measure.
      * @return {Vec2} The size of the text.
      */
-    QSizeF textSize(OverlayID id, const QString& text) const;
+    QSizeF textSize(OverlayID id, const QString& text);
 
     /**jsdoc
      * Get the width of the virtual 2D HUD.
@@ -245,7 +247,7 @@ public slots:
      * @function Overlays.width
      * @return {float} The width of the 2D HUD.
      */
-    float width() const;
+    float width();
 
     /**jsdoc
      * Get the height of the virtual 2D HUD.
@@ -253,11 +255,12 @@ public slots:
      * @function Overlays.height
      * @return {float} The height of the 2D HUD.
      */
-    float height() const;
+    float height();
 
     /// return true if there is an overlay with that id else false
     bool isAddedOverlay(OverlayID id);
 
+#if OVERLAY_PANELS
     OverlayID getParentPanel(OverlayID childId) const;
     void setParentPanel(OverlayID childId, OverlayID panelId);
 
@@ -279,6 +282,8 @@ public slots:
     /// return true if there is a panel with that id else false
     bool isAddedPanel(OverlayID id) { return _panels.contains(id); }
 
+#endif
+
     void sendMousePressOnOverlay(OverlayID overlayID, const PointerEvent& event);
     void sendMouseReleaseOnOverlay(OverlayID overlayID, const PointerEvent& event);
     void sendMouseMoveOnOverlay(OverlayID overlayID, const PointerEvent& event);
@@ -287,7 +292,7 @@ public slots:
     void sendHoverOverOverlay(OverlayID id, PointerEvent event);
     void sendHoverLeaveOverlay(OverlayID id, PointerEvent event);
 
-    OverlayID getKeyboardFocusOverlay() const;
+    OverlayID getKeyboardFocusOverlay();
     void setKeyboardFocusOverlay(OverlayID id);
 
 signals:
@@ -316,13 +321,15 @@ private:
 
     QMap<OverlayID, Overlay::Pointer> _overlaysHUD;
     QMap<OverlayID, Overlay::Pointer> _overlaysWorld;
+#if OVERLAY_PANELS
     QMap<OverlayID, OverlayPanel::Pointer> _panels;
+#endif
     QList<Overlay::Pointer> _overlaysToDelete;
     unsigned int _stackOrder { 1 };
 
-    QReadWriteLock _lock;
-    QReadWriteLock _deleteLock;
+#if OVERLAY_PANELS
     QScriptEngine* _scriptEngine;
+#endif
     bool _enabled = true;
 
     PointerEvent calculatePointerEvent(Overlay::Pointer overlay, PickRay ray, RayToOverlayIntersectionResult rayPickResult,
@@ -331,7 +338,7 @@ private:
     OverlayID _currentClickingOnOverlayID { UNKNOWN_OVERLAY_ID };
     OverlayID _currentHoverOverOverlayID { UNKNOWN_OVERLAY_ID };
 
-    RayToOverlayIntersectionResult findRayIntersectionInternal(const PickRay& ray, bool precisionPicking,
+    Q_INVOKABLE RayToOverlayIntersectionResult findRayIntersectionInternal(const PickRay& ray, bool precisionPicking,
                                                                const QVector<OverlayID>& overlaysToInclude,
                                                                const QVector<OverlayID>& overlaysToDiscard,
                                                                bool visibleOnly = false, bool collidableOnly = false);
diff --git a/interface/src/ui/overlays/PanelAttachable.cpp b/interface/src/ui/overlays/PanelAttachable.cpp
index 7f1c4e2e50..421155083c 100644
--- a/interface/src/ui/overlays/PanelAttachable.cpp
+++ b/interface/src/ui/overlays/PanelAttachable.cpp
@@ -16,11 +16,15 @@
 #include "OverlayPanel.h"
 
 bool PanelAttachable::getParentVisible() const {
+#if OVERLAY_PANELS
     if (getParentPanel()) {
         return getParentPanel()->getVisible() && getParentPanel()->getParentVisible();
     } else {
         return true;
     }
+#else
+    return true;
+#endif
 }
 
 QVariant PanelAttachable::getProperty(const QString& property) {
@@ -61,11 +65,13 @@ void PanelAttachable::applyTransformTo(Transform& transform, bool force) {
     if (force || usecTimestampNow() > _transformExpiry) {
         const quint64 TRANSFORM_UPDATE_PERIOD = 100000; // frequency is 10 Hz
         _transformExpiry = usecTimestampNow() + TRANSFORM_UPDATE_PERIOD;
+#if OVERLAY_PANELS
         if (getParentPanel()) {
             getParentPanel()->applyTransformTo(transform, true);
             transform.postTranslate(getOffsetPosition());
             transform.postRotate(getOffsetRotation());
             transform.postScale(getOffsetScale());
         }
+#endif
     }
 }
diff --git a/interface/src/ui/overlays/PanelAttachable.h b/interface/src/ui/overlays/PanelAttachable.h
index 270addbfcf..4f37cd2258 100644
--- a/interface/src/ui/overlays/PanelAttachable.h
+++ b/interface/src/ui/overlays/PanelAttachable.h
@@ -30,6 +30,8 @@
 #ifndef hifi_PanelAttachable_h
 #define hifi_PanelAttachable_h
 
+#define OVERLAY_PANELS 0
+
 #include <memory>
 
 #include <glm/glm.hpp>
@@ -39,18 +41,21 @@
 #include <QUuid>
 
 class OverlayPanel;
-
 class PanelAttachable {
 public:
     // getters
+#if OVERLAY_PANELS
     std::shared_ptr<OverlayPanel> getParentPanel() const { return _parentPanel; }
+#endif
     glm::vec3 getOffsetPosition() const { return _offset.getTranslation(); }
     glm::quat getOffsetRotation() const { return _offset.getRotation(); }
     glm::vec3 getOffsetScale() const { return _offset.getScale(); }
     bool getParentVisible() const;
 
     // setters
+#if OVERLAY_PANELS
     void setParentPanel(std::shared_ptr<OverlayPanel> panel) { _parentPanel = panel; }
+#endif
     void setOffsetPosition(const glm::vec3& position) { _offset.setTranslation(position); }
     void setOffsetRotation(const glm::quat& rotation) { _offset.setRotation(rotation); }
     void setOffsetScale(float scale) { _offset.setScale(scale); }
@@ -66,7 +71,9 @@ protected:
     quint64 _transformExpiry = 0;
 
 private:
+#if OVERLAY_PANELS
     std::shared_ptr<OverlayPanel> _parentPanel = nullptr;
+#endif
     Transform _offset;
 };
 
diff --git a/interface/src/ui/overlays/QmlOverlay.cpp b/interface/src/ui/overlays/QmlOverlay.cpp
index eb909de993..15e72cf1e3 100644
--- a/interface/src/ui/overlays/QmlOverlay.cpp
+++ b/interface/src/ui/overlays/QmlOverlay.cpp
@@ -32,21 +32,20 @@ QmlOverlay::QmlOverlay(const QUrl& url, const QmlOverlay* textOverlay)
 }
 
 void QmlOverlay::buildQmlElement(const QUrl& url) {
+    if (QThread::currentThread() != thread()) {
+        QMetaObject::invokeMethod(this, "buildQmlElement", Q_ARG(QUrl, url));
+        return;
+    }
+
     auto offscreenUi = DependencyManager::get<OffscreenUi>();
-    offscreenUi->returnFromUiThread([=] {
-        offscreenUi->load(url, [=](QQmlContext* context, QObject* object) {
-            QQuickItem* rawPtr = dynamic_cast<QQuickItem*>(object);
-            // Create a shared ptr with a custom deleter lambda, that calls deleteLater
-            _qmlElement = std::shared_ptr<QQuickItem>(rawPtr, [](QQuickItem* ptr) {
-                if (ptr) {
-                    ptr->deleteLater();
-                }
-            });
+    offscreenUi->load(url, [=](QQmlContext* context, QObject* object) {
+        QQuickItem* rawPtr = dynamic_cast<QQuickItem*>(object);
+        // Create a shared ptr with a custom deleter lambda, that calls deleteLater
+        _qmlElement = std::shared_ptr<QQuickItem>(rawPtr, [](QQuickItem* ptr) {
+            if (ptr) {
+                ptr->deleteLater();
+            }
         });
-        while (!_qmlElement) {
-            qApp->processEvents();
-        }
-        return QVariant();
     });
 }
 
@@ -55,20 +54,23 @@ QmlOverlay::~QmlOverlay() {
 }
 
 void QmlOverlay::setProperties(const QVariantMap& properties) {
+    if (QThread::currentThread() != thread()) {
+        QMetaObject::invokeMethod(this, "setProperties", Q_ARG(QVariantMap, properties));
+        return;
+    }
+
     Overlay2D::setProperties(properties);
     auto bounds = _bounds;
     std::weak_ptr<QQuickItem> weakQmlElement = _qmlElement;
-    DependencyManager::get<OffscreenUi>()->executeOnUiThread([weakQmlElement, bounds, properties] {
-        // check to see if qmlElement still exists
-        auto qmlElement = weakQmlElement.lock();
-        if (qmlElement) {
-            qmlElement->setX(bounds.left());
-            qmlElement->setY(bounds.top());
-            qmlElement->setWidth(bounds.width());
-            qmlElement->setHeight(bounds.height());
-            QMetaObject::invokeMethod(qmlElement.get(), "updatePropertiesFromScript", Qt::DirectConnection, Q_ARG(QVariant, properties));
-        }
-    });
+    // check to see if qmlElement still exists
+    auto qmlElement = weakQmlElement.lock();
+    if (qmlElement) {
+        qmlElement->setX(bounds.left());
+        qmlElement->setY(bounds.top());
+        qmlElement->setWidth(bounds.width());
+        qmlElement->setHeight(bounds.height());
+        QMetaObject::invokeMethod(qmlElement.get(), "updatePropertiesFromScript", Qt::DirectConnection, Q_ARG(QVariant, properties));
+    }
 }
 
 void QmlOverlay::render(RenderArgs* args) {
diff --git a/interface/src/ui/overlays/QmlOverlay.h b/interface/src/ui/overlays/QmlOverlay.h
index 736d3884b5..ced2b6fa1f 100644
--- a/interface/src/ui/overlays/QmlOverlay.h
+++ b/interface/src/ui/overlays/QmlOverlay.h
@@ -32,7 +32,7 @@ public:
     void render(RenderArgs* args) override;
 
 private:
-    void buildQmlElement(const QUrl& url);
+    Q_INVOKABLE void buildQmlElement(const QUrl& url);
 
 protected:
     std::shared_ptr<QQuickItem> _qmlElement;
diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
index 01af69e3ad..c630fe09e4 100644
--- a/libraries/audio-client/src/AudioClient.cpp
+++ b/libraries/audio-client/src/AudioClient.cpp
@@ -259,10 +259,21 @@ void AudioClient::customDeleter() {
 void AudioClient::cleanupBeforeQuit() {
     // FIXME: this should be put in customDeleter, but there is still a reference to this when it is called,
     //        so this must be explicitly, synchronously stopped
+    static ConditionalGuard guard;
+    if (QThread::currentThread() != thread()) {
+        // This will likely be called from the main thread, but we don't want to do blocking queued calls 
+        // from the main thread, so we use a normal auto-connection invoke, and then use a conditional to wait 
+        // for completion
+        // The effect is the same, yes, but we actually want to avoid the use of Qt::BlockingQueuedConnection 
+        // in the code
+        QMetaObject::invokeMethod(this, "cleanupBeforeQuit");
+        guard.wait();
+        return;
+    }
 
     stop();
-
     _checkDevicesTimer->stop();
+    guard.trigger();
 }
 
 void AudioClient::handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec) {
@@ -1859,7 +1870,7 @@ void AudioClient::startThread() {
 }
 
 void AudioClient::setInputVolume(float volume) { 
-    if (_audioInput && volume != _audioInput->volume()) { 
+    if (_audioInput && volume != (float)_audioInput->volume()) { 
         _audioInput->setVolume(volume); 
         emit inputVolumeChanged(_audioInput->volume());
     }
diff --git a/libraries/ui/src/QmlWebWindowClass.cpp b/libraries/ui/src/QmlWebWindowClass.cpp
index 68bb872667..a370d7999b 100644
--- a/libraries/ui/src/QmlWebWindowClass.cpp
+++ b/libraries/ui/src/QmlWebWindowClass.cpp
@@ -13,6 +13,7 @@
 #include <QtScript/QScriptContext>
 #include <QtScript/QScriptEngine>
 
+#include <shared/QtHelpers.h>
 #include "OffscreenUi.h"
 
 static const char* const URL_PROPERTY = "source";
@@ -21,39 +22,51 @@ static const char* const SCRIPT_PROPERTY = "scriptUrl";
 // Method called by Qt scripts to create a new web window in the overlay
 QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
     auto properties = parseArguments(context);
-    QmlWebWindowClass* retVal { nullptr };
     auto offscreenUi = DependencyManager::get<OffscreenUi>();
-    offscreenUi->executeOnUiThread([&] {
-        retVal = new QmlWebWindowClass();
-        retVal->initQml(properties);
-    }, true);
+    QmlWebWindowClass* retVal = new QmlWebWindowClass();
     Q_ASSERT(retVal);
+    if (QThread::currentThread() != qApp->thread()) {
+        retVal->moveToThread(qApp->thread());
+        QMetaObject::invokeMethod(retVal, "initQml", Q_ARG(QVariantMap, properties));
+    } else {
+        retVal->initQml(properties);
+    }
     connect(engine, &QScriptEngine::destroyed, retVal, &QmlWindowClass::deleteLater);
     return engine->newQObject(retVal);
 }
 
-QString QmlWebWindowClass::getURL() const {
-    QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant {
-        if (_qmlWindow.isNull()) {
-            return QVariant();
-        }
-        return _qmlWindow->property(URL_PROPERTY);
-    });
-    return result.toString();
+QString QmlWebWindowClass::getURL() {
+    if (QThread::currentThread() != thread()) {
+        QString result = false;
+        hifi::qt::blockingInvokeMethod(this, "getURL", Q_RETURN_ARG(QString, result));
+        return result;
+    }
+
+    if (_qmlWindow.isNull()) {
+        return QString();
+    }
+        
+    return _qmlWindow->property(URL_PROPERTY).toString();
 }
 
 void QmlWebWindowClass::setURL(const QString& urlString) {
-    DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
-        if (!_qmlWindow.isNull()) {
-            _qmlWindow->setProperty(URL_PROPERTY, urlString);
-        }
-    });
+    if (QThread::currentThread() != thread()) {
+        QMetaObject::invokeMethod(this, "setURL", Q_ARG(QString, urlString));
+        return;
+    }
+
+    if (!_qmlWindow.isNull()) {
+        _qmlWindow->setProperty(URL_PROPERTY, urlString);
+    }
 }
 
 void QmlWebWindowClass::setScriptURL(const QString& script) {
-    DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
-        if (!_qmlWindow.isNull()) {
-            _qmlWindow->setProperty(SCRIPT_PROPERTY, script);
-        }
-    });
+    if (QThread::currentThread() != thread()) {
+        QMetaObject::invokeMethod(this, "setScriptURL", Q_ARG(QString, script));
+        return;
+    }
+
+    if (!_qmlWindow.isNull()) {
+        _qmlWindow->setProperty(SCRIPT_PROPERTY, script);
+    }
 }
diff --git a/libraries/ui/src/QmlWebWindowClass.h b/libraries/ui/src/QmlWebWindowClass.h
index 15ebe74a4f..cdc07265cd 100644
--- a/libraries/ui/src/QmlWebWindowClass.h
+++ b/libraries/ui/src/QmlWebWindowClass.h
@@ -20,7 +20,7 @@ public:
     static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
 
 public slots:
-    QString getURL() const;
+    QString getURL();
     void setURL(const QString& url);
     void setScriptURL(const QString& script);
 
diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp
index f5bb880957..e773ea0c5d 100644
--- a/libraries/ui/src/QmlWindowClass.cpp
+++ b/libraries/ui/src/QmlWindowClass.cpp
@@ -23,6 +23,7 @@
 #include <QtCore/QJsonDocument>
 #include <QtCore/QJsonObject>
 
+#include <shared/QtHelpers.h>
 #include "OffscreenUi.h"
 
 static const char* const SOURCE_PROPERTY = "source";
@@ -73,13 +74,15 @@ QVariantMap QmlWindowClass::parseArguments(QScriptContext* context) {
 // Method called by Qt scripts to create a new web window in the overlay
 QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
     auto properties = parseArguments(context);
-    QmlWindowClass* retVal { nullptr };
     auto offscreenUi = DependencyManager::get<OffscreenUi>();
-    offscreenUi->executeOnUiThread([&] {
-        retVal = new QmlWindowClass();
-        retVal->initQml(properties);
-    }, true);
+    QmlWindowClass* retVal = new QmlWindowClass();
     Q_ASSERT(retVal);
+    if (QThread::currentThread() != qApp->thread()) {
+        retVal->moveToThread(qApp->thread());
+        hifi::qt::blockingInvokeMethod(retVal, "initQml", Q_ARG(QVariantMap, properties));
+    } else {
+        retVal->initQml(properties);
+    }
     connect(engine, &QScriptEngine::destroyed, retVal, &QmlWindowClass::deleteLater);
     return engine->newQObject(retVal);
 }
@@ -214,49 +217,64 @@ QQuickItem* QmlWindowClass::asQuickItem() const {
 }
 
 void QmlWindowClass::setVisible(bool visible) {
+    if (QThread::currentThread() != thread()) {
+        QMetaObject::invokeMethod(this, "setVisible", Q_ARG(bool, visible));
+        return;
+    }
+
     QQuickItem* targetWindow = asQuickItem();
     if (_toolWindow) {
         // For tool window tabs we special case visibility as a function call on the tab parent
         // The tool window itself has special logic based on whether any tabs are visible
         QMetaObject::invokeMethod(targetWindow, "showTabForUrl", Qt::QueuedConnection, Q_ARG(QVariant, _source), Q_ARG(QVariant, visible));
     } else {
-        DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
-            targetWindow->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible);
-        });
+        targetWindow->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible);
     }
 }
 
-bool QmlWindowClass::isVisible() const {
+bool QmlWindowClass::isVisible() {
+    if (QThread::currentThread() != thread()) {
+        bool result = false;
+        hifi::qt::blockingInvokeMethod(this, "isVisible", Q_RETURN_ARG(bool, result));
+        return result;
+    }
+
     // The tool window itself has special logic based on whether any tabs are enabled
-    return DependencyManager::get<OffscreenUi>()->returnFromUiThread([&] {
-        if (_qmlWindow.isNull()) {
-            return QVariant::fromValue(false);
-        }
-        if (_toolWindow) {
-            return QVariant::fromValue(dynamic_cast<QQuickItem*>(_qmlWindow.data())->isEnabled());
-        } else {
-            return QVariant::fromValue(asQuickItem()->isVisible());
-        }
-    }).toBool();
+    if (_qmlWindow.isNull()) {
+        return false;
+    }
+
+    if (_toolWindow) {
+        return dynamic_cast<QQuickItem*>(_qmlWindow.data())->isEnabled();
+    } 
+      
+    return asQuickItem()->isVisible();
 }
 
-glm::vec2 QmlWindowClass::getPosition() const {
-    QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant {
-        if (_qmlWindow.isNull()) {
-            return QVariant(QPointF(0, 0));
-        }
-        return asQuickItem()->position();
-    });
-    return toGlm(result.toPointF());
+glm::vec2 QmlWindowClass::getPosition() {
+    if (QThread::currentThread() != thread()) {
+        vec2 result;
+        hifi::qt::blockingInvokeMethod(this, "getPosition", Q_RETURN_ARG(vec2, result));
+        return result;
+    }
+
+    if (_qmlWindow.isNull()) {
+        return {};
+    }
+
+    return toGlm(asQuickItem()->position());
 }
 
 
 void QmlWindowClass::setPosition(const glm::vec2& position) {
-    DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
-        if (!_qmlWindow.isNull()) {
-            asQuickItem()->setPosition(QPointF(position.x, position.y));
-        }
-    });
+    if (QThread::currentThread() != thread()) {
+        QMetaObject::invokeMethod(this, "setPosition", Q_ARG(vec2, position));
+        return;
+    }
+
+    if (!_qmlWindow.isNull()) {
+        asQuickItem()->setPosition(QPointF(position.x, position.y));
+    }
 }
 
 void QmlWindowClass::setPosition(int x, int y) {
@@ -268,23 +286,29 @@ glm::vec2 toGlm(const QSizeF& size) {
     return glm::vec2(size.width(), size.height());
 }
 
-glm::vec2 QmlWindowClass::getSize() const {
-    QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant {
-        if (_qmlWindow.isNull()) {
-            return QVariant(QSizeF(0, 0));
-        }
-        QQuickItem* targetWindow = asQuickItem();
-        return QSizeF(targetWindow->width(), targetWindow->height());
-    });
-    return toGlm(result.toSizeF());
+glm::vec2 QmlWindowClass::getSize() {
+    if (QThread::currentThread() != thread()) {
+        vec2 result;
+        hifi::qt::blockingInvokeMethod(this, "getSize", Q_RETURN_ARG(vec2, result));
+        return result;
+    }
+
+    if (_qmlWindow.isNull()) {
+        return {};
+    }
+    QQuickItem* targetWindow = asQuickItem();
+    return vec2(targetWindow->width(), targetWindow->height());
 }
 
 void QmlWindowClass::setSize(const glm::vec2& size) {
-    DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
-        if (!_qmlWindow.isNull()) {
-            asQuickItem()->setSize(QSizeF(size.x, size.y));
-        }
-    });
+    if (QThread::currentThread() != thread()) {
+        QMetaObject::invokeMethod(this, "setSize", Q_ARG(vec2, size));
+        return;
+    }
+
+    if (!_qmlWindow.isNull()) {
+        asQuickItem()->setSize(QSizeF(size.x, size.y));
+    }
 }
 
 void QmlWindowClass::setSize(int width, int height) {
@@ -292,28 +316,33 @@ void QmlWindowClass::setSize(int width, int height) {
 }
 
 void QmlWindowClass::setTitle(const QString& title) {
-    DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
-        if (!_qmlWindow.isNull()) {
-            asQuickItem()->setProperty(TITLE_PROPERTY, title);
-        }
-    });
+    if (QThread::currentThread() != thread()) {
+        QMetaObject::invokeMethod(this, "setTitle", Q_ARG(QString, title));
+        return;
+    }
+
+    if (!_qmlWindow.isNull()) {
+        asQuickItem()->setProperty(TITLE_PROPERTY, title);
+    }
 }
 
 void QmlWindowClass::close() {
-    if (_qmlWindow) {
-        if (_toolWindow) {
-            auto offscreenUi = DependencyManager::get<OffscreenUi>();
-            offscreenUi->executeOnUiThread([=] {
-                auto toolWindow = offscreenUi->getToolWindow();
-                auto invokeResult = QMetaObject::invokeMethod(toolWindow, "removeTabForUrl", Qt::DirectConnection,
-                    Q_ARG(QVariant, _source));
-                Q_ASSERT(invokeResult);
-            });
-        } else {
-            _qmlWindow->deleteLater();
-        }
-        _qmlWindow = nullptr;
+    if (QThread::currentThread() != thread()) {
+        QMetaObject::invokeMethod(this, "close");
+        return;
     }
+
+    if (_toolWindow) {
+        auto offscreenUi = DependencyManager::get<OffscreenUi>();
+        auto toolWindow = offscreenUi->getToolWindow();
+        auto invokeResult = QMetaObject::invokeMethod(toolWindow, "removeTabForUrl", Qt::DirectConnection,
+            Q_ARG(QVariant, _source));
+        Q_ASSERT(invokeResult);
+        return;
+    } else if (_qmlWindow) {
+        _qmlWindow->deleteLater();
+    }
+    _qmlWindow = nullptr;
 }
 
 void QmlWindowClass::hasMoved(QVector2D position) {
@@ -325,10 +354,13 @@ void QmlWindowClass::hasClosed() {
 }
 
 void QmlWindowClass::raise() {
+    if (QThread::currentThread() != thread()) {
+        QMetaObject::invokeMethod(this, "raise");
+        return;
+    }
+
     auto offscreenUi = DependencyManager::get<OffscreenUi>();
-    offscreenUi->executeOnUiThread([=] {
-        if (!_qmlWindow.isNull()) {
-            QMetaObject::invokeMethod(asQuickItem(), "raise", Qt::DirectConnection);
-        }
-    });
+    if (_qmlWindow) {
+        QMetaObject::invokeMethod(asQuickItem(), "raise", Qt::DirectConnection);
+    }
 }
diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h
index 4f604133a5..d3e1912d45 100644
--- a/libraries/ui/src/QmlWindowClass.h
+++ b/libraries/ui/src/QmlWindowClass.h
@@ -31,18 +31,18 @@ public:
     QmlWindowClass();
     ~QmlWindowClass();
 
-    virtual void initQml(QVariantMap properties);
+    Q_INVOKABLE virtual void initQml(QVariantMap properties);
     QQuickItem* asQuickItem() const;
 
 public slots:
-    bool isVisible() const;
+    bool isVisible();
     void setVisible(bool visible);
 
-    glm::vec2 getPosition() const;
+    glm::vec2 getPosition();
     void setPosition(const glm::vec2& position);
     void setPosition(int x, int y);
 
-    glm::vec2 getSize() const;
+    glm::vec2 getSize();
     void setSize(const glm::vec2& size);
     void setSize(int width, int height);
     void setTitle(const QString& title);
diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp
index 573d873bab..19cd37a6cb 100644
--- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp
+++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp
@@ -887,28 +887,6 @@ QQmlContext* OffscreenQmlSurface::getSurfaceContext() {
     return _qmlContext;
 }
 
-void OffscreenQmlSurface::executeOnUiThread(std::function<void()> function, bool blocking ) {
-    if (QThread::currentThread() != thread()) {
-        QMetaObject::invokeMethod(this, "executeOnUiThread", blocking ? Qt::BlockingQueuedConnection : Qt::QueuedConnection,
-            Q_ARG(std::function<void()>, function));
-        return;
-    }
-
-    function();
-}
-
-QVariant OffscreenQmlSurface::returnFromUiThread(std::function<QVariant()> function) {
-    if (QThread::currentThread() != thread()) {
-        QVariant result;
-        hifi::qt::blockingInvokeMethod(this, "returnFromUiThread",
-            Q_RETURN_ARG(QVariant, result),
-            Q_ARG(std::function<QVariant()>, function));
-        return result;
-    }
-
-    return function();
-}
-
 void OffscreenQmlSurface::focusDestroyed(QObject *obj) {
     _currentFocusItem = nullptr;
 }
diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h
index ae81ae48b4..54f27e3b1f 100644
--- a/libraries/ui/src/ui/OffscreenQmlSurface.h
+++ b/libraries/ui/src/ui/OffscreenQmlSurface.h
@@ -55,10 +55,6 @@ public:
         return load(QUrl(qmlSourceFile), f);
     }
     void clearCache();
-
-    Q_INVOKABLE void executeOnUiThread(std::function<void()> function, bool blocking = false);
-    Q_INVOKABLE QVariant returnFromUiThread(std::function<QVariant()> function);
-
     void setMaxFps(uint8_t maxFps) { _maxFps = maxFps; }
     // Optional values for event handling
     void setProxyWindow(QWindow* window);
diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp
index ade8c73df5..0dbcfac25c 100644
--- a/libraries/ui/src/ui/TabletScriptingInterface.cpp
+++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp
@@ -214,20 +214,18 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
 
         // create new desktop window
         auto offscreenUi = DependencyManager::get<OffscreenUi>();
-        offscreenUi->executeOnUiThread([=] {
-            auto tabletRootWindow = new TabletRootWindow();
-            tabletRootWindow->initQml(QVariantMap());
-            auto quickItem = tabletRootWindow->asQuickItem();
-            _desktopWindow = tabletRootWindow;
-            QMetaObject::invokeMethod(quickItem, "setShown", Q_ARG(const QVariant&, QVariant(false)));
+        auto tabletRootWindow = new TabletRootWindow();
+        tabletRootWindow->initQml(QVariantMap());
+        auto quickItem = tabletRootWindow->asQuickItem();
+        _desktopWindow = tabletRootWindow;
+        QMetaObject::invokeMethod(quickItem, "setShown", Q_ARG(const QVariant&, QVariant(false)));
 
-            QObject::connect(quickItem, SIGNAL(windowClosed()), this, SLOT(desktopWindowClosed()));
+        QObject::connect(quickItem, SIGNAL(windowClosed()), this, SLOT(desktopWindowClosed()));
 
-            QObject::connect(tabletRootWindow, SIGNAL(webEventReceived(QVariant)), this, SLOT(emitWebEvent(QVariant)), Qt::DirectConnection);
+        QObject::connect(tabletRootWindow, SIGNAL(webEventReceived(QVariant)), this, SLOT(emitWebEvent(QVariant)), Qt::DirectConnection);
 
-            // forward qml surface events to interface js
-            connect(tabletRootWindow, &QmlWindowClass::fromQml, this, &TabletProxy::fromQml);
-        });
+        // forward qml surface events to interface js
+        connect(tabletRootWindow, &QmlWindowClass::fromQml, this, &TabletProxy::fromQml);
     } else {
         _state = State::Home;
         removeButtonsFromToolbar();