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!!!
This commit is contained in:
parent
e8b36cecdf
commit
9fe3cb3bda
6 changed files with 34 additions and 25 deletions
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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::<Vec<serde_json::Value>>();
|
||||
.collect::<Vec<crate::Object>>();
|
||||
|
||||
view! {
|
||||
<For
|
||||
each=root_values
|
||||
key=|k| k.id().unwrap_or_default().to_string()
|
||||
children=move |object: serde_json::Value| {
|
||||
children=move |object: crate::Object| {
|
||||
match object.object_type() {
|
||||
Some(apb::ObjectType::Activity(apb::ActivityType::Create)) => {
|
||||
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<Option<String>>) {
|
||||
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}")),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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! {
|
||||
<div><b>?</b>" "<a class="clean hover" href={Uri::web(FetchKind::User, &id)}>{Uri::pretty(&id)}</a></div>
|
||||
<div><b>?</b>" "<a class="clean hover" href={Uri::web(FetchKind::User, id)}>{Uri::pretty(id)}</a></div>
|
||||
},
|
||||
serde_json::Value::Object(_) => {
|
||||
let uid = object.id().unwrap_or_default().to_string();
|
||||
|
|
|
@ -21,23 +21,25 @@ lazy_static::lazy_static! {
|
|||
pub static ref CACHE: ObjectCache = ObjectCache::default();
|
||||
}
|
||||
|
||||
pub type Object = Arc<serde_json::Value>;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ObjectCache(pub Arc<dashmap::DashMap<String, serde_json::Value>>);
|
||||
pub struct ObjectCache(pub Arc<dashmap::DashMap<String, Object>>);
|
||||
|
||||
impl ObjectCache {
|
||||
pub fn get(&self, k: &str) -> Option<serde_json::Value> {
|
||||
pub fn get(&self, k: &str) -> Option<Object> {
|
||||
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<serde_json::Value> {
|
||||
pub async fn fetch(&self, k: &str, kind: FetchKind) -> reqwest::Result<Object> {
|
||||
match self.get(k) {
|
||||
Some(x) => Ok(x),
|
||||
None => {
|
||||
|
@ -45,7 +47,7 @@ impl ObjectCache {
|
|||
.await?
|
||||
.json::<serde_json::Value>()
|
||||
.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"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::<serde_json::Value>(&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<html::Input> = create_node_ref();
|
||||
let auth = use_context::<Auth>().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::<serde_json::Value>(&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 {
|
|||
</form>
|
||||
</div>
|
||||
<pre class="ma-1" >
|
||||
{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())}
|
||||
</pre>
|
||||
</div>
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue