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_mainThreadSuspendResumeControl;
53Q_CONSTINIT QVector<QEventDispatcherWasm *> QEventDispatcherWasm::g_secondaryThreadEventDispatchers;
54Q_CONSTINIT std::mutex QEventDispatcherWasm::g_staticDataMutex;
57QEventDispatcherWasm::QEventDispatcherWasm(std::shared_ptr<QWasmSuspendResumeControl> suspendResumeControl)
70 qCDebug(lcEventDispatcher) <<
"Creating QEventDispatcherWasm instance" <<
this
71 <<
"is main thread" << emscripten_is_main_runtime_thread();
73 if (emscripten_is_main_runtime_thread()) {
77 Q_ASSERT(g_mainThreadEventDispatcher ==
nullptr);
78 g_mainThreadEventDispatcher =
this;
80 if (suspendResumeControl) {
81 g_mainThreadSuspendResumeControl = suspendResumeControl;
83 g_mainThreadSuspendResumeControl = std::make_shared<QWasmSuspendResumeControl>();
87 m_wakeupTimer = std::make_unique<QWasmTimer>(g_mainThreadSuspendResumeControl.get(), [](){ onWakeup(); });
90 m_nativeTimer = std::make_unique<QWasmTimer>(g_mainThreadSuspendResumeControl.get(), []() { onTimer(); });
93 m_suspendTimer = std::make_unique<QWasmTimer>(g_mainThreadSuspendResumeControl.get(), []() { onProcessNativeEventsResume(); });
96 std::lock_guard<std::mutex> lock(g_staticDataMutex);
97 g_secondaryThreadEventDispatchers.append(
this);
101 m_timerInfo = std::make_unique<QTimerInfoList>();
104QEventDispatcherWasm::~QEventDispatcherWasm()
106 qCDebug(lcEventDispatcher) <<
"Destroying QEventDispatcherWasm instance" <<
this;
109 m_wakeupTimer.reset();
110 m_nativeTimer.reset();
111 m_suspendTimer.reset();
114 if (isSecondaryThreadEventDispatcher()) {
115 std::lock_guard<std::mutex> lock(g_staticDataMutex);
116 g_secondaryThreadEventDispatchers.remove(g_secondaryThreadEventDispatchers.indexOf(
this));
120 QWasmSocket::clearSocketNotifiers();
121 g_mainThreadEventDispatcher =
nullptr;
122 g_mainThreadSuspendResumeControl.reset();
126bool QEventDispatcherWasm::isMainThreadEventDispatcher()
128 return this == g_mainThreadEventDispatcher;
131bool QEventDispatcherWasm::isSecondaryThreadEventDispatcher()
133 return this != g_mainThreadEventDispatcher;
136bool QEventDispatcherWasm::isValidEventDispatcher()
138 return isValidEventDispatcherPointer(
this);
141bool QEventDispatcherWasm::isValidEventDispatcherPointer(QEventDispatcherWasm *eventDispatcher)
143 if (eventDispatcher == g_mainThreadEventDispatcher)
146 if (g_secondaryThreadEventDispatchers.contains(eventDispatcher))
152bool QEventDispatcherWasm::processEvents(QEventLoop::ProcessEventsFlags flags)
156 if (useAsyncify() && isMainThreadEventDispatcher()) {
157 auto control = QWasmSuspendResumeControl::get();
158 auto currentEvent = control->currentEvent();
159 if (!currentEvent.isUndefined() &&
160 !currentEvent[
"isInstanceOfEvent"].isUndefined() &&
161 !currentEvent[
"isInstanceOfEvent"].isNull() &&
162 currentEvent[
"isInstanceOfEvent"].as<
bool>()) {
163 currentEvent.call<
void>(
"preventDefault");
164 currentEvent.call<
void>(
"stopPropagation");
165 control->setCurrentEvent(emscripten::val::undefined());
171 if (!useAsyncify() && isMainThreadEventDispatcher())
172 handleNonAsyncifyErrorCases(flags);
174 bool didSendEvents = sendAllEvents(flags);
176 if (!isValidEventDispatcher())
180 m_interrupted =
false;
184 bool shouldWait = flags.testFlag(QEventLoop::WaitForMoreEvents);
185 if (!shouldWait || didSendEvents)
186 return didSendEvents;
190 return sendAllEvents(flags);
193bool QEventDispatcherWasm::sendAllEvents(QEventLoop::ProcessEventsFlags flags)
195 bool didSendEvents =
false;
197 didSendEvents |= sendPostedEvents();
198 if (!isValidEventDispatcher())
201 didSendEvents |= sendNativeEvents(flags);
202 if (!isValidEventDispatcher())
205 didSendEvents |= sendTimerEvents();
206 if (!isValidEventDispatcher())
209 return didSendEvents;
212bool QEventDispatcherWasm::sendNativeEvents(QEventLoop::ProcessEventsFlags flags)
217 if (!isMainThreadEventDispatcher())
225 int sentEventCount = 0;
226 sentEventCount += g_mainThreadSuspendResumeControl->sendPendingEvents();
231 if (flags.testFlag(QEventLoop::EventLoopExec))
232 return sentEventCount > 0;
244 m_wakeFromSuspendTimer =
false;
246 m_suspendTimer->setTimeout(0ms);
247 g_mainThreadSuspendResumeControl->suspend();
248 QScopedValueRollback scoped(m_isSendingNativeEvents,
true);
249 sentEventCount += g_mainThreadSuspendResumeControl->sendPendingEvents();
250 }
while (!m_wakeFromSuspendTimer);
252 return sentEventCount > 1;
255bool QEventDispatcherWasm::sendPostedEvents()
257 QCoreApplication::sendPostedEvents();
265 bool didWakeup = m_wakeup;
270bool QEventDispatcherWasm::sendTimerEvents()
272 int activatedTimers = m_timerInfo->activateTimers();
273 if (activatedTimers > 0)
275 return activatedTimers > 0;
278void QEventDispatcherWasm::registerTimer(Qt::TimerId timerId, Duration interval, Qt::TimerType timerType, QObject *object)
281 if (qToUnderlying(timerId) < 1 || interval < 0ns || !object) {
282 qWarning(
"QEventDispatcherWasm::registerTimer: invalid arguments");
284 }
else if (object->thread() != thread() || thread() != QThread::currentThread()) {
285 qWarning(
"QEventDispatcherWasm::registerTimer: timers cannot be started from another "
290 qCDebug(lcEventDispatcherTimers) <<
"registerTimer" <<
int(timerId) << interval << timerType << object;
292 m_timerInfo->registerTimer(timerId, interval, timerType, object);
296bool QEventDispatcherWasm::unregisterTimer(Qt::TimerId timerId)
299 if (qToUnderlying(timerId) < 1) {
300 qWarning(
"QEventDispatcherWasm::unregisterTimer: invalid argument");
302 }
else if (thread() != QThread::currentThread()) {
303 qWarning(
"QEventDispatcherWasm::unregisterTimer: timers cannot be stopped from another "
309 qCDebug(lcEventDispatcherTimers) <<
"unregisterTimer" <<
int(timerId);
311 bool ans = m_timerInfo->unregisterTimer(timerId);
316bool QEventDispatcherWasm::unregisterTimers(QObject *object)
320 qWarning(
"QEventDispatcherWasm::unregisterTimers: invalid argument");
322 }
else if (object->thread() != thread() || thread() != QThread::currentThread()) {
323 qWarning(
"QEventDispatcherWasm::unregisterTimers: timers cannot be stopped from another "
329 qCDebug(lcEventDispatcherTimers) <<
"registerTimer" << object;
331 bool ans = m_timerInfo->unregisterTimers(object);
336QList<QAbstractEventDispatcher::TimerInfoV2>
337QEventDispatcherWasm::timersForObject(QObject *object)
const
341 qWarning(
"QEventDispatcherWasm:registeredTimers: invalid argument");
346 return m_timerInfo->registeredTimers(object);
349QEventDispatcherWasm::Duration QEventDispatcherWasm::remainingTime(Qt::TimerId timerId)
const
351 return m_timerInfo->remainingDuration(timerId);
354void QEventDispatcherWasm::interrupt()
356 m_interrupted =
true;
360void QEventDispatcherWasm::wakeUp()
364 if (isSecondaryThreadEventDispatcher()) {
365 std::lock_guard<std::mutex> lock(m_mutex);
366 m_wakeUpCalled =
true;
367 m_moreEvents.notify_one();
371 QEventDispatcherWasm *eventDispatcher =
this;
372 qwasmglobal::runOnMainThreadAsync([eventDispatcher]() {
373 if (isValidEventDispatcherPointer(eventDispatcher)) {
374 if (!eventDispatcher->m_wakeupTimer->hasTimeout())
375 eventDispatcher->m_wakeupTimer->setTimeout(0ms);
381void QEventDispatcherWasm::handleNonAsyncifyErrorCases(QEventLoop::ProcessEventsFlags flags)
383 Q_ASSERT(!useAsyncify());
385 if (flags & QEventLoop::ApplicationExec) {
390 const bool simulateInfiniteLoop =
true;
391 emscripten_set_main_loop([](){
392 emscripten_pause_main_loop();
393 }, 0, simulateInfiniteLoop);
394 }
else if (flags & QEventLoop::DialogExec) {
395 qFatal() <<
"Calling exec() is not supported on Qt for WebAssembly in this configuration. Please build"
396 <<
"with asyncify support, or use an asynchronous API like QDialog::open()";
397 }
else if (flags & QEventLoop::WaitForMoreEvents) {
398 qFatal(
"QEventLoop::WaitForMoreEvents is not supported on the main thread without asyncify");
405bool QEventDispatcherWasm::wait(
int timeout)
407 auto tim = timeout > 0 ? std::optional<std::chrono::milliseconds>(timeout) : std::nullopt;
408 if (isSecondaryThreadEventDispatcher())
409 return secondaryThreadWait(tim);
417void QEventDispatcherWasm::processEventsWait()
419 if (isMainThreadEventDispatcher()) {
420 asyncifyWait(std::nullopt);
422 auto nanoWait = m_timerInfo->timerWait();
423 std::optional<std::chrono::milliseconds> milliWait;
424 if (nanoWait.has_value())
425 milliWait = std::chrono::duration_cast<std::chrono::milliseconds>(*nanoWait);
426 secondaryThreadWait(milliWait);
430void QEventDispatcherWasm::asyncifyWait(std::optional<std::chrono::milliseconds> timeout)
432 Q_ASSERT(emscripten_is_main_runtime_thread());
433 Q_ASSERT(isMainThreadEventDispatcher());
434 Q_ASSERT(useAsyncify());
435 if (timeout.has_value())
436 m_suspendTimer->setTimeout(timeout.value());
437 g_mainThreadSuspendResumeControl->suspend();
440bool QEventDispatcherWasm::secondaryThreadWait(std::optional<std::chrono::milliseconds> timeout)
443 Q_ASSERT(QThread::currentThread() == thread());
444 using namespace std::chrono_literals;
445 std::unique_lock<std::mutex> lock(m_mutex);
450 if (m_wakeUpCalled) {
451 m_wakeUpCalled =
false;
455 auto waitTime = timeout.value_or(std::chrono::milliseconds::max());
456 bool wakeUpCalled = m_moreEvents.wait_for(lock, waitTime, [
this] {
return m_wakeUpCalled; });
457 m_wakeUpCalled =
false;
465void QEventDispatcherWasm::onTimer()
467 Q_ASSERT(emscripten_is_main_runtime_thread());
468 if (!g_mainThreadEventDispatcher)
476 g_mainThreadEventDispatcher->sendTimerEvents();
479void QEventDispatcherWasm::onWakeup()
481 Q_ASSERT(emscripten_is_main_runtime_thread());
482 if (!g_mainThreadEventDispatcher)
488 if (g_mainThreadEventDispatcher->m_isSendingNativeEvents)
491 g_mainThreadEventDispatcher->processEvents(QEventLoop::AllEvents);
494void QEventDispatcherWasm::onProcessNativeEventsResume()
496 Q_ASSERT(emscripten_is_main_runtime_thread());
497 if (!g_mainThreadEventDispatcher)
499 g_mainThreadEventDispatcher->m_wakeFromSuspendTimer =
true;
505void QEventDispatcherWasm::updateNativeTimer()
508 Q_ASSERT(QThread::currentThread() == thread());
513 if (!isMainThreadEventDispatcher())
517 const std::optional<std::chrono::nanoseconds> nanoWait = m_timerInfo->timerWait();
518 if (!nanoWait.has_value()) {
519 m_nativeTimer->clearTimeout();
523 auto milliWait = std::chrono::duration_cast<std::chrono::milliseconds>(*nanoWait);
524 const auto newTargetTime = m_timerInfo->currentTime + milliWait;
527 if (m_nativeTimer->hasTimeout() && newTargetTime == m_timerTargetTime)
531 qCDebug(lcEventDispatcherTimers)
532 <<
"Created new native timer timeout" << milliWait.count() <<
"ms"
533 <<
"previous target time" << m_timerTargetTime.time_since_epoch()
534 <<
"new target time" << newTargetTime.time_since_epoch();
535 m_nativeTimer->clearTimeout();
536 m_nativeTimer->setTimeout(milliWait);
537 m_timerTargetTime = newTargetTime;
541 int g_startupTasks = 0;
550void QEventDispatcherWasm::registerStartupTask()
555void QEventDispatcherWasm::completeStarupTask()
558 callOnLoadedIfRequired();
561void QEventDispatcherWasm::callOnLoadedIfRequired()
563 if (g_startupTasks > 0)
566 static bool qtLoadedCalled =
false;
569 qtLoadedCalled =
true;
572void QEventDispatcherWasm::onLoaded()
581void QEventDispatcherWasm::registerSocketNotifier(QSocketNotifier *notifier)
583 QWasmSocket::registerSocketNotifier(notifier);
586void QEventDispatcherWasm::unregisterSocketNotifier(QSocketNotifier *notifier)
588 QWasmSocket::unregisterSocketNotifier(notifier);
591void QEventDispatcherWasm::socketSelect(
int timeout,
int socket,
bool waitForRead,
bool waitForWrite,
592 bool *selectForRead,
bool *selectForWrite,
bool *socketDisconnect)
594 QEventDispatcherWasm *eventDispatcher =
static_cast<QEventDispatcherWasm *>(
595 QAbstractEventDispatcher::instance(QThread::currentThread()));
597 if (!eventDispatcher) {
598 qWarning(
"QEventDispatcherWasm::socketSelect called without eventdispatcher instance");
602 QWasmSocket::waitForSocketState(eventDispatcher, timeout, socket, waitForRead, waitForWrite,
603 selectForRead, selectForWrite, socketDisconnect);
608#include "moc_qeventdispatcher_wasm_p.cpp"
static bool useAsyncify()
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")