Skip to content

Commit 78ff17b

Browse files
committed
feat(native): libuv-free WebTransport — raw UDP poll driver (desktop + mobile)
1 parent 89e3290 commit 78ff17b

5 files changed

Lines changed: 224 additions & 154 deletions

File tree

CMakeLists.txt

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -857,17 +857,39 @@ endif()
857857
# download-deps.mjs (like libuv/draco/swc) — the engine repo never compiles
858858
# Rust/BoringSSL itself. WebTransport is disabled (stubbed) if the library is
859859
# not present, so the build still succeeds without it.
860-
# Prebuilt layout: quiche/include/quiche.h, quiche/libquiche.a (or quiche.lib)
861860
#
862-
# Mobile: library-builder also produces iOS/Android libquiche prebuilts
863-
# (quiche-ios/<variant>, quiche-android/<abi>; fetched via download-deps --ios/
864-
# --android). They are intentionally NOT linked here yet: WebTransport also needs
865-
# libuv (MYSTRAL_HAS_LIBUV), which is desktop-only today. Once libuv is available
866-
# on mobile, extend this block to select the per-arch mobile libquiche and drop
867-
# the `NOT IOS AND NOT ANDROID` guard. See docs/realtimecommunication.md.
861+
# WebTransport drives its QUIC UDP socket + timers directly on the runtime poll
862+
# loop with raw non-blocking sockets (no libuv), so it is enabled on desktop AND
863+
# mobile. download-deps.mjs lays prebuilts out per platform/variant:
864+
# desktop : quiche/{include/quiche.h, libquiche.a|quiche.lib}
865+
# iOS : quiche-ios/<device|simulatorArm64|simulatorX64>/{include, libquiche.a}
866+
# Android : quiche-android/<aarch64|armv7|x86_64>/{include, libquiche.a}
867+
# (arm64 iOS simulator is not yet shipped — see docs/realtimecommunication.md.)
868868
option(MYSTRAL_USE_QUICHE "Enable QUIC/HTTP3 WebTransport via quiche" ON)
869-
set(QUICHE_DIR ${THIRD_PARTY_DIR}/quiche)
870-
if(MYSTRAL_USE_QUICHE AND EXISTS ${QUICHE_DIR} AND NOT IOS AND NOT ANDROID)
869+
if(MYSTRAL_USE_QUICHE)
870+
# Select the prebuilt directory for this target.
871+
if(IOS)
872+
if(CMAKE_OSX_SYSROOT MATCHES "[Ss]imulator")
873+
if(CMAKE_OSX_ARCHITECTURES MATCHES "x86_64")
874+
set(QUICHE_DIR ${THIRD_PARTY_DIR}/quiche-ios/simulatorX64)
875+
else()
876+
set(QUICHE_DIR ${THIRD_PARTY_DIR}/quiche-ios/simulatorArm64)
877+
endif()
878+
else()
879+
set(QUICHE_DIR ${THIRD_PARTY_DIR}/quiche-ios/device)
880+
endif()
881+
elseif(ANDROID)
882+
if(CMAKE_ANDROID_ARCH_ABI STREQUAL "arm64-v8a" OR ANDROID_ABI STREQUAL "arm64-v8a")
883+
set(QUICHE_DIR ${THIRD_PARTY_DIR}/quiche-android/aarch64)
884+
elseif(CMAKE_ANDROID_ARCH_ABI STREQUAL "armeabi-v7a" OR ANDROID_ABI STREQUAL "armeabi-v7a")
885+
set(QUICHE_DIR ${THIRD_PARTY_DIR}/quiche-android/armv7)
886+
else()
887+
set(QUICHE_DIR ${THIRD_PARTY_DIR}/quiche-android/x86_64)
888+
endif()
889+
else()
890+
set(QUICHE_DIR ${THIRD_PARTY_DIR}/quiche)
891+
endif()
892+
871893
set(QUICHE_INCLUDE_DIR ${QUICHE_DIR}/include)
872894
if(WIN32)
873895
set(QUICHE_LIB_PATH ${QUICHE_DIR}/quiche.lib)
@@ -885,14 +907,8 @@ if(MYSTRAL_USE_QUICHE AND EXISTS ${QUICHE_DIR} AND NOT IOS AND NOT ANDROID)
885907
message(STATUS "Found quiche: ${QUICHE_LIB_PATH}")
886908
message(STATUS "quiche includes: ${QUICHE_INCLUDE_DIR}")
887909
else()
888-
message(STATUS "quiche library not built - WebTransport disabled. Run 'node scripts/download-deps.mjs --only quiche' to enable.")
889-
message(STATUS " Expected library: ${QUICHE_LIB_PATH}")
890-
endif()
891-
else()
892-
if(MYSTRAL_USE_QUICHE AND NOT IOS AND NOT ANDROID)
893-
message(STATUS "quiche not found - WebTransport disabled. Run 'node scripts/download-deps.mjs --only quiche' to enable.")
894-
elseif(MYSTRAL_USE_QUICHE AND (IOS OR ANDROID))
895-
message(STATUS "quiche on mobile: libquiche prebuilt available but WebTransport stays disabled until libuv lands on mobile (see docs/realtimecommunication.md).")
910+
message(STATUS "quiche not found at ${QUICHE_LIB_PATH} - WebTransport disabled (stub).")
911+
message(STATUS " Run 'node scripts/download-deps.mjs --only quiche' (desktop) or '--ios' / '--android' (mobile) to enable.")
896912
endif()
897913
endif()
898914

