Skip to content

Commit

Permalink
feat: autoposter handles!
Browse files Browse the repository at this point in the history
  • Loading branch information
null8626 committed Dec 15, 2023
1 parent 737a300 commit bf841ab
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 255 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "topgg"
version = "1.2.0"
version = "1.2.1"
edition = "2021"
authors = ["null (https://github.com/null8626)", "Top.gg <[email protected]> (https://top.gg)"]
description = "The official Rust wrapper for the Top.gg API"
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,8 @@ async fn main() {
// ... then in some on ready/new guild event ...
let server_count = 12345;
autoposter
.feed(Stats::count_based(server_count, None))
.await;
let stats = Stats::count_based(server_count, None);
autoposter.feed(stats).await;
}
```

Expand Down
161 changes: 100 additions & 61 deletions src/autoposter.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{client::InnerClient, Stats};
use core::{mem::MaybeUninit, time::Duration};
use core::{mem::MaybeUninit, ops::Deref, time::Duration};
use std::sync::Arc;
use tokio::{
sync::Mutex,
Expand All @@ -12,47 +12,103 @@ struct PendingData {
stats: Stats,
}

/// A fully [`Clone`]able and thread-safe struct that lets you remotely feed bot statistics to the [`Autoposter`].
pub struct AutoposterHandle {
data: Arc<Mutex<PendingData>>,
}

impl AutoposterHandle {
/// Feeds new bot stats to this autoposter handle. The [autoposter itself][Autoposter] will automatically post it to the [Top.gg](https://top.gg) servers once appropiate.
///
/// # Examples
///
/// Direct usage with an [`Autoposter`]:
///
/// ```rust,no_run
/// use core::time::Duration;
/// use topgg::{Client, Stats};
///
/// let client = Client::new(env!("TOPGG_TOKEN").to_string());
///
/// // creates an autoposter that posts data to Top.gg every 1800 seconds (30 minutes).
/// // the autopost thread will stop once it's dropped.
/// let autoposter = client.new_autoposter(Duration::from_secs(1800));
///
/// // ... then in some on ready/new guild event ...
/// let server_count = 12345;
/// let stats = Stats::count_based(server_count, None);
/// autoposter.feed(stats).await;
/// ```
///
/// Remote usage with an [`AutoposterHandle`]:
///
/// ```rust,no_run
/// use core::time::Duration;
/// use topgg::{Client, Stats};
///
/// let client = Client::new(env!("TOPGG_TOKEN").to_string());
///
/// // creates an autoposter that posts data to Top.gg every 1800 seconds (30 minutes).
/// // the autopost thread will stop once it's dropped.
/// let autoposter = client.new_autoposter(Duration::from_secs(1800));
///
/// let server_count = 12345;
/// autoposter
/// .feed(Stats::count_based(server_count, None))
/// .await;
///
/// // this handle can be cloned and tossed around threads!
/// let new_handle = autoposter.handle();
///
/// // do the same thing...
/// new_handle
/// .feed(Stats::count_based(server_count, None))
/// .await;
///
/// let another_handle = new_handle.clone();
///
/// // do the same thing...
/// another_handle
/// .feed(Stats::count_based(server_count, None))
/// .await;
/// ```
pub async fn feed(&self, new_stats: Stats) {
let mut lock = self.data.lock().await;

lock.ready = true; // flag the PendingData object as containing new data.
lock.stats = new_stats;
}
}

/// Creates another handle that points to the same reference handle. Somewhat similar to an [`Arc::clone`].
impl Clone for AutoposterHandle {
#[inline(always)]
fn clone(&self) -> Self {
Self {
data: Arc::clone(&self.data),
}
}
}

