wasm: introduce QWasmSuspendResumeControl
QWasmSuspendResumeControl manages transfer of control from JS to the wasm instance, when the wasm instance is asyncify suspended. Supported use cases include registerEventHandler()-type event handlers, and also JS APIs which take a callback functions, such as timers and requrestAnimationFrame. The specific use cases are handled by adapters (not included in this commit), which call the QWasmSuspendResumeControl API to register JS/C++ event handler pairs: uint32_t index = control->registerEventHandler([](val){ ... }); val jsHandler = control->jsEventHandler(index); The C++ handler contains the user code, while the JS handler can be passed to registerEventHandler or be used as a callback. Change-Id: Ia1b1fd8884f0906759690dc7bc949c65a0248618 Reviewed-by: Jøger Hansegård <joger.hansegard@qt.io>
This commit is contained in:
parent
94e19f42e8
commit
3c72f99770
@ -1442,6 +1442,7 @@ qt_internal_extend_target(Core CONDITION WASM
|
|||||||
SOURCES
|
SOURCES
|
||||||
platform/wasm/qstdweb.cpp platform/wasm/qstdweb_p.h
|
platform/wasm/qstdweb.cpp platform/wasm/qstdweb_p.h
|
||||||
platform/wasm/qwasmsocket.cpp platform/wasm/qwasmsocket_p.h
|
platform/wasm/qwasmsocket.cpp platform/wasm/qwasmsocket_p.h
|
||||||
|
platform/wasm/qwasmsuspendresumecontrol.cpp platform/wasm/qwasmsuspendresumecontrol_p.h
|
||||||
kernel/qeventdispatcher_wasm.cpp kernel/qeventdispatcher_wasm_p.h
|
kernel/qeventdispatcher_wasm.cpp kernel/qeventdispatcher_wasm_p.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
166
src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp
Normal file
166
src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
// Copyright (C) 2025 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 "qwasmsuspendresumecontrol_p.h"
|
||||||
|
#include "qstdweb_p.h"
|
||||||
|
|
||||||
|
#include <emscripten.h>
|
||||||
|
#include <emscripten/val.h>
|
||||||
|
#include <emscripten/bind.h>
|
||||||
|
|
||||||
|
using emscripten::val;
|
||||||
|
|
||||||
|
/*
|
||||||
|
QWasmSuspendResumeControl controls asyncify suspend and resume when handling native events.
|
||||||
|
|
||||||
|
The class supports registering C++ event handlers, and creates a corresponding
|
||||||
|
JavaScript event handler which can be passed to addEventListener() or similar
|
||||||
|
API:
|
||||||
|
|
||||||
|
auto handler = [](emscripten::val argument){
|
||||||
|
// handle event
|
||||||
|
};
|
||||||
|
uint32_t index = control->registerEventHandler(handler);
|
||||||
|
element.call<void>("addEventListener", "eventname", control->jsEventHandlerAt(index));
|
||||||
|
|
||||||
|
The wasm instance suspends itself by calling the suspend() function, which resumes
|
||||||
|
and returns whenever there was a native event. Call sendPendingEvents() to send
|
||||||
|
the native event and invoke the C++ event handlers.
|
||||||
|
|
||||||
|
// about to suspend
|
||||||
|
control->suspend(); // <- instance/app sleeps here
|
||||||
|
// was resumed, send event(s)
|
||||||
|
control->sendPendingEvents();
|
||||||
|
|
||||||
|
QWasmSuspendResumeControl also supports the case where the wasm instance returns
|
||||||
|
control to the browser's event loop (without suspending), and will call the C++
|
||||||
|
event handlers directly in that case.
|
||||||
|
*/
|
||||||
|
|
||||||
|
QWasmSuspendResumeControl *QWasmSuspendResumeControl::s_suspendResumeControl = nullptr;
|
||||||
|
|
||||||
|
// Setup/constructor function for Module.suspendResumeControl.
|
||||||
|
// FIXME if assigning to the Module object from C++ is/becomes possible
|
||||||
|
// then this does not need to be a separate JS function.
|
||||||
|
EM_JS(void, qtSuspendResumeControlClearJs, (), {
|
||||||
|
Module.qtSuspendResumeControl = {
|
||||||
|
resume: null,
|
||||||
|
eventHandlers: {},
|
||||||
|
pendingEvents: [],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Suspends the calling thread
|
||||||
|
EM_ASYNC_JS(void, qtSuspendJs, (), {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
Module.qtSuspendResumeControl.resume = resolve;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Registers a JS event handler which when called registers its index
|
||||||
|
// as the "current" event handler, and then resumes the wasm instance.
|
||||||
|
// The wasm instance will then call the C++ event after it is resumed.
|
||||||
|
EM_JS(void, qtRegisterEventHandlerJs, (int index), {
|
||||||
|
let control = Module.qtSuspendResumeControl;
|
||||||
|
let handler = (arg) => {
|
||||||
|
control.pendingEvents.push({
|
||||||
|
index: index,
|
||||||
|
arg: arg
|
||||||
|
});
|
||||||
|
if (control.resume) {
|
||||||
|
const resume = control.resume;
|
||||||
|
control.resume = null;
|
||||||
|
resume();
|
||||||
|
} else {
|
||||||
|
Module.qtSendPendingEvents(); // not suspended, call the handler directly
|
||||||
|
}
|
||||||
|
};
|
||||||
|
control.eventHandlers[index] = handler;
|
||||||
|
});
|
||||||
|
|
||||||
|
QWasmSuspendResumeControl::QWasmSuspendResumeControl()
|
||||||
|
{
|
||||||
|
#if QT_CONFIG(thread)
|
||||||
|
Q_ASSERT(emscripten_is_main_runtime_thread());
|
||||||
|
#endif
|
||||||
|
qtSuspendResumeControlClearJs();
|
||||||
|
Q_ASSERT(!QWasmSuspendResumeControl::s_suspendResumeControl);
|
||||||
|
QWasmSuspendResumeControl::s_suspendResumeControl = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
QWasmSuspendResumeControl::~QWasmSuspendResumeControl()
|
||||||
|
{
|
||||||
|
qtSuspendResumeControlClearJs();
|
||||||
|
Q_ASSERT(QWasmSuspendResumeControl::s_suspendResumeControl);
|
||||||
|
QWasmSuspendResumeControl::s_suspendResumeControl = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
QWasmSuspendResumeControl *QWasmSuspendResumeControl::get()
|
||||||
|
{
|
||||||
|
Q_ASSERT_X(s_suspendResumeControl, "QWasmSuspendResumeControl", "Must create a QWasmSuspendResumeControl instance first");
|
||||||
|
return s_suspendResumeControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Registers a C++ event handler.
|
||||||
|
uint32_t QWasmSuspendResumeControl::registerEventHandler(std::function<void(val)> handler)
|
||||||
|
{
|
||||||
|
static uint32_t i = 0;
|
||||||
|
++i;
|
||||||
|
m_eventHandlers.emplace(i, std::move(handler));
|
||||||
|
qtRegisterEventHandlerJs(i);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes a C++ event handler
|
||||||
|
void QWasmSuspendResumeControl::removeEventHandler(uint32_t index)
|
||||||
|
{
|
||||||
|
m_eventHandlers.erase(index);
|
||||||
|
suspendResumeControlJs()["eventHandlers"].set(index, val::null());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the JS event handler for the given index
|
||||||
|
val QWasmSuspendResumeControl::jsEventHandlerAt(uint32_t index)
|
||||||
|
{
|
||||||
|
return suspendResumeControlJs()["eventHandlers"][index];
|
||||||
|
}
|
||||||
|
|
||||||
|
emscripten::val QWasmSuspendResumeControl::suspendResumeControlJs()
|
||||||
|
{
|
||||||
|
return val::module_property("qtSuspendResumeControl");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suspends the calling thread.
|
||||||
|
void QWasmSuspendResumeControl::suspend()
|
||||||
|
{
|
||||||
|
qtSuspendJs();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sends any pending events. Returns true if an event was sent, false otherwise.
|
||||||
|
bool QWasmSuspendResumeControl::sendPendingEvents()
|
||||||
|
{
|
||||||
|
#if QT_CONFIG(thread)
|
||||||
|
Q_ASSERT(emscripten_is_main_runtime_thread());
|
||||||
|
#endif
|
||||||
|
emscripten::val pendingEvents = suspendResumeControlJs()["pendingEvents"];
|
||||||
|
int count = pendingEvents["length"].as<int>();
|
||||||
|
if (count == 0)
|
||||||
|
return false;
|
||||||
|
while (count-- > 0) {
|
||||||
|
// Grab one event (handler and arg), and call it
|
||||||
|
emscripten::val event = pendingEvents.call<val>("shift");
|
||||||
|
auto it = m_eventHandlers.find(event["index"].as<int>());
|
||||||
|
Q_ASSERT(it != m_eventHandlers.end());
|
||||||
|
it->second(event["arg"]);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void qtSendPendingEvents()
|
||||||
|
{
|
||||||
|
if (QWasmSuspendResumeControl::s_suspendResumeControl)
|
||||||
|
QWasmSuspendResumeControl::s_suspendResumeControl->sendPendingEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
EMSCRIPTEN_BINDINGS(qtSuspendResumeControl) {
|
||||||
|
emscripten::function("qtSendPendingEvents", qtSendPendingEvents QT_WASM_EMSCRIPTEN_ASYNC);
|
||||||
|
}
|
49
src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h
Normal file
49
src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// Copyright (C) 2025 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
|
||||||
|
|
||||||
|
#ifndef QWASMSUSPENDRESUMECONTROL_P_H
|
||||||
|
#define QWASMSUSPENDRESUMECONTROL_P_H
|
||||||
|
|
||||||
|
#include <QtCore/qglobal.h>
|
||||||
|
#include <emscripten/val.h>
|
||||||
|
#include <map>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
//
|
||||||
|
// W A R N I N G
|
||||||
|
// -------------
|
||||||
|
//
|
||||||
|
// This file is not part of the Qt API. It exists purely as an
|
||||||
|
// implementation detail. This header file may change from version to
|
||||||
|
// version without notice, or even be removed.
|
||||||
|
//
|
||||||
|
// We mean it.
|
||||||
|
//
|
||||||
|
|
||||||
|
class Q_CORE_EXPORT QWasmSuspendResumeControl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QWasmSuspendResumeControl();
|
||||||
|
~QWasmSuspendResumeControl();
|
||||||
|
|
||||||
|
QWasmSuspendResumeControl(const QWasmSuspendResumeControl&) = delete;
|
||||||
|
QWasmSuspendResumeControl& operator=(const QWasmSuspendResumeControl&) = delete;
|
||||||
|
|
||||||
|
static QWasmSuspendResumeControl *get();
|
||||||
|
|
||||||
|
uint32_t registerEventHandler(std::function<void(emscripten::val)> handler);
|
||||||
|
void removeEventHandler(uint32_t index);
|
||||||
|
emscripten::val jsEventHandlerAt(uint32_t index);
|
||||||
|
static emscripten::val suspendResumeControlJs();
|
||||||
|
|
||||||
|
void suspend();
|
||||||
|
bool sendPendingEvents();
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend void qtSendPendingEvents();
|
||||||
|
|
||||||
|
static QWasmSuspendResumeControl *s_suspendResumeControl;
|
||||||
|
std::map<int, std::function<void(emscripten::val)>> m_eventHandlers;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
Loading…
x
Reference in New Issue
Block a user