Skip to main content

veloren_voxygen/menu/main/
client_init.rs

1use client::{
2    Client, ClientInitStage, CredentialProvider, Credentials, 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 InitOutMsg {
32    IsAuthTrusted(String),
33    RequestCredentials(Option<String>),
34    Done(Result<Client, Error>),
35}
36
37pub struct AuthResponseMsg(String, bool);
38pub struct CredentialsResponseMsg(Option<String>, Result<Credentials, ()>);
39
40struct UiCredentialProvider {
41    singleplayer: bool,
42    request_tx: Sender<InitOutMsg>,
43    auth_response_rx: Receiver<AuthResponseMsg>,
44    credentials_response_rx: Receiver<CredentialsResponseMsg>,
45}
46
47impl CredentialProvider for UiCredentialProvider {
48    fn auth_trusted(&self, auth_provider: &Option<String>) -> bool {
49        match auth_provider {
50            None => true, //acccept servers without auth
51            Some(auth_provider) => {
52                let _ = self
53                    .request_tx
54                    .send(InitOutMsg::IsAuthTrusted(auth_provider.clone()));
55
56                self.auth_response_rx
57                    .recv()
58                    .map(|AuthResponseMsg(server, trust)| trust && server == *auth_provider)
59                    .unwrap_or(false)
60            },
61        }
62    }
63
64    async fn get_credentials(&self, auth_provider: &Option<String>) -> Result<Credentials, ()> {
65        if self.singleplayer {
66            return Ok(Credentials::Username("singleplayer".to_string()));
67        }
68
69        let _ = self
70            .request_tx
71            .send(InitOutMsg::RequestCredentials(auth_provider.clone()));
72
73        let response = self.credentials_response_rx.recv().map_err(|_| ())?;
74        if &response.0 == auth_provider {
75            response.1
76        } else {
77            Err(())
78        }
79    }
80}
81
82// Used to asynchronously parse the server address, resolve host names,
83// and create the client (which involves establishing a connection to the
84// server).
85pub struct ClientInit {
86    rx: Receiver<InitOutMsg>,
87    stage_rx: Receiver<ClientInitStage>,
88    auth_tx: Sender<AuthResponseMsg>,
89    credentials_tx: Sender<CredentialsResponseMsg>,
90    cancel: Arc<AtomicBool>,
91}
92impl ClientInit {
93    pub fn new(
94        connection_args: ConnectionArgs,
95        runtime: Arc<runtime::Runtime>,
96        locale: Option<String>,
97        config_dir: &Path,
98        client_type: ClientType,
99    ) -> Self {
100        let (tx, rx) = unbounded();
101        let (auth_tx, auth_rx) = unbounded();
102        let (credentials_tx, credentials_rx) = unbounded();
103        let (init_stage_tx, init_stage_rx) = unbounded();
104        let cancel = Arc::new(AtomicBool::new(false));
105        let cancel2 = Arc::clone(&cancel);
106
107        let runtime2 = Arc::clone(&runtime);
108        let config_dir = config_dir.to_path_buf();
109
110        runtime.spawn(async move {
111            let credential_provider = UiCredentialProvider {
112                singleplayer: matches!(connection_args, ConnectionArgs::Mpsc(_)),
113                request_tx: tx.clone(),
114                auth_response_rx: auth_rx,
115                credentials_response_rx: credentials_rx,
116            };
117
118            let mut last_err = None;
119
120            const FOUR_MINUTES_RETRIES: u64 = 48;
121            'tries: for _ in 0..FOUR_MINUTES_RETRIES {
122                if cancel2.load(Ordering::Relaxed) {
123                    break;
124                }
125                let mismatched_server_info: Arc<std::sync::Mutex<Option<ServerInfo>>> =
126                    Default::default();
127
128                match Client::new(
129                    connection_args.clone(),
130                    Arc::clone(&runtime2),
131                    locale.clone(),
132                    &credential_provider,
133                    &|stage| {
134                        if let ClientInitStage::ServerVersionArrived(ref info) = stage {
135                            *mismatched_server_info.lock().unwrap() = Some(info.clone());
136                        }
137                        let _ = init_stage_tx.send(stage);
138                    },
139                    crate::ecs::sys::add_local_systems,
140                    config_dir.clone(),
141                    client_type,
142                )
143                .await
144                {
145                    Ok(client) => {
146                        let _ = tx.send(InitOutMsg::Done(Ok(client)));
147                        tokio::task::block_in_place(move || drop(runtime2));
148                        return;
149                    },
150                    Err(ClientError::NetworkErr(NetworkError::ConnectFailed(
151                        NetworkConnectError::Io(e),
152                    ))) => {
153                        warn!(?e, "Failed to connect to the server. Retrying...");
154                    },
155                    Err(e) => {
156                        trace!(?e, "Aborting server connection attempt");
157                        let mismatched_server_info = mismatched_server_info.lock().unwrap().take();
158                        last_err = Some(Error::ClientError {
159                            error: e,
160                            mismatched_server_info,
161                        });
162                        break 'tries;
163                    },
164                }
165                tokio::time::sleep(Duration::from_secs(5)).await;
166            }
167
168            // Parsing/host name resolution successful but no connection succeeded
169            // If last_err is None this typically means there was no server up at the input
170            // address and all the attempts timed out.
171            let _ = tx.send(InitOutMsg::Done(Err(
172                last_err.unwrap_or(Error::ServerNotFound)
173            )));
174
175            // Safe drop runtime
176            tokio::task::block_in_place(move || drop(runtime2));
177        });
178
179        ClientInit {
180            rx,
181            stage_rx: init_stage_rx,
182            auth_tx,
183            credentials_tx,
184            cancel,
185        }
186    }
187
188    /// Poll if the thread is complete.
189    /// Returns None if the thread is still running, otherwise returns the
190    /// Result of client creation.
191    pub fn poll(&self) -> Option<InitOutMsg> {
192        match self.rx.try_recv() {
193            Ok(msg) => Some(msg),
194            Err(TryRecvError::Empty) => None,
195            Err(TryRecvError::Disconnected) => Some(InitOutMsg::Done(Err(Error::ClientCrashed))),
196        }
197    }
198
199    /// Poll for connection stage updates from the client
200    pub fn stage_update(&self) -> Option<ClientInitStage> { self.stage_rx.try_recv().ok() }
201
202    /// Answer a request, by giving the trust status for a requested auth_server
203    pub fn auth_trust(&self, auth_server: String, trusted: bool) {
204        let _ = self.auth_tx.send(AuthResponseMsg(auth_server, trusted));
205    }
206
207    /// Answer a request, by giving the credentials for a requested auth_server
208    pub fn provide_credentials(
209        &self,
210        auth_server: Option<String>,
211        credentials: Result<Credentials, ()>,
212    ) {
213        let _ = self
214            .credentials_tx
215            .send(CredentialsResponseMsg(auth_server, credentials));
216    }
217
218    pub fn cancel(&mut self) { self.cancel.store(true, Ordering::Relaxed); }
219}
220
221impl Drop for ClientInit {
222    fn drop(&mut self) { self.cancel(); }
223}