Compare commits

...

12 Commits

Author SHA1 Message Date
Flori fe8106d2a1
"Self updating structs" API improvements (#467)
This PR slightly improves the ergonomics of working with self-updating
structs, by making the changes as documented in #466.
2024-01-22 15:19:24 +01:00
bitfl0wer 21699e5899
Loosen bounds on IntoShared<T> 2024-01-22 15:00:46 +01:00
bitfl0wer 5372d2c475
Make IntoShared trait with blanket implementation 2024-01-22 14:56:23 +01:00
bitfl0wer 29f3ee802a
Fix errors by moving into_shared out of Composite 2024-01-22 14:50:33 +01:00
bitfl0wer 6637f14b18
Replace Arc, Rwlock with Shared 2024-01-21 20:24:17 +01:00
bitfl0wer a571a9e137
Write documentation for observe 2024-01-21 20:13:00 +01:00
bitfl0wer 57214fd2fe
Add documentation for into_shared 2024-01-21 17:15:11 +01:00
bitfl0wer ca58767372
Rename to_shared to into_shared 2024-01-21 17:10:24 +01:00
bitfl0wer 2a7cae30b8
Define public method `to_shared` for dyn Composite 2024-01-21 17:07:54 +01:00
bitfl0wer 0660e25bdb
rustfmt 2024-01-21 17:07:30 +01:00
bitfl0wer 36ac6c1e5e
Replace use of Arc<RwLock<T>> with Shared<T> 2024-01-21 17:07:19 +01:00
bitfl0wer 315fe8e33b
Define type alias `Shared` 2024-01-21 17:06:43 +01:00
7 changed files with 58 additions and 29 deletions

View File

@ -40,10 +40,19 @@ impl GatewayHandle {
.unwrap(); .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>>( pub async fn observe<T: Updateable + Clone + Debug + Composite<T>>(
&self, &self,
object: Arc<RwLock<T>>, object: Shared<T>,
) -> Arc<RwLock<T>> { ) -> Shared<T> {
let mut store = self.store.lock().await; let mut store = self.store.lock().await;
let id = object.read().unwrap().id(); let id = object.read().unwrap().id();
if let Some(channel) = store.get(&id) { if let Some(channel) = store.get(&id) {
@ -84,7 +93,7 @@ impl GatewayHandle {
/// with all of its observable fields being observed. /// with all of its observable fields being observed.
pub async fn observe_and_into_inner<T: Updateable + Clone + Debug + Composite<T>>( pub async fn observe_and_into_inner<T: Updateable + Clone + Debug + Composite<T>>(
&self, &self,
object: Arc<RwLock<T>>, object: Shared<T>,
) -> T { ) -> T {
let channel = self.observe(object.clone()).await; let channel = self.observe(object.clone()).await;
let object = channel.read().unwrap().clone(); let object = channel.read().unwrap().clone();

View File

@ -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>>;

View File

@ -23,6 +23,8 @@ pub use user_settings::*;
pub use voice_state::*; pub use voice_state::*;
pub use webhook::*; pub use webhook::*;
use crate::gateway::Shared;
#[cfg(feature = "client")] #[cfg(feature = "client")]
use crate::gateway::Updateable; use crate::gateway::Updateable;
@ -69,9 +71,9 @@ pub trait Composite<T: Updateable + Clone + Debug> {
async fn watch_whole(self, gateway: &GatewayHandle) -> Self; async fn watch_whole(self, gateway: &GatewayHandle) -> Self;
async fn option_observe_fn( async fn option_observe_fn(
value: Option<Arc<RwLock<T>>>, value: Option<Shared<T>>,
gateway: &GatewayHandle, gateway: &GatewayHandle,
) -> Option<Arc<RwLock<T>>> ) -> Option<Shared<T>>
where where
T: Composite<T> + Debug, T: Composite<T> + Debug,
{ {
@ -84,9 +86,9 @@ pub trait Composite<T: Updateable + Clone + Debug> {
} }
async fn option_vec_observe_fn( async fn option_vec_observe_fn(
value: Option<Vec<Arc<RwLock<T>>>>, value: Option<Vec<Shared<T>>>,
gateway: &GatewayHandle, gateway: &GatewayHandle,
) -> Option<Vec<Arc<RwLock<T>>>> ) -> Option<Vec<Shared<T>>>
where where
T: Composite<T>, T: Composite<T>,
{ {
@ -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 where
T: Composite<T>, T: Composite<T>,
{ {
gateway.observe(value).await gateway.observe(value).await
} }
async fn vec_observe_fn( async fn vec_observe_fn(value: Vec<Shared<T>>, gateway: &GatewayHandle) -> Vec<Shared<T>>
value: Vec<Arc<RwLock<T>>>,
gateway: &GatewayHandle,
) -> Vec<Arc<RwLock<T>>>
where where
T: Composite<T>, T: Composite<T>,
{ {
@ -122,3 +121,19 @@ pub trait Composite<T: Updateable + Clone + Debug> {
vec 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))
}
}

View File

@ -1,6 +1,5 @@
use std::sync::{Arc, RwLock}; use chorus::gateway::{Gateway, Shared};
use chorus::types::IntoShared;
use chorus::gateway::Gateway;
use chorus::{ use chorus::{
instance::{ChorusUser, Instance}, instance::{ChorusUser, Instance},
types::{ types::{
@ -16,9 +15,9 @@ pub(crate) struct TestBundle {
pub urls: UrlBundle, pub urls: UrlBundle,
pub user: ChorusUser, pub user: ChorusUser,
pub instance: Instance, pub instance: Instance,
pub guild: Arc<RwLock<Guild>>, pub guild: Shared<Guild>,
pub role: Arc<RwLock<RoleObject>>, pub role: Shared<RoleObject>,
pub channel: Arc<RwLock<Channel>>, pub channel: Shared<Channel>,
} }
#[allow(unused)] #[allow(unused)]
@ -119,9 +118,9 @@ pub(crate) async fn setup() -> TestBundle {
urls, urls,
user, user,
instance, instance,
guild: Arc::new(RwLock::new(guild)), guild: guild.into_shared(),
role: Arc::new(RwLock::new(role)), role: role.into_shared(),
channel: Arc::new(RwLock::new(channel)), channel: channel.into_shared(),
} }
} }

View File

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