6#include <QtCore/qcoreapplication.h>
7#include <QtCore/qthread.h>
8#include <QtCore/qscopedvaluerollback.h>
9#include <QtCore/private/qobject_p.h>
10#include <QtCore/private/qwasmglobal_p.h>
11#include <QtCore/private/qstdweb_p.h>
12#include <QtCore/private/qwasmsocket_p.h>
14using namespace std::chrono;
15using namespace std::chrono_literals;
25#define LOCK_GUARD(M) std::lock_guard<std::mutex> lock(M)
32static bool useAsyncify()
34 return qstdweb::haveAsyncify();
49Q_CONSTINIT std::shared_ptr<QWasmSuspendResumeControl> QEventDispatcherWasm::g_mainThreadSuspendResumeControl;
52Q_CONSTINIT QVector<QEventDispatcherWasm *> QEventDispatcherWasm::g_secondaryThreadEventDispatchers;
53Q_CONSTINIT std::mutex QEventDispatcherWasm::g_staticDataMutex;
56QEventDispatcherWasm::QEventDispatcherWasm(std::shared_ptr<QWasmSuspendResumeControl> suspendResumeControl)
69 qCDebug(lcEventDispatcher) <<
"Creating QEventDispatcherWasm instance" <<
this
70 <<
"is main thread" << emscripten_is_main_runtime_thread();
72 if (emscripten_is_main_runtime_thread()) {
76 Q_ASSERT(g_mainThreadEventDispatcher ==
nullptr);
77 g_mainThreadEventDispatcher =
this;
79 if (suspendResumeControl) {
80 g_mainThreadSuspendResumeControl = suspendResumeControl;
82 g_mainThreadSuspendResumeControl = std::make_shared<QWasmSuspendResumeControl>();
86 m_wakeupTimer = std::make_unique<QWasmTimer>(g_mainThreadSuspendResumeControl.get(), [](){ onWakeup(); });
89 m_nativeTimer = std::make_unique<QWasmTimer>(g_mainThreadSuspendResumeControl.get(), []() { onTimer(); });
92 m_suspendTimer = std::make_unique<QWasmTimer>(g_mainThreadSuspendResumeControl.get(), []() { onProcessNativeEventsResume(); });
95 std::lock_guard<std::mutex> lock(g_staticDataMutex);
96 g_secondaryThreadEventDispatchers.append(
this);
100 m_timerInfo = std::make_unique<QTimerInfoList>();
103QEventDispatcherWasm::~QEventDispatcherWasm()
105 qCDebug(lcEventDispatcher) <<
"Destroying QEventDispatcherWasm instance" <<
this;
108 m_wakeupTimer.reset();
109 m_nativeTimer.reset();
110 m_suspendTimer.reset();
113 if (isSecondaryThreadEventDispatcher()) {
114 std::lock_guard<std::mutex> lock(g_staticDataMutex);
115 g_secondaryThreadEventDispatchers.remove(g_secondaryThreadEventDispatchers.indexOf(
this));
119 QWasmSocket::clearSocketNotifiers();
120 g_mainThreadEventDispatcher =
nullptr;
121 g_mainThreadSuspendResumeControl.reset();
125bool QEventDispatcherWasm::isMainThreadEventDispatcher()
127 return this == g_mainThreadEventDispatcher;
130bool QEventDispatcherWasm::isSecondaryThreadEventDispatcher()
132 return this != g_mainThreadEventDispatcher;
135bool QEventDispatcherWasm::isValidEventDispatcher()
137 return isValidEventDispatcherPointer(
this);
140bool QEventDispatcherWasm::isValidEventDispatcherPointer(QEventDispatcherWasm *eventDispatcher)
142 if (eventDispatcher == g_mainThreadEventDispatcher)
145 if (g_secondaryThreadEventDispatchers.contains(eventDispatcher))
151bool QEventDispatcherWasm::processEvents(QEventLoop::ProcessEventsFlags flags)
155 if (useAsyncify() && isMainThreadEventDispatcher()) {
156 auto control = QWasmSuspendResumeControl::get();
157 auto currentEvent = control->currentEvent();
158 if (!currentEvent.isUndefined() &&
159 !currentEvent[
"isInstanceOfEvent"].isUndefined() &&
160 !currentEvent[
"isInstanceOfEvent"].isNull() &&
161 currentEvent[
"isInstanceOfEvent"].as<
bool>()) {
162 currentEvent.call<
void>(
"preventDefault");
163 currentEvent.call<
void>(
"stopPropagation");
164 control->setCurrentEvent(emscripten::val::undefined());
170 if (!useAsyncify() && isMainThreadEventDispatcher())
171 handleNonAsyncifyErrorCases(flags);
173 bool didSendEvents = sendAllEvents(flags);
175 if (!isValidEventDispatcher())
179 m_interrupted =
false;
183 bool shouldWait = flags.testFlag(QEventLoop::WaitForMoreEvents);
184 if (!shouldWait || didSendEvents)
185 return didSendEvents;
189 return sendAllEvents(flags);
192bool QEventDispatcherWasm::sendAllEvents(QEventLoop::ProcessEventsFlags flags)
194 bool didSendEvents =
false;
196 didSendEvents |= sendPostedEvents();
197 if (!isValidEventDispatcher())
200 didSendEvents |= sendNativeEvents(flags);
201 if (!isValidEventDispatcher())
204 didSendEvents |= sendTimerEvents();
205 if (!isValidEventDispatcher())
208 return didSendEvents;
211bool QEventDispatcherWasm::sendNativeEvents(QEventLoop::ProcessEventsFlags flags)
216 if (!isMainThreadEventDispatcher())
224 int sentEventCount = 0;
225 sentEventCount += g_mainThreadSuspendResumeControl->sendPendingEvents();
230 if (flags.testFlag(QEventLoop::EventLoopExec))
231 return sentEventCount > 0;
243 m_wakeFromSuspendTimer =
false;
245 m_suspendTimer->setTimeout(0ms);
246 g_mainThreadSuspendResumeControl->suspend();
247 QScopedValueRollback scoped(m_isSendingNativeEvents,
true);
248 sentEventCount += g_mainThreadSuspendResumeControl->sendPendingEvents();
249 }
while (!m_wakeFromSuspendTimer);
251 return sentEventCount > 1;
254bool QEventDispatcherWasm::sendPostedEvents()
256 QCoreApplication::sendPostedEvents();
264 bool didWakeup = m_wakeup;
269bool QEventDispatcherWasm::sendTimerEvents()
271 int activatedTimers = m_timerInfo->activateTimers();
272 if (activatedTimers > 0)
274 return activatedTimers > 0;
277void QEventDispatcherWasm::registerTimer(Qt::TimerId timerId, Duration interval, Qt::TimerType timerType, QObject *object)
280 if (qToUnderlying(timerId) < 1 || interval < 0ns || !object) {
281 qWarning(
"QEventDispatcherWasm::registerTimer: invalid arguments");
283 }
else if (object->thread() != thread() || thread() != QThread::currentThread()) {
284 qWarning(
"QEventDispatcherWasm::registerTimer: timers cannot be started from another "
289 qCDebug(lcEventDispatcherTimers) <<
"registerTimer" <<
int(timerId) << interval << timerType << object;
291 m_timerInfo->registerTimer(timerId, interval, timerType, object);
295bool QEventDispatcherWasm::unregisterTimer(Qt::TimerId timerId)
298 if (qToUnderlying(timerId) < 1) {
299 qWarning(
"QEventDispatcherWasm::unregisterTimer: invalid argument");
301 }
else if (thread() != QThread::currentThread()) {
302 qWarning(
"QEventDispatcherWasm::unregisterTimer: timers cannot be stopped from another "
308 qCDebug(lcEventDispatcherTimers) <<
"unregisterTimer" <<
int(timerId);
310 bool ans = m_timerInfo->unregisterTimer(timerId);
315bool QEventDispatcherWasm::unregisterTimers(QObject *object)
319 qWarning(
"QEventDispatcherWasm::unregisterTimers: invalid argument");
321 }
else if (object->thread() != thread() || thread() != QThread::currentThread()) {
322 qWarning(
"QEventDispatcherWasm::unregisterTimers: timers cannot be stopped from another "
328 qCDebug(lcEventDispatcherTimers) <<
"registerTimer" << object;
330 bool ans = m_timerInfo->unregisterTimers(object);
335QList<QAbstractEventDispatcher::TimerInfoV2>
336QEventDispatcherWasm::timersForObject(QObject *object)
const
340 qWarning(
"QEventDispatcherWasm:registeredTimers: invalid argument");
345 return m_timerInfo->registeredTimers(object);
348QEventDispatcherWasm::Duration QEventDispatcherWasm::remainingTime(Qt::TimerId timerId)
const
350 return m_timerInfo->remainingDuration(timerId);
353void QEventDispatcherWasm::interrupt()
355 m_interrupted =
true;
359void QEventDispatcherWasm::wakeUp()
363 if (isSecondaryThreadEventDispatcher()) {
364 std::lock_guard<std::mutex> lock(m_mutex);
365 m_wakeUpCalled =
true;
366 m_moreEvents.notify_one();
370 QEventDispatcherWasm *eventDispatcher =
this;
371 qwasmglobal::runOnMainThreadAsync([eventDispatcher]() {
372 if (isValidEventDispatcherPointer(eventDispatcher)) {
373 if (!eventDispatcher->m_wakeupTimer->hasTimeout())
374 eventDispatcher->m_wakeupTimer->setTimeout(0ms);
380void QEventDispatcherWasm::handleNonAsyncifyErrorCases(QEventLoop::ProcessEventsFlags flags)
382 Q_ASSERT(!useAsyncify());
384 if (flags & QEventLoop::ApplicationExec) {
389 const bool simulateInfiniteLoop =
true;
390 emscripten_set_main_loop([](){
391 emscripten_pause_main_loop();
392 }, 0, simulateInfiniteLoop);
393 }
else if (flags & QEventLoop::DialogExec) {
394 qFatal() <<
"Calling exec() is not supported on Qt for WebAssembly in this configuration. Please build"
395 <<
"with asyncify support, or use an asynchronous API like QDialog::open()";
396 }
else if (flags & QEventLoop::WaitForMoreEvents) {
397 qFatal(
"QEventLoop::WaitForMoreEvents is not supported on the main thread without asyncify");
404bool QEventDispatcherWasm::wait(
int timeout)
406 auto tim = timeout > 0 ? std::optional<std::chrono::milliseconds>(timeout) : std::nullopt;
407 if (isSecondaryThreadEventDispatcher())
408 return secondaryThreadWait(tim);
416void QEventDispatcherWasm::processEventsWait()
418 if (isMainThreadEventDispatcher()) {
419 asyncifyWait(std::nullopt);
421 auto nanoWait = m_timerInfo->timerWait();
422 std::optional<std::chrono::milliseconds> milliWait;
423 if (nanoWait.has_value())
424 milliWait = std::chrono::duration_cast<std::chrono::milliseconds>(*nanoWait);
425 secondaryThreadWait(milliWait);
429void QEventDispatcherWasm::asyncifyWait(std::optional<std::chrono::milliseconds> timeout)
431 Q_ASSERT(emscripten_is_main_runtime_thread());
432 Q_ASSERT(isMainThreadEventDispatcher());
433 Q_ASSERT(useAsyncify());
434 if (timeout.has_value())
435 m_suspendTimer->setTimeout(timeout.value());
436 g_mainThreadSuspendResumeControl->suspend();
439bool QEventDispatcherWasm::secondaryThreadWait(std::optional<std::chrono::milliseconds> timeout)
442 Q_ASSERT(QThread::currentThread() == thread());
443 using namespace std::chrono_literals;
444 std::unique_lock<std::mutex> lock(m_mutex);
449 if (m_wakeUpCalled) {
450 m_wakeUpCalled =
false;
454 auto waitTime = timeout.value_or(std::chrono::milliseconds::max());
455 bool wakeUpCalled = m_moreEvents.wait_for(lock, waitTime, [
this] {
return m_wakeUpCalled; });
456 m_wakeUpCalled =
false;
464void QEventDispatcherWasm::onTimer()
466 Q_ASSERT(emscripten_is_main_runtime_thread());
467 if (!g_mainThreadEventDispatcher)
475 g_mainThreadEventDispatcher->sendTimerEvents();
478void QEventDispatcherWasm::onWakeup()
480 Q_ASSERT(emscripten_is_main_runtime_thread());
481 if (!g_mainThreadEventDispatcher)
487 if (g_mainThreadEventDispatcher->m_isSendingNativeEvents)
490 g_mainThreadEventDispatcher->processEvents(QEventLoop::AllEvents);
493void QEventDispatcherWasm::onProcessNativeEventsResume()
495 Q_ASSERT(emscripten_is_main_runtime_thread());
496 if (!g_mainThreadEventDispatcher)
498 g_mainThreadEventDispatcher->m_wakeFromSuspendTimer =
true;
504void QEventDispatcherWasm::updateNativeTimer()
507 Q_ASSERT(QThread::currentThread() == thread());
512 if (!isMainThreadEventDispatcher())
516 const std::optional<std::chrono::nanoseconds> nanoWait = m_timerInfo->timerWait();
517 if (!nanoWait.has_value()) {
518 m_nativeTimer->clearTimeout();
522 auto milliWait = std::chrono::duration_cast<std::chrono::milliseconds>(*nanoWait);
523 const auto newTargetTime = m_timerInfo->currentTime + milliWait;
526 if (m_nativeTimer->hasTimeout() && newTargetTime == m_timerTargetTime)
530 qCDebug(lcEventDispatcherTimers)
531 <<
"Created new native timer timeout" << milliWait.count() <<
"ms"
532 <<
"previous target time" << m_timerTargetTime.time_since_epoch()
533 <<
"new target time" << newTargetTime.time_since_epoch();
534 m_nativeTimer->clearTimeout();
535 m_nativeTimer->setTimeout(milliWait);
536 m_timerTargetTime = newTargetTime;
540 int g_startupTasks = 0;
549void QEventDispatcherWasm::registerStartupTask()
554void QEventDispatcherWasm::completeStarupTask()
557 callOnLoadedIfRequired();
560void QEventDispatcherWasm::callOnLoadedIfRequired()
562 if (g_startupTasks > 0)
565 static bool qtLoadedCalled =
false;
568 qtLoadedCalled =
true;
571void QEventDispatcherWasm::onLoaded()
580void QEventDispatcherWasm::registerSocketNotifier(QSocketNotifier *notifier)
582 QWasmSocket::registerSocketNotifier(notifier);
585void QEventDispatcherWasm::unregisterSocketNotifier(QSocketNotifier *notifier)
587 QWasmSocket::unregisterSocketNotifier(notifier);
590void QEventDispatcherWasm::socketSelect(
int timeout,
int socket,
bool waitForRead,
bool waitForWrite,
591 bool *selectForRead,
bool *selectForWrite,
bool *socketDisconnect)
593 QEventDispatcherWasm *eventDispatcher =
static_cast<QEventDispatcherWasm *>(
594 QAbstractEventDispatcher::instance(QThread::currentThread()));
596 if (!eventDispatcher) {
597 qWarning(
"QEventDispatcherWasm::socketSelect called without eventdispatcher instance");
601 QWasmSocket::waitForSocketState(eventDispatcher, timeout, socket, waitForRead, waitForWrite,
602 selectForRead, selectForWrite, socketDisconnect);
607#include "moc_qeventdispatcher_wasm_p.cpp"
static bool useAsyncify()
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")