veloren_voxygen/menu/main/
client_init.rs1use 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)] pub 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, 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
82pub 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 let _ = tx.send(InitOutMsg::Done(Err(
172 last_err.unwrap_or(Error::ServerNotFound)
173 )));
174
175 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 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 pub fn stage_update(&self) -> Option<ClientInitStage> { self.stage_rx.try_recv().ok() }
201
202 pub fn auth_trust(&self, auth_server: String, trusted: bool) {
204 let _ = self.auth_tx.send(AuthResponseMsg(auth_server, trusted));
205 }
206
207 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}