"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.
This commit is contained in:
Flori 2024-01-22 15:19:24 +01:00 committed by GitHub
commit fe8106d2a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 58 additions and 29 deletions

View File

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

View File

@ -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) {
@ -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();

View File

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

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 webhook::*;
use crate::gateway::Shared;
#[cfg(feature = "client")]
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 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,
{
@ -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>,
{
@ -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>,
{
@ -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))
}
}

View File

@ -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::{
@ -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)]
@ -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(),
}
}

View File

@ -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")]
@ -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();
@ -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