use std::borrow::Cow;
use crate::{
CacheableModels, InMemoryCache, UpdateCache,
config::ResourceType,
model::member::ComputedInteractionMember,
traits::{CacheableGuild, CacheableMember},
};
use twilight_model::{
application::interaction::InteractionMember,
gateway::payload::incoming::{MemberAdd, MemberChunk, MemberRemove, MemberUpdate},
guild::{Member, PartialMember},
id::{
Id,
marker::{GuildMarker, UserMarker},
},
};
impl<CacheModels: CacheableModels> InMemoryCache<CacheModels> {
pub(crate) fn cache_members(
&self,
guild_id: Id<GuildMarker>,
members: impl IntoIterator<Item = Member>,
) {
for member in members {
self.cache_member(guild_id, member);
}
}
pub(crate) fn cache_member(&self, guild_id: Id<GuildMarker>, member: Member) {
let member_id = member.user.id;
let id = (guild_id, member_id);
if let Some(m) = self.members.get(&id)
&& *m == member
{
return;
}
self.cache_user(Cow::Borrowed(&member.user), Some(guild_id));
let cached = CacheModels::Member::from(member);
self.members.insert(id, cached);
self.guild_members
.entry(guild_id)
.or_default()
.insert(member_id);
}
pub(crate) fn cache_borrowed_partial_member(
&self,
guild_id: Id<GuildMarker>,
member: &PartialMember,
user_id: Id<UserMarker>,
) {
let id = (guild_id, user_id);
if let Some(m) = self.members.get(&id)
&& &*m == member
{
return;
}
self.guild_members
.entry(guild_id)
.or_default()
.insert(user_id);
let cached = CacheModels::Member::from((user_id, member.clone()));
self.members.insert(id, cached);
}
pub(crate) fn cache_borrowed_interaction_member(
&self,
guild_id: Id<GuildMarker>,
member: &InteractionMember,
user_id: Id<UserMarker>,
) {
let id = (guild_id, user_id);
let (avatar, deaf, mute) = match self.members.get(&id) {
Some(m) if &*m == member => return,
Some(m) => (m.avatar(), m.deaf(), m.mute()),
None => (None, None, None),
};
self.guild_members
.entry(guild_id)
.or_default()
.insert(user_id);
let cached = CacheModels::Member::from(ComputedInteractionMember {
avatar,
deaf,
interaction_member: member.clone(),
mute,
user_id,
});
self.members.insert(id, cached);
}
}
impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for MemberAdd {
fn update(&self, cache: &InMemoryCache<CacheModels>) {
if cache.wants(ResourceType::GUILD)
&& let Some(mut guild) = cache.guilds.get_mut(&self.guild_id)
{
guild.increase_member_count(1);
}
if !cache.wants(ResourceType::MEMBER) {
return;
}
cache.cache_member(self.guild_id, self.member.clone());
}
}
impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for MemberChunk {
fn update(&self, cache: &InMemoryCache<CacheModels>) {
if !cache.wants(ResourceType::MEMBER) {
return;
}
if self.members.is_empty() {
return;
}
cache.cache_members(self.guild_id, self.members.clone());
}
}
impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for MemberRemove {
fn update(&self, cache: &InMemoryCache<CacheModels>) {
if cache.wants(ResourceType::GUILD)
&& let Some(mut guild) = cache.guilds.get_mut(&self.guild_id)
{
guild.decrease_member_count(1);
}
if !cache.wants(ResourceType::MEMBER) {
return;
}
cache.members.remove(&(self.guild_id, self.user.id));
if let Some(mut members) = cache.guild_members.get_mut(&self.guild_id) {
members.remove(&self.user.id);
}
let mut remove_user = false;
if let Some(mut user_guilds) = cache.user_guilds.get_mut(&self.user.id) {
user_guilds.remove(&self.guild_id);
remove_user = user_guilds.is_empty();
}
if remove_user {
cache.users.remove(&self.user.id);
}
}
}
impl<CacheModels: CacheableModels> UpdateCache<CacheModels> for MemberUpdate {
fn update(&self, cache: &InMemoryCache<CacheModels>) {
if !cache.wants(ResourceType::MEMBER) {
return;
}
let key = (self.guild_id, self.user.id);
if let Some(mut member) = cache.members.get_mut(&key) {
member.update_with_member_update(self);
cache.cache_user(Cow::Borrowed(&self.user), Some(self.guild_id));
}
}
}
#[cfg(test)]
mod tests {
use crate::{DefaultInMemoryCache, test};
use std::borrow::Cow;
use twilight_model::{
gateway::payload::incoming::{MemberRemove, MemberUpdate},
guild::{Member, MemberFlags},
id::Id,
};
#[test]
fn cache_guild_member() {
let cache = DefaultInMemoryCache::new();
{
let guild_1_user_ids = (1..=10).map(Id::new).collect::<Vec<_>>();
let guild_1_members = guild_1_user_ids
.iter()
.copied()
.map(test::member)
.collect::<Vec<_>>();
for member in guild_1_members {
cache.cache_member(Id::new(1), member);
}
let cached_roles = cache.guild_members(Id::new(1)).unwrap();
assert_eq!(cached_roles.len(), guild_1_user_ids.len());
assert!(guild_1_user_ids.iter().all(|id| cached_roles.contains(id)));
assert!(
guild_1_user_ids
.iter()
.all(|id| cache.member(Id::new(1), *id).is_some())
);
assert!(guild_1_user_ids.iter().all(|id| cache.user(*id).is_some()));
}
{
let guild_2_user_ids = (1..=10).map(Id::new).collect::<Vec<_>>();
let guild_2_members = guild_2_user_ids
.iter()
.copied()
.map(test::member)
.collect::<Vec<_>>();
cache.cache_members(Id::new(2), guild_2_members);
let cached_roles = cache.guild_members(Id::new(1)).unwrap();
assert_eq!(cached_roles.len(), guild_2_user_ids.len());
assert!(guild_2_user_ids.iter().all(|id| cached_roles.contains(id)));
assert!(
guild_2_user_ids
.iter()
.copied()
.all(|id| cache.member(Id::new(1), id).is_some())
);
assert!(guild_2_user_ids.iter().all(|id| cache.user(*id).is_some()));
}
}
#[test]
fn cache_user_guild_state() {
let user_id = Id::new(2);
let cache = DefaultInMemoryCache::new();
cache.cache_user(Cow::Owned(test::user(user_id)), Some(Id::new(1)));
{
let user_guilds = cache.user_guilds(user_id).unwrap();
assert!(user_guilds.contains(&Id::new(1)));
assert_eq!(1, user_guilds.len());
}
cache.cache_user(Cow::Owned(test::user(user_id)), Some(Id::new(3)));
{
let user_guilds = cache.user_guilds(user_id).unwrap();
assert!(user_guilds.contains(&Id::new(3)));
assert_eq!(2, user_guilds.len());
}
cache.update(&MemberRemove {
guild_id: Id::new(3),
user: test::user(user_id),
});
{
let user_guilds = cache.user_guilds(user_id).unwrap();
assert!(!user_guilds.contains(&Id::new(3)));
assert_eq!(1, user_guilds.len());
}
cache.update(&MemberRemove {
guild_id: Id::new(1),
user: test::user(user_id),
});
assert!(!cache.users.contains_key(&user_id));
}
#[test]
fn member_update_updates_user() {
let user_id = Id::new(2);
let guild_id = Id::new(3);
let cache = DefaultInMemoryCache::new();
let member = Member {
avatar: None,
avatar_decoration_data: None,
banner: None,
communication_disabled_until: None,
deaf: false,
flags: MemberFlags::empty(),
joined_at: None,
mute: false,
nick: None,
pending: false,
premium_since: None,
roles: Vec::new(),
user: test::user(user_id),
};
cache.cache_member(guild_id, member);
let mut updated_user = test::user(user_id);
updated_user.name = String::from("updated_username");
cache.update(&MemberUpdate {
avatar: None,
communication_disabled_until: None,
guild_id,
flags: None,
deaf: None,
joined_at: None,
mute: None,
nick: None,
pending: false,
premium_since: None,
roles: Vec::new(),
user: updated_user.clone(),
});
let cached_user = cache.user(user_id).unwrap();
assert_eq!(cached_user.value(), &updated_user);
}
}