Revision control
Copy as Markdown
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qcore_ohos_p.h"
#include <QtCore/qmetaobject.h>
#include <QtCore/private/qohoslogger_p.h>
#include <deque>
#include <exception>
#include <mutex>
#include <stdexcept>
#include <napi.h>
#include <tuple>
QT_BEGIN_NAMESPACE
QOhosJsState::~QOhosJsState() = default;
QOhosJsState::QOhosJsState() = default;
QOhosJsThreadOps::~QOhosJsThreadOps() = default;
QOhosJsThreadOps::QOhosJsThreadOps() = default;
static QOhosJsThreadOps *qOhosJsThreadOpsInstance = nullptr;
void QOhosJsThreadOps::registerInstance(QOhosJsThreadOps *ops)
{
qOhosJsThreadOpsInstance = ops;
}
QOhosJsThreadOps &QOhosJsThreadOps::instance()
{
Q_ASSERT(qOhosJsThreadOpsInstance != nullptr);
return *qOhosJsThreadOpsInstance;
}
void QOhosJsThreadGateway::invoke(std::function<void(QOhosJsState &)> &&task)
{
QOhosJsThreadOps::instance().invoke(std::move(task));
}
void QOhosJsThreadGateway::invokeAndWaitForContinue(
std::function<void(QOhosJsState &, QOhosTaskPromise<>)> &&task,
std::string callerContextName)
{
QOhosJsThreadOps::instance().invokeAndWaitForContinue(
std::move(task), std::move(callerContextName));
}
void QOhosJsThreadGateway::runAndWait(
const std::function<void(QOhosJsState &)> &task,
std::string callerContextName)
{
QOhosJsThreadOps::instance().runAndWait(task, std::move(callerContextName));
}
namespace QtOhos {
namespace {
template<typename Task>
class PreQueuingTasksExecutor
{
public:
void setUnderlyingExecutor(
std::function<void(Task)> executor,
std::function<void(Task)> optSyncFlushExecutor = nullptr);
void invokeTask(Task task);
private:
std::recursive_mutex m_executorMutex;
std::function<void(Task)> m_optUnderlyingExecutor;
std::deque<Task> m_pendingTasks;
};
template<typename Task>
void PreQueuingTasksExecutor<Task>::setUnderlyingExecutor(
std::function<void(Task)> executor, std::function<void(Task)> optSyncFlushExecutor)
{
std::lock_guard<std::recursive_mutex> executorLock(m_executorMutex);
auto &flushExecutor = optSyncFlushExecutor ? optSyncFlushExecutor : executor;
while (!m_pendingTasks.empty()) {
auto task = std::move(m_pendingTasks.front());
m_pendingTasks.pop_front();
flushExecutor(std::move(task));
}
m_optUnderlyingExecutor = std::move(executor);
}
template<typename Task>
void PreQueuingTasksExecutor<Task>::invokeTask(Task task)
{
std::lock_guard<std::recursive_mutex> executorLock(m_executorMutex);
if (m_optUnderlyingExecutor)
m_optUnderlyingExecutor(std::move(task));
else
m_pendingTasks.push_back(std::move(task));
}
QOhosConsumer<std::function<void()>> makeQtThreadTasksExecutor()
{
auto qobj = std::make_shared<QObject>();
return [qobj](std::function<void()> task) {
auto sharedTask = moveToSharedPtr(std::move(task));
QMetaObject::invokeMethod(
qobj.get(),
[sharedTask]() {
(*sharedTask)();
},
Qt::QueuedConnection);
};
}
class QtStateImpl : public QtState
{
public:
QtStateImpl() = default;
void initInQtThread();
bool isQtThread() const override;
void invokeTask(std::function<void()> &&task) override;
private:
PreQueuingTasksExecutor<std::function<void()>> m_tasksExecutor;
QOhosMutexProtectedValue<pthread_t> m_qtThread;
};
void QtStateImpl::initInQtThread()
{
m_qtThread.processValue(
[](auto &qtThread) {
qtThread = pthread_self();
});
m_tasksExecutor.setUnderlyingExecutor(
makeQtThreadTasksExecutor(),
[](auto task) {
task();
});
}
bool QtStateImpl::isQtThread() const
{
return m_qtThread.evalWithValue(
[](const auto &qtThread) {
return qtThread == pthread_self();
});
}
void QtStateImpl::invokeTask(std::function<void()> &&task)
{
m_tasksExecutor.invokeTask(std::move(task));
}
QtStateImpl &getQtStateImpl()
{
static QtStateImpl qtStateImpl;
return qtStateImpl;
}
}
Q_GLOBAL_STATIC(QObjectThreadSafeRef::ObjectRefsMap, refsMap)
QObjectThreadSafeRef::QObjectThreadSafeRef()
: QObjectThreadSafeRef(nullptr)
{
}
QObjectThreadSafeRef::QObjectThreadSafeRef(QPointer<QObject> obj)
: m_creatorThread(::pthread_self())
{
using namespace std::string_literals;
if (obj.isNull())
return;
QMutexLocker refsMapLock{&refsMapMutex};
auto refIter = refsMap->find(obj);
if (refIter == refsMap->end()) {
auto refName =
std::to_string(reinterpret_cast<std::size_t>(obj.data()))
+ "/"s + std::to_string(refsMapInsertCounter);
++refsMapInsertCounter;
QObjectRef ref = {
.obj = obj,
.refName = refName,
};
std::tie(refIter, std::ignore) = refsMap->emplace(obj, std::make_shared<QObjectRef>(ref));
QObject::connect(
obj, &QObject::destroyed, obj,
[](QObject *obj) {
if (refsMap.isDestroyed())
return;
QMutexLocker refsMapLock{&refsMapMutex};
refsMap->erase(obj);
});
}
m_refName = refIter->second->refName;
m_weakObjRef = refIter->second;
}
QObjectThreadSafeRef::QObjectThreadSafeRef(const QObjectThreadSafeRef &other) = default;
QObjectThreadSafeRef &QObjectThreadSafeRef::operator=(const QObjectThreadSafeRef &other) = default;
bool QObjectThreadSafeRef::operator==(const QObjectThreadSafeRef &other) const
{
return m_refName == other.m_refName;
}
bool QObjectThreadSafeRef::operator!=(const QObjectThreadSafeRef &other) const
{
return !(*this == other);
}
std::string QObjectThreadSafeRef::refName() const
{
return m_refName;
}
QPointer<QObject> QObjectThreadSafeRef::data() const
{
if (m_refName.empty())
return nullptr;
if (::pthread_equal(::pthread_self(), m_creatorThread) == 0) {
qOhosPrintfError("QObjectThreadSafeRef: accessing pointer from wrong thread");
return nullptr;
}
auto objRef = m_weakObjRef.lock();
return objRef ? objRef->obj : nullptr;
}
void QObjectThreadSafeRef::visitInQtThreadIfAlive(std::function<void(QObject &)> &&visitFunc) const
{
invokeInQtThread(
[objRef = *this, sharedVisitFunc = takeFunctionToSharedPtr(visitFunc)]() {
const auto takenVisitFunc = std::exchange(*sharedVisitFunc, nullptr);
QPointer<QObject> obj = objRef.data();
if (!obj.isNull())
takenVisitFunc(*obj);
});
}
QtState::QtState() = default;
QtState::~QtState() = default;
void initQtThreadState()
{
getQtStateImpl().initInQtThread();
}
QtState &getQtState()
{
return getQtStateImpl();
}
void invokeInQtThread(std::function<void()> &&task)
{
getQtStateImpl().invokeTask(
[sharedTask = takeFunctionToSharedPtr(task)]() {
const auto takenTask = std::exchange(*sharedTask, nullptr);
takenTask();
});
}
void logJsCallbackError(const QOhosCallbackInfo &cbInfo, const char *errorMessagePrefix)
{
static const std::pair<const char *, bool (QNapi::Value::*)() const> errorPropsDefs[] = {
{"name", &QNapi::Value::IsString},
{"message", &QNapi::Value::IsString},
{"code", &QNapi::Value::IsNumber},
};
Napi::HandleScope funcScope(cbInfo.Env());
auto optCbArg = cbInfo.Length() != 0
? cbInfo.getFirstArg<QNapi::Value>(Q_FUNC_INFO)
: cbInfo.Env().Undefined();
auto error = optCbArg.IsObject()
? QNapi::checkedCast<QNapi::Object>(optCbArg)
: QNapi::Object::New(cbInfo.Env());
std::string errorDetailsStr;
for (const auto &propDef : errorPropsDefs) {
const auto *propName = propDef.first;
const auto &typeCheckMemFun = propDef.second;
auto optProp = QNapi::getPropOrUndefined(error, propName);
if ((optProp.*typeCheckMemFun)()) {
std::string propStr = optProp.ToString();
if (!errorDetailsStr.empty())
errorDetailsStr += ", ";
errorDetailsStr += propName;
errorDetailsStr += "='";
errorDetailsStr += propStr;
errorDetailsStr += "'";
}
}
std::string errorStr = errorMessagePrefix;
if (!errorDetailsStr.empty()) {
errorStr += ": ";
errorStr += errorDetailsStr;
}
qOhosPrintfError("%s", errorStr.c_str());
}
std::function<void(const QOhosCallbackInfo &)> makeErrorLoggingJsCallback(std::string callContext)
{
using namespace std::string_literals;
return [callContext = std::move(callContext)](const QOhosCallbackInfo &cbInfo) {
auto errorMessagePrefix = "Got error from '"s + callContext + "'"s;
QtOhos::logJsCallbackError(cbInfo, errorMessagePrefix.c_str());
};
}
}
QOhosJsState &QOhosCallbackInfo::jsState() const
{
return QOhosJsThreadOps::instance().jsState();
}
QT_END_NAMESPACE