/// A struct that lets you automate the process of posting bot statistics to the [Top.gg API](https://docs.top.gg) in intervals.
///
/// # Examples
///
/// Basic usage:
///
/// ```rust,no_run
/// use core::time::Duration;
/// use topgg::{Client, Stats};
///
/// #[tokio::main]
/// async fn main() {
/// let client = Client::new(env!("TOPGG_TOKEN").to_string());
///
/// // creates an autoposter that posts data to Top.gg every 1800 seconds (30 minutes).
/// // the autopost thread will stop once it's dropped.
/// let autoposter = client.new_autoposter(Duration::from_secs(1800));
///
/// // ... then in some on ready/new guild event ...
/// let server_count = 12345;
/// autoposter
/// .feed(Stats::count_based(server_count, None))
/// .await;
/// }
/// ```
#[must_use]
pub struct Autoposter {
thread: JoinHandle<()>,
data: Arc<Mutex<PendingData>>,
handle: AutoposterHandle,
}

impl Autoposter {
#[allow(invalid_value, clippy::uninit_assumed_init)]
pub(crate) fn new(client: Arc<InnerClient>, interval: Duration) -> Self {
// SAFETY: post_stats will be called ONLY when the ready flag is set to true.
let current_thread_data = Arc::new(Mutex::new(PendingData {
ready: false,
stats: unsafe { MaybeUninit::uninit().assume_init() },
}));
let handle = AutoposterHandle {
data: Arc::new(Mutex::new(PendingData {
ready: false,
stats: unsafe { MaybeUninit::uninit().assume_init() },
})),
};

let thread_data = Arc::clone(&current_thread_data);
let thread_data = Arc::clone(&handle.data);

Self {
thread: spawn(async move {
Expand All @@ -67,40 +123,23 @@ impl Autoposter {
}
}
}),
data: current_thread_data,
handle,
}
}

/// Feeds new bot stats to the autoposter. The autoposter will automatically post it to the [Top.gg](https://top.gg) servers in intervals.
///
/// # Examples
///
/// Basic usage:
///
/// ```rust,no_run
/// use core::time::Duration;
/// use topgg::{Client, Stats};
///
/// #[tokio::main]
/// async fn main() {
/// let client = Client::new(env!("TOPGG_TOKEN").to_string());
///
/// // creates an autoposter that posts data to Top.gg every 1800 seconds (30 minutes).
/// // the autopost thread will stop once it's dropped.
/// let autoposter = client.new_autoposter(Duration::from_secs(1800));
///
/// // ... then in some on ready/new guild event ...
/// let server_count = 12345;
/// autoposter
/// .feed(Stats::count_based(server_count, None))
/// .await;
/// }
/// ```
pub async fn feed(&self, new_stats: Stats) {
let mut lock = self.data.lock().await;
/// Creates an [`AutoposterHandle`] that lets you remotely feed bot statistics to this [`Autoposter`]. This struct is fully [`Clone`]able and thread-safe.
#[inline(always)]
pub fn handle(&self) -> AutoposterHandle {
self.handle.clone()
}
}

lock.ready = true; // flag the PendingData object as containing new data.
lock.stats = new_stats;
impl Deref for Autoposter {
type Target = AutoposterHandle;

#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.handle
}
}

