@@ -19,6 +19,7 @@ use crate::{
1919 convert:: { from_bytes, to_bytes} ,
2020 error:: { Full , InitError } ,
2121 func:: { WebWorkerChannelFn , WebWorkerFn } ,
22+ Channel ,
2223} ;
2324
2425/// An internal type for the callback.
@@ -238,23 +239,30 @@ impl WebWorker {
238239 /// [`crate::webworker_channel!`] macro. This ensures type safety and that the function
239240 /// is correctly exposed to the worker.
240241 ///
242+ /// Returns a [`Channel`] for bidirectional communication and a future that resolves
243+ /// to the function's return value. The channel and result share the same underlying
244+ /// `MessagePort`, guaranteeing that all channel messages sent during execution are
245+ /// delivered before the result.
246+ ///
241247 /// If a task limit has been set, this function will yield until previous tasks have been finished.
242248 ///
243249 /// Example:
244250 /// ```ignore
245- /// worker.run_channel(webworker_channel!(process_with_progress), &my_data, port).await
251+ /// let (channel, result) = worker.run_channel(webworker_channel!(process_with_progress), &my_data).await;
252+ /// let progress: Progress = channel.recv().await.unwrap();
253+ /// channel.send(&Continue { should_continue: true });
254+ /// let result = result.await;
246255 /// ```
247256 pub async fn run_channel < T , R > (
248257 & self ,
249258 func : WebWorkerChannelFn < T , R > ,
250259 arg : & T ,
251- port : MessagePort ,
252- ) -> R
260+ ) -> ( Channel , impl std:: future:: Future < Output = R > + ' _ )
253261 where
254262 T : Serialize + for < ' de > Deserialize < ' de > ,
255263 R : Serialize + for < ' de > Deserialize < ' de > ,
256264 {
257- self . run_channel_internal ( func, arg, port ) . await
265+ self . run_channel_internal ( func, arg) . await
258266 }
259267
260268 /// This function differs from [`WebWorker::run`] by returning early if the given task limit is reached.
@@ -364,25 +372,103 @@ impl WebWorker {
364372 }
365373
366374 /// Internal function to schedule a channel task to the worker.
375+ ///
376+ /// Creates a `MessageChannel` internally. The worker-side port is transferred
377+ /// to the worker; the main-side port is used for both user messages AND the
378+ /// task result (tagged with `__wasmworker_result`). Because everything travels
379+ /// through a single `MessagePort`, FIFO ordering is guaranteed: all channel
380+ /// messages sent during execution arrive before the result.
367381 pub ( crate ) async fn run_channel_internal < T , R > (
368382 & self ,
369383 func : WebWorkerChannelFn < T , R > ,
370384 arg : & T ,
371- port : MessagePort ,
372- ) -> R
385+ ) -> ( Channel , impl std:: future:: Future < Output = R > + ' _ )
373386 where
374387 T : Serialize + for < ' de > Deserialize < ' de > ,
375388 R : Serialize + for < ' de > Deserialize < ' de > ,
376389 {
390+ use tokio:: sync:: mpsc;
391+ use web_sys:: MessageChannel ;
392+
377393 // Acquire permit if necessary.
378- let _permit = if let Some ( ref s) = self . task_limit {
394+ let permit = if let Some ( ref s) = self . task_limit {
379395 Some ( s. acquire ( ) . await . unwrap ( ) )
380396 } else {
381397 None
382398 } ;
383399
384- // Convert arg and result.
385- self . force_run ( func. name , arg, true , Some ( port) ) . await
400+ // Create a MessageChannel. The worker-side port is transferred to the
401+ // worker. The main-side port receives both user messages and the tagged
402+ // result, so we set up a dual-purpose onmessage callback.
403+ let msg_channel =
404+ MessageChannel :: new ( ) . expect_throw ( "Could not create MessageChannel" ) ;
405+ let main_port = msg_channel. port1 ( ) ;
406+ let worker_port = msg_channel. port2 ( ) ;
407+
408+ let ( result_tx, result_rx) = oneshot:: channel :: < JsValue > ( ) ;
409+ let result_tx = Rc :: new ( RefCell :: new ( Some ( result_tx) ) ) ;
410+ let ( user_tx, user_rx) = mpsc:: unbounded_channel :: < JsValue > ( ) ;
411+
412+ // Callback that routes __wasmworker_result to result_tx, everything else
413+ // to user_tx (the Channel's message stream).
414+ let callback: Closure < dyn FnMut ( MessageEvent ) > = {
415+ let result_tx = Rc :: clone ( & result_tx) ;
416+ Closure :: new ( move |event : MessageEvent | {
417+ let data = event. data ( ) ;
418+ // Check for the result tag (an Object with __wasmworker_result).
419+ // User messages are Uint8Array, so this check is unambiguous.
420+ if data. is_object ( ) {
421+ if let Ok ( result) = js_sys:: Reflect :: get (
422+ & data,
423+ & JsValue :: from_str ( "__wasmworker_result" ) ,
424+ ) {
425+ if !result. is_undefined ( ) {
426+ if let Some ( tx) = result_tx. borrow_mut ( ) . take ( ) {
427+ let _ = tx. send ( result) ;
428+ }
429+ return ;
430+ }
431+ }
432+ }
433+ let _ = user_tx. send ( data) ;
434+ } )
435+ } ;
436+ main_port. set_onmessage ( Some ( callback. as_ref ( ) . unchecked_ref ( ) ) ) ;
437+ callback. forget ( ) ;
438+
439+ let channel = Channel :: from_parts ( user_rx, main_port) ;
440+
441+ // Serialize and send request (with port transfer, no open_tasks registration).
442+ let id = self . current_task . fetch_add ( 1 , Ordering :: Relaxed ) ;
443+ let request = Request {
444+ id,
445+ func_name : func. name ,
446+ is_channel : true ,
447+ arg : to_bytes ( arg) ,
448+ } ;
449+
450+ let transfer = Array :: new ( ) ;
451+ transfer. push ( & worker_port) ;
452+ self . worker
453+ . post_message_with_transfer (
454+ & serde_wasm_bindgen:: to_value ( & request)
455+ . expect_throw ( "Could not serialize request" ) ,
456+ & transfer,
457+ )
458+ . expect_throw ( "WebWorker gone" ) ;
459+
460+ // The result future awaits the tagged result from the port.
461+ let result_future = async move {
462+ let _permit = permit; // keep permit alive until result arrives
463+ let raw = result_rx
464+ . await
465+ . expect_throw ( "Channel result sender dropped" ) ;
466+ let array = js_sys:: Uint8Array :: new ( & raw ) ;
467+ let bytes = array. to_vec ( ) ;
468+ from_bytes :: < R > ( & bytes)
469+ } ;
470+
471+ ( channel, result_future)
386472 }
387473
388474 /// This function handles the communication with the worker
0 commit comments