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)
33 return qstdweb::haveAsyncify();
37Q_CONSTINIT std::shared_ptr<QWasmSuspendResumeControl> QEventDispatcherWasm::g_customMainThreadSuspendResumeControl;
41Q_CONSTINIT QVector<QEventDispatcherWasm *> QEventDispatcherWasm::g_secondaryThreadEventDispatchers;
42Q_CONSTINIT std::mutex QEventDispatcherWasm::g_staticDataMutex;
45QEventDispatcherWasm::QEventDispatcherWasm(std::shared_ptr<QWasmSuspendResumeControl> suspendResumeControl)
58 qCDebug(lcEventDispatcher) <<
"Creating QEventDispatcherWasm instance" <<
this
59 <<
"is main thread" << emscripten_is_main_runtime_thread();
61 if (emscripten_is_main_runtime_thread()) {
65 Q_ASSERT(g_mainThreadEventDispatcher ==
nullptr);
66 g_mainThreadEventDispatcher =
this;
68 if (suspendResumeControl) {
69 g_customMainThreadSuspendResumeControl = suspendResumeControl;
70 g_mainThreadSuspendResumeControl = g_customMainThreadSuspendResumeControl.get();
72 g_mainThreadSuspendResumeControl = QWasmSuspendResumeControl::get();
76 m_wakeupTimer = std::make_unique<QWasmTimer>(g_mainThreadSuspendResumeControl, [](){ onWakeup(); });
79 m_nativeTimer = std::make_unique<QWasmTimer>(g_mainThreadSuspendResumeControl, []() { onTimer(); });
82 m_suspendTimer = std::make_unique<QWasmTimer>(g_mainThreadSuspendResumeControl, []() { onProcessNativeEventsResume(); });
85 std::lock_guard<std::mutex> lock(g_staticDataMutex);
86 g_secondaryThreadEventDispatchers.append(
this);
90 m_timerInfo = std::make_unique<QTimerInfoList>();
93QEventDispatcherWasm::~QEventDispatcherWasm()
95 qCDebug(lcEventDispatcher) <<
"Destroying QEventDispatcherWasm instance" <<
this;
98 m_wakeupTimer.reset();
99 m_nativeTimer.reset();
100 m_suspendTimer.reset();
103 if (isSecondaryThreadEventDispatcher()) {
104 std::lock_guard<std::mutex> lock(g_staticDataMutex);
105 g_secondaryThreadEventDispatchers.remove(g_secondaryThreadEventDispatchers.indexOf(
this));
109 QWasmSocket::clearSocketNotifiers();
110 g_mainThreadEventDispatcher =
nullptr;
111 g_customMainThreadSuspendResumeControl.reset();
112 g_mainThreadSuspendResumeControl =
nullptr;
116bool QEventDispatcherWasm::isMainThreadEventDispatcher()
118 return this == g_mainThreadEventDispatcher;
121bool QEventDispatcherWasm::isSecondaryThreadEventDispatcher()
123 return this != g_mainThreadEventDispatcher;
126bool QEventDispatcherWasm::isValidEventDispatcher()
128 return isValidEventDispatcherPointer(
this);
131bool QEventDispatcherWasm::isValidEventDispatcherPointer(QEventDispatcherWasm *eventDispatcher)
133 if (eventDispatcher == g_mainThreadEventDispatcher)
136 if (g_secondaryThreadEventDispatchers.contains(eventDispatcher))
142bool QEventDispatcherWasm::processEvents(QEventLoop::ProcessEventsFlags flags)
146 if (useAsyncify() && isMainThreadEventDispatcher()) {
147 auto control = QWasmSuspendResumeControl::get();
148 auto currentEvent = control->currentEvent();
149 if (!currentEvent.isUndefined() &&
150 !currentEvent[
"isInstanceOfEvent"].isUndefined() &&
151 !currentEvent[
"isInstanceOfEvent"].isNull() &&
152 currentEvent[
"isInstanceOfEvent"].as<
bool>()) {
153 currentEvent.call<
void>(
"preventDefault");
154 currentEvent.call<
void>(
"stopPropagation");
155 control->setCurrentEvent(emscripten::val::undefined());
161 if (!useAsyncify() && isMainThreadEventDispatcher())
162 handleNonAsyncifyErrorCases(flags);
164 bool didSendEvents = sendAllEvents(flags);
166 if (!isValidEventDispatcher())
170 m_interrupted =
false;
174 bool shouldWait = flags.testFlag(QEventLoop::WaitForMoreEvents);
175 if (!shouldWait || didSendEvents)
176 return didSendEvents;
180 return sendAllEvents(flags);
183bool QEventDispatcherWasm::sendAllEvents(QEventLoop::ProcessEventsFlags flags)
185 bool didSendEvents =
false;
187 didSendEvents |= sendPostedEvents();
188 if (!isValidEventDispatcher())
191 didSendEvents |= sendNativeEvents(flags);
192 if (!isValidEventDispatcher())
195 didSendEvents |= sendTimerEvents();
196 if (!isValidEventDispatcher())
199 return didSendEvents;
202bool QEventDispatcherWasm::sendNativeEvents(QEventLoop::ProcessEventsFlags flags)
207 if (!isMainThreadEventDispatcher())
215 int sentEventCount = 0;
216 sentEventCount += g_mainThreadSuspendResumeControl->sendPendingEvents();
221 if (flags.testFlag(QEventLoop::EventLoopExec))
222 return sentEventCount > 0;
234 m_wakeFromSuspendTimer =
false;
236 m_suspendTimer->setTimeout(0ms);
237 g_mainThreadSuspendResumeControl->suspend();
238 QScopedValueRollback scoped(m_isSendingNativeEvents,
true);
239 sentEventCount += g_mainThreadSuspendResumeControl->sendPendingEvents();
240 }
while (!m_wakeFromSuspendTimer);
242 return sentEventCount > 1;
245bool QEventDispatcherWasm::sendPostedEvents()
247 QCoreApplication::sendPostedEvents();
255 bool didWakeup = m_wakeup;
260bool QEventDispatcherWasm::sendTimerEvents()
262 int activatedTimers = m_timerInfo->activateTimers();
263 if (activatedTimers > 0)
265 return activatedTimers > 0;
268void QEventDispatcherWasm::registerTimer(Qt::TimerId timerId, Duration interval, Qt::TimerType timerType, QObject *object)
271 if (qToUnderlying(timerId) < 1 || interval < 0ns || !object) {
272 qWarning(
"QEventDispatcherWasm::registerTimer: invalid arguments");
274 }
else if (object->thread() != thread() || thread() != QThread::currentThread()) {
275 qWarning(
"QEventDispatcherWasm::registerTimer: timers cannot be started from another "
280 qCDebug(lcEventDispatcherTimers) <<
"registerTimer" <<
int(timerId) << interval << timerType << object;
282 m_timerInfo->registerTimer(timerId, interval, timerType, object);
286bool QEventDispatcherWasm::unregisterTimer(Qt::TimerId timerId)
289 if (qToUnderlying(timerId) < 1) {
290 qWarning(
"QEventDispatcherWasm::unregisterTimer: invalid argument");
292 }
else if (thread() != QThread::currentThread()) {
293 qWarning(
"QEventDispatcherWasm::unregisterTimer: timers cannot be stopped from another "
299 qCDebug(lcEventDispatcherTimers) <<
"unregisterTimer" <<
int(timerId);
301 bool ans = m_timerInfo->unregisterTimer(timerId);
306bool QEventDispatcherWasm::unregisterTimers(QObject *object)
310 qWarning(
"QEventDispatcherWasm::unregisterTimers: invalid argument");
312 }
else if (object->thread() != thread() || thread() != QThread::currentThread()) {
313 qWarning(
"QEventDispatcherWasm::unregisterTimers: timers cannot be stopped from another "
319 qCDebug(lcEventDispatcherTimers) <<
"registerTimer" << object;
321 bool ans = m_timerInfo->unregisterTimers(object);
326QList<QAbstractEventDispatcher::TimerInfoV2>
327QEventDispatcherWasm::timersForObject(QObject *object)
const
331 qWarning(
"QEventDispatcherWasm:registeredTimers: invalid argument");
336 return m_timerInfo->registeredTimers(object);
339QEventDispatcherWasm::Duration QEventDispatcherWasm::remainingTime(Qt::TimerId timerId)
const
341 return m_timerInfo->remainingDuration(timerId);
344void QEventDispatcherWasm::interrupt()
346 m_interrupted =
true;
350void QEventDispatcherWasm::wakeUp()
354 if (isSecondaryThreadEventDispatcher()) {
355 std::lock_guard<std::mutex> lock(m_mutex);
356 m_wakeUpCalled =
true;
357 m_moreEvents.notify_one();
361 QEventDispatcherWasm *eventDispatcher =
this;
362 qwasmglobal::runOnMainThreadAsync([eventDispatcher]() {
363 if (isValidEventDispatcherPointer(eventDispatcher)) {
364 if (!eventDispatcher->m_wakeupTimer->hasTimeout())
365 eventDispatcher->m_wakeupTimer->setTimeout(0ms);
371void QEventDispatcherWasm::handleNonAsyncifyErrorCases(QEventLoop::ProcessEventsFlags flags)
373 Q_ASSERT(!useAsyncify());
375 if (flags & QEventLoop::ApplicationExec) {
380 const bool simulateInfiniteLoop =
true;
381 emscripten_set_main_loop([](){
382 emscripten_pause_main_loop();
383 }, 0, simulateInfiniteLoop);
384 }
else if (flags & QEventLoop::DialogExec) {
385 qFatal() <<
"Calling exec() is not supported on Qt for WebAssembly in this configuration. Please build"
386 <<
"with asyncify support, or use an asynchronous API like QDialog::open()";
387 }
else if (flags & QEventLoop::WaitForMoreEvents) {
388 qFatal(
"QEventLoop::WaitForMoreEvents is not supported on the main thread without asyncify");
395bool QEventDispatcherWasm::wait(
int timeout)
397 auto tim = timeout > 0 ? std::optional<std::chrono::milliseconds>(timeout) : std::nullopt;
398 if (isSecondaryThreadEventDispatcher())
399 return secondaryThreadWait(tim);
407void QEventDispatcherWasm::processEventsWait()
409 if (isMainThreadEventDispatcher()) {
410 asyncifyWait(std::nullopt);
412 auto nanoWait = m_timerInfo->timerWait();
413 std::optional<std::chrono::milliseconds> milliWait;
414 if (nanoWait.has_value())
415 milliWait = std::chrono::duration_cast<std::chrono::milliseconds>(*nanoWait);
416 secondaryThreadWait(milliWait);
420void QEventDispatcherWasm::asyncifyWait(std::optional<std::chrono::milliseconds> timeout)
422 Q_ASSERT(emscripten_is_main_runtime_thread());
423 Q_ASSERT(isMainThreadEventDispatcher());
424 Q_ASSERT(useAsyncify());
425 if (timeout.has_value())
426 m_suspendTimer->setTimeout(timeout.value());
427 g_mainThreadSuspendResumeControl->suspend();
430bool QEventDispatcherWasm::secondaryThreadWait(std::optional<std::chrono::milliseconds> timeout)
433 Q_ASSERT(QThread::currentThread() == thread());
434 using namespace std::chrono_literals;
435 std::unique_lock<std::mutex> lock(m_mutex);
440 if (m_wakeUpCalled) {
441 m_wakeUpCalled =
false;
445 auto waitTime = timeout.value_or(std::chrono::milliseconds::max());
446 bool wakeUpCalled = m_moreEvents.wait_for(lock, waitTime, [
this] {
return m_wakeUpCalled; });
447 m_wakeUpCalled =
false;
455void QEventDispatcherWasm::onTimer()
457 Q_ASSERT(emscripten_is_main_runtime_thread());
458 if (!g_mainThreadEventDispatcher)
466 g_mainThreadEventDispatcher->sendTimerEvents();
469void QEventDispatcherWasm::onWakeup()
471 Q_ASSERT(emscripten_is_main_runtime_thread());
472 if (!g_mainThreadEventDispatcher)
478 if (g_mainThreadEventDispatcher->m_isSendingNativeEvents)
481 g_mainThreadEventDispatcher->processEvents(QEventLoop::AllEvents);
484void QEventDispatcherWasm::onProcessNativeEventsResume()
486 Q_ASSERT(emscripten_is_main_runtime_thread());
487 if (!g_mainThreadEventDispatcher)
489 g_mainThreadEventDispatcher->m_wakeFromSuspendTimer =
true;
495void QEventDispatcherWasm::updateNativeTimer()
498 Q_ASSERT(QThread::currentThread() == thread());
503 if (!isMainThreadEventDispatcher())
507 const std::optional<std::chrono::nanoseconds> nanoWait = m_timerInfo->timerWait();
508 if (!nanoWait.has_value()) {
509 m_nativeTimer->clearTimeout();
513 auto milliWait = std::chrono::duration_cast<std::chrono::milliseconds>(*nanoWait);
514 const auto newTargetTime = m_timerInfo->currentTime + milliWait;
517 if (m_nativeTimer->hasTimeout() && newTargetTime == m_timerTargetTime)
521 qCDebug(lcEventDispatcherTimers)
522 <<
"Created new native timer timeout" << milliWait.count() <<
"ms"
523 <<
"previous target time" << m_timerTargetTime.time_since_epoch()
524 <<
"new target time" << newTargetTime.time_since_epoch();
525 m_nativeTimer->clearTimeout();
526 m_nativeTimer->setTimeout(milliWait);
527 m_timerTargetTime = newTargetTime;
531 int g_startupTasks = 0;
534void QEventDispatcherWasm::registerSocketNotifier(QSocketNotifier *notifier)
536 QWasmSocket::registerSocketNotifier(notifier);
539void QEventDispatcherWasm::unregisterSocketNotifier(QSocketNotifier *notifier)
541 QWasmSocket::unregisterSocketNotifier(notifier);
544void QEventDispatcherWasm::socketSelect(
int timeout,
int socket,
bool waitForRead,
bool waitForWrite,
545 bool *selectForRead,
bool *selectForWrite,
bool *socketDisconnect)
547 QEventDispatcherWasm *eventDispatcher =
static_cast<QEventDispatcherWasm *>(
548 QAbstractEventDispatcher::instance(QThread::currentThread()));
550 if (!eventDispatcher) {
551 qWarning(
"QEventDispatcherWasm::socketSelect called without eventdispatcher instance");
555 QWasmSocket::waitForSocketState(eventDispatcher, timeout, socket, waitForRead, waitForWrite,
556 selectForRead, selectForWrite, socketDisconnect);
561#include "moc_qeventdispatcher_wasm_p.cpp"
static bool useAsyncify()
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")