twilight_cache_inmemory/event/
member.rs

1use std::borrow::Cow;
2
3use crate::{
4    CacheableModels, InMemoryCache, UpdateCache,
5    config::ResourceType,
6    model::member::ComputedInteractionMember,
7    traits::{CacheableGuild, CacheableMember},
8};
9use twilight_model::{
10    application::interaction::InteractionMember,
11    gateway::payload::incoming::{MemberAdd, MemberChunk, MemberRemove, MemberUpdate},
12    guild::{Member, PartialMember},
13    id::{
14        Id,
15        marker::{GuildMarker, UserMarker},
16    },
17};
18
19impl<CacheModels: CacheableModels> InMemoryCache<CacheModels> {
20    pub(crate) fn cache_members(
21        &self,
22        guild_id: Id<GuildMarker>,
23        members: impl IntoIterator<Item = Member>,
24    ) {
25        for member in members {
26            self.cache_member(guild_id, member);
27        }
28    }
29
30    pub(crate) fn cache_member(&self, guild_id: Id<GuildMarker>, member: Member) {
31        let member_id = member.user.id;
32        let id = (guild_id, member_id);
33
34        if let Some(m) = self.members.get(&id)
35            && *m == member
36        {
37            return;
38        }
39
40        self.cache_user(Cow::Borrowed(&member.user), Some(guild_id));
41        let cached = CacheModels::Member::from(member);
42        self.members.insert(id, cached);
43        self.guild_members
44            .entry(guild_id)
45            .or_default()
46            .insert(member_id);
47    }
48
49    pub(crate) fn cache_borrowed_partial_member(
50        &self,
51        guild_id: Id<GuildMarker>,
52        member: &PartialMember,
53        user_id: Id<UserMarker>,
54    ) {
55        let id = (guild_id, user_id);
56
57        if let Some(m) = self.members.get(&id)
58            && &*m == member
59        {
60            return;
61        }
62
63        self.guild_members
64            .entry(guild_id)
65            .or_default()
66            .insert(user_id);
67
68        let cached = CacheModels::Member::from((user_id, member.clone()));
69        self.members.insert(id, cached);
70    }
71
72    pub(crate) fn cache_borrowed_interaction_member(
73        &self,
74        guild_id: Id<GuildMarker>,
75        member: &InteractionMember,
76        user_id: Id<UserMarker>,
77    ) {
78        let id = (guild_id, user_id);
79
80        let (avatar, deaf, mute) = match self.members.get(&id) {
81            Some(m) if &*m == member => return,
82            Some(m) => (m.avatar(), m.deaf(), m.mute()),
83            None => (None, None, None),
84        };
85
86        self.guild_members
87            .entry(guild_id)
88            .or_default()
89            .insert(user_id);
90
91        let cached = CacheModels::Member::from(ComputedInteractionMember {
92            avatar,
93            deaf,
94            interaction_member: member.clone(),
95            mute,
96            user_id,
97        });
98
99        self.members.insert(id, cached);
100    }
101}
102
103impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for MemberAdd {
104    fn update(&self, cache: &InMemoryCache<CacheModels>) {
105        if cache.wants(ResourceType::GUILD)
106            && let Some(mut guild) = cache.guilds.get_mut(&self.guild_id)
107        {
108            guild.increase_member_count(1);
109        }
110
111        if !cache.wants(ResourceType::MEMBER) {
112            return;
113        }
114
115        cache.cache_member(self.guild_id, self.member.clone());
116    }
117}
118
119impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for MemberChunk {
120    fn update(&self, cache: &InMemoryCache<CacheModels>) {
121        if !cache.wants(ResourceType::MEMBER) {
122            return;
123        }
124
125        if self.members.is_empty() {
126            return;
127        }
128
129        cache.cache_members(self.guild_id, self.members.clone());
130    }
131}
132
133impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for MemberRemove {
134    fn update(&self, cache: &InMemoryCache<CacheModels>) {
135        if cache.wants(ResourceType::GUILD)
136            && let Some(mut guild) = cache.guilds.get_mut(&self.guild_id)
137        {
138            guild.decrease_member_count(1);
139        }
140
141        if !cache.wants(ResourceType::MEMBER) {
142            return;
143        }
144
145        cache.members.remove(&(self.guild_id, self.user.id));
146
147        if let Some(mut members) = cache.guild_members.get_mut(&self.guild_id) {
148            members.remove(&self.user.id);
149        }
150
151        // Avoid a deadlock by mutating the user, dropping the lock to the map,
152        // and then removing the user later if they are in no guilds.
153        let mut remove_user = false;
154
155        if let Some(mut user_guilds) = cache.user_guilds.get_mut(&self.user.id) {
156            user_guilds.remove(&self.guild_id);
157
158            remove_user = user_guilds.is_empty();
159        }
160
161        if remove_user {
162            cache.users.remove(&self.user.id);
163        }
164    }
165}
166
167impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for MemberUpdate {
168    fn update(&self, cache: &InMemoryCache<CacheModels>) {
169        if !cache.wants(ResourceType::MEMBER) {
170            return;
171        }
172
173        let key = (self.guild_id, self.user.id);
174
175        if let Some(mut member) = cache.members.get_mut(&key) {
176            member.update_with_member_update(self);
177            cache.cache_user(Cow::Borrowed(&self.user), Some(self.guild_id));
178        }
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use crate::{DefaultInMemoryCache, test};
185    use std::borrow::Cow;
186    use twilight_model::{
187        gateway::payload::incoming::{MemberRemove, MemberUpdate},
188        guild::{Member, MemberFlags},
189        id::Id,
190    };
191
192    #[test]
193    fn cache_guild_member() {
194        let cache = DefaultInMemoryCache::new();
195
196        // Single inserts
197        {
198            let guild_1_user_ids = (1..=10).map(Id::new).collect::<Vec<_>>();
199            let guild_1_members = guild_1_user_ids
200                .iter()
201                .copied()
202                .map(test::member)
203                .collect::<Vec<_>>();
204
205            for member in guild_1_members {
206                cache.cache_member(Id::new(1), member);
207            }
208
209            // Check for the cached guild members ids
210            let cached_roles = cache.guild_members(Id::new(1)).unwrap();
211            assert_eq!(cached_roles.len(), guild_1_user_ids.len());
212            assert!(guild_1_user_ids.iter().all(|id| cached_roles.contains(id)));
213
214            // Check for the cached members
215            assert!(
216                guild_1_user_ids
217                    .iter()
218                    .all(|id| cache.member(Id::new(1), *id).is_some())
219            );
220
221            // Check for the cached users
222            assert!(guild_1_user_ids.iter().all(|id| cache.user(*id).is_some()));
223        }
224
225        // Bulk inserts
226        {
227            let guild_2_user_ids = (1..=10).map(Id::new).collect::<Vec<_>>();
228            let guild_2_members = guild_2_user_ids
229                .iter()
230                .copied()
231                .map(test::member)
232                .collect::<Vec<_>>();
233            cache.cache_members(Id::new(2), guild_2_members);
234
235            // Check for the cached guild members ids
236            let cached_roles = cache.guild_members(Id::new(1)).unwrap();
237            assert_eq!(cached_roles.len(), guild_2_user_ids.len());
238            assert!(guild_2_user_ids.iter().all(|id| cached_roles.contains(id)));
239
240            // Check for the cached members
241            assert!(
242                guild_2_user_ids
243                    .iter()
244                    .copied()
245                    .all(|id| cache.member(Id::new(1), id).is_some())
246            );
247
248            // Check for the cached users
249            assert!(guild_2_user_ids.iter().all(|id| cache.user(*id).is_some()));
250        }
251    }
252
253    #[test]
254    fn cache_user_guild_state() {
255        let user_id = Id::new(2);
256        let cache = DefaultInMemoryCache::new();
257        cache.cache_user(Cow::Owned(test::user(user_id)), Some(Id::new(1)));
258
259        // Test the guild's ID is the only one in the user's set of guilds.
260        {
261            let user_guilds = cache.user_guilds(user_id).unwrap();
262            assert!(user_guilds.contains(&Id::new(1)));
263            assert_eq!(1, user_guilds.len());
264        }
265
266        // Test that a second guild will cause 2 in the set.
267        cache.cache_user(Cow::Owned(test::user(user_id)), Some(Id::new(3)));
268
269        {
270            let user_guilds = cache.user_guilds(user_id).unwrap();
271            assert!(user_guilds.contains(&Id::new(3)));
272            assert_eq!(2, user_guilds.len());
273        }
274
275        // Test that removing a user from a guild will cause the ID to be
276        // removed from the set, leaving the other ID.
277        cache.update(&MemberRemove {
278            guild_id: Id::new(3),
279            user: test::user(user_id),
280        });
281
282        {
283            let user_guilds = cache.user_guilds(user_id).unwrap();
284            assert!(!user_guilds.contains(&Id::new(3)));
285            assert_eq!(1, user_guilds.len());
286        }
287
288        // Test that removing the user from its last guild removes the user's
289        // entry.
290        cache.update(&MemberRemove {
291            guild_id: Id::new(1),
292            user: test::user(user_id),
293        });
294        assert!(!cache.users.contains_key(&user_id));
295    }
296
297    #[test]
298    fn member_update_updates_user() {
299        let user_id = Id::new(2);
300        let guild_id = Id::new(3);
301        let cache = DefaultInMemoryCache::new();
302
303        let member = Member {
304            avatar: None,
305            avatar_decoration_data: None,
306            banner: None,
307            communication_disabled_until: None,
308            deaf: false,
309            flags: MemberFlags::empty(),
310            joined_at: None,
311            mute: false,
312            nick: None,
313            pending: false,
314            premium_since: None,
315            roles: Vec::new(),
316            user: test::user(user_id),
317        };
318
319        cache.cache_member(guild_id, member);
320
321        let mut updated_user = test::user(user_id);
322        updated_user.name = String::from("updated_username");
323
324        // Test that a member update also updates the user.
325        cache.update(&MemberUpdate {
326            avatar: None,
327            communication_disabled_until: None,
328            guild_id,
329            flags: None,
330            deaf: None,
331            joined_at: None,
332            mute: None,
333            nick: None,
334            pending: false,
335            premium_since: None,
336            roles: Vec::new(),
337            user: updated_user.clone(),
338        });
339
340        let cached_user = cache.user(user_id).unwrap();
341        assert_eq!(cached_user.value(), &updated_user);
342    }
343}