diff options
Diffstat (limited to 'src/corelib/kernel/qeventdispatcher_wasm.cpp')
-rw-r--r-- | src/corelib/kernel/qeventdispatcher_wasm.cpp | 433 |
1 files changed, 294 insertions, 139 deletions
diff --git a/src/corelib/kernel/qeventdispatcher_wasm.cpp b/src/corelib/kernel/qeventdispatcher_wasm.cpp index c733f46c14..4aa435b64b 100644 --- a/src/corelib/kernel/qeventdispatcher_wasm.cpp +++ b/src/corelib/kernel/qeventdispatcher_wasm.cpp @@ -7,12 +7,16 @@ #include <QtCore/qcoreapplication.h> #include <QtCore/qthread.h> #include <QtCore/qsocketnotifier.h> +#include <QtCore/private/qstdweb_p.h> #include "emscripten.h" #include <emscripten/html5.h> #include <emscripten/threading.h> #include <emscripten/val.h> +using namespace std::chrono; +using namespace std::chrono_literals; + QT_BEGIN_NAMESPACE // using namespace emscripten; @@ -26,18 +30,70 @@ Q_LOGGING_CATEGORY(lcEventDispatcherTimers, "qt.eventdispatcher.timers"); #define LOCK_GUARD(M) #endif -#ifdef QT_HAVE_EMSCRIPTEN_ASYNCIFY - // Emscripten asyncify currently supports one level of suspend - // recursion is not permitted. We track the suspend state here // on order to fail (more) gracefully, but we can of course only // track Qts own usage of asyncify. static bool g_is_asyncify_suspended = false; +#if defined(QT_STATIC) + +static bool useAsyncify() +{ + return qstdweb::haveAsyncify(); +} + +static bool useJspi() +{ + return qstdweb::haveJspi(); +} + +// clang-format off +EM_ASYNC_JS(void, qt_jspi_suspend_js, (), { + ++Module.qtJspiSuspensionCounter; + + await new Promise(resolve => { + Module.qtAsyncifyWakeUp.push(resolve); + }); +}); + +EM_JS(bool, qt_jspi_resume_js, (), { + if (!Module.qtJspiSuspensionCounter) + return false; + + --Module.qtJspiSuspensionCounter; + + setTimeout(() => { + const wakeUp = (Module.qtAsyncifyWakeUp ?? []).pop(); + if (wakeUp) wakeUp(); + }); + return true; +}); + +EM_JS(bool, qt_jspi_can_resume_js, (), { + return Module.qtJspiSuspensionCounter > 0; +}); + +EM_JS(void, init_jspi_support_js, (), { + Module.qtAsyncifyWakeUp = []; + Module.qtJspiSuspensionCounter = 0; +}); +// clang-format on + +void initJspiSupport() { + init_jspi_support_js(); +} + +Q_CONSTRUCTOR_FUNCTION(initJspiSupport); + +// clang-format off EM_JS(void, qt_asyncify_suspend_js, (), { + if (Module.qtSuspendId === undefined) + Module.qtSuspendId = 0; let sleepFn = (wakeUp) => { Module.qtAsyncifyWakeUp = wakeUp; }; + ++Module.qtSuspendId; return Asyncify.handleSleep(sleepFn); }); @@ -46,11 +102,61 @@ EM_JS(void, qt_asyncify_resume_js, (), { if (wakeUp == undefined) return; Module.qtAsyncifyWakeUp = undefined; + const suspendId = Module.qtSuspendId; // Delayed wakeup with zero-timer. Workaround/fix for // https://github.com/emscripten-core/emscripten/issues/10515 - setTimeout(wakeUp); + setTimeout(() => { + // Another suspend occurred while the timeout was in queue. + if (Module.qtSuspendId !== suspendId) + return; + wakeUp(); + }); }); +// clang-format on + +#else + +// EM_JS is not supported for side modules; disable asyncify + +static bool useAsyncify() +{ + return false; +} + +static bool useJspi() +{ + return false; +} + +void qt_jspi_suspend_js() +{ + Q_UNREACHABLE(); +} + +bool qt_jspi_resume_js() +{ + Q_UNREACHABLE(); + return false; +} + +bool qt_jspi_can_resume_js() +{ + Q_UNREACHABLE(); + return false; +} + +void qt_asyncify_suspend_js() +{ + Q_UNREACHABLE(); +} + +void qt_asyncify_resume_js() +{ + Q_UNREACHABLE(); +} + +#endif // defined(QT_STATIC) // Suspends the main thread until qt_asyncify_resume() is called. Returns // false immediately if Qt has already suspended the main thread (recursive @@ -67,39 +173,27 @@ bool qt_asyncify_suspend() // Wakes any currently suspended main thread. Returns true if the main // thread was suspended, in which case it will now be asynchronously woken. -bool qt_asyncify_resume() +void qt_asyncify_resume() { if (!g_is_asyncify_suspended) - return false; + return; g_is_asyncify_suspended = false; qt_asyncify_resume_js(); - return true; } -// Yields control to the browser, so that it can process events. Must -// be called on the main thread. Returns false immediately if Qt has -// already suspended the main thread. Returns true after yielding. -bool qt_asyncify_yield() -{ - if (g_is_asyncify_suspended) - return false; - emscripten_sleep(0); - return true; -} - -#endif // QT_HAVE_EMSCRIPTEN_ASYNCIFY Q_CONSTINIT QEventDispatcherWasm *QEventDispatcherWasm::g_mainThreadEventDispatcher = nullptr; #if QT_CONFIG(thread) Q_CONSTINIT QVector<QEventDispatcherWasm *> QEventDispatcherWasm::g_secondaryThreadEventDispatchers; Q_CONSTINIT std::mutex QEventDispatcherWasm::g_staticDataMutex; +emscripten::ProxyingQueue QEventDispatcherWasm::g_proxyingQueue; +pthread_t QEventDispatcherWasm::g_mainThread; #endif // ### dynamic initialization: std::multimap<int, QSocketNotifier *> QEventDispatcherWasm::g_socketNotifiers; std::map<int, QEventDispatcherWasm::SocketReadyState> QEventDispatcherWasm::g_socketState; QEventDispatcherWasm::QEventDispatcherWasm() - : QAbstractEventDispatcher() { // QEventDispatcherWasm operates in two main modes: // - On the main thread: @@ -121,6 +215,15 @@ QEventDispatcherWasm::QEventDispatcherWasm() // dispatchers so we set a global pointer to it. Q_ASSERT(g_mainThreadEventDispatcher == nullptr); g_mainThreadEventDispatcher = this; +#if QT_CONFIG(thread) + g_mainThread = pthread_self(); +#endif + + // Call the "onLoaded" JavaScript callback, unless startup tasks + // have been registered which should complete first. Run async + // to make sure event dispatcher construction (in particular any + // subclass construction) has completed first. + runAsync(callOnLoadedIfRequired); } else { #if QT_CONFIG(thread) std::lock_guard<std::mutex> lock(g_staticDataMutex); @@ -175,7 +278,6 @@ bool QEventDispatcherWasm::isValidEventDispatcherPointer(QEventDispatcherWasm *e if (eventDispatcher == g_mainThreadEventDispatcher) return true; #if QT_CONFIG(thread) - Q_ASSERT(!g_staticDataMutex.try_lock()); // caller must lock mutex if (g_secondaryThreadEventDispatchers.contains(eventDispatcher)) return true; #endif @@ -184,12 +286,9 @@ bool QEventDispatcherWasm::isValidEventDispatcherPointer(QEventDispatcherWasm *e bool QEventDispatcherWasm::processEvents(QEventLoop::ProcessEventsFlags flags) { - emit awake(); + qCDebug(lcEventDispatcher) << "QEventDispatcherWasm::processEvents flags" << flags; - bool hasPendingEvents = qGlobalPostedEventsCount() > 0; - - qCDebug(lcEventDispatcher) << "QEventDispatcherWasm::processEvents flags" << flags - << "pending events" << hasPendingEvents; + emit awake(); if (isMainThreadEventDispatcher()) { if (flags & QEventLoop::DialogExec) @@ -198,33 +297,36 @@ bool QEventDispatcherWasm::processEvents(QEventLoop::ProcessEventsFlags flags) handleApplicationExec(); } - if (!(flags & QEventLoop::ExcludeUserInputEvents)) - pollForNativeEvents(); +#if QT_CONFIG(thread) + { + // Reset wakeUp state: if wakeUp() was called at some point before + // this then processPostedEvents() below will service that call. + std::unique_lock<std::mutex> lock(m_mutex); + m_wakeUpCalled = false; + } +#endif - hasPendingEvents = qGlobalPostedEventsCount() > 0; + processPostedEvents(); - if (!hasPendingEvents && (flags & QEventLoop::WaitForMoreEvents)) - wait(); + // The processPostedEvents() call above may process an event which deletes the + // application object and the event dispatcher; stop event processing in that case. + if (!isValidEventDispatcherPointer(this)) + return false; if (m_interrupted) { m_interrupted = false; return false; } + if (flags & QEventLoop::WaitForMoreEvents) + wait(); + if (m_processTimers) { m_processTimers = false; processTimers(); } - hasPendingEvents = qGlobalPostedEventsCount() > 0; - QCoreApplication::sendPostedEvents(); - processWindowSystemEvents(flags); - return hasPendingEvents; -} - -void QEventDispatcherWasm::processWindowSystemEvents(QEventLoop::ProcessEventsFlags flags) -{ - Q_UNUSED(flags); + return false; } void QEventDispatcherWasm::registerSocketNotifier(QSocketNotifier *notifier) @@ -234,7 +336,7 @@ void QEventDispatcherWasm::registerSocketNotifier(QSocketNotifier *notifier) bool wasEmpty = g_socketNotifiers.empty(); g_socketNotifiers.insert({notifier->socket(), notifier}); if (wasEmpty) - runOnMainThread([]{ setEmscriptenSocketCallbacks(); }); + runOnMainThread([] { setEmscriptenSocketCallbacks(); }); } void QEventDispatcherWasm::unregisterSocketNotifier(QSocketNotifier *notifier) @@ -250,13 +352,13 @@ void QEventDispatcherWasm::unregisterSocketNotifier(QSocketNotifier *notifier) } if (g_socketNotifiers.empty()) - runOnMainThread([]{ clearEmscriptenSocketCallbacks(); }); + runOnMainThread([] { clearEmscriptenSocketCallbacks(); }); } -void QEventDispatcherWasm::registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *object) +void QEventDispatcherWasm::registerTimer(Qt::TimerId timerId, Duration interval, Qt::TimerType timerType, QObject *object) { #ifndef QT_NO_DEBUG - if (timerId < 1 || interval < 0 || !object) { + if (qToUnderlying(timerId) < 1 || interval < 0ns || !object) { qWarning("QEventDispatcherWasm::registerTimer: invalid arguments"); return; } else if (object->thread() != thread() || thread() != QThread::currentThread()) { @@ -265,16 +367,16 @@ void QEventDispatcherWasm::registerTimer(int timerId, qint64 interval, Qt::Timer return; } #endif - qCDebug(lcEventDispatcherTimers) << "registerTimer" << timerId << interval << timerType << object; + qCDebug(lcEventDispatcherTimers) << "registerTimer" << int(timerId) << interval << timerType << object; m_timerInfo->registerTimer(timerId, interval, timerType, object); updateNativeTimer(); } -bool QEventDispatcherWasm::unregisterTimer(int timerId) +bool QEventDispatcherWasm::unregisterTimer(Qt::TimerId timerId) { #ifndef QT_NO_DEBUG - if (timerId < 1) { + if (qToUnderlying(timerId) < 1) { qWarning("QEventDispatcherWasm::unregisterTimer: invalid argument"); return false; } else if (thread() != QThread::currentThread()) { @@ -284,7 +386,7 @@ bool QEventDispatcherWasm::unregisterTimer(int timerId) } #endif - qCDebug(lcEventDispatcherTimers) << "unregisterTimer" << timerId; + qCDebug(lcEventDispatcherTimers) << "unregisterTimer" << int(timerId); bool ans = m_timerInfo->unregisterTimer(timerId); updateNativeTimer(); @@ -311,22 +413,22 @@ bool QEventDispatcherWasm::unregisterTimers(QObject *object) return ans; } -QList<QAbstractEventDispatcher::TimerInfo> -QEventDispatcherWasm::registeredTimers(QObject *object) const +QList<QAbstractEventDispatcher::TimerInfoV2> +QEventDispatcherWasm::timersForObject(QObject *object) const { #ifndef QT_NO_DEBUG if (!object) { qWarning("QEventDispatcherWasm:registeredTimers: invalid argument"); - return QList<TimerInfo>(); + return {}; } #endif return m_timerInfo->registeredTimers(object); } -int QEventDispatcherWasm::remainingTime(int timerId) +QEventDispatcherWasm::Duration QEventDispatcherWasm::remainingTime(Qt::TimerId timerId) const { - return m_timerInfo->timerRemainingTime(timerId); + return m_timerInfo->remainingDuration(timerId); } void QEventDispatcherWasm::interrupt() @@ -342,7 +444,9 @@ void QEventDispatcherWasm::wakeUp() // event loop. Make sure the thread is unblocked or make it // process events. bool wasBlocked = wakeEventDispatcherThread(); - if (!wasBlocked && isMainThreadEventDispatcher()) { + // JSPI does not need a scheduled call to processPostedEvents, as the stack is not unwound + // at startup. + if (!qstdweb::haveJspi() && !wasBlocked && isMainThreadEventDispatcher()) { { LOCK_GUARD(m_mutex); if (m_pendingProcessEvents) @@ -350,7 +454,7 @@ void QEventDispatcherWasm::wakeUp() m_pendingProcessEvents = true; } runOnMainThreadAsync([this](){ - QEventDispatcherWasm::callProcessEvents(this); + QEventDispatcherWasm::callProcessPostedEvents(this); }); } } @@ -365,42 +469,26 @@ void QEventDispatcherWasm::handleApplicationExec() // Note that we don't use asyncify here: Emscripten supports one level of // asyncify only and we want to reserve that for dialog exec() instead of // using it for the one qApp exec(). - const bool simulateInfiniteLoop = true; - emscripten_set_main_loop([](){ - emscripten_pause_main_loop(); - }, 0, simulateInfiniteLoop); + // When JSPI is used, awaited async calls are allowed to be nested, so we + // proceed normally. + if (!qstdweb::haveJspi()) { + const bool simulateInfiniteLoop = true; + emscripten_set_main_loop([](){ + emscripten_pause_main_loop(); + }, 0, simulateInfiniteLoop); + } } void QEventDispatcherWasm::handleDialogExec() { -#ifndef QT_HAVE_EMSCRIPTEN_ASYNCIFY - qWarning() << "Warning: dialog exec() is not supported on Qt for WebAssembly in this" - << "configuration. Please use show() instead, or enable experimental support" - << "for asyncify.\n" - << "When using exec() (without asyncify) the dialog will show, the user can interact" - << "with it and the appropriate signals will be emitted on close. However, the" - << "exec() call never returns, stack content at the time of the exec() call" - << "is leaked, and the exec() call may interfere with input event processing"; - emscripten_sleep(1); // This call never returns -#endif + if (!useAsyncify()) { + qWarning() << "Warning: exec() is not supported on Qt for WebAssembly in this configuration. Please build" + << "with asyncify support, or use an asynchronous API like QDialog::open()"; + emscripten_sleep(1); // This call never returns + } // For the asyncify case we do nothing here and wait for events in wait() } -void QEventDispatcherWasm::pollForNativeEvents() -{ - // Secondary thread event dispatchers do not support native events - if (isSecondaryThreadEventDispatcher()) - return; - -#if HAVE_EMSCRIPTEN_ASYNCIFY - // Asyncify allows us to yield to the browser and have it process native events - - // but this will fail if we are recursing and are already in a yield. - bool didYield = qt_asyncify_yield(); - if (!didYield) - qWarning("QEventDispatcherWasm::processEvents() did not asyncify process native events"); -#endif -} - // Blocks/suspends the calling thread. This is possible in two cases: // - Caller is a secondary thread: block on m_moreEvents // - Caller is the main thread and asyncify is enabled: suspend using qt_asyncify_suspend() @@ -414,7 +502,12 @@ bool QEventDispatcherWasm::wait(int timeout) if (isSecondaryThreadEventDispatcher()) { std::unique_lock<std::mutex> lock(m_mutex); - m_wakeUpCalled = false; + // If wakeUp() was called there might be pending events in the event + // queue which should be processed. Don't block, instead return + // so that the event loop can spin and call processEvents() again. + if (m_wakeUpCalled) + return true; + auto wait_time = timeout > 0 ? timeout * 1ms : std::chrono::duration<int, std::micro>::max(); bool wakeUpCalled = m_moreEvents.wait_for(lock, wait_time, [=] { return m_wakeUpCalled; }); return wakeUpCalled; @@ -422,20 +515,24 @@ bool QEventDispatcherWasm::wait(int timeout) #endif Q_ASSERT(emscripten_is_main_runtime_thread()); Q_ASSERT(isMainThreadEventDispatcher()); -#ifdef QT_HAVE_EMSCRIPTEN_ASYNCIFY - if (timeout > 0) - qWarning() << "QEventDispatcherWasm asyncify wait with timeout is not supported; timeout will be ignored"; // FIXME - - bool didSuspend = qt_asyncify_suspend(); - if (!didSuspend) { - qWarning("QEventDispatcherWasm: current thread is already suspended; could not asyncify wait for events"); - return false; + if (useAsyncify()) { + if (timeout > 0) + qWarning() << "QEventDispatcherWasm asyncify wait with timeout is not supported; timeout will be ignored"; // FIXME + + if (useJspi()) { + qt_jspi_suspend_js(); + } else { + bool didSuspend = qt_asyncify_suspend(); + if (!didSuspend) { + qWarning("QEventDispatcherWasm: current thread is already suspended; could not asyncify wait for events"); + return false; + } + } + return true; + } else { + qWarning("QEventLoop::WaitForMoreEvents is not supported on the main thread without asyncify"); + Q_UNUSED(timeout); } - return true; -#else - qWarning("QEventLoop::WaitForMoreEvents is not supported on the main thread without asyncify"); - Q_UNUSED(timeout); -#endif return false; } @@ -453,18 +550,27 @@ bool QEventDispatcherWasm::wakeEventDispatcherThread() } #endif Q_ASSERT(isMainThreadEventDispatcher()); -#ifdef QT_HAVE_EMSCRIPTEN_ASYNCIFY - if (g_is_asyncify_suspended) { - runOnMainThread([]{ qt_asyncify_resume(); }); - return true; - } + if (useJspi()) { + +#if QT_CONFIG(thread) + return qstdweb::runTaskOnMainThread<bool>( + []() { return qt_jspi_can_resume_js() && qt_jspi_resume_js(); }, &g_proxyingQueue); +#else + return qstdweb::runTaskOnMainThread<bool>( + []() { return qt_jspi_can_resume_js() && qt_jspi_resume_js(); }); #endif - return false; + + } else { + if (!g_is_asyncify_suspended) + return false; + runOnMainThread([]() { qt_asyncify_resume(); }); + } + return true; } // Process event activation callbacks for the main thread event dispatcher. // Must be called on the main thread. -void QEventDispatcherWasm::callProcessEvents(void *context) +void QEventDispatcherWasm::callProcessPostedEvents(void *context) { Q_ASSERT(emscripten_is_main_runtime_thread()); @@ -472,7 +578,7 @@ void QEventDispatcherWasm::callProcessEvents(void *context) if (!g_mainThreadEventDispatcher) return; - // In the unlikely event that we get a callProcessEvents() call for + // In the unlikely event that we get a callProcessPostedEvents() call for // a previous main thread event dispatcher (i.e. the QApplication // object was deleted and created again): just ignore it and return. if (context != g_mainThreadEventDispatcher) @@ -482,7 +588,14 @@ void QEventDispatcherWasm::callProcessEvents(void *context) LOCK_GUARD(g_mainThreadEventDispatcher->m_mutex); g_mainThreadEventDispatcher->m_pendingProcessEvents = false; } - g_mainThreadEventDispatcher->processEvents(QEventLoop::AllEvents); + + g_mainThreadEventDispatcher->processPostedEvents(); +} + +bool QEventDispatcherWasm::processPostedEvents() +{ + QCoreApplication::sendPostedEvents(); + return false; } void QEventDispatcherWasm::processTimers() @@ -506,33 +619,32 @@ void QEventDispatcherWasm::updateNativeTimer() // access to m_timerInfo), and then call native API to set the new // wakeup time on the main thread. - auto timespecToNanosec = [](timespec ts) -> uint64_t { - return ts.tv_sec * 1000 + ts.tv_nsec / (1000 * 1000); - }; - timespec toWait; - bool hasTimer = m_timerInfo->timerWait(toWait); - uint64_t currentTime = timespecToNanosec(m_timerInfo->currentTime); - uint64_t toWaitDuration = timespecToNanosec(toWait); - uint64_t newTargetTime = currentTime + toWaitDuration; - - auto maintainNativeTimer = [this, hasTimer, toWaitDuration, newTargetTime]() { + const std::optional<std::chrono::nanoseconds> wait = m_timerInfo->timerWait(); + const auto toWaitDuration = duration_cast<milliseconds>(wait.value_or(0ms)); + const auto newTargetTimePoint = m_timerInfo->currentTime + toWaitDuration; + auto epochNsecs = newTargetTimePoint.time_since_epoch(); + auto newTargetTime = std::chrono::duration_cast<std::chrono::milliseconds>(epochNsecs); + auto maintainNativeTimer = [this, wait, toWaitDuration, newTargetTime]() { Q_ASSERT(emscripten_is_main_runtime_thread()); - if (!hasTimer) { + if (!wait) { if (m_timerId > 0) { emscripten_clear_timeout(m_timerId); m_timerId = 0; + m_timerTargetTime = 0ms; } return; } - if (m_timerTargetTime != 0 && newTargetTime >= m_timerTargetTime) + if (m_timerTargetTime != 0ms && newTargetTime >= m_timerTargetTime) return; // existing timer is good qCDebug(lcEventDispatcherTimers) - << "Created new native timer with wait" << toWaitDuration << "timeout" << newTargetTime; + << "Created new native timer with wait" << toWaitDuration.count() << "ms" + << "timeout" << newTargetTime.count() << "ms"; emscripten_clear_timeout(m_timerId); - m_timerId = emscripten_set_timeout(&QEventDispatcherWasm::callProcessTimers, toWaitDuration, this); + m_timerId = emscripten_set_timeout(&QEventDispatcherWasm::callProcessTimers, + toWaitDuration.count(), this); m_timerTargetTime = newTargetTime; }; @@ -546,8 +658,8 @@ void QEventDispatcherWasm::updateNativeTimer() // and keep the mutex locked while updating the native timer to // prevent it from being deleted. LOCK_GUARD(g_staticDataMutex); - if (isValidEventDispatcherPointer(this)) - maintainNativeTimer(); + if (isValidEventDispatcherPointer(this)) + maintainNativeTimer(); }); } @@ -563,7 +675,7 @@ void QEventDispatcherWasm::callProcessTimers(void *context) // Process timers on this thread if this is the main event dispatcher if (reinterpret_cast<QEventDispatcherWasm *>(context) == g_mainThreadEventDispatcher) { - g_mainThreadEventDispatcher->m_timerTargetTime = 0; + g_mainThreadEventDispatcher->m_timerTargetTime = 0ms; g_mainThreadEventDispatcher->processTimers(); return; } @@ -573,7 +685,7 @@ void QEventDispatcherWasm::callProcessTimers(void *context) std::lock_guard<std::mutex> lock(g_staticDataMutex); if (g_secondaryThreadEventDispatchers.contains(context)) { QEventDispatcherWasm *eventDispatcher = reinterpret_cast<QEventDispatcherWasm *>(context); - eventDispatcher->m_timerTargetTime = 0; + eventDispatcher->m_timerTargetTime = 0ms; eventDispatcher->m_processTimers = true; eventDispatcher->wakeUp(); } @@ -790,6 +902,50 @@ void QEventDispatcherWasm::socketSelect(int timeout, int socket, bool waitForRea } namespace { + int g_startupTasks = 0; +} + +// The following functions manages sending the "qtLoaded" event/callback +// from qtloader.js on startup, once Qt initialization has been completed +// and the application is ready to display the first frame. This can be +// either as soon as the event loop is running, or later, if additional +// startup tasks (e.g. local font loading) have been registered. + +void QEventDispatcherWasm::registerStartupTask() +{ + ++g_startupTasks; +} + +void QEventDispatcherWasm::completeStarupTask() +{ + --g_startupTasks; + callOnLoadedIfRequired(); +} + +void QEventDispatcherWasm::callOnLoadedIfRequired() +{ + if (g_startupTasks > 0) + return; + + static bool qtLoadedCalled = false; + if (qtLoadedCalled) + return; + qtLoadedCalled = true; + + Q_ASSERT(g_mainThreadEventDispatcher); + g_mainThreadEventDispatcher->onLoaded(); +} + +void QEventDispatcherWasm::onLoaded() +{ + // TODO: call qtloader.js onLoaded from here, in order to delay + // hiding the "Loading..." message until the app is ready to paint + // the first frame. Currently onLoaded must be called early before + // main() in order to ensure that the screen/container elements + // have valid geometry at startup. +} + +namespace { void trampoline(void *context) { auto async_fn = [](void *context){ @@ -808,24 +964,19 @@ void QEventDispatcherWasm::run(std::function<void(void)> fn) fn(); } -// Runs a function asynchronously. Main thread only. -void QEventDispatcherWasm::runAsync(std::function<void(void)> fn) -{ - trampoline(new std::function<void(void)>(fn)); -} - -// Runs a function on the main thread. The function runs synchronusly if -// the calling thread is then main thread. void QEventDispatcherWasm::runOnMainThread(std::function<void(void)> fn) { #if QT_CONFIG(thread) - if (!emscripten_is_main_runtime_thread()) { - void *context = new std::function<void(void)>(fn); - emscripten_async_run_in_main_runtime_thread_(EM_FUNC_SIG_VI, reinterpret_cast<void *>(trampoline), context); - return; - } + qstdweb::runTaskOnMainThread<void>(fn, &g_proxyingQueue); +#else + qstdweb::runTaskOnMainThread<void>(fn); #endif - fn(); +} + +// Runs a function asynchronously. Main thread only. +void QEventDispatcherWasm::runAsync(std::function<void(void)> fn) +{ + trampoline(new std::function<void(void)>(fn)); } // Runs a function on the main thread. The function always runs asynchronously, @@ -835,7 +986,9 @@ void QEventDispatcherWasm::runOnMainThreadAsync(std::function<void(void)> fn) void *context = new std::function<void(void)>(fn); #if QT_CONFIG(thread) if (!emscripten_is_main_runtime_thread()) { - emscripten_async_run_in_main_runtime_thread_(EM_FUNC_SIG_VI, reinterpret_cast<void *>(trampoline), context); + g_proxyingQueue.proxyAsync(g_mainThread, [context]{ + trampoline(context); + }); return; } #endif @@ -843,3 +996,5 @@ void QEventDispatcherWasm::runOnMainThreadAsync(std::function<void(void)> fn) } QT_END_NAMESPACE + +#include "moc_qeventdispatcher_wasm_p.cpp" |