steel_core/world/player_index/
map.rs1use std::sync::Arc;
4
5use arc_swap::ArcSwap;
6use scc::HashMap;
7use steel_utils::locks::SyncMutex;
8use uuid::Uuid;
9
10use crate::{entity::Entity, player::Player};
11
12struct PlayerSlot {
13 player: ArcSwap<Player>,
14}
15
16impl PlayerSlot {
17 fn new(player: Arc<Player>) -> Self {
18 Self {
19 player: ArcSwap::new(player),
20 }
21 }
22
23 fn load(&self) -> Arc<Player> {
24 self.player.load_full()
25 }
26
27 fn replace(&self, expected: &Arc<Player>, replacement: Arc<Player>) -> bool {
28 let previous = self.player.compare_and_swap(expected, replacement);
29 Arc::ptr_eq(&previous, expected)
30 }
31}
32
33pub struct PlayerMap {
38 by_uuid: HashMap<Uuid, Arc<PlayerSlot>>,
40 by_entity_id: HashMap<i32, Arc<PlayerSlot>>,
42 order: SyncMutex<Vec<Uuid>>,
44 mutations: SyncMutex<()>,
46}
47
48impl Default for PlayerMap {
49 fn default() -> Self {
50 Self::new()
51 }
52}
53
54impl PlayerMap {
55 #[must_use]
57 pub fn new() -> Self {
58 Self {
59 by_uuid: HashMap::new(),
60 by_entity_id: HashMap::new(),
61 order: SyncMutex::new(Vec::new()),
62 mutations: SyncMutex::new(()),
63 }
64 }
65
66 pub fn insert(&self, player: Arc<Player>) -> bool {
76 let uuid = player.gameprofile.id;
77 let entity_id = player.id();
78 let _mutation = self.mutations.lock();
79 let slot = Arc::new(PlayerSlot::new(player));
80
81 if self.by_uuid.insert_sync(uuid, Arc::clone(&slot)).is_err() {
82 return false;
83 }
84
85 if self.by_entity_id.insert_sync(entity_id, slot).is_err() {
86 let _ = self.by_uuid.remove_sync(&uuid);
87 panic!("player entity id {entity_id} is already registered");
88 }
89 self.order.lock().push(uuid);
90 true
91 }
92
93 #[expect(
97 clippy::unused_async,
98 clippy::unused_async_trait_impl,
99 reason = "keeps the existing asynchronous PlayerMap API while mutation is synchronized internally"
100 )]
101 pub async fn remove(&self, uuid: &Uuid) -> Option<Arc<Player>> {
102 self.remove_sync(uuid)
103 }
104
105 #[expect(
111 clippy::unused_async,
112 clippy::unused_async_trait_impl,
113 reason = "keeps the existing asynchronous PlayerMap API while mutation is synchronized internally"
114 )]
115 pub async fn remove_player(&self, player: &Arc<Player>) -> Option<Arc<Player>> {
116 self.remove_player_sync(player)
117 }
118
119 pub fn remove_sync(&self, uuid: &Uuid) -> Option<Arc<Player>> {
124 let _mutation = self.mutations.lock();
125 let (_, slot) = self.by_uuid.remove_sync(uuid)?;
126 let player = slot.load();
127 let _ = self
128 .by_entity_id
129 .remove_if_sync(&player.id(), |current| Arc::ptr_eq(current, &slot));
130 self.order.lock().retain(|player_uuid| player_uuid != uuid);
131 Some(player)
132 }
133
134 pub fn remove_player_sync(&self, player: &Arc<Player>) -> Option<Arc<Player>> {
136 let uuid = player.gameprofile.id;
137 let _mutation = self.mutations.lock();
138 let slot = self
139 .by_uuid
140 .read_sync(&uuid, |_, current| Arc::clone(current))?;
141 let current = slot.load();
142 if !Arc::ptr_eq(¤t, player) {
143 return None;
144 }
145
146 let (_, removed_slot) = self
147 .by_uuid
148 .remove_if_sync(&uuid, |current| Arc::ptr_eq(current, &slot))?;
149 let _ = self
150 .by_entity_id
151 .remove_if_sync(¤t.id(), |indexed| Arc::ptr_eq(indexed, &removed_slot));
152 self.order
153 .lock()
154 .retain(|indexed_uuid| *indexed_uuid != uuid);
155 Some(current)
156 }
157
158 pub fn replace_player(&self, expected: &Arc<Player>, replacement: Arc<Player>) -> bool {
167 let uuid = expected.gameprofile.id;
168 let entity_id = expected.id();
169 if replacement.gameprofile.id != uuid || replacement.id() != entity_id {
170 return false;
171 }
172
173 let _mutation = self.mutations.lock();
174 let Some(slot) = self
175 .by_uuid
176 .read_sync(&uuid, |_, current| Arc::clone(current))
177 else {
178 return false;
179 };
180 let Some(entity_slot) = self
181 .by_entity_id
182 .read_sync(&entity_id, |_, current| Arc::clone(current))
183 else {
184 return false;
185 };
186 if !Arc::ptr_eq(&slot, &entity_slot) {
187 return false;
188 }
189
190 slot.replace(expected, replacement)
191 }
192
193 #[must_use]
195 pub fn get_by_uuid(&self, uuid: &Uuid) -> Option<Arc<Player>> {
196 self.by_uuid.read_sync(uuid, |_, slot| slot.load())
197 }
198
199 #[must_use]
201 pub fn get_by_entity_id(&self, entity_id: i32) -> Option<Arc<Player>> {
202 self.by_entity_id
203 .read_sync(&entity_id, |_, slot| slot.load())
204 }
205
206 pub fn iter_players<F>(&self, mut f: F)
210 where
211 F: FnMut(&Uuid, &Arc<Player>) -> bool,
212 {
213 let order = self.order.lock().iter().copied().collect::<Vec<_>>();
214 for uuid in order {
215 let Some(player) = self.get_by_uuid(&uuid) else {
216 continue;
217 };
218 if !f(&uuid, &player) {
219 return;
220 }
221 }
222 }
223
224 #[must_use]
226 pub fn len(&self) -> usize {
227 self.by_uuid.len()
228 }
229
230 #[must_use]
232 pub fn is_empty(&self) -> bool {
233 self.by_uuid.is_empty()
234 }
235}
236
237#[cfg(test)]
238mod tests;