Skip to main content

twilight_gateway/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3#![warn(
4    clippy::missing_const_for_fn,
5    clippy::missing_docs_in_private_items,
6    clippy::pedantic,
7    missing_docs,
8    unsafe_code
9)]
10#![allow(
11    clippy::module_name_repetitions,
12    clippy::must_use_candidate,
13    clippy::unnecessary_wraps
14)]
15
16pub mod error;
17
18mod channel;
19mod command;
20#[cfg(any(feature = "zlib", feature = "zstd"))]
21mod compression;
22mod config;
23mod event;
24mod json;
25mod latency;
26mod message;
27mod ratelimiter;
28mod session;
29mod shard;
30mod stream;
31
32pub use self::{
33    channel::MessageSender,
34    command::Command,
35    config::{Config, ConfigBuilder},
36    event::EventTypeFlags,
37    json::parse,
38    latency::Latency,
39    message::Message,
40    ratelimiter::CommandRatelimiter,
41    session::Session,
42    shard::{Shard, ShardState},
43    stream::StreamExt,
44};
45pub use twilight_model::gateway::{CloseFrame, Intents, ShardId};
46
47#[doc(no_inline)]
48pub use twilight_gateway_queue as queue;
49#[doc(no_inline)]
50pub use twilight_model::gateway::event::{Event, EventType};
51
52#[allow(deprecated)]
53#[cfg(feature = "twilight-http")]
54use self::error::{StartRecommendedError, StartRecommendedErrorType};
55#[cfg(feature = "twilight-http")]
56use twilight_http::Client;
57
58/// Discord Gateway API version used by this crate.
59pub const API_VERSION: u8 = 10;
60
61/// Creates an iterator of a single bucket's worth of shard identifiers.
62///
63/// Each bucket holds a consecutive range of identifiers and all identifiers
64/// share the same `total` value.
65///
66/// # Strategy
67///
68/// Shards may be bucketed per-thread for a thread-per-core architecture and/or
69/// per-machine for horizontal scaling.
70///
71/// # Examples
72///
73/// Create 1 bucket with the recommended shard count:
74///
75/// ```no_run
76/// use std::env;
77/// use twilight_http::Client;
78///
79/// # #[tokio::main(flavor = "current_thread")]
80/// # async fn main() -> anyhow::Result<()> {
81/// let http = Client::new(env::var("TOKEN")?);
82/// let info = http.gateway().authed().await?.model().await?;
83///
84/// let shards = twilight_gateway::bucket(0, 1, info.shards);
85/// assert_eq!(shards.len(), info.shards as usize);
86/// # anyhow::Ok(())
87/// # }
88/// ```
89///
90/// Create 2 buckets with 25 identifiers:
91///
92/// ```
93/// let bucket_1 = twilight_gateway::bucket(0, 2, 25);
94/// let bucket_2 = twilight_gateway::bucket(1, 2, 25);
95///
96/// assert_eq!(bucket_1.len(), 13);
97/// assert_eq!(bucket_2.len(), 12);
98/// ```
99///
100/// # Panics
101///
102/// Panics if the bucket id is greater than or equal to the total number of
103/// buckets.
104#[track_caller]
105pub fn bucket(
106    bucket_id: u16,
107    buckets: u16,
108    shards: u32,
109) -> impl DoubleEndedIterator<Item = ShardId> + ExactSizeIterator {
110    let bucket_id = u32::from(bucket_id);
111    let buckets = u32::from(buckets);
112    assert!(bucket_id < buckets, "bucket_id must be less than buckets");
113
114    let (q, r) = (shards / buckets, shards % buckets);
115
116    let len = q + u32::from(bucket_id < r);
117    let start = bucket_id * q + r.min(bucket_id);
118
119    (start..start + len).map(move |id| ShardId::new(id, shards))
120}
121
122/// Create a single bucket's worth of shards.
123///
124/// Passing a primary config is required. Further customization of this config
125/// may be performed in the callback.
126///
127/// Internally calls [`create_iterator`] with `(bucket_id..total).step_by(concurrency)`.
128///
129/// # Panics
130///
131/// Panics if `bucket_id >= total`, `bucket_id >= concurrency`, or `concurrency >= total`.
132///
133/// Panics if loading TLS certificates fails.
134#[deprecated(
135    since = "0.17.2",
136    note = "creates non-consecutive shards; use `bucket` instead"
137)]
138#[track_caller]
139pub fn create_bucket<F, Q>(
140    bucket_id: u16,
141    concurrency: u16,
142    total: u32,
143    config: Config<Q>,
144    per_shard_config: F,
145) -> impl ExactSizeIterator<Item = Shard<Q>>
146where
147    F: FnMut(ShardId, ConfigBuilder<Q>) -> Config<Q>,
148    Q: Clone,
149{
150    assert!(
151        u32::from(bucket_id) < total,
152        "bucket id must be less than the total"
153    );
154    assert!(
155        bucket_id < concurrency,
156        "bucket id must be less than concurrency"
157    );
158    assert!(
159        (u32::from(concurrency)) < total,
160        "concurrency must be less than the total"
161    );
162
163    #[allow(deprecated)]
164    create_iterator(
165        (u32::from(bucket_id)..total).step_by(concurrency.into()),
166        total,
167        config,
168        per_shard_config,
169    )
170}
171
172/// Create a iterator of shards.
173///
174/// Passing a primary config is required. Further customization of this config
175/// may be performed in the callback.
176///
177/// # Examples
178///
179/// Start 10 out of 10 shards and count them:
180///
181/// ```no_run
182/// use std::{collections::HashMap, env, sync::Arc};
183/// use twilight_gateway::{Config, Intents};
184///
185/// let token = env::var("DISCORD_TOKEN")?;
186///
187/// let config = Config::new(token.clone(), Intents::GUILDS);
188/// let shards = twilight_gateway::create_iterator(0..10, 10, config, |_, builder| builder.build());
189///
190/// assert_eq!(shards.len(), 10);
191/// # Ok::<(), Box<dyn std::error::Error>>(())
192/// ```
193///
194/// # Panics
195///
196/// Panics if `range` contains values larger than `total`.
197///
198/// Panics if loading TLS certificates fails.
199#[deprecated(since = "0.17.2", note = "use `bucket` instead")]
200#[track_caller]
201pub fn create_iterator<F, Q>(
202    numbers: impl ExactSizeIterator<Item = u32>,
203    total: u32,
204    config: Config<Q>,
205    mut per_shard_config: F,
206) -> impl ExactSizeIterator<Item = Shard<Q>>
207where
208    F: FnMut(ShardId, ConfigBuilder<Q>) -> Config<Q>,
209    Q: Clone,
210{
211    numbers.map(move |index| {
212        let id = ShardId::new(index, total);
213        let config = per_shard_config(id, ConfigBuilder::from(config.clone()));
214
215        Shard::with_config(id, config)
216    })
217}
218
219/// Create a range of shards from Discord's recommendation.
220///
221/// Passing a primary config is required. Further customization of this config
222/// may be performed in the callback.
223///
224/// Internally calls [`create_iterator`] with the values from [`GetGatewayAuthed`].
225///
226/// # Errors
227///
228/// Returns a [`StartRecommendedErrorType::Deserializing`] error type if the
229/// response body failed to deserialize.
230///
231/// Returns a [`StartRecommendedErrorType::Request`] error type if the request
232/// failed to complete.
233///
234/// # Panics
235///
236/// Panics if loading TLS certificates fails.
237///
238/// [`GetGatewayAuthed`]: twilight_http::request::GetGatewayAuthed
239#[allow(deprecated)]
240#[cfg(feature = "twilight-http")]
241#[deprecated(
242    since = "0.17.2",
243    note = "fetch the recommended shard count via `client.gateway().authed()` instead (from `twilight_http::Client`)"
244)]
245pub async fn create_recommended<F, Q>(
246    client: &Client,
247    config: Config<Q>,
248    per_shard_config: F,
249) -> Result<impl ExactSizeIterator<Item = Shard<Q>> + use<F, Q>, StartRecommendedError>
250where
251    F: FnMut(ShardId, ConfigBuilder<Q>) -> Config<Q>,
252    Q: Clone,
253{
254    let request = client.gateway().authed();
255    let response = request.await.map_err(|source| StartRecommendedError {
256        kind: StartRecommendedErrorType::Request,
257        source: Some(Box::new(source)),
258    })?;
259    let info = response
260        .model()
261        .await
262        .map_err(|source| StartRecommendedError {
263            kind: StartRecommendedErrorType::Deserializing,
264            source: Some(Box::new(source)),
265        })?;
266
267    #[allow(deprecated)]
268    Ok(create_iterator(
269        0..info.shards,
270        info.shards,
271        config,
272        per_shard_config,
273    ))
274}