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(crate) fn get_by_nfc_uid(&self, nfc_uid: NfcUid) -> Option<Arc<RwLock<Player>>> {
80 self.by_nfc_uid.get(&nfc_uid).cloned()
81 }
82
83 pub fn read_by_id(&self, id: Uuid) -> Option<RwLockReadGuard<'_, Player>> {
87 self.by_id.get(&id).map(|player| player.read().unwrap())
88 }
89
90 pub fn read_by_nfc_uid(&self, nfc_uid: NfcUid) -> Option<RwLockReadGuard<'_, Player>> {
94 self.by_nfc_uid
95 .get(&nfc_uid)
96 .map(|player| player.read().unwrap())
97 }
98
99 pub fn contains_id(&self, id: Uuid) -> bool {
101 self.by_id.contains_key(&id)
102 }
103
104 pub fn mutate_by_id<F>(&mut self, id: Uuid, mutator: F)
111 where
112 F: FnOnce(&mut Player),
113 {
114 if let Some(player_arc) = self.by_id.get(&id) {
115 let mut player = player_arc.write().unwrap();
116 let old_nfc_uid = player.nfc_uid();
117
118 mutator(&mut player);
119
120 let new_nfc_uid = player.nfc_uid();
121
122 if old_nfc_uid != new_nfc_uid {
123 self.by_nfc_uid.remove(&old_nfc_uid);
124 self.by_nfc_uid.insert(new_nfc_uid, player_arc.clone());
125 }
126 }
127 }
128
129 pub fn add(&mut self, player: Player) {
135 let id = player.id();
136 let nfc_uid = player.nfc_uid();
137 let player = Arc::new(RwLock::new(player));
138
139 self.by_id.insert(id, player.clone());
140 self.by_nfc_uid.insert(nfc_uid, player);
141 }
142
143 pub fn remove(&mut self, id: Uuid) {
148 let Some(player) = self.by_id.remove(&id) else {
149 return;
150 };
151
152 let player = player.read().unwrap();
153 self.by_nfc_uid.remove(&player.nfc_uid());
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use crate::test_utils::MockPlayer;
161
162 #[test]
163 fn adding_and_reading_player_by_nfc_uid_works() {
164 let (player, id) = MockPlayer::builder().bloops_count(5).build();
165 let player = Arc::try_unwrap(player).unwrap().into_inner().unwrap();
166
167 let registry = PlayerRegistry::new(vec![player]);
168
169 let player_read = registry.read_by_nfc_uid(NfcUid::default()).unwrap();
170 assert_eq!(player_read.total_bloops(), 5);
171 assert!(registry.contains_id(id));
172 }
173
174 #[test]
175 fn mutating_player_updates_nfc_uid_and_bloops_correctly() {
176 let (player, id) = MockPlayer::builder().bloops_count(0).build();
177 let player = Arc::try_unwrap(player).unwrap().into_inner().unwrap();
178
179 let mut registry = PlayerRegistry::new(vec![player]);
180
181 registry.mutate_by_id(id, |player| {
182 player.bloops_count = 42;
183 });
184
185 let player_read = registry.read_by_id(id).unwrap();
186 assert_eq!(player_read.total_bloops(), 42);
187 }
188
189 #[test]
190 fn removing_player_removes_from_all_indexes() {
191 let (player, id) = MockPlayer::builder().bloops_count(1).build();
192 let nfc_uid = NfcUid::default();
193 let player = Arc::try_unwrap(player).unwrap().into_inner().unwrap();
194
195 let mut registry = PlayerRegistry::new(vec![player]);
196
197 registry.remove(id);
198
199 assert!(registry.read_by_id(id).is_none());
200 assert!(registry.read_by_nfc_uid(nfc_uid).is_none());
201 }
202
203 #[test]
204 fn adding_player_overwrites_existing_player_with_same_id_or_nfc_uid() {
205 let (player1, id) = MockPlayer::builder().bloops_count(5).build();
206 let player1 = Arc::try_unwrap(player1).unwrap().into_inner().unwrap();
207
208 let (player2, _) = MockPlayer::builder().bloops_count(10).build();
209 let mut player2 = Arc::try_unwrap(player2).unwrap().into_inner().unwrap();
210
211 player2.id = id;
212 player2.registration_number = 99;
213
214 let mut registry = PlayerRegistry::new(vec![player1]);
215 registry.add(player2);
216
217 let player_read = registry.read_by_id(id).unwrap();
218 assert_eq!(player_read.total_bloops(), 10);
219 assert_eq!(player_read.registration_number, 99);
220 }
221}