1 /****************************************************************************
2 **
3 ** Copyright (C) 2018 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the plugins of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 or (at your option) any later version
20 ** approved by the KDE Free Qt Foundation. The licenses are as published by
21 ** the Free Software Foundation and appearing in the file LICENSE.GPL3
22 ** included in the packaging of this file. Please review the following
23 ** information to ensure the GNU General Public License requirements will
24 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25 **
26 ** $QT_END_LICENSE$
27 **
28 ****************************************************************************/
29
30 #include "qwasmeventdispatcher.h"
31
32 #include <QtCore/qcoreapplication.h>
33
34 #include <emscripten.h>
35
36 #if QT_CONFIG(thread)
37 #if (__EMSCRIPTEN_major__ > 1 || __EMSCRIPTEN_minor__ > 38 || __EMSCRIPTEN_minor__ == 38 && __EMSCRIPTEN_tiny__ >= 22)
38 # define EMSCRIPTEN_HAS_ASYNC_RUN_IN_MAIN_RUNTIME_THREAD
39 #endif
40 #endif
41
42 #ifdef EMSCRIPTEN_HAS_ASYNC_RUN_IN_MAIN_RUNTIME_THREAD
43 #include <emscripten/threading.h>
44 #endif
45
46 class QWasmEventDispatcherPrivate : public QEventDispatcherUNIXPrivate
47 {
48
49 };
50
51 QWasmEventDispatcher *g_htmlEventDispatcher;
52
QWasmEventDispatcher(QObject * parent)53 QWasmEventDispatcher::QWasmEventDispatcher(QObject *parent)
54 : QUnixEventDispatcherQPA(parent)
55 {
56
57 g_htmlEventDispatcher = this;
58 }
59
~QWasmEventDispatcher()60 QWasmEventDispatcher::~QWasmEventDispatcher()
61 {
62 g_htmlEventDispatcher = nullptr;
63 }
64
registerRequestUpdateCallback(std::function<void (void)> callback)65 bool QWasmEventDispatcher::registerRequestUpdateCallback(std::function<void(void)> callback)
66 {
67 if (!g_htmlEventDispatcher || !g_htmlEventDispatcher->m_hasMainLoop)
68 return false;
69
70 g_htmlEventDispatcher->m_requestUpdateCallbacks.append(callback);
71 emscripten_resume_main_loop();
72 return true;
73 }
74
maintainTimers()75 void QWasmEventDispatcher::maintainTimers()
76 {
77 if (!g_htmlEventDispatcher || !g_htmlEventDispatcher->m_hasMainLoop)
78 return;
79
80 g_htmlEventDispatcher->doMaintainTimers();
81 }
82
processEvents(QEventLoop::ProcessEventsFlags flags)83 bool QWasmEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
84 {
85 // WaitForMoreEvents is not supported (except for in combination with EventLoopExec below),
86 // and we don't want the unix event dispatcher base class to attempt to wait either.
87 flags &= ~QEventLoop::WaitForMoreEvents;
88
89 // Handle normal processEvents.
90 if (!(flags & QEventLoop::EventLoopExec))
91 return QUnixEventDispatcherQPA::processEvents(flags);
92
93 // Handle processEvents from QEventLoop::exec():
94 //
95 // At this point the application has created its root objects on
96 // the stack and has called app.exec() which has called into this
97 // function via QEventLoop.
98 //
99 // The application now expects that exec() will not return until
100 // app exit time. However, the browser expects that we return
101 // control to it periodically, also after initial setup in main().
102
103 // EventLoopExec for nested event loops is not supported.
104 Q_ASSERT(!m_hasMainLoop);
105 m_hasMainLoop = true;
106
107 // Call emscripten_set_main_loop_arg() with a callback which processes
108 // events. Also set simulateInfiniteLoop to true which makes emscripten
109 // return control to the browser without unwinding the C++ stack.
110 auto callback = [](void *eventDispatcher) {
111 QWasmEventDispatcher *that = static_cast<QWasmEventDispatcher *>(eventDispatcher);
112
113 // Save and clear updateRequest callbacks so we can register new ones
114 auto requestUpdateCallbacksCopy = that->m_requestUpdateCallbacks;
115 that->m_requestUpdateCallbacks.clear();
116
117 // Repaint all windows
118 for (auto callback : qAsConst(requestUpdateCallbacksCopy))
119 callback();
120
121 // Pause main loop if no updates were requested. Updates will be
122 // restarted again by registerRequestUpdateCallback().
123 if (that->m_requestUpdateCallbacks.isEmpty())
124 emscripten_pause_main_loop();
125
126 that->doMaintainTimers();
127 };
128 int fps = 0; // update using requestAnimationFrame
129 int simulateInfiniteLoop = 1;
130 emscripten_set_main_loop_arg(callback, this, fps, simulateInfiniteLoop);
131
132 // Note: the above call never returns, not even at app exit
133 return false;
134 }
135
doMaintainTimers()136 void QWasmEventDispatcher::doMaintainTimers()
137 {
138 Q_D(QWasmEventDispatcher);
139
140 // This functon schedules native timers in order to wake up to
141 // process events and activate Qt timers. This is done using the
142 // emscripten_async_call() API which schedules a new timer.
143 // There is unfortunately no way to cancel or update a current
144 // native timer.
145
146 // Schedule a zero-timer to continue processing any pending events.
147 if (!m_hasZeroTimer && hasPendingEvents()) {
148 auto callback = [](void *eventDispatcher) {
149 QWasmEventDispatcher *that = static_cast<QWasmEventDispatcher *>(eventDispatcher);
150 that->m_hasZeroTimer = false;
151 that->QUnixEventDispatcherQPA::processEvents(QEventLoop::AllEvents);
152
153 // Processing events may have posted new events or created new timers
154 that->doMaintainTimers();
155 };
156
157 emscripten_async_call(callback, this, 0);
158 m_hasZeroTimer = true;
159 return;
160 }
161
162 auto timespecToNanosec = [](timespec ts) -> uint64_t { return ts.tv_sec * 1000 + ts.tv_nsec / (1000 * 1000); };
163
164 // Get current time and time-to-first-Qt-timer. This polls for system
165 // time, and we use this time as the current time for the duration of this call.
166 timespec toWait;
167 bool hasTimers = d->timerList.timerWait(toWait);
168 if (!hasTimers)
169 return; // no timer needed
170
171 uint64_t currentTime = timespecToNanosec(d->timerList.currentTime);
172 uint64_t toWaitDuration = timespecToNanosec(toWait);
173
174 // The currently scheduled timer target is stored in m_currentTargetTime.
175 // We can re-use it if the new target is equivalent or later.
176 uint64_t newTargetTime = currentTime + toWaitDuration;
177 if (newTargetTime >= m_currentTargetTime)
178 return; // existing timer is good
179
180 // Schedule a native timer with a callback which processes events (and timers)
181 auto callback = [](void *eventDispatcher) {
182 QWasmEventDispatcher *that = static_cast<QWasmEventDispatcher *>(eventDispatcher);
183 that->m_currentTargetTime = std::numeric_limits<uint64_t>::max();
184 that->QUnixEventDispatcherQPA::processEvents(QEventLoop::AllEvents);
185
186 // Processing events may have posted new events or created new timers
187 that->doMaintainTimers();
188 };
189 emscripten_async_call(callback, this, toWaitDuration);
190 m_currentTargetTime = newTargetTime;
191 }
192
wakeUp()193 void QWasmEventDispatcher::wakeUp()
194 {
195 #ifdef EMSCRIPTEN_HAS_ASYNC_RUN_IN_MAIN_RUNTIME_THREAD
196 if (!emscripten_is_main_runtime_thread())
197 if (m_hasMainLoop)
198 emscripten_async_run_in_main_runtime_thread_(EM_FUNC_SIG_VI, (void*)(&QWasmEventDispatcher::mainThreadWakeUp), this);
199 #endif
200 QEventDispatcherUNIX::wakeUp();
201 }
202
mainThreadWakeUp(void * eventDispatcher)203 void QWasmEventDispatcher::mainThreadWakeUp(void *eventDispatcher)
204 {
205 emscripten_resume_main_loop(); // Service possible requestUpdate Calls
206 static_cast<QWasmEventDispatcher *>(eventDispatcher)->processEvents(QEventLoop::AllEvents);
207 }
208