Skip to content

Commit f6ec348

Browse files
committed
Test
1 parent 50eaf5f commit f6ec348

6 files changed

Lines changed: 201 additions & 103 deletions

File tree

README.md

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -182,28 +182,23 @@ pub async fn process_with_progress(data: Vec<u8>, channel: Channel) -> ProcessRe
182182
Then use the `webworker_channel!` macro and `run_channel` method:
183183

184184
```rust,ignore
185-
use wasmworker::{webworker_channel, Channel, WebWorker};
185+
use wasmworker::{webworker_channel, WebWorker};
186186
187187
let worker = WebWorker::new(None).await?;
188188
189-
// Create a channel for bidirectional communication
190-
let (main_channel, worker_port) = Channel::new()?;
191-
192-
// Start the async task
193-
let task = worker.run_channel(
194-
webworker_channel!(process_with_progress),
195-
&data,
196-
worker_port,
197-
);
189+
// Start the async task — returns a ChannelTask for communication + result
190+
let task = worker
191+
.run_channel(webworker_channel!(process_with_progress), &data)
192+
.await;
198193
199194
// Receive progress from worker
200-
let progress: Progress = main_channel.recv().await.unwrap();
195+
let progress: Progress = task.recv().await.unwrap();
201196
202197
// Send response back to worker
203-
main_channel.send(&Continue { should_continue: true });
198+
task.send(&Continue { should_continue: true });
204199
205200
// Wait for task completion
206-
let result = task.await;
201+
let result = task.result().await;
207202
```
208203

209204
### Bundler support (Vite)

src/channel_task.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
use std::marker::PhantomData;
2+
3+
use serde::{de::DeserializeOwned, Serialize};
4+
use tokio::sync::oneshot;
5+
6+
use crate::{channel::Channel, convert::from_bytes};
7+
8+
/// A handle to a running channel task on a WebWorker.
9+
///
10+
/// `ChannelTask` combines a bidirectional [`Channel`] for sending and receiving
11+
/// messages with the worker, and a future that resolves to the task's final result.
12+
///
13+
/// This type is returned by [`crate::WebWorker::run_channel`] and
14+
/// [`crate::pool::WebWorkerPool::run_channel`]. It allows you to exchange messages
15+
/// with the worker (e.g., for progress reporting) and then consume the final result.
16+
///
17+
/// # Example
18+
///
19+
/// ```ignore
20+
/// let task = worker
21+
/// .run_channel(webworker_channel!(process_with_progress), &data)
22+
/// .await;
23+
///
24+
/// let progress: Progress = task.recv().await.expect("progress");
25+
/// task.send(&Continue { should_continue: true });
26+
///
27+
/// let result: ProcessResult = task.result().await;
28+
/// ```
29+
pub struct ChannelTask<R> {
30+
channel: Channel,
31+
result_rx: oneshot::Receiver<Vec<u8>>,
32+
_phantom: PhantomData<R>,
33+
}
34+
35+
impl<R: DeserializeOwned> ChannelTask<R> {
36+
/// Create a new `ChannelTask` from a channel and a result receiver.
37+
pub(crate) fn new(channel: Channel, result_rx: oneshot::Receiver<Vec<u8>>) -> Self {
38+
Self {
39+
channel,
40+
result_rx,
41+
_phantom: PhantomData,
42+
}
43+
}
44+
45+
/// Receive the next deserialized message from the worker.
46+
///
47+
/// Returns `None` if the channel's sender side has been dropped
48+
/// (i.e., the worker has finished and closed the channel).
49+
pub async fn recv<T: DeserializeOwned>(&self) -> Option<T> {
50+
self.channel.recv().await
51+
}
52+
53+
/// Receive raw bytes from the worker.
54+
///
55+
/// Returns `None` if the channel's sender side has been dropped.
56+
pub async fn recv_bytes(&self) -> Option<Box<[u8]>> {
57+
self.channel.recv_bytes().await
58+
}
59+
60+
/// Send a serialized message to the worker.
61+
pub fn send<T: Serialize>(&self, msg: &T) {
62+
self.channel.send(msg);
63+
}
64+
65+
/// Send raw bytes to the worker.
66+
pub fn send_bytes(&self, bytes: &[u8]) {
67+
self.channel.send_bytes(bytes);
68+
}
69+
70+
/// Await the task's final result, consuming the `ChannelTask`.
71+
pub async fn result(self) -> R {
72+
let bytes = self
73+
.result_rx
74+
.await
75+
.expect("WebWorker result sender dropped");
76+
from_bytes(&bytes)
77+
}
78+
}

