bloop_server_framework/
player.rs1use crate::nfc_uid::NfcUid;
15use chrono::{DateTime, Utc};
16use std::collections::HashMap;
17use std::sync::{Arc, RwLock, RwLockReadGuard};
18use uuid::Uuid;
19
20pub trait PlayerInfo {
22 fn id(&self) -> Uuid;
24
25 fn nfc_uid(&self) -> NfcUid;
27
28 fn total_bloops(&self) -> usize;
30
31 fn awarded_achievements(&self) -> &HashMap<Uuid, DateTime<Utc>>;
33}
34
35pub trait PlayerMutator {
37 fn increment_bloops(&mut self);
39
40 fn add_awarded_achievement(&mut self, achievement_id: Uuid, awarded_at: DateTime<Utc>);
42}
43
44#[derive(Debug)]
49pub struct PlayerRegistry<Player: PlayerInfo> {
50 by_id: HashMap<Uuid, Arc<RwLock<Player>>>,
51 by_nfc_uid: HashMap<NfcUid, Arc<RwLock<Player>>>,
52}
53
54impl<Player: PlayerInfo> From<Vec<Player>> for PlayerRegistry<Player> {
55 fn from(players: Vec<Player>) -> Self {
56 Self::new(players)
57 }
58}
59
60impl<Player: PlayerInfo> PlayerRegistry<Player> {
61 pub fn new(players: Vec<Player>) -> Self {
63 let mut by_id = HashMap::new();
64 let mut by_nfc_uid = HashMap::new();
65
66 for player in players {
67 let id = player.id();
68 let nfc_uid = player.nfc_uid();
69 let player = Arc::new(RwLock::new(player));
70
71 by_id.insert(id, player.clone());
72 by_nfc_uid.insert(nfc_uid, player);
73 }
74
75 Self { by_id, by_nfc_uid }
76 }
77
78 pub fn get_by_id(&self, id: Uuid) -> Option<Arc<RwLock<Player>>> {
82 self.by_id.get(&id).cloned()
83 }
84
85 pub fn get_by_nfc_uid(&self, nfc_uid: NfcUid) -> Option<Arc<RwLock<Player>>> {
89 self.by_nfc_uid.get(&nfc_uid).cloned()
90 }
91
92 pub fn read_by_id(&self, id: Uuid) -> Option<RwLockReadGuard<'_, Player>> {
96 self.by_id.get(&id).map(|player| player.read().unwrap())
97 }
98
99 pub fn read_by_nfc_uid(&self, nfc_uid: NfcUid) -> Option<RwLockReadGuard<'_, Player>> {
103 self.by_nfc_uid
104 .get(&nfc_uid)
105 .map(|player| player.read().unwrap())
106 }
107
108 pub fn contains_id(&self, id: Uuid) -> bool {
110 self.by_id.contains_key(&id)
111 }
112
113 pub fn mutate_by_id<F>(&mut self, id: Uuid, mutator: F)
120 where
121 F: FnOnce(&mut Player),
122 {
123 if let Some(player_arc) = self.by_id.get(&id) {
124 let mut player = player_arc.write().unwrap();
125 let old_nfc_uid = player.nfc_uid();
126
127 mutator(&mut player);
128
129 let new_nfc_uid = player.nfc_uid();
130
131 if old_nfc_uid != new_nfc_uid {
132 self.by_nfc_uid.remove(&old_nfc_uid);
133 self.by_nfc_uid.insert(new_nfc_uid, player_arc.clone());
134 }
135 }
136 }
137
138 pub fn add(&mut self, player: Player) {
144 let id = player.id();
145 let nfc_uid = player.nfc_uid();
146 let player = Arc::new(RwLock::new(player));
147
148 self.by_id.insert(id, player.clone());
149 self.by_nfc_uid.insert(nfc_uid, player);
150 }
151
152 pub fn remove(&mut self, id: Uuid) {
157 let Some(player) = self.by_id.remove(&id) else {
158 return;
159 };
160
161 let player = player.read().unwrap();
162 self.by_nfc_uid.remove(&player.nfc_uid());
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use crate::test_utils::MockPlayer;
170
171 #[test]
172 fn adding_and_reading_player_by_nfc_uid_works() {
173 let (player, id) = MockPlayer::builder().bloops_count(5).build();
174 let player = Arc::try_unwrap(player).unwrap().into_inner().unwrap();
175
176 let registry = PlayerRegistry::new(vec![player]);
177
178 let player_read = registry.read_by_nfc_uid(NfcUid::default()).unwrap();
179 assert_eq!(player_read.total_bloops(), 5);
180 assert!(registry.contains_id(id));
181 }
182
183 #[test]
184 fn mutating_player_updates_nfc_uid_and_bloops_correctly() {
185 let (player, id) = MockPlayer::builder().bloops_count(0).build();
186 let player = Arc::try_unwrap(player).unwrap().into_inner().unwrap();
187
188 let mut registry = PlayerRegistry::new(vec![player]);
189
190 registry.mutate_by_id(id, |player| {
191 player.bloops_count = 42;
192 });
193
194 let player_read = registry.read_by_id(id).unwrap();
195 assert_eq!(player_read.total_bloops(), 42);
196 }
197
198 #[test]
199 fn removing_player_removes_from_all_indexes() {
200 let (player, id) = MockPlayer::builder().bloops_count(1).build();
201 let nfc_uid = NfcUid::default();
202 let player = Arc::try_unwrap(player).unwrap().into_inner().unwrap();
203
204 let mut registry = PlayerRegistry::new(vec![player]);
205
206 registry.remove(id);
207
208 assert!(registry.read_by_id(id).is_none());
209 assert!(registry.read_by_nfc_uid(nfc_uid).is_none());
210 }
211
212 #[test]
213 fn adding_player_overwrites_existing_player_with_same_id_or_nfc_uid() {
214 let (player1, id) = MockPlayer::builder().bloops_count(5).build();
215 let player1 = Arc::try_unwrap(player1).unwrap().into_inner().unwrap();
216
217 let (player2, _) = MockPlayer::builder().bloops_count(10).build();
218 let mut player2 = Arc::try_unwrap(player2).unwrap().into_inner().unwrap();
219
220 player2.id = id;
221 player2.registration_number = 99;
222
223 let mut registry = PlayerRegistry::new(vec![player1]);
224 registry.add(player2);
225
226 let player_read = registry.read_by_id(id).unwrap();
227 assert_eq!(player_read.total_bloops(), 10);
228 assert_eq!(player_read.registration_number, 99);
229 }
230}