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 | |
| 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')
| -rw-r--r-- | src/client/context.rs | 12 | ||||
| -rw-r--r-- | src/client/dispatch.rs | 33 | ||||
| -rw-r--r-- | src/client/event_store.rs | 11 | ||||
| -rw-r--r-- | src/client/gateway/shard.rs | 4 | ||||
| -rw-r--r-- | src/client/mod.rs | 8 | ||||
| -rw-r--r-- | src/ext/cache/mod.rs | 667 | ||||
| -rw-r--r-- | src/ext/framework/mod.rs | 18 | ||||
| -rw-r--r-- | src/model/channel.rs | 68 | ||||
| -rw-r--r-- | src/model/gateway.rs | 3 | ||||
| -rw-r--r-- | src/model/guild.rs | 154 | ||||
| -rw-r--r-- | src/model/misc.rs | 10 | ||||
| -rw-r--r-- | src/model/mod.rs | 7 | ||||
| -rw-r--r-- | src/model/user.rs | 44 | ||||
| -rw-r--r-- | src/model/utils.rs | 21 |
14 files changed, 634 insertions, 426 deletions
diff --git a/src/client/context.rs b/src/client/context.rs index bb87c35..ebe6491 100644 --- a/src/client/context.rs +++ b/src/client/context.rs @@ -407,12 +407,12 @@ impl Context { #[cfg(feature="cache")] { - match channel_id.get()? { - Channel::Guild(ref c) if c.kind != ChannelType::Text && - c.kind != ChannelType::Voice => { - return Err(Error::Client(ClientError::UnexpectedChannelType(c.kind))); - }, - _ => {}, + if let Channel::Guild(ref channel) = channel_id.get()? { + let ch = channel.read().unwrap(); + + if ch.kind != ChannelType::Text && ch.kind != ChannelType::Voice { + return Err(Error::Client(ClientError::UnexpectedChannelType(ch.kind))); + } } } diff --git a/src/client/dispatch.rs b/src/client/dispatch.rs index e70f280..33267ff 100644 --- a/src/client/dispatch.rs +++ b/src/client/dispatch.rs @@ -25,6 +25,22 @@ macro_rules! handler { } macro_rules! update { + ($method:ident, @$event:expr) => { + { + #[cfg(feature="cache")] + { + CACHE.write().unwrap().$method(&mut $event) + } + } + }; + ($method:ident, @$event:expr, $old:expr) => { + { + #[cfg(feature="cache")] + { + CACHE.write().unwrap().$method(&mut $event, $old) + } + } + }; ($method:ident, $event:expr) => { { #[cfg(feature="cache")] @@ -107,7 +123,7 @@ fn dispatch_message(context: Context, } } -#[allow(cyclomatic_complexity)] +#[allow(cyclomatic_complexity, unused_mut)] fn handle_event(event: Event, conn: &Arc<Mutex<Shard>>, data: &Arc<Mutex<ShareMap>>, @@ -207,8 +223,8 @@ fn handle_event(event: Event, thread::spawn(move || (handler)(context, event)); } }, - Event::ChannelRecipientAdd(event) => { - update!(update_with_channel_recipient_add, event); + Event::ChannelRecipientAdd(mut event) => { + update!(update_with_channel_recipient_add, @event); if let Some(handler) = handler!(on_channel_recipient_addition, event_store) { let context = context(Some(event.channel_id), @@ -241,8 +257,7 @@ fn handle_event(event: Event, feature_cache! {{ let before = CACHE.read() .unwrap() - .get_channel(event.channel.id()) - .map(|x| x.clone_inner()); + .get_channel(event.channel.id()); update!(update_with_channel_update, event); thread::spawn(move || (handler)(context, before, event.channel)); @@ -324,8 +339,8 @@ fn handle_event(event: Event, thread::spawn(move || (handler)(context, event.guild_id)); } }, - Event::GuildMemberAdd(event) => { - update!(update_with_guild_member_add, event); + Event::GuildMemberAdd(mut event) => { + update!(update_with_guild_member_add, @event); if let Some(handler) = handler!(on_guild_member_addition, event_store) { let context = context(None, conn, data, login_type); @@ -516,8 +531,8 @@ fn handle_event(event: Event, thread::spawn(move || (handler)(context, event.presences)); } }, - Event::PresenceUpdate(event) => { - update!(update_with_presence_update, event); + Event::PresenceUpdate(mut event) => { + update!(update_with_presence_update, @event); if let Some(handler) = handler!(on_presence_update, event_store) { let context = context(None, conn, data, login_type); diff --git a/src/client/event_store.rs b/src/client/event_store.rs index 7cc7e81..f63c03a 100644 --- a/src/client/event_store.rs +++ b/src/client/event_store.rs @@ -14,6 +14,9 @@ use ::model::event::{ }; use ::model::*; +#[cfg(feature="cache")] +use std::sync::RwLock; + #[cfg(not(feature="cache"))] use ::model::event::{ CallUpdateEvent, @@ -44,11 +47,11 @@ use ::model::event::{ pub struct EventStore { pub on_call_create: Option<Arc<Fn(Context, Call) + Send + Sync + 'static>>, #[cfg(feature="cache")] - pub on_call_delete: Option<Arc<Fn(Context, ChannelId, Option<Call>) + Send + Sync + 'static>>, + pub on_call_delete: Option<Arc<Fn(Context, ChannelId, Option<Arc<RwLock<Call>>>) + Send + Sync + 'static>>, #[cfg(not(feature="cache"))] pub on_call_delete: Option<Arc<Fn(Context, ChannelId) + Send + Sync + 'static>>, #[cfg(feature="cache")] - pub on_call_update: Option<Arc<Fn(Context, Option<Call>, Option<Call>) + Send + Sync + 'static>>, + pub on_call_update: Option<Arc<Fn(Context, Option<Arc<RwLock<Call>>>, Option<Arc<RwLock<Call>>>) + Send + Sync + 'static>>, #[cfg(not(feature="cache"))] pub on_call_update: Option<Arc<Fn(Context, CallUpdateEvent) + Send + Sync + 'static>>, pub on_channel_create: Option<Arc<Fn(Context, Channel) + Send + Sync + 'static>>, @@ -67,7 +70,7 @@ pub struct EventStore { pub on_guild_ban_removal: Option<Arc<Fn(Context, GuildId, User) + Send + Sync + 'static>>, pub on_guild_create: Option<Arc<Fn(Context, Guild) + Send + Sync + 'static>>, #[cfg(feature="cache")] - pub on_guild_delete: Option<Arc<Fn(Context, PartialGuild, Option<Guild>) + Send + Sync + 'static>>, + pub on_guild_delete: Option<Arc<Fn(Context, PartialGuild, Option<Arc<RwLock<Guild>>>) + Send + Sync + 'static>>, #[cfg(not(feature="cache"))] pub on_guild_delete: Option<Arc<Fn(Context, PartialGuild) + Send + Sync + 'static>>, pub on_guild_emojis_update: Option<Arc<Fn(Context, GuildId, HashMap<EmojiId, Emoji>) + Send + Sync + 'static>>, @@ -94,7 +97,7 @@ pub struct EventStore { pub on_guild_sync: Option<Arc<Fn(Context, GuildSyncEvent) + Send + Sync + 'static>>, pub on_guild_unavailable: Option<Arc<Fn(Context, GuildId) + Send + Sync + 'static>>, #[cfg(feature="cache")] - pub on_guild_update: Option<Arc<Fn(Context, Option<Guild>, PartialGuild) + Send + Sync + 'static>>, + pub on_guild_update: Option<Arc<Fn(Context, Option<Arc<RwLock<Guild>>>, PartialGuild) + Send + Sync + 'static>>, #[cfg(not(feature="cache"))] pub on_guild_update: Option<Arc<Fn(Context, PartialGuild) + Send + Sync + 'static>>, pub on_message: Option<Arc<Fn(Context, Message) + Send + Sync + 'static>>, diff --git a/src/client/gateway/shard.rs b/src/client/gateway/shard.rs index 283c7cd..8902b0b 100644 --- a/src/client/gateway/shard.rs +++ b/src/client/gateway/shard.rs @@ -668,8 +668,7 @@ impl Shard { match game.as_ref() { Some(game) => { - object.insert_object("game", move |o| o - .insert("name", &game.name)) + object.insert_object("game", move |o| o.insert("name", &game.name)) }, None => object.insert("game", Value::Null), } @@ -682,6 +681,7 @@ impl Shard { { let mut cache = CACHE.write().unwrap(); let current_user_id = cache.user.id; + cache.presences.get_mut(¤t_user_id).map(|presence| { presence.game = game.clone(); presence.last_modified = Some(now); diff --git a/src/client/mod.rs b/src/client/mod.rs index 5ef770a..d0593fe 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -868,7 +868,7 @@ impl Client { /// /// [`CallDelete`]: ../model/event/enum.Event.html#variant.CallDelete pub fn on_call_delete<F>(&mut self, handler: F) - where F: Fn(Context, ChannelId, Option<Call>) + Send + Sync + 'static { + where F: Fn(Context, ChannelId, Option<Arc<RwLock<Call>>>) + Send + Sync + 'static { self.event_store.write() .unwrap() .on_call_delete = Some(Arc::new(handler)); @@ -878,7 +878,7 @@ impl Client { /// /// [`CallUpdate`]: ../model/event/enum.Event.html#variant.CallUpdate pub fn on_call_update<F>(&mut self, handler: F) - where F: Fn(Context, Option<Call>, Option<Call>) + Send + Sync + 'static { + where F: Fn(Context, Option<Arc<RwLock<Call>>>, Option<Arc<RwLock<Call>>>) + Send + Sync + 'static { self.event_store.write() .unwrap() .on_call_update = Some(Arc::new(handler)); @@ -910,7 +910,7 @@ impl Client { /// [`Role`]: ../model/struct.Role.html /// [`Cache`]: ../ext/cache/struct.Cache.html pub fn on_guild_delete<F>(&mut self, handler: F) - where F: Fn(Context, PartialGuild, Option<Guild>) + Send + Sync + 'static { + where F: Fn(Context, PartialGuild, Option<Arc<RwLock<Guild>>>) + Send + Sync + 'static { self.event_store.write() .unwrap() .on_guild_delete = Some(Arc::new(handler)); @@ -977,7 +977,7 @@ impl Client { /// /// [`GuildUpdate`]: ../model/event/enum.Event.html#variant.GuildUpdate pub fn on_guild_update<F>(&mut self, handler: F) - where F: Fn(Context, Option<Guild>, PartialGuild) + Send + Sync + 'static { + where F: Fn(Context, Option<Arc<RwLock<Guild>>>, PartialGuild) + Send + Sync + 'static { self.event_store.write() .unwrap() .on_guild_update = Some(Arc::new(handler)); diff --git a/src/ext/cache/mod.rs b/src/ext/cache/mod.rs index b6a32cd..d0906bc 100644 --- a/src/ext/cache/mod.rs +++ b/src/ext/cache/mod.rs @@ -76,8 +76,9 @@ //! [`rest`]: ../../client/rest/index.html use std::collections::hash_map::Entry; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::default::Default; +use std::sync::{Arc, RwLock}; use std::mem; use ::model::*; use ::model::event::*; @@ -124,13 +125,24 @@ pub struct Cache { /// [`Group`]: ../../model/struct.Group.html /// [`PrivateChannel`]: ../../model/struct.PrivateChannel.html /// [special cases]: index.html#special-cases-in-the-cache - pub calls: HashMap<ChannelId, Call>, + pub calls: HashMap<ChannelId, Arc<RwLock<Call>>>, + /// A map of channels in [`Guild`]s that the current user has received data + /// for. + /// + /// When a [`Event::GuildDelete`] or [`Event::GuildUnavailable`] is + /// received and processed by the cache, the relevant channels are also + /// removed from this map. + /// + /// [`Event::GuildDelete`]: ../../model/event/struct.GuildDeleteEvent.html + /// [`Event::GuildUnavailable`]: ../../model/event/struct.GuildUnavailableEvent.html + /// [`Guild`]: ../../model/struct.Guild.html + pub channels: HashMap<ChannelId, Arc<RwLock<GuildChannel>>>, /// A map of the groups that the current user is in. /// /// For bot users this will always be empty, except for in [special cases]. /// /// [special cases]: index.html#special-cases-in-the-cache - pub groups: HashMap<ChannelId, Group>, + pub groups: HashMap<ChannelId, Arc<RwLock<Group>>>, /// Settings specific to a guild. /// /// This will always be empty for bot users. @@ -140,7 +152,7 @@ pub struct Cache { /// /// [`Emoji`]: ../../model/struct.Emoji.html /// [`Role`]: ../../model/struct.Role.html - pub guilds: HashMap<GuildId, Guild>, + pub guilds: HashMap<GuildId, Arc<RwLock<Guild>>>, /// A map of notes that a user has made for individual users. /// /// An empty note is equivalent to having no note, and creating an empty @@ -154,7 +166,7 @@ pub struct Cache { pub presences: HashMap<UserId, Presence>, /// A map of direct message channels that the current user has open with /// other users. - pub private_channels: HashMap<ChannelId, PrivateChannel>, + pub private_channels: HashMap<ChannelId, Arc<RwLock<PrivateChannel>>>, /// A map of relationships that the current user has with other users. /// /// For bot users this will always be empty, except for in [special cases]. @@ -172,7 +184,7 @@ pub struct Cache { /// /// [`Event::GuildCreate`]: ../../model/enum.Event.html#variant.GuildCreate /// [`Event::GuildUnavailable`]: ../../model/enum.Event.html#variant.GuildUnavailable - pub unavailable_guilds: Vec<GuildId>, + pub unavailable_guilds: HashSet<GuildId>, /// The current user "logged in" and for which events are being received /// for. /// @@ -184,6 +196,34 @@ pub struct Cache { /// [`CurrentUser`]: ../../model/struct.CurrentUser.html /// [`User`]: ../../model/struct.User.html pub user: CurrentUser, + /// A map of users that the current user sees. + /// + /// Users are added to - and updated from - this map via the following + /// received events: + /// + /// - [`ChannelRecipientAdd`][`ChannelRecipientAddEvent`] + /// - [`GuildMemberAdd`][`GuildMemberAddEvent`] + /// - [`GuildMemberRemove`][`GuildMemberRemoveEvent`] + /// - [`GuildMembersChunk`][`GuildMembersChunkEvent`] + /// - [`GuildSync`][`GuildSyncEvent`] + /// - [`PresenceUpdate`][`PresenceUpdateEvent`] + /// - [`Ready`][`ReadyEvent`] + /// - [`RelationshipAdd`][`RelationshipAddEvent`] + /// + /// Note, however, that users are _not_ removed from the map on removal + /// events such as [`GuildMemberRemove`][`GuildMemberRemoveEvent`], as other + /// structs such as members or recipients may still exist. + /// + /// [`ChannelRecipientAddEvent`]: ../../model/event/struct.ChannelRecipientAddEvent.html + /// [`GuildMemberAddEvent`]: ../../model/event/struct.GuildMemberAddEvent.html + /// [`GuildMemberRemoveEvent`]: ../../model/event/struct.GuildMemberRemoveEvent.html + /// [`GuildMemberUpdateEvent`]: ../../model/event/struct.GuildMemberUpdateEvent.html + /// [`GuildMembersChunkEvent`]: ../../model/event/struct.GuildMembersChunkEvent.html + /// [`GuildSyncEvent`]: ../../model/event/struct.GuildSyncEvent.html + /// [`PresenceUpdateEvent`]: ../../model/event/struct.PresenceUpdateEvent.html + /// [`Ready`]: ../../model/event/struct.ReadyEvent.html + /// [`RelationshipAdd`][`RelationshipAddEvent`] + pub users: HashMap<UserId, Arc<RwLock<User>>>, } impl Cache { @@ -193,16 +233,17 @@ impl Cache { /// _member_s that have not had data downloaded. A single [`User`] may have /// multiple associated member objects that have not been received. /// - /// This can be used in combination with [`Shard::sync_guilds`], and can be + /// This can be used in combination with [`Shard::chunk_guilds`], and can be /// used to determine how many members have not yet been downloaded. /// /// [`Member`]: ../../model/struct.Member.html - /// [`Shard::sync_guilds`]: ../../client/gateway/struct.Shard.html#method.sync_guilds /// [`User`]: ../../model/struct.User.html pub fn unknown_members(&self) -> u64 { let mut total = 0; for guild in self.guilds.values() { + let guild = guild.read().unwrap(); + let members = guild.members.len() as u64; if guild.member_count > members { @@ -243,73 +284,77 @@ impl Cache { pub fn all_guilds(&self) -> Vec<GuildId> { self.guilds .values() - .map(|g| g.id) + .map(|g| g.read().unwrap().id) .chain(self.unavailable_guilds.iter().cloned()) .collect() } - #[doc(hidden)] - pub fn __download_members(&mut self) -> Vec<GuildId> { - self.guilds - .values_mut() - .filter(|guild| guild.large) - .map(|ref mut guild| { - guild.members.clear(); - - guild.id - }) - .collect::<Vec<GuildId>>() - } - /// Retrieves a reference to a [`Call`] from the cache based on the /// associated [`Group`]'s channel Id. /// /// [`Call`]: ../../model/struct.Call.html /// [`Group`]: ../../model/struct.Group.html - pub fn get_call<C: Into<ChannelId>>(&self, group_id: C) -> Option<&Call> { - self.calls.get(&group_id.into()) + #[inline] + pub fn get_call<C: Into<ChannelId>>(&self, group_id: C) -> Option<Arc<RwLock<Call>>> { + self.calls.get(&group_id.into()).cloned() } /// Retrieves a [`Channel`] from the cache based on the given Id. /// - /// This will search the [`groups`] map, the [`private_channels`] map, and - /// then the map of [`guilds`] to find the channel. + /// This will search the [`channels`] map, the [`private_channels`] map, and + /// then the map of [`groups`] to find the channel. + /// + /// If you know what type of channel you're looking for, you should instead + /// manually retrieve from one of the respective maps or methods: + /// + /// - [`GuildChannel`]: [`get_guild_channel`] or [`channels`] + /// - [`PrivateChannel`]: [`get_private_channel`] or [`private_channels`] + /// - [`Group`]: [`get_group`] or [`groups`] /// /// [`Channel`]: ../../model/enum.Channel.html + /// [`Group`]: ../../model/struct.Group.html /// [`Guild`]: ../../model/struct.Guild.html + /// [`channels`]: #structfield.channels + /// [`get_group`]: #method.get_group + /// [`get_guild_channel`]: #method.get_guild_channel + /// [`get_private_channel`]: #method.get_private_channel /// [`groups`]: #structfield.groups /// [`private_channels`]: #structfield.private_channels - /// [`guilds`]: #structfield.guilds - pub fn get_channel<C: Into<ChannelId>>(&self, id: C) -> Option<ChannelRef> { + pub fn get_channel<C: Into<ChannelId>>(&self, id: C) -> Option<Channel> { let id = id.into(); - if let Some(private_channel) = self.private_channels.get(&id) { - return Some(ChannelRef::Private(private_channel)); + if let Some(channel) = self.channels.get(&id) { + return Some(Channel::Guild(channel.clone())); } - if let Some(group) = self.groups.get(&id) { - return Some(ChannelRef::Group(group)); + if let Some(private_channel) = self.private_channels.get(&id) { + return Some(Channel::Private(private_channel.clone())); } - for guild in self.guilds.values() { - for channel in guild.channels.values() { - if channel.id == id { - return Some(ChannelRef::Guild(channel)); - } - } + if let Some(group) = self.groups.get(&id) { + return Some(Channel::Group(group.clone())); } None } - /// Retrieves a reference to a guild from the cache based on the given Id. - pub fn get_guild<G: Into<GuildId>>(&self, id: G) -> Option<&Guild> { - self.guilds.get(&id.into()) + /// Retrieves a guild from the cache based on the given Id. + /// + /// The only advantage of this method is that you can pass in anything that + /// is indirectly a [`GuildId`]. + /// + /// [`GuildId`]: ../../model/struct.GuildId.html + #[inline] + pub fn get_guild<G: Into<GuildId>>(&self, id: G) -> Option<Arc<RwLock<Guild>>> { + self.guilds.get(&id.into()).cloned() } /// Retrieves a reference to a [`Guild`]'s channel. Unlike [`get_channel`], /// this will only search guilds for the given channel. /// + /// The only advantage of this method is that you can pass in anything that + /// is indirectly a [`ChannelId`]. + /// /// # Examples /// /// Getting a guild's channel via the Id of the message received through a @@ -332,31 +377,34 @@ impl Cache { /// }; /// ``` /// + /// [`ChannelId`]: ../../model/struct.ChannelId.html /// [`Client::on_message`]: ../../client/struct.Client.html#method.on_message /// [`Guild`]: ../../model/struct.Guild.html /// [`get_channel`]: #method.get_channel - pub fn get_guild_channel<C: Into<ChannelId>>(&self, id: C) -> Option<&GuildChannel> { - let id = id.into(); - - for guild in self.guilds.values() { - if let Some(channel) = guild.channels.get(&id) { - return Some(channel); - } - } - - None + #[inline] + pub fn get_guild_channel<C: Into<ChannelId>>(&self, id: C) + -> Option<Arc<RwLock<GuildChannel>>> { + self.channels.get(&id.into()).cloned() } /// Retrieves a reference to a [`Group`] from the cache based on the given /// associated channel Id. /// + /// The only advantage of this method is that you can pass in anything that + /// is indirectly a [`ChannelId`]. + /// + /// [`ChannelId`]: ../../model/struct.ChannelId.html /// [`Group`]: ../../model/struct.Group.html - pub fn get_group<C: Into<ChannelId>>(&self, id: C) -> Option<&Group> { - self.groups.get(&id.into()) + #[inline] + pub fn get_group<C: Into<ChannelId>>(&self, id: C) -> Option<Arc<RwLock<Group>>> { + self.groups.get(&id.into()).cloned() } - /// Retrieves a reference to a [`Guild`]'s member from the cache based on - /// the guild's and user's given Ids. + /// Retrieves a [`Guild`]'s member from the cache based on the guild's and + /// user's given Ids. + /// + /// **Note**: This will clone the entire member. Instead, retrieve the guild + /// and retrieve from the guild's [`members`] map to avoid this. /// /// # Examples /// @@ -398,65 +446,80 @@ impl Cache { /// /// [`Client::on_message`]: ../../client/struct.Client.html#method.on_message /// [`Guild`]: ../../model/struct.Guild.html - pub fn get_member<G, U>(&self, guild_id: G, user_id: U) -> Option<&Member> + /// [`members`]: ../../model/struct.Guild.html#structfield.members + pub fn get_member<G, U>(&self, guild_id: G, user_id: U) -> Option<Member> where G: Into<GuildId>, U: Into<UserId> { self.guilds .get(&guild_id.into()) .map(|guild| { - guild.members.get(&user_id.into()) + guild.write().unwrap().members.get(&user_id.into()).cloned() }).and_then(|x| match x { Some(x) => Some(x), None => None, }) } - /// Retrieves a reference to a `User` based on appearance in - /// the first server they are in. - pub fn get_user<U: Into<UserId>>(&self, user_id: U) -> Option<&User> { - let user_id = user_id.into(); - - for guild in self.guilds.values() { - if let Some(member) = guild.members.get(&user_id) { - return Some(&member.user); - } - } - - None + /// Retrieves a [`PrivateChannel`] from the cache's [`private_channels`] + /// map, if it exists. + /// + /// The only advantage of this method is that you can pass in anything that + /// is indirectly a [`ChannelId`]. + #[inline] + pub fn get_private_channel<C: Into<ChannelId>>(&self, channel_id: C) + -> Option<Arc<RwLock<PrivateChannel>>> { + self.private_channels.get(&channel_id.into()).cloned() } - /// Retrieves a reference to a [`Guild`]'s role by their Ids. + /// Retrieves a [`Guild`]'s role by their Ids. + /// + /// **Note**: This will clone the entire role. Instead, retrieve the guild + /// and retrieve from the guild's [`roles`] map to avoid this. /// /// [`Guild`]: ../../model/struct.Guild.html - pub fn get_role<G, R>(&self, guild_id: G, role_id: R) -> Option<&Role> + /// [`roles`]: ../../model/struct.Guild.html#structfield.roles + pub fn get_role<G, R>(&self, guild_id: G, role_id: R) -> Option<Role> where G: Into<GuildId>, R: Into<RoleId> { - if let Some(guild) = self.guilds.get(&guild_id.into()) { - guild.roles.get(&role_id.into()) - } else { - None - } + self.guilds.get(&guild_id.into()) + .map(|g| g.read().unwrap().roles.get(&role_id.into()).cloned()) + .and_then(|x| match x { + Some(x) => Some(x), + None => None, + }) + } + + /// Retrieves a `User` from the cache's [`users`] map, if it exists. + /// + /// The only advantage of this method is that you can pass in anything that + /// is indirectly a [`UserId`]. + /// + /// [`UserId`]: ../../model/struct.UserId.html + /// [`users`]: #structfield.users + #[inline] + pub fn get_user<U: Into<UserId>>(&self, user_id: U) -> Option<Arc<RwLock<User>>> { + self.users.get(&user_id.into()).cloned() } #[doc(hidden)] pub fn update_with_call_create(&mut self, event: &CallCreateEvent) { match self.calls.entry(event.call.channel_id) { Entry::Vacant(e) => { - e.insert(event.call.clone()); + e.insert(Arc::new(RwLock::new(event.call.clone()))); }, Entry::Occupied(mut e) => { - e.get_mut().clone_from(&event.call); + *e.get_mut() = Arc::new(RwLock::new(event.call.clone())); }, } } #[doc(hidden)] pub fn update_with_call_delete(&mut self, event: &CallDeleteEvent) - -> Option<Call> { + -> Option<Arc<RwLock<Call>>> { self.calls.remove(&event.channel_id) } #[doc(hidden)] pub fn update_with_call_update(&mut self, event: &CallUpdateEvent, old: bool) - -> Option<Call> { + -> Option<Arc<RwLock<Call>>> { let item = if old { self.calls.get(&event.channel_id).cloned() } else { @@ -466,6 +529,8 @@ impl Cache { self.calls .get_mut(&event.channel_id) .map(|call| { + let mut call = call.write().unwrap(); + call.region.clone_from(&event.region); call.ringing.clone_from(&event.ringing); }); @@ -474,24 +539,40 @@ impl Cache { } #[doc(hidden)] - pub fn update_with_channel_create(&mut self, event: &ChannelCreateEvent) - -> Option<Channel> { + pub fn update_with_channel_create(&mut self, event: &ChannelCreateEvent) -> Option<Channel> { match event.channel { Channel::Group(ref group) => { - let ch = self.groups.insert(group.channel_id, group.clone()); + let group = group.clone(); - ch.map(Channel::Group) - }, - Channel::Private(ref channel) => { - let ch = self.private_channels.insert(channel.id, channel.clone()); + let channel_id = { + let writer = group.write().unwrap(); - ch.map(Channel::Private) + for (recipient_id, recipient) in &mut group.write().unwrap().recipients { + self.update_user_entry(&recipient.read().unwrap()); + + *recipient = self.users[recipient_id].clone(); + } + + writer.channel_id + }; + + let ch = self.groups.insert(channel_id, group); + + ch.map(Channel::Group) }, Channel::Guild(ref channel) => { + let (guild_id, channel_id) = { + let channel = channel.read().unwrap(); + + (channel.guild_id, channel.id) + }; + + self.channels.insert(channel_id, channel.clone()); + let ch = self.guilds - .get_mut(&channel.guild_id) + .get_mut(&guild_id) .map(|guild| { - guild.channels.insert(channel.id, channel.clone()) + guild.write().unwrap().channels.insert(channel_id, channel.clone()) }); match ch { @@ -499,24 +580,48 @@ impl Cache { _ => None, } }, + Channel::Private(ref channel) => { + let channel = channel.clone(); + + let mut channel_writer = channel.write().unwrap(); + + let user_id = { + let user_reader = channel_writer.recipient.read().unwrap(); + + self.update_user_entry(&user_reader); + + user_reader.id + }; + + channel_writer.recipient = self.users[&user_id].clone(); + + let ch = self.private_channels.insert(channel.read().unwrap().id, channel.clone()); + ch.map(Channel::Private) + }, } } #[doc(hidden)] - pub fn update_with_channel_delete(&mut self, event: &ChannelDeleteEvent) - -> Option<Channel> { + pub fn update_with_channel_delete(&mut self, event: &ChannelDeleteEvent) -> Option<Channel> { match event.channel { Channel::Group(ref group) => { - self.groups.remove(&group.channel_id).map(Channel::Group) + self.groups.remove(&group.read().unwrap().channel_id).map(Channel::Group) }, Channel::Private(ref channel) => { - self.private_channels.remove(&channel.id) - .map(Channel::Private) + self.private_channels.remove(&channel.read().unwrap().id).map(Channel::Private) }, Channel::Guild(ref channel) => { + let (channel_id, guild_id) = { + let channel = channel.read().unwrap(); + + (channel.id, channel.guild_id) + }; + + self.channels.remove(&channel_id); + let ch = self.guilds - .get_mut(&channel.guild_id) - .map(|guild| guild.channels.remove(&channel.id)); + .get_mut(&guild_id) + .map(|guild| guild.write().unwrap().channels.remove(&channel_id)); match ch { Some(Some(ch)) => Some(Channel::Guild(ch)), @@ -527,83 +632,98 @@ impl Cache { } #[doc(hidden)] - pub fn update_with_channel_pins_update(&mut self, - event: &ChannelPinsUpdateEvent) { - if let Some(channel) = self.private_channels.get_mut(&event.channel_id) { - channel.last_pin_timestamp = event.last_pin_timestamp.clone(); + pub fn update_with_channel_pins_update(&mut self, event: &ChannelPinsUpdateEvent) { + if let Some(channel) = self.channels.get(&event.channel_id) { + channel.write().unwrap().last_pin_timestamp = event.last_pin_timestamp.clone(); return; } - if let Some(group) = self.groups.get_mut(&event.channel_id) { - group.last_pin_timestamp = event.last_pin_timestamp.clone(); + if let Some(channel) = self.private_channels.get_mut(&event.channel_id) { + channel.write().unwrap().last_pin_timestamp = event.last_pin_timestamp.clone(); return; } - // Guild searching is last because it is expensive - // in comparison to private channel and group searching. - for guild in self.guilds.values_mut() { - for channel in guild.channels.values_mut() { - if channel.id == event.channel_id { - channel.last_pin_timestamp = event.last_pin_timestamp.clone(); + if let Some(group) = self.groups.get_mut(&event.channel_id) { + group.write().unwrap().last_pin_timestamp = event.last_pin_timestamp.clone(); - return; - } - } + return; } } #[doc(hidden)] - pub fn update_with_channel_recipient_add(&mut self, - event: &ChannelRecipientAddEvent) { + pub fn update_with_channel_recipient_add(&mut self, event: &mut ChannelRecipientAddEvent) { + self.update_user_entry(&event.user); + let user = self.users[&event.user.id].clone(); + self.groups .get_mut(&event.channel_id) - .map(|group| group.recipients.insert(event.user.id, - event.user.clone())); + .map(|group| { + group.write() + .unwrap() + .recipients + .insert(event.user.id, user); + }); } #[doc(hidden)] - pub fn update_with_channel_recipient_remove(&mut self, - event: &ChannelRecipientRemoveEvent) { + pub fn update_with_channel_recipient_remove(&mut self, event: &ChannelRecipientRemoveEvent) { self.groups .get_mut(&event.channel_id) - .map(|group| group.recipients.remove(&event.user.id)); + .map(|group| group.write().unwrap().recipients.remove(&event.user.id)); } #[doc(hidden)] pub fn update_with_channel_update(&mut self, event: &ChannelUpdateEvent) { match event.channel { Channel::Group(ref group) => { - match self.groups.entry(group.channel_id) { + let (ch_id, no_recipients) = { + let group = group.read().unwrap(); + + (group.channel_id, group.recipients.is_empty()) + }; + + match self.groups.entry(ch_id) { Entry::Vacant(e) => { e.insert(group.clone()); }, Entry::Occupied(mut e) => { - let dest = e.get_mut(); + let mut dest = e.get_mut().write().unwrap(); - if group.recipients.is_empty() { + if no_recipients { let recipients = mem::replace(&mut dest.recipients, HashMap::new()); - dest.clone_from(group); + dest.clone_from(&group.read().unwrap()); dest.recipients = recipients; } else { - dest.clone_from(group); + dest.clone_from(&group.read().unwrap()); } }, } }, Channel::Guild(ref channel) => { + let (channel_id, guild_id) = { + let channel = channel.read().unwrap(); + + (channel.id, channel.guild_id) + }; + + self.channels.insert(channel_id, channel.clone()); self.guilds - .get_mut(&channel.guild_id) - .map(|guild| guild.channels - .insert(channel.id, channel.clone())); + .get_mut(&guild_id) + .map(|guild| { + guild.write() + .unwrap() + .channels + .insert(channel_id, channel.clone()) + }); }, Channel::Private(ref channel) => { self.private_channels - .get_mut(&channel.id) + .get_mut(&channel.read().unwrap().id) .map(|private| private.clone_from(channel)); }, } @@ -611,48 +731,67 @@ impl Cache { #[doc(hidden)] pub fn update_with_guild_create(&mut self, event: &GuildCreateEvent) { - self.unavailable_guilds.retain(|guild_id| *guild_id != event.guild.id); + self.unavailable_guilds.remove(&event.guild.id); + + let mut guild = event.guild.clone(); - self.guilds.insert(event.guild.id, event.guild.clone()); + for (user_id, member) in &mut guild.members { + self.update_user_entry(&member.user.read().unwrap()); + let user = self.users[user_id].clone(); + + member.user = user.clone(); + } + + self.channels.extend(guild.channels.clone()); + self.guilds.insert(event.guild.id, Arc::new(RwLock::new(guild))); } #[doc(hidden)] pub fn update_with_guild_delete(&mut self, event: &GuildDeleteEvent) - -> Option<Guild> { - if !self.unavailable_guilds.contains(&event.guild.id) { - self.unavailable_guilds.push(event.guild.id); - } + -> Option<Arc<RwLock<Guild>>> { + // Remove channel entries for the guild if the guild is found. + self.guilds.remove(&event.guild.id).map(|guild| { + for channel_id in guild.read().unwrap().channels.keys() { + self.channels.remove(channel_id); + } - self.guilds.remove(&event.guild.id) + guild + }) } #[doc(hidden)] - pub fn update_with_guild_emojis_update(&mut self, - event: &GuildEmojisUpdateEvent) { + pub fn update_with_guild_emojis_update(&mut self, event: &GuildEmojisUpdateEvent) { self.guilds .get_mut(&event.guild_id) - .map(|guild| guild.emojis.extend(event.emojis.clone())); + .map(|guild| guild.write().unwrap().emojis.extend(event.emojis.clone())); } #[doc(hidden)] - pub fn update_with_guild_member_add(&mut self, - event: &GuildMemberAddEvent) { + pub fn update_with_guild_member_add(&mut self, event: &mut GuildMemberAddEvent) { + let user_id = event.member.user.read().unwrap().id; + self.update_user_entry(&event.member.user.read().unwrap()); + + // Always safe due to being inserted above. + event.member.user = self.users[&user_id].clone(); + self.guilds .get_mut(&event.guild_id) .map(|guild| { + let mut guild = guild.write().unwrap(); + guild.member_count += 1; - guild.members.insert(event.member.user.id, - event.member.clone()); + guild.members.insert(user_id, event.member.clone()); }); } #[doc(hidden)] - pub fn update_with_guild_member_remove(&mut self, - event: &GuildMemberRemoveEvent) - -> Option<Member> { + pub fn update_with_guild_member_remove(&mut self, event: &GuildMemberRemoveEvent) + -> Option<Member> { let member = self.guilds .get_mut(&event.guild_id) .map(|guild| { + let mut guild = guild.write().unwrap(); + guild.member_count -= 1; guild.members.remove(&event.user.id) }); @@ -664,10 +803,13 @@ impl Cache { } #[doc(hidden)] - pub fn update_with_guild_member_update(&mut self, - event: &GuildMemberUpdateEvent) - -> Option<Member> { + pub fn update_with_guild_member_update(&mut self, event: &GuildMemberUpdateEvent) + -> Option<Member> { + self.update_user_entry(&event.user); + if let Some(guild) = self.guilds.get_mut(&event.guild_id) { + let mut guild = guild.write().unwrap(); + let mut found = false; let item = if let Some(member) = guild.members.get_mut(&event.user.id) { @@ -675,7 +817,7 @@ impl Cache { member.nick.clone_from(&event.nick); member.roles.clone_from(&event.roles); - member.user.clone_from(&event.user); + member.user.write().unwrap().clone_from(&event.user); found = true; @@ -691,7 +833,7 @@ impl Cache { mute: false, nick: event.nick.clone(), roles: event.roles.clone(), - user: event.user.clone(), + user: Arc::new(RwLock::new(event.user.clone())), }); } @@ -702,28 +844,34 @@ impl Cache { } #[doc(hidden)] - pub fn update_with_guild_members_chunk(&mut self, - event: &GuildMembersChunkEvent) { + pub fn update_with_guild_members_chunk(&mut self, event: &GuildMembersChunkEvent) { + for member in event.members.values() { + self.update_user_entry(&member.user.read().unwrap()); + } + self.guilds .get_mut(&event.guild_id) - .map(|guild| guild.members.extend(event.members.clone())); + .map(|guild| { + guild.write().unwrap().members.extend(event.members.clone()) + }); } #[doc(hidden)] - pub fn update_with_guild_role_create(&mut self, - event: &GuildRoleCreateEvent) { + pub fn update_with_guild_role_create(&mut self, event: &GuildRoleCreateEvent) { self.guilds .get_mut(&event.guild_id) - .map(|guild| guild.roles.insert(event.role.id, event.role.clone())); + .map(|guild| { + guild.write().unwrap().roles.insert(event.role.id, event.role.clone()) + }); } #[doc(hidden)] - pub fn update_with_guild_role_delete(&mut self, - event: &GuildRoleDeleteEvent) - -> Option<Role> { + pub fn update_with_guild_role_delete(&mut self, event: &GuildRoleDeleteEvent) -> Option<Role> { let role = self.guilds .get_mut(&event.guild_id) - .map(|guild| guild.roles.remove(&event.role_id)); + .map(|guild| { + guild.write().unwrap().roles.remove(&event.role_id) + }); match role { Some(Some(x)) => Some(x), @@ -732,14 +880,16 @@ impl Cache { } #[doc(hidden)] - pub fn update_with_guild_role_update(&mut self, - event: &GuildRoleUpdateEvent) - -> Option<Role> { + pub fn update_with_guild_role_update(&mut self, event: &GuildRoleUpdateEvent) -> Option<Role> { let item = self.guilds .get_mut(&event.guild_id) - .map(|guild| guild.roles - .get_mut(&event.role.id) - .map(|role| mem::replace(role, event.role.clone()))); + .map(|guild| { + guild.write() + .unwrap() + .roles + .get_mut(&event.role.id) + .map(|role| mem::replace(role, event.role.clone())) + }); match item { Some(Some(x)) => Some(x), @@ -749,9 +899,15 @@ impl Cache { #[doc(hidden)] pub fn update_with_guild_sync(&mut self, event: &GuildSyncEvent) { + for member in event.members.values() { + self.update_user_entry(&member.user.read().unwrap()); + } + self.guilds .get_mut(&event.guild_id) .map(|guild| { + let mut guild = guild.write().unwrap(); + guild.large = event.large; guild.members.clone_from(&event.members); guild.presences.clone_from(&event.presences); @@ -759,11 +915,9 @@ impl Cache { } #[doc(hidden)] - pub fn update_with_guild_unavailable(&mut self, - event: &GuildUnavailableEvent) { - if !self.unavailable_guilds.contains(&event.guild_id) { - self.unavailable_guilds.push(event.guild_id); - } + pub fn update_with_guild_unavailable(&mut self, event: &GuildUnavailableEvent) { + self.unavailable_guilds.insert(event.guild_id); + self.guilds.remove(&event.guild_id); } #[doc(hidden)] @@ -771,6 +925,8 @@ impl Cache { self.guilds .get_mut(&event.guild.id) .map(|guild| { + let mut guild = guild.write().unwrap(); + guild.afk_timeout = event.guild.afk_timeout; guild.afk_channel_id.clone_from(&event.guild.afk_channel_id); guild.icon.clone_from(&event.guild.icon); @@ -784,8 +940,8 @@ impl Cache { #[doc(hidden)] pub fn update_with_presences_replace(&mut self, event: &PresencesReplaceEvent) { - self.presences.clone_from(&{ - let mut p = HashMap::default(); + self.presences.extend({ + let mut p: HashMap<UserId, Presence> = HashMap::default(); for presence in &event.presences { p.insert(presence.user_id, presence.clone()); @@ -796,41 +952,51 @@ impl Cache { } #[doc(hidden)] - pub fn update_with_presence_update(&mut self, event: &PresenceUpdateEvent) { + pub fn update_with_presence_update(&mut self, event: &mut PresenceUpdateEvent) { + let user_id = event.presence.user_id; + + if let Some(user) = event.presence.user.as_mut() { + self.update_user_entry(&user.read().unwrap()); + *user = self.users[&user_id].clone(); + } + if let Some(guild_id) = event.guild_id { if let Some(guild) = self.guilds.get_mut(&guild_id) { - // If the user was modified, update the member list - if let Some(user) = event.presence.user.as_ref() { - guild.members - .get_mut(&user.id) - .map(|member| member.user.clone_from(user)); - } + let mut guild = guild.write().unwrap(); - update_presence(&mut guild.presences, &event.presence); + // If the member went offline, remove them from the presence list. + if event.presence.status == OnlineStatus::Offline { + guild.presences.remove(&event.presence.user_id); + } else { + guild.presences.insert(event.presence.user_id, event.presence.clone()); + } } + } else if event.presence.status != OnlineStatus::Offline { + self.presences.insert(event.presence.user_id, event.presence.clone()); + } else { + self.presences.remove(&event.presence.user_id); } } #[doc(hidden)] - pub fn update_with_ready(&mut self, ready: &ReadyEvent) { - let ready = ready.ready.clone(); + pub fn update_with_ready(&mut self, event: &ReadyEvent) { + let mut ready = event.ready.clone(); for guild in ready.guilds { match guild { PossibleGuild::Offline(guild_id) => { - self.unavailable_guilds.push(guild_id); - } + self.unavailable_guilds.insert(guild_id); + self.guilds.remove(&guild_id); + }, PossibleGuild::Online(guild) => { - self.guilds.insert(guild.id, guild); + self.channels.extend(guild.channels.clone()); + self.guilds.insert(guild.id, Arc::new(RwLock::new(guild))); }, } } - self.unavailable_guilds.sort(); - self.unavailable_guilds.dedup(); - // The private channels sent in the READY contains both the actual - // private channels, and the groups. + // private channels and the groups. for (channel_id, channel) in ready.private_channels { match channel { Channel::Group(group) => { @@ -839,53 +1005,53 @@ impl Cache { Channel::Private(channel) => { self.private_channels.insert(channel_id, channel); }, - Channel::Guild(_) => {}, + Channel::Guild(guild) => warn!("Got a guild in DMs: {:?}", guild), } } - for guild in ready.user_guild_settings.unwrap_or_default() { - self.guild_settings.insert(guild.guild_id, guild); + if let Some(user_guild_settings) = ready.user_guild_settings { + for guild in user_guild_settings { + self.guild_settings.insert(guild.guild_id, guild); + } } - for (user_id, presence) in ready.presences { - self.presences.insert(user_id, presence); - } + for (user_id, presence) in &mut ready.presences { + if let Some(ref user) = presence.user { + self.update_user_entry(&user.read().unwrap()); + } - for (user_id, relationship) in ready.relationships { - self.relationships.insert(user_id, relationship); + presence.user = self.users.get(user_id).cloned(); } + self.presences.extend(ready.presences); + self.relationships.extend(ready.relationships); self.notes.extend(ready.notes); - self.settings = ready.user_settings; self.user = ready.user; } #[doc(hidden)] pub fn update_with_relationship_add(&mut self, event: &RelationshipAddEvent) { - self.relationships.insert(event.relationship.id, - event.relationship.clone()); + self.update_user_entry(&event.relationship.user); + + self.relationships.insert(event.relationship.id, event.relationship.clone()); } #[doc(hidden)] - pub fn update_with_relationship_remove(&mut self, - event: &RelationshipRemoveEvent) { + pub fn update_with_relationship_remove(&mut self, event: &RelationshipRemoveEvent) { self.relationships.remove(&event.user_id); } #[doc(hidden)] - pub fn update_with_user_guild_settings_update(&mut self, - event: &UserGuildSettingsUpdateEvent) - -> Option<UserGuildSettings> { + pub fn update_with_user_guild_settings_update(&mut self, event: &UserGuildSettingsUpdateEvent) + -> Option<UserGuildSettings> { self.guild_settings .get_mut(&event.settings.guild_id) .map(|guild_setting| mem::replace(guild_setting, event.settings.clone())) } #[doc(hidden)] - pub fn update_with_user_note_update(&mut self, - event: &UserNoteUpdateEvent) - -> Option<String> { + pub fn update_with_user_note_update(&mut self, event: &UserNoteUpdateEvent) -> Option<String> { if event.note.is_empty() { self.notes.remove(&event.user_id) } else { @@ -894,10 +1060,8 @@ impl Cache { } #[doc(hidden)] - pub fn update_with_user_settings_update(&mut self, - event: &UserSettingsUpdateEvent, - old: bool) - -> Option<UserSettings> { + pub fn update_with_user_settings_update(&mut self, event: &UserSettingsUpdateEvent, old: bool) + -> Option<UserSettings> { let item = if old { self.settings.clone() } else { @@ -923,21 +1087,20 @@ impl Cache { } #[doc(hidden)] - pub fn update_with_user_update(&mut self, event: &UserUpdateEvent) - -> CurrentUser { + pub fn update_with_user_update(&mut self, event: &UserUpdateEvent) -> CurrentUser { mem::replace(&mut self.user, event.current_user.clone()) } #[doc(hidden)] - pub fn update_with_voice_state_update(&mut self, - event: &VoiceStateUpdateEvent) { + pub fn update_with_voice_state_update(&mut self, event: &VoiceStateUpdateEvent) { if let Some(guild_id) = event.guild_id { if let Some(guild) = self.guilds.get_mut(&guild_id) { + let mut guild = guild.write().unwrap(); + if event.voice_state.channel_id.is_some() { // Update or add to the voice state list { - let finding = guild.voice_states - .get_mut(&event.voice_state.user_id); + let finding = guild.voice_states.get_mut(&event.voice_state.user_id); if let Some(srv_state) = finding { srv_state.clone_from(&event.voice_state); @@ -960,6 +1123,8 @@ impl Cache { if let Some(channel) = event.voice_state.channel_id { // channel id available, insert voice state if let Some(call) = self.calls.get_mut(&channel) { + let mut call = call.write().unwrap(); + { let finding = call.voice_states .get_mut(&event.voice_state.user_id); @@ -971,13 +1136,26 @@ impl Cache { } } - call.voice_states.insert(event.voice_state.user_id, - event.voice_state.clone()); + call.voice_states.insert(event.voice_state.user_id, event.voice_state.clone()); } } else { // delete this user from any group call containing them for call in self.calls.values_mut() { - call.voice_states.remove(&event.voice_state.user_id); + call.write().unwrap().voice_states.remove(&event.voice_state.user_id); + } + } + } + + // Adds or updates a user entry in the [`users`] map with a received user. + // + // [`users`]: #structfield.users + fn update_user_entry(&mut self, user: &User) { + match self.users.entry(user.id) { + Entry::Vacant(e) => { + e.insert(Arc::new(RwLock::new(user.clone()))); + }, + Entry::Occupied(mut e) => { + e.get_mut().write().unwrap().clone_from(user); } } } @@ -987,6 +1165,7 @@ impl Default for Cache { fn default() -> Cache { Cache { calls: HashMap::default(), + channels: HashMap::default(), groups: HashMap::default(), guild_settings: HashMap::default(), guilds: HashMap::default(), @@ -995,7 +1174,7 @@ impl Default for Cache { private_channels: HashMap::default(), relationships: HashMap::default(), settings: None, - unavailable_guilds: Vec::default(), + unavailable_guilds: HashSet::default(), user: CurrentUser { avatar: None, bot: false, @@ -1006,46 +1185,8 @@ impl Default for Cache { mobile: None, name: String::default(), verified: false, - } - } - } -} - -fn update_presence(presences: &mut HashMap<UserId, Presence>, - presence: &Presence) { - if presence.status == OnlineStatus::Offline { - // Remove the user from the presence list - presences.remove(&presence.user_id); - } else { - // Update or add to the presence list - if let Some(ref mut guild_presence) = presences.get(&presence.user_id) { - if presence.user.is_none() { - guild_presence.clone_from(&presence); - } - - return; - } - presences.insert(presence.user_id, presence.clone()); - } -} - -/// A reference to a private channel, guild's channel, or group. -pub enum ChannelRef<'a> { - /// A group's channel - Group(&'a Group), - /// A guild channel and its guild - Guild(&'a GuildChannel), - /// A private channel - Private(&'a PrivateChannel), -} - -impl<'a> ChannelRef<'a> { - /// Clones the inner value of the variant. - pub fn clone_inner(&self) -> Channel { - match *self { - ChannelRef::Group(group) => Channel::Group(group.clone()), - ChannelRef::Guild(channel) => Channel::Guild(channel.clone()), - ChannelRef::Private(private) => Channel::Private(private.clone()), + }, + users: HashMap::default(), } } } diff --git a/src/ext/framework/mod.rs b/src/ext/framework/mod.rs index 76f3418..30a5c8f 100644 --- a/src/ext/framework/mod.rs +++ b/src/ext/framework/mod.rs @@ -74,13 +74,11 @@ use std::default::Default; use std::sync::Arc; use std::thread; use ::client::Context; -use ::model::{Message, UserId}; +use ::model::{Channel, Message, UserId}; use ::utils; #[cfg(feature="cache")] use ::client::CACHE; -#[cfg(feature="cache")] -use ::ext::cache::ChannelRef; /// A macro to generate "named parameters". This is useful to avoid manually /// using the "arguments" parameter and manually parsing types. @@ -410,7 +408,7 @@ impl Framework { let guild_id = { match CACHE.read().unwrap().get_channel(message.channel_id) { - Some(ChannelRef::Guild(channel)) => Some(channel.guild_id), + Some(Channel::Guild(channel)) => Some(channel.read().unwrap().guild_id), _ => None, } }; @@ -425,7 +423,7 @@ impl Framework { } if let Some(guild) = guild_id.find() { - if self.configuration.blocked_users.contains(&guild.owner_id) { + if self.configuration.blocked_users.contains(&guild.read().unwrap().owner_id) { if let Some(ref message) = self.configuration.blocked_guild_message { let _ = context.say(message); } @@ -515,9 +513,11 @@ impl Framework { let member = { let mut member_found = None; - if let Some(ChannelRef::Guild(channel)) = cache.get_channel(message.channel_id) { - if let Some(guild) = channel.guild_id.find() { - if let Some(member) = guild.members.get(&message.author.id) { + if let Some(Channel::Guild(channel)) = cache.get_channel(message.channel_id) { + let guild_id = channel.read().unwrap().guild_id; + + if let Some(guild) = guild_id.find() { + if let Some(member) = guild.read().unwrap().members.get(&message.author.id) { member_found = Some(member.clone()); } } @@ -529,7 +529,7 @@ impl Framework { if let Some(member) = member { if let Ok(guild_id) = member.find_guild() { if let Some(guild) = cache.get_guild(guild_id) { - let perms = guild.permissions_for(message.channel_id, message.author.id); + let perms = guild.read().unwrap().permissions_for(message.channel_id, message.author.id); permissions_fulfilled = perms.contains(command.required_permissions); } diff --git a/src/model/channel.rs b/src/model/channel.rs index ba5dcc2..e6ebb27 100644 --- a/src/model/channel.rs +++ b/src/model/channel.rs @@ -4,6 +4,7 @@ use std::borrow::Cow; use std::fmt::{self, Write}; use std::io::Read; use std::mem; +use std::sync::{Arc, RwLock}; use super::utils::{ decode_id, into_map, @@ -29,8 +30,6 @@ use ::utils::decode_array; use super::utils; #[cfg(feature="cache")] use ::client::CACHE; -#[cfg(feature="cache")] -use ::ext::cache::ChannelRef; impl Attachment { /// If this attachment is an image, then a tuple of the width and height @@ -170,11 +169,11 @@ impl Channel { let map = into_map(value)?; match req!(map.get("type").and_then(|x| x.as_u64())) { 0 | 2 => GuildChannel::decode(Value::Object(map)) - .map(Channel::Guild), + .map(|x| Channel::Guild(Arc::new(RwLock::new(x)))), 1 => PrivateChannel::decode(Value::Object(map)) - .map(Channel::Private), + .map(|x| Channel::Private(Arc::new(RwLock::new(x)))), 3 => Group::decode(Value::Object(map)) - .map(Channel::Group), + .map(|x| Channel::Group(Arc::new(RwLock::new(x)))), other => Err(Error::Decode("Expected value Channel type", Value::U64(other))), } @@ -189,13 +188,13 @@ impl Channel { pub fn delete(&self) -> Result<()> { match *self { Channel::Group(ref group) => { - let _ = group.leave()?; + let _ = group.read().unwrap().leave()?; }, Channel::Guild(ref public_channel) => { - let _ = public_channel.delete()?; + let _ = public_channel.read().unwrap().delete()?; }, Channel::Private(ref private_channel) => { - let _ = private_channel.delete()?; + let _ = private_channel.read().unwrap().delete()?; }, } @@ -325,9 +324,9 @@ impl Channel { /// [`PrivateChannel`]: struct.PrivateChannel.html pub fn id(&self) -> ChannelId { match *self { - Channel::Group(ref group) => group.channel_id, - Channel::Guild(ref channel) => channel.id, - Channel::Private(ref channel) => channel.id, + Channel::Group(ref group) => group.read().unwrap().channel_id, + Channel::Guild(ref channel) => channel.read().unwrap().id, + Channel::Private(ref channel) => channel.read().unwrap().id, } } @@ -386,13 +385,20 @@ impl fmt::Display for Channel { /// [`GuildChannel`]: struct.GuildChannel.html /// [`PrivateChannel`]: struct.PrivateChannel.html fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let out = match *self { - Channel::Group(ref group) => group.name().to_owned(), - Channel::Guild(ref channel) => Cow::Owned(format!("{}", channel)), - Channel::Private(ref channel) => Cow::Owned(channel.recipient.name.clone()), - }; + match *self { + Channel::Group(ref group) => { + fmt::Display::fmt(&group.read().unwrap().name(), f) + }, + Channel::Guild(ref ch) => { + fmt::Display::fmt(&ch.read().unwrap().id.mention(), f) + }, + Channel::Private(ref ch) => { + let channel = ch.read().unwrap(); + let recipient = channel.recipient.read().unwrap(); - fmt::Display::fmt(&out, f) + fmt::Display::fmt(&recipient.name, f) + }, + } } } @@ -615,7 +621,7 @@ impl ChannelId { /// Search the cache for the channel with the Id. #[cfg(feature="cache")] pub fn find(&self) -> Option<Channel> { - CACHE.read().unwrap().get_channel(*self).map(|x| x.clone_inner()) + CACHE.read().unwrap().get_channel(*self) } /// Search the cache for the channel. If it can't be found, the channel is @@ -624,7 +630,7 @@ impl ChannelId { #[cfg(feature="cache")] { if let Some(channel) = CACHE.read().unwrap().get_channel(*self) { - return Ok(channel.clone_inner()); + return Ok(channel); } } @@ -837,9 +843,9 @@ impl From<Channel> for ChannelId { /// Gets the Id of a `Channel`. fn from(channel: Channel) -> ChannelId { match channel { - Channel::Group(group) => group.channel_id, - Channel::Guild(channel) => channel.id, - Channel::Private(channel) => channel.id, + Channel::Group(group) => group.read().unwrap().channel_id, + Channel::Guild(ch) => ch.read().unwrap().id, + Channel::Private(ch) => ch.read().unwrap().id, } } } @@ -1054,12 +1060,12 @@ impl Group { Some(ref name) => Cow::Borrowed(name), None => { let mut name = match self.recipients.values().nth(0) { - Some(recipient) => recipient.name.clone(), + Some(recipient) => recipient.read().unwrap().name.clone(), None => return Cow::Borrowed("Empty Group"), }; for recipient in self.recipients.values().skip(1) { - let _ = write!(name, ", {}", recipient.name); + let _ = write!(name, ", {}", recipient.read().unwrap().name); } Cow::Owned(name) @@ -1327,7 +1333,7 @@ impl Message { #[cfg(feature="cache")] pub fn guild_id(&self) -> Option<GuildId> { match CACHE.read().unwrap().get_channel(self.channel_id) { - Some(ChannelRef::Guild(channel)) => Some(channel.guild_id), + Some(Channel::Guild(ch)) => Some(ch.read().unwrap().guild_id), _ => None, } } @@ -1336,7 +1342,7 @@ impl Message { #[cfg(feature="cache")] pub fn is_private(&self) -> bool { match CACHE.read().unwrap().get_channel(self.channel_id) { - Some(ChannelRef::Group(_)) | Some(ChannelRef::Private(_)) => true, + Some(Channel::Group(_)) | Some(Channel::Private(_)) => true, _ => false, } } @@ -1568,14 +1574,14 @@ impl PrivateChannel { pub fn decode(value: Value) -> Result<PrivateChannel> { let mut map = into_map(value)?; let mut recipients = decode_array(remove(&mut map, "recipients")?, - User::decode)?; + User::decode)?; Ok(PrivateChannel { id: remove(&mut map, "id").and_then(ChannelId::decode)?, kind: remove(&mut map, "type").and_then(ChannelType::decode)?, last_message_id: opt(&mut map, "last_message_id", MessageId::decode)?, last_pin_timestamp: opt(&mut map, "last_pin_timestamp", into_string)?, - recipient: recipients.remove(0), + recipient: Arc::new(RwLock::new(recipients.remove(0))), }) } @@ -1750,7 +1756,7 @@ impl PrivateChannel { impl fmt::Display for PrivateChannel { /// Formats the private channel, displaying the recipient's username. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(&self.recipient.name) + f.write_str(&self.recipient.read().unwrap().name) } } @@ -2093,8 +2099,8 @@ impl GuildChannel { /// **Note**: Right now this performs a clone of the guild. This will be /// optimized in the future. #[cfg(feature="cache")] - pub fn guild(&self) -> Option<Guild> { - CACHE.read().unwrap().get_guild(self.guild_id).cloned() + pub fn guild(&self) -> Option<Arc<RwLock<Guild>>> { + CACHE.read().unwrap().get_guild(self.guild_id) } /// Pins a [`Message`] to the channel. diff --git a/src/model/gateway.rs b/src/model/gateway.rs index 9758e4b..dc25d0d 100644 --- a/src/model/gateway.rs +++ b/src/model/gateway.rs @@ -1,3 +1,4 @@ +use std::sync::{Arc, RwLock}; use super::utils::*; use super::*; use ::internal::prelude::*; @@ -69,7 +70,7 @@ impl Presence { status: remove(&mut value, "status").and_then(OnlineStatus::decode_str)?, last_modified: opt(&mut value, "last_modified", |v| Ok(req!(v.as_u64())))?, game: game, - user: user, + user: user.map(RwLock::new).map(Arc::new), nick: opt(&mut value, "nick", into_string)?, }) } 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 } } diff --git a/src/model/misc.rs b/src/model/misc.rs index eaae890..3784a59 100644 --- a/src/model/misc.rs +++ b/src/model/misc.rs @@ -30,13 +30,13 @@ impl Mentionable for Channel { fn mention(&self) -> String { match *self { Channel::Guild(ref x) => { - format!("<#{}>", x.id.0) + format!("<#{}>", x.read().unwrap().id.0) }, Channel::Private(ref x) => { - format!("<#{}>", x.id.0) + format!("<#{}>", x.read().unwrap().id.0) }, Channel::Group(ref x) => { - format!("<#{}>", x.channel_id.0) + format!("<#{}>", x.read().unwrap().channel_id.0) } } } @@ -50,7 +50,7 @@ impl Mentionable for Emoji { impl Mentionable for Member { fn mention(&self) -> String { - format!("<@{}>", self.user.id.0) + format!("<@{}>", self.user.read().unwrap().id.0) } } @@ -86,7 +86,7 @@ impl FromStr for User { match utils::parse_username(s) { Some(x) => { match UserId(x as u64).find() { - Some(user) => Ok(user), + Some(user) => Ok(user.read().unwrap().clone()), _ => Err(()) } }, diff --git a/src/model/mod.rs b/src/model/mod.rs index 2a4f97d..790e378 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -39,6 +39,7 @@ pub use self::webhook::*; use self::utils::*; use std::collections::HashMap; +use std::sync::{Arc, RwLock}; use time::Timespec; use ::internal::prelude::*; use ::utils::{Colour, decode_array}; @@ -123,18 +124,18 @@ id! { #[derive(Clone, Debug)] pub enum Channel { /// A group. A group comprises of only one channel. - Group(Group), + Group(Arc<RwLock<Group>>), /// A [text] or [voice] channel within a [`Guild`]. /// /// [`Guild`]: struct.Guild.html /// [text]: enum.ChannelType.html#variant.Text /// [voice]: enum.ChannelType.html#variant.Voice - Guild(GuildChannel), + Guild(Arc<RwLock<GuildChannel>>), /// A private channel to another [`User`]. No other users may access the /// channel. For multi-user "private channels", use a group. /// /// [`User`]: struct.User.html - Private(PrivateChannel), + Private(Arc<RwLock<PrivateChannel>>), } /// A container for guilds. diff --git a/src/model/user.rs b/src/model/user.rs index 8473623..fe2d3bb 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -24,6 +24,8 @@ use ::utils::builder::EditProfile; use ::utils::decode_array; #[cfg(feature="cache")] +use std::sync::{Arc, RwLock}; +#[cfg(feature="cache")] use ::client::CACHE; impl CurrentUser { @@ -87,7 +89,6 @@ impl CurrentUser { } /// Gets a list of guilds that the current user is in. - #[inline] pub fn guilds(&self) -> Result<Vec<GuildInfo>> { rest::get_guilds(GuildPagination::After(GuildId(1)), 100) } @@ -191,15 +192,36 @@ impl User { /// /// [`PrivateChannel`]: struct.PrivateChannel.html /// [`User::dm`]: struct.User.html#method.dm + // A tale with Clippy: + // + // A person named Clippy once asked you to unlock a box and take something + // from it, but you never re-locked it, so you'll die and the universe will + // implode because the box must remain locked unless you're there, and you + // can't just borrow that item from it and take it with you forever. + // + // Instead what you do is unlock the box, take the item out of it, make a + // copy of said item, and then re-lock the box, and take your copy of the + // item with you. + // + // The universe is still fine, and nothing implodes. + // + // (AKA: Clippy is wrong and so we have to mark as allowing this lint.) + #[allow(let_and_return)] pub fn direct_message(&self, content: &str) -> Result<Message> { let private_channel_id = feature_cache! {{ - let finding = CACHE.read() - .unwrap() - .private_channels - .values() - .find(|ch| ch.recipient.id == self.id) - .map(|ch| ch.id); + let finding = { + let cache = CACHE.read().unwrap(); + + let finding = cache.private_channels + .values() + .map(|ch| ch.read().unwrap()) + .find(|ch| ch.recipient.read().unwrap().id == self.id) + .map(|ch| ch.id) + .clone(); + + finding + }; if let Some(finding) = finding { finding @@ -331,7 +353,7 @@ impl User { .unwrap() .guilds .get(&_guild_id) - .map(|g| g.roles.contains_key(&role_id)) + .map(|g| g.read().unwrap().roles.contains_key(&role_id)) .unwrap_or(false) } else { true @@ -393,8 +415,8 @@ impl UserId { /// Search the cache for the user with the Id. #[cfg(feature="cache")] - pub fn find(&self) -> Option<User> { - CACHE.read().unwrap().get_user(*self).cloned() + pub fn find(&self) -> Option<Arc<RwLock<User>>> { + CACHE.read().unwrap().get_user(*self) } /// Gets a user by its Id over the REST API. @@ -416,7 +438,7 @@ impl From<CurrentUser> for UserId { impl From<Member> for UserId { /// Gets the Id of a `Member`. fn from(member: Member) -> UserId { - member.user.id + member.user.read().unwrap().id } } diff --git a/src/model/utils.rs b/src/model/utils.rs index 58701bb..8de7dd2 100644 --- a/src/model/utils.rs +++ b/src/model/utils.rs @@ -1,4 +1,5 @@ use std::collections::{BTreeMap, HashMap}; +use std::sync::{Arc, RwLock}; use super::{ Channel, ChannelId, @@ -22,8 +23,6 @@ use ::utils::{decode_array, into_array}; use super::permissions::{self, Permissions}; #[cfg(feature="cache")] use ::client::CACHE; -#[cfg(feature="cache")] -use ::ext::cache::ChannelRef; #[macro_escape] macro_rules! req { @@ -93,7 +92,9 @@ pub fn decode_members(value: Value) -> Result<HashMap<UserId, Member>> { let mut members = HashMap::new(); for member in decode_array(value, Member::decode)? { - members.insert(member.user.id, member); + let user_id = member.user.read().unwrap().id; + + members.insert(user_id, member); } Ok(members) @@ -134,8 +135,8 @@ pub fn decode_private_channels(value: Value) for private_channel in decode_array(value, Channel::decode)? { let id = match private_channel { - Channel::Group(ref group) => group.channel_id, - Channel::Private(ref channel) => channel.id, + Channel::Group(ref group) => group.read().unwrap().channel_id, + Channel::Private(ref channel) => channel.read().unwrap().id, Channel::Guild(_) => unreachable!("Guild private channel decode"), }; @@ -217,11 +218,11 @@ pub fn decode_shards(value: Value) -> Result<[u64; 2]> { ]) } -pub fn decode_users(value: Value) -> Result<HashMap<UserId, User>> { +pub fn decode_users(value: Value) -> Result<HashMap<UserId, Arc<RwLock<User>>>> { let mut users = HashMap::new(); for user in decode_array(value, User::decode)? { - users.insert(user.id, user); + users.insert(user.id, Arc::new(RwLock::new(user))); } Ok(users) @@ -306,10 +307,10 @@ pub fn user_has_perms(channel_id: ChannelId, }; let guild_id = match channel { - ChannelRef::Group(_) | ChannelRef::Private(_) => { + Channel::Group(_) | Channel::Private(_) => { return Ok(permissions == permissions::MANAGE_MESSAGES); }, - ChannelRef::Guild(channel) => channel.guild_id, + Channel::Guild(channel) => channel.read().unwrap().guild_id, }; let guild = match cache.get_guild(guild_id) { @@ -317,7 +318,7 @@ pub fn user_has_perms(channel_id: ChannelId, None => return Err(Error::Client(ClientError::ItemMissing)), }; - let perms = guild.permissions_for(channel_id, current_user.id); + let perms = guild.read().unwrap().permissions_for(channel_id, current_user.id); permissions.remove(perms); |