7#include <QtCore/qcoreapplication.h>
8#include <QtCore/qthread.h>
9#include <QtCore/qscopedvaluerollback.h>
10#include <QtCore/private/qobject_p.h>
11#include <QtCore/private/qwasmglobal_p.h>
12#include <QtCore/private/qstdweb_p.h>
13#include <QtCore/private/qwasmsocket_p.h>
15using namespace std::chrono;
16using namespace std::chrono_literals;
26#define LOCK_GUARD(M) std::lock_guard<std::mutex> lock(M)
33static bool useAsyncify()
35 return qstdweb::haveAsyncify();
50Q_CONSTINIT std::shared_ptr<QWasmSuspendResumeControl> QEventDispatcherWasm::g_customMainThreadSuspendResumeControl;
54Q_CONSTINIT QVector<QEventDispatcherWasm *> QEventDispatcherWasm::g_secondaryThreadEventDispatchers;
55Q_CONSTINIT std::mutex QEventDispatcherWasm::g_staticDataMutex;
58QEventDispatcherWasm::QEventDispatcherWasm(std::shared_ptr<QWasmSuspendResumeControl> suspendResumeControl)
71 qCDebug(lcEventDispatcher) <<
"Creating QEventDispatcherWasm instance" <<
this
72 <<
"is main thread" << emscripten_is_main_runtime_thread();
74 if (emscripten_is_main_runtime_thread()) {
78 Q_ASSERT(g_mainThreadEventDispatcher ==
nullptr);
79 g_mainThreadEventDispatcher =
this;
81 if (suspendResumeControl) {
82 g_customMainThreadSuspendResumeControl = suspendResumeControl;
83 g_mainThreadSuspendResumeControl = g_customMainThreadSuspendResumeControl.get();
85 g_mainThreadSuspendResumeControl = QWasmSuspendResumeControl::get();
89 m_wakeupTimer = std::make_unique<QWasmTimer>(g_mainThreadSuspendResumeControl, [](){ onWakeup(); });
92 m_nativeTimer = std::make_unique<QWasmTimer>(g_mainThreadSuspendResumeControl, []() { onTimer(); });
95 m_suspendTimer = std::make_unique<QWasmTimer>(g_mainThreadSuspendResumeControl, []() { onProcessNativeEventsResume(); });
98 std::lock_guard<std::mutex> lock(g_staticDataMutex);
99 g_secondaryThreadEventDispatchers.append(
this);
103 m_timerInfo = std::make_unique<QTimerInfoList>();
106QEventDispatcherWasm::~QEventDispatcherWasm()
108 qCDebug(lcEventDispatcher) <<
"Destroying QEventDispatcherWasm instance" <<
this;
111 m_wakeupTimer.reset();
112 m_nativeTimer.reset();
113 m_suspendTimer.reset();
116 if (isSecondaryThreadEventDispatcher()) {
117 std::lock_guard<std::mutex> lock(g_staticDataMutex);
118 g_secondaryThreadEventDispatchers.remove(g_secondaryThreadEventDispatchers.indexOf(
this));
122 QWasmSocket::clearSocketNotifiers();
123 g_mainThreadEventDispatcher =
nullptr;
124 g_customMainThreadSuspendResumeControl.reset();
125 g_mainThreadSuspendResumeControl =
nullptr;
129bool QEventDispatcherWasm::isMainThreadEventDispatcher()
131 return this == g_mainThreadEventDispatcher;
134bool QEventDispatcherWasm::isSecondaryThreadEventDispatcher()
136 return this != g_mainThreadEventDispatcher;
139bool QEventDispatcherWasm::isValidEventDispatcher()
141 return isValidEventDispatcherPointer(
this);
144bool QEventDispatcherWasm::isValidEventDispatcherPointer(QEventDispatcherWasm *eventDispatcher)
146 if (eventDispatcher == g_mainThreadEventDispatcher)
149 if (g_secondaryThreadEventDispatchers.contains(eventDispatcher))
155bool QEventDispatcherWasm::processEvents(QEventLoop::ProcessEventsFlags flags)
159 if (useAsyncify() && isMainThreadEventDispatcher()) {
160 auto control = QWasmSuspendResumeControl::get();
161 auto currentEvent = control->currentEvent();
162 if (!currentEvent.isUndefined() &&
163 !currentEvent[
"isInstanceOfEvent"].isUndefined() &&
164 !currentEvent[
"isInstanceOfEvent"].isNull() &&
165 currentEvent[
"isInstanceOfEvent"].as<
bool>()) {
166 currentEvent.call<
void>(
"preventDefault");
167 currentEvent.call<
void>(
"stopPropagation");
168 control->setCurrentEvent(emscripten::val::undefined());
174 if (!useAsyncify() && isMainThreadEventDispatcher())
175 handleNonAsyncifyErrorCases(flags);
177 bool didSendEvents = sendAllEvents(flags);
179 if (!isValidEventDispatcher())
183 m_interrupted =
false;
187 bool shouldWait = flags.testFlag(QEventLoop::WaitForMoreEvents);
188 if (!shouldWait || didSendEvents)
189 return didSendEvents;
193 return sendAllEvents(flags);
196bool QEventDispatcherWasm::sendAllEvents(QEventLoop::ProcessEventsFlags flags)
198 bool didSendEvents =
false;
200 didSendEvents |= sendPostedEvents();
201 if (!isValidEventDispatcher())
204 didSendEvents |= sendNativeEvents(flags);
205 if (!isValidEventDispatcher())
208 didSendEvents |= sendTimerEvents();
209 if (!isValidEventDispatcher())
212 return didSendEvents;
215bool QEventDispatcherWasm::sendNativeEvents(QEventLoop::ProcessEventsFlags flags)
220 if (!isMainThreadEventDispatcher())
228 int sentEventCount = 0;
229 sentEventCount += g_mainThreadSuspendResumeControl->sendPendingEvents();
234 if (flags.testFlag(QEventLoop::EventLoopExec))
235 return sentEventCount > 0;
247 m_wakeFromSuspendTimer =
false;
249 m_suspendTimer->setTimeout(0ms);
250 g_mainThreadSuspendResumeControl->suspend();
251 QScopedValueRollback scoped(m_isSendingNativeEvents,
true);
252 sentEventCount += g_mainThreadSuspendResumeControl->sendPendingEvents();
253 }
while (!m_wakeFromSuspendTimer);
255 return sentEventCount > 1;
258bool QEventDispatcherWasm::sendPostedEvents()
260 QCoreApplication::sendPostedEvents();
268 bool didWakeup = m_wakeup;
273bool QEventDispatcherWasm::sendTimerEvents()
275 int activatedTimers = m_timerInfo->activateTimers();
276 if (activatedTimers > 0)
278 return activatedTimers > 0;
281void QEventDispatcherWasm::registerTimer(Qt::TimerId timerId, Duration interval, Qt::TimerType timerType, QObject *object)
284 if (qToUnderlying(timerId) < 1 || interval < 0ns || !object) {
285 qWarning(
"QEventDispatcherWasm::registerTimer: invalid arguments");
287 }
else if (object->thread() != thread() || thread() != QThread::currentThread()) {
288 qWarning(
"QEventDispatcherWasm::registerTimer: timers cannot be started from another "
293 qCDebug(lcEventDispatcherTimers) <<
"registerTimer" <<
int(timerId) << interval << timerType << object;
295 m_timerInfo->registerTimer(timerId, interval, timerType, object);
299bool QEventDispatcherWasm::unregisterTimer(Qt::TimerId timerId)
302 if (qToUnderlying(timerId) < 1) {
303 qWarning(
"QEventDispatcherWasm::unregisterTimer: invalid argument");
305 }
else if (thread() != QThread::currentThread()) {
306 qWarning(
"QEventDispatcherWasm::unregisterTimer: timers cannot be stopped from another "
312 qCDebug(lcEventDispatcherTimers) <<
"unregisterTimer" <<
int(timerId);
314 bool ans = m_timerInfo->unregisterTimer(timerId);
319bool QEventDispatcherWasm::unregisterTimers(QObject *object)
323 qWarning(
"QEventDispatcherWasm::unregisterTimers: invalid argument");
325 }
else if (object->thread() != thread() || thread() != QThread::currentThread()) {
326 qWarning(
"QEventDispatcherWasm::unregisterTimers: timers cannot be stopped from another "
332 qCDebug(lcEventDispatcherTimers) <<
"registerTimer" << object;
334 bool ans = m_timerInfo->unregisterTimers(object);
339QList<QAbstractEventDispatcher::TimerInfoV2>
340QEventDispatcherWasm::timersForObject(QObject *object)
const
344 qWarning(
"QEventDispatcherWasm:registeredTimers: invalid argument");
349 return m_timerInfo->registeredTimers(object);
352QEventDispatcherWasm::Duration QEventDispatcherWasm::remainingTime(Qt::TimerId timerId)
const
354 return m_timerInfo->remainingDuration(timerId);
357void QEventDispatcherWasm::interrupt()
359 m_interrupted =
true;
363void QEventDispatcherWasm::wakeUp()
367 if (isSecondaryThreadEventDispatcher()) {
368 std::lock_guard<std::mutex> lock(m_mutex);
369 m_wakeUpCalled =
true;
370 m_moreEvents.notify_one();
374 QEventDispatcherWasm *eventDispatcher =
this;
375 qwasmglobal::runOnMainThreadAsync([eventDispatcher]() {
376 if (isValidEventDispatcherPointer(eventDispatcher)) {
377 if (!eventDispatcher->m_wakeupTimer->hasTimeout())
378 eventDispatcher->m_wakeupTimer->setTimeout(0ms);
384void QEventDispatcherWasm::handleNonAsyncifyErrorCases(QEventLoop::ProcessEventsFlags flags)
386 Q_ASSERT(!useAsyncify());
388 if (flags & QEventLoop::ApplicationExec) {
393 const bool simulateInfiniteLoop =
true;
394 emscripten_set_main_loop([](){
395 emscripten_pause_main_loop();
396 }, 0, simulateInfiniteLoop);
397 }
else if (flags & QEventLoop::DialogExec) {
398 qFatal() <<
"Calling exec() is not supported on Qt for WebAssembly in this configuration. Please build"
399 <<
"with asyncify support, or use an asynchronous API like QDialog::open()";
400 }
else if (flags & QEventLoop::WaitForMoreEvents) {
401 qFatal(
"QEventLoop::WaitForMoreEvents is not supported on the main thread without asyncify");
408bool QEventDispatcherWasm::wait(
int timeout)
410 auto tim = timeout > 0 ? std::optional<std::chrono::milliseconds>(timeout) : std::nullopt;
411 if (isSecondaryThreadEventDispatcher())
412 return secondaryThreadWait(tim);
420void QEventDispatcherWasm::processEventsWait()
422 if (isMainThreadEventDispatcher()) {
423 asyncifyWait(std::nullopt);
425 auto nanoWait = m_timerInfo->timerWait();
426 std::optional<std::chrono::milliseconds> milliWait;
427 if (nanoWait.has_value())
428 milliWait = std::chrono::duration_cast<std::chrono::milliseconds>(*nanoWait);
429 secondaryThreadWait(milliWait);
433void QEventDispatcherWasm::asyncifyWait(std::optional<std::chrono::milliseconds> timeout)
435 Q_ASSERT(emscripten_is_main_runtime_thread());
436 Q_ASSERT(isMainThreadEventDispatcher());
437 Q_ASSERT(useAsyncify());
438 if (timeout.has_value())
439 m_suspendTimer->setTimeout(timeout.value());
440 g_mainThreadSuspendResumeControl->suspend();
443bool QEventDispatcherWasm::secondaryThreadWait(std::optional<std::chrono::milliseconds> timeout)
446 Q_ASSERT(QThread::currentThread() == thread());
447 using namespace std::chrono_literals;
448 std::unique_lock<std::mutex> lock(m_mutex);
453 if (m_wakeUpCalled) {
454 m_wakeUpCalled =
false;
458 auto waitTime = timeout.value_or(std::chrono::milliseconds::max());
459 bool wakeUpCalled = m_moreEvents.wait_for(lock, waitTime, [
this] {
return m_wakeUpCalled; });
460 m_wakeUpCalled =
false;
468void QEventDispatcherWasm::onTimer()
470 Q_ASSERT(emscripten_is_main_runtime_thread());
471 if (!g_mainThreadEventDispatcher)
479 g_mainThreadEventDispatcher->sendTimerEvents();
482void QEventDispatcherWasm::onWakeup()
484 Q_ASSERT(emscripten_is_main_runtime_thread());
485 if (!g_mainThreadEventDispatcher)
491 if (g_mainThreadEventDispatcher->m_isSendingNativeEvents)
494 g_mainThreadEventDispatcher->processEvents(QEventLoop::AllEvents);
497void QEventDispatcherWasm::onProcessNativeEventsResume()
499 Q_ASSERT(emscripten_is_main_runtime_thread());
500 if (!g_mainThreadEventDispatcher)
502 g_mainThreadEventDispatcher->m_wakeFromSuspendTimer =
true;
508void QEventDispatcherWasm::updateNativeTimer()
511 Q_ASSERT(QThread::currentThread() == thread());
516 if (!isMainThreadEventDispatcher())
520 const std::optional<std::chrono::nanoseconds> nanoWait = m_timerInfo->timerWait();
521 if (!nanoWait.has_value()) {
522 m_nativeTimer->clearTimeout();
526 auto milliWait = std::chrono::duration_cast<std::chrono::milliseconds>(*nanoWait);
527 const auto newTargetTime = m_timerInfo->currentTime + milliWait;
530 if (m_nativeTimer->hasTimeout() && newTargetTime == m_timerTargetTime)
534 qCDebug(lcEventDispatcherTimers)
535 <<
"Created new native timer timeout" << milliWait.count() <<
"ms"
536 <<
"previous target time" << m_timerTargetTime.time_since_epoch()
537 <<
"new target time" << newTargetTime.time_since_epoch();
538 m_nativeTimer->clearTimeout();
539 m_nativeTimer->setTimeout(milliWait);
540 m_timerTargetTime = newTargetTime;
544 int g_startupTasks = 0;
553void QEventDispatcherWasm::registerStartupTask()
558void QEventDispatcherWasm::completeStarupTask()
561 callOnLoadedIfRequired();
564void QEventDispatcherWasm::callOnLoadedIfRequired()
566 if (g_startupTasks > 0)
569 static bool qtLoadedCalled =
false;
572 qtLoadedCalled =
true;
575void QEventDispatcherWasm::onLoaded()
584void QEventDispatcherWasm::registerSocketNotifier(QSocketNotifier *notifier)
586 QWasmSocket::registerSocketNotifier(notifier);
589void QEventDispatcherWasm::unregisterSocketNotifier(QSocketNotifier *notifier)
591 QWasmSocket::unregisterSocketNotifier(notifier);
594void QEventDispatcherWasm::socketSelect(
int timeout,
int socket,
bool waitForRead,
bool waitForWrite,
595 bool *selectForRead,
bool *selectForWrite,
bool *socketDisconnect)
597 QEventDispatcherWasm *eventDispatcher =
static_cast<QEventDispatcherWasm *>(
598 QAbstractEventDispatcher::instance(QThread::currentThread()));
600 if (!eventDispatcher) {
601 qWarning(
"QEventDispatcherWasm::socketSelect called without eventdispatcher instance");
605 QWasmSocket::waitForSocketState(eventDispatcher, timeout, socket, waitForRead, waitForWrite,
606 selectForRead, selectForWrite, socketDisconnect);
611#include "moc_qeventdispatcher_wasm_p.cpp"
static bool useAsyncify()
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")