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