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#[inline]
105#[track_caller]
106pub fn bucket(
107    bucket_id: u16,
108    buckets: u16,
109    shards: u32,
110) -> impl DoubleEndedIterator<Item = ShardId> + ExactSizeIterator {
111    let bucket_id = u32::from(bucket_id);
112    let buckets = u32::from(buckets);
113    assert!(bucket_id < buckets, "bucket_id must be less than buckets");
114
115    let (q, r) = (shards / buckets, shards % buckets);
116
117    let len = q + u32::from(bucket_id < r);
118    let start = bucket_id * q + r.min(bucket_id);
119
120    // Compiler hint deduced from mathematical proof.
121    if shards < start + len {
122        std::process::abort();
123    }
124    (start..start + len).map(move |id| ShardId::new(id, shards))
125}
126
127/// Create a single bucket's worth of shards.
128///
129/// Passing a primary config is required. Further customization of this config
130/// may be performed in the callback.
131///
132/// Internally calls [`create_iterator`] with `(bucket_id..total).step_by(concurrency)`.
133///
134/// # Panics
135///
136/// Panics if `bucket_id >= total`, `bucket_id >= concurrency`, or `concurrency >= total`.
137///
138/// Panics if loading TLS certificates fails.
139#[deprecated(
140    since = "0.17.2",
141    note = "creates non-consecutive shards; use `bucket` instead"
142)]
143#[track_caller]
144pub fn create_bucket<F, Q>(
145    bucket_id: u16,
146    concurrency: u16,
147    total: u32,
148    config: Config<Q>,
149    per_shard_config: F,
150) -> impl ExactSizeIterator<Item = Shard<Q>>
151where
152    F: FnMut(ShardId, ConfigBuilder<Q>) -> Config<Q>,
153    Q: Clone,
154{
155    assert!(
156        u32::from(bucket_id) < total,
157        "bucket id must be less than the total"
158    );
159    assert!(
160        bucket_id < concurrency,
161        "bucket id must be less than concurrency"
162    );
163    assert!(
164        (u32::from(concurrency)) < total,
165        "concurrency must be less than the total"
166    );
167
168    #[allow(deprecated)]
169    create_iterator(
170        (u32::from(bucket_id)..total).step_by(concurrency.into()),
171        total,
172        config,
173        per_shard_config,
174    )
175}
176
177/// Create a iterator of shards.
178///
179/// Passing a primary config is required. Further customization of this config
180/// may be performed in the callback.
181///
182/// # Examples
183///
184/// Start 10 out of 10 shards and count them:
185///
186/// ```no_run
187/// use std::{collections::HashMap, env, sync::Arc};
188/// use twilight_gateway::{Config, Intents};
189///
190/// let token = env::var("DISCORD_TOKEN")?;
191///
192/// let config = Config::new(token.clone(), Intents::GUILDS);
193/// let shards = twilight_gateway::create_iterator(0..10, 10, config, |_, builder| builder.build());
194///
195/// assert_eq!(shards.len(), 10);
196/// # Ok::<(), Box<dyn std::error::Error>>(())
197/// ```
198///
199/// # Panics
200///
201/// Panics if `range` contains values larger than `total`.
202///
203/// Panics if loading TLS certificates fails.
204#[deprecated(since = "0.17.2", note = "use `bucket` instead")]
205#[track_caller]
206pub fn create_iterator<F, Q>(
207    numbers: impl ExactSizeIterator<Item = u32>,
208    total: u32,
209    config: Config<Q>,
210    mut per_shard_config: F,
211) -> impl ExactSizeIterator<Item = Shard<Q>>
212where
213    F: FnMut(ShardId, ConfigBuilder<Q>) -> Config<Q>,
214    Q: Clone,
215{
216    numbers.map(move |index| {
217        let id = ShardId::new(index, total);
218        let config = per_shard_config(id, ConfigBuilder::from(config.clone()));
219
220        Shard::with_config(id, config)
221    })
222}
223
224/// Create a range of shards from Discord's recommendation.
225///
226/// Passing a primary config is required. Further customization of this config
227/// may be performed in the callback.
228///
229/// Internally calls [`create_iterator`] with the values from [`GetGatewayAuthed`].
230///
231/// # Errors
232///
233/// Returns a [`StartRecommendedErrorType::Deserializing`] error type if the
234/// response body failed to deserialize.
235///
236/// Returns a [`StartRecommendedErrorType::Request`] error type if the request
237/// failed to complete.
238///
239/// # Panics
240///
241/// Panics if loading TLS certificates fails.
242///
243/// [`GetGatewayAuthed`]: twilight_http::request::GetGatewayAuthed
244#[allow(deprecated)]
245#[cfg(feature = "twilight-http")]
246#[deprecated(
247    since = "0.17.2",
248    note = "fetch the recommended shard count via `client.gateway().authed()` instead (from `twilight_http::Client`)"
249)]
250pub async fn create_recommended<F, Q>(
251    client: &Client,
252    config: Config<Q>,
253    per_shard_config: F,
254) -> Result<impl ExactSizeIterator<Item = Shard<Q>> + use<F, Q>, StartRecommendedError>
255where
256    F: FnMut(ShardId, ConfigBuilder<Q>) -> Config<Q>,
257    Q: Clone,
258{
259    let request = client.gateway().authed();
260    let response = request.await.map_err(|source| StartRecommendedError {
261        kind: StartRecommendedErrorType::Request,
262        source: Some(Box::new(source)),
263    })?;
264    let info = response
265        .model()
266        .await
267        .map_err(|source| StartRecommendedError {
268            kind: StartRecommendedErrorType::Deserializing,
269            source: Some(Box::new(source)),
270        })?;
271
272    #[allow(deprecated)]
273    Ok(create_iterator(
274        0..info.shards,
275        info.shards,
276        config,
277        per_shard_config,
278    ))
279}