diff --git a/glib-macros/src/lib.rs b/glib-macros/src/lib.rs index c8f6f0e741ea..bf2887c83c0f 100644 --- a/glib-macros/src/lib.rs +++ b/glib-macros/src/lib.rs @@ -645,6 +645,59 @@ pub fn object_subclass(_attr: TokenStream, item: TokenStream) -> TokenStream { } } +/// Macro for boilerplate of [`ObjectSubclass`] implementations specific to +/// module types. +/// +/// A module type must be explicitly registered when a module is loaded (see +/// [`TypeModule`]). +/// Therefore, unlike for non module types a module type can be registered +/// several times. +/// +/// A module type is never unregistered but is marked as unloaded by the glib +/// type system when the module is unloaded (see [`TypeModuleExt::unuse`]). +/// Therefore, a module type marked as unloaded by the glib type system must be +/// registered again, when the module is reloaded. +/// +/// This macro provides two behaviors when registering a module type: +/// +/// By default the module type is registered when the module is loaded, +/// ```ignore +/// #[glib::module_object_subclass] +/// impl ObjectSubclass for MyType { ... } +/// ``` +/// +/// Optionally by setting the macro attribute to `lazy_registration = true` to +/// postpone the registration of the module type on first use (when `type_()` +/// is called for the first time), similarly to the [`macro@object_subclass`] +/// macro. +/// ```ignore +/// #[glib::module_object_subclass(lazy_registration = true)] +/// impl ObjectSubclass for MyType { ... } +/// ``` +/// +/// [`ObjectSubclass`]: ../glib/subclass/types/trait.ObjectSubclass.html +/// [`TypeModule`]: ../glib/gobject/auto/type_module/struct.TypeModule.html +/// [`TypeModuleExt::unuse`]: ../glib/gobject/auto/type_module/trait.TypeModuleExt.html#method.unuse +#[proc_macro_attribute] +#[proc_macro_error] +pub fn module_object_subclass(attr: TokenStream, item: TokenStream) -> TokenStream { + use proc_macro_error::abort_call_site; + let attr = if attr.is_empty() { + None + } else { + match syn::parse::(attr) { + Ok(expr) => Some(expr), + Err(_) => abort_call_site!(object_subclass_attribute::WRONG_EXPRESSION_MSG), + } + }; + match syn::parse::(item) { + Ok(input) => { + object_subclass_attribute::impl_module_object_subclass(attr.as_ref(), &input).into() + } + Err(_) => abort_call_site!(object_subclass_attribute::WRONG_PLACE_MSG), + } +} + /// Macro for boilerplate of [`ObjectInterface`] implementations. /// /// This adds implementations for the `get_type()` method, which should probably never be defined @@ -670,6 +723,60 @@ pub fn object_interface(_attr: TokenStream, item: TokenStream) -> TokenStream { } } +/// Macro for boilerplate of [`ObjectInterface`] implementations specific to +/// module interfaces. +/// +/// A module interface must be explicitly registered when a module is loaded +/// (see [`TypeModule`]). +/// +/// A module interface is never unregistered but is marked as unloaded when the +/// module is unloaded (see [`TypeModuleExt::unuse`]). Therefore, a module +/// interface marked as unloaded must be registered again, when the module is +/// reloaded. +/// +/// Calling `type_()` will panic if the module is unloaded (the module +/// interface cannot be used). +/// +/// This macro provides two behaviors when registering a module interface: +/// +/// By default the module interface is registered when the module is loaded, +/// ```ignore +/// #[glib::module_object_interface] +/// unsafe impl ObjectInterface for MyInterface { ... } +/// ``` +/// +/// Optionally by setting the macro attribute to `lazy_registration = true` to +/// postpone the registration of the module interface on first use (when +/// `type_()` is called for the first time), similarly to the +/// [`macro@object_interface`] macro. +/// ```ignore +/// #[glib::module_object_interface(lazy_registration = true)] +/// unsafe impl ObjectInterface for MyInterface { ... } +/// ``` +/// +/// [`ObjectInterface`]: ../glib/subclass/interface/trait.ObjectInterface.html +/// [`TypeModule`]: ../glib/gobject/auto/type_module/struct.TypeModule.html +/// [`TypeModuleExt::unuse`]: ../glib/gobject/auto/type_module/trait.TypeModuleExt.html#method.unuse +#[proc_macro_attribute] +#[proc_macro_error] +pub fn module_object_interface(attr: TokenStream, item: TokenStream) -> TokenStream { + use proc_macro_error::abort_call_site; + let attr = if attr.is_empty() { + None + } else { + match syn::parse::(attr) { + Ok(expr) => Some(expr), + Err(_) => abort_call_site!(object_interface_attribute::WRONG_EXPRESSION_MSG), + } + }; + match syn::parse::(item) { + Ok(input) => { + object_interface_attribute::impl_module_object_interface(attr.as_ref(), &input).into() + } + Err(_) => abort_call_site!(object_interface_attribute::WRONG_PLACE_MSG), + } +} + /// Macro for deriving implementations of [`glib::clone::Downgrade`] and /// [`glib::clone::Upgrade`] traits and a weak type. /// diff --git a/glib-macros/src/object_interface_attribute.rs b/glib-macros/src/object_interface_attribute.rs index abec274d4097..ec6c4cf34355 100644 --- a/glib-macros/src/object_interface_attribute.rs +++ b/glib-macros/src/object_interface_attribute.rs @@ -4,10 +4,242 @@ use proc_macro2::TokenStream; use proc_macro_error::abort_call_site; use quote::quote; +pub const WRONG_EXPRESSION_MSG: &str = "This macro's attribute should be a valid expression"; + +pub const UNSUPPORTED_EXPRESSION_MSG: &str = + "This macro's attribute should be 'lazy_registration = true|false' expression"; + pub const WRONG_PLACE_MSG: &str = "This macro should be used on `impl` block for `glib::ObjectInterface` trait"; pub fn impl_object_interface(input: &syn::ItemImpl) -> TokenStream { + let crate_ident = crate::utils::crate_ident_new(); + let syn::ItemImpl { self_ty, .. } = &input; + + // register the interface on first use (lazy registration) + let register_interface = quote! { + impl #self_ty { + /// Registers the interface only once. + #[inline] + fn register_interface() -> #crate_ident::Type { + static ONCE: ::std::sync::Once = ::std::sync::Once::new(); + static mut TYPE: #crate_ident::Type = #crate_ident::Type::INVALID; + + ONCE.call_once(|| unsafe { + TYPE = #crate_ident::subclass::register_interface::(); + }); + + unsafe { + TYPE + } + } + } + }; + + impl_object_interface_(register_interface, input) +} + +pub fn impl_module_object_interface( + attr: Option<&syn::Expr>, + input: &syn::ItemImpl, +) -> TokenStream { + let crate_ident = crate::utils::crate_ident_new(); + let syn::ItemImpl { self_ty, .. } = &input; + + // attribute must be `None` (immediate registration) or must be an assign + // expression: `lazy_registration = true|false` + let lazy_registration = match attr { + None => false, + Some(syn::Expr::Assign(syn::ExprAssign { left, right, .. })) => { + match (*left.to_owned(), *right.to_owned()) { + ( + syn::Expr::Path(syn::ExprPath { path, .. }), + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Bool(syn::LitBool { value, .. }), + .. + }), + ) if path.is_ident(&"lazy_registration") => value, + _ => abort_call_site!(UNSUPPORTED_EXPRESSION_MSG), + } + } + _ => abort_call_site!(UNSUPPORTED_EXPRESSION_MSG), + }; + + // The following implementations follow the lifecycle of modules and their + // types (see [`TypeModuleExt::unuse`]). + // A module interface can be reregistered (see [`TypeModuleExt::register_type`]). + let register_interface = if lazy_registration { + // Register the module interface on first use (lazy registration). The module + // pointer must be stored to be used later on first use of the module interface. + // This implementation relies on a static storage of the module pointer. The + // pointer value is interpreted to know if the module has been loaded and if + // the interface has been registered: + // - null pointer means that the module has never been loaded, + // - valid pointer means that the module has been loaded but that the interface + // has not been registered yet, + // - dangling pointer means that the module has been loaded and that the + // interface has been registered. + quote! { + impl #self_ty { + /// Returns a mutable reference to the glib type. + /// This is safe because the mutable reference guarantees that no other threads + /// are concurrently accessing the atomic data. + #[inline] + fn get_type_mut() -> &'static mut #crate_ident::ffi::GType { + static mut TYPE: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::AtomicUsize::new(#crate_ident::gobject_ffi::G_TYPE_INVALID); + unsafe { TYPE.get_mut() } + } + + /// Returns a mutable reference to the pointer of the module associated to this + /// interface. This is safe because the mutable reference guarantees that no + /// other threads are concurrently accessing the atomic data. + #[inline] + fn get_type_module_ptr_mut() -> &'static mut *mut #crate_ident::gobject_ffi::GTypeModule { + static mut TYPE_MODULE_PTR: ::std::sync::atomic::AtomicPtr<#crate_ident::gobject_ffi::GTypeModule> = ::std::sync::atomic::AtomicPtr::new(::std::ptr::null_mut()); + unsafe { TYPE_MODULE_PTR.get_mut() } + } + + /// Registers and associates the interface with the module only once. The + /// module must has been loaded at least once. + /// Nothing will be done if the module has never been loaded or if the + /// interface is already registered. + #[inline] + fn register_interface() -> #crate_ident::Type { + use #crate_ident::Cast; + use #crate_ident::translate::{FromGlib, IntoGlib, ToGlibPtr}; + let type_module_ptr_mut = Self::get_type_module_ptr_mut(); + match *type_module_ptr_mut { + // null pointer means that the module has never been loaded, so the interface + // cannot be registered. + type_module_ptr if type_module_ptr.is_null() => (), + // valid pointer means that the module has been loaded and that the interface + // has not been registered yet, so register it. + type_module_ptr if unsafe { ::std::ptr::NonNull::new_unchecked(type_module_ptr) } != ::std::ptr::NonNull::dangling() => { + use #crate_ident::translate::FromGlibPtrBorrow; + let type_module = unsafe { #crate_ident::TypeModule::from_glib_borrow(type_module_ptr) }; + let type_mut = Self::get_type_mut(); + *type_mut = #crate_ident::subclass::register_module_interface::(type_module.as_ref()).into_glib(); + if *type_mut != #crate_ident::gobject_ffi::G_TYPE_INVALID { + // use dangling pointer to mark interface is registered + *type_module_ptr_mut = ::std::ptr::NonNull::dangling().as_ptr(); + } + }, + // dangling pointer means interface has already been registered. + _ => () + }; + unsafe { #crate_ident::Type::from_glib(*Self::get_type_mut()) } + } + + /// Depending on the module lifecycle state and on the interface lifecycle + /// state: + /// If the module is loaded for the first time, postpones the registration by + /// storing the module pointer. + /// If the module is reloaded and the interface has been already registered, + /// reregisters it. The interface can be reregistered several times. + /// If the module is reloaded and the interface has not been registered yet, + /// nothing will be done. + #[inline] + pub fn on_module_load>(type_module: &M) -> bool { + use #crate_ident::Cast; + use #crate_ident::translate::{FromGlib, IntoGlib, ToGlibPtr}; + let type_module_ptr_mut = Self::get_type_module_ptr_mut(); + match *type_module_ptr_mut { + // null pointer means that the module has never been loaded (this is the first + // time), so postpone the interface registration on first use. + type_module_ptr if type_module_ptr.is_null() => { + *type_module_ptr_mut = type_module.upcast_ref::<#crate_ident::TypeModule>().to_glib_none().0; + true + }, + // dangling pointer means that the module has been loaded at least one time and + // that the interface has been registered at least one time, so re-register it. + type_module_ptr if unsafe { ::std::ptr::NonNull::new_unchecked(type_module_ptr) } == ::std::ptr::NonNull::dangling() => { + let type_mut = Self::get_type_mut(); + *type_mut = #crate_ident::subclass::register_module_interface::(type_module.as_ref()).into_glib(); + *type_mut != #crate_ident::gobject_ffi::G_TYPE_INVALID + }, + // valid pointer means that the module has been loaded at least one time but + // that the interface has not been registered yet, so simply check that the + // module is same. + type_module_ptr => { + *type_module_ptr_mut == type_module.upcast_ref::<#crate_ident::TypeModule>().to_glib_none().0 + } + } + } + + /// Depending on the module lifecycle state and on the interface lifecycle + /// state: + /// If the module has been loaded (or reloaded) but the interface has not been + /// registered yet, cancels the postponed registration by clearing the module + /// pointer. Else nothing will be done. + #[inline] + pub fn on_module_unload>(type_module_: &M) -> bool { + let type_module_ptr_mut = Self::get_type_module_ptr_mut(); + match *type_module_ptr_mut { + // null pointer means that the module has never been loaded, so unload it is + // unexpected. + type_module_ptr if type_module_ptr.is_null() => false, + // dangling pointer means that the module has been loaded at least one time and + // that the interface has been registered at least one time. + type_module_ptr if unsafe { ::std::ptr::NonNull::new_unchecked(type_module_ptr) } == ::std::ptr::NonNull::dangling() => true, + // valid pointer means that the module has been loaded at least one time but + // that the interface has not been registered yet, so cancel the postponed + // registration. + type_module_ptr => { + *type_module_ptr_mut = ::std::ptr::null_mut(); + true + } + } + } + } + } + } else { + // register the module interface immediately. + quote! { + use glib::translate::{FromGlib, IntoGlib}; + + impl #self_ty { + /// Returns a mutable reference to the glib type. + /// This is safe because the mutable reference guarantees that no other threads + /// are concurrently accessing the atomic data. + #[inline] + fn get_type_mut() -> &'static mut #crate_ident::ffi::GType { + static mut TYPE: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::AtomicUsize::new(#crate_ident::gobject_ffi::G_TYPE_INVALID); + unsafe { TYPE.get_mut() } + } + + /// Registers explicitly the interface is ignored as it is done when the + /// module is loaded. + #[inline] + fn register_interface() -> #crate_ident::Type { + unsafe { #crate_ident::Type::from_glib(*Self::get_type_mut()) } + } + + /// Registers and associates the interface with the module. The interface can + /// be registered several times. + #[inline] + pub fn on_module_load>(type_module: &M) -> bool { + let type_mut = Self::get_type_mut(); + *type_mut = #crate_ident::subclass::register_module_interface::(type_module.as_ref()).into_glib(); + *type_mut != #crate_ident::gobject_ffi::G_TYPE_INVALID + } + + /// Nothing will be done as types and interfaces associated with the module are + /// not unregistered. + #[inline] + pub fn on_module_unload>(type_module_: &M) -> bool { + true + } + } + } + }; + + impl_object_interface_(register_interface, input) +} + +pub fn impl_object_interface_( + register_interface: TokenStream, + input: &syn::ItemImpl, +) -> TokenStream { let mut has_prerequisites = false; for item in &input.items { if let syn::ImplItem::Type(type_) = item { @@ -53,20 +285,10 @@ pub fn impl_object_interface(input: &syn::ItemImpl) -> TokenStream { unsafe impl #crate_ident::subclass::interface::ObjectInterfaceType for #self_ty { #[inline] fn type_() -> #crate_ident::Type { - static ONCE: ::std::sync::Once = ::std::sync::Once::new(); - static mut TYPE: #crate_ident::Type = #crate_ident::Type::INVALID; - - ONCE.call_once(|| { - let type_ = #crate_ident::subclass::register_interface::(); - unsafe { - TYPE = type_; - } - }); - - unsafe { - TYPE - } + Self::register_interface() } } + + #register_interface } } diff --git a/glib-macros/src/object_subclass_attribute.rs b/glib-macros/src/object_subclass_attribute.rs index dab232f212a8..ecfea6c25e37 100644 --- a/glib-macros/src/object_subclass_attribute.rs +++ b/glib-macros/src/object_subclass_attribute.rs @@ -4,10 +4,201 @@ use proc_macro2::TokenStream; use proc_macro_error::abort_call_site; use quote::quote; +pub const WRONG_EXPRESSION_MSG: &str = "This macro's attribute should be a valid expression"; + +pub const UNSUPPORTED_EXPRESSION_MSG: &str = + "This macro's attribute should be 'lazy_registration = true|false' expression"; + pub const WRONG_PLACE_MSG: &str = "This macro should be used on `impl` block for `glib::ObjectSubclass` trait"; pub fn impl_object_subclass(input: &syn::ItemImpl) -> TokenStream { + let crate_ident = crate::utils::crate_ident_new(); + let syn::ItemImpl { self_ty, .. } = &input; + + // register the type on first use (lazy registration) + let register_type = quote! { + impl #self_ty { + /// Registers the type only once. + #[inline] + fn register_type() { + static ONCE: ::std::sync::Once = ::std::sync::Once::new(); + + ONCE.call_once(|| { + #crate_ident::subclass::register_type::(); + }) + } + } + }; + + impl_object_subclass_(register_type, input) +} + +pub fn impl_module_object_subclass(attr: Option<&syn::Expr>, input: &syn::ItemImpl) -> TokenStream { + let crate_ident = crate::utils::crate_ident_new(); + let syn::ItemImpl { self_ty, .. } = &input; + + // attribute must be `None` (immediate registration) or must be an assign + // expression: `lazy_registration = true|false` + let lazy_registration = match attr { + None => false, + Some(syn::Expr::Assign(syn::ExprAssign { left, right, .. })) => { + match (*left.to_owned(), *right.to_owned()) { + ( + syn::Expr::Path(syn::ExprPath { path, .. }), + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Bool(syn::LitBool { value, .. }), + .. + }), + ) if path.is_ident(&"lazy_registration") => value, + _ => abort_call_site!(UNSUPPORTED_EXPRESSION_MSG), + } + } + _ => abort_call_site!(UNSUPPORTED_EXPRESSION_MSG), + }; + + // The following implementations follow the lifecycle of modules and their + // types (see [`TypeModuleExt::unuse`]). + // A module type can be reregistered (see [`TypeModuleExt::register_type`]). + let register_type = if lazy_registration { + // Register the module type on first use (lazy registration). The module + // pointer must be stored to be used later on first use of the module type. + // This implementation relies on a static storage of the module pointer. The + // pointer value is interpreted to know if the module has been loaded and if + // the type has been registered: + // - null pointer means that the module has never been loaded, + // - valid pointer means that the module has been loaded but that the type + // has not been registered yet, + // - dangling pointer means that the module has been loaded and that the type + // has been registered. + quote! { + impl #self_ty { + /// Returns a mutable reference to the pointer of the module associated to this + /// type. This is safe because the mutable reference guarantees that no other + /// threads are concurrently accessing the atomic data. + #[inline] + fn get_type_module_ptr_mut() -> &'static mut *mut #crate_ident::gobject_ffi::GTypeModule { + static mut TYPE_MODULE_PTR: ::std::sync::atomic::AtomicPtr<#crate_ident::gobject_ffi::GTypeModule> = ::std::sync::atomic::AtomicPtr::new(::std::ptr::null_mut()); + unsafe { TYPE_MODULE_PTR.get_mut() } + } + + /// Registers and associates the type with the module only once. The + /// module must has been loaded at least once. + /// Nothing will be done if the module has never been loaded or if the + /// type is already registered. + #[inline] + fn register_type() { + let type_module_ptr_mut = Self::get_type_module_ptr_mut(); + match *type_module_ptr_mut { + // null pointer means that the module has never been loaded, so the type cannot + // be registered. + type_module_ptr if type_module_ptr.is_null() => (), + // valid pointer means that the module has been loaded and that the type has + // not been registered yet, so register it. + type_module_ptr if unsafe { ::std::ptr::NonNull::new_unchecked(type_module_ptr) } != ::std::ptr::NonNull::dangling() => { + use #crate_ident::translate::FromGlibPtrBorrow; + let type_module = unsafe { #crate_ident::TypeModule::from_glib_borrow(type_module_ptr) }; + let type_ = #crate_ident::subclass::register_module_type::(type_module.as_ref()); + if type_ != #crate_ident::Type::INVALID { + // use dangling pointer to mark type is registered + *type_module_ptr_mut = ::std::ptr::NonNull::dangling().as_ptr(); + } + }, + // dangling pointer means type has already been registered. + _ => () + } + } + + /// Depending on the module lifecycle state and on the type lifecycle state: + /// If the module is loaded for the first time, postpones the registration by + /// storing the module pointer. + /// If the module is reloaded and the type has been already registered, + /// reregisters it. The type can be reregistered several times. + /// If the module is reloaded and the type has not been registered yet, + /// nothing will be done. + #[inline] + pub fn on_module_load>(type_module: &M) -> bool { + use #crate_ident::Cast; + use #crate_ident::translate::ToGlibPtr; + let type_module_ptr_mut = Self::get_type_module_ptr_mut(); + match *type_module_ptr_mut { + // null pointer means that the module has never been loaded (this is the first + // time), so postpone the type registration on first use. + type_module_ptr if type_module_ptr.is_null() => { + *type_module_ptr_mut = type_module.upcast_ref::<#crate_ident::TypeModule>().to_glib_none().0; + true + }, + // dangling pointer means that the module has been loaded at least one time and + // that the type has been registered at least one time, so re-register it. + type_module_ptr if unsafe { ::std::ptr::NonNull::new_unchecked(type_module_ptr) } == ::std::ptr::NonNull::dangling() => { + let type_ = #crate_ident::subclass::register_module_type::(type_module.as_ref()); + type_ != #crate_ident::Type::INVALID + }, + // valid pointer means that the module has been loaded at least one time but + // that the type has not been registered yet, so simply check that the + // module is same. + type_module_ptr => { + *type_module_ptr_mut == type_module.upcast_ref::<#crate_ident::TypeModule>().to_glib_none().0 + } + } + } + + /// Depending on the module lifecycle state and on the type lifecycle state: + /// If the module has been loaded (or reloaded) but the type has not been + /// registered yet, cancels the postponed registration by clearing the module + /// pointer. Else nothing will be done. + #[inline] + pub fn on_module_unload>(type_module_: &M) -> bool { + let type_module_ptr_mut = Self::get_type_module_ptr_mut(); + match *type_module_ptr_mut { + // null pointer means that the module has never been loaded, so unload it is + // unexpected. + type_module_ptr if type_module_ptr.is_null() => false, + // dangling pointer means that the module has been loaded at least one time and + // that the type has been registered at least one time. + type_module_ptr if unsafe { ::std::ptr::NonNull::new_unchecked(type_module_ptr) } == ::std::ptr::NonNull::dangling() => true, + // valid pointer means that the module has been loaded at least one time but + // that the type has not been registered yet, so cancel the postponed + // registration. + type_module_ptr => { + *type_module_ptr_mut = ::std::ptr::null_mut(); + true + } + } + } + } + } + } else { + // register the module type immediately. + quote! { + impl #self_ty { + /// Registers explicitly the type is ignored as it is done when the module is + /// loaded. + #[inline] + fn register_type() { } + + /// Registers and associates the type with the module. The type can be + /// registered several times. + #[inline] + pub fn on_module_load>(type_module: &M) -> bool { + let type_ = #crate_ident::subclass::register_module_type::(type_module.as_ref()); + type_ != #crate_ident::Type::INVALID + } + + /// Nothing will be done as types and interfaces associated with the module are + /// not unregistered. + #[inline] + pub fn on_module_unload>(type_module_: &M) -> bool { + true + } + } + } + }; + + impl_object_subclass_(register_type, input) +} + +fn impl_object_subclass_(register_type: TokenStream, input: &syn::ItemImpl) -> TokenStream { let mut has_new = false; let mut has_parent_type = false; let mut has_interfaces = false; @@ -101,11 +292,7 @@ pub fn impl_object_subclass(input: &syn::ItemImpl) -> TokenStream { #[inline] fn type_() -> #crate_ident::Type { - static ONCE: ::std::sync::Once = ::std::sync::Once::new(); - - ONCE.call_once(|| { - #crate_ident::subclass::register_type::(); - }); + Self::register_type(); unsafe { let data = Self::type_data(); @@ -116,6 +303,8 @@ pub fn impl_object_subclass(input: &syn::ItemImpl) -> TokenStream { } } + #register_type + #[doc(hidden)] impl #crate_ident::subclass::types::FromObject for #self_ty { type FromObjectType = ::Type; diff --git a/glib-macros/tests/test.rs b/glib-macros/tests/test.rs index 39803d4211b5..6d40c23ee04a 100644 --- a/glib-macros/tests/test.rs +++ b/glib-macros/tests/test.rs @@ -280,6 +280,279 @@ fn subclassable() { } } +#[test] +fn module_subclassable() { + mod foo { + mod static_ { + use glib::subclass::{prelude::*, types::IsSubclassable}; + + pub mod imp { + use super::*; + + // impl for a static interface + #[derive(Clone, Copy)] + #[repr(C)] + pub struct MyStaticInterface { + parent: glib::gobject_ffi::GTypeInterface, + } + + #[glib::object_interface] + unsafe impl ObjectInterface for MyStaticInterface { + const NAME: &'static str = "MyStaticInterface"; + } + + // impl for a static type that implements `MyStaticInterface` + #[derive(Default)] + pub struct MyStaticType; + + #[glib::object_subclass] + impl ObjectSubclass for MyStaticType { + const NAME: &'static str = "MyStaticType"; + type Type = super::MyStaticType; + type Interfaces = (super::MyStaticInterface,); + } + + impl ObjectImpl for MyStaticType {} + + impl super::MyStaticInterfaceImpl for MyStaticType {} + } + + // a static interface + glib::wrapper! { + pub struct MyStaticInterface(ObjectInterface); + } + + pub trait MyStaticInterfaceImpl: ObjectImpl + ObjectSubclass {} + + unsafe impl IsImplementable for MyStaticInterface {} + + // a static type that implements `MyStaticInterface` + glib::wrapper! { + pub struct MyStaticType(ObjectSubclass) @implements MyStaticInterface; + } + + pub trait MyStaticTypeImpl: ObjectImpl + ObjectSubclass {} + + unsafe impl IsSubclassable for MyStaticType {} + } + + pub mod module { + use super::*; + use glib::subclass::prelude::*; + + pub mod imp { + use super::*; + + // impl for a module interface that extends `static_::MyStaticInterface` + #[derive(Clone, Copy)] + #[repr(C)] + pub struct MyModuleInterface { + parent: glib::gobject_ffi::GTypeInterface, + } + + #[glib::module_object_interface] + unsafe impl ObjectInterface for MyModuleInterface { + const NAME: &'static str = "MyModuleInterface"; + type Prerequisites = (static_::MyStaticInterface,); + } + + // impl for a module type that extends `static_::MyStaticType` and that implements `static_::MyStaticInterface` and `MyModuleInterface` + #[derive(Default)] + pub struct MyModuleType; + + #[glib::module_object_subclass] + impl ObjectSubclass for MyModuleType { + const NAME: &'static str = "MyModuleType"; + type Type = super::MyModuleType; + type ParentType = static_::MyStaticType; + type Interfaces = (static_::MyStaticInterface, super::MyModuleInterface); + } + + impl ObjectImpl for MyModuleType {} + + impl static_::MyStaticTypeImpl for MyModuleType {} + + impl static_::MyStaticInterfaceImpl for MyModuleType {} + + impl super::MyModuleInterfaceImpl for MyModuleType {} + + // impl for a module interface that extends `static_::MyStaticInterface` and that is lazy registered + #[derive(Clone, Copy)] + #[repr(C)] + pub struct MyModuleInterfaceLazy { + parent: glib::gobject_ffi::GTypeInterface, + } + + #[glib::module_object_interface(lazy_registration = true)] + unsafe impl ObjectInterface for MyModuleInterfaceLazy { + const NAME: &'static str = "MyModuleInterfaceLazy"; + type Prerequisites = (static_::MyStaticInterface,); + } + + // impl for a module type that extends `static_::MyStaticType` and that implements `static_::MyStaticInterface` and `MyModuleInterfaceLazy` and that is lazy registered + #[derive(Default)] + pub struct MyModuleTypeLazy; + + #[glib::module_object_subclass(lazy_registration = true)] + impl ObjectSubclass for MyModuleTypeLazy { + const NAME: &'static str = "MyModuleTypeLazy"; + type Type = super::MyModuleTypeLazy; + type ParentType = static_::MyStaticType; + type Interfaces = (static_::MyStaticInterface, super::MyModuleInterfaceLazy); + } + + impl ObjectImpl for MyModuleTypeLazy {} + + impl static_::MyStaticTypeImpl for MyModuleTypeLazy {} + + impl static_::MyStaticInterfaceImpl for MyModuleTypeLazy {} + + impl super::MyModuleInterfaceLazyImpl for MyModuleTypeLazy {} + + // impl for a type module (must extend `glib::TypeModule` and must implement `glib::TypePlugin`) + #[derive(Default)] + pub struct MyModule; + + #[glib::object_subclass] + impl ObjectSubclass for MyModule { + const NAME: &'static str = "MyModule"; + type Type = super::MyModule; + type ParentType = glib::TypeModule; + type Interfaces = (glib::TypePlugin,); + } + + impl ObjectImpl for MyModule {} + + impl TypeModuleImpl for MyModule { + fn load(&self) -> bool { + // register module types and interfaces + MyModuleInterface::on_module_load(self.obj().as_ref()) + && MyModuleType::on_module_load(self.obj().as_ref()) + && MyModuleInterfaceLazy::on_module_load(self.obj().as_ref()) + && MyModuleTypeLazy::on_module_load(self.obj().as_ref()) + } + + fn unload(&self) { + // mark the module types and interfaces as unregistered + MyModuleTypeLazy::on_module_unload(self.obj().as_ref()); + MyModuleInterfaceLazy::on_module_unload(self.obj().as_ref()); + MyModuleType::on_module_unload(self.obj().as_ref()); + MyModuleInterface::on_module_unload(self.obj().as_ref()); + } + } + + impl TypePluginImpl for MyModule {} + } + + // a module interface that extends `static_::MyStaticInterface` + glib::wrapper! { + pub struct MyModuleInterface(ObjectInterface) @requires static_::MyStaticInterface; + } + + trait MyModuleInterfaceImpl: ObjectImpl + ObjectSubclass {} + + unsafe impl IsImplementable for MyModuleInterface {} + + // a module type that extends `static_::MyStaticType` and that implements `static_::MyStaticInterface` and `MyModuleInterface` + glib::wrapper! { + pub struct MyModuleType(ObjectSubclass) @extends static_::MyStaticType, @implements static_::MyStaticInterface, MyModuleInterface; + } + + // a module interface that extends `static_::MyStaticInterface` and that is lazy registered + glib::wrapper! { + pub struct MyModuleInterfaceLazy(ObjectInterface) @requires static_::MyStaticInterface; + } + + trait MyModuleInterfaceLazyImpl: ObjectImpl + ObjectSubclass {} + + unsafe impl IsImplementable for MyModuleInterfaceLazy {} + + // a module type that extends `static_::MyStaticType` that implements `static_::MyStaticInterface` and `MyModuleInterfaceLazy` and that is lazy registered + glib::wrapper! { + pub struct MyModuleTypeLazy(ObjectSubclass) @extends static_::MyStaticType, @implements static_::MyStaticInterface, MyModuleInterfaceLazy; + } + + // a module (must extend `glib::TypeModule` and must implement `glib::TypePlugin`) + glib::wrapper! { + pub struct MyModule(ObjectSubclass) + @extends glib::TypeModule, @implements glib::TypePlugin; + } + } + } + + use foo::*; + use glib::subclass::interface::ObjectInterfaceType; + use glib::subclass::types::ObjectSubclassType; + use glib::translate::*; + + // check types of module types and of module interfaces are invalid (module is not loaded yet) + assert!(!module::imp::MyModuleInterface::type_().is_valid()); + assert!(!module::imp::MyModuleType::type_().is_valid()); + assert!(!module::imp::MyModuleInterfaceLazy::type_().is_valid()); + assert!(!module::imp::MyModuleTypeLazy::type_().is_valid()); + + // simulate the glib type system to load/unload the module + let module = glib::Object::new::(); + TypeModuleExt::use_(&module); + TypeModuleExt::unuse(&module); + + // check types of module types and of module interfaces that are immediately registered are valid (module was loaded) + assert!(module::imp::MyModuleInterface::type_().is_valid()); + assert!(module::imp::MyModuleType::type_().is_valid()); + // check types of module types and of module interfaces that are lazy registered are still invalid (module was loaded) + assert!(!module::imp::MyModuleInterfaceLazy::type_().is_valid()); + assert!(!module::imp::MyModuleTypeLazy::type_().is_valid()); + + // simulate the glib type system to load the module + TypeModuleExt::use_(&module); + + // check types of module types and of module interfaces are valid (module is loaded) + let iface_type = module::imp::MyModuleInterface::type_(); + assert!(iface_type.is_valid()); + let obj_type = module::imp::MyModuleType::type_(); + assert!(obj_type.is_valid()); + let iface_lazy_type = module::imp::MyModuleInterfaceLazy::type_(); + assert!(iface_lazy_type.is_valid()); + let obj_lazy_type = module::imp::MyModuleTypeLazy::type_(); + assert!(obj_lazy_type.is_valid()); + + // check plugin of module types and of module interfaces is `MyModule` + assert_eq!( + unsafe { glib::gobject_ffi::g_type_get_plugin(iface_type.into_glib()) }, + module.upcast_ref::().to_glib_none().0 + ); + assert_eq!( + unsafe { glib::gobject_ffi::g_type_get_plugin(obj_type.into_glib()) }, + module.upcast_ref::().to_glib_none().0 + ); + assert_eq!( + unsafe { glib::gobject_ffi::g_type_get_plugin(iface_lazy_type.into_glib()) }, + module.upcast_ref::().to_glib_none().0 + ); + assert_eq!( + unsafe { glib::gobject_ffi::g_type_get_plugin(obj_lazy_type.into_glib()) }, + module.upcast_ref::().to_glib_none().0 + ); + + // simulate the glib type system to unload the module + TypeModuleExt::unuse(&module); + + // check types of module types and of module interfaces are still valid (should have been marked as unloaded by the glib type system but this cannot be checked) + assert!(module::imp::MyModuleInterface::type_().is_valid()); + assert!(module::imp::MyModuleType::type_().is_valid()); + assert!(module::imp::MyModuleInterfaceLazy::type_().is_valid()); + assert!(module::imp::MyModuleTypeLazy::type_().is_valid()); + + // simulate the glib type system to reload the module + TypeModuleExt::use_(&module); + + // check types of module types and of module interfaces are still valid (should have been marked as loaded by the glib type system but this cannot be checked) + assert!(module::imp::MyModuleInterface::type_().is_valid()); + assert!(module::imp::MyModuleType::type_().is_valid()); + assert!(module::imp::MyModuleInterfaceLazy::type_().is_valid()); + assert!(module::imp::MyModuleTypeLazy::type_().is_valid()); +} + #[test] fn derive_variant() { #[derive(Debug, PartialEq, Eq, glib::Variant)] diff --git a/glib/src/lib.rs b/glib/src/lib.rs index e3a6104f87c1..5a9274464563 100644 --- a/glib/src/lib.rs +++ b/glib/src/lib.rs @@ -10,8 +10,9 @@ pub use ffi; #[doc(hidden)] pub use glib_macros::cstr_bytes; pub use glib_macros::{ - clone, closure, closure_local, derived_properties, flags, object_interface, object_subclass, - Boxed, Downgrade, Enum, ErrorDomain, Properties, SharedBoxed, ValueDelegate, Variant, + clone, closure, closure_local, derived_properties, flags, module_object_interface, + module_object_subclass, object_interface, object_subclass, Boxed, Downgrade, Enum, ErrorDomain, + Properties, SharedBoxed, ValueDelegate, Variant, }; pub use gobject_ffi; #[doc(hidden)] diff --git a/glib/src/subclass/interface.rs b/glib/src/subclass/interface.rs index b8c9bba52e7b..2355bcf1d2c1 100644 --- a/glib/src/subclass/interface.rs +++ b/glib/src/subclass/interface.rs @@ -221,6 +221,10 @@ pub fn register_interface() -> Type { /// A module interface must be explicitly registered when a module is loaded (see [`TypeModuleImpl::load`]). /// Therefore, unlike for non module interfaces a module interface can be registered several times. /// +/// The [`module_object_interface!`] macro will create `register_interface()` and `on_module_load()` functions around this, which will +/// ensure that the function is called when necessary. +/// +/// [`module_object_interface!`]: ../../../glib_macros/attr.module_object_interface.html /// [`TypeModuleImpl::load`]: ../type_module/trait.TypeModuleImpl.html#method.load pub fn register_module_interface(type_module: &TypeModule) -> Type { unsafe { diff --git a/glib/src/subclass/mod.rs b/glib/src/subclass/mod.rs index 9a6a393ffbe8..ed6046abb58e 100644 --- a/glib/src/subclass/mod.rs +++ b/glib/src/subclass/mod.rs @@ -197,6 +197,94 @@ //! } //! ``` //! +//! # Example for registering a `glib::Object` subclass within a module +//! +//! The following code implements a subclass of `glib::Object` and registers it as +//! a module type. +//! +//! ```rust +//! use glib::prelude::*; +//! use glib::subclass::prelude::*; +//! use glib::traits::TypeModuleExt; +//! +//! pub mod imp { +//! use super::*; +//! +//! // SimpleModuleObject is one of the module types. +//! #[derive(Default)] +//! pub struct SimpleModuleObject; +//! +//! #[glib::module_object_subclass] +//! impl ObjectSubclass for SimpleModuleObject { +//! const NAME: &'static str = "SimpleModuleObject"; +//! type Type = super::SimpleModuleObject; +//! } +//! +//! impl ObjectImpl for SimpleModuleObject {} +//! +//! // SimpleTypeModule is the type module within module types are registered. +//! #[derive(Default)] +//! pub struct SimpleTypeModule; +//! +//! #[glib::object_subclass] +//! impl ObjectSubclass for SimpleTypeModule { +//! const NAME: &'static str = "SimpleTypeModule"; +//! type Type = super::SimpleTypeModule; +//! type ParentType = glib::TypeModule; +//! type Interfaces = (glib::TypePlugin,); +//! } +//! +//! impl ObjectImpl for SimpleTypeModule {} +//! +//! impl TypeModuleImpl for SimpleTypeModule { +//! /// Loads the module and registers one or more types +//! fn load(&self) -> bool { +//! SimpleModuleObject::on_module_load(self.obj().as_ref()) +//! } +//! +//! /// Unloads the module. +//! fn unload(&self) { +//! SimpleModuleObject::on_module_unload(self.obj().as_ref()); +//! } +//! } +//! +//! impl TypePluginImpl for SimpleTypeModule {} +//! } +//! +//! // Optionally, define a wrapper type to make SimpleModuleObject more ergonomic to use from Rust +//! glib::wrapper! { +//! pub struct SimpleModuleObject(ObjectSubclass); +//! } +//! +//! // Optionally, define a wrapper type to make SimpleTypeModule more ergonomic to use from Rust +//! glib::wrapper! { +//! pub struct SimpleTypeModule(ObjectSubclass) +//! @extends glib::TypeModule, @implements glib::TypePlugin; +//! } +//! +//! impl SimpleTypeModule { +//! // Create an object instance of the new type. +//! pub fn new() -> Self { +//! glib::Object::new() +//! } +//! } +//! +//! pub fn main() { +//! let simple_type_module = SimpleTypeModule::new(); +//! // At this step, SimpleTypeModule has not been loaded therefore +//! // SimpleModuleObject must not be registered yet. +//! let simple_module_object_type = imp::SimpleModuleObject::type_(); +//! assert!(!simple_module_object_type.is_valid()); +//! +//! // Simulate the glib type system to load the module. +//! simple_type_module.use_(); +//! +//! // At this step, SimpleModuleObject must have been registered. +//! let simple_module_object_type = imp::SimpleModuleObject::type_(); +//! assert!(simple_module_object_type.is_valid()); +//! } +//! ``` +//! //! # Example for registering a boxed type for a Rust struct //! //! The following code boxed type for a tuple struct around `String` and uses it in combination diff --git a/glib/src/subclass/types.rs b/glib/src/subclass/types.rs index 9018ba69e267..20d8f9af58fb 100644 --- a/glib/src/subclass/types.rs +++ b/glib/src/subclass/types.rs @@ -1058,6 +1058,10 @@ pub fn register_type() -> Type { /// A module type must be explicitly registered when a module is loaded (see [`TypeModuleImpl::load`]). /// Therefore, unlike for non module types a module type can be registered several times. /// +/// The [`module_object_subclass!`] macro will create `register_type()` and `on_module_load()` functions around this, which will +/// ensure that the function is called when necessary. +/// +/// [`module_object_subclass!`]: ../../../glib_macros/attr.module_object_subclass.html /// [`TypeModuleImpl::load`]: ../type_module/trait.TypeModuleImpl.html#method.load pub fn register_module_type(type_module: &TypeModule) -> Type { // GLib aligns the type private data to two gsizes, so we can't safely store any type there that