| 38 | //============================================================================= |
| 39 | |
| 40 | class CoroutineRuntimeWasm : public ICoroutineRuntime { |
| 41 | public: |
| 42 | void pumpCoroutines(fl::u32 us) FL_NOEXCEPT override { |
| 43 | if (CoroutineContext::isInsideCoroutine()) { |
| 44 | // Called from within a coroutine — yield back to runner |
| 45 | CoroutineContext::suspend(); |
| 46 | } else { |
| 47 | // Called from main thread — give time to background coroutines |
| 48 | CoroutineRunner::instance().run(us); |
| 49 | } |
| 50 | } |
| 51 | |
| 52 | /// Park on a real futex instead of busy-yielding. |
| 53 | /// |
| 54 | /// fl::platforms::await() polls a Promise with this method between |
| 55 | /// re-checks. The calling pthread parks via WASM `Atomics.wait` until |
| 56 | /// either (a) the timeout elapses or (b) wakeWaiters() notifies on the |
| 57 | /// shared counter from a JS callback. |
| 58 | /// |
| 59 | /// emscripten_atomic_wait_u32 is only valid off the browser main UI |
| 60 | /// thread, but with -sPROXY_TO_PTHREAD both main() and every coroutine |
| 61 | /// run on worker pthreads, so any caller that reaches this path is on |
| 62 | /// a parkable thread. |
| 63 | void suspendMainthread(fl::u32 us) FL_NOEXCEPT override { |
| 64 | // Inside a coroutine: hand the baton back to the runner so other |
| 65 | // coroutines (and the main loop) keep progressing — same as default. |
| 66 | if (CoroutineContext::isInsideCoroutine()) { |
| 67 | CoroutineContext::suspend(); |
| 68 | return; |
| 69 | } |
| 70 | // Outside a coroutine (e.g. await called from setup/loop): |
| 71 | // snapshot the futex counter then park on it for up to `us`. Any |
| 72 | // wakeWaiters() call between the snapshot and the wait will be |
| 73 | // observed and short-circuit the timeout. |
| 74 | fl::u32 snapshot = mWakeupCounter.load(); |
| 75 | fl::i64 timeout_ns = static_cast<fl::i64>(us) * 1000; |
| 76 | // emscripten_atomic_wait_u32 takes plain void* — fl::atomic<u32> is |
| 77 | // a standard-layout wrapper around a single u32, so its address |
| 78 | // coincides with the underlying word. |
| 79 | emscripten_atomic_wait_u32( |
| 80 | static_cast<void*>(&mWakeupCounter), // ok reinterpret_cast — atomic wrapper is layout-compat with its u32 |
| 81 | snapshot, |
| 82 | timeout_ns); |
| 83 | } |
| 84 | |
| 85 | /// Wake every pthread parked in suspendMainthread(). |
| 86 | /// Called from async callbacks (currently js_fetch_success_callback / |
| 87 | /// js_fetch_error_callback) after they have committed Promise state, so |
| 88 | /// the awaiting pthread re-checks promptly. |
| 89 | void wakeWaiters() FL_NOEXCEPT override { |
| 90 | mWakeupCounter.fetch_add(1); |
| 91 | // emscripten_atomic_notify expects a 64-bit count; pass a sentinel |
| 92 | // that wakes all parked threads. |
| 93 | emscripten_atomic_notify( |
| 94 | static_cast<void*>(&mWakeupCounter), |
| 95 | static_cast<fl::i64>(0x7fffffff)); |
| 96 | } |
| 97 | |