Skip to content

Commit

Permalink
add glib_macros::{module_object_subclass,module_object_interface}
Browse files Browse the repository at this point in the history
Signed-off-by: fbrouille <[email protected]>
  • Loading branch information
fbrouille committed Aug 12, 2023
1 parent 37aa425 commit 906bb66
Show file tree
Hide file tree
Showing 8 changed files with 908 additions and 20 deletions.
107 changes: 107 additions & 0 deletions glib-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<syn::Expr>(attr) {
Ok(expr) => Some(expr),
Err(_) => abort_call_site!(object_subclass_attribute::WRONG_EXPRESSION_MSG),
}
};
match syn::parse::<syn::ItemImpl>(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
Expand All @@ -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::<syn::Expr>(attr) {
Ok(expr) => Some(expr),
Err(_) => abort_call_site!(object_interface_attribute::WRONG_EXPRESSION_MSG),
}
};
match syn::parse::<syn::ItemImpl>(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.
///
Expand Down
248 changes: 235 additions & 13 deletions glib-macros/src/object_interface_attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Self>();
});

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::<Self>(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<M: #crate_ident::IsA<#crate_ident::TypeModule>>(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::<Self>(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<M: #crate_ident::IsA<#crate_ident::TypeModule>>(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<M: #crate_ident::IsA<#crate_ident::TypeModule>>(type_module: &M) -> bool {
let type_mut = Self::get_type_mut();
*type_mut = #crate_ident::subclass::register_module_interface::<Self>(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<M: #crate_ident::IsA<#crate_ident::TypeModule>>(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 {
Expand Down Expand Up @@ -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::<Self>();
unsafe {
TYPE = type_;
}
});

unsafe {
TYPE
}
Self::register_interface()
}
}

#register_interface
}
}
Loading

0 comments on commit 906bb66

Please sign in to comment.