wasm: Use new event dispatcher for QtGui

The event dispatcher implementation is now in QtCore,
except for the call to QWindowSystemInterface::sendWindowSystemEvents().

Implement QWasmWindow::requestUpdate() using emscripten_request_animation_frame(),
instead of the previous registerRequestUpdateCallback() function
which now is removed.

Pick-to: 6.3
Change-Id: I7a13eb5391d48dba0f2afe4704ef3188b8daa74b
Reviewed-by: Lorn Potter <lorn.potter@gmail.com>
This commit is contained in:
Morten Johan Sørvig 2021-09-12 20:37:42 +02:00
parent e98f5de6e1
commit 23964ce05f
5 changed files with 18 additions and 230 deletions

View File

@ -29,197 +29,11 @@
#include "qwasmeventdispatcher.h"
#include <QtCore/qcoreapplication.h>
#include <QtGui/qpa/qwindowsysteminterface.h>
#include <emscripten.h>
#if QT_CONFIG(thread)
#if (__EMSCRIPTEN_major__ > 1 || __EMSCRIPTEN_minor__ > 38 || __EMSCRIPTEN_minor__ == 38 && __EMSCRIPTEN_tiny__ >= 22)
# define EMSCRIPTEN_HAS_ASYNC_RUN_IN_MAIN_RUNTIME_THREAD
#endif
#endif
#ifdef EMSCRIPTEN_HAS_ASYNC_RUN_IN_MAIN_RUNTIME_THREAD
#include <emscripten/threading.h>
#endif
class QWasmEventDispatcherPrivate : public QEventDispatcherUNIXPrivate
// Note: All event dispatcher functionality is implemented in QEventDispatcherWasm
// in QtCore, except for processWindowSystemEvents() below which uses API from QtGui.
void QWasmEventDispatcher::processWindowSystemEvents(QEventLoop::ProcessEventsFlags flags)
{
};
QWasmEventDispatcher *g_htmlEventDispatcher;
QWasmEventDispatcher::QWasmEventDispatcher(QObject *parent)
: QUnixEventDispatcherQPA(parent)
{
g_htmlEventDispatcher = this;
}
QWasmEventDispatcher::~QWasmEventDispatcher()
{
g_htmlEventDispatcher = nullptr;
}
bool QWasmEventDispatcher::registerRequestUpdateCallback(std::function<void(void)> callback)
{
if (!g_htmlEventDispatcher || !g_htmlEventDispatcher->m_hasMainLoop)
return false;
g_htmlEventDispatcher->m_requestUpdateCallbacks.append(callback);
emscripten_resume_main_loop();
return true;
}
void QWasmEventDispatcher::maintainTimers()
{
if (!g_htmlEventDispatcher || !g_htmlEventDispatcher->m_hasMainLoop)
return;
g_htmlEventDispatcher->doMaintainTimers();
}
bool QWasmEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
{
// WaitForMoreEvents is not supported (except for in combination with EventLoopExec below),
// and we don't want the unix event dispatcher base class to attempt to wait either.
flags &= ~QEventLoop::WaitForMoreEvents;
// Handle normal processEvents.
if (!(flags & QEventLoop::EventLoopExec))
return QUnixEventDispatcherQPA::processEvents(flags);
if (flags & QEventLoop::DialogExec) {
qWarning() << "Warning: dialog exec() is not supported on Qt for WebAssembly, please use"
<< "show() instead. When using exec() 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
}
// Handle processEvents from QEventLoop::exec():
//
// At this point the application has created its root objects on
// the stack and has called app.exec() which has called into this
// function via QEventLoop.
//
// The application now expects that exec() will not return until
// app exit time. However, the browser expects that we return
// control to it periodically, also after initial setup in main().
// EventLoopExec for nested event loops is not supported.
Q_ASSERT(!m_hasMainLoop);
m_hasMainLoop = true;
// Call emscripten_set_main_loop_arg() with a callback which processes
// events. Also set simulateInfiniteLoop to true which makes emscripten
// return control to the browser without unwinding the C++ stack.
auto callback = [](void *eventDispatcher) {
QWasmEventDispatcher *that = static_cast<QWasmEventDispatcher *>(eventDispatcher);
// Save and clear updateRequest callbacks so we can register new ones
auto requestUpdateCallbacksCopy = that->m_requestUpdateCallbacks;
that->m_requestUpdateCallbacks.clear();
// Repaint all windows
for (auto callback : qAsConst(requestUpdateCallbacksCopy))
callback();
// Pause main loop if no updates were requested. Updates will be
// restarted again by registerRequestUpdateCallback().
if (that->m_requestUpdateCallbacks.isEmpty())
emscripten_pause_main_loop();
that->doMaintainTimers();
};
int fps = 0; // update using requestAnimationFrame
int simulateInfiniteLoop = 1;
emscripten_set_main_loop_arg(callback, this, fps, simulateInfiniteLoop);
// Note: the above call never returns, not even at app exit
return false;
}
void QWasmEventDispatcher::doMaintainTimers()
{
Q_D(QWasmEventDispatcher);
// This function schedules native timers in order to wake up to
// process events and activate Qt timers. This is done using the
// emscripten_async_call() API which schedules a new timer.
// There is unfortunately no way to cancel or update a current
// native timer.
// Schedule a zero-timer to continue processing any pending events.
extern uint qGlobalPostedEventsCount(); // from qapplication.cpp
if (!m_hasZeroTimer && (qGlobalPostedEventsCount() || QWindowSystemInterface::windowSystemEventsQueued())) {
auto callback = [](void *eventDispatcher) {
QWasmEventDispatcher *that = static_cast<QWasmEventDispatcher *>(eventDispatcher);
that->m_hasZeroTimer = false;
that->QUnixEventDispatcherQPA::processEvents(QEventLoop::AllEvents);
// Processing events may have posted new events or created new timers
that->doMaintainTimers();
};
emscripten_async_call(callback, this, 0);
m_hasZeroTimer = true;
return;
}
auto timespecToNanosec = [](timespec ts) -> uint64_t { return ts.tv_sec * 1000 + ts.tv_nsec / (1000 * 1000); };
// Get current time and time-to-first-Qt-timer. This polls for system
// time, and we use this time as the current time for the duration of this call.
timespec toWait;
bool hasTimers = d->timerList.timerWait(toWait);
if (!hasTimers)
return; // no timer needed
uint64_t currentTime = timespecToNanosec(d->timerList.currentTime);
uint64_t toWaitDuration = timespecToNanosec(toWait);
// The currently scheduled timer target is stored in m_currentTargetTime.
// We can re-use it if the new target is equivalent or later.
uint64_t newTargetTime = currentTime + toWaitDuration;
if (newTargetTime >= m_currentTargetTime)
return; // existing timer is good
// Schedule a native timer with a callback which processes events (and timers)
auto callback = [](void *eventDispatcher) {
QWasmEventDispatcher *that = static_cast<QWasmEventDispatcher *>(eventDispatcher);
that->m_currentTargetTime = std::numeric_limits<uint64_t>::max();
that->QUnixEventDispatcherQPA::processEvents(QEventLoop::AllEvents);
// Processing events may have posted new events or created new timers
that->doMaintainTimers();
};
emscripten_async_call(callback, this, toWaitDuration);
m_currentTargetTime = newTargetTime;
}
void QWasmEventDispatcher::wakeUp()
{
#ifdef EMSCRIPTEN_HAS_ASYNC_RUN_IN_MAIN_RUNTIME_THREAD
if (!emscripten_is_main_runtime_thread() && m_hasMainLoop) {
// Make two-step async call to mainThreadWakeUp in order to make sure the
// call is made at a point where the main thread is idle.
void (*intermediate)(void *) = [](void *eventdispatcher){
emscripten_async_call(QWasmEventDispatcher::mainThreadWakeUp, eventdispatcher, 0);
};
emscripten_async_run_in_main_runtime_thread_(EM_FUNC_SIG_VI, (void *)intermediate, this);
}
#endif
QEventDispatcherUNIX::wakeUp();
}
void QWasmEventDispatcher::mainThreadWakeUp(void *eventDispatcher)
{
emscripten_resume_main_loop(); // Service possible requestUpdate Calls
static_cast<QWasmEventDispatcher *>(eventDispatcher)->processEvents(QEventLoop::AllEvents);
QWindowSystemInterface::sendWindowSystemEvents(flags);
}

