Skip to main content

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}