Expand Down
96 changes: 32 additions & 64 deletions src/bot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,10 @@ impl Bot {
/// ```rust,no_run
/// use topgg::Client;
///
/// #[tokio::main]
/// async fn main() {
/// let client = Client::new(env!("TOPGG_TOKEN").to_string());
///
/// let bot = client.get_bot(264811613708746752).await.unwrap();
///
/// println!("{}", bot.created_at());
/// }
/// let client = Client::new(env!("TOPGG_TOKEN").to_string());
/// let bot = client.get_bot(264811613708746752).await.unwrap();
///
/// println!("{}", bot.created_at());
/// ```
#[must_use]
#[inline(always)]
Expand All @@ -146,14 +142,10 @@ impl Bot {
/// ```rust,no_run
/// use topgg::Client;
///
/// #[tokio::main]
/// async fn main() {
/// let client = Client::new(env!("TOPGG_TOKEN").to_string());
///
/// let bot = client.get_bot(264811613708746752).await.unwrap();
///
/// println!("{}", bot.avatar());
/// }
/// let client = Client::new(env!("TOPGG_TOKEN").to_string());
/// let bot = client.get_bot(264811613708746752).await.unwrap();
///
/// println!("{}", bot.avatar());
/// ```
#[must_use]
#[inline(always)]
Expand All @@ -170,14 +162,10 @@ impl Bot {
/// ```rust,no_run
/// use topgg::Client;
///
/// #[tokio::main]
/// async fn main() {
/// let client = Client::new(env!("TOPGG_TOKEN").to_string());
///
/// let bot = client.get_bot(264811613708746752).await.unwrap();
///
/// println!("{}", bot.invite());
/// }
/// let client = Client::new(env!("TOPGG_TOKEN").to_string());
/// let bot = client.get_bot(264811613708746752).await.unwrap();
///
/// println!("{}", bot.invite());
/// ```
#[must_use]
pub fn invite(&self) -> String {
Expand All @@ -199,14 +187,10 @@ impl Bot {
/// ```rust,no_run
/// use topgg::Client;
///
/// #[tokio::main]
/// async fn main() {
/// let client = Client::new(env!("TOPGG_TOKEN").to_string());
///
/// let bot = client.get_bot(264811613708746752).await.unwrap();
///
/// println!("{}", bot.shard_count());
/// }
/// let client = Client::new(env!("TOPGG_TOKEN").to_string());
/// let bot = client.get_bot(264811613708746752).await.unwrap();
///
/// println!("{}", bot.shard_count());
/// ```
#[must_use]
#[inline(always)]
Expand All @@ -223,14 +207,10 @@ impl Bot {
/// ```rust,no_run
/// use topgg::Client;
///
/// #[tokio::main]
/// async fn main() {
/// let client = Client::new(env!("TOPGG_TOKEN").to_string());
///
/// let bot = client.get_bot(264811613708746752).await.unwrap();
///
/// println!("{}", bot.url());
/// }
/// let client = Client::new(env!("TOPGG_TOKEN").to_string());
/// let bot = client.get_bot(264811613708746752).await.unwrap();
///
/// println!("{}", bot.url());
/// ```
#[must_use]
#[inline(always)]
Expand Down Expand Up @@ -360,14 +340,10 @@ impl Stats {
/// ```rust,no_run
/// use topgg::Client;
///
/// #[tokio::main]
/// async fn main() {
/// let client = Client::new(env!("TOPGG_TOKEN").to_string());
///
/// let stats = client.get_stats().await.unwrap();
///
/// println!("{:?}", stats.shards());
/// }
/// let client = Client::new(env!("TOPGG_TOKEN").to_string());
/// let stats = client.get_stats().await.unwrap();
///
/// println!("{:?}", stats.shards());
/// ```
#[must_use]
#[inline(always)]
Expand All @@ -387,14 +363,10 @@ impl Stats {
/// ```rust,no_run
/// use topgg::Client;
///
/// #[tokio::main]
/// async fn main() {
/// let client = Client::new(env!("TOPGG_TOKEN").to_string());
///
/// let stats = client.get_stats().await.unwrap();
///
/// println!("{:?}", stats.shard_count());
/// }
/// let client = Client::new(env!("TOPGG_TOKEN").to_string());
/// let stats = client.get_stats().await.unwrap();
///
/// println!("{:?}", stats.shard_count());
/// ```
#[must_use]
#[inline(always)]
Expand All @@ -414,14 +386,10 @@ impl Stats {
/// ```rust,no_run
/// use topgg::Client;
///
/// #[tokio::main]
/// async fn main() {
/// let client = Client::new(env!("TOPGG_TOKEN").to_string());
///
/// let stats = client.get_stats().await.unwrap();
///
/// println!("{:?}", stats.server_count());
/// }
/// let client = Client::new(env!("TOPGG_TOKEN").to_string());
/// let stats = client.get_stats().await.unwrap();
///
/// println!("{:?}", stats.server_count());
/// ```
#[must_use]
pub fn server_count(&self) -> Option<usize> {
Expand Down
Loading

0 comments on commit bf841ab

Please sign in to comment.