netrunner_gui/engine.rs
1//! Bridges the async, Tokio-based [`netrunner_core`] engine to GPUI.
2//!
3//! GPUI runs its own (non-Tokio) executor, while `netrunner-core` uses `reqwest`
4//! which requires a Tokio runtime. We therefore run the speed test on a
5//! dedicated background thread with its own multi-threaded Tokio runtime and
6//! stream [`TestEvent`]s back over a `futures` channel that GPUI can poll from
7//! its foreground executor.
8
9use futures::channel::mpsc::{unbounded, UnboundedReceiver};
10use netrunner_core::{SpeedTest, TestConfig, TestEvent};
11
12/// Start a speed test on a background Tokio runtime.
13///
14/// Returns a stream of [`TestEvent`]s. The test begins immediately; drop the
15/// receiver to stop caring about its progress (the background thread finishes
16/// on its own).
17pub fn spawn_speed_test(config: TestConfig) -> UnboundedReceiver<TestEvent> {
18 let (ui_tx, ui_rx) = unbounded::<TestEvent>();
19
20 std::thread::spawn(move || {
21 let runtime = match tokio::runtime::Builder::new_multi_thread()
22 .enable_all()
23 .build()
24 {
25 Ok(rt) => rt,
26 Err(e) => {
27 let _ = ui_tx
28 .unbounded_send(TestEvent::Status(format!("Failed to start runtime: {e}")));
29 return;
30 }
31 };
32
33 runtime.block_on(async move {
34 // Ensure a rustls crypto provider is installed for this runtime.
35 let _ = rustls::crypto::ring::default_provider().install_default();
36
37 let (core_tx, mut core_rx) = tokio::sync::mpsc::unbounded_channel::<TestEvent>();
38 let test = match SpeedTest::with_events(config, Some(core_tx)) {
39 Ok(test) => test,
40 Err(e) => {
41 let _ = ui_tx.unbounded_send(TestEvent::Status(format!(
42 "Failed to create speed test: {e}"
43 )));
44 return;
45 }
46 };
47
48 let engine = test.run_full_test();
49 tokio::pin!(engine);
50
51 loop {
52 tokio::select! {
53 biased;
54 maybe = core_rx.recv() => match maybe {
55 Some(event) => {
56 if ui_tx.unbounded_send(event).is_err() {
57 break; // GUI dropped the receiver.
58 }
59 }
60 None => break,
61 },
62 res = &mut engine => {
63 if let Err(e) = res {
64 let _ = ui_tx.unbounded_send(TestEvent::Status(format!(
65 "Speed test failed: {e}"
66 )));
67 }
68 break;
69 }
70 }
71 }
72
73 // Forward any events buffered after the engine finished.
74 while let Ok(event) = core_rx.try_recv() {
75 let _ = ui_tx.unbounded_send(event);
76 }
77 });
78 });
79
80 ui_rx
81}