svg_img: Fix memory leak in SvgImg (#818)

This commit is contained in:
Sunli 2025-04-28 14:42:37 +08:00 committed by GitHub
parent 547c9c1827
commit dc776224f9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 51 additions and 23 deletions

View file

@ -1,5 +1,6 @@
use gpui::{
px, App, AppContext, Entity, FocusHandle, Focusable, ParentElement as _, Render, Styled, Window,
px, App, AppContext, ElementId, Entity, FocusHandle, Focusable, ParentElement as _, Render,
Styled, Window,
};
use gpui_component::{dock::PanelControl, v_flex, SvgImg};
@ -9,7 +10,6 @@ const GOOGLE_LOGO: &str = include_str!("./fixtures/google.svg");
pub struct ImageStory {
focus_handle: gpui::FocusHandle,
google_logo: SvgImg,
}
impl super::Story for ImageStory {
@ -34,7 +34,6 @@ impl ImageStory {
pub fn new(_: &mut Window, cx: &mut App) -> Self {
Self {
focus_handle: cx.focus_handle(),
google_logo: SvgImg::new().source(GOOGLE_LOGO.as_bytes(), px(300.), px(300.)),
}
}
@ -57,11 +56,15 @@ impl Render for ImageStory {
) -> impl gpui::IntoElement {
v_flex().gap_4().size_full().child(
section("SVG Image")
.child(self.google_logo.clone().size(px(100.)).flex_grow())
.child(self.google_logo.clone().size(px(100.)).flex_grow())
.child(self.google_logo.clone().size_80().flex_grow())
.child(self.google_logo.clone().size_12().flex_grow())
.child(self.google_logo.clone().size(px(100.))),
.child(svg_img("logo1").size(px(100.)).flex_grow())
.child(svg_img("logo2").size(px(100.)).flex_grow())
.child(svg_img("logo3").size_80().flex_grow())
.child(svg_img("logo4").size_12().flex_grow())
.child(svg_img("logo5").size(px(100.))),
)
}
}
fn svg_img(id: impl Into<ElementId>) -> SvgImg {
SvgImg::new(id).source(GOOGLE_LOGO.as_bytes(), px(300.), px(300.))
}

View file

@ -5,9 +5,9 @@ use std::{
};
use gpui::{
px, size, App, Asset, Bounds, Element, ElementId, GlobalElementId, Hitbox, ImageCacheError,
InteractiveElement, Interactivity, IntoElement, IsZero, Pixels, RenderImage, SharedString,
Size, StyleRefinement, Styled, Window,
hash, px, size, App, Asset, Bounds, Element, ElementId, GlobalElementId, Hitbox,
ImageCacheError, InteractiveElement, Interactivity, IntoElement, IsZero, Pixels, RenderImage,
SharedString, Size, StyleRefinement, Styled, Window,
};
use image::Frame;
use smallvec::SmallVec;
@ -15,6 +15,9 @@ use smallvec::SmallVec;
use image::ImageBuffer;
const SCALE: f32 = 2.;
struct SvgImgState(Option<(u64, Arc<RenderImage>)>);
static OPTIONS: LazyLock<usvg::Options> = LazyLock::new(|| {
let mut options = usvg::Options::default();
options.fontdb_mut().load_system_fonts();
@ -56,6 +59,7 @@ impl From<&'static str> for SvgSource {
impl Clone for SvgImg {
fn clone(&self) -> Self {
Self {
id: self.id.clone(),
interactivity: Interactivity::default(),
source: self.source.clone(),
size: self.size,
@ -146,6 +150,7 @@ impl Asset for Image {
}
pub struct SvgImg {
id: ElementId,
interactivity: Interactivity,
source: Option<ImageSource>,
size: Size<Pixels>,
@ -155,8 +160,9 @@ impl SvgImg {
/// Create a new svg image element.
///
/// The `src_width` and `src_height` are the original width and height of the svg image.
pub fn new() -> Self {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
interactivity: Interactivity::default(),
source: None,
size: Size::default(),
@ -200,7 +206,7 @@ impl Element for SvgImg {
type PrepaintState = (Option<Hitbox>, Option<Arc<RenderImage>>);
fn id(&self) -> Option<ElementId> {
self.interactivity.element_id.clone()
Some(self.id.clone())
}
fn request_layout(
@ -214,18 +220,37 @@ impl Element for SvgImg {
.request_layout(global_id, window, cx, |style, window, cx| {
window.request_layout(style, None, cx)
});
let global_id = global_id.unwrap();
let source = self.source.clone();
let data = if let Some(source) = source {
match window.use_asset::<Image>(&source, cx) {
Some(Ok(data)) => Some(data),
_ => None,
window.with_element_state::<SvgImgState, _>(global_id, |state, window| {
match (state, &self.source) {
(_, None) => ((layout_id, None), SvgImgState(None)),
(Some(SvgImgState(Some((prev_hash, image)))), Some(source))
if hash(source) == prev_hash =>
{
(
(layout_id, Some(image.clone())),
SvgImgState(Some((prev_hash, image))),
)
}
(state, Some(source)) => {
if let Some(SvgImgState(Some((_, prev_image)))) = state {
// Drop the previous image from the cache
_ = window.drop_image(prev_image);
}
let image = window
.use_asset::<Image>(&source, cx)
.transpose()
.ok()
.flatten();
(
(layout_id, image.clone()),
SvgImgState(image.map(|image| (hash(source), image))),
)
}
}
} else {
None
};
(layout_id, data)
})
}
fn prepaint(