diff --git a/src/command/join.rs b/src/command/join.rs index 670b848..8809ae0 100644 --- a/src/command/join.rs +++ b/src/command/join.rs @@ -50,9 +50,6 @@ enum GetGuildAndVoiceChannelIdError { /// there is no user who invoked this command NoUser, - /// there are no voice chats in this guild - NoVCsInGuild, - /// the user is not in a voice chat in this guild UserNotInVC, } @@ -70,9 +67,11 @@ fn get_guild_and_voice_channel_id( .and_then(|member| member.user.as_ref().map(|user| user.id)) .context(NoUserSnafu)?; - let guild_vcs = vcs.get(&guild_id).context(NoVCsInGuildSnafu)?; - - let &voice_channel_id = guild_vcs.get_left_for(&user_id).context(UserNotInVCSnafu)?; + let voice_channel_id = vcs + .with_guild(guild_id, |guild_vcs| { + guild_vcs.get_left_for(&user_id).copied() + }) + .context(UserNotInVCSnafu)?; Ok((guild_id, voice_channel_id)) } @@ -85,9 +84,6 @@ fn get_guild_and_vc_error_to_embed(error: GetGuildAndVoiceChannelIdError) -> Emb GetGuildAndVoiceChannelIdError::NoUser => { EmbedBuilder::new().title("Not invoked by a user").description("This command works by joining the same VC as the user, but this bot didn't receive any user data. So did no user invoke it?! (This error should be impossible!)").validate().unwrap().build() }, - GetGuildAndVoiceChannelIdError::NoVCsInGuild => { - EmbedBuilder::new().title("No VCs in this server").description("This bot can't find a VC to join because there aren't any in this server right now.").validate().unwrap().build() - }, GetGuildAndVoiceChannelIdError::UserNotInVC => { EmbedBuilder::new().title("You're not in a VC").description("This bot can't follow you into VC if you aren't in one in this server.").validate().unwrap().build() }, diff --git a/src/command/leave.rs b/src/command/leave.rs index 0d03df2..02b5d8d 100644 --- a/src/command/leave.rs +++ b/src/command/leave.rs @@ -37,9 +37,6 @@ pub enum GetGuildAndVoiceChannelIdError { /// there is no user who invoked this command NoUser, - /// there are no voice chats in this guild - NoVCsInGuild, - /// the bot is not in a voice chat in this guild BotNotInVC, } @@ -58,10 +55,10 @@ pub fn get_user_and_guild_and_voice_channel_id( let guild_id = interaction.guild_id.context(NotInGuildSnafu)?; - let guild_vcs = vcs.get(&guild_id).context(NoVCsInGuildSnafu)?; - - let &voice_channel_id = guild_vcs - .get_left_for(&bot_user_id) + let voice_channel_id = vcs + .with_guild(guild_id, |guild_vcs| { + guild_vcs.get_left_for(&bot_user_id).copied() + }) .context(BotNotInVCSnafu)?; Ok((user_id, guild_id, voice_channel_id)) @@ -75,9 +72,6 @@ fn get_guild_and_vc_error_to_embed(error: GetGuildAndVoiceChannelIdError) -> Emb GetGuildAndVoiceChannelIdError::NoUser => { EmbedBuilder::new().title("Not invoked by a user").description("This command works by joining the same VC as the user, but this bot didn't receive any user data. So did no user invoke it?! (This error should be impossible!)").validate().unwrap().build() }, - GetGuildAndVoiceChannelIdError::NoVCsInGuild => { - EmbedBuilder::new().title("No VCs in this server").description("This bot can't leave VC because there aren't any in this server right now (therefore the bot must not be in any).").validate().unwrap().build() - }, GetGuildAndVoiceChannelIdError::BotNotInVC => { EmbedBuilder::new().title("Not in a VC").description("This bot can't leave VC if it isn't in one in this server.").validate().unwrap().build() }, diff --git a/src/track_vcs.rs b/src/track_vcs.rs index 26f3c8e..db34e3d 100644 --- a/src/track_vcs.rs +++ b/src/track_vcs.rs @@ -2,6 +2,7 @@ use std::collections::BTreeMap; use dashmap::DashMap; use futures::{StreamExt, stream::FuturesUnordered}; +use tokio::sync::watch; use twilight_model::{ gateway::payload::incoming::VoiceStateUpdate, id::{ @@ -16,7 +17,37 @@ pub type GuildVoiceChannelToTextChannel = BTreeMap, OneToOneBTreeMap, Id>>; type VCsInGuild = OneToManyUniqueBTreeMapWithData, Id, UserInVCData>; -pub type VCs = DashMap, VCsInGuild>; + +#[derive(Debug, Default)] +pub struct VCs(DashMap, watch::Sender>); + +impl Extend<(Id, VCsInGuild)> for VCs { + fn extend, VCsInGuild)>>(&mut self, iter: T) { + for (id, guild_vcs) in iter { + self.0.insert(id, watch::Sender::new(guild_vcs)); + } + } +} + +impl VCs { + pub fn with_guild(&self, id: Id, f: impl FnOnce(&VCsInGuild) -> R) -> R { + f(&*self.0.entry(id).or_default().borrow()) + } + + pub fn update_guild(&self, id: Id, f: impl FnOnce(&mut VCsInGuild) -> R) -> R { + let mut ret_opt = None; + self.0.entry(id).or_default().send_modify(|guild_vcs| { + let ret = f(guild_vcs); + _ = ret_opt.insert(ret); + }); + let ret = ret_opt.unwrap(); + ret + } + + pub fn subscribe_to_guild(&self, id: Id) -> watch::Receiver { + self.0.entry(id).or_default().subscribe() + } +} #[tracing::instrument(skip(discord_client), ret)] async fn initialize_user_in_vc( @@ -107,9 +138,9 @@ pub fn update_vcs(voice_state_update: &VoiceStateUpdate, vcs: &VCs) { .build(); let user_in_vc_data = voice_status.into(); - vcs.entry(guild_id) - .or_default() - .insert(channel_id, user_id, user_in_vc_data); + vcs.update_guild(guild_id, |guild_vcs| { + guild_vcs.insert(channel_id, user_id, user_in_vc_data) + }); tracing::info!( ?guild_id, @@ -120,9 +151,7 @@ pub fn update_vcs(voice_state_update: &VoiceStateUpdate, vcs: &VCs) { } None => { - if let Some(mut channel_vcers) = vcs.get_mut(&guild_id) { - channel_vcers.remove_right(&user_id); - } + vcs.update_guild(guild_id, |guild_vcs| guild_vcs.remove_right(&user_id)); tracing::info!(?guild_id, ?user_id, "disconnected"); }