@@ -1320,6 +1336,9 @@ if(TARGET quiche::quiche)
13201336
"-framework Security"
13211337
"-framework CoreFoundation"
13221338
"-framework SystemConfiguration")
1339+
elseif(WIN32)
1340+
# Sockets (WebTransport UDP) + system libs required by BoringSSL on Windows.
1341+
target_link_libraries(mystral-runtime PUBLIC ws2_32 advapi32 crypt32 bcrypt userenv ntdll)
13231342
elseif(UNIX)
13241343
target_link_libraries(mystral-runtime PUBLIC pthread dl m)
13251344
endif()

docs/docs/guides/webtransport.mdx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ for fast state updates) and **reliable streams** (ordered, for chat, RPC, asset
77
transfer).
88

99
WebTransport is implemented natively on top of [Cloudflare quiche](https://github.com/cloudflare/quiche)
10-
(QUIC + HTTP/3) running on the same libuv event loop used by `fetch` and timers.
10+
(QUIC + HTTP/3). The QUIC UDP socket is a raw non-blocking socket polled on the
11+
runtime's per-frame loop (timers via `steady_clock`) — no libuv dependency, so it
12+
runs the same on desktop and mobile.
1113

1214
## Requirements
1315

include/mystral/webtransport/webtransport.h

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
* HTTP/3 datagrams (RFC 9297, quarter-stream-id prefix) over QUIC datagrams,
1313
* and WebTransport streams are carried as raw QUIC streams using the
1414
* WEBTRANSPORT_STREAM (0x41) / WT uni stream type (0x54) signal framing.
15-
* - The UDP socket and timers run on the shared libuv event loop. All JS
15+
* - The QUIC UDP socket is a raw non-blocking socket polled every frame from
16+
* processEvents(); the loss/idle timers are driven with steady_clock. No
17+
* libuv dependency, so WebTransport works on desktop and mobile alike. All JS
1618
* callbacks are dispatched on the main thread from processEvents().
1719
*
18-
* When quiche/libuv are not compiled in, a stub is used so that constructing a
20+
* When quiche is not compiled in, a stub is used so that constructing a
1921
* WebTransport in JS rejects cleanly instead of failing to link.
2022
*/
2123

@@ -48,10 +50,17 @@ bool initBindings(js::Engine* engine);
4850

4951
/**
5052
* Drive QUIC I/O for all sessions and dispatch any queued events to JS.
51-
* Must be called once per frame from the runtime poll loop, after the libuv
52-
* event loop has been pumped (EventLoop::runOnce()).
53+
* Must be called once per frame from the runtime poll loop. This polls each
54+
* session's UDP socket, advances the QUIC/handshake state machine, fires any due
55+
* timers, and dispatches events on the main thread.
5356
*/
5457
void processEvents();
5558

59+
/**
60+
* True if there is at least one active WebTransport session. The runtime uses
61+
* this to keep its no-SDL/headless poll loop alive while a session is pending.
62+
*/
63+
bool hasActiveSessions();
64+
5665
} // namespace webtransport
5766
} // namespace mystral

src/runtime.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,8 @@ class RuntimeImpl : public Runtime {
703703

704704
// In no-SDL (headless) mode, exit when there's no more work to do
705705
if (config_.noSdl) {
706-
bool hasWork = !rafCallbacks_.empty() || hasActiveTimers();
706+
bool hasWork = !rafCallbacks_.empty() || hasActiveTimers() ||
707+
webtransport::hasActiveSessions();
707708
if (!hasWork) {
708709
idleFrames++;
709710
if (idleFrames >= maxIdleFrames) {

0 commit comments

Comments
 (0)