Markdown pictures: an asset leaves the cache with its last holder
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ab9a14c692
commit
8b9820be74
2 files changed
+86
-14
No files matched your search
+41
-8
@@ -1,26 +1,59 @@
|
|||||||
use std::sync::Arc;
|
use std::{
|
||||||
|
any::TypeId,
|
||||||
|
collections::HashMap,
|
||||||
|
hash::{DefaultHasher, Hash, Hasher},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
use gpui::{App, Asset, ElementId, RenderImage, Window};
|
use gpui::{App, Asset, ElementId, Global, RenderImage, Window};
|
||||||
|
|
||||||
/// An element's asset; freed on change or when it leaves.
|
/// Holders of each asset, keyed as gpui's cache keys it.
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Holders(HashMap<(TypeId, u64), usize>);
|
||||||
|
|
||||||
|
impl Global for Holders {}
|
||||||
|
|
||||||
|
fn key<A: Asset>(source: &A::Source) -> (TypeId, u64) {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
source.hash(&mut hasher);
|
||||||
|
(TypeId::of::<A>(), hasher.finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An element's asset; the last holder to leave frees it.
|
||||||
pub(crate) struct Held<A: Asset> {
|
pub(crate) struct Held<A: Asset> {
|
||||||
source: Option<A::Source>,
|
source: Option<A::Source>,
|
||||||
image: Option<Arc<RenderImage>>,
|
image: Option<Arc<RenderImage>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: Asset> Held<A> {
|
impl<A: Asset> Held<A> {
|
||||||
|
fn take(&mut self, source: &A::Source, cx: &mut App) {
|
||||||
|
let holders = &mut cx.default_global::<Holders>().0;
|
||||||
|
*holders.entry(key::<A>(source)).or_default() += 1;
|
||||||
|
self.source = Some(source.clone());
|
||||||
|
}
|
||||||
|
|
||||||
fn free(&mut self, cx: &mut App) {
|
fn free(&mut self, cx: &mut App) {
|
||||||
if let Some(source) = self.source.take() {
|
let Some(source) = self.source.take() else {
|
||||||
cx.remove_asset::<A>(&source);
|
return;
|
||||||
|
};
|
||||||
|
let image = self.image.take();
|
||||||
|
let holders = &mut cx.default_global::<Holders>().0;
|
||||||
|
let key = key::<A>(&source);
|
||||||
|
let count = holders.get_mut(&key).expect("a held asset is counted");
|
||||||
|
*count -= 1;
|
||||||
|
if *count > 0 {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if let Some(image) = self.image.take() {
|
holders.remove(&key);
|
||||||
|
cx.remove_asset::<A>(&source);
|
||||||
|
if let Some(image) = image {
|
||||||
// The window may be drawing; free after it.
|
// The window may be drawing; free after it.
|
||||||
cx.defer(move |cx| cx.drop_image(image, None));
|
cx.defer(move |cx| cx.drop_image(image, None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `source` as `A` for element `id`; the previous one freed.
|
/// `source` as `A` for element `id`; the previous one let go.
|
||||||
pub(crate) fn hold<A: Asset>(
|
pub(crate) fn hold<A: Asset>(
|
||||||
id: impl Into<ElementId>,
|
id: impl Into<ElementId>,
|
||||||
source: &A::Source,
|
source: &A::Source,
|
||||||
@@ -43,7 +76,7 @@ where
|
|||||||
held.update(cx, |held, cx| {
|
held.update(cx, |held, cx| {
|
||||||
if held.source.as_ref() != Some(source) {
|
if held.source.as_ref() != Some(source) {
|
||||||
held.free(cx);
|
held.free(cx);
|
||||||
held.source = Some(source.clone());
|
held.take(source, cx);
|
||||||
}
|
}
|
||||||
if let Some(loaded) = &loaded {
|
if let Some(loaded) = &loaded {
|
||||||
held.image = image(loaded);
|
held.image = image(loaded);
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use gpui::AppContext as _;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn formula(source: &str, display: bool) -> Formula {
|
fn formula(source: &str, display: bool) -> Formula {
|
||||||
@@ -101,6 +103,12 @@ fn settle(cx: &mut gpui::VisualTestContext) {
|
|||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Draws, then lets gpui release what the frame dropped.
|
||||||
|
fn flush(cx: &mut gpui::VisualTestContext) {
|
||||||
|
cx.run_until_parked();
|
||||||
|
cx.update(|_, _| ());
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether `source`'s picture sits in the asset cache.
|
/// Whether `source`'s picture sits in the asset cache.
|
||||||
fn cached(source: &str, cx: &mut gpui::VisualTestContext) -> bool {
|
fn cached(source: &str, cx: &mut gpui::VisualTestContext) -> bool {
|
||||||
cx.update(|window, cx| {
|
cx.update(|window, cx| {
|
||||||
@@ -128,13 +136,44 @@ fn a_formula_frees_its_picture_when_it_changes_or_leaves(cx: &mut gpui::TestAppC
|
|||||||
assert!(!cached("b^2", cx), "gone with its element");
|
assert!(!cached("b^2", cx), "gone with its element");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One formula in a view of its own.
|
||||||
|
struct One;
|
||||||
|
|
||||||
|
impl gpui::Render for One {
|
||||||
|
fn render(&mut self, _: &mut Window, _: &mut gpui::Context<Self>) -> impl IntoElement {
|
||||||
|
Latex::parse("one", "c^2").expect("parses")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Views of one formula, each drawn from gpui's view cache.
|
||||||
|
struct Pair(Vec<gpui::Entity<One>>);
|
||||||
|
|
||||||
|
impl gpui::Render for Pair {
|
||||||
|
fn render(&mut self, _: &mut Window, _: &mut gpui::Context<Self>) -> impl IntoElement {
|
||||||
|
div().children(
|
||||||
|
self.0.iter().map(|one| {
|
||||||
|
gpui::AnyView::from(one.clone()).cached(gpui::StyleRefinement::default())
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn a_shared_formula_draws_on_after_one_user_leaves(cx: &mut gpui::TestAppContext) {
|
fn a_shared_formula_stays_until_its_last_holder_leaves(cx: &mut gpui::TestAppContext) {
|
||||||
let (view, cx) = open(vec!["c^2", "c^2"], cx);
|
cx.update(crate::theme::Theme::init);
|
||||||
view.update(cx, |view, cx| {
|
let (pair, cx) = cx.add_window_view(|_, cx| Pair(vec![cx.new(|_| One), cx.new(|_| One)]));
|
||||||
view.0.pop();
|
settle(cx);
|
||||||
|
assert!(cached("c^2", cx), "drawn");
|
||||||
|
pair.update(cx, |pair, cx| {
|
||||||
|
pair.0.pop();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
settle(cx);
|
flush(cx);
|
||||||
assert!(cached("c^2", cx), "the one left loads it again");
|
assert!(cached("c^2", cx), "the cached survivor still holds it");
|
||||||
|
pair.update(cx, |pair, cx| {
|
||||||
|
pair.0.clear();
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
flush(cx);
|
||||||
|
assert!(!cached("c^2", cx), "the last holder freed it");
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user