Skip to content

Commit

Permalink
"Self updating structs" API improvements (#467)
Browse files Browse the repository at this point in the history
This PR slightly improves the ergonomics of working with self-updating
structs, by making the changes as documented in #466.
  • Loading branch information
bitfl0wer authored Jan 22, 2024
2 parents 163db42 + 2209efc commit ed3786c
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 29 deletions.
4 changes: 2 additions & 2 deletions examples/gateway_simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ async fn main() {
identify.token = token;

// Send off the event
gateway.send_identify(identify).await;
gateway.send_identify(identify).await;

// Do something on the main thread so we don't quit
loop {
sleep(Duration::from_secs(3600)).await;
Expand Down
15 changes: 12 additions & 3 deletions src/gateway/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,19 @@ impl GatewayHandle {
.unwrap();
}

/// Recursively observes a [`Shared`] object, by making sure all [`Composite `] fields within
/// that object and its children are being watched.
///
/// Observing means, that if new information arrives about the observed object or its children,
/// the object automatically gets updated, without you needing to request new information about
/// the object in question from the API, which is expensive and can lead to rate limiting.
///
/// The [`Shared`] object returned by this method points to a different object than the one
/// being supplied as a &self function argument.
pub async fn observe<T: Updateable + Clone + Debug + Composite<T>>(
&self,
object: Arc<RwLock<T>>,
) -> Arc<RwLock<T>> {
object: Shared<T>,
) -> Shared<T> {
let mut store = self.store.lock().await;
let id = object.read().unwrap().id();
if let Some(channel) = store.get(&id) {
Expand Down Expand Up @@ -84,7 +93,7 @@ impl GatewayHandle {
/// with all of its observable fields being observed.
pub async fn observe_and_into_inner<T: Updateable + Clone + Debug + Composite<T>>(
&self,
object: Arc<RwLock<T>>,
object: Shared<T>,
) -> T {
let channel = self.observe(object.clone()).await;
let object = channel.read().unwrap().clone();
Expand Down
2 changes: 1 addition & 1 deletion src/gateway/heartbeat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl HeartbeatHandler {
let mut last_heartbeat_timestamp: Instant = Instant::now();
let mut last_heartbeat_acknowledged = true;
let mut last_seq_number: Option<u64> = None;

loop {
if kill_receive.try_recv().is_ok() {
trace!("GW: Closing heartbeat task");
Expand Down
8 changes: 8 additions & 0 deletions src/gateway/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,11 @@ impl<T: WebSocketEvent> GatewayEvent<T> {
}
}
}

/// A type alias for [`Arc<RwLock<T>>`], used to make the public facing API concerned with
/// Composite structs more ergonomic.
/// ## Note
///
/// While `T` does not have to implement `Composite` to be used with `Shared`,
/// the primary use of `Shared` is with types that implement `Composite`.
pub type Shared<T> = Arc<RwLock<T>>;
33 changes: 24 additions & 9 deletions src/types/entities/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ pub use user_settings::*;
pub use voice_state::*;
pub use webhook::*;

use crate::gateway::Shared;

#[cfg(feature = "client")]
use crate::gateway::Updateable;

Expand Down Expand Up @@ -69,9 +71,9 @@ pub trait Composite<T: Updateable + Clone + Debug> {
async fn watch_whole(self, gateway: &GatewayHandle) -> Self;

async fn option_observe_fn(
value: Option<Arc<RwLock<T>>>,
value: Option<Shared<T>>,
gateway: &GatewayHandle,
) -> Option<Arc<RwLock<T>>>
) -> Option<Shared<T>>
where
T: Composite<T> + Debug,
{
Expand All @@ -84,9 +86,9 @@ pub trait Composite<T: Updateable + Clone + Debug> {
}

async fn option_vec_observe_fn(
value: Option<Vec<Arc<RwLock<T>>>>,
value: Option<Vec<Shared<T>>>,
gateway: &GatewayHandle,
) -> Option<Vec<Arc<RwLock<T>>>>
) -> Option<Vec<Shared<T>>>
where
T: Composite<T>,
{
Expand All @@ -101,17 +103,14 @@ pub trait Composite<T: Updateable + Clone + Debug> {
}
}

async fn value_observe_fn(value: Arc<RwLock<T>>, gateway: &GatewayHandle) -> Arc<RwLock<T>>
async fn value_observe_fn(value: Shared<T>, gateway: &GatewayHandle) -> Shared<T>
where
T: Composite<T>,
{
gateway.observe(value).await
}

async fn vec_observe_fn(
value: Vec<Arc<RwLock<T>>>,
gateway: &GatewayHandle,
) -> Vec<Arc<RwLock<T>>>
async fn vec_observe_fn(value: Vec<Shared<T>>, gateway: &GatewayHandle) -> Vec<Shared<T>>
where
T: Composite<T>,
{
Expand All @@ -122,3 +121,19 @@ pub trait Composite<T: Updateable + Clone + Debug> {
vec
}
}

pub trait IntoShared {
/// Uses [`Shared`] to provide an ergonomic alternative to `Arc::new(RwLock::new(obj))`.
///
/// [`Shared<Self>`] can then be observed using the [`Gateway`], turning the underlying
/// `dyn Composite<Self>` into a self-updating struct, which is a tracked variant of a chorus
/// entity struct, updating its' held information when new information concerning itself arrives
/// over the [`Gateway`] connection, reducing the need for expensive network-API calls.
fn into_shared(self) -> Shared<Self>;
}

impl<T: Sized> IntoShared for T {
fn into_shared(self) -> Shared<Self> {
Arc::new(RwLock::new(self))
}
}
17 changes: 8 additions & 9 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::sync::{Arc, RwLock};

use chorus::gateway::Gateway;
use chorus::gateway::{Gateway, Shared};
use chorus::types::IntoShared;
use chorus::{
instance::{ChorusUser, Instance},
types::{
Expand All @@ -16,9 +15,9 @@ pub(crate) struct TestBundle {
pub urls: UrlBundle,
pub user: ChorusUser,
pub instance: Instance,
pub guild: Arc<RwLock<Guild>>,
pub role: Arc<RwLock<RoleObject>>,
pub channel: Arc<RwLock<Channel>>,
pub guild: Shared<Guild>,
pub role: Shared<RoleObject>,
pub channel: Shared<Channel>,
}

#[allow(unused)]
Expand Down Expand Up @@ -119,9 +118,9 @@ pub(crate) async fn setup() -> TestBundle {
urls,
user,
instance,
guild: Arc::new(RwLock::new(guild)),
role: Arc::new(RwLock::new(role)),
channel: Arc::new(RwLock::new(channel)),
guild: guild.into_shared(),
role: role.into_shared(),
channel: channel.into_shared(),
}
}

Expand Down
8 changes: 3 additions & 5 deletions tests/gateway.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
mod common;

use std::sync::{Arc, RwLock};

use chorus::errors::GatewayError;
use chorus::gateway::*;
use chorus::types::{self, ChannelModifySchema, RoleCreateModifySchema, RoleObject};
use chorus::types::{self, ChannelModifySchema, IntoShared, RoleCreateModifySchema, RoleObject};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")]
Expand Down Expand Up @@ -100,7 +98,7 @@ async fn test_recursive_self_updating_structs() {
bundle
.user
.gateway
.observe(Arc::new(RwLock::new(role.clone())))
.observe(role.clone().into_shared())
.await;
// Update Guild and check for Guild
let inner_guild = guild.read().unwrap().clone();
Expand All @@ -113,7 +111,7 @@ async fn test_recursive_self_updating_structs() {
let role_inner = bundle
.user
.gateway
.observe_and_into_inner(Arc::new(RwLock::new(role.clone())))
.observe_and_into_inner(role.clone().into_shared())
.await;
assert_eq!(role_inner.name, "yippieee");
// Check if the change propagated
Expand Down

0 comments on commit ed3786c

Please sign in to comment.