Qt 6.x
The Qt SDK
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
qeventdispatcher_wasm.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtCore/private/qabstracteventdispatcher_p.h> // for qGlobalPostedEventsCount()
7#include <QtCore/qcoreapplication.h>
8#include <QtCore/qthread.h>
9#include <QtCore/qsocketnotifier.h>
10#include <QtCore/private/qstdweb_p.h>
11
12#include "emscripten.h"
13#include <emscripten/html5.h>
14#include <emscripten/threading.h>
15#include <emscripten/val.h>
16
17using namespace std::chrono_literals;
18
20
21// using namespace emscripten;
22
23Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher");
24Q_LOGGING_CATEGORY(lcEventDispatcherTimers, "qt.eventdispatcher.timers");
25
26#if QT_CONFIG(thread)
27#define LOCK_GUARD(M) std::lock_guard<std::mutex> lock(M)
28#else
29#define LOCK_GUARD(M)
30#endif
31
32// Emscripten asyncify currently supports one level of suspend -
33// recursion is not permitted. We track the suspend state here
34// on order to fail (more) gracefully, but we can of course only
35// track Qts own usage of asyncify.
36static bool g_is_asyncify_suspended = false;
37
38#if defined(QT_STATIC)
39
40static bool useAsyncify()
41{
42 return qstdweb::haveAsyncify();
43}
44
45static bool useJspi()
46{
47 return qstdweb::haveJspi();
48}
49
50EM_ASYNC_JS(void, qt_jspi_suspend_js, (), {
51 ++Module.qtJspiSuspensionCounter;
52
53 await new Promise(resolve => {
54 Module.qtAsyncifyWakeUp.push(resolve);
55 });
56});
57
58EM_JS(bool, qt_jspi_resume_js, (), {
59 if (!Module.qtJspiSuspensionCounter)
60 return false;
61
62 --Module.qtJspiSuspensionCounter;
63
64 setTimeout(() => {
65 const wakeUp = (Module.qtAsyncifyWakeUp ?? []).pop();
66 if (wakeUp) wakeUp();
67 });
68 return true;
69});
70
71EM_JS(bool, qt_jspi_can_resume_js, (), {
72 return Module.qtJspiSuspensionCounter > 0;
73});
74
75EM_JS(void, init_jspi_support_js, (), {
76 Module.qtAsyncifyWakeUp = [];
77 Module.qtJspiSuspensionCounter = 0;
78});
79
80void initJspiSupport() {
81 init_jspi_support_js();
82}
83
84Q_CONSTRUCTOR_FUNCTION(initJspiSupport);
85
86EM_JS(void, qt_asyncify_suspend_js, (), {
87 if (Module.qtSuspendId === undefined)
88 Module.qtSuspendId = 0;
89 let sleepFn = (wakeUp) => {
90 Module.qtAsyncifyWakeUp = wakeUp;
91 };
92 ++Module.qtSuspendId;
93 return Asyncify.handleSleep(sleepFn);
94});
95
96EM_JS(void, qt_asyncify_resume_js, (), {
97 let wakeUp = Module.qtAsyncifyWakeUp;
98 if (wakeUp == undefined)
99 return;
100 Module.qtAsyncifyWakeUp = undefined;
101 const suspendId = Module.qtSuspendId;
102
103 // Delayed wakeup with zero-timer. Workaround/fix for
104 // https://github.com/emscripten-core/emscripten/issues/10515
105 setTimeout(() => {
106 // Another suspend occurred while the timeout was in queue.
107 if (Module.qtSuspendId !== suspendId)
108 return;
109 wakeUp();
110 });
111});
112
113#else
114
115// EM_JS is not supported for side modules; disable asyncify
116
117static bool useAsyncify()
118{
119 return false;
120}
121
123{
124 Q_UNREACHABLE();
125}
126
128{
129 Q_UNREACHABLE();
130}
131
132#endif // defined(QT_STATIC)
133
134// Suspends the main thread until qt_asyncify_resume() is called. Returns
135// false immediately if Qt has already suspended the main thread (recursive
136// suspend is not supported by Emscripten). Returns true (after resuming),
137// if the thread was suspended.
139{
141 return false;
144 return true;
145}
146
147// Wakes any currently suspended main thread. Returns true if the main
148// thread was suspended, in which case it will now be asynchronously woken.
150{
152 return;
155}
156
157
158Q_CONSTINIT QEventDispatcherWasm *QEventDispatcherWasm::g_mainThreadEventDispatcher = nullptr;
159#if QT_CONFIG(thread)
160Q_CONSTINIT QVector<QEventDispatcherWasm *> QEventDispatcherWasm::g_secondaryThreadEventDispatchers;
161Q_CONSTINIT std::mutex QEventDispatcherWasm::g_staticDataMutex;
162emscripten::ProxyingQueue QEventDispatcherWasm::g_proxyingQueue;
163pthread_t QEventDispatcherWasm::g_mainThread;
164#endif
165// ### dynamic initialization:
166std::multimap<int, QSocketNotifier *> QEventDispatcherWasm::g_socketNotifiers;
167std::map<int, QEventDispatcherWasm::SocketReadyState> QEventDispatcherWasm::g_socketState;
168
171{
172 // QEventDispatcherWasm operates in two main modes:
173 // - On the main thread:
174 // The event dispatcher can process native events but can't
175 // block and wait for new events, unless asyncify is used.
176 // - On a secondary thread:
177 // The event dispatcher can't process native events but can
178 // block and wait for new events.
179 //
180 // Which mode is determined by the calling thread: construct
181 // the event dispatcher object on the thread where it will live.
182
183 qCDebug(lcEventDispatcher) << "Creating QEventDispatcherWasm instance" << this
184 << "is main thread" << emscripten_is_main_runtime_thread();
185
186 if (emscripten_is_main_runtime_thread()) {
187 // There can be only one main thread event dispatcher at a time; in
188 // addition the main instance is used by the secondary thread event
189 // dispatchers so we set a global pointer to it.
190 Q_ASSERT(g_mainThreadEventDispatcher == nullptr);
191 g_mainThreadEventDispatcher = this;
192#if QT_CONFIG(thread)
193 g_mainThread = pthread_self();
194#endif
195 } else {
196#if QT_CONFIG(thread)
197 std::lock_guard<std::mutex> lock(g_staticDataMutex);
198 g_secondaryThreadEventDispatchers.append(this);
199#endif
200 }
201}
202
204{
205 qCDebug(lcEventDispatcher) << "Destroying QEventDispatcherWasm instance" << this;
206
207 delete m_timerInfo;
208
209#if QT_CONFIG(thread)
210 if (isSecondaryThreadEventDispatcher()) {
211 std::lock_guard<std::mutex> lock(g_staticDataMutex);
212 g_secondaryThreadEventDispatchers.remove(g_secondaryThreadEventDispatchers.indexOf(this));
213 } else
214#endif
215 {
216 if (m_timerId > 0)
217 emscripten_clear_timeout(m_timerId);
218 if (!g_socketNotifiers.empty()) {
219 qWarning("QEventDispatcherWasm: main thread event dispatcher deleted with active socket notifiers");
220 clearEmscriptenSocketCallbacks();
221 g_socketNotifiers.clear();
222 }
223 g_mainThreadEventDispatcher = nullptr;
224 if (!g_socketNotifiers.empty()) {
225 qWarning("QEventDispatcherWasm: main thread event dispatcher deleted with active socket notifiers");
226 clearEmscriptenSocketCallbacks();
227 g_socketNotifiers.clear();
228 }
229
230 g_socketState.clear();
231 }
232}
233
234bool QEventDispatcherWasm::isMainThreadEventDispatcher()
235{
236 return this == g_mainThreadEventDispatcher;
237}
238
239bool QEventDispatcherWasm::isSecondaryThreadEventDispatcher()
240{
241 return this != g_mainThreadEventDispatcher;
242}
243
244bool QEventDispatcherWasm::isValidEventDispatcherPointer(QEventDispatcherWasm *eventDispatcher)
245{
246 if (eventDispatcher == g_mainThreadEventDispatcher)
247 return true;
248#if QT_CONFIG(thread)
249 Q_ASSERT(!g_staticDataMutex.try_lock()); // caller must lock mutex
250 if (g_secondaryThreadEventDispatchers.contains(eventDispatcher))
251 return true;
252#endif
253 return false;
254}
255
256bool QEventDispatcherWasm::processEvents(QEventLoop::ProcessEventsFlags flags)
257{
258 qCDebug(lcEventDispatcher) << "QEventDispatcherWasm::processEvents flags" << flags;
259
260 emit awake();
261
262 if (isMainThreadEventDispatcher()) {
264 handleDialogExec();
266 handleApplicationExec();
267 }
268
270
271 if (m_interrupted) {
272 m_interrupted = false;
273 return false;
274 }
275
277 wait();
278
279 if (m_processTimers) {
280 m_processTimers = false;
281 processTimers();
282 }
283
284 return false;
285}
286
288{
289 LOCK_GUARD(g_staticDataMutex);
290
291 bool wasEmpty = g_socketNotifiers.empty();
292 g_socketNotifiers.insert({notifier->socket(), notifier});
293 if (wasEmpty)
294 runOnMainThread([]{ setEmscriptenSocketCallbacks(); });
295}
296
298{
299 LOCK_GUARD(g_staticDataMutex);
300
301 auto notifiers = g_socketNotifiers.equal_range(notifier->socket());
302 for (auto it = notifiers.first; it != notifiers.second; ++it) {
303 if (it->second == notifier) {
304 g_socketNotifiers.erase(it);
305 break;
306 }
307 }
308
309 if (g_socketNotifiers.empty())
310 runOnMainThread([]{ clearEmscriptenSocketCallbacks(); });
311}
312
313void QEventDispatcherWasm::registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *object)
314{
315#ifndef QT_NO_DEBUG
316 if (timerId < 1 || interval < 0 || !object) {
317 qWarning("QEventDispatcherWasm::registerTimer: invalid arguments");
318 return;
319 } else if (object->thread() != thread() || thread() != QThread::currentThread()) {
320 qWarning("QEventDispatcherWasm::registerTimer: timers cannot be started from another "
321 "thread");
322 return;
323 }
324#endif
325 qCDebug(lcEventDispatcherTimers) << "registerTimer" << timerId << interval << timerType << object;
326
327 m_timerInfo->registerTimer(timerId, interval, timerType, object);
328 updateNativeTimer();
329}
330
332{
333#ifndef QT_NO_DEBUG
334 if (timerId < 1) {
335 qWarning("QEventDispatcherWasm::unregisterTimer: invalid argument");
336 return false;
337 } else if (thread() != QThread::currentThread()) {
338 qWarning("QEventDispatcherWasm::unregisterTimer: timers cannot be stopped from another "
339 "thread");
340 return false;
341 }
342#endif
343
344 qCDebug(lcEventDispatcherTimers) << "unregisterTimer" << timerId;
345
346 bool ans = m_timerInfo->unregisterTimer(timerId);
347 updateNativeTimer();
348 return ans;
349}
350
352{
353#ifndef QT_NO_DEBUG
354 if (!object) {
355 qWarning("QEventDispatcherWasm::unregisterTimers: invalid argument");
356 return false;
357 } else if (object->thread() != thread() || thread() != QThread::currentThread()) {
358 qWarning("QEventDispatcherWasm::unregisterTimers: timers cannot be stopped from another "
359 "thread");
360 return false;
361 }
362#endif
363
364 qCDebug(lcEventDispatcherTimers) << "registerTimer" << object;
365
366 bool ans = m_timerInfo->unregisterTimers(object);
367 updateNativeTimer();
368 return ans;
369}
370
373{
374#ifndef QT_NO_DEBUG
375 if (!object) {
376 qWarning("QEventDispatcherWasm:registeredTimers: invalid argument");
377 return QList<TimerInfo>();
378 }
379#endif
380
381 return m_timerInfo->registeredTimers(object);
382}
383
385{
386 return m_timerInfo->timerRemainingTime(timerId);
387}
388
390{
391 m_interrupted = true;
392 wakeUp();
393}
394
396{
397 // The event dispatcher thread may be blocked or suspended by
398 // wait(), or control may have been returned to the browser's
399 // event loop. Make sure the thread is unblocked or make it
400 // process events.
401 bool wasBlocked = wakeEventDispatcherThread();
402 if (!wasBlocked && isMainThreadEventDispatcher()) {
403 {
404 LOCK_GUARD(m_mutex);
405 if (m_pendingProcessEvents)
406 return;
407 m_pendingProcessEvents = true;
408 }
409 runOnMainThreadAsync([this](){
410 QEventDispatcherWasm::callProcessPostedEvents(this);
411 });
412 }
413}
414
415void QEventDispatcherWasm::handleApplicationExec()
416{
417 // Start the main loop, and then stop it on the first callback. This
418 // is done for the "simulateInfiniteLoop" functionality where
419 // emscripten_set_main_loop() throws a JS exception which returns
420 // control to the browser while preserving the C++ stack.
421 //
422 // Note that we don't use asyncify here: Emscripten supports one level of
423 // asyncify only and we want to reserve that for dialog exec() instead of
424 // using it for the one qApp exec().
425 // When JSPI is used, awaited async calls are allowed to be nested, so we
426 // proceed normally.
427 if (!qstdweb::haveJspi()) {
428 const bool simulateInfiniteLoop = true;
429 emscripten_set_main_loop([](){
430 emscripten_pause_main_loop();
431 }, 0, simulateInfiniteLoop);
432 }
433}
434
435void QEventDispatcherWasm::handleDialogExec()
436{
437 if (!useAsyncify()) {
438 qWarning() << "Warning: exec() is not supported on Qt for WebAssembly in this configuration. Please build"
439 << "with asyncify support, or use an asynchronous API like QDialog::open()";
440 emscripten_sleep(1); // This call never returns
441 }
442 // For the asyncify case we do nothing here and wait for events in wait()
443}
444
445// Blocks/suspends the calling thread. This is possible in two cases:
446// - Caller is a secondary thread: block on m_moreEvents
447// - Caller is the main thread and asyncify is enabled: suspend using qt_asyncify_suspend()
448// Returns false if the wait timed out.
449bool QEventDispatcherWasm::wait(int timeout)
450{
451#if QT_CONFIG(thread)
452 using namespace std::chrono_literals;
454
455 if (isSecondaryThreadEventDispatcher()) {
456 std::unique_lock<std::mutex> lock(m_mutex);
457
458 m_wakeUpCalled = false;
459 auto wait_time = timeout > 0 ? timeout * 1ms : std::chrono::duration<int, std::micro>::max();
460 bool wakeUpCalled = m_moreEvents.wait_for(lock, wait_time, [=] { return m_wakeUpCalled; });
461 return wakeUpCalled;
462 }
463#endif
464 Q_ASSERT(emscripten_is_main_runtime_thread());
465 Q_ASSERT(isMainThreadEventDispatcher());
466 if (useAsyncify()) {
467 if (timeout > 0)
468 qWarning() << "QEventDispatcherWasm asyncify wait with timeout is not supported; timeout will be ignored"; // FIXME
469
470 if (useJspi()) {
471 qt_jspi_suspend_js();
472 } else {
473 bool didSuspend = qt_asyncify_suspend();
474 if (!didSuspend) {
475 qWarning("QEventDispatcherWasm: current thread is already suspended; could not asyncify wait for events");
476 return false;
477 }
478 }
479 return true;
480 } else {
481 qWarning("QEventLoop::WaitForMoreEvents is not supported on the main thread without asyncify");
483 }
484 return false;
485}
486
487// Wakes a blocked/suspended event dispatcher thread. Returns true if the
488// thread is unblocked or was resumed, false if the thread state could not
489// be determined.
490bool QEventDispatcherWasm::wakeEventDispatcherThread()
491{
492#if QT_CONFIG(thread)
493 if (isSecondaryThreadEventDispatcher()) {
494 std::lock_guard<std::mutex> lock(m_mutex);
495 m_wakeUpCalled = true;
496 m_moreEvents.notify_one();
497 return true;
498 }
499#endif
500 Q_ASSERT(isMainThreadEventDispatcher());
501 if (useJspi()) {
502 if (!qt_jspi_can_resume_js())
503 return false;
504 runOnMainThread([]{ qt_jspi_resume_js(); });
505 return true;
506 }
508 runOnMainThread([]{ qt_asyncify_resume(); });
509 return true;
510 }
511 return false;
512}
513
514// Process event activation callbacks for the main thread event dispatcher.
515// Must be called on the main thread.
516void QEventDispatcherWasm::callProcessPostedEvents(void *context)
517{
518 Q_ASSERT(emscripten_is_main_runtime_thread());
519
520 // Bail out if Qt has been shut down.
521 if (!g_mainThreadEventDispatcher)
522 return;
523
524 // In the unlikely event that we get a callProcessPostedEvents() call for
525 // a previous main thread event dispatcher (i.e. the QApplication
526 // object was deleted and created again): just ignore it and return.
527 if (context != g_mainThreadEventDispatcher)
528 return;
529
530 {
531 LOCK_GUARD(g_mainThreadEventDispatcher->m_mutex);
532 g_mainThreadEventDispatcher->m_pendingProcessEvents = false;
533 }
534
535 g_mainThreadEventDispatcher->processPostedEvents();
536}
537
539{
541 return false;
542}
543
544void QEventDispatcherWasm::processTimers()
545{
546 m_timerInfo->activateTimers();
547 updateNativeTimer(); // schedule next native timer, if any
548}
549
550// Updates the native timer based on currently registered Qt timers.
551// Must be called on the event dispatcher thread.
552void QEventDispatcherWasm::updateNativeTimer()
553{
554#if QT_CONFIG(thread)
556#endif
557
558 // Multiplex Qt timers down to a single native timer, maintained
559 // to have a timeout corresponding to the shortest Qt timer. This
560 // is done in two steps: first determine the target wakeup time
561 // on the event dispatcher thread (since this thread has exclusive
562 // access to m_timerInfo), and then call native API to set the new
563 // wakeup time on the main thread.
564
565 using namespace std::chrono;
566 auto timespecToMsec = [](timespec ts) -> milliseconds {
567 return duration_cast<milliseconds>(seconds{ts.tv_sec} + nanoseconds{ts.tv_nsec});
568 };
569 timespec toWait;
570 bool hasTimer = m_timerInfo->timerWait(toWait);
571 const milliseconds toWaitDuration = timespecToMsec(toWait);
572 const time_point newTargetTimePoint = m_timerInfo->currentTime + toWaitDuration;
573 auto newTargetTime = duration_cast<milliseconds>(newTargetTimePoint.time_since_epoch());
574 auto maintainNativeTimer = [this, hasTimer, toWaitDuration, newTargetTime]() {
575 Q_ASSERT(emscripten_is_main_runtime_thread());
576
577 if (!hasTimer) {
578 if (m_timerId > 0) {
579 emscripten_clear_timeout(m_timerId);
580 m_timerId = 0;
581 m_timerTargetTime = 0ms;
582 }
583 return;
584 }
585
586 if (m_timerTargetTime != 0ms && newTargetTime >= m_timerTargetTime)
587 return; // existing timer is good
588
589 qCDebug(lcEventDispatcherTimers)
590 << "Created new native timer with wait" << toWaitDuration.count() << "ms"
591 << "timeout" << newTargetTime.count() << "ms";
592 emscripten_clear_timeout(m_timerId);
593 m_timerId = emscripten_set_timeout(&QEventDispatcherWasm::callProcessTimers,
594 toWaitDuration.count(), this);
595 m_timerTargetTime = newTargetTime;
596 };
597
598 // Update the native timer for this thread/dispatcher. This must be
599 // done on the main thread where we have access to native API.
600 runOnMainThread([this, maintainNativeTimer]() {
601 Q_ASSERT(emscripten_is_main_runtime_thread());
602
603 // "this" may have been deleted, or may be about to be deleted.
604 // Check if the pointer we have is still a valid event dispatcher,
605 // and keep the mutex locked while updating the native timer to
606 // prevent it from being deleted.
607 LOCK_GUARD(g_staticDataMutex);
608 if (isValidEventDispatcherPointer(this))
609 maintainNativeTimer();
610 });
611}
612
613// Static timer activation callback. Must be called on the main thread
614// and will then either process timers on the main thread or wake and
615// process timers on a secondary thread.
616void QEventDispatcherWasm::callProcessTimers(void *context)
617{
618 Q_ASSERT(emscripten_is_main_runtime_thread());
619
620 // Note: "context" may be a stale pointer here,
621 // take care before casting and dereferencing!
622
623 // Process timers on this thread if this is the main event dispatcher
624 if (reinterpret_cast<QEventDispatcherWasm *>(context) == g_mainThreadEventDispatcher) {
625 g_mainThreadEventDispatcher->m_timerTargetTime = 0ms;
626 g_mainThreadEventDispatcher->processTimers();
627 return;
628 }
629
630 // Wake and process timers on the secondary thread if this a secondary thread dispatcher
631#if QT_CONFIG(thread)
632 std::lock_guard<std::mutex> lock(g_staticDataMutex);
633 if (g_secondaryThreadEventDispatchers.contains(context)) {
634 QEventDispatcherWasm *eventDispatcher = reinterpret_cast<QEventDispatcherWasm *>(context);
635 eventDispatcher->m_timerTargetTime = 0ms;
636 eventDispatcher->m_processTimers = true;
637 eventDispatcher->wakeUp();
638 }
639#endif
640}
641
642void QEventDispatcherWasm::setEmscriptenSocketCallbacks()
643{
644 qCDebug(lcEventDispatcher) << "setEmscriptenSocketCallbacks";
645
646 emscripten_set_socket_error_callback(nullptr, QEventDispatcherWasm::socketError);
647 emscripten_set_socket_open_callback(nullptr, QEventDispatcherWasm::socketOpen);
648 emscripten_set_socket_listen_callback(nullptr, QEventDispatcherWasm::socketListen);
649 emscripten_set_socket_connection_callback(nullptr, QEventDispatcherWasm::socketConnection);
650 emscripten_set_socket_message_callback(nullptr, QEventDispatcherWasm::socketMessage);
651 emscripten_set_socket_close_callback(nullptr, QEventDispatcherWasm::socketClose);
652}
653
654void QEventDispatcherWasm::clearEmscriptenSocketCallbacks()
655{
656 qCDebug(lcEventDispatcher) << "clearEmscriptenSocketCallbacks";
657
658 emscripten_set_socket_error_callback(nullptr, nullptr);
659 emscripten_set_socket_open_callback(nullptr, nullptr);
660 emscripten_set_socket_listen_callback(nullptr, nullptr);
661 emscripten_set_socket_connection_callback(nullptr, nullptr);
662 emscripten_set_socket_message_callback(nullptr, nullptr);
663 emscripten_set_socket_close_callback(nullptr, nullptr);
664}
665
666void QEventDispatcherWasm::socketError(int socket, int err, const char* msg, void *context)
667{
668 Q_UNUSED(err);
669 Q_UNUSED(msg);
671
672 // Emscripten makes socket callbacks while the main thread is busy-waiting for a mutex,
673 // which can cause deadlocks if the callback code also tries to lock the same mutex.
674 // This is most easily reproducible by adding print statements, where each print requires
675 // taking a mutex lock. Work around this by running the callback asynchronously, i.e. by using
676 // a native zero-timer, to make sure the main thread stack is completely unwond before calling
677 // the Qt handler.
678 // It is currently unclear if this problem is caused by code in Qt or in Emscripten, or
679 // if this completely fixes the problem.
680 runAsync([socket](){
681 auto notifiersRange = g_socketNotifiers.equal_range(socket);
682 std::vector<std::pair<int, QSocketNotifier *>> notifiers(notifiersRange.first, notifiersRange.second);
683 for (auto [_, notifier]: notifiers) {
685 }
686 setSocketState(socket, true, true);
687 });
688}
689
690void QEventDispatcherWasm::socketOpen(int socket, void *context)
691{
693
694 runAsync([socket](){
695 auto notifiersRange = g_socketNotifiers.equal_range(socket);
696 std::vector<std::pair<int, QSocketNotifier *>> notifiers(notifiersRange.first, notifiersRange.second);
697 for (auto [_, notifier]: notifiers) {
698 if (notifier->type() == QSocketNotifier::Write) {
700 }
701 }
702 setSocketState(socket, false, true);
703 });
704}
705
706void QEventDispatcherWasm::socketListen(int socket, void *context)
707{
710}
711
712void QEventDispatcherWasm::socketConnection(int socket, void *context)
713{
716}
717
718void QEventDispatcherWasm::socketMessage(int socket, void *context)
719{
721
722 runAsync([socket](){
723 auto notifiersRange = g_socketNotifiers.equal_range(socket);
724 std::vector<std::pair<int, QSocketNotifier *>> notifiers(notifiersRange.first, notifiersRange.second);
725 for (auto [_, notifier]: notifiers) {
726 if (notifier->type() == QSocketNotifier::Read) {
728 }
729 }
730 setSocketState(socket, true, false);
731 });
732}
733
734void QEventDispatcherWasm::socketClose(int socket, void *context)
735{
737
738 // Emscripten makes emscripten_set_socket_close_callback() calls to socket 0,
739 // which is not a valid socket. see https://github.com/emscripten-core/emscripten/issues/6596
740 if (socket == 0)
741 return;
742
743 runAsync([socket](){
744 auto notifiersRange = g_socketNotifiers.equal_range(socket);
745 std::vector<std::pair<int, QSocketNotifier *>> notifiers(notifiersRange.first, notifiersRange.second);
746 for (auto [_, notifier]: notifiers)
747 QCoreApplication::postEvent(notifier, new QEvent(QEvent::SockClose));
748
749 setSocketState(socket, true, true);
750 clearSocketState(socket);
751 });
752}
753
754void QEventDispatcherWasm::setSocketState(int socket, bool setReadyRead, bool setReadyWrite)
755{
756 LOCK_GUARD(g_staticDataMutex);
757 SocketReadyState &state = g_socketState[socket];
758
759 // Additively update socket ready state, e.g. if it
760 // was already ready read then it stays ready read.
761 state.readyRead |= setReadyRead;
762 state.readyWrite |= setReadyWrite;
763
764 // Wake any waiters for the given readiness. The waiter consumes
765 // the ready state, returning the socket to not-ready.
766 if (QEventDispatcherWasm *waiter = state.waiter)
767 if ((state.readyRead && state.waitForReadyRead) || (state.readyWrite && state.waitForReadyWrite))
768 waiter->wakeEventDispatcherThread();
769}
770
771void QEventDispatcherWasm::clearSocketState(int socket)
772{
773 LOCK_GUARD(g_staticDataMutex);
774 g_socketState.erase(socket);
775}
776
777void QEventDispatcherWasm::waitForSocketState(int timeout, int socket, bool checkRead, bool checkWrite,
778 bool *selectForRead, bool *selectForWrite, bool *socketDisconnect)
779{
780 // Loop until the socket becomes readyRead or readyWrite. Wait for
781 // socket activity if it currently is neither.
782 while (true) {
783 *selectForRead = false;
784 *selectForWrite = false;
785
786 {
787 LOCK_GUARD(g_staticDataMutex);
788
789 // Access or create socket state: we want to register that a thread is waitng
790 // even if we have not received any socket callbacks yet.
791 SocketReadyState &state = g_socketState[socket];
792 if (state.waiter) {
793 qWarning() << "QEventDispatcherWasm::waitForSocketState: a thread is already waiting";
794 break;
795 }
796
797 bool shouldWait = true;
798 if (checkRead && state.readyRead) {
799 shouldWait = false;
800 state.readyRead = false;
801 *selectForRead = true;
802 }
803 if (checkWrite && state.readyWrite) {
804 shouldWait = false;
805 state.readyWrite = false;
806 *selectForRead = true;
807 }
808 if (!shouldWait)
809 break;
810
811 state.waiter = this;
812 state.waitForReadyRead = checkRead;
813 state.waitForReadyWrite = checkWrite;
814 }
815
816 bool didTimeOut = !wait(timeout);
817 {
818 LOCK_GUARD(g_staticDataMutex);
819
820 // Missing socket state after a wakeup means that the socket has been closed.
821 auto it = g_socketState.find(socket);
822 if (it == g_socketState.end()) {
823 *socketDisconnect = true;
824 break;
825 }
826 it->second.waiter = nullptr;
827 it->second.waitForReadyRead = false;
828 it->second.waitForReadyWrite = false;
829 }
830
831 if (didTimeOut)
832 break;
833 }
834}
835
836void QEventDispatcherWasm::socketSelect(int timeout, int socket, bool waitForRead, bool waitForWrite,
837 bool *selectForRead, bool *selectForWrite, bool *socketDisconnect)
838{
839 QEventDispatcherWasm *eventDispatcher = static_cast<QEventDispatcherWasm *>(
841
842 if (!eventDispatcher) {
843 qWarning("QEventDispatcherWasm::socketSelect called without eventdispatcher instance");
844 return;
845 }
846
847 eventDispatcher->waitForSocketState(timeout, socket, waitForRead, waitForWrite,
848 selectForRead, selectForWrite, socketDisconnect);
849}
850
851namespace {
852 void trampoline(void *context) {
853
854 auto async_fn = [](void *context){
855 std::function<void(void)> *fn = reinterpret_cast<std::function<void(void)> *>(context);
856 (*fn)();
857 delete fn;
858 };
859
860 emscripten_async_call(async_fn, context, 0);
861 }
862}
863
864// Runs a function right away
865void QEventDispatcherWasm::run(std::function<void(void)> fn)
866{
867 fn();
868}
869
870// Runs a function asynchronously. Main thread only.
871void QEventDispatcherWasm::runAsync(std::function<void(void)> fn)
872{
873 trampoline(new std::function<void(void)>(fn));
874}
875
876// Runs a function on the main thread. The function runs synchronusly if
877// the calling thread is then main thread.
878void QEventDispatcherWasm::runOnMainThread(std::function<void(void)> fn)
879{
880#if QT_CONFIG(thread)
881 if (!emscripten_is_main_runtime_thread()) {
882 void *context = new std::function<void(void)>(fn);
883 g_proxyingQueue.proxyAsync(g_mainThread, [context]{
884 trampoline(context);
885 });
886 return;
887 }
888#endif
889 fn();
890}
891
892// Runs a function on the main thread. The function always runs asynchronously,
893// also if the calling thread is the main thread.
894void QEventDispatcherWasm::runOnMainThreadAsync(std::function<void(void)> fn)
895{
896 void *context = new std::function<void(void)>(fn);
897#if QT_CONFIG(thread)
898 if (!emscripten_is_main_runtime_thread()) {
899 g_proxyingQueue.proxyAsync(g_mainThread, [context]{
900 trampoline(context);
901 });
902 return;
903 }
904#endif
905 trampoline(context);
906}
907
909
910#include "moc_qeventdispatcher_wasm_p.cpp"
DarwinBluetooth::LECBManagerNotifier * notifier
static QAbstractEventDispatcher * instance(QThread *thread=nullptr)
Returns a pointer to the event dispatcher object for the specified thread.
void awake()
This signal is emitted after the event loop returns from a function that could block.
\inmodule QtCore
static void postEvent(QObject *receiver, QEvent *event, int priority=Qt::NormalEventPriority)
static void sendPostedEvents(QObject *receiver=nullptr, int event_type=0)
Immediately dispatches all events which have been previously queued with QCoreApplication::postEvent(...
void interrupt() override
Interrupts event dispatching.
void wakeUp() override
\threadsafe
void registerSocketNotifier(QSocketNotifier *notifier) override
Registers notifier with the event loop.
QList< QAbstractEventDispatcher::TimerInfo > registeredTimers(QObject *object) const override
Returns a list of registered timers for object.
void registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *object) override
Register a timer with the specified timerId, interval, and timerType for the given object.
int remainingTime(int timerId) override
Returns the remaining time in milliseconds with the given timerId.
bool processEvents(QEventLoop::ProcessEventsFlags flags) override
Processes pending events that match flags until there are no more events to process.
bool unregisterTimers(QObject *object) override
Unregisters all the timers associated with the given object.
bool unregisterTimer(int timerId) override
Unregisters the timer with the given timerId.
void unregisterSocketNotifier(QSocketNotifier *notifier) override
Unregisters notifier from the event dispatcher.
static void socketSelect(int timeout, int socket, bool waitForRead, bool waitForWrite, bool *selectForRead, bool *selectForWrite, bool *socketDisconnect)
@ ApplicationExec
Definition qeventloop.h:33
@ WaitForMoreEvents
Definition qeventloop.h:29
\inmodule QtCore
Definition qcoreevent.h:45
@ SockAct
Definition qcoreevent.h:98
Definition qlist.h:74
\inmodule QtCore
Definition qobject.h:90
QThread * thread() const
Returns the thread in which the object lives.
Definition qobject.cpp:1561
\inmodule QtCore
static QThread * currentThread()
Definition qthread.cpp:966
std::chrono::steady_clock::time_point currentTime
bool timerWait(timespec &)
qint64 timerRemainingTime(int timerId)
bool unregisterTimers(QObject *object)
void registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *object)
QList< QAbstractEventDispatcher::TimerInfo > registeredTimers(QObject *object) const
bool unregisterTimer(int timerId)
QSet< QString >::iterator it
else opt state
[0]
Combined button and popup list for selecting options.
TimerType
bool haveAsyncify()
Definition qstdweb.cpp:855
bool haveJspi()
Definition qstdweb.cpp:861
static void * context
static QT_BEGIN_NAMESPACE bool await(IAsyncOperation< T > &&asyncInfo, T &result, uint timeout=0)
Q_CONSTRUCTOR_FUNCTION(initializeStandardUserDefaults)
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
static bool useAsyncify()
bool qt_asyncify_suspend()
void qt_asyncify_resume_js()
void qt_asyncify_resume()
void qt_asyncify_suspend_js()
static bool g_is_asyncify_suspended
#define LOCK_GUARD(M)
#define qWarning
Definition qlogging.h:162
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
GLuint object
[3]
GLbitfield GLuint64 timeout
[4]
GLbitfield flags
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define emit
#define Q_UNUSED(x)
long long qint64
Definition qtypes.h:55
const bool wasBlocked
[52]
QTcpSocket * socket
[1]
QReadWriteLock lock
[0]
socketLayer waitForWrite()