From 9fe3cb3bdace8054ee31d78a4c6648f2846f2d66 Mon Sep 17 00:00:00 2001 From: alemi Date: Wed, 1 May 2024 16:06:46 +0200 Subject: [PATCH] feat(web): use reference counting rather than clones before, cache would clone the underlying object every time it was accessed. this was definitely incredibly wasteful! now it clones the Arc, which is super cheap. the extra work necessary while fetching things is most likely more than compensated by saved clones every time we render stuff. there is some dumb arc creation here and there (like for the debug page), but it's mostly my lazyness, they can probably go and should not be that much of a big deal anyway. TLDR things were already fast but should be EVEN FASTER!!! --- web/src/components/activity.rs | 5 +++-- web/src/components/object.rs | 6 ++++-- web/src/components/timeline.rs | 12 ++++++------ web/src/components/user.rs | 6 +++--- web/src/lib.rs | 14 ++++++++------ web/src/page.rs | 16 ++++++++++------ 6 files changed, 34 insertions(+), 25 deletions(-) diff --git a/web/src/components/activity.rs b/web/src/components/activity.rs index 8821c716..0f9da67d 100644 --- a/web/src/components/activity.rs +++ b/web/src/components/activity.rs @@ -1,3 +1,4 @@ +use std::sync::Arc; use leptos::*; use crate::prelude::*; @@ -6,10 +7,10 @@ use apb::{target::Addressed, Activity, Actor, Base, Object}; #[component] -pub fn ActivityLine(activity: serde_json::Value) -> impl IntoView { +pub fn ActivityLine(activity: crate::Object) -> impl IntoView { let object_id = activity.object().id().unwrap_or_default(); let actor_id = activity.actor().id().unwrap_or_default(); - let actor = CACHE.get_or(&actor_id, serde_json::Value::String(actor_id.clone())); + let actor = CACHE.get_or(&actor_id, serde_json::Value::String(actor_id.clone()).into()); let avatar = actor.icon().get().map(|x| x.url().id().unwrap_or_default()).unwrap_or_default(); let username = actor.preferred_username().unwrap_or_default().to_string(); let domain = actor.id().unwrap_or_default().replace("https://", "").split('/').next().unwrap_or_default().to_string(); diff --git a/web/src/components/object.rs b/web/src/components/object.rs index e2545330..3a6bb390 100644 --- a/web/src/components/object.rs +++ b/web/src/components/object.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use leptos::*; use crate::{prelude::*, URL_SENSITIVE}; @@ -77,11 +79,11 @@ pub fn Attachment( #[component] -pub fn Object(object: serde_json::Value) -> impl IntoView { +pub fn Object(object: crate::Object) -> impl IntoView { let oid = object.id().unwrap_or_default().to_string(); let content = dissolve::strip_html_tags(object.content().unwrap_or_default()); let author_id = object.attributed_to().id().unwrap_or_default(); - let author = CACHE.get_or(&author_id, serde_json::Value::String(author_id.clone())); + let author = CACHE.get_or(&author_id, serde_json::Value::String(author_id.clone()).into()); let sensitive = object.sensitive().unwrap_or_default(); let addressed = object.addressed(); let public = addressed.iter().any(|x| x.as_str() == apb::target::PUBLIC); diff --git a/web/src/components/timeline.rs b/web/src/components/timeline.rs index a934cc82..9bfee7ea 100644 --- a/web/src/components/timeline.rs +++ b/web/src/components/timeline.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeSet, pin::Pin}; +use std::{collections::BTreeSet, pin::Pin, sync::Arc}; use apb::{Activity, Base, Object}; use leptos::*; @@ -78,13 +78,13 @@ pub fn TimelineRepliesRecursive(tl: Timeline, root: String) -> impl IntoView { Some(apb::ObjectType::Activity(_)) => x.object().id().map(|o| o == root).unwrap_or(false), _ => x.in_reply_to().id().map(|r| r == root).unwrap_or(false), }) - .collect::>(); + .collect::>(); view! { { let oid = object.object().id().unwrap_or_default().to_string(); @@ -220,7 +220,7 @@ async fn process_activities( actors_seen.insert(attributed_to); } if let Some(object_uri) = object.id() { - CACHE.put(object_uri.to_string(), object.clone()); + CACHE.put(object_uri.to_string(), Arc::new(object.clone())); } else { tracing::warn!("embedded object without id: {object:?}"); } @@ -239,7 +239,7 @@ async fn process_activities( out.push(activity_id.to_string()); CACHE.put( activity_id.to_string(), - activity.clone().set_object(apb::Node::maybe_link(object_id)) + Arc::new(activity.clone().set_object(apb::Node::maybe_link(object_id))) ); } else if let Some(object_id) = activity.object().id() { out.push(object_id); @@ -271,7 +271,7 @@ async fn process_activities( async fn fetch_and_update(kind: FetchKind, id: String, auth: Signal>) { match Http::fetch(&Uri::api(kind, &id, false), auth).await { - Ok(data) => CACHE.put(id, data), + Ok(data) => CACHE.put(id, Arc::new(data)), Err(e) => console_warn(&format!("could not fetch '{id}': {e}")), } } diff --git a/web/src/components/user.rs b/web/src/components/user.rs index 60fe8997..57916566 100644 --- a/web/src/components/user.rs +++ b/web/src/components/user.rs @@ -5,10 +5,10 @@ use apb::{Actor, Base, Object}; #[component] -pub fn ActorBanner(object: serde_json::Value) -> impl IntoView { - match object { +pub fn ActorBanner(object: crate::Object) -> impl IntoView { + match object.as_ref() { serde_json::Value::String(id) => view! { - + }, serde_json::Value::Object(_) => { let uid = object.id().unwrap_or_default().to_string(); diff --git a/web/src/lib.rs b/web/src/lib.rs index f2161dd7..22f92651 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -21,23 +21,25 @@ lazy_static::lazy_static! { pub static ref CACHE: ObjectCache = ObjectCache::default(); } +pub type Object = Arc; + #[derive(Debug, Clone, Default)] -pub struct ObjectCache(pub Arc>); +pub struct ObjectCache(pub Arc>); impl ObjectCache { - pub fn get(&self, k: &str) -> Option { + pub fn get(&self, k: &str) -> Option { self.0.get(k).map(|x| x.clone()) } - pub fn get_or(&self, k: &str, or: serde_json::Value) -> serde_json::Value { + pub fn get_or(&self, k: &str, or: Object) -> Object { self.get(k).unwrap_or(or) } - pub fn put(&self, k: String, v: serde_json::Value) { + pub fn put(&self, k: String, v: Object) { self.0.insert(k, v); } - pub async fn fetch(&self, k: &str, kind: FetchKind) -> reqwest::Result { + pub async fn fetch(&self, k: &str, kind: FetchKind) -> reqwest::Result { match self.get(k) { Some(x) => Ok(x), None => { @@ -45,7 +47,7 @@ impl ObjectCache { .await? .json::() .await?; - self.put(k.to_string(), obj); + self.put(k.to_string(), Arc::new(obj)); Ok(self.get(k).expect("not found in cache after insertion")) } } diff --git a/web/src/page.rs b/web/src/page.rs index 03940f55..6d8e4fb9 100644 --- a/web/src/page.rs +++ b/web/src/page.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use apb::{Actor, Base, Collection, Object}; use leptos::*; @@ -51,6 +53,7 @@ pub fn UserPage(tl: Timeline) -> impl IntoView { Some(x) => Some(x.clone()), None => { let user : serde_json::Value = Http::fetch(&Uri::api(FetchKind::User, &id, true), auth).await.ok()?; + let user = Arc::new(user); CACHE.put(Uri::full(FetchKind::User, &id), user.clone()); Some(user) }, @@ -163,6 +166,7 @@ pub fn ObjectPage(tl: Timeline) -> impl IntoView { Some(x) => Some(x.clone()), None => { let obj = Http::fetch::(&Uri::api(FetchKind::Object, &oid, true), auth).await.ok()?; + let obj = Arc::new(obj); CACHE.put(Uri::full(FetchKind::Object, &oid), obj.clone()); Some(obj) } @@ -225,9 +229,9 @@ pub fn TimelinePage(name: &'static str, tl: Timeline) -> impl IntoView { #[component] pub fn DebugPage() -> impl IntoView { - let (object, set_object) = create_signal(serde_json::Value::String( + let (object, set_object) = create_signal(Arc::new(serde_json::Value::String( "use this view to fetch remote AP objects and inspect their content".into()) - ); + )); let cached_ref: NodeRef = create_node_ref(); let auth = use_context::().expect("missing auth context"); let (query, set_query) = create_signal("".to_string()); @@ -242,14 +246,14 @@ pub fn DebugPage() -> impl IntoView { if cached { match CACHE.get(&fetch_url) { Some(x) => set_object.set(x), - None => set_object.set(serde_json::Value::String("not in cache!".into())), + None => set_object.set(Arc::new(serde_json::Value::String("not in cache!".into()))), } } else { let url = format!("{URL_BASE}/dbg?id={fetch_url}"); spawn_local(async move { match Http::fetch::(&url, auth).await { - Ok(x) => set_object.set(x), - Err(e) => set_object.set(serde_json::Value::String(e.to_string())), + Ok(x) => set_object.set(Arc::new(x)), + Err(e) => set_object.set(Arc::new(serde_json::Value::String(e.to_string()))), } }); } @@ -273,7 +277,7 @@ pub fn DebugPage() -> impl IntoView {
-				{move || serde_json::to_string_pretty(&object.get()).unwrap_or("unserializable".to_string())}
+				{move || serde_json::to_string_pretty(object.get().as_ref()).unwrap_or("unserializable".to_string())}
 			
}