bevy_realtime/
presence.rs

1use std::collections::HashMap;
2
3use bevy::{ecs::system::SystemId, prelude::*};
4use bevy_crossbeam_event::CrossbeamEventSender;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8use crate::Channel;
9
10/// Enum of presence event types
11#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
12#[serde(rename_all = "snake_case")]
13pub enum PresenceEvent {
14    Track,
15    Untrack,
16    Join,
17    Leave,
18    Sync,
19}
20
21pub type RawPresenceState = HashMap<String, RawPresenceMetas>;
22
23#[derive(Clone)]
24pub(crate) struct PresenceCallback(pub SystemId<In<(String, PresenceState, PresenceState)>>);
25
26#[derive(Event, Clone)]
27pub struct PresenceCallbackEvent(
28    pub  (
29        SystemId<In<(String, PresenceState, PresenceState)>>,
30        (String, PresenceState, PresenceState),
31    ),
32);
33//{
34//  abc123: {1: {foo: bar}, 2: {foo: baz} },
35//  def456: {3: {foo: baz}, 4: {foo: bar} },
36//}
37//
38// triple nested hashmap, fantastic. gonna need to write some helper functions for this one
39pub type PresenceStateInner = HashMap<String, PhxMap>;
40
41pub type PhxMap = HashMap<String, StateData>;
42
43pub type StateData = HashMap<String, Value>;
44
45/// PresenceState triple nested hashmap.
46///
47/// Layout:
48/// HashMap<id, HashMap<phx_ref, HashMap<key, value>>>
49/// { \[id\]: { \[ref\]: { \[key\]: value } } }
50#[derive(Default, Clone, Debug)]
51pub struct PresenceState(pub PresenceStateInner);
52
53impl PresenceState {
54    /// Returns a once flattened map of presence data:
55    /// HashMap<phx_ref, <key, value>>
56    pub fn get_phx_map(&self) -> PhxMap {
57        let mut new_map = HashMap::new();
58        for (_id, map) in self.0.clone() {
59            for (phx_id, state_data) in map {
60                new_map.insert(phx_id, state_data);
61            }
62        }
63        new_map
64    }
65}
66
67type PresenceIteratorItem = (String, HashMap<String, HashMap<String, Value>>);
68
69impl FromIterator<PresenceIteratorItem> for PresenceState {
70    fn from_iter<T: IntoIterator<Item = PresenceIteratorItem>>(iter: T) -> Self {
71        let mut new_id_map = HashMap::new();
72
73        for (id, id_map) in iter {
74            new_id_map.insert(id, id_map);
75        }
76
77        PresenceState(new_id_map)
78    }
79}
80
81/// Raw presence meta data
82#[derive(Serialize, Deserialize, Debug, Clone, Default)]
83pub struct RawPresenceMeta {
84    pub phx_ref: String,
85    #[serde(flatten)]
86    pub state_data: HashMap<String, Value>,
87}
88
89/// Collection of raw presence metas
90#[derive(Serialize, Deserialize, Debug, Clone, Default)]
91pub struct RawPresenceMetas {
92    pub metas: Vec<RawPresenceMeta>,
93}
94
95impl From<RawPresenceState> for PresenceState {
96    fn from(val: RawPresenceState) -> Self {
97        let mut transformed_state = PresenceState(HashMap::new());
98
99        for (id, metas) in val {
100            let mut transformed_inner = HashMap::new();
101
102            for meta in metas.metas {
103                transformed_inner.insert(meta.phx_ref, meta.state_data);
104            }
105
106            transformed_state.0.insert(id, transformed_inner);
107        }
108
109        transformed_state
110    }
111}
112
113/// Internal, visibility skill issues mean still visible to crate consumer TODO
114#[derive(Serialize, Deserialize, Debug, Clone)]
115pub struct RawPresenceDiff {
116    joins: RawPresenceState,
117    leaves: RawPresenceState,
118}
119
120impl From<RawPresenceDiff> for PresenceDiff {
121    fn from(val: RawPresenceDiff) -> Self {
122        PresenceDiff {
123            joins: val.joins.into(),
124            leaves: val.leaves.into(),
125        }
126    }
127}
128
129#[derive(Debug, Clone)]
130pub(crate) struct PresenceDiff {
131    joins: PresenceState,
132    leaves: PresenceState,
133}
134
135pub(crate) struct Presence {
136    pub state: PresenceState,
137    callbacks: HashMap<PresenceEvent, Vec<PresenceCallback>>,
138    presence_callback_event_sender: CrossbeamEventSender<PresenceCallbackEvent>,
139}
140
141impl Presence {
142    pub(crate) fn from_channel_builder(
143        callbacks: HashMap<PresenceEvent, Vec<PresenceCallback>>,
144        presence_callback_event_sender: CrossbeamEventSender<PresenceCallbackEvent>,
145    ) -> Self {
146        Self {
147            state: PresenceState::default(),
148            callbacks,
149            presence_callback_event_sender,
150        }
151    }
152
153    pub(crate) fn sync(&mut self, new_state: PresenceState) {
154        // TODO state? functional? Nah both mixed together. lol and also lmao even
155        let joins: PresenceState = new_state
156            .0
157            .clone()
158            .into_iter()
159            .map(|(new_id, mut new_phx_map)| {
160                new_phx_map.retain(|new_phx_ref, _new_state_data| {
161                    let mut retain = true;
162                    let _ = self.state.0.clone().into_values().map(|self_phx_map| {
163                        if self_phx_map.contains_key(new_phx_ref) {
164                            retain = false;
165                        }
166                    });
167                    retain
168                });
169
170                (new_id, new_phx_map)
171            })
172            .collect();
173
174        let leaves: PresenceState = self
175            .state
176            .0
177            .clone()
178            .into_iter()
179            .map(|(current_id, mut current_phx_map)| {
180                current_phx_map.retain(|current_phx_ref, _current_state_data| {
181                    let mut retain = false;
182                    let _ = new_state.0.clone().into_values().map(|new_phx_map| {
183                        if !new_phx_map.contains_key(current_phx_ref) {
184                            retain = true;
185                        }
186                    });
187                    retain
188                });
189
190                (current_id, current_phx_map)
191            })
192            .collect();
193
194        let prev_state = self.state.clone();
195
196        self.sync_diff(PresenceDiff { joins, leaves });
197
198        for (id, _data) in self.state.0.clone() {
199            for cb in self
200                .callbacks
201                .get_mut(&PresenceEvent::Sync)
202                .unwrap_or(&mut vec![])
203            {
204                self.presence_callback_event_sender
205                    .send(PresenceCallbackEvent((
206                        cb.0,
207                        (id.clone(), prev_state.clone(), self.state.clone()),
208                    )));
209            }
210        }
211    }
212
213    pub(crate) fn sync_diff(&mut self, diff: PresenceDiff) -> &PresenceState {
214        // mutate own state with diff
215        // return new state
216        // trigger diff callbacks
217
218        for (id, _data) in diff.joins.0.clone() {
219            for cb in self
220                .callbacks
221                .get_mut(&PresenceEvent::Join)
222                .unwrap_or(&mut vec![])
223            {
224                self.presence_callback_event_sender
225                    .send(PresenceCallbackEvent((
226                        cb.0,
227                        (id.clone(), self.state.clone(), diff.clone().joins),
228                    )));
229            }
230        }
231
232        for (id, _data) in diff.leaves.0.clone() {
233            for cb in self
234                .callbacks
235                .get_mut(&PresenceEvent::Leave)
236                .unwrap_or(&mut vec![])
237            {
238                self.presence_callback_event_sender
239                    .send(PresenceCallbackEvent((
240                        cb.0,
241                        (id.clone(), self.state.clone(), diff.clone().leaves),
242                    )));
243            }
244
245            self.state.0.remove(&id);
246        }
247
248        self.state.0.extend(diff.joins.0);
249
250        &self.state
251    }
252}
253
254// State tracking
255
256#[derive(Component)]
257pub struct PrescenceTrack {
258    pub payload: HashMap<String, Value>,
259}
260
261pub fn update_presence_track(
262    q: Query<(&PrescenceTrack, &Channel), Or<(Changed<PrescenceTrack>, Added<Channel>)>>,
263) {
264    for (p, c) in q.iter() {
265        c.track(p.payload.clone()).unwrap();
266    }
267}
268
269pub fn presence_untrack(q: Query<&Channel>, mut removed: RemovedComponents<PrescenceTrack>) {
270    for r in removed.read() {
271        if let Ok(c) = q.get(r) {
272            c.untrack().unwrap();
273        }
274    }
275}