steel_core/server/
known_players.rs1use super::{
2 Arc, GameProfile, KnownPlayer, KnownPlayerNameLookup, KnownPlayers, ProfileLookupError, Server,
3 Uuid, io, is_valid_player_name, lookup_online_profile, offline_uuid,
4};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub(super) enum UncachedPlayerTarget {
8 DirectUuid(Uuid),
9 OfflineName,
10 OnlineName,
11}
12
13pub(super) fn classify_uncached_player_target(
14 target: &str,
15 online_mode: bool,
16) -> UncachedPlayerTarget {
17 if let Ok(uuid) = Uuid::parse_str(target) {
18 return UncachedPlayerTarget::DirectUuid(uuid);
19 }
20 if online_mode {
21 UncachedPlayerTarget::OnlineName
22 } else {
23 UncachedPlayerTarget::OfflineName
24 }
25}
26
27pub(super) fn direct_uuid_profile(uuid: Uuid) -> KnownPlayer {
28 KnownPlayer::new(uuid, uuid.to_string())
29}
30
31pub(super) struct KnownPlayerCacheState {
32 players: KnownPlayers,
33 generation: u64,
34 worker_running: bool,
35 closed: bool,
36}
37
38impl KnownPlayerCacheState {
39 pub(super) const fn new(players: KnownPlayers) -> Self {
40 Self {
41 players,
42 generation: 0,
43 worker_running: false,
44 closed: false,
45 }
46 }
47
48 pub(super) fn record(&mut self, uuid: Uuid, name: String) -> bool {
49 if self.closed || !self.players.record(uuid, name) {
50 return false;
51 }
52 self.mark_changed()
53 }
54
55 pub(super) const fn mark_changed(&mut self) -> bool {
56 if self.closed {
57 return false;
58 }
59 self.generation = self.generation.wrapping_add(1);
60 if self.worker_running {
61 false
62 } else {
63 self.worker_running = true;
64 true
65 }
66 }
67
68 pub(super) fn snapshot(&self) -> (KnownPlayers, u64) {
69 (self.players.clone(), self.generation)
70 }
71
72 pub(super) const fn is_current(&self, generation: u64) -> bool {
73 !self.closed && self.generation == generation
74 }
75
76 pub(super) const fn finish_save(&mut self, generation: u64) -> KnownPlayerSaveStep {
77 if !self.closed && self.generation != generation {
78 KnownPlayerSaveStep::SaveAgain
79 } else {
80 self.worker_running = false;
81 KnownPlayerSaveStep::Finished
82 }
83 }
84
85 pub(super) fn close_if_idle(&mut self) -> Option<KnownPlayers> {
86 if self.worker_running {
87 return None;
88 }
89 self.closed = true;
90 Some(self.players.clone())
91 }
92}
93
94#[derive(Clone, Copy, Debug, PartialEq, Eq)]
95pub(super) enum KnownPlayerSaveStep {
96 SaveAgain,
97 Finished,
98}
99
100impl Server {
101 #[must_use]
103 pub fn known_players(&self) -> KnownPlayers {
104 self.known_players.lock().players.clone()
105 }
106
107 pub fn record_known_player(self: &Arc<Self>, profile: &GameProfile) -> Option<String> {
110 let mut known = self.known_players.lock();
111 let previous = known
112 .players
113 .by_uuid(profile.id)
114 .map(|entry| entry.last_known_name().to_owned());
115 let start_worker = known.record(profile.id, profile.name.clone());
116 drop(known);
117 if start_worker {
118 self.start_known_player_save_worker();
119 }
120 previous
121 }
122
123 pub fn record_known_profile(self: &Arc<Self>, uuid: Uuid, last_known_name: impl Into<String>) {
125 let start_worker = self
126 .known_players
127 .lock()
128 .record(uuid, last_known_name.into());
129 if start_worker {
130 self.start_known_player_save_worker();
131 }
132 }
133
134 pub async fn resolve_player_profile(
144 self: &Arc<Self>,
145 name: &str,
146 ) -> Result<KnownPlayer, ProfileLookupError> {
147 if let Some(profile) = self.cached_player_profile(name) {
148 return Ok(profile);
149 }
150
151 match classify_uncached_player_target(name, self.config.online_mode) {
152 UncachedPlayerTarget::DirectUuid(uuid) => {
153 return Ok(direct_uuid_profile(uuid));
156 }
157 UncachedPlayerTarget::OfflineName => {
158 let profile = KnownPlayer::new(offline_uuid(name), name.to_owned());
159 self.record_known_profile(profile.uuid(), profile.last_known_name().to_owned());
160 return Ok(profile);
161 }
162 UncachedPlayerTarget::OnlineName => {}
163 }
164 if !is_valid_player_name(name) {
165 return Err(ProfileLookupError::UnknownPlayer(name.to_owned()));
166 }
167
168 let profile = lookup_online_profile(
169 &self.profile_lookup_client,
170 self.config.profile_server.as_deref(),
171 name,
172 )
173 .await?;
174 self.record_known_profile(profile.uuid(), profile.last_known_name().to_owned());
175 Ok(profile)
176 }
177
178 fn cached_player_profile(self: &Arc<Self>, name: &str) -> Option<KnownPlayer> {
179 let uuid = Uuid::parse_str(name).ok();
180 if let Some(player) = self.get_players().into_iter().find(|player| {
181 player.gameprofile.name.eq_ignore_ascii_case(name)
182 || uuid.is_some_and(|uuid| player.gameprofile.id == uuid)
183 }) {
184 return Some(KnownPlayer::new(
185 player.gameprofile.id,
186 player.gameprofile.name.clone(),
187 ));
188 }
189
190 let mut known = self.known_players.lock();
191 if let Some(uuid) = uuid {
192 return known.players.resolve_uuid(uuid);
193 }
194 let (profile, start_worker) = match known
195 .players
196 .resolve_name(name, chrono::Utc::now().timestamp_millis())
197 {
198 KnownPlayerNameLookup::Found(profile) => (Some(profile), false),
199 KnownPlayerNameLookup::Missing => (None, false),
200 KnownPlayerNameLookup::Expired => {
201 let start_worker = known.mark_changed();
202 (None, start_worker)
203 }
204 };
205 drop(known);
206 if start_worker {
207 self.start_known_player_save_worker();
208 }
209 profile
210 }
211
212 fn start_known_player_save_worker(self: &Arc<Self>) {
213 let server = Arc::clone(self);
214 tokio::spawn(async move {
215 server.run_known_player_save_worker().await;
216 });
217 }
218
219 async fn run_known_player_save_worker(self: &Arc<Self>) {
220 loop {
221 let (players, generation) = self.known_players.lock().snapshot();
222 let result = self
223 .player_data_storage
224 .save_known_players_if_current(&players, || {
225 self.known_players.lock().is_current(generation)
226 })
227 .await;
228 match result {
229 Ok(true | false) => {}
230 Err(error) => {
231 tracing::error!(%error, "failed to save known player cache");
232 }
233 }
234 let step = self.known_players.lock().finish_save(generation);
235 if step == KnownPlayerSaveStep::SaveAgain {
236 continue;
237 }
238 self.known_player_save_idle.notify_one();
239 return;
240 }
241 }
242
243 pub async fn flush_known_players(&self) -> io::Result<()> {
251 let players = loop {
252 let idle = self.known_player_save_idle.notified();
253 let snapshot = self.known_players.lock().close_if_idle();
254 if let Some(players) = snapshot {
255 break players;
256 }
257 idle.await;
258 };
259 self.player_data_storage
260 .save_known_players_if_current(&players, || true)
261 .await
262 .map(|_| ())
263 }
264}