View File

@ -30,35 +30,14 @@
#ifndef QWASMEVENTDISPATCHER_H
#define QWASMEVENTDISPATCHER_H
#include <QtCore/qhash.h>
#include <QtCore/qloggingcategory.h>
#include <QtGui/private/qunixeventdispatcher_qpa_p.h>
#include <QtCore/private/qeventdispatcher_wasm_p.h>
QT_BEGIN_NAMESPACE
class QWasmEventDispatcherPrivate;
class QWasmEventDispatcher : public QUnixEventDispatcherQPA
class QWasmEventDispatcher : public QEventDispatcherWasm
{
Q_DECLARE_PRIVATE(QWasmEventDispatcher)
public:
explicit QWasmEventDispatcher(QObject *parent = nullptr);
~QWasmEventDispatcher();
static bool registerRequestUpdateCallback(std::function<void(void)> callback);
static void maintainTimers();
protected:
bool processEvents(QEventLoop::ProcessEventsFlags flags) override;
void doMaintainTimers();
void wakeUp() override;
static void mainThreadWakeUp(void *eventDispatcher);
private:
bool m_hasMainLoop = false;
bool m_hasZeroTimer = false;
uint64_t m_currentTargetTime = std::numeric_limits<uint64_t>::max();
QList<std::function<void(void)>> m_requestUpdateCallbacks;
void processWindowSystemEvents(QEventLoop::ProcessEventsFlags flags) override;
};
QT_END_NAMESPACE

View File

@ -371,7 +371,6 @@ int QWasmEventTranslator::mouse_cb(int eventType, const EmscriptenMouseEvent *mo
{
QWasmEventTranslator *translator = (QWasmEventTranslator*)userData;
bool accepted = translator->processMouse(eventType,mouseEvent);
QWasmEventDispatcher::maintainTimers();
return accepted;
}
@ -592,7 +591,6 @@ int QWasmEventTranslator::wheel_cb(int eventType, const EmscriptenWheelEvent *wh
bool accepted = QWindowSystemInterface::handleWheelEvent(window2, getTimestamp(), localPoint,
globalPoint, QPoint(), pixelDelta, modifiers);
QWasmEventDispatcher::maintainTimers();
return static_cast<int>(accepted);
}
@ -676,8 +674,6 @@ int QWasmEventTranslator::handleTouch(int eventType, const EmscriptenTouchEvent
if (eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL)
accepted = QWindowSystemInterface::handleTouchCancelEvent(window2, getTimestamp(), touchDevice, keyModifier);
QWasmEventDispatcher::maintainTimers();
return static_cast<int>(accepted);
}
@ -873,8 +869,6 @@ bool QWasmEventTranslator::processKeyboard(int eventType, const EmscriptenKeyboa
accepted = false; // continue on to event
}
QWasmEventDispatcher::maintainTimers();
return accepted;
}

View File

@ -63,6 +63,8 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingSt
QWasmWindow::~QWasmWindow()
{
m_compositor->removeWindow(this);
if (m_requestAnimationFrameId > -1)
emscripten_cancel_animation_frame(m_requestAnimationFrameId);
}
void QWasmWindow::destroy()
@ -395,16 +397,14 @@ qreal QWasmWindow::devicePixelRatio() const
void QWasmWindow::requestUpdate()
{
QPointer<QWindow> windowPointer(window());
bool registered = QWasmEventDispatcher::registerRequestUpdateCallback([=](){
if (windowPointer.isNull())
return;
deliverUpdateRequest();
});
if (!registered)
QPlatformWindow::requestUpdate();
static auto frame = [](double time, void *context) -> int {
Q_UNUSED(time);
QWasmWindow *window = static_cast<QWasmWindow *>(context);
window->m_requestAnimationFrameId = -1;
window->deliverUpdateRequest();
return 0;
};
m_requestAnimationFrameId = emscripten_request_animation_frame(frame, this);
}
bool QWasmWindow::hasTitleBar() const

View File

@ -122,6 +122,7 @@ protected:
WId m_winid = 0;
bool m_hasTitle = false;
bool m_needsCompositor = false;
long m_requestAnimationFrameId = -1;
friend class QWasmCompositor;
friend class QWasmEventTranslator;
bool windowIsPopupType(Qt::WindowFlags flags) const;