veloren_voxygen/menu/main/
client_init.rs

1use client::{
2    Client, ClientInitStage, ServerInfo,
3    addr::ConnectionArgs,
4    error::{Error as ClientError, NetworkConnectError, NetworkError},
5};
6use common_net::msg::ClientType;
7use crossbeam_channel::{Receiver, Sender, TryRecvError, unbounded};
8use std::{
9    path::Path,
10    sync::{
11        Arc,
12        atomic::{AtomicBool, Ordering},
13    },
14    time::Duration,
15};
16use tokio::runtime;
17use tracing::{trace, warn};
18
19#[derive(Debug)]
20#[expect(clippy::enum_variant_names)] //TODO: evaluate ClientError ends with Enum name
21pub enum Error {
22    ClientError {
23        error: ClientError,
24        mismatched_server_info: Option<ServerInfo>,
25    },
26    ClientCrashed,
27    ServerNotFound,
28}
29
30#[expect(clippy::large_enum_variant)]
31pub enum Msg {
32    IsAuthTrusted(String),
33    Done(Result<Client, Error>),
34}
35
36pub struct AuthTrust(String, bool);
37
38// Used to asynchronously parse the server address, resolve host names,
39// and create the client (which involves establishing a connection to the
40// server).
41pub struct ClientInit {
42    rx: Receiver<Msg>,
43    stage_rx: Receiver<ClientInitStage>,
44    trust_tx: Sender<AuthTrust>,
45    cancel: Arc<AtomicBool>,
46}
47impl ClientInit {
48    pub fn new(
49        connection_args: ConnectionArgs,
50        username: String,
51        password: String,
52        runtime: Arc<runtime::Runtime>,
53        locale: Option<String>,
54        config_dir: &Path,
55        client_type: ClientType,
56    ) -> Self {
57        let (tx, rx) = unbounded();
58        let (trust_tx, trust_rx) = unbounded();
59        let (init_stage_tx, init_stage_rx) = unbounded();
60        let cancel = Arc::new(AtomicBool::new(false));
61        let cancel2 = Arc::clone(&cancel);
62
63        let runtime2 = Arc::clone(&runtime);
64        let config_dir = config_dir.to_path_buf();
65
66        runtime.spawn(async move {
67            let trust_fn = |auth_server: &str| {
68                let _ = tx.send(Msg::IsAuthTrusted(auth_server.to_string()));
69                trust_rx
70                    .recv()
71                    .map(|AuthTrust(server, trust)| trust && server == *auth_server)
72                    .unwrap_or(false)
73            };
74
75            let mut last_err = None;
76
77            const FOUR_MINUTES_RETRIES: u64 = 48;
78            'tries: for _ in 0..FOUR_MINUTES_RETRIES {
79                if cancel2.load(Ordering::Relaxed) {
80                    break;
81                }
82                let mut mismatched_server_info = None;
83                match Client::new(
84                    connection_args.clone(),
85                    Arc::clone(&runtime2),
86                    &mut mismatched_server_info,
87                    &username,
88                    &password,
89                    locale.clone(),
90                    trust_fn,
91                    &|stage| {
92                        let _ = init_stage_tx.send(stage);
93                    },
94                    crate::ecs::sys::add_local_systems,
95                    config_dir.clone(),
96                    client_type,
97                )
98                .await
99                {
100                    Ok(client) => {
101                        let _ = tx.send(Msg::Done(Ok(client)));
102                        tokio::task::block_in_place(move || drop(runtime2));
103                        return;
104                    },
105                    Err(ClientError::NetworkErr(NetworkError::ConnectFailed(
106                        NetworkConnectError::Io(e),
107                    ))) => {
108                        warn!(?e, "Failed to connect to the server. Retrying...");
109                    },
110                    Err(e) => {
111                        trace!(?e, "Aborting server connection attempt");
112                        last_err = Some(Error::ClientError {
113                            error: e,
114                            mismatched_server_info,
115                        });
116                        break 'tries;
117                    },
118                }
119                tokio::time::sleep(Duration::from_secs(5)).await;
120            }
121
122            // Parsing/host name resolution successful but no connection succeeded
123            // If last_err is None this typically means there was no server up at the input
124            // address and all the attempts timed out.
125            let _ = tx.send(Msg::Done(Err(last_err.unwrap_or(Error::ServerNotFound))));
126
127            // Safe drop runtime
128            tokio::task::block_in_place(move || drop(runtime2));
129        });
130
131        ClientInit {
132            rx,
133            stage_rx: init_stage_rx,
134            trust_tx,
135            cancel,
136        }
137    }
138
139    /// Poll if the thread is complete.
140    /// Returns None if the thread is still running, otherwise returns the
141    /// Result of client creation.
142    pub fn poll(&self) -> Option<Msg> {
143        match self.rx.try_recv() {
144            Ok(msg) => Some(msg),
145            Err(TryRecvError::Empty) => None,
146            Err(TryRecvError::Disconnected) => Some(Msg::Done(Err(Error::ClientCrashed))),
147        }
148    }
149
150    /// Poll for connection stage updates from the client
151    pub fn stage_update(&self) -> Option<ClientInitStage> { self.stage_rx.try_recv().ok() }
152
153    /// Report trust status of auth server
154    pub fn auth_trust(&self, auth_server: String, trusted: bool) {
155        let _ = self.trust_tx.send(AuthTrust(auth_server, trusted));
156    }
157
158    pub fn cancel(&mut self) { self.cancel.store(true, Ordering::Relaxed); }
159}
160
161impl Drop for ClientInit {
162    fn drop(&mut self) { self.cancel(); }
163}