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
|
||||
platform/wasm/qstdweb.cpp platform/wasm/qstdweb_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
|
||||
)
|
||||
|
||||
|
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