Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
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;
18using namespace std::chrono_literals;
19
21
22// using namespace emscripten;
23
24Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher");
25Q_LOGGING_CATEGORY(lcEventDispatcherTimers, "qt.eventdispatcher.timers");
26
27#if QT_CONFIG(thread)
28#define LOCK_GUARD(M) std::lock_guard<std::mutex> lock(M)
29#else
30#define LOCK_GUARD(M)
31#endif
32
33// Emscripten asyncify currently supports one level of suspend -
34// recursion is not permitted. We track the suspend state here
35// on order to fail (more) gracefully, but we can of course only
36// track Qts own usage of asyncify.
37static bool g_is_asyncify_suspended = false;
38
39#if defined(QT_STATIC)
40
41static bool useAsyncify()
42{
43 return qstdweb::haveAsyncify();
44}
45
46static bool useJspi()
47{
48 return qstdweb::haveJspi();
49}
50
51// clang-format off
52EM_ASYNC_JS(void, qt_jspi_suspend_js, (), {
53 ++Module.qtJspiSuspensionCounter;
54
55 await new Promise(resolve => {
56 Module.qtAsyncifyWakeUp.push(resolve);
57 });
58});
59
60EM_JS(bool, qt_jspi_resume_js, (), {
61 if (!Module.qtJspiSuspensionCounter)
62 return false;
63
64 --Module.qtJspiSuspensionCounter;
65
66 setTimeout(() => {
67 const wakeUp = (Module.qtAsyncifyWakeUp ?? []).pop();
68 if (wakeUp) wakeUp();
69 });
70 return true;
71});
72
73EM_JS(bool, qt_jspi_can_resume_js, (), {
74 return Module.qtJspiSuspensionCounter > 0;
75});
76
77EM_JS(void, init_jspi_support_js, (), {
78 Module.qtAsyncifyWakeUp = [];
79 Module.qtJspiSuspensionCounter = 0;
80});
81// clang-format on
82
83void initJspiSupport() {
84 init_jspi_support_js();
85}
86
87Q_CONSTRUCTOR_FUNCTION(initJspiSupport);
88
89// clang-format off
90EM_JS(void, qt_asyncify_suspend_js, (), {
91 if (Module.qtSuspendId === undefined)
92 Module.qtSuspendId = 0;
93 let sleepFn = (wakeUp) => {
94 Module.qtAsyncifyWakeUp = wakeUp;
95 };
96 ++Module.qtSuspendId;
97 return Asyncify.handleSleep(sleepFn);
98});
99
100EM_JS(void, qt_asyncify_resume_js, (), {
101 let wakeUp = Module.qtAsyncifyWakeUp;
102 if (wakeUp == undefined)
103 return;
104 Module.qtAsyncifyWakeUp = undefined;
105 const suspendId = Module.qtSuspendId;
106
107 // Delayed wakeup with zero-timer. Workaround/fix for
108 // https://github.com/emscripten-core/emscripten/issues/10515
109 setTimeout(() => {
110 // Another suspend occurred while the timeout was in queue.
111 if (Module.qtSuspendId !== suspendId)
112 return;
113 wakeUp();
114 });
115});
116// clang-format on
117
118#else
119
120// EM_JS is not supported for side modules; disable asyncify
121
122static bool useAsyncify()
123{
124 return false;
125}
126
127static bool useJspi()
128{
129 return false;
130}
131
133{
134 Q_UNREACHABLE();
135}
136
138{
139 Q_UNREACHABLE();
140 return false;
141}
142
144{
145 Q_UNREACHABLE();
146 return false;
147}
148
150{
151 Q_UNREACHABLE();
152}
153
155{
156 Q_UNREACHABLE();
157}
158
159#endif // defined(QT_STATIC)
160
161// Suspends the main thread until qt_asyncify_resume() is called. Returns
162// false immediately if Qt has already suspended the main thread (recursive
163// suspend is not supported by Emscripten). Returns true (after resuming),
164// if the thread was suspended.
166{
168 return false;
171 return true;
172}
173
174// Wakes any currently suspended main thread. Returns true if the main
175// thread was suspended, in which case it will now be asynchronously woken.
177{
179 return;
182}
183
184
185Q_CONSTINIT QEventDispatcherWasm *QEventDispatcherWasm::g_mainThreadEventDispatcher = nullptr;
186#if QT_CONFIG(thread)
187Q_CONSTINIT QVector<QEventDispatcherWasm *> QEventDispatcherWasm::g_secondaryThreadEventDispatchers;
188Q_CONSTINIT std::mutex QEventDispatcherWasm::g_staticDataMutex;
189emscripten::ProxyingQueue QEventDispatcherWasm::g_proxyingQueue;
190pthread_t QEventDispatcherWasm::g_mainThread;
191#endif
192// ### dynamic initialization:
193std::multimap<int, QSocketNotifier *> QEventDispatcherWasm::g_socketNotifiers;
194std::map<int, QEventDispatcherWasm::SocketReadyState> QEventDispatcherWasm::g_socketState;
195
197{
198 // QEventDispatcherWasm operates in two main modes:
199 // - On the main thread:
200 // The event dispatcher can process native events but can't
201 // block and wait for new events, unless asyncify is used.
202 // - On a secondary thread:
203 // The event dispatcher can't process native events but can
204 // block and wait for new events.
205 //
206 // Which mode is determined by the calling thread: construct
207 // the event dispatcher object on the thread where it will live.
208
209 qCDebug(lcEventDispatcher) << "Creating QEventDispatcherWasm instance" << this
210 << "is main thread" << emscripten_is_main_runtime_thread();
211
212 if (emscripten_is_main_runtime_thread()) {
213 // There can be only one main thread event dispatcher at a time; in
214 // addition the main instance is used by the secondary thread event
215 // dispatchers so we set a global pointer to it.
216 Q_ASSERT(g_mainThreadEventDispatcher == nullptr);
217 g_mainThreadEventDispatcher = this;
218#if QT_CONFIG(thread)
219 g_mainThread = pthread_self();
220#endif
221
222 // Call the "onLoaded" JavaScript callback, unless startup tasks
223 // have been registered which should complete first. Run async
224 // to make sure event dispatcher construction (in particular any
225 // subclass construction) has completed first.
226 runAsync(callOnLoadedIfRequired);
227 } else {
228#if QT_CONFIG(thread)
229 std::lock_guard<std::mutex> lock(g_staticDataMutex);
230 g_secondaryThreadEventDispatchers.append(this);
231#endif
232 }
233}
234
236{
237 qCDebug(lcEventDispatcher) << "Destroying QEventDispatcherWasm instance" << this;
238
239 delete m_timerInfo;
240
241#if QT_CONFIG(thread)
242 if (isSecondaryThreadEventDispatcher()) {
243 std::lock_guard<std::mutex> lock(g_staticDataMutex);
244 g_secondaryThreadEventDispatchers.remove(g_secondaryThreadEventDispatchers.indexOf(this));
245 } else
246#endif
247 {
248 if (m_timerId > 0)
249 emscripten_clear_timeout(m_timerId);
250 if (!g_socketNotifiers.empty()) {
251 qWarning("QEventDispatcherWasm: main thread event dispatcher deleted with active socket notifiers");
252 clearEmscriptenSocketCallbacks();
253 g_socketNotifiers.clear();
254 }
255 g_mainThreadEventDispatcher = nullptr;
256 if (!g_socketNotifiers.empty()) {
257 qWarning("QEventDispatcherWasm: main thread event dispatcher deleted with active socket notifiers");
258 clearEmscriptenSocketCallbacks();
259 g_socketNotifiers.clear();
260 }
261
262 g_socketState.clear();
263 }
264}
265
266bool QEventDispatcherWasm::isMainThreadEventDispatcher()
267{
268 return this == g_mainThreadEventDispatcher;
269}
270
271bool QEventDispatcherWasm::isSecondaryThreadEventDispatcher()
272{
273 return this != g_mainThreadEventDispatcher;
274}
275
276bool QEventDispatcherWasm::isValidEventDispatcherPointer(QEventDispatcherWasm *eventDispatcher)
277{
278 if (eventDispatcher == g_mainThreadEventDispatcher)
279 return true;
280#if QT_CONFIG(thread)
281 if (g_secondaryThreadEventDispatchers.contains(eventDispatcher))
282 return true;
283#endif
284 return false;
285}
286
287bool QEventDispatcherWasm::processEvents(QEventLoop::ProcessEventsFlags flags)
288{
289 qCDebug(lcEventDispatcher) << "QEventDispatcherWasm::processEvents flags" << flags;
290
291 emit awake();
292
293 if (isMainThreadEventDispatcher()) {
295 handleDialogExec();
297 handleApplicationExec();
298 }
299
300#if QT_CONFIG(thread)
301 {
302 // Reset wakeUp state: if wakeUp() was called at some point before
303 // this then processPostedEvents() below will service that call.
304 std::unique_lock<std::mutex> lock(m_mutex);
305 m_wakeUpCalled = false;
306 }
307#endif
308
310
311 // The processPostedEvents() call above may process an event which deletes the
312 // application object and the event dispatcher; stop event processing in that case.
313 if (!isValidEventDispatcherPointer(this))
314 return false;
315
316 if (m_interrupted) {
317 m_interrupted = false;
318 return false;
319 }
320
322 wait();
323
324 if (m_processTimers) {
325 m_processTimers = false;
326 processTimers();
327 }
328
329 return false;
330}
331
333{
334 LOCK_GUARD(g_staticDataMutex);
335
336 bool wasEmpty = g_socketNotifiers.empty();
337 g_socketNotifiers.insert({notifier->socket(), notifier});
338 if (wasEmpty)
339 runOnMainThread([] { setEmscriptenSocketCallbacks(); });
340}
341
343{
344 LOCK_GUARD(g_staticDataMutex);
345
346 auto notifiers = g_socketNotifiers.equal_range(notifier->socket());
347 for (auto it = notifiers.first; it != notifiers.second; ++it) {
348 if (it->second == notifier) {
349 g_socketNotifiers.erase(it);
350 break;
351 }
352 }
353
354 if (g_socketNotifiers.empty())
355 runOnMainThread([] { clearEmscriptenSocketCallbacks(); });
356}
357
359{
360#ifndef QT_NO_DEBUG
361 if (qToUnderlying(timerId) < 1 || interval < 0ns || !object) {
362 qWarning("QEventDispatcherWasm::registerTimer: invalid arguments");
363 return;
364 } else if (object->thread() != thread() || thread() != QThread::currentThread()) {
365 qWarning("QEventDispatcherWasm::registerTimer: timers cannot be started from another "
366 "thread");
367 return;
368 }
369#endif
370 qCDebug(lcEventDispatcherTimers) << "registerTimer" << int(timerId) << interval << timerType << object;
371
372 m_timerInfo->registerTimer(timerId, interval, timerType, object);
373 updateNativeTimer();
374}
375
377{
378#ifndef QT_NO_DEBUG
379 if (qToUnderlying(timerId) < 1) {
380 qWarning("QEventDispatcherWasm::unregisterTimer: invalid argument");
381 return false;
382 } else if (thread() != QThread::currentThread()) {
383 qWarning("QEventDispatcherWasm::unregisterTimer: timers cannot be stopped from another "
384 "thread");
385 return false;
386 }
387#endif
388
389 qCDebug(lcEventDispatcherTimers) << "unregisterTimer" << int(timerId);
390
391 bool ans = m_timerInfo->unregisterTimer(timerId);
392 updateNativeTimer();
393 return ans;
394}
395
397{
398#ifndef QT_NO_DEBUG
399 if (!object) {
400 qWarning("QEventDispatcherWasm::unregisterTimers: invalid argument");
401 return false;
402 } else if (object->thread() != thread() || thread() != QThread::currentThread()) {
403 qWarning("QEventDispatcherWasm::unregisterTimers: timers cannot be stopped from another "
404 "thread");
405 return false;
406 }
407#endif
408
409 qCDebug(lcEventDispatcherTimers) << "registerTimer" << object;
410
411 bool ans = m_timerInfo->unregisterTimers(object);
412 updateNativeTimer();
413 return ans;
414}
415
416QList<QAbstractEventDispatcher::TimerInfoV2>
418{
419#ifndef QT_NO_DEBUG
420 if (!object) {
421 qWarning("QEventDispatcherWasm:registeredTimers: invalid argument");
422 return {};
423 }
424#endif
425
426 return m_timerInfo->registeredTimers(object);
427}
428
433
435{
436 m_interrupted = true;
437 wakeUp();
438}
439
441{
442 // The event dispatcher thread may be blocked or suspended by
443 // wait(), or control may have been returned to the browser's
444 // event loop. Make sure the thread is unblocked or make it
445 // process events.
446 bool wasBlocked = wakeEventDispatcherThread();
447 // JSPI does not need a scheduled call to processPostedEvents, as the stack is not unwound
448 // at startup.
449 if (!qstdweb::haveJspi() && !wasBlocked && isMainThreadEventDispatcher()) {
450 {
451 LOCK_GUARD(m_mutex);
452 if (m_pendingProcessEvents)
453 return;
454 m_pendingProcessEvents = true;
455 }
456 runOnMainThreadAsync([this](){
457 QEventDispatcherWasm::callProcessPostedEvents(this);
458 });
459 }
460}
461
462void QEventDispatcherWasm::handleApplicationExec()
463{
464 // Start the main loop, and then stop it on the first callback. This
465 // is done for the "simulateInfiniteLoop" functionality where
466 // emscripten_set_main_loop() throws a JS exception which returns
467 // control to the browser while preserving the C++ stack.
468 //
469 // Note that we don't use asyncify here: Emscripten supports one level of
470 // asyncify only and we want to reserve that for dialog exec() instead of
471 // using it for the one qApp exec().
472 // When JSPI is used, awaited async calls are allowed to be nested, so we
473 // proceed normally.
474 if (!qstdweb::haveJspi()) {
475 const bool simulateInfiniteLoop = true;
476 emscripten_set_main_loop([](){
477 emscripten_pause_main_loop();
478 }, 0, simulateInfiniteLoop);
479 }
480}
481
482void QEventDispatcherWasm::handleDialogExec()
483{
484 if (!useAsyncify()) {
485 qWarning() << "Warning: exec() is not supported on Qt for WebAssembly in this configuration. Please build"
486 << "with asyncify support, or use an asynchronous API like QDialog::open()";
487 emscripten_sleep(1); // This call never returns
488 }
489 // For the asyncify case we do nothing here and wait for events in wait()
490}
491
492// Blocks/suspends the calling thread. This is possible in two cases:
493// - Caller is a secondary thread: block on m_moreEvents
494// - Caller is the main thread and asyncify is enabled: suspend using qt_asyncify_suspend()
495// Returns false if the wait timed out.
496bool QEventDispatcherWasm::wait(int timeout)
497{
498#if QT_CONFIG(thread)
499 using namespace std::chrono_literals;
501
502 if (isSecondaryThreadEventDispatcher()) {
503 std::unique_lock<std::mutex> lock(m_mutex);
504
505 // If wakeUp() was called there might be pending events in the event
506 // queue which should be processed. Don't block, instead return
507 // so that the event loop can spin and call processEvents() again.
508 if (m_wakeUpCalled)
509 return true;
510
511 auto wait_time = timeout > 0 ? timeout * 1ms : std::chrono::duration<int, std::micro>::max();
512 bool wakeUpCalled = m_moreEvents.wait_for(lock, wait_time, [=] { return m_wakeUpCalled; });
513 return wakeUpCalled;
514 }
515#endif
516 Q_ASSERT(emscripten_is_main_runtime_thread());
517 Q_ASSERT(isMainThreadEventDispatcher());
518 if (useAsyncify()) {
519 if (timeout > 0)
520 qWarning() << "QEventDispatcherWasm asyncify wait with timeout is not supported; timeout will be ignored"; // FIXME
521
522 if (useJspi()) {
524 } else {
525 bool didSuspend = qt_asyncify_suspend();
526 if (!didSuspend) {
527 qWarning("QEventDispatcherWasm: current thread is already suspended; could not asyncify wait for events");
528 return false;
529 }
530 }
531 return true;
532 } else {
533 qWarning("QEventLoop::WaitForMoreEvents is not supported on the main thread without asyncify");
535 }
536 return false;
537}
538
539// Wakes a blocked/suspended event dispatcher thread. Returns true if the
540// thread is unblocked or was resumed, false if the thread state could not
541// be determined.
542bool QEventDispatcherWasm::wakeEventDispatcherThread()
543{
544#if QT_CONFIG(thread)
545 if (isSecondaryThreadEventDispatcher()) {
546 std::lock_guard<std::mutex> lock(m_mutex);
547 m_wakeUpCalled = true;
548 m_moreEvents.notify_one();
549 return true;
550 }
551#endif
552 Q_ASSERT(isMainThreadEventDispatcher());
553 if (useJspi()) {
554
555#if QT_CONFIG(thread)
556 return qstdweb::runTaskOnMainThread<bool>(
557 []() { return qt_jspi_can_resume_js() && qt_jspi_resume_js(); }, &g_proxyingQueue);
558#else
559 return qstdweb::runTaskOnMainThread<bool>(
560 []() { return qt_jspi_can_resume_js() && qt_jspi_resume_js(); });
561#endif
562
563 } else {
565 return false;
567 }
568 return true;
569}
570
571// Process event activation callbacks for the main thread event dispatcher.
572// Must be called on the main thread.
573void QEventDispatcherWasm::callProcessPostedEvents(void *context)
574{
575 Q_ASSERT(emscripten_is_main_runtime_thread());
576
577 // Bail out if Qt has been shut down.
578 if (!g_mainThreadEventDispatcher)
579 return;
580
581 // In the unlikely event that we get a callProcessPostedEvents() call for
582 // a previous main thread event dispatcher (i.e. the QApplication
583 // object was deleted and created again): just ignore it and return.
584 if (context != g_mainThreadEventDispatcher)
585 return;
586
587 {
588 LOCK_GUARD(g_mainThreadEventDispatcher->m_mutex);
589 g_mainThreadEventDispatcher->m_pendingProcessEvents = false;
590 }
591
592 g_mainThreadEventDispatcher->processPostedEvents();
593}
594
600
601void QEventDispatcherWasm::processTimers()
602{
603 m_timerInfo->activateTimers();
604 updateNativeTimer(); // schedule next native timer, if any
605}
606
607// Updates the native timer based on currently registered Qt timers.
608// Must be called on the event dispatcher thread.
609void QEventDispatcherWasm::updateNativeTimer()
610{
611#if QT_CONFIG(thread)
613#endif
614
615 // Multiplex Qt timers down to a single native timer, maintained
616 // to have a timeout corresponding to the shortest Qt timer. This
617 // is done in two steps: first determine the target wakeup time
618 // on the event dispatcher thread (since this thread has exclusive
619 // access to m_timerInfo), and then call native API to set the new
620 // wakeup time on the main thread.
621
622 const std::optional<std::chrono::nanoseconds> wait = m_timerInfo->timerWait();
623 const auto toWaitDuration = duration_cast<milliseconds>(wait.value_or(0ms));
624 const auto newTargetTimePoint = m_timerInfo->currentTime + toWaitDuration;
625 auto epochNsecs = newTargetTimePoint.time_since_epoch();
626 auto newTargetTime = std::chrono::duration_cast<std::chrono::milliseconds>(epochNsecs);
627 auto maintainNativeTimer = [this, wait, toWaitDuration, newTargetTime]() {
628 Q_ASSERT(emscripten_is_main_runtime_thread());
629
630 if (!wait) {
631 if (m_timerId > 0) {
632 emscripten_clear_timeout(m_timerId);
633 m_timerId = 0;
634 m_timerTargetTime = 0ms;
635 }
636 return;
637 }
638
639 if (m_timerTargetTime != 0ms && newTargetTime >= m_timerTargetTime)
640 return; // existing timer is good
641
642 qCDebug(lcEventDispatcherTimers)
643 << "Created new native timer with wait" << toWaitDuration.count() << "ms"
644 << "timeout" << newTargetTime.count() << "ms";
645 emscripten_clear_timeout(m_timerId);
646 m_timerId = emscripten_set_timeout(&QEventDispatcherWasm::callProcessTimers,
647 toWaitDuration.count(), this);
648 m_timerTargetTime = newTargetTime;
649 };
650
651 // Update the native timer for this thread/dispatcher. This must be
652 // done on the main thread where we have access to native API.
653 runOnMainThread([this, maintainNativeTimer]() {
654 Q_ASSERT(emscripten_is_main_runtime_thread());
655
656 // "this" may have been deleted, or may be about to be deleted.
657 // Check if the pointer we have is still a valid event dispatcher,
658 // and keep the mutex locked while updating the native timer to
659 // prevent it from being deleted.
660 LOCK_GUARD(g_staticDataMutex);
661 if (isValidEventDispatcherPointer(this))
662 maintainNativeTimer();
663 });
664}
665
666// Static timer activation callback. Must be called on the main thread
667// and will then either process timers on the main thread or wake and
668// process timers on a secondary thread.
669void QEventDispatcherWasm::callProcessTimers(void *context)
670{
671 Q_ASSERT(emscripten_is_main_runtime_thread());
672
673 // Note: "context" may be a stale pointer here,
674 // take care before casting and dereferencing!
675
676 // Process timers on this thread if this is the main event dispatcher
677 if (reinterpret_cast<QEventDispatcherWasm *>(context) == g_mainThreadEventDispatcher) {
678 g_mainThreadEventDispatcher->m_timerTargetTime = 0ms;
679 g_mainThreadEventDispatcher->processTimers();
680 return;
681 }
682
683 // Wake and process timers on the secondary thread if this a secondary thread dispatcher
684#if QT_CONFIG(thread)
685 std::lock_guard<std::mutex> lock(g_staticDataMutex);
686 if (g_secondaryThreadEventDispatchers.contains(context)) {
687 QEventDispatcherWasm *eventDispatcher = reinterpret_cast<QEventDispatcherWasm *>(context);
688 eventDispatcher->m_timerTargetTime = 0ms;
689 eventDispatcher->m_processTimers = true;
690 eventDispatcher->wakeUp();
691 }
692#endif
693}
694
695void QEventDispatcherWasm::setEmscriptenSocketCallbacks()
696{
697 qCDebug(lcEventDispatcher) << "setEmscriptenSocketCallbacks";
698
699 emscripten_set_socket_error_callback(nullptr, QEventDispatcherWasm::socketError);
700 emscripten_set_socket_open_callback(nullptr, QEventDispatcherWasm::socketOpen);
701 emscripten_set_socket_listen_callback(nullptr, QEventDispatcherWasm::socketListen);
702 emscripten_set_socket_connection_callback(nullptr, QEventDispatcherWasm::socketConnection);
703 emscripten_set_socket_message_callback(nullptr, QEventDispatcherWasm::socketMessage);
704 emscripten_set_socket_close_callback(nullptr, QEventDispatcherWasm::socketClose);
705}
706
707void QEventDispatcherWasm::clearEmscriptenSocketCallbacks()
708{
709 qCDebug(lcEventDispatcher) << "clearEmscriptenSocketCallbacks";
710
711 emscripten_set_socket_error_callback(nullptr, nullptr);
712 emscripten_set_socket_open_callback(nullptr, nullptr);
713 emscripten_set_socket_listen_callback(nullptr, nullptr);
714 emscripten_set_socket_connection_callback(nullptr, nullptr);
715 emscripten_set_socket_message_callback(nullptr, nullptr);
716 emscripten_set_socket_close_callback(nullptr, nullptr);
717}
718
719void QEventDispatcherWasm::socketError(int socket, int err, const char* msg, void *context)
720{
721 Q_UNUSED(err);
722 Q_UNUSED(msg);
724
725 // Emscripten makes socket callbacks while the main thread is busy-waiting for a mutex,
726 // which can cause deadlocks if the callback code also tries to lock the same mutex.
727 // This is most easily reproducible by adding print statements, where each print requires
728 // taking a mutex lock. Work around this by running the callback asynchronously, i.e. by using
729 // a native zero-timer, to make sure the main thread stack is completely unwond before calling
730 // the Qt handler.
731 // It is currently unclear if this problem is caused by code in Qt or in Emscripten, or
732 // if this completely fixes the problem.
733 runAsync([socket](){
734 auto notifiersRange = g_socketNotifiers.equal_range(socket);
735 std::vector<std::pair<int, QSocketNotifier *>> notifiers(notifiersRange.first, notifiersRange.second);
736 for (auto [_, notifier]: notifiers) {
738 }
739 setSocketState(socket, true, true);
740 });
741}
742
743void QEventDispatcherWasm::socketOpen(int socket, void *context)
744{
746
747 runAsync([socket](){
748 auto notifiersRange = g_socketNotifiers.equal_range(socket);
749 std::vector<std::pair<int, QSocketNotifier *>> notifiers(notifiersRange.first, notifiersRange.second);
750 for (auto [_, notifier]: notifiers) {
751 if (notifier->type() == QSocketNotifier::Write) {
753 }
754 }
755 setSocketState(socket, false, true);
756 });
757}
758
759void QEventDispatcherWasm::socketListen(int socket, void *context)
760{
763}
764
765void QEventDispatcherWasm::socketConnection(int socket, void *context)
766{
769}
770
771void QEventDispatcherWasm::socketMessage(int socket, void *context)
772{
774
775 runAsync([socket](){
776 auto notifiersRange = g_socketNotifiers.equal_range(socket);
777 std::vector<std::pair<int, QSocketNotifier *>> notifiers(notifiersRange.first, notifiersRange.second);
778 for (auto [_, notifier]: notifiers) {
779 if (notifier->type() == QSocketNotifier::Read) {
781 }
782 }
783 setSocketState(socket, true, false);
784 });
785}
786
787void QEventDispatcherWasm::socketClose(int socket, void *context)
788{
790
791 // Emscripten makes emscripten_set_socket_close_callback() calls to socket 0,
792 // which is not a valid socket. see https://github.com/emscripten-core/emscripten/issues/6596
793 if (socket == 0)
794 return;
795
796 runAsync([socket](){
797 auto notifiersRange = g_socketNotifiers.equal_range(socket);
798 std::vector<std::pair<int, QSocketNotifier *>> notifiers(notifiersRange.first, notifiersRange.second);
799 for (auto [_, notifier]: notifiers)
800 QCoreApplication::postEvent(notifier, new QEvent(QEvent::SockClose));
801
802 setSocketState(socket, true, true);
803 clearSocketState(socket);
804 });
805}
806
807void QEventDispatcherWasm::setSocketState(int socket, bool setReadyRead, bool setReadyWrite)
808{
809 LOCK_GUARD(g_staticDataMutex);
810 SocketReadyState &state = g_socketState[socket];
811
812 // Additively update socket ready state, e.g. if it
813 // was already ready read then it stays ready read.
814 state.readyRead |= setReadyRead;
815 state.readyWrite |= setReadyWrite;
816
817 // Wake any waiters for the given readiness. The waiter consumes
818 // the ready state, returning the socket to not-ready.
819 if (QEventDispatcherWasm *waiter = state.waiter)
820 if ((state.readyRead && state.waitForReadyRead) || (state.readyWrite && state.waitForReadyWrite))
821 waiter->wakeEventDispatcherThread();
822}
823
824void QEventDispatcherWasm::clearSocketState(int socket)
825{
826 LOCK_GUARD(g_staticDataMutex);
827 g_socketState.erase(socket);
828}
829
830void QEventDispatcherWasm::waitForSocketState(int timeout, int socket, bool checkRead, bool checkWrite,
831 bool *selectForRead, bool *selectForWrite, bool *socketDisconnect)
832{
833 // Loop until the socket becomes readyRead or readyWrite. Wait for
834 // socket activity if it currently is neither.
835 while (true) {
836 *selectForRead = false;
837 *selectForWrite = false;
838
839 {
840 LOCK_GUARD(g_staticDataMutex);
841
842 // Access or create socket state: we want to register that a thread is waitng
843 // even if we have not received any socket callbacks yet.
844 SocketReadyState &state = g_socketState[socket];
845 if (state.waiter) {
846 qWarning() << "QEventDispatcherWasm::waitForSocketState: a thread is already waiting";
847 break;
848 }
849
850 bool shouldWait = true;
851 if (checkRead && state.readyRead) {
852 shouldWait = false;
853 state.readyRead = false;
854 *selectForRead = true;
855 }
856 if (checkWrite && state.readyWrite) {
857 shouldWait = false;
858 state.readyWrite = false;
859 *selectForRead = true;
860 }
861 if (!shouldWait)
862 break;
863
864 state.waiter = this;
865 state.waitForReadyRead = checkRead;
866 state.waitForReadyWrite = checkWrite;
867 }
868
869 bool didTimeOut = !wait(timeout);
870 {
871 LOCK_GUARD(g_staticDataMutex);
872
873 // Missing socket state after a wakeup means that the socket has been closed.
874 auto it = g_socketState.find(socket);
875 if (it == g_socketState.end()) {
876 *socketDisconnect = true;
877 break;
878 }
879 it->second.waiter = nullptr;
880 it->second.waitForReadyRead = false;
881 it->second.waitForReadyWrite = false;
882 }
883
884 if (didTimeOut)
885 break;
886 }
887}
888
889void QEventDispatcherWasm::socketSelect(int timeout, int socket, bool waitForRead, bool waitForWrite,
890 bool *selectForRead, bool *selectForWrite, bool *socketDisconnect)
891{
892 QEventDispatcherWasm *eventDispatcher = static_cast<QEventDispatcherWasm *>(
894
895 if (!eventDispatcher) {
896 qWarning("QEventDispatcherWasm::socketSelect called without eventdispatcher instance");
897 return;
898 }
899
900 eventDispatcher->waitForSocketState(timeout, socket, waitForRead, waitForWrite,
901 selectForRead, selectForWrite, socketDisconnect);
902}
903
904namespace {
905 int g_startupTasks = 0;
906}
907
908// The following functions manages sending the "qtLoaded" event/callback
909// from qtloader.js on startup, once Qt initialization has been completed
910// and the application is ready to display the first frame. This can be
911// either as soon as the event loop is running, or later, if additional
912// startup tasks (e.g. local font loading) have been registered.
913
915{
916 ++g_startupTasks;
917}
918
920{
921 --g_startupTasks;
923}
924
926{
927 if (g_startupTasks > 0)
928 return;
929
930 static bool qtLoadedCalled = false;
931 if (qtLoadedCalled)
932 return;
933 qtLoadedCalled = true;
934
935 Q_ASSERT(g_mainThreadEventDispatcher);
936 g_mainThreadEventDispatcher->onLoaded();
937}
938
940{
941 // TODO: call qtloader.js onLoaded from here, in order to delay
942 // hiding the "Loading..." message until the app is ready to paint
943 // the first frame. Currently onLoaded must be called early before
944 // main() in order to ensure that the screen/container elements
945 // have valid geometry at startup.
946}
947
948namespace {
949 void trampoline(void *context) {
950
951 auto async_fn = [](void *context){
952 std::function<void(void)> *fn = reinterpret_cast<std::function<void(void)> *>(context);
953 (*fn)();
954 delete fn;
955 };
956
957 emscripten_async_call(async_fn, context, 0);
958 }
959}
960
961// Runs a function right away
962void QEventDispatcherWasm::run(std::function<void(void)> fn)
963{
964 fn();
965}
966
967void QEventDispatcherWasm::runOnMainThread(std::function<void(void)> fn)
968{
969#if QT_CONFIG(thread)
970 qstdweb::runTaskOnMainThread<void>(fn, &g_proxyingQueue);
971#else
972 qstdweb::runTaskOnMainThread<void>(fn);
973#endif
974}
975
976// Runs a function asynchronously. Main thread only.
977void QEventDispatcherWasm::runAsync(std::function<void(void)> fn)
978{
979 trampoline(new std::function<void(void)>(fn));
980}
981
982// Runs a function on the main thread. The function always runs asynchronously,
983// also if the calling thread is the main thread.
984void QEventDispatcherWasm::runOnMainThreadAsync(std::function<void(void)> fn)
985{
986 void *context = new std::function<void(void)>(fn);
987#if QT_CONFIG(thread)
988 if (!emscripten_is_main_runtime_thread()) {
989 g_proxyingQueue.proxyAsync(g_mainThread, [context]{
990 trampoline(context);
991 });
992 return;
993 }
994#endif
995 trampoline(context);
996}
997
999
1000#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.
std::chrono::nanoseconds Duration
A {std::chrono::duration} type that is used in various API in this class.
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
bool unregisterTimer(Qt::TimerId timerId) override final
void registerSocketNotifier(QSocketNotifier *notifier) override
Registers notifier with the event loop.
void registerTimer(Qt::TimerId timerId, Duration interval, Qt::TimerType timerType, QObject *object) override final
Duration remainingTime(Qt::TimerId timerId) const override final
Returns the remaining time of the timer with the given timerId.
bool processEvents(QEventLoop::ProcessEventsFlags flags) override
Processes pending events that match flags until there are no more events to process.
QList< TimerInfoV2 > timersForObject(QObject *object) const override final
bool unregisterTimers(QObject *object) override final
Unregisters all the timers associated with the given object.
static void runOnMainThread(std::function< void(void)> fn)
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
\inmodule QtCore
Definition qobject.h:103
QThread * thread() const
Returns the thread in which the object lives.
Definition qobject.cpp:1598
\inmodule QtCore
static QThread * currentThread()
Definition qthread.cpp:1039
bool unregisterTimer(Qt::TimerId timerId)
std::chrono::steady_clock::time_point currentTime
Duration remainingDuration(Qt::TimerId timerId) const
bool unregisterTimers(QObject *object)
std::optional< Duration > timerWait()
void registerTimer(Qt::TimerId timerId, Duration interval, Qt::TimerType timerType, QObject *object)
QList< TimerInfo > registeredTimers(QObject *object) const
QSet< QString >::iterator it
else opt state
[0]
Combined button and popup list for selecting options.
TimerType
bool haveJspi()
Definition qstdweb.cpp:823
bool haveAsyncify()
Definition qstdweb.cpp:829
static void * context
static QT_BEGIN_NAMESPACE bool await(IAsyncOperation< T > &&asyncInfo, T &result, uint timeout=0)
Q_CONSTRUCTOR_FUNCTION(qt_apple_check_os_version)
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()
static bool useJspi()
void qt_asyncify_resume()
void qt_asyncify_suspend_js()
static bool g_is_asyncify_suspended
bool qt_jspi_can_resume_js()
bool qt_jspi_resume_js()
#define LOCK_GUARD(M)
void qt_jspi_suspend_js()
#define qWarning
Definition qlogging.h:166
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
GLuint object
[3]
GLbitfield GLuint64 timeout
[4]
GLbitfield flags
static const QQmlJSScope * resolve(const QQmlJSScope *current, const QStringList &names)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define emit
#define Q_UNUSED(x)
QT_BEGIN_NAMESPACE constexpr std::underlying_type_t< Enum > qToUnderlying(Enum e) noexcept
const bool wasBlocked
[52]
QTcpSocket * socket
[1]
QReadWriteLock lock
[0]
socketLayer waitForWrite()