diff options
| author | Zeyla Hellyer <[email protected]> | 2017-02-09 13:34:08 -0800 |
|---|---|---|
| committer | Zeyla Hellyer <[email protected]> | 2017-02-09 13:34:08 -0800 |
| commit | 0c9ec377aa7281fb3d4bc390c896b426660a5387 (patch) | |
| tree | a355bda0c0d02d8b67331e0a99090c7b26206cb1 /src/model/guild.rs | |
| parent | Release v0.1.5 (diff) | |
| download | serenity-0c9ec377aa7281fb3d4bc390c896b426660a5387.tar.xz serenity-0c9ec377aa7281fb3d4bc390c896b426660a5387.zip | |
Optimize caching
Improve the cache by keeping track of new maps, making other maps have
`Arc<RwLock>` values, optimizing already-existing methods, and take advantage
of new, more efficient retrievals (e.g. simply keying a value from a map rather
than iterating over vecs or maps and then itering over another vec).
Keep track of two new maps in the cache:
- **channels**: a map of all guild channels that exist, so that they can be
efficiently found, and so a message's guild can be efficiently found
- **users**: a map of all users that exist, so that it can be shared across
all members and presences
Other cache fields now have `Arc<RwLock>` values:
- `groups`
- `guilds`
- `private_channels`
`Cache::unavailable_guilds` is now a `HashSet<GuildId>` instead of a
`Vec<GuildId>`. This should slightly optimize removals/insertions for large
bots.
`ext::cache::ChannelRef` has been removed as it became equivilant in
functionality to `model::Channel`. Also, `model::Channel` now has all variant
data encased in `Arc<RwLock>`s. E.g., `Channel::Group(Group)` is now
`Channel::Group(Arc<RwLock<Group>>)`.
Some model struct fields are now wrapped in an `Arc<RwLock>`. These are:
- `Group::recipients`: `HashMap<UserId, User>` -> `HashMap<UserId, Arc<RwLock<User>>>`
- `Guild::channels`: `HashMap<ChannelId, GuildChannel>` -> `HashMap<ChannelId, Arc<RwLock<GuildChannel>>>`
- `Member::user`: `User` -> `Arc<RwLock<User>>`
- `PrivateChannel::recipient`: `User` -> `Arc<RwLock<User>>`
Some (cache-enabled) event handler signatures have changed to use
`Arc<RwLock>`s:
- `Client::on_call_delete`
- `Client::on_call_update`
- `Client::on_guild_delete`
- `Client::on_guild_update`
Many function signatures have changed:
- `Cache::get_call` now returns a `Option<Arc<RwLock<Call>>>` instead of a
`Option<&Call>`
- `Cache::get_channel` now returns a `Option<Channel>` instead of a
`Option<ChannelRef>`. This now also retrieves directly from the
`Guild::channels` instead of iterating over guilds' for a guild channel
- `Cache::get_guild` now returns a `Option<Arc<RwLock<Guild>>>` instead of a
`Option<&Guild>`
- `Cache::get_guild_channel` now returns a `Option<Arc<RwLock<GuildChannel>>>`
instead of a `Option<&GuildChannel>`
- `Cache::get_group` now returns a `Option<Arc<RwLock<Group>>>` instead of a
`Option<&Group>`
- `Cache::get_member` now returns a `Option<Member>` instead of a
`Option<&Member>`, due to guilds being behind a lock themselves
- `Cache::get_role` now returns a `Option<Role>` instead of a `Option<&Role>`
for the above reason
- `Cache::get_user` now returns a `Option<Arc<RwLock<User>>>` instead of a
`Option<&User>`
- `GuildId::find` now returns a `Option<Arc<RwLock<Guild>>>` instead of a
`Option<Guild>`
- `UserId::find` now returns a `Option<Arc<RwLock<User>>>` instead of a
`Option<User>`
- `Member::display_name` now returns a `Cow<String>` instead of a `&str`
A new cache method has been added, `Cache::get_private_channel`, to retrieve a
`PrivateChannel`.
The `Display` formatter for `Channel` has been optimized to not clone.
Diffstat (limited to 'src/model/guild.rs')
| -rw-r--r-- | src/model/guild.rs | 154 |
1 files changed, 86 insertions, 68 deletions
diff --git a/src/model/guild.rs b/src/model/guild.rs index 0d557c6..0ee68fd 100644 --- a/src/model/guild.rs +++ b/src/model/guild.rs @@ -1,6 +1,8 @@ use serde_json::builder::ObjectBuilder; +use std::borrow::Cow; use std::cmp::Ordering; use std::collections::HashMap; +use std::sync::{Arc, RwLock}; use std::fmt; use super::utils::{ decode_emojis, @@ -95,12 +97,15 @@ impl Emoji { /// [`Guild`]: struct.Guild.html #[cfg(feature="cache")] pub fn find_guild_id(&self) -> Option<GuildId> { - CACHE.read() - .unwrap() - .guilds - .values() - .find(|guild| guild.emojis.contains_key(&self.id)) - .map(|guild| guild.id) + for guild in CACHE.read().unwrap().guilds.values() { + let guild = guild.read().unwrap(); + + if guild.emojis.contains_key(&self.id) { + return Some(guild.id); + } + } + + None } /// Generates a URL to the emoji's image. @@ -161,7 +166,7 @@ impl Guild { None => return Err(Error::Client(ClientError::ItemMissing)), }; - let perms = self.permissions_for(ChannelId(self.id.0), member.user.id); + let perms = self.permissions_for(ChannelId(self.id.0), member.user.read().unwrap().id); permissions.remove(perms); Ok(permissions.is_empty()) @@ -387,23 +392,23 @@ impl Guild { let id = remove(&mut map, "id").and_then(GuildId::decode)?; - let public_channels = { - let mut public_channels = HashMap::new(); + let channels = { + let mut channels = HashMap::new(); let vals = decode_array(remove(&mut map, "channels")?, |v| GuildChannel::decode_guild(v, id))?; - for public_channel in vals { - public_channels.insert(public_channel.id, public_channel); + for channel in vals { + channels.insert(channel.id, Arc::new(RwLock::new(channel))); } - public_channels + channels }; Ok(Guild { afk_channel_id: opt(&mut map, "afk_channel_id", ChannelId::decode)?, afk_timeout: req!(remove(&mut map, "afk_timeout")?.as_u64()), - channels: public_channels, + channels: channels, default_message_notifications: req!(remove(&mut map, "default_message_notifications")?.as_u64()), emojis: remove(&mut map, "emojis").and_then(decode_emojis)?, features: remove(&mut map, "features").and_then(|v| decode_array(v, Feature::decode_str))?, @@ -756,9 +761,9 @@ impl Guild { self.members .values() .find(|member| { - let name_matches = member.user.name == name; + let name_matches = member.user.read().unwrap().name == name; let discrim_matches = match discrim { - Some(discrim) => member.user.discriminator == discrim, + Some(discrim) => member.user.read().unwrap().discriminator == discrim, None => true, }; @@ -889,7 +894,7 @@ impl Guild { permissions |= role.permissions; } else { warn!("(╯°□°)╯︵ ┻━┻ {} on {} has non-existent role {:?}", - member.user.id, + member.user.read().unwrap().id, self.id, role); } @@ -901,6 +906,8 @@ impl Guild { } if let Some(channel) = self.channels.get(&channel_id) { + let channel = channel.read().unwrap(); + // If this is a text channel, then throw out voice permissions. if channel.kind == ChannelType::Text { permissions &= !(CONNECT | SPEAK | MUTE_MEMBERS | @@ -1366,8 +1373,8 @@ impl GuildId { /// Search the cache for the guild. #[cfg(feature="cache")] - pub fn find(&self) -> Option<Guild> { - CACHE.read().unwrap().get_guild(*self).cloned() + pub fn find(&self) -> Option<Arc<RwLock<Guild>>> { + CACHE.read().unwrap().get_guild(*self) } /// Requests the guild over REST. @@ -1650,7 +1657,7 @@ impl Member { let guild_id = self.find_guild()?; - match rest::add_member_role(guild_id.0, self.user.id.0, role_id.0) { + match rest::add_member_role(guild_id.0, self.user.read().unwrap().id.0, role_id.0) { Ok(()) => { self.roles.push(role_id); @@ -1674,7 +1681,7 @@ impl Member { let map = EditMember::default().roles(&self.roles).0.build(); - match rest::edit_member(guild_id.0, self.user.id.0, map) { + match rest::edit_member(guild_id.0, self.user.read().unwrap().id.0, map) { Ok(()) => Ok(()), Err(why) => { self.roles.retain(|r| !role_ids.contains(r)); @@ -1699,7 +1706,7 @@ impl Member { /// [Ban Members]: permissions/constant.BAN_MEMBERS.html #[cfg(feature="cache")] pub fn ban(&self, delete_message_days: u8) -> Result<()> { - rest::ban_user(self.find_guild()?.0, self.user.id.0, delete_message_days) + rest::ban_user(self.find_guild()?.0, self.user.read().unwrap().id.0, delete_message_days) } /// Determines the member's colour. @@ -1712,13 +1719,13 @@ impl Member { let cache = CACHE.read().unwrap(); let guild = match cache.guilds.get(&guild_id) { - Some(guild) => guild, + Some(guild) => guild.read().unwrap(), None => return None, }; let mut roles = self.roles .iter() - .filter_map(|id| guild.roles.get(id)) + .filter_map(|role_id| guild.roles.get(role_id)) .collect::<Vec<&Role>>(); roles.sort_by(|a, b| b.cmp(a)); @@ -1731,14 +1738,16 @@ impl Member { /// /// The nickname takes priority over the member's username if it exists. #[inline] - pub fn display_name(&self) -> &str { - self.nick.as_ref().unwrap_or(&self.user.name) + pub fn display_name(&self) -> Cow<String> { + self.nick.as_ref() + .map(Cow::Borrowed) + .unwrap_or_else(|| Cow::Owned(self.user.read().unwrap().name.clone())) } /// Returns the DiscordTag of a Member, taking possible nickname into account. #[inline] pub fn distinct(&self) -> String { - format!("{}#{}", self.display_name(), self.user.discriminator) + format!("{}#{}", self.display_name(), self.user.read().unwrap().discriminator) } /// Edits the member with the given data. See [`Context::edit_member`] for @@ -1754,7 +1763,7 @@ impl Member { let guild_id = self.find_guild()?; let map = f(EditMember::default()).0.build(); - rest::edit_member(guild_id.0, self.user.id.0, map) + rest::edit_member(guild_id.0, self.user.read().unwrap().id.0, map) } /// Finds the Id of the [`Guild`] that the member is in. @@ -1768,22 +1777,19 @@ impl Member { /// [`Guild`]: struct.Guild.html #[cfg(feature="cache")] pub fn find_guild(&self) -> Result<GuildId> { - CACHE.read() - .unwrap() - .guilds - .values() - .find(|guild| { - guild.members - .values() - .any(|member| { - let joined_at = member.joined_at == self.joined_at; - let user_id = member.user.id == self.user.id; + for guild in CACHE.read().unwrap().guilds.values() { + let guild = guild.read().unwrap(); + + let predicate = guild.members + .values() + .any(|m| m.joined_at == self.joined_at && m.user.read().unwrap().id == self.user.read().unwrap().id); - joined_at && user_id - }) - }) - .map(|x| x.id) - .ok_or(Error::Client(ClientError::GuildNotFound)) + if predicate { + return Ok(guild.id); + } + } + + Err(Error::Client(ClientError::GuildNotFound)) } /// Removes a [`Role`] from the member, editing its roles in-place if the @@ -1803,7 +1809,7 @@ impl Member { let guild_id = self.find_guild()?; - match rest::remove_member_role(guild_id.0, self.user.id.0, role_id.0) { + match rest::remove_member_role(guild_id.0, self.user.read().unwrap().id.0, role_id.0) { Ok(()) => { self.roles.retain(|r| r.0 != role_id.0); @@ -1826,7 +1832,7 @@ impl Member { let map = EditMember::default().roles(&self.roles).0.build(); - match rest::edit_member(guild_id.0, self.user.id.0, map) { + match rest::edit_member(guild_id.0, self.user.read().unwrap().id.0, map) { Ok(()) => Ok(()), Err(why) => { self.roles.extend_from_slice(role_ids); @@ -1846,12 +1852,18 @@ impl Member { CACHE.read().unwrap() .guilds .values() - .find(|g| g.members + .find(|guild| guild + .read() + .unwrap() + .members .values() - .any(|m| m.user.id == self.user.id && m.joined_at == *self.joined_at)) - .map(|g| g.roles + .any(|m| m.user.read().unwrap().id == self.user.read().unwrap().id && m.joined_at == *self.joined_at)) + .map(|guild| guild + .read() + .unwrap() + .roles .values() - .filter(|r| self.roles.contains(&r.id)) + .filter(|role| self.roles.contains(&role.id)) .cloned() .collect()) } @@ -1870,7 +1882,7 @@ impl Member { /// [Ban Members]: permissions/constant.BAN_MEMBERS.html #[cfg(feature="cache")] pub fn unban(&self) -> Result<()> { - rest::remove_ban(self.find_guild()?.0, self.user.id.0) + rest::remove_ban(self.find_guild()?.0, self.user.read().unwrap().id.0) } } @@ -1886,7 +1898,7 @@ impl fmt::Display for Member { /// // This is in the format of `<@USER_ID>`. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.user.mention(), f) + fmt::Display::fmt(&self.user.read().unwrap().mention(), f) } } @@ -2457,13 +2469,15 @@ impl Role { /// [`ClientError::GuildNotFound`]: ../client/enum.ClientError.html#variant.GuildNotFound #[cfg(feature="cache")] pub fn find_guild(&self) -> Result<GuildId> { - CACHE.read() - .unwrap() - .guilds - .values() - .find(|guild| guild.roles.contains_key(&RoleId(self.id.0))) - .map(|x| x.id) - .ok_or(Error::Client(ClientError::GuildNotFound)) + for guild in CACHE.read().unwrap().guilds.values() { + let guild = guild.read().unwrap(); + + if guild.roles.contains_key(&RoleId(self.id.0)) { + return Ok(guild.id); + } + } + + Err(Error::Client(ClientError::GuildNotFound)) } /// Check that the role has the given permission. @@ -2523,17 +2537,21 @@ impl RoleId { /// Search the cache for the role. #[cfg(feature="cache")] pub fn find(&self) -> Option<Role> { - CACHE.read() - .unwrap() - .guilds - .values() - .find(|guild| guild.roles.contains_key(self)) - .map(|guild| guild.roles.get(self)) - .and_then(|v| match v { - Some(v) => Some(v), - None => None, - }) - .cloned() + let cache = CACHE.read().unwrap(); + + for guild in cache.guilds.values() { + let guild = guild.read().unwrap(); + + if !guild.roles.contains_key(self) { + continue; + } + + if let Some(role) = guild.roles.get(self) { + return Some(role.clone()); + } + } + + None } } |