summaryrefslogtreecommitdiffstats
path: root/src/corelib/kernel/qeventdispatcher_wasm.cpp
blob: 0d1b2553fa0c90d279c63750c089257d1c932d94 (plain)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
// Copyright (C) 2021 The Qt Company Ltd.// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only#include"qeventdispatcher_wasm_p.h"#include <QtCore/qcoreapplication.h>#include <QtCore/qthread.h>#include <QtCore/qscopedvaluerollback.h>#include <QtCore/private/qobject_p.h>#include <QtCore/private/qwasmglobal_p.h>#include <QtCore/private/qstdweb_p.h>#include <QtCore/private/qwasmsocket_p.h>using namespacestd::chrono;using namespacestd::chrono_literals; QT_BEGIN_NAMESPACE usingemscripten::val;Q_LOGGING_CATEGORY(lcEventDispatcher,"qt.eventdispatcher");Q_LOGGING_CATEGORY(lcEventDispatcherTimers,"qt.eventdispatcher.timers");#if QT_CONFIG(thread)#define LOCK_GUARD(M) std::lock_guard<std::mutex> lock(M)#else#define LOCK_GUARD(M)#endif#if defined(QT_STATIC)static booluseAsyncify(){returnqstdweb::haveAsyncify();}#else// EM_JS is not supported for side modules; disable asyncifystatic booluseAsyncify(){return false;}#endif// defined(QT_STATIC) Q_CONSTINIT QEventDispatcherWasm *QEventDispatcherWasm::g_mainThreadEventDispatcher =nullptr; Q_CONSTINIT std::shared_ptr<QWasmSuspendResumeControl>QEventDispatcherWasm::g_mainThreadSuspendResumeControl;#if QT_CONFIG(thread) Q_CONSTINIT QVector<QEventDispatcherWasm *>QEventDispatcherWasm::g_secondaryThreadEventDispatchers; Q_CONSTINIT std::mutex QEventDispatcherWasm::g_staticDataMutex;#endifQEventDispatcherWasm::QEventDispatcherWasm(std::shared_ptr<QWasmSuspendResumeControl> suspendResumeControl){// QEventDispatcherWasm operates in two main modes:// - On the main thread:// The event dispatcher can process native events but can't// block and wait for new events, unless asyncify is used.// - On a secondary thread:// The event dispatcher can't process native events but can// block and wait for new events.//// Which mode is determined by the calling thread: construct// the event dispatcher object on the thread where it will live.qCDebug(lcEventDispatcher) <<"Creating QEventDispatcherWasm instance"<<this<<"is main thread"<<emscripten_is_main_runtime_thread();if(emscripten_is_main_runtime_thread()) {// There can be only one main thread event dispatcher at a time; in// addition the main instance is used by the secondary thread event// dispatchers so we set a global pointer to it.Q_ASSERT(g_mainThreadEventDispatcher ==nullptr); g_mainThreadEventDispatcher =this;if(suspendResumeControl) { g_mainThreadSuspendResumeControl = suspendResumeControl;}else{ g_mainThreadSuspendResumeControl =std::make_shared<QWasmSuspendResumeControl>();}// Zero-timer used on wake() calls m_wakeupTimer =std::make_unique<QWasmTimer>(g_mainThreadSuspendResumeControl.get(), [](){onWakeup(); });// Timer set to fire at the next Qt timer timeout m_nativeTimer =std::make_unique<QWasmTimer>(g_mainThreadSuspendResumeControl.get(), []() {onTimer(); });// Timer used when suspending to process native events m_suspendTimer =std::make_unique<QWasmTimer>(g_mainThreadSuspendResumeControl.get(), []() {onProcessNativeEventsResume(); });}else{#if QT_CONFIG(thread)std::lock_guard<std::mutex>lock(g_staticDataMutex); g_secondaryThreadEventDispatchers.append(this);#endif} m_timerInfo =std::make_unique<QTimerInfoList>();}QEventDispatcherWasm::~QEventDispatcherWasm(){qCDebug(lcEventDispatcher) <<"Destroying QEventDispatcherWasm instance"<<this;// Reset to ensure destruction before g_mainThreadSuspendResumeControl m_wakeupTimer.reset(); m_nativeTimer.reset(); m_suspendTimer.reset();#if QT_CONFIG(thread)if(isSecondaryThreadEventDispatcher()) {std::lock_guard<std::mutex>lock(g_staticDataMutex); g_secondaryThreadEventDispatchers.remove(g_secondaryThreadEventDispatchers.indexOf(this));}else#endif{QWasmSocket::clearSocketNotifiers(); g_mainThreadEventDispatcher =nullptr; g_mainThreadSuspendResumeControl.reset();}}boolQEventDispatcherWasm::isMainThreadEventDispatcher(){return this== g_mainThreadEventDispatcher;}boolQEventDispatcherWasm::isSecondaryThreadEventDispatcher(){return this!= g_mainThreadEventDispatcher;}boolQEventDispatcherWasm::isValidEventDispatcher(){returnisValidEventDispatcherPointer(this);}boolQEventDispatcherWasm::isValidEventDispatcherPointer(QEventDispatcherWasm *eventDispatcher){if(eventDispatcher == g_mainThreadEventDispatcher)return true;#if QT_CONFIG(thread)if(g_secondaryThreadEventDispatchers.contains(eventDispatcher))return true;#endifreturn false;}boolQEventDispatcherWasm::processEvents(QEventLoop::ProcessEventsFlags flags){ emit awake();if(!useAsyncify() &&isMainThreadEventDispatcher())handleNonAsyncifyErrorCases(flags);bool didSendEvents =false; didSendEvents |=sendPostedEvents();if(!isValidEventDispatcher())return false; didSendEvents |=sendNativeEvents(flags);if(!isValidEventDispatcher())return false; didSendEvents |=sendTimerEvents();if(!isValidEventDispatcher())return false;if(m_interrupted) { m_interrupted =false;return false;}if(flags &QEventLoop::WaitForMoreEvents)processEventsWait();return didSendEvents;}boolQEventDispatcherWasm::sendNativeEvents(QEventLoop::ProcessEventsFlags flags){// TODO: support ExcludeUserInputEvents and ExcludeSocketNotifiers// Secondary threads do not support native eventsif(!isMainThreadEventDispatcher())return false;// Can't suspend without asyncifyif(!useAsyncify())return false;// Send any pending events, andbool didSendEvents =false; didSendEvents|= g_mainThreadSuspendResumeControl->sendPendingEvents();// if the processEvents() call is made from an exec() call then we assume// that the main thread has just resumed, and that it will suspend again// at the end of processEvents(). This makes the suspend loop below superfluous.if(flags &QEventLoop::EventLoopExec)return didSendEvents;// Run a suspend-resume loop until all pending native events have// been processed. Suspending returns control to the browsers'event// loop and makes it process events. If any event was for us then// the wasm instance will resume (via event handling code in QWasmSuspendResumeControl// and process the event.//// Set a zero-timer to exit the loop via the m_wakeFromSuspendTimer flag.// This timer will be added to the end of the native event queue and// ensures that all pending (at the time of this sendNativeEvents() call)// native events are processed. m_wakeFromSuspendTimer =false;do{ m_suspendTimer->setTimeout(0ms); g_mainThreadSuspendResumeControl->suspend(); QScopedValueRollback scoped(m_isSendingNativeEvents,true); didSendEvents |= g_mainThreadSuspendResumeControl->sendPendingEvents();}while(!m_wakeFromSuspendTimer);return didSendEvents;}boolQEventDispatcherWasm::sendPostedEvents(){QCoreApplication::sendPostedEvents();return false;}boolQEventDispatcherWasm::sendTimerEvents(){int activatedTimers = m_timerInfo->activateTimers();if(activatedTimers >0)updateNativeTimer();return activatedTimers >0;}voidQEventDispatcherWasm::registerTimer(Qt::TimerId timerId, Duration interval,Qt::TimerType timerType, QObject *object){#ifndef QT_NO_DEBUGif(qToUnderlying(timerId) <1|| interval <0ns || !object) {qWarning("QEventDispatcherWasm::registerTimer: invalid arguments");return;}else if(object->thread() !=thread() ||thread() !=QThread::currentThread()) {qWarning("QEventDispatcherWasm::registerTimer: timers cannot be started from another ""thread");return;}#endifqCDebug(lcEventDispatcherTimers) <<"registerTimer"<<int(timerId) << interval << timerType << object; m_timerInfo->registerTimer(timerId, interval, timerType, object);updateNativeTimer();}boolQEventDispatcherWasm::unregisterTimer(Qt::TimerId timerId){#ifndef QT_NO_DEBUGif(qToUnderlying(timerId) <1) {qWarning("QEventDispatcherWasm::unregisterTimer: invalid argument");return false;}else if(thread() !=QThread::currentThread()) {qWarning("QEventDispatcherWasm::unregisterTimer: timers cannot be stopped from another ""thread");return false;}#endifqCDebug(lcEventDispatcherTimers) <<"unregisterTimer"<<int(timerId);bool ans = m_timerInfo->unregisterTimer(timerId);updateNativeTimer();return ans;}boolQEventDispatcherWasm::unregisterTimers(QObject *object){#ifndef QT_NO_DEBUGif(!object) {qWarning("QEventDispatcherWasm::unregisterTimers: invalid argument");return false;}else if(object->thread() !=thread() ||thread() !=QThread::currentThread()) {qWarning("QEventDispatcherWasm::unregisterTimers: timers cannot be stopped from another ""thread");return false;}#endifqCDebug(lcEventDispatcherTimers) <<"registerTimer"<< object;bool ans = m_timerInfo->unregisterTimers(object);updateNativeTimer();return ans;} QList<QAbstractEventDispatcher::TimerInfoV2>QEventDispatcherWasm::timersForObject(QObject *object)const{#ifndef QT_NO_DEBUGif(!object) {qWarning("QEventDispatcherWasm:registeredTimers: invalid argument");return{};}#endifreturn m_timerInfo->registeredTimers(object);}QEventDispatcherWasm::Duration QEventDispatcherWasm::remainingTime(Qt::TimerId timerId)const{return m_timerInfo->remainingDuration(timerId);}voidQEventDispatcherWasm::interrupt(){ m_interrupted =true;wakeUp();}voidQEventDispatcherWasm::wakeUp(){#if QT_CONFIG(thread)if(isSecondaryThreadEventDispatcher()) {std::lock_guard<std::mutex>lock(m_mutex); m_wakeUpCalled =true; m_moreEvents.notify_one();}else#endif{ QEventDispatcherWasm *eventDispatcher =this;qwasmglobal::runOnMainThreadAsync([eventDispatcher]() {if(isValidEventDispatcherPointer(eventDispatcher)) {if(!eventDispatcher->m_wakeupTimer->hasTimeout()) eventDispatcher->m_wakeupTimer->setTimeout(0ms);}});}}voidQEventDispatcherWasm::handleNonAsyncifyErrorCases(QEventLoop::ProcessEventsFlags flags){Q_ASSERT(!useAsyncify());if(flags &QEventLoop::ApplicationExec) {// Start the main loop, and then stop it on the first callback. This// is done for the "simulateInfiniteLoop" functionality where// emscripten_set_main_loop() throws a JS exception which returns// control to the browser while preserving the C++ stack.const bool simulateInfiniteLoop =true;emscripten_set_main_loop([](){emscripten_pause_main_loop();},0, simulateInfiniteLoop);}else if(flags &QEventLoop::DialogExec) {qFatal() <<"Calling exec() is not supported on Qt for WebAssembly in this configuration. Please build"<<"with asyncify support, or use an asynchronous API like QDialog::open()";}else if(flags &QEventLoop::WaitForMoreEvents) {qFatal("QEventLoop::WaitForMoreEvents is not supported on the main thread without asyncify");}}// Blocks or suspends the current thread for the given amount of time.// The event dispatcher does not process events while blocked. TODO:// make it not process events while blocked.boolQEventDispatcherWasm::wait(int timeout){auto tim = timeout >0?std::optional<std::chrono::milliseconds>(timeout) :std::nullopt;if(isSecondaryThreadEventDispatcher())returnsecondaryThreadWait(tim);if(useAsyncify())asyncifyWait(tim);return true;}// Waits for more events by blocking or suspending the current thread. Should be called from// processEvents() only.voidQEventDispatcherWasm::processEventsWait(){if(isMainThreadEventDispatcher()) {asyncifyWait(std::nullopt);}else{auto nanoWait = m_timerInfo->timerWait();std::optional<std::chrono::milliseconds> milliWait;if(nanoWait.has_value()) milliWait =std::chrono::duration_cast<std::chrono::milliseconds>(*nanoWait);secondaryThreadWait(milliWait);}}voidQEventDispatcherWasm::asyncifyWait(std::optional<std::chrono::milliseconds> timeout){Q_ASSERT(emscripten_is_main_runtime_thread());Q_ASSERT(isMainThreadEventDispatcher());Q_ASSERT(useAsyncify());if(timeout.has_value()) m_suspendTimer->setTimeout(timeout.value()); g_mainThreadSuspendResumeControl->suspend();}boolQEventDispatcherWasm::secondaryThreadWait(std::optional<std::chrono::milliseconds> timeout){#if QT_CONFIG(thread)Q_ASSERT(QThread::currentThread() ==thread());using namespacestd::chrono_literals;std::unique_lock<std::mutex>lock(m_mutex);// 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) { m_wakeUpCalled =false;return true;}auto waitTime = timeout.value_or(std::chrono::milliseconds::max());bool wakeUpCalled = m_moreEvents.wait_for(lock, waitTime, [this] {return m_wakeUpCalled; }); m_wakeUpCalled =false;return wakeUpCalled;#elseQ_UNREACHABLE();return false;#endif}voidQEventDispatcherWasm::onTimer(){Q_ASSERT(emscripten_is_main_runtime_thread());if(!g_mainThreadEventDispatcher)return; g_mainThreadEventDispatcher->sendTimerEvents();}voidQEventDispatcherWasm::onWakeup(){Q_ASSERT(emscripten_is_main_runtime_thread());if(!g_mainThreadEventDispatcher)return;// In the case where we are suspending from sendNativeEvents() we don't want// to call processEvents() again, since we are then already in processEvents()// and are already awake.if(g_mainThreadEventDispatcher->m_isSendingNativeEvents)return; g_mainThreadEventDispatcher->processEvents(QEventLoop::AllEvents);}voidQEventDispatcherWasm::onProcessNativeEventsResume(){Q_ASSERT(emscripten_is_main_runtime_thread());if(!g_mainThreadEventDispatcher)return; g_mainThreadEventDispatcher->m_wakeFromSuspendTimer =true;}// Updates the native timer based on currently registered Qt timers,// by setting a timeout equivalent to the shortest timer.// Must be called on the event dispatcher thread.voidQEventDispatcherWasm::updateNativeTimer(){#if QT_CONFIG(thread)Q_ASSERT(QThread::currentThread() ==thread());#endif// On secondary threads, the timeout is managed by setting the WaitForMoreEvents// timeout in processEventsWait().if(!isMainThreadEventDispatcher())return;// Clear any timer if there are no active timersconststd::optional<std::chrono::nanoseconds> nanoWait = m_timerInfo->timerWait();if(!nanoWait.has_value()) { m_nativeTimer->clearTimeout();return;}auto milliWait =std::chrono::duration_cast<std::chrono::milliseconds>(*nanoWait);constauto newTargetTime = m_timerInfo->currentTime + milliWait;// Keep existing timer if the timeout has not changed.if(m_nativeTimer->hasTimeout() && newTargetTime == m_timerTargetTime)return;// Clear current and set new timerqCDebug(lcEventDispatcherTimers)<<"Created new native timer timeout"<< milliWait.count() <<"ms"<<"previous target time"<< m_timerTargetTime.time_since_epoch()<<"new target time"<< newTargetTime.time_since_epoch(); m_nativeTimer->clearTimeout(); m_nativeTimer->setTimeout(milliWait); m_timerTargetTime = newTargetTime;}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.voidQEventDispatcherWasm::registerStartupTask(){++g_startupTasks;}voidQEventDispatcherWasm::completeStarupTask(){--g_startupTasks;callOnLoadedIfRequired();}voidQEventDispatcherWasm::callOnLoadedIfRequired(){if(g_startupTasks >0)return;static bool qtLoadedCalled =false;if(qtLoadedCalled)return; qtLoadedCalled =true;}voidQEventDispatcherWasm::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.}voidQEventDispatcherWasm::registerSocketNotifier(QSocketNotifier *notifier){QWasmSocket::registerSocketNotifier(notifier);}voidQEventDispatcherWasm::unregisterSocketNotifier(QSocketNotifier *notifier){QWasmSocket::unregisterSocketNotifier(notifier);}voidQEventDispatcherWasm::socketSelect(int timeout,int socket,bool waitForRead,bool waitForWrite,bool*selectForRead,bool*selectForWrite,bool*socketDisconnect){ QEventDispatcherWasm *eventDispatcher =static_cast<QEventDispatcherWasm *>(QAbstractEventDispatcher::instance(QThread::currentThread()));if(!eventDispatcher) {qWarning("QEventDispatcherWasm::socketSelect called without eventdispatcher instance");return;}QWasmSocket::waitForSocketState(eventDispatcher, timeout, socket, waitForRead, waitForWrite, selectForRead, selectForWrite, socketDisconnect);} QT_END_NAMESPACE #include"moc_qeventdispatcher_wasm_p.cpp"
close