Skip to main content

auraxis/realtime/
subscription.rs

1use crate::realtime::utils::{
2    serialize_all_subscription, serialize_char_ids_subscription, serialize_world_ids_subscription,
3};
4
5use crate::realtime::event::EventNames;
6use crate::realtime::Service;
7use crate::{CharacterID, WorldID};
8use serde::Serialize;
9use std::collections::HashSet;
10
11#[derive(Serialize, Clone, Debug, PartialEq, Eq)]
12#[serde(untagged)]
13pub enum CharacterSubscription {
14    #[serde(serialize_with = "serialize_all_subscription")]
15    All,
16    #[serde(serialize_with = "serialize_char_ids_subscription")]
17    Ids(Vec<CharacterID>),
18}
19
20#[derive(Serialize, Clone, Debug, PartialEq, Eq)]
21#[serde(untagged)]
22pub enum WorldSubscription {
23    #[serde(serialize_with = "serialize_all_subscription")]
24    All,
25    // TODO: WorldIds enum instead of WorldId u64?
26    #[serde(serialize_with = "serialize_world_ids_subscription")]
27    Ids(Vec<WorldID>),
28}
29
30#[derive(Serialize, Clone, Debug, PartialEq, Eq)]
31#[serde(untagged)]
32pub enum EventSubscription {
33    #[serde(serialize_with = "serialize_all_subscription")]
34    All,
35    Ids(Vec<EventNames>),
36}
37
38#[derive(Serialize, Clone, Debug, PartialEq, Eq)]
39#[serde(rename_all = "camelCase")]
40pub struct SubscriptionSettings {
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub event_names: Option<EventSubscription>,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub characters: Option<CharacterSubscription>,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub logical_and_characters_with_worlds: Option<bool>,
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub worlds: Option<WorldSubscription>,
49    pub service: Service,
50}
51
52impl Default for SubscriptionSettings {
53    fn default() -> Self {
54        Self {
55            event_names: Some(EventSubscription::All),
56            characters: Some(CharacterSubscription::All),
57            logical_and_characters_with_worlds: None,
58            worlds: Some(WorldSubscription::All),
59            service: Service::Event,
60        }
61    }
62}
63
64impl SubscriptionSettings {
65    pub fn empty() -> Self {
66        Self {
67            event_names: None,
68            characters: None,
69            logical_and_characters_with_worlds: None,
70            worlds: None,
71            service: Service::Event,
72        }
73    }
74
75    pub fn is_empty(&self) -> bool {
76        self.event_names.is_none()
77            && self.characters.is_none()
78            && self.worlds.is_none()
79            && self.logical_and_characters_with_worlds.is_none()
80    }
81
82    pub fn merge(&mut self, other: Self) {
83        self.event_names = merge_event_subscription(self.event_names.take(), other.event_names);
84        self.characters = merge_character_subscription(self.characters.take(), other.characters);
85        self.worlds = merge_world_subscription(self.worlds.take(), other.worlds);
86        self.logical_and_characters_with_worlds = other
87            .logical_and_characters_with_worlds
88            .or(self.logical_and_characters_with_worlds);
89        self.service = other.service;
90    }
91
92    pub fn clear(&mut self, other: &Self) {
93        self.event_names =
94            clear_event_subscription(self.event_names.take(), other.event_names.as_ref());
95        self.characters =
96            clear_character_subscription(self.characters.take(), other.characters.as_ref());
97        self.worlds = clear_world_subscription(self.worlds.take(), other.worlds.as_ref());
98
99        if other.logical_and_characters_with_worlds.is_some() {
100            self.logical_and_characters_with_worlds = None;
101        }
102    }
103}
104
105fn merge_event_subscription(
106    current: Option<EventSubscription>,
107    update: Option<EventSubscription>,
108) -> Option<EventSubscription> {
109    match (current, update) {
110        (Some(EventSubscription::All), _) | (_, Some(EventSubscription::All)) => {
111            Some(EventSubscription::All)
112        }
113        (Some(EventSubscription::Ids(mut current)), Some(EventSubscription::Ids(update))) => {
114            current.extend(update);
115            current.dedup();
116            Some(EventSubscription::Ids(current))
117        }
118        (None, Some(update)) | (Some(update), None) => Some(update),
119        (None, None) => None,
120    }
121}
122
123fn merge_character_subscription(
124    current: Option<CharacterSubscription>,
125    update: Option<CharacterSubscription>,
126) -> Option<CharacterSubscription> {
127    match (current, update) {
128        (Some(CharacterSubscription::All), _) | (_, Some(CharacterSubscription::All)) => {
129            Some(CharacterSubscription::All)
130        }
131        (
132            Some(CharacterSubscription::Ids(mut current)),
133            Some(CharacterSubscription::Ids(update)),
134        ) => {
135            let mut seen = current.iter().copied().collect::<HashSet<_>>();
136            for id in update {
137                if seen.insert(id) {
138                    current.push(id);
139                }
140            }
141            Some(CharacterSubscription::Ids(current))
142        }
143        (None, Some(update)) | (Some(update), None) => Some(update),
144        (None, None) => None,
145    }
146}
147
148fn merge_world_subscription(
149    current: Option<WorldSubscription>,
150    update: Option<WorldSubscription>,
151) -> Option<WorldSubscription> {
152    match (current, update) {
153        (Some(WorldSubscription::All), _) | (_, Some(WorldSubscription::All)) => {
154            Some(WorldSubscription::All)
155        }
156        (Some(WorldSubscription::Ids(mut current)), Some(WorldSubscription::Ids(update))) => {
157            let mut seen = current.iter().copied().collect::<HashSet<_>>();
158            for id in update {
159                if seen.insert(id) {
160                    current.push(id);
161                }
162            }
163            Some(WorldSubscription::Ids(current))
164        }
165        (None, Some(update)) | (Some(update), None) => Some(update),
166        (None, None) => None,
167    }
168}
169
170fn clear_event_subscription(
171    current: Option<EventSubscription>,
172    clear: Option<&EventSubscription>,
173) -> Option<EventSubscription> {
174    match (current, clear) {
175        (None, _) => None,
176        (Some(_), Some(EventSubscription::All)) => None,
177        (Some(EventSubscription::All), Some(EventSubscription::Ids(_))) => {
178            Some(EventSubscription::All)
179        }
180        (Some(EventSubscription::Ids(current)), Some(EventSubscription::Ids(clear))) => {
181            let clear = clear.iter().cloned().collect::<HashSet<_>>();
182            let remaining = current
183                .into_iter()
184                .filter(|event| !clear.contains(event))
185                .collect::<Vec<_>>();
186
187            (!remaining.is_empty()).then_some(EventSubscription::Ids(remaining))
188        }
189        (some, None) => some,
190    }
191}
192
193fn clear_character_subscription(
194    current: Option<CharacterSubscription>,
195    clear: Option<&CharacterSubscription>,
196) -> Option<CharacterSubscription> {
197    match (current, clear) {
198        (None, _) => None,
199        (Some(_), Some(CharacterSubscription::All)) => None,
200        (Some(CharacterSubscription::All), Some(CharacterSubscription::Ids(_))) => {
201            Some(CharacterSubscription::All)
202        }
203        (Some(CharacterSubscription::Ids(current)), Some(CharacterSubscription::Ids(clear))) => {
204            let clear = clear.iter().copied().collect::<HashSet<_>>();
205            let remaining = current
206                .into_iter()
207                .filter(|id| !clear.contains(id))
208                .collect::<Vec<_>>();
209
210            (!remaining.is_empty()).then_some(CharacterSubscription::Ids(remaining))
211        }
212        (some, None) => some,
213    }
214}
215
216fn clear_world_subscription(
217    current: Option<WorldSubscription>,
218    clear: Option<&WorldSubscription>,
219) -> Option<WorldSubscription> {
220    match (current, clear) {
221        (None, _) => None,
222        (Some(_), Some(WorldSubscription::All)) => None,
223        (Some(WorldSubscription::All), Some(WorldSubscription::Ids(_))) => {
224            Some(WorldSubscription::All)
225        }
226        (Some(WorldSubscription::Ids(current)), Some(WorldSubscription::Ids(clear))) => {
227            let clear = clear.iter().copied().collect::<HashSet<_>>();
228            let remaining = current
229                .into_iter()
230                .filter(|id| !clear.contains(id))
231                .collect::<Vec<_>>();
232
233            (!remaining.is_empty()).then_some(WorldSubscription::Ids(remaining))
234        }
235        (some, None) => some,
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use super::{
242        CharacterSubscription, EventSubscription, SubscriptionSettings, WorldSubscription,
243    };
244    use crate::realtime::event::EventNames;
245    use crate::WorldID;
246
247    #[test]
248    fn merge_is_additive() {
249        let mut subscription = SubscriptionSettings::empty();
250        subscription.merge(SubscriptionSettings {
251            event_names: Some(EventSubscription::Ids(vec![EventNames::Death])),
252            ..SubscriptionSettings::empty()
253        });
254        subscription.merge(SubscriptionSettings {
255            worlds: Some(WorldSubscription::Ids(vec![WorldID::Emerald])),
256            ..SubscriptionSettings::empty()
257        });
258
259        assert_eq!(
260            subscription.event_names,
261            Some(EventSubscription::Ids(vec![EventNames::Death]))
262        );
263        assert_eq!(
264            subscription.worlds,
265            Some(WorldSubscription::Ids(vec![WorldID::Emerald]))
266        );
267    }
268
269    #[test]
270    fn clear_removes_requested_entries() {
271        let mut subscription = SubscriptionSettings {
272            event_names: Some(EventSubscription::Ids(vec![
273                EventNames::Death,
274                EventNames::PlayerLogin,
275            ])),
276            characters: Some(CharacterSubscription::Ids(vec![1, 2])),
277            worlds: Some(WorldSubscription::Ids(vec![WorldID::Emerald])),
278            logical_and_characters_with_worlds: Some(true),
279            ..SubscriptionSettings::empty()
280        };
281
282        subscription.clear(&SubscriptionSettings {
283            event_names: Some(EventSubscription::Ids(vec![EventNames::Death])),
284            characters: Some(CharacterSubscription::Ids(vec![2])),
285            logical_and_characters_with_worlds: Some(true),
286            ..SubscriptionSettings::empty()
287        });
288
289        assert_eq!(
290            subscription.event_names,
291            Some(EventSubscription::Ids(vec![EventNames::PlayerLogin]))
292        );
293        assert_eq!(
294            subscription.characters,
295            Some(CharacterSubscription::Ids(vec![1]))
296        );
297        assert_eq!(subscription.logical_and_characters_with_worlds, None);
298    }
299}