diff --git a/glib-macros/src/closure.rs b/glib-macros/src/closure.rs index 8e8b0f3afa4d..549ed0e1d8e1 100644 --- a/glib-macros/src/closure.rs +++ b/glib-macros/src/closure.rs @@ -32,7 +32,10 @@ impl Capture { let alias = self.alias(); let name = &self.name; match self.kind { - CaptureKind::Watch | CaptureKind::WeakAllowNone => quote! { + CaptureKind::Watch => quote! { + let #alias = #crate_ident::object::Watchable::watched_object(&#name); + }, + CaptureKind::WeakAllowNone => quote! { let #alias = #crate_ident::clone::Downgrade::downgrade(&#name); }, CaptureKind::Strong => quote! { @@ -45,7 +48,7 @@ impl Capture { let name = &self.name; match self.kind { CaptureKind::Watch => quote! { - #crate_ident::object::Object::watch_closure(&#name, &#closure_ident); + #crate_ident::object::Watchable::watch_closure(&#name, &#closure_ident); }, _ => Default::default(), } @@ -55,12 +58,9 @@ impl Capture { let alias = self.alias(); match self.kind { CaptureKind::Watch => { - let err_msg = format!("failed to upgrade `{}`", alias); quote! { - let #alias = match #crate_ident::clone::Upgrade::upgrade(&#alias) { - Some(val) => val, - None => panic!(#err_msg) - }; + let #alias = unsafe { #alias.borrow() }; + let #alias = ::core::convert::AsRef::as_ref(&#alias); } } CaptureKind::WeakAllowNone => quote! { diff --git a/glib-macros/tests/test.rs b/glib-macros/tests/test.rs index 68b5ebd461e0..d4ed759c1b87 100644 --- a/glib-macros/tests/test.rs +++ b/glib-macros/tests/test.rs @@ -359,7 +359,7 @@ fn closure() { assert_eq!(obj.ref_count(), 1); let weak_test = glib::closure_local!(@watch obj => move || obj.ref_count()); assert_eq!(obj.ref_count(), 1); - assert_eq!(weak_test.invoke::(&[]), 3); + assert_eq!(weak_test.invoke::(&[]), 2); assert_eq!(obj.ref_count(), 1); weak_test @@ -379,7 +379,7 @@ fn closure() { } let obj = glib::Object::new::(&[]).unwrap(); - assert_eq!(obj.ref_count_in_closure(), 3); + assert_eq!(obj.ref_count_in_closure(), 2); } { @@ -398,7 +398,7 @@ fn closure() { let a = A { obj: glib::Object::new::(&[]).unwrap(), }; - assert_eq!(a.ref_count_in_closure(), 3); + assert_eq!(a.ref_count_in_closure(), 2); } let strong_test = { @@ -450,4 +450,46 @@ fn closure() { }); assert_eq!(struct_test.invoke::(&[]), 2); } + + { + use glib::prelude::*; + use glib::subclass::prelude::*; + + #[derive(Default)] + pub struct FooPrivate {} + + #[glib::object_subclass] + impl ObjectSubclass for FooPrivate { + const NAME: &'static str = "MyFoo2"; + type Type = Foo; + } + + impl ObjectImpl for FooPrivate {} + + glib::wrapper! { + pub struct Foo(ObjectSubclass); + } + + impl Foo { + fn my_ref_count(&self) -> u32 { + self.ref_count() + } + } + + let cast_test = { + let foo = glib::Object::new::(&[]).unwrap(); + + assert_eq!(foo.my_ref_count(), 1); + let cast_test = glib::closure_local!(@watch foo => move || foo.my_ref_count()); + assert_eq!(foo.my_ref_count(), 1); + assert_eq!(cast_test.invoke::(&[]), 2); + assert_eq!(foo.my_ref_count(), 1); + + let foo_ref = &foo; + let _ = glib::closure_local!(@watch foo_ref => move || foo_ref.my_ref_count()); + + cast_test + }; + cast_test.invoke::<()>(&[]); + } } diff --git a/glib/src/object.rs b/glib/src/object.rs index 9fe3145dd82b..2e621213a680 100644 --- a/glib/src/object.rs +++ b/glib/src/object.rs @@ -3168,6 +3168,53 @@ impl ObjectExt for T { } } +// Helper struct to avoid creating an extra ref on objects inside closure watches. This is safe +// because `watch_closure` ensures the object has a ref when the closure is called. +#[doc(hidden)] +pub struct WatchedObject(ptr::NonNull); + +#[doc(hidden)] +unsafe impl Send for WatchedObject {} + +#[doc(hidden)] +impl WatchedObject { + pub fn new(obj: &T) -> Self { + Self(unsafe { ptr::NonNull::new_unchecked(obj.as_ptr()) }) + } + pub unsafe fn borrow(&self) -> Borrowed + where + T: FromGlibPtrBorrow<*mut ::GlibType>, + { + from_glib_borrow(self.0.as_ptr()) + } +} + +#[doc(hidden)] +pub trait Watchable { + fn watched_object(&self) -> WatchedObject; + fn watch_closure(&self, closure: &impl AsRef); +} + +#[doc(hidden)] +impl Watchable for T { + fn watched_object(&self) -> WatchedObject { + WatchedObject::new(self) + } + fn watch_closure(&self, closure: &impl AsRef) { + ObjectExt::watch_closure(self, closure) + } +} + +#[doc(hidden)] +impl Watchable for &T { + fn watched_object(&self) -> WatchedObject { + WatchedObject::new(*self) + } + fn watch_closure(&self, closure: &impl AsRef) { + ObjectExt::watch_closure(*self, closure) + } +} + // Validate that the given property value has an acceptable type for the given property pspec // and if necessary update the value fn validate_property_type(