src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
#![doc = include_str!("../README.md")]
22
#![allow(clippy::borrowed_box)]
33
pub use channel::Channel;
4+
pub use channel_task::ChannelTask;
45
pub use global::{
56
has_worker_pool, init_optimized_worker_pool, init_worker_pool, worker_pool, AlreadyInitialized,
67
};
78
pub use pool::WorkerPoolOptions;
8-
pub use web_sys::MessagePort;
99
pub use webworker::WebWorker;
1010

11+
#[doc(hidden)]
12+
pub use web_sys::MessagePort;
13+
1114
// Re-export WebWorkerPool from pool module
1215
pub use pool::WebWorkerPool;
1316

1417
mod channel;
18+
mod channel_task;
1519
pub mod convert;
1620
pub mod error;
1721
pub mod func;

src/pool/mod.rs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ pub use scheduler::Strategy;
77
use serde::{Deserialize, Serialize};
88

99
use wasm_bindgen_futures::JsFuture;
10-
use web_sys::{window, MessagePort};
10+
use web_sys::window;
1111

1212
use crate::{
13+
channel_task::ChannelTask,
1314
error::InitError,
1415
func::{WebWorkerChannelFn, WebWorkerFn},
1516
WebWorker,
@@ -211,25 +212,29 @@ impl WebWorkerPool {
211212

212213
/// Run an async function with bidirectional channel support on this [`WebWorkerPool`].
213214
///
215+
/// Returns a [`ChannelTask`] that provides both the communication channel and the
216+
/// task result. The `MessageChannel` is created internally.
217+
///
214218
/// The `func`: [`WebWorkerChannelFn`] argument should normally be instantiated using the
215219
/// [`crate::webworker_channel!`] macro. This ensures type safety and that the function
216220
/// is correctly exposed to the worker.
217221
///
218222
/// Example:
219223
/// ```ignore
220-
/// worker_pool().await.run_channel(webworker_channel!(process_with_progress), &my_data, port).await
224+
/// let task = worker_pool().await
225+
/// .run_channel(webworker_channel!(process_with_progress), &data)
226+
/// .await;
227+
///
228+
/// let progress: Progress = task.recv().await.expect("progress");
229+
/// task.send(&Continue { should_continue: true });
230+
/// let result: ProcessResult = task.result().await;
221231
/// ```
222-
pub async fn run_channel<T, R>(
223-
&self,
224-
func: WebWorkerChannelFn<T, R>,
225-
arg: &T,
226-
port: MessagePort,
227-
) -> R
232+
pub async fn run_channel<T, R>(&self, func: WebWorkerChannelFn<T, R>, arg: &T) -> ChannelTask<R>
228233
where
229234
T: Serialize + for<'de> Deserialize<'de>,
230235
R: Serialize + for<'de> Deserialize<'de>,
231236
{
232-
self.run_channel_internal(func, arg, port).await
237+
self.run_channel_internal(func, arg).await
233238
}
234239

235240
/// This function can outsource a task on a [`WebWorkerPool`] which has `Box<[u8]>` both as input and output.
@@ -271,15 +276,14 @@ impl WebWorkerPool {
271276
&self,
272277
func: WebWorkerChannelFn<T, R>,
273278
arg: &T,
274-
port: MessagePort,
275-
) -> R
279+
) -> ChannelTask<R>
276280
where
277281
T: Serialize + for<'de> Deserialize<'de>,
278282
R: Serialize + for<'de> Deserialize<'de>,
279283
{
280284
let worker_id = self.scheduler.schedule(self);
281285
self.workers[worker_id]
282-
.run_channel_internal(func, arg, port)
286+
.run_channel_internal(func, arg)
283287
.await
284288
}
285289

src/webworker/worker.rs

Lines changed: 73 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ use serde::{Deserialize, Serialize};
1212
use tokio::sync::{oneshot, Semaphore};
1313
use wasm_bindgen::{prelude::Closure, JsCast, JsValue, UnwrapThrowExt};
1414
use web_sys::{
15-
Blob, BlobPropertyBag, MessageEvent, MessagePort, Url, Worker, WorkerOptions, WorkerType,
15+
Blob, BlobPropertyBag, MessageChannel, MessageEvent, MessagePort, Url, Worker, WorkerOptions,
16+
WorkerType,
1617
};
1718

1819
use crate::{
20+
channel::Channel,
21+
channel_task::ChannelTask,
1922
convert::{from_bytes, to_bytes},
2023
error::{Full, InitError},
2124
func::{WebWorkerChannelFn, WebWorkerFn},
@@ -234,6 +237,10 @@ impl WebWorker {
234237

235238
/// Run an async function with bidirectional channel support on this [`WebWorker`].
236239
///
240+
/// Returns a [`ChannelTask`] that provides both the communication channel and the
241+
/// task result. The `MessageChannel` is created internally — callers interact only
242+
/// through the returned `ChannelTask`.
243+
///
237244
/// The `func`: [`WebWorkerChannelFn`] argument should normally be instantiated using the
238245
/// [`crate::webworker_channel!`] macro. This ensures type safety and that the function
239246
/// is correctly exposed to the worker.
@@ -242,19 +249,20 @@ impl WebWorker {
242249
///
243250
/// Example:
244251
/// ```ignore
245-
/// worker.run_channel(webworker_channel!(process_with_progress), &my_data, port).await
252+
/// let task = worker
253+
/// .run_channel(webworker_channel!(process_with_progress), &data)
254+
/// .await;
255+
///
256+
/// let progress: Progress = task.recv().await.expect("progress");
257+
/// task.send(&Continue { should_continue: true });
258+
/// let result: ProcessResult = task.result().await;
246259
/// ```
247-
pub async fn run_channel<T, R>(
248-
&self,
249-
func: WebWorkerChannelFn<T, R>,
250-
arg: &T,
251-
port: MessagePort,
252-
) -> R
260+
pub async fn run_channel<T, R>(&self, func: WebWorkerChannelFn<T, R>, arg: &T) -> ChannelTask<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,12 +372,13 @@ impl WebWorker {
364372
}
365373

366374
/// Internal function to schedule a channel task to the worker.
375+
/// Creates a `MessageChannel` internally, sends one port to the worker,
376+
/// and returns a `ChannelTask` wrapping the other port and the result future.
367377
pub(crate) async fn run_channel_internal<T, R>(
368378
&self,
369379
func: WebWorkerChannelFn<T, R>,
370380
arg: &T,
371-
port: MessagePort,
372-
) -> R
381+
) -> ChannelTask<R>
373382
where
374383
T: Serialize + for<'de> Deserialize<'de>,
375384
R: Serialize + for<'de> Deserialize<'de>,
@@ -381,8 +390,15 @@ impl WebWorker {
381390
None
382391
};
383392

384-
// Convert arg and result.
385-
self.force_run(func.name, arg, true, Some(port)).await
393+
// Create the MessageChannel internally.
394+
let msg_channel = MessageChannel::new().expect_throw("Could not create MessageChannel");
395+
let channel = Channel::from(msg_channel.port1());
396+
let worker_port = msg_channel.port2();
397+
398+
// Send the request and get a receiver for the result bytes.
399+
let result_rx = self.send_channel_request(func.name, arg, worker_port);
400+
401+
ChannelTask::new(channel, result_rx)
386402
}
387403

388404
/// This function handles the communication with the worker
@@ -447,6 +463,50 @@ impl WebWorker {
447463
.expect_throw("Could not find function")
448464
}
449465

466+
/// Sends a channel request to the worker and returns a receiver for the result bytes.
467+
/// Unlike `send_request`, this does not await the result — it returns immediately
468+
/// so the caller can interact with the channel before consuming the result.
469+
fn send_channel_request<T>(
470+
&self,
471+
func_name: &'static str,
472+
arg: &T,
473+
port: MessagePort,
474+
) -> oneshot::Receiver<Vec<u8>>
475+
where
476+
T: Serialize + for<'de> Deserialize<'de>,
477+
{
478+
let id = self.current_task.fetch_add(1, Ordering::Relaxed);
479+
let request = Request {
480+
id,
481+
func_name,
482+
is_channel: true,
483+
arg: to_bytes(arg),
484+
};
485+
486+
let (sender, receiver) = oneshot::channel();
487+
self.open_tasks.borrow_mut().insert(id, sender);
488+
489+
let transfer = Array::new();
490+
transfer.push(&port);
491+
492+
self.worker
493+
.post_message_with_transfer(
494+
&serde_wasm_bindgen::to_value(&request).expect_throw("Could not serialize request"),
495+
&transfer,
496+
)
497+
.expect_throw("WebWorker gone");
498+
499+
// Map the receiver to extract just the response bytes.
500+
let (byte_sender, byte_receiver) = oneshot::channel();
501+
wasm_bindgen_futures::spawn_local(async move {
502+
if let Ok(response) = receiver.await {
503+
let _ = byte_sender.send(response.response.expect("Could not find function"));
504+
}
505+
});
506+
507+
byte_receiver
508+
}
509+
450510
/// Return the current capacity for new tasks.
451511
pub fn capacity(&self) -> Option<usize> {
452512
self.task_limit.as_ref().map(|s| s.available_permits())

0 commit comments

Comments
 (0)