From 306f3d07c97af5c1f21d360f3a300d2cf0445923 Mon Sep 17 00:00:00 2001
From: Heather Anderson <heath@odysseus.anderson.name>
Date: Mon, 21 Mar 2022 00:24:27 -0700
Subject: [PATCH] remove requirement that ScriptManager::evaluate be
 meta-invokable

---
 interface/src/ui/JSConsole.cpp            |  7 ++--
 libraries/shared/src/shared/QtHelpers.cpp | 10 ++++++
 libraries/shared/src/shared/QtHelpers.h   | 44 +++++++++++++++++++++++
 3 files changed, 57 insertions(+), 4 deletions(-)

diff --git a/interface/src/ui/JSConsole.cpp b/interface/src/ui/JSConsole.cpp
index 89b20918d7..7d4fadab8e 100644
--- a/interface/src/ui/JSConsole.cpp
+++ b/interface/src/ui/JSConsole.cpp
@@ -355,10 +355,9 @@ void JSConsole::executeCommand(const QString& command) {
         ScriptValue result;
         auto scriptManager = weakScriptManager.lock();
         if (scriptManager) {
-            BLOCKING_INVOKE_METHOD(scriptManager.get(), "evaluate",
-                Q_RETURN_ARG(ScriptValue, result),
-                Q_ARG(const QString&, command),
-                Q_ARG(const QString&, consoleFileName));
+            BLOCKING_INVOKE_METHOD(scriptManager.get(), [&scriptManager, &consoleFileName, &command, &result]() -> void {
+                result = scriptManager->evaluate(command, consoleFileName);
+            });
         }
         return result;
     });
diff --git a/libraries/shared/src/shared/QtHelpers.cpp b/libraries/shared/src/shared/QtHelpers.cpp
index ed387a9763..477176ecb9 100644
--- a/libraries/shared/src/shared/QtHelpers.cpp
+++ b/libraries/shared/src/shared/QtHelpers.cpp
@@ -40,6 +40,16 @@ void addBlockingForbiddenThread(const QString& name, QThread* thread) {
     threadHash[thread] = name;
 }
 
+QString isBlockingForbiddenThread(QThread* currentThread) {
+    QReadLocker locker(&threadHashLock);
+    for (const auto& thread : threadHash.keys()) {
+        if (currentThread == thread) {
+            return threadHash[thread];
+        }
+    }
+    return QString();
+}
+
 bool blockingInvokeMethod(
     const char* function,
     QObject *obj, const char *member,
diff --git a/libraries/shared/src/shared/QtHelpers.h b/libraries/shared/src/shared/QtHelpers.h
index 9a9d33a3ce..b87caf0185 100644
--- a/libraries/shared/src/shared/QtHelpers.h
+++ b/libraries/shared/src/shared/QtHelpers.h
@@ -11,14 +11,21 @@
 #define hifi_Shared_QtHelpers_h
 
 #include <QtCore/QObject>
+#include <QtCore/QLoggingCategory>
+
+#include "../Profile.h"
 
 #if defined(Q_OS_WIN)
 // Enable event queue debugging
 #define DEBUG_EVENT_QUEUE
 #endif
 
+class QLoggingCategory;
+const QLoggingCategory& thread_safety();
+
 namespace hifi { namespace qt {
 void addBlockingForbiddenThread(const QString& name, QThread* thread = nullptr);
+QString isBlockingForbiddenThread(QThread* currentThread);
 
 bool blockingInvokeMethod(
     const char* function,
@@ -49,6 +56,43 @@ bool blockingInvokeMethod(
     QGenericArgument val8 = QGenericArgument(),
     QGenericArgument val9 = QGenericArgument());
 
+// handling unregistered functions
+template <typename Func, typename ReturnType>
+typename std::enable_if<!std::is_convertible<Func, const char*>::value, bool>::type
+blockingInvokeMethod(const char* callingFunction, QObject* context, Func function, ReturnType* retVal) {
+    auto currentThread = QThread::currentThread();
+    if (currentThread == qApp->thread()) {
+        qCWarning(thread_safety) << "BlockingQueuedConnection invoked on main thread from " << callingFunction;
+        return QMetaObject::invokeMethod(context, function, Qt::BlockingQueuedConnection, retVal);
+    }
+
+    QString forbiddenThread = isBlockingForbiddenThread(currentThread);
+    if (!forbiddenThread.isEmpty()) {
+        qCWarning(thread_safety) << "BlockingQueuedConnection invoked on forbidden thread " << forbiddenThread;
+    }
+
+    PROFILE_RANGE(app, callingFunction);
+    return QMetaObject::invokeMethod(context, function, Qt::BlockingQueuedConnection, retVal);
+}
+
+template <typename Func>
+typename std::enable_if<!std::is_convertible<Func, const char*>::value, bool>::type
+blockingInvokeMethod(const char* callingFunction, QObject* context, Func function) {
+    auto currentThread = QThread::currentThread();
+    if (currentThread == qApp->thread()) {
+        qCWarning(thread_safety) << "BlockingQueuedConnection invoked on main thread from " << callingFunction;
+        return QMetaObject::invokeMethod(context, function, Qt::BlockingQueuedConnection);
+    }
+
+    QString forbiddenThread = isBlockingForbiddenThread(currentThread);
+    if (!forbiddenThread.isEmpty()) {
+        qCWarning(thread_safety) << "BlockingQueuedConnection invoked on forbidden thread " << forbiddenThread;
+    }
+
+    PROFILE_RANGE(app, callingFunction);
+    return QMetaObject::invokeMethod(context, function, Qt::BlockingQueuedConnection);
+}
+
 // Inspecting of the qt event queue
 // requres access to private Qt datastructures
 // Querying the event queue should be done with