chore: refactor VCs to use a watch channel internally (but I'm going to rework this in the next commit anyway)

This commit is contained in:
2026-05-06 19:27:36 -04:00
parent bb51f1cc63
commit fa88bd495f
3 changed files with 45 additions and 26 deletions

View File

@@ -50,9 +50,6 @@ enum GetGuildAndVoiceChannelIdError {
/// there is no user who invoked this command /// there is no user who invoked this command
NoUser, NoUser,
/// there are no voice chats in this guild
NoVCsInGuild,
/// the user is not in a voice chat in this guild /// the user is not in a voice chat in this guild
UserNotInVC, UserNotInVC,
} }
@@ -70,9 +67,11 @@ fn get_guild_and_voice_channel_id(
.and_then(|member| member.user.as_ref().map(|user| user.id)) .and_then(|member| member.user.as_ref().map(|user| user.id))
.context(NoUserSnafu)?; .context(NoUserSnafu)?;
let guild_vcs = vcs.get(&guild_id).context(NoVCsInGuildSnafu)?; let voice_channel_id = vcs
.with_guild(guild_id, |guild_vcs| {
let &voice_channel_id = guild_vcs.get_left_for(&user_id).context(UserNotInVCSnafu)?; guild_vcs.get_left_for(&user_id).copied()
})
.context(UserNotInVCSnafu)?;
Ok((guild_id, voice_channel_id)) Ok((guild_id, voice_channel_id))
} }
@@ -85,9 +84,6 @@ fn get_guild_and_vc_error_to_embed(error: GetGuildAndVoiceChannelIdError) -> Emb
GetGuildAndVoiceChannelIdError::NoUser => { 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() 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 => { 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() 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()
}, },

View File

@@ -37,9 +37,6 @@ pub enum GetGuildAndVoiceChannelIdError {
/// there is no user who invoked this command /// there is no user who invoked this command
NoUser, NoUser,
/// there are no voice chats in this guild
NoVCsInGuild,
/// the bot is not in a voice chat in this guild /// the bot is not in a voice chat in this guild
BotNotInVC, 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_id = interaction.guild_id.context(NotInGuildSnafu)?;
let guild_vcs = vcs.get(&guild_id).context(NoVCsInGuildSnafu)?; let voice_channel_id = vcs
.with_guild(guild_id, |guild_vcs| {
let &voice_channel_id = guild_vcs guild_vcs.get_left_for(&bot_user_id).copied()
.get_left_for(&bot_user_id) })
.context(BotNotInVCSnafu)?; .context(BotNotInVCSnafu)?;
Ok((user_id, guild_id, voice_channel_id)) 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 => { 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() 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 => { 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() 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()
}, },

View File

@@ -2,6 +2,7 @@ use std::collections::BTreeMap;
use dashmap::DashMap; use dashmap::DashMap;
use futures::{StreamExt, stream::FuturesUnordered}; use futures::{StreamExt, stream::FuturesUnordered};
use tokio::sync::watch;
use twilight_model::{ use twilight_model::{
gateway::payload::incoming::VoiceStateUpdate, gateway::payload::incoming::VoiceStateUpdate,
id::{ id::{
@@ -16,7 +17,37 @@ pub type GuildVoiceChannelToTextChannel =
BTreeMap<Id<GuildMarker>, OneToOneBTreeMap<Id<ChannelMarker>, Id<ChannelMarker>>>; BTreeMap<Id<GuildMarker>, OneToOneBTreeMap<Id<ChannelMarker>, Id<ChannelMarker>>>;
type VCsInGuild = OneToManyUniqueBTreeMapWithData<Id<ChannelMarker>, Id<UserMarker>, UserInVCData>; type VCsInGuild = OneToManyUniqueBTreeMapWithData<Id<ChannelMarker>, Id<UserMarker>, UserInVCData>;
pub type VCs = DashMap<Id<GuildMarker>, VCsInGuild>;
#[derive(Debug, Default)]
pub struct VCs(DashMap<Id<GuildMarker>, watch::Sender<VCsInGuild>>);
impl Extend<(Id<GuildMarker>, VCsInGuild)> for VCs {
fn extend<T: IntoIterator<Item = (Id<GuildMarker>, 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<R>(&self, id: Id<GuildMarker>, f: impl FnOnce(&VCsInGuild) -> R) -> R {
f(&*self.0.entry(id).or_default().borrow())
}
pub fn update_guild<R>(&self, id: Id<GuildMarker>, 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<R>(&self, id: Id<GuildMarker>) -> watch::Receiver<VCsInGuild> {
self.0.entry(id).or_default().subscribe()
}
}
#[tracing::instrument(skip(discord_client), ret)] #[tracing::instrument(skip(discord_client), ret)]
async fn initialize_user_in_vc( async fn initialize_user_in_vc(
@@ -107,9 +138,9 @@ pub fn update_vcs(voice_state_update: &VoiceStateUpdate, vcs: &VCs) {
.build(); .build();
let user_in_vc_data = voice_status.into(); let user_in_vc_data = voice_status.into();
vcs.entry(guild_id) vcs.update_guild(guild_id, |guild_vcs| {
.or_default() guild_vcs.insert(channel_id, user_id, user_in_vc_data)
.insert(channel_id, user_id, user_in_vc_data); });
tracing::info!( tracing::info!(
?guild_id, ?guild_id,
@@ -120,9 +151,7 @@ pub fn update_vcs(voice_state_update: &VoiceStateUpdate, vcs: &VCs) {
} }
None => { None => {
if let Some(mut channel_vcers) = vcs.get_mut(&guild_id) { vcs.update_guild(guild_id, |guild_vcs| guild_vcs.remove_right(&user_id));
channel_vcers.remove_right(&user_id);
}
tracing::info!(?guild_id, ?user_id, "disconnected"); tracing::info!(?guild_id, ?user_id, "disconnected");
} }