From e9d1f85c2a0e49b3cfdb740cdfe807f411ca40e5 Mon Sep 17 00:00:00 2001 From: Michael Krasnitski Date: Sat, 17 Aug 2024 03:22:06 -0400 Subject: [PATCH] Rearrange gateway `bridge` module into `sharding` and spruce up docs --- src/gateway/bridge/event.rs | 20 -- src/gateway/bridge/mod.rs | 114 ---------- src/gateway/bridge/shard_runner_message.rs | 53 ----- src/gateway/client/mod.rs | 20 +- src/gateway/mod.rs | 144 ++---------- src/gateway/{shard.rs => sharding/mod.rs} | 208 ++++++++++++++++-- .../{bridge => sharding}/shard_manager.rs | 4 +- .../{bridge => sharding}/shard_messenger.rs | 0 .../{bridge => sharding}/shard_queuer.rs | 18 +- .../{bridge => sharding}/shard_runner.rs | 66 +++++- src/gateway/{bridge => }/voice.rs | 0 11 files changed, 293 insertions(+), 354 deletions(-) delete mode 100644 src/gateway/bridge/event.rs delete mode 100644 src/gateway/bridge/mod.rs delete mode 100644 src/gateway/bridge/shard_runner_message.rs rename src/gateway/{shard.rs => sharding/mod.rs} (79%) rename src/gateway/{bridge => sharding}/shard_manager.rs (99%) rename src/gateway/{bridge => sharding}/shard_messenger.rs (100%) rename src/gateway/{bridge => sharding}/shard_queuer.rs (96%) rename src/gateway/{bridge => sharding}/shard_runner.rs (89%) rename src/gateway/{bridge => }/voice.rs (100%) diff --git a/src/gateway/bridge/event.rs b/src/gateway/bridge/event.rs deleted file mode 100644 index 0ee307c091d..00000000000 --- a/src/gateway/bridge/event.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! A collection of events created by the client, not a part of the Discord API itself. - -use crate::gateway::ConnectionStage; -use crate::model::id::ShardId; - -/// An event denoting that a shard's connection stage was changed. -/// -/// # Examples -/// -/// This might happen when a shard changes from [`ConnectionStage::Identifying`] to -/// [`ConnectionStage::Connected`]. -#[derive(Clone, Debug)] -pub struct ShardStageUpdateEvent { - /// The new connection stage. - pub new: ConnectionStage, - /// The old connection stage. - pub old: ConnectionStage, - /// The ID of the shard that had its connection stage change. - pub shard_id: ShardId, -} diff --git a/src/gateway/bridge/mod.rs b/src/gateway/bridge/mod.rs deleted file mode 100644 index f5b2bbe845c..00000000000 --- a/src/gateway/bridge/mod.rs +++ /dev/null @@ -1,114 +0,0 @@ -//! The client gateway bridge is support essential for the [`client`] module. -//! -//! This is made available for user use if one wishes to be lower-level or avoid the higher -//! functionality of the [`Client`]. -//! -//! Of interest are three pieces: -//! -//! ### [`ShardManager`] -//! -//! The shard manager is responsible for being a clean interface between the user and the shard -//! runners, providing essential functions such as [`ShardManager::shutdown`] to shutdown a shard -//! and [`ShardManager::restart`] to restart a shard. -//! -//! If you are using the `Client`, this is likely the only piece of interest to you. Refer to [its -//! documentation][`ShardManager`] for more information. -//! -//! ### [`ShardQueuer`] -//! -//! The shard queuer is a light wrapper around an mpsc receiver that receives -//! [`ShardQueuerMessage`]s. It should be run in its own thread so it can receive messages to -//! start shards in a queue. -//! -//! Refer to [its documentation][`ShardQueuer`] for more information. -//! -//! ### [`ShardRunner`] -//! -//! The shard runner is responsible for actually running a shard and communicating with its -//! respective WebSocket client. -//! -//! It is performs all actions such as sending a presence update over the client and, with the help -//! of the [`Shard`], will be able to determine what to do. This is, for example, whether to -//! reconnect, resume, or identify with the gateway. -//! -//! ### In Conclusion -//! -//! For almost every - if not every - use case, you only need to _possibly_ be concerned about the -//! [`ShardManager`] in this module. -//! -//! [`client`]: crate::client -//! [`Client`]: crate::Client -//! [`Shard`]: crate::gateway::Shard - -mod event; -mod shard_manager; -mod shard_messenger; -mod shard_queuer; -mod shard_runner; -mod shard_runner_message; -#[cfg(feature = "voice")] -mod voice; - -use std::fmt; -use std::num::NonZeroU16; -use std::sync::Arc; -use std::time::Duration as StdDuration; - -pub use self::event::ShardStageUpdateEvent; -pub use self::shard_manager::{ShardManager, ShardManagerOptions}; -pub use self::shard_messenger::ShardMessenger; -pub use self::shard_queuer::{ShardQueue, ShardQueuer}; -pub use self::shard_runner::{ShardRunner, ShardRunnerOptions}; -pub use self::shard_runner_message::ShardRunnerMessage; -#[cfg(feature = "voice")] -pub use self::voice::VoiceGatewayManager; -use super::ChunkGuildFilter; -use crate::gateway::ConnectionStage; -use crate::model::event::Event; -use crate::model::id::ShardId; - -/// A message to be sent to the [`ShardQueuer`]. -#[derive(Clone, Debug)] -pub enum ShardQueuerMessage { - /// Message to set the shard total. - SetShardTotal(NonZeroU16), - /// Message to start a shard. - Start { shard_id: ShardId, concurrent: bool }, - /// Message to shutdown the shard queuer. - Shutdown, - /// Message to dequeue/shutdown a shard. - ShutdownShard { shard_id: ShardId, code: u16 }, -} - -/// Information about a [`ShardRunner`]. -/// -/// The [`ShardId`] is not included because, as it stands, you probably already know the Id if you -/// obtained this. -#[derive(Debug)] -pub struct ShardRunnerInfo { - /// The latency between when a heartbeat was sent and when the acknowledgement was received. - pub latency: Option, - /// The channel used to communicate with the shard runner, telling it what to do with regards - /// to its status. - pub runner_tx: ShardMessenger, - /// The current connection stage of the shard. - pub stage: ConnectionStage, -} - -/// Newtype around a callback that will be called on every incoming request. As long as this -/// collector should still receive events, it should return `true`. Once it returns `false`, it is -/// removed. -#[derive(Clone)] -pub struct CollectorCallback(pub Arc bool + Send + Sync>); - -impl std::fmt::Debug for CollectorCallback { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("CollectorCallback").finish() - } -} - -impl PartialEq for CollectorCallback { - fn eq(&self, other: &Self) -> bool { - Arc::ptr_eq(&self.0, &other.0) - } -} diff --git a/src/gateway/bridge/shard_runner_message.rs b/src/gateway/bridge/shard_runner_message.rs deleted file mode 100644 index dc9ce9b97c7..00000000000 --- a/src/gateway/bridge/shard_runner_message.rs +++ /dev/null @@ -1,53 +0,0 @@ -use tokio_tungstenite::tungstenite::Message; - -use super::ShardId; -use crate::gateway::{ActivityData, ChunkGuildFilter}; -use crate::model::id::GuildId; -use crate::model::user::OnlineStatus; - -/// A message to send from a shard over a WebSocket. -#[derive(Debug)] -pub enum ShardRunnerMessage { - /// Indicator that a shard should be restarted. - Restart(ShardId), - /// Indicator that a shard should be fully shutdown without bringing it - /// back up. - Shutdown(ShardId, u16), - /// Indicates that the client is to send a member chunk message. - ChunkGuild { - /// The IDs of the [`Guild`] to chunk. - /// - /// [`Guild`]: crate::model::guild::Guild - guild_id: GuildId, - /// The maximum number of members to receive [`GuildMembersChunkEvent`]s for. - /// - /// [`GuildMembersChunkEvent`]: crate::model::event::GuildMembersChunkEvent - limit: Option, - /// Used to specify if we want the presences of the matched members. - /// - /// Requires [`crate::model::gateway::GatewayIntents::GUILD_PRESENCES`]. - presences: bool, - /// A filter to apply to the returned members. - filter: ChunkGuildFilter, - /// Optional nonce to identify [`GuildMembersChunkEvent`] responses. - /// - /// [`GuildMembersChunkEvent`]: crate::model::event::GuildMembersChunkEvent - nonce: Option, - }, - /// Indicates that the client is to close with the given status code and reason. - /// - /// You should rarely - if _ever_ - need this, but the option is available. Prefer to use the - /// [`ShardManager`] to shutdown WebSocket clients if you are intending to send a 1000 close - /// code. - /// - /// [`ShardManager`]: super::ShardManager - Close(u16, Option), - /// Indicates that the client is to send a custom WebSocket message. - Message(Message), - /// Indicates that the client is to update the shard's presence's activity. - SetActivity(Option), - /// Indicates that the client is to update the shard's presence in its entirety. - SetPresence(Option, OnlineStatus), - /// Indicates that the client is to update the shard's presence's status. - SetStatus(OnlineStatus), -} diff --git a/src/gateway/client/mod.rs b/src/gateway/client/mod.rs index b38a32656d9..d3f225c36b0 100644 --- a/src/gateway/client/mod.rs +++ b/src/gateway/client/mod.rs @@ -2,7 +2,24 @@ //! events to handlers and starting sharded gateway connections is handled directly by the client. //! In addition, the client automatically handles caching via the [`Cache`] struct. //! +//! # Sharding +//! +//! If you do not require sharding - such as for a small bot - then use [`Client::start`]. If you +//! don't know what sharding is, refer to the [`sharding`] module documentation. +//! +//! There are a few methods of sharding available: +//! - [`Client::start_autosharded`]: retrieves the number of shards Discord recommends using from +//! the API, and then automatically starts that number of shards. +//! - [`Client::start_shard`]: starts a single shard for use in the instance, handled by the +//! instance of the Client. Use this if you only want 1 shard handled by this instance. +//! - [`Client::start_shards`]: starts all shards in this instance. This is best for when you want a +//! completely shared State. +//! - [`Client::start_shard_range`]: start a range of shards within this instance. This should be +//! used when you, for example, want to split 10 shards across 3 instances. +//! //! Click [here][Client#examples] for an example on how to use a [`Client`]. +//! +//! [`sharding`]: crate::gateway::sharding mod context; pub(crate) mod dispatch; @@ -357,7 +374,7 @@ impl IntoFuture for ClientBuilder { /// The Client is the way to be able to start sending authenticated requests over the REST API, as /// well as initializing a WebSocket connection through [`Shard`]s. Refer to the [documentation on -/// using sharding][sharding docs] for more information. +/// using sharding][super::sharding] for more information. /// /// # Event Handlers /// @@ -399,7 +416,6 @@ impl IntoFuture for ClientBuilder { /// /// [`Shard`]: crate::gateway::Shard /// [`Event::MessageCreate`]: crate::model::event::Event::MessageCreate -/// [sharding docs]: crate::gateway#sharding pub struct Client { data: Arc, /// A HashMap of all shards instantiated by the Client. diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index a3dbc611419..26b4b0cd1ce 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -1,60 +1,30 @@ -//! The gateway module contains the pieces - primarily the `Shard` - responsible for maintaining a -//! WebSocket connection with Discord. +//! Contains the necessary plumping for maintaining a connection with Discord. +//! The primary building blocks are the [`Client`] and the [`Shard`]. //! -//! A shard is an interface for the lower-level receiver and sender. It provides what can otherwise -//! be thought of as "sugar methods". A shard represents a single connection to Discord. You can -//! make use of a method named "sharding" to have multiple shards, potentially offloading some -//! server load to another server(s). +//! The [`Client`] is a high-level interface that takes care of communicating with Discord's REST +//! API as well as receiving and dispatching events from the gateway using a WebSocket client. //! -//! # Sharding -//! -//! Sharding is a method to split portions of bots into separate processes. This is an enforced -//! strategy by Discord once a bot reaches a certain number of guilds (2500). Once this number is -//! reached, a bot must be sharded in a way that only 2500 guilds maximum may be allocated per -//! shard. -//! -//! The "recommended" number of guilds per shard is _around_ 1000. Sharding can be useful for -//! splitting processes across separate servers. Often you may want some or all shards to be in the -//! same process, allowing for a shared State. This is possible through this library. -//! -//! See [Discord's documentation][docs] for more information. -//! -//! If you do not require sharding - such as for a small bot - then use [`Client::start`]. -//! -//! There are a few methods of sharding available: -//! - [`Client::start_autosharded`]: retrieves the number of shards Discord recommends using from -//! the API, and then automatically starts that number of shards. -//! - [`Client::start_shard`]: starts a single shard for use in the instance, handled by the -//! instance of the Client. Use this if you only want 1 shard handled by this instance. -//! - [`Client::start_shards`]: starts all shards in this instance. This is best for when you want a -//! completely shared State. -//! - [`Client::start_shard_range`]: start a range of shards within this instance. This should be -//! used when you, for example, want to split 10 shards across 3 instances. -//! -//! [`Client`]: crate::Client -//! [`Client::start`]: crate::Client::start -//! [`Client::start_autosharded`]: crate::Client::start_autosharded -//! [`Client::start_shard`]: crate::Client::start_shard -//! [`Client::start_shard_range`]: crate::Client::start_shard_range -//! [`Client::start_shards`]: crate::Client::start_shards -//! [docs]: https://discordapp.com/developers/docs/topics/gateway#sharding +//! On the other hand, the [`Shard`] is a low-level receiver and sender representing a single +//! connection to Discord. The client will handle shard management automatically for you, so you +//! should only care about using it directly if you really need to. See the [`sharding`] module for +//! details and documentation. -mod bridge; pub mod client; mod error; -mod shard; +pub mod sharding; +#[cfg(feature = "voice")] +mod voice; mod ws; -use std::fmt; - #[cfg(feature = "http")] use reqwest::IntoUrl; use reqwest::Url; -pub use self::bridge::*; pub use self::client::*; pub use self::error::Error as GatewayError; -pub use self::shard::Shard; +pub use self::sharding::*; +#[cfg(feature = "voice")] +pub use self::voice::VoiceGatewayManager; pub use self::ws::WsClient; use crate::internal::prelude::*; use crate::model::gateway::{Activity, ActivityType}; @@ -169,92 +139,6 @@ impl From for ActivityData { } } -/// Indicates the current connection stage of a [`Shard`]. -/// -/// This can be useful for knowing which shards are currently "down"/"up". -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -#[non_exhaustive] -pub enum ConnectionStage { - /// Indicator that the [`Shard`] is normally connected and is not in, e.g., a resume phase. - Connected, - /// Indicator that the [`Shard`] is connecting and is in, e.g., a resume phase. - Connecting, - /// Indicator that the [`Shard`] is fully disconnected and is not in a reconnecting phase. - Disconnected, - /// Indicator that the [`Shard`] is currently initiating a handshake. - Handshake, - /// Indicator that the [`Shard`] has sent an IDENTIFY packet and is awaiting a READY packet. - Identifying, - /// Indicator that the [`Shard`] has sent a RESUME packet and is awaiting a RESUMED packet. - Resuming, -} - -impl ConnectionStage { - /// Whether the stage is a form of connecting. - /// - /// This will return `true` on: - /// - [`Connecting`][`ConnectionStage::Connecting`] - /// - [`Handshake`][`ConnectionStage::Handshake`] - /// - [`Identifying`][`ConnectionStage::Identifying`] - /// - [`Resuming`][`ConnectionStage::Resuming`] - /// - /// All other variants will return `false`. - /// - /// # Examples - /// - /// Assert that [`ConnectionStage::Identifying`] is a connecting stage: - /// - /// ```rust - /// use serenity::gateway::ConnectionStage; - /// - /// assert!(ConnectionStage::Identifying.is_connecting()); - /// ``` - /// - /// Assert that [`ConnectionStage::Connected`] is _not_ a connecting stage: - /// - /// ```rust - /// use serenity::gateway::ConnectionStage; - /// - /// assert!(!ConnectionStage::Connected.is_connecting()); - /// ``` - #[must_use] - pub fn is_connecting(self) -> bool { - use self::ConnectionStage::{Connecting, Handshake, Identifying, Resuming}; - matches!(self, Connecting | Handshake | Identifying | Resuming) - } -} - -impl fmt::Display for ConnectionStage { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match *self { - Self::Connected => "connected", - Self::Connecting => "connecting", - Self::Disconnected => "disconnected", - Self::Handshake => "handshaking", - Self::Identifying => "identifying", - Self::Resuming => "resuming", - }) - } -} - -#[derive(Debug)] -#[non_exhaustive] -pub enum ShardAction { - Heartbeat, - Identify, - Reconnect(ReconnectType), -} - -/// The type of reconnection that should be performed. -#[derive(Debug)] -#[non_exhaustive] -pub enum ReconnectType { - /// Indicator that a new connection should be made by sending an IDENTIFY. - Reidentify, - /// Indicator that a new connection should be made by sending a RESUME. - Resume, -} - /// [Discord docs](https://discord.com/developers/docs/topics/gateway-events#request-guild-members). #[derive(Clone, Debug)] pub enum ChunkGuildFilter { diff --git a/src/gateway/shard.rs b/src/gateway/sharding/mod.rs similarity index 79% rename from src/gateway/shard.rs rename to src/gateway/sharding/mod.rs index 69c6cfd13a9..4f5248fa1ea 100644 --- a/src/gateway/shard.rs +++ b/src/gateway/sharding/mod.rs @@ -1,3 +1,44 @@ +//! Sharding is a method for load-balancing bots across separate threads or processes. Sharding is +//! enforced on bots by Discord once they reach a certain number of guilds (2500). Once this +//! threshold is reached, a but must be sharded such that at most 2500 guilds are allocated per +//! shard. +//! +//! The "recommended" number of guilds per shard is _around_ 1000. Sharding allows for bots to be +//! distributed by handing shards off to separate processes or even separate machines in a +//! distributed network (e.g. cloud workers). However, sometimes you may wish for all shards to +//! share some global state. Serenity accomodates both of these usecases. +//! +//! See [Discord's documentation][docs] for more information. +//! +//! This module also provides some lower-level facilities for performing sharding manually: +//! +//! ### [`ShardManager`] +//! +//! The shard manager provides a clean interface for communicating with shard runners either +//! individually or collectively, with functions such as [`ShardManager::shutdown`] and +//! [`ShardManager::restart`] to manage shards in a fine-grained way. +//! +//! For most use cases, the [`ShardManager`] will fit all your low-level sharding needs. +//! +//! ### [`ShardQueuer`] +//! +//! A light wrapper around an mpsc receiver that receives [`ShardQueuerMessage`]s. It should be run +//! in its own thread so it can receive messages to start shards concurrently in a queue. +//! +//! ### [`ShardRunner`] +//! +//! The shard runner is responsible for directly running a single shard and communicating with the +//! gateway through its respective WebSocket client. It performs actions such as identifying, +//! reconnecting, resuming, and sending presence updates to the gateway. +//! +//! [docs]: https://discordapp.com/developers/docs/topics/gateway#sharding + +mod shard_manager; +mod shard_messenger; +mod shard_queuer; +mod shard_runner; + +use std::fmt; use std::sync::Arc; use std::time::{Duration as StdDuration, Instant}; @@ -7,25 +48,20 @@ use tokio_tungstenite::tungstenite::protocol::frame::CloseFrame; use tracing::{debug, error, info, trace, warn}; use url::Url; -use super::{ - ActivityData, - ChunkGuildFilter, - ConnectionStage, - GatewayError, - PresenceData, - ReconnectType, - ShardAction, - WsClient, -}; +pub use self::shard_manager::{ShardManager, ShardManagerOptions}; +pub use self::shard_messenger::ShardMessenger; +pub use self::shard_queuer::{ShardQueue, ShardQueuer, ShardQueuerMessage}; +pub use self::shard_runner::{ShardRunner, ShardRunnerMessage, ShardRunnerOptions}; +use super::{ActivityData, ChunkGuildFilter, GatewayError, PresenceData, WsClient}; use crate::constants::{self, close_codes}; use crate::http::Token; use crate::internal::prelude::*; use crate::model::event::{Event, GatewayEvent}; use crate::model::gateway::{GatewayIntents, ShardInfo}; -use crate::model::id::{ApplicationId, GuildId}; +use crate::model::id::{ApplicationId, GuildId, ShardId}; use crate::model::user::OnlineStatus; -/// A Shard is a higher-level handler for a websocket connection to Discord's gateway. The shard +/// A Shard is an abstract handler for a websocket connection to Discord's gateway. The shard /// allows for sending and receiving messages over the websocket, such as setting the active /// activity, reconnecting, syncing guilds, and more. /// @@ -33,15 +69,13 @@ use crate::model::user::OnlineStatus; /// multiple shards, if you need to. /// /// Note that there are additional methods available if you are manually managing a shard yourself, -/// although they are hidden from the documentation since there are few use cases for doing such. +/// although they are hidden from the documentation since there are few use cases for doing so. /// /// # Stand-alone shards /// -/// You may instantiate a shard yourself - decoupled from the [`Client`] - if you need to. For most -/// use cases, you will not need to do this, and you can leave the client to do it. -/// -/// This can be done by passing in the required parameters to [`Self::new`]. You can then manually -/// handle the shard yourself. +/// You may instantiate a shard yourself - decoupled from the [`Client`] - by calling +/// [`Shard::new`]. Most use cases will not necessitate this, and unless you're doing something +/// really weird you can just let the client do it for you. /// /// **Note**: You _really_ do not need to do this. Just call one of the appropriate methods on the /// [`Client`]. @@ -778,3 +812,141 @@ async fn connect(base_url: &str) -> Result { WsClient::connect(url).await } + +#[derive(Debug)] +#[non_exhaustive] +pub enum ShardAction { + Heartbeat, + Identify, + Reconnect(ReconnectType), +} + +/// Information about a [`ShardRunner`]. +/// +/// The [`ShardId`] is not included because, as it stands, you probably already know the Id if you +/// obtained this. +#[derive(Debug)] +pub struct ShardRunnerInfo { + /// The latency between when a heartbeat was sent and when the acknowledgement was received. + pub latency: Option, + /// The channel used to communicate with the shard runner, telling it what to do with regards + /// to its status. + pub runner_tx: ShardMessenger, + /// The current connection stage of the shard. + pub stage: ConnectionStage, +} + +/// An event denoting that a shard's connection stage was changed. +/// +/// # Examples +/// +/// This might happen when a shard changes from [`ConnectionStage::Identifying`] to +/// [`ConnectionStage::Connected`]. +#[derive(Clone, Debug)] +pub struct ShardStageUpdateEvent { + /// The new connection stage. + pub new: ConnectionStage, + /// The old connection stage. + pub old: ConnectionStage, + /// The ID of the shard that had its connection stage change. + pub shard_id: ShardId, +} + +/// Indicates the current connection stage of a [`Shard`]. +/// +/// This can be useful for knowing which shards are currently "down"/"up". +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[non_exhaustive] +pub enum ConnectionStage { + /// Indicator that the [`Shard`] is normally connected and is not in, e.g., a resume phase. + Connected, + /// Indicator that the [`Shard`] is connecting and is in, e.g., a resume phase. + Connecting, + /// Indicator that the [`Shard`] is fully disconnected and is not in a reconnecting phase. + Disconnected, + /// Indicator that the [`Shard`] is currently initiating a handshake. + Handshake, + /// Indicator that the [`Shard`] has sent an IDENTIFY packet and is awaiting a READY packet. + Identifying, + /// Indicator that the [`Shard`] has sent a RESUME packet and is awaiting a RESUMED packet. + Resuming, +} + +impl ConnectionStage { + /// Whether the stage is a form of connecting. + /// + /// This will return `true` on: + /// - [`Connecting`][`ConnectionStage::Connecting`] + /// - [`Handshake`][`ConnectionStage::Handshake`] + /// - [`Identifying`][`ConnectionStage::Identifying`] + /// - [`Resuming`][`ConnectionStage::Resuming`] + /// + /// All other variants will return `false`. + /// + /// # Examples + /// + /// Assert that [`ConnectionStage::Identifying`] is a connecting stage: + /// + /// ```rust + /// use serenity::gateway::ConnectionStage; + /// + /// assert!(ConnectionStage::Identifying.is_connecting()); + /// ``` + /// + /// Assert that [`ConnectionStage::Connected`] is _not_ a connecting stage: + /// + /// ```rust + /// use serenity::gateway::ConnectionStage; + /// + /// assert!(!ConnectionStage::Connected.is_connecting()); + /// ``` + #[must_use] + pub fn is_connecting(self) -> bool { + use self::ConnectionStage::{Connecting, Handshake, Identifying, Resuming}; + matches!(self, Connecting | Handshake | Identifying | Resuming) + } +} + +impl fmt::Display for ConnectionStage { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match *self { + Self::Connected => "connected", + Self::Connecting => "connecting", + Self::Disconnected => "disconnected", + Self::Handshake => "handshaking", + Self::Identifying => "identifying", + Self::Resuming => "resuming", + }) + } +} + +/// The type of reconnection that should be performed. +#[derive(Debug)] +#[non_exhaustive] +pub enum ReconnectType { + /// Indicator that a new connection should be made by sending an IDENTIFY. + Reidentify, + /// Indicator that a new connection should be made by sending a RESUME. + Resume, +} + +/// Newtype around a callback that will be called on every incoming request. As long as this +/// collector should still receive events, it should return `true`. Once it returns `false`, it is +/// removed. +#[cfg(feature = "collector")] +#[derive(Clone)] +pub struct CollectorCallback(pub Arc bool + Send + Sync>); + +#[cfg(feature = "collector")] +impl fmt::Debug for CollectorCallback { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("CollectorCallback").finish() + } +} + +#[cfg(feature = "collector")] +impl PartialEq for CollectorCallback { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.0, &other.0) + } +} diff --git a/src/gateway/bridge/shard_manager.rs b/src/gateway/sharding/shard_manager.rs similarity index 99% rename from src/gateway/bridge/shard_manager.rs rename to src/gateway/sharding/shard_manager.rs index 80e3c5eb368..413f886f34a 100644 --- a/src/gateway/bridge/shard_manager.rs +++ b/src/gateway/sharding/shard_manager.rs @@ -11,13 +11,13 @@ use tokio::sync::Mutex; use tokio::time::timeout; use tracing::{info, warn}; -#[cfg(feature = "voice")] -use super::VoiceGatewayManager; use super::{ShardId, ShardQueue, ShardQueuer, ShardQueuerMessage, ShardRunnerInfo}; #[cfg(feature = "cache")] use crate::cache::Cache; #[cfg(feature = "framework")] use crate::framework::Framework; +#[cfg(feature = "voice")] +use crate::gateway::VoiceGatewayManager; use crate::gateway::{ConnectionStage, GatewayError, InternalEventHandler, PresenceData}; use crate::http::Http; use crate::internal::prelude::*; diff --git a/src/gateway/bridge/shard_messenger.rs b/src/gateway/sharding/shard_messenger.rs similarity index 100% rename from src/gateway/bridge/shard_messenger.rs rename to src/gateway/sharding/shard_messenger.rs diff --git a/src/gateway/bridge/shard_queuer.rs b/src/gateway/sharding/shard_queuer.rs similarity index 96% rename from src/gateway/bridge/shard_queuer.rs rename to src/gateway/sharding/shard_queuer.rs index 054ec0d86c2..a68a934a98c 100644 --- a/src/gateway/bridge/shard_queuer.rs +++ b/src/gateway/sharding/shard_queuer.rs @@ -10,13 +10,10 @@ use tokio::sync::Mutex; use tokio::time::{sleep, timeout, Duration, Instant}; use tracing::{debug, info, warn}; -#[cfg(feature = "voice")] -use super::VoiceGatewayManager; use super::{ ShardId, ShardManager, ShardMessenger, - ShardQueuerMessage, ShardRunner, ShardRunnerInfo, ShardRunnerOptions, @@ -25,6 +22,8 @@ use super::{ use crate::cache::Cache; #[cfg(feature = "framework")] use crate::framework::Framework; +#[cfg(feature = "voice")] +use crate::gateway::VoiceGatewayManager; use crate::gateway::{ ConnectionStage, InternalEventHandler, @@ -342,3 +341,16 @@ impl ShardQueue { self.buckets.iter().all(|b| !b.is_empty()) } } + +/// A message to be sent to the [`ShardQueuer`]. +#[derive(Clone, Debug)] +pub enum ShardQueuerMessage { + /// Message to set the shard total. + SetShardTotal(NonZeroU16), + /// Message to start a shard. + Start { shard_id: ShardId, concurrent: bool }, + /// Message to shutdown the shard queuer. + Shutdown, + /// Message to dequeue/shutdown a shard. + ShutdownShard { shard_id: ShardId, code: u16 }, +} diff --git a/src/gateway/bridge/shard_runner.rs b/src/gateway/sharding/shard_runner.rs similarity index 89% rename from src/gateway/bridge/shard_runner.rs rename to src/gateway/sharding/shard_runner.rs index f0dbaabaa89..e8599352f1e 100644 --- a/src/gateway/bridge/shard_runner.rs +++ b/src/gateway/sharding/shard_runner.rs @@ -5,31 +5,26 @@ use futures::channel::mpsc::{self, UnboundedReceiver as Receiver, UnboundedSende use tokio_tungstenite::tungstenite; use tokio_tungstenite::tungstenite::error::Error as TungsteniteError; use tokio_tungstenite::tungstenite::protocol::frame::CloseFrame; +use tokio_tungstenite::tungstenite::Message; use tracing::{debug, error, info, trace, warn}; -use super::event::ShardStageUpdateEvent; #[cfg(feature = "collector")] use super::CollectorCallback; -#[cfg(feature = "voice")] -use super::VoiceGatewayManager; -use super::{ShardId, ShardManager, ShardRunnerMessage}; +use super::{ReconnectType, Shard, ShardAction, ShardId, ShardManager, ShardStageUpdateEvent}; #[cfg(feature = "cache")] use crate::cache::Cache; #[cfg(feature = "framework")] use crate::framework::Framework; use crate::gateway::dispatch::dispatch_model; -use crate::gateway::{ - Context, - GatewayError, - InternalEventHandler, - ReconnectType, - Shard, - ShardAction, -}; +#[cfg(feature = "voice")] +use crate::gateway::VoiceGatewayManager; +use crate::gateway::{ActivityData, ChunkGuildFilter, Context, GatewayError, InternalEventHandler}; use crate::http::Http; use crate::internal::prelude::*; use crate::internal::tokio::spawn_named; use crate::model::event::{Event, GatewayEvent}; +use crate::model::id::GuildId; +use crate::model::user::OnlineStatus; /// A runner for managing a [`Shard`] and its respective WebSocket client. pub struct ShardRunner { @@ -524,3 +519,50 @@ pub struct ShardRunnerOptions { pub cache: Arc, pub http: Arc, } + +/// A message to send from a shard over a WebSocket. +#[derive(Debug)] +pub enum ShardRunnerMessage { + /// Indicator that a shard should be restarted. + Restart(ShardId), + /// Indicator that a shard should be fully shutdown without bringing it + /// back up. + Shutdown(ShardId, u16), + /// Indicates that the client is to send a member chunk message. + ChunkGuild { + /// The IDs of the [`Guild`] to chunk. + /// + /// [`Guild`]: crate::model::guild::Guild + guild_id: GuildId, + /// The maximum number of members to receive [`GuildMembersChunkEvent`]s for. + /// + /// [`GuildMembersChunkEvent`]: crate::model::event::GuildMembersChunkEvent + limit: Option, + /// Used to specify if we want the presences of the matched members. + /// + /// Requires [`crate::model::gateway::GatewayIntents::GUILD_PRESENCES`]. + presences: bool, + /// A filter to apply to the returned members. + filter: ChunkGuildFilter, + /// Optional nonce to identify [`GuildMembersChunkEvent`] responses. + /// + /// [`GuildMembersChunkEvent`]: crate::model::event::GuildMembersChunkEvent + nonce: Option, + }, + /// Indicates that the client is to close with the given status code and reason. + /// + /// You should rarely - if _ever_ - need this, but the option is available. Prefer to use the + /// [`ShardManager`] to shutdown WebSocket clients if you are intending to send a 1000 close + /// code. + /// + /// [`ShardManager`]: super::ShardManager + Close(u16, Option), + /// Indicates that the client is to send a custom WebSocket message. + Message(Message), + /// Indicates that the client is to update the shard's presence's activity. + SetActivity(Option), + /// Indicates that the client is to update the shard's presence in its entirety. + SetPresence(Option, OnlineStatus), + /// Indicates that the client is to update the shard's presence's status. + SetStatus(OnlineStatus), +} diff --git a/src/gateway/bridge/voice.rs b/src/gateway/voice.rs similarity index 100% rename from src/gateway/bridge/voice.rs rename to src/gateway/